【Unity3D游戏教程】记忆翻牌游戏

游戏引擎:Unity3D - 5.4.1

脚本编辑器:Visual Studio 2012

脚本语言:C#

【随便聊聊】

一直在CSDN做博文,突然发现简书这个非常好的平台,会把一些好的文章往这边搬搬。初来乍到还请大家多多指教喲~

【这里是正文】

一. 策划部分

游戏说明:

游戏两两翻牌,一样则记成功一对;不一样,则两张都恢复初始状态。当所有卡牌配对成功后即获得胜利。

游戏流程:

1、玩家点击开始游戏后进入游戏;
2、系统初始化一组卡牌显示到界面上;
3、系统计时;
4、玩家点击卡牌将卡牌翻开;
5、系统检测到当翻开的卡牌为两张时进行评判,若相同则增加计数并判断是否达到指定数量,若不同则两张卡牌均恢复为原始状态。

具体规则:

1、需在规定时间内完成游戏,否则系统提示游戏失败;
2、当有两张卡牌被翻开时,需等待2秒后才能对其他卡牌进行点击,在2秒内对其他卡牌的点击都是无效的。

基于以上的流程和规则,我们罗列出以下主要技术点(其中括号中均为简写):

1、搭建界面,卡牌池使用自动布局(Grid Layout Group和Content Size Fitter组件);
2、构建GameManager类;
3、构建Card类;
4、配置卡牌列表,随机排列卡牌(List<T>,Random.Range);
5、生成卡牌作为卡牌子物体(Local相关属性配置)
6、设置计时器(Time.deltaTime);
7、翻牌添加到数组(List<T>);
8、两张翻牌时,使玩家点击失效2秒(协同程序)。
9、配对成功不可再点击(Button interactable=false)

二. 开发部分

搭建界面:

我们创建工程,在场景中创建Panel,为其添加Gird Layout Group组件和Content Size Fitter组件。Gird Layout Group组件可对Panel的子元素进行排列布局,我们这里要用到的是Padding下面的四个属性(元素距离四边的像素距离)、Cell Size(元素的宽高像素)、Spacing(元素间横向与纵向间隔)以及Constraint(行列约束)。Content Size Fitter组件用来约束当前Panel的尺寸。这两个组件一般搭配使用。

我们进行如图设置。

这里写图片描述

  我们指定了子元素的大小和间距,以及与父元素边界距离,并指定以列为基准一共四列;最后设置横纵的尺寸都按照最小来自适应,就会按照我们之前的设置来自动布置当前Panel的大小。此时可以做个测试,在Panel下疯狂创建Image(也不用特别疯狂,创建一个Image后选中它ctrl+d就行。。。)创建十二个Image,发现会自动布局成我们想要的效果。


这里写图片描述

  大家也可以根据自己的审美风格来调整比例和大小。开始界面和结束界面这些在此处不再赘述,非常简单,大家可以尝试自己制作。

设计Card类,制作卡牌Prefab:

1、属性设计

(1)卡牌需有唯一标识id,利用同种卡牌id相同,不同卡牌id不同的原理对卡牌配对是否成功做出判断;
(2)卡牌会有三种显示情况:未被翻开状态,被翻开状态,配对成功状态。这三种状态用不同的图片来区分,均为Sprite类型;
(3)基于(2)中的不同状态图片,都需要指定给卡牌的Image组件才能显示,所以我们需要声明Image类型的字段来获取Image组件对象;
(4)根据游戏机制,卡牌存在不能点击的时刻,此时我们需要获取到卡牌Button组件中的属性来设置卡牌能否被点击。

2、方法设计

(1)卡牌需要有初始化方法,用来初始化Card类的各项属性。由于初始化需要传参和外部调用,这里我们自己写一个带四个属性参数的InitCard方法;
(2)牌被点击,设置卡牌不可被点击,显示图片为frontImg,需要实现SetFanPai方法,即设置翻拍(这里由于亦泽的词汇量达到了顶峰,所以用拼音了。。。);
(3)两张牌相同,设置卡牌显示图片为successImg,这里需要实现SetSuccess,即设置匹配成功;
(4)两张牌不同,则设置卡牌显示图片为backImg,并设置卡牌可以点击,需要实现SetRecover方法。

新建Card类,添加如下代码:

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class Card : MonoBehaviour {

    public int ID
    {
        get
        {
            return id;
        }
    }
    private int id;

    private Sprite frontImg;
    private Sprite backImg;
    private Sprite successImg;

    private Image showImg;
    public Button cardBtn;

    public void InitCard(int Id, Sprite FrontImg,Sprite BackImg,Sprite SuccessImg)
    {
        this.id = Id;
        this.frontImg = FrontImg;
        this.backImg = BackImg;
        this.successImg = SuccessImg;

        showImg = GetComponent<Image>();
        showImg.sprite = this.backImg;

        cardBtn = GetComponent<Button>();
    }

    public void SetFanPai()
    {
        showImg.sprite = frontImg;
        cardBtn.interactable = false;
    }

    public void SetSuccess()
    {
        showImg.sprite = successImg;
    }

    public void SetRecover()
    {
        showImg.sprite = backImg;
        cardBtn.interactable = true;
    }
}

我们在刚才疯狂创建的Image对象中,随便选一个,改名为CardPre,为其添加Button组件和Card脚本(其对象默认含有Image组件),将其Transition属性设置为None。这些做好后将其制作为Prefab,并将Panel下的子物体全部删除。

设计GameManger类:

1、属性设计

(1)整型常量字段winCardCouples,表示游戏胜利需要的卡牌对数,按照我们上述设计,这里制定为6;
(2)整型变量curCardCouples,用来记录当前已经匹配完成的卡牌对数;
(3)bool型变量canPlayerClick,用来控制宏观点击,即根据策划,当两张牌均翻开时等待两秒才会出现结果,此时所有卡牌均不可以点击,所以这里设置此变量;
(4)Sprite类型的backImg和successImg两属性用来存放背面图和成功图;
(5)Sprite类型的数组FrontSprites,用来存放6种不同的卡牌图片;
(6)GameObject类型的卡牌实例列表集合CardObjs,此数组本质作用是过度,即用6张图片来生成12张卡牌对象,将每个对象初始化好后存入当前数组,然后随机取出,做矫正设置后再指定给Panel作为子对象;
(7)Card类型的列表集合FaceCards,用来存放被翻开的卡牌;
(8)GameObject类型的CardPre;
(9)Transfrom类型的CardsView,用来存放Panel;

2、方法设计

(1)在Start方法中设置初始化卡牌属性,并将其乱序添加到Panel下;
(2)添加CardOnClick方法,作为卡牌被点击的监听事件;
(3)添加JugdeTwoCards方法,此方法为协同程序的方法,用于判断两张牌是否一致。

新建GameManager脚本,添加如下代码:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Card_GameManager : MonoBehaviour {

    private const int winCardCouples = 6;
    private int curCardCouples = 0;
    private bool canPlayerClick = true;

    public Sprite BackSprite;
    public Sprite SuccessSprite;
    public Sprite[] FrontSprites;

    public GameObject CardPre;
    public Transform CardsView;
    private List<GameObject> CardObjs;
    private List<Card> FaceCards;

    // Use this for initialization
    void Start () {

        CardObjs = new List<GameObject>();
        FaceCards = new List<Card>();

        //将12张卡牌制作完成后添加到CardObjs数组
        for (int i = 0; i < 6; i++)
        {
            Sprite FrontSprite = FrontSprites[i];
            for (int j = 0; j < 2; j++)
            {
                //实例化对象
                GameObject go = (GameObject)Instantiate(CardPre);
                //获取Card组件进行初始化,点击事件由游戏管理器统一处理
                //所以卡牌的点击事件的监听在管理器指定
                Card card = go.GetComponent<Card>();
                card.InitCard(i, FrontSprite, BackSprite, SuccessSprite);
                card.cardBtn.onClick.AddListener(() => CardOnClick(card));

                CardObjs.Add(go);
            }
        }

        while (CardObjs.Count > 0)
        {
            //取随机数,左闭右开区间
            int ran = Random.Range(0, CardObjs.Count);
            GameObject go = CardObjs[ran];
            //将对象指定给Panel作为子物体,这样就会被我们的组件自动布局
            go.transform.parent = CardsView;
            //local就表示相对于父物体的相对坐标系,此处做校正处理
            //有兴趣的同学可以把下面两句代码注释掉看看效果
            go.transform.localPosition = Vector3.zero;
            go.transform.localScale = Vector3.one;
            //从CardObjs列表中移除该索引指向对象,列表对象数量减少1个
            CardObjs.RemoveAt(ran);
        }
    }


    private void CardOnClick(Card card)
    {
        if (canPlayerClick)
        {
            //先判断是否可以点击,可点击则直接翻牌
            card.SetFanPai();
            //添加到比对数组中
            FaceCards.Add(card);
            //如果有两张牌了,则不可再点击,进入协同程序
            if (FaceCards.Count == 2)
            {
                canPlayerClick = false;
                StartCoroutine(JugdeTwoCards());
            }
        }
    }

    IEnumerator JugdeTwoCards()
    {
        //获取到两张卡牌对象
        Card card1 = FaceCards[0];
        Card card2 = FaceCards[1];
        //对ID进行比对
        if (card1.ID == card2.ID)
        {
            Debug.Log("Success......");
            //此时会在此处等待0.8秒后再执行下一条语句
            //协程不影响主程序的进行,这里可以做个小实验
            //将下面的0.8改成8秒,在Update中打印Time.time会发现不会有停顿的时候
            yield return new WaitForSeconds(0.8f);
            card1.SetSuccess();
            card2.SetSuccess();
            curCardCouples++;
            if (curCardCouples == winCardCouples)
            {
                //此处可以弹出游戏成功等字样的UI,同学们可以自由设定
                Debug.Log("Win!");
            }
        }
        else
        {
            Debug.Log("Failure......");
            //配对失败等待的时间要更长,因为要让玩家记忆更深刻
            yield return new WaitForSeconds(1.5f);
            card1.SetRecover();
            card2.SetRecover();
        }

        FaceCards = new List<Card>();
        canPlayerClick = true;
    }
}

新建空物体作为GameManager的对象,指定GameManager脚本,在属性面板设置其各项属性,如图所示。


这里写图片描述

这里亦泽随便用了一些图放上去,运行游戏,效果如下若干图所示。做的不是很美观,有美术基础的读者可以将其更好的完善。


这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

本文源码GitHub地址:

https://github.com/Eazey/Unity3D_GameDemo/tree/master/RememberCard

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,914评论 25 707
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,381评论 0 17
  • 是啊,我们是很有可能单身一辈子啊,但是当你变得足够美好,你的局部最优也会越来越美好。那还有什么可担心的呢?我的爱情...
    深夜告白阅读 432评论 3 6
  • Gradle 是以 Groovy 语言为基础,面向Java应用为主。基于DSL(领域特定语言)语法的自动化构建工具...
    TsuiJin阅读 344评论 0 1
  • 此时无声胜有声 接了一个心里没底的活,内心有点虚。 没底气,没吃过牛皮,没看过草稿, 最坏的就是浪费点时间,招来不...
    巨石传说阅读 171评论 0 0