카테고리 없음

Unity에서 다른 스크립트 가져오는 방법 정리

wook101118 2026. 5. 21. 07:30

GetComponent, SerializeField, 싱글톤까지

Unity에서 게임을 만들다 보면 한 스크립트에서 다른 스크립트의 변수나 함수를 사용해야 하는 경우가 많다.

예를 들어 플레이어가 적에게 맞았을 때 PlayerHealth의 체력을 줄이거나, 총알이 몬스터와 충돌했을 때 Enemy의 TakeDamage() 함수를 실행하는 상황이 있다.

이때 다른 스크립트를 가져오는 방법은 여러 가지가 있다.

대표적으로는 다음과 같은 방법들이 있다.


1. 같은 오브젝트에 붙어 있는 스크립트 가져오기

가장 기본적인 방법은 GetComponent를 사용하는 것이다.

 
PlayerHealth hp = GetComponent<PlayerHealth>();
hp.TakeDamage(10);
 

이 코드는 현재 스크립트가 붙어 있는 같은 GameObject에서 PlayerHealth 컴포넌트를 찾는다.

예를 들어 하나의 Player 오브젝트에 이런 식으로 스크립트가 붙어 있다고 하자.

 
Player
 ├ PlayerMove
 └ PlayerHealth
 

이때 PlayerMove 안에서 PlayerHealth를 가져오고 싶다면 GetComponent<PlayerHealth>()를 사용할 수 있다.

조금 더 안전하게 쓰려면 TryGetComponent를 사용한다.

 
if (TryGetComponent(out PlayerHealth hp))
{
    hp.TakeDamage(10);
}
 

TryGetComponent는 해당 컴포넌트가 있을 때만 실행되기 때문에, 없는 컴포넌트를 가져오다가 오류가 나는 상황을 줄일 수 있다.


2. 자식 오브젝트에서 스크립트 가져오기

자식 오브젝트에 붙어 있는 스크립트를 가져올 때는 GetComponentInChildren을 사용한다.

 
Weapon weapon = GetComponentInChildren<Weapon>();
 

예를 들어 구조가 이렇게 되어 있다고 하자.

 
Player
 └ Weapon
     └ Weapon.cs
 

이때 Player에 붙은 스크립트에서 자식인 Weapon 오브젝트의 Weapon 스크립트를 가져올 수 있다.


3. 부모 오브젝트에서 스크립트 가져오기

반대로 부모 오브젝트 쪽에 있는 스크립트를 가져올 때는 GetComponentInParent를 사용한다.

 
Player player = GetComponentInParent<Player>();
 

예를 들어 총알이나 무기가 Player의 자식 오브젝트로 들어가 있는 경우, 자식 오브젝트에서 부모인 Player의 스크립트를 가져올 때 사용할 수 있다.


4. 충돌한 오브젝트에서 스크립트 가져오기

충돌한 대상의 스크립트를 가져올 때도 GetComponent를 많이 사용한다.

 
private void OnCollisionEnter2D(Collision2D collision)
{
    Bullet bullet = collision.gameObject.GetComponent<Bullet>();

    if (bullet != null)
    {
        int damage = bullet.damage;
    }
}
 

트리거를 사용하는 경우에는 이렇게 쓴다.

 
private void OnTriggerEnter2D(Collider2D other)
{
    Bullet bullet = other.GetComponent<Bullet>();

    if (bullet != null)
    {
        int damage = bullet.damage;
    }
}
 

충돌한 오브젝트가 총알인지 확인하고, 총알이라면 데미지 값을 가져오는 방식이다.

더 깔끔하게 쓰려면 TryGetComponent를 사용할 수 있다.

 
private void OnTriggerEnter2D(Collider2D other)
{
    if (other.TryGetComponent(out Bullet bullet))
    {
        int damage = bullet.damage;
    }
}
 

5. 이름으로 GameObject 찾기

씬 안에서 이름으로 오브젝트를 찾는 방법도 있다.

 
GameObject playerObj = GameObject.Find("Player");
Player player = playerObj.GetComponent<Player>();
 

이 방식은 간단하지만 단점이 있다.

오브젝트 이름이 "Player"에서 "Player_01"처럼 바뀌면 코드가 작동하지 않을 수 있다.

그래서 많이 쓰는 것은 추천하지 않는다.


6. 태그로 GameObject 찾기

이름 대신 태그로 찾는 방법도 있다.

 
GameObject playerObj = GameObject.FindWithTag("Player");
Player player = playerObj.GetComponent<Player>();
 

이 방식은 이름으로 찾는 것보다 조금 더 안정적이다.

하지만 오브젝트에 태그를 미리 설정해줘야 한다.

예를 들어 Player 오브젝트에 Player 태그가 붙어 있어야 한다.


7. 씬 전체에서 타입으로 찾기

씬 안에서 특정 타입의 스크립트를 가진 오브젝트를 찾고 싶다면 FindFirstObjectByType을 사용할 수 있다.

 
Player player = FindFirstObjectByType<Player>();
 

씬 안에 있는 Player 스크립트를 찾아서 가져오는 코드다.

여러 개를 찾고 싶다면 이렇게 쓴다.

 
Enemy[] enemies = FindObjectsByType<Enemy>(FindObjectsSortMode.None);
 

단, Find 계열 함수들은 씬 전체를 검색하기 때문에 자주 사용하면 무거울 수 있다.

특히 Update() 안에서 매 프레임 사용하는 것은 좋지 않다.

 
private void Update()
{
    Player player = FindFirstObjectByType<Player>(); // 비추천
}
 

보통은 Awake()나 Start()에서 한 번만 찾아서 변수에 저장해 둔다.

 
private Player player;

private void Start()
{
    player = FindFirstObjectByType<Player>();
}
 

8. transform.Find로 특정 자식 찾기

특정 이름을 가진 자식 오브젝트를 찾을 때는 transform.Find를 사용할 수 있다.

 
Transform child = transform.Find("Weapon");
Weapon weapon = child.GetComponent<Weapon>();
 

이 코드는 현재 오브젝트의 자식 중에서 이름이 "Weapon"인 오브젝트를 찾고, 그 오브젝트에서 Weapon 스크립트를 가져온다.

하지만 이것도 이름에 의존하기 때문에 이름이 바뀌면 문제가 생길 수 있다.


9. 함수 매개변수로 참조 넘겨받기

다른 스크립트가 직접 참조를 넘겨주는 방식도 있다.

 
public class Enemy : MonoBehaviour
{
    private Player targetPlayer;

    public void SetTarget(Player player)
    {
        targetPlayer = player;
    }
}
 

다른 스크립트에서 이렇게 호출할 수 있다.

 
enemy.SetTarget(player);
 

이 방식은 스크립트끼리 직접 찾는 것이 아니라, 필요한 대상을 외부에서 넣어주는 방식이다.

구조가 커질수록 이런 방식이 더 깔끔해질 때가 많다.


10. 이벤트로 연결하기

이벤트를 사용하면 스크립트끼리 직접 강하게 연결하지 않고 신호를 보낼 수 있다.

 
public event System.Action<int> OnDamaged;
 

예를 들어 플레이어가 데미지를 받았을 때 UI가 체력바를 갱신해야 한다고 하자.

이때 UI가 매번 PlayerHealth를 직접 확인하는 것이 아니라, PlayerHealth가 “체력이 바뀌었다”는 신호를 보내고 UI가 그 신호를 받아 처리할 수 있다.

간단한 예시는 다음과 같다.

 
public class PlayerHealth : MonoBehaviour
{
    public event System.Action<int> OnHealthChanged;

    private int hp = 100;

    public void TakeDamage(int damage)
    {
        hp -= damage;
        OnHealthChanged?.Invoke(hp);
    }
}
 

이벤트는 처음에는 조금 어렵지만, UI, 사운드, 퀘스트 시스템처럼 여러 시스템이 연결될 때 유용하다.


11. SerializeField로 인스펙터에서 직접 연결하기

Unity에서 가장 많이 쓰는 방법 중 하나가 SerializeField를 이용하는 것이다.

 
[SerializeField] private PlayerHealth playerHealth;
 

이렇게 작성하면 변수는 private 상태를 유지하면서도 Unity 인스펙터에서 직접 오브젝트를 연결할 수 있다.

예를 들어 EnemyAttack 스크립트에서 PlayerHealth를 사용하고 싶다면 이렇게 할 수 있다.

 
public class EnemyAttack : MonoBehaviour
{
    [SerializeField] private PlayerHealth playerHealth;

    public void Attack()
    {
        playerHealth.TakeDamage(10);
    }
}
 

이 방식의 장점은 코드에서 직접 찾지 않아도 된다는 점이다.

GetComponent, Find, FindWithTag를 사용하지 않고도 Unity 에디터에서 직접 연결할 수 있다.

SerializeField의 장점

SerializeField의 장점은 다음과 같다.

첫째, private 변수를 유지할 수 있다.

 
[SerializeField] private int damage;
 

그냥 public으로 만들면 다른 스크립트에서 마음대로 접근할 수 있다.

 
public int damage;
 

하지만 SerializeField를 사용하면 외부 스크립트에서는 함부로 접근하지 못하게 하면서, Unity 인스펙터에서는 값을 수정할 수 있다.

즉, 캡슐화를 어느 정도 지킬 수 있다.

둘째, 씬에 있는 특정 오브젝트를 직접 연결할 수 있다.

 
[SerializeField] private Transform firePoint;
[SerializeField] private GameObject bulletPrefab;
 

이런 식으로 총알 발사 위치나 프리팹을 연결할 때도 많이 사용한다.

SerializeField의 단점

단점도 있다.

인스펙터에서 연결을 안 해두면 NullReferenceException 오류가 날 수 있다.

 
playerHealth.TakeDamage(10);
 

여기서 playerHealth가 비어 있으면 오류가 발생한다.

그래서 중요한 참조는 실행 전에 제대로 연결되어 있는지 확인하는 습관이 좋다.

 
private void Awake()
{
    if (playerHealth == null)
    {
        Debug.LogError("PlayerHealth가 연결되지 않았습니다.");
    }
}
 

12. 싱글톤 패턴으로 가져오기

싱글톤 패턴은 특정 클래스의 인스턴스를 하나만 만들어서 어디서든 쉽게 접근할 수 있게 하는 방식이다.

Unity에서는 보통 Manager 계열 스크립트에 많이 사용한다.

예를 들어 GameManager, SoundManager, UIManager 같은 스크립트에 사용한다.

 
public class GameManager : MonoBehaviour
{
    public static GameManager Instance;

    private void Awake()
    {
        Instance = this;
    }
}
 

이렇게 해두면 다른 스크립트에서 다음과 같이 접근할 수 있다.

 
GameManager.Instance.GameOver();
 

예시를 조금 더 완성하면 다음과 같다.

 
public class GameManager : MonoBehaviour
{
    public static GameManager Instance;

    public int score;

    private void Awake()
    {
        Instance = this;
    }

    public void AddScore(int amount)
    {
        score += amount;
    }
}
 

다른 스크립트에서는 이렇게 사용할 수 있다.

 
GameManager.Instance.AddScore(100);
 

싱글톤을 조금 더 안전하게 쓰기

위 코드는 간단하지만, 같은 GameManager가 두 개 생기면 문제가 생길 수 있다.

그래서 보통은 이렇게 작성한다.

 
public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }

    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }

        Instance = this;
    }
}
 

이렇게 하면 이미 Instance가 존재할 경우 새로 생긴 GameManager를 제거할 수 있다.

씬이 바뀌어도 유지하고 싶다면 DontDestroyOnLoad를 사용할 수 있다.

 
private void Awake()
{
    if (Instance != null && Instance != this)
    {
        Destroy(gameObject);
        return;
    }

    Instance = this;
    DontDestroyOnLoad(gameObject);
}
 

싱글톤의 장점

싱글톤은 접근이 쉽다.

 
SoundManager.Instance.PlaySound();
UIManager.Instance.OpenShop();
GameManager.Instance.GameOver();
 

이런 식으로 어디서든 바로 사용할 수 있다.

특히 사운드, 게임 상태, 전체 UI 관리처럼 프로젝트 전체에서 하나만 있어야 하는 시스템에 편하다.

싱글톤의 단점

하지만 싱글톤을 너무 많이 쓰면 코드가 서로 강하게 연결된다.

예를 들어 여러 스크립트에서 전부 GameManager.Instance에 의존하면, 나중에 구조를 바꾸기 어려워진다.

또 테스트하기도 어려워지고, 어떤 스크립트가 어떤 매니저에 의존하는지 한눈에 보기 힘들어진다.

그래서 싱글톤은 아무 스크립트에나 쓰기보다는 정말 전역으로 하나만 존재해야 하는 매니저에만 사용하는 것이 좋다.

추천 예시는 다음과 같다.

 
GameManager
SoundManager
UIManager
SceneLoader
SaveManager
 

반대로 이런 것들은 싱글톤으로 만들지 않는 것이 좋다.

 
PlayerHealth
Enemy
Bullet
Item
Weapon
 

이런 오브젝트들은 여러 개 생길 수 있거나, 특정 대상에만 붙어 있는 경우가 많기 때문이다.


13. 방법별 추천 상황 정리

상황추천 방법
같은 오브젝트에 붙은 스크립트 가져오기 GetComponent, TryGetComponent
자식 오브젝트에서 가져오기 GetComponentInChildren
부모 오브젝트에서 가져오기 GetComponentInParent
충돌한 대상에서 가져오기 collision.gameObject.GetComponent, other.GetComponent
인스펙터에서 직접 연결하기 [SerializeField]
씬 전체에서 하나 찾기 FindFirstObjectByType
여러 개 찾기 FindObjectsByType
게임 전체 매니저 접근 싱글톤 패턴
구조를 깔끔하게 만들기 함수 매개변수, 이벤트

14. 초보자 기준 추천 순서

초보자라면 처음부터 너무 어려운 구조를 쓰기보다는 상황에 맞게 쉬운 방법부터 사용하는 것이 좋다.

1순위: GetComponent 계열

같은 오브젝트, 부모, 자식 관계가 명확하다면 GetComponent 계열을 쓰는 것이 좋다.

 
GetComponent<PlayerHealth>();
GetComponentInChildren<Weapon>();
GetComponentInParent<Player>();
 

2순위: SerializeField

특정 오브젝트나 프리팹을 직접 연결해야 한다면 SerializeField가 좋다.

 
[SerializeField] private GameObject bulletPrefab;
[SerializeField] private Transform firePoint;
[SerializeField] private PlayerHealth playerHealth;
 

인스펙터에서 직접 연결할 수 있어서 편하고, 코드도 깔끔해진다.

3순위: FindFirstObjectByType

씬 안에서 하나만 존재하는 오브젝트를 찾을 때 사용할 수 있다.

 
private Player player;

private void Start()
{
    player = FindFirstObjectByType<Player>();
}
 

다만 Update() 안에서 계속 찾는 것은 피해야 한다.

4순위: 싱글톤

게임 전체에서 하나만 있어야 하는 매니저라면 싱글톤을 사용할 수 있다.

 
GameManager.Instance.GameOver();
SoundManager.Instance.PlaySound();
 

하지만 싱글톤은 너무 많이 사용하면 코드가 복잡해질 수 있으므로 꼭 필요한 곳에만 사용하는 것이 좋다.


15. 결론

Unity에서 다른 스크립트를 가져오는 방법은 다양하다.

가장 기본은 GetComponent이고, 인스펙터에서 직접 연결하고 싶을 때는 SerializeField를 사용한다.

게임 전체에서 하나만 존재해야 하는 매니저는 싱글톤 패턴을 사용할 수 있다.

하지만 모든 상황에서 싱글톤이나 Find를 쓰는 것은 좋지 않다.

상황에 따라 적절한 방법을 고르는 것이 중요하다.

정리하면 다음과 같다.

 
// 같은 오브젝트
GetComponent<PlayerHealth>();

// 자식 오브젝트
GetComponentInChildren<Weapon>();

// 부모 오브젝트
GetComponentInParent<Player>();

// 인스펙터 연결
[SerializeField] private PlayerHealth playerHealth;

// 씬에서 타입으로 찾기
FindFirstObjectByType<Player>();

// 싱글톤
GameManager.Instance.AddScore(100);
 

처음에는 GetComponent, TryGetComponent, GetComponentInChildren, SerializeField를 중심으로 익히는 것이 좋다.

그다음 프로젝트가 커지면 싱글톤, 이벤트, 매개변수 전달 방식까지 함께 사용하면 더 깔끔한 구조를 만들 수 있다.