코루틴은 Unity에서 비동기적으로 실행할 수 있는 메서드로, 게임 로직에서 특정 동작을 순차적으로 실행하거나 시간 지연을 두고 실행할 때 사용됩니다. 예를 들어, 3, 2, 1, START! 같은 게임 시작 전 알림에 사용할 수 있습니다. 주요 특징을 알아보겠습니다.
- 비동기 실행: 코루틴은 메인 게임 루프와 독립적 실행으로 특정 조건이 충족될 때까지 기다리거나 지연할 수 있습니다.
- IEnumerator 반환: 코루틴은 IEnumerator 타입을 반환하는 메서드로 정의됩니다. yield return문을 사용하여 실행을 일시 중지할 수 있습니다.
- 시간 지연: yield return new WitForSeconds(seconds); 등을 통해 특정 시간 실행 지연 후 다음 코드를 진행할 수 있습니다.
- 조건에 따른 실행: yield return null을 사용하면 다음 프레임까지 실행 중단하고, 특정 조건 충족까지 기다릴 수 있습니다.
- 여러 코루틴 실행: 복수 코루틴을 동시 실행 가능하며, 각 코루틴은 독립적으로 작동합니다. 이를 통해 복잡한 로직을 간단하게 구현할 수 있습니다.
StartCoroutine(코루틴명()); 이를 사용하면 해당 코루틴을 호출할 수 있습니다. 코루틴은 해당 스크립트를 보유한 오브젝트가 SetActive값이 false가 되면 그 때 이후로 해당 스크립트가 발동하지 않습니다. 이러한 코루틴은 스타크래프트, 롤의 피킹 시스템을 구현할 수 있습니다. 세 개의 스크립트를 해석하며 알아보기 전에 특정 코드들을 먼저 다뤄보겠습니다.
- magnitude: 벡터의 크기를 나타냅니다. 2D 또는 3D 벡터의 길이를 계산할 때 사용하며, 일반적으로 유클리드 거리로 측정됩니다.
- Atan2 (아크 탄젠트): 두 점 간의 각도를 계산하는 함수입니다. 주로 직각 삼각형의 두 변의 길이를 이용해 해당 각도를 반환합니다.
- Mathf.PI: 수학적 개념 중 원주율(π)의 값으로, 약 3.14159입니다. 수학적 계산에서 원과 관련된 여러 함수에 사용됩니다.
- Rad2Deg: 라디안을 도(degree)로 변환하는 상수입니다. 1 라디안은 약 57.2958.....도입니다.
- Quaternion: 3D 회전을 나타내기 위한 수학적 구조입니다. 오일러 각과는 다르게, 기하학적 회전을 표현할 때 기울어짐, 꼬임, 뒤틀림 등을 효과적으로 처리할 수 있습니다. 유니티에선 부드러운 회전에 사용됩니다.
- Euler: 오일러 각을 사용하여 회전을 표현하는 방식으로, 3개의 축(X, Y, Z)에 대해 순차적으로 회전하는 각도를 나타냅니다. 유니티에서 Quaternion과 같이 사용합니다.
- Input.GetMouseButton(): 마우스 클릭의 뜻으로 0이면 좌클릭, 1이면 우클릭을 뜻합니다.
이제 위의 2가지 Picking을 만들어보겠습니다. 참고로 충돌감지를 사용하지않고 수학적 방식으로 접근하겠습니다. 때문에 오브젝트들에 Collider, Rigidbody를 추가하지 않아도 됩니다.
1번 상황. Shift키를 누른채로 바닥을 좌클릭하면 한 만큼 여러 개의 깃발이 꼽히고 첫 번째 피킹한 깃발은 빨간색으로 표시됩니다. 유닛은 빨간색 깃발로 이동하고 닿일 시 해당 깃발은 사라지고 다음 향할 깃발의 색이 빨간색으로 바뀌고 그 쪽으로 향해 갑니다.
2번 상황. 바닥을 클릭 시 빨간색 깃발이 표시되고 다른 곳을 클릭하면 해당 깃발은 사라지고 유닛이 바뀐 깃발로 경로 변경을 합니다.
스크립트 순서는 Waypoint, Unit, WaypointManager 순서로 진행합니다. 깃발은 Prefabs를 통해 오브젝트를 설정하면 됩니다.
using UnityEngine;
public class CoWaypoint : MonoBehaviour
{
private bool isSelected = false;
public Vector3 Position
{
get { return transform.position; }
set { transform.position = value; }
}
public void Selection()
{
if (isSelected) return;
MeshRenderer[] mrs = GetComponentsInChildren<MeshRenderer>();
mrs[1].material.color = Color.red;
isSelected = true;
}
}
Waypoint 스크립트는 Prefabs에 넣어놓은 깃발 오브젝트에 넣어놓습니다.
- isSelected: 깃발 오브젝트가 처음엔 존재하지 않기 때문에 false로 시작합니다.
- Position: get에는 생성된 곳의 현재 위치를 반환합니다. set은 다른 스크립트에서 해당 Position을 다뤄 위치를 변경할 수 있습니다.
- Selection: 조건문을 통해 웨이포인트가 이미 선택된 경우 메서드를 종료합니다. 중복 선택을 방지하기 위한 조건문입니다.
- MeshRenderer[ ] mrs을 통해 자식 오브젝트들의 MeshRenderer 컴포넌트를 가져옵니다. 그리고 mrs[1].material.color로 자식 오브젝트 중 2번째 것의 material 색을 빨간색으로 바꿔줍니다. 그리고 waypoint가 선택됐다(true)는 것입니다.
위의 스크립트는 Prefabs에서 해당 깃발 오브젝트가 생성된 후의 기능을 다루고 있습니다.
using UnityEngine;
public class CoUnit : MonoBehaviour
{
private float MoveSpeed = 10f;
private float finishDist = 0.1f;
public bool MoveToWaypoint(CoWaypoint _waypoint)
{
_waypoint.Selection();
RotateToWaypoint(_waypoint);
//Moving
Vector3 dir = (_waypoint.Position - transform.position).normalized;
transform.position = transform.position + dir * MoveSpeed * Time.deltaTime;
//Vector3,Distance
//sqrt(x^2 * y^2 * z^2)
if ((_waypoint.Position - transform.position).magnitude < finishDist)
{
return false;
}
return true;
}
private void RotateToWaypoint(CoWaypoint _waypoint)
{
Vector3 dir = (_waypoint.Position - transform.position).normalize;
float thetaRad = Mathf.Atan2(dir.z, dir.x) - (Mathf.PI * 0.5f); //세타, 아크탄젠트
float thetaDeg = thetaRad * Mathf.Rad2Deg; //라디안을 Degree로 변환
transform.rotation = Quaternion.Euler(0f, -thetaDeg, 0f);
}
}
- MoveSpeed: 유닛이 움직일 속도
- finishDist: 오브젝트와 닿인걸 확인할 수 있는 거리
- MoveToWaypoint는 위의 CoWaypoint를 _waypoint로 변수를 가진 메서드로 Selection() 메서드를 사용합니다. RotateToWaypoint( ) 메서드를 사용합니다. dir은 waypoint의 위치 벡터값을 유닛의 위치 벡터값을 뺀 것에 정규화시켜주어 방향의 기능만을 가집니다. 그리고 유닛의 위치는 현재 위치 + 방향 * 움직임 속도 * Time.deltaTime으로 이동합니다. 그리고 waypoint의 위치와 유닛의 위치 간의 거리의 벡터의 크기가 finishDist보다 낮을 경우 해당 메서드를 종료시킵니다. 이외는 true로 메서드를 발동시킵니다.
- RotateToWaypoint는 유닛이 깃발을 향할 때 그에 따라 회전하기 위한 메서드입니다. 방향 벡터 dir을 이용하여 각도를 구합니다. Mathf.PI * 0.5f는 y축 각도를 빼는 함수입니다. 이번 피킹 기능에 y축은 사용하지 않을 것이기 때문입니다. 그리고 thetaDeg에서 돌 각도를 구합니다. 이후 transform.rotation으로 유닛이 깃발 위치에 따라 회전하도록 설정합니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WaypointManager : MonoBehaviour
{
[SerializeField] private GameObject waypointPrefab = null;
[SerializeField] private CoUnit unit = null;
[SerializeField] private LayerMask layerMask;
private List<CoWaypoint> waypointList = null;
private void Awake()
{
waypointList = new List<CoWaypoint>();
}
private void Start()
{
StartCoroutine(UpdateCoroutine());
}
private IEnumerator UpdateCoroutine()
{
Vector3 pickPoint = Vector3.zero;
while (true)
{
if (Input.GetMouseButtonDown(0) &&
PickingToPoint(ref pickPoint))
{
if (Input.GetKey(KeyCode.LeftShift))
{
SpawnWaypoint(pickPoint);
}
else
{
if (waypointList.Count > 1)
DestroyWaypointAll();
if (waypointList.Count == 0)
SpawnWaypoint(pickPoint);
else
waypointList[0].Position = pickPoint;
}
}
if (waypointList.Count > 0 &&
!unit.MoveToWaypoint(waypointList[0]))
{
Destroy(waypointList[0].gameObject);
waypointList.RemoveAt(0);
yield return new WaitForSeconds(0.5f);
}
yield return null;
}
}
private bool PickingToPoint(ref Vector3 _point)
{
Vector3 mousePos = Input.mousePosition;
Ray ray = Camera.main.ScreenPointToRay(mousePos);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 1000f))
{
_point = hit.point;
return true;
}
return false;
}
private void SpawnWaypoint(Vector3 _pos)
{
GameObject waypointGo = Instantiate(waypointPrefab,
_pos, Quaternion.identity);
CoWaypoint waypoint = waypointGo.GetComponent<CoWaypoint>();
waypointList.Add(waypoint);
}
private void DestroyWaypointAll()
{
foreach (CoWaypoint wp in waypointList)
Destroy(wp.gameObject);
waypointList.Clear();
}
}
- waypointPrefab, unit, layerMask: 모두 Inspector창에서 해당 오브젝트를 지정하여 수정해줍니다.
- waypointList: 여러 개의 깃발을 띄어야하기 때문에 Cowaypoint를 사용하는 List로 만들어보겠습니다.
- Awake, Start: List 동적 할당, Update코루틴 호출
- UpdateCoroutine(): 좌클릭으로 깃발을 소환하는 기능, Shift와 함께 클릭 시 여러 개의 깃발이 List에 들어가는 등의 주요 기능들이 모두 들어갑니다.
- PickingToPoint(ref Vector3 _point): 클릭 시 벡터위치를 게임 화면으로 변환해주는 메서드입니다.
- SpawnWaypoint(Vector3 _pos): CoWaypoint 스크립트를 이용하여 깃발을 리스트에 포함하고 소환하는 메서드입니다.
- DestroyWaypointAll(): 현재 활성화된 모든 waypoint(깃발)을 제거하고 다시 리스트를 채울 수 있도록 만들어주는 메서드입니다.
위의 3개의 스크립트와 알맞게 Unity 내에서 환경을 세팅해주면 작동합니다.
'유니티(Unity) 프로그래밍' 카테고리의 다른 글
UI - Sprite, Image 통해 HP 게이지 표현하기 (0) | 2024.10.21 |
---|---|
UI Canvas (0) | 2024.10.21 |
Unity 카메라 (1) | 2024.09.29 |
Unity Prefabs과 Stack, Queue (1) | 2024.09.26 |
Unity Collider(Trigger, Collision) (2) | 2024.09.24 |