Menu
CoddyTech

Герой

Часть раздела Архитектура игр в Unity путешествия по Unity на Coddy. Урок 50 из 64.

В этой главе мы создадим Arena Survivor — последнюю игру курса. Враги волнами заполняют огороженную арену, а ты отбиваешься от них мечом: одни просто идут на тебя, другие ждут, а затем бросаются в атаку. После победы над волной остаётся усиление, HUD отображает здоровье и номер волны, а когда здоровье заканчивается, забег завершается и лучший результат сохраняется. В игре используются инструменты из этого раздела там, где они подходят: абстрактный Enemy с двумя разновидностями, IDamageable для всего, что можно повредить, волны в виде ресурса ScriptableObject, предметы для подбора через интерфейс, HUD, который только реагирует на события C#, и сохранение в JSON. Все двенадцать скриптов есть в проекте с самого начала. Пока работает только Hero; остальные содержат поля, которые понадобятся в следующих уроках, а в каждом уроке мы будем добавлять всё больше кода.

Меч героя поражает то, что находится перед ним, поэтому для замаха нужно проверить, находится ли цель «впереди». Vector3.Dot(a, b) перемножает соответствующие компоненты двух векторов и складывает их: a.x * b.x + a.y * b.y + a.z * b.z. Для двух направлений длиной 1 это косинус угла между ними: 1, если они направлены одинаково, 0.866 при 30 градусах, 0.707 при 45, 0 при прямом угле и -1, если они направлены в противоположные стороны. Поэтому условие «в пределах 30 градусов от направления взгляда» превращается в «скалярное произведение не меньше 0.866» — вычислять угол не нужно. Этот робот-часовой поднимает тревогу, когда игрок находится не дальше 10 единиц и попадает в его поле зрения шириной 30 градусов:

public Transform player;
public float range = 10f;

void Update()
{
    Vector3 toPlayer = player.position - transform.position;
    toPlayer.y = 0f;
    bool near = toPlayer.magnitude <= range;
    bool inView = Vector3.Dot(transform.forward, toPlayer.normalized) >= 0.866f;
    if (near && inView)
    {
        Debug.Log("Alarm!");
    }
}

Оба направления должны иметь длину 1. У transform.forward она всегда такая, но длина toPlayer равна расстоянию, поэтому сначала вектор нормализуется; иначе игрок вдвое дальше дал бы вдвое большее скалярное произведение. Перед этим значение y устанавливается в 0, поэтому игрок на верхнем мостике учитывается по его положению на карте, а не по высоте. Знак отличает переднюю сторону от задней: при скалярном произведении больше 0 цель находится где угодно в передней половине, а при значении меньше 0 — где угодно позади.

challenge icon

Задание

Средне

Дайте герою меч.

  1. В Update(), когда нажата клавиша пробела или левая кнопка мыши и Time.time >= nextSwing, задайте nextSwing = Time.time + swingCooldown; и вызовите Swing().
  2. В Swing() переберите Physics.OverlapSphere(transform.position, reach).
  3. Уничтожьте каждую цель с тегом Enemy, плоское направление toHit которой (y установлено в 0) даёт Vector3.Dot(transform.forward, toHit.normalized) >= 0.5f.

Нажмите «Играть» и атакуйте: манекены впереди исчезнут. Затем нажмите «Проверить».

Попробуйте сами

using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;

public class Hero : MonoBehaviour
{
    [SerializeField] private float speed = 5f;
    [SerializeField] private float reach = 2f;
    [SerializeField] private float swingCooldown = 0.5f;
    [SerializeField] private GameObject slash;

    private CharacterController controller;
    private InputAction moveAction;
    private float nextSwing;

    void Start()
    {
        controller = GetComponent<CharacterController>();
        moveAction = InputSystem.actions.FindAction("Move");
    }

    void Update()
    {
        Vector2 input = moveAction.ReadValue<Vector2>();
        Vector3 move = new Vector3(input.x, 0f, input.y);
        controller.SimpleMove(move * speed);
        if (move != Vector3.zero)
        {
            transform.rotation = Quaternion.LookRotation(move);
        }

        // Нажатие Space или левой кнопки мыши вызывает удар не чаще одного раза в swingCooldown секунд
    }

    void Swing()
    {
        StartCoroutine(ShowSlash());
        // уничтожить все объекты с тегом Enemy в пределах досягаемости перед героем
    }

    IEnumerator ShowSlash()
    {
        slash.SetActive(true);
        yield return new WaitForSeconds(0.1f);
        slash.SetActive(false);
    }
}

Все уроки раздела Архитектура игр в Unity

Потренируйтесь самостоятельно: Песочница Unity