2018. 6. 28. 04:40 IT/Unity 게임개발
[게임 개발] 도시경영 전략 게임 로직 만들기 - 5 (캐릭터 구현)
이번 내용은 캐릭터 구현!
이라고는 하지만 실질적으로 구현된 것은 길찾기, 시설 사용하기 정도? 네요.
아래와 같은 것을 만드는 겁니다.
(초록색 캐릭은 Gym 시설을 사용하는 마을 캐릭터고, 일반 캐릭은 Sell시설을 사용하는 관광객입니다.)
음... 원래 시설이 캐릭터가 시설을 사용할 때의 애니메이션 인덱스를 가지고 캐릭터가 사용할 때 지정을 해줘야하는데... 현재 만들어진 애니메이션이 없어서 진행하지 않았습니다.
일단 이전 포스팅들에 비해 약간 변경이 된 점이 시설 설치시 시설이 너비탐색을 통해 자신의 로드맵을 생성합니다. 그리고 캐릭터는 목표 시설의 로드맵을 보고 이동하게 됩니다. ( 사실 길찾기의 경우 다양한 길찾기 방법을 시도해 보면서 공부하려고 생각 중입니다. 그래서 초창기에 별도로 정해두지 않았습니다.)
캐릭터의 State는 Idle, Move, Job으로 나뉘어집니다. 최초에는 Idle 상태로 랜덤한 방향으로 서성거리다가 목표 시설(Fame 순으로 빈 시설 탐색) 발견시 Move 상태로 변경되어 목표시설로 이동합니다. 시설에 들어가는 순간 Job 상태가 되고, 시설을 나오면 Idle로 전환됩니다.
Character
public abstract class Character : MonoBehaviour { CharacterState state ; int money = 150; int maxHp = 100; int hp; int str = 100; int spd = 100; Facility useFacility; protected TileController tileController; protected FacilityController facilityController; Tile currentTile; Tile destTile; Tile nextTile; protected Facility beforeFacility; public Animator anim; public string textPoolName = "TextPool"; void Start() { tileController = GameObject.Find("TileController").GetComponent<TileController>(); facilityController = GameObject.Find("FacilityController").GetComponent<FacilityController>(); hp = maxHp; anim = GetComponent<Animator>(); state = CharacterState.stateIdle; currentTile = tileController.getTile(0, 0); nextTile = state.FindNext(this, tileController); } void Update() { if (state.Move(this, nextTile)) { currentTile = nextTile; state = state.CheckTile(this); nextTile = state.FindNext(this, tileController); } } public void setDestTile(Tile _dest) { destTile = _dest; } public Tile getDestTile() { return destTile; } public Tile getCurrentTile() { return currentTile; } public void addHp(int _hp) { hp = Mathf.Min(maxHp, hp + _hp); ShowMoveText("Hp " + _hp); } public bool isFullHp() { return hp >= maxHp; } public int getHp() { return hp; } public void addMoney(int _moeny) { money += _moeny; ShowMoveText("Price " + _moeny); } public bool isEmptyMoney(int price) { return money-price >= 0; } public int getMoney() { return money; } public void addStr(int _str) { str += _str; ShowMoveText("Strong " + _str); } public int getStr() { return str; } public void addSpd(int _spd) { spd += _spd; ShowMoveText("Speed " + _spd); } public int getSpd() { return spd; } // 건물에 들어가 애니메이션 동작. (건물별로 애니메이션 번호) public void EnterFacility(Facility _facility) { beforeFacility = _facility; useFacility = _facility; useFacility.EnterFacility(this); state = CharacterState.stateJob; StartCoroutine(UseFacilityInTime(useFacility.getUseTime())); } IEnumerator UseFacilityInTime(float time) { yield return new WaitForSeconds(time); while (useFacility.InteractWithCharacter(this)) { yield return new WaitForSeconds(time); } ExitFacility(); } public void ExitFacility() { useFacility.ExitFacility(); useFacility = new NullFacility(); state = CharacterState.stateIdle; nextTile = state.FindNext(this, tileController); } private void ShowMoveText(string _text) { MoveText textObj = PoolManager.Instance.getObject(textPoolName,transform).GetComponent<MoveText>(); textObj.setTextMessage(_text); textObj.transform.position = transform.position + new Vector3(0, 0, -1); textObj.gameObject.SetActive(true); } public abstract Tile FindDestination(); }
어우 길어... 외부에서 접근하기 위한 함수들 Setter,Getter는 빼고 설명하겠습니다.
Update - 대부분의 로직은 State에 위임. 기본 동작은 이동 -> 상태 유지체크 -> 다음 이동 타일 확인입니다. 자세한 건 CharacterState에서...
EnterFacility - 건물 들어갈시 동작
UseFacilityInTime - 건물 사용 함수
ExitFacility - 건물 나갈시 동작
FindDestination - 각 캐릭터별로 빈 시설이 있는지 확인하여 리턴하는 함수.
public class TouristCharacter : Character { public override Tile FindDestination() { List<SellFacility> builds = facilityController.getSellFacility(); if (!isEmptyMoney(100)) { return tileController.getTile(0, 0); } foreach (Facility build in builds) { if (build == beforeFacility) { continue; } if (build.isEmpty()) return build.getPositionTile(); } return null; } } public class TownCharacter : Character { Facility homeFacility; public void setHomeFacility(Facility _home) { homeFacility = _home; } public override Tile FindDestination() { List<GymFacility> builds = facilityController.getGymFacility(); ; if (getHp() < 10) { return homeFacility.getPositionTile(); } foreach (Facility build in builds) { if (build == beforeFacility) { continue; } if (build.isEmpty()) return build.getPositionTile(); } return null; } }
상단이 관광객, 하단이 마을 주민입니다. 각 캐릭터들은 FindDestination을 상속받아 구현하고 있고, 관광객은 판매점을 빈 곳이 있는지 탐색하고, 돈이 부족하면 되돌아갑니다. 마을 주민은 운동시설을 빈 곳이 있는지 탐색하고, 체력이 부족하면 집으로 돌아갑니다. 단, 이전에 사용한 건물은 다른 건물을 사용하긴 전까지 사용하지 않습니다. 안그러면 혼자 독점할 것 같아서요.
CharacterState
public abstract class CharacterState { static public CharacterStateIdle stateIdle = new CharacterStateIdle(); static public CharacterStateJob stateJob = new CharacterStateJob(); static public CharacterStateMove stateMove = new CharacterStateMove(); protected bool isRangeOut(int x, int y) { return (x < 0) || (y < 0) || (x >= Constants.Width) || (y >= Constants.Height); } public abstract bool Move(Character _character,Tile dest); public abstract Tile FindNext(Character _character, TileController tileController); public abstract CharacterState CheckTile(Character _character); }
추상 클래스는 위와 같습니다. 어차피 구현될 상태 클래스들은 함수만을 가지고 매개변수로 전달된 character 클래스의 필드를 이용해 로직이 진행될 것입니다. 그래서 static으로 각 state 인스턴스를 미리 만들어두고 모든 캐릭터가 공용으로 쓸 예정입니다.
Move - dest 타일로 이동하는 함수입니다. ( 이동은 한 칸씩 합니다.)
FindNext - 다음에 이동할 dest 타일을 찾는 함수입니다.
CheckTile - State를 변경해야할지 체크할 함수입니다.
public class CharacterStateIdle : CharacterState { public override bool Move(Character _character, Tile dest) { if (dest == null) { return true; } Transform trans = _character.transform; Vector3 desPos = dest.GetTileObject().transform.position + new Vector3(0, 0, -1.5f); Vector3 direct = desPos - trans.position; trans.Translate(direct.normalized * 0.4f *Time.deltaTime); float diffPos = Vector3.Distance(desPos, trans.position); if (diffPos > 0.1f) return false; return true; } public override Tile FindNext(Character _character, TileController tileController) { IntVector2[] checkPos = new IntVector2[4] { new IntVector2(0,1),new IntVector2(1,0),new IntVector2(0,-1),new IntVector2(-1,0) }; int startIndex = Random.Range(0, 4); Tile currentTile = _character.getCurrentTile(); int checkX = currentTile.GetPosX() + checkPos[startIndex].x; int checkY = currentTile.GetPosY() + checkPos[startIndex].y; for (int i = 0; i < 4; i++) { int index = (startIndex + i) % 4; int tempX = currentTile.GetPosX() + checkPos[index].x; int tempY = currentTile.GetPosY() + checkPos[index].y; if (isRangeOut(tempX,tempY)) { continue; } if (tileController.getTile(tempX, tempY).getFacility().isNull()) { _character.anim.SetFloat("PosX", checkPos[index].x); _character.anim.SetFloat("PosY", checkPos[index].y); return tileController.getTile(tempX, tempY); } checkX = tempX; checkY = tempY; } //사방이 건물로 막혔을 경우엔 마지막 체크지점을 반환. return tileController.getTile(checkX, checkY); } public override CharacterState CheckTile(Character _character) { Tile dest = _character.FindDestination(); if(dest != null) { _character.setDestTile(dest); return CharacterState.stateMove; } return CharacterState.stateIdle; } }
서성거리는 상태인 Idle입니다. 길을 랜덤한 방향으로 이동하면서, CheckTile을 통해서 빈 시설을 찾으면 Move 상태로 변경됩니다.
public class CharacterStateMove : CharacterState { public override bool Move(Character _character, Tile dest) { Transform trans = _character.transform; Vector3 desPos = dest.GetTileObject().transform.position + new Vector3(0, 0, -1.5f); Vector3 direct = desPos - trans.position; trans.Translate(direct.normalized * 0.4f * Time.deltaTime); float diffPos = Vector3.Distance(desPos, trans.position); if (diffPos > 0.1f) return false; if(dest == _character.getDestTile()) { if (!dest.getFacility().isNull()) { if (dest.getFacility().isEmpty()) { _character.EnterFacility(dest.getFacility()); } } } return true; } public override Tile FindNext(Character _character, TileController tileController) { IntVector2[] checkPos = new IntVector2[4] { new IntVector2(0,1),new IntVector2(1,0),new IntVector2(0,-1),new IntVector2(-1,0) }; Tile currentTile = _character.getCurrentTile(); Tile destTile = _character.getDestTile(); int value = destTile.getFacility().GetPathValue(currentTile.GetPosX(), currentTile.GetPosY()); int checkX = currentTile.GetPosX() + checkPos[0].x; int checkY = currentTile.GetPosY() + checkPos[0].y; for (int i = 0; i < 4; i++) { int tempX = currentTile.GetPosX() + checkPos[i].x; int tempY = currentTile.GetPosY() + checkPos[i].y; if (isRangeOut(tempX,tempY)) { continue; } if(destTile.getFacility().GetPathValue(tempX, tempY) == value-1) { _character.anim.SetFloat("PosX", checkPos[i].x); _character.anim.SetFloat("PosY", checkPos[i].y); return tileController.getTile(tempX, tempY); } //그 어디에도 v-1구간이 없다면 가장 작은 값을 가진 타일로 이동한다. if (destTile.getFacility().GetPathValue(tempX, tempY) < destTile.getFacility().GetPathValue(checkX, checkY)) { checkX = tempX; checkY = tempY; } } return tileController.getTile(checkX, checkY); } public override CharacterState CheckTile(Character _character) { Tile dest = _character.getDestTile(); if (dest.getFacility().isEmpty() && !dest.getFacility().isNull()) { return CharacterState.stateMove; } return CharacterState.stateIdle; } }
목적지로 이동하는 Move상태입니다. 시설이 가지고 있는 로드맵을 따라 작은 값에 수렴하도록 이동합니다. 그리고 목적지에 도착한다면 시설에 들어갑니다. 목적지 시설이 파괴되거나 타인이 먼저 사용을하게 되면 다시 Idle 상태로 돌아갑니다.
public class CharacterStateJob : CharacterState { public override bool Move(Character _character, Tile dest) { return false; } public override Tile FindNext(Character _character, TileController tileController) { return null; } public override CharacterState CheckTile(Character _character) { return CharacterState.stateJob; } }
Job은 실제 건물에 들어간 상태인데....이건 뭐 Null이랑 다를바가 없습니다. 캐릭터의 능력치를 업하는 것은 건물이 캐릭터에게 부여해주는 형식이기때문에 별다른 행동은 하지 않고있습니다.
크게는 시설쪽에서 로드맵 구현이 있는데... 이 부분은 향후에 기능에 살을 붙여나가면서 다시 포스팅이 될 것 같으니 그때 다시 하도록 하겠습니다.
이제... 다음엔 DB 연동을 통해 시설물을 좀 더 풍부하게 해볼 예정입니다. (전 sqlite를 사용할 예정입니다.) 그 이후부터는 기본 기능에 대해 살을 붙여나가면서 리펙토리을 같이 겸하는 포스팅이 될 것 같습니다.
현재까지 진행된 git url ( Unity 2017.4.1f1)
- https://github.com/zprooo915/box
'IT > Unity 게임개발' 카테고리의 다른 글
[게임 개발] 도시경영 전략 게임 로직 만들기 - 4 (건물 기능 / 캐릭터 프로토타입) (0) | 2018.06.23 |
---|---|
[게임 개발] 도시경영 전략 게임 로직 만들기 - 3 (건물 설치) (0) | 2018.06.18 |
[게임 개발] 도시경영 전략 게임 로직 만들기 - 2 (Isometric(마름모 타일) 구현) (9) | 2018.06.17 |
[게임 개발]도시경영 전략 게임 로직 만들기 - 1 (0) | 2018.06.15 |
[unity] 구글 플레이 연동 및 파이어 베이스 사용기 (환경 및 오류 위주) (1) | 2018.05.01 |