AbilityManager
AbilityManager은 몬스터의 능력을 발동시킨다. Update 문에서 지속적으로 확인한다. 몬스터의 능력은 여러가지 상황에서 발동되는데, 턴 시작 시, 소환 시, 공격 시, 공격을 당할 시, 턴 종료 시, 몬스터가 죽을 시 등 다양한 상황에 발동된다. 위의 상황들을 enum을 통해 상태를 관리한다. 능력을 구현하는 코드는 방대하여, partial로 나누어 따로 작성하였다. 능력 중 일부만 구현하였고, 그 중 한 개씩만 posting
하였다.
1.turnstart
내 턴이 시작하면 능력이 발동된다. 그 중에 감염
이라는 능력을 구현하는 것을 올린다. 감염의 효과는 우선 “내 턴 시작 시에 무작위 몬스터를 지정하여 공격력 1 감소한다”는 능력이다. 이 능력은 턴이 시작하면 바로 발동되며, 상대방 몬스터가 없을 시 능력은 발동하지 않는다.
먼저 턴이 시작하는 것을 확인을 해야 한다. 내 턴이 시작되는 것은 TurnManager에서 StartTurnCo가 호출될 때 TurnStart 상태가 된다.
IEnumerator StartTurnCo()
{
isLoading =true;
SetGauge(myTurn);
if(myTurn)
{
GameManager.Inst.Notification("나의 턴!");
AbilityManager.Inst.eTurnState=AbilityManager.ETurnState.turnStart;
AbilityManager.Inst.stateTrigger =true; // enum 상태가 바뀌고, Trigger를 true로 바꾼다.
}
yield return delay07;
if(myTurn)
OnAddCard?.Invoke(myTurn);
yield return delay07;
isLoading =false;
OnTurnStarted?.Invoke(myTurn);
}
Trigger가 동작되면, AbilityManager의 Update에서 함수를 호출한다.
void Update()
{
GetEntities(); // entity list를 가져온다.
if(stateTrigger)
{
stateTrigger = false; //trigger를 원 상태로 돌린다.
Debug.Log(eTurnState.ToString());
UseAbility(); //능력 발동
eTurnState = ETurnState.normal; //상태를 다시 기본 상태로 돌린다.
}
}
UseAbility는 Switch문을 바탕으로 구현되어 있다.
case ETurnState.turnStart:
isThereEntities = CheckAbl(true,eTurnState); //CheckAbl은 소환된 내 entity 중에서 능력이 turnstart일 때 발동하는 entity list들을 불러온다.
if(isThereEntities != null)
{
StartCoroutine(DoAbility0(myEntities,otherEntities,isThereEntities)); //코루틴을 통해 능력 구현
}
isThereEntities = null;
break;
IEnumerator DoAbility0(List<Entity> myEntities, List<Entity> otherEntities, List<Entity> ablEntities)
{
int[] abilityNum = { 9, 15, 20, 23, 24, 26, 27, 34, 37, 47 , 5};
int entityCnt = ablEntities.Count;
foreach (Entity entity in ablEntities)
{
switch (entity.ablcode1)
{
case 9:
//감염
yield return StartCoroutine(Ability9(myEntities, otherEntities, entity));
yield return new WaitForSeconds(6.0f);
break;
}
}
}
일부의 코드만 긁어 왔다.
위의 코루틴은 다시 코루틴을 호출하게 된다.
//감염
IEnumerator Ability9(List<Entity> myEntities, List<Entity> otherEntities, Entity entity)
{
yield return new WaitForSeconds(3.0f);
List<Entity> targetList = new List<Entity>();
foreach (Entity target in otherEntities)
{
if (target.attack > 0)
targetList.Add(target);
}
if (targetList != null)
{
entity.GetComponent<Order>().SetOrder(0, "Ability");
GameManager.Inst.Notification("감염 발동!");
yield return new WaitForSeconds(2.0f);
entity.transform.DOScale(1.5f, 0.5f);
yield return new WaitForSeconds(1.0f);
int index = Random.Range(0, targetList.Count);
byte[] send = new byte[3] {0x09, Convert.ToByte(entity.gameObject.name),Convert.ToByte(targetList[index].gameObject.name)};
BackEndMatch.SendMessage(send);
targetList[index].attack--;
targetList[index].gameObject.transform.Find("TMP_ATT").transform.GetComponent<TextMeshPro>().text = targetList[index].attack.ToString();
targetList[index].gameObject.transform.Find("TMP_ATT").transform.DOShakeScale(1.0f, 2, 10, 90, true);
targetList[index].gameObject.transform.Find("TMP_ATT").transform.GetComponent<TextMeshPro>().fontMaterial = blueColor;
entity.transform.DOScale(0.8f, 0.5f);
entity.GetComponent<Order>().SetOriginOrder(0);
}
}
2.summonsMon
몬스터를 소환했을 때 발동한다. 능력 중 소환
이라는 능력의 코드를 일부 긁어 왔다. 소환이라는 능력은 “몬스터 소환 시 m을 소환한다” 이다. 이 때 m은 item 코드에 link라는 항목이 있는데, link된 몬스터 중 무작위 소환된다. summonsMon은 CardManager에서 TryPutCard가 동작할 때 trigger가 동작한다.
public bool TryPutCard(bool isMine, int code)
{
if (isMine)
{
Card card = selectCard;
var spawnPos = Utils.MousePos;
var targetCards = myCards;
if (card.item.cost_num <= TurnManger.Inst.myNowGaugeCnt)
{
if (EntityManeger.Inst.SpawnEntity(isMine, card.item, spawnPos))
{
targetCards.Remove(card);
card.transform.DOKill();
DestroyImmediate(card.gameObject);
if (isMine)
{
selectCard = null;
myPutCount++;
TurnManger.Inst.ConsumeCost(isMine, card.item.cost_num);
}
CardAlignment(isMine);
AbilityManager.Inst.eTurnState = AbilityManager.ETurnState.summonsMon;
AbilityManager.Inst.stateTrigger = true; // trigger 동작
BackEndMatch.SendSpawn(card.item.code);
return true;
}
else
{
targetCards.ForEach(x => x.GetComponent<Order>().SetMostFrontOrder(false));
CardAlignment(isMine);
return false;
}
}
else
{
return false;
}
}
else
{
Card card = new Card();
for(int i =0;i<otherCards.Count;i++)
{
if(otherCards[i].item.code==code)
{
card = otherCards[i];
}
}
var spawnPos = otherCardSpawnPoint.position;
var targetCards = otherCards;
if (EntityManeger.Inst.SpawnEntity(isMine, card.item, spawnPos))
{
targetCards.Remove(card);
card.transform.DOKill();
DestroyImmediate(card.gameObject);
if (isMine)
{
selectCard = null;
myPutCount++;
TurnManger.Inst.ConsumeCost(isMine, card.item.cost_num);
}
CardAlignment(isMine);
return true;
}
else
{
targetCards.ForEach(x => x.GetComponent<Order>().SetMostFrontOrder(false));
CardAlignment(isMine);
return false;
}
}
}
trigger가 동작하면 turnstart와 동일하게 함수가 2개의 코루틴이 동작한다.
case ETurnState.summonsMon:
entity = CheckAblEntity1(true,eTurnState);
if(entity != null)
{
StartCoroutine(DoAbility1(myEntities,otherEntities,entity));
}
entity = null;
break;
IEnumerator DoAbility1(List<Entity> myEntities, List<Entity> otherEntities, Entity entity)
{
switch (entity.ablcode1)
{
case 2:
//소환
yield return StartCoroutine(Ability2(myEntities, entity));
break;
}
} // 코드 일부 발췌
//소환
IEnumerator Ability2(List<Entity> myEntities, Entity entity)
{
yield return new WaitForSeconds(3.0f);
int code = int.Parse(entity.link[Random.Range(0, entity.link.Count)]);
SpawnEntity(SetEntity(code)); //AbilityManager에서 따로 SpawnEntity를 작성
entity.ablcode1 = 0;
byte[] send = new byte[4]{0x02,BackEndMatch.ConvertByte(code)[0],BackEndMatch.ConvertByte(code)[1],BackEndMatch.ConvertByte(code)[2]};
BackEndMatch.SendMessage(send);
}
3.attacking
공격 시 동작하는 부분이다. 이 부분은 데이터 전송하는 단계에서 문제가 생길 것으로 생각되어, 공격 받는 부분도 같이 구현하였다. 공격을 받았을 때 동작은 내가 공격을 한 상대의 능력이 공격 당했을 시 나타나는 능력일 경우 동작한다. 이 때는 내 능력이 아닌 상대의 능력 code를 확인한다. 공격 시
에서 불기둥
code와 공격 당했을 시
에서는 회피
의 code를 리뷰하겠다.
case ETurnState.attacking:
entity = EntityManeger.Inst.castEntity;
if(entity != null)
{
if(abilityNum[entity.ablcode1].state == (int)ETurnState.attacking)
{
StartCoroutine(DoAbility2(myEntities,otherEntities,entity,EntityManeger.Inst.targetEntity));
}
if(abilityNum[EntityManeger.Inst.targetEntity.ablcode1].state == (int)ETurnState.beAttacked)
{
StartCoroutine(DoAbility7(myEntities,otherEntities,entity,EntityManeger.Inst.targetEntity));
}
}
entity = null;
break;
둘 다 한 번에 동작할 수 있게 if는 따로 써주었다.
불기둥 : 공격 시 필드에 있는 모든 상대 몬스터와 플레이어에게 1의 데미지를 준다.
//불기둥
IEnumerator Ability25(List<Entity> otherEntities, Entity entity)
{
List<GameObject> fires = new List<GameObject>(); //이펙트 (진짜 메이플스토리 게임 속의 "아니"의 스킬 구현)
List<Entity> dieEntity = new List<Entity>(); //공격을 당해 죽는 entity
yield return null;
byte[] send =new byte[2] {Convert.ToByte(25),Convert.ToByte(entity.gameObject.name)};
BackEndMatch.SendMessage(send);//데이터 전송
foreach(var target in otherEntities)
{
var fire = Instantiate(firePrefab,target.transform.position,target.transform.rotation);
fires.Add(fire); // 타겟 위에 fire 이펙트 설정
target.Damaged(1);// 타겟들에게 1 데미지
if(target.health <=0) //entity가 방어력이 0이 되면, 죽는 entity list에 추가
{
dieEntity.Add(target);
}
}
var fire2 = Instantiate(firePrefab,otherBoss.transform.position,otherBoss.transform.rotation); // 플레이어에게도 데미지
fires.Add(fire2);
otherBoss.Damaged(1);
yield return new WaitForSeconds(1.0f);
foreach(var fire in fires)
{
Destroy(fire.gameObject);
} // 이펙트는 1초 진행되고 파괴
foreach(var target in otherEntities)
{
EntityManeger.Inst.SpawnDamage(1,target.transform); //데미지 오브젝트 spawn
}
EntityManeger.Inst.SpawnDamage(1,otherBoss.transform);
foreach(var row in dieEntity)
{
EntityManeger.Inst.AttackCallback(row); //entity가 죽을 경우 attackcallback을 통해 entity 파괴
}
yield return StartCoroutine(EntityManeger.Inst.CheckBossDie()); //플레이어에게 데미지를 1 주어서 플레이어가 죽었는 지 확인
}
회피 : 50% 확률로 공격을 회피한다.
IEnumerator Ability4(Entity attacker,Entity defender)
{
yield return null;
if(Random.Range(0,2) ==0) // 1 또는 0
{
byte[] send = new byte[3] {Convert.ToByte(4),Convert.ToByte(attacker.gameObject.name),Convert.ToByte(defender.gameObject.name)};
BackEndMatch.SendMessage(send);
attacker.attackable = false;
Sequence sequence = DOTween.Sequence()
.Append(attacker.transform.DOMove(defender.transform.position, 0.4f)).SetEase(Ease.InSine)
.AppendCallback(() =>
{ //attack과 동일하지만 움직임만 있고 공격 데미지가 없다
})
.Append(attacker.transform.DOMove(attacker.transform.position, 0.4f)).SetEase(Ease.OutSine)
.OnComplete(() => EntityManeger.Inst.AttackCallback(attacker, defender));
}
else
{
if(abilityNum[attacker.ablcode1].state != (int)ETurnState.attacking)
{
byte[] send = new byte[3] { 0x00, Convert.ToByte(attacker.gameObject.name), Convert.ToByte(defender.gameObject.name) };
BackEndMatch.SendMessage(send);
Attack(attacker,defender); // 회피 실패 시 기본 공격
}
}
}
4.drag
entity를 드래그 했을 때 발동한다. 여기서는 발동하는 능력이 1개 밖에 없다. 저주
라는 능력인데, 저주는 “상대 플레이어를 직접 공격할 수 없다”이다. 그래서 저주
능력을 가진 entity를 드래그할 경우 상대 플레이어를 공격 대상으로 삼을 수 없다.
case ETurnState.drag:
entity = CheckAblEntity3(true,eTurnState);
if(entity != null)
{
StartCoroutine(DoAbility3(myEntities,otherEntities,entity));
}
entity = null;
break;
//저주
IEnumerator Ability29(Entity entity)
{
otherBoss.canBeAttacked = false;
while(true)
{
yield return null;
if(entity != EntityManeger.Inst.selectEntity)
{
otherBoss.canBeAttacked = true;
break;
}
}
}
5.turnEnd
턴 종료 시에 발동 된다. 턴 종료 버튼을 누를 때 호출된다. 치유
능력 code를 리뷰하겠다. 치유는 “내 턴이 종료할 때마다 내 필드 위의 모든 몬스터 방어력 1 회복한다.”이다.
case ETurnState.turnEnd:
isThereEntities = CheckAbl(true,eTurnState);
if(isThereEntities != null)
{
StartCoroutine(DoAbility5(myEntities,otherEntities,isThereEntities));
}
isThereEntities = null;
break;
//치유
IEnumerator Ability11(List<Entity> myEntities, Entity entitiy)
{
foreach(var row in myEntities)
{
row.health+=1;
yield return UpdateATTDEF(row,true); //공격력이나 방어력이 바뀔 때 숫자의 색깔이 바뀐다. true일땐 빨간색, false일 때는 파란색
}
byte[] send = new byte[2] {0x0B, Convert.ToByte(entitiy.gameObject.name)};
BackEndMatch.SendMessage(send);
}
6.myMonDie
내 entity가 죽었을 때 호출된다. 이 중에 제물
code를 리뷰하겠다. 제물은 “이 몬스터가 죽을 시 m을 소환한다.”이다.
case ETurnState.myMonDie:
entity = CheckAblEntity7(true,ETurnState.beAttacked);
if(entity != null)
{
Debug.Log(entity);
StartCoroutine(DoAbility9(myEntities,otherEntities,entity));
}
isThereEntities = null;
break;
//제물
IEnumerator Ability3(Entity entity)
{
yield return null;
if(myEntities.Count<10)
{
int index = Random.Range(0,entity.link.Count);
yield return new WaitForSeconds(1.0f);
SpawnEntity(SetEntity(int.Parse(entity.link[index])));
byte[] send = new byte[4]{0x02,BackEndMatch.ConvertByte(index)[0],BackEndMatch.ConvertByte(index)[1],BackEndMatch.ConvertByte(index)[2]};
BackEndMatch.SendMessage(send);
}
}
모든 상황 별 능력을 1개씩만 리뷰하였다. 이와 비슷한 코루틴이 약 50개 정도이다. 몬스터의 능력을 모두 구현하였고, 마법카드와 장비카드의 능력은 구현하지 않았다.