Сегодняшний обзор расскажет о том, как игрок взаимодействует с окружающей средой с помощью raycasting.

Я закончил GLI Framework для курса GLI. Цель состояла в том, чтобы закончить некоторые основные механики в игре Sci-Fi Duck Hunt. Мне нужно было реализовать навигационную сетку, ИИ, поведение игрока, HUD, игровой звук, условия победы, условия проигрыша, два бонусных задания, включая взрывную бочку и здоровье барьера.

Цель игры — не дать врагам добраться до верхней двери, чтобы сбежать. Поэтому враги должны путешествовать от начальной точки к конечной. В то же время игрок находится в сторожевой башне, стреляя из винтовки, чтобы остановить их.

Сегодняшний обзор расскажет о том, как игрок взаимодействует с окружающей средой и Raycasting.

Поведение игрока

У игрока есть два простых действия: движение и стрельба. Также к плееру прикреплены три скрипта: один скрипт для контроллера FPS, скрипт запуска и скрипт менеджера плеера.

  • Перемещение ограничено внутри сторожевой башни и контролируется WASD.
  • Стрельба привязана к левой кнопке мыши и полуогню. Это означает, что оружие срабатывает только при нажатии и сбрасывается при отпускании. С небольшим похолоданием.

Скрипт запуска

Сценарий стрельбы стреляет лучом в центр экрана, где находится прицельная сетка.

_ray = _camera.ViewportPointToRay(new Vector3(0.5f, 0.5f));

Сейчас я использую новую систему ввода, и в этом скрипте я включаю карту игрока и вызываю выполненное событие. Мой метод, прикрепленный к событию, проверяет, может ли игрок стрелять из своего оружия. Если это так, бросьте луч из центра, включив дульную вспышку, воспроизведите звук, снова отключите проигрыватель от стрельбы до отпускания, а затем мы вызовем другой метод, который обрабатывает действия выпущенного луча.

Вспышка дула — это актив с Filebase.com, а звук — бесплатный источник, который я нашел в Интернете.

Все, что я сделал для дульной вспышки, это прикрепил систему частиц к стволу.

public class Fire : MonoBehaviour
{
    private Ray _ray;
    private ActionMaps _inputs;
    private AudioSource _gunFire;
    [SerializeField] private AudioClip _barrier;
    [SerializeField] private GameObject _muzzleFlash;
    private bool _canFire = true;
    [SerializeField] private Camera _camera;
    private PlayerManager _playerManager;
    [SerializeField] private LayerMask _mask;
    private float _timer = 0.5f;
    private IDamage _damagable;

    private void Awake()
    {
        _playerManager = GetComponent<PlayerManager>();
        _gunFire = GetComponent<AudioSource>();
        _inputs = new ActionMaps();
    }

    private void OnEnable()
    {
        _inputs.Player.Enable();
        _inputs.Player.Fire.performed += Fire_performed;
        _inputs.Player.Fire.canceled += Fire_canceled;
        _muzzleFlash.SetActive(false);
    }

    private void Fire_canceled(InputAction.CallbackContext obj)
    {
            _canFire = true;
    }

    private void Fire_performed(InputAction.CallbackContext obj)
    {
        if (_canFire) 
        {
            _ray = _camera.ViewportPointToRay(new Vector3(0.5f, 0.5f));
            _muzzleFlash.SetActive(true);
            _gunFire.Play();
            _canFire = false;
            _timer += Time.time;
            Firing();
        }
    }
}

Метод стрельбы

В этом методе запускается сопрограмма охлаждения, чтобы отключить эффект частиц для вспышки дульного выстрела.

 IEnumerator FiringCoolDownRoutine()
    {
        yield return new WaitForSeconds(0.2f);
        _muzzleFlash.SetActive(false);
    }

Затем метод проверяет, попадает ли raycast на объект с правильной маской слоя. (ИИ, Барьеры, Взрывчатка)

если это так, у меня есть три оператора if-else, которые сравнивают тег объектов и определяют результат.

  • ИИ (враг) Мы вызываем менеджера возрождения и уменьшаем общее количество врагов, появляющихся в данный момент.
  • Позвоните менеджеру-игроку и добавьте 60 очков к счету игрока.
  • Затем уведомите сценарий ИИ выстрела, чтобы запустить метод смерти.
  StartCoroutine(FiringCoolDownRoutine());
        if (Physics.Raycast(_ray, out RaycastHit _hit, Mathf.Infinity,_mask))
        {
            if (_hit.transform.CompareTag("Enemy"))
            {
                _hit.transform.gameObject.TryGetComponent<AI>(out AI ai);
                SpawnManager._instance._spawnCount--;
                _playerManager.PlayerScore(60);
                ai.Death();
            }
  • Компонент барьера хранится в переменной, и я вызываю метод повреждения (интерфейс) и проигрываю звуковой сигнал, когда снаряд попадает в барьер.
            else if (_hit.transform.CompareTag("Barriers"))
            {
                _damagable = _hit.transform.gameObject.GetComponent<Barrier>();
                _damagable.Damage(5);
                AudioSource.PlayClipAtPoint(_barrier, new Vector3(0, 11, 32), 500);
            }

  • Наконец, что касается взрывных бочек, всякий раз, когда луч попадает в бочку, мы сохраняем взрывной компонент в переменной и уведомляем скрипт, чтобы зажечь бочку и взорваться.

            else 
            {
                Explosive_Barrel barrel = _hit.transform.gameObject.GetComponentInParent<Explosive_Barrel>();
                barrel._isIgnited = true;
                Debug.Log("BOOM");
            }

Скрипт менеджера игроков

Скрипт обрабатывает счет игрока. Вероятно, я мог бы написать сценарий для одиночной игры, в котором учитывались бы и стрельба, и счет, но вместо этого я пошел по этому пути.

public class PlayerManager : MonoBehaviour
{
    [SerializeField] public int Score { get; private set; }

    private void Awake()
    {
        Cursor.visible = false;
    }
    public void PlayerScore(int value) 
    {
        Score += value;
        UIManager._instance.PlayerScore(Score);
    }
}

В заключение, вот краткий обзор того, как я настраивал сценарии игроков и использовал raycasting в проекте для воспроизведения снайперской винтовки. Опять же, движение персонажа контролируется контроллером персонажа FPS. В следующей статье я могу углубиться в начальные и финальные сцены или рассказать о разрушаемых барьерах.