案例学习项目报告书
一.项目设计方案
二.实施过程分析
三.项目的改进
四.项目设计成果
五.提升及展望
一.项目设计方案
- 本次学习的案例是一款轻松休闲游戏,名字叫做“跳一跳”。
- 这个游戏是一个小人从台子上跳到另一个台子上,根据按下屏幕的时间可以控制小人跳跃的力度和远近,如果成功跳跃到台子上,则继续朝下一个台子进行跳跃,如果没有跳到台子上,则停止游戏,同时通过上传分数可以看到排名前十的成绩。
- 主要是通过美术资源的设置,特效的设置,逻辑功能这三个方面的实现。
二.实施过程分析
根据“跳一跳”游戏的基本功能需求,相关实施过程如下:
- 工程,角色及场景设置
- 角色跳跃
- 台子自动生成
- 相机跟随
- 死亡判定及重新开始游戏
- 分数统计
- 角色蓄力的粒子效果
- 台子蓄力效果
1. 工程,角色及场景设置
- 创建舞台
- 在Hierarchy层级面板里创建一个立方体cube,按F2快捷键重命名为“Stage”作为小人进行跳跃的舞台。适当改其参数。
- 创建地面
- 新建平面plane,重命名为“Ground”作为小人落下地面的承接物,
- 为了和台子有所区分,在Assets资产里建一个文件夹,取名叫“Materials”材质 用于放置材质球,建好材质球后拖到Ground地面上,修改参数,使其颜色有所区分。
- 会发现一个小问题,在修改完台子的相关参数后,发现台子陷入到地面里了,这是因为“cube”的中心轴在它的正中心,对Y轴进行调整,改为0.25。
- Alt+鼠标左键可以使场景中的物体进行旋转,便于观察。
- 创建小人
- 新建一个游戏对象空物体,快捷键“ctrl+shift+N”,命名为“Player”同样的道理,轴心在物体的正中心,可以调整Y轴,使其正好落在台子上,在其作为父对象创建小人的头和身子,适当调整参数。
- 保存
- 基本的场景建完后对场景进行保存,在“Assets”里新建“_Scenes”文件夹,用于存放场景,并进行保存,加下划线是为了让其排在第一个方便查找。
2. 角色跳跃
-
添加刚体组件
- 给“player”添加刚体组件“rigidbody”
-
新建脚本
- 在“Assets”里新建“scripts”文件夹,目的是存放脚本,并新建“c# ”的脚本,
- 脚本的名字在代码里的类名需要一致才能流畅地进行代码运行。
-
定义刚体组件
在类里面定义一下刚体组件private Rigidbody _rigidbody;
-
获取刚体组件
在Start 方法里获取这个组件_rigidbody=GetComponent<Rigidbody>();
-
思考
跳跃的距离根据按下时长决定的,那么需要检测按下鼠标的动作,在update方法里进行检测:if (Input.GetMouseButtonDown(0)){ }//鼠标按下时返回值 if (Input.GetMouseButtonUp(0)){ }//鼠标抬起时返回值 if (Input.GetMouseButton(0)){ }//鼠标整个按下和抬起的返回值,可以多次返回
-
标记开始时间
在按下鼠标时标记一个开始时间,首先需要定义一个私有的成员的浮点型变量private float _startTime;
然后在按下鼠标的函数里
if (Input.GetMouseButtonDown(0)){ }
对这个变量进行赋值
_startTime = Time.time;
-
计算按下时长
在鼠标抬起时,计算总共按下鼠标的时长,在鼠标抬起的时候,利用抬起的时间减去按下时的时间得到一个流逝的时间var elapse = Time.time - _startTime;
-
跳跃的方法
定义一个跳跃的方法/函数void OnJump(float elapse){ }
-
括号里是参数,在鼠标抬起(button up)时调用这个函数,
-
在这个函数里,通过对刚体施加一个向上的力和一个向前的力,并且向前的力受到消逝时间和一个可以调整的参数的影响,以此达到通过控制按下时长来决定小人的跳跃距离。
-
这个函数所需要用到的可调整参数需要进行定义
public float Factor;
Public类型可以在代码外更改这个值。
在OnJump函数内调用以下代码_rigidbody.AddForce(new Vector3(0, 5f, 0) + (_direction) * elapse * Factor,ForceMode.Impulse);
“AddForce”的参数,第一个是作用的力 第二个默认是持续性的力
ForceMode.Impulse指的是给物体添加一个瞬间的力并使用其质量,它是AddForce的四个重载之一 -
-
调整重心
-
测试后发现小人重心不稳,是因为小人身体是胶囊的“Collider” 更改为“box collider” 特别注意的是 此时小人和台子的接触点为四个
-
根据物理学,小人会有重心,此时小人的重心偏高,需要用代码更改小人的重心,回到脚本进行更改
_rigidbody.centerOfMass = new Vector3(0, 0, 0);
-
-
调整摄像机
- 因为发现游戏视角里的场景并不利于玩家进行游戏
- 有一个技巧是,在“scene场景”里调好恰当的视角好按“ctrl+shif+F”。
- 然后在gameobject主导航里点击align with view 这样将游戏场景里的摄像机调整为和场景里一样的视角。
3.台子自动生成
-
基准盒子
为了能让游戏持续进行下去,我们需要自动生成台子,我们需要一个基准盒子,然后需要按照随机距离和方向进行生成。 -
定义相关参数
定义一个盒子随机最远的距离的参数public float MaxDistance = 5;
定义一个基准盒子物体
public GameObject Stage;
定义一个生成盒子的函数
void SpawnStage(){ }
-
生成台子
那么生成的新的盒子就可以通生成的方法进行var stage = Instantiate(Stage);
但生成的新的盒子的位置如何确定呢?我们需要定义一个当前台子以便调取当前台子物体的位置
privateGameObject_currentStage;”
在函数里写下
stage.transform.position = _currentStage.transform.position + _direction * Random.Range(1.1f, MaxDistance);
在start方法里写下
_currentStage = Stage; SpawnStage();
将基准台子赋值到当天前台子,并调用生成台子的函数
-
碰撞检测方法
但此时无法形成随着小人的跳跃动态生成的效果,这是因为没有检测到小人的碰撞情况。当检测到小人跳到盒子上后,再次调用生成盒子的函数。用到刚体的一个碰撞检测方法
void OnCollisionEnter(Collision collision){ }
括号里的参数是指碰撞后的物体,此检测的原理是当物体的collider 或rigid body 碰到了另一个物体时进入开始调用
-
区分碰撞
小人碰撞的事件包含两个子事件,包含碰撞到盒子和里面这两个情况,怎么区分呢?通过collision.gameObject.name;
来区分。
需要用到条件语句,使小人只有在碰到当前的盒子,且当前的盒子不等于当前承接小人的盒子时,才会生成新的盒子,这样避免了小人碰到其他物体也生产,和小人在承接它的盒子上连续跳跃也生成的两个bug。具体通过条件语句进行判断。
4.相机跟随
-
目的
实现小人跳到下一个台子后进行移动,而不是在跳的过程中进行移动 -
相对位置
需要获取相机相对小人的向量,也就是相对位置,当小人进行移动后,相机进行相对移动,相机移动到于之前小人相同视角的位置,也就是每次相机的移动都保证时以一个相对位置进行移动的。private Vector3 _cameraRelativePosition;
设定一个相机的相对位置(向量)
-
获取相机的相对位置
怎么获取呢,在start方法里进行获取_cameraRelativePosition = Camera.main.transform.position - transform.position;
就是相机的位置减去小人的位置
-
相机移动
定义一个新的函数,并在小人成功跳到新的盒子后进行调用void MoveCamera(){ };
我们采用插件的形式让相机平滑地进行移动。当前相机位置加上一个相对位置就是相机移动后地位置
Camera.main.transform.DOMove(transform.position + _cameraRelativePosition, 1);
第一个参数是产生地移动,第二个参数是持续的时间,别忘了头命名进行引用
using DG.Tweening;
这个插件在官方软件里进行免费下载导入
-
相机更改
将透视改为正交,就不会产生近大远小了
5.死亡判定及重新开始游戏
- 结束游戏进程条件
-
当检测到小人落到地面或者小人不成功地跳跃,比如碰到了边界,或者小人自身不是直立状态时都会视为小人死亡,结束当前游戏进程。
-
这里为了让游戏更加完整,会定义一个新的游戏结束地方法函数,在符合小人死亡的情况下调用这个函数
private void OnGameOver(){ };
-
-
结构图及代码
具体在这个方法里如何进行死亡判定呢,如下图所示:
结构图手稿.png
-
特别注意的是,问号那里是指只要小人接触地面 仍会存在游戏失败的情况
-
在条件语句中,需要定义一个contacts的数组,用来代表碰撞的接触点,如果接触点是一个,用来判断是不是落在了台子的顶面,这里用到normal法线,因为及时接触点是一个,但落在了棱角上,法线方向就不是正上方
-
总之,只有判断是否接触点,且人物朝上才算符合加分的条件,否则游戏结束
Length可以获取这个数组长度if (collision.gameObject.name == "Ground") { OnGameOver(); } else { if (_currentStage != collision.gameObject)//条件 当前的小人所在的盒子不是碰撞后的游戏对象 { var contacts = collision.contacts;//var定义数组变量contact 将被碰撞后的物体的接触点集(集合)赋值给这个数组变量 //check if player's feet on the stage if (contacts.Length == 1 && contacts[0].normal == Vector3.up)//数组的长度就是这个接触集合是否为1(是否落到台子上),数组的法线是否是向上的(小人直立状态) { _currentStage = collision.gameObject;//将当前的游戏对象赋值给当前的盒子 AddScore(contacts); RandomDirection(); SpawnStage(); MoveCamera(); _enableInput = true;//可以继续进行点击鼠标 } else // 虽然小人跳出去了,但可能由于跳偏了,或者小人的身子不是直立状态 { OnGameOver(); } } else //still on the same box 仍在当前的盒子上跳 { var contacts = collision.contacts; //check if player's feet on the stage if (contacts.Length == 1 && contacts[0].normal == Vector3.up) { _enableInput = true;//还可以继续进行点击,也就是说允许在原地跳的 } else // body just collides with this box { OnGameOver(); } } }
- 场景变暗bug
- 这里进行检测时会发现游戏在跳转到另一个场景后会变暗,这是因为lighting下的setting中需要将 auto generate给关掉,让环境不自动生成一个光线资源 这样可以提高开发效率。
6.分数统计
-
UI功能设置
- 这里需要用到“unity”中的UI功能,在“hierarchy”面板创建“Text” 用来显示分数,将场景编辑改为2D模式,方便对UI进行设置。
- 在“inspector” 面板里对“recttransform” 进行设置“anckors”(相当于将一个物体挂在固定的方位,当移动游戏场景时,固定在某个方位不变) 同时按住“alt” 会使ui物体也放置在相应的位置
-
编写脚本
定义一个私有成员的量,相当于申请空间public Text TotalScoreText;
别忘了命名空间
using UnityEngine.UI;
否则会报错。记录分数需要一个变量进行记录
private int _score;
为了合理的管理并编辑积分系统,同时为了能达到如果准确度高的分数可以成倍增加,可以定义一个方法 函数
private void AddScore(ContactPoint[] contacts){ }”
-
定义一个变量“hitPoint”,把“ ContactPoint[] contacts”里的数组的“point”赋值
给“hitPoint”这个变量(point位置)。 -
使hitPoint的Y轴赋值0把三维的位置转化为二位的,同理, 把现在的台子的位置赋值给变量 stagePos , 让这个变量的Y轴也赋值成0
-
最后 定义一个变量 precision ,获取两点之间的距离 (hipPoint,stagePos)是小人碰撞的位置点到台子中心位置的距离
-
如果precision的值小于0.1 则进行两倍。
_score += _lastReward;
分数加等于一个值
TotalScoreText.text = _score.ToString();
将整型的值转为字符串,并在text里进行显示
-
7.角色蓄力的粒子效果
-
添加粒子系统
- 给游戏对象添加一个effect 里的particle system ,对这个粒子系统进行参数修改,粒子发生的起始形状做一下更改,
- 将初始的“cube”改为“hemisphere”半球的形状,并进行旋转。
- radius也就是半径也适当做一些更改,初始的速度改为负值,这样可以让 它可以往内部发射。
- Start size 开始的尺寸,范围。Start lifetime开始时间的时长。颜色调整。
-
显示效果
这个效果应该在什么时候才会显示出来呢?当按下鼠标进行蓄力时才会触发这个效果。这个需要在代码里进行实现。定义一个公有的游戏对象public GameObject Particle;
然后在鼠标按下时让它的状态显示为真“ Particle.SetActive(true);”在抬起鼠标后状态关闭
Particle.SetActive(false);
-
去掉钩选
初始的粒子效果状态设为“false”把钩选项去掉。
8.角色蓄力效果
-
分析
为了产生使小人在点击鼠标后产生蓄力的效果,其实是对小人的身体进行延Y轴的缩放,并使小人的头部进行向下移动,这里用代码实现比较好,因为是需要根据按下鼠标的时长来确定蓄力效果的。 -
定义变量
获取小人的头和身体,需要定义两个共有的变量public Transform Head;”“public Transform Body;
在botton的方法里进行使用这两个变量,区分按下 抬起 每次按鼠标这三个不同的方法。这里每一次按下鼠标都会产生蓄力所以在“Input.GetMouseButton(0)”里。
-
调整缩放
首先对身体进行改变Body.transform.localScale += new Vector3(1, -1, 1) * 0.05f * Time.deltaTime;
获取身体的的形状属性进行加一个三维向量,x,z是正的,y是负的,乘以一个0.05的速率再乘上每秒刷新的时间
然后对头进行改变Head.transform.localPosition += new Vector3(0, -1, 0) * 0.1f * Time.deltaTime;
方法同前一个身体,只不过调取的是头的位置。
-
还原
蓄力结束后并没有复原,这时候就需要在鼠标抬起的函数里进行复原。以动画的形式进行复原,这里用到一个插件。首先,需要对小人的身体的“scale”和头的“position” 的原始参数有个了解。Body.transform.DOScale(0.1f, 0.2f);
第一个参数也可以是一个三维向量,这里的一个值表示三个值都是一样的。后面的参数是动画播放的时间。
Head.transform.DOLocalMoveY(0.29f, 0.2f);
同理头部进行还原。这里的DOLocalMoveY指对Y轴进行移动。
4.台子蓄力效果
-
原理
同小人的缩放,也需要用到插件,我们考虑到对台子的Y轴进行缩放,由于轴中心在台子的中间,所以需要对台子进行一个移动,以免出现穿帮。也是在按下鼠标时进行调用。其次要考虑的是对那个盒子进行缩放,前面以及定义了一个当前的盒子,可以直接用。 -
代码
_currentStage.transform.localScale += new Vector3(0, -1, 0) * 0.15f * Time.deltaTime;”
进行Y轴缩放
_currentStage.transform.localPosition += new Vector3(0, -1, 0) * 0.15f * Time.deltaTime;”
进行位置的移动
-
还原
同样需要进行还原_currentStage.transform.DOLocalMoveY(-0.25f, 0.2f); _currentStage.transform.DOScaleY(0.5f, 0.2f);”
三.项目的改进
- 台子随机生成的大小和颜色变换
- 台子生成的随机方向功能,
- 加分UI的的实时动态更新的漂浮效果
- 成倍加分的功能
- 联网排行榜功能的实现
- 随机生成不同形状的台子
- 小人翻转跳跃
- 音频
1.台子随机生成的大小和颜色变换
-
大小随机变换
改变台子的大小其实就是对x,z值进行改变,y不变。
定义一个随机值var randomScale = Random.Range(0.5f, 1);
它的范围是0.5-1,这样,这个随机值也就可以放在一个三维向量的x,z,上了。
stage.transform.localScale = new Vector3(randomScale, 0.5f, randomScale);
这里的localScale是相对于父级物体变换的缩放,定义一个“randomScale”
是为了使盒子长和宽一致,以免出现细长的情况。 -
颜色随机变换
为了增加视觉效果,让台子的颜色随机变换,如何实现呢?我们需要获取台子的“renderer”的组件,并对“materials”里的“color”进行修改。stage.GetComponent<Renderer>().material.color = new Color(Random.Range(0f, 1), Random.Range(0f, 1), Random.Range(0f, 1));
这里的Random.Range(0f, 1)里如果没有f,就会默认是整型,而且后面的一个数不会取值比如(0,1)其实只取了0这一个数,加上f后代表0-1之间的浮点数,不包含1 。
2.台子生成的随机方向功能
-
定义初始方向
首先定义一个向量确定初始方向Vector3 _direction = new Vector3(1, 0, 0);
这是一个朝x正方向的值。将生成盒子里的函数的普通向量改为这个定义的“_direction”通过变换这个“_direction”的x,z的值就可使盒子进行不同方向的生成。怎么去变换这个x,z的值呢?
-
定义方法
定义一个随机方向的方法
void RandomDirection(){ };
-
在这个里面进行判断
这里主要通过首先定义一个种子,给它一个随机范围(0,2)这里其实只有0,1 这两个值,通过条件语句进行判断,从而实现方向的随机进行实现。var seed = Random.Range(0, 2); if (seed == 0) {_direction = new Vector3(1, 0, 0);} else {_direction = new Vector3(0, 0, 1);}”
也可以用三元判断
_direction = seed == 0 ? newVector3(1,0,0):newVector3(0,0,1);”
-
小人的位置变换
-
台子的方向改变后,小人进行跳跃的方向也需要改变,
-
这时就需要在这个随机方法函数里,让小人的位置进行变换,
-
为了使旋转后的小人的朝向始终是一致的,这里需要用到transform.right功能,
-
可以想象这里的right就像是小人的右手,让小人始终朝right (朝红色的也就是x轴进行旋转,以保证朝向)把_direction赋值给这个transform.right
transform.right = _direction;
-
3.加分UI的的实时动态更新的漂浮效果
-
UI显示
为了使加分显示。在canvas里新建一个text,为了和前面的总分相区分,改一下名称,分别是“TotalScore”和“Score”,然后调整Score的位置,在小人的周边位置。 -
获取Text
在代码里获取这个新建的textpublic Text SingleScoreText;
-
定义
方法只有在效得分才会让text里有内容显示,新定义一个方法,并在碰撞检测里进行调取。private void ShowScoreAnimation(){ }
在这个函数里让实现分数的动态显示。
-
获取实时位置
- 为了能达到实时飘分,并不受到摄像机的移动而改变飘分的位置便宜,我们需要得到小人的实时位置,
- 然后再通过打开和关闭text的显示状态,并将世界坐标系转到屏幕坐标系,然后用domove的动画方法让分数的Y轴方向加一个值,让它进行移动,
- 动画时长可以设定为1秒。
-
定义变量
具体代码里,首先定义一个成员变量,用来控制是否播放动画,_isUpdateScoreAnimation = true;
前面别忘了定义一下bool值
private bool _isUpdateScoreAnimation;
bool默认是false
-
实时飘分
需要一个动画播放开始的时间,定义一个变量private float _scoreAnimationStartTime;”
并让播放的时间为当前的时间,
_scoreAnimationStartTime = Time.time;
把bool值设为真
_isUpdateScoreAnimation = true;”
-
分数实时更新
怎么让分数实时更新呢,并且让UI能获得小人的实时位置,以免因为相机的移动让UI产生相对运动,这里定义一个更新的方法void UpdateScoreAnimation(){ };”
在这个方法里我们实现动态更新以及飘分的相关效果。并获取到小人的实时位置
-
关闭飘分
首先,我们需要让飘分动画播放一定时间后关闭,这里用到条件语句if (Time.time - _scoreAnimationStartTime > 1) _isUpdateScoreAnimation = false;
-
获得实时位置
-
为了获得小人的实时位置,首先我们需要获取一个小人的当前屏幕所在的位置,
-
因为小人在三维空间里,我们需要把世界坐标系转化为屏幕坐标系
-
这里用到UI的工具箱功能(UI的transform组件都是rectTransform)
里面有这样一个工具进行转换,转换时还需要获取渲染时摄像机的位置,获取相机的组件,世界坐标系是小人的位置, -
转换好后 我们定义一个变量来接收转换好的屏幕坐标位置的返回值。
var playerScreenPos=RectTransformUtility.WorldToScreenPoint(Camera.main,transform.position);
-
接下来就可以把飘分的text的位置设置为这个二维坐标位置。
-
-
实现飘起效果
-
为了让分数实现飘起来的效果,我们用到了Vector2.Lerp的功能(已经转换到屏幕上了,所以是2),
-
初始位置到最终位置的一个线性移动(角色脚下位置到向Y轴偏移的一个向量位置)移动的时间就是当前时间减去动画开始播放的时间。
SingleScoreText.color = Color.Lerp(Color.black, new Color(0, 0, 0, 0), Time.time - _scoreAnimationStartTime);”
-
-
颜色渐变
让分数在颜色上有一个渐变,也用到Lerp的功能,从黑色到透明。SingleScoreText.color = Color.Lerp(Color.black, new Color(0, 0, 0, 0), Time.time - _scoreAnimationStartTime);
“new color”里的四个参数,前三个是RGB,最后一个是通道阿尔法,目的是为了产生透明效果。
-
关闭初始显示
在游戏编辑里把这个text关掉,可以运行检测一下。
4.成倍加分的功能
-
定义方法
-
首先需要定义一个新的加分方法
private void AddScore(ContactPoint[] contacts){ };
-
碰撞集ContactPoint[]用来看小人接触面的点集,并用到条件语句进行判断是否符合成倍加分的条件,
-
即如果小人的位置和台子的位置之间的距离值的误差很小,就在原来加分的基础上再加一个控制值,这里需要定义一个整型的变量来控制最终得分
private int _lastReward = 4;
初始设置为4,用来替代之前的score+=;
-
-
评估加分
-
在这个条件语句里怎么进行评估呢,首先需要保证接触数组里大于0,
-
然后获取这个接触点和当前台子的位置,
-
同时将这两个位置值的Y值设为0再来计算他们之间的距离,如果距离小于0.1,
则执行加分的语句,让_lastReward = 2;
-
同理需要将整型转换为字符串进行显示,并显示飘分动画。
if (contacts.Length > 0) { var hitPoint = contacts[0].point; hitPoint.y = 0; var stagePos = _currentStage.transform.position; stagePos.y = 0; var precision = Vector3.Distance(hitPoint, stagePos); if (precision < 0.1) _lastReward *= 2; else _lastReward = 1; _score += _lastReward; TotalScoreText.text = _score.ToString(); ShowScoreAnimation(); }
-
-
注意事项
别忘了,在udate方法里调用,if (_isUpdateScoreAnimation){ UpdateScoreAnimation(); }
5.联网排行榜功能的实现
-
前期准备
- 首先需要申请一个在线云服务器leancloud,
- 注册后可以免费使用一些功能,
- 创建新项目后会获得一个appid和appkey
- 下载leancloud unity sdk 并导入到unity里(不用导Engine.dll)
- 在leancloud的里面有相关文档,初始化,存储,等都有教程。
- 里面的代码可以直接用。
-
初始化
- 按照官网上的指南
- 首先进行初始化
- hierarchy面板里创建游戏空物体
- 命名为leancloud,把导入的资源包里core底下的代码拖到这个空物体上
- 并把id和key复制过来
-
UI设置
- 思考 我们需要什么功能。
- 首先需要把每个人的分数上传到服务端,然后每个客户端可以从服务端获取前十
名的数据。 - 分数已经有了,应该如何上传到服务器上呢?
- 查看相关文档 关于保存对象的知识
- 设置一个功能,当游戏结束后弹出一个文本框,让玩家输入一个自定义的名字,
并通过按“上传分数”的按钮进行保存分数和对应的玩家昵称,也就是上传到服务器上。 - 创建UI右键panel
- 设置游戏结束后出现的这个面板,转换到2D模式有利于编辑。1.
- 在这个panel下创建一个InputField用来让用户填写自己的名字。
- 然后同样的方法创建一个按钮,这个按钮的功能是为了提交分数,
并且有利于下一步的重新开始游戏。 - 适当调整它们的位置,并找到其下的text改变font size 。
- 在placeholder里设置一些参数,比如提示字符的大小,改变为“请输入昵称”
- 同理button里也需要改变text的大小和内容“上传分数”
-
在代码里实现
游戏结束后弹出这个panel,玩家填写昵称,并将分数和昵称上传到服务器上。上传完分数后出现排行榜的列表。
一步一步来,首先定义public GameObject SaveScorePanel; public InputField NameField;public Button SaveButton;
相当于初始征求空间后才能进行引擎。
然后在游戏结束时让面板显示SaveScorePanel.SetActive(true);”
然后需要为button绑定一个事件,在这里用代码的方法进行实现。
在start 方法里SaveButton.onClick.AddListener(OnClickSaveButton);
调取savebutton的链接功能,链接的是一个方法
void OnClickSaveButton(){ };
在这个方法里首先获取用户填写的昵称并给这个昵称定义一个变量
var nickname = NameField.text;
之后需要到leancloud里对象保存的方法,里面的代码可以拿过来使用,记得引入头文件
using LeanCloud;
保存对象的代码如下
AVObject gameScore = new AVObject("GameScore"); gameScore["score"] = _score; gameScore["playerName"] = "nickname"; gameScore.SaveAsync()”
-
显示分数排名
-
同前面的上传分数一样,在UI里需要建一个panel,
-
为了与前一个有所区分,重命名一下“RankPanel”
-
在它的基础上建一个“Scroll View”并在content下新建text,
-
在text里设置一个昵称和分数,用于模拟编辑,
-
给content添加一个组件“vertical layout group”
-
还需要一个重新开始的“button”用于重新进行游戏。
-
开始在代码里实现排名和重新开始的功能。
同理先定义变量public GameObject RankPanel;public GameObject RankItem;public Button RestartButton;
同理给这个Button进行一个时间连接
RestartButton.onClick.AddListener(()=> { SceneManager.LoadScene(0); });”
-
-
实现排名
-
如何实现排名呢?定义一个函数
void ShowRankPanel(){ };
-
要实现排名的功能,需要先获取数据,用到官方文档里的 查询模块的讲解,复制查询的相关代码
AVQuery<AVObject> query = new AVQuery<AVObject>("GameScore").OrderByDescending("score").Limit(10);
- 这里是用到查询的功能,对查找到的数据进行降序排列,然后对数据有一个10个的限制。
接着需要获得这个数据
query.FindAsync()
-
因为对这些数据是在子线程进行操作的,为了加快进程,我们要下载一个UNIRX的 插件,用其中的功能去回调结果,从而得到查找的结果。(在查询结束后就调用)
-
对整个子线程设为t,在这个子线程t里进行操作,获取t里的结果(Result)用一个变量results装起来.再定义一个字符串的列表用scores装起来。
-
在foreach里进行循环
foreach(var result in results){ }”
foreach是循环遍历results集合里面的元素直到遍历完results中的所有元素,遍历
results时,每次遍历都将results集合中的元素作为var类型赋给result)。 -
前面已经定义了一个字符串的列表了,我们需要对列表进行一个填充,
var score = result["playerName"] + ":" + result["score"];
- 让遍历后的结果(result)以“昵称:分数”的形式(score)。把这个形式放入前面定义过的字符串列表scores里。
-
-
转换到主线程
-
使用
MainThreadDispatcher.Send();
传送插件,在这个括号里用匿名函数进行遍历,将子线程的数据返回到主线程,
这样才能把游戏场景里的东西和代码串起来。 -
“=> { }”(匿名函数,下划线代表这个变量不会被引用)在这个函数下用
foreach(var sco in scores){ }
-
将scores里遍历的结果用sco装起来,为了让游戏场景里的ui物体显示出来,“{}”里面先写下
var item = Instantiate(RankItem);
-
将生成的text(RankItem)用变量item装起来
item.GetComponent<Text>().text = sco;
-
获取到变量item的text组件设置为sco;send现在不需要设为null;把分数排行的panel状态打开
RankPanel.SetActive(true);
-
-
串联起来
-
把相对应的ui拖进空槽,并在inspector 面板里关闭。
-
把它们串起来,在点击上传的方法( void OnClickSaveButton(){})里,
在保存数据的同时执行上面写下的的ShowRankPanel();
的函数。这里同样用匿名函数的方式
gameScore.SaveAsync().ContinueWith(_=>{ });
组后将这个插件在start方法里初始化一下
MainThreadDispatcher.Initialize();
-
-
完善
foreach(var sco in scores){ }
里的内容,为了使item和rankItem平级,用setParent 把item的parent 设置为rankItem的parent.
再回到游戏场景里,将content下的text禁用掉,与此同时,代码里需要将它打开item.SetActive(true);
6.随机生成不同形状的台子
-
预制体
在场景里的assets里建立prefab文件夹,并将新建的两个形状作为预制体,相当于磨具放到预制体的文件夹里 -
代码实现
为了得到一个盒子仓库,可以放上各种盒子的prefab,用于动态生成。定义一个数组。public GameObject[] BoxTemplates;
在
void SpawnStage(){};
的方法里进行生成,命名一个游戏对象
GameObject prefab;
然后进行随机生成数,从仓库里取出盒子进行生成
if (BoxTemplates.Length > 0) { //从盒子库中随机取盒子进行动态生成 prefab = BoxTemplates[Random.Range(0, BoxTemplates.Length)]; } else { prefab = Stage; }”
将生成的语句里原来的stage 改为
var stage = Instantiate(prefab);
7.小人翻转跳跃
-
插件使用
现在用到UniRX这个插件里的小人翻转跳跃的实现,为什么要用这个插件,因为以上都是在子线程,没有对象参与,所以需要把子线程转化到主线程。应用一下命名空间“using UniRx;”,这样才不会报红。 -
代码实现
这里用到doMoveRotate实现小人的旋转transform.DOLocalRotate(new Vector3(0, 0, -360), 0.6f, RotateMode.LocalAxisAdd);
前面已经定义了
public GameObject RankPanel; public GameObject RankItem;”现在可以开始使用插件了。
8.添加音频
- 基础操作
新建一个空物体,添加audio source 组件,导入音频文件 - 代码
对音频进行调用 - 建立链接
拖拽代码和音频四.项目学习成果
本次项目学习所获得的知识点有如下:
- 编辑器的基本用法及操作
- c sharp 编程的基础知识
- 刚体rigid body组件的相关使用方法
- Do-Tween插件的动画功能使用
- 蓄力粒子系统
- 简单的UGUI
- 联网排行榜的功能实现
- 预制体的使用
五.提升及展望
- UI界面不够美观 界面不统一
- 关卡的设置不够完善
- 音频只添加了背景音乐 不够生动
- 游戏的场景和角色不够生动