Sunday, February 17, 2013

Задача 1: Вращение, перемещение и масштабирование объекта.




Практическое использование движка 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

    /// 
    /// 
    /// 
    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
    ///////////////////////////////////////////////////////////////////////////
}

А теперь рассмотрим основные моменты реализации класса «TransformObject». 
Флаг «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 Обновил статью. Теперь скрипт поддерживает вращение, перемещение и масштабирование объекта для мобильных устройств.

Посмотреть результаты работы можно посмотреть здесь:

9 comments:

  1. Добрый день, а как это реализовать для мобильных платформ Android и IOS&

    ReplyDelete
  2. Здравствуйте! Я подготовлю скрипт для Вас, и выложу его в течении недели-двух.

    ReplyDelete
  3. Привет! Отличный урок! как раз то что искал, спасибо!) Но у меня почему то перестает работать вращение мышью после компиляции во flash :(, а для этого как раз и искал. Ты случайно не сталкивался с такой проблемой? Подскажи пожалуйста если можешь в скайп: viteggg

    ReplyDelete
    Replies
    1. Привет, Vizer!
      Спасибо :) Скрипт должен работать для Android, iOS, Standalone. Я отредактирую скрипт для тебя, чтоб он работал для флеша и перезалью его.


      Delete
  4. сделайте ссылки активными, так будет намного удобнее)

    ReplyDelete
  5. Автор огромное спасибо))

    ReplyDelete