这篇文章默认大家已经对Unity3D和PlayMaker有一定的了解,所以没有把所有操作步骤都写的非常详细,不太看得明白的同学可以先行阅读:
- PlayMaker入门介绍
- Unity3D的数据类型以及PlayMaker的变量
- PlayMaker:触发事件
- PlayMaker简单实例(1):PM_Cube
- PlayMaker简单实例(2):角色与摄影机的运动控制
视频教程已经上传YouTube:[教程]使用PlayMaker重制Unity官方“滚动小球”教程
视频文件及素材资源下载地址在:百度网盘
Unity3D官方教程是非常非常优秀的新手入门学习资源,将其PlayMaker化是一个挺有趣的体验。
本教程是Unity官方教程第一篇,原教程地址:Roll-a-ball tutorial
准备场景
Roll-a-ball的场景很简单,一块2×1×2的蓝色Plane做地面(命名为Ground
),四个0.5×1×20.5的Cube做墙壁(放在Walls
空物体之中),墙壁物体距离坐标原点10单位长度,一个1×1×1的Sphere作为玩家操控的小球(命名为Player
)。
设置Main Camera的位置和旋转,修改Directional Light的位置与旋转,得到合适的摄影机位置以及光照效果。
添加小球运动
给Player添加一个Rigidbody组件。
Rigidbody将游戏物体变成一个“主动刚体”,使其可以受到力的作用而改变其位置和旋转,并且会根据其碰撞体(Collider)组件的设置而与其他物体相互作用。
Unity中的刚体动力学是这样划分的:
- 没有添加Collider组件的游戏物体不参与动力学计算;
- 只有Collider组件且没有勾选Is Trigger选项的游戏物体被认为是“被动刚体”,不会被动力学改变其运动状态,也就是说不会被撞开;
- 勾选了Is Trigger选项的Collider不会起到阻挡其他Collider的作用,也就是不会发生碰撞现象,但依然能够感知到其与其他Collider之间发生的交互作用,比如有其他Collider进入、停留或离开该Collider范围时,该Collider是能够感知到的;
- 有Collider组件也有Rigidbody组件的游戏物体被认为是“主动刚体”,主动刚体会受到动力学作用而改变运动状态,我们可以给主动刚体施加作用力或设置运动速度,也可以用其他刚体去撞击主动刚体;
- 勾选了Use Gravity(使用重力)选项的Rigidbody会持续受到重力作用而向-Y方向运动,除非被其他刚体阻挡;
- 勾选了Is Kinematic(运动学)选项的Rigidbody将忽略外力作用,也就是说重力和刚体撞击都不会使其产生运动,但我们直接设置其速度(velocity)来让其运动起来,算是一种特殊的“主动刚体”吧。
为Player添加Fsm,在State 1中添加Get Axis Vector以获得输入向量,添加Add Force为Player施加作用力。
与控制角色运动不同,控制小球运动我们不仅希望改变其位置,还希望小球能够“滚动”起来,这时候使用“施加作用力”的方式就比“设定运动速度”的方式要合适一些。并且,通过“施加作用力”来控制物体(尤其是球形物体)并不能非常精确的控制对象的运动(因为有惯性作用),所以用在这里反而能给游戏添加一些难度和乐趣。
这里我指定了两个变量,一个speed变量用来设置小球的运动速度,一个input axis变量用来储存输入向量值。speed变量勾选了Inspector选项,使其可以在Inspector中可见。
添加可拾取的小方块(Pickup)
在场景中新建一个Cube(改名为Pickup),修改其位移属性为<<0, 1, 0>>,修改其旋转属性为<<45, 45, 45>>,修改其缩放属性为<<0.5, 0.5, 0.5>>,添加一个黄色材质给Pickup。
为Pickup添加一个Fsm,在State 1添加一个Rotate:
勾选了Every Frame选项之后,Rotate行为可以按照Vector参数中所设定的旋转速度,持续旋转指定游戏物体。为了获得平滑的旋转效果,可以勾选Per Second选项和Fixed Update选项,让其按照“每秒多少度”的方式来进行旋转。
这里我没有直接输入旋转速度,而是通过新建一个名称为rotate speed的变量来控制,rotate speed变量的初始值设置为<<15, 30, 45>>,并在Inspector中可见。
将设置好的Pickup物体转换成prefab,然后在场景中复制摆放。新建一个名为Pickups的空物体来放置所有的Pickup物体,以保持场景整洁。
小球“拾取”黄色方块
Roll A Ball小游戏的基本交互逻辑是:玩家控制小球在场景中“拾取”所有的黄色小方块。为了达成这个目的,我们需要进行“碰撞”判定:当小球的碰撞体与黄色方块的碰撞体相交时,黄色方块消失。
由于小球和黄色方块上都具有Collider组件,所以这个判定工作可以被放在小球上,也可以被放在黄色小方块上。在这个范例中我们选择将判定工作放在小球上,是因为这个判定是每帧执行的,如果放在小方块上,则所有的小方块都会每帧执行判定操作,显然执行效率不如将判定放在小球这一个物体上。
为Player添加一个Trigger Event,设定其当有其他Trigger进入小球的碰撞体范围时,将这个Trigger物体储存在一个叫做pickup object的变量中,并触发hit a pickup事件。
为了避免别的trigger物体的干扰,我们设定这个Trigger Event行为仅针对那些被Tag为Pickup的Trigger物体起效果。因此,我们需要添加一个名为Pickup的Tag,并将所有的Trigger物体标记上这个Tag。
当hit a pickup事件被触发时,Player的Fsm状态从State 1跳转为State 2。
在State 2中,我们添加一个Activate Game Object,设置pickup object变量中所储存的游戏物体(也就是进入了Player碰撞体的Trigger物体)的Activate属性进行取消操作。
Activate实际上就是游戏物体Inspector面板中名称前面的小勾选框,如果取消勾选,这个游戏物体就“隐藏”了,或者说“失效”了,虽然依然存在于游戏场景中,却不会起到任何作用。
同时,我们还需要将Pickup物体(黄色小方块)的碰撞体设置为Trigger类型(勾选Box Collider组件中的Is Trigger选项),并设置其为Kinematic(勾选Rigidbody组件中的Is Kinematic选项)。
将小方块设置为Trigger类型是因为我们不希望小球和方块发生实际碰撞,只希望检测到双方相互触碰而已;将小方块设置为Kinematic是因为我们不希望小球在添加了Rigidbody之后受到重力作用而改变其“浮空自动旋转”的运动状态设置。
注意,如果是在场景中对Pickup物体进行的修改,需要点击Apply按钮将其修改应用到其他所有Pickup物体上,或者直接对prefab物体进行修改。
运行测试,我们现在可以用小球“吃掉”所有的黄色小方块了。
添加UI
我们希望每“吃掉”一个小方块,就能够得到1分,“吃完”所有的小方块,就可以赢得游戏!
在场景中添加两个UI Text物体:Score Text和Win Text:
可以看到,系统自动创建了一个Canvas,并将UI Text物体放置在Canvas中。同时,系统还创建了一个EventSystem,这两个自动创建的节点都不要随便删掉。
修改两个UI Text物体的参数,获得如下UI显示:
注意,这时候我们在Inspector中输入的UI文字内容只是一个预设值,我们后面会用PlayMaker控制其中文字内容的。
在Fsm中控制UI显示内容
这时候需要对Player的Fsm进行一些较大的修改了。
首先,State 2中需要加入“计分”的功能,并判断是否所有的Pickup物体都被“吃掉”了。如果是,则跳转到State 4(游戏获胜状态),如果否,则跳转回State 1继续游戏。同时,State 2中还需要进行UI文字内容更新操作,使其显示最新的分数。
其次,需要制作游戏获胜状态State 4,这里需要显示Win Text。
最后,需要在State 1之前添加一个State 3,在State 3中做一些必要的预设工作。
在State 3中:
- 用Set Int Value设置一个score变量,并将其数值初始化为0;
- 用Set Game Object设置一个score text变量,并指定Score Text物体给score text变量;
- 用Set Game Object设置一个win text变量,并指定Win Text物体给win text变量;
- 用Convert Int To String将score变量值转换成一个字符串变量score string,以便指定为UI文字的内容;
- 用U Gui Text Set Text将score text物体的Text参数值设置为“ ”(也就是无内容,如果什么字都不打PlayMaker会报错,所以打个空格就好了);
- 用U Gui Text Set Text将win text物体的Text参数值也设置为“ ”。
注意:U Gui Text Set Text行为需要自行安装uGui行为包。
在State 2中:
- 添加Int Add让score数值加1,代表得到1分;
- 添加Int Compare比较新的score数值和一个total pickups变量(手动设置为12)中的数值谁大谁小,如果score >= total pickups,则触发win事件(跳转到State 4);
- 添加Convert Int To String将score变量值转换成一个字符串变量score string,设置Format参数使其显示为“Score: 1”这样的格式;
- 添加U Gui Text Set Text将score string指定给score text物体的Text参数值,更新score text的显示内容。
注意,Int Compare放在Convert Int To String前面,当“吃到”最后一个小方块的时候,score text不会更新为Score: 12,因为已经直接跳转到State 4了。如果希望在State 4的时候能看到Score: 12这样的分数显示,则需要将Int Compare放在U Gui Text Set Text的后面。
在State 4中:
- 添加U Gui Text Set Text将win text的文字直接设置成“You Win!”。
这里其实偷了个巧,懒得去做显示隐藏Win Text物体的操作,就直接用“ ”来假装Win Text未出现。
最终的UI显示效果如下:
到此为止,就完成了Unity官方Roll A Ball教程的PlayMaker化!
一些小改进
发布游戏之前,添加了一些小改进:
- 给小球添加了贴图,可以清晰看到小球的旋转;
- 添加了一个RESTART按钮让玩家可以重新开始游戏;
- 烘焙了光照贴图;
- 添加了一点点后期效果:抗锯齿、边缘暗化;
- 添加了背景音乐和“吃掉”小方块时的音效。
WebGL版游戏演示:Roll A Ball
其他的设计思路
Roll A Ball小游戏非常简单,但我们可以在这个基础上开开脑洞,设计一些其他玩法,比如:
- 添加复杂的墙壁,修改小方块布局,以得到不同的关卡设计;
- 设置墙壁不可触碰,碰到墙壁则重置游戏;
- 规定时限,时限内没有吃到所有的小方块则任务失败;
- 添加陷阱,比如地面有个洞;
- 添加机关、道具,比如传送门;
- 为小球添加跳跃行为;
- 添加坡度,将目前的平面关卡设计转换为立体关卡设计;
- ……