Перейти до основного вмісту
Приклади

Цю сторінку ще не перекладено — показано англійську версію.

Аптечки в Tanks

До
танк, який дістав пошкодження, так і лишається пошкодженим до самої загибелі. Відновитися неможливо, тож кожен бій — це зворотний відлік, а їздити ареною немає сенсу.
Після
на арені з’являються аптечки — на відстані одна від одної і подалі від тих, хто саме б’ється. Проїхали по аптечці — підлікувалися. Більше в грі нічого не змінюється, і власного коду в кімнати (room) як не було, так і немає.

Це другий урок на Tanks. У ньому три кроки й жодного нового модуля: оголошення (declaration) аптечки, оголошення місць, де з’являються аптечки, і один хук, який визначає, що дає підібрана аптечка. Проходьте його після сторінки Перші кроки — до чи після уроку про таблицю лідерів, неважливо.

Оголосіть аптечку

Аптечка — це сутність (entity) із застосованим пресетом об’єктів світу і тілом, яке повідомляє про дотик, але нікого не зупиняє. Саме Response.Pass на шарі pickups робить її предметом для підбору, а не перешкодою: платформа повідомляє про дотик, а рух проходить крізь аптечку без змін.

Аптечка, що існує лише під час гри, на шарі 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

Як оголошувати через атрибути в 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 на клієнті працює зі згенерованим типізованим інтерфейсом (HealthCrate).

Двох речей писати не довелося. Перша — де малювати аптечку: клієнт уже рендерить оголошені об’єкти світу (world objects). Друга — як її позиція потрапляє до клієнтів: [Sync] і є мережевим викликом.

Докладно — на сторінках Entity Presets і Collision.

Оголосіть, де з’являються аптечки

Розміщення — теж оголошення, і саме цей крок вирішує, чи здаватиметься механіка чесною. Відстань між аптечками не дає їм збиватися в купу, відстань від гравців (player) не дає їм з’являтися посеред дуелі, а правило без повторів не дає щоразу обирати ту саму точку.

Аптечки на шарі землі — на відстані одна від одної, подалі від бою і ніколи двічі поспіль в одній точці
[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

Як оголошувати через атрибути в 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.

Лікування під час підбору

Один хук — і це єдиний код в уроці. Він працює на платформі як хмарна функція, тому його немає на жодній вкладці рушіїв.

Хук підбору лікує танк, а якщо лікувати нічого, чемно відмовляє
[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

Як оголошувати через атрибути в Go, поки не вирішено (O-1). Приклади з’являться, коли оберуть спосіб запису.

Виконується поза рушієм

Хук — це хмарна функція: він працює на платформі, а не в рушії. Напишіть його на C#, TypeScript або Python, а Unreal підпишеться на події зміни характеристики й підбору, які він породжує. У планах — функції, написані в самому рушії: графи Blueprint і C++ (його транслюватимуть у скрипт, який перевіряє платформа). Їх аналізуватимуть і завантажуватимуть, як будь-яке оголошення, а виконувати їх і далі буде платформа.

Виконується поза рушієм

Хук — це хмарна функція: він працює на платформі, а не в рушії. Напишіть його на C#, TypeScript або Python, а Unity підпишеться на події зміни характеристики й підбору, які він породжує.

Чого хукові робити не потрібно:

  • Відмова типізована. Танк із повним здоров’ям отримує already at full health із причиною, яку клієнт може показати гравцеві, а аптечка лишається на місці для того, кому вона потрібніша.
  • Характеристики змінює лише сервер. Клієнт не може попросити змінити характеристику (stat), тож писати для підбору виняток в античиті не треба.
  • HUD оновлюється сам. Adjust породжує changed, а клієнт уже підписаний на оголошені характеристики танка. Мережевого повідомлення ви не писали.

Докладно — на сторінках Extensibility і Entity Presets.

Що далі

  • Entity Presets — генератор випадання, об’єкти світу і модель характеристик, на які спирався цей урок. Усі три — пресети entity, а не модулі.
  • Collision — шари, реакції і різниця між дотиком, про який лише повідомляють, і дотиком, що зупиняє рух.
  • Map — як обирають допустиму позицію і що означає «досяжна».
  • Extensibility — усі точки виклику хуків по порядку і правила вето.
  • Таблиця лідерів у Tanks — другий приклад на Tanks.