Перейти к основному содержимому
Примеры

Эта страница ещё не переведена — показана английская версия.

Аптечки в 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 работает на клиенте со сгенерированным типизированным API (HealthCrate).

Две вещи писать не пришлось. Где рисовать аптечку — клиент и так отрисовывает объявленные объекты мира. И как её позиция доходит до клиентов — [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.