이번 포스팅은 좀 간단할 듯 하네요.


건물 버프 기능과 캐릭터와의 상호작용 부분입니다.

내용 자체는 기획에 따라 변경될거라 별거 없지만, Facility에 대해 짚고 넘어가고자 진행합니다. 


아래와 같은 것을 만드는 겁니다. 



(화면상에 눈에 보이는게 없어서 급조해서 만든 Text와 머리만 있는 캐릭터가 참... 없어보이네요. )

 현재는 캐릭터가 지정된 타일로 이동해서 타일의 시설을 사용하고 되돌아오는 테스트 코드만 작성된 상태입니다. (캐릭터는 길찾기 및 사용할 시설 선택 로직까지 구현하고, 아마 다음 포스팅에 올리겠습니다. 캐릭터 몸통과 애니메이션도 그때 같이 진행할 예정입니다.)


이번에는 간단하게 Facility를 어떻게 구조를 잡았고, 어떤 것을 하는지만 얘기하겠습니다.

딱 기본이 될 정도만 하다보니 빈 함수도 많고, 구현내용이 많지가 않아요. (기획에 따라 계층구조를 다시 생각해 볼 필요도 있겠네요...)


우선 Facility


public abstract class Facility { public FacilityType type; public Sprite facilitySprite; protected GameObject facilityObj; float useTime = 4.0f; int useAnimation = 0; protected Tile positionTile; public float getUseTime() { return useTime; } public int getUseAnimaion() { return useAnimation; } public void CreateFacility(Tile tile) { GameObject obj = new GameObject("facility"); obj.transform.SetParent(tile.GetTileObject().transform); obj.AddComponent<SpriteRenderer>().sprite = facilitySprite; obj.transform.localPosition = new Vector3(0, 0.5f, -1); facilityObj = obj; positionTile = tile; MakeFacility(); } public abstract bool isEmpty(); public abstract bool isNull(); public abstract void ApplyBuff(Dictionary<BuffType, int> buffList); public abstract void MakeFacility(); public abstract bool InteractWithCharacter(Character character); public abstract void ExitFacility(); }


각 함수와 필드에 대해 간단히 설명하겠습니다.


useTime - 캐릭터가 들어와서 사용시 사용할 시간.

useAnimation - 캐릭터가 사용시 플레이될 애니메이션 번호 ( 애니메이션은 캐릭터가 개별적으로 가지고, 건물 사용시 마크된 애니메이션을 플레이 시킬 예정입니다.)


isEmpty - 향후 캐릭터가 이동할 건물을 검색할 시 사용될 건물이 비었는지 판별 함수.

isNull - NullFacility 구별용

ApplyBuff - 매개변수를 통해 전달된 버프를 이용해 시설의 데이터를 변경하는 함수. (버프는 타일이 가지고 있다가 건물이 설치되면 건물에 전달.)

MakeFacility - 건물 설치시 하위 클래스가 해야하는게 있으면 할 함수.

InteractWithCharacter - 건물을 사용하는 캐릭터와 상호작용 

ExitFacility - 캐릭터가 나갈시 호출 함수.



그리고 Facility를 상속받아 NullFacility, EtcFacility, SellFacility, GymFacility, DecoFacility가 만들어집니다.

(NullFacility는 Null체크 회피를 위한 클래스일 뿐이라 제외하겠습니다.)


판매점 클래스인 SellFacility 입니다.

public class SellFacility : Facility { public int fame = 0; public int originPrice = 100; public int price; Character user; string textPoolName = "TextPool"; public SellFacility(FacilityType _type) { type = _type; facilitySprite = SpriteManager.Instance.getFacilitySprite(_type); price = originPrice; } public override bool isNull() { return false; } public override void ApplyBuff(Dictionary<BuffType, int> buffList) { if (buffList.ContainsKey(BuffType.Envir)) { fame = buffList[BuffType.Envir]; ShowMoveText("Fame " + buffList[BuffType.Envir]); } if (buffList.ContainsKey(BuffType.Price)) { price = originPrice + buffList[BuffType.Price]; ShowMoveText("Price " + buffList[BuffType.Price]); } Debug.Log("fame = " + fame + " price = " + price); } private void ShowMoveText(string _text) { MoveText textObj = PoolManager.Instance.getObject(textPoolName, facilityObj.transform).GetComponent<MoveText>(); textObj.setTextMessage(_text); textObj.transform.position = facilityObj.transform.position + new Vector3(0, 0, -1); textObj.gameObject.SetActive(true); } public override void MakeFacility() { } public override bool InteractWithCharacter(Character character) { user = character; if (user.isEmptyMoney(price)) { user.addMoney(-1 * price); } return false; } public override bool isEmpty() { return user == null; } public override void ExitFacility() { user = null; } }


아래의 GymFacility와 비슷해서 밑에서 같이 설명하겠습니다. 


체육관 유형 클래스인 GymFacility

public enum AbilityType
{
    Str,Spd
}

public class GymFacility : Facility
{
    public int fame = 0;
    AbilityType ability = AbilityType.Str;
    int abiliValue = 20;

    Character user;

    string textPoolName = "TextPool";
    public GymFacility(FacilityType _type)
    {
        type = _type;
        facilitySprite = SpriteManager.Instance.getFacilitySprite(_type);

    }
    public override bool isNull()
    {
        return false;
    }
    public override void ApplyBuff(Dictionary<BuffType, int> buffList)
    {
        if (buffList.ContainsKey(BuffType.Envir))
        {
            fame = buffList[BuffType.Envir];
            ShowMoveText("Fame " + buffList[BuffType.Envir]);
        }
        Debug.Log("fame = " + fame);

    }
    private void ShowMoveText(string _text)
    {

        MoveText textObj = PoolManager.Instance.getObject(textPoolName, facilityObj.transform).GetComponent<MoveText>();
        textObj.setTextMessage(_text);
        textObj.transform.position = facilityObj.transform.position + new Vector3(0, 0, -1);
        textObj.gameObject.SetActive(true);
    }
    public override bool InteractWithCharacter(Character character)
    {
        user = character;
        if(ability == AbilityType.Str)
        {
            user.addStr(abiliValue);
        }
        else if(ability == AbilityType.Spd)
        {
            user.addSpd(abiliValue);
        }
        return false;

    }

    public override void MakeFacility()
    {
    }

    public override bool isEmpty()
    {
        return user==null;
    }
    public override void ExitFacility()
    {
        user = null;
    }
}


ShowMoveText 함수는 화면상에 변경되는 데이터양을 표현하기 위해 급조한 함수입니다;;

그 외에 Gym과 Sell에서 구현된 함수는 ApplyBuff와 InteractWithCharacter입니다.

전달된 매개변수에 따라 데이터에 적용하고, InteractWithCharacter 는 캐릭터가 건물사용시 호출되는 함수로 건물별로 캐릭터에 데이터를 적용하고 있습니다.

MakeFacility는 아직 구현이 안되있지만 향후 캐릭터가 목표 건물을 탐색하기위해 리스트에 Add하는 코드를 구현할 예정입니다.


조형물 유형 클래스인 DecoFacility

public class DecoFacility : Facility
{
    int range;
    BuffType buffType;
    int buffValue;

    TileController tileController;

    public DecoFacility(FacilityType _type,int _range,BuffType _buffType, int _buffValue)
    {
        type = _type;
        facilitySprite = SpriteManager.Instance.getFacilitySprite(_type);

        tileController = GameObject.Find("TileController").GetComponent<TileController>();
        
        range = _range;
        buffType = _buffType;
        buffValue = _buffValue;
    }
    public override bool isNull()
    {
        return false;
    }
    public override void ApplyBuff(Dictionary<BuffType, int> buffList)
    {

    }
    public override bool InteractWithCharacter(Character character)
    {

        return false;
    }

    public override void MakeFacility()
    {
        int posX = positionTile.GetPosX();
        int posY = positionTile.GetPosY();

        tileController.SetTileBuff(range, posX, posY, buffType, buffValue);
    }
    public override bool isEmpty()
    {
        return false;
    }
    public override void ExitFacility()
    {
    }
}
TileController  
  public void SetTileBuff(int range, int posX, int posY, BuffType type, int value)
    {
        for(int i = -range; i <= range; i++)
        {
            for(int j = -range; j <= range; j++)
            {
                if (i == 0 && j == 0) continue;

                int _x = posX + j;
                int _y = posY + i;
                if (isRangeOut(_y, _x)) continue;

                tiles[_y, _x].AddBuffList(type, value);
            }
        }

    }


 조형물은 주요 역할이 주변 건물에 버프를 주는 것입니다. 때문에 주변 타일에 자신의 버프를 적용하고 있습니다.

 버프는 우선 타일에 적용되고, 타일은 버프가 변경될 때마다 Facility에 적용하는 함수를 호출해줍니다.


그 외 Facility ( 현재는 캐릭터의 집을 나타냅니다.)

public class EtcFacility : Facility
{
    Character master;

    bool isUse = false;
    int recovery = 10;

    public EtcFacility(FacilityType _type)
    {
        type = _type;
        facilitySprite = SpriteManager.Instance.getFacilitySprite(_type);

    }
    public override bool isNull()
    {
        return false;
    }

    public override void ApplyBuff(Dictionary<BuffType, int> buffList)
    {

    }
    public override bool InteractWithCharacter(Character character)
    {

        character.addHp(recovery);

        return !character.isFullHp();

    }
    
    public override void MakeFacility()
    {
    }

    public override bool isEmpty()
    {
        return isUse;
    }
    public override void ExitFacility()
    {
        
    }
}



음....쓰고나니 더 별 내용이 없네요...간단한데도 불구하고 포스팅이 늦었는데...흠... 앞으로 더 늦어질수도 있습니다;;;

안드로이드 쪽을 너무 공부를 안하고 있었어서, 최근에 다시 들여다보면서 안드로이드 네이티브 앱도 같이 개발을 좀 연습하다보니...


이번에는 캐릭터 클래스는 설명이 없는데, 다음 포스팅에서는 캐릭터 건물 탐색 및 길찾기로 캐릭터 구현을 진행하게 될테니 같이 설명하겠습니다.

Posted by 검은거북

세번째, 메뉴를 통해서 건물 설치하는 부분까지.


아래 같은 것을 만드는 겁니다.




원래는 메뉴 / 건물 설치로 하려고 했는데, 메뉴는 별 내용이 없어서... 건물 설치 파트와 이전 버전에서 변경된 지점만 진행을 하겠습니다. 


우선 이전의 InputController와 State 클래스입니다.

이전 포스팅의 InputController를 일반 / 설치 / 메뉴 상태로 나누어 변경된 지점입니다.

public class InputController : MonoBehaviour {
    
    Vector3 lastMousePos;

    TileController tileController;
    FacilityController facilityController;

    Dictionary<statetype,state> stateMap = new Dictionary<statetype, state>();

    State state;
	// Use this for initialization
	void Start () {

        tileController = GameObject.Find("TileController").GetComponent<tilecontroller>();
        facilityController = GameObject.Find("FacilityController").GetComponent<facilitycontroller>();
        CreateState();
        
        SetState(StateType.Normal);
    }
	void CreateState()
    {
        stateMap.Add(StateType.Normal, new NormalState(tileController));
        stateMap.Add(StateType.Install, new InstallState(tileController,facilityController, this));
        stateMap.Add(StateType.Menu, new MenuState());
    }

	// Update is called once per frame
	void Update () {
        if (Input.GetMouseButton(1))
        {
            state.UpdateDrag(lastMousePos);
        }

        lastMousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        lastMousePos.z = 0;

        state.UpdateMove(lastMousePos);

        if (Input.GetMouseButtonDown(0))
        {
            state.UpdateClick();
        }
    }
    public void SetState(StateType _type)
    {
        if (state != null)
        {
            state.ExitState();
        }
        state = stateMap[_type];
        state.EnterState();
    }
}

State는 각각 미리 객체를 생성해두어 Dictionary로 객체 풀 관리를 합니다. (CreateState)

호출시에는 SetState를 통해 state 변경시 바꾸기 직전, 직후 해야할 일을 진행하도록 하였읍니다.

그리고 Update에 있던 기존의 코드는 모두 NormalState로 옮겨 state에 위임하도록 변경하였습니다. 솔직히 Drag,Click,Move 다 하나로 만들어도 될거 같긴한데.... 역할별로 작성하는게 더 용이하게 느껴지더군요.

(InputController는 가능하면 앞으로 건들지 않았으면 좋겠네요.)


다음은 State와 그 하위클래스들.

 아래 하위 클래스 외에 Null처리를 해줄 NullState와 메뉴상태의 MenuState가 있는데 별도로 언급할 내용이 없습니다.

public enum StateType
{
    Normal,Install,Menu
}

public abstract class State  {

    public abstract void EnterState();
    public abstract void ExitState();
    public abstract void UpdateMove(Vector3 _pos);
    public abstract void UpdateClick();
    public abstract void UpdateDrag(Vector3 _pos);
    
    protected Vector3 getTileCenterPosFromMouse(Vector3 _mousePos)
    {
        _mousePos.y = _mousePos.y * 2;
        int mPosX = Mathf.FloorToInt(_mousePos.x);
        int mPosY = Mathf.FloorToInt(_mousePos.y);

        // 홀짝 구분 / 노출되는 타일들의 중앙점은 합이 짝수다.
        int checkEven = (mPosX + mPosY) & 1;
        // 홀수라면 remainX에 곱하여 양수로 바꿔준다.
        int tempEven = (checkEven * -2) + 1;

        // 홀수라면 짝수로 기준센터 이동.
        mPosX += checkEven;

        float remainX = _mousePos.x - mPosX;
        float remainY = _mousePos.y - mPosY;

        // 소수점 이하의 수를 더하여, 1보다 크면 이동.
        float remainSum = (tempEven * remainX) + remainY;
        // 더한 값을 내림하여, 1.0 이상이면1로 만들어 최종 계산식에 사용.
        int floorSum = Mathf.FloorToInt(remainSum);

        // 더한 값이 1.0 이상이고, checkEven이 짝수라면 x+1,y+1  (floorSum = 1 , tempEven = 1)
        // 더한 값이 1.0 이상이고, checkEven이 홀수라면 x-1,y+1  (floorSum = 1 , tempEven = -1)
        // 더한 값이 1.0 이하라면, x,y  (floorSum = 0)
        Vector3 result = new Vector3(mPosX + (floorSum * tempEven), (mPosY + floorSum) * 0.5f, -1);

        //Debug.Log("_mousePos = " +_mousePos + " mPosX = " + mPosX + " mPosY = " + mPosY + " checkEven = " + checkEven + " tempEven = " + tempEven + " remainX = " + remainX + " remainY = " + remainY + " remainSum = " + remainSum +" FloorSum = " + floorSum + " result = " + result);

        return result;
    }
}


추상클래스인 State는 

State변경 직후 필요한 부분을 작성하는 EnterState

State변경 직전 필요한 부분을 작성하는 ExitState

마우스 움직임에 대응하는 UpdateMove

마우스 왼쪽 클릭에 대응하는 UpdateClick

마우스 오른쪽 드래그에 대응하는 UpdateDrag

그리고 이전에 작성한 그대로인 getTileCenterPosFromMouse로 이루어져있습니다.


public class NormalState : State
{
    TileController tileController;
    
    GameObject mousePointer;

    public NormalState(TileController _tile)
    {
        tileController = _tile;
        mousePointer = new GameObject("MousePointer");

        mousePointer.AddComponent<SpriteRenderer>().sprite = SpriteManager.Instance.getFacilitySprite(FacilityType.Empty);

        Color facilColor = mousePointer.GetComponent<SpriteRenderer>().color;
        facilColor.a = 1.0f;
        mousePointer.GetComponent<SpriteRenderer>().color = facilColor;

    }
    public override void UpdateClick()
    {
        Vector3 _pos = mousePointer.transform.position;
        int _posX = Mathf.FloorToInt(_pos.x);
        int _posY = Mathf.FloorToInt(_pos.y*2);

        int _x = (_posY + _posX) >> 1;
        int _y = (_posY - _posX) >> 1;

        tileController.getTile(_x, _y).GetInformation();
    }

    public override void UpdateDrag(Vector3 _pos)
    {
        Vector3 currentPos = currentPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        currentPos.z = 0;

        Camera.main.transform.Translate(_pos - currentPos);
    }

    public override void UpdateMove(Vector3 _pos)
    {
        mousePointer.transform.position = getTileCenterPosFromMouse(_pos);
    }

    public override void EnterState()
    {
        mousePointer.SetActive(true);
    }

    public override void ExitState()
    {
        mousePointer.SetActive(false);
    }
}

public class InstallState : State
{
    Facility selectFacility;
    TileController tileController;
    FacilityController facilityController;
    InputController input;
    GameObject mousePointer;

    public InstallState(TileController _tile, FacilityController _facilityController, InputController _inputController)
    {
        tileController = _tile;
        selectFacility = new NullFacility();
        facilityController = _facilityController;
        input = _inputController;

        mousePointer = new GameObject("buildPointer");
        mousePointer.AddComponent<SpriteRenderer>().sprite = selectFacility.facilitySprite;

        Color facilColor = mousePointer.GetComponent<SpriteRenderer>().color;
        facilColor.a = 0.5f;
        mousePointer.GetComponent<SpriteRenderer>().color = facilColor;    
    }
    public void SetSelectFacility(Facility _selectFacility)
    {
        selectFacility = _selectFacility;
        mousePointer.GetComponent<SpriteRenderer>().sprite = _selectFacility.facilitySprite;
    }
    public override void UpdateClick()
    {

        Vector3 _pos = mousePointer.transform.position;
        int _posX = Mathf.FloorToInt(_pos.x);
        int _posY = Mathf.FloorToInt(_pos.y * 2);

        int _x = (_posY + _posX) >> 1;
        int _y = (_posY - _posX) >> 1;

        bool isInstall = tileController.getTile(_x, _y).ChangeFacility(selectFacility);

        if (isInstall)
        {
            input.SetState(StateType.Normal);
        }
    }

    public override void UpdateDrag(Vector3 _pos)
    {
        Vector3 currentPos = currentPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        currentPos.z = 0;

        Camera.main.transform.Translate(_pos - currentPos);
    }

    public override void UpdateMove(Vector3 _pos)
    {
        Vector3 movePos = getTileCenterPosFromMouse(_pos);
        movePos.y += 0.5f;
        mousePointer.transform.position = movePos;
    }
    public override void EnterState()
    {
        SetSelectFacility(facilityController.getSelectFacility());
        mousePointer.SetActive(true);
    }

    public override void ExitState()
    {
        mousePointer.SetActive(false);
    }

}


위에가 Normal상태, 아래가 Install 상태로,

각각 자신이 사용할 포인터( 건물모양, 하이라이트)를 생성하고, EnterState에서 활성화를 해주고, ExitState에서 비활성화를 해주고 있습니다.


 Normal은 이전 포스팅에서 작성한걸 역할별로 나누어 작성되어있을 뿐입니다.

 Install은 EnterState에서 FacilityController로부터 선택된 Facility를 받아오고, 마우스 포인터를 해당 건물의 모양으로 변경해주고 있습니다. (FacilityController는 설치할 건물 선택시 selectFacility로 선택된 건물을 저장하고 있습니다.)

그리고 빈 타일에 클릭시 타일에 선택된 건물을 생성해주고, Normal상태로 변경합니다.

(옮기고나니 중복 코드 구린내가 좀 보이는 것 같네요....흠..)


다음은 Facility와 FacilityController.

public abstract class Facility  {
    
    public FacilityType type;
    public Sprite facilitySprite;
    GameObject facilityObj;

    public void CreateFacility(GameObject tileObj)
    {
        GameObject obj = new GameObject("facility");
        obj.transform.SetParent(tileObj.transform);
        obj.AddComponent<SpriteRenderer>().sprite = facilitySprite;
        obj.transform.localPosition = new Vector3(0, 0.5f, -1);

        InteractWithFacility();
    }
    public abstract bool isNull();
    public abstract void InteractWithFacility();
    public abstract void InteractWithCharacter();
}


public class FacilityController : MonoBehaviour {

    Facility selectFacility;
    InputController inputController;
    void Start()
    {
        inputController = GameObject.Find("InputController").GetComponent<InputController>();
    }
    public Facility getSelectFacility()
    {
        return selectFacility;
    }
    
    public void BuildButton(int _typeIndex)
    {
        //Test용

        // 향후 DB 인덱스로 변경하여 DB와 연동하여 건물 데이터 얻어오도록 변경. (Type까지 DB로 관리)
        FacilityType _type = (FacilityType)_typeIndex;

        Facility build;

        if (_type == FacilityType.Room1)
        {
            build = new EtcFacility(_type);
        }
        else if (_type == FacilityType.Seller)
        {
            build = new SellFacility(_type);

        }
        else if (_type == FacilityType.Flower)
        {
            build = new DecoFacility(_type);
        }
        else if (_type == FacilityType.Playground)
        {
            build = new GymFacility(_type);
        }
        else
        {
            build = new NullFacility();
        }
        selectFacility = build;
        inputController.SetState(StateType.Install);
    }
}


Facility의 하위클래스들은 다음에 건물 기능과 동작 추가를 하면서 같이 하고, 이번에는 뺏습니다. 설명도 다음 포스팅에 같이 하겠습니다. 이번에는 설치까지만 됐으니까요.


CreateFacility는 타일 오브젝트 하위에 Facility 객체를 생성하는 공통 함수로, InstallState에서 타일의 건물 교체를 호출하면 타일을 통해 호출되는 함수입니다.


FacilityController는 Facility  생성과 관리에 관여하는 클래스로, Test를 위해서 BuildButton을 만드는데 일단은 FacilityController에 옮겨놨습니다. 실제 메뉴에서 설치할 건물을 클릭하면 BuildButton이 호출됩니다. 향후에 DB가 연동된다면,(기능 구현 뒤에 예정) 변경할 예정입니다.


다음엔 건물 기능/ 동작 추가 하고, 캐릭터 프로토타입까지일듯 하네요.

Posted by 검은거북

앞으로는 단순화해서 포스팅하는 것보다 진행부분 별로 포스팅하도록 바꿀 생각입니다. 파트별로 진행을 해서 포스팅하면서도 좀 정리를 할 겸. 그래서 전략 게임 진행의 첫번째로 Isometric - 마름모 타일 구현하기!


아래 이미지 같은걸 만드는 겁니다. 참고하시길






위와 같은 타일을 isometic 이라고 많이 하더라고요.


솔직히 타일 만드는 것 자체는 쉬운데, 타일 위를 마우스가 지나갈 때 타일별로  하이라이트를 주는 부분에서 생각보다 오래 걸렸네요.


아주 단순하게 생각하면 타일 별로 콜라이더를 적용해서 판별하면 되는데, 이렇게 규칙적으로 생성된 타일은 분명 연산식으로 처리가능 할거고, 앞으로 캐릭터랑 건물들 연산량 쌓일거 생각하면... 반드시 연산식으로 바꿔야 겠다고 생각해서... 고민하느라 오래 걸렸네요.


우선 타일 생성 및 배열 컨트롤을 담당할 TileController

public class TileController : MonoBehaviour { Tile[,] tiles; public Sprite tileImage; // Use this for initialization void Start () { tiles = new Tile[32, 32]; for(int i = 0; i < 32; i++) { for (int j = 0; j < 32; j++) { // i = y , j = x GameObject tileObj = new GameObject("Tile_" + i + "_" + j); tileObj.transform.position = new Vector3((-1*i) +j, (i*0.5f) + (j*0.5f),0); tileObj.transform.SetParent(transform); tileObj.AddComponent<spriterenderer>().sprite = tileImage; tiles[i, j] = new Tile(i,j,tileObj); } } } }


public class Tile { int posX; int posY; Facility facility; GameObject tileObj; public Tile() { facility = new NullFacility(); } public Tile(int y,int x, GameObject _obj) { posX = x; posY = y; tileObj = _obj; facility = new NullFacility(); } public bool ChangeFacility(Facility _facility) { if (facility.isNull()) { _facility.CreateFacility(tileObj); facility = _facility; return true; } return false; } public void GetInformation() { Debug.Log("posX = " + posX + " posY = " + posY); } }

지금은 그냥 딱 타일 생성만 하는 부분입니다. 

로직 관련해서는 너무 단순해서 딱히 설명 할게 없네요.

타일에 관해서는 GameObject가 타일을 컴포넌트로 관리할지, 타일이 GameObject를 가져서 처리를 할지는 좀 더 생각해야겠습니다. (현재는 후자)



InputController - 마우스 컨트롤을 관리하는 클래스

public class InputController : MonoBehaviour { public Sprite mousePointerSprite; GameObject mousePointer; Vector3 lastMousePos; // Use this for initialization void Start () { mousePointer = new GameObject("MousePointer"); mousePointer.AddComponent<spriterenderer>().sprite = mousePointerSprite; } // Update is called once per frame void Update () { if (Input.GetMouseButton(1)) { Vector3 currentPos = currentPos = Camera.main.ScreenToWorldPoint(Input.mousePosition); currentPos.z = 0; Camera.main.transform.Translate(lastMousePos - currentPos); } lastMousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition); lastMousePos.z = 0; mousePointer.transform.position = getTileCenterPosFromMouse(lastMousePos); } private Vector3 getTileCenterPosFromMouse(Vector3 _mousePos) { _mousePos.y = _mousePos.y * 2; int mPosX = Mathf.FloorToInt(_mousePos.x); int mPosY = Mathf.FloorToInt(_mousePos.y); // 홀짝 구분 / 노출되는 타일들의 중앙점은 합이 짝수다. int checkEven = (mPosX + mPosY) & 1; // 홀수라면 remainX에 곱하여 양수로 바꿔준다. int tempEven = (checkEven * -2) + 1; // 홀수라면 짝수로 기준센터 이동. mPosX += checkEven; float remainX = _mousePos.x - mPosX; float remainY = _mousePos.y - mPosY; // 소수점 이하의 수를 더하여, 1보다 크면 이동. float remainSum = (tempEven *remainX) + remainY; // 더한 값을 내림하여, 1.0 이상이면1로 만들어 최종 계산식에 사용. int floorSum = Mathf.FloorToInt(remainSum); // 더한 값이 1.0 이상이고, checkEven이 짝수라면 x+1,y+1 (floorSum = 1 , tempEven = 1) // 더한 값이 1.0 이상이고, checkEven이 홀수라면 x-1,y+1 (floorSum = 1 , tempEven = -1) // 더한 값이 1.0 이하라면, x,y (floorSum = 0) Vector3 result = new Vector3(mPosX + (floorSum * tempEven),(mPosY + floorSum)*0.5f,-1); //Debug.Log("_mousePos = " +_mousePos + " mPosX = " + mPosX + " mPosY = " + mPosY + " checkEven = " + checkEven + " tempEven = " + tempEven + " remainX = " + remainX + " remainY = " + remainY + " remainSum = " + remainSum +" FloorSum = " + floorSum + " result = " + result); return result; } }

 Update에서는 마우스 오른쪽 클릭을 하고, 마우스를 이동시 이전 프레임의 좌표와 현재좌표를 연산하여 카메라를 이동시켜 드래그하여 이동하는 연출을 하고 있습니다.

 Update는 나중에 상태 패턴을 이용해 평상시와 건물 설치시로 나눌 예정입니다.


그리고 위에서 말한 타일별 하이라이트를 주는 연산식이 getTileCenterPosFromMouse 입니다.

복잡한 연산할 때는 주석을 미리 써놓고 코딩해서 주석에서 설명을 거의 하고는 있지만, 조금 부연 설명을 하자면... (원래는 각 칸의 높이는 0.5지만, 편의상 1로 하겠습니다.)


 목적은 내 하이라이트 타일의 중심점을 각 타일의 중심점에 맞춰주는 것. (절대 (0,1)과 같은 타일의 중간값에 하이라이트가 위치해선 안됨)

 

 사진을 보시면, 모든 마름모 타일의 중앙은 x+y가 짝수이고, 모든 사각형 공간 안의 타일은 그림의 1번과 2번의 두 형태만 존재하는 것에 착안해서 만들었습니다.

그리고 마우스 좌표는 내림을 하면 좌하에 가장 가까운 좌표를 얻을 수 있죠.

  1. 마우스 좌표를 내림하여 기준 센터를 짝수로 통일 
    • 마우스 좌표의 내림 합이 짝수면, 2번 영역에 마우스가 있다는 것.
    • 마우스 좌표의 내림 합이 홀수면, 1번 영역에 마우스가 있다는 것. (x+1을 하여 짝수로)
      • 예를 들면, 내림이 (-1,0)이면 (0,0)을 기준으로 1번 영역에 있는 것이므로 (0,0)으로 기준 변경.
  2. 각 영역에 맞게 연산하고, 하이라이트 센터 값에 적용.
    • 1번은 (0,0)을 기준으로 x,y 소수점 절대값 합이 1.0이상이면 x-1, y+1을 해주고,
    • 2번은 (0,0)을 기준으로 x,y 소수점 절대값 합이 1.0이상이면 x+1, y+1을 해줍니다.
    • 공통적으로 절대값의 합이 1.0이하면 x,y


isometric 연산식들을 찾아도 봤는데, 위에 말한 단순한 콜라이더 방식도 나오고, 다른 연산은 if-else 문을 이용해서 길게 하는 것도 있더라고요. 마음에 드는 걸 못 찾아서 그냥 만들었습니다. (좀 더 수학공식이나 그래프에 빠삭하면 하는 아쉬움이...) 혹시 비슷한걸 만드시려는 분이시려면 더 좋은 로직을 만드시길... 아마 좀 더 찾으면 대단하신 분들이 간단하게 한게 있을거 같긴한데....


다음에는 건물 설치/ 상태 패턴 적용 을 하겠습니다.

Posted by 검은거북

블로그 이미지
프로그래밍 공부 요약 및 공부하며 발생한 궁금증과 해결과정을 포스팅합니다.
검은거북

공지사항

Yesterday
Today
Total

달력

 « |  » 2025.1
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

최근에 올라온 글

최근에 달린 댓글

글 보관함