本页尚未翻译,当前显示英文版本。
Tanks 中的医疗箱
这是第二节 Tanks 课程,共三步,不引入新模块:一份声明定义医疗箱(health crate),一份声明定义医疗箱在哪里出现,再用一个 hook 定义 拾取后的效果。请在 快速入门 之后学习,与 排行榜课程 的先后顺序不限。
声明医疗箱
医疗箱是一个实体(entity),套用了世界物件预设,并带有一个只报告接触、不阻挡任何人的碰撞体。正是 pickups 层上的 Response.Pass 让它成为拾取物(pickup),而不是障碍物:照常报告接触,运动则直接穿过。
pickups 层上仅存在于运行时的医疗箱:报告接触,不影响运动[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)])Go 中基于特性的声明方式尚未确定(O-1),确定写法后会补上示例。
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声明在服务器项目中编写,用 playserv push 推送;Unity 绑定在客户端使用生成的类型化 API(HealthCrate)。
有两件事你不必写:医疗箱画在哪里(客户端已经会渲染声明的世界物件),以及它的位置怎样到达各个客户端:[Sync] 本身就是网络调用。
相关模块:Entity Presets 和 Collision。
声明医疗箱出现的位置
放置位置同样是声明,这一步决定了这个功能玩起来是否公平。间距让医疗箱不会扎堆,与玩家保持距离让它们不会在对决现场生成,不重复规则则避免每次都落在同一个地点。
[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)Go 中基于特性的声明方式尚未确定(O-1),确定写法后会补上示例。
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声明在服务器项目中编写,用 playserv push 推送;Unity 客户端看到的结果是生成的世界物品和拾取事件。
有效位置由 Map 提供:掉落表(drop table)在 ground 层上请求一个地点,地图返回一个确实可到达的地点,所以医疗箱永远不会落进墙里。
相关模块:Entity Presets 和 Map。
拾取时回血
只有一个 hook,也是本课唯一的代码。它作为云函数(cloud function)在平台上运行,所以两个引擎标签页里都没有它。
[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)Go 中基于特性的声明方式尚未确定(O-1),确定写法后会补上示例。
hook 是云函数,在平台上执行,不在引 擎里执行。用 C#、TypeScript 或 Python 编写,Unreal 订阅由此产生的属性变更和拾取事件。在引擎中编写函数的方式已在计划中:Blueprint 图,以及 C++(转换为经平台校验的脚本目标)。两者都像其他声明一样经过分析并推送,执行仍在平台上。
hook 是云函数,在平台上执行,不在引擎里执行。用 C#、TypeScript 或 Python 编写,Unity 订阅由此产生的属性变更和拾取事件。
这个 hook 不必做的事:
- 拒绝是有类型的。 满血的坦克会收到
already at full health,附带客户端可以直接显示的原因,医疗箱则留给需要它的人。 - 调整属性由服务器权威决定。 客户端无法请求修改属性(stat),所以不需要为拾取编写反作弊例外。
- HUD 自动更新。
Adjust发出changed,而客户端早已订阅了坦克声明的属性。你没有写任何网络消息。
相关模块:Extensibility 和 Entity Presets。
后续阅读
- Entity Presets:本课依赖的掉落生成器、世界物件和属性模型,三者都是
entity的预设,而不是模块。 - Collision:层、响应,以及只报告的接触与阻挡型接触的区别。
- Map:有效位置如何选出, 以及“可到达”的含义。
- Extensibility:按顺序列出所有 hook 挂载点及否决约定。
- Tanks 中的排行榜:另一个 Tanks 示例。