Pull to refresh

Тир. Стрельба рейкастами на Unity 3D

Reading time6 min
Views14K

Учебные материалы для школы программирования. Часть 17

Предыдущие уроки можно найти здесь:

В этом проекте рассмотрим процесс работы:

  • с рейкастами и векторами;

  • с методами других пользовательских классов;

  • с AudioSource и с Rigidbody через код;

  • три основных составляющих выстрела, психологически действующих на игрока (звук, свет и свечение, анимация и след от выстрела);

  • инстанцирование префабов.

Ключевыми темами проекта выступают именно рейкасты и векторы. Последним, необходимо уделять довольно много времени каждый день, и на простых примерах объяснять, как они устроены. Но если вам удалось быстро выполнить проект с учениками, то в этом уроке будет уместно рассмотреть mecanim-систему.

Порядок выполнения

Создаём новый проект, импортируем приложенный ассет.

Помимо стандартных ресурсов пакет имеет сторонний плагин для рисовки декалей. Его работа в контексте данного урока не рассматривается.

Проект урока разбит на 2 части - тир и гранаты.

Тир

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

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

Внутри проекта есть скрипт DecalShooter, который создаёт декали, и в котором расположен весь код стрельбы, включая рейкаст. В нём будет вводиться код взаимодействия с мишенью.
Для начала, необходимо подготовить саму мишень. Ею служит цилиндр, который необходимо уменьшить по Y до состоянии платины, удалить CapsuleCollider и поставить MeshCollider с галочкой Convex. Дополнительно, на цилиндр устанавливается текстура мишени, внутри цилиндра создаётся point light, подсвечивающий мишень, и объект с AudioSource для воспроизведения звука, а на сам цилиндр устанавливается Rigidbody с обработкой коллизий типа Continius Dynamic и галочкой isKinematik. У AudioSource не забудьте убрать галочку PlayOnAwake и закинуть звук попадания в мишень.

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

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

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Target : MonoBehaviour {
    public GameObject light;
    public Rigidbody rig;
    public AudioSource src;
 
    bool enabled = true;
 
    // Use this for initialization 
    void Start() {
        rig = GetComponent<Rigidbody>();
        src = GetComponent<AudioSource>(); 
    }
 
    // Update is called once per frame 
    void Update() {
 
    }
 
    public void shoot() {
        if (!enabled) {
           return;
        }
 
        rig.isKinematic = false; 
        light.SetActive(false); 
        src.Play();
 
        enabled = false;
    }
 
}

Код мишени содержит публичный метод ​shoot,​ который необходим для того, чтобы другие скрипты могли обращаться к нашей мишени. Следующий шаг - проверка из файла DecalShooter попали ли мы в мишень и вызов метода ​shoot. В рейкаст необходимо включить следующий участок кода:

   if (Input.GetKeyDown(KeyCode.Mouse0)) {
            time = 0.3f; 
            ShootSource.Play(); 
            anim.Play("fire"); 
            Muzzleflash.SetActive(true);
            // Сама стрельба 
            RaycastHit hitInfo;
            Vector3 fwd = transform.TransformDirection(Vector3.forward); 
            
            if (Physics.Raycast(transform.position, fwd, out hitInfo, 100f)) {
                GameObject go = Instantiate(
                    DecalPrefab,
                    hitInfo.point, 
                    Quaternion.LookRotation(
                        Vector3.Slerp(-hitInfo.normal, fwd, normalization) 
                    )
                ) as GameObject;
                go.GetComponent<DecalUpdater>().UpdateDecalTo(
                    hitInfo.collider.gameObject, 
                    true
                );
                Vector3 explosionPos = hitInfo.point;
                Target trg = hitInfo.collider.GetComponent<Target>();
                
                if (trg) {
                    trg.shoot();
                }
                
                Rigidbody rb = hitInfo.collider.GetComponent<Rigidbody>();
                
                if (rb != null) {
                    rb.AddForceAtPosition(fwd * power, hitInfo.point, ForceMode.Impulse);
                    Debug.Log("rb!");
                } 
            }
            // Сама стрельба 
        }

Данный код пытается получить компонент из объекта hitInfo и, если это удаётся, вызывает метод ​shoot.​ Мишень падает, свет от мишени выключается, звук попадания воспроизводится. Далее, желательно дать группе свободное задание по кастомизации своего проекта. Как альтернативу, можно предложить изменить код таким образом, чтобы мишень меняла цвет при попадании. Делается это заменой в Target строк:

light.SetActive(false);

на

light.GetComponent<Light>().color = Color.red;

Таким образом, свет меняется и не удаляется.

Гранаты

Эта часть урока должна ещё больше закрепить понимание векторов и работы рейкастов.

Для начала, создадим другой скрипт - дальномер.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
 
public class Lenght :  MonoBehaviour {
    public Text Dalnost;
    float rasstoyanie = 0; // переменная для расстояния до цели
 
    // Use this for initialization 
    void Start() {
 
    }
 
    // Update is called once per frame 
    void Update() {
        RaycastHit hitInfo;
        
        if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hitInfo, 200)) {
            rasstoyanie = hitInfo.distance;
            Dalnost.text = rasstoyanie.ToString();
        }
    }
}

Скрипт закинем в FPScontroller/FirstPersonCharacter. В Canvas создадим текст, закинем его в скрипт.
В этом скрипте реализован простейший рейкаст, и на его примере мы разбираем, как рейкаст передаёт информацию в структуру и как нам получать из структуры эту информацию.
При срабатывании рейкаста мы выводим дальность на экран.

Следующий шаг - бросок гранаты. Мы не используем никаких анимаций, мы создаём гранату на небольшом расстоянии от игрока и придаём ей скорость по направлению взгляда. Изменим скрипт, добавив в него поля для префаба гранаты и код броска.

using System.Collections;
using System.Collections.Generic; 
using UnityEngine;
using UnityEngine.UI;
 
public class Length :  MonoBehaviour {
    public Text Dalnost;
    float rasstoyanie = 0; // переменная для расстояния до цели 
    public GameObject sharik;
 
    // Use this for initialization
    void Start() {
 
    }
 
    // Update is called once per frame 
    void Update() {
        RaycastHit hitInfo; 
        
        if(Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), outhitInfo, 200)) {
            rasstoyanie = hitInfo.distance; 
            Dalnost.text = rasstoyanie.ToString ();
            if(Input.GetKeyDown(KeyCode.Mouse1)) {
                GameObject go = Instantiate(
                    sharik, 
                    transform.position + Vector3.Normalize(hitInfo.point - transform.position), 
                    transform.rotation
                );
                Rigidbody rig = go.GetComponent<Rigidbody>();
                rig.velocity = Vector3.Normalize(hitInfo.point - transform.position) * 10;
            } 
        }
    } 
}

Сразу же возникнет вопрос - зачем такое решение? К тому же, при направлении в небо бросок не произойдёт. Сделано всё это в учебных целях. Т.к. в рамках урока код пишется поэтапно, на данном участке объясняется, как найти вектор направления на объект по конечным и начальным координатам. Теперь дело за самой гранатой. Создадим скрипт Grenade.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Grenade :  MonoBehaviour {
    public Transform explosionPrefab;
 
    void OnCollisionEnter(Collision collision) {
        ContactPoint contact = collision.contacts[0];
        
        // Rotate the object so that the y-axis faces along the normal of the surface
        Quaternion rot = Quaternion.FromToRotation(Vector3.up, contact.normal);
        Vector3 pos = contact.point; 
        Instantiate(explosionPrefab, pos, rot);
        Destroy(gameObject);
    }
}

Закидываем на сцену гранату, на меш Body ставим коллайдеру Convex, добавляем гранате RIgidbody и наш скрипт. Получившуюся гранату добавляем в префаб и удаляем со сцены.

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

Создадим эффект взрыва. В нём должен быть свет от взрыва, AudioSource с галочкой PlayOnAwake и звуком взрыва, Spital Blend на 90 процентов переведённый в 3д и увеличенный радиус распространения звука.

Для правильной отработки всех эффектов и разлёта Rigidbody нужно создать ещё один скрипт. Его мы назовём Explosion:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Explosion :  MonoBehaviour {
    public float radius = 5.0f;
    public float power = 10.0f;
    public GameObject svet;

    void Start() {
        Destroy(svet, 0.1f);

        Vector3 explosionPos = transform.position;
        Collider[] colliders = Physics.OverlapSphere(explosionPos, radius);

        foreach(Collider hit in colliders) {
            Rigidbodyrb = hit.GetComponent<Rigidbody>();
            if (rb != null) {
                rb.AddExplosionForce(power, explosionPos, radius, 3.0f);
            }
        }
    }
}

Его нужно закинуть на эффект взрыва и создать префаб.

Данный префаб и используется в скрипте гранаты для создания взрыва.

Готово!

Tags:
Hubs:
Total votes 1: ↑1 and ↓0+1
Comments0

Articles