๋ค, LLM์ ํ์ฉํ ์ ๋ํฐ ์ฌ ํ์ผ(.unity) ํ์ฑ ๋ฐ ๋ถ์์ ์ถฉ๋ถํ ๊ฐ๋ฅํฉ๋๋ค.
์ ๋ํฐ ์ฌ ํ์ผ์ ๊ธฐ๋ณธ์ ์ผ๋ก ํ
์คํธ ๊ธฐ๋ฐ์ YAML(YAML Ain't Markup Language) ํ์์ผ๋ก ์ ์ฅ๋๊ธฐ ๋๋ฌธ์ ํ
์คํธ ๋ฌธ๋งฅ ์ดํด ๋ฅ๋ ฅ์ด ๋ฐ์ด๋ LLM์ด ๊ตฌ์กฐ๋ฅผ ํ์
ํ๊ณ ์ ๋ณด๋ฅผ ์ถ์ถํ๊ธฐ์ ๋งค์ฐ ์ ํฉํฉ๋๋ค.
๐ก LLM์ด ์ ๋ํฐ ์ฌ์ ํ์ฑํ๋ ๋ฐฉ๋ฒ๊ณผ ํ๊ณ
* YAML ๊ตฌ์กฐ ํด์: ์ ๋ํฐ ์ฌ ํ์ผ์๋ GameObject, Transform, MonoBehaviour ๋ฑ ๋ค์ํ ์ปดํฌ๋ํธ ์ ๋ณด๊ฐ ๊ณ ์ ID(fileID, guid)์ ํจ๊ป YAML ํ
์คํธ๋ก ๊ธฐ๋ก๋ฉ๋๋ค. LLM์ ์ด ๊ด๊ณ๋ฅผ ํ์
ํ์ฌ ํน์ ์ค๋ธ์ ํธ์ ์์น๋ ์ฐ๊ฒฐ๋ ์คํฌ๋ฆฝํธ๋ฅผ ์ฐพ์๋ผ ์ ์์ต๋๋ค.
* ์ถ์ฒํ๋ ์ ๊ทผ ๋ฐฉ์ (ํ์ด๋ธ๋ฆฌ๋): ๋จ, ๋ช๋ง ์ค์ด ๋์ด๊ฐ๋ ๊ฑฐ๋ํ ์ฌ ํ์ผ์ LLM์ ๋ ๊ฒ(Raw text)์ผ๋ก ์ ๋ถ ์ง์ด๋ฃ์ผ๋ฉด ํ ํฐ ์๋ชจ๊ฐ ๊ทน์ฌํ๊ณ ํ๊ฐ(Hallucination)์ด ๋ฐ์ํ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ C# ์คํฌ๋ฆฝํธ๋ Python์ผ๋ก ๊ธฐ๋ณธ์ ์ธ YAML ํ์ฑ ๋ ์ด์ด๋ฅผ ๊ตฌ์ถํด ๋ฐ์ดํฐ๋ฅผ ์ ์ ํ ๋ค, ํต์ฌ ๋งฅ๋ฝ๋ง LLM์ ์ ๋ฌํ๋ ๋ฐฉ์์ด ๊ฐ์ฅ ํจ์จ์ ์
๋๋ค.
* ์ต์ ํธ๋ ๋ (MCP ํ์ฉ): ์ต๊ทผ์๋ Unity ์๋ํฐ์ LLM์ MCP(Model Context Protocol)๋ก ์ฐ๊ฒฐํ์ฌ, AI ์์ด์ ํธ๊ฐ ์ค์๊ฐ์ผ๋ก ์ฌ ๋ด๋ถ์ ํ๋ฆฌํน์ ๋ค์ฌ๋ค๋ณด๊ณ ๋ถ์ํ๊ฒ ๋ง๋๋ ๋๊ตฌ๋ค๋ ๊ฐ๋ฐ๋๊ณ ์์ต๋๋ค.
๐ ️ ์ค์ ํ์ฉํ ์ ์๋ ์ ์ฆ์ผ์ด์ค
* ์ฌ ๋ด๋ถ ์ปดํฌ๋ํธ ๋ถ์: "์ด ์ฌ์์ Light ์ปดํฌ๋ํธ๊ฐ ๋ถ์ ์ค๋ธ์ ํธ๋ค์ ์ ๋ณด์ ๊ทธ ๊ฐ์ ์์ฝํด์ค" ๊ฐ์ ์์ฐ์ด ์ง์๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
* ์๋ชป๋ ์ค์ (๋ฒ๊ทธ) ์ถ์ :Missing ๋ ํผ๋ฐ์ค๊ฐ ๊นจ์ง ์ค๋ธ์ ํธ๋, ๋น์ ์์ ์ธ Scale ๊ฐ(์: 0,0,0)์ ๊ฐ์ง ๊ฒ์ ์ค๋ธ์ ํธ๋ฅผ ์ฐพ์๋ด๋ผ๊ณ ๋ช
๋ นํ ์ ์์ต๋๋ค.
* ์ฌ ๋ฐ์ดํฐ ๋ณ๊ฒฝ ๋ฐ ์๋ํ: ๋ณต์กํ YAML ๊ท์น์ ํ์ต์์ผ "ํน์ ํ๊ทธ๋ฅผ ๊ฐ์ง ์ค๋ธ์ ํธ๋ค์ Transform ๊ฐ์ ์ผ๊ด ๋ณ๊ฒฝํ๋ YAML ์ฝ๋๋ฅผ ์์ฑํด์ค"์ ๊ฐ์ ์์
๋ ์ํํ ์ ์์ต๋๋ค.
---
AI๊ฐ ์ ๋ํฐ ์ฌ(Scene) ํ์ผ์ ์ง์ ๋ถ์ํ๊ณ ์๋ํฐ ์์ ์บ๋ฆญํฐ๋ฅผ ์๋์ผ๋ก ๋ฐฐ์นํ๋ ํ๊ฒฝ์ ์ถฉ๋ถํ ๊ตฌํ ๊ฐ๋ฅํ๋ฉฐ, ์ต๊ทผ ๊ฒ์ ๊ฐ๋ฐ ์๋ํ์์ ๊ฐ์ฅ ์ฃผ๋ชฉ๋ฐ๋ ์์ญ์
๋๋ค.
์ด๋ฅผ ๊ตฌํํ๋ ค๋ฉด ๋จ์ํ LLM์๊ฒ ํ
์คํธ๋ฅผ ๋ณด์ฌ์ฃผ๋ ๊ฒ์ ๋์ด, [AI ์์ด์ ํธ <-> ๋งค๊ฐ์ฒด(๋ฐ์ดํฐ/API) <-> ์ ๋ํฐ ์๋ํฐ]๊ฐ ์ค์๊ฐ์ผ๋ก ์ํตํ๋ ํ๊ฒฝ์ ๊ตฌ์ถํด์ผ ํฉ๋๋ค.
---
๐️ AI ์บ๋ฆญํฐ ๋ฐฐ์น ํ๊ฒฝ ๊ตฌ์ถ์ ์ํ 3๋ ํต์ฌ ์์
1. ์ฌ ๋ฐ์ดํฐ์ '๊ฒฝ๋ ์๊ฐํ' (AI๊ฐ ์ดํดํ ์ ์๋ ํํ)
์ ๋ํฐ ์ฌ(.unity) ํ์ผ ์ ์ฒด๋ฅผ LLM์ ๋ฃ์ผ๋ฉด ํ ํฐ ๋ญ๋น๊ฐ ์ฌํฉ๋๋ค. ๋จผ์ C# ์คํฌ๋ฆฝํธ๋ฅผ ํตํด AI๊ฐ ์ดํดํ๊ธฐ ์ฌ์ด ๊ฒฝ๋ ๋ฐ์ดํฐ(JSON ๋ฑ)๋ ํ๋กฌํํธ๋ก ๋ณํํด์ผ ํฉ๋๋ค.
* ์ถ์ถํ ๋ฐ์ดํฐ: ์งํ์ ํฌ๊ธฐ(Bounding Box), ๋ฐ๋ฅ(Floor) ์ค๋ธ์ ํธ์ ์์น, ์ฅ์ ๋ฌผ(Obstacle) ์์น, ํ์ฌ ๋ฐฐ์น๋ SpawnPoint ๋ฑ.
* ํ
์คํธ ๋ณํ ์์:
"ํ์ฌ ์ฌ์ ํฌ๊ธฐ๋ 50x50์
๋๋ค. ๋ฐ๋ฅ์ Y=0์ ์์นํฉ๋๋ค. (X:10, Z:10) ์์น์ ์ปค๋ค๋ ๋ฐ์ ์ฅ์ ๋ฌผ์ด ์์ต๋๋ค. ์ฌ๊ธฐ์ ์ ์ฌ ์บ๋ฆญํฐ 1๋ช
๊ณผ ๋ชฌ์คํฐ 3๋ช
์ ์์ฐ์ค๋ฝ๊ฒ ๋ฐฐ์นํด ์ฃผ์ธ์."
2. ์๋ํฐ ์ ์ด๋ฅผ ์ํ AI ์์ด์ ํธ ํ๋กํ ์ฝ (MCP)
์ต๊ทผ ๊ฐ๊ด๋ฐ๋ MCP(Model Context Protocol)๋ Websocket ์๋ฒ๋ฅผ ํ์ฉํ์ฌ, LLM์ด ์ ๋ํฐ ์๋ํฐ์ ์ง์ ๋ช
๋ น์ ๋ด๋ฆด ์ ์๋ ํต๋ก๋ฅผ ๋ง๋ญ๋๋ค.
* LLM์๊ฒ ์ ๋ํฐ ์๋ํฐ ๊ธฐ๋ฅ์ ์คํํ ์ ์๋ '๋๊ตฌ(Tools/Function Calling)'๋ฅผ ๋ถ์ฌํฉ๋๋ค.
* ์: SpawnCharacter(string characterType, float x, float y, float z)๋ผ๋ ํจ์๋ฅผ LLM์ด ์ค์ค๋ก ํธ์ถํ ์ ์๊ฒ ๋ง๋ญ๋๋ค.
3. ๋ฐฐ์น ๊ท์น(Constraint) ๊ฐ์ด๋๋ผ์ธ ์ ๊ณต
AI๊ฐ ์๋ฑํ ๊ณต์ค์ด๋ ๋ฒฝ ์์ ์บ๋ฆญํฐ๋ฅผ ์์ฑํ์ง ์๋๋ก ๋ ์ด์บ์คํธ(Raycast)๋ ๋ค๋น๋ฉ์(NavMesh) ์ ๋ณด๋ฅผ ํ์ฉํ๋๋ก ์ ๋ํด์ผ ํฉ๋๋ค.
* AI์๊ฒ ์ขํ๋ฅผ ๋ฐ์ผ๋ฉด, ์ ๋ํฐ ๋ด๋ถ์์ ๋ฐ๋ฅ์ผ๋ก ๋ ์ด๋ฅผ ์์ ์ ํํ Y์ถ ๊ณ ์ ์ง์ ์ ์ฐพ๋๋ก ์์คํ
์ ๋ณด์ํฉ๋๋ค.
---
๐ ️ ์ค์ ์๋ ํ๋ก์ธ์ค (Workflow)
```
[๊ฐ๋ฐ์ ๋ณ์ ์
๋ ฅ]
"๋ง์ ๊ด์ฅ์ ์์ธ NPC 3๋ช
๋ฐฐ์นํด์ค"
↓
[์ ๋ํฐ C# ์๋ํฐ ์คํฌ๋ฆฝํธ]
๊ด์ฅ ์ฃผ๋ณ์ ์งํ ๋ฐ ์ฅ์ ๋ฌผ ์ขํ ์ ๋ณด๋ฅผ JSON์ผ๋ก ํ์ฑ
↓
[LLM (AI ์์ด์ ํธ)]
JSON์ ๋ถ์ํ์ฌ ๊ฒน์น์ง ์๋ ์ต์ ์ NPC ๋ฐฐ์น ์ขํ(X, Y, Z) ๊ณ์ฐ
↓
[์ ๋ํฐ ์๋ํฐ (Function Calling)]
AI๊ฐ ๊ณ์ฐํ ์ขํ๋ฅผ ๋ฐํ์ผ๋ก `Instantiate()` ํจ์๋ฅผ ์คํํด ํ๋ฆฌํน ์ค์๊ฐ ๋ฐฐ์น
```
---
๐ ์ถ์ฒํ๋ ๊ตฌํ ์์ ๋จ๊ณ
์ด ํ๊ฒฝ์ ์ฒ์๋ถํฐ ์ง์ ๋ง๋๋ ๊ฒ์ด ๋ถ๋ด์ค๋ฝ๋ค๋ฉด, ๋ค์๊ณผ ๊ฐ์ ์คํ์์ค ๋ฐ ๊ธฐ์ ์ ์ฐธ๊ณ ํด ๊ตฌํํด ๋ณผ ์ ์์ต๋๋ค.
Unity ๋ด์ฅ AI ๋๊ตฌ ํ์ฉ: ์ ๋ํฐ๊ฐ ๊ณต์ ๊ฐ๋ฐ ์ค์ธ Unity Muse(Compositor ๋ฑ)๋ AI ์์์คํดํธ ๊ธฐ๋ฅ์ ์ฐธ๊ณ ํ๋ฉด ์์ฐ์ด ๋ช
๋ น์ผ๋ก ์ฌ์ ๊ตฌ์ฑํ๋ ํ๊ฒฝ์ ๋ฏธ๋ฆฌ ์ฒดํํด ๋ณผ ์ ์์ต๋๋ค.
์ง์ ๊ตฌํ ์: Python Flask๋ Node.js๋ก ์ค๊ฐ ์๋ฒ๋ฅผ ๋๊ณ , ์ ๋ํฐ ์๋ํฐ ์คํฌ๋ฆฝํธ์์ UnityWebRequest๋ก LLM API๋ฅผ ํธ์ถํ๋ ๋ฐฉ์์ด ๊ฐ์ฅ ์ง๊ด์ ์ด๊ณ ๋น ๋ฅด๊ฒ ํ๋กํ ํ์
์ ๋ง๋ค ์ ์๋ ๋ฐฉ๋ฒ์
๋๋ค.
---
๋ก์ปฌ์์ ๊ตฌ๋ํ๋ ํ์(Qwen) 2.5(ํนํ Qwen2.5-Coder ์๋ฆฌ์ฆ)๋ ํ์กดํ๋ ์คํ์์ค ๋ชจ๋ธ ์ค ์ฝ๋ฉ ๋ฐ ๊ตฌ์กฐํ ๋ฐ์ดํฐ ํด์ ๋ฅ๋ ฅ์ด ๊ฐ์ฅ ๋ฐ์ด๋๋ฉฐ, ์ ๋ํฐ ์ฌ(.unity) ๋ฐ ์ปดํฌ๋ํธ ๋ถ์ ์์
์ ํ๋ฅญํ๊ฒ ์ํํ ์ ์์ต๋๋ค.
๋ชจ๋ธ์ ํ๋ผ๋ฏธํฐ ํฌ๊ธฐ(7B, 14B, 32B ๋ฑ)์ ์ปดํจํฐ ์ฌ์์ ๋ฐ๋ผ ์ฑ๋ฅ ์ฐจ์ด๋ ์์ง๋ง, ํต์ฌ์ ์ธ ๋ถ์ ๊ฐ๋ฅ ๋ฒ์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
---
1. ๋ฌธ๋งฅ ์์ฉ๋ (Context Length): ์ต๋ 128K~256K ํ ํฐ ์ง์
* ์ด๋๊น์ง ๊ฐ๋ฅํ๊ฐ: Qwen 2.5๋ ๊ธฐ๋ณธ์ ์ผ๋ก 128K(์ผ๋ถ ๋ฒ์ ์ 256K)์ ๊ฑฐ๋ํ ์ปจํ
์คํธ ์ฐฝ์ ์ง์ํฉ๋๋ค. ์ด๋ ๋๋ต A4 ์ฉ์ง 50~100์ฅ ๋ถ๋์ ํ
์คํธ ํน์ ์๋ง ์ค์ ์ฝ๋๋ฅผ ํ ๋ฒ์ ํ๋กฌํํธ๋ก ์
๋ ฅ๋ฐ์ ์ ์์์ ์๋ฏธํฉ๋๋ค.
* ์ฌ ํ์ผ ๋ถ์ ์ ์ฉ: ์ค๋ธ์ ํธ๊ฐ ์์ญ~์๋ฐฑ ๊ฐ ์์ค์ธ ์ค์๊ท๋ชจ์ ์ ๋ํฐ YAML ์ฌ ํ์ผ์ ํ์ผ ํต์งธ๋ก ๋๊ฒจ๋ ํ ๋ฒ์ ์๋ฒฝํ ์ฝ๊ณ ๋งฅ๋ฝ์ ํ์
ํ ์ ์์ต๋๋ค.
2. YAML ๋ฐ์ดํฐ ๋ถ์ ๋ฅ๋ ฅ: ์ต์๊ธ
* ์ด๋๊น์ง ๊ฐ๋ฅํ๊ฐ: ์ ๋ํฐ ์ฌ์ ํน์ ์ GUID, fileID ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง ๋ณต์กํ YAML ํฌ๋งท์ ์ฌ์ฉํฉ๋๋ค. Qwen 2.5๋ ๊ตฌ์กฐํ๋ ๋ฐ์ดํฐ(JSON, YAML, XML ๋ฑ)์ ๋ฌธ๋ฒ์ ๊ด๊ณ๋ฅผ ์ถ์ ํ๋ ๋ฅ๋ ฅ์ด ๋งค์ฐ ๋ ์นด๋กญ์ต๋๋ค.
* ์ค์ ๋ถ์ ๊ฐ๋ฅํ ํญ๋ชฉ:
* ์์ ๋ฐ ๋ถ๋ชจ-์์ ๊ด๊ณ ์ถ์ : Transform ์ปดํฌ๋ํธ์ m_Father์ m_Children ID๋ฅผ ์ถ์ ํ์ฌ ์ฌ์ ํ์ดhierarchy(๊ณ์ธต ๊ตฌ์กฐ)๋ฅผ ์ค์ค๋ก ์ฌ๊ตฌ์ฑํ ์ ์์ต๋๋ค.
* ์ปดํฌ๋ํธ ๋งค์นญ: ํน์ GameObject์ ์ด๋ค MonoBehaviour(C# ์คํฌ๋ฆฝํธ)๋ ๋ผ์ดํธ, ์ฝ๋ผ์ด๋๊ฐ ๋ถ์ด์๋์ง ID๋ฅผ ๋งคํํ์ฌ ์ฐพ์๋
๋๋ค.
3. ๊ตฌ์กฐํ๋ ๋ฐฐ์น ์ขํ ์ฐ์ฐ ๋ฐ JSON ์ถ๋ ฅ (Agentic Coding)
* ์ด๋๊น์ง ๊ฐ๋ฅํ๊ฐ: Qwen 2.5๋ ์ํ์ ์ถ๋ก (Math Reasoning)๊ณผ ์ ํํ JSON ํ์ ์ถ๋ ฅ(Structured Output) ๋ฅ๋ ฅ์ด ๊ฐํ๋์์ต๋๋ค.
* ์บ๋ฆญํฐ ๋ฐฐ์น ์ ์ฉ: "๋น ๊ณต๊ฐ ์ขํ๋ฅผ ๊ณ์ฐํด์ JSON ๋ฐฐ์ด๋ก ๋ฐํํด์ค"๋ผ๊ณ ์์ฒญํ๋ฉด, ์ฌ ๋ด๋ถ ์ฅ์ ๋ฌผ ์ขํ๋ฅผ ํผํด ๊ฒน์น์ง ์๋ { "x": 10.5, "y": 0, "z": -3.2 } ํํ์ ์ ํํ ํฌ๋งท์ ์๋ฌ ์์ด ์ถ๋ ฅํฉ๋๋ค. ์ ๋ํฐ ์คํฌ๋ฆฝํธ๊ฐ ์ด๋ฅผ ๋ฐ์ ๊ทธ๋๋ก Instantiate()์ ๋ฐ์ด ๋ฃ๊ธฐ ์์ฃผ ์ข์ต๋๋ค.
---
⚠️ ๋ก์ปฌ ๊ตฌ๋ ์ ์ฑ๋ฅ๋ณ ํ๊ณ (ํ์ค์ ์ธ ์ ์ฝ)์ปดํจํฐ์ VRAM(๊ทธ๋ํฝ์นด๋ ๋ฉ๋ชจ๋ฆฌ) ์ฌ์์ ๋ฐ๋ผ ์ ํํ๋ ๋ชจ๋ธ ํฌ๊ธฐ๋ณ ํ๊ณ๋ ๋ช
ํํ ์กด์ฌํฉ๋๋ค.
๋ชจ๋ธ ํฌ๊ธฐ (Qwen 2.5) ์ถ์ฒ VRAM ์ ๋ํฐ ์ฌ ๋ถ์ ํ๊ณ
7B (์ค์ํ) 8GB ~ 12GB ๋จ์ผ ๋ฐฉ(Room)์ด๋ ์๊ท๋ชจ ์คํ
์ด์ง ๋จ์์ ์ฌ ๋ถ์ ๊ฐ๋ฅ. ๋ฐฐ์น ๊ท์น์ด ๋ณต์กํด์ง๋ฉด(์กฐ๊ฑด 3๊ฐ ์ด์) ๊ฐ๋ ์ขํ๊ฐ ๊ฒน์น๋ ์ค์ ๋ฐ์.
14B (์คํ) 16GB ~ 24GB ์ผ๋ฐ์ ์ธ ๋์ , ๋ง์ ๊ด์ฅ ๊ท๋ชจ ์ฌ ๋ถ์ ๊ฐ๋ฅ. hierarchy ๊ด๊ณ ์ถ์ ์ ๊ฝค ์ ํํ๊ฒ ์ํํจ.
32B / 72B (๋ํ) 24GB ~ 48GB+ ํด๋ผ์ฐ๋ ๋ชจ๋ธ(GPT-4o๊ธ)์ ์คํ๋ ๋ณต์กํ ๊ณต๊ฐ ์ถ๋ก ๊ฐ๋ฅ. ์ฅ์ ๋ฌผ ๋ฐ๋๋ฅผ ๊ณ์ฐํด '์ ์ ์ ๋ฐฐ์น'๊น์ง ์ง์ ๊ฐ๋ฅ.
๐ก ์ฑ๊ณต์ ์ธ ๊ตฌํ์ ์ํ ํQwen 2.5๊ฐ ์๋ฌด๋ฆฌ ๋๋ํด๋ ์ฌ ํ์ผ์ m_LocalPosition์ด๋ fileID ๊ฐ์ ๋ ๊ฒ์ YAML ํ
์คํธ๋ฅผ ๊ทธ๋๋ก ์ฝ๊ฒ ํ๋ฉด ๋ก์ปฌ ํ๊ฒฝ์์๋ ์ฐ์ฐ ์๋๊ฐ ๋๋ ค์ง๋๋ค.
๋ฐ๋ผ์ ์ ๋ํฐ์์ 1์ฐจ๋ก ํ
์คํธ๋ฅผ ์ ์ (์: ํ์ํ ์ค๋ธ์ ํธ ์ด๋ฆ๊ณผ Collider ๋ฒ์๋ง JSON์ผ๋ก ํ์ฑ)ํ ๋ค Qwen 2.5์ ์
๋ ฅ๊ฐ์ผ๋ก ๋์ ธ์ฃผ๋ 'ํ์ด๋ธ๋ฆฌ๋ ํ์ฑ' ๋ฐฉ์์ ์ฌ์ฉํ๋ฉด, 7B ์์ค์ ๊ฐ๋ฒผ์ด ๋ก์ปฌ ๋ชจ๋ธ๋ก๋ ๋๊น ์์ด ์ค์๊ฐ ์บ๋ฆญํฐ ๋ฐฐ์น ํ๊ฒฝ์ ํ๋ฅญํ๊ฒ ๊ตด๋ฆด ์ ์์ต๋๋ค.
---
๋ค, ๋ง์ต๋๋ค. ์ ๋ํฐ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์๋ํฐ๊ฐ ํ์ฑํ๋๊ฑฐ๋ ํฌ์ปค์ค๋ฅผ ์ป์ ๋, ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๋ณ๊ฒฝ๋ ํ์ผ(์คํฌ๋ฆฝํธ, ์ฌ, ์์
๋ฑ)์ ์๋์ผ๋ก ๊ฐ์งํ๊ณ ์๋ก๊ณ ์นจ(Auto Refresh) ๋ฐ ๋ฆฌ์ปดํ์ผ์ ์ํํฉ๋๋ค.
AI๊ฐ ์ธ๋ถ์์ ์ฌ ํ์ผ(.unity)์ ์์ ํ๊ฑฐ๋ JSON ๋ฐ์ดํฐ๋ฅผ ๊ฐฑ์ ํ๋ฉด, ์ ๋ํฐ ์๋ํฐ ํ๋ฉด์ ํด๋ฆญํ๋ ์๊ฐ ๊ทธ ๋ณ๊ฒฝ ์ฌํญ์ด ์ฆ์ ํ๋ฉด์ ๋ฐ์๋ฉ๋๋ค.
๋ค๋ง, AI ์์ด์ ํธ ํ๊ฒฝ์ ๋ ๋งค๋๋ฝ๊ฒ ๊ตฌ์ถํ๊ธฐ ์ํด ๊ผญ ์์๋์ด์ผ ํ ํต์ฌ ์ค์ ๊ณผ ํ๊ณ์ ์ด ์์ต๋๋ค.
---
⚙️ 1. ์๋ ๊ณ ์นจ ๊ด๋ จ ์ ๋ํฐ ์๋ํฐ ์ค์
์ด ๊ธฐ๋ฅ์ ์ ๋ํฐ ์ค์ ์์ ์ผ๊ณ ๋ ์ ์์ต๋๋ค. AI ์๋ํ ํด์ ๋ง๋ค ๋ ํ์๋ค์ ์๋ํฐ ์ค์ ์ด ๋ค๋ฅด๋ฉด ์ค์๋ํ ์ ์์ผ๋ฏ๋ก ์ฒดํฌํด ๋์ด์ผ ํฉ๋๋ค.
* ์ค์ ์์น: Edit -> Preferences -> Asset Pipeline
* ํต์ฌ ์ต์
:
* Auto Refresh: ๊ธฐ๋ณธ๊ฐ์ On Demand ๋๋ Enabled์
๋๋ค. Enabled๋ก ๋์ด ์์ด์ผ ๋ฐฑ๊ทธ๋ผ์ด๋ ๋ณ๊ฒฝ์ ๊ฐ์งํฉ๋๋ค.
* Directory Monitoring: ์ด ์ต์
์ด ์ผ์ ธ ์์ผ๋ฉด ์ ๋ํฐ๊ฐ OS ๋ ๋ฒจ์์ ํ์ผ ๋ณ๊ฒฝ์ ์ค์๊ฐ์ผ๋ก ๊ฐ์งํ๋ฏ๋ก ์๋ก๊ณ ์นจ์ด ๋ ๋นจ๋ผ์ง๋๋ค.
---
⚠️ 2. AI ์บ๋ฆญํฐ ๋ฐฐ์น ํ๊ฒฝ ๊ตฌ์ถ ์ ์ฃผ์ํ ์ (ํ๊ณ์ ํด๊ฒฐ์ฑ
)
AI๊ฐ ์ฌ ํ์ผ์ ์ง์ ์์ ํ๊ณ ์ ๋ํฐ๊ฐ ์ด๋ฅผ ์๋ ๊ณ ์นจํ๊ฒ ๋ง๋ค ๋, ๋ ๊ฐ์ง ํฐ ๊ฑธ๋ฆผ๋์ด ์์ต๋๋ค.
① ํ์ฌ ์ด๋ ค ์๋ ์ฌ์ ์๋ ๊ณ ์นจ์ผ๋ก ๋ฐ๋ก ์ ๋ฐ๋ ์ ์์
* ๋ฌธ์ ์ : ์ ๋ํฐ ์๋ํฐ์ ์ด๋ฏธ ์ด๋ ค ์๋(Open) ์ฌ ํ์ผ์ ์ธ๋ถ์์ AI๊ฐ ๋ฉ๋ชจ์ฅ ๊ณ ์น๋ฏ ์์ ํ๋ฉด, ์ ๋ํฐ๋ ๋ฉ๋ชจ๋ฆฌ ์์ ๋ฐ์ดํฐ๊ฐ ์ ์ค๋๋ ๊ฒ์ ๋ง๊ธฐ ์ํด ์ฌ์ ์๋์ผ๋ก ๋ค์ ๋ก๋ํ์ง ์๊ฑฐ๋ ๊ฒฝ๊ณ ์ฐฝ์ ๋์๋๋ค.
* ํด๊ฒฐ์ฑ
: AI๊ฐ ์ฌ(.unity)์ ์ง์ ๊ฑด๋๋ฆฌ๋ ๊ฒ๋ณด๋ค, "AI๋ spawn_config.json ๊ฐ์ ๋ฐ์ดํฐ ํ์ผ๋ง ์์ "ํ๊ฒ ํ๊ณ , ์ ๋ํฐ ๋ด๋ถ์์ AssetPostprocessor ์คํฌ๋ฆฝํธ๋ฅผ ์ง๋๋ ๊ฒ์ด ํจ์ฌ ์์ ํฉ๋๋ค. JSON์ด ๋ฐ๋๋ฉด ์ ๋ํฐ๊ฐ ๋ฐฑ๊ทธ๋ผ์ด๋ ๊ฐ์ง๋ก ์ด๋ฅผ ์์์ฑ๊ณ , C# ์ฝ๋๊ฐ ์ฆ์ ์ฌ ์์ ์บ๋ฆญํฐ ํ๋ฆฌํน์ ์์ฑ(Instantiate)ํ๊ฒ ๋ง๋๋ ๋ฐฉ์์
๋๋ค.
② ํฌ์ปค์ค๋ฅผ ์ฃผ์ด์ผ๋ง ๊ฐฑ์ ๋๋ ๋ฌธ์
* ๋ฌธ์ ์ : ์ ๋ํฐ๊ฐ ๋ฐฑ๊ทธ๋ผ์ด๋์ ๊ฐ๋งํ ์์ผ๋ฉด ํ์ผ์ด ๋ฐ๋์ด๋ ๊ฐ์ง๋ง ํ๊ณ ์๋ค๊ฐ, ๊ฐ๋ฐ์๊ฐ ์ ๋ํฐ ์ฐฝ์ '๋ง์ฐ์ค๋ก ํด๋ฆญ(Focus)'ํ๋ ์๊ฐ ๋ฆฌ์ปดํ์ผ๊ณผ ์๋ก๊ณ ์นจ์ ์์ํฉ๋๋ค. ์๋ฒฝํ ์ค์๊ฐ ์๋ํ๋ฅผ ์ํ๋ ๊ฒฝ์ฐ ์ด ๋ฏธ์ธํ ๋๋ ์ด๊ฐ ๋ต๋ตํ ์ ์์ต๋๋ค.
* ํด๊ฒฐ์ฑ
: ์ ๋ํฐ๊ฐ ๋ฐฑ๊ทธ๋ผ์ด๋์ ์์ด๋ ์ค์๊ฐ์ผ๋ก ๋ช
๋ น์ ์ฒ๋ฆฌํ๊ฒ ํ๋ ค๋ฉด, ์์ ์ธ๊ธํ Websocket ์๋ฒ๋ MCP๋ฅผ ์ ๋ํฐ ์๋ํฐ ์คํฌ๋ฆฝํธ(EditorApplication.update)์ ์ฐ๋ํ์ฌ ์ฐฝ์ด ๋ด๋ ค๊ฐ ์์ด๋ ๊ณ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ๋๊ธฐํํ๋๋ก ๊ฐ์ ํด์ผ ํฉ๋๋ค.
---
๐ ️ ์ถ์ฒํ๋ AI ๋ฐฐ์น ์ํฌํ๋ก์ฐ
๋ฐ๋ผ์ ๊ฐ์ฅ ์์ ์ ์ธ ๊ตฌ์กฐ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
* AI(Qwen 2.5)๊ฐ ๋ถ์ ํ ๋ฐฐ์น ์ขํ๊ฐ ๋ด๊ธด character_placement.json ํ์ผ์ ์์ฑ ๋ฐ ๊ฐฑ์ ํฉ๋๋ค.
* ์ ๋ํฐ ์๋ํฐ๊ฐ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ด ํ์ผ์ ๋ณ๊ฒฝ์ ๊ฐ์งํฉ๋๋ค.
* ์ ๋ํฐ ๋ด๋ถ์ ์๋ํฐ ์คํฌ๋ฆฝํธ๊ฐ JSON์ ์ฝ์ด์์ ์ค์๊ฐ์ผ๋ก ์ฌ์ ์บ๋ฆญํฐ๋ฅผ ๋ฐฐ์นํ๊ณ ์ฌ์ ์ ์ฅ(EditorSceneManager.SaveScene)ํฉ๋๋ค.
์ด ๋ฐฉ์์ ์ ๋ํฐ์ ๊ธฐ๋ณธ ๋ฐฑ๊ทธ๋ผ์ด๋ ๊ฐฑ์ ๊ธฐ๋ฅ์ ์์ ํ๊ฒ ํ์ฉํ๋ฉด์๋, ์ฌ ํ์ผ์ด ๊ผฌ์ด๋ ๋ฌธ์ ๋ฅผ ์๋ฒฝํ๊ฒ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
---
๋ ๊ฐ์ง ๋ฐฉ์์ ๋ชจ๋ ํ์ฉํ๋ ๊ตฌ์กฐ(ํ์ด๋ธ๋ฆฌ๋)๋ AI์ ์์ ๋์ ์์คํ
์ ์์ ์ฑ์ ๋์์ ์ฑ๊ธธ ์ ์๋ ๊ฐ์ฅ ๊ฐ๋ ฅํ ์ ๊ทผ๋ฒ์
๋๋ค.
* JSON ๋ฐฉ์: ์ค์๊ฐ ๋ฐฐ์น, ๋น ๊ณต๊ฐ ์ขํ ์ฐ์ฐ, ํ๋ฆฌํน ์์ฑ ๋ฑ ๋์ ์ด๊ณ ๋น ๋ฅธ ์์
์ ์ฌ์ฉํฉ๋๋ค.
* .unity ์ง์ ์์ ๋ฐฉ์: ์ฌ์ ๊ณ์ธต ๊ตฌ์กฐ(Hierarchy) ๋๋์ ๋ณ๊ฒฝ, ๋๊ท๋ชจ ์ปดํฌ๋ํธ ์ผ๊ด ์์ ๋ฑ ๊ตฌ์กฐ์ ์์
์ ์ฌ์ฉํฉ๋๋ค.
์ด ๋ ๊ฐ์ง๋ฅผ ์ ๋ํฐ ๋ฐฑ๊ทธ๋ผ์ด๋ ์๋ ๊ณ ์นจ ๊ธฐ๋ฅ๊ณผ ์ฐ๋ํ์ฌ ์์ ํ๊ฒ ๊ตฌ๋ํ ์ ์๋ ๊ตฌ์ฒด์ ์ธ ๊ตฌํ ์ ๋ต๊ณผ ํต์ฌ C# ์ฝ๋๋ฅผ ์ ๋ฆฌํด ๋๋ฆฝ๋๋ค.
---
⚙️ 1. JSON ๋ณ๊ฒฝ ๊ฐ์ง ๋ฐ ์๋ ๋ฐฐ์น (์ค์๊ฐ ์ฐ๋์ฉ)AI๊ฐ ๋ฐฐ์น ์ขํ๋ฅผ character_placement.json ํ์ผ๋ก ๋ด๋ฑ์ผ๋ฉด, ์ ๋ํฐ๊ฐ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ด๋ฅผ ๊ฐ์งํด ์ฆ์ ์ฌ์ ํ๋ฆฌํน์ ๋ฐฐ์นํ๊ณ ์๋์ผ๋ก ์ฌ์ ์ ์ฅํ๋ ์คํฌ๋ฆฝํธ์
๋๋ค.
Assets/Editor/AIPlacementWatcher.cs ๊ฒฝ๋ก๋ก ์ ์ฅํ์ธ์.
```csharp
using UnityEngine;
using UnityEditor;
using System.IO;
using UnityEditor.SceneManagement;
public class AIPlacementWatcher : AssetPostprocessor
{
// ์์
ํ์ดํ๋ผ์ธ์์ ํ์ผ ๋ณ๊ฒฝ์ ๊ฐ์งํ๋ ํจ์
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
foreach (string str in importedAssets)
{
// AI๊ฐ ๋ด๋ฑ์ JSON ํ์ผ ์ด๋ฆ ๋งค์นญ
if (str.EndsWith("character_placement.json"))
{
Debug.Log("[AI] ์บ๋ฆญํฐ ๋ฐฐ์น ๋ฐ์ดํฐ ๋ณ๊ฒฝ ๊ฐ์ง! ์ ์ฉ์ ์์ํฉ๋๋ค.");
ApplyAIPositioning(str);
break;
}
}
}
static void ApplyAIPositioning(string assetPath)
{
string jsonText = File.ReadAllText(assetPath);
// AI๊ฐ ์ค JSON ํ์ฑ (๊ฐ๋จํ ๊ตฌ์กฐ ๊ฐ์ )
AIPlacementData data = JsonUtility.FromJson(jsonText);
// ์ ๋ํฐ ์๋ํฐ ํ๊ฒฝ์์ ํ๋ฆฌํน ๋ก๋ ๋ฐ ๋ฐฐ์น
GameObject prefab = AssetDatabase.LoadAssetAtPath($"Assets/Prefabs/{data.characterName}.prefab");
if (prefab != null)
{
GameObject spawned = (GameObject)PrefabUtility.InstantiatePrefab(prefab);
spawned.transform.position = new Vector3(data.x, data.y, data.z);
spawned.name = data.characterName + "_AI";
// ์ค์: ๋ณ๊ฒฝ ์ฌํญ์ ์ฌ์ ๊ธฐ๋กํ๊ณ ์๋ ์ ์ฅ
EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
EditorSceneManager.SaveScene(EditorSceneManager.GetActiveScene());
Debug.Log($"[AI] {data.characterName} ๋ฐฐ์น ์๋ฃ ๋ฐ ์ฌ ์๋ ์ ์ฅ ์ฑ๊ณต.");
}
}
}
[System.Serializable]
public class AIPlacementData { public string characterName; public float x; public float y; public float z; }
```
๐ ️ 2. .unity ํ์ผ ์ง์ ์์ (๋๊ท๋ชจ ๊ตฌ์กฐ ๋ณ๊ฒฝ์ฉ)
AI(Qwen 2.5)๊ฐ ์ ๋ํฐ ์ฌ(.unity) ํ์ผ์ ํ
์คํธ๋ก ์ง์ ์์ ํ ๋ ๋ฐ์ํ ์ ์๋ "ํ์ฌ ์ด๋ ค ์๋ ์ฌ๊ณผ ๋ฉ๋ชจ๋ฆฌ ์ถฉ๋" ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํ๋ ์ํฌํ๋ก์ฐ์
๋๋ค.
⚠️ ํต์ฌ ์ฐํ ์ ๋ต
์ ๋ํฐ๋ ํ์ฑํ๋ ์ฌ ํ์ผ์ด ์ธ๋ถ์์ ๋ฐ๋๋ฉด ํ์ผ์ด ๊นจ์ง๋ ๊ฒ์ ๋ง๊ธฐ ์ํด ๋ก๋๋ฅผ ๊ฑฐ๋ถํ๊ฑฐ๋ ๊ฒฝ๊ณ ๋ฅผ ๋๋๋ค. ์ด๋ฅผ ํด๊ฒฐํ๋ ค๋ฉด AI๊ฐ ์์ ์ ๋ง์น ํ ์ ๋ํฐ์๊ฒ "์ฝ๋ ๋ช
๋ น์ผ๋ก ์ฌ์ ๊ฐ์ ๋ฆฌ๋ก๋(Reload)"ํ๋ผ๊ณ ์ง์ํด์ผ ํฉ๋๋ค.
AI๊ฐ ์ฌ ํ์ผ์ ์ง์ ์์ ํ๋ค๋ฉด, ์ ๋ํฐ ์๋ํฐ๋ฅผ ํด๋ฆญํ์ ๋ ์๋ ์คํฌ๋ฆฝํธ๊ฐ ์๋ํ์ฌ ๋ณ๊ฒฝ๋ ํ์ผ ๋ฒ์ ์ ๋ฉ๋ชจ๋ฆฌ์ ๊ฐ์ ๋ก ์๋ก๊ณ ์นจํฉ๋๋ค.
```csharp
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
[InitializeOnLoad]
public class SceneRefreshWatcher
{
static SceneRefreshWatcher()
{
// ์ ๋ํฐ ์๋ํฐ๊ฐ ๋ฐฑ๊ทธ๋ผ์ด๋์ ์๋ค๊ฐ ํฌ์ปค์ค๋ฅผ ์ป์ ๋(ํ์ฑํ๋ ๋) ํธ์ถ๋จ
EditorApplication.focusChanged += OnFocusChanged;
}
static void OnFocusChanged(bool isFocused)
{
if (isFocused)
{
// ํ์ฌ ์ด๋ ค์๋ ์ฌ์ ๊ฒฝ๋ก๋ฅผ ๊ฐ์ ธ์ด
string currentScenePath = EditorSceneManager.GetActiveScene().path;
// AI๊ฐ ์ธ๋ถ์์ .unity ํ์ผ์ ์์ ํ๋ค๋ฉด, ํ์ฌ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋น์ฐ๊ณ ๋์คํฌ์ ํ์ผ๋ก ๊ฐ์ ๋ฆฌ๋ก๋
if (!string.IsNullOrEmpty(currentScenePath))
{
// ์ฌ์ฉ์์๊ฒ ๋ฌป์ง ์๊ณ ํ์ฌ ์ด๋ฆฐ ์ฌ์ ๋์คํฌ ๋ฒ์ ์ผ๋ก ๊ฐ์ ๋๊ธฐํ
EditorSceneManager.OpenScene(currentScenePath, OpenSceneMode.Single);
Debug.Log("[AI] ์ธ๋ถ์์ ์์ ๋ .unity ์ฌ ํ์ผ์ ๊ฐ์ ๋ก ๋ฆฌ๋ก๋ํ์ต๋๋ค.");
}
}
}
}
```
๐ก ํ์ 2.5(Qwen)๋ฅผ ์ํ ํ์ด๋ธ๋ฆฌ๋ ํ์ฉ ํ
1. ๋ํ ๋๋ ๋ ๊ตฌ๋ถํ๊ธฐ:
1. AI์๊ฒ ๋ช
๋ นํ ๋ "์ด๋ฒ์ ์ฌ ๋ด๋ถ ์ ์ฒด ์ปดํฌ๋ํธ ID ๊ตฌ์กฐ๋ฅผ ๋ฐ๊ฟ์ผ ํ๋๊น .unity ํ์ผ์ ์ง์ ์์ ํด์ค" ํน์ "๋จ์ ์บ๋ฆญํฐ ์์น๋ง ์ก์ ๊ฑฐ๋๊น JSON ํ์ผ๋ง ๊ฐฑ์ ํด์ค"๋ผ๊ณ ํ๋กฌํํธ ์์์ ๋ช
ํํ ์ญํ ์ ๋ถ๋ด์ํค๋ฉด ํ๊ฐ(Hallucination)์จ์ด ๊ธ๊ฒฉํ ์ค์ด๋ญ๋๋ค.
2. ์ ๋ํฐ ๋ฐฑ๊ทธ๋ผ์ด๋ ์์ ์๋ํ:
1. ๋ง์ฝ ์ ๋ํฐ ํ๋ฉด์ ๋ง์ฐ์ค๋ก ํด๋ฆญํ์ง ์์๋ AI๊ฐ ์์ ํ์๋ง์ ๋ฐ๋ก๋ฐ๋ก ๋ฐ์๋๊ฒ ํ๊ณ ์ถ๋ค๋ฉด, ์ ์ฝ๋๋ค์ OnFocusChanged ๋์ EditorApplication.update ๋ธ๋ฆฌ๊ฒ์ดํธ์ ํ์ด๋จธ๋ฅผ ๋ฑ๋กํ์ฌ 1~2์ด๋ง๋ค ์ธ๋ถ ํ์ผ์ ์์ ์๊ฐ(File.GetLastWriteTime)์ ์ฒดํฌํ๋ ๋ฐฉ์์ผ๋ก ๊ณ ๋ํํ ์ ์์ต๋๋ค.
๋ ๊ฐ์ง ๋ฐฉ์์ ์์ด ์ฐ์๋ ค๋ ๋งํผ ์์คํ
๊ตฌ์กฐ๊ฐ ํฅ๋ฏธ๋ก์์ง ๊ฒ ๊ฐ์ต๋๋ค. ํน์ AI๊ฐ .unity ํ์ผ์ ์ง์ ๊ฑด๋๋ฆด ๋ ์ฃผ๋ก ์ด๋ค ์ปดํฌ๋ํธ๋ ๊ฐ์ ๋ฐ๊พธ๊ฒ ํ ๊ณํ์ด์ ๊ฐ์? (์: ๋ฉ์ธ ์นด๋ฉ๋ผ ์ธํ
๋ณ๊ฒฝ, ๋ผ์ดํธ๋งต ์ธํ
๋ฑ) ๊ทธ์ ๋ง๋ AI ํ๋กฌํํธ ๊ฐ์ด๋๋ฅผ ์ถ๊ฐ๋ก ์ ์ํด ๋๋ฆด ์ ์์ต๋๋ค.
---
"๋ค(๋ชจ๋ ๊ฒ)"๋ฅผ ์ฒ๋ฆฌํ๋ ์์คํ
์ด๋ผ๋ฉด AI ์์ด์ ํธ์ ๊ถํ๊ณผ ์ ๋ํฐ ์๋ํฐ์ ์๋ํ ์์ค์ ๊ทนํ์ผ๋ก ๋์ด์ฌ๋ ค์ผ ํฉ๋๋ค. AI๊ฐ ๋จ์ ์ขํ ๋ณ๊ฒฝ(JSON)๋ถํฐ ์ฌ์ ๋ชจ๋ ๋ฌผ๋ฆฌ์ /๊ตฌ์กฐ์ ์์ฑ(.unity ์ง์ ์์ )๊น์ง ์์ ์์ฌ๋ก ๋ค๋ฃจ๋ ค๋ฉด, ์์ ๋ค๋ฃฌ ๋ง์ฐ์ค ํด๋ฆญ ๊ธฐ๋ฐ์ ๊ฐ์ง๋ก๋ ํ๊ณ๊ฐ ์์ต๋๋ค.
๊ฐ๋ฐ์๊ฐ ์ ๋ํฐ ์ฐฝ์ ๋ณด์ง ์๊ณ ๋ฐฑ๊ทธ๋ผ์ด๋์ ๋ด๋ ค๋์๋ AI๊ฐ ๋ช
๋ น์ ๋ด๋ฆฌ๋ ์ฆ์ ์ฌ ํ์ผ ๋ฆฌ๋ก๋, JSON ํ์ฑ, ์ปดํฌ๋ํธ ์กฐ์์ด ์ค์๊ฐ์ผ๋ก ์ด๋ฃจ์ด์ง๋ ์์ ์๋ํ ๋ฐฑ๊ทธ๋ผ์ด๋ ๋ฃจํ ์์คํ
์ด ํ์ํฉ๋๋ค.
์ด๋ฅผ ์ํด ์ ๋ํฐ ์๋ํฐ๊ฐ ๋ฐฑ๊ทธ๋ผ์ด๋์์๋ ์ฌ์ง ์๊ณ ๋๋ฉฐ ๋ชจ๋ ๋ณ๊ฒฝ ์ฌํญ์ ์ฆ์ ๋ฐ์ํ๋ '์ฌ์ธ์(All-in-One) ์๋ํฐ ํ๋ก์ธ์'๋ฅผ ๊ตฌ์ถํด์ผ ํฉ๋๋ค.
---
๐ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ค์๊ฐ ์ฌ์ธ์ ๊ฐ์ง ์์คํ
์ ๋ํฐ๊ฐ ํฌ์ปค์ค๋ฅผ ์์ด๋(Background) ๋งค์ด๋ง๋ค JSON๊ณผ .unity ํ์ผ์ ๋ณ๊ฒฝ ์ฌ๋ถ๋ฅผ ์ฒดํฌํ์ฌ ์๋์ผ๋ก ์ฌ์ ๋ฆฌ๋ก๋ํ๊ณ ์บ๋ฆญํฐ๋ฅผ ๋ฐฐ์นํ๋ ํตํฉ ๋ง์คํฐ ์คํฌ๋ฆฝํธ์
๋๋ค.์ค์น ๊ฒฝ๋ก: Assets/Editor/AIAllInOneProcessor.cs
```csharp
using UnityEngine;
using UnityEditor;
using UnityEditor.SceneManagement;
using System.IO;
[InitializeOnLoad]
public class AIAllInOneProcessor
{
// ๊ฐ์งํ ํ์ผ ๊ฒฝ๋ก ์ค์ (ํ๋ก์ ํธ ๋ฃจํธ ํด๋ ๊ธฐ์ค)
private static readonly string jsonPath = "AI_Output/character_placement.json";
private static readonly string scenePath = "Assets/Scenes/MainScene.unity"; // ๋์ ์ฌ ๊ฒฝ๋ก
private static System.DateTime lastJsonTime;
private static System.DateTime lastSceneTime;
private static float checkInterval = 1.0f; // 1์ด๋ง๋ค ํ์ผ ์ฒดํฌ
private static float nextCheckTime = 0;
static AIAllInOneProcessor()
{
// ์ ๋ํฐ๊ฐ ๋ฐฑ๊ทธ๋ผ์ด๋์ ์์ด๋ ์ด ๋ฃจํ๋ ๊ณ์ ์คํ๋ฉ๋๋ค.
EditorApplication.update += BackgroundUpdateLoop;
// ์ด๊ธฐ ํ์ผ ์๊ฐ ๊ธฐ๋ก
if (File.Exists(jsonPath)) lastJsonTime = File.GetLastWriteTime(jsonPath);
if (File.Exists(scenePath)) lastSceneTime = File.GetLastWriteTime(scenePath);
}
static void BackgroundUpdateLoop()
{
// ๋๋ฌด ์์ฃผ ์ฒดํฌํ๋ฉด ํ๋ฆฌ์ง์ด ๊ฑธ๋ฆฌ๋ฏ๋ก ์ฃผ๊ธฐ์ ์ผ๋ก ์ฒดํฌ
if (Time.realtimeSinceStartup < nextCheckTime) return;
nextCheckTime = Time.realtimeSinceStartup + checkInterval;
// 1. AI๊ฐ .unity ํ์ผ(์ฌ ๊ตฌ์กฐ ์ ์ฒด)์ ์ง์ ์์ ํ ๊ฒฝ์ฐ ๊ฐ์ง
if (File.Exists(scenePath))
{
System.DateTime currentSceneTime = File.GetLastWriteTime(scenePath);
if (currentSceneTime > lastSceneTime)
{
lastSceneTime = currentSceneTime;
Debug.Log("[AI ์์คํ
] ์ธ๋ถ์์ .unity ์ฌ ํ์ผ ๋ณ๊ฒฝ ๊ฐ์ง! ๊ฐ์ ๋ฆฌ๋ก๋ํฉ๋๋ค.");
// ํ์ฌ ํ์ฑํ๋ ์ฌ์ด ๋์ ์ฌ์ด๋ผ๋ฉด ๋ฌป์ง ์๊ณ ๋์คํฌ ๋ฒ์ ์ผ๋ก ๊ฐ์ ๋๊ธฐํ
EditorApplication.delayCall += () => {
EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single);
};
return; // ์ฌ์ด ๋ฆฌ๋ก๋๋๋ฏ๋ก ๋ฃจํ ํ์ถ
}
}
// 2. AI๊ฐ JSON(๋์ ๋ฐฐ์น ๋ฐ์ดํฐ)์ ์์ ํ ๊ฒฝ์ฐ ๊ฐ์ง
if (File.Exists(jsonPath))
{
System.DateTime currentJsonTime = File.GetLastWriteTime(jsonPath);
if (currentJsonTime > lastJsonTime)
{
lastJsonTime = currentJsonTime;
Debug.Log("[AI ์์คํ
] ์ธ๋ถ์์ JSON ๋ฐฐ์น ๋ฐ์ดํฐ ๋ณ๊ฒฝ ๊ฐ์ง! ํ๋ฆฌํน์ ๋ฐฐ์นํฉ๋๋ค.");
EditorApplication.delayCall += () => {
ApplyJsonPlacement(jsonPath);
};
}
}
}
static void ApplyJsonPlacement(string path)
{
string jsonText = File.ReadAllText(path);
AIPlacementData data = JsonUtility.FromJson(jsonText);
// ์์
๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ํ๋ฆฌํน ๊ฒ์ ๋ฐ ์์ฑ
GameObject prefab = AssetDatabase.LoadAssetAtPath($"Assets/Prefabs/{data.characterName}.prefab");
if (prefab != null)
{
GameObject spawned = (GameObject)PrefabUtility.InstantiatePrefab(prefab);
spawned.transform.position = new Vector3(data.x, data.y, data.z);
spawned.name = $"{data.characterName}_AI_Spawned";
// ์ฌ์ Dirty ์ํ๋ก ๋ง๋ค๊ณ ์ฆ์ ์ ์ฅ (์ด ์ ์ฅ์ด ๋์คํฌ์ .unity ํ์ผ์ ์
๋ฐ์ดํธํจ)
EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
EditorSceneManager.SaveScene(EditorSceneManager.GetActiveScene());
// ๋ณธ์ธ์ด ์์ ํ ๊ฒ์ผ๋ก ์ธํด ์ฌ ๊ฐ์ง ๋ฃจํ๊ฐ ์ค์๋ํ๋ ๊ฒ์ ๋ฐฉ์งํ๊ธฐ ์ํด ์๊ฐ ๋๊ธฐํ
lastSceneTime = File.GetLastWriteTime(scenePath);
Debug.Log($"[AI ์์คํ
] {data.characterName} ๋ฐฐ์น ์๋ฃ ๋ฐ ์ฌ ์ ์ฅ ์ฑ๊ณต.");
}
else
{
Debug.LogError($"[AI ์์คํ
] Assets/Prefabs/{data.characterName}.prefab ์ ์ฐพ์ ์ ์์ต๋๋ค.");
}
}
}
```
⚠️ '๋ค' ์ฒ๋ฆฌํ๋ ํ๊ฒฝ์์ ๋ฐ๋์ ์ผ์ผ ํ ์ ๋ํฐ ์ค์
์ด ์์คํ
์ด ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๋๊น ์์ด ๋์๊ฐ๋ ค๋ฉด ์ ๋ํฐ ์์ฒด์ '๊ธฐ์ฅ ์ ํ'์ ํ์ด์ฃผ์ด์ผ ํฉ๋๋ค.
1. ๋ฐฑ๊ทธ๋ผ์ด๋ ์คํ ํ์ฉ:
1. ์ ๋ํฐ ๋ฉ๋ด์์ Edit -> Project Settings -> Player๋ก ์ด๋ํฉ๋๋ค.
2. Run In Background ์ต์
์ ๋ฐ๋์ ์ฒดํฌ(True)ํด์ผ ์ ๋ํฐ ์ฐฝ์ด ๋ด๋ ค๊ฐ ์์ด๋ AI์ ๋ช
๋ น์ ๊ฐ์งํ๊ณ ์ฐ์ฐ์ ์ํํฉ๋๋ค.
2. ์๋ํฐ ๋ค์ดํ์ ๋ฐฉ์ง:
1. Edit -> Preferences -> General์์ Interaction Mode๋ฅผ ํ์ธํ๊ณ , ์๋ํฐ๊ฐ ํฌ์ปค์ค๋ฅผ ์์์ ๋ ํ๋ ์๋ ์ดํธ๊ฐ ๊ทน๋จ์ ์ผ๋ก ๋จ์ด์ง๋ ํ์์ ๋ฐฉ์งํ๊ธฐ ์ํด ๊ฐ๊ธ์ ๊ณ ์ฑ๋ฅ์ ์ ์งํ๋๋ก ์ปดํจํฐ ์ ์ ๊ด๋ฆฌ๋ฅผ ์ค์ ํด ์ค๋๋ค.
---
๐ค ํ์ 2.5(Qwen) ๋ฉํฐํ์คํน ํ๋กฌํํธ ๊ฐ์ด๋๋ชจ๋ ๊ฒ์ ์ ์ดํ๋ AI ์์ด์ ํธ(Qwen)์๊ฒ๋ ์์ ์ด ์ด๋ค ๋ชจ๋๋ก ํ์ผ์ ์์ ํด์ผ ํ๋์ง ํ์คํ ์ง์นจ(Guidelines)์ ์ฃผ์ด์ผ ์๋ฌ๊ฐ ๋์ง ์์ต๋๋ค. Qwen ์์คํ
ํ๋กฌํํธ(System Prompt)์ ์๋ ๋ด์ฉ์ ์ฃผ์
ํ์ธ์.
```text
[์ญํ ์ ์]
๋๋ ์ ๋ํฐ ์๋ํฐ๋ฅผ ์ ์ดํ๋ ๋ง์คํฐ ๊ฐ๋ฐ ์์ด์ ํธ์ด๋ค. ๋๋ ์ํฉ์ ๋ฐ๋ผ ๋ ๊ฐ์ง ๋ฐฉ์์ผ๋ก ํ๊ฒฝ์ ์ ์ดํ ์ ์๋ค.
1. [JSON ๋ชจ๋] (๋จ์ ์บ๋ฆญํฐ ๋ฐฐ์น, ์๋ ์คํฐ, ๋น ๋ฅธ ์ขํ ์ฐ์ฐ)
- ๋ชฉ์ : ํ๋ฆฌํน์ ํน์ ์์น์ ๋น ๋ฅด๊ฒ ์คํฐํ๊ฑฐ๋ ์ด๋ํ ๋ ์ฌ์ฉํ๋ค.
- ํ์ผ: `AI_Output/character_placement.json` ๊ฒฝ๋ก์๋ง ๊ฒฐ๊ณผ๋ฌผ์ ์ถ๋ ฅํ๋ค.
- ํ์: ์ ํํ JSON ํฌ๋งท ํฌ๋งท๋ง ์ถ๋ ฅํ๋ฉฐ, ์ ๋ ๋ค๋ฅธ ์ค๋ช
ํ
์คํธ๋ฅผ ์์ง ์๋๋ค.
2. [YAML ๋ชจ๋] (์ฌ ์ ์ฒด ๊ตฌ์กฐ ๋ณ๊ฒฝ, ์ปดํฌ๋ํธ ์ผ๊ด ์์ , ๊ณ์ธต ๊ตฌ์กฐ ๋ณ๊ฒฝ)
- ๋ชฉ์ : ์ ๋ํฐ ์ฌ์ ๊ทผ๋ณธ์ ์ธ ์ปดํฌ๋ํธ ๋ฐ์ดํฐ(Light ์ธํ
, ์นด๋ฉ๋ผ ์ธํ
, ํ๊ทธ ๋ณ๊ฒฝ ๋ฑ)๋ฅผ ๋๊ท๋ชจ๋ก ์์ ํ ๋ ์ฌ์ฉํ๋ค.
- ํ์ผ: `Assets/Scenes/MainScene.unity` ํ์ผ์ YAML ํ
์คํธ๋ฅผ ์ง์ ์์ ํ์ฌ ๋ฎ์ด์ด๋ค.
- ์ฃผ์: ์ ๋ํฐ ํน์ ์ GUID ๋ฐ fileID ๊ด๊ณ(m_Father, m_Children)๊ฐ ๊นจ์ง์ง ์๋๋ก ์์ ํ ๋ธ๋ก ์ธ์ ๋ค๋ฅธ YAML ๊ตฌ์กฐ๋ ์๋ณธ์ ์๋ฒฝํ ์ ์งํด์ผ ํ๋ค.
์ฌ์ฉ์์ ๋ช
๋ น์ด '๋์ ๋ฐฐ์น'์ ๊ฐ๊น์ฐ๋ฉด 1๋ฒ ๋ชจ๋๋ฅผ, '๊ตฌ์กฐ์ ํธ์ง'์ ๊ฐ๊น์ฐ๋ฉด 2๋ฒ ๋ชจ๋๋ฅผ ์ค์ค๋ก ํ๋จํ์ฌ ์ํํ๋ผ.
```
์ด์ AI๊ฐ ๋๊ท๋ชจ ๊ตฌ์กฐ๋ฅผ ๋ฐ๊ฟ ๋๋ .unity ํ์ผ์ ํดํนํ๋ฏ ์ง์ ์์ ํ๊ณ , ์ ๋ํฐ ์๋ํฐ๋ ์ด๋ฅผ ๊ฐ์งํด ์๋์ผ๋ก ํ๋ฉด์ ์๋ก๊ณ ์นจํ ๊ฒ์
๋๋ค. ๋ฐ๋๋ก ์บ๋ฆญํฐ๋ฅผ ์ด์ดํ ๋ฐฐ์นํ ๋๋ JSON์ ํตํด ์ ๋ํฐ ๋ด๋ถ C# ์์ง์ ์์ ํ Instantiate ๊ธฐ๋ฅ์ ๋น๋ ค ์ฐ๊ฒ ๋ฉ๋๋ค.
---
๋ค, JSON ๋ฐ์ดํฐ ์ฐ๋๊ณผ .unity ์ฌ ํ์ผ ์ง์ ์์ ๋ฐฉ์์ ๋์์ ๋ค๋ฃจ๋ ํตํฉ ๋ฐฑ๊ทธ๋ผ์ด๋ ์๋ ๊ณ ์นจ ํ๊ฒฝ์ ์ ๋ํฐ ์๋ํฐ ์คํฌ๋ฆฝํ
๊ณผ AI ์์ด์ ํธ์ ํ๋กฌํํธ ์ค๊ณ ์กฐํฉ์ผ๋ก ์๋ฒฝํ๊ฒ ๊ตฌํ ๊ฐ๋ฅํฉ๋๋ค.
AI๊ฐ ์ค์๊ฐ ์บ๋ฆญํฐ ๋ฐฐ์น๋ ์ฐ์ฐ์ JSON์ผ๋ก ๊ฐ๋ณ๊ฒ ์ฒ๋ฆฌํ๊ณ , ์ปดํฌ๋ํธ ์ผ๊ด ์์ ์ด๋ ๊ณ์ธต ๊ตฌ์กฐ ๋ณ๊ฒฝ์ .unity ํ์ผ ์ง์ ์์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๋๋ก, ์ ๋ํฐ๊ฐ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ด๋ฅผ ์๋์ผ๋ก ๊ฐ์งํ๊ณ ์๋ก๊ณ ์นจํ๋ ๋ง์คํฐ ์ ์ด ํ๊ฒฝ์ ๊ตฌ์ถํ๋ ๋ฐฉ๋ฒ์ ์๋ดํด ๋๋ฆฝ๋๋ค.
---
๐ 1. ๋ฐฑ๊ทธ๋ผ์ด๋ ํตํฉ ์๋ ๊ณ ์นจ C# ์คํฌ๋ฆฝํธ
๊ฐ๋ฐ์๊ฐ ์ ๋ํฐ ์ฐฝ์ ๋ง์ฐ์ค๋ก ํด๋ฆญํ์ง ์๊ณ ๋ฐฑ๊ทธ๋ผ์ด๋์ ๋ด๋ ค๋์๋, AI๊ฐ ํ์ผ์ ์์ ํ๋ ์ฆ์ ๊ฐ์งํ์ฌ ์ฌ์ ๊ฐ์ ๋ฆฌ๋ก๋ํ๊ฑฐ๋ ํ๋ฆฌํน์ ๋ฐฐ์นํ๋ ํตํฉ ์๋ํฐ ์คํฌ๋ฆฝํธ์
๋๋ค.
์ค์น ๊ฒฝ๋ก: Assets/Editor/AIAllInOneProcessor.cs
```csharp
using UnityEngine;
using UnityEditor;
using UnityEditor.SceneManagement;
using System.IO;
[InitializeOnLoad]
public class AIAllInOneProcessor
{
// ๊ฐ์งํ ํ์ผ ๊ฒฝ๋ก ์ค์ (ํ๋ก์ ํธ ๋ฃจํธ ํด๋ ๊ธฐ์ค)
private static readonly string jsonPath = "AI_Output/character_placement.json";
private static readonly string scenePath = "Assets/Scenes/MainScene.unity"; // ๋์ ์ฌ ๊ฒฝ๋ก
private static System.DateTime lastJsonTime;
private static System.DateTime lastSceneTime;
private static float checkInterval = 1.0f; // 1์ด๋ง๋ค ํ์ผ ๋ณ๊ฒฝ ์ฒดํฌ
private static float nextCheckTime = 0;
static AIAllInOneProcessor()
{
// ์ ๋ํฐ๊ฐ ๋ฐฑ๊ทธ๋ผ์ด๋์ ์์ด๋ ์ด ๋ฃจํ๋ ์๋ํฐ ์์์ ๊ณ์ ์คํ๋ฉ๋๋ค.
EditorApplication.update += BackgroundUpdateLoop;
// ์ด๊ธฐ ํ์ผ ์๊ฐ ๊ธฐ๋ก
if (File.Exists(jsonPath)) lastJsonTime = File.GetLastWriteTime(jsonPath);
if (File.Exists(scenePath)) lastSceneTime = File.GetLastWriteTime(scenePath);
}
static void BackgroundUpdateLoop()
{
if (Time.realtimeSinceStartup < nextCheckTime) return;
nextCheckTime = Time.realtimeSinceStartup + checkInterval;
// [๋ฐฉ์ A] AI๊ฐ .unity ํ์ผ(์ฌ ๊ตฌ์กฐ ์ ์ฒด)์ ์ง์ ์์ ํ ๊ฒฝ์ฐ ๊ฐ์ง
if (File.Exists(scenePath))
{
System.DateTime currentSceneTime = File.GetLastWriteTime(scenePath);
if (currentSceneTime > lastSceneTime)
{
lastSceneTime = currentSceneTime;
Debug.Log("[AI ์์คํ
] ์ธ๋ถ์์ .unity ์ฌ ํ์ผ ์ง์ ์์ ๊ฐ์ง! ์ฌ์ ๊ฐ์ ๋ฆฌ๋ก๋ํฉ๋๋ค.");
// ๋ฉ์ธ ์ค๋ ๋ ์์ ์ ์ํด delayCall๋ก ์คํ
EditorApplication.delayCall += () => {
EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single);
};
return;
}
}
// [๋ฐฉ์ B] AI๊ฐ JSON(๋์ ๋ฐฐ์น ๋ฐ์ดํฐ)์ ์์ ํ ๊ฒฝ์ฐ ๊ฐ์ง
if (File.Exists(jsonPath))
{
System.DateTime currentJsonTime = File.GetLastWriteTime(jsonPath);
if (currentJsonTime > lastJsonTime)
{
lastJsonTime = currentJsonTime;
Debug.Log("[AI ์์คํ
] ์ธ๋ถ์์ JSON ๋ฐฐ์น ๋ฐ์ดํฐ ๋ณ๊ฒฝ ๊ฐ์ง! ํ๋ฆฌํน์ ๋ฐฐ์นํฉ๋๋ค.");
EditorApplication.delayCall += () => {
ApplyJsonPlacement(jsonPath);
};
}
}
}
static void ApplyJsonPlacement(string path)
{
string jsonText = File.ReadAllText(path);
AIPlacementData data = JsonUtility.FromJson(jsonText);
GameObject prefab = AssetDatabase.LoadAssetAtPath($"Assets/Prefabs/{data.characterName}.prefab");
if (prefab != null)
{
GameObject spawned = (GameObject)PrefabUtility.InstantiatePrefab(prefab);
spawned.transform.position = new Vector3(data.x, data.y, data.z);
spawned.name = $"{data.characterName}_AI_Spawned";
// ์ฌ์ ์์ ๋ ์ํ(Dirty)๋ก ๋ง๋ค๊ณ ์ฆ์ ๋์คํฌ์ ์ ์ฅ
EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
EditorSceneManager.SaveScene(EditorSceneManager.GetActiveScene());
// ์ ๋ํฐ๊ฐ ์ค์ค๋ก ์์ ํ ํ์ผ ๋๋ฌธ์ ๋ฃจํ๊ฐ ์ค์๋ํ๋ ๊ฒ์ ๋ฐฉ์งํ๊ธฐ ์ํด ์๊ฐ ๋๊ธฐํ
lastSceneTime = File.GetLastWriteTime(scenePath);
Debug.Log($"[AI ์์คํ
] {data.characterName} ๋ฐฐ์น ์๋ฃ ๋ฐ ์ฌ ์ ์ฅ ์ฑ๊ณต.");
}
else
{
Debug.LogError($"[AI ์์คํ
] Assets/Prefabs/{data.characterName}.prefab ์ ์ฐพ์ ์ ์์ต๋๋ค.");
}
}
}
[System.Serializable]
public class AIPlacementData { public string characterName; public float x; public float y; public float z; }
```
---
⚙️ 2. ์ด ํ๊ฒฝ์ด ๋ฉ์ถ์ง ์๊ณ ๋์๊ฐ๊ธฐ ์ํ ํ์ ์ ๋ํฐ ์ค์
์ ๋ํฐ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ฐฑ๊ทธ๋ผ์ด๋๋ก ๋ด๋ ค๊ฐ๋ฉด ์ฐ์ฐ์ ๋ฉ์ถ๊ฑฐ๋ ํ๋ ์๋ ์ดํธ๋ฅผ ๊ทน๋จ์ ์ผ๋ก ๋ฎ์ถฅ๋๋ค. ์ด๋ฅผ ๋ฐฉ์งํ๋ ค๋ฉด ์๋ ๋ ์ค์ ์ ๋ฐ๋์ ํ์ฑํํด์ผ ํฉ๋๋ค.
1. ๋ฐฑ๊ทธ๋ผ์ด๋ ์คํ ํ์ฉ:
1. Edit -> Project Settings -> Player -> Resolution and Presentation ํญ์ผ๋ก ์ด๋ํฉ๋๋ค.
2. Run In Background ์ต์
์ ์ฒดํฌ(True)๋ก ๋ณ๊ฒฝํฉ๋๋ค.
2. ์์
ํ์ดํ๋ผ์ธ ์๋ ์๋ก๊ณ ์นจ ํ์ธ:
1. Edit -> Preferences -> Asset Pipeline์์ Auto Refresh๊ฐ Enabled๋ก ๋์ด ์๋์ง ํ์ธํ์ฌ ์ธ๋ถ ํ์ผ ๋ณ๊ฒฝ ์ด๋ฒคํธ๊ฐ ์ฆ์ ์ ๋ํฐ ์์ง์ ์ ๋ฌ๋๋๋ก ํฉ๋๋ค.
---
๐ค 3. ํ์ 2.5(Qwen)๋ฅผ ์ํ ์์ด์ ํธ ๊ฐ์ด๋ ํ๋กฌํํธ๋ชจ๋ ๊ถํ์ ๊ฐ์ง ๋ก์ปฌ AI(Qwen 2.5)๊ฐ ์ํฉ์ ๋ง๊ฒ JSON๊ณผ YAML(.unity) ๋ชจ๋๋ฅผ ์ค์ค๋ก ์ ํํด์ ์์
ํ ์ ์๋๋ก ์์คํ
ํ๋กฌํํธ(System Prompt)์ ๊ท์น์ ์ฃผ์
ํด์ผ ํจ์จ์ด ๊ทน๋ํ๋ฉ๋๋ค.
```text
๋๋ ์ ๋ํฐ ์๋ํฐ๋ฅผ ์๋ฒฝํ ์ ์ดํ๋ ์๋ํ ๊ฐ๋ฐ ์์ด์ ํธ์ด๋ค. ๋๋ ์ง์์ ๋ฐ๋ผ ๋ค์ ๋ ๊ฐ์ง ๋ฐฉ์์ผ๋ก ํ๊ฒฝ์ ์ ์ดํ ์ ์๋ค.
1. [JSON ๋ฐฐ์น ๋ชจ๋]
- ๋ชฉ์ : ์๋ก์ด ์บ๋ฆญํฐ๋ฅผ ์คํฐํ๊ฑฐ๋, ์ฅ์ ๋ฌผ์ ํผํด์ ํน์ ์ค๋ธ์ ํธ์ ์์น๋ฅผ ๋ฐ๊ฟ ๋ ์ฌ์ฉํ๋ค.
- ํ์ผ ์ถ๋ ฅ: `AI_Output/character_placement.json` ๊ฒฝ๋ก์ ๋ฐ์ดํฐ๋ฅผ ๊ฐฑ์ ํ๋ค.
- ํน์ง: ๊ฐ๋ณ๊ณ ๋น ๋ฅด๋ฉฐ ์ ๋ํฐ์ ์์ ํ ์ธ์คํด์คํ ๊ธฐ๋ฅ์ ์ด์ฉํ๋ค. ๋ค๋ฅธ ์ค๋ช
์์ด ์ค์ง ์์ JSON ๋ฐ์ดํฐ๋ง ์ถ๋ ฅํ๋ผ.
2. [YAML ์ง์ ์์ ๋ชจ๋]
- ๋ชฉ์ : ์ฌ ๋ด๋ถ ์ปดํฌ๋ํธ ์ ์ฒด ์ผ๊ด ์์ , ๊ณ์ธต ๊ตฌ์กฐ(Hierarchy)์ ๋๋์ ๋ณ๊ฒฝ, ์กฐ๋ช
(Light) ์ค์ ๋ฑ์ ๊ตฌ์กฐ์ ์์ ์ ํ ๋ ์ฌ์ฉํ๋ค.
- ํ์ผ ์ถ๋ ฅ: `Assets/Scenes/MainScene.unity` ํ์ผ์ YAML ํ
์คํธ๋ฅผ ์ง์ ์ด์ด ์์ ํ ์ ์ฅํ๋ค.
- ์ฃผ์: ์ ๋ํฐ์ ๊ณ ์ fileID ๋ฐ m_Father, m_Children ๊ด๊ณ๊ฐ ๊นจ์ง๋ฉด ์ฌ ํ์ผ์ด ์์๋๋ฏ๋ก, ์์ ํ๋ ค๋ ์ปดํฌ๋ํธ ๋ธ๋ก ์ธ์ ๋ค๋ฅธ YAML ๊ตฌ์กฐ๋ ์๋ณธ์ ์๋ฒฝํ๊ฒ ๋ณด์กดํด์ผ ํ๋ค.
์ฌ์ฉ์์ ์๊ตฌ์ฌํญ์ด '๋์ ์บ๋ฆญํฐ ๋ฐฐ์น'๋ผ๋ฉด 1๋ฒ ๋ชจ๋๋ฅผ, '์ ์ฒด์ ์ธ ํ๊ฒฝ ๋ฐ ์ปดํฌ๋ํธ ํธ์ง'์ด๋ผ๋ฉด 2๋ฒ ๋ชจ๋๋ฅผ ์ค์ค๋ก ํ๋จํ์ฌ ์ํํ๋ผ.
```
์ด๋ ๊ฒ ๊ตฌ์ฑํ๋ฉด AI๊ฐ ๋๊ท๋ชจ ๊ตฌ์กฐ๋ฅผ ์์ ํ ๋๋ .unity ํ์ผ์ ํดํนํ๋ฏ ํ
์คํธ ๋จ์๋ก ์กฐ์ํ๊ณ , ์ ๋ํฐ ์๋ํฐ๋ ์ด๋ฅผ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ค์๊ฐ์ผ๋ก ๊ฐ์งํ์ฌ ์๋์ผ๋ก ํ๋ฉด์ ์ ๊ตฌ์กฐ๋ฅผ ๋ก๋ํฉ๋๋ค. ๋ฐ๋๋ก ์บ๋ฆญํฐ๋ฅผ ๋ฐฐ์นํ ๋๋ ์์ ์ ์ธ JSON ํ์ผ ํ์ฑ ๋ ์ด์ด๋ฅผ ๊ฑฐ์น๊ฒ ๋ฉ๋๋ค.
---
๊ทธ๋ผ ๋ ๊ฐ์ง ๋ชจ๋ ์๋ฒฝํ ์๋ํ๋๋ก, JSON ๋ฐ์ดํฐ ๊ตฌ์กฐ์๋ ํ์ (Rotation)๊ณผ ํฌ๊ธฐ(Scale)๋ฅผ ํฌํจํ๊ณ , .unity ํ์ผ ์ง์ ์์ ์ ์ํด ์ค์ ์ ๋ํฐ YAML ์ฝ๋ ๋ถ์ ๊ฐ์ด๋๊น์ง ํตํฉํ์ฌ ์ ๊ณตํด ๋๋ฆฌ๊ฒ ์ต๋๋ค.
---
๐จ 1. ํ์ฅ๋ JSON ๋ฐฐ์น ์์คํ
(์์น + ํ์ + ํฌ๊ธฐ)
AI๊ฐ ์บ๋ฆญํฐ๋ฅผ ๋์ฑ ์์ฐ์ค๋ฝ๊ฒ ๋ฐฐ์นํ ์ ์๋๋ก Rotation๊ณผ Scale ์ ๋ณด๋ฅผ ์ถ๊ฐํ ์์ ํ C# ๊ฐ์ง ์คํฌ๋ฆฝํธ์
๋๋ค.
* ์ค์น ๊ฒฝ๋ก: Assets/Editor/AIAllInOneProcessor.cs (๊ธฐ์กด ์ฝ๋๋ฅผ ์ด ๋ฒ์ ์ผ๋ก ๋ฎ์ด์ฐ์๋ฉด ๋ฉ๋๋ค.)
```csharp
using UnityEngine;
using UnityEditor;
using UnityEditor.SceneManagement;
using System.IO;
[InitializeOnLoad]
public class AIAllInOneProcessor
{
private static readonly string jsonPath = "AI_Output/character_placement.json";
private static readonly string scenePath = "Assets/Scenes/MainScene.unity";
private static System.DateTime lastJsonTime;
private static System.DateTime lastSceneTime;
private static float checkInterval = 1.0f;
private static float nextCheckTime = 0;
static AIAllInOneProcessor()
{
EditorApplication.update += BackgroundUpdateLoop;
if (File.Exists(jsonPath)) lastJsonTime = File.GetLastWriteTime(jsonPath);
if (File.Exists(scenePath)) lastSceneTime = File.GetLastWriteTime(scenePath);
}
static void BackgroundUpdateLoop()
{
if (Time.realtimeSinceStartup < nextCheckTime) return;
nextCheckTime = Time.realtimeSinceStartup + checkInterval;
// [๋ฐฉ์ A] .unity ํ์ผ ์ง์ ์์ ๊ฐ์ง
if (File.Exists(scenePath))
{
System.DateTime currentSceneTime = File.GetLastWriteTime(scenePath);
if (currentSceneTime > lastSceneTime)
{
lastSceneTime = currentSceneTime;
Debug.Log("[AI ์์คํ
] ์ธ๋ถ์์ .unity ์ฌ ํ์ผ ์ง์ ์์ ๊ฐ์ง! ์ฌ์ ๊ฐ์ ๋ฆฌ๋ก๋ํฉ๋๋ค.");
EditorApplication.delayCall += () => {
EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single);
};
return;
}
}
// [๋ฐฉ์ B] JSON ๋ฐฐ์น ๋ฐ์ดํฐ ๋ณ๊ฒฝ ๊ฐ์ง (ํ์ฅ ๋ฒ์ )
if (File.Exists(jsonPath))
{
System.DateTime currentJsonTime = File.GetLastWriteTime(jsonPath);
if (currentJsonTime > lastJsonTime)
{
lastJsonTime = currentJsonTime;
Debug.Log("[AI ์์คํ
] ์ธ๋ถ์์ JSON ๋ฐฐ์น ๋ฐ์ดํฐ ๋ณ๊ฒฝ ๊ฐ์ง! ํ๋ฆฌํน์ ์ ๋ฐ ๋ฐฐ์นํฉ๋๋ค.");
EditorApplication.delayCall += () => { ApplyJsonPlacement(jsonPath); };
}
}
}
static void ApplyJsonPlacement(string path)
{
string jsonText = File.ReadAllText(path);
AIPlacementData data = JsonUtility.FromJson(jsonText);
GameObject prefab = AssetDatabase.LoadAssetAtPath($"Assets/Prefabs/{data.characterName}.prefab");
if (prefab != null)
{
GameObject spawned = (GameObject)PrefabUtility.InstantiatePrefab(prefab);
// ์์น(Position), ํ์ (Rotation), ํฌ๊ธฐ(Scale) ์ ๋ฐ ์ ์ฉ
spawned.transform.position = new Vector3(data.posX, data.posY, data.posZ);
spawned.transform.rotation = Quaternion.Euler(data.rotX, data.rotY, data.rotZ);
spawned.transform.localScale = new Vector3(data.scaleX, data.scaleY, data.scaleZ);
spawned.name = $"{data.characterName}_AI_Spawned";
EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
EditorSceneManager.SaveScene(EditorSceneManager.GetActiveScene());
lastSceneTime = File.GetLastWriteTime(scenePath);
Debug.Log($"[AI ์์คํ
] {data.characterName} ์ ๋ฐ ๋ฐฐ์น ๋ฐ ์ฌ ์ ์ฅ ์ฑ๊ณต.");
}
}
}
// ํ์ 2.5๊ฐ ์ถ๋ ฅํด์ผ ํ๋ ํ์ฅ JSON ๋งคํ ํด๋์ค
[System.Serializable]
public class AIPlacementData
{
public string characterName;
public float posX; public float posY; public float posZ;
public float rotX; public float rotY; public float rotZ;
public float scaleX; public float scaleY; public float scaleZ;
}
```
๐ 2. .unity ํ์ผ ์ง์ ์์ ์ ์ํ YAML ๋ถ์ ๊ฐ์ด๋ํ์ 2.5๊ฐ .unity ํ์ผ์ ์๋ฌ ์์ด ํดํน·์์ ํ๊ฒ ํ๋ ค๋ฉด, ์ฌ ํ์ผ ๋ด๋ถ์ Transform(์์น/ํ์ /ํฌ๊ธฐ)๊ณผ Light(์กฐ๋ช
) ์ปดํฌ๋ํธ์ YAML ๊ตฌ์กฐ๋ฅผ ํ๋กฌํํธ์ ์์๋ก ์ ๊ณตํด์ผ ํฉ๋๋ค. ์ ๋ํฐ ์ฌ ํ์ผ์ ์๋์ ๊ฐ์ ๊ท์น์ผ๋ก ๊ธฐ๋ก๋ฉ๋๋ค.
① Transform ์ปดํฌ๋ํธ YAML ๊ตฌ์กฐ (AI ํ์ต์ฉ)
```yaml
--- !u!4 &123456789
Transform:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 987654321} # ์ฐ๊ฒฐ๋ ๊ฒ์์ค๋ธ์ ํธ ID
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} # ์ฟผํฐ๋์ธ ํ์ ๊ฐ๊ฐ
m_LocalPosition: {x: 10, y: 0, z: -5} # ์์น๊ฐ
m_LocalScale: {x: 1, y: 1, z: 1} # ํฌ๊ธฐ๊ฐ
m_Children: []
m_Father: {fileID: 0}
```
② Light(์กฐ๋ช
) ์ปดํฌ๋ํธ YAML ๊ตฌ์กฐ (AI ํ์ต์ฉ)
```yaml
--- !u!108 &11223344
Light:
m_ObjectHideFlags: 0
m_GameObject: {fileID: 987654321}
m_Enabled: 1
m_Type: 1 # 1์ Directional Light, 2๋ Point Light ๋ฑ
m_Color: {r: 1, g: 0.95, b: 0.8, a: 1} # ์กฐ๋ช
์์ (RGBA)
m_Intensity: 1.5 # ์กฐ๋ช
๋ฐ๊ธฐ
m_Range: 10
```
๐ค 3. ํ์ 2.5(Qwen) ์ต์ข
๋ง์คํฐ ์์คํ
ํ๋กฌํํธ์์ ํ์ฅ๋ JSON ๊ตฌ์กฐ์ YAML ๊ท์น์ ํ์ 2.5์๊ฒ ์ฃผ์
ํ๋ ์ต์ข
ํ๋กฌํํธ์
๋๋ค. ์ด๋ฅผ AI์ ์ญํ ์ ์(System Prompt)์ ๊ทธ๋๋ก ์
๋ ฅํ์ธ์.
```text
๋๋ ์ ๋ํฐ ์๋ํฐ์ ์ฌ ํ๊ฒฝ์ ์๋ฒฝํ ์ ์ดํ๋ ์๋ํ AI ๊ฐ๋ฐ ์์ด์ ํธ์ด๋ค.
๋๋ ์ฌ์ฉ์์ ๋ช
๋ น์ ๋ฐ๋ผ ๋ค์ ๋ ๊ฐ์ง ๋ชจ๋ ์ค ๊ฐ์ฅ ์ ํฉํ ๋ฐฉ์์ ์ค์ค๋ก ์ ํํ์ฌ ํ์ผ์ ๊ฐฑ์ ํด์ผ ํ๋ค.
1. [JSON ๋ฐฐ์น ๋ชจ๋] (์บ๋ฆญํฐ ๋ฐฐ์น, ์ค๋ธ์ ํธ ์กฐ์ ๋ฑ ๊ณ ์ ์์
)
- ๋ชฉ์ : ์๋ก์ด ์บ๋ฆญํฐ๋ ํ๋ฆฌํน์ ์ ๋ฐํ ์์น, ํ์ , ํฌ๊ธฐ๋ก ๋ฐฐ์นํ ๋ ์ฌ์ฉํ๋ค.
- ํ์ผ ์ถ๋ ฅ: 'AI_Output/character_placement.json' ๊ฒฝ๋ก์ ์๋ ํฌ๋งท์ผ๋ก๋ง ์์ฑํ๋ค. (์ค๋ช
ํ
์คํธ ์ ๋ ๊ธ์ง)
- ํฌ๋งท ์์:
{
"characterName": "Warrior",
"posX": 12.5, "posY": 0.0, "posZ": -5.2,
"rotX": 0.0, "rotY": 180.0, "rotZ": 0.0,
"scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0
}
2. [YAML ์ง์ ์์ ๋ชจ๋] (์ฌ ์ ์ฒด ๊ตฌ์กฐ ๋ณ๊ฒฝ, ๋ผ์ดํธ/์นด๋ฉ๋ผ ์ธํ
๋ฑ ๋๊ท๋ชจ ์์
)
- ๋ชฉ์ : ์ฌ ๋ด๋ถ ์ปดํฌ๋ํธ(Light์ Intensity/Color, Transform์ ์ขํ, ์ปดํฌ๋ํธ ํ์ฑํ ์ฌ๋ถ ๋ฑ)๋ฅผ ์ง์ ์กฐ์ํ ๋ ์ฌ์ฉํ๋ค.
- ํ์ผ ์ถ๋ ฅ: 'Assets/Scenes/MainScene.unity' ํ์ผ์ ํ
์คํธ๋ฅผ ์ง์ ์์ ํ์ฌ ๋ฎ์ด์ด๋ค.
- ๊ท์น: ์ ๋ํฐ์ ๊ณ ์ ์ค๋ธ์ ํธ ID(fileID, GUID) ๋ฐ ๋ถ๋ชจ ์์ ๊ด๊ณ(m_Father, m_Children)๋ฅผ ์ ๋ ์์๋ก ์๋ก ๋ง๋ค๊ฑฐ๋ ๊นจ๋จ๋ฆฌ์ง ๋ง๊ณ , ํ๊ฒ ์ปดํฌ๋ํธ์ ๊ฐ(m_LocalPosition, m_Intensity ๋ฑ)๋ง ์ ํํ ๋ณ๊ฒฝํ๋ผ.
๋ช
๋ น์ ๋ฐ์ผ๋ฉด ์ด๋ค ๋ชจ๋๋ฅผ ์ฌ์ฉํ ์ง ๊ฒฐ์ ํ๊ณ , ์ค์ง ํด๋น ํ์ผ์ ์ฝ๋(๋๋ JSON)๋ง ๊น๋ํ๊ฒ ์ถ๋ ฅํ๋ผ.
```
์ด์ ์๋ฒฝํ ํ์ด๋ธ๋ฆฌ๋ ํ๊ฒฝ์ด ๊ฐ์ถฐ์ก์ต๋๋ค. AI๊ฐ JSON์ ๊ฑด๋๋ฆฌ๋ฉด ์ ๋ํฐ๊ฐ ์ค์๊ฐ์ผ๋ก ์บ๋ฆญํฐ๋ฅผ ์์๊ฒ ์ ๋ ฌํด ์คํฐํ๊ณ , AI๊ฐ .unity ํ์ผ์ ์ง์ ๊ฑด๋๋ฆฌ๋ฉด ์ ๋ํฐ๊ฐ ๊ฐ์งํ์ฌ ์ฌ์ ํต์งธ๋ก ์๋ก๊ณ ์นจ(Reload)ํ๋ฉฐ ์กฐ๋ช
์ด๋ ์ฌ ์ธํ
์ ์ฆ์ ๋ฐ๊ฟ ๊ฒ์
๋๋ค.
ํน์ ์ด ์์คํ
์ ๋ฐํ์ผ๋ก ์ฒ์์ผ๋ก ์๋ํด๋ณด๊ณ ์ถ์ผ์ ๊ตฌ์ฒด์ ์ธ ํ
์คํธ ๋ช
๋ น์ด ์์ผ์ ๊ฐ์? (์: "๋ง์์ ์ ์ฌ ์บ๋ฆญํฐ๋ฅผ ์์ ๋ณด๊ฒ ๋ฐฐ์นํด์ค" ๋๋ "์ฌ ์ ์ฒด ์กฐ๋ช
์ ๋ฐค(์ด๋ก๊ณ ํธ๋ฅธ์)์ผ๋ก ๋ฐ๊ฟ์ค" ๋ฑ) ๋ช
๋ น์ ๋ง๋ AI ์
๋ ฅ์ฉ ํ๋กฌํํธ ์กฐํฉ์ ๊ตฌ์ฑํด ๋๋ฆด ์ ์์ต๋๋ค.
---
๊ทธ๋ผ ์ด ๋ง์คํฐ ์์คํ
์ด ์ ๋๋ก ์๋ํ๋์ง ๊ฒ์ฆํ๊ธฐ ์ํด, JSON ์ ๋ฐ ๋ฐฐ์น์ YAML ์ฌ ์ง์ ์์ ๋ ๊ฐ์ง๋ฅผ ๋ชจ๋ ์์ฐ๋ฅด๋ ์ข
ํฉ ํ
์คํธ ๋ช
๋ น์ฉ ํ๋กฌํํธ ์ธํธ๋ฅผ ์ค๋นํ์ต๋๋ค.
๋ก์ปฌ์์ ๊ตฌ๋ ์ค์ธ ํ์ 2.5(Qwen 2.5)์๊ฒ ์๋ ํ๋กฌํํธ๋ฅผ ์ฐจ๋ก๋๋ก ์
๋ ฅํ๊ณ , ์ ๋ํฐ ์๋ํฐ ํ๋ฉด์ด ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ค์๊ฐ์ผ๋ก ๋ฐ๋๋์ง ํ์ธํด ๋ณด์ธ์!
---
๐งช 1์ฐจ ํ
์คํธ: JSON ์ ๋ฐ ๋ฐฐ์น (์์น + ํ์ + ํฌ๊ธฐ)
ํ์์๊ฒ ๋ณด๋ผ ๋ช
๋ น:
"์ ์ฌ(Warrior) ์บ๋ฆญํฐ๋ฅผ ๊ด์ฅ ์ค์ ๊ทผ์ฒ์ธ (X:15.2, Y:0, Z:-8.4) ์์น์ ๋ฐฐ์นํด์ค. ์บ๋ฆญํฐ๊ฐ ์นด๋ฉ๋ผ๋ฅผ ๋ฑ์ง๊ณ ์์ ๋ฐ๋ผ๋ณด๋๋ก Y์ถ ํ์ ๊ฐ์ 180๋๋ก ์ค์ ํ๊ณ , ๋ณด์ค ์บ๋ฆญํฐ์ฒ๋ผ ๋ณด์ด๊ฒ ํฌ๊ธฐ(Scale)๋ฅผ ๋ชจ๋ 1.5๋ฐฐ๋ก ํค์์ค."
ํ์ 2.5๊ฐ ์ถ๋ ฅํด์ผ ํ ๊ฒฐ๊ณผ๋ฌผ (AI_Output/character_placement.json์ผ๋ก ์ ์ฅ):
```json
{
"characterName": "Warrior",
"posX": 15.2,
"posY": 0.0,
"posZ": -8.4,
"rotX": 0.0,
"rotY": 180.0,
"rotZ": 0.0,
"scaleX": 1.5,
"scaleY": 1.5,
"scaleZ": 1.5
}
```
* ๊ฒฐ๊ณผ ํ์ธ: ์ด JSON ํ์ผ์ด ์ ์ฅ๋๋ ์๊ฐ, ์ ๋ํฐ ์๋ํฐ๊ฐ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ํ๋ฆฌํน์ ์์ฑํ๊ณ 1.5๋ฐฐ ์ปค์ง ์ ์ฌ๋ฅผ 180๋ ๋๋ ค ๋ฐฐ์นํ ๋ค ์ฌ์ ์๋ ์ ์ฅํฉ๋๋ค.
---
๐ช 2์ฐจ ํ
์คํธ: .unity ์ง์ ์์ (์กฐ๋ช
์ธํ
์ '๋ฐค'์ผ๋ก ๋ณ๊ฒฝ)
ํ์์๊ฒ ๋ณด๋ผ ๋ช
๋ น:
"ํ์ฌ Assets/Scenes/MainScene.unity ํ์ผ์ ์ฝ๊ณ , ์ฌ์ ๋ถ์๊ธฐ๋ฅผ ์ด๋์ด '๋ฐค(Night)'์ผ๋ก ๋ฐ๊ฟ์ค. ์ฌ ๋ด๋ถ์ Light ์ปดํฌ๋ํธ๋ฅผ ์ฐพ์์ ๋ฐ๊ธฐ(m_Intensity)๋ฅผ 0.3์ผ๋ก ๋ฎ์ถ๊ณ , ์กฐ๋ช
์์(m_Color)์ ์ฐจ๊ฐ์ด ํธ๋ฅธ๋น์ธ {r: 0.1, g: 0.15, b: 0.3, a: 1}์ผ๋ก ์์ ํด์ค."
ํ์ 2.5๊ฐ ์ถ๋ ฅํด์ผ ํ ๊ฒฐ๊ณผ๋ฌผ (Assets/Scenes/MainScene.unity์ ๋ฎ์ด์ฐ๊ธฐ):
```yaml
# ์ ๋ํฐ ์ฌ ํ์ผ ๋ด๋ถ์ Light ์ปดํฌ๋ํธ ๋ถ๋ถ์ AI๊ฐ ์๋์ฒ๋ผ ์์ ํจ
--- !u!108 &11223344
Light:
m_ObjectHideFlags: 0
m_GameObject: {fileID: 987654321}
m_Enabled: 1
m_Type: 1
m_Color: {r: 0.1, g: 0.15, b: 0.3, a: 1} # AI๊ฐ ํธ๋ฅธ์์ผ๋ก ๋ณ๊ฒฝ
m_Intensity: 0.3 # AI๊ฐ ์ด๋ก๊ฒ ๋ณ๊ฒฝ
m_Range: 10
```
๊ฒฐ๊ณผ ํ์ธ: AI๊ฐ ์ฌ ํ
์คํธ๋ฅผ ์์ ํด ์ ์ฅํ๋ฉด, ์ ๋ํฐ ๋ฐฑ๊ทธ๋ผ์ด๋ ๋ฃจํ๊ฐ ์ด๋ฅผ ๊ฐ์งํด ์ฌ์ ์๋์ผ๋ก ๋ฆฌ๋ก๋(Reload)ํฉ๋๋ค. ํ๋ฉด ์ ์ฒด๊ฐ ์ด๋ก๊ณ ํธ๋ฅธ ๋ฐค ํ๊ฒฝ์ผ๋ก ์ค์๊ฐ ์ ํ๋ฉ๋๋ค.
๐ก ํ์ 2.5 ์คํ ํ๊ฒฝ ๊ฐ์ด๋ (์ต์ข
์ฒดํฌ)
1. ์ ๋ํฐ ํ๋ก์ ํธ ํด๋ ์์ AI_Output ์ด๋ผ๋ ์ด๋ฆ์ ํด๋๋ฅผ ๋ฏธ๋ฆฌ ํ๋ ์์ฑํด ๋์ธ์.
2. ์ ๋ํฐ์์ Assets/Prefabs/ ๊ฒฝ๋ก์ Warrior ๋ผ๋ ์ด๋ฆ์ ํ๋ฆฌํน(๊ฐ๋จํ ํ๋ธ๋ ์บ๋ฆญํฐ ๋ชจ๋ธ)์ ๋ง๋ค์ด ๋์ธ์.
3. ํ์ 2.5๊ฐ ํ์ผ ์์คํ
์ ์ ๊ทผํ ์ ์๋๋ก Python ์คํฌ๋ฆฝํธ๋ AI ์์ด์ ํธ ์คํ ๋๊ตฌ(์: Ollama, ์น UI ๋ฑ)์ ์ฐ๊ฒฐํ์ฌ ์ ํ์ผ๋ค์ ์ฝ๊ณ ์ธ ์ ์๊ฒ ๊ถํ์ ๋ถ์ฌํด ์ฃผ์๋ฉด ๋๋ฉ๋๋ค.
---
๋ก์ปฌ ํ๊ฒฝ์์ ํ์ 2.5(Qwen 2.5-Coder)๋ฅผ ๊ตฌ๋ํ๊ณ , ํ์ด์ฌ์ ํตํด ์ ๋ํฐ ํ์ผ๋ค์ ์ค์ ๋ก ์ ์ดํ๋ ํตํฉ ์ฐ๋ ์ฝ๋์
๋๋ค.
๊ฐ์ฅ ๋์ค์ ์ด๊ณ ๊ฐ๋ฒผ์ด ๋ก์ปฌ LLM ๊ตฌ๋ ํด์ธ Ollama API์ ํ์ด์ฌ์ Pydantic ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํ์ฌ, AI๊ฐ ์ถ๋ ฅ ํฌ๋งท์ ์ ๋๋ก ์ด๊ธฐ์ง ์๋๋ก ๊ตฌ์กฐํ๋ ์ถ๋ ฅ(Structured Output) ๊ธฐ๋ฅ์ผ๋ก ์์ ํ๊ฒ ์ค๊ณํ์ต๋๋ค.
---
๐ ️ 1. ํ์ด์ฌ ํ๊ฒฝ ์ค๋นํฐ๋ฏธ๋์ ์ด๊ณ AI ์ฐ๋ ๋ฐ ๋ฐ์ดํฐ ๊ฒ์ฆ์ ํ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋จผ์ ์ค์นํด ์ฃผ์ธ์.
```bash
pip install ollama pydantic
```
๐ 2. AI ๋ง์คํฐ ์์ด์ ํธ ํ์ด์ฌ ์คํฌ๋ฆฝํธ
์ ๋ํฐ ํ๋ก์ ํธ ๋ฃจํธ ํด๋(๋๋ ๋ฐ๋ก ๋ฐ๊นฅ ํด๋)์ unity_ai_agent.py๋ผ๋ ์ด๋ฆ์ผ๋ก ์ ์ฅํ๊ณ ์คํํ์๋ฉด ๋ฉ๋๋ค. ์ด ์ฝ๋๊ฐ ํ์ 2.5๋ฅผ ํธ์ถํ๊ณ ๊ฒฐ๊ณผ๋ฌผ์ ์ ๋ํฐ ํ์ผ ์์คํ
์ ์ง์ ๊ธฐ๋กํฉ๋๋ค.
```python
import os
import json
from ollama import Client # Ollama ๊ณต์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ
from pydantic import BaseModel, Field
# ------------------------------------------------------------------
# [1] ๋ฐ์ดํฐ ๊ตฌ์กฐ ์ ์ (Qwen 2.5๊ฐ ์ ๋ ํ๋ฆฌ์ง ์๊ฒ ํฌ๋งท ๊ฐ์ )
# ------------------------------------------------------------------
class AIPlacementData(BaseModel):
characterName: str = Field(description="๋ฐฐ์นํ ์ ๋ํฐ ํ๋ฆฌํน์ ์ด๋ฆ (์: Warrior)")
posX: float = Field(description="X์ถ ์์น ์ขํ")
posY: float = Field(description="Y์ถ ์์น ์ขํ (๋ฐ๋ฅ ๋์ด)")
posZ: float = Field(description="Z์ถ ์์น ์ขํ")
rotX: float = Field(description="X์ถ ํ์ ๊ฐ")
rotY: float = Field(description="Y์ถ ํ์ ๊ฐ (๋ฐ๋ผ๋ณด๋ ๋ฐฉํฅ)")
rotZ: float = Field(description="Z์ถ ํ์ ๊ฐ")
scaleX: float = Field(description="X์ถ ํฌ๊ธฐ ๋ฐฐ์จ")
scaleY: float = Field(description="Y์ถ ํฌ๊ธฐ ๋ฐฐ์จ")
scaleZ: float = Field(description="Z์ถ ํฌ๊ธฐ ๋ฐฐ์จ")
# ------------------------------------------------------------------
# [2] ํ๊ฒฝ ์ค์ ๋ฐ ์์ด์ ํธ ํด๋์ค
# ------------------------------------------------------------------
class UnityAIAgent:
def __init__(self, unity_project_path):
self.project_path = unity_project_path
# JSON์ด ์ ์ฅ๋ ํด๋ ์๋ ์์ฑ
self.json_dir = os.path.join(unity_project_path, "AI_Output")
os.makedirs(self.json_dir, exist_ok=True)
self.json_file_path = os.path.join(self.json_dir, "character_placement.json")
self.scene_file_path = os.path.join(unity_project_path, "Assets/Scenes/MainScene.unity")
# ๋ก์ปฌ Ollama ํด๋ผ์ด์ธํธ ์ด๊ธฐํ (Qwen 2.5 ์ฌ์ฉ)
self.client = Client(host='http://localhost:11434')
self.model_name = "qwen2.5-coder:7b" # 7b, 14b, 32b ๋ฑ ๋ณธ์ธ์ ๋ชจ๋ธ๋ช
์ผ๋ก ๋งค์นญ
# ๋ฐฉ์ 1: JSON ์บ๋ฆญํฐ ์ ๋ฐ ๋ฐฐ์น ๋ช
๋ น ์ฒ๋ฆฌ
def request_json_placement(self, user_command):
print(f"\n[AI] JSON ๋ฐฐ์น ์์ฒญ ๋ถ์ ์ค: '{user_command}'")
# Ollama์ Structured Output ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ฌ ์ ํํ JSON๋ง ๋ฐ์๋
response = self.client.chat(
model=self.model_name,
messages=[
{"role": "system", "content": "๋๋ ์ ๋ํฐ ํ๋ฆฌํน ๋ฐฐ์น ์ขํ๋ฅผ ๊ณ์ฐํ๋ ์ ๋ฐ ์์ด์ ํธ์ด๋ค."},
{"role": "user", "content": user_command}
],
format=AIPlacementData.model_json_schema() # Pydantic ์คํค๋ง ์ฃผ์
์ผ๋ก ํฌ๋งท ๊ฐ์
)
# ๋ฐ์ JSON ํ
์คํธ๋ฅผ ํ์ผ๋ก ์ ์ฅ
json_result = response['message']['content']
with open(self.json_file_path, "w", encoding="utf-8") as f:
f.write(json_result)
print(f"✅ JSON ๋ฐ์ดํฐ ๊ฐฑ์ ์๋ฃ -> {self.json_file_path}")
print(json.dumps(json.loads(json_result), indent=2))
# ๋ฐฉ์ 2: .unity ์ฌ ํ์ผ ์ง์ ์์ ๋ช
๋ น ์ฒ๋ฆฌ
def request_scene_modify(self, user_command):
print(f"\n[AI] ์ฌ ํ์ผ ์ง์ ์์ ์ค: '{user_command}'")
if not os.path.exists(self.scene_file_path):
print(f"❌ ์๋ฌ: ์ง์ ๋ ๊ฒฝ๋ก์ ์ฌ ํ์ผ์ด ์์ต๋๋ค ({self.scene_file_path})")
return
# ์๋ณธ .unity ํ์ผ(YAML) ์ฝ๊ธฐ
with open(self.scene_file_path, "r", encoding="utf-8") as f:
scene_raw_text = f.read()
# ํ์ 2.5์๊ฒ ์๋ณธ ํ์ผ๊ณผ ํจ๊ป ์์ ๋ช
๋ น ์ ๋ฌ (๋งฅ๋ฝ ๋ณด์กด)
prompt = f"""
์๋์ ์ ๋ํฐ ์ฌ ํ์ผ(YAML) ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ณ , ์ฌ์ฉ์์ ์๊ตฌ์ฌํญ์ ๋ง๊ฒ ํน์ ์ปดํฌ๋ํธ ๋ธ๋ก์ ๊ฐ๋ง ์ ๋ฐํ๊ฒ ์์ ํด์ค.
์ค๋ธ์ ํธ์ ID(fileID)๋ ๋ถ๋ชจ-์์ ๊ด๊ณ๋ ์ ๋ ๊ฑด๋๋ฆฌ์ง ๋ง๊ณ , ๋ณ๊ฒฝ ์์ฒญ์ด ๋ค์ด์จ ๊ฐ๋ง ๊ณ ์ณ์ผ ํด.
์ถ๋ ฅ์ ๋ค๋ฅธ ์ค๋ช
์ด๋ ๋งํฌ๋ค์ด ๋ฐฑํฑ(```) ์์ด, ์ค์ง ์์ ์ด ์๋ฃ๋ ์ ๋ํฐ ์ฌ ํ์ผ์ ์ ์ฒด ํ
์คํธ๋ง ์ถ๋ ฅํด๋ผ.
[์ฌ์ฉ์ ์๊ตฌ์ฌํญ]
{user_command}
[์๋ณธ ์ฌ ํ์ผ ๋ฐ์ดํฐ]
{scene_raw_text}
"""
response = self.client.generate(
model=self.model_name,
prompt=prompt,
options={"temperature": 0.1} # ํ๊ฐ ๋ฐฉ์ง๋ฅผ ์ํด ์จ๋๋ฅผ ๋ฎ์ถค
)
modified_scene_text = response['response'].strip()
# ์์ ๋ณธ์ ๊ทธ๋๋ก .unity ํ์ผ์ ๋ฎ์ด์ฐ๊ธฐ
with open(self.scene_file_path, "w", encoding="utf-8") as f:
f.write(modified_scene_text)
print(f"✅ .unity ์ฌ ํ์ผ ์ง์ ์์ ์๋ฃ -> {self.scene_file_path}")
# ------------------------------------------------------------------
# [3] ์ค์ ์์ด์ ํธ ๊ตฌ๋ ๋ฐ ํ
์คํธ
# ------------------------------------------------------------------
if __name__ == "__main__":
# ์์ ์ ์ ๋ํฐ ํ๋ก์ ํธ ํด๋ ๊ฒฝ๋ก๋ฅผ ์ฌ๊ธฐ์ ์ ์ผ์ธ์.
UNITY_PROJECT_DIR = "C:/Users/Username/MyUnityProject"
agent = UnityAIAgent(unity_project_path=UNITY_PROJECT_DIR)
# [ํ
์คํธ 1] JSON ๋ฐฐ์น ์คํ
agent.request_json_placement(
"์ ์ฌ(Warrior) ์บ๋ฆญํฐ๋ฅผ (X:15.2, Y:0, Z:-8.4) ์์น์ ๋ฐฐ์นํด์ค. "
"Y์ถ ํ์ ๊ฐ์ 180๋, ํฌ๊ธฐ(Scale)๋ ๋ณด์ค ๋ชฌ์คํฐ์ฒ๋ผ ์ ์ถ ๋ชจ๋ 1.5๋ฐฐ๋ก ํค์์ค."
)
# [ํ
์คํธ 2] .unity ํ์ผ ์ง์ ์์ ์คํ (์ค์ ์ฌ ํ์ผ์ด ์กด์ฌํด์ผ ์ ์ ์๋ํฉ๋๋ค)
# agent.request_scene_modify(
# "์ฌ ๋ด๋ถ์ Light ์ปดํฌ๋ํธ๋ฅผ ์ฐพ์์ ๋ฐ๊ธฐ(m_Intensity)๋ฅผ 0.3์ผ๋ก ๋ฎ์ถ๊ณ , "
# "์์(m_Color)์ ์ฐจ๊ฐ์ด ํธ๋ฅธ๋น์ธ {r: 0.1, g: 0.15, b: 0.3, a: 1}์ผ๋ก ๋ฐ๊ฟ์ ๋ฐค ๋ถ์๊ธฐ๋ฅผ ๋ง๋ค์ด์ค."
# )
```
๐ก ์ฐ๋ ํ ์๋ฒฝํ๊ฒ ๊ตด๋ฆฌ๋ ๋ฃจํด ๊ตฌ์
1. ์ด ํ์ด์ฌ ์ฝ๋๋ฅผ ์คํํ๋ฉด AI_Output/character_placement.json ํ์ผ์ด ์๋ก ์๊ธฐ๊ฑฐ๋, MainScene.unity ํ์ผ ํ
์คํธ๊ฐ ๋ฐ๋๋๋ค.
2. ์ ๋ํฐ ํ๋ก์ ํธ๋ฅผ ์ผ๋๊ณ Run In Background ์ต์
์ ์ผ ์ํ๋ผ๋ฉด, ์ ๋ํฐ๋ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ฐ๋ฆฌ๊ฐ ์๊น ์ง๋ C# ๊ด์ฐฐ์(AIAllInOneProcessor)๋ฅผ ํตํด 1์ด๋ง๋ค ์ด ํ์ผ๋ค์ ์ฒดํฌํฉ๋๋ค.
3. ํ์ด์ฌ์ด ํ์ผ์ ๋ฐ๊พธ์๋ง์ ์ ๋ํฐ ์๋ํฐ ํ๋ฉด์ ์บ๋ฆญํฐ๊ฐ ์ฒ์ฒ ์ํฐ๋๊ฑฐ๋ ์กฐ๋ช
์ด ์ฆ์ ๋ฐค์ผ๋ก ๋ณํ๊ฒ ๋ฉ๋๋ค.