<유니티> 게임 오브젝트 생성 함수

상태
시작 전
담당자
날짜
숫자
0
여러분. 게임을 하다보면 다양한 게임 오브젝트들을 사용하게 됩니다. 만약 여러 마리의 적을 구현해야할 때, 원본을 만들고 이를 통해 복제하는 방식으로 게임 오브젝트를 만들게 됩니다. 원본 A가 있고, A를 복제해서 B, C, D를 만든다고 가정하겠습니다. 이 때, 원본으로 사용 중인 A를 처치하게 되면 어떻게 될까요? 그렇게 되면 복사할 원본이 없어 에러가 발생하게 되고 더 이상 적을 생성할 수 없게 됩니다.
이를 방지하기 위해 원본 오브젝트는 파일로 안전하게 보관(프리팹)하고 원본은 게임에 등장하지 않도록 설정합니다.
이번에는 게임 오브젝트를 복제하여 생성할 수 있게 해주는 Prefab과 Instantiate에 대해 학습해보고 그 외에 회전 연산을 담당하는 오일러와 쿼터니온에 대해 학습해보겠습니다.

프리팹을 활용하여 게임오브젝트를 복제하는 예제

using UnityEngine; public class ObjectSpawner : MonoBehaviour { [SerializeField] private GameObject boxPrefab; // boxPrefab 변수를 인스펙터 창에 노출시켜서 유니티 에디터에서 프리팹 할당 // 박스를 스폰하기 위한 프리팹을 저장하는 변수 private void Awake() { // Instantiate(GameObject 원본); // 원본 게임오브젝트(프리팹)를 복제해서 생성 (복제되는 오브젝트의 모든 컴포넌트 정보가 원본과 완전히 동일) Instantiate(boxPrefab); } }
C#
복사
using UnityEngine; public class ObjectSpawner : MonoBehaviour { [SerializeField] private GameObject boxPrefab; private void Awake() { // Instantiate(GameObject 원본, Vector3 position, Quaternion rotation); // 원본 게임오브젝트를 복제해서 생성하고, 생성된 복제본의 위치와 회전 설정 Instantiate(boxPrefab, new Vector3(3, 3, 0), Quaternion.identity); Instantiate(boxPrefab, new Vector3(-1, -2, 0), Quaternion.identity); // Quaternion.identity = 회전 값(0,0,0) }
C#
복사

오일러, 쿼터니온

회전 정보를 나타내고 연산하는 방식에는 오일러와 쿼터니온 방식이 있습니다. 각 방식에 따른 특징과 장단점에 대해 알아보겠습니다.

오일러

3차원의 3개 각도를 표현하기 위해 사용하는 3X3 크기의 행렬 회전 순서에 따라 결과가 달라지기 때문에 회전 순서에 주의해야함 (유니티에서는 따로 계산할 일이 없기 때문에 걱정할 필요 X)
장점 : 우리가 알고 있는 0~360 각도를 표현할 수 있다.
단점 : 지속적으로 회전을 하는 연산을 할 때 쿼터니온보다 연산속도가 느리고, 짐벌락 현상이 발생할 수 있다.
짐벌락 : 세 개의 축이 서로 종속적인 관계를 가지고 있기 때문에 발생하는 문제로, 회전 연산 도중 축이 하나 사라져 3차원의 오브젝트가 일그러지는 현상

쿼터니온

사원수로 3개의 벡터 요소와 하나의 스칼라 요소로 구성(4개의 -1~1 사이의 값)
장점 : 연산속도가 빠르고, 짐벌락 현상 발생 X
단점 : 우리가 알고 있는 0~360의 각도가 아니기 때문에 특정 각도를 표현하기 힘들다.

유니티

transform.rotation : 게임오브젝트의 쿼터니온 회전 정보
transform.localScale : 게임오브젝트의 오일러 회전 정보
Quaternion q = Quaternion.Euler(0,0,0); - 오일러 회전 정보를 입력해서 쿼터니온 회전 값으로 변경
transform.Rotate(new Vector3(1,0,0)); - x축으로 빙글빙글 돌아라 와 같이 지속적인 회전 함수
GameObject clone = Instantiate(GameObject 원본, Vector3 position, Quarternion rotation);
원본 게임오브젝트를 복제해서 생성하고, 생성된 복제 오브젝트의 정보를 clone에 저장 clone과 원본 오브젝트는 동일.(clone의 정보가 바뀌면 Hierarchy View에 생성된 오브젝트의 정보가 바뀜)

Instantiate() 활용 예제

이번에는 Instantiate()를 활용하여 갈수록 점점 오브젝트의 위치와 회전이 변화되는 코드를 작성해보겠습니다.
using UnityEngine; public class ObjectSpawner : MonoBehaviour { [SerializeField] private GameObject boxPrefab; private void Awake() { for (int i = 0; i < 10; ++i) { Vector3 position = new Vector3(-4.5f + i, 0, 0); // 박스가 생성될 위치 결정 Quaternion rotation = Quaternion.Euler(0, 0, i * 10); // 박스의 회전 결정 Instantiate(boxPrefab, position, rotation); } } }
C#
복사
위의 코드는 반복문을 활용하여 position과 rotation 변수의 값을 점점 증가시킵니다. 그리고 Instantiate(boxPrefab, position, rotation); 에서 원하는 프리팹을 복제하고 위치와 회전의 변수에 할당된 값을 통해 오브젝트의 위치와 회전을 결정합니다. 이렇게 하면 오브젝트의 위치와 회전을 지속적으로 변화시킬 수 있습니다.

격자 형태로 오브젝트 생성

그럼 이번에는 Instantiate와 중첩 반복문을 활용하여 오브젝트를 격자 형태로 생성해보겠습니다.
using UnityEngine; public class ObjectSpawner : MonoBehaviour { [SerializeField] private GameObject boxPrefab; private void Awake() { // 외부 반복문 (격자의 y축 계산용으로 활용) for (int y = 0; y < 10; ++y) { for (int x = 0; x < 10; ++x) { // 내부 반복문 (격자의 x축 계산용으로 활용) Vector3 position = new Vector3(-4.5f + x, 4.5f - y, 0); Instantiate(boxPrefab, position, Quaternion.identity); } } }
C#
복사
위의 코드에서 Instantiate(boxPrefab, position, Quaternion.identity);를 보시면 position 변수는 계속 변화하는데 Quaternion.identity를 통해 회전 값은 (0,0,0)으로 변화시키지 않는 것을 볼 수 있습니다. 이렇게 중첩반복문을 통해 오브젝트를 생성하게 되면 격자 형태로 구현할 수 있습니다. 이는 주로 맵을 만들 때 유용하게 사용할 수 있습니다.

배열을 이용하여 오브젝트 위치를 무작위로 생성

이번에는 배열을 이용하여 오브젝트의 위치를 무작위로 생성해보겠습니다.
using UnityEngine; public class ObjectSpawner : MonoBehaviour { [SerializeField] private GameObject[] PrefabArray; private void Awake() { for (int i = 0; i < 10; ++i) { int index = Random.Range(0, PrefabArray.Length); Vector3 position = new Vector3(-4.5f + i, 0, 0); Instantiate(PrefabArray[index], position, Quaternion.identity); } } }
C#
복사
오브젝트를 배열로 생성할 때는 배열을 만들어줘야하는데요. private GameObject[] PrefabArray;를 통해 PrefabArray라는 변수를 배열로 사용하였습니다. 이후 0부터 배열의 크기 중 무작위 수를 할당하여 위치를 변화하게 합니다.

PrefabArray 배열 길이 설정

PrefabArray 배열의 길이를 설정하는 방법에 대해 알아보겠습니다.
배열의 크기를 바꾸는 방법에는 코드로 설정하는 방법과 유니티 인스펙터 창에서 설정하는 방법이 있습니다.
위의 코드는 유니티 인스펙터 창에서 설정하는 코드입니다. 위의 코드를 작성한 후 ObjectSpawner 스크립트를 원하는 게임 오브젝트의 컴포넌트에 추가한 후 인스펙터 창에서 PrefabArray에 원하는 크기를 입력해주시면 됩니다. 이렇게 유니티 인스펙터 창에서 배열을 초기화하게 되면 코드 수정 없이 유니티 에디터에서 바로 변경할 수 있습니다.
만약 코드로 설정하고 싶다면 아래 코드처럼 사용하시면 됩니다.
using UnityEngine; public class ObjectSpawner : MonoBehaviour { [SerializeField] private GameObject[] prefabArray; // 임의로 설정한 프리팹 배열의 크기 및 할당 (디버그용 예제) [SerializeField] private GameObject prefab1; [SerializeField] private GameObject prefab2; [SerializeField] private GameObject prefab3; private void Awake() { // 배열 크기 설정 prefabArray = new GameObject[3]; // 프리팹 할당 prefabArray[0] = prefab1; prefabArray[1] = prefab2; prefabArray[2] = prefab3; for (int i = 0; i < 10; ++i) { int index = Random.Range(0, prefabArray.Length); Vector3 position = new Vector3(-4.5f + i, 0, 0); Instantiate(prefabArray[index], position, Quaternion.identity); } } }
C#
복사
이렇게 코드를 통해 배열을 초기화하게 되면 게임 플레이 중에 동적으로 배열을 할당할 수 있게 됩니다.

삼항연산자

이번에는 삼항연산자의 정의와 활용 예제를 알아보겠습니다.
D = A ? B : C;
A의 조건문이 참이면 B를 D에 저장하고, 거짓이면 C를 D에 저장
아래의 코드와 동일한 결과
if(A) { D = B; } else { D = C; }
using UnityEngine; public class ObjectSpawner : MonoBehaviour { [SerializeField] private int objectSpawnCount = 30; [SerializeField] private GameObject[] PrefabArray; [SerializeField] private Transform[] SpawnPointArray; private void Awake() { for (int i = 0; i < objectSpawnCount; ++i) { int prefabIndex = Random.Range(0, PrefabArray.Length); int spawnIndex = Random.Range(0, SpawnPointArray.Length); Vector3 position = SpawnPointArray[spawnIndex].position; GameObject clone = Instantiate(PrefabArray[prefabIndex], position, Quaternion.identity); // spawnIndex가 0인 오브젝트가 왼쪽에 있기 때문에 오른쪽으로 이동 // spawnIndex가 1인 오브젝트가 오른쪽에 있기 때문에 왼쪽으로 이동 Vector3 moveDirection = (spawnIndex == 0 ? Vector3.right : Vector3.left); clone.GetComponent<Movement2D>().Setup(moveDirection); } } }
C#
복사
using UnityEngine; public class Movement2D : MonoBehaviour { private float MoveSpeed = 5.0f; // 이동속도 private Vector3 moveDirection; public void Setup(Vector3 direction) { moveDirection = direction; } private void Update() { // 새로운 위치 = 현재 위치 + (방향 * 속도) transform.position += moveDirection * MoveSpeed * Time.deltaTime; } }
C#
복사

생성되는 함수를 시간에 따라 하나씩 오브젝트 생성

using UnityEngine; public class ObjectSpawner : MonoBehaviour { [SerializeField] private int ObjectSpawnCount = 30; [SerializeField] private GameObject[] PrefabArray; [SerializeField] private Transform[] SpawnPointArray; private int CurrentObjectCount = 0; //현재까지 생성한 오브젝트 개수 private float ObjectSpawnTime = 0.0f; private void Update() { // ObjectSpawnCount 개수만큼만 생성하고 더이상 생성하지 않도록 하기 위해 설정 if (CurrentObjectCount + 1 > ObjectSpawnCount) { return; } // 원하는 시간마다 오브젝트를 생성하기 위한 시간 변수 연산 ObjectSpawnTime += Time.deltaTime; // 0.5초에 한번씩 실행 if (ObjectSpawnTime >= 0.5f) { int prefabIndex = Random.Range(0, PrefabArray.Length); int SpawnIndex = Random.Range(0, SpawnPointArray.Length); Vector3 position = SpawnPointArray[SpawnIndex].position; GameObject clone = Instantiate(PrefabArray[prefabIndex], position, Quaternion.identity); // SpawnIndex가 0인 오브젝트가 왼쪽에 있기 때문에 오른쪽으로 이동 // spawnIndex가 1인 오브젝트가 오른쪽에 있기 때문에 왼쪽으로 이동 Vector3 moveDirection = (SpawnIndex == 0 ? Vector3.right : Vector3.left); clone.GetComponent<Movement2D>().Setup(moveDirection); CurrentObjectCount++; // 현재 생성된 오브젝트의 개수를 1 증가 ObjectSpawnTime = 0.0f; // 시간을 0으로 초기화해야 다시 0.5초 계산 가능 } } }
C#
복사

플레이어 위치에서 오브젝트 생성

using UnityEngine; public class Player : MonoBehaviour { [SerializeField] private KeyCode KeyCodeFire = KeyCode.Space; [SerializeField] private GameObject bulletPrefab; private float MoveSpeed = 3.0f; private Vector3 LastMoveDirection = Vector3.right; private void Update() { // 플레이어 오브젝트 이동 float x = Input.GetAxisRaw("Horizontal"); float y = Input.GetAxisRaw("Vertical"); transform.position += new Vector3(x, y, 0) * MoveSpeed * Time.deltaTime; // 마지막에 입력된 방향키의 방향을 총알의 발사 방향으로 활용 if (x != 0 || y != 0) { LastMoveDirection = new Vector3 (x, y, 0); } // 플레이어 오브젝트 총알 발사 if (Input.GetKeyDown(KeyCodeFire)) { GameObject clone = Instantiate(bulletPrefab, transform.position, Quaternion.identity); clone.name = "Bullet"; clone.transform.localScale = Vector3.one * 0.5f; clone.GetComponent<SpriteRenderer>().color = Color.red; clone.GetComponent<Movement2D>().Setup(LastMoveDirection); } } }
C#
복사