PlayMaker简单实例(1):PM_Cube

前置阅读:PlayMaker:触发事件

Demo演示:PM_Cube


准备场景

  • 新建场景,保存为PM_Cube
  • 创建一个Plane当地面,命名为Ground,重置位置(在Transform组件上点击右上角的“小齿轮”,选择Reset)。再创建一个Cube物体,重置位置,修改Y Position为 0.5。
  • GroundCube分别创建材质球,修改颜色以示区别。
  • 在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中。

PM_Cube_002.png

在我们添加好的Action中,修改Material参数为我们设置给Cube的材质(点击Material参数后面的小圆点,然后选择material,或者直接将material从Project面板中拖到这里),修改Color参数为我们想要的颜色。

PM_Cube_003.png

这个Action的作用是:将指定GameObject(Game Object)上Mesh Renderer组件中的特定材质球(Material Index)或者任意指定的材质球(Material)上特定名称(Named Color)的颜色参数,修改为指定颜色(Color)。

  • Use Owner代表“自己”,表示使用该Fsm所在的GameObject
  • 参数后面的“=”按钮代表这个参数可以使用一个FSM变量
  • Every Frame如果被勾选,代表每帧都会执行一次该Action的操作

运行场景(点击Play按钮),我们发现Cube变成了绿色。

PM_Cube_004.png

退出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”。

PM_Cube_006.png

然后到Variables栏中,就会看到有新的变量出现了,这个变量名称为color,类型是“Color”。

给PlayMaker的动作参数指定变量,变量类型必须与参数的数据类型一致,所以最好的办法是在参数中创建新变量,这样类型就一定是匹配的了。

如果想直接在变量面板中新建变量,首先在空白处点击以取消当前选择的参数,然后在下方New Variable栏中输入名称,Variable Type栏中选择类型,点击Add或者按回车都可以完成创建。

我勾选了这个color变量的Inspector选项以使它能够在Inspector面板中可见,然后修改了默认颜色。

PM_Cube_0010.png

接下来改造Graph:
我添加了3个新的state,用来将颜色分别修改成绿色、红色和蓝色,在Events面板中创建了3个新的event:Set GreenSet RedSet Blue用来触发状态的转换,在State 1中使用这3个event做transition,分别指向红绿蓝三个state。最后给红绿蓝三个state添加FINISHED转换让他们完成操作后重新回到State 1

PM_Cube_005.png

在红绿蓝三个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行为,分别指定其ButtonLeftMiddleRight,然后设置其Sent EventSet RedSet BlueSet Green

PM_Cube_0011.png

运行测试:
当我们在任意位置按下鼠标左中右键时,Cube的颜色会相应变成红、蓝、绿。

  1. Get Mouse Button Down和之前的系统事件MOUSE DOWN是不一样的,前者只是检查鼠标按键是否被按下,而后者是检查是否在游戏物体上按下鼠标左键;
  2. 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数值的操作。

PM_Cube_012.png

Mouse Pick只是获取Ray碰撞到的第一个GameObject,我将这个GameObject储存到一个新的变量picked object中。判断还需要用Game Object Compare来完成,比较变量picked object是否等于“自身”(Owner),如果不是,则说明鼠标不在该物体上方,于是触发一个新事件Cancel,转换回State 1

Red上设置好Mouse PickGame Object Compare后,测试通过,然后选择这两个Action,复制(Ctrl + c),进入另外两个state,粘贴(Ctrl + v),就可以把Action序列放进去了。这是PlayMaker的一种常用的操作方法。

可以看到,不论是Mouse Pick还是Game Object Compare,我都没有勾选Every Frame选项,因为这个判断做一次就可以了,无需实时监控。

此外,Mouse PickGame 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

PM_Cube_014.png

这时我们运行场景,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.png

State 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,颜色确实是随机变化了。

PM_Cube_018.png

但是现在还有几个问题没有解决,一是都挤在一堆了,二是没有“发射”的感觉。

Create Object动作为我们生成了一个Cube,由于我们没有设置PositionRotation,所以这个Cube会自动被放在坐标原点。现在我们希望它出现在鼠标点击的位置。

鼠标点击的位置其实是摄影机平面的位置,于是我们需要把摄影机平面坐标转换成三维空间立体坐标,这件事情可以由Screen To World Point动作来完成。为了方便调试,我Fsm中新建了一个State 3并设成START

PM_Cube_019.png

Screen To World Point需要输入屏幕坐标,于是我还需要获取屏幕坐标:

  • 我找了半天也找不到一次性获取屏幕坐标(Vector2)的动作,所以使用Get Mouse XGet Mouse Y来分别获取,储存到mouse xmouse y两个变量中
  • 因为Get Mouse XGet Mouse Y中都勾选了Normalize,于是我也把Screen To World Point中的Normalized给勾上,并设置Screen Z等于2
  • 然后在Screen To World Point中把Screen XScreen Y分别设置为mouse xmouse y
  • 再将获得的世界坐标(Store World Vector)储存在一个叫world position的变量里
  • 最后勾选上Every Frame

为了验证这个世界坐标是否正确,我又添加了一个Set Position的动作,并在场景中放入了一个Cube 1.prefab

  • 指定Game Object为场景中的Cube 1
  • 指定Vector为变量world position
  • 勾选上Every FrameLate Update(设置每帧到最后再更新位置)

测试结果还不错,于是把这套Action搬去真正的Graph:

  • State 3中的Get Mouse XGet Mouse YScreen To World Point复制到State 2里面,使其处于Create Object上方
  • 取消Screen To World PointEvery Frame的勾选
  • 最后把State 1设置回开始状态(点击右键,选择Set As Start State
PM_Cube_021.png

测试运行,有问题!创建完第一个Cube之后就回不去State 1了。这是因为Get Mouse XGet Mouse Y都是每帧执行的动作,根本不会自然结束,所以需要手动添加一个Next Frame Event行为,让它来触发FINISHED事件。

PM_Cube_022.png

结果有效,但貌似诡异的。

PM_Cube_023.png

PM_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的方向就有点复杂了。大家可以点开XYZ三个参数后面的“=”符号,手动设置Force的向量分量(要注意是在什么坐标空间(Space)中),也可以开动脑筋做一点点数学计算,让Force符合我们的希望。


最后面这一段有点超纲了,大家看不太懂的可以自己手动用XYZ值试出一个合适的数值出来。

我选择让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 Multiplyforce vector乘上(Multiply By)一个新建的叫做force的变量
  • 最后在Add Force中设置Vector参数为force vector,并更改Force ModeImpulse以表示这是一个瞬发的力量

这个范例也显示了PlayMaker比较蛋疼的一个问题,就是特别不方便做比较复杂的数值计算。Add Force上方四五个Action,用数学公式来写其实一行就可以搞定:
force_vector = force * Normalize (cube_position - camera_position)
但在PlayMaker里面就搞得既复杂又拗口。


项目工程文件 (不包括PlayMaker插件及其uGUI扩展Action包)


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

推荐阅读更多精彩内容