Статьи

Создание игры дополненной реальности в стиле покемонов GO с Vuforia: часть 2

Что вы будете создавать

В последнем посте этой серии мы узнали, как настроить Vuforia и начать разработку AR-игры с нуля, применяя логику, аналогичную той, которая используется в Pokémon GO!

  • Создайте игру дополненной реальности Pokémon GO Style с Vuforia

  • Покемон GO Стиль дополненной реальности с Vuforia

  • Покемон GO Style Дополненная реальность: маркеры

Мы начали разработку игры дополненной реальности под названием Shoot the Cubes. Теперь пришло время улучшить игру, добавив взаимодействие и сделав опыт более увлекательным. Мы сконцентрируемся в основном на возможностях, которые нам предоставляет Unity, оставляя в стороне специфику Vuforia. Опыт работы с движком Unity не обязателен.

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

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

Давайте отредактируем класс SpawnScript , добавив некоторые переменные для управления процессом порождения.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public class SpawnScript : MonoBehaviour {
    // Cube element to spawn
    public GameObject mCubeObj;
     
    // Qtd of Cubes to be Spawned
    public int mTotalCubes = 10;
 
    // Time to spawn the Cubes
    public float mTimeToSpawn = 1f;
 
    // hold all cubes on stage
    private GameObject[] mCubes;
 
    // define if position was set
    private bool mPositionSet;
}

Мы создадим сопрограмму под названием SpawnLoop для управления процессом появления. Он также будет ответственен за установку начальной позиции _SpawnController в начале игры. Обратите внимание, что метод Random.insideUnitSphere вызывает создание экземпляров кубов в случайных местах в сферической области вокруг _SpawnManager .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class SpawnScript : MonoBehaviour {
     
    // Loop Spawning cube elements
    private IEnumerator SpawnLoop()
    {
        // Defining the Spawning Position
        StartCoroutine( ChangePosition() );
 
        yield return new WaitForSeconds(0.2f);
 
        // Spawning the elements
        int i = 0;
        while ( i <= (mTotalCubes-1) ) {
 
            mCubes[i] = SpawnElement();
            i++;
            yield return new WaitForSeconds(Random.Range(mTimeToSpawn, mTimeToSpawn*3));
        }
    }
 
    // Spawn a cube
    private GameObject SpawnElement()
    {
        // spawn the element on a random position, inside a imaginary sphere
        GameObject cube = Instantiate(mCubeObj, (Random.insideUnitSphere*4) + transform.position, transform.rotation ) as GameObject;
        // define a random scale for the cube
        float scale = Random.Range(0.5f, 2f);
        // change the cube scale
        cube.transform.localScale = new Vector3( scale, scale, scale );
        return cube;
    }
}

Наконец, отредактируйте функцию Start() . Обязательно удалите StartCoroutine( ChangePosition() ); и замените его вызовом для запуска сопрограммы SpawnLoop .

01
02
03
04
05
06
07
08
09
10
public class SpawnScript : MonoBehaviour {
    void Start () {
        // Initializing spawning loop
        StartCoroutine( SpawnLoop() );
 
        // Initialize Cubes array according to
        // the desired quantity
        mCubes = new GameObject[ mTotalCubes ];
    }
}

Теперь вернувшись в Unity, вам нужно будет создать префаб куба, который будет создан сценарием.

  • Создайте папку с именем Prefabs в окне проекта .
  • Измените масштаб элемента куба по всем осям на 1 и измените вращение на 0 по всем осям.
  • Перетащите куб в папку Prefabs .
  • Удалите Куб из окна Иерархии .
  • Выберите _SpawnController и перетащите сборный куб из папки Prefabs в поле M Cube Obj , расположенное в области SpawnScript инспектора.

Наконец, удалите Сферу, расположенную внутри _SpawnManager .

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

Нам нужно добавить движение к этим кубам, чтобы сделать вещи более интересными. Давайте вращать кубики вокруг их осей и над ARCamera . Было бы также здорово добавить некоторый случайный фактор к движению куба, чтобы создать более органичное ощущение.

Перетащите сборный куб из папки « Сборные » в иерархию.

  • В папке Scripts создайте новый скрипт C # с именем CubeBehaviorScript .
  • Перетащите сценарий в сборный куб и откройте его для редактирования.

Каждый куб будет иметь случайные характеристики. Размер, скорость вращения и направление вращения будут определены случайным образом, используя некоторые ранее определенные ссылки. Давайте создадим некоторые переменные контроллера и инициализируем состояние куба.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using UnityEngine;
using System.Collections;
 
public class CubeBehaviorScript : MonoBehaviour {
    // Cube’s Max/Min scale
    public float mScaleMax = 2f;
    public float mScaleMin = 0.5f;
 
    // Orbit max Speed
    public float mOrbitMaxSpeed = 30f;
 
    // Orbit speed
    private float mOrbitSpeed;
 
    // Anchor point for the Cube to rotate around
    private Transform mOrbitAnchor;
 
    // Orbit direction
    private Vector3 mOrbitDirection;
 
    // Max Cube Scale
    private Vector3 mCubeMaxScale;
     
    // Growing Speed
    public float mGrowingSpeed = 10f;
    private bool mIsCubeScaled = false;
 
    void Start () {
        CubeSettings();
    }
 
    // Set initial cube settings
    private void CubeSettings(){
        // defining the anchor point as the main camera
        mOrbitAnchor = Camera.main.transform;
 
        // defining the orbit direction
        float x = Random.Range(-1f,1f);
        float y = Random.Range(-1f,1f);
        float z = Random.Range(-1f,1f);
        mOrbitDirection = new Vector3( x, y , z );
 
        // defining speed
        mOrbitSpeed = Random.Range( 5f, mOrbitMaxSpeed );
 
        // defining scale
        float scale = Random.Range(mScaleMin, mScaleMax);
        mCubeMaxScale = new Vector3( scale, scale, scale );
 
        // set cube scale to 0, to grow it lates
        transform.localScale = Vector3.zero;
    }
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
// Update is called once per frame
   void Update () {
       // makes the cube orbit and rotate
       RotateCube();
   }
    
   // Makes the cube rotate around a anchor point
   // and rotate around its own axis
   private void RotateCube(){
       // rotate cube around camera
       transform.RotateAround(
           mOrbitAnchor.position, mOrbitDirection, mOrbitSpeed * Time.deltaTime);
 
       // rotating around its axis
       transform.Rotate( mOrbitDirection * 30 * Time.deltaTime);
   }

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
// Update is called once per frame
   void Update () {
       // makes the cube orbit and rotate
       RotateCube();
 
       // scale cube if needed
       if ( !mIsCubeScaled )
           ScaleObj();
   }
    
   // Scale object from 0 to 1
   private void ScaleObj(){
 
       // growing obj
       if ( transform.localScale != mCubeMaxScale )
           transform.localScale = Vector3.Lerp( transform.localScale, mCubeMaxScale, Time.deltaTime * mGrowingSpeed );
       else
           mIsCubeScaled = true;
   }

Эти кубики слишком счастливы, что летают вот так. Мы должны уничтожить их лазерами! Давайте создадим оружие в нашей игре и начнем стрелять в них.

Лазерный выстрел должен быть подключен к ARCamera и его вращению. Каждый раз, когда игрок «стучит» по экрану устройства, будет выпущен лазер. Мы будем использовать класс Physics.Raycast чтобы проверить, попал ли лазер в цель, и если это так, мы удалим из него немного здоровья.

  • Создайте новый пустой объект с именем _PlayerController и еще один пустой объект с именем _LaserController внутри него.
  • Добавьте скрипт C # с именем LaserScript в папку Scripts и перетащите его в _LaserController .

Внутри LaserScript мы будем использовать LineRenderer для отображения лазерного луча, используя точку начала, соединенную с нижней частью ARCamera . Чтобы получить исходную точку лазерного луча — ствол виртуальной пушки — мы получим камеру Transform в момент, когда лазер выстрелит, и сдвинем его на 10 единиц вниз.

Сначала давайте создадим некоторые переменные для управления настройками лазера и получим mLaserLine .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using UnityEngine;
using System.Collections;
 
public class LaserScript : MonoBehaviour {
 
    public float mFireRate = .5f;
    public float mFireRange = 50f;
    public float mHitForce = 100f;
    public int mLaserDamage = 100;
 
    // Line render that will represent the Laser
    private LineRenderer mLaserLine;
 
    // Define if laser line is showing
    private bool mLaserLineEnabled;
 
    // Time that the Laser lines shows on screen
    private WaitForSeconds mLaserDuration = new WaitForSeconds(0.05f);
 
    // time of the until the next fire
    private float mNextFire;
     
    // Use this for initialization
    void Start () {
        // getting the Line Renderer
        mLaserLine = GetComponent<LineRenderer>();
    }
}

Функция, ответственная за стрельбу — это Fire() . Это будет вызываться каждый раз, когда игрок нажимает кнопку огня. Обратите внимание, что Camera.main.transform используется для определения положения и вращения ARCamera и что лазер расположен на 10 единиц ниже этого. Это позиционирует лазер в нижней части камеры.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
// Shot the Laser
    private void Fire(){
        // Get ARCamera Transform
        Transform cam = Camera.main.transform;
 
        // Define the time of the next fire
        mNextFire = Time.time + mFireRate;
 
        // Set the origin of the RayCast
        Vector3 rayOrigin = cam.position;
 
        // Set the origin position of the Laser Line
        // It will always 10 units down from the ARCamera
        // We adopted this logic for simplicity
        mLaserLine.SetPosition(0, transform.up * -10f );
}

Чтобы проверить, была ли цель поражена, мы будем использовать Raycast .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
// Shot the Laser
    private void Fire(){
        // Hold the Hit information
        RaycastHit hit;
 
        // Checks if the RayCast hit something
        if ( Physics.Raycast( rayOrigin, cam.forward, out hit, mFireRange )){
 
            // Set the end of the Laser Line to the object hit
            mLaserLine.SetPosition(1, hit.point );
 
        } else {
            // Set the enfo of the laser line to be forward the camera
            // using the Laser range
            mLaserLine.SetPosition(1, cam.forward * mFireRange );
        }
}

Наконец, пришло время проверить, была ли нажата кнопка огня, и вызвать лазерные эффекты при выстреле.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
// Update is called once per frame
void Update () {
    if ( Input.GetButton(«Fire1») && Time.time > mNextFire ){
        Fire();
        }
    }
 
private void Fire() {
    // anterior code supressed for simplicity
     
    // Show the Laser using a Coroutine
    StartCoroutine(LaserFx());
}
 
// Show the Laser Effects
private IEnumerator LaserFx(){
    mLaserLine.enabled = true;
 
    // Way for a specific time to remove the LineRenderer
    yield return mLaserDuration;
    mLaserLine.enabled = false;
}

Вернувшись в Unity, нам нужно добавить компонент LineRenderer в объект _LaserController .

  • Выберите _LaserController и в окне инспектора нажмите « Добавить компонент» . Назовите его « Line Renderer » и выберите новый компонент.
  • Создайте новую папку с именем Materials и создайте внутри нее новый материал, который называется Laser .
  • Выберите материал Laser и измените его на любой понравившийся вам цвет.
  • Выберите _LaserController и перетащите материал Laser в поле Материалы компонента LineRenderer .
  • Все еще в LineRenderer , в разделе « Параметры» установите « Начать с» на 1 и « Конец с» на 0 .

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

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

  • Перетащите сборный кубик из папки prefabs в любое место на сцене .
  • Выберите куб и выберите Добавить компонент в   окно инспектора . Назовите новый компонент » RigidBody » и выберите его.
  • Для компонента RigidBody установите для параметра Гравитация и Is Kinematic значение Выкл .
  • Примените эти изменения к сборному.

Теперь давайте отредактируем скрипт CubeBehavior чтобы создать функцию, отвечающую за нанесение урона кубу, и еще одну за его уничтожение, когда здоровье падает ниже 0.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class CubeBehaviorScript : MonoBehaviour {
    // Cube Health
    public int mCubeHealth = 100;
     
    // Define if the Cube is Alive
    private bool mIsAlive = true;
     
    // Cube got Hit
    // return ‘false’ when cube was destroyed
    public bool Hit( int hitDamage ){
        mCubeHealth -= hitDamage;
        if ( mCubeHealth >= 0 && mIsAlive ) {
            StartCoroutine( DestroyCube());
            return true;
        }
        return false;
    }
 
    // Destroy Cube
    private IEnumerator DestroyCube(){
        mIsAlive = false;
 
        // Make the cube desappear
        GetComponent<Renderer>().enabled = false;
 
        // we’ll wait some time before destroying the element
        // this is usefull when using some kind of effect
        // like a explosion sound effect.
        // in that case we could use the sound lenght as waiting time
        yield return new WaitForSeconds(0.5f);
        Destroy(gameObject);
    }
}

Хорошо, теперь куб может получить урон и быть уничтоженным. Давайте отредактируем LaserScript чтобы применить повреждение к кубу. Все, что нам нужно сделать, это изменить функцию Fire() для вызова метода CubeBehavior скрипта CubeBehavior .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class LaserScript : MonoBehaviour {
     
    // Shot the Laser
    private void Fire(){
        // code supressed for simplicity …
 
        // Checks if the RayCast hit something
        if ( Physics.Raycast( rayOrigin, cam.forward, out hit, mFireRange )){
 
            // Set the end of the Laser Line to the object hitted
            mLaserLine.SetPosition(1, hit.point );
 
            // Get the CubeBehavior script to apply damage to target
            CubeBehaviorScript cubeCtr = hit.collider.GetComponent<CubeBehaviorScript>();
            if ( cubeCtr != null ) {
                if ( hit.rigidbody != null ) {
                    // apply force to the target
                    hit.rigidbody.AddForce(-hit.normal*mHitForce);
                    // apply damage the target
                    cubeCtr.Hit( mLaserDamage );
                }
            }
    }
}

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

Мы создали интересный AR-эксперимент с использованием Vuforia на Unity, однако у нас еще есть много интересных функций, которые нужно охватить. Мы не видели ни одного из более сложных ресурсов Vuforia: Targets, Smart Terrain, Cloud и так далее. Следите за следующими уроками, в которых мы рассмотрим больше этих функций, всегда используя один и тот же пошаговый подход.

До скорого!