✅ 기존 클래스의 기능을 새로운 클래스에서 재사용하기 위해
✅ Enemy와 Player 클래스에서 각각 Character의 Attack() 메서드를 오버라이딩하여 자신만의 공격을 구현한다.
2.유연성: Character 클래스는 기본적인 공격 구조만 정의하고, 구체적인 공격 방식은 Enemy와 Player 클래스에서 각자의 필요에 맞게 구현함으로써 유연성이 높아진다.
✅ 모든 게임 요소의 기본 구성 요소로 작동
GameObject는 Unity에서 모든 게임 요소의 기본 구성 요소로, 씬에 존재하는 모든 오브젝트의 기반이 된다.
GameObject 자체는 단순한 빈 오브젝트이지만, 다양한 컴포넌트(예: Transform, Renderer, Collider 등)를 추가하여 캐릭터, 환경, UI 요소 등 여러 가지 역할을 수행할 수 있게 한다.
조명, 물리 엔진, 2D 애니메이션, 오디오 효과 등의 기능은 모두 GameObject에 특정 컴포넌트를 추가함으로써 된다.
✅ Mathf.Clamp 함수를 사용하여 특정 각도 범위 내에서 회전을 제한한다.
Mathf.Clamp는 값을 특정 최소 및 최대 범위 내로 제한하는 함수로, 카메라 회전 각도를 제한하는 데 유용하다.
FPS 플레이어 컨트롤러에서는 마우스나 키보드 입력으로 카메라가 상하로 회전할 때, 특정 범위 이상으로 각도가 넘어가지 않도록 하기 위해 Mathf.Clamp를 사용해 카메라 각도를 제한할 수 있습니다.
예를 들어, 상하 회전 각도를 -90도에서 90도 사이로 제한하여 카메라가 뒤집히거나 지나치게 움직이지 않게 할 수 있습니다.

✅ 오브젝트가 화면 밖으로 나가면 자동으로 풀에 반환되도록 한다.
적 투사체와 같은 오브젝트는 화면 밖으로 나가거나 특정 조건이 충족되면 풀에 반환되어야 한다.
화면 밖으로 나가면 자동으로 풀에 반환되도록 구현하면, 오브젝트가 필요 이상으로 풀에 쌓이는 것을 방지할 수 있다.
if( ( i != 0 ) && ( j == 1 )
위와 같은 조건문이 있다고 가정하자. 위 if문은 유효성 검사에 실패했다. 그 이유는 아래 내용을 참고하자.
복잡한 문제를 작은 하위 문제로 나누고, 하위 문제의 해를 저장하여 중복 계산을 피함으로써 효율적으로 전체 문제를 해결하는 방법
동적 계획법의 첫번째 핵심은 점화식이다. 점화식이 성립해야지만 동적계획법을 이용할 수 있다.
동적 계획법의 두번째 핵심은 기저상태이다. 기저상태는 문제를 해결하는 최소 단위이자, 점화식을 성립시키기 위한 토대이다.
기저 상태는 각 문제의 논리에 맞게 설정해야 한다
기저상태는 0번째와 1번째로 나누는데, 예를 들어 0번째 계단은 아무것도 하지 않음을 의미하므로 1가지 방법으로 간주한다. 1번째 계단을 오르는 것은 1가지 밖에 없다. 따라서 1이된다. 정리하자면, DP[0] = 1 , DP[1] = 1이 된다.
기저상태는 점화식아 두 개 이상의 이전 값을 참조한다면 기저 상태가 두 가지로 나뉘게 된다. 예를들어 DP[0] , DP[1] 처럼 말이다. 또한, 두 가지로 나뉘게 되어야 dp[2], dp[3] 을 구할 수 있다. DP[2] = D[0] + DP[-1] , DP[3] = DP[1] + DP[0] 에서 결국, DP[0]과 DP[1]의 값이 필요하게 되기 때문이다.

먼저, 이진 탐색에 대한 개념을 알아야 한다.

low = 0 high = 6이다. 인덱스라고 생각하면 된다. mid = 3 이 될 것이다.
Mid는 중간값을 의미하므로, (low + high) / 2 = 3 이므로, 3번째 인덱스인 4가 나올 것이다.
만약 찾으려는(탐색하려는) 값이 6이라면, Mid의 값과 비교한 뒤 Mid가 작다면 Mid을 제외한 왼쪽을 제외 시킨다. Mid가 크다면 Mid을 제외한 오른쪽을 제외시킨다. 여기서는 Mid가 작으므로 왼쪽을 제외시킨다.

제외시키고 나서 다시 Mid를 구한다. Mid = (low + high) / 2 = 1 이므로, 1번째 인덱스 값인 7이 Mid가 될 것이다. 다시, 찾으려는 값과 Mid의 값을 비교한다. Mid가 더 크므로 Mid을 제외한 오른쪽을 제외시킨다.

마지막 남은 6이 원래의 찾으려던 값이였으므로 이진탐색을 종료한다.
이진 탐색 종료 조건은 목표 값을 찾거나 배열에 없는 값일 경우 종료 된다.
만약, Mid 값과 찾으려는 값이 일치한다면 해당 Mid 값을 반환하면 된다.
using System;
class Program
{
static void Main(string[] args)
{
// 정렬된 배열
int[] arr = { 1, 2, 3, 4, 6, 7, 9 };
// 목표 값
int target = 6;
// BinarySearch 호출
int result = BinarySearch(arr, target, 0, arr.Length - 1);
// 결과 출력
if (result != -1)
{
Console.WriteLine($"목표 값 {target}은(는) 인덱스 {result}에 있습니다.");
}
else
{
Console.WriteLine("목표 값이 배열에 없습니다.");
}
}
// 이진 탐색 함수
static int BinarySearch(int[] arr, int target, int low, int high)
{
// 기저 조건: 범위가 없으면 찾을 수 없으므로 -1 반환
if (low > high)
{
return -1; // target이 배열에 없다는 의미
}
// 중간 값 계산
int mid = low + (high - low) / 2;
// 목표 값과 중간 값 비교
if (arr[mid] == target)
{
return mid; // 목표 값을 찾으면 해당 인덱스 반환
}
else if (arr[mid] > target)
{
// 목표 값이 작으면 왼쪽 절반에서 재귀 호출
return BinarySearch(arr, target, low, mid - 1);
}
else
{
// 목표 값이 크면 오른쪽 절반에서 재귀 호출
return BinarySearch(arr, target, mid + 1, high);
}
}
}
int mid = low + (high - low) / 2;
중간값 계산 방식이 달라진 이유는 오버플로우 예방 차원이다. 이 방법은 low와 high의 합을 먼저 계산하지 않고, 그 차이를 먼저 구한 뒤 중간 값을 계산하여 방식이다.
return BinarySearch(arr, target, mid + 1, high);
mid + 1 이라는 것은 중간 값을 제외한 다음의 숫자를 의미하고, high는 오른쪽 절반의 끝을 의미한다.
return BinarySearch(arr, target, low, mid - 1);
mid - 1 이라는 것은 중간 값을 제외한 다음의 숫자를 의미하고, low는 왼쪽 절반의 끝을 의미한다.
int i = 1;
while (i < n)
{
Console.WriteLine(i);
i *= 2;
}
i 가 1부터 시작해서 2씩 곱해져간다. 즉, 1,2,4,8,16,32 ... 2^n 씩 증가 될 것이다. 즉,
log2(n)이 성립이 되고 log(n)으로 생각할 수 있다. 만약 i가 3부터 시작해서 2씩 곱해진다면 3,6,12,24 .... 3^k 씩 증가 될 것이고 n번 반복하면 log3(n)이 성립이 된다.
log2(2) = 1,
따라서 이 반복문은 log(n)이므로 O(log(n))가 시간 복잡도가 된다.
만약 n = 16일 때 첫번째 반복 i = 2 , 두번째 반복 i = 4, 세번째 반복 i = 8 , 4번째 반복 i = 16 이다. log2(16) = 4 즉, 네번째 반복일 때 i = 16이므로 네번째 반복일 때 반복문의 조건이 거짓이므로 반복문 종료한다.
분할 정복에 대한 개념이 필요하다.
분할 정복은 합병 정렬, 퀵 정렬, 이진 탐색으로 나뉜다. 이진 탐색에 대한 개념은 위 내용을 참고하자.
분할 정복은 대부분 재귀적으로 구현하고, 문제를 작은 하위 문제로 나누고 그 결과를 합성하는 방식이다. 또한, 재귀적으로 호출되므로 기저조건이 필요하다.
분할 정복에 기본적인 개념은 1. 분할 -> 정복 -> 결합 순으로 이루어진다. 이진탐색 처럼 Mid를 구한 뒤 low와 high를 분할하고, 정복(목표값이 있는 쪽으로 탐색을 진행)한 뒤, 결합( 목표 값이 발견되면 인덱스를 반환하고 종료)하는 개념이다.
[1,2,3,4,5,6,7] 이 있다고 가정하자.


using System;
using System.Collections.Generic;
class Program
{
static void GenerateSubsets(int[] nums)
{
HashSet<string> resultSet = new HashSet<string>(); // 중복을 방지할 HashSet
// 백트래킹 함수
void Backtrack(int start, List<int> currentSubset)
{
// 현재 부분 집합을 문자열로 변환하여 중복 체크
string subsetString = string.Join(",", currentSubset);
if (!resultSet.Contains(subsetString))
{
resultSet.Add(subsetString); // 중복되지 않으면 추가
Console.WriteLine("[" + subsetString + "]"); // 출력
}
// 나머지 원소들에 대해 포함 여부를 결정하면서 재귀 호출
for (int i = start; i < nums.Length; i++)
{
currentSubset.Add(nums[i]); // 원소를 포함시킴
Backtrack(i + 1, currentSubset); // 다음 원소로 진행
currentSubset.RemoveAt(currentSubset.Count - 1); // 원소를 제외시키고 돌아감 (백트래킹)
}
}
// 초기 호출
Backtrack(0, new List<int>());
}
static void Main()
{
int[] nums = { 1, 2, 3 }; // 주어진 집합
GenerateSubsets(nums); // 부분 집합 생성
}
}

A -> B -> D - E - > C - > F









using UnityEngine;
public class PlayerController : MonoBehaviour
{
private StateMachine stateMachine;
// 🔹 이동 속도
[SerializeField] private float moveSpeed = 5f;
private void Start()
{
stateMachine = new StateMachine();
// 🔹 초기 상태를 Idle로 설정
stateMachine.ChangeState(IdleState);
}
private void Update()
{
stateMachine.Update();
// 🔹 입력에 따라 상태 변경
if (Input.GetKeyDown(KeyCode.W))
{
stateMachine.ChangeState(WalkState);
}
else if (Input.GetKeyDown(KeyCode.Space))
{
stateMachine.ChangeState(AttackState);
}
else if (Input.GetKeyDown(KeyCode.S))
{
stateMachine.ChangeState(IdleState);
}
}
// 🟦 **Idle 상태**
private void IdleState()
{
Debug.Log("플레이어가 대기 중입니다.");
// Idle 상태에서는 아무 동작도 하지 않음
}
// 🟩 **Walk 상태**
private void WalkState()
{
Debug.Log("플레이어가 걷고 있습니다.");
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
// 🔹 플레이어 이동
Vector3 direction = new Vector3(horizontal, vertical, 0).normalized;
transform.position += direction * moveSpeed * Time.deltaTime;
}
// 🟥 **Attack 상태**
private void AttackState()
{
Debug.Log("플레이어가 공격 중입니다.");
// 🔹 애니메이션, 파티클 효과 등을 여기에 추가 가능
// 이 예시에서는 1초 후 Idle로 상태 변경
Invoke("ReturnToIdle", 1.0f);
}
private void ReturnToIdle()
{
stateMachine.ChangeState(IdleState);
}
}
using System;
using UnityEngine;
public class StateMachine
{
// 🔹 현재 상태의 동작을 저장할 Action 델리게이트
private Action currentState;
// 🔹 현재 상태를 설정 (상태 전환)
public void ChangeState(Action newState)
{
currentState = newState;
}
// 🔹 현재 상태의 동작을 실행 (Update에서 호출됨)
public void Update()
{
currentState?.Invoke();
}
}



using System.Collections.Generic;
using UnityEngine;
// 명령 인터페이스
public interface ICommand
{
void Execute(); // 실행
void Undo(); // 실행 취소
}
// 명령 구현 (이동 명령)
public class MoveCommand : ICommand
{
private Transform player;
private Vector3 direction;
public MoveCommand(Transform player, Vector3 direction)
{
this.player = player;
this.direction = direction;
}
public void Execute()
{
player.position += direction;
}
public void Undo()
{
player.position -= direction;
}
}
// 호출자 (CommandInvoker)
public class CommandInvoker : MonoBehaviour
{
private Stack<ICommand> commandStack = new Stack<ICommand>();
public void ExecuteCommand(ICommand command)
{
command.Execute();
commandStack.Push(command);
}
public void UndoCommand()
{
if (commandStack.Count > 0)
{
ICommand lastCommand = commandStack.Pop();
lastCommand.Undo();
}
}
}
// 수신자 (PlayerController)
public class PlayerController : MonoBehaviour
{
private CommandInvoker invoker;
private void Start()
{
invoker = FindObjectOfType<CommandInvoker>();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.W))
{
invoker.ExecuteCommand(new MoveCommand(transform, Vector3.up));
}
else if (Input.GetKeyDown(KeyCode.S))
{
invoker.ExecuteCommand(new MoveCommand(transform, Vector3.down));
}
else if (Input.GetKeyDown(KeyCode.U)) // Undo
{
invoker.UndoCommand();
}
}
}

using UnityEngine;
// Product 인터페이스
public interface IEnemy
{
void Attack();
}
// Concrete Product 클래스
public class Goblin : IEnemy
{
public void Attack()
{
Debug.Log("Goblin attacks!");
}
}
public class Orc : IEnemy
{
public void Attack()
{
Debug.Log("Orc attacks!");
}
}
// Factory 클래스
public static class EnemyFactory
{
public static IEnemy CreateEnemy(string enemyType)
{
switch (enemyType)
{
case "Goblin":
return new Goblin();
case "Orc":
return new Orc();
default:
throw new System.Exception("Unknown enemy type");
}
}
}
// 클라이언트 코드
public class GameController : MonoBehaviour
{
private void Start()
{
IEnemy goblin = EnemyFactory.CreateEnemy("Goblin");
IEnemy orc = EnemyFactory.CreateEnemy("Orc");
goblin.Attack(); // Goblin attacks!
orc.Attack(); // Orc attacks!
}
}

using System.Collections.Generic;
using UnityEngine;
// 오브젝트 풀 클래스
public class ObjectPool : MonoBehaviour
{
public GameObject prefab; // 생성할 프리팹
private Queue<GameObject> pool = new Queue<GameObject>();
public GameObject GetObject()
{
if (pool.Count > 0)
{
GameObject obj = pool.Dequeue();
obj.SetActive(true);
return obj;
}
else
{
return Instantiate(prefab);
}
}
public void ReturnObject(GameObject obj)
{
obj.SetActive(false);
pool.Enqueue(obj);
}
}
// 총알 관리 예제
public class Bullet : MonoBehaviour
{
private void OnDisable()
{
// 비활성화 시 오브젝트 풀로 반환
FindObjectOfType<ObjectPool>().ReturnObject(this.gameObject);
}
}
public class Player : MonoBehaviour
{
public ObjectPool bulletPool;
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
GameObject bullet = bulletPool.GetObject();
bullet.transform.position = transform.position;
}
}
}


Minimax 알고리즘의 "Max" 단계는 최대화 플레이어가 최선의 선택을 하도록 설계되었습니다.
최대화 플레이어는 자식 노드 중 가장 큰 점수를 선택합니다. 즉, 상대방(최소화 플레이어)이 어떤 선택을 하더라도 자신의 점수를 최대화하려고 합니다.

상태 별로 클래스를 만들어야 하므로 복잡하다.
간단한 행동이나 로직을 구현할 때 State Machine을 사용하면 필요 이상으로 복잡해질 수 있습니다.
예: 단순히 몇 가지 조건에 따라 동작이 바뀌는 경우라면, 조건문으로 처리하는 것이 더 간단합니다.












Unity에서 캔버스(Canvas)는 UI 요소를 렌더링하는 단위이며, 캔버스 내의 요소가 변경되면 전체 캔버스가 다시 렌더링됩니다.






Interpolation 설정은 오브젝트의 움직임을 부드럽게 보이도록 하기 위해 사용되지만, 물리 엔진 성능에는 부정적인 영향을 줄 수 있습니다. Interpolation은 물리 연산의 추가 작업을 요구하므로, 이를 남용하면 성능이 저하될 가능성이 있습니다. 따라서 성능 최적화가 중요한 경우 불필요한 Interpolation 설정은 최소화하거나 비활성화하는 것이 좋습니다.








































