Аватар в мире гравитации

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

Как будет выглядеть результат глазами игрока:

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

Чтобы получить такой результат выполните следующие шаги: Добавьте к UI объект Image, установите свойство Color как белый цвет.

Переименуйте его как BorderImage, он послужит нам в качестве контура для миникарты. Установите его свойство Anchor как на картинке:

Это свойство указывает на то каким образом будет задаваться позиция, в данном случае позиция будет задаваться относительно правого нижнего угла. Куда бы не стал двигаться правый нижний угол родителя(в данном случае угол интерфейса) этот компонент будет сохранять расстояние указанное в свойствах Pos X Pos Y, установите эти свойства в 0. Это будет означать что опорная точка этого объекта будет точно в позиции левого нижнего угла. Сама опорная точка(Pivot point) задаётся чуть ниже:

Установите свойства Width и Height в 256, Pivot X в 1, Pivot Y в 0. Опорная точка выставляется относительно объекта, позиция (0;0) означает левый нижний угол объекта, а (1;1) правый верхний. Опорная точка работает точно так же как и в пакетах 3D моделирования, все трансформации производятся относительно этой точки, т.е. если мы будем вращать по оси Z, то картинка будет вращаться вокруг этой точки. Поправьте PosX и PosY, т.к. изменение точки опоры не меняет фактического положения объекта, а заново вычисляет PosX и PosY.

Теперь создайте потомком BorderImage объект типа RawImage и переименуйте его в MinimapRawImage.

Установите свойства объекта в соответствии с картинкой. Заметьте свойство Anchor теперь другое, этот вид Anchor позволяет задать расстояние до каждого края и сохранять его, т.е. если родитель будет менять свой размер, то этот потомок будет повторять все изменения с заданным отступом. И последним создайте потомком MinimapRawImage объект Text и задайте следующие свойства:

PosX и PosY не имеют значения, т.к. мы будем менять эти параметры кодом. После создания всех элементов интерфейса должно получится что-то такое:

Теперь, чтобы вывести картинку с камеры на миникарту надо в окне Project создать текстуру в которую будет осуществляться рендер, задайте её имя Minimap:

После создания надо указать камере, что именно в эту текстуру надо осуществлять ренедер:

И последний шаг в настройке ренедера миникарты, установите эту же текстуру в качестве картинки для MinimapRawImage.

Теперь давайте приступим к созданию корабля. Создайте в окне Hierarchy новый пустой объект и назовите его GiantShip, добавьте к нему кубик и назовите его Geometry. Так же потомком создайте новую камеру и ParticleSystem:

Переименуйте систему частиц в Smoke. И задайте свойства в соответствии со скриншотом:

Кубик геометрии можете настроить по своему вкусу чтобы он был примерно в два раза меньше земли. Учтите, что коллайдеры потомков GiantShip будут участвовать в коллизиях. Добавьте так же к GiantShip компонент Rigidbody, не забудьте снять галочку Use gravity. Настройка сцены закончена. Чтобы всё заработало надо создать скрипт и изменить пару старых.

using UnityEngine; using System.Collections; using UnityEngine.UI; [RequireComponent(typeof(Camera))] public class DrawHighlight : MonoBehaviour { public Transform highlightTarget; public Material material; // Добавлена ссылка на текстовый объект, // с надписью "Земля" public Text earthText; public float marginSize = 0.05f; public float underlineSize = 0.20f; public int labelHeight = 20; private new Camera camera; void Start () { camera = GetComponent<Camera>(); } void OnPostRender() { if (!material) { Debug.LogError("Please Assign a material on the inspector"); return; } GL.PushMatrix(); material.SetPass(0); GL.LoadOrtho(); GL.Begin(GL.LINES); GL.Color(Color.red); Vector3 pos = camera.WorldToScreenPoint(highlightTarget.position); // Устанавливаем позицию текста в локальных координатах, корректируем // на половину ширины и высоты т.к. центр экрана это координата (0;0) earthText.transform.localPosition = new Vector3(pos.x - camera.pixelWidth/2 + marginSize * camera.pixelWidth, pos.y - camera.pixelHeight/2 + marginSize* camera.pixelHeight, 0); pos.x /= camera.pixelWidth; pos.y /= camera.pixelHeight; GL.Vertex3(pos.x, pos.y, 0); GL.Vertex3(pos.x + marginSize, pos.y + marginSize, 0); GL.Vertex3(pos.x + marginSize, pos.y + marginSize, 0); GL.Vertex3(pos.x + marginSize + underlineSize, pos.y + marginSize, 0); GL.End(); GL.PopMatrix(); } }

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

Заметьте, что в коде раньше было Screen.width, а теперь camera.pixelWidth, дело в том, что теперь эта камера осуществляет рендер не во весь экран, а только в размер рендер-текстуры. Соответственно надо и все значения изменить под этот нюанс.

using UnityEngine; using System.Collections; using System.Collections.Generic; [RequireComponent(typeof(Camera))] public class DrawPath : MonoBehaviour { public Transform target; public Material material; public int trailLength = 5000; private new Camera camera; private List<Vector3> vertices = new List<Vector3>(); void Start () { camera = GetComponent<Camera>(); } void Update () { vertices.Add(target.position); if (vertices.Count > trailLength) vertices.RemoveAt(0); } void OnPostRender() { if (!material) { Debug.LogError("Please Assign a material on the inspector"); return; } GL.PushMatrix(); material.SetPass(0); GL.LoadOrtho(); GL.Begin(GL.LINES); GL.Color(Color.red); for (int i = 1; i < vertices.Count; i++) { Vector3 pos = camera.WorldToScreenPoint(vertices[i - 1]); pos.x /= camera.pixelWidth; pos.y /= camera.pixelHeight; GL.Vertex3(pos.x, pos.y, 0); pos = camera.WorldToScreenPoint(vertices[i]); pos.x /= camera.pixelWidth; pos.y /= camera.pixelHeight; GL.Vertex3(pos.x, pos.y, 0); } GL.End(); GL.PopMatrix(); } }

Этот скрипт изменился только в тех местах, где раньше было Screen.width/Screen.height.

using UnityEngine; using System.Collections; public class GiantShipController : CelestialBody { protected float accel = 0; protected float steering = 0; protected float engineForceScale = 0.1f; protected float rotationForceScale = 0.01f; void Start () { } void Update() { if (Input.GetKey(KeyCode.A)) { steering = -1; } else if (Input.GetKey(KeyCode.D)) { steering = 1; } else { steering = 0; } if (Input.GetKey(KeyCode.W)) { accel = 1; } else { accel = 0; } } void FixedUpdate () { Rigidbody.AddForce(transform.forward * accel * engineForceScale, ForceMode.Impulse); Rigidbody.AddRelativeTorque(transform.up * steering * rotationForceScale, ForceMode.Impulse); } }

Этот скрипт отвечает за управление кораблём. Добавьте его как компонент к GiantShip. Заметьте, что данный класс унаследован от CelestialBody, что автоматически добавляет влияние гравитации на него. Ещё один интересный момент в том, что нажатие кнопок на клавиатуре не влияет напрямую на физическое тело корабля, а всё влияние осуществляется внутри FixedUpdate.

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

Скачать проект законченного урока.

results matching ""

    No results matching ""