在很多实际场景中,我们经常需要根据特定的事件(比如玩家输入,敌人受到攻击等)来播放不同的动画。这需要我们了解一下Animator,Animator Controller和基础的动画状态机。
首先我们来创建一个简单的开门动画,示例中的门的模型来自官方教程。其实我们也可以用一个简单的Cube调整一下做成门的形状来做。无论是哪种方式,我们首先需要检查一下这个游戏物体的pivot point。一个pivot point是物体进行移动的基准点。对于门来说,这个点非常重要。如果pivot点设置不对,则开门动画会看起来非常奇怪(比如门沿着自身中轴线旋转,当然,如果你是要制作旋转门动画就另说,示例中是一个普通的门)。
1. 首先,选中门对应的游戏物体。此时Unity编辑器中会出现移动widget,如果这个widget的位置处于游戏物体正中心,如下图:

2. 我们点击Toggle Tool Handle Position按钮(上图左上角红框处),将它从Center改为Pivot

3. 在Animation编辑器中,点击Create按钮,创建一个新的动画片段,我们将其命名为Door_Open并保存

4. 在Animation编辑器中,点击Record,然后点击Add Property,添加Transform -> Rotation属性

5. 将Playhead移动到1:00处(第二个关键帧),然后调整一下门的旋转角度,将门的Inspector里的Transform -> Rotation里的Y改成90

6. 预览一下开门动画,没问题后再次点击Record退出Recording模式。
7. 在工程文件窗口中,找到刚刚创建的Door_Open动画文件,点击后,在Inspector窗口中,禁用掉Loop Time。禁用这个选项后动画只会播放一次,而不是无限循环。

再次选择Door游戏物体,在Inspector中我们可以看到出现了一个新的Animator组件。对于一个选中的游戏物体,当我们第一次为其创建动画片段时,Unity会自动为它创建一个Animator组件。

Animator负责为游戏物体分配动画。但它并不控制实际的动画片段,这个任务交给了Animator Controller,这个也是在第一个动画片段创建时自动创建的。在Animator组件中的Controller这一栏中,可以看到有一个名为Door的Animator Controller(在保存动画的目录中,一般为Animations目录,下面也有一个同样名字的Door文件)。
1. 在工程文件窗口中,选中Door这个文件,将其重命名为Door_Controller

2. 双击这个文件,打开Animator窗口

3. 为了方便演示,将Animator窗口拖动到工程窗口处

4. 在场景中选中Door游戏物体,然后点击Play进入游戏模式

当场景进入Play模式后,可以看到门被打开了,同时,我们会看到Animator窗口中的Door_Open动画下方有一个蓝色的进度条开始慢慢填满,这表示Door_Open动画片段的播放过程。Animator窗口中会实时地显示当前正在播放的动画片段以及动画片段播放进度。
Animation Controller运行在一种叫做状态机(State Machine)的特殊系统上。状态机会跟踪一个物体所能执行的所有可能的动作,并且根据当前的情况(状态,state)选择合适的动作。对于当前状态的控制逻辑,一般是通过脚本来控制。状态机处理的从一个动作转到另一个动作的过程,叫做转换(Transition)。
为了更好地理解状态机是如何工作的,我们以案例里的门为例子,它可以执行的动作有开门和关门两种。
因此门可能处于的状态有:
接下来我们画出这些状态之间的关系:

门的默认状态处于关闭状态。当玩家足够靠近门时,状态机会切换到正在开门的状态,然后会切换到打开状态。在玩家离开之前,门会一直处于打开状态。玩家离开后,门会进入正在关闭状态,然后切换到关闭状态。此时状态机又回到了初始状态。
基于此,我们可以看到有两种转换(transitions)需要一些逻辑输入(检测玩家靠近和远离门),有两种状态会自动转换到下一个状态(正在开门转换到开门,正在关门转换到关门)。
前面小节中,我们创建的Door_Open动画,只要场景开始运行就会自动播放。这不是我们想要的效果,造成这个问题的原因是,当前只有这个动画和门关联,在添加这个动画时,Unity的Animation Controller自动将其设置为了默认状态。接下来我们来解决一下这个问题。
1. 在Animator的工作区中(背景是网格,包含了各种状态的区域),点击右键并选择Create State -> Empty,创建一个空状态
2. 选择新创建的节点,在Inspector面板中,将它的名字从New State改为Closed
3. 右键点击Closed状态,然后选择“Set as Layer Default State”

我们可以看到,现在Closed状态变成了橙色,并且Entry节点指向了它。Entry节点和Closed节点之间的橙色线表示了一个转换(transition)。Entry节点是状态机开始运行时所处的第一个节点,在场景开始运行的时候会自动运行Entry节点。
4. 再次点击运行按钮,看看门的行为有什么差异。
下一步,我们来实现检测玩家并切换到开门动画的功能。要检测玩家是否靠近到门,通常的逻辑是通过一个Trigger碰撞器加上脚本来实现。我们先完成状态机部分的设置,增加脚本要使用的引用变量即可。
1. 右键点击Closed节点,选择Make Transition。此时有一个白色的箭头会出现在光标处。
2. 选择Door_Open节点,让这个箭头和它连接起来。

3. 点击运行按钮测试一下效果。
运行过程中,我们可以看到,在Animation Controller中,首先会经过Closed节点。当Closed节点完成后,会立即调到Door_Oepn动画,随后停在了开门动画上。为了避免Closed节点直接调到开门动画节点,我们要在转换条件上附加一些逻辑来做控制。要实现这个控制,我们需要使用状态机参数(Parameters)参数是Animator内部创建的变量,可以在Animator编辑器的左边找到Parameters标签页。
4. 选择Parameters标签页,在Parameteres列表的顶部,点击“+”并且选择Trigger(触发器)

Trigger参数会在它被脚本所修改时立即变化。
5. 将Trigger变量的名字设置为PlayerProximity,注意后面在脚本里引用这个变量时,名字一定不要搞错了。
6. 选中Closed和Door_Open节点中间的带白色箭头线。然后在其Inspector中找到Conditions。
7. 在Condiditons列表底部,选择“+”。由于目前只有一个参数,PlayerProximity会自动加到列表中。

8. 点击运行按钮,运行场景。
9. 在参数列表中,点击PlayerProximity参数旁边的单选按钮,手动触发这个变量。

我们可以看到,当PlayerProximity被修改后,状态切换到了Door_Open并开始播放开门动画。
接下来我们用类似的方法实现关门状态。
1. 在Animator里选中Door_Open节点,然后按Ctrl/Command + d复制一份这个节点。
2. 在Inspector中,将复制的节点的名字修改为Door_Close,并且将它的Speed参数设置为-1,这样会让动画的帧反向播放。
3. 右键点击Door_Open节点,选择Make Transition,然后将Door_Open和Door_Close节点连接起来。

4. 选择这个新建的转换(transition),在Condiditons列表中点击“+”。同样,PlayerProximity会自动出现。
5. 点击运行按钮,运行场景,同样我们使用PlayerProximity旁边的单选按钮,来测试先开门后关门的动作是否正常。
Animator中的Trigger参数在当前转换(从一个状态切换到下一个状态)完成后,会立即失效。因此我们在开门和关门的时候,可以安全地复用这个参数。后面我们会用脚本来操作这个参数。
到目前为止,我们以及实现了通过Animator参数来实现开门和关门的动画播放和相关状态。但状态机最后会停留在关门状态,这个过程只会发生一次。为了让动画每次在PlayerProximity发生变化时还能正常表现,当关门动画播放完毕时,我们要将动画状态机复位到初始状态。我们可以通过使用Exit节点来实现这个功能。当状态转换到Exit节点时,状态机会自动地切换到Entry节点。
1. 右键点击Door_Close节点,选择Make Transition。
2. 点击Exit节点,连接Door_Close节点和Exit节点。

3. 点击运行按钮,运行场景
现在我们可以看到,当Door_Close播放完成后会自动转换到Exit,然后再回到Closed状态。这里我们还会注意到一点,在我们修改了PlayerProximity的值之后到门被打开之间会有点延迟。这是因为默认情况下,在一个状态节点执行转换之前,要先走完整个动画播放时间(Animator里状态节点的进度条可以直观感受到这个时间)。我们可以选择将禁用这个行为,让切换到Door_Open的转换立即发生。
4. 选择Closed和Door_Open节点之间的转换(transition),在Inspector中,禁用掉Has Exit Time。

6. 重新运行场景,看看现在的效果。
前面我们通过手动点击PlayerProximity的方式来做状态机的测试。接下来我们用一个Trigger碰撞器和一个脚本来实现对这个参数的动态控制。关于普通的collider和tirgger collider的区别,本笔记不会讨论,请自行参考网上资料。
1. 在Hierarchy中,点击右键选择Create Empty创建一个空游戏物体。
2. 将这个游戏物体命名为Door_Trigger。
3. 选中Door_Trigger,在Inspector中选择Add Component,添加一个Box Collider。

4. 在Box Collider组件中,启用“Is Trigger”。
5. 调整Box Collider的大小,让其能够覆盖到门前后一片空间。

6. 编写DoorTrigger脚本,并且将脚本添加到Door_Trigger游戏物体上。
DoorTrigger脚本代码如下:

代码意思很好懂,OnTriggerEnter/Exit函数中,检测到Player进入/离开Trigger Collider之后,会去修改PlayerProximity参数。
7. 在Door_Trigger的Inspector中,将Door游戏物体拖动到Door Trigger脚本里的Anim属性方框中,这能够将Door的animator和脚本里的Anim参数关联到一起(当然,也可以直接在脚本里通过GetComponent方法获取这个Animator)。

8. 运行场景,控制Player进入和离开Trigger Collider的区域,观察结果(案例中有一个可用键盘控制的人物模型,我们也可以直接在场景中添加一个小球或立方体来代替,通过在Editor中拖动这个物体来实现Trigger Collider的检测)。
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po