我的第一个unity项目是卡牌游戏,是看着慕课网以及用同学分享的项目资源跟着老师做的。慕课网课程的链接,希望老师允许我把课程的链接放在这里,宁静方致远 分享的项目,内含素材,希望他们可以同意把他们的链接放在这里。
游戏功能是判断两张翻转的卡牌是否相同,如果相同就消掉两张卡牌,不相同就把卡牌翻转到背面,游戏共有三个关卡,分别有23、24,2*5张卡牌。下面是游戏的界面
1.panel_start界面
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 update

    void Start()// 从MonoBehaviour继承的方法,start在MonoBehaviour的生命周期中只会被执行一次
    {
        //跳转到关卡一
        btnLevel1.onClick.AddListener(() =>
        {
            panelStart.gameObject.SetActive(false);//设置panelStart为不可用,不显示panelStart
            panelCard.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的父节点设置为null
            Destroy(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,cardBack
	void Start()
    {
		cardFront = transform.Find("Image_front");
		cardBack = transform.Find("Image_back");
	}

    // Update is called once per frame
    void 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;
		}
	}
}


Logo

苏州本地的技术开发者社区,在这里可以交流本地的好吃好玩的,可以交流技术,可以交流招聘等等,没啥限制。

更多推荐