즉, 씬이 로드될 때 모든 오브젝트가 로드된 상태에서 Awake()가 실행되므로, Awake()에서 다른 오브젝트를 참조하는 건 안전
Unity에서 파일을 저장하거나 불러올 수 있는 주요 경로들은 다음과 같습니다. 플랫폼별 경로 차이를 유의해야 합니다.
C:\Users\사용자\AppData\LocalLow\회사명\게임명~/Library/Application Support/회사명/게임명/storage/emulated/0/Android/data/패키지명/files/Application Sandbox/Documents/string path = Path.Combine(Application.persistentDataPath, "savefile.json");
File.WriteAllText(path, jsonData);Assets/StreamingAssets/jar:file:///android_asset/Application Sandbox/StreamingAssets/string path = Path.Combine(Application.streamingAssetsPath, "config.json");
string jsonData = File.ReadAllText(path);IEnumerator LoadJsonAndroid()
{
string path = Path.Combine(Application.streamingAssetsPath, "config.json");
UnityWebRequest request = UnityWebRequest.Get(path);
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
string jsonData = request.downloadHandler.text;
}
}Assets 폴더 위치를 나타냅니다.<프로젝트 폴더>/Assets/data/app/패키지명/base.apkApplication Sandbox/Data/string path = Path.Combine(Application.dataPath, "Resources/config.json");
string jsonData = File.ReadAllText(path);C:\Users\사용자\AppData\Local\Temp\회사명\게임명/data/data/패키지명/cache/Application Sandbox/Library/Caches/string tempPath = Path.Combine(Application.temporaryCachePath, "tempfile.txt");
File.WriteAllText(tempPath, "Temporary Data");Resources 폴더에 있는 파일을 런타임에 로드할 수 있습니다.Resources 폴더의 모든 파일은 빌드 시 메모리에 포함되므로 과도한 사용은 피해야 합니다.TextAsset textAsset = Resources.Load<TextAsset>("config");
string jsonData = textAsset.text;Addressables을 활용하여 동적 로딩을 고려string desktopPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Desktop);
string savePath = Path.Combine(desktopPath, "game_save.json");
File.WriteAllText(savePath, saveData);Desktop: 바탕화면 경로MyDocuments: 내 문서 폴더ApplicationData: 사용자별 설정 데이터 (AppData\Roaming)LocalApplicationData: 로컬 설정 데이터 (AppData\Local)| 경로 | 설명 | 읽기/쓰기 가능 여부 | 주요 용도 |
|---|---|---|---|
Application.persistentDataPath | 영구 저장 경로 | 읽기/쓰기 가능 | 세이브 데이터, 설정 파일 |
Application.streamingAssetsPath | 빌드에 포함된 정적 파일 | 읽기 가능 (Android는 웹 요청 필요) | 비디오, 설정 파일, 텍스처 |
Application.dataPath | 프로젝트 Assets 폴더 위치 | 읽기 가능 | 내부 참조 (런타임 저장 X) |
Application.temporaryCachePath | 임시 캐시 저장 | 읽기/쓰기 가능 | 다운로드한 데이터, 캐시 |
Resources 폴더 | Unity의 미리 포함된 리소스 | 읽기 가능 (메모리 상주) | 이미지, 오디오, 프리팹 |
System.Environment.GetFolderPath() | OS의 기본 폴더 경로 | 읽기/쓰기 가능 | 문서, 바탕화면 저장 |
원래대로라면 UI를 만들고 어떤 기물로 승진할지 결정할 수 있게 해줘야 겠지만,
일단 간단하게 퀸으로 바꿔놓는 걸로 임시 구현.
/// <summary>
/// 폰이 마지막 칸에 도착했다면 위치를 옮겨주기 전 게임 오브젝트 바꿔치기
/// </summary>
private GameObject TryPromotion(Pawn pawn, int my)
{
int x = (int)pawn.transform.position.x;
int y = (int)pawn.transform.position.y;
if (pawn.isWhite && my == 8)
{
Destroy(pieces[x, y]);
return Instantiate(white_Queen, new Vector2(x, y), Quaternion.identity);
}
if (!pawn.isWhite && my == 1)
{
Destroy(pieces[x, y]);
return Instantiate(black_Queen, new Vector2(x, y), Quaternion.identity);
}
return pawn.gameObject;
}
새로 만들어진 퀸에 의한 체크 여부를 확인하기 위해 PossibleMove()를 조회할 때
아직 GameManager가 할당되지 않아 오류 뜸. 초기화를 Start()가 아니라 Awake()에서 해주어 해결.
https://official-stockfish.github.io/docs/stockfish-wiki/UCI-&-Commands.html
quit : 종료uci : 부팅 시 UCI 사용 설정. UCI 모드가 되면 uciok라고 메시지 보내준다setoption : 엔진 설정값 변경할 때 사용. button들은 value 필요 없다. setoption name <id> [value <x>]Threads type spin default 1 min 1 max 1024 : 수 찾기에 사용될 CPU 쓰레드 수. 최고 성능 내려면 가능한 CPU 최대 개수로 놓으면 된다.Hash type spin default 16 min 1 max 33554432 : MB 단위로 해쉬 테이블 크기 설정. Threads 설정 후에 Hash를 설정하는 걸 추천한다.MultiPV type spin default 1 min 1 max 500 : 뭔지 모르겠는데 그냥 1로 두면 된다고 함.NumaPolicy type string default aturo : 쓰레드를 NUMA 노드에 바인드해서 어쩌구Clear Hash type button : 해시 테이블 지우기Ponder type check default false : 상대가 생각중일 때 다음 수 생각하게 할 건지EvalFile type string default nn-[SHA256 first 12 digits].nnue : NNUE 평가 파라미터 어쩌구UCI_LimitStrength type check default false : UCI_elo로 설정한 Elo rating에 맞는 약한 플레이를 목표한다. Skill Level을 override한다.UCI type spin default 1320 min 1320 max 3190 : UCI_Strength가 켜져 있으면, 주어진 Elo를 목표한다. 120초 기본 시간 + 한 수당 1초 증가라는 제한시간과 평균 40수당 4분(= 240초) 정도의 시간 설정 기준.Skill Level type spin default 20 min 0 max 20 : Stockfish가 약하게 플레이하게 한다. MultiPV가 활성화돼있으면 Skill Level에 따라 약하게 플레이.SyzygyPath type string default <empty> : 엔드게임 데이터베이스 경로 (6기물도 150GB, 비워둬서 비활성화하는게 나을듯)Move Overhead type spin default 10 min 0 max 5000 : Stockfish가 계산한 수를 GUI에서 받아들이는 딜레이. 이거 설정 안 하면 1초 남았을 때 1초 다 쓰고 결과값을 줘서 스톡피시가 패배하는 경우가 생긴다. 내 게임은 그냥 스톡피시 믿고 0으로 둬도 될듯?Debug Log File type string default : Write all communication to and from the engine into a text file.position : fenstring에 적힌대로 포지션 세팅한다. 시작 포지션에서부터 시작하는거라면 startpos를 포함해야 함.position [fen <fenstring> | startpos ] moves <move1> .... <movei>ucinewgame을 중간에 보내줘야 함ucinewgame : 다음 서치가 새 게임에서 이루어질 때 position과 go와 함께 엔진에 보낸다. 이거 보낸 후엔 isready 보내서 엔진 켜졌는지 확인해라.isready : 엔진 초기화됐는지 확인. 켜지고 나면 readyok 응답. 엔진이 탐색 중일 때도 무조건 readyok 보내준다.go : 현재 포지션에 대해 계산 시작. 여러 매개변수가 있다. 매개변수 안 보내면 go depth 245 실행됨.searchmoves <move1> .... <movei> : 해당 수들로만 움직이도록 제한ponder : 고민 시작. 메이트여도 서치 안 멈춤.wtime <x> : 하양 시간 얼마 남았는지winc <x> : 수마다 몇 초 추가할지 설정movestogo <x> : 시간 추가 언제 되는지infinite : stop 명령어 주어질 때까지 탐색.ponderhit : Stockfish가 예측한 수를 유저가 뒀으면 GUI에서 ponderhit를 보내주면 답변 좀 더 빨라짐체스에서는 "시간 조정(time control)" 개념이 있어.
시간 조정 방식에는 크게 두 가지가 있음:
Increment 방식 (매 수마다 일정 시간 추가)
예: "10+5" → 기본 10분 + 매 수마다 5초 추가
이 경우 movestogo 필요 없음.
Fixed Moves 방식 (일정 수를 두면 추가 시간 제공)
예: "40수까지 90분, 이후 30분 추가"
즉, 40수까지 90분을 쓰고, 40수가 지나면 30분 추가됨.
이때 Stockfish에게 "40수까지 몇 수 남았는지"를 movestogo로 알려줘야 함!
public class AIManager : MonoBehaviour
{
private Process stockfish;
private StreamWriter input;
private StreamReader output;
void Start()
{
StartCoroutine(RunStockfish());
}
IEnumerator RunStockfish()
{
stockfish = new Process();
stockfish.StartInfo.FileName = Path.Combine(Application.streamingAssetsPath, "stockfish/stockfish-windows-x86-64-avx2.exe"); ; // Stockfish 실행 파일 경로
stockfish.StartInfo.RedirectStandardInput = true;
stockfish.StartInfo.RedirectStandardOutput = true;
stockfish.StartInfo.UseShellExecute = false;
stockfish.StartInfo.CreateNoWindow = true;
stockfish.Start();
input = stockfish.StandardInput;
output = stockfish.StandardOutput;
// Stockfish 초기화
input.WriteLine("uci");
input.Flush();
// Stockfish 응답 읽기
while (true)
{
if (stockfish.HasExited) yield break;
string response = output.ReadLine();
if (!string.IsNullOrEmpty(response))
{
UnityEngine.Debug.Log("Stockfish 응답: " + response);
if (response.Contains("uciok")) break;
}
yield return null;
}
// 명령어 예시: "go depth 10" (깊이 10까지 탐색)
input.WriteLine("go depth 10");
input.Flush();
while (true)
{
if (stockfish.HasExited) yield break;
string response = output.ReadLine();
if (!string.IsNullOrEmpty(response))
{
UnityEngine.Debug.Log("Stockfish 응답: " + response);
if (response.StartsWith("bestmove")) break;
}
yield return null;
}
stockfish.Close();
}
void OnApplicationQuit()
{
if (stockfish != null && !stockfish.HasExited)
{
stockfish.Kill(); // 게임 종료 시 Stockfish 프로세스도 종료
}
}
}

에디터에서 실행 꺼도 Close() 때문에 꺼지지는 않으면서 변수에 할당했던게 release되어버려서 stockfish process를 kill할 수 없는 현상 발생했었음. Close() 주석처리 하니 바로 해결.
p.119~170

Marine::Marine() {
hp = 50;
coord_x = coord_y = 0;
damage = 5;
is_dead = false;
}
이 생성자를
Marine::Marine() : hp(50), coord_x(0), coord_y(0), damage(5), is_dead(false), name(NULL) {}
이렇게 요약 가능하다
생성자 뒤에 오는 걸 초기화 리스트라고 부름
초기화 리스트를 사용하면 생성과 초기화를 동시에 함.
초기화 리스트를 사용하지 않는다면 생성을 먼저 하고 그 다음에 대입함.
int a = 10;
int a;
a = 10;
이 둘의 차이와 같다.
따라서 초기화 리스트가 좀 더 효율적이다.
상수와 레퍼런스들은 모두 생성과 동시에 초기화가 되어야 하기 때문에 클래스 내부에 그런 것들 넣고 싶으면 초기화 리스트 무조건 써야된다.
static 멤버 변수 : 전역 변수 같지만 클래스 하나에만 종속
어떤 클래스의 static 멤버 변수의 경우, 멤버 변수들 처럼 객체가 소멸될 때 소멸되는 것이 아닌, 프로그램이 종료될 때 소멸되는 것
static 멤버 변수의 경우, 클래스의 모든 객체들이 '공유' 하는 변수로써 각 객체 별로 따로 존재하는 멤버 변수들과는 달리 모든 객체들이 '하나의' static 멤버 변수를 사용
static 변수는 클래스 내부에서 위와 같이 초기화 하는 것은 불가능 합니다. 위와 같은 꼴이 되는유일한 경우는 const static 변수일 때만 가능
static 함수 : 클래스 안에 딱 1개만 존재하는 함수
static 변수가 어떠한 객체에 종속되는 것이 아니라, 그냥 클래스 자체에 딱 1 개 존재하는 것인 것 처럼, static 함수 역시 어떤 특정 객체에 종속되는 것이 아니라 클래스 전체에 딱 1 개 존재하는 함수입니다.
즉, static 이 아닌 멤버 함수들의 경우 객체를 만들어야지만 각 멤버 함수들을 호출할 수 있지만 static 함수의 경우, 객체가 없어도 그냥 클래스 자체에서 호출할 수 있게 됩니다.
static 함수 내에서는 클래스의 static 변수 만을 이용할 수 밖에 없습니다.
Marine& Marine::be_attacked(int damage_earn) {
hp -= damage_earn;
if (hp <= 0) is_dead = true;
return *this;
}
this 라는 것이 C++ 언어 차원에서 정의되어 있는 키워드 인데, 이는 객체 자신을 가리키는 포인터의 역할을 합니다. 즉, 이 멤버 함수를 호출하는 객체 자신을 가리킨다
int& access_x() { return x; }
int& c = a.access_x();
은
int &c = x; // 여기서 x 는 a 의 x
와 동일하다.
int Marine::attack() const { return default_damage; }
위 attack 함수는 '상수 멤버 함수' 로 정의된 것입니다. 우리는 상수 함수로 이 함수를 정의함으로써, 이 함수는 다른 변수의 값을 바꾸지 않는 함수라고 다른 프로그래머에게 명시 시킬 수 있습니다. 당연하게도, 상수 함수 내에서는 객체들의 '읽기' 만이 수행되며, 상수 함수 내에서 호출 할 수 있는 함수로는 다른 상수 함수 밖에 없습니다.