translatedTitles.Article
Ke
Kerioth
11/25/25

Unity. Урок 3

Привет!

Меня зовут Аристарх, я веду занятия по разработке на Unity и буду вашим ментором.

Рад, что вы приступаете к разработке прототипа шутера на интенсиве Практик•ON.

При возникновении вопросов по Unity, пишите мне в чате в ветке Unity с тегом @Kerioth.

Вступайте в чат Практик•ON для прохождения интенсива


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

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


Из чего состоит стрельба?

Если попробовать упростить механику стрельбы до базовых компонентов, их останется всего 4:

  • Кто стреляет (игрок)
  • Из чего стреляет (оружие)
  • Чем стреляет (пуля)
  • В кого стреляет (мишень)

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


Создадим четыре скрипта:

  • WeaponManager – управляет выбором и переключением оружия.
  • WeaponController – логика конкретного оружия.
  • Projectile – снаряд, который летит и наносит урон.
  • Damagable – объект, который может получать урон.


Чуть подробнее про логику их взаимодействия


WeaponManager

  • Хранит список оружия игрока.
  • Переключает оружие.
  • Активирует текущее оружие.
  • Передаёт команды стрельбы от игрока к оружию.


WeaponController

  • Логика конкретного оружия:
    • скорость стрельбы,
    • тип снаряда,
    • эффекты,
    • количество патронов.
  • Производит выстрел и создаёт Projectile.


Projectile

  • Летит вперёд с нужной скоростью.
  • Сталкивается с объектами
  • Передаёт урон через компонент Damagable.
  • Разрушает себя после удара.


Damagable

  • Принимает урон.
  • Управляет здоровьем.
  • Реагирует на смерть объекта.


Реализовывать будем в обратном порядке и начнем с Damagable


Damagable

Напишем логику для объектов, которые будут получать урон.


C#using UnityEngine;

public class Damagable : MonoBehaviour
{
    // здоровье
    public float health = 100f;

    // метод получения урона
    public void TakeDamage(float amount)
    {
        // уменьшаем здоровье
        health -= amount;

        // если здоровье меньше или 0
        if (health <= 0f)
        {
            Die();
        }
    }

    // метод для обработки смерти
    private void Die()
    {
        // удаляем объект
        Destroy(gameObject); 
    }
}

Этот скрипт можно повесить на любой объект, который должен реагировать на попадания пуль.

Главное добавьте компоненты коллайдера и твердого тела, чтобы пуля могла считать столкновение

Projectile


Напишем логику для пули и других объектов, которыми будем стрелять


C# using UnityEngine;

public class Projectile : MonoBehaviour
{
    public float lifetime = 3f; // время жизни
    public int damage = 25; // урон

    private Vector3 velocity; // вектор скорости пули

    // инициализация, которая будет производиться через контроллер оружия
    public void Initialize(float speed)
    {
        // вычисляем вектор скорости
        velocity = transform.forward * speed;
        // указываем удаление объекта через время жизни
        Destroy(gameObject, lifetime);
    }

    void Update()
    {
        MoveForward();
    }

    // движение вперед
  	private void MoveForward()
  	{
  		  transform.position += velocity * Time.deltaTime;
  	}
    
    // при столкновении с чем-либо
    private void OnCollisionEnter(Collision other)
    {
        // проверяем наличие Damagable 
        if (other.collider.TryGetComponent(out Damagable d))
            d.TakeDamage(damage);

        // удаляем объект
        Destroy(gameObject);
    }
}


Пуля необходимо создать как отдельный префаб (игровой объект, который затем помещается в ассеты).

Можете сделать её сферой, вытянутой по оси Z или любой другой фигурой. Главное добавьте на неё коллайдер, по которому и будет проверяться наличие столкновений через метод OnCollisionEnter().

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

WeaponController


Напишем логику работы оружия. В ней будут настройки стрельбы и метод для создания пули


C#using UnityEngine;

public class WeaponController : MonoBehaviour
{
    [Header("Shooting")] 
    public float fireRate = 0.2f;
    private float nextShotTime;

    [Header("Projectile")]
    public Projectile projectilePrefab;
    public Transform firePoint;
    public float projectileSpeed = 30f;
    private float nextFireTime;
    
    public void TryShoot()
    {
        if (Time.time < nextShotTime) return;

        Shoot();
        nextShotTime = Time.time + fireRate;
    }

    private void Shoot()
    {
        // Создаём снаряд
        Projectile bullet = Instantiate(projectilePrefab, firePoint.position, firePoint.rotation);
        // Задаетм ему скорость
        bullet.Initialize(projectileSpeed);
    }
}


Сейчас выстрел происходит очень просто, по созданию пули.

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


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

Поместите объект как дочерний к камере, находящейся в объекте игрока, для того, чтобы он поворачивался в соответствии со взглядом игрока. Я так же добавил промежуточный объект WeaponHolder, чтобы несколько видов оружия не лежали прямо в камере, но логика текущей реализации будет работать и без этого.

Обязательно добавьте в объект оружия точку выстрела FirePoint .

Это может быть пустой объект, потому что нам нужен только его трансформ, чтобы знать откуда и в каком направлении производить выстрел. Но я рекомендую добавить 3D объект сферы, уменьшить его и убирать коллайдер, так можно наглядно видеть его положение и легче настроить.

После того, как зафиксируете его положение, можно отключить визуал (Mesh Renderer), чтобы он не мешал в игре.

WeaponManager

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

Перед тем, как перейти к написанию скрипта, создадим новые экшены ввода для стрельбы и перезарядки

Эти экшены будут использоваться в менеджере оружия.


C#using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class WeaponManager : MonoBehaviour
{
    public InputActionReference Fire;
    public InputActionReference Reload;
     
    public List<WeaponController> weapons; // список оружия
    private int currentWeaponIndex = 0; // индекс текущего оружия
    
    private void Start()
    {
        SelectWeapon(0);
    }

    void Update()
    {
        HandleFire();
    }

    void HandleFire()
    {
        // если был вызван экшен стрельбы
        if(Fire.action.triggered)
            // стреляем из текущего оружия
            weapons[currentWeaponIndex].TryShoot();
    }

    public void SelectWeapon(int index)
    {
        // для всего оружия в списке оружия
        for (int i = 0; i < weapons.Count; i++)
             // активируем только если нужный индекс, остальные будут деактивированы
            weapons[i].gameObject.SetActive(i == index);
    }
}


Этот скрипт помещается на объект игрока, в нем надо указать экшены ввода для стрельбы и перезарядки, которые мы задали до этого, а так же оружие, которое настраивали до этого. После этого попробуйте протестировать стрельбу.

assetEditor.textBlockNoText

Патроны и перезарядка

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


В контроллер оружия добавим поля для настройки количества патронов и времени перезарядки

C#[Header("Ammo")]
public int maxAmmo = 30; // максимальное количество патронов
public int currentAmmo; // текущее количество патронов
public float reloadTime = 1.5f; // время перезарядки
private bool isReloading; // флаг показывающий идёт ли сейчас перезарядка


На старте сделаем текущее количество патронов максимальным

C#private void Start()
{
    currentAmmo = maxAmmo;
}


Добавим новый метод перезарядки.

Он будет асинхронным, для того, чтобы сделать ожидание времени перезарядки, некую задержку, которую можно прописать в том же методе. Асинхронность можно реализовать разными методами, например через корутины (Corutines, IEnumerator) или таски (Task, UniTask). Но для того, чтобы выбрать самый подходящий метод, надо знать их особенности, преимущества и недочеты. Поэтому мы будем использовать самый простой метод для понимания и применения - Awaitables. Это решение от Unity, которое решает проблему неудобства использование IEnumerator и проблему работы системном потоке Task. В этом смысле он чем-то похож на UniTask хоть и с гораздо меньшим функционалом, но в отличие от UniTask, Awaitables не нужно докачивать отдельной библиотекой и они работают прямо так.

C#// модификатор async показывает что метод будет асинхронным
// использование async void не рекомендуется, но сейчас выбран для простоты
public async void Reload()
{
    // если перезарядка уже идёт или не нужна, выходим из метода
    if (isReloading || currentAmmo == maxAmmo)
        return;

    // начинаем перезарядку
    print("Reloading start"); 
    isReloading = true;

    // ожидание времени перезарядки, модификатор await показывает что метод нужно ожидать
    await Awaitable.WaitForSecondsAsync(reloadTime);

    // обновляем текущее количество патронов
    currentAmmo = maxAmmo;
    // заканчиваем перезарядку
    print("Reloading end");
    isReloading = false;
}


Так же необходимо обновить методы проверки стрельбы и самой стрельбы

C#public void TryShoot()
{
    // добавлена проверка на перезарядку
    if (Time.time < nextShotTime || isReloading) return;
    
    // если нет патронов, выполняем перезарядку
    if (currentAmmo <= 0)
    {
        Reload();
        return;
    }

    Shoot();
    nextShotTime = Time.time + fireRate;
}

private void Shoot()
{
    // уменьшаем число патронов
    currentAmmo--;
    // Создаём снаряд
    Projectile bullet = Instantiate(projectilePrefab, firePoint.position, firePoint.rotation);
    bullet.Initialize(projectileSpeed);
}


Так же обновим менеджер оружия для того, чтобы обрабатывать перезарядку

C#void Update()
{
    HandleReload();
    HandleFire();
}

void HandleReload()
{
    if (Reload.action.triggered)
        weapons[currentWeaponIndex].Reload();
}

На этом настройка перезарядки закончена, попробуйте протестировать её работу и поиграть с настройкой значений.

Смена оружия

Ещё одна механика, которую можно добавить в менеджер это смена оружия

Для менеджера оружия можно добавить метод выбора следующего или предыдущего оружия


C#// если direction = 1 выбирается следующее, если -1, то предыдущее
void SwitchWeapon(int direction)
{
    //меняем индекс
    currentWeaponIndex += direction; 

    // если индекс выходит за предел вниз
    if (currentWeaponIndex < 0) 
        currentWeaponIndex = weapons.Count - 1;
    
    // если индекс выходит за предел вверх
    if (currentWeaponIndex >= weapons.Count)
        currentWeaponIndex = 0;

    // выбираем оружие
    SelectWeapon(currentWeaponIndex);
}

Осталось лишь подвязать вызов этого метода к экшенам ввода на смену оружия. Эту задачу попробуйте решить самостоятельно. Для тех кто хочет решение попроще – в экшенах ввода уже есть Previous и Next. А для тех, кто хочет проверить себя – попробуйте подвязать смену оружия ещё и на колесико мыши.


Что ещё можно добавить?

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

Попробуйте самостоятельно сделать несколько интересных вещей или просто подумать над реализацией:

  • Новые виды оружия и пуль с разными параметрами.
  • Попробуйте добавить общее число патронов в оружие и прописать логику их изменения
  • Подумайте как и на какой из объектов добавить отдачу
  • Попробуйте добавить пулям "взрыв" на столкновение с объектами
  • Какие ещё параметры можно хранить в оружии, а какие в пулях? Как оружие будет передавать эти параметры?
  • Попробуйте сделать прицел, на основе объекта Image


Ещё раз напомню, что у вас всегда есть возможность задать вопрос в чате или комментариях.

В следующей статье мы доработаем стрельбу, чтобы улучшить игровой опыт.

Желаю успехов!



hub.comments