Unity에서 초기화란 변수, 컴포넌트, 오브젝트를 처음 사용할 수 있는 상태로 준비하는 작업을 말한다.
게임을 만들다 보면 체력, 이동 속도, 공격력, 타이머 같은 값을 처음에 정해줘야 한다.
또 Rigidbody2D, Animator, PlayerHealth 같은 컴포넌트도 미리 가져와야 한다.
이런 준비 과정을 초기화라고 한다.
예를 들어 다음과 같은 코드가 있다.
private int hp;
private float speed;
private Rigidbody2D rb;
이 변수들은 선언만 된 상태이다.
아직 실제 값이나 컴포넌트가 제대로 들어가 있지 않다.
그래서 보통 Awake()나 Start()에서 값을 넣어준다.
private void Awake()
{
hp = 100;
speed = 5f;
rb = GetComponent<Rigidbody2D>();
}
이렇게 하면 게임이 시작될 때 필요한 값과 컴포넌트가 준비된다.
즉, 초기화는 게임이 제대로 동작하기 위한 준비 단계라고 볼 수 있다.
초기화를 왜 해야 할까?
초기화를 하는 이유는 크게 4가지가 있다.
첫 번째는 변수에 시작값을 넣기 위해서이다.
private int hp;
private void Start()
{
hp = 100;
}
체력의 시작값이 정해져 있지 않으면 게임에서 체력을 제대로 사용할 수 없다.
두 번째는 컴포넌트를 미리 가져오기 위해서이다.
private Rigidbody2D rb;
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
}
이렇게 미리 가져와야 나중에 이동 코드에서 사용할 수 있다.
rb.linearVelocity = new Vector2(5f, rb.linearVelocity.y);
세 번째는 NullReferenceException 오류를 막기 위해서이다.
예를 들어 다음 코드가 있다고 하자.
private PlayerHealth playerHealth;
private void Start()
{
playerHealth.TakeDamage(10);
}
playerHealth에 아무것도 들어있지 않다면 오류가 난다.
이런 오류를 막기 위해 먼저 참조를 연결해야 한다.
private void Awake()
{
playerHealth = GetComponent<PlayerHealth>();
}
네 번째는 오브젝트를 다시 사용할 때 이전 값을 정리하기 위해서이다.
예를 들어 오브젝트 풀링을 사용할 때 총알이나 몬스터는 꺼졌다가 다시 켜질 수 있다.
이때 이전에 사용하던 값이 남아 있으면 문제가 생길 수 있다.
private void OnEnable()
{
lifeTime = 3f;
speed = 10f;
}
이렇게 켜질 때마다 값을 다시 설정해주면 안전하다.
초기화의 기본 예시
플레이어 이동 스크립트를 예로 들어보자.
using UnityEngine;
public class PlayerMove : MonoBehaviour
{
private Rigidbody2D rb;
private float speed;
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
speed = 5f;
}
private void Update()
{
float x = Input.GetAxisRaw("Horizontal");
rb.linearVelocity = new Vector2(x * speed, rb.linearVelocity.y);
}
}
여기서 초기화에 해당하는 부분은 이 부분이다.
rb = GetComponent<Rigidbody2D>();
speed = 5f;
rb에는 Rigidbody2D 컴포넌트를 넣고,
speed에는 이동 속도의 시작값을 넣었다.
이렇게 해야 Update()에서 rb와 speed를 안전하게 사용할 수 있다.
Awake에서 초기화하기
Awake()는 오브젝트가 생성되거나 활성화될 때 가장 먼저 실행되는 Unity 이벤트 함수이다.
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
}
Awake()는 보통 내 오브젝트 안에 있는 컴포넌트를 가져올 때 많이 사용한다.
예를 들어:
private Rigidbody2D rb;
private Animator animator;
private SpriteRenderer spriteRenderer;
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
animator = GetComponent<Animator>();
spriteRenderer = GetComponent<SpriteRenderer>();
}
이런 식으로 현재 오브젝트에 붙어 있는 컴포넌트를 미리 가져온다.
Awake에서 자주 하는 초기화
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
animator = GetComponent<Animator>();
playerHealth = GetComponent<PlayerHealth>();
}
Awake()는 보통 다음 상황에 사용한다.
| 컴포넌트 가져오기 | GetComponent<Rigidbody2D>() |
| 싱글톤 인스턴스 설정 | Instance = this; |
| 내부 변수 기본값 설정 | hp = maxHp; |
Start에서 초기화하기
Start()는 Awake()가 모두 실행된 뒤, 첫 번째 Update() 전에 한 번 실행된다.
private void Start()
{
hp = maxHp;
}
Start()는 보통 다른 오브젝트의 초기화가 끝난 뒤에 실행되어야 하는 작업에 사용한다.
예를 들어 플레이어가 GameManager나 UIManager를 참조해야 한다면 Start()에서 처리하는 경우가 많다.
private GameManager gameManager;
private void Start()
{
gameManager = GameManager.Instance;
}
Start에서 자주 하는 초기화
private void Start()
{
hp = maxHp;
score = 0;
timer = 0f;
}
Start()는 보통 다음 상황에 사용한다.
| 게임 시작값 설정 | hp = maxHp; |
| 다른 오브젝트와 연결 | GameManager.Instance |
| UI 초기 표시 | hpText.text = hp.ToString(); |
Awake와 Start의 차이
Awake()와 Start()는 둘 다 초기화에 자주 사용되지만, 실행 순서와 용도가 조금 다르다.
| 호출 주체 | Unity가 자동 호출 | Unity가 자동 호출 |
| 실행 시점 | 더 먼저 실행 | Awake 이후 실행 |
| 실행 횟수 | 한 번 | 한 번 |
| 주 용도 | 컴포넌트 참조 연결 | 게임 시작값 설정 |
| 예시 | GetComponent | hp = maxHp |
간단히 정리하면 다음과 같다.
private void Awake()
{
// 내 컴포넌트를 미리 가져오기
rb = GetComponent<Rigidbody2D>();
}
private void Start()
{
// 게임 시작 후 값 설정
hp = maxHp;
}
쉽게 말하면,
Awake = 먼저 준비
Start = 시작 직전 세팅
이라고 생각하면 된다.
OnEnable에서 초기화하기
OnEnable()은 오브젝트가 활성화될 때마다 실행된다.
private void OnEnable()
{
hp = 100;
}
Awake()와 Start()는 보통 한 번만 실행되지만, OnEnable()은 오브젝트가 꺼졌다 켜질 때마다 실행된다.
이 점이 중요하다.
예를 들어 총알을 오브젝트 풀링으로 관리한다고 하자.
public class Bullet : MonoBehaviour
{
private float lifeTime;
private void OnEnable()
{
lifeTime = 3f;
}
private void Update()
{
lifeTime -= Time.deltaTime;
if (lifeTime <= 0)
{
gameObject.SetActive(false);
}
}
}
총알이 다시 켜질 때마다 lifeTime이 3초로 초기화된다.
만약 초기화를 하지 않으면, 이전에 0초가 된 값이 남아서 켜지자마자 바로 꺼질 수 있다.
OnEnable에서 자주 하는 초기화
private void OnEnable()
{
hp = maxHp;
timer = 0f;
isDead = false;
}
OnEnable()은 보통 다음 상황에 사용한다.
| 오브젝트 풀링 | 총알, 몬스터 재사용 |
| 켜질 때마다 값 리셋 | timer = 0f |
| 이벤트 구독 | OnDamaged += UpdateHpUI |
Init이란 무엇인가?
Init은 Initialize의 줄임말이다.
뜻은 초기화하다이다.
하지만 중요한 점은 Init()은 Unity가 자동으로 실행해주는 함수가 아니라는 것이다.
Awake(), Start(), OnEnable()은 Unity가 자동으로 호출한다.
하지만 Init()은 개발자가 직접 만들어서 직접 호출해야 한다.
public void Init(int hp, float speed)
{
this.hp = hp;
this.speed = speed;
}
이 함수는 보통 외부에서 값을 넣어주며 오브젝트를 준비시킬 때 사용한다.
Init 사용 예시
몬스터를 생성할 때 체력과 이동 속도를 정해주고 싶다고 하자.
public class Enemy : MonoBehaviour
{
private int hp;
private float speed;
public void Init(int hp, float speed)
{
this.hp = hp;
this.speed = speed;
}
}
다른 스크립트에서 몬스터를 만들고 Init()을 호출한다.
Enemy enemy = Instantiate(enemyPrefab).GetComponent<Enemy>();
enemy.Init(100, 3f);
이 코드는 다음과 같은 의미이다.
몬스터 체력 = 100
몬스터 속도 = 3
즉, Init()은 생성된 오브젝트에게 필요한 값을 넣어주는 초기화 함수이다.
Bullet에서 Init 사용하기
총알은 생성될 때마다 데미지, 속도, 방향이 달라질 수 있다.
이럴 때 Init()을 사용하면 좋다.
using UnityEngine;
public class Bullet : MonoBehaviour
{
private int damage;
private float speed;
private Vector2 direction;
public void Init(int damage, float speed, Vector2 direction)
{
this.damage = damage;
this.speed = speed;
this.direction = direction.normalized;
}
private void Update()
{
transform.Translate(direction * speed * Time.deltaTime);
}
}
총알을 생성하는 쪽에서는 이렇게 사용할 수 있다.
Bullet bullet = Instantiate(bulletPrefab).GetComponent<Bullet>();
bullet.Init(10, 15f, Vector2.right);
이렇게 하면 총알이 생성된 후 외부에서 필요한 값을 넣을 수 있다.
Awake, Start, OnEnable, Init 차이 정리
| Awake() | 자동 실행 | 가장 먼저 | 컴포넌트 참조 연결 |
| OnEnable() | 자동 실행 | 오브젝트가 켜질 때마다 | 재사용 값 초기화 |
| Start() | 자동 실행 | 첫 Update 전 1회 | 시작값 설정 |
| Init() | 직접 호출 | 원하는 시점 | 외부에서 값 주입 |
간단히 정리하면 다음과 같다.
private void Awake()
{
// 내 컴포넌트 가져오기
}
private void OnEnable()
{
// 켜질 때마다 값 리셋
}
private void Start()
{
// 게임 시작 후 값 설정
}
public void Init(int value)
{
// 외부에서 값 넣기
}
SerializeField와 초기화
초기화할 때 SerializeField를 같이 사용하는 경우도 많다.
[SerializeField] private int maxHp = 100;
[SerializeField] private float speed = 5f;
이렇게 하면 Unity 인스펙터에서 값을 조절할 수 있다.
그리고 실제 현재 체력은 시작할 때 초기화한다.
[SerializeField] private int maxHp = 100;
private int currentHp;
private void Start()
{
currentHp = maxHp;
}
여기서 maxHp는 최대 체력이고, currentHp는 현재 체력이다.
게임 시작 시 현재 체력을 최대 체력으로 맞춰주는 것이 초기화이다.
currentHp = maxHp;
SerializeField로 컴포넌트 연결하기
컴포넌트를 GetComponent로 가져오지 않고, 인스펙터에서 직접 연결할 수도 있다.
[SerializeField] private Rigidbody2D rb;
[SerializeField] private Animator animator;
이 방식은 코드에서 찾는 것이 아니라 Unity 에디터에서 직접 연결하는 방식이다.
public class PlayerMove : MonoBehaviour
{
[SerializeField] private Rigidbody2D rb;
[SerializeField] private float speed = 5f;
private void Update()
{
float x = Input.GetAxisRaw("Horizontal");
rb.linearVelocity = new Vector2(x * speed, rb.linearVelocity.y);
}
}
이 경우 rb를 Awake()에서 가져오지 않아도 된다.
대신 Unity 인스펙터에서 Rigidbody2D를 직접 넣어줘야 한다.
연결하지 않으면 rb가 비어 있어서 오류가 날 수 있다.
초기화할 때 자주 나는 오류
초기화에서 가장 자주 나는 오류는 NullReferenceException이다.
private Rigidbody2D rb;
private void Update()
{
rb.linearVelocity = Vector2.right;
}
위 코드에서 rb에 아무것도 넣지 않았다면 오류가 난다.
해결 방법은 Awake()에서 넣어주는 것이다.
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
}
또는 SerializeField로 직접 연결한다.
[SerializeField] private Rigidbody2D rb;
초기화 체크 코드
초기화가 제대로 되었는지 확인하고 싶다면 Debug.Log를 사용할 수 있다.
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
if (rb == null)
{
Debug.LogError("Rigidbody2D가 없습니다.");
}
}
또는 SerializeField로 연결한 값이 비어 있는지 확인할 수도 있다.
private void Awake()
{
if (playerHealth == null)
{
Debug.LogError("PlayerHealth가 연결되지 않았습니다.");
}
}
이런 코드를 넣어두면 어디서 문제가 생겼는지 찾기 쉽다.
초기화와 생성자는 다르다
C#에는 생성자라는 개념도 있다.
public class PlayerData
{
public int hp;
public PlayerData()
{
hp = 100;
}
}
생성자는 객체가 만들어질 때 실행된다.
하지만 Unity에서 MonoBehaviour를 상속받는 스크립트는 보통 생성자를 직접 사용하지 않는다.
public class Player : MonoBehaviour
{
public Player()
{
// Unity에서는 보통 이렇게 하지 않는다.
}
}
Unity의 컴포넌트는 new Player()로 만드는 것이 아니라, 오브젝트에 붙거나 AddComponent로 생성되기 때문이다.
그래서 Unity에서는 생성자 대신 Awake(), Start(), Init() 같은 방식을 많이 사용한다.
초기화 위치를 어떻게 정해야 할까?
초기화를 어디서 해야 할지 헷갈릴 수 있다.
간단히 기준을 잡으면 된다.
내 컴포넌트를 가져온다면 Awake
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
}
게임 시작값을 설정한다면 Start
private void Start()
{
currentHp = maxHp;
}
오브젝트가 켜질 때마다 리셋해야 한다면 OnEnable
private void OnEnable()
{
timer = 0f;
isDead = false;
}
외부에서 값을 넣어줘야 한다면 Init
public void Init(int damage, Vector2 direction)
{
this.damage = damage;
this.direction = direction;
}
초기화 예시: 플레이어 체력
using UnityEngine;
public class PlayerHealth : MonoBehaviour
{
[SerializeField] private int maxHp = 100;
private int currentHp;
private void Start()
{
currentHp = maxHp;
}
public void TakeDamage(int damage)
{
currentHp -= damage;
if (currentHp <= 0)
{
Die();
}
}
private void Die()
{
Debug.Log("플레이어 사망");
}
}
여기서 초기화는 이 부분이다.
currentHp = maxHp;
게임 시작 시 현재 체력을 최대 체력으로 맞춰준다.
초기화 예시: 몬스터 스포너
using UnityEngine;
public class EnemySpawner : MonoBehaviour
{
[SerializeField] private Enemy enemyPrefab;
[SerializeField] private Transform spawnPoint;
private void Start()
{
SpawnEnemy();
}
private void SpawnEnemy()
{
Enemy enemy = Instantiate(enemyPrefab, spawnPoint.position, Quaternion.identity);
enemy.Init(100, 3f);
}
}
몬스터 스크립트는 다음과 같다.
using UnityEngine;
public class Enemy : MonoBehaviour
{
private int hp;
private float speed;
public void Init(int hp, float speed)
{
this.hp = hp;
this.speed = speed;
}
}
이 구조에서는 EnemySpawner가 몬스터를 생성하고, Init()으로 체력과 속도를 넣어준다.
초기화 예시: 오브젝트 풀링
오브젝트 풀링에서는 OnEnable() 초기화가 중요하다.
using UnityEngine;
public class PooledBullet : MonoBehaviour
{
private float lifeTime;
private void OnEnable()
{
lifeTime = 3f;
}
private void Update()
{
lifeTime -= Time.deltaTime;
if (lifeTime <= 0)
{
gameObject.SetActive(false);
}
}
}
총알이 다시 켜질 때마다 lifeTime이 3초로 리셋된다.
오브젝트 풀링에서는 오브젝트가 새로 만들어지는 것이 아니라 재사용되기 때문에,
켜질 때마다 값을 다시 초기화해주는 것이 중요하다.
초기화할 때 주의할 점
초기화를 할 때는 몇 가지를 조심해야 한다.
첫 번째, Update()에서 계속 초기화하면 안 된다.
private void Update()
{
hp = 100; // 비추천
}
이렇게 하면 매 프레임 체력이 100으로 돌아간다.
그러면 데미지를 받아도 체력이 줄어들지 않는 문제가 생긴다.
두 번째, Find 계열 함수를 매 프레임 사용하지 않는 것이 좋다.
private void Update()
{
Player player = FindFirstObjectByType<Player>(); // 비추천
}
Find 계열은 씬에서 오브젝트를 검색하기 때문에 자주 사용하면 성능에 좋지 않다.
보통은 Awake()나 Start()에서 한 번만 찾아서 변수에 저장한다.
private Player player;
private void Start()
{
player = FindFirstObjectByType<Player>();
}
세 번째, 초기화 순서를 조심해야 한다.
어떤 스크립트는 아직 준비되지 않았는데 다른 스크립트가 먼저 접근하면 오류가 날 수 있다.
이럴 때는 Awake()와 Start()의 역할을 나눠서 사용하는 것이 좋다.
private void Awake()
{
// 내 컴포넌트 준비
}
private void Start()
{
// 다른 오브젝트와 연결
}
최종 정리
Unity에서 초기화란 변수와 컴포넌트를 처음 사용할 수 있는 상태로 준비하는 것이다.
초기화를 하는 이유는 다음과 같다.
| 시작값 설정 | 체력, 속도, 점수 같은 값을 정함 |
| 컴포넌트 연결 | Rigidbody2D, Animator 등을 가져옴 |
| 오류 방지 | NullReferenceException을 막음 |
| 재사용 준비 | 오브젝트 풀링에서 이전 값을 초기화함 |
| 외부 값 적용 | Init()으로 생성 후 값을 넣음 |
초기화에 자주 쓰는 함수는 다음과 같다.
| Awake() | 가장 먼저 실행, 컴포넌트 참조 연결 |
| Start() | 게임 시작 직전 실행, 시작값 설정 |
| OnEnable() | 오브젝트가 켜질 때마다 실행, 재사용 값 리셋 |
| Init() | 직접 호출하는 초기화 함수, 외부 값 전달 |
마지막으로 한 줄로 정리하면 다음과 같다.
초기화는 게임 오브젝트가 제대로 동작하기 전에 필요한 값과 연결을 미리 준비하는 과정이다.