PlayMaker简单实例(1):PM_Cube
前置阅读:PlayMaker:触发事件
Demo演示:PM_Cube
准备场景
- 新建场景,保存为
PM_Cube
。 - 创建一个Plane当地面,命名为
Ground
,重置位置(在Transform组件上点击右上角的“小齿轮”,选择Reset
)。再创建一个Cube物体,重置位置,修改Y Position
为 0.5。 - 为
Ground
和Cube
分别创建材质球,修改颜色以示区别。 - 在Scene View中调整好一个合适的摄影机角度,选择
Main Camera
,从菜单选择GameObject
>Align With View
将其与Scene View对齐。
PM_Cube version 0.1
做一个有“颜值”的Cube!
选择Cube
,打开PlayMaker Editor为其添加一个Fsm(命名为FSM_Color)。选择State 1
,点击Action Browser
打开动作浏览器,在搜索栏输入“color”,然后选择Material类别中的Set Material Color
,双击将其添加到State 1
中。
在我们添加好的Action中,修改Material
参数为我们设置给Cube的材质(点击Material
参数后面的小圆点,然后选择material,或者直接将material从Project面板中拖到这里),修改Color
参数为我们想要的颜色。
这个Action的作用是:将指定GameObject(Game Object
)上Mesh Renderer组件中的特定材质球(Material Index
)或者任意指定的材质球(Material
)上特定名称(Named Color
)的颜色参数,修改为指定颜色(Color
)。
-
Use Owner
代表“自己”,表示使用该Fsm所在的GameObject - 参数后面的“=”按钮代表这个参数可以使用一个FSM变量
-
Every Frame
如果被勾选,代表每帧都会执行一次该Action的操作
运行场景(点击Play
按钮),我们发现Cube变成了绿色。
退出Play模式,我们发现Cube保持为绿色,这是因为我们直接对材质球的颜色做了修改,而材质球并不是场景物体,而是Asset资源,所以在Play模式被修改了以后,退出Play模式也不会恢复。
在Play模式下我们对场景物体进行的修改,退出Play模式以后都不会被保存,但初学者经常忘记这一点。所以可以从菜单
Edit
>Preferences
打开Unity Preferences面板,然后选择Colors
栏,将第一行的Playmode tint
颜色修改成一个比较显眼的颜色,这样就不会忘记退出Play Mode了。
PM_Cube version 0.2
Cube也是会翻脸的哦!
下面我们改造一下:
首先我取消了Material
参数上的设置(点击小圆点,选择None
)。这是因为我既然已经设置了使用模型本身Material Index
为0的那个材质进行修改,就无需再指定特定材质球了。
修改“GameObject上Material Index = 0的材质”和“修改对应的
.mat
文件”还是有所区别的,我们可以认为游戏物体上实际附加的材质是Assets目录下.mat
文件的一份拷贝,修改Material Index = 0的材质参数实际上不会改变.mat
文件本身。
所以做了这个修改以后,就不会出现退出Play模式颜色依然保持改变的情况了。通常情况下,我们都尽量不要直接对Asset本体进行修改,调用以后修改GameObject就可以了。
按下Color后面的“=”按钮,然后点击红色显示的None
,选择New Variable...
以创建一个新的变量,起名为“color”。
然后到Variables栏中,就会看到有新的变量出现了,这个变量名称为color,类型是“Color”。
给PlayMaker的动作参数指定变量,变量类型必须与参数的数据类型一致,所以最好的办法是在参数中创建新变量,这样类型就一定是匹配的了。
如果想直接在变量面板中新建变量,首先在空白处点击以取消当前选择的参数,然后在下方
New Variable
栏中输入名称,Variable Type
栏中选择类型,点击Add
或者按回车都可以完成创建。
我勾选了这个color变量的Inspector
选项以使它能够在Inspector面板中可见,然后修改了默认颜色。
接下来改造Graph:
我添加了3个新的state,用来将颜色分别修改成绿色、红色和蓝色,在Events面板中创建了3个新的event:Set Green
、Set Red
、Set Blue
用来触发状态的转换,在State 1
中使用这3个event做transition,分别指向红绿蓝三个state。最后给红绿蓝三个state添加FINISHED
转换让他们完成操作后重新回到State 1
。
在红绿蓝三个state中我都使用了Set Color Value
这个Action来修改颜色,因为在这3个状态中我不是要去改变材质颜色,而是要去改变颜色变量color
的数值。
运行测试:
一开始会呈现color值预设的颜色,手动转换状态(Alt
+ 左键单击),可以改变Cube的颜色。
实际的逻辑是这样的,红绿蓝三个state修改了变量color
的值,然后返回State 1
的时候,State 1
读取变量color
最新的值并赋给材质的_Color
参数,颜色改变。每一次进入State 1
,都会执行一次“读取color
并赋值”的操作,所以保证了每次修改完变量值,Cube的颜色都会发生改变。
当然,想要达到这样的交互效果,这并不是唯一的设计方案。大家可以在我们逐渐将这个实例改造得越来越复杂之后再回过头来想一想,为什么这里会采用这样的设计方案,为什么有时候即使只添加了一点新的需求,原来的设计方案会立刻显得不合适而需要大幅度的修改。
PM_Cube version 0.3
Cube这次是来真的了!
下面添加一些触发条件来让这个交互逻辑变得Playable。
在State 1中添加3个Get Mouse Button Down
行为,分别指定其Button
为Left
、Middle
、Right
,然后设置其Sent Event
为Set Red
、Set Blue
、Set Green
。
运行测试:
当我们在任意位置按下鼠标左中右键时,Cube的颜色会相应变成红、蓝、绿。
Get Mouse Button Down
和之前的系统事件MOUSE DOWN
是不一样的,前者只是检查鼠标按键是否被按下,而后者是检查是否在游戏物体上按下鼠标左键;In Update Only
选项非常重要。Get Mouse Button Down
检测的是按键是否处于被按下状态,如果是,立刻转换状态。但由于我们的目标状态也是可以立即完成并转换回State 1
的,而我们人类从按下鼠标到松开鼠标还有一段时间,在这段时间两个状态是会不断进行循环的,这就产生了“无限循环”错误。而In Update Only
选项则强制状态转换只在Update(也就是刷新至下一帧)时发生,从而避免了在一帧时间内发生多次转换的情况发生。
PM_Cube version 0.4
Cube不会无缘无故翻脸的啦!
现在在任何地方按下鼠标键都可以会使Cube颜色发生改变,如果我希望只是在“点击”Cube的时候才会发生颜色改变的,就要让PlayMaker学会判断点击时鼠标下方是什么物体。
如果看过 PlayMaker:触发事件 一文就会知道,我们可以使用Mouse Pick
行为来进行这个判断,但问题是应该把Mouse Pick
放在哪个状态中呢?
State 1
是进行输入判断的,把Mouse Pick
放在这里肯定会有冲突。因为虽然一个state中的所有action其实都会在1帧时间内全部执行一遍,但还是有先后顺序的。不论我把Mouse Pick
放在Get Mouse Button Down
的前面还是后面,一旦有event被触发,状态立刻发生转换,后面的Action根本就不会被执行。
于是我决定把Mouse Pick
放在3个颜色state里面。按下鼠标键后转换到颜色state,首先执行判断鼠标是否处于物体之上,如果不处于,直接转换回State 1
,不执行修改color数值的操作。
Mouse Pick
只是获取Ray碰撞到的第一个GameObject,我将这个GameObject储存到一个新的变量picked object
中。判断还需要用Game Object Compare
来完成,比较变量picked object
是否等于“自身”(Owner
),如果不是,则说明鼠标不在该物体上方,于是触发一个新事件Cancel
,转换回State 1
。
在
Red
上设置好Mouse Pick
和Game Object Compare
后,测试通过,然后选择这两个Action,复制(Ctrl
+c
),进入另外两个state,粘贴(Ctrl
+v
),就可以把Action序列放进去了。这是PlayMaker的一种常用的操作方法。
可以看到,不论是Mouse Pick
还是Game Object Compare
,我都没有勾选Every Frame
选项,因为这个判断做一次就可以了,无需实时监控。
此外,Mouse Pick
和Game Object Compare
一定要在Set Color Value
之前,否则即便条件成立,系统也会先执行完设置颜色的操作再转换状态。
接下来我们可以把Cube复制几个,然后运行测试,每个小方块都可以被鼠标单独点击改变颜色。
PM_Cube_013.png删除多余的方块,把Cube拖到Project面板中以创建成一个prefab(会自动命名为Cube.prefab
)。
我们可以删除场景中的Cube,然后把
Cube.prefab
拖入场景测试一下这个预设物体是否运行正常。这个例子里面当然是正常的,但也有可能生成的prefab再放入场景就完全不起作用了。原因就在于,如果一个组件中的某个变量被手动指定为场景中的某个游戏物体,而且这个游戏物体又没有被包括在prefab中的话,这个变量赋值信息是不会被保存在prefab中的。比如,我们在
Game Object Compare
行为中,如果不是指定Use Owner
去与picked object
做比较,而是手动指定场景中的地面,那么在prefab中相应Fsm里的这个Game Object
参数其实是没有被赋值的。
PM_Cube version 1.0
偷偷告诉你,Cube是女生哦!
换个玩法,点击屏幕从鼠标发射一个随机颜色的方块到场景中。
首先我们要制作一个随机选取颜色方块。
在场景中重新创建一个Cube,附上之前的材质。添加Fsm,在State 1上添加一个Select Random Color
动作和一个Set Material Color
动作。
Select Random Color
可以在给定的多个颜色值中随机选取一个颜色,我们设置Store Color
为一个新变量color
。
在Set Material Color
中把Color
参数设置为使用变量color
。
这时我们运行场景,Cube会随机显示成红、绿、蓝中任意一种颜色(大家可以自行设置七彩小Cube)。将这个新的Cube做成prefab(由于已经有一个Cube.prefab
了,所以应该会被自动命名为Cube 1.prefab
),删除场景中的Cube。
在场景中Create
一个Empty Group
,命名为GameManager
。Fsm必须作为组件被附加在一个GameObject上,所以这个空物体就是我们Fsm的载体。
按下图创建Graph:
PM_Cube_015.png PM_Cube_016.png PM_Cube_017.pngState 1
中检测鼠标左键是否按下,如果是,触发LMB Down事件跳转到State 2
。
State 2
中添加一个Create Object
行为,设置Game Object为Cube 1.prefab
(点击后面的小圆点,然后在Assets栏中选择Cube 1
)。
注意,选择GameObject的时候一定要分清楚选择的是Assets中的prefab还是Scene中的GameObject,两者的图标是不一样的。
运行场景,每次点击鼠标都会创建一个新的Cube 1(Clone)
(我们可以从Hierarchy面板中看到,(Clone)
代表这是一个实时创建出来的克隆体)。但由于所有的Cube都拥挤在原点位置,所以看上去好像只有一个的样子。拖出来一些Cube,颜色确实是随机变化了。
但是现在还有几个问题没有解决,一是都挤在一堆了,二是没有“发射”的感觉。
Create Object
动作为我们生成了一个Cube,由于我们没有设置Position
和Rotation
,所以这个Cube会自动被放在坐标原点。现在我们希望它出现在鼠标点击的位置。
鼠标点击的位置其实是摄影机平面的位置,于是我们需要把摄影机平面坐标转换成三维空间立体坐标,这件事情可以由Screen To World Point
动作来完成。为了方便调试,我Fsm中新建了一个State 3
并设成START
。
Screen To World Point
需要输入屏幕坐标,于是我还需要获取屏幕坐标:
- 我找了半天也找不到一次性获取屏幕坐标(Vector2)的动作,所以使用
Get Mouse X
和Get Mouse Y
来分别获取,储存到mouse x
和mouse y
两个变量中 - 因为
Get Mouse X
和Get Mouse Y
中都勾选了Normalize
,于是我也把Screen To World Point
中的Normalized
给勾上,并设置Screen Z
等于2 - 然后在
Screen To World Point
中把Screen X
和Screen Y
分别设置为mouse x
和mouse y
- 再将获得的世界坐标(
Store World Vector
)储存在一个叫world position
的变量里 - 最后勾选上
Every Frame
为了验证这个世界坐标是否正确,我又添加了一个Set Position
的动作,并在场景中放入了一个Cube 1.prefab
:
- 指定
Game Object
为场景中的Cube 1 - 指定
Vector
为变量world position
- 勾选上
Every Frame
和Late Update
(设置每帧到最后再更新位置)
测试结果还不错,于是把这套Action搬去真正的Graph:
- 将
State 3
中的Get Mouse X
、Get Mouse Y
、Screen To World Point
复制到State 2
里面,使其处于Create Object
上方 - 取消
Screen To World Point
的Every Frame
的勾选 - 最后把
State 1
设置回开始状态(点击右键,选择Set As Start State
)
测试运行,有问题!创建完第一个Cube之后就回不去State 1
了。这是因为Get Mouse X
和Get Mouse Y
都是每帧执行的动作,根本不会自然结束,所以需要手动添加一个Next Frame Event
行为,让它来触发FINISHED
事件。
结果有效,但貌似诡异的。
PM_Cube_023.pngPM_Cube version 1.1
就问你怕不怕!
下面终于可以开始制作“发射”的效果了!!!
为了让Cube能够被射出去,需要用到动力学(Physics),所以首先就需要给Cube添加一个Rigidbody
组件。我们可以直接选取Cube 1.prefab
,然后在Inspector最下面点击Add Component
按钮,输入“rigidbody”,按回车确认选择,就将Cube变成了一个动力学物体。
运行场景,动力学物体的确好玩,但还没有添加初速度,就还不算“发射”。
选择GameManager,打开PlayMaker编辑器。在State 2
的最后,添加一个Add Force
行为。
Add Force
首先提示我们GameObject需要Rigidbody组件,别忙着加,我们这是在GameManager,这个Force并不是加给GameManager的。将Game Object
设置成cube
变量,这个警告提示自然就没有了。
Add Force
需要知道这个Force加在物体的什么位置(At Position
),以及这个Force的方向及大小(Vector)。位置不需要设置,自动就会处于物体中心,但Force的方向就有点复杂了。大家可以点开X
、Y
、Z
三个参数后面的“=”符号,手动设置Force的向量分量(要注意是在什么坐标空间(Space
)中),也可以开动脑筋做一点点数学计算,让Force符合我们的希望。
最后面这一段有点超纲了,大家看不太懂的可以自己手动用
X
、Y
、Z
值试出一个合适的数值出来。
我选择让Cube沿着摄影机的方向被发射出去,所以需要用Cube出生位置减去摄影机所在的位置来获得这个方向矢量(Vector3),然后将这个方向矢量标准化(也就是让它方向保持不变但长度变为1),最后再倍乘上一个力度值,就可以得到所需要的方向和力度的一个Force了。
详细的Action设置可以看下面这张图:
PM_Cube_025.png- 在
Add Force
上方我先添加了一个Get Position
,指定让它获取Main Camera的位置并储存为camera position
变量 - 然后再接一个
Vector3 Operator
把之前获得的world position
减去camera position
,结果储存为force vector
- 接着使用
Vector3 Normalize
把这个force vector
标准化 - 然后使用
Vector3 Multiply
把force vector
乘上(Multiply By
)一个新建的叫做force
的变量 - 最后在
Add Force
中设置Vector
参数为force vector
,并更改Force Mode
为Impulse
以表示这是一个瞬发的力量
这个范例也显示了PlayMaker比较蛋疼的一个问题,就是特别不方便做比较复杂的数值计算。Add Force上方四五个Action,用数学公式来写其实一行就可以搞定:
force_vector = force * Normalize (cube_position - camera_position)
但在PlayMaker里面就搞得既复杂又拗口。
项目工程文件 (不包括PlayMaker插件及其uGUI扩展Action包)