Not translated yet — showing English.
Health crates in Tanks
This is the second Tanks lesson. It takes three steps and no new module: a declaration for the crate, a declaration for where crates appear, and one hook for what picking one up does. Take it after Getting Started, in either order with the leaderboard lesson.
Declare the crate
A crate is an entity with the world-objects preset applied and a body that reports contact without stopping anyone. Response.Pass on the pickups layer is what makes it a pickup rather than an obstacle: the contact is reported, the motion passes straight through.
pickups layer — contact reported, motion unaffected[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)])Attribute-style declaration in Go is still open (O-1) — the samples land when the carrier is decided.
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 CIDeclarations are authored in the server project and pushed with playserv push; the Unity binding consumes the generated typed API (HealthCrate) on the client surface.
Two things you did not have to write: where the crate is drawn (the client already renders declared world objects) and how its position reaches the clients: [Sync] is the network call.
Owned by Entity Presets and Collision.
Declare where crates appear
Placement is a declaration too, and this is the step that decides whether the feature feels fair. Spacing keeps crates from clumping, distance from players keeps them from spawning into a duel, and the no-repeat rule keeps the same spot from being the answer every time.
[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)Attribute-style declaration in Go is still open (O-1) — the samples land when the carrier is decided.
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 CIDeclarations are authored in the server project and pushed with playserv push; the Unity client sees the results as spawned world items and pickup events.
Valid positions come from Map — the table asks for a spot on the ground layer and the map answers with one that is actually reachable, so a crate never lands inside a wall.
Owned by Entity Presets and Map.
Heal on pickup
One hook, and it is the only code in the lesson. It runs on the platform as a cloud function, which is why it appears on neither engine tab.
[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)Attribute-style declaration in Go is still open (O-1) — the samples land when the carrier is decided.
A hook is a cloud function: it executes on the platform, not in the engine. Write it in C#, TypeScript or Python — Unreal subscribes to the resulting stat-changed and pickup events. Engine-authored functions are planned: Blueprint graphs, and C++ (translated to a platform-validated script target). Both are analyzed and pushed like any declaration; execution stays on the platform.
A hook is a cloud function: it executes on the platform, not in the engine. Write it in C#, TypeScript or Python — Unity subscribes to the resulting stat-changed and pickup events.
What the hook doesn't have to do:
- The refusal is typed. A tank at full health gets
already at full healthwith a reason a client can show, and the crate is still there for someone who needs it. - Adjusting a stat is server-authoritative. It is not something a client can ask for, so there is no anti-cheat exception to write for pickups.
- The HUD updates without being told to.
Adjustemitschanged; the client is already subscribed to the tank's declared stats. You did not write a network message.
Owned by Extensibility and Entity Presets.
Where to go next
- Entity Presets — the drop generator, world objects and the stat model this lesson leaned on, all three of them presets of
entityrather than modules. - Collision — layers, responses, and the difference between a reported contact and a blocking one.
- Map — how a valid position is chosen, and what "reachable" means.
- Extensibility — every hook point in order, with the veto contract.
- A leaderboard in Tanks — the other Tanks example.