園子荒廢多年,閒來無事,用Unity3D來嘗試作一個簡單的小遊戲,一方面是對最近研究的Unity3D有點總結,一方面跟廣大的園友相互學習和提升。話很少說,進入正題~git
1.建立Unity2017的2D項目,暫且叫作ChinesePoker吧,就用自帶的UGUI來編輯UI, 目前只導入iTween插件,用來方便控制動畫效果。github
目錄結構以下:ide
考慮卡牌須要動態生成,我把圖片資源放到Resource目錄,並按照Card_類型(大小王,紅桃,黑桃,方片,梅花 )_數字(卡牌所在類型中的數字)命名。學習
素材都是網上找的,沒有美工基礎,就是這麼個意思,你們將就看吧,:)測試
2.建第一個場景,默認叫001_Playing,做爲主要玩牌的場景,暫時做爲第1個場景,後期新場景添加進來,咱們可能再調整場景的順序。優化
添加一個UI->Image,選擇一個背景圖片;動畫
添加3個UI->Canvas,分別取名叫Player0,Player1,Player2,表明玩家,對手1,對手2;ui
每一個Player底下,添加一個Image,選擇卡牌背面圖片,分別表示發牌時各自牌堆的位置,並在桌面放置一個總牌堆的位置,默認not active;this
建一個卡牌的圖片,命名爲Card,並做爲預製件,放入Player0中間一個,稍微偏移必定位置再放置一個,用來計算每張牌跟臨牌相對位置,設置not active;spa
建一個卡牌的背面圖片,命名Cover,也做爲預製件;
添加一個測試按鈕TestButton;
差很少了,大概結構以下:
1.新建CardInfo類,主要不要繼承默認的MonoBehaviour類,用來做爲卡牌的實體類;
實現IComparable接口,後面手牌排序會用到。
public class CardInfo : IComparable { public string cardName; //卡牌圖片名 public CardTypes cardType; //牌的類型 public int cardIndex; //牌在所在類型的索引1-13 public CardInfo(string cardName) { this.cardName = cardName; var splits = cardName.Split('_'); switch (splits[1]) { case "1": cardType = CardTypes.Hearts; cardIndex = int.Parse(splits[2]); break; case "2": cardType = CardTypes.Spades; cardIndex = int.Parse(splits[2]); break; case "3": cardType = CardTypes.Diamonds; cardIndex = int.Parse(splits[2]); break; case "4": cardType = CardTypes.Clubs; cardIndex = int.Parse(splits[2]); break; case "joker": cardType = CardTypes.Joker; cardIndex = int.Parse(splits[2]); break; default: throw new Exception(string.Format("卡牌文件名{0}非法!", cardName)); } } //卡牌大小比較 public int CompareTo(object obj) { CardInfo other = obj as CardInfo; if (other == null) throw new Exception("比較對象類型非法!"); //若是當前是大小王 if (cardType == CardTypes.Joker) { //對方也是大小王 if (other.cardType == CardTypes.Joker) { return cardIndex.CompareTo(other.cardIndex); } //對方不是大小王 return 1; } //若是是通常的牌 else { //對方是大小王 if (other.cardType == CardTypes.Joker) { return -1; } //若是對方也是通常的牌 else { //計算牌力 var thisNewIndex = (cardIndex == 1 || cardIndex == 2) ? 13 + cardIndex : cardIndex; var otherNewIndex = (other.cardIndex == 1 || other.cardIndex == 2) ? 13 + other.cardIndex : other.cardIndex; if (thisNewIndex == otherNewIndex) { return -cardType.CompareTo(other.cardType); } return thisNewIndex.CompareTo(otherNewIndex); } } } }
2.Card預製件上,添加Card腳本,主要保存對應CardInfo信息、選中狀態,並加載卡牌圖片;
public class Card : MonoBehaviour { private Image image; //牌的圖片 private CardInfo cardInfo; //卡牌信息 private bool isSelected; //是否選中 void Awake() { image = GetComponent<Image>(); } /// <summary> /// 初始化圖片 /// </summary> /// <param name="cardInfo"></param> public void InitImage(CardInfo cardInfo) { this.cardInfo = cardInfo; image.sprite = Resources.Load("Images/Cards/" + cardInfo.cardName, typeof(Sprite)) as Sprite; } /// <summary> /// 設置選擇狀態 /// </summary> public void SetSelectState() { if (isSelected) { iTween.MoveTo(gameObject, transform.position - Vector3.up * 10f, 1f); } else { iTween.MoveTo(gameObject, transform.position + Vector3.up * 10f, 1f); } isSelected = !isSelected; } }
3.考慮玩家分爲2種類型,先建立一個公共的基類,實現玩家公共的方法,好比增長一張卡牌、清空全部卡片、排序等;
public class Player : MonoBehaviour { protected List<CardInfo> cardInfos = new List<CardInfo>(); //我的所持卡牌 private Text cardCoutText; void Start() { cardCoutText = transform.Find("HeapPos/Text").GetComponent<Text>(); } /// <summary> /// 增長一張卡牌 /// </summary> /// <param name="cardName"></param> public void AddCard(string cardName) { cardInfos.Add(new CardInfo(cardName)); cardCoutText.text = cardInfos.Count.ToString(); } /// <summary> /// 清空全部卡片 /// </summary> public void DropCards() { cardInfos.Clear(); } protected void Sort() { cardInfos.Sort(); cardInfos.Reverse(); } }
4.添加第一種玩家(自身玩家)PlayerSelf,繼承Player,並掛載到Player0對象上;
實現整理手牌的邏輯:發牌後,從中間的位置,根據大小依次將牌展開;
獲取牌面點擊事件,將牌選中或取消選中;
public class PlayerSelf : Player { public GameObject prefab; //預製件 private Transform originPos1; //牌的初始位置 private Transform originPos2; //牌的初始位置 private List<GameObject> cards=new List<GameObject>(); void Awake() { originPos1 = transform.Find("OriginPos1"); originPos2 = transform.Find("OriginPos2"); } //整理手牌 public void GenerateAllCards() { //排序 Sort(); //計算每張牌的偏移 var offsetX = originPos2.position.x - originPos1.position.x; //獲取最左邊的起點 int leftCount = (cardInfos.Count / 2); var startPos = originPos1.position + Vector3.left * offsetX * leftCount; for (int i = 0; i < cardInfos.Count; i++) { //生成卡牌 var card = Instantiate(prefab, originPos1.position, Quaternion.identity, transform); card.GetComponent<RectTransform>().localScale = Vector3.one * 0.6f; card.GetComponent<Card>().InitImage(cardInfos[i]); var targetPos = startPos + Vector3.right * offsetX * i; card.transform.SetAsLastSibling(); //動畫移動 iTween.MoveTo(card, targetPos, 2f); cards.Add(card); } } public void DestroyAllCards() { cards.ForEach(Destroy); cards.Clear(); } /// <summary> /// 點擊卡牌處理 /// </summary> /// <param name="data"></param> public void CardClick(BaseEventData data) { //叫牌或出牌階段才能夠選牌 if (CardManager._instance.cardManagerState == CardManagerStates.Bid || CardManager._instance.cardManagerState == CardManagerStates.Playing) { var eventData = data as PointerEventData; if (eventData == null) return; var card = eventData.pointerCurrentRaycast.gameObject.GetComponent<Card>(); if (card == null) return; card.SetSelectState(); } } }
5.添加另外一種玩家(對手玩家)PlayerOther,繼承Player,並掛載到Player1,Player2對象上;
暫時沒有實現任何其餘功能:
public class PlayerOther : Player { }
在Camera上添加卡牌管理腳本:CardManager
1.實現洗牌邏輯,這裏用生成GUID隨機性後排序,達到洗牌的目的;
2.記錄當前發牌回合,每發一張牌,跳轉給下一個玩家;
3.記錄當前玩牌回合(未來可能用到),每玩一局,跳轉下個玩家開始發牌;
4.發牌邏輯:
設置牌堆的顯示,動畫依次給每位玩家發一張卡牌,發完牌後,隱藏牌堆,並將玩家的卡牌排序並展現;
public class CardManager : MonoBehaviour { public float dealCardSpeed = 20; //發牌速度 public Player[] Players; //玩家的集合 public GameObject coverPrefab; //背面排預製件 public Transform heapPos; //牌堆位置 public Transform[] playerHeapPos; //玩家牌堆位置 public static CardManager _instance; //單例 public CardManagerStates cardManagerState; private string[] cardNames; //全部牌集合 private int termStartIndex = 0; //回合開始玩家索引 private int termCurrentIndex = 0; //回合當前玩家索引 private List<GameObject> covers = new List<GameObject>(); //背面卡牌對象,發牌結束後銷燬 void Awake() { _instance = this; cardNames = GetCardNames(); } /// <summary> /// 洗牌 /// </summary> public void ShuffleCards() { //進入洗牌階段 cardManagerState = CardManagerStates.ShuffleCards; cardNames = cardNames.OrderBy(c => Guid.NewGuid()).ToArray(); } /// <summary> /// 發牌 /// </summary> public IEnumerator DealCards() { //進入發牌階段 cardManagerState = CardManagerStates.DealCards; //顯示牌堆 heapPos.gameObject.SetActive(true); playerHeapPos.ToList().ForEach(s => { s.gameObject.SetActive(true); }); foreach (var cardName in cardNames) { //給當前玩家發一張牌 Players[termCurrentIndex].AddCard(cardName); var cover = Instantiate(coverPrefab, heapPos.position, Quaternion.identity, heapPos.transform); cover.GetComponent<RectTransform>().localScale = Vector3.one; covers.Add(cover); iTween.MoveTo(cover, playerHeapPos[termCurrentIndex].position, 0.3f); yield return new WaitForSeconds(1 / dealCardSpeed); //下一個須要發牌者 SetNextPlayer(); } //隱藏牌堆 heapPos.gameObject.SetActive(false); playerHeapPos[0].gameObject.SetActive(false); //顯示玩家手牌 Players.ToList().ForEach(s => { var player0 = s as PlayerSelf; if (player0 != null) { player0.GenerateAllCards(); } }); //動畫結束,進入叫牌階段 yield return new WaitForSeconds(2f); covers.ForEach(Destroy); cardManagerState = CardManagerStates.Bid; } /// <summary> /// 清空牌局 /// </summary> public void ClearCards() { //清空全部玩家卡牌 Players.ToList().ForEach(s => s.DropCards()); //顯示玩家手牌 Players.ToList().ForEach(s => { var player0 = s as PlayerSelf; if (player0 != null) { player0.DestroyAllCards(); } }); } /// <summary> /// 獲取下個玩家 /// </summary> /// <returns></returns> private void SetNextPlayer() { termCurrentIndex = (termCurrentIndex + 1) % Players.Length; } /// <summary> /// 切換開始回合玩家 /// </summary> public void SetNextTerm() { termStartIndex = (termStartIndex + 1) % Players.Length; } private string[] GetCardNames() { //路徑 string fullPath = "Assets/Resources/Images/Cards/"; if (Directory.Exists(fullPath)) { DirectoryInfo direction = new DirectoryInfo(fullPath); FileInfo[] files = direction.GetFiles("*.png", SearchOption.AllDirectories); return files.Select(s => Path.GetFileNameWithoutExtension(s.Name)).ToArray(); } return null; } //for test public void OnTestClick() { ClearCards(); ShuffleCards(); StartCoroutine(DealCards()); } }
其實發牌後的動畫,能夠由override基類的方法,交由Player子類處理,不用CardManager集中管理,你們能夠優化一下。
大致邏輯完成,咱們驗證下效果吧: