Pular para o conteúdo principal
Exemplos

Esta página ainda não foi traduzida: mostrando a versão em inglês.

Kits médicos no Tanks

Antes
um tanque que leva dano continua danificado até morrer. Não há como se recuperar, então cada luta é uma contagem regressiva e ninguém tem motivo para circular pela arena.
Depois
kits médicos aparecem pela arena, espaçados entre si e longe de quem está lutando. Passar por cima de um cura o seu tanque. Nada mais muda no jogo, e a sala continua sem nenhum código próprio.

Esta é a segunda lição do Tanks. São três passos e nenhum módulo novo: uma declaração para o kit médico (health crate), uma declaração para onde os kits aparecem e um hook para o que acontece na coleta (pickup). Faça depois de Primeiros passos, antes ou depois da lição do ranking.

Declare o kit médico

O kit é uma entidade com o preset de objetos do mundo aplicado e um corpo que informa o contato sem bloquear ninguém. É o Response.Pass na camada pickups que faz dele um item de coleta, e não um obstáculo: o contato é informado e o movimento passa direto.

Um kit só de runtime na camada pickups — contato informado, movimento livre
[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)])
Em breve — Go

Ainda não está decidido como declarar com atributos em Go (O-1). Os exemplos chegam quando a forma de escrita for escolhida.

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 CI
Executa fora da engine

As declarações ficam no projeto do servidor e são publicadas com playserv push; o binding de Unity usa a API tipada gerada (HealthCrate) no lado do cliente.

Duas coisas que você não precisou escrever: onde o kit é desenhado (o cliente já renderiza os objetos do mundo declarados) e como a posição dele chega aos clientes — o próprio [Sync] é a chamada de rede.

Módulos responsáveis: Entity Presets e Collision.

Declare onde os kits aparecem

A alocação também é uma declaração, e é este passo que decide se o recurso parece justo. O espaçamento impede que os kits se amontoem, a distância dos jogadores impede que surjam no meio de um duelo, e a regra de não repetir evita que o mesmo lugar seja sempre a resposta.

Kits na camada do chão — espaçados, longe das lutas e nunca no mesmo lugar duas vezes seguidas
[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)
Em breve — Go

Ainda não está decidido como declarar com atributos em Go (O-1). Os exemplos chegam quando a forma de escrita for escolhida.

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 CI
Executa fora da engine

As declarações ficam no projeto do servidor e são publicadas com playserv push; o cliente Unity vê o resultado como itens do mundo que deram spawn e eventos de coleta.

As posições válidas vêm do Map: a tabela pede um lugar na camada ground, e o mapa responde com um que dá mesmo para alcançar — assim um kit nunca vai parar dentro de uma parede.

Módulos responsáveis: Entity Presets e Map.

Cure na coleta

Um hook, e é o único código da lição. Ele roda na plataforma como cloud function, por isso não aparece em nenhuma das abas de engine.

O hook de coleta cura o tanque e recusa com educação quando não há nada para curar
[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)
Em breve — Go

Ainda não está decidido como declarar com atributos em Go (O-1). Os exemplos chegam quando a forma de escrita for escolhida.

Executa fora da engine

Um hook é uma cloud function: ele roda na plataforma, não na engine. Escreva em C#, TypeScript ou Python — o Unreal assina os eventos de mudança de stat e de coleta que resultam dele. Funções escritas na engine estão planejadas: grafos Blueprint e C++ (traduzido para um formato de script validado pela plataforma). Os dois serão analisados e publicados como qualquer declaração, e a execução continua na plataforma.

Executa fora da engine

Um hook é uma cloud function: ele roda na plataforma, não na engine. Escreva em C#, TypeScript ou Python — o Unity assina os eventos de mudança de stat e de coleta que resultam dele.

O que o hook não precisa fazer:

  • A recusa é tipada. Um tanque com a vida cheia recebe already at full health, com um motivo que o cliente pode mostrar, e o kit continua lá para quem precisar.
  • Ajustar um stat é autoritativo no servidor. O cliente não tem como pedir isso, então não há exceção de anti-cheat para escrever para as coletas.
  • O HUD se atualiza sem ninguém mandar. Adjust emite changed, e o cliente já assina os stats declarados do tanque. Você não escreveu nenhuma mensagem de rede.

Módulos responsáveis: Extensibility e Entity Presets.

Próximos passos

  • Entity Presets — o gerador de drops, os objetos do mundo e o modelo de stats em que esta lição se apoiou; os três são presets de entity, não módulos.
  • Collision — camadas, respostas e a diferença entre um contato que só é informado e um que bloqueia.
  • Map — como uma posição válida é escolhida e o que significa “alcançável”.
  • Extensibility — todos os pontos de hook, em ordem, com o contrato de veto.
  • Um ranking no Tanks — o outro exemplo do Tanks.