Unity와 C#(Script)의 관계

Kyu hyunSung·2025년 1월 26일
0

Unity-dev

목록 보기
5/5

C#은 .Net의 프로그래밍 언어이다.

.Net이라는 오픈소스 프레임워크를 통해 것들을 할 수 있는데,

오늘은 Unity와의 상관관계에 대해서만 알아보도록 하자.


1.C#

좀 특이한 언어인게, C#은 JAVA와 C++이 짬뽕되어 있는 언어이다.

자바와의 공통점을 보자면,

  • JVM = .NET
  • 가비지 컬렉션을 통한 자동 메모리 관리
  • 클래스 기반의 객체 지향 프로그래밍
  • 단일 상속만 허용

C++과의 공통점을 보자면,

  • 구조체(struct) 지원
  • unsafe 코드에서 포인터 사용 가능
  • 연산자 오버로딩 지원
  • RAII(Resource Acquisition Is Initialization) 패턴 지원 (C#의 using 문)

등등이 있다. 실제 코드를 한번 봐보자,

using System; 
// 여기에 사용할 .NET 네임스페이스를 정의한다.
// 예: 제네릭 컬렉션 -> using System.Collections.Generic;
//     파일 입출력   -> using System.IO;
//     정규표현식    -> using System.Text.RegularExpressions;

namespace CSharpExample
{
    // 모든 C# 클래스는 한 네임스페이스 안에 선언한다.
    class Program
    {
        // 여기에 멤버 변수, 프로퍼티, 메소드 등을 선언한다.
        static void Main(string[] args)
        {
            //여기에 돌아갈 코드를 작성한다.
        }
    }
}

가장 기본적인 코드의 형태이다.

여기서 C# 종특을 보자면,

1.프로퍼티를 통한 간결한 getter/setter

// Java에서의 일반적인 getter/setter
public class PersonJava {
    private String name;
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
}

자바는 저렇게 일일이 Getter/setter를 해야한다 근데

// C#의 프로퍼티 구현
public class PersonCSharp {
    // 자동 구현 프로퍼티
    public string Name { get; set; }
    
    // 커스텀 로직이 필요한 경우
    private int age;
    public int Age {
        get { return age; }
        set {
            if (value >= 0) {
                age = value;
            }
        }
    }
}

// 사용 방법
var person = new Person();
person.Name = "Kim";  // 마치 변수처럼 사용
person.Age = 20;      // 하지만 내부적으로는 메서드가 동작

여기서는 미친 딸깍으로 get; set;을 지정해주면 된다. 필요한경우
아래처럼 커스텀해주면 된다.

2.LINQ

LINQ가 뭐냐면

LINQ(Language Integrated Query)는 C#과 같은 .NET 언어에서 데이터 소스와 상호 작용하는 간편한 방법을 제공하는 통합 쿼리 기능입니다.

라고 하는데 걍 쿼리문 쓸 수 있도록 하는 기능이다.

var numbers = new List<int> { 1, 2, 3, 4, 5 };

// 짝수만 골라서 2배로 만들기
var result = numbers
    .Where(n => n % 2 == 0)   // 짝수 선택
    .Select(n => n * 2);      // 2배로 만들기

// 같은 작업을 SQL 스타일로 작성
var result2 = 
    from n in numbers
    where n % 2 == 0
    select n * 2;
    

3.구조화된 비동기 프로그래밍 (async/await)

쉽게말해 동시작업임

// 기다림이 필요한 작업을 비동기로 처리
public async Task<string> GetDataFromServer() {
    // await: 이 작업이 끝날 때까지 기다림
    // 다른 코드는 계속 실행될 수 있음
    string result = await FetchDataAsync();
    return "처리된 데이터: " + result;
}

// 사용 방법
var data = await GetDataFromServer();
Console.WriteLine(data);

4. 확장 메서드

확장명 메서드를 사용하면 새 파생 형식을 만들거나 다시 컴파일하거나 원래 형식을 수정하지 않고도 기존 형식에 메서드를 "추가"할 수 있습니다.

확장 메서드는 정적 메서드이지만 확장 형식의 인스턴스 메서드인 것처럼 호출된다.


// string 클래스에 새로운 기능 추가
public static class StringExtensions {
    public static int GetWordCount(this string text) {
        return text.Split(' ').Length;
    }
}

// 사용 방법
string text = "Hello World";
int wordCount = text.GetWordCount();  // 마치 원래 있던 메서드처럼 사용

5. nullable 타입

Nullable은 변수형 타입을 의미하는데,
변수형을 Nullable로 만들면 값으로 Null을 가지지 못하는 변수형도 Null 값을 가질 수 있다.

// 일반 int는 null이 될 수 없음
int number = null;  // 컴파일 에러

// nullable int는 null이 가능
int? nullableNumber = null;  // 가능!

// 안전하게 사용하기
if (nullableNumber.HasValue) {
    Console.WriteLine(nullableNumber.Value);
}

6. 메모리관리 : 자동 관리 (GC) + 필요시 수동 관리 가능 (unsafe)

솔직히 쓸일이 있을까 싶기는 하지만, 기본적으로 자바와 같이 자동관리를 해줄 수 있다.

하지만 Unsafe 키워드를 사용하면 포인터를 사용해 메모리에 직접 접근 할 수 있다.

근데 임베디드쪽아니면 잘 안쓸듯;

// C# - 기본적으로는 GC가 관리
public class Person {
    public string Name { get; set; }
}

public void Example() {
    // Java처럼 GC가 관리
    var person = new Person { Name = "Kim" };
    
    // unsafe 키워드로 직접 메모리 관리도 가능
    unsafe {
        // 포인터를 사용한 직접 메모리 접근
        int* numbers = stackalloc int[3];
        numbers[0] = 1;
        numbers[1] = 2;
        numbers[2] = 3;
    }
}

7. 제네릭 : 런타임에도 타입 정보 유지

참조 타입: 코드 공유 + 타입 정보 유지
값 타입: 타입별 코드 생성 + 타입 정보 유지

즉, Object에 담긴 상태로 타입정보가 유지된다는것이다.

진짜 C++ JAVA 짬뽕


// C# 제네릭
// 런타임에도 타입 정보가 유지됨
public class Box<T> { 
    private T data;
    
    public void PrintTypeInfo() {
        // 런타임에 실제 타입 정보 사용 가능
        Console.WriteLine($"현재 박스의 타입: {typeof(T).Name}");
    }
}

public void Example() {
    var intBox = new Box<int>();
    var strBox = new Box<string>();
    
    intBox.PrintTypeInfo();  // 출력: "현재 박스의 타입: Int32"
    strBox.PrintTypeInfo();  // 출력: "현재 박스의 타입: String"
}

2. Unity Script

2-1. 동작방식

이제 Unity에서 동작은 어떤 방식으로 이루어질까 ?

유니티는 GameObject 라는 것이 있다.

이런식으로 Point Light 라는 GameObject가 있고,

바로 옆에는 Light라는 속성이 있다. 우리가 할 행동은 Component들을 만들거나 적용시켜,
원하는대로 동작하게 하는 것이 주요 목표이다.

간단한 예시를 보도록하자.

Person 이라는 GameObject가 있다. 우리는 여기에 값을 받을 수 있도록 Component를 만들어

속성을 적용시키고싶다.

그럴때 해야하는 것이 스크립트이다.

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

public class NewBehaviourScript : MonoBehaviour
{
  public int a = 0;
    // Start is called before the first frame update
    void Start()
    {
        
    }

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

우리는 Public 이라는 접근 지정자를 통해 "속성" 으로 만들 수 있다.

public int a = 0;

단지 이 필드만 넣고, Person오브젝트에 컴포넌트로 코드를 넣었을 뿐인데 이렇게 작동된다.

우리는 이 Unity API를 통해 이러한 작업을 수행해 원하는 목적을 이루게 될것이다.

그게 캐릭터건 연출이건..


3. Unity API

아래는 유니티가 제공하는 가장 기본적인 코드이다.

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

public class NewBehaviourScript : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

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

바로 봐야할 부분은 MonoBehaviour 를 상속받는다는 것이다.

기본적으로 나와있는 Start()Update()도 MonoBehaviour의 메서드를 가져다 쓰는것이다.

3-1. MonoBehaviour

기본 변수

변수명설명
runInEditMode특정 MonoBehaviour 인스턴스가 에디터 모드에서 실행되도록 허용합니다(에디터에서만 사용 가능).
useGUILayout이를 비활성화하면 GUI 레이아웃 단계를 건너뛸 수 있습니다.

Public 함수

함수명설명
CancelInvoke이 MonoBehaviour의 모든 Invoke 호출을 취소합니다.
Invoketime 초 후에 methodName 메서드를 호출합니다.
InvokeRepeatingtime 초 후에 methodName 메서드를 호출하고, 이후 repeatRate 초마다 반복합니다.
IsInvokingmethodName에 대한 호출이 대기 중인지 확인합니다.
StartCoroutine코루틴을 시작합니다.
StopAllCoroutines이 behaviour에서 실행 중인 모든 코루틴을 중지합니다.
StopCoroutine지정된 이름의 첫 번째 코루틴이나 routine에 저장된 코루틴을 중지합니다.

주요 생명주기 메시지

메시지설명
Awake스크립트 인스턴스가 로드될 때 호출됩니다.
Start스크립트가 활성화될 때 첫 Update 호출 직전에 실행됩니다.
UpdateMonoBehaviour가 활성화된 경우 매 프레임마다 호출됩니다.
FixedUpdate물리 연산을 위해 고정된 시간 간격으로 호출됩니다.
LateUpdate모든 Update 호출이 완료된 후 매 프레임 호출됩니다.

이벤트 메시지

메시지 종류관련 함수
렌더링 관련OnBecameVisible, OnBecameInvisible, OnPreRender, OnRenderObject, OnPostRender, OnRenderImage
충돌 관련OnCollisionEnter, OnCollisionStay, OnCollisionExit, OnTriggerEnter, OnTriggerStay, OnTriggerExit
입력 관련OnMouseDown, OnMouseUp, OnMouseDrag, OnMouseEnter, OnMouseExit, OnMouseOver
애플리케이션OnApplicationFocus, OnApplicationPause, OnApplicationQuit
네트워크OnConnectedToServer, OnDisconnectedFromServer, OnNetworkInstantiate
기타OnValidate, OnDestroy, OnEnable, OnDisable

상속된 주요 기능

카테고리기능
컴포넌트 접근GetComponent, GetComponentInChildren, GetComponentInParent
메시지 전달SendMessage, BroadcastMessage, SendMessageUpwards
오브젝트 관리Instantiate, Destroy, DontDestroyOnLoad
오브젝트 찾기FindObjectOfType, FindObjectsOfType

주요 속성

속성설명
enabled컴포넌트의 활성화 상태를 제어합니다.
gameObject이 컴포넌트가 부착된 게임 오브젝트입니다.
transform게임 오브젝트의 Transform 컴포넌트입니다.
tag게임 오브젝트의 태그입니다.
name게임 오브젝트의 이름입니다.

MonoBehaviour는 생명주기(LifeCycle) 과도 깊은 연관이 있다.

예를 들어, Awake()는 오브젝트가 생성될 때, Start()는 첫 프레임이 시작될 때, OnDestroy()는 오브젝트가 파괴될 때 호출된다.

**1. 초기화 (Initialization)**
 게임이 시작될 때 Awake와 OnEnable이 호출되고, Reset과 Start 함수가 실행됩니다. Start는 스크립트당 단 한 번만 호출되는 특징이 있습니다.
 
**2. 물리 업데이트 (Physics)**
FixedUpdate를 통해 물리 연산이 이루어집니다. 물리 엔진의 시간 간격이 실제 프레임 업데이트 시간보다 짧을 경우 한 프레임 안에서 여러 번 실행될 수 있습니다.

**3. 입력 처리 (Input events)**
사용자의 입력을 처리하는 단계로, 마우스나 키보드 등의 입력 이벤트를 처리합니다.

**4. 게임 로직 (Game logic)**
실제 게임의 동작을 처리하는 핵심 부분으로, 상태 머신 업데이트, 애니메이션 처리 등이 포함됩니다.

**5. 렌더링 (Rendering)**
장면 렌더링, GUI 렌더링 등 화면에 표시되는 모든 요소들을 그리는 단계입니다.
프레임 종료 및 정리
프레임의 마지막 단계로, 일시 정지나 종료 처리가 필요한 경우 처리됩니다.

정도의 순서가 될 것이다.

또한 MonoBehaviour는 코루틴(Coroutine)을 지원하여 시간에 따른 동작을 제어할 수 있게 해주며, 다양한 이벤트 함수들을 통해 충돌 감지, 렌더링, 입력 처리 등의 기능을 제공한다.

여기서 코루틴은

// IEnumerator는 코루틴을 만들기 위한 특별한 반환 타입입니다.
// 일반 함수의 void와 달리, 실행을 일시 중지하고 재개할 수 있게 해줍니다.
IEnumerator FadeOutSlowly()
{
    // alpha 값을 1(완전 불투명)에서 시작하여 0(완전 투명)까지
    // 매번 0.1씩 감소시키면서 반복합니다.
    // 예: 1.0 -> 0.9 -> 0.8 -> 0.7 ... -> 0.0
    for(float alpha = 1; alpha >= 0; alpha -= 0.1f)
    {
        // 현재 alpha 값을 캐릭터의 투명도에 적용합니다.
        // 이렇게 하면 캐릭터가 점점 투명해지게 됩니다.
        character.alpha = alpha;

        // 여기가 코루틴의 마법이 일어나는 부분입니다!
        // yield return은 코루틴의 실행을 일시 중지하라는 신호입니다.
        // WaitForSeconds(0.1f)는 "0.1초 동안 기다리세요"라는 의미입니다.
        // 
        // 실행 순서:
        // 1. alpha 값을 설정
        // 2. 0.1초 대기
        // 3. 다음 alpha 값으로 돌아가서 반복
        //
        // 이렇게 하면 캐릭터가 1초 동안(0.1초 × 10단계) 
        // 부드럽게 페이드 아웃되는 효과가 만들어집니다.
        yield return new WaitForSeconds(0.1f);
    }
}

일반 메서드와 다르게 비동기 처리에 유용하게 쓰여지는데, 상태관리에 엄청나게 유용하다.

3-2. Behaviour

MonoBehaviour 를 상속받는 클래스는 Behaviour이다.

Behaviour는 주로 Unity의 컴포넌트 시스템에서 '활성화/비활성화가 가능한 컴포넌트'의 기초가 되는 클래스입니다.

Component 클래스를 상속받아서 만들어졌고, 이를 통해 게임 오브젝트에 부착할 수 있는 동작(behaviour)을 정의할 수 있다.

또한 다음과 같은 클래스들에 기반이 되기도한다.

  • MonoBehaviour: 사용자 정의 스크립트의 기본 클래스
  • Animator: 애니메이션 제어
  • AudioBehaviour: 오디오 관련 동작
  • Collider: 충돌 감지
  • Light: 조명 효과

주요 변수

변수명설명
enabled활성화된 Behaviour는 Update되고, 비활성화된 Behaviour는 Update되지 않습니다.
isActiveAndEnabledBehaviour가 active 및 enabled 호출을 받았는지 여부를 나타냅니다.

상속된 변수

변수명설명
gameObject이 컴포넌트가 부착된 게임 오브젝트입니다. 컴포넌트는 항상 게임 오브젝트에 부착되어 있습니다.
tag이 게임 오브젝트의 태그입니다.
transform이 GameObject에 부착된 Transform입니다.
hideFlags오브젝트를 숨기거나, 씬과 함께 저장하거나, 사용자가 수정할 수 있는지 여부를 결정합니다.
name오브젝트의 이름입니다.

Public 함수

함수명설명
BroadcastMessage이 게임 오브젝트나 자식 오브젝트의 모든 MonoBehaviour에서 methodName이라는 메서드를 호출합니다.
CompareTag이 게임 오브젝트가 지정된 태그로 태그되어 있는지 확인합니다.
GetComponentGameObject에 부착된 지정된 Type의 컴포넌트를 반환하며, 없으면 null을 반환합니다. 비활성화된 컴포넌트도 반환합니다.
GetComponentInChildren깊이 우선 검색을 사용하여 GameObject 또는 그 자식에서 Type 타입의 컴포넌트를 반환합니다.
GetComponentInParentGameObject 또는 그 부모에서 Type 타입의 컴포넌트를 반환합니다.
GetComponentsGameObject의 모든 Type 타입 컴포넌트를 반환합니다.
GetComponentsInChildrenGameObject 또는 그 자식의 모든 Type 타입 컴포넌트를 반환합니다. 재귀적으로 작동합니다.
GetComponentsInParentGameObject 또는 그 부모의 모든 Type 타입 컴포넌트를 반환합니다.
SendMessage이 게임 오브젝트의 모든 MonoBehaviour에서 methodName이라는 메서드를 호출합니다.
SendMessageUpwards이 게임 오브젝트와 모든 상위 객체의 MonoBehaviour에서 methodName이라는 메서드를 호출합니다.
TryGetComponent지정된 타입의 컴포넌트가 존재하는 경우 가져옵니다.
GetInstanceID오브젝트의 인스턴스 ID를 가져옵니다.
ToString오브젝트의 이름을 반환합니다.

정적 함수

함수명설명
DestroyGameObject, 컴포넌트 또는 에셋을 제거합니다.
DestroyImmediateobj를 즉시 파괴합니다. 대신 Destroy를 사용하는 것이 강력히 권장됩니다.
DontDestroyOnLoad새로운 Scene을 로드할 때 대상 Object를 파괴하지 않습니다.
FindObjectOfTypeType 타입의 첫 번째 활성화된 로드된 오브젝트를 반환합니다.
FindObjectsOfTypeType 타입의 모든 로드된 오브젝트 목록을 가져옵니다.
Instantiateoriginal 오브젝트를 복제하고 복제본을 반환합니다.

연산자

연산자설명
bool오브젝트가 존재하는지 여부를 확인합니다.
operator !=두 오브젝트가 서로 다른 오브젝트를 참조하는지 비교합니다.
operator ==두 오브젝트 참조가 동일한 오브젝트를 참조하는지 비교합니다.

3-3. Component

Component 클래스의 핵심 개념은 "게임오브젝트에 부착될 수 있는 모든 것의 기본"이라는 것이다.

직접 Component를 만들어 사용하는 것이 아니라, 이를 상속받은 다양한 컴포넌트들(예: MonoBehaviour, Collider, Renderer 등)을 통해 게임오브젝트에 기능을 부여가 가능하다.

핵심 변수

변수명설명용도
gameObject이 컴포넌트가 부착된 게임 오브젝트컴포넌트의 소유자 게임오브젝트 참조
tag게임 오브젝트의 태그게임오브젝트 분류 및 검색에 활용
transform게임오브젝트에 부착된 Transform위치, 회전, 크기 제어에 사용

주요 기능

컴포넌트 검색 함수들

함수명설명사용 예시
GetComponent현재 게임오브젝트의 특정 타입 컴포넌트 반환GetComponent<Rigidbody>()
GetComponentInChildren자식 포함 특정 타입 컴포넌트 검색GetComponentInChildren<Collider>()
GetComponentInParent부모 포함 특정 타입 컴포넌트 검색GetComponentInParent<Canvas>()
TryGetComponent안전하게 컴포넌트 가져오기 시도TryGetComponent(out Renderer renderer)

예제

// 물리 시스템 접근
Rigidbody rb = GetComponent<Rigidbody>();
if(rb != null) {
    rb.AddForce(Vector3.up * 10);
}

// 더 안전한 방식
if(TryGetComponent<Renderer>(out var renderer)) {
    renderer.material.color = Color.red;
}

메시지 전달 시스템

함수명설명범위
SendMessage게임오브젝트의 모든 MonoBehaviour에 메시지 전달현재 오브젝트
BroadcastMessage자식들을 포함한 모든 MonoBehaviour에 메시지 전달현재 + 자식
SendMessageUpwards부모들을 포함한 모든 MonoBehaviour에 메시지 전달현재 + 부모

예제

// 모든 자식 오브젝트에게 "Wake" 메시지 전달
BroadcastMessage("Wake", SendMessageOptions.DontRequireReceiver);

// 부모 방향으로 메시지 전달
SendMessageUpwards("OnAlert");

상속된 주요 기능

오브젝트 관리

함수명설명주의사항
Destroy게임오브젝트/컴포넌트/에셋 제거점진적 제거
DestroyImmediate즉시 제거에디터에서만 권장
DontDestroyOnLoad씬 전환시 보존루트 오브젝트에만 적용
Instantiate오브젝트 복제새로운 인스턴스 생성

예제

// 부모-자식 관계 설정
transform.SetParent(newParent);

// 자식 컴포넌트 검색
Light[] lights = GetComponentsInChildren<Light>();

3-4. Object 클래스

기본 설명

Unity의 Object 클래스는 엔진에서 참조할 수 있는 모든 객체의 기본 클래스. 이 클래스를 상속받은 모든 public 변수는 Unity 인스펙터에서 자동으로 표시되어 GUI를 통해 조작가능

기본 변수

변수명설명용도
name객체의 이름인스펙터와 디버깅에서 객체를 식별하는데 사용
hideFlags객체의 표시, 저장, 수정 가능 여부를 제어에디터에서 객체의 동작 방식을 제어

예제

// 객체의 이름을 관리
public string name;

// 객체의 표시와 저장 방식을 제어하는 플래그
public HideFlags hideFlags;

Public 함수

함수명설명사용 예시
GetInstanceID객체의 고유 식별자를 반환객체 비교나 해시테이블에서 키로 사용
ToString객체의 이름을 문자열로 반환디버그 로그나 UI 표시에 사용

정적 함수

함수명설명주의사항
Destroy게임오브젝트, 컴포넌트, 에셋을 제거실제 제거는 프레임 종료 시 발생
DestroyImmediate객체를 즉시 제거에디터 스크립팅에서만 사용 권장
DontDestroyOnLoad씬 전환시 객체 보존루트 오브젝트에만 적용 가능
FindObjectOfType지정된 타입의 첫 번째 활성화된 객체 찾기성능상 자주 호출하지 않도록 주의
FindObjectsOfType지정된 타입의 모든 활성화된 객체 찾기성능상 자주 호출하지 않도록 주의
Instantiate객체를 복제하여 새 인스턴스 생성프리팹 생성에 주로 사용

예제- 핵심 메서드

// 각 객체의 고유 ID를 가져옴
public int GetInstanceID();

// 객체 제거 (비동기적, 안전한 방식)
public static void Destroy(Object obj);

// 객체 즉시 제거 (동기적, 주의 필요)
public static void DestroyImmediate(Object obj);

// 씬 전환시에도 객체 유지
public static void DontDestroyOnLoad(Object target);

예제 - 객체 생성과 찾기

// 객체 복제
public static Object Instantiate(Object original);

// 특정 타입의 첫 번째 객체 찾기
public static Object FindObjectOfType(Type type);

// 특정 타입의 모든 객체 찾기
public static Object[] FindObjectsOfType(Type type);

연산자

연산자설명활용
bool (암시적 변환)객체의 존재 여부 확인null 체크에 사용
operator ==두 객체 참조가 동일한지 비교객체 동일성 검사
operator !=두 객체 참조가 다른지 비교객체 차이 검사

주요 특징

특징설명
인스펙터 표시Object를 상속받은 public 변수는 자동으로 인스펙터에 표시
null 연산자 제한null 조건 연산자(?.)와 null 병합 연산자(??)를 지원하지 않음
메모리 관리Unity의 자체 메모리 관리 시스템을 통해 관리됨
직렬화 지원Unity의 시리얼라이제이션 시스템과 통합

전체 코드 예제

// 객체 생성과 제거
GameObject newObject = Instantiate(prefab);
if(newObject) {  // bool 연산자 활용
    Debug.Log($"Created: {newObject.name}");  // ToString 활용
    Destroy(newObject, 5f);  // 5초 후 제거
}

// 씬 전환시 보존
DontDestroyOnLoad(gameObject);

// 객체 찾기
Camera mainCam = FindObjectOfType<Camera>();

3-5 전체 구조

이렇듯 우리는 using UnityEngine; 에서 제공하는 클래스들을 통해 Object들에게 기능을 부여할 수 있다.


참고 문서 스크립트 생성 및 사용

참고문서 코루틴

참고문서 유니티-코루틴-사용법-정지-Coroutine-이유-최적화

참고 문서 이벤트 함수의 실행 순서

참고 문서 중요 클래스 - MonoBehaviour

참고문서 MonoBehaviour

참고문서 Behaviour

참고문서 Component

참고문서 Object

profile
디지털 치매 예방

0개의 댓글