AI ๊ฐ€ ๋ชฉํ‘œ๋ฌผ์„ ํ–ฅํ•ด ์ด๋™ํ•  ๋•Œ,

๊ฒฝ๋กœ ๋˜๋Š” ๋ฐ”๋กœ ๋ฐœ ๋ฐ‘์— ์ ์˜ ๋ฒ”์œ„๊ณต๊ฒฉ ์žฅํŒ์ด ๊น”๋ฆด๋•Œ๊ฐ€ ์žˆ๋‹ค.

์ด๋Ÿด๊ฒฝ์šฐ ๊ทธ ์žฅํŒ์„ ๋นจ๋ฆฌ ๋น ์ ธ๋‚˜์™€์„œ ๋‹ค์‹œ ๋ชฉํ‘œ๋ฌผ์„ ํ–ฅํ•ด ์ด๋™ํ•ด์•ผ ํ•œ๋‹ค.

๊ทธ ํ–‰๋™์„ A* pathfinding ์„ ์ด์šฉํ•ด์„œ ๊ฐ„๋‹จํ•œ ๋กœ์ง์„ ๊ตฌํ˜„ํ–ˆ๋‹ค.


AI ์˜ ํ–‰๋™์–‘์‹

1. ๋ชฉํ‘œ๋ฌผ์— ์ด๋™์‹œ, ๋ฒ”์œ„๊ณต๊ฒฉ์ด ๋ฐœ์ƒํ•˜๋ฉด ๊ทธ ๋ฒ”์œ„๋ฅผ ๋Œ์•„๊ฐ€๋Š” ๊ฒฝ๋กœ๋ฅผ ๊ณ„์‚ฐ

2. ๋งŒ์ผ ๋ฒ”์œ„๊ณต๊ฒฉ์ด ๊ด‘๋ฒ”์œ„ํ•ด์„œ ๋„ˆ๋ฌด ๋งŽ์ด ๋Œ์•„๊ฐ€์•ผ ํ•  ๊ฒฝ์šฐ, ๋ฒ”์œ„๊ณต๊ฒฉ์„ ๊ทธ๋Œ€๋กœ ํ†ต๊ณผํ•ด์„œ ์ง์ง„.

3. ๋ฒ”์œ„๊ณต๊ฒฉ์ด ํฌ์ง€ ์•Š๊ณ  ๋Œ์•„๊ฐˆ ๋งŒ ํ•˜๋‹ค๊ณ  ํŒ๋‹จ์‹œ, ๋ฒ”์œ„๊ณต๊ฒฉ์„ ๋Œ์•„์„œ ๋ชฉํ‘œ๋กœ ์ด๋™.


์•„๋ž˜ ๋™์˜์ƒ์€ ๊ทธ ์›€์ง์ž„์„ ๋…นํ™”ํ•œ ๊ฒƒ.





๋กœ์ง์€ AStar Pathfinding project ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•ด์„œ ๋งค์šฐ ๋‹จ์ˆœํ•˜๊ฒŒ ๊ตฌํ˜„ํ–ˆ๋‹ค.

AStar Pathfinding Project ์˜ ๊ทธ๋ฆฌ๋“œ ๊ทธ๋ž˜ํ”„๋ฅผ ์ด์šฉํ•ด์„œ ์‹  ์ „์ฒด๋ฅผ ๊ณ„์‚ฐํ•ด์ฃผ๊ณ ,

์žฅํŒ์€ ์›ํ•˜๋Š” ํฌ๊ธฐ์˜ ์˜ค๋ธŒ์ ํŠธ์—ย DynamicGridObstacle๋ฅผ ์ปดํฌ๋„ŒํŠธ ์ถ”๊ฐ€ ํ•ด์„œ,ย 

๋‹จ์ˆœํžˆ ์›ํ•˜๋Š” ์œ„์น˜์— ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์˜จ ์˜คํ”„ ํ•˜๋ฉด ๊ทธ๋ž˜ํ”„๊ฐ€ ๊ฒฝ์‹ ๋˜์–ด ์•Œ์•„์„œ AI๊ฐ€ ์ง€๋‚˜๊ฐˆ ์ˆ˜ ์—†๋Š” ๋ฒ”์œ„๋ฅผ ์ƒ์„ฑํ•˜๊ฒŒ ํ–ˆ๋‹ค.ย 

๊ทธ๋ž˜๊ณ  AI๋Š” ๊ทธ๋ž˜ํ”„๊ฐ€ ๋ณ€ํ™”ํ•  ๋•Œ๋งˆ๋‹ค ๋‹ค์‹œ ํŒจ์Šค๋ฅผ ๊ณ„์‚ฐํ•˜๋„๋ก ํŠธ๋ฆฌ๊ฑฐ ํ•ด์ฃผ๋ฉด ๋.


ํ•„์š”ํ•œ ํด๋ž˜์Šค๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.


์žฅํŒ ์˜ค๋ธŒ์ ํŠธ์— ๋ถ™์—ฌ์ฃผ๋Š” ํด๋ž˜์Šค

public class TestDynamicGrid : DynamicGridObstacle { public TestChannel MyChannel; protected override void OnDisable() { base.OnDisable(); MyChannel.RaiseGraphUpdatedEvent(); } public override void OnGraphsPostUpdate() { MyChannel.RaiseGraphUpdatedEvent(); } }


์ฑ„๋„์€ ๊ทธ๋ž˜ํ”„์—๊ฒŒ ๋ณ€๋™์ด ์žˆ์„ ๊ฒฝ์šฐ ์ด๋ฒคํŠธ ํŠธ๋ฆฌ๊ฑฐ์šฉ.

์žฅํŒ์ด ์˜จ ์˜คํ”„ ํ•  ๋•Œ๋งˆ๋‹ค RaiseGraphUpdatedEvent ๊ฐ€ ์‹คํ–‰๋œ๋‹ค.

[CreateAssetMenu(menuName = "SO/TestChannel")] public class TestChannel : ScriptableObject { public UnityAction OnGraphUpdated; public void RaiseGraphUpdatedEvent() { if (OnGraphUpdated != null) { OnGraphUpdated.Invoke(); } else { Debug.LogWarning("Graph updated event raised, but no one was listening"); } } }


AI ์˜ ๊ธธ์ฐพ๊ธฐ ๊ธฐ๋Šฅ์„ ๋‹ด๋‹นํ•˜๋Š” ํด๋ž˜์Šค

public class TestAIPathFinder : MonoBehaviour { public Transform Goal; public TestChannel MyChannel; private TestMovement _movement; private Seeker _seeker; private void Start() { _movement = GetComponent<TestMovement>(); _seeker = GetComponent<Seeker>(); } private void OnEnable() { MyChannel.OnGraphUpdated += OnGraphUpdated; } private void OnDisable() { MyChannel.OnGraphUpdated -= OnGraphUpdated; } private void OnGraphUpdated() { _seeker.StartPath(transform.position, Goal.position, OnPathComplete); } private void OnPathComplete(Path p) { _movement.StartPath(p, Goal.position); } }


๋งˆ์ง€๋ง‰์œผ๋กœ AI์˜ ์›€์ง์ž„์„ ๋‹ด๋‹นํ•˜๋Š” ํด๋ž˜์Šค

using Pathfinding; using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class TestMovement : MonoBehaviour { public float Speed; public float MaxDist; private float _th = 0.3f; private int _nodeNum = 0; private int _nodeNow = 0; private Path _path = null; private bool _isPathMoving = false; private bool _isDirectMoving; private Vector3 _moveDirection; private Vector3 _nodePos; private Vector3 _destPos; internal void StartPath(Path p, Vector3 DestPos) { // TODO // check how to do pooling //if (_path != null) //{ // _path.Release(this); //} _destPos = DestPos; // check if the path is too long. if so, move directly to the target. if (p.GetTotalLength() > MaxDist) { _isDirectMoving = true; _isPathMoving = false; _moveDirection = _destPos - transform.position; } else { _isDirectMoving = false; _isPathMoving = true; _path = p; _nodeNum = p.vectorPath.Count; _nodeNow = 0; _nodePos = _path.vectorPath[_nodeNow]; _moveDirection = _nodePos - transform.position; } } private void Update() { if (_isPathMoving) { // move towards it transform.Translate(_moveDirection.normalized * Speed * Time.deltaTime); // if object is close to the node, move on to the next node float dist = Vector3.Distance(this.transform.position, _nodePos); if (dist < _th) { _nodeNow += 1; if (_nodeNow >= _nodeNum) { // reached the goal, stop moving _isPathMoving = false; return; } // towards the next node _nodePos = _path.vectorPath[_nodeNow]; _moveDirection = _nodePos - transform.position; } } else if (_isDirectMoving) { // move towards destination directly transform.Translate(_moveDirection.normalized * Speed * Time.deltaTime); } } }


ํ…Œ์ŠคํŠธ์šฉ์œผ๋กœ ๋Œ€์ถฉ ์“ด ์ฝ”๋“œ๋ผ์„œ ๋”๋Ÿฌ์›Œ๋„ ์–‘ํ•ด๋ฐ”๋žŒ