https://store.steampowered.com/app/2114090/Goetita_Turnbased_City/

 

Goetita: Turn-based City on Steam

Goetita: Turn-based city is a turn-based strategy game controlling the element of luck through rational judgment. Survive even in a desperate situation when all your resources are running out with your strategy in the cold and dark city!

store.steampowered.com

 일단 당면 과제가 에너미의 이동과 센서를 구현하는 거다. 이게 사놓은 행동 트리 에셋을 이용하면 쉽게 구현할 수 있을 듯싶은데 그게 유니티 자체 길 찾기 시스템과 실시간 RPG를 염두에 두고 만든 거라서 내가 기존에 만들어 놓은 그리드 기반 시스템과 호환이 되지 않는 문제가 있다. 따라서 그건 나중에 히트맨 같은 실시간 잠입 게임을 만들 때 써야 할 듯싶다. 그전에 저번에 턴 관련해서 마무리 짓지 못한 것부터 해결해야겠다.

 턴제게임에서 에너미 인공지능이 기능하기 위해서는 자신의 턴이 왔다는 걸 먼저 감지를 해야 할 듯싶다. 물론 에너미 턴의 연산을 최적화하기 위해서는 플레이어 탄에서 틈틈이 일정 부분 생각을 하는 팁도 있겠지만... 일단은 에너미 턴으로 왔을 때부터 생각을 하도록 코딩을 할 생각이다.

 우선 코드를 보면 게임메니저에서 이 두 함수가 이 턴을 넘기는 역할을 하고 있다. 이게 턴이 넘어간 걸 업데이트로 감지를 할 수도 있겠지만 일단 턴을 감지하는걸 이들이 발동되었을 때에 한해서 진행하도록 하면 될 듯싶다. 

    void CheckEnemyTurn()
    {
        turnState = Turn.PlayerTurn;
        print("PlayerTurn");
        
        if (playerList.Count > 0)
        {
            foreach (var player in playerList)
            {
                player.currentVigor = 2;
                player.playerTurnState = PlayerTurnState.Waiting;
            }
        }
    }

    void CheckPlayerTurn (List<Player> waitingPlayerList)
    {
        if (waitingPlayerList.Count != 0)
        {
            currentPlayer = waitingPlayerList[0];
            SelectPlayer (currentPlayer);
            return;
        }
        else
        {
            currentPlayer = null;
            turnState = Turn.EnemyTurn;
            print("EnemyTurn");
            // TestEnemyAI();
            return;
        }
    }

 

 진행하다가 문득 에너미가 순차적으로 이동을 해야 하는지 동시에 이동을 해야 하는지 의문이 들었다. 일단은 안개가 있는 곳에서는 기다리는 시간이 지루하니 순간이동을 해야 한다. 안개가 없는 곳에서는 컴터가 순차적으로 이동하는 걸 보여주는 게 게임 플레이상 보다 바람직한 듯싶다. 한꺼번에 움직여 버리면 어떻게 돌아가는 건지 전황 파악이 어려우니 말이다. 죽이려고 이동했는데 다른 놈이 죽여버러서 인공지능이 꼬이는 경우도 발생할 거구 말이다.

 순차적으로 진행하게 하려면 전체적으로 조율하는건 게임 매니저에서 하고 구체적인 건 각 에너미 클래스에서 조율하도록 하는 게 좋을 듯싶다. 왠지 이걸 게임 매니저랑 구별하는 게 좋을 듯해서 별도의 EnemyAI 클래스로 만들었다. 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyAI : MonoBehaviour {

    public GameManager gameManager;

    void Awake()
    {
        gameManager = FindObjectOfType<GameManager>();
    }
}

 

 참고로 참조에서 빨간줄이 뜰 때는 비주얼 스튜디오를 껐다가 다시 키면 문제가 해결된다.

    void CheckPlayerTurn (List<Player> waitingPlayerList)
    {
        if (waitingPlayerList.Count != 0)
        {
            currentPlayer = waitingPlayerList[0];
            SelectPlayer (currentPlayer);
            return;
        }
        else
        {
            currentPlayer = null;
            turnState = Turn.EnemyTurn;
            print("EnemyTurn");
            // TestEnemyAI();
            enemyAI.StartAI();
            return;
        }
    }

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyAI : MonoBehaviour {

    public GameManager gameManager;

    void Awake()
    {
        gameManager = FindObjectOfType<GameManager>();
    }

    public void StartAI()
    {
        print ("Enemy AI is ready");
    }
}

 

 이걸로 준비는 끝이 난듯 싶다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyMove : MonoBehaviour {

    public Vector3 destination;
public EaseType easetype = EaseType.linear;

public float moveSpeed = 3f;
    public float turnSpeed = 1f;
public float iTweenDelay = 0f;

public bool isMoving = false;

Board board;
Enemy enemy;
    GameManager gameManager;

void Awake()
{
enemy = GetComponent<Enemy>();
board = FindObjectOfType<Board>();
        gameManager = FindObjectOfType<GameManager>();
}
}

 

 우선 이동에 관한 에너미 무버 클래스를 만들어야겠다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum EnemyTurnState
{
    Waiting, Active, Ending
}

public enum EnemyActiveState
{
    Ready, Moving
}


public class Enemy : MonoBehaviour {

    int Vigor = 2;

    public bool CanTargeted = false;
    public Base enemyBase;
    GameManager gameManager;
    EnemyMove enemyMoving;

    public EnemyTurnState enemyTurnState = EnemyTurnState.Waiting;
    public EnemyActiveState enemyActiveState = EnemyActiveState.Ready;

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyMove : MonoBehaviour {

    public Vector3 destination;
public GameObject destinationPoint;
public EaseType easetype = EaseType.linear;

public float moveSpeed = 3f;
    public float turnSpeed = 1f;
public float iTweenDelay = 0f;

public bool isMoving = false;

Board board;
Enemy enemy;
    GameManager gameManager;

void Awake()
{
enemy = GetComponent<Enemy>();
board = FindObjectOfType<Board>();
        gameManager = FindObjectOfType<GameManager>();
}

public void MoveUnit (Vector3 destinationPos, float delayTime = 0f)
{
StartCoroutine (MoveRoutine (destinationPos, delayTime));
}

public List<Vector3> MakeTransform (List<TileNode> way){

var transfromList = new List<Vector3>();

foreach (var node in way)
{
transfromList.Add (GameUtility.CoordinateToTransfom (node.Coordinate));
}

return transfromList;
}

// start
    public void IndicateUnit (List<TileNode> way)
{
way.Reverse();
StartCoroutine (Indicator (MakeTransform (way)));
}

IEnumerator Indicator (List<Vector3> way)
{
        // start
var destinationFlag = Instantiate (destinationPoint, way[way.Count -1], Quaternion.identity);
enemy.GetComponent<CapsuleCollider>().enabled = false;
board.ResetBoard();

yield return null;

if (way == null)
{
print ("No Way");
DestroyImmediate (destinationFlag);
enemy.GetComponent<CapsuleCollider>().enabled = true;
board.ResetBoard();
enemy.enemyActiveState = EnemyActiveState.Ready;
}

        // do
foreach (var destination in way)
{
MoveUnit (destination);
while (enemy.enemyActiveState == EnemyActiveState.Moving)
{
yield return null;
}
}

yield return null;

        // end
DestroyImmediate (destinationFlag);
enemy.GetComponent<CapsuleCollider>().enabled = true;
board.ResetBoard();
// player.activeOff.Invoke();
enemy.enemyActiveState = EnemyActiveState.Ready;
}

IEnumerator MoveRoutine (Vector3 destinationPos, float delayTime)
{
enemy.enemyActiveState = EnemyActiveState.Moving;

yield return new WaitForSeconds (delayTime);

gameObject.MoveTo(destinationPos, moveSpeed, delayTime, easetype);
gameObject.LookTo(destinationPos, turnSpeed, delayTime, easetype);

while (Vector3.Distance (destinationPos, transform.position) > 0.01f)
{
yield return null;
}

iTween.Stop (gameObject);
transform.position = destinationPos;
 
enemy.enemyActiveState = EnemyActiveState.Ready;
}
}

 

 그냥 복사 붙여 넣기 수준이다. 다이어트가 필요할까 싶기도 한데 일단은 그냥 놓아두자. 

// start
    public void IndicateUnit (List<TileNode> way)
{
way.Reverse();
StartCoroutine (Indicator (MakeTransform (way)));
}

 

 이 부분이 일종의 스타트 함수인 듯싶다. 이제 클래스가 실제로 작동을 잘하는지 테스트를 해보면 될 듯싶다. 잘 작동하면 이 두 클래스를 무버로 통합할 수도 있긴 할 텐데. 아직 잘은 몰라서 그래도 시도는 해봐야겠지.

     void Start()

    {
        var testList = new List<TileNode>();
        testList.Add(FindObjectOfType<TileNode>());
        moving.IndicateUnit (testList);
    }

 

 간단한 테스트 코드인데 매우 잘 작동을 한다. 이제 두 클래스를 통합하는 작업을 해야겠다. 근데... 음... 먼가 복잡하다. 나중에 잘하는 사람한테 물어보도록 하고 일단 넘어가야겠다.

    // void Start()
    // {
    //  var testList = new List<TileNode>();
    //  testList.Add(FindObjectOfType<TileNode>());
    //  moving.IndicateUnit (testList);
    // }

 

 테스트 코드는 비활성화한다. 그다음으로 센서를 구현해야 할 거 같다. 전지적으로 판단을 내리는 것과 제한된 상황에서 판단을 내리는 두 가지를 좀 구분을 해서 만들어야 할 듯싶다. 머랄까 다 알고 있는데... 적절한 연기를 해야 하는 부분이 있어야 하기 때문이다. 이게 게임 인공지능과 그냥 인공지능의 큰 차이중 하나인 듯 싶다. 결국 목적은 극적으로 져주는 거니 말이다. 

 일단은 우선적으로 만들어야 할게 엑스컴에 보면 그냥 움직이다가 플레이어가 근처에 있을 때 장애물로 산개하는 거다. 이게 엑스컴 라이크 장르별로 다르긴 한데 우선은 엑스컴 리메이크에 충실하게 만들고 나중에 적절히 바꿔야 할 듯싶다.

 반응을 하는 걸 구현하고 그걸 응용해서 장애물을 만들고 산개하는 걸 만들어야겠다. 장애물로 산개하는 건 효용 기반을 어느 정도 조합해서 만들어야 할 듯싶다. 완엄패라고 좋다고 이동했는데 아군 라인속으로 들어간다든지 해버리면 골치 아프니 말이다. 왠지 효용 기반으로 짤 때 공식을 만드는 게 쉽지는 않을듯 싶다. 꽤 많은 시행착오가 필요할듯 싶다. 

 일단 애드라고 하는 이걸 만드는게 급선무인 듯싶다. 보통 이건 플레이어가 맞닿게 할 수 있고 에너미가 맞닿게 할 수가 있다. 따라서 플레이어가 이동을 했을 때나. 플레이어가 에너미에게 공격을 했을때라든지. 이럴래 발동이 되게 하는거니. 게임 매니저에서 관리를 해야 할듯 싶다. 아글고 한 3명정도 에너미를 팀을 이루게 하는거가 필요하니 그룹을 짓게 하는것도 필요할듯 싶다. 

 그전에 플레이어가 이동을 했을때 감지하는 걸 만들어야겠다. 아 그리고 전장의 안개도 만들어야 할 듯싶다. 먼가  해야 할게 한두 개가 아닌 듯싶다. 일단 전장의 안개는 좀 후반부로 미루어야 겠다. 

 음... 이걸 만드는 방식을 생각해 봤는데. 플레이어와 에너미의 거리를 기록하는 방식이 하나가 있을 거다. 이렇게 하면 플레이어와 에너미의 숫자가 많아지면 좀 복잡할 수도 있을 듯싶다. 일단 플레이어는 최대 6명으로 제약이 되니 문제가 안될 수도 있지만 말이다. 아니면 에너미에게 범위를 만드는 방식이 있을수도 있다. 아니면 플레이어에게 범위를 만드는 방식도 있을수 있다. 일단은 에너미에게 범위를 만드는 방식이 합당해 보인다. 다만 나중에 은신시 장애물이 있어서 범위 내에 있지만 보이지 않는 걸로 처러할 경우에 제하는게 난관이 될수도  있겠다.

 이걸 디비니티 방식으로 하면 행동 트리 에셋에 있는 센서를 활용할 수가 있어서 편할 듯싶다. 그런데 만들려고 하려고 하니까 머리가 좀 깨지는 듯싶다. 차기작은 그리드 방식이 아니라 디비니티 방식으로 해야겠다.

 일단 고민되는 게 사각 범위로 해야 하는지 아니면 조금 현실적이게 원형으로 해야 하는지 다. 엑스컴 보면 사각형이 아니라 언뜻 보면 8 각형처럼 느껴지기도 한다. 

 이게 합리적인 이동 범위라는 게 정해진 절대 숫자가 있는 듯싶다. 이런 식으로 먼가 딱 안 맞는 게 있어서 말이다.

 일단 헥사로 딱 떨어지는 경우를 정리를 하면

  이동력이 3인 경우이다. 오브젝트의 크기는 12...

 이동력이 4인 경우는 좀 덜 이쁘게 나온다. 오브젝트 크기는 16...

 이동력이 5인 경우. 오브젝트의 크기는 20...

 기본이 동력을 3 4 5중 하나로 잡아야 될 듯싶다.

 기본이 동력을 5로 한다면 최대 이동 범위는 이렇게 되고 40이다. 나름 그럴듯하다.

 

 기본 이동력을 3으로 잡으면 최대 이동 범위는 이렇게 되고 이것도 나름 그럴듯하다. 24이다.

 작전 반경과 오브젝트 배치 반경 등 많은 요소가 영향을 받을 수밖에 없기 때문에 둘 중 하나를 신중하게 결정해야 할 듯싶다. 

 아무래도 기본 이동력이 5이고 최대 이동력이 10은 돼야 좀 움직이는 맛이 있고 그러지 않을까 싶다.

 카메라 뷰나 각도는 이 정도는 돼야 답답하진 않을 듯싶다. 한 번에 10칸씩 이동하니 말이다.

 최대 이동 가능 범위가 이게 딱 이동 칸이라는 맞아떨어지는 숫자가 아니다 보니 조금 계산적으로 플레이하는 사람에겐 불합리 할수도 있다. 어느정도 직관적으로 감으로 플레이 하는 사람에게는 오히려 이 시스템이 보다 합리적이지 않나 싶다. 

 엑스컴의 경우에는 7칸인데 한 타 일의 크기가 좀 더 작다. 내쪽이 좀 더 뛰엄뛰엄이다. 따라서 체감상 느낌은 비슷하지 않을까 싶다. 보통 엄패물이 5칸씩 떨어져 있으니. 엄패물이 3칸 내에는 있어야 안전하게 이동할 수 있을 듯싶다. 

 그냥 7칸 28로 하는 게 좋을 듯싶다. 5칸으로 하면 너무 엄패물들이 붙게 되니 맵이 갑갑해 보일 수도 있고 말이다.  최대 거리인 56도 꽤 합리적이다.

 일단 이를 인지하는 레이어 마스크를 따로 만들어야 할 듯싶은데. 우선 이동에 관해서는 이동 범위가 7인걸 먼저 만들고. 한 번에 최대로 이동할 수 있는 시스템을 만들어야 할 듯싶다. 14 이동범 위중 7 이동범 위를 빼면 될 듯싶다.

 우선 타일 노드 클래스로 가서 해당 영역을 표시하는 것부터 만들고 이동 가능 영역을 제한하는 것을 만들어야 할 듯싶다. 노드가 일정한 에어리어에서만 클릭을 동작하게 말이다. 

 일단 노멀 한 타일, 1 행동력을 소모해서 이동할 수 있는 타일, 2 행동력을 소모해서 이동할수 있는 타일, 그리고 이동할수 없는 타일 이런 게 나누어야 하니 enum을 사용하는 게 합당할 듯싶다. 나중에는 불이 붇은 타일과 독 타일도 추가해야 할듯 싶다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum TileStyle
{
    Normal, OneVigor, TwoVigor, NonWalkable, Burn, Acid
}

public class TileNode : MonoBehaviour {

 

 기존에 walkable 부울을 대체해야 할 필요가 있을 듯싶다. 일단 하나하나 바꿔 나가에겠다. 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum TileStyle
{
    Normal, OneVigor, TwoVigor, NonWalkable, Burn, Acid
}

public class TileNode : MonoBehaviour {

    public float farFormTarget;

    // bool isPathFinding = false;
    // public bool IsPathFinding {get {return isPathFinding;} set {isPathFinding = value;}}

    TileStyle tileStyle = TileStyle.Normal;

    bool walkable = true;
    public bool Walkable {set {walkable = value;} get {return walkable;}}

    Vector2 coordinate;
    public Vector2 Coordinate {set {coordinate = value;} get {return coordinate;}}

    GameManager gameManager;
    Board board;
    PathFinding pathFinding;
    PlayerMove moving;
    
    void Awake()
    {
        board = FindObjectOfType<Board>();
        pathFinding = board.GetComponent<PathFinding>();
        gameManager = FindObjectOfType<GameManager>();
    }

    void OnDrawGizmos()
    {   
        Gizmos.color = (tileStyle == TileStyle.Normal) ? Color.white : Color.red;
        Gizmos.DrawCube (transform.position, new Vector3 (1.8f, 0.1f, 1.8f));
    }

    void OnMouseDown()
    {
        // print (this.name);

        if (gameManager.currentPlayer != null)
        {
            
            if (tileStyle == TileStyle.Normal && gameManager.currentPlayer.playerTurnState == PlayerTurnState.Active && gameManager.currentPlayer.currentVigor > 0)
            {
                if (gameManager.currentPlayer.playerActiveState == PlayerActiveState.Ready)
                {
                    Moving();
                }
            }
        }
    }

    void Moving()
    {
        if (!UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject())
        {
            var player = gameManager.currentPlayer;
            if (player == null) return;
            var startNode = board.nodeList.Find (i => i.coordinate == GameUtility.Coordinate (player.transform.position));
            if (startNode != null)

            {
                var way = pathFinding.GreedPathFinding (startNode, this, board.nodeList);
                if (way != null)
                {
                    player.playerMove.IndicateUnit (way);
                    gameManager.SpendSkill(Skill.Moving);
                    // add code of map and player coordinate reset
                }
            }
            // way.ForEach (i => print (i));
        }
    }
}

 

 일단 타일 노드 내에서는 이런 식으로 바꿔 놓는다. 타일 노드 바깥에 있는 클래스를  바꾼 걸 올리는 건 생략하겠다.

 이제 플레이어가 이동할 수 있는 7칸을 표시하는 게 필요할 듯싶다. 가장 쉽게 생각할 수 있는 게 플레이어 노드에 하위로 캡슐 콜라이더를 붇어놓고 보드를 리셋할 때 활성화하는 거다. 하위 노드의 경우에는 아마 레이어를 달리 해야겠지. 

    void OnDrawGizmos()
    {   
        ColorPick();
        Gizmos.DrawCube (transform.position, new Vector3 (1.9f, 0.1f, 1.9f));
    }

    void ColorPick()
    {
        if (tileStyle == TileStyle.Normal)
        {
            Gizmos.color = Color.white;
        }
        else if (tileStyle == TileStyle.NonWalkable)
        {
            Gizmos.color = Color.red;
        }
        else if (tileStyle == TileStyle.OneVigor)
        {
            Gizmos.color = Color.blue;
        }
        else if (tileStyle == TileStyle.OneVigor)
        {
            Gizmos.color = Color.yellow;
        }
    }

 

 무난히 작동한다. 이제 타일의 스타일을 정하는 게 필요하다. 보드 클래스에서 관장하고 있는 듯싶다.

void MakeNode (int x, int y)
{
var worldPos = new Vector3 (x * interval, 0, y * interval);
var node = Instantiate<GameObject> (tile, worldPos , tile.transform.rotation);
 
node.transform.parent = this.transform;
node.name = "Node : (" + x + ", " +y + ")";
var getNode = node.GetComponent<TileNode>();
 
if (getNode == null)
{
node.AddComponent<TileNode>();
getNode = node.GetComponent<TileNode>();
}

getNode.Coordinate = new Vector2 (x, y);
 
SetTileStyle (getNode);
}

public void ResetBoard ()
{
foreach (var node in nodeList)
{
SetTileStyle (node);
}
}

void SetTileStyle (TileNode node)
{
if (Physics.CheckSphere (node.transform.position, nodeRadius, LayerMask.GetMask ("Obstacle", "Player", "Enemy")))
{
node.tileStyle = TileStyle.NonWalkable;
}
else
{
node.tileStyle = TileStyle.Normal;
}
}

 

 우선 편집이 쉽게 코드를 깔끔하게 정리한다. 초기화와 리셋 보드 둘 다 똑같은 코드가 들어가야 하기 때문이다. 두 번 작업할 필요 없으니 말이다.

    public GameObject moveBaseArea;
    public GameObject moveMaxArea;

    public PlayerMove playerMove;
    GameManager gameManager;
    public Base playerBase;

    public PlayerTurnState playerTurnState = PlayerTurnState.Waiting;
    public PlayerActiveState playerActiveState = PlayerActiveState.Ready;

    void Awake()
    {
        currentVigor = baseVigor;

        board = FindObjectOfType<Board>();
        gameManager = FindObjectOfType<GameManager>();
        playerMove = GetComponent<PlayerMove>();
        playerBase = GetComponent<Base>();
        moveBaseArea = transform.Find ("Utility").Find("MoveBaseArea").gameObject;
        moveMaxArea = transform.Find ("Utility").Find("MoveMaxArea").gameObject;
    }

 

 일단 해당 기능을 켜고 끄기 위해서 등록을 했는데 다음 단계가 잘 안된다.

조금 고생 끝에 해당 기능을 구현했다.

    void ColorPick()
    {
        if (tileStyle == TileStyle.Normal)
        {
            Gizmos.color = Color.white;
        }
        else if (tileStyle == TileStyle.NonWalkable)
        {
            Gizmos.color = Color.red;
        }
        else if (tileStyle == TileStyle.OneVigor)
        {
            Gizmos.color = Color.green;
        }
        else if (tileStyle == TileStyle.OneVigor)
        {
            Gizmos.color = Color.yellow;
        }
    }

 

public void ResetBoard ()
{
MoveAreaOn();

foreach (var node in nodeList)
{
SetTileStyle (node);
}

MoveAreaOff();
}

void MoveAreaOn()
{
gameManager.currentPlayer.moveBaseArea.SetActive (true);
}
void MoveAreaOff()
{
gameManager.currentPlayer.moveBaseArea.SetActive (false);
}

 

 이제 다음 단계는 그린 에어리어만 이동이 가능하도록 제한하는 것이다.

void MoveAreaOn()
{
if (gameManager.currentPlayer != null)
{
gameManager.currentPlayer.moveBaseArea.SetActive (true);
}
 
}
void MoveAreaOff()
{
if (gameManager.currentPlayer != null)
{
gameManager.currentPlayer.moveBaseArea.SetActive (false);
}
}

 

    void OnMouseDown()
    {
        // print (this.name);

        if (gameManager.currentPlayer != null)
        {
            
            if (tileStyle == TileStyle.OneVigor && gameManager.currentPlayer.playerTurnState == PlayerTurnState.Active && gameManager.currentPlayer.currentVigor > 0)
            {
                if (gameManager.currentPlayer.playerActiveState == PlayerActiveState.Ready)
                {
                    Moving();
                }
            }
        }
    }

 

List<TileNode> GetNears (TileNode currentNode, TileNode endNode,List<TileNode> map) {

var neighbours = new List<TileNode> ();

foreach (var direction in Nears) {

Vector2 foundNear = currentNode.Coordinate + direction;

            if (endNode.Coordinate == foundNear)
            {
                neighbours.Clear();
                neighbours.Add (endNode);
            }

            foreach (var tile in map)
            {
                if (tile.Coordinate == foundNear && tile.tileStyle == TileStyle.OneVigor)
                {
                    neighbours.Add (tile);
                }
            }
}

        if (neighbours == null)
        {
            print ("There is no nears");
        }

return neighbours;
}

 

 

 이 정도면 된 듯싶다. 이제 엑스컴처럼 한 번에 행동을 모두 다으면서 이동할 수 있도록 만들어야 한다. 먼저 Max를 만들어 놓고 덮어쓰기를 하면 되려나.

 엑스컴의 경우에는 이동 가능 범위가 아닌 데를 클릭해도 알아서 이동 가능한 최대 거리로 이동을 한다. 스마트한 기능이다. 일단 그거는 구현을 안 할 생각이다.

void MoveAreaOn()
{
if (gameManager.currentPlayer != null)
{
gameManager.currentPlayer.moveMaxArea.SetActive (true);
gameManager.currentPlayer.moveBaseArea.SetActive (true);
}
 
}
void MoveAreaOff()
{
if (gameManager.currentPlayer != null)
{
gameManager.currentPlayer.moveMaxArea.SetActive (false);
gameManager.currentPlayer.moveBaseArea.SetActive (false);
}
}

 

 일단 맥스 에어리어도 타일을 만들때 껐다가 키도록 만들어준다. 그 다음에 해야 할께 타일을 만들때 맥스에어리어 해당하는 걸 만들어야 할 듯싶다. 단 맥스 에어리어 중에 안쪽은 베이스 에어리어로 만드는 도넛 모양을 만들어야 한다. 그다음에 맥스 에어리어일 때는 행동력을 2를 소비하도록 할 필요가 있다. 거기까지 하면 플레이어 이동 관련해서는 일단 일단락이 될 듯싶다. 

    void ColorPick()
    {
        if (tileStyle == TileStyle.Normal)
        {
            Gizmos.color = Color.white;
        }
        else if (tileStyle == TileStyle.NonWalkable)
        {
            Gizmos.color = Color.red;
        }
        else if (tileStyle == TileStyle.OneVigor)
        {
            Gizmos.color = Color.green;
        }
        else if (tileStyle == TileStyle.TwoVigor)
        {
            Gizmos.color = Color.yellow;
        }
    }

 

 일단 천천히 하나하나 해 나가자.

 타일을 까는 건 보드에 있다.

void SetTileStyle (TileNode node)
{
if (Physics.CheckSphere (node.transform.position, nodeRadius, LayerMask.GetMask ("Obstacle", "Player", "Enemy")))
{
node.tileStyle = TileStyle.NonWalkable;
}
else if (Physics.CheckSphere (node.transform.position, nodeRadius, LayerMask.GetMask ("MoveBaseArea")))
{
node.tileStyle = TileStyle.OneVigor;
}
else if (Physics.CheckSphere (node.transform.position, nodeRadius, LayerMask.GetMask ("MoveMaxArea")))
{
node.tileStyle = TileStyle.TwoVigor;
}
else
{
node.tileStyle = TileStyle.Normal;
}

 

 이제 해당 에어리어로 이동할 수 있게 하고 이경우 행동력을 2를 소비하도록 해야겠다. 이동하게 하는 건 타일 노드 클래스에 있다.

    void OnMouseDown()
    {
        // print (this.name);

        // enable moving
        if (gameManager.currentPlayer != null)
        {
            if ((tileStyle == TileStyle.OneVigor || tileStyle == TileStyle.TwoVigor )
            && gameManager.currentPlayer.playerTurnState == PlayerTurnState.Active && gameManager.currentPlayer.currentVigor > 0)
            {
                if (gameManager.currentPlayer.playerActiveState == PlayerActiveState.Ready)
                {
                    Moving();
                }
            }
        }
    }
 
 
 잘 안되는 거 보니 먼가 빼먹은게 있는듯 싶다. 일단 그전에 버그로 길을 반환하지 않는다면 행동력을 제하지 않도록 해야 겠다. 길찾기 클래스에서 null을 반환하도록 해준다.
 
 
        print ("There is no way");
        return null;
 
 
 문득 드는 생각이 버그가 발생하면 캐릭터가 나타나서 버그가 발생했으니 개발진에게 문의를 하라는 메세지를 주고 홈페이지 주소가 뜨게하면 좋을듯 싶다. 일종의 제 4의 벽처럼 말이다.
 
 이제 길을 찾지 못하는 원인을 찾아 내야 될듯 싶다.
 ...
 ...
 ...

 그게 검색을 base 타일만 검색을 하고 스타일이 다른 max타일을 검색을 못하는 게 문제인 듯싶다. 

            foreach (var tile in map)
            {
                if (tile.Coordinate == foundNear && (tile.tileStyle == TileStyle.OneVigor || tile.tileStyle == TileStyle.TwoVigor))
                {
                    neighbours.Add (tile);
                }
            }
 
 패스파인더 클래스에서 이부분도 수정을 해준다. 이제 맥스로 이동을 하는 경우 이동력을 2를 소비를 하도록 바꿔주는 일만 남은듯 싶다.
 
    public void SpendSkill (Skill skillName, TileNode clickedTile = null)
    {

        if (skillName == Skill.Moving && clickedTile != null)
        {
            // print (clickedTile.tileStyle);

            if (clickedTile.tileStyle == TileStyle.OneVigor)
            {
                currentPlayer.currentVigor = currentPlayer.currentVigor -1;
            }
            else if (clickedTile.tileStyle == TileStyle.TwoVigor)
            {
                currentPlayer.currentVigor = currentPlayer.currentVigor -2;
            }
            else
            {
                currentPlayer.currentVigor = currentPlayer.currentVigor -1;
            }
        }
        else if (skillName == Skill.Attack)
        {
            currentPlayer.currentVigor = currentPlayer.currentVigor -2;
        }
        
        board.ResetBoard();
        CheckVigor();
    }
 
 
 게임매니저 클래스에서는 이런식으로 바꿔준다.
 
    void Moving()
    {
        if (!UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject())
        {
            var player = gameManager.currentPlayer;
            if (player == null) return;
            var startNode = board.nodeList.Find (i => i.coordinate == GameUtility.Coordinate (player.transform.position));

            if (startNode != null)
            {
                var way = pathFinding.GreedPathFinding (startNode, this, board.nodeList);
                if (way != null)
                {
                    gameManager.SpendSkill (Skill.Moving, this);
                    player.playerMove.IndicateUnit (way);
                    // add code of map and player coordinate reset
                }
            }
            // way.ForEach (i => print (i));
        }
    }
}
 
 
 타일노드 클래스에서는 이런식으로 바꿔주면 된다. 
 플레이어 이동이 끝났으니 다음 단계는 에너미의 이동을 구현할 차례다. 그건 다음 포스트로 다루도록 하겠다.
 

 

'턴제제작' 카테고리의 다른 글

엄패  (0) 2018.09.23
씬시티 느낌  (0) 2018.09.07
캐릭터의 이동  (0) 2018.09.04
다시 코딩  (0) 2018.08.25
프로토 타입  (1) 2018.07.24

WRITTEN BY
아이고이아

,