Esta página aún no está traducida: se muestra la versión en inglés.
Botiquines en Tanks
Esta es la segunda lección de Tanks. Son tres pasos y ningún módulo nuevo: una declaración para el botiquín, otra para dónde aparecen los botiquines y un hook para lo que pasa al recoger uno. Hazla después de Primeros pasos, antes o después de la lección de la tabla de clasificación.
Declara el botiquín
Un botiquín es una entidad con el preset de objetos del mundo (world objects) aplicado y un cuerpo que reporta el contacto sin detener a nadie. Response.Pass en la capa pickups es lo que lo convierte en un objeto que se recoge y no en un obstáculo: el contacto se reporta y el movimiento sigue de largo.
pickups — el contacto se reporta y el movimiento no cambia[Entity("health-crate", Persistence = Persistence.Runtime, Presets = new[] { Preset.WorldObjects })]
public class HealthCrate
{
[Sync] public Vector3 Position;
[Body(Shape.Sphere, Radius = 0.5f, Layer = "pickups")]
[CollidesWith("vehicles", Response.Pass)] // reported, motion passes through
public Body Body;
}@Entity('health-crate', { persistence: Persistence.Runtime, presets: [Preset.WorldObjects] })
export class HealthCrate {
@Sync position: Vector3;
@Body({ shape: 'sphere', radius: 0.5, layer: 'pickups' })
@CollidesWith('vehicles', Response.Pass) // reported, motion passes through
body: Body;
}@entity("health-crate", persistence=Persistence.RUNTIME, presets=[Preset.WORLD_OBJECTS])
class HealthCrate:
position: Vector3 = sync()
body: Body = body(shape="sphere", radius=0.5, layer="pickups",
collides_with=[("vehicles", Response.PASS)])Todavía no está decidido cómo declarar con atributos en Go (O-1). Los ejemplos llegarán cuando se elija la forma de escribirlo.
UCLASS(PSEntity = (Name = "health-crate", Persistence = "Runtime", Presets = "world-objects"))
class UHealthCrate : public UObject
{
GENERATED_BODY()
UPROPERTY(PSSync) FVector3f Position;
UPROPERTY(PSBody = (Shape = "Sphere", Radius = "0.5", Layer = "pickups"),
PSCollidesWith = "vehicles:Pass") // reported, motion passes through
FPSBody Body;
};
// declarations compile into the same pushed model — playserv push from the UE project or CILas declaraciones se escriben en el proyecto del servidor y se suben con playserv push; el binding de Unity usa desde el cliente la API tipada que se genera (HealthCrate).
Hay dos cosas que no tuviste que escribir: dónde se dibuja el botiquín (el cliente ya renderiza los objetos del mundo declarados) y cómo llega su posición a los clientes: [Sync] ya es la llamada de red.
De esto se encargan Entity Presets y Collision.
Declara dónde aparecen los botiquines
La ubicación también es una declaración, y este es el paso que decide si la mecánica se siente justa. El espaciado evita que los botiquines se amontonen, la distancia a los jugadores (player) evita que aparezcan en medio de un duelo y la regla de no repetición impide que siempre salgan en el mismo punto.
[DropTable("health-crates", Layer = "ground", MinSpacing = 8, AwayFromPlayers = 10, NoRepeat = 3)]
public static partial class HealthCrates
{
public static readonly Drop Crate = Drop.Of<HealthCrate>(weight: 1);
}@DropTable('health-crates', { layer: 'ground', minSpacing: 8, awayFromPlayers: 10, noRepeat: 3 })
export class HealthCrates {
static crate = Drop.of(HealthCrate, { weight: 1 });
}@drop_table("health-crates", layer="ground", min_spacing=8, away_from_players=10, no_repeat=3)
class HealthCrates:
crate = drop_of(HealthCrate, weight=1)Todavía no está decidido cómo declarar con atributos en Go (O-1). Los ejemplos llegarán cuando se elija la forma de escribirlo.
USTRUCT(PSDropTable = (Name = "health-crates", Layer = "ground", MinSpacing = 8,
AwayFromPlayers = 10, NoRepeat = 3))
struct FHealthCrates
{
GENERATED_BODY()
UPROPERTY(PSEntry = (Entity = "health-crate", Weight = 1)) FPSDrop Crate;
};
// declarations compile into the same pushed model — playserv push from the UE project or CILas declaraciones se escriben en el proyecto del servidor y se suben con playserv push; el cliente de Unity ve el resultado como objetos del mundo que aparecen y eventos de recogida.
Las posiciones válidas vienen de Map: la tabla de botín (drop table) pide un punto en la capa ground y el mapa responde con uno al que realmente se puede llegar, así que un botiquín nunca cae dentro de una pared.
De esto se encargan Entity Presets y Map.
Cura al recoger
Un solo hook, y es el único código de la lección. Se ejecuta en la plataforma como función en la nube; por eso no aparece en ninguna de las pestañas de motor.
[Before(Drops.Pickup)]
public static Verdict HealOnPickup(PickupIntent p)
{
if (p.WorldItem.Kind != "health-crate") return Hook.Continue(p);
if (p.Player.Tank.Hp.IsFull) return Hook.Reject("already at full health");
p.Player.Tank.Hp.Adjust(+40, by: p.Player);
return Hook.Continue(p);
}export const healOnPickup = before(Drops.pickup, (p: PickupIntent) => {
if (p.worldItem.kind !== 'health-crate') return Hook.continue(p);
if (p.player.tank.hp.isFull) return Hook.reject('already at full health');
p.player.tank.hp.adjust(+40, { by: p.player });
return Hook.continue(p);
});@before(drops.pickup)
def heal_on_pickup(p: PickupIntent) -> Verdict:
if p.world_item.kind != "health-crate":
return Hook.continue_(p)
if p.player.tank.hp.is_full:
return Hook.reject("already at full health")
p.player.tank.hp.adjust(+40, by=p.player)
return Hook.continue_(p)Todavía no está decidido cómo declarar con atributos en Go (O-1). Los ejemplos llegarán cuando se elija la forma de escribirlo.
Un hook es una función en la nube: se ejecuta en la plataforma, no en el motor. Escríbelo en C#, TypeScript o Python; Unreal se suscribe a los eventos de cambio de estadística y de recogida que resultan. Están previstas funciones escritas desde el motor: grafos de Blueprint y C++ (traducido a un script que valida la plataforma). Ambos se analizan y se suben como cualquier declaración; la ejecución sigue en la plataforma.
Un hook es una función en la nube: se ejecuta en la plataforma, no en el motor. Escríbelo en C#, TypeScript o Python; Unity se suscribe a los eventos de cambio de estadística y de recogida que resultan.
Lo que el hook no tiene que hacer:
- El rechazo es tipado. Un tanque con la salud al máximo recibe
already at full health, con un motivo que el cliente puede mostrar, y el botiquín sigue ahí para quien lo necesite. - Solo el servidor ajusta estadísticas. Un cliente no puede pedir que se ajuste una estadística (stat), así que no hay ninguna excepción antitrampas que escribir para los objetos que se recogen.
- El HUD se actualiza sin que nadie se lo pida.
Adjustemitechanged, y el cliente ya está suscrito a las estadísticas declaradas del tanque. No escribiste ningún mensaje de red.
De esto se encargan Extensibility y Entity Presets.
Siguientes pasos
- Entity Presets — el generador de botín, los objetos del mundo y el modelo de estadísticas en los que se apoyó esta lección; los tres son presets de
entity, no módulos. - Collision — capas, respuestas y la diferencia entre un contacto que solo se reporta y uno que bloquea.
- Map — cómo se elige una posición válida y qué significa “alcanzable”.
- Extensibility — todos los puntos de enganche de hooks, en orden, con el contrato de veto.
- Tabla de clasificación en Tanks — el otro ejemplo de Tanks.