C#은 .Net의 프로그래밍 언어이다.
.Net이라는 오픈소스 프레임워크를 통해 것들을 할 수 있는데,
오늘은 Unity와의 상관관계에 대해서만 알아보도록 하자.
좀 특이한 언어인게, C#은 JAVA와 C++이 짬뽕되어 있는 언어이다.
자바와의 공통점을 보자면,
C++과의 공통점을 보자면,
등등이 있다. 실제 코드를 한번 봐보자,
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# 종특을 보자면,
// 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;을 지정해주면 된다. 필요한경우
아래처럼 커스텀해주면 된다.
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;
쉽게말해 동시작업임
// 기다림이 필요한 작업을 비동기로 처리
public async Task<string> GetDataFromServer() {
// await: 이 작업이 끝날 때까지 기다림
// 다른 코드는 계속 실행될 수 있음
string result = await FetchDataAsync();
return "처리된 데이터: " + result;
}
// 사용 방법
var data = await GetDataFromServer();
Console.WriteLine(data);
확장명 메서드를 사용하면 새 파생 형식을 만들거나 다시 컴파일하거나 원래 형식을 수정하지 않고도 기존 형식에 메서드를 "추가"할 수 있습니다.
확장 메서드는 정적 메서드이지만 확장 형식의 인스턴스 메서드인 것처럼 호출된다.
// 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);
}
솔직히 쓸일이 있을까 싶기는 하지만, 기본적으로 자바와 같이 자동관리를 해줄 수 있다.
하지만 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;
}
}
참조 타입: 코드 공유 + 타입 정보 유지
값 타입: 타입별 코드 생성 + 타입 정보 유지
즉, 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"
}
이제 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를 통해 이러한 작업을 수행해 원하는 목적을 이루게 될것이다.
그게 캐릭터건 연출이건..
아래는 유니티가 제공하는 가장 기본적인 코드이다.
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의 메서드를 가져다 쓰는것이다.
변수명 | 설명 |
---|---|
runInEditMode | 특정 MonoBehaviour 인스턴스가 에디터 모드에서 실행되도록 허용합니다(에디터에서만 사용 가능). |
useGUILayout | 이를 비활성화하면 GUI 레이아웃 단계를 건너뛸 수 있습니다. |
함수명 | 설명 |
---|---|
CancelInvoke | 이 MonoBehaviour의 모든 Invoke 호출을 취소합니다. |
Invoke | time 초 후에 methodName 메서드를 호출합니다. |
InvokeRepeating | time 초 후에 methodName 메서드를 호출하고, 이후 repeatRate 초마다 반복합니다. |
IsInvoking | methodName에 대한 호출이 대기 중인지 확인합니다. |
StartCoroutine | 코루틴을 시작합니다. |
StopAllCoroutines | 이 behaviour에서 실행 중인 모든 코루틴을 중지합니다. |
StopCoroutine | 지정된 이름의 첫 번째 코루틴이나 routine에 저장된 코루틴을 중지합니다. |
메시지 | 설명 |
---|---|
Awake | 스크립트 인스턴스가 로드될 때 호출됩니다. |
Start | 스크립트가 활성화될 때 첫 Update 호출 직전에 실행됩니다. |
Update | MonoBehaviour가 활성화된 경우 매 프레임마다 호출됩니다. |
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);
}
}
일반 메서드와 다르게 비동기 처리에 유용하게 쓰여지는데, 상태관리에 엄청나게 유용하다.
MonoBehaviour 를 상속받는 클래스는 Behaviour이다.
Behaviour는 주로 Unity의 컴포넌트 시스템에서 '활성화/비활성화가 가능한 컴포넌트'의 기초가 되는 클래스입니다.
Component 클래스를 상속받아서 만들어졌고, 이를 통해 게임 오브젝트에 부착할 수 있는 동작(behaviour)을 정의할 수 있다.
또한 다음과 같은 클래스들에 기반이 되기도한다.
변수명 | 설명 |
---|---|
enabled | 활성화된 Behaviour는 Update되고, 비활성화된 Behaviour는 Update되지 않습니다. |
isActiveAndEnabled | Behaviour가 active 및 enabled 호출을 받았는지 여부를 나타냅니다. |
변수명 | 설명 |
---|---|
gameObject | 이 컴포넌트가 부착된 게임 오브젝트입니다. 컴포넌트는 항상 게임 오브젝트에 부착되어 있습니다. |
tag | 이 게임 오브젝트의 태그입니다. |
transform | 이 GameObject에 부착된 Transform입니다. |
hideFlags | 오브젝트를 숨기거나, 씬과 함께 저장하거나, 사용자가 수정할 수 있는지 여부를 결정합니다. |
name | 오브젝트의 이름입니다. |
함수명 | 설명 |
---|---|
BroadcastMessage | 이 게임 오브젝트나 자식 오브젝트의 모든 MonoBehaviour에서 methodName이라는 메서드를 호출합니다. |
CompareTag | 이 게임 오브젝트가 지정된 태그로 태그되어 있는지 확인합니다. |
GetComponent | GameObject에 부착된 지정된 Type의 컴포넌트를 반환하며, 없으면 null을 반환합니다. 비활성화된 컴포넌트도 반환합니다. |
GetComponentInChildren | 깊이 우선 검색을 사용하여 GameObject 또는 그 자식에서 Type 타입의 컴포넌트를 반환합니다. |
GetComponentInParent | GameObject 또는 그 부모에서 Type 타입의 컴포넌트를 반환합니다. |
GetComponents | GameObject의 모든 Type 타입 컴포넌트를 반환합니다. |
GetComponentsInChildren | GameObject 또는 그 자식의 모든 Type 타입 컴포넌트를 반환합니다. 재귀적으로 작동합니다. |
GetComponentsInParent | GameObject 또는 그 부모의 모든 Type 타입 컴포넌트를 반환합니다. |
SendMessage | 이 게임 오브젝트의 모든 MonoBehaviour에서 methodName이라는 메서드를 호출합니다. |
SendMessageUpwards | 이 게임 오브젝트와 모든 상위 객체의 MonoBehaviour에서 methodName이라는 메서드를 호출합니다. |
TryGetComponent | 지정된 타입의 컴포넌트가 존재하는 경우 가져옵니다. |
GetInstanceID | 오브젝트의 인스턴스 ID를 가져옵니다. |
ToString | 오브젝트의 이름을 반환합니다. |
함수명 | 설명 |
---|---|
Destroy | GameObject, 컴포넌트 또는 에셋을 제거합니다. |
DestroyImmediate | obj를 즉시 파괴합니다. 대신 Destroy를 사용하는 것이 강력히 권장됩니다. |
DontDestroyOnLoad | 새로운 Scene을 로드할 때 대상 Object를 파괴하지 않습니다. |
FindObjectOfType | Type 타입의 첫 번째 활성화된 로드된 오브젝트를 반환합니다. |
FindObjectsOfType | Type 타입의 모든 로드된 오브젝트 목록을 가져옵니다. |
Instantiate | original 오브젝트를 복제하고 복제본을 반환합니다. |
연산자 | 설명 |
---|---|
bool | 오브젝트가 존재하는지 여부를 확인합니다. |
operator != | 두 오브젝트가 서로 다른 오브젝트를 참조하는지 비교합니다. |
operator == | 두 오브젝트 참조가 동일한 오브젝트를 참조하는지 비교합니다. |
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>();
Unity의 Object 클래스는 엔진에서 참조할 수 있는 모든 객체의 기본 클래스. 이 클래스를 상속받은 모든 public 변수는 Unity 인스펙터에서 자동으로 표시되어 GUI를 통해 조작가능
변수명 | 설명 | 용도 |
---|---|---|
name | 객체의 이름 | 인스펙터와 디버깅에서 객체를 식별하는데 사용 |
hideFlags | 객체의 표시, 저장, 수정 가능 여부를 제어 | 에디터에서 객체의 동작 방식을 제어 |
// 객체의 이름을 관리
public string name;
// 객체의 표시와 저장 방식을 제어하는 플래그
public HideFlags hideFlags;
함수명 | 설명 | 사용 예시 |
---|---|---|
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>();
이렇듯 우리는 using UnityEngine;
에서 제공하는 클래스들을 통해 Object들에게 기능을 부여할 수 있다.
참고 문서 스크립트 생성 및 사용
참고문서 코루틴
참고 문서 이벤트 함수의 실행 순서
참고 문서 중요 클래스 - MonoBehaviour
참고문서 MonoBehaviour
참고문서 Behaviour
참고문서 Component
참고문서 Object