Queue

FIFO(First-In-First-Ou)t - 먼저 들어간애가 제일 먼저 나온다.

Queue<int> queue = new Queue<int>();

관련 메서드

01 Enqueue

큐에 요소 추가

queue.Enqueue(1);


02 Dequeue

큐의 제일 앞 요소 제거

queue.Dequeue();


03 Count

큐의 저장된 요소의 수 반환

int count = queue.Count;

04 Contains

큐에 특정 요소가 있는지 여부 확인

bool contains = queue.Contains(1);

05 ToArray

큐의 모든 요소를 배열로 반환

string[] elements = queue.ToArray();

코드 예시

01 Enqueue를 5번한 결과

Queue<int> queue = new Queue<int>();

queue.Enqueue(1);
queue.Enqueue(2);
queue.Enqueue(3);
queue.Enqueue(4);
queue.Enqueue(5);

int[] elements = queue.ToArray();
Console.WriteLine(string.Join(", ", elements));


02 Dequeue를 한번 실행시키면

제일 먼저 들어간 1이 없어진 것을 확인할 수 있다.



(하이라키창에 있는)게임오브젝트의 gameobject, transform 접근하는 방법,
gamobject,transform의 객체를 가져오는 방법

 

GameObjectTest.Cs

 

 

using Sample.ObjectTest;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;

namespace Sample
{

    public class GameObjectTest : MonoBehaviour
    {
        //필드
        //2)
        public Transform publicTransform;
        public GameObject publicObject;

        //3)
        private GameObject[] tagObjects;
        private GameObject tagObject;

        //4)
        public GameObject prefabObject;

        //5)
        public Transform parentObject;
        private Transform[] childObjects;


        // Start is called before the first frame update
        void Start()
        {
            //1)
            //this.gameObject
            //this.transform

            //2)
            //publicTransform
            //publicObject

            //3)
            //tagObjects = GameObject.FindGameObjectsWithTag("tagString");
            //tagObject = GameObject.FindGameObjectWithTag("tagString");

            //4)
            //GameObject prefabGo = Instantiate(prefabObject, this.transform.position, Quaternion.identity);

            //5)
            //childObjects = new Transform[parentObject.childCount];
            //for (int i = 0; i < childObjects.Length; i++)
            //{
            //    childObjects[i] = parentObject.GetChild(i);
            //}

            //6)
            //StaticClass.number = 10;

            //Singleton
            var objectA = Singleton.Instance;
            var objectB = Singleton.Instance;
            if(objectA == objectB)
            {
                Debug.Log(objectA);
            }

            //SingletonTest
            SingletonTest.Instance.number = 10;
        }
    }
}

/*
(하이라키창에 있는)게임오브젝트의 gameobject, transform 접근하는 방법,
gamobject,transform의 객체를 가져오는 방법

1) gameobject에 스크립트 소스를 컴포넌트로 추가하여 직접(this.)로 가져온다
2) 가져오려는 오브젝트의  gameobject, transform 객체(인스턴스)를 public 한 필드로 선언한 후 
유니티 에디터 툴에서 인스펙터 창으로 직접 드래그 해서 가져온다  
3) Find - 유니티에서 제공하는 API를 이용해서 gameobject, transform의 객체를 반환받아 가져온다
   GameObject.FindGameObjectsWithTag ,   GameObject.FindGameObjectWithTag
4) prefab 게임오브젝트 생성시  Instantiate 함수의 반환값으로 gameobject의 객체를 가져온다
5) 부모 자식관계를 이용해서 gameobject의 객체를 가져온다
6) static 필드: 클래스이름.필드이름,

 */

 


StaticClass.cs

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Sample
{
    public class StaticClass : MonoBehaviour

    {

        //필드 
        public static int number = 0;

        // Start is called before the first frame update
        void Start()
        {

        }

        // Update is called once per frame
        void Update()
        {

        }
    }
}

 

Singleton.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Sample.ObjectTest;

namespace Sample
{
    namespace ObjectTest
    {

        public class Singleton
        {
            private static Singleton instance;

            //속성 - 읽기전용
            public static Singleton Instance
            {
                get
                {
                    if (instance == null)
                    {
                        instance = new Singleton();
                    }
                    return instance;
                }
            }

            //메서드
            /*public static Singleton Instance()
            {
                if(instance == null)
                {
                    instance = new Singleton();
                }
                return instance;
            }*/
        }
    }


}

 

SingletonTest.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


namespace Sample
{
    public class SingletonTest : MonoBehaviour
    {
        //클래스의 인스턴스 변수를 static으로 선언
        private static SingletonTest instance;

        public static SingletonTest Instance
        {
            get
            {
                return instance;
            }
        }

        //실행과 동시에 instance에 객체를 받아온다
        private void Awake()
        {
            if (instance != null)
            {
                Destroy(gameObject);
                return;
            }
            instance = this;

            //gameObject는 신 종료시 파괴가 안된다
            DontDestroyOnLoad(gameObject);
        }

        public int number = 0;
    }
}

 

Hittest.cs

using Sample;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Sample
{


    public class ComponentTest : MonoBehaviour
    {
        //필드

        //속도
        public float moveSpeed = 5f;

        //타겟
        public Transform target;        //Target 게임 오브젝트의 Transform 객체로 접근
        public GameObject gTarget;      //Target 게임 오브젝트의 객체로 접근
        public TargetTest cTarget;      //TargetTest 에 직접 접근

        // Start is called before the first frame update
        void Start()
        {
            //문법
            //TargetTest tTest = new TargetTest();
            //tTest.b = 30;

            //target 오브젝트에 트랜스폼에 붙어있는 TargetTest의 객체에 접근
            //TargetTest tTest = target.GetComponent<TargetTest>();
            //tTest.b = 30;   //public 한 필드를 가져와 사용
            //Debug.Log(tTest.GetA()); // private a 를 public한 메서드로 접근해서 사용

            //ComponentTest 스크립트가 붙어있는 게임 오브젝트의 트랜스폼 객체
            //this.transform
            //ComponentTest 스크립트가 붙어있는 게임 오브젝트의 객체
            //this.gameObject

            //target 오브젝트에 붙어있는 TargetTest의 객체에 접근
            //TargetTest gTest = gTarget.GetComponent<TargetTest>();
            //gTest.b = 50;   //public 한 필드를 가져와 사용
            //Debug.Log(gTest.GetA());// private a 를 public한 메서드로 접근해서 사용

            //Target 게임 오브젝트에 붙어있는 TargetTest의 객체에 직접 접근
            cTarget.b = 70;
            Debug.Log(cTarget.b);
            Debug.Log(cTarget.transform.position);

            //플레이어
            //ComponentTest 스크립트가 붙어있는 게임 오브젝트의 트랜스폼 객체
            //this.transform
            //this.transform.GetComponent<>
            //ComponentTest 스크립트가 붙어있는 게임 오브젝트의 객체
            //this.gameObject
            //this.gameObject.GetComponent<>

            //this.transform.gameObject //ComponentTest 스크립트가 붙어있는 트랜스폼에 붙어있는 게임오브젝트에 접근
            //this.gameObject.transform //ComponentTest 스크립트가 붙어있는 게임오브젝트에 붙어있는  트랜스폼에 접근



        }

        // Update is called once per frame
        void Update()
        {
            //타겟으로 이동
            // 방향 * Time.deltaTime * 속도
            Vector3 dir = target.position - this.transform.position;
            this.transform.Translate(dir.normalized * Time.deltaTime * moveSpeed);

        }


    }

}

 

 

TargetTest.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UnityEngine.GraphicsBuffer;

namespace Sample
{
    // 사각형 데이터를 관리하는 구조체
    public struct BoxData
    {
        public float x; // 박스의 중심 x 좌표
        public float y; // 박스의 중심 y 좌표
        public float w; // 박스의 너비
        public float h; // 박스의 높이
    }

    // 원 데이터를 관리하는 구조체
    public struct CircleDate
    {
        public float x; // 원의 중심 x 좌표
        public float y; // 원의 중심 y 좌표
        public float r; // 원의 높이
    }


    public class Hittest : MonoBehaviour
    {
        //필드
        //타겟
        public Transform target;

        //이동 속도 
        private float moveSpeed = 300;

        // Start is called before the first frame update
        void Start()
        {

        }

        // Update is called once per frame
        void Update()
        {
            if (CeackPassPosition(target))
            {
                Debug.Log("충돌");
            }
        }

        // 매개변수로 받은 두 개의 박스가 충돌했는지 체크
        // 충돌하면 true, 충돌하지 않으면 false 반환
        public bool CheakHitBox(BoxData a, BoxData b)
        {
            // A와 B의 각 변의 절반 길이를 미리 계산하여 변수에 저장
            float halfWidthA = a.w / 2;
            float halfHeightA = a.h / 2;
            float halfWidthB = b.w / 2;
            float halfHeightB = b.h / 2;

            // A와 B의 중심 사이의 거리와 각 박스의 절반 너비 및 높이를 비교

            // x축에서의 충돌 체크
            if (Mathf.Abs(a.x - b.x) > (halfWidthA + halfWidthB))
            {
                return false;
            }

            // y축에서의 충돌 체크
            if (Mathf.Abs(a.y - b.y) > (halfHeightA + halfHeightB))
            {
                return false;
            }

            // 충돌이 발생한 경우
            return true;
        }

        //매개변수로 받은 두개의 원이 충돌했는지 체크
        // 충돌하면 true, 충돌하지 않으면 false 반환
        public bool CheakHitCircle(CircleDate a, CircleDate b)
        {
            // x y r

            float distX = a.x - b.x;
            float distY = a.y - b.y;

            //두원의 거리 (Mathf.Sqrt) = 제곱근
            float distance = Mathf.Sqrt(distX * distX + distY * distY);

            //두원의 반지름의 합
            float sumradius = a.r + b.r;

            //두원의 거리보다 두원의 반지름의 합보다 작으면 충돌(true)
            if (distance <= sumradius)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        //타겟까지의 거리가 일정 거리안에 있으면 충돌이라고 판정
        private bool CheckArrivePosition(Transform target)
        {
            float distance = Vector3.Distance(this.transform.position, target.position);

            if (distance <= 0.5f)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        //타겟까지 이동할 남은 거리가 이번(한)프레임에 이동하는 거리보다 작을때 충돌이라고 판정
        private bool CeackPassPosition(Transform target)
        {
            //타켓까지 이동할 남은 거리
            float distance = Vector3.Distance(this.transform.position, target.position);
            //이번(한)프레임에 이동하는 거리
            float distanceThisFrame = Time.deltaTime * moveSpeed;

            if (distance < distanceThisFrame) //초당속도
            {
                return true;
            }
            else
            {
                return false;
            }
        }

    }
}

 

ComponentTest.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Sample
{

    public class TargetTest : MonoBehaviour
    {
        //필드
        private int a = 10;     //
        public int b = 20;      //

        // Start is called before the first frame update
        void Start()
        {

        }

        // Update is called once per frame
        void Update()
        {

        }

        //private a를 public한 메서드 반환
        public int GetA()
        {
            return a;
        }
    }
}

 

 

 

RotateTest.cs

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class RotateTest : MonoBehaviour
{
    // 회전 속도를 조절할 수 있는 public 변수입니다.
    public float turnSpeed = 5f;

    // 회전할 때 바라볼 목표 대상(타겟)입니다.
    public Transform target;

    void Start()
    {
        // Start 함수는 게임이 시작될 때 한 번만 호출됩니다.
        // 여기에 초기 회전값 설정 코드를 넣을 수 있습니다.
        // 주석된 코드들은 각각 Y, X, Z축으로 회전값을 설정하는 예제입니다.

        // Y축으로 -90도 회전
        // this.transform.rotation = Quaternion.Euler(0f, -90f, 0f);

        // X축으로 90도 회전
        // this.transform.rotation = Quaternion.Euler(90f, 0f, 0f);

        // Z축으로 90도 회전
        // this.transform.rotation = Quaternion.Euler(0f, 0f, 90f);
    }

    void Update()
    {
        // Update 함수는 매 프레임마다 호출됩니다. 따라서 회전 로직을 여기에 넣으면 실시간으로 객체가 회전합니다.

        // 아래 주석된 코드들은 특정 축을 기준으로 객체를 회전시키는 예제입니다.
        // x 값을 증가시키면서 객체를 회전시킬 수 있습니다.

        // float x = 0f;
        // x += 1;
        // Y축 기준 회전
        // this.transform.rotation = Quaternion.Euler(0f, x, 0f);
        // X축 기준 회전
        // this.transform.rotation = Quaternion.Euler(x, 0f, 0f);
        // Z축 기준 회전
        // this.transform.rotation = Quaternion.Euler(0f, 0f, x);

        // 이 부분은 객체를 특정 축을 기준으로 회전시키는 예제입니다.

        // Y축 기준 회전 (Vector3.up은 (0, 1, 0) 벡터를 의미)
        // this.transform.Rotate(Vector3.up * Time.deltaTime * turnSpeed);

        // X축 기준 회전 (Vector3.right는 (1, 0, 0) 벡터를 의미)
        // this.transform.Rotate(Vector3.right * Time.deltaTime * turnSpeed);

        // Z축 기준 회전 (Vector3.forward는 (0, 0, 1) 벡터를 의미)
        // this.transform.Rotate(Vector3.forward * Time.deltaTime * turnSpeed);

        // RotateAround는 객체가 타겟을 중심으로 회전하도록 만듭니다. 
        // 여기서는 타겟의 위치를 중심으로 Y축(Vector3.up)을 기준으로 회전합니다.
        // 지구가 태양 주위를 도는 것(공전)과 유사한 방식입니다.
        // transform.RotateAround(target.position, Vector3.up, turnSpeed * Time.deltaTime);

        // 다음은 타겟을 바라보도록 객체를 회전시키는 로직입니다.

        // 1. 타겟을 향하는 방향 벡터를 계산합니다.
        Vector3 dir = target.position - transform.position;

        // 2. 방향 벡터를 기준으로 회전값(Quaternion)을 생성합니다.
        Quaternion lookRotation = Quaternion.LookRotation(dir);

        // 3. 현재 회전값(transform.rotation)과 타겟을 바라보는 회전값(lookRotation) 사이를 보간(Lerp)하여 부드럽게 회전시킵니다.
        // Lerp는 선형 보간(Linear Interpolation) 함수로, 두 값을 시간에 따라 일정하게 섞어줍니다.
        // t 값이 0에 가까울수록 시작점(transform.rotation)에 가깝고, 1에 가까울수록 목표점(lookRotation)에 가까워집니다.
        Quaternion qRotation = Quaternion.Lerp(this.transform.rotation, lookRotation, turnSpeed * Time.deltaTime);

        // 4. 구한 회전값 qRotation에서 오일러 각도를 추출합니다. 오일러 각도는 Quaternion을 세 축의 각도로 변환한 값입니다.
        Vector3 eRotation = qRotation.eulerAngles;

        // 5. 오일러 각도 중 Y축 회전 값만을 사용하여 객체의 회전을 설정합니다.
        // Y축 값만 사용하는 이유는 객체가 타겟을 바라보되, X축이나 Z축으로의 회전은 제한하기 위해서입니다.
        transform.rotation = Quaternion.Euler(0f, eRotation.y, 0f);

        // 주석된 코드: 이 코드는 X, Y, Z 축 전체에 대해 보간된 회전값을 적용합니다.
        // transform.rotation = Quaternion.Lerp(this.transform.rotation, lookRotation, turnSpeed * Time.deltaTime);
    }
}

/*
Lerp(a, b, t) 함수 설명:
- Lerp 함수는 두 값(a와 b) 사이의 값을 보간합니다.
- t는 보간 계수로, 0에서 1 사이의 값을 가집니다.
  - t가 0이면 a 값과 동일한 값을 반환하고,
  - t가 1이면 b 값과 동일한 값을 반환합니다.
  - t가 0.5이면 a와 b의 중간 값을 반환합니다.

예제 1:
a=0, b=10인 경우,
t 값을 변경해가며 Lerp(a, b, t)를 호출하면 다음과 같은 결과를 얻을 수 있습니다.
- Lerp(0, 10, 0.1) // 1
- Lerp(0, 10, 0.2) // 2
- Lerp(0, 10, 0.3) // 3
...
- Lerp(0, 10, 0.8) // 8
- Lerp(0, 10, 0.9) // 9
- Lerp(0, 10, 1)   // 10

예제 2:
Lerp 결과를 a에 다시 할당하여 반복적으로 호출하면, 점점 a가 b에 가까워집니다.
a=0, b=10인 경우,
- a = Lerp(a, b, 0.1) // a=1
- a = Lerp(a, b, 0.1) // a=1.9 (처음 a가 1로 업데이트되었으므로, Lerp(1, 10, 0.1)이 실행됨)
- a = Lerp(a, b, 0.1) // a=2.71 (두 번째 a가 1.9로 업데이트되었으므로, Lerp(1.9, 10, 0.1)이 실행됨)
*/

int input = int.Parse(Console.ReadLine()!);


int bunmo = 1;
int bunja = 1;


int count = 1; //대각선 순서
int room = 1;  //순번

//짝수일때 분모++ 분자--
//홀수일때 분모-- 분자++
//2 =count 3=1 4=2 5=3 6=4 

while (room < input)
{
    room = room + count + 1;
    count++;
}

//짝수일때
if (count % 2 == 0)
{
    bunja = count;

    for (int i = 0; i < room - input; i++)
    {
        bunmo++;
        bunja--;

    }
}
//홀수일때
else
{
    bunmo = count;

    for (int i = 0; i < room - input; i++)
    {
        bunmo--;
        bunja++;
    }
}
count++;



Console.WriteLine($"{bunja}/{bunmo}");

// 1 23  456 78910

 

 

챗 gpt 최적화 코드

int input = int.Parse(Console.ReadLine()!); // 사용자로부터 입력 값을 받습니다.

int count = 1; // 대각선의 순서를 저장할 변수입니다.
int room = 1;  // 방(순번)을 나타내는 변수입니다.

// 입력된 방 번호를 찾기 위한 반복문
while (room < input)
{
    count++; // 다음 대각선 순서로 넘어갑니다.
    room += count; // 방 번호를 다음 대각선의 시작 위치로 갱신합니다.
}

// 대각선 순서에 따라 분모와 분자를 계산
int difference = room - input;
int bunmo, bunja;

if (count % 2 == 0)
{
    bunmo = 1 + difference;
    bunja = count - difference;
}
else
{
    bunmo = count - difference;
    bunja = 1 + difference;
}

Console.WriteLine($"{bunja}/{bunmo}"); // 최종적으로 계산된 분자와 분모를 출력합니다.

'코딩 문제 풀이' 카테고리의 다른 글

백준 8958 OX퀴즈  (0) 2024.08.17
백준 2920 음계  (0) 2024.08.17
백준 10250 ACM 호텔  (0) 2024.08.17
백준 2903 - 중앙 이동 알기  (0) 2024.08.16
백준 1002 - 터렛  (0) 2024.08.14

. GameObject original

- 생성하고자 하는 게임오브젝트명. 현재 씬에 있는 게임오브젝트나 Prefab으로 선언된 객체를 의미함.

 

2. Vector3 position

- Vector3으로 생성될 위치를 설정함.

 

3. Quaternion rotation

- 생성될 게임오브젝트의 회전값을 지정한다. 

- 회전을 굳이 줘야할 상황이 아니라면, 그냥 기본값으로 설정하는 것. --> Quaternion.identity

- 또는 게임오브젝트에서 설정된 회전값. 즉, original.transform.rotation으로 작성해도 됨.

 

아래와 같이 선언해서 obj라는 게임오브젝트 객체를 동적으로 생성한다.

 

Instantiate(obj, new Vector3(x,y,z), Quaternion.identity);      // 그냥 회전없음.

또는 

Instantiate(obj, new Vector3(x,y,z), obj.transform.rotation);   // obj의 회전값.

 

 

 

============================================================================================

 

코루틴

 

 

 

// 응용 

 

                                                           Update()에서 호출하기

 

#SpawnManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;

namespace MyDefence
{


    public class SpawnManager : MonoBehaviour
    {
        //필드
        #region Variable
        //enemy 프리팹
        public GameObject enemyPrefab;
        //스폰 위치(적 시작 위치)
        public Transform startPoint;
        //스폰 타이머       
        public float spwanTimer = 5f;
        private float countdown = 0f;
        #endregion


        // Start is called before the first frame update
        void Start()
        {
            //시작지점 위치에 Enemy 1개를 생성
            //SpawnEnemy();
            //5초 간격으로 Enemy 1마리씩 스폰하기

        }

        // Update is called once per frame
        void Update()
        {
            ////5초 간격으로 Enemy 1마리씩 스폰하기 //12345
            //countdown += Time.deltaTime;
            //Debug.Log($"countdown: {countdown}");

            //if (countdown  >= spwanTimer)
            //{
            //    SpawnEnemy();

            //    //초기화
            //    countdown = 0;
            //}

            //5초 간격으로 Enemy 1마리씩 스폰하기 //54321
            //countdown -= Time.deltaTime;
            //Debug.Log($"countdown: {countdown}");

            //if (countdown <= 0f)
            //{
            //    SpawnEnemy();

            //    //초기화
            //    countdown = spwanTimer;
            //}


            //5초 간격으로 Enemy 1마리씩 스폰하기 //12345
            countdown += Time.deltaTime;
            Debug.Log($"countdown: {countdown}");

            if (countdown >= spwanTimer)
            {
                SpawnEnemy();

                //초기화
                countdown = 0;
            }

        }

        //시작지점 위치에 Enemy 를 생성
        private void SpawnEnemy()
        {

            Instantiate(enemyPrefab, startPoint.position, Quaternion.identity);

        }

    }
}

 

 

목표 : 적(Enemy) 하나 만들어서 적이 서있는 지점(시점)에서 종점까지 이동하는 프로그램 구현
        (게임 오브젝트의 이동) A 지점 -> B 지점 까지 이동

<오늘의 과제>
1. 카메라 시점 결정 - 유저 플레이 화면 결정
2. 조명 방향 조정하기 - 그림자 방향 결정
3. Enemy 만들기 (구 : 크기 1x1x1, 하늘색) - 프리팹 저장
4. 이동 경로 만들기 - WayPoint 지정
- Enemy 이동시 방향을 전환 지점에 포인트 지정하기 : (포인트 빈 오브젝트 세우기)
5. Enemy 이동 구현( 이동 경로에 따라 Enemy 종점까지 이동하기)
- 첫번째 Point로 출발
- 첫번째 Point에 도착 (도착 판정)
Debug.Log("도착!!!!");  
   -> 두번째 Point로 출발

6. 종점까지 이동 완료하면 (종점 판정)
- 오브젝트 kill (Destroy) 
    Debug.Log("종점 도착!!!!");

<스킬>
1. Awake, Start, Update
2. Transform
3. Translate, Time.deltaTime
4. Destroy

<Sample 씬>
MoveTest
1. 앞, 뒤, 좌, 우로 이동하기
2. 5의 속도로 이동하기
3. 목표지점으로 이동하기 (현재위치에서 지정한 위치로 이동)
4. 목표지점에 도착판정





이렇게 #region  #endregion 으로 감싸두면 코드를 접어둘 수 있어 가독성이 좋다. C#의 문법이다

    void foo()
    {
# region 코드 설명
        /* 주석처리해두거나 접어두고 싶은 코드 */
# endregion

    }

접기 전 모습

접은 후 모습


깔끔!

접었다 펴는데 단축키도 있다!
ctrl+M+M

캐릭터 이동을 하는데

tr.Translate(moveDir.normalized * Time.deltaTime * moveSpeed, Space.Self)

이런 코드가 있었다.
어떤 각도로 이동해도 같은 속도로 이동하기 위함이라고 한다.

normalized

오브젝트 균일한 이동을 위하여 벡터의 정규화가 필요합니다.

그 이유는 모든 방향의 벡터 길이가 1 이어야 방향에 따른 이동 속도가 같아지기 때문이지요.

https://seojingames.tistory.com/entry/%EB%B0%A9%ED%96%A5-%EB%B2%A1%ED%84%B0-%EB%B2%A1%ED%84%B0%EC%9D%98-%EC%A0%95%EA%B7%9C%ED%99%94normalized-%EC%9C%A0%EB%8B%88%ED%8B%B0

'유니티 공부' 카테고리의 다른 글

직렬화(Serialization)  (0) 2024.09.11
디자인패턴의 기초 SOLID (솔리드 ) 원칙 이해하기  (6) 2024.09.10
오브젝트 이동(2)  (0) 2024.08.29
오브젝트 이동(1)  (0) 2024.08.29
Time.deltaTime이란?  (0) 2024.08.29

목표

- 오브젝트가 특정 경로를 따라 이동하게 구현

- 좌표와 애니메이션을 이용해 오브젝트가 움직이며 이동하게 구현


특정 경로를 따라 이동

 이전 [유니티] 오브젝트 이동(1)에서는 기본적인 이동방법 2가지인 transform.Translate와 transform.position에 대해 간략하게 알아보았다. 이번 파트에서는 이 두 가지 방법 중 하나를 이용해서 경로를 따라 이동하는 것을 구현해보자. 

 

 다음과 같은 타일을 맵으로 해서 노란색 경로를 따라 지나가다 End를 만나면 멈추는 것을 구현해보자. Start부터 시작해서 꺾이는 분기점을 지나게 하려면 어떻게 해야할까?

 

- Vector3.moveTowards

 유니티 공식 문서에 따르면 Vector3의 프로퍼티중 MoveTowards는 다음과 같다.

 

 

 current에서 target까지 maxDistanceDelta의 속도로 이동한다고 생각하면 된다. 이때 예제를 살펴보았을 때 maxDistanceDelta의 속도에 Time.deltaTime을 곱해주는 경우가 많은데, 여기서 Time.deltaTime 1/초당프레임을 의미한다. 즉, 컴퓨터 사양에 구애받지 않고 시간을 다루기 위해 사용된다.

 

Time.deltaTime의 의미

 

 따라서 start에서 출발해서 end로 가기 위한 코드를 짜면 다음과 같다. 

public float speed = 3f;
public Transform target;

void Update()
{
    transform.position = Vector3.MoveTowards(transform.position, target.position, speed * Time.deltaTime);
    // 매 프레임마다 speed의 속도만큼 이동
}

 

1. 경로(Path) 설정

 다시 우리가 이동하고자 하는 맵을 확인해보자. 

 

 

 노란색 경로 위에 있는 저 빨간색 동그라미를 지나쳐야 올바르게 이동하는 것이므로, 해당 경로 지점을 설정해주는 작업이 필요할 것 같다. 따라서 Hieararchy창에 빈 오브젝트를 생성해 경로를 담아두는 용도로 지정해놓고, 그 안에 경로 포인트를 담도록 해보자.

 

검은색 큐브가 있는 지점을 Start로 해서 Curve (1), Curve (2)를 지나 End에 도달하게 오브젝트를 생성해놨다.

 

2. 경로 이동

 모든 준비가 끝났으니, 이제 코드로 구현할 차례만 남았다. 저기서 이동을 하려면 어떤 알고리즘이 필요할지 한 번 생각해보면, Scene에서 경로 포인트를 찾아야 하고, 그 경로를 정해진 순서에 따라 이동을 해야 한다는 걸 알 수 있다. 

 

 먼저 경로 포인트들을 담을 스크립트 PathPoints를 하나 작성하자.

// PathPoints.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PathPoints : MonoBehaviour
{
    public Transform[] points;
}

 

 이 스크립트는 단순히 이동 경로들을 저장할 용도이므로 Transform을 배열 형식으로 초기화 해놓으면 된다. 그 다음은 Path Object에 이 스크립트를 추가해 경로들을 지정해주면 된다.

 

0번부터 3번까지 순서대로 지정해야 한다.

 

 이제 경로를 지정했으니, 경로를 찾아 끝지점에 도달할 때까지 이동할 코드를 작성해야한다. 스크립트를 다음과 같이 작성해보자.

using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;

public class PathMove : MonoBehaviour
{
    [SerializeField] PathPoints thePath;
    private int currentPoint; //
    private bool endPoint; 
    private float speed = 3f; 

    void Start()
    {
        thePath = FindObjectOfType<PathPoints>();
    }

    void Update()
    {
        if(!endPoint)
        {
            this.transform.position 
                = Vector3.MoveTowards(this.transform.position, thePath.points[currentPoint].position, 
                	speed * Time.deltaTime);
            
            if(Vector3.Distance(this.transform.position, thePath.points[currentPoint].position) < 0.1f)
            {
                currentPoint++;

                if(currentPoint >= thePath.points.Length)
                    endPoint = true;
            }
        }
    }
}

 

 코드가 작동되는 과정을 살펴보자. 

[SerializeField] PathPoints thePath;
private int currentPoint; 
private bool endPoint; 
private float speed = 3f;

 먼저 아까 작성한 지점들을 받아와야 하므로 PathPoints 타입의 변수 thePath를 지정해주고, 배열의 인덱스에 따라 이동할 지점이 결정되므로 정수형 변수 currentPoint를 초기화해준다. 그후 끝지점 도달 여부를 체크할 bool 변수 endPoint와 큐브의 이동속도를 담당할 변수 speed를 초기화해준다.

 

void Start()
{
    thePath = FindObjectOfType<PathPoints>();
}

 경로를 지정해논 thePath를 찾아줘야 하는데, 이때 FindObjectOfType<>() 함수를 써주면 편리하다. 공식 문서를 살펴보면 다음과 같다.

 

출처: https://docs.unity3d.com/ScriptReference/Object.FindObjectOfType.html

 

 간단하게 꺾새<> 안에 적혀있는 타입의 오브젝트를 찾아준다고 생각하면 된다. 굉장히 간편하지만 성능적인 측면에서 느리다는 단점이 존재한다. 하지만 우리는 프로젝트 규모가 크지 않고, Start()문에서 한 번만 사용할 것이므로 크게 개의치 않아도 된다. 

 

void Update()
{
    if(!endPoint)
    {
        this.transform.position 
            = Vector3.MoveTowards(this.transform.position, thePath.points[currentPoint].position, 
            	speed * Time.deltaTime);

        if(Vector3.Distance(this.transform.position, thePath.points[currentPoint].position) < 0.1f)
        {
            currentPoint++;

            if(currentPoint >= thePath.points.Length)
                endPoint = true;
        }
    }
}

 경로를 찾은 후에는, 내가 이동할 포인트를 찾아야 한다. 우리의 목표는 지정된 경로를 따라 이동하되, 끝지점에 도달하면 멈춰야하므로 이동과 관련된 코드들을 endPoint에 도달하지 않았을 때만 업데이트 되도록 조건을 걸어놓자.

 

 만약 끝지점이 아니라면, 앞서 배운 MoveToWards를 통해 이동을 하도록 구현했다. 이때 위치는 자신의 위치에서 인덱싱한 배열을 따라 움직이게 설계했는데, 만약 자신의 거리와 목적지의 거리가 일정 수준으로 좁혀지면 다음 경로로 이동할 수 있도록 currentPoint를 증가시켰다.

 

 그렇게 이동하다보면 현재의 위치 currentPoint와 thePath의 길이와 같거나 커지는 순간이 오는데, 이때가 끝지점에 도달했다는 것이므로 endPoint를 true로 바꾸면 된다.


애니매이션을 이용해 오브젝트의 이동을 생동감 있게 표현

 위의 과정을 잘 따라왔다면 큐브가 빨간 포인트를 잘 지나갈 것이다. 하지만 뭔가 밋밋하다. 좀더 생동감있게 표현하기 위해 애니메이션을 활용해 점프하며 지나가는 듯한 느낌을 줘보자. 애니메이션을 구현하기 위해 빈오브젝트를 생성한 후 이를 Model로 이름 짓고 그 안에 Cube를 넣자(이렇게 하지 않으면 필자가 구현하려는 방식으론 애니메이션을 구현할 때 본체의 좌표가 고정되어서 구현하기 번거롭다). 또한 Model과 Cube의 위치가 꼬일 수 있으므로 반드시 각 오브젝트의 위치를 초기화를 한 번 진행하고 작업해야 한다.

 

 

 우리는 큐브가 점프하는 것을 구현할 것이므로 Ctrl + 6번(혹은 Windows -> Animation -> Animation)을 눌러 애니메이션을 생성하자.

 

 

 그러면 이런 창이 뜰 것인데, 반드시 Hieararchy창에서 Cube를 선택한 후 Create 버튼을 눌러 적당한 이름을 짓고 애니메이션 파일을 생성하자. 

 

 

 해당 창이 활성화되었다면 성공적으로 작업을 할 준비가 되었다는 것이다. 이번 시간에서는 애니메이션에 대해 깊게 파고들 것은 아니므로, 간단하게 큐브의 위치를 연속적으로 변화시켜 마치 점프하며 이동하는 듯한 모션을 구현해보자. 

 

 우측에 시간이 표시된 쪽에 마우스를 클릭하면 흰 선이 이동할 텐데, 이를 이용하면 해당 시간대에 Cube의 상태를 표현할 수가 있다. 예를 들어 0:00에 이벤트를 하나 만들고, 0:10에 흰 선을 옮겨놓고 Cube의 y축 position을 0.5로 해놓으면, y축이 0에서 0.5로 계속해서 변화하므로 점프하는 듯한 모션을 줄 수가 있다.

 

 

 좌측에 Cube의 Position 프로퍼티를 추가하면 다음과 같은 화면이 될 것이다. 점 표시가 활성화된 것이 이벤트인데, 이벤트를 활성화하여 y를 0부터 1까지 변화시킬 것이다.

 

 이제 좌측에 플레이 버튼을 누른 후 원하는 시간대에 클릭을 하여 y의 값을 변경해보자.

 

 

 이렇게 하면 큐브가 마치 점프하며 움직이는 듯한 모션을 간단하게 구현할 수 있다. 


요약

- 경로를 transform 배열로 지정해, 배열을 순회하며 경로를 이동할 수 있다.
- 애니메이션을 이용하여 특정 행동을 구현할 수 있다.

'유니티 공부' 카테고리의 다른 글

디자인패턴의 기초 SOLID (솔리드 ) 원칙 이해하기  (6) 2024.09.10
normalized 하는 이유  (0) 2024.08.29
오브젝트 이동(1)  (0) 2024.08.29
Time.deltaTime이란?  (0) 2024.08.29
유니티의 생명주기(Lifecycle)  (0) 2024.08.29

+ Recent posts