5. 입력 처리(** Ray.. RayCast ...)

2013. 7. 9. 00:37게임 개발/Unity

 

http://www.raywenderlich.com/ko/26705/%EC%9C%A0%EB%8B%88%ED%8B%B0%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-2-5d-%EA%B2%8C%EC%9E%84%EC%9D%84-%EB%A7%8C%EB%93%9C%EB%8A%94-%EB%B0%A9%EB%B2%95-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-2

 

 

 유니티에서는 기본적으로 마우스나 키보드와 같은 입력 처리를 하는 다양한 API를 제공하고 있습니다. 그 외에도 모바일 디바이스로도 유니티로 작성한 게임들을 포팅 할 수 있습니다. 모바일 디바이스 같은 경우에는 마우스나 키보드 같은 입력장치를 사용하지 않고 터치스크린을 이용해서 입력을 받게 되는데요 터치 스크린을 위한 입력들도 유니티에서 제공하고 있습니다.

 

** Ray.. RayCast ...

RayCast 3차원 공간에서 어느 한 점(시작점)에서 Ray 를 정해진 방향(direction vector)으로 쏴
Ray
와 충돌되는 객체를 구하는 방법이다.
Unity3D
에서는 Ray Cast 를 위한 몇가지 유용한 클래스 와 struct, method 등을 제공함.

Ray : Ray Cast
를 위한 재료 중 가장 중요한 Ray의 정보를 담는다.
origin : Ray
가 시작되는 지점.
direction : Ray
가 시작 되는 지점에서 쏘여지는 방향 설정

Physics.Raycast(Physics class , Raycast Method) : Ray Cast
를 실행하여 Ray 와 객체가 충돌하는지 체크하는 메소드 객체와 충돌 하는 경우 true 반환

 

RayCastHit(Struct) : Physics.Raycast 메서드의 파라미터로 값을 할당(out) 하여 Ray 에 충돌 된 객체의 정보 담음.

 

1.     Ray 객체 생성
Ray ray = MainCamera.ScreenPointToRay(Input.mousePosition);

ScreenPointToRay
메서드는 Screen 상에서의 좌표를 World space 상에서의 좌표로 변환하여 시작점으로 설정하고, direction을 카메라가 비추는 방향으로 설정 된 Ray 객체를 반환.

2.     Ray Cast 실행
RaycastHit hitObj;
if(Physucs.Raycast(ray, out hitObj , Mathf.infinity)){

}
physics.Raycast
메서드를 실행하면, Ray 객체의 origin 에서 direction 으로 ray를 쏴 준다.
그리고 충돌 되는 객체가 있으면 true 반환, RaycastHit 객체를 out 키워드를 통해 파라미터로 던져 주면 충돌 된 객체의 정보를 담아서 반환 해 준다.
메서드 실행 시 Ray 길이를 float 값으로 받는데, 이 값은 ray가 얼마만큼 쏴 지도록 설정하는 것, Mathf.Infinity를 할당 하면 무한대로 쏴서 체크함.

 

 

 

1.     Mouse Input 정리

 

A.     Click Movement Handler


Start
함수상에서는 대부분 해당 객체에 대한 컴포넌트가 잘 붙어 있는지 확인 해주는 역할을 하고, LateUpdate 함수(매 프레임 마다 마지막으로 호출되는 함수) 상에서는 주로 input 처리를 한다고 합니다. 입력에 대한 처리는 클릭한 지점을 알 수 있는 방법으로 임의 광선을 쏴서 해당 위치를 확인 할 수 있다
.

// Click MovementHandler Source
public class ClickMovementHandler : MonoBehaviour
{
 //assign a player character gameobject.
public GameObject avatar = null;

//the assigned player character should have MovementController component.
MovementController moveControl = null;

//use this for initialization
void Start()
{
 if(avatar)
 {
  moveControl = avatar.gameObject.GetComponent<MovementController>();
  if(moveControl == null)
  {
    Debug.LogError(“The gameobject does not have MovementController.”);
  }
}
else
{
  Debug.LogError(“”);
}
}

//LateUpdate is called every frame after all Update functions have been called.
void LateUpdate()
{
  InputController.Update();

  //Mouse L-Button is down.
  if(InputController.isLButtonDown)
  {
    //calculate the position at the mouse is clicked.
    var ray = Camera.main.ScreenPointToRay(InputController.Position);
    InputController.CalculateHitPoint(ray);

   if(avatar)
   {
     //move to the given position
     moveControl.StartMove(InputController.HitPoint , 1f);
   }
   InputController.InvalidateLButtonDown();
  }

}
}

B.     InputController Class 알아보기

좌우측 마우스 버튼 클릭 여부에 대한 변수와 더불어 클릭한 위치 등을 포함 하고 있으며,
특이하게 MonoBehaviour 를 상속 받지 않은 클래스 입니다
.
여기서 광선에 대한 부분이 나오는데, Ray , RaycastHit , Physics.Raycast , ignoreMask 등 생소한 클래스들이 나오고 있습니다
.

// The class which handles inputs.
public static class InputController
{
    public static bool IsLButtonDown { get; private set; }
    public static bool IsRButtonDown { get; private set; }

    public static Vector2 Position { get; private set; }
    public static Vector3 HitPoint { get; private set; }

    private static Vector2? ClickedPosition;
    private static bool previousLButtonDown;
    private static bool previousRButtonDown;

           static InputController()
           {
                     // center position on the screen.
                     Position = new Vector2(Screen.width, Screen.height) * 0.5f;
           }
           public static void InvalidateLButtonDown()
           {
                     IsLButtonDown = false;
           }
           public static void InvalidateRButtonDown()
           {
                     IsRButtonDown = false;
           }
           private static void Drag()
           {
                     ;
           }
   
        // Calcuate hit point from the given ray.
           public static void CalculateHitPoint(Ray ray)
           {
                     RaycastHit hit;              
                     HitPoint = Vector3.zero;
                     var ignoreMask = 0x00000004;
                     if (Physics.Raycast(ray, out hit, float.MaxValue, ~(ignoreMask)))
                  {
                                HitPoint = hit.point;
                                //Debug.Log ("HitPoint:" + HitPoint.ToString());
                     }
                     else
                     {
                                float d;
                                Plane p = new Plane(Vector3.up, 0);

                                if (p.Raycast(ray, out d))
                                          HitPoint = ray.origin + ray.direction * d;
                     }
           }
      
        // This should be called within any update() funtion.
           public static void Update()
           {
                     Position = Input.mousePosition;
                     IsLButtonDown = false;

                     // left mouse down.
                     if (Input.GetMouseButton(0))
                     {
                                if (ClickedPosition == null)
                                {
                                          ClickedPosition = Position;
                                }
                                else
                                {
// mouse l-button is down on the previous update then move cursor now
// which means it starts to drag.
                            Drag();
                                }
                     }
                     else
                     {
// mouse l-button is down on the previous update and does not move the cursor
// so ClickedPoisition is not nuill
                                if (ClickedPosition != null)
                                {
                                          // mouse l-button is down.
                            IsLButtonDown = true;
                                }
                                ClickedPosition = null;
                     }

                     IsRButtonDown = Input.GetMouseButton(1) && !previousRButtonDown;
                previousLButtonDown = Input.GetMouseButton(0);
                     previousRButtonDown = Input.GetMouseButton(1);
           }
}

C.     MovementController 클래스 알아보기

NavMeshAgent
에 대해서도 좀 확인이 필요하다.
Animation
상에서는 WrapMode 를 설정하는 부분도 확인이 필요하다
.
그전에 나도 잘 모르고 넘어갔던 부분인데, animation.CrossFade 라는 부분인데, 1,2번 애니메이션이 있을 경우에 그 애니메이션의 연결 부분을 자연스럽게 해주는 효과.



// Handles NavMeshAgent's movement.
public class MovementController : MonoBehaviour
{
           private Transform tm = null;
           private NavMeshAgent agent = null;
           public Vector3? TargetPosition { get; private set; }
           public Vector3 Velocity { get { return agent.velocity; } }
           private float stoppingDistanceSquared = 0.01f; // = 0.1 * 0.1
           private Animation animation = null;
           private string idleAnimName = "idle";
           private string walkAnimName = "walk";    
           // Use this for initialization
           void Start ()
           {
                     this.tm = this.gameObject.GetComponent<Transform>();
                     agent = this.gameObject.GetComponent<NavMeshAgent>();
                     if (agent == null)
                     {
                                Debug.Log ("Failed to get NavMeshAgent component.");
                     }
                     this.animation = this.gameObject.GetComponent<Animation>();
                     if (this.animation != null)
                     {
                                this.animation[idleAnimName].wrapMode = WrapMode.Loop;
                                this.animation[walkAnimName].wrapMode = WrapMode.Loop;
                                this.animation.CrossFade(idleAnimName);
                     }
           }
           // Stop movement if the player is whithin the stoppingDistanceSquared
           void Update()
           {
                     // more fast than using Vector3.Distance()
                     // See the following page for more details:
//
http://docs.unity3d.com/Documentation/Manual/DirectionDistanceFromOneObjectToAnother.html
                     // var dist = this.agent.destination - this.tm.position;
                     // if (dist.sqrMagnitude < stoppingDistanceSquared)
                if(Vector3.Distance(agent.destination, this.tm.position < 0.1f)
                     {
                                this.StopMove();
                     }

           }
        // Start to move the agent
           public void StartMove(Vector3 position, float stopDistance)
           {
                     this.animation.CrossFade(walkAnimName, 0.2f, PlayMode.StopAll);
                     TargetPosition = position;
                     agent.SetDestination(TargetPosition.Value);
           }

           // Stop movement of the agent.
           public void StopMove()
           {
                     this.animation.CrossFade(idleAnimName, 0.1f, PlayMode.StopAll);
                     agent.Stop();
           }
}

2.     활용 팁

A.     MonoBehaviour 를 상속 받는 클래스의 함수들 중에서 게임 루프에서 반복해서 호출되는 함수들은 FixedUpdate, Update, lateUpdate 함수 들이다.

B.     일반적인 게임의 로직에 대한 처리는 Update 함수 내에서 처리하고, 마우스 버튼을 클릭했는지의 여부나 특정 키가 눌러졌는지와 같은 입력에 대한 판단 여부는 LateUpdate 내에서 하도록 구분하는 것도 좋은 방법이다.

C.     LateUpdate 함수는 업데이트 루프 내의 마지막 프레임에 호출되는 함수 이므로, LateUpdate 에서 입력과 관련된 처리가 변경되면 이 변경과 관련된 게임의 로직 처리는 다음 프레임 Update 함수를 통해 갱신되어 나타난다.