CardManager
CardManager를 통해 카드를 관리한다.
1.덱 구성, shuffle, pop
item class의 구성 요소이다.
public class Item
{
public string name;
public int attack;
public int health;
public Sprite main;
public Sprite cardbase;
public Sprite background;
public string abl;
public Sprite cost;
public int cost_num;
public int grade;
public int code;
public string ablcode;
public string link;
}
아래의 코드는 밑의 post
에 들어가면 확인할 수 있다.
post
public void LoadMydeck()
{
itemBuffer = new List<Item>(50);
string[] type = new string[3] {"monster.xml","magic.xml","equip.xml"};
var bro = Backend.GameData.GetMyData("character",new Where());
var keys = bro.Rows()[0]["deck"][0].Keys;
foreach(var key in keys)
{
if(key.Substring(0,1)== "1")
{
XDocument document = XDocument.Load(@"C:\nexon_maplestory_battle_monsters_league_s\Assets\Resources\CARD\xml\" + type[0]);
var books = from row in document.Descendants("row")
where(row.Element("CODE").Value == key)
select new
{
name = row.Element("NAME").Value,
grade = row.Element("GRADE").Value,
cost = row.Element("COST").Value,
att = row.Element("ATT").Value,
def = row.Element("DEF").Value,
abl = row.Element("ABL").Value,
code = row.Element("CODE").Value,
area = row.Element("AREA").Value,
ablcode = row.Element("ABLCODE").Value,
link = row.Element("LINK").Value
};
for(int i=0;i<int.Parse(bro.Rows()[0]["deck"][0][key][0].ToString());i++)
{
foreach(var row in books)
{
Item item = new Item();
item.name = row.name;
item.grade = int.Parse(row.grade);
item.cost = Resources.Load<Sprite>("CARD/num/"+row.cost);
item.cost_num = int.Parse(row.cost);
item.attack = int.Parse(row.att);
item.health=int.Parse(row.def);
item.abl = row.abl;
item.code = int.Parse(row.code);
item.main = Resources.Load<Sprite>("CARD/monster/"+row.code);
item.background = Resources.Load<Sprite>("CARD/background/"+row.area);
item.cardbase = card_base_mon[item.grade];
item.ablcode = row.ablcode;
item.link = row.link;
itemBuffer.Add(item);
}
}
}
if(key.Substring(0,1)== "2")
{
XDocument document = XDocument.Load(@"C:\nexon_maplestory_battle_monsters_league_s\Assets\Resources\CARD\xml\" + type[1]);
var books = from row in document.Descendants("row")
where(row.Element("CODE").Value == key)
select new
{
name = row.Element("NAME").Value,
grade = row.Element("GRADE").Value,
cost = row.Element("COST").Value,
abl = row.Element("ABL").Value,
code = row.Element("CODE").Value
};
for(int i=0;i<int.Parse(bro.Rows()[0]["deck"][0][key][0].ToString());i++)
{
foreach(var row in books)
{
Item item = new Item();
item.name = row.name;
item.grade = int.Parse(row.grade);
item.cost = Resources.Load<Sprite>("CARD/num/"+row.cost);
item.cost_num = int.Parse(row.cost);
item.attack = -1;
item.health=-1;
item.abl = row.abl;
item.code = int.Parse(row.code);
if(int.Parse(row.code.Substring(3,2))<=27)
{
item.background = Resources.Load<Sprite>("CARD/magic/"+row.code);
item.cardbase =card_base[item.grade];
item.main = null;
}
itemBuffer.Add(item);
}
}
}
if(key.Substring(0,1)== "3")
{
XDocument document = XDocument.Load(@"C:\nexon_maplestory_battle_monsters_league_s\Assets\Resources\CARD\xml\" + type[2]);
var books = from row in document.Descendants("row")
where(row.Element("CODE").Value == key)
select new
{
name = row.Element("NAME").Value,
grade = row.Element("GRADE").Value,
cost = row.Element("COST").Value,
att = row.Element("ATT").Value,
def = row.Element("DEF").Value,
abl = row.Element("ABL").Value,
code =row.Element("CODE").Value
};
for(int i=0;i<int.Parse(bro.Rows()[0]["deck"][0][key][0].ToString());i++)
{
foreach(var row in books)
{
Item item = new Item();
item.name = row.name;
item.grade = int.Parse(row.grade);
item.cost = Resources.Load<Sprite>("CARD/num/"+row.cost);
item.attack = int.Parse(row.att);
item.health=int.Parse(row.def);
item.abl = row.abl;
item.code = int.Parse(row.code);
item.cardbase = card_base_mon[item.grade];
item.cost_num = int.Parse(row.cost);
item.background = back[item.grade];
item.main = Resources.Load<Sprite>("CARD/equip/"+row.code);
itemBuffer.Add(item);
}
}
}
}
}
위의 코드로 itembuffer에 item을 넣는다. 그 뒤에 덱을 섞는다.
void ShuffleItemBuffer()
{
for(int i=0;i<itemBuffer.Count;i++)
{
int rand = UnityEngine.Random.Range(i,itemBuffer.Count);
Item temp = itemBuffer[i];
itemBuffer[i]=itemBuffer[rand];
itemBuffer[rand]=temp;
}
}
게임 중에 카드를 뽑을 때는 가장 앞의 요소를 뽑고 삭제한다.
public Item PopItem(bool isMine)
{
List<Item> items = isMine ? itemBuffer : otherItemBuffer;
if(items.Count ==0)
{
Debug.Log("끝");
return null;
}
Item item = items[0];
items.RemoveAt(0);
return item;
}
2.AddCard
덱에서 카드를 뽑고 카드를 손에 위치하게 한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class PRS
{
public Vector3 pos;
public Quaternion rot;
public Vector3 scale;
public PRS(Vector3 pos,Quaternion rot, Vector3 scale)
{
this.pos =pos;
this.rot=rot;
this.scale =scale;
}
}
public class Utils
{
public static Quaternion QI => Quaternion.identity;
public static Vector3 MousePos
{
get
{
Vector3 result =Camera.main.ScreenToWorldPoint(Input.mousePosition);
result.z =-10;
return result;
}
}
}
Utils.cs라는 스크립트를 만들어 오브젝트의 위치를 좀 더 쉽게 찾을 수 있게 하였다.
public void AddCard(bool isMine)
{
if(isMine) //내 차례일 때
{
var cardObject = Instantiate(cardPrefab,cardSpwanPoint.position,Utils.QI);
var card =cardObject.GetComponent<Card>();
card.Setup(PopItem(isMine), isMine);
myCards.Add(card); // mycards -> 내 손의 카드 (list<card>)
cardObject.transform.parent = CardList.transform;
SetOriginOrder(isMine); //카드가 겹쳐있을 떄 제일 뒤 쪽의 카드가 앞 쪽으로 오게 해주는 함수
CardAlignment(isMine); //카드 정렬
byte[] send = new byte[1] {Convert.ToByte(107)};
Backend.Match.SendDataToInGameRoom(send);
}
else
{
var cardObject = Instantiate(cardPrefab,otherCardSpawnPoint.position,Utils.QI);
var card =cardObject.GetComponent<Card>();
card.Setup(PopItem(isMine), isMine);
otherCards.Add(card);
SetOriginOrder(isMine);
CardAlignment(isMine);
}
}
3.SetOriginOrder
카드를 뽑았을 때 호출되며, 카드를 뽑으면 카드가 둥근 모습으로 겹쳐 보이는데 이 때 나중에 뽑은 카드일수록 앞쪽에 있어야 하므로 사용
void SetOriginOrder(bool isMine)
{
int count = isMine ? myCards.Count : otherCards.Count;
for(int i=0;i<count;i++)
{
var targetCard = isMine ? myCards[i] : otherCards[i];
targetCard?.GetComponent<Order>().SetOriginOrder(i); //Order.cs
}
}
//order.cs
public void SetOriginOrder(int originOrder)
{
this.originOrder=originOrder;
SetOrder(originOrder,sortingLayerName);
}
//order.cs
public void SetOrder(int order,string sortingLayerName) //카드 prefebs에도 sprite와 text 들의 순위를 정하여 정렬(text는 무조건 제일 앞으로)
{
int mulOrder = order * 10;
foreach(var renderer in backRenderers)
{
renderer.sortingLayerName = sortingLayerName;
renderer.sortingOrder = mulOrder;
}
foreach(var renderer in middleRenderers)
{
renderer.sortingLayerName = sortingLayerName;
renderer.sortingOrder = mulOrder+1;
}
foreach(var renderer in frontRenderers)
{
renderer.sortingLayerName = sortingLayerName;
renderer.sortingOrder = mulOrder+2;
}
}
3.CardAlignment
카드를 뽑아서 손에 놓았을 때 카드가 자연스럽게 둥근 형태를 띄게 바꾼다.(1~3장까지는 일반 형태)
< CardAlignment >
아래의 code는 고라니
님의 영상을 보면 자세히 확인할 수 있다.
YouTube
public void CardAlignment(bool isMine)
{
List<PRS> originCardPRSs = new List<PRS>();
if(isMine)
{
originCardPRSs =RoundAlignment(myCardLeft,myCardRight,myCards.Count,0.5f,UnityEngine.Vector3.one*0.8f); // 내카드
}
else
{
originCardPRSs =RoundAlignment(otherCardLeft,otherCardRight,otherCards.Count,-0.5f,UnityEngine.Vector3.one*0.8f); //상대 카드는 뒤집어서 진행
}
var targetCards = isMine ? myCards : otherCards;
for(int i =0;i<targetCards.Count;i++)
{
var targetCard =targetCards[i];
targetCard.originPRS = originCardPRSs[i];
targetCard.MoveTransform(targetCard.originPRS,true,0.7f);
}
}
List<PRS> RoundAlignment(Transform leftTr, Transform rightTr, int objCount, float height,UnityEngine.Vector3 scale)
{
float[] objLerps =new float[objCount];
List<PRS> results =new List<PRS>(objCount);
switch(objCount)
{
case 1: objLerps =new float[] {0.5f}; break;
case 2: objLerps =new float[] {0.27f, 0.73f}; break;
case 3: objLerps =new float[] {0.1f,0.5f,0.9f};break; // 카드 3개까지는 그대로
default:
float interval = 1f / (objCount-1);
for (int i=0;i<objCount;i++)
objLerps[i]=interval*i;
break;
}
for(int i=0;i<objCount;i++)
{
var targetPos = UnityEngine.Vector3.Lerp(leftTr.position,rightTr.position,objLerps[i]);
var targetRot = Utils.QI;
if(objCount>=4)
{
float curve = Mathf.Sqrt(Mathf.Pow(height,2)- Mathf.Pow(objLerps[i]-0.5f,2));
curve = height >= 0 ? curve : -curve;
targetPos.y +=curve;
targetRot =UnityEngine.Quaternion.Slerp(leftTr.rotation,rightTr.rotation,objLerps[i]);
}
results.Add(new PRS(targetPos,targetRot,scale));
}
return results;
}
4.TryPutCard
카드를 필드에 소환한다.
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)) // entity 소환
{
targetCards.Remove(card); // 내 카드 목록에서 카드 삭제
card.transform.DOKill();
DestroyImmediate(card.gameObject); //카드 바로 삭제 (사용시 주의)
if (isMine)
{
selectCard = null;
myPutCount++;
TurnManger.Inst.ConsumeCost(isMine, card.item.cost_num); //카드의 코스트만큼 코스트 사용
}
CardAlignment(isMine); // 카드의 갯수가 1개 줄어 들어 다시 카드 정렬
AbilityManager.Inst.eTurnState = AbilityManager.ETurnState.summonsMon;
AbilityManager.Inst.stateTrigger = true;
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;
}
}
}
위에보면 DestroyImmediate()라는 함수가 있다. Destroy()와의 차이점은 파괴되는 시점이다. Destroy()는 한 프레임이 지난 시점에 파괴되고, DestroyImmediate()는 함수가 호출된 시점에 바로 파괴가 된다. 두 번째 차이점은 Asset의 파괴 가능 유무이다. 자세한 내용은 아래 링크를 달아둔다.
Link
5.카드의 움직임
카드를 클릭하거나, 마우스를 올릴 때 마우스에 따라 같이 움직여야 카드를 소환하거나 할 수 있다. 카드 prefebs에서 onmouse함수를 통해 진행되고 호출되는 함수는 CardManager에서 관리한다.
카드에 마우스가 올라갔을 때 내 카드일 경우, 카드를 확대시켜 준다.
public void CardMouseOver(Card card)
{
if(eCardState==ECardState.Nothing)
return;
selectCard = card;
EnlargeCard(true,card);
}
확대
void EnlargeCard(bool isEnlarge, Card card)
{
if(isEnlarge)
{
UnityEngine.Vector3 enlargePos = new UnityEngine.Vector3(card.originPRS.pos.x,-10f,-10f); // 카드 확대
card.MoveTransform(new PRS(enlargePos,Utils.QI,Vector3.one*1.0f),false);
}
else
card.MoveTransform(card.originPRS,false); //원래 자리로 이동
card.GetComponent<Order>().SetMostFrontOrder(isEnlarge);//카드 확인을 위해 제일 앞으로 이동
}
카드에서 마우스 커서가 나가 확대를 멈춤
public void CardMouseExit(Card card)
{
EnlargeCard(false,card);
}
카드를 클릭했을 때
public void CardMouseDown()
{
if(eCardState !=ECardState.CanMouseDrag)
return;
isMyCardDrag =true;
}
카드의 클릭을 취소했을 때
public void CardMouseUp()
{
isMyCardDrag =false;
if(eCardState !=ECardState.CanMouseDrag)
return;
if(onMyCardArea)
EntityManeger.Inst.RemoveMyEmptyEntity(); //다시 카드로 이동
else
TryPutCard(true,selectCard.item.code); // 카드를 소환
}
void CardDrag()
{
if(eCardState !=ECardState.CanMouseDrag)
return;
if(!onMyCardArea)
{
selectCard.MoveTransform(new PRS(Utils.MousePos,Utils.QI,selectCard.originPRS.scale*0.8f),false); //카드를 0.8배로해서 작게 마우스를 따라다님
EntityManeger.Inst.InsertMyEmptyEntity(Utils.MousePos.x);
}
}
6.마우스 상태
Update 함수에서 지속적인 호출을 통해 마우스의 상태를 지속적으로 업데이트한다.
enum ECardState {Nothing,CanMouseOver,CanMouseDrag}
void SetECardState()
{
if(TurnManger.Inst.isLoading) //로딩 중에는 마우스로 아무것도 할 수 없다.
eCardState = ECardState.Nothing;
else if(!TurnManger.Inst.myTurn || TurnManger.Inst.myNowGaugeCnt <=0) // 내 코스트가 0이고, 내 턴이 아닐 때는 클릭을 할 수 없다.
eCardState =ECardState.CanMouseOver;
else if(TurnManger.Inst.myTurn) // 내턴일 때는 드래그를 할 수 있다.
eCardState = ECardState.CanMouseDrag;
}