턴제제작

길찾기

아이고이아 2019. 2. 21. 14:46

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

 

 우선 마우스를 가져다 대면 알려주는 시스템을 만들어야겠다. 

 엑스컴의 특징 중에 하나가 마우스가 이동 가능 영역을 넘어가도 일단 길 찾기는 진행되는데 이 표시 아이콘은 이동 가능 영역을 넘어가지 않는다. 그러니까 아이콘이 길 찾기 노드 중에 바운더리랑 겹치는 좌표에 머무르게 된다. 마우스가 에어리어를 넘어가게 되면 타일 노드에 투 블록 바운더리를 따로 배열로 만들 거랑 길 찾기와 for문을 돌려서 겹치는 좌표에 아이콘을 머무르게 하는 게 필요한 듯싶다. 일단 아직은 그것까지 만들면 복잡하니까 단순하게 영역을 넘어가면 길 찾기를 하지 않는 걸 만들어야겠다.

    void OnMouseEnter()
    {
        if (gameManager.currentPlayer != null)
{
if (gameManager.currentPlayer.playerActiveState == PlayerActiveState.NotAnything || gameManager.currentPlayer.playerTurnState == PlayerTurnState.Waiting)
{
MakeShield();
                gameManager.round.position = this.transform.position;
}
}
    }

 

 일단 이런 식으로 만들어 봤다. 

    void OnMouseEnter()
    {
        if (gameManager.currentPlayer != null)
{
if (gameManager.currentPlayer.playerActiveState == PlayerActiveState.NotAnything || gameManager.currentPlayer.playerTurnState == PlayerTurnState.Waiting)
{
MakeShield();
                if (tileStyle == TileStyle.OneArea || tileStyle == TileStyle.TwoArea)
                {
                    gameManager.currntTileNode = this.GetComponent<TileNode>();
                    gameManager.round.position = this.transform.position;
                }
                
}
}
    }

 

 길 찾기를 추가해야겠다. 근데 길을 표시해 주는걸 어떻게 만들어야 할지가 감이 안 온다. 그게 문제다. 

 검색해 보니 라인 랜더러라는 게 있는 듯싶다. 

    void OnMouseEnter()
    {
        if (gameManager.currentPlayer != null)
{
if (gameManager.currentPlayer.playerActiveState == PlayerActiveState.NotAnything || gameManager.currentPlayer.playerTurnState == PlayerTurnState.Waiting)
{
MakeShield();
                ShowPath();
}
}
    }

    void ShowPath()
    {
        if (tileStyle != TileStyle.OneArea && tileStyle != TileStyle.TwoArea) return;

        gameManager.currntTileNode = this.GetComponent<TileNode>();
        gameManager.round.position = this.transform.position;
        var way = board.PathFinding.GreedPathFinding (gameManager.startNode, this, board.NodeList);
        Vector3[] wayArray = new Vector3[way.Count];
        for (int i = 0; i < way.Count; i++)
        {
            wayArray[i] = way[i].transform.position;
        }
        gameManager.GetComponent<LineRenderer>().positionCount = way.Count;
        gameManager.GetComponent<LineRenderer>().SetPositions (wayArray);
            
    }

 

 일단 이런식으로 만들어 봤다. 조금 예쁘게 다듬는 작업이 좀 필요할 듯싶다. 

 

일단 본진에서 출발부분이 빠졌으니 추가해 준다. 

    void ShowPath()
    {
        if (tileStyle != TileStyle.OneArea && tileStyle != TileStyle.TwoArea) return;

        gameManager.currntTileNode = this.GetComponent<TileNode>();
        gameManager.round.position = this.transform.position;
        var way = board.PathFinding.GreedPathFinding (gameManager.startNode, this, board.NodeList);
        Vector3[] wayArray = new Vector3[way.Count+1];
        for (int i = 0; i < way.Count; i++)
        {
            wayArray[i] = way[i].transform.position;
        }
        wayArray[way.Count] = gameManager.startNode.transform.position;
        gameManager.GetComponent<LineRenderer>().positionCount = way.Count+1;
        gameManager.GetComponent<LineRenderer>().SetPositions (wayArray);
    }

 

 두깨도 손을 봐야 하고 알파 값도 조금 들어가야 할 필요가 있다. 문제는 렌더러 자체가 컬러에서 알파 값이 안 들어가 진다. 매터리얼에 알파 값을 넣는 방법을 알아봐야겠다. 

 알파값 넣는 법만 알면 될 듯싶다. 매터리얼에 직접 넣는 방법을 찾아봐야겠다. 그다음에 해결해야 될게 엑스컴과 같은 편의성을 제공하는 거다. 

 피같은 파티클이 뛰는 건 콜라이더에 온컬리전엔터를 쓰면 될 듯싶다. 이즈 트리거랑 해서 말이다. 

 unlit/Transparent 쉐이더를 쓰면 투명도가 반영이 된다.

 원하는 대로 됬다.

 이게 좀 더 자연스럽나 모르겠다. 이제 엑스컴의 편의 기능을 추가할 차례인 듯싶다. 

public int cameraZoomOut = 15;
public int cameraZoomIn = 3;
 
 
    void Update()
    {
        if (Input.GetKeyDown (KeyCode.T))
        {
            print ("T");
            if (cameraZoomOut == 15) return;
            cameraZoomOut += 6;
            thisCamera.orthographicSize = cameraZoomOut;
        }
        else if (Input.GetKeyDown (KeyCode.G))
        {
            print ("G");
            if (cameraZoomOut == 3) return;
            cameraZoomOut -= 6;
            thisCamera.orthographicSize = cameraZoomOut;
        }
    }

 

 그전에 카메라 문제를 손봐주어야겠다. 휠 스크롤로도 화면을 확대하고 축소하면 좋을 듯싶다. 

    void LateUpdate()
    {
        if (Input.GetKeyDown (KeyCode.T))
        {
            if (cameraZoomOut == maxCameraSize) return;
            CameraUp();
        }
        else if (Input.GetKeyDown (KeyCode.G))
        {
            if (cameraZoomOut == minCameraSize) return;
            CameraDown();
        }

        if(Input.GetAxis("Mouse ScrollWheel") < 0)
        {
            if (cameraZoomOut == maxCameraSize) return;
            CameraUp();
        }
        else if(Input.GetAxis("Mouse ScrollWheel") > 0)
        {
            if (cameraZoomOut == minCameraSize) return;
            CameraDown();
        }
    }

    void CameraUp()
    {
        cameraZoomOut += change;
            thisCamera.orthographicSize = cameraZoomOut;
    }

    void CameraDown()
    {
        cameraZoomOut -= change;
        thisCamera.orthographicSize = cameraZoomOut;
    }

 

 휠 스크롤 기능도 추가했다. 

 이게 새로 길 찾기를 한번 더 하는 게 아니라 enter때 웨이를 클릭 시에도 쓰게 하면 좋을 듯싶다. 

    void Moving()
    {
        if (!UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject())
        {
            var player = gameManager.currentPlayer;
            if (player == null) return;
            

            if (gameManager.startNode != null)
            {
                if (board.currentWay == null) return;
                
                gameManager.MovingSkill (this);
                player.playerMove.IndicateUnit (board.currentWay);
                board.currentWay = null;
            }
        }
    }

 

 최근에 코딩 스타일을 바꿨기 때문에 온 김에 거기에 맞추어서 return 체크 시리즈를 만들어 준다. 먼가 더 파악하기 깔끔하지 않은가. 

    void Moving()
    {
        if (UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject()) return;
        if (gameManager.currentPlayer == null) return;
        if (gameManager.startNode == null) return;
        if (board.currentWay == null) return;
                
        gameManager.MovingSkill (this);
        gameManager.currentPlayer.playerMove.IndicateUnit (board.currentWay);
        board.currentWay = null;
    }

 

 이제 편의 기능을 만들어야겠다. 

 일단은 노멀 타일에서도 감지를 하고 길 찾기를 하도록 만든 다음에 그 찾는 길에서 타입이 노멀인 거는 제하고 추려내는 작업을 해야 할 듯싶다. 그런 다음에 가장 마지막 노드를 추출하고 거기에 이정표를 세우는 거다. 이게 저번에 구상했던 알고리즘보다 더 효율적인 듯싶다. 아 근데 배가 너무 고파서 일단 밥을 먹고 와야 할 듯싶다. 

  Chimera of Tactics 3라는 턴제 전략 게임으로 축구를 접목한 전투 부분만 랜덤으로 생성되는 게임인데 이 게임의 UI처럼 위쪽으로 상태 UI를 만드는 방식을 고민해 봐야 할 듯싶다. 골대와 공과의 거리를 좀 일정하게 멀어지도록 랜덤 배치했으면 더 좋았을 텐데 가끔 한두턴에 어이없게 이기거나 지는 경우가 있다는 게 단점이다. 그 부분만 정교하게 했으면 하는 아쉬움이 남는 게임이다. 일단 만드는 사람이 어린 듯하니까 차차 개선이 될 듯싶다.  

 pit people 이게임 만만치 않게 재미가 있다. 독특한 게 동료를 케이지에 생포하는 시스템이다. 일종의 포로라고 해야 하나. 용병으로 팔 수도 있는데 이 게임 특성상 케이지 살 돈만 있으면 돼서 사실상 돈이 필요가 없는 거나 마찬가지에서 막일을 할 필요가 없다. 진득하게 하고 싶은데 게임 개발을 해야 해서 앞부분만 할 수밖에 없는 게 아쉬운 게임이다. 엄청 잘 만든 게임인 듯싶다. 이게임도 엑스컴과 마찬가지로 이동에 있어서 편의 기능을 제공하고 있다. 독특한 건 누구를 때릴지는 인공지능이 알아서 정한다. 플레이어는 주로 진영을 짜는 역할이다. 물론 요령껏 특정 적을 타격하도록 할 수는 있다. 매우 잘 만든 게임이다. 강추한다. 여캐가 초반에 둘이 나오는데 상당히 매력적이다. 

 나도 나중에는 두 번째 작품부터는 팀을 짜서 이 정도 수준의 게임을 만들어야 하지 않을까 싶다. 이거 말고 베틀 브라더스라고 용병단 게임도 해봐야 하는데 짬이 잘 안 난다. 

  여하튼 핏 피플도 그렇고 최신 턴제에서는 편의 기능을 다 제공하는 듯싶으니 피해 갈 수 없겠다. 일단 첫 작품에서는 턴제 게임을 만드는데 필요한 기본적인 기술적인 부분은 다 구현해 볼 생각이다. 

     void ShowPathOut()

    {
        if (tileStyle != TileStyle.Normal) return;
        var way = board.PathFinding.GreedPathFinding (gameManager.startNode, this, board.NodeList);
        
    }

 

 일단 노멀일 경우에도 길을 구하도록 한다. 여기서 잘나 내는 게 핵심이다. 여기서 타일 스타일이 노멀인 것을 지워내야 될 듯싶다. 

    void ShowPathOut()
    {
        if (tileStyle != TileStyle.Normal) return;
        var way = board.PathFinding.GreedPathFinding (gameManager.startNode, this, board.NodeList);
        var newWay = new List<TileNode>();
        
        foreach (var node in way)
        {
            if (node.tileStyle == TileStyle.OneArea || node.tileStyle == TileStyle.TwoArea)
            {
                newWay.Add (node);
            }
        }
        board.currentWay = newWay;
        MakeWayLine (newWay);
    }

 

 이럴게 만들었는데  내가 노멀 타일의 경우에는 검색을 못하도록 막아 두었나 싶다. 

 

public enum PathFindingStyle
{
    In, Out,
}

 

 이런 걸 하나 만들어 놓고 기본 스타일은 in으로 디폴트를 지정해 두어야 할 듯싶다. 아니다 아예 반환하는 걸 노멀을 제하고 반환하면 되겠다. 

                    if (pathFindingStyle == PathFindingStyle.In)
                    {
                        return wayList;
                    }
                    else if (pathFindingStyle == PathFindingStyle.Out)
                    {
                        var newWayList = new List<TileNode>();
        
                        foreach (var tileNode in wayList)
                        {
                            if (tileNode.tileStyle == TileStyle.OneArea || tileNode.tileStyle == TileStyle.TwoArea)
                            {
                                newWayList.Add (tileNode);
                            }
                        }
                        return newWayList;
                    }

 

 참고로 그리드 패스 파인딩 전체 코드다.

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

public enum PathFindingStyle
{
    In, Out,
}

public class PathFinding : MonoBehaviour {

    // debug
[SerializeField] GameObject openObject;
[SerializeField] GameObject closedObject;
[SerializeField] GameObject wayObject;

    public bool debugMod = false;

Transform debugPath;
    int pathFindingCount = 32;

    void Awake()
    {
        debugPath = transform.Find ("Path");
    }

    void DebugPath (GameObject ObjectType, Vector2 pos)
    {
Instantiate (ObjectType, new Vector3 (pos.x, 0, pos.y) * 2, Quaternion.identity, debugPath);
}

    readonly Vector2[] Nears =
    {
        new Vector2 (1, 0), new Vector2 (-1, -1), new Vector2 (0, -1), new Vector2 (1, -1),
new Vector2 (-1, 1), new Vector2 (0, 1), new Vector2 (1, 1), new Vector2 (-1, 0),
};

List<TileNode> GetNears (TileNode currentNode, TileNode endNode,List<TileNode> map, PathFindingStyle pathFindingStyle)
    {
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 (pathFindingStyle == PathFindingStyle.In)
                {
                    if (tile.Coordinate == foundNear && (tile.tileStyle == TileStyle.OneArea || tile.tileStyle == TileStyle.TwoArea))
                    {
                        neighbours.Add (tile);
                    }
                }
                else if (pathFindingStyle == PathFindingStyle.Out)
                {
                    if (tile.Coordinate == foundNear && (tile.tileStyle == TileStyle.OneArea || tile.tileStyle == TileStyle.TwoArea || tile.tileStyle == TileStyle.Normal))
                    {
                        neighbours.Add (tile);
                    }
                }

                
            }
}

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

return neighbours;
}

    TileNode GetMin (TileNode startNode, TileNode endNode, List<TileNode> openList)
    {
        foreach (var viaNode in openList)
        {
            viaNode.farFormTarget = Vector2.Distance (viaNode.Coordinate, endNode.Coordinate);
        }

        var valueList = new List<float>();
        foreach (var viaNode in openList)
        {
            valueList.Add (viaNode.farFormTarget);
        }

        return openList.Find (i => i.farFormTarget == valueList.Min());
    }

    List<TileNode> retrace (TileNode startNode, TileNode endNode, List<TileNode> closedMap, PathFindingStyle pathFindingStyle)
    {
        var openList = new List<TileNode>();
        var wayList = new List<TileNode>();
        TileNode current;

        openList.Add (startNode);
        var limit = pathFindingCount;
        
        while (limit > 0 && openList.Count > 0)
        {   
            current = GetMin (startNode, endNode, openList);

            openList.Remove (current);
            wayList.Add (current);

            foreach (var node in GetNears (current, endNode, closedMap, pathFindingStyle))
            {
                if (node == endNode)
                {
                    return wayList;
                }
                if (openList.Contains (node) || wayList.Contains (node)) continue;
                openList.Add (node);
            }

            limit--;
        }

        return wayList;
    }

    public List<TileNode> GreedPathFinding (TileNode startNode, TileNode endNode, List<TileNode> map, PathFindingStyle pathFindingStyle = PathFindingStyle.In)
    {
        // GreedPathFinfing need to consider level design risk
        var openList = new List<TileNode>();
        var closedList = new List<TileNode>();
        var wayList = new List<TileNode>();
        
        TileNode current;

        openList.Add (startNode);
        var limit = pathFindingCount;

        while (limit > 0 && openList.Count > 0)
        {
            current = GetMin (startNode, endNode, openList);
            openList.Remove (current);
            closedList.Add (current);

            foreach (var node in GetNears (current, endNode, map, pathFindingStyle))
            {
                if (node == endNode)
                {
                    wayList.AddRange (retrace (endNode, startNode, closedList, pathFindingStyle));

                    if (pathFindingStyle == PathFindingStyle.In)
                    {
                        return wayList;
                    }
                    else if (pathFindingStyle == PathFindingStyle.Out)
                    {
                        var newWayList = new List<TileNode>();
        
                        foreach (var tileNode in wayList)
                        {
                            if (tileNode.tileStyle == TileStyle.OneArea || tileNode.tileStyle == TileStyle.TwoArea)
                            {
                                newWayList.Add (tileNode);
                            }
                        }
                        return newWayList;
                    }
                }

                if (openList.Contains (node) || closedList.Contains (node)) continue;

                openList.Add (node);
            }

            if (debugMod == true)
            {
                openList.ForEach (i => DebugPath (openObject, i.Coordinate));
                closedList.ForEach (i => DebugPath (closedObject, i.Coordinate));
            }
            
            limit--;
        }

        print ("There is no way");
        return null;
    }
}

 

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