我的第一个unity项目是卡牌游戏,是看着慕课网以及用同学分享的项目资源跟着老师做的。慕课网课程的链接,希望老师允许我把课程的链接放在这里,宁静方致远 分享的项目,内含素材,希望他们可以同意把他们的链接放在这里。
游戏功能是判断两张翻转的卡牌是否相同,如果相同就消掉两张卡牌,不相同就把卡牌翻转到背面,游戏共有三个关卡,分别有23、24,2*5张卡牌。下面是游戏的界面
1.panel_start界面
2.panel_card界面
3.panel_over界面
项目的构建过程
1.先建一个unity的3D项目,然后在File——>Build Setting中建一个叫game的Scenes,后期build生成apk的时候用。
2.建目录,在Project window下的Assets(资源)下建各种文件夹用于组织各种资源。我建了四个目录,第一个是Resources,用于保存项目需要的静态资源(图片);Scenes目录,用于用于放置unity场景文件,方便大包时引用;Script目录用于保存文件脚本(C#文件);Testure用于也是用于保存游戏需要的图片。然后把需要的资源导入相应的目录,把Txeture Type(纹理类型)设置为Sprite(2D and UI)
3.在game场景下建一个画布Canvas,在Canvas下建一个个panel,设置其layer为UI,在把Testure下的游戏背景图拖进Source Image,点击set native size,这样一个有图片的panel就弄好了。因为都是同一个背景,所以可以通过Ctrl+d复制panel两次,分别取名Panelstart(游戏开始的主界面),PanelCard(卡牌界面),PanelOver(结束界面)。
4.在PanelStart 下面建一个Button,设置layer为UI,在把Testure下的level1拖进Source Image,点击set native size,在自己调整合适的Button大小。Ctrl复制两次,分别取名ButtonLevel1(关卡一的按钮)、ButtonLevel2(关卡二的按钮)ButtonLevel3(关卡三的按钮),然后在更改ButtonLevel2,ButtonLevel3的Source Image为Level2,Level3图片。通过同样的步骤建立如下的机构,Image_Back是卡牌的背面,Image_front是卡牌的正面,Button_to_start是一个关卡通过后从新回到PanelStart的按钮,Button_to_end关闭游戏。
4.Script下建立两个脚本,分别是叫gameMain,CardFlipAnimtionCtrl。
gameMain的代码:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class gameMain : MonoBehaviour//只有继承于monobehavior 类的游戏对象才可以直接在unity 中运行
{public Button btnLevel1;public Button btnLevel2;public Button btnLevel3;public Button Button_to_start;public Button Button_to_end;public Transform panelStart;public Transform panelCard;public Transform panelOver;// Start is called before the first frame updatevoid Start()// 从MonoBehaviour继承的方法,start在MonoBehaviour的生命周期中只会被执行一次{//跳转到关卡一btnLevel1.onClick.AddListener(() =>{panelStart.gameObject.SetActive(false);//设置panelStart为不可用,不显示panelStartpanelCard.gameObject.SetActive(true);//设置panelCard为可用LoadLevelCard(3,2);});btnLevel2.onClick.AddListener(() =>{panelStart.gameObject.SetActive(false);panelCard.gameObject.SetActive(true);LoadLevelCard(4,2);});btnLevel3.onClick.AddListener(() =>{panelStart.gameObject.SetActive(false);panelCard.gameObject.SetActive(true);LoadLevelCard(5,2);});//跳转到游戏开始的页面,关闭游戏卡牌界面和游戏结束见面Button_to_start.onClick.AddListener(() => {panelStart.gameObject.SetActive(true);panelCard.gameObject.SetActive(false);panelOver.gameObject.SetActive(false);});//关闭游戏Button_to_end.onClick.AddListener(() => {Application.Quit();});}//跳转到游戏开始界面,即观其选择的那个界面private void ToGameStartPage(){panelStart.gameObject.SetActive(true);panelCard.gameObject.SetActive(false);panelOver.gameObject.SetActive(false);}//为levelCrad界面加载卡牌void LoadLevelCard(int width,int height)//width:卡牌的一行的数量;heigth:卡牌一列的数量{//1.加载卡牌图片Sprite[] sps = Resources.LoadAll<Sprite>("");//加载根目录下的资源,加载Assets下Resources目录下的资源,这个目录名字必须是Resources,卡牌的格式都被设置为Sprite(2D and UI),所以泛型的参数是Sprite/** for(int i=0;i<sps.Length;i++){Debug.LogError(sps[i].name);//在Unity控制台打印数据的名称}*///2.计算需要加载卡牌的数量,因为每一种牌有两张,所以实际上只需要一半不同的卡牌int totalCount = width * height / 2;//3.计算随机加载卡牌的索引List<Sprite> spsList = new List<Sprite>();//把数组里的数据存到列表里,列表可以调用RemoveAt()删除已经加载的卡牌索引,这样就不会加载同样的图片for (int i = 0; i < sps.Length; i++){spsList.Add(sps[i]);}List<Sprite> needShowCardList = new List<Sprite>();while(totalCount>0){int randomIndex = UnityEngine.Random.Range(0, spsList.Count);needShowCardList.Add(spsList[randomIndex]);//需要加载两个needShowCardList.Add(spsList[randomIndex]);spsList.RemoveAt(randomIndex);//删除已经加载的图片totalCount--;}//高等级通关后在玩低等级要销毁对象,且解除关联Transform contentRoot = panelCard.Find("Panel");//获取panelCard下的Panel节点的对象for (int i = 1; i < contentRoot.childCount; ++i){GameObject itemTemp = contentRoot.GetChild(i).gameObject;Sprite ss = itemTemp.transform.Find("Image_front").GetComponent<Image>().sprite;//获取Image_front的对象Debug.Log(i + "," + ss.name);//打印Image_front对象的名字itemTemp.transform.SetParent(null);//把itemTemp的父节点设置为nullDestroy(itemTemp);//删除itemTemp}//4.显示卡牌到UI上int maxCount = Mathf.Max(contentRoot.childCount, needShowCardList.Count);//比较panel这个节点的子节点的数量和需要展示的卡牌数量的大小GameObject itemPrefab = contentRoot.GetChild(0).gameObject;//获取容器的第一个节点for(int i=0;i<maxCount; i++){int index = Random.Range(0, needShowCardList.Count);GameObject itemObject = null;if(i<contentRoot.childCount){itemObject = contentRoot.GetChild(i).gameObject;//用容器原有的子节点对象给空的对象引用赋值}else{itemObject = GameObject.Instantiate<GameObject>(itemPrefab);//克隆对象itemObject.transform.SetParent(contentRoot, false);//将itemObject的父节点设置为contentRoot,也就是把itemObject添加到panel下}itemObject.transform.Find("Image_front").GetComponent<Image>().sprite = needShowCardList[index];//给图片资源对象赋给sprite属性needShowCardList.RemoveAt(index);//从需要展示的列表里删除已经赋给sprite的图片资源CardFlipAnimtionCtrl cardAniCtrl = itemObject.GetComponent<CardFlipAnimtionCtrl>();cardAniCtrl.SetDefaultState();//设置卡牌默认初始状态}GridLayoutGroup glg = contentRoot.GetComponent<GridLayoutGroup>();//获取contentRoot(panel的对象)的网格布局组的组件/*自动计算panel节点的宽高*宽=元素的个数*元素的宽+元素距离左边的距离+元素距离右边的距离+(元素个数-1 )*元素的间距x**/float panelWidth = width * glg.cellSize.x + glg.padding.left +glg.padding.right + (width - 1) * glg.spacing.x;float panelHeight = height * glg.cellSize.y + glg.padding.top +glg.padding.bottom + (height - 1) * glg.spacing.y;contentRoot.GetComponent<RectTransform>().sizeDelta =new Vector2(panelWidth, panelHeight);//给contenRoot的大小赋值}
//判断玩家是否通关public void CheckIsGameOver(){CardFlipAnimtionCtrl[] allCards= GameObject.FindObjectsOfType<CardFlipAnimtionCtrl>();//首相获取所有对象,找的方法是通过全局检查,找的类型是卡牌动画脚本。if(allCards!=null&&allCards.Length>0){List<CardFlipAnimtionCtrl> cardInFront = new List<CardFlipAnimtionCtrl>(); //这个list是用来存储遍历过程中翻到正面的卡牌,快捷键:选中一个变量,按f12,快速跳到这变量所在个的位置for(int i=0;i<allCards.Length;i++){CardFlipAnimtionCtrl cardTem = allCards[i];if(cardTem.isInFront && !cardTem.isOver){cardInFront.Add(cardTem);}if(cardInFront.Count>=2)//当翻转的两张牌了两张牌以上{string cardImageName1 = cardInFront[0].getCradImageName();string cardImageName2 = cardInFront[1].getCradImageName();if(cardImageName1==cardImageName2){cardInFront[0].MatchSuccess();//需要做的事情为,把这两张牌标记为匹配结束状态cardInFront[1].MatchSuccess();}else{cardInFront[0].MatchFail();//把卡牌翻转到反面cardInFront[1].MatchFail();}bool isAllOver =true;for (int j=0;j<allCards.Length;j++){isAllOver &= allCards[j].isOver;//当数组中的CardFlipAnimtionCtrl的isOver为ture时isAllover才为ture}if(isAllOver){ToGameOverPage();}break;}}}}private void ToGameOverPage(){panelStart.gameObject.SetActive(false);panelCard.gameObject.SetActive(false);panelOver.gameObject.SetActive(true);}//ctrl+—号快速返回原来的位置
}
在脚本里声明的的button变量只有和button按钮关联才会有作用,关联的方法:第一种方法点击Main Camera,在将对应的button按钮拖到对应的变量上面
后者点击变量旁边的小圆圈,在Select Button选择对应的Button
为panel添加Grid Layout Group组件:点击add Component,在弹出的搜索框中搜索Grid Layout Group
Grid Layout Group是为了组织卡牌元素。
CardFlipAnimationCtrl(卡牌翻转动画控制)代码
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;//封装卡牌的属性和操作
public class CardFlipAnimtionCtrl : MonoBehaviour,IPointerClickHandler
{Transform cardFront;//卡牌的正面Transform cardBack;//卡牌的反面float flipDuaration=0.2f;public bool isInFront = false;//卡牌是否正面朝上public bool isOver=false;//正面朝上的卡牌是否匹配成功public void OnPointerClick(PointerEventData eventData)//当鼠标点击触发的事件{if (isOver) return;//如果已经配对成功就不需要任何操作了if (!isInFront){StartCoroutine(FlipCardToFront());//将翻转到正面}else{StartCoroutine(FlipCardToBack());}}//把卡牌翻转到正面IEnumerator FlipCardToFront(){//1.翻转反面到90度cardFront.gameObject.SetActive(false);cardBack.gameObject.SetActive(true);cardFront.rotation = Quaternion.identity;//卡牌初始角度,Quaternion.identity相当于Quaternion.Euler(0,0,0)while (cardBack.rotation.eulerAngles.y < 90)//欧拉角小于90°的时候,一直执行{cardBack.rotation *= Quaternion.Euler(0, Time.deltaTime*90f*(1f/flipDuaration), 0);//0.2秒内翻转90°if(cardBack.rotation.eulerAngles.y>90)//当欧拉角大90°时设置为90°{cardBack.rotation = Quaternion.Euler(0, 90, 0);break;}yield return new WaitForFixedUpdate();}//翻转卡牌到正面cardFront.gameObject.SetActive(true);cardBack.gameObject.SetActive(false);cardFront.rotation = Quaternion.Euler(0, 90, 0);//设置卡牌初始状态的欧拉角为90度while(cardFront.rotation.eulerAngles.y>0){cardFront.rotation *= Quaternion.Euler(0, -Time.deltaTime * 90f * (1f / flipDuaration),0);if(cardFront.rotation.eulerAngles.y>90){cardFront.rotation = Quaternion.Euler(0, 0, 0);break;}yield return new WaitForFixedUpdate();}isInFront = true;Camera.main.GetComponent<gameMain>().CheckIsGameOver();//每次将牌翻转到正面都要调用gameMain类中的CheckIsGameOver()的方法判断是不是所有的开牌都匹配成功//应为这个脚本是放在主相机,通过主相机的对象可以获取这个脚本的对象,然后获取其方法。}//把卡牌翻转到反面IEnumerator FlipCardToBack(){//翻转正面到90°,翻转反面到0°cardFront.gameObject.SetActive(true);cardBack.gameObject.SetActive(false);cardBack.rotation = Quaternion.identity;while(cardFront.rotation.eulerAngles.y<90){cardFront.rotation *= Quaternion.Euler(0, Time.deltaTime * 90f * (1f / flipDuaration), 0);if(cardFront.rotation.eulerAngles.y>90){cardFront.rotation = Quaternion.Euler(0, 90, 0);break;}yield return new WaitForFixedUpdate();}cardFront.gameObject.SetActive(false);cardBack.gameObject.SetActive(true);while(cardBack.rotation.eulerAngles.y>0){cardBack.rotation *= Quaternion.Euler(0, -Time.deltaTime * 90f * (1f / flipDuaration), 0);if(cardBack.rotation.eulerAngles.y>90){cardBack.rotation = Quaternion.Euler(0, 0, 0);break;}yield return new WaitForFixedUpdate();}isInFront = false;}// Start is called before the first frame update//初始化cardFront,cardBackvoid Start(){cardFront = transform.Find("Image_front");cardBack = transform.Find("Image_back");}// Update is called once per framevoid Update(){}//得到卡牌的名字internal string getCradImageName(){return cardFront.GetComponent<Image>().sprite.name;}//两只卡牌匹配成功internal void MatchSuccess(){isOver = true;//卡牌匹配成功cardFront.gameObject.SetActive(false);cardBack.gameObject.SetActive(false);}//两张卡牌匹配失败internal void MatchFail(){StartCoroutine(FlipCardToBack());//将卡牌翻转为反面朝上}//设置卡牌的默认状态即初始状态internal void SetDefaultState(){isInFront = false;isOver = false;if (cardFront != null){cardFront.gameObject.SetActive(false);cardFront.rotation = Quaternion.identity;}if (cardBack != null){cardBack.gameObject.SetActive(true);cardBack.rotation = Quaternion.identity;}}
}