Unity实现简单卡牌游戏框架

Unity卡牌游戏教程(一)简单框架

前言

项目的初衷是以项目形式串起unity各种零散知识,语言风格较详细(啰嗦)

需求

先来看需要实现什么东西

构建卡牌对象

简单的UI系统

UI和场景的简单交互

前期准备

打开unity,新建2D项目

左键点击Main Camera,在Inspector面板中点击Background修改背景颜色,同时将size修改为屏幕像素高度/100f/2),比如你的屏幕像素是1920x1080,那么size*就应该设置为5.4,这样屏幕上一个像素对应图像中一个像素(所谓Pixel-Perfect)

导入资源。在Project新建文件夹存放我们需要的资源,然后直接把文件(这个项目用到的全是图片)拖进Project面板里。我们新建的是2D项目,所以可以看到在Inspector面板里纹理类型是Sprite。需要注意的是Pixels Per Unit这一项,该数值为屏幕上的一个单元对应原图片的多少像素点,简单理解,该值越大,图片就显示得越小

(PS:导入图片时最好确保所有同一用途的图片大小以及Pixels Per Unit都保持一致,图片大小可以在点击图片后在Inspector面板右下角查看,如果大小不一建议在导入前用PhotoShop的裁剪工具处理,尤其是你素材是自己随便截下来的时候

构建卡牌对象

在Hierarchy窗口右键:2D Object->Sprite 新建一个空白物体,顺手在Inspector面板将他的Position改成(0,0,1)

(PS:由于Unity使用左手坐标系,xyz轴的正方向分别是右、上、前,离我们越远的物体Z值越大,因此个人习惯将摄像机的Z值设置为0,将其他物体Z值设置为1)

将图片拖拽到Sprite Renderer下Sprite处(下文简称为“槽”),完成“图片赋值”

但还没完!一张卡牌应该有卡背。在刚刚创建的对象下用同样的步骤新建一个子物体,将卡背图片拖拽,但这次将Position设置为(0,0,-0.1)此时的位置是与父物体的相对位置,为了保持神秘感,我们先将卡牌翻面,因此将Z值设为-0.1(理由见同上

是时候敲点代码了!在Project中Create一个脚本(希望你已经建好了一个用于存放脚本的文件夹),并将它拖给你的卡牌

//	BasicCard.cs
public class BasicCard : MonoBehaviour
{private GameObject Card_Back;public void OnMouseDown(){if (Card_Back.activeSelf)Card_Back.SetActive(false);elseCard_Back.SetActive(true);}void Start(){//  查找子物体,获得卡背Card_Back = transform.Find("Card_Back").gameObject;}
}

transform.Find()函数能让你获取该物体下的子物体,是父子物体通信的有效手段,我们接下来还会遇到物体与物体间通信的问题

重写了鼠标响应事件函数之后,我们现在可以点击卡牌使它翻面了…吗?好像还不行,因为只有带有碰撞器组件的游戏物体才能对鼠标做出反应。于是我们给BasicCard添加一个Box Collider2D(点Add Component)
现在我们有卡牌了!
还差最后一步。我们不希望每次创建卡牌都要重复一次上述过程,因此可以用刚刚创建好的BasicCard创建一个预置体

将BasicCard从Hierarchy视图拖到Project视图中,你会发现Hierarchy视图中的箱子图标变蓝了。此时删除原来的BasicCard,对预置体没有影响

简单的UI系统

Unity有两种GUI系统,一种是直接模式(immedia mode),在每帧显示地发出绘制命令,完全基于代码,它的核心是OnGUI()方法,会在渲染完场景中所有物体后调用。它可以实现一些很简单的东西(比如FPS游戏中的准心),但缺点显而易见(顺便一提,可能是我匆匆忙忙学艺不精,感觉QT的UI逻辑就是这种,十分折磨)

所以我们选择另一种保留模式(retained mode),只需一次定义后系统自动每帧绘制,无需重新定义;而且能在编译器里工作,方便直观(最常用)

右键->UI->Canvas,我们创建了一个画布对象,之后所有的UI图像和操作都附加在上面。它缩放的很大,但不用害怕,因为场景中1个单位相当于UI上1个像素。你会发现自动创建了个EventSystem,暂时不管它

(P.S:Canvas组件下的Render Mode选项有三个模式,默认Overlay会将UI渲染在摄像机顶部;Camera则在前者基础上加上可以进行透视效果的旋转;World Space则将Canvas对象放置场景中,可以用该选项提高UI的浸入感,比如光环中将武器剩余子弹数显示在武器上)

新建一个Text和Button对象(自动变成Canvas的子物体),自行设置图片字体大小之类的(按照设置sprite的方法直接拖就行),之后分别放在右上角和左上角。为了在游戏画面缩放时能自适应屏幕大小,我们分别设置好对应的锚点(相对位置)

用脚本控制卡牌生成

在整合UI和场景之前,我们需要一些准备工作。我们需要一个SenceController帮我们自动化生成卡牌,以代替目前在inspect面板手动添加的方式

新建一个空物体,再写一个脚本,命名为SceneController

//	SceneController.cs
public class SceneController : MonoBehaviour
{[SerializeField] private GameObject CardPrefab;[SerializeField] private int MaxCardsNumber = 8;    //  最大总卡牌数[SerializeField] private int colNum = 4;            //  每行最大卡牌数[SerializeField] private float offset = 2.5f;       //  相邻卡牌间隔//	卡牌初始位置private Vector3 firstPos = new Vector3(-2.5f, -2.5f, 0.0f);private int CardsNum = 0;						  //  记录卡牌数void createCards(){while (CardsNum < MaxCardsNumber){GameObject newCard = Instantiate(CardPrefab);//	控制卡牌按间隔分布if (CardsNum >= colNum)newCard.transform.position = new Vector3((CardsNum - colNum) * offset, offset * 2, 1) + firstPos;elsenewCard.transform.position = new Vector3(CardsNum * offset, 0, 1) + firstPos;++CardsNum;}}void Start(){createCards();}
}

SerializeField意为序列化,简单理解就是能在inspect面板里面修改脚本中的属性(注意当你序列化变量之后会以inspect上的值为准,此时你光修改代码是无法改变变量的值的,必须在inspect作出修改)当然,将变量属性设置为public能达到同样的效果,但这就破坏了所谓封装性。所以SerializeField是实用性和封装性之间的一个小小妥协

另一个新东西是Instantiate()方法。该方法返回一个原对象的克隆值,新对象的激活状态和原对象保持一致。我们用刚刚创建好的卡牌预置体来生成新卡牌

将预置体拖入“组件槽”中,运行游戏,如无意外你会看到八张整齐排列的卡牌:
感觉有哪里不对...
好像还不戳,但你逐一点开之后发现八张都是一样的(和预制体保持一致)。想生成不同的卡牌有两种办法:

一、将所有不同种类的卡牌制成预置体

二、不预设图片,生成卡牌对象时在动态“赋值”

这里我们选择第二种方法,原因主要是不同的卡牌只有图片一个属性是不一样的,可以通过SceneController动态生成一个数组来给不同的卡牌“赋值”;其次生成不同预置体后还要一个个拖到“槽”上“赋值”,十分麻烦(其实可以用GameObject数组来管理优化,不过现阶段没必要这么麻烦,如无必要勿增实体

(P.S:不妨设想什么情况下第一种方法是更好的选择)

首先确定需求。我们需要一个存放Sprite的数组用于“赋值”(总比GameObject数组好,不是吗);接着修改代码,让其能随机地生成卡牌(的封面)

我们首先将卡牌预置体中的Sprite属性重新设置为None;接着修改脚本

//	SceneController.cs
//	之前已存在且无需修改的部分用...表示
public class SceneController : MonoBehaviour
{				...[SerializeField] private Sprite[] Card_Front;
}				...

保存后看inspect面板,在size一行输入你想要的大小,再将将对应sprite拖到“槽”上赋值

还需要稍微修改生成卡牌的代码,产生0—3的随机数给图片赋值

//	SceneController.cs
void createCards()
{while (CardsNum < MaxCardsNumber){...if (CardsNum >= colNum)...	else...newCard.GetComponent<SpriteRenderer>().sprite = 									Card_Front[Random.Range(0,4)];...}
}

GetComponent<>()可以获取本游戏物体上的组件;Random.Range(int min,int max)则返回一个范围内的随机int(包括min但不包括max)

(P.S.:浮点数重载的Random.Range(float min,float max)的范围是包括min也包括max,使用时要注意)
现在的效果是这样的

消息系统

unity中父子物体间通信可以用transform.Find()解决,那不同物体间的通信呢?unity其实提供了自带的消息系统(还记得我们创建Canvas对象时自动创建的那个EventSystem吗)其核心方法是FindObjectOfType().SendMessage("methodName",参数),FindObjectOfType()可以返回第一个类型为 T 的已加载的激活对象的游戏物体;SendMessage()则调用此游戏对象中的每个 MonoBehaviour 上名为 methodName 的方法,同时可以传递参数;结合这两个方法可以实现不同物体间(单对单)通信。优点是简单,同时能保证通信必定成功(计网中的面向连接可靠通信)因为发出的消息没被接受会报错…

缺点是只能实现单对单通信,虽然有FindObjectsOfType()版本能返回一个符合条件的对象数组,但挺麻烦(要写很多个SendMessage());另外FindObjectOfType().的效率很低,不建议每帧都使用。

但还有另一种选择,观察者模式。有通信需求的游戏物体挂载一个监听器,消息发出者向全体监听者广播一条消息,监听者发现这条消息是针对自己的,就做出响应(如果不是,那么就丢弃不管)这样就轻松实现了一对多通信

为了教学目的,这个例子中我们用第一种方法(其实是因为简单

UI和场景的简单交互

有来有回才算是交互!我们需要让UI响应场景的变化,同时也能通过在UI层的操作改变场景

先看需求:我们希望Text能显示场上的卡牌数;按Button可以生成卡牌

先实现第一部分:场景->UI

在Canvas下新建一个脚本,命名为UIController

//	UIController.cs
public class UIController : MonoBehaviour
{ void showCardsNum(int num){//	应该不陌生了Text t = transform.Find("Text").GetComponent<Text>();t.text = "目前场上卡牌数:" + num.ToString();}
}

然后在SceneController(下面简称SCer)给UIController(简称UICer)发信息(谁取的名字啊,又臭又长.jpg

//	SceneController.cs
void createCards()
{while (CardsNum < MaxCardsNumber){...//  unity自带的消息系统FindObjectOfType<Canvas>().SendMessage("showCardsNum", CardsNum);}
}

再实现第二部分:UI->场景

button想发送消息十分简单,点开button的inspect面板,发现有一个On Click()条目,左上角的“槽”设置响应模式(编译器下或是游戏运行时),点右下角的小加号,将SceneController对象拖到右下角的“槽”;右上角的“槽”可以选择调用该游戏物体下的组件中的方法,这里我们选SceneController->SendMessage(string),接着在文本输入域内写你想调用的方法名,我们写createCards

(P.S:OnClick()是按钮组件暴露给外部的唯一事件,但我们也有对所有类型的游戏物体都适用的,响应各种不同交互方式的办法,答案是使用EventTrigger组件;添加组件后点Add New Event Type,选择你想响应的活动。接下来的操作就和上面一样了)
甚至贴心写出能响应的事件

打开SCer,将createCards()从Start()删去

Congratulations!现在能看到完整效果了

在这里插入图片描述

回顾一下我们实现了什么东西

构建卡牌对象:有不同封面和默认卡背,能自动生成的卡牌

简单的UI系统:按钮和文本框

UI和场景的简单交互:按钮生成卡牌;文本框显示卡牌数

这节涉及的前置知识较多,代码量和操作难度其实不大,但有了基本框架后,我们可以做一些比较cool的东西了

下期预告:
小游♂戏

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/58483.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

卡牌游戏算法原理、代码

1、原理 卡片游戏算法桌上有一叠牌&#xff0c;从第一张牌&#xff08;即位于顶面的牌&#xff09;开始从上往下依次编号为1~n。当至少还剩两张牌时进行一下操作&#xff1a;把第一张牌扔掉&#xff0c;然后把第二张牌放到整叠牌的最后&#xff0c;以此往复。输入卡牌数量n&am…

Unity制作卡牌游戏

我的第一个unity项目是卡牌游戏&#xff0c;是看着慕课网以及用同学分享的项目资源跟着老师做的。慕课网课程的链接&#xff0c;希望老师允许我把课程的链接放在这里&#xff0c;宁静方致远 分享的项目&#xff0c;内含素材&#xff0c;希望他们可以同意把他们的链接放在这里。…

百度搜索框搜索时显示或者隐藏历史搜索记录

1.首先进入百度首页 2.找到设置下的搜索设置&#xff0c;如下图所示 3.点击“搜索设置”会出现如下图的弹框&#xff0c;有搜索历史记录的设置&#xff0c;“显示”或者“不显示”。

在谷歌搜索框内不显示搜索记录

在谷歌搜索框内不显示搜索记录 在不删除谷歌历史浏览记录的前提下&#xff0c;不主动显示浏览记录。 操作步骤如上图所示&#xff0c;在数据与个性化中找到网络与应用活动记录&#xff0c;点进去&#xff0c;然后关闭 推荐用edge&#xff0c;可以轻松切换用户&#xff0c;管理…

google 输入栏不显示历史搜索记录方法

网上看了很多回答&#xff0c;试了都不起作用。这里记录一下我的方法&#xff0c;供大家参考。 第一步&#xff1a;在google输入栏输入&#xff1a;chrome://version 查看浏览器信息。其中有个个人资料路径 第二步&#xff1a;在文件夹中找到该路径&#xff0c;路径下有个His…

企业数据分析分四步走:描述、诊断、预测、指导

谈到数据,大家都喜欢拿大数据说事儿,精准营销、客户管理、企业洞察,但事实上,对于大部分中小型企业来说,把创立至今多少年来各个渠道积攒下来的数据统合到一起,也仅仅只是小数据而已。 这些数据,选用合适的工具,可以非常轻松的管好。不过,在选工具之前可以先问自己这…

什么是RLHF

什么是RLHF&#xff1f; **字面翻译&#xff1a;**RLHF (Reinforcement Learning from Human Feedback) &#xff0c;即以强化学习方式依据人类反馈优化语言模型。 强化学习从人类反馈&#xff08;RLHF&#xff09;是一种先进的AI系统训练方法&#xff0c;它将强化学习与人类…

ChatGPT,我跟不动了,你呢?

周末扒出来一个 10 多年前的老系统&#xff0c;搁现在绝对得老破旧一个&#xff0c;如果要升级改造&#xff0c;绝对不如重写速度快。打开编辑器&#xff0c;从 JSP 翻到 XML 配置文件&#xff0c;基本还算看得懂&#xff0c;不过还是太久远了&#xff0c;把玩起来难度比较大。…

chatgpt赋能python:用Python开发软件的步骤

用 Python 开发软件的步骤 Python 是一种广泛使用的编程语言&#xff0c;因其灵活性、易读性以及可扩展性而备受推崇。Python 也有许多应用场景。在本文中&#xff0c;我们将重点介绍使用 Python 开发软件的基本步骤&#xff0c;帮助初学者入门。 第一步&#xff1a;确定需求…

关于在手机端可以看到加入的百度网盘群,在pc段找不到的原因

1.应该有人和我遇到过一样的问题&#xff0c;在手机上加入的百度网盘群&#xff0c;一直都存在&#xff0c;而在电脑端的时候&#xff0c;有时候登陆会显示出群&#xff0c;有时候却找不到&#xff0c;遇到群友分享的学习资料下载下来却找不到路&#xff0c;你说气不气。为了让…

盖茨笔记:人工智能时代已经开始

来源&#xff1a;比尔盖茨 In my lifetime, I’ve seen two demonstrations of technology that struck me as revolutionary. 我平生见识过两次令我印象深刻、革命性的技术演示。 The first time was in 1980, when I was introduced to a graphical user interface—the fore…

人工智能时代已经开始

In my lifetime, I’ve seen two demonstrations of technology that struck me as revolutionary. 我平生见识过两次令我印象深刻、革命性的技术演示。 The first time was in 1980, when I was introduced to a graphical user interface—the forerunner of every modern op…

警惕GPT对个人电脑中文件的读取!!!

最近在使用chatgpt帮忙写代码时&#xff0c;出现了不可思议的一幕&#xff0c;有可能是是我的见识太浅薄。 由于和gpt对话时&#xff0c;用的多了以后&#xff0c;我的提问方式变得比较简洁&#xff0c;想不到GPT并没有给我代码&#xff0c;而是让我告诉他数据的路径和对应的列…

【工具】1744- Claude2:GPT4 强劲竞争对手来了,完全免费!

关注 “AI 工具派” 探索最新 AI 工具&#xff0c;发现 AI 带来的无限可能性&#xff01; 「近期热门」 AI Colors&#xff1a;轻松定制你的网页配色方案Albus&#xff1a;探索你的无限创意PMAI&#xff1a;优秀的产品经理 AI 帮手Forefront Chat&#xff1a;免费的 GPT-4 聊天…

照片生成漫画头像的软件,试试这个方法很好用

相信很多朋友都喜欢看动漫或者漫画吧&#xff0c;有时候遇到喜欢的人物有没有过把自己代入漫画里呢&#xff1f;我倒是有过&#xff0c;想象自己在漫画里是什么样子&#xff0c;但是如果想把照片变成漫画效果&#xff0c;找画师的话既费时又费钱。其实只要使用把照片生成漫画的…

分享一个把照片变成漫画的方法

不知道小伙伴们平时喜欢看卡通动漫吗&#xff1f;在一些二次元圈子里的小伙伴肯定都会有一些自己非常喜欢的画风和角色&#xff0c;我也有将喜欢的动漫人物的卡通图片用作头像&#xff0c;这样的话不会像真人头像那样会暴露隐私说不定还会发现一些同样爱好的小伙伴。但是还有一…

不知道图片变漫画怎么弄?来跟我学这几个简单的方法

我想问经常换头像的朋友一个问题&#xff0c;你们有用过自己的漫画脸当头像吗&#xff1f;如果我们把自己的头像做成漫画脸不仅不容易和别人撞头像而且更加能贴合我们自身的形象。那你们知道漫画脸怎么拍吗&#xff1f;今天我来给大家分享几个漫画脸制作都的方法。有兴趣的朋友…

照片变漫画怎么做?分享这几个照片变漫画的技巧给你

大家在网上是否有看到过一些绘画博主给别人画漫画图呢&#xff1f;这些图片中的人物形象与现实中的非常相像&#xff0c;而且看起来真的很像漫画中的人物一般&#xff0c;画出来对于一些没有绘画功底的人来说是比较困难的。那么我们又该如何得到我们在漫画中的图片呢&#xff1…

瞧瞧我们对漫画图片都做了什么!?

动手点关注 干货不迷路 概述 漫画是一种以图片为主体的内容形式&#xff0c;我们在实现漫画业务需求时&#xff0c;不可避免地会和图片打交道。本文总结了番茄小说业务场景中两个和图片相关的技术需求&#xff0c;在此抛出遇到的问题与团队的解决思路&#xff0c;望能抛砖引玉。…

数据结构---绪论

个人复习&#xff0c;欢迎指正&#xff01; 参考教材《数据结构教程》&#xff08;第五版&#xff09; 李春葆主编 清华大学出版社 1.1.1数据结构的定义 数据&#xff1a;描述客观事物的数和字符的集合&#xff1b; 数据元素&#xff1a;数据的基本单位&#xff1b…