처음 C#을 배우며 가장 먼저 접하는 문법들을 정리했다. Unity에 어떻게 적용될지를 생각하며 학습을 진행했다.
Console.WriteLine() → 콘솔에 문자열 출력Console.ReadLine() → 사용자 입력 받기 (항상 문자열로 입력됨)int.Parse() 또는 Convert.ToInt32() 사용Console.WriteLine("이름을 입력하세요:");
string name = Console.ReadLine();
Console.WriteLine("안녕하세요, " + name + "님!");
Console.WriteLine("나이를 입력하세요:");
int age = int.Parse(Console.ReadLine());
Console.WriteLine("당신의 나이는 " + age + "세입니다.");
조건에 따라 흐름을 분기할 때 사용.
int age = 20;
if (age >= 65)
{
Console.WriteLine("노년층입니다.");
}
else if (age >= 18)
{
Console.WriteLine("성인입니다.");
}
else
{
Console.WriteLine("미성년자입니다.");
}
동일한 동작을 반복할 때 사용.
for (int i = 0; i < 5; i++)
{
Console.WriteLine("for 반복: " + i);
}
int i = 0;
while (i < 5)
{
Console.WriteLine("while 반복: " + i);
i++;
}
int i = 0;
do
{
Console.WriteLine("do-while 반복: " + i);
i++;
} while (i < 5);
for: 반복 횟수가 정해졌을 때 적합
while: 조건이 참인 동안 반복
do-while: 최소 1회는 실행 보장
0부터 시작)int[] numbers = new int[5]; // 크기 5짜리 int 배열
int[] numbers = { 1, 2, 3, 4, 5 };
string[] fruits = new string[] { "apple", "banana", "orange" };
int[] scores = { 10, 20, 30 };
Console.WriteLine(scores[0]); // 10
scores[1] = 50;
Console.WriteLine(scores[1]); // 50
for (int i = 0; i < numbers.Length; i++)
{
Console.WriteLine(numbers[i]);
}
foreach (int num in numbers)
{
Console.WriteLine(num);
}
for, foreach)List<int>, int[] 등)| 구분 | 배열 (Array) | 컬렉션 (List 등) |
|---|---|---|
| 크기 | 고정 | 유동적 |
| 기능 | 제한적 | 삽입/삭제 유연 |
| 타입 | 단일 타입 | 제네릭 기반 |
List 예시
List<int> scores = new List<int>();
scores.Add(100);
scores.Add(80);
Console.WriteLine(scores[0]); // 100
→ 배열은 단순하고 빠르지만, List는 더 유연해서 실무에서 더 자주 쓰임.
클래스는 C#과 Unity 개발에서 가장 기본이자 핵심이 되는 개념이다. 데이터를 담는 그릇이자, 행동을 정의하는 틀. 게임 오브젝트 하나하나가 결국 클래스라는 점에서 반드시 정확히 이해하고 넘어가야 한다.
참고: MonoBehaviour는 Unity에서 스크립트를 오브젝트에 부착할 수 있게 해주는 특별한 클래스입니다. 이걸 상속받아야 Unity가 Start(), Update(), OnTriggerEnter() 같은 메서드를 자동으로 호출해줍니다. 게임 오브젝트와 연결되는 모든 스크립트는 MonoBehaviour를 상속받아야 하며, 이를 통해 transform, gameObject, GetComponent() 같은 엔진 기능도 사용할 수 있게 됩니다.
public class Player
{
public string name;
public int hp;
public Player(string name)
{
this.name = name;
hp = 100;
}
public void ShowStatus()
{
Console.WriteLine(name + "의 체력: " + hp);
}
}
Player p = new Player("루크");
p.ShowStatus();
캐릭터, 몬스터, 총알, 아이템, 스킬, UI 버튼까지…
게임에 나오는 거의 모든 요소는 속성과 동작을 가진 오브젝트다.
이런 오브젝트들을 체계적으로 다루기 위해선 설계도(클래스)가 필요하다.
public class Enemy
{
public string name;
public int hp;
public void TakeDamage(int dmg)
{
hp -= dmg;
Console.WriteLine(name + "이(가) 피해를 입었습니다. 남은 체력: " + hp);
}
}
→ 이 Enemy 클래스로 몬스터를 수십 마리 만들 수 있다.
예를 들어 몬스터가 여러 종류 있는데, 공통 동작은 비슷하고 일부만 다를 때:
public class Monster
{
public int hp;
public virtual void Attack() { /* 기본 공격 */ }
}
public class Goblin : Monster
{
public override void Attack() { /* 고블린 특수 공격 */ }
}
→ 공통 기능은 상속으로 묶고, 특수 기능은 오버라이딩(재정의) 하면 편하다.
Unity에서 MonoBehaviour를 상속한 모든 스크립트는 클래스다.
즉, PlayerController, EnemyAI, ItemPickup 이런 스크립트들 전부 클래스 기반이고, Unity는 이 클래스들을 바탕으로 컴포넌트 시스템을 굴리는 것이다.
public class PlayerController : MonoBehaviour
{
void Update()
{
Move();
}
void Move()
{
// 입력 받아서 이동 처리
}
}
씬에 배치된 오브젝트들이 서로 어떻게 상호작용하는지 = 클래스들 간의 관계
플레이어 → 무기 → 총알, 플레이어 → UI, 적 → 상태머신 등
모든 관계가 클래스 간의 구조로 표현된다.
상태 머신, 싱글톤, 팩토리 패턴 같은 게임에서 자주 쓰는 구조들도 전부 클래스를 전제로 한 설계 패턴이다.
예: 게임 매니저는 대부분 싱글톤 클래스
public class GameManager : MonoBehaviour
{
public static GameManager Instance;
void Awake()
{
if (Instance == null) Instance = this;
}
}
| 게임 요소 | 클래스가 하는 일 |
|---|---|
| 플레이어/몬스터 | 상태, 행동 정의 |
| 무기/아이템 | 속성, 효과 정의 |
| UI/이벤트 관리 | 데이터 저장 + 인터랙션 처리 |
| 전체 게임 흐름 | 매니저 클래스, 싱글톤 등 구조 관리 |
→ 게임을 만든다는 건 결국 객체 간 상호작용을 설계하는 것,
→ 그리고 그 객체들의 설계도가 바로 클래스다.
public class Car
{
public string brand;
public int speed;
public void Drive()
{
Console.WriteLine(brand + "가 " + speed + "km/h로 달립니다.");
}
}
Car myCar = new Car();
myCar.brand = "현대";
myCar.speed = 100;
myCar.Drive();
클래스는 객체지향의 출발점이자, Unity 스크립트의 핵심이다.
다양한 오브젝트를 구조화하고, 재사용성과 확장성을 확보하려면 반드시 익숙해져야 할 개념. 이걸 제대로 이해해야 이후에 나올 상속, 인터페이스, 추상 클래스, 가상 메서드도 자연스럽게 연결된다.
상속(Inheritance)은 객체지향 프로그래밍의 핵심 개념 중 하나로, 기존 클래스의 설계를 재사용하고 확장할 수 있게 해준다. 게임처럼 다양한 오브젝트가 존재하는 환경에서는 코드 중복을 줄이고 관리하기 위해 반드시 이해해야 할 개념이다.
public class Animal // 부모 클래스
{
public string name;
public void Breathe()
{
Console.WriteLine(name + "가 숨을 쉽니다.");
}
}
public class Dog : Animal // Animal을 상속받은 자식 클래스
{
public void Bark()
{
Console.WriteLine("멍멍!");
}
}
Dog d = new Dog();
d.name = "초코";
d.Breathe(); // 초코가 숨을 쉽니다.
d.Bark(); // 멍멍!
// 중복된 클래스들
public class Goblin {
public int hp;
public void Die() { Console.WriteLine("죽음"); }
}
public class Orc {
public int hp;
public void Die() { Console.WriteLine("죽음"); }
}
// 상속으로 정리
public class Enemy {
public int hp;
public void Die() { Console.WriteLine("죽음"); }
}
public class Goblin : Enemy { }
public class Orc : Enemy { }
public class Goblin : Enemy
{
public void ThrowStone()
{
Console.WriteLine("돌을 던졌다!");
}
}
public class Enemy {
public virtual void Attack() {
Console.WriteLine("기본 공격");
}
}
public class Orc : Enemy {
public override void Attack() {
Console.WriteLine("도끼 공격!");
}
}
virtual: 부모 클래스에서 재정의 가능하다고 표시override: 자식 클래스에서 해당 메서드를 새롭게 구현| 구분 | 설명 | 상속 가능성 |
|---|---|---|
public class | 외부 공개 | ✅ 가능 |
class (생략) | internal (같은 프로젝트에서만 사용) | ⚠️ 제한적 가능 |
private class | 클래스 내부에서만 사용 | ❌ 불가 |
상속 가능 여부는 클래스의 "접근 제한자"에 따라 달라진다.
Unity에서는 대부분의 스크립트를 public class로 작성한다. 그래야 다른 스크립트에서 불러오거나 상속할 수 있으며, MonoBehaviour를 상속한 Unity 컴포넌트 스크립트들도 모두 public으로 선언된다.. 그래야 다른 스크립트에서 불러오거나 상속 가능.
C#에서 private은 클래스 내부에서만 멤버를 사용할 수 있도록 제한하는 접근 제어자다. Unity에서도 private은 매우 자주 사용되며, 다음과 같은 이유에서 활용된다:
데이터 보호
public class Player
{
private int hp = 100;
public void TakeDamage(int damage)
{
hp -= damage;
if (hp < 0) hp = 0;
}
}
캡슐화 (Encapsulation)
public class Enemy
{
private int hp;
public void SetHp(int value)
{
if (value >= 0) hp = value;
}
public int GetHp()
{
return hp;
}
}
유지보수성 향상
Unity 기준 사용 예
private 변수는 인스펙터에 안 보임[SerializeField] private 조합을 사용하면 인스펙터에 보이면서도 외부 접근은 차단 가능private 멤버는 자식 클래스에서 직접 접근할 수 없음protected를 사용하면 자식 클래스에서도 접근 가능public class Character {
private int hp; // ❌ 자식 클래스에서 접근 불가
protected int mp; // ✅ 자식 클래스에서 접근 가능
}
public class Character
{
public string name;
public int hp;
public virtual void Attack()
{
Console.WriteLine(name + " 기본 공격!");
}
}
public class Warrior : Character
{
public override void Attack()
{
Console.WriteLine(name + " 검으로 공격!");
}
}
public class Mage : Character
{
public override void Attack()
{
Console.WriteLine(name + " 파이어볼!");
}
}
Character c1 = new Warrior();
c1.name = "가로쉬";
c1.Attack(); // 가로쉬 검으로 공격!
Character c2 = new Mage();
c2.name = "제이나";
c2.Attack(); // 제이나 파이어볼!
→ 부모 타입(Character)으로 자식 클래스(Warrior, Mage)를 다룰 수 있는 게 다형성(polymorphism)의 시작이다. 다형성을 활용하면 공통된 코드에서 다양한 동작을 유연하게 실행할 수 있어, 코드 구조가 더 깔끔하고 유지보수가 쉬워진다.
상속은 재사용성, 유지보수성, 확장성 모든 면에서 강력한 도구다. Unity에서도 MonoBehaviour를 기반으로 상속을 사용하는 일이 많기 때문에, 이 개념을 확실히 이해해두면 구조적인 게임 설계에 큰 도움이 된다.
인터페이스(Interface)는 "공통 기능을 가진 클래스들이 같은 메서드 구조를 따르도록 강제"하는 장치다.
특히 Unity처럼 다양한 오브젝트가 공존하는 환경에서는 코드의 일관성과 유연성을 높이는 데 필수적인 개념이다.
public interface IDamageable
{
void TakeDamage(int damage);
}
public class Enemy : MonoBehaviour, IDamageable
{
public void TakeDamage(int damage)
{
Debug.Log("적이 " + damage + "만큼 피해를 입었다!");
}
}
public class Player : MonoBehaviour, IDamageable
{
public void TakeDamage(int damage)
{
Debug.Log("플레이어가 " + damage + "만큼 피해를 입었다!");
}
}
→ IDamageable을 구현한 클래스는 모두 TakeDamage(int) 메서드를 반드시 구현해야 한다.
이런 구조를 통해 다양한 객체에서 동일한 행동을 보장할 수 있다.
| 특징 | 설명 |
|---|---|
| 강제성 | 인터페이스를 구현한 클래스는 정의된 모든 메서드를 반드시 구현해야 함 |
| 다중 구현 | 하나의 클래스가 여러 인터페이스를 동시에 구현 가능 |
| 구현 없음 | 메서드 본문 없이 이름, 매개변수만 정의됨 |
| 접근 제한자 생략 | 인터페이스 내부 메서드는 항상 public으로 간주됨 |
public interface IInteractable
{
void Interact();
}
public class Door : MonoBehaviour, IInteractable
{
public void Interact()
{
Debug.Log("문 열림");
}
}
public class NPC : MonoBehaviour, IInteractable
{
public void Interact()
{
Debug.Log("대화 시작");
}
}
// 공통 기능 호출
void InteractWithTarget(GameObject target)
{
IInteractable obj = target.GetComponent<IInteractable>();
if (obj != null)
{
obj.Interact();
}
}
→ 다양한 오브젝트를 인터페이스 하나로 통합하여 제어할 수 있다.
→ 코드 의존도를 낮추고, 기능을 "묶는 기준"으로 삼을 수 있다.
Unity에서는 인터페이스를 통해 다양한 컴포넌트를 범용적으로 가져올 수 있다.
인터페이스는 플레이어에게 직접적으로 드러나지는 않지만, 개발자에게는 설계를 구성하는 가장 강력한 도구 중 하나다.
IDamageable 인터페이스가 있으면 Player, Enemy, Boss 모두 동일하게 처리 가능DummyEnemy → OrcEnemy로 쉽게 교체 가능List<IInteractable>처럼 다양한 객체를 한 컬렉션으로 관리 가능GetComponent<IInterface>() 방식으로 Unity 오브젝트에서 인터페이스 구현체를 통합 접근인터페이스는 설계 도구로서의 역할이 매우 강력하다.
특히 Unity처럼 다양한 오브젝트가 존재하고 기능이 겹치는 상황에서 인터페이스를 잘 활용하면, 코드의 유연성, 재사용성, 유지보수성을 모두 높일 수 있다.
추상 클래스(Abstract Class)는 "공통되는 속성과 기능은 미리 구현하고, 나머지 구현은 자식 클래스에게 맡기는 설계 도구"다. 인터페이스와 비슷하지만 일부 구현이 포함될 수 있다는 점에서 차별화되며, 기능이 유사한 클래스들을 설계할 때 매우 유용하다.
// 추상 클래스 선언
public abstract class 클래스이름
{
// 일반 메서드 (공통 구현 가능)
public void 일반메서드() { ... }
// 추상 메서드 (자식이 반드시 구현해야 함)
public abstract void 메서드이름();
}
// 자식 클래스에서 상속 후 구현
public class 자식클래스 : 클래스이름
{
public override void 메서드이름()
{
// 구체적인 동작 구현
}
}
public abstract class Character
{
public string name;
public void Move()
{
Console.WriteLine(name + "이(가) 이동합니다.");
}
public abstract void Attack();
}
public class Warrior : Character
{
public override void Attack()
{
Console.WriteLine(name + "이(가) 검으로 공격!");
}
}
Warrior w = new Warrior();
w.name = "루크";
w.Move(); // 루크이(가) 이동합니다.
w.Attack(); // 루크이(가) 검으로 공격!
→ 공통 기능은 부모 클래스에서 구현하고, 개별 동작은 자식 클래스에서 구현한다.
| 항목 | 설명 |
|---|---|
| 인스턴스 생성 여부 | ❌ 직접 생성 불가 |
| 추상 메서드 포함 여부 | ✅ 최소 1개 이상 필요 |
| 공통 메서드 구현 | ✅ 가능 |
| 필드/속성 가짐 | ✅ 가능 |
| 생성자 사용 가능 | ✅ 가능 |
| 상속 수 제한 | ❌ 단일 상속만 가능 |
| 항목 | 추상 클래스 | 인터페이스 |
|---|---|---|
| 구현 포함 | ✅ 일부 구현 가능 | ❌ 구현 불가능 |
| 필드/속성 | ✅ 가능 | ❌ 없음 |
| 생성자 | ✅ 가능 | ❌ 없음 |
| 다중 상속 | ❌ 불가능 | ✅ 가능 |
| 사용 목적 | 공통 기능 + 개별 구현 구분 목적 | 기능 명세(구조) 강제화 목적 |
추상 클래스는 "서로 관련 있는 클래스 간 공통 기능을 묶을 때",
인터페이스는 "다른 계열이라도 같은 동작을 하게 만들 때" 사용하면 좋다.
비유로 정리하면:
public abstract class Character
{
public string name;
public void Move()
{
Console.WriteLine(name + " 이동!");
}
public abstract void Attack();
}
public class Mage : Character
{
public override void Attack()
{
Console.WriteLine(name + " 파이어볼!");
}
}
public class Archer : Character
{
public override void Attack()
{
Console.WriteLine(name + " 활 공격!");
}
}
Character mage = new Mage();
mage.name = "제이나";
mage.Move(); // 제이나 이동!
mage.Attack(); // 제이나 파이어볼!
Move, Die는 부모에서 구현, Attack은 자식이 개별 구현할 경우→ 기본 구현 + 커스터마이징이 동시에 필요한 상황에 적합
추상 클래스는 공통 기능을 묶으면서도, 개별 클래스로 구체적인 기능을 나누고자 할 때 매우 유용한 도구다.
유지보수가 쉬운 구조와 깔끔한 상속 설계를 위해 꼭 익혀야 할 객체지향 개념 중 하나다.
가상 메서드(Virtual Method)는 부모 클래스에서 기본 동작을 정의하되, 자식 클래스가 원하면 그 동작을 바꿀 수 있도록 허용하는 메서드다. 이는 상속과 다형성을 효과적으로 활용할 수 있게 해주는 C#의 핵심 기능 중 하나다.
virtual 키워드로 정의된 부모 클래스의 메서드override 키워드를 사용해 재정의 가능public class Enemy
{
public virtual void Attack()
{
Console.WriteLine("기본 공격!");
}
}
public class Orc : Enemy
{
public override void Attack()
{
Console.WriteLine("도끼 공격!");
}
}
Enemy e1 = new Enemy();
e1.Attack(); // 기본 공격!
Enemy e2 = new Orc();
e2.Attack(); // 도끼 공격!
→ 부모 클래스에서 기본 기능을 제공하고, 자식 클래스는 필요할 때만 재정의하면 됨.
| 항목 | 가상 메서드 (virtual) | 일반 메서드 |
|---|---|---|
| 재정의 가능 여부 | ✅ 자식 클래스에서 override 가능 | ❌ 재정의 불가 (숨기거나 별도 구현해야 함) |
| 다형성 활용 | ✅ 가능 | ❌ 제한적 |
| 동작 확장성 | 유연하게 커스터마이징 가능 | 고정된 구현만 가능 |
public class Enemy
{
public void NormalAttack()
{
Console.WriteLine("기본 공격!");
}
public virtual void SpecialAttack()
{
Console.WriteLine("기본 특수 공격!");
}
}
public class Boss : Enemy
{
// 일반 메서드는 숨기기 (new 키워드 필요)
public new void NormalAttack()
{
Console.WriteLine("강력한 일반 공격!");
}
// 가상 메서드는 재정의 가능
public override void SpecialAttack()
{
Console.WriteLine("궁극기 발동!");
}
}
Enemy boss = new Boss();
boss.NormalAttack(); // 기본 공격!
boss.SpecialAttack(); // 궁극기 발동!
→ 일반 메서드는 숨기기(new), 가상 메서드는 재정의(override). 부모 타입으로 호출할 경우 가상 메서드만 제대로 동작 확장됨.
public class Enemy
{
public string name;
public virtual void Attack()
{
Console.WriteLine(name + "이(가) 기본 공격!");
}
}
public class Goblin : Enemy
{
public override void Attack()
{
Console.WriteLine(name + "이(가) 돌을 던진다!");
}
}
public class Skeleton : Enemy
{
// override하지 않음 → 기본 공격 그대로 사용
}
Enemy goblin = new Goblin();
goblin.name = "고블린";
goblin.Attack(); // 고블린이(가) 돌을 던진다!
Enemy skeleton = new Skeleton();
skeleton.name = "스켈레톤";
skeleton.Attack(); // 스켈레톤이(가) 기본 공격!
→ 같은 부모 타입으로 다룰 수 있으면서도, 행동은 클래스에 따라 다르게 할 수 있다.
→ 전략적 유연성을 부여하고, 다형성을 극대화하는 데 유리하다.
| 개념 | 설명 |
|---|---|
virtual | 부모 클래스에서 재정의 가능한 메서드 표시 |
override | 자식 클래스에서 해당 메서드를 재정의할 때 사용 |
| 선택적 오버라이드 | 자식 클래스가 원할 때만 기능을 변경 가능 |
| 다형성 활용 | 부모 타입으로 자식 객체를 다루되, 동작은 다르게 구현 가능 |
가상 메서드는 기본을 제공하되, 자유롭게 바꿀 수 있는 유연한 구조를 만들 때 매우 유용하다.
Unity에서도 MonoBehaviour의 Start(), Update() 등이 사실상 가상 메서드처럼 작동하며,
사용자 정의 클래스에서도 유연한 동작 확장을 위한 핵심 도구로 활용된다.
| 구분 | 인터페이스 | 추상 클래스 | 가상 메서드 |
|---|---|---|---|
| 구현 여부 | ❌ 구현 없음 | ✅ 일부 구현 가능 | ✅ 기본 구현 있음 |
| 필드/속성 사용 | ❌ 불가 | ✅ 가능 | ✅ 가능 |
| 상속 개수 | ✅ 다중 가능 | ❌ 하나만 가능 | (클래스 내 포함 개념) |
| 구현 강제성 | ✅ 전부 구현해야 함 | ✅ abstract만 구현 필요 | ❌ 선택적 override |
| 사용 목적 | 규격 통일 / 강제 구현 | 공통 + 강제 설계 | 공통 제공 + 유연한 확장 |
처음엔 "다 똑같은 거 아닌가?" 싶지만,
virtualabstract classinterface이렇게 명확히 구분해서 쓰면 된다.
Unity에서 시스템을 짜기 시작할수록 이 구분이 굉장히 중요해진다. 개념이 아니라 설계 감각으로 체득해야 할 듯 싶다.
게임 개발에서 랜덤(Random)은 핵심적인 역할을 한다. 몬스터의 등장 위치, 보상 아이템 드롭, 치명타 발생 확률 등 예측 불가능한 요소를 통해 플레이어에게 다양성과 긴장감을 제공한다. C#에서는 System.Random 클래스를 사용하며, Unity에서는 UnityEngine.Random을 통해 손쉽게 구현할 수 있다.
Random rand = new Random();
int number = rand.Next(); // 0 이상 int.MaxValue 이하의 정수
int num = rand.Next(1, 101); // 1 이상 100 이하의 랜덤 정수
double d = rand.NextDouble(); // 0.0 이상 1.0 미만의 실수
Random.Next()는 정수만 생성 가능, 실수가 필요할 경우NextDouble()을 사용한다.
Unity에서는 UnityEngine.Random 클래스를 사용하며, 사용 방식은 직관적이다.
int value = Random.Range(0, 10); // 0 이상 10 미만 (int)
float value2 = Random.Range(0f, 1f); // 0.0 이상 1.0 이하 (float)
⚠️ 주의:
int는 최댓값 미포함,float은 최댓값 포함이다.
이 점을 고려하지 않으면 경계값에서 의도치 않은 버그가 발생할 수 있다.
public GameObject enemyPrefab;
void SpawnEnemy()
{
float x = Random.Range(-10f, 10f);
float z = Random.Range(-10f, 10f);
Vector3 spawnPos = new Vector3(x, 0, z);
Instantiate(enemyPrefab, spawnPos, Quaternion.identity);
}
→ Random.Range()를 이용해 적을 맵의 랜덤 위치에 소환할 수 있다. 좌표의 범위를 바꾸면 특정 지역 내 생성도 가능하다.
| 활용 분야 | 예시 설명 |
|---|---|
| 전투 확률 계산 | 치명타 발생 확률, 회피 확률 등 확률 기반 전투 판정 |
| 아이템 드랍 시스템 | 확률 기반 드롭 테이블로 아이템 지급 |
| 맵 구성 | 타일 배치, 오브젝트 위치 랜덤 설정 |
| AI 의사결정 | 일정 확률로 도망, 공격, 대기 등 행동 패턴 결정 |
| 퍼즐 로직 | 매번 다른 숫자 조합 제공 (2048류 게임 등) |
| 기능 구분 | 설명 |
|---|---|
Random.Next() | 0 이상 정수 생성 |
Random.Next(a, b) | a 이상 b 미만 정수 생성 |
Random.NextDouble() | 0.0 이상 1.0 미만 실수 생성 |
Random.Range(a, b) (Unity, int) | a 이상 b 미만 정수 생성 |
Random.Range(a, b) (Unity, float) | a 이상 b 이하 실수 생성 |
랜덤은 간단한 기능이지만, 게임의 재미와 변수 설계에 큰 영향을 미친다.
특히 Unity의 Random.Range()는 직관적이면서도 강력한 도구로, 반복 가능한 콘텐츠를 신선하게 만드는 데 핵심적인 역할을 한다.
string message = "Hello, World!";
int length = message.Length;
string a = "Hi", b = "hi";
bool equal = a.Equals(b, StringComparison.OrdinalIgnoreCase);
string full = a + " " + b;
string formatted = string.Format("{0} {1}", a, b);
string interpolated = $"{a} {b}";
string s = "apple,banana";
string[] arr = s.Split(','); // ["apple", "banana"]
string cut = s.Substring(0, 5); // "apple"
string replaced = s.Replace("apple", "orange");
string trimmed = " hi ".Trim();
char[] chars = "hello".ToCharArray();
문자열은 UI, 로그, 대사 시스템, 저장/로드 등 거의 모든 곳에서 쓰인다.
C#에서 ref와 out은 메서드와 호출자 간에 값을 어떻게 공유할 것인지를 결정하는 키워드다.
게임 개발에서는 체력 계산, 무기 효과 반환, 상태 전달 등 다양한 곳에서 사용된다.
ref는 초기화된 값을 메서드에 넘기고, 메서드가 그 값을 직접 수정할 수 있도록 한다.void AddTen(ref int number)
{
number += 10;
}
int value = 5;
AddTen(ref value);
Console.WriteLine(value); // 출력: 15
out은 초기화하지 않은 변수도 메서드에 넘길 수 있다.void CreateRandom(out int number)
{
number = new Random().Next(1, 101);
}
int value;
CreateRandom(out value);
Console.WriteLine(value); // 랜덤한 값 출력
| 항목 | ref | out |
|---|---|---|
| 초기화 여부 | ✅ 메서드 호출 전 반드시 초기화해야 함 | ❌ 초기화 없이 전달 가능 |
| 메서드 내 값 설정 필수 | ❌ 선택 (안 해도 됨) | ✅ 반드시 할당해야 함 |
| 대표 용도 | 기존 값 수정, 누적 계산, 상태 전달 등 | 새 값 반환, 랜덤 값 생성, 다중 결과 반환 등 |
ref: 기존 값에 메서드 안에서 변화가 생기고, 그 결과가 밖에서도 반영되어야 할 때out: 메서드 안에서 새로 값을 만들어서 돌려줄 때 유용함이 두 키워드를 잘 이해하면, return 하나로는 부족했던 데이터를 메서드 바깥으로 간결하게 전달할 수 있고,
Unity에서도 체력 변경, 아이템 생성 결과, 적 중첩 판정 등에 매우 자주 사용된다.
C#에서는 객체의 타입을 확인하거나 형변환을 할 때 is와 as 키워드를 사용한다.
이 둘은 특히 Unity에서 컴포넌트 캐스팅, 이벤트 처리, 다형성 활용 시 매우 자주 등장한다.
is는 객체가 특정 타입인지 확인하는 데 사용된다.true 또는 false로 반환된다.object obj = "Hello";
if (obj is string)
{
Console.WriteLine("문자열입니다");
}
as는 형변환을 시도하되, 실패하면 예외를 던지지 않고 null을 반환한다.is는 검사만 하지만, as는 실제로 형변환된 객체를 돌려준다.object obj = "Hello";
string text = obj as string;
if (text != null)
{
Console.WriteLine("변환 성공: " + text);
}
| 항목 | is | as |
|---|---|---|
| 기능 | 타입 확인 (bool 반환) | 안전한 형변환 (실패 시 null) |
| 반환값 | true / false | 변환된 객체 or null |
| 예외 발생 여부 | ❌ 없음 | ❌ 없음 |
| 대표 용도 | 조건문에서 타입 비교 | 안전한 다운캐스팅 |
void OnTriggerEnter(Collider other)
{
Enemy enemy = other.GetComponent<Enemy>();
if (enemy != null)
{
enemy.TakeDamage(10);
}
// 또는 as 키워드 활용
IDamageable target = other.GetComponent<IDamageable>() as IDamageable;
if (target != null)
{
target.TakeDamage(10);
}
}
as는 인터페이스나 상속 기반 구조에서 다운캐스팅할 때 매우 유용하며,
is는 형 확인 후 캐스팅 전에 조건 검사를 할 때 사용된다.
if (obj is Player) DoSomething();
IInteractable interactable = obj as IInteractable;
if (interactable != null) interactable.Use();
is: 객체가 특정 타입인지 확인할 때. 조건문에서 주로 사용됨.as: 객체를 안전하게 캐스팅할 때. 실패해도 예외 없이 null 반환.이 두 키워드를 잘 활용하면 형변환에서 발생할 수 있는 예외를 줄이고, 유연한 타입 설계를 할 수 있다.
이번 학습을 통해 기본 개념부터 객체에 대한 개념까지 C#의 주요 문법과 개념들을 실제 게임 개발에 어떻게 활용할 수 있는지 체계적으로 이해하게 되었다.
단순히 문법을 외우는 것이 아니라, 각각이 어떤 상황에서 쓰이고 왜 중요한지를 맥락 속에서 연결하며 배운 점이 가장 큰 수확이다.
이제 C#이라는 도구를 훨씬 더 유연하고 명확하게 다룰 수 있을 것 같고, Unity 개발을 위한 기초 체력도 훨씬 단단해졌다는 느낌이다.