Практическое использование движка Unity3d
Вращение, перемещение и масштабирование трёхмерного объекта
Содержание
- Введение
- Задача 1: вращение, перемещение и масштабирование объекта.
- Создание сцены
- Вращение, масштабирование и перемещение объекта
Введение
Здравствуйте! Это первая статья по работе с трёхмерной графикой в движке Unity3d. В своих статьях я постараюсь описать наиболее часто встречающиеся задачи разработки приложений в Unity3d и их решение.
Задача 1: вращение, перемещение и масштабирование объекта.
Теперь давайте рассмотрим задачу: необходимо продемонстрировать модель квартиры. Этот объект можно вращать, масштабировать и перемещать.
Создание сцены
Для начала создадим комнату, которая будет выглядеть схематично, из простых боксов, создаваемых в Unity3d.
Первым делом необходимо создать пустую сцену. Для этого выберем пункт меню “File/New Scene” как показано на рис.1.1.
Рисунок 1.1. – Создание новой сцены
В результате получим пустую сцену, изображенную на рис. 1.2.
Рисунок 1.2. – Пустая сцена в Unity3d
Теперь создадим пол квартиры, для этого выберем пункт меню“GameObject/CreateOther/Cube” (рис.1.3).
Рисунок 1.3. – Как создать куб в Unity3d
С помощью масштабирования этого куба получим «пол» (рис. 1.4).
Рисунок 1.4. – Созданный «пол» комнаты
Изменим названия «пола» в окне «Hierarchy». Назовём его «Floor». Процесс изменения объекта показан на рис. 1.5.
Рисунок 1.5. – Процесс изменения названия объекта.
Теперь с помощью таких же кубов, создадим «комнаты». Также в сцену добавим источник света. После этого, добавим в сцену пустой объект – «квартира». Пол, комнаты и источник света сделаем дочерними по отношению к квартире. В результате получим сцену, показанную на рис. 1.6.
Рисунок 1.6. – Сцена, содержащая в себе 3 комнаты, пол, источник света и камеру Прошу заметить, что комнаты являются дочерними к квартире. Это хорошо видно в иерархии объектов представленной на рис. 1.6.
Вращение, масштабирование и перемещение объекта
На данный момент в интернете имеется масса готовых скриптов для того, чтобы обеспечить перемещение, вращение и масштабирование объекта. Для того, чтобы однозначно понимать как происходит вращение объекта, предлагаю использовать готовый скрипт (TransformObject.cs). Этот скрипт хорошо прокомментирован.
Обычно в интернете Вы можете встретить набор скриптов, которые можно в редакторе просто повесить на объект, и далее он будет работать параллельно с основным набором скриптов. Редактор Unity3d позволяет назначать на несколько объектов скрипты, которые будут работать параллельно. Но я сторонник создания приложения, в основе которого есть только одна входная точка. Как использовать возможность назначения в редакторе дополнительных скриптов на объект, я расскажу в следующих статьях.
И так, алгоритм использования скрипта TransformObject: Создадим класс «AppRoot». AppRoot – это входная точка нашего приложения. В AppRoot создадим экземпляр класса TransformObject, и в методе «AppRoot.Start» инициализируем его. Далее установим объект, вокруг которого будет вращаться камера с помощью метода «SetTransformRotateAround».
using UnityEngine; using System.Collections; public class AppRoot : MonoBehaviour { private TransformObject mTransform; // TransformObject implements rotate / pan / zoom private GameObject mGOFlat; // GO rotate around private const string cGONameFlat = "Flat"; void Start() { // Find cGONameFlat in scene mGOFlat = GameObject.Find(cGONameFlat); // instantiate TransformObject and sets its rotate around object mTransform = new TransformObject(); mTransform.SetTransformRotateAround(mGOFlat.transform); } void Update() { mTransform.Update(); } }Я также рекомендую использовать класс «TransformObject», т.к. в дальнейшем мы будем его расширять, например, для использования в устройствах Android и iOS.
Приведем исходный код класса «TransformObject»:
using UnityEngine; using System; public class TransformObject { /////////////////////////////////////////////////////////////////////////// #region Variables // variables #if UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_WEBPLAYER private float RotationSpeed = 1500; private float MoveSpeed = 10.0f; private float ZoomSpeed = 15.3f; #endif // UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_WEBPLAYER #if UNITY_ANDROID || UNITY_IPHONE private float RotationSpeed = 9.5f; private float MoveSpeed = 1.09f; private float ZoomSpeed = 0.009f; private float mOldFingerDelta = 0; private const float mFingerDistanceEpsilon = 1.0f; #endif // UNITY_ANDROID || UNITY_IPHONE public float MinDist = 2.0f; public float MaxDist = 50.0f; private Transform mMoveObject = null; #endregion /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// #region Public methods ///А теперь рассмотрим основные моменты реализации класса «TransformObject»./// /// public TransformObject() { EnabledMoving = true; } ////// Sets transform that will be used as "center" of the rotate / pan / zoom /// public void SetTransformRotateAround(Transform goMove) { mMoveObject = goMove; if (mMoveObject == null) { Debug.LogWarning("Error! Cannot find object!"); return; } } public void Update() { if (!EnabledMoving) { return; } Vector3 dir = mMoveObject.position - Camera.main.transform.position; float dist = Math.Abs(dir.magnitude); Vector3 camDir = Camera.main.transform.forward; Vector3 camLeft = Vector3.Cross(camDir, Vector3.down); Vector3 camDown = Vector3.Cross(camDir, camLeft); //Vector3 camUp = Vector3.Cross(camLeft, camDir); #if UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_WEBPLAYER float dx = Input.GetAxis("Mouse X"); float dy = Input.GetAxis("Mouse Y"); // rotate if (Input.GetMouseButton(0)) { mMoveObject.Rotate(camLeft, dy * RotationSpeed * Time.deltaTime, Space.World); mMoveObject.Rotate(Vector3.down, dx * RotationSpeed * Time.deltaTime, Space.Self); } // move if (Input.GetMouseButton(1)) { Vector3 camPos = Camera.main.transform.position; camPos += -camLeft * MoveSpeed * dx * Time.deltaTime; camPos += -camDown * MoveSpeed * dy * Time.deltaTime; Camera.main.transform.position = camPos; } // zoom if (Input.GetAxis("Mouse ScrollWheel") > 0) { if (dist > MinDist) { mMoveObject.Translate(-dir * ZoomSpeed * Time.deltaTime, Space.World); } } if (Input.GetAxis("Mouse ScrollWheel") < 0) { if (dist < MaxDist) { mMoveObject.Translate(dir * ZoomSpeed * Time.deltaTime, Space.World); } } #endif // UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_WEBPLAYER #if UNITY_ANDROID || UNITY_IPHONE // rotate if (Input.touchCount == 1) { mMoveObject.Rotate(camLeft, Input.touches[0].deltaPosition.y * RotationSpeed * Time.deltaTime, Space.World); mMoveObject.Rotate(Vector3.down, Input.touches[0].deltaPosition.x * RotationSpeed * Time.deltaTime, Space.Self); } if (Input.touchCount == 2) { UnityEngine.Vector2 deltaFingerVec = Input.touches[0].position - Input.touches[1].position; float deltaFingerValue = Mathf.Sqrt(deltaFingerVec.x * deltaFingerVec.x + deltaFingerVec.y * deltaFingerVec.y); // move bool moved = false; { float moveX = 0; float moveY = 0; // moveX if (Input.touches[0].deltaPosition.x < 0 && Input.touches[1].deltaPosition.x < 0) { moveX = Mathf.Max(Input.touches[0].deltaPosition.x, Input.touches[1].deltaPosition.x); moved = true; } else if (Input.touches[0].deltaPosition.x > 0 && Input.touches[1].deltaPosition.x > 0) { moveX = Mathf.Min(Input.touches[0].deltaPosition.x, Input.touches[1].deltaPosition.x); moved = true; } // moveY if (Input.touches[0].deltaPosition.y < 0 && Input.touches[1].deltaPosition.y < 0) { moveY = Mathf.Max(Input.touches[0].deltaPosition.y, Input.touches[1].deltaPosition.y); moved = true; } else if (Input.touches[0].deltaPosition.y > 0 && Input.touches[1].deltaPosition.y > 0) { moveY = Mathf.Min(Input.touches[0].deltaPosition.y, Input.touches[1].deltaPosition.y); moved = true; } Vector3 camPos = Camera.main.transform.position; camPos += -camLeft * MoveSpeed * moveX * Time.deltaTime; camPos += -camDown * MoveSpeed * moveY * Time.deltaTime; Camera.main.transform.position = camPos; } // zoom if (!moved && Mathf.Abs(deltaFingerValue - mOldFingerDelta) > mFingerDistanceEpsilon) { if (deltaFingerValue - mOldFingerDelta > 0) { if (dist > MinDist) { mMoveObject.transform.Translate(-dir * ZoomSpeed * Time.deltaTime * deltaFingerValue, Space.World); } } if (deltaFingerValue - mOldFingerDelta < 0) { if (dist < MaxDist) { mMoveObject.transform.Translate(dir * ZoomSpeed * Time.deltaTime * deltaFingerValue, Space.World); } } } mOldFingerDelta = deltaFingerValue; } #endif // UNITY_ANDROID || UNITY_IPHONE } #endregion /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// #region Properties ////// Gets or set value indicating if transformation is enabled /// public bool EnabledMoving { get; set; } ////// Gets game object that moves around /// public Transform MoveObject { get { return mMoveObject; } } #endregion /////////////////////////////////////////////////////////////////////////// }
Флаг «EnabledMoving» определяет выполнятся ли вращение / перемещение / масштабирование. Использовать метод «SetTransformRotateAround» необходимо, чтобы установить объект, вокруг которого будет происходит вращение. В случае если пользователь передал нулевой объект, Unity3d оповестит пользователя об этом в «логе». Пользователю класса также необходимо вызвать метод «Update». Именно в этом методе будут происходить все манипуляции по перемещение / вращение / масштабированию. Ключевые моменты функции «Update»:
Вектор направление камеры.
Vector3 camDir = Camera.main.transform.forward;
Вектор, который указывает направление «влево» относительно направления камеры.
Vector3 camLeft = Vector3.Cross(camDir, Vector3.down);
Вектор, которые указывает направление «вниз» относительно направления камеры.
Vector3 camDown = Vector3.Cross(camDir, camLeft);
Расстояние пройденное мышкой за кадр по осям:
float dx = Input.GetAxis("Mouse X"); float dy = Input.GetAxis("Mouse Y");
Вращение выполняется по левой клавише мыши:
if (Input.GetMouseButton(0))Рассмотрим вращение объекта. При изменении координаты мыши по оси Y, будет осуществляться вращение вокруг оси «влево камеры». Угол вычисляем как расстояние пройденное мышкой по оси Y (Input.GetAxis("Mouse Y")) умноженное на скорость вращения. В качестве пространства, в котором будет происходить вращение, указываем мировое.
mMoveObject.Rotate(camLeft, dy * RotationSpeed * Time.deltaTime, Space.World);
Аналогично вращаем объект вокруг оси «вниз». Только теперь угол вычисляем как расстояние пройденное мышкой по оси X (Input.GetAxis("Mouse Х")) умноженное на скорость вращения.
mMoveObject.Rotate(Vector3.down, dx * RotationSpeed * Time.deltaTime, Space.Self);Хочу заметить, что во всех подобных вычислениях обязательно необходимо учитывать время, пройденное с прошедшего кадра (Time.deltaTime), иначе скорость вращения будет зависеть от производительности системы.
Если вращение мы реализовывали по левой клавише мыши, то перемещение объекта будем реализовывать по правой клавише мыши.
if (Input.GetMouseButton(1))На самом деле мы будем перемещать не объект, а камеру, которая смотрит на объект. И так, мы вычисляем вектор движения камеры при перемещении мыши по оси Х, и по оси У. Далее увеличиваем размер вычисленных векторов, и просто прибавляем эти вектора к позиции камеры.
Vector3 camPos = Camera.main.transform.position; camPos += -camLeft * MoveSpeed * dx * Time.deltaTime; camPos += -camDown * MoveSpeed * dy * Time.deltaTime; Camera.main.transform.position = camPos;
Масштабирование будем выполнять при вращении колесика мыши:
Input.GetAxis("Mouse ScrollWheel")А для выполнения перемещения объекта будем использовать метод «Transform.Translate». В качестве параметра мы вычислим вектор движения и его размер. Все остальное метод сделает за нас.
mMoveObject.Translate(-dir * ZoomSpeed * Time.deltaTime, Space.World);
В итоге у нас получилась сцена, в которой есть объект. С помощью мыши мы можем вращать, перемещать и масштабировать этот объект.
2013.09.08 Обновил статью. Теперь скрипт поддерживает вращение, перемещение и масштабирование объекта для мобильных устройств..
Посмотреть результаты работы можно посмотреть здесь:
Добрый день, а как это реализовать для мобильных платформ Android и IOS&
ReplyDeleteОбновил статью и исходники
DeleteЗдравствуйте! Я подготовлю скрипт для Вас, и выложу его в течении недели-двух.
ReplyDeleteПривет! Отличный урок! как раз то что искал, спасибо!) Но у меня почему то перестает работать вращение мышью после компиляции во flash :(, а для этого как раз и искал. Ты случайно не сталкивался с такой проблемой? Подскажи пожалуйста если можешь в скайп: viteggg
ReplyDeleteПривет, Vizer!
DeleteСпасибо :) Скрипт должен работать для Android, iOS, Standalone. Я отредактирую скрипт для тебя, чтоб он работал для флеша и перезалью его.
сделайте ссылки активными, так будет намного удобнее)
ReplyDeleteDone :)
DeleteАвтор огромное спасибо))
ReplyDeleteРад что помогло :)
Delete