Cette page n’est pas encore traduite : la version anglaise est affichée.
Des trousses de soin dans Tanks
Voici la deuxième leçon Tanks. Elle tient en trois étapes, sans nouveau module : une déclaration pour la trousse, une autre pour les endroits où elle apparaît, et un hook pour l’effet de son ramassage (pickup). Suivez-la après Premiers pas, avant ou après la leçon sur le classement, peu importe l’ordre.
Déclarer la trousse de soin
Une trousse est une entité dotée du preset des objets du monde (world objects) et d’un corps qui signale le contact sans arrêter personne. C’est Response.Pass sur la couche pickups qui en fait un objet à ramasser plutôt qu’un obstacle : le contact est signalé, et le mouvement passe au travers.
pickups — contact signalé, mouvement inchangé[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)])La déclaration par attributs en Go n’est pas encore tranchée (O-1). Les exemples arriveront quand la forme d’écriture sera choisie.
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 CILes déclarations s’écrivent dans le projet serveur et se publient avec playserv push ; côté client, le binding Unity utilise l’API typée générée (HealthCrate).
Vous n’avez eu à écrire ni l’affichage de la trousse (le client dessine déjà les objets du monde déclarés), ni l’envoi de sa position aux clients : l’appel réseau, c’est [Sync].
Modules concernés : Entity Presets et Collision.
Déclarer où apparaissent les trousses
Le placement est lui aussi une déclaration, et c’est à cette étape que se joue le sentiment d’équité. L’espacement empêche les trousses de s’agglutiner, la distance aux joueurs les empêche d’apparaître en plein duel, et la règle de non-répétition évite que le même endroit revienne à chaque fois.
[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)La déclaration par attributs en Go n’est pas encore tranchée (O-1). Les exemples arriveront quand la forme d’écriture sera choisie.
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 CILes déclarations s’écrivent dans le projet serveur et se publient avec playserv push ; le client Unity en voit le résultat sous forme d’objets apparus dans le monde et d’événements de ramassage.
Les positions valides viennent de Map : la table de butin (drop table) demande un emplacement sur la couche ground, et la carte lui en renvoie un réellement accessible. Une trousse n’atterrit donc jamais dans un mur.
Modules concernés : Entity Presets et Map.
Soigner au ramassage
Un seul hook, et c’est le seul code de la leçon. Il s’exécute sur la plateforme, comme fonction cloud ; voilà pourquoi il n’apparaît dans aucun des deux onglets moteur.
[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)La déclaration par attributs en Go n’est pas encore tranchée (O-1). Les exemples arriveront quand la forme d’écriture sera choisie.
Un hook est une fonction cloud : il s’exécute sur la plateforme, pas dans le moteur. Écrivez-le en C#, TypeScript ou Python ; Unreal s’abonne aux événements de changement de statistique et de ramassage qui en résultent. Des fonctions écrites dans le moteur sont prévues : des graphes Blueprint et du C++ (traduit vers une cible de script validée par la plateforme). Les deux seront analysés et publiés comme n’importe quelle déclaration, et l’exécution restera sur la plateforme.
Un hook est une fonction cloud : il s’exécute sur la plateforme, pas dans le moteur. Écrivez-le en C#, TypeScript ou Python ; Unity s’abonne aux événements de changement de statistique et de ramassage qui en résultent.
Ce dont le hook n’a pas à se charger :
- Le refus est typé. Un tank en pleine santé reçoit
already at full health, avec un motif qu’un client peut afficher, et la trousse reste là pour quelqu’un qui en a besoin. - Modifier une statistique revient au serveur, qui fait autorité. Un client ne peut pas le demander : vous n’avez aucune exception anti-triche à écrire pour les ramassages.
- Le HUD se met à jour sans qu’on le lui demande.
Adjustémetchanged, et le client est déjà abonné aux statistiques déclarées du tank. Vous n’avez écrit aucun message réseau.
Modules concernés : Extensibility et Entity Presets.
Pour aller plus loin
- Entity Presets — le générateur de butin, les objets du monde et le modèle de statistiques sur lesquels repose cette leçon : tous trois sont des presets d'
entity, pas des modules. - Collision — les couches, les réponses, et la différence entre un contact signalé et un contact bloquant.
- Map — comment une position valide est choisie, et ce que veut dire « accessible ».
- Extensibility — tous les points d’accroche des hooks, dans l’ordre, avec le contrat de veto.
- Un classement dans Tanks — l’autre exemple Tanks.