游戏显示本机运行志高空调制热效果不佳可能不佳是怎么了

游戏本质分析(1)_百度文库
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
游戏本质分析(1)
&&游戏本质分析
阅读已结束,下载本文需要
想免费下载本文?
定制HR最喜欢的简历
下载文档到电脑,方便使用
还剩1页未读,继续阅读
定制HR最喜欢的简历
你可能喜欢游戏开发入门指南游戏开发入门指南对游戏开发感兴趣的同学们欢迎关注,定期更新教程关注专栏更多最新文章{&debug&:false,&apiRoot&:&&,&paySDK&:&https:\u002F\u002Fpay.zhihu.com\u002Fapi\u002Fjs&,&wechatConfigAPI&:&\u002Fapi\u002Fwechat\u002Fjssdkconfig&,&name&:&production&,&instance&:&column&,&tokens&:{&X-XSRF-TOKEN&:null,&X-UDID&:null,&Authorization&:&oauth c3cef7c66aa9e6a1e3160e20&}}{&database&:{&Post&:{&&:{&title&:&给猫看的游戏AI实战(六)行为树和Behavior Designer插件(上篇)&,&author&:&ma-yao-73&,&content&:&\u003Cp\u003E本系列第三节花了很长篇幅介绍怎样用有限状态机(FSM)实现一个AI敌人。当时实现的敌人状态较少,智商也很捉急。本来我打算在第四节改进那个敌人,为它加上寻找掩体等策略。但是由于逻辑过于复杂,不利于讲解,就改为讨论其他内容了。\u003C\u002Fp\u003E\u003Cp\u003E其实不继续讲状态机还有个原因——用状态机实现敌人AI之后,我又尝试了行为树的模式以及Behavior Tree插件,花了几天时间才摸索出了大概,将AI逻辑重新实现了一遍。如果你能和我一起对比着用状态机和行为树实现同样的AI,那么收获会非常大,也会比较有成就感。\u003C\u002Fp\u003E\u003Cp\u003E本文将再次探讨AI逻辑的核心问题,(不像前两章那样在外围问题上划水了( ̄ y ̄))。那么,和我一起来进入AI的世界吧!\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E1、行为树与状态机对比\u003C\u002Fh2\u003E\u003Cp\u003E举个简单的例子,还是咱们第二节讲过的问题。设计一个简单的AI士兵,他具有以下特性:\u003C\u002Fp\u003E\u003Col\u003E\u003Cli\u003E他具有真实视野,发现敌人则进攻。\u003C\u002Fli\u003E\u003Cli\u003E进攻时会瞄准敌人并射击,同时跑向敌人。\u003C\u002Fli\u003E\u003Cli\u003E离初始位置过远,有可能是被调虎离山了,这时要回到初始位置。\u003C\u002Fli\u003E\u003Cli\u003E进攻时离敌人过远,代表敌人逃跑成功,也要回到初始位置。\u003C\u002Fli\u003E\u003C\u002Fol\u003E\u003Cp\u003E画一个简单的状态机:\u003C\u002Fp\u003E\u003Cimg src=\&v2-da9debdfc808e3db20de51bdf46a440d.png\& data-rawwidth=\&494\& data-rawheight=\&251\&\u003E\u003Cp\u003E很简单吧,我们之前在Update函数中用一些 if else 条件判断,就实现了这个状态机。后来我想改进这个状态机,但是改进的AI士兵过于复杂了,咱们看看到底能复杂到什么程度:\u003C\u002Fp\u003E\u003Cimg src=\&v2-3c5b980bf3ec.png\& data-rawwidth=\&671\& data-rawheight=\&342\&\u003E\u003Cp\u003E对于游戏设计师来说,我们只增加了一个简单的功能:当AI士兵被远距离狙击时,就跑到建筑内部的指定位置,以躲避狙击手的攻击。这个功能会增加两个状态,就是上图左边的:跑向掩体和掩体待机。\u003C\u002Fp\u003E\u003Cp\u003E问题是——数一数连线,最早我们有4条有效线段(去掉2个“无”的连线就是4条),代表4种状态转移的条件。而改进后的AI士兵,多出了5条线段,比原来的复杂度扩大了一倍还多。\u003C\u002Fp\u003E\u003Cp\u003E9种状态转移并不多,问题是:这样子的AI士兵还远远达不到设计要求,为了让AI能应付各种情况,我们最终可能需要12种状态。那么这12种状态如果用状态机管理,线段会有多少条呢?去掉某些不可能的状态转移,少说需要几十条线段吧……这可不是什么好消息。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E下面我们直观的看一下用行为树解决同样的问题,是什么样子的:\u003C\u002Fp\u003E\u003Cimg src=\&v2-baa188e93ea62c.png\& data-rawwidth=\&735\& data-rawheight=\&388\&\u003E\u003Cp\u003E是不是有一种耳目一新的感觉? ( ̄ y ̄)~*\u003C\u002Fp\u003E\u003Cp\u003E从直观感受上来说,\u003Cb\u003E状态机是以多个状态为核心,以状态转移为线索\u003C\u002Fb\u003E的一种图表。\u003C\u002Fp\u003E\u003Cp\u003E而\u003Cb\u003E行为树是以行为逻辑为框架,以具体行动作为节点\u003C\u002Fb\u003E的一种树状图。\u003C\u002Fp\u003E\u003Cp\u003E上图我们简单改变一下写法,就变成了更容易理解的形式:\u003C\u002Fp\u003E\u003Cimg src=\&v2-bc7ce47bb8af0.png\& data-rawwidth=\&737\& data-rawheight=\&404\&\u003E\u003Cp\u003E黄色节点别我翻译为大家容易理解的 and &&、or || 和while循环,暂且可以这么理解。而红色节点,是一种判断行为(相当于if语句),绿色节点,是真正的行动Action节点。这个图是从示例工程中实际用到的行为树简化而来的,非常具有说服力。\u003C\u002Fp\u003E\u003Cp\u003E而实际使用中树的结构远远没有这么简单,行为树会引出很多新的概念与使用要点,而且我们会用Behavior Designer这款大名鼎鼎的行为树插件来作为示范,在后面我们会详细解释。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E2、Behavior Designer 简单介绍\u003C\u002Fh2\u003E\u003Cp\u003E借用Behavior Designer官方文档的介绍:\u003C\u002Fp\u003E\u003Cp\u003E\u003Ci\u003EBehavior Designer 是一个行为树插件!是为了让设计师,程序员,美术人员方便使用的可视化编辑器!Behavior Designer 提供了强大的 API 可以让你轻松的创建 tasks(任务),配合 uScript 和 PlayMaker 这样的插件,可以不费吹灰之力就能够创建出强大的 AI 系统,而无需写一行代码!\u003C\u002Fi\u003E \u003C\u002Fp\u003E\u003Cp\u003E其实,按照我的理解,Behavior Designer的主要作用并非可以不写代码(还是要写不少代码的),而是能让游戏中逻辑最混乱的模块——AI模块能更有序的组织,方便查看、调试和修改。\u003C\u002Fp\u003E\u003Cp\u003E可以打开我们的示例工程,或者安装Behavior Designer插件。该插件是以Package的形式提供的,可以任意拷贝,本文不提供盗版下载( ̄工 ̄lll) 。\u003C\u002Fp\u003E\u003Cp\u003E1、打开Behavior Designer窗口的方法如图:\u003C\u002Fp\u003E\u003Cimg src=\&v2-e28d7efe7f9aa73bc9aae0d.png\& data-rawwidth=\&763\& data-rawheight=\&269\&\u003E\u003Cp\u003E2、为任意对象添加Behavior组件:\u003C\u002Fp\u003E\u003Cimg src=\&v2-c2f19c42deda.png\& data-rawwidth=\&941\& data-rawheight=\&359\&\u003E\u003Cp\u003E如图,在上面的菜单里选择“Add Behavior Tree”即可。观察该GameObject的属性,可以在下图中可以看到这个组件实际上是一个脚本,默认参数目前不需要任何修改。\u003C\u002Fp\u003E\u003Cimg src=\&v2-b6b8ceac26d110ddeaa46.png\& data-rawwidth=\&414\& data-rawheight=\&346\&\u003E\u003Cp\u003E下面开始按步骤实现并讲解一个基本的行为添加过程,如有懵圈的情况,及时询问或查找网上详细的Behavior Designer资料。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E3、现在可以编辑这个对象的Behavior Tree了:\u003C\u002Fp\u003E\u003Cp\u003E要点1、按照步骤1可以打开Behavior Designer编辑窗口,在窗口打开的情况下点击包含了BTree组件的对象,就可以对它进行编辑:\u003C\u002Fp\u003E\u003Cimg src=\&v2-cab.png\& data-rawwidth=\&848\& data-rawheight=\&331\&\u003E\u003Cp\u003E这里添加一个Task -& Decorators -& UntilSuccess节点,它是一种Decorator即修饰器,修饰器在行为树中起到骨架的作用,就像是程序里的循环和判断一样不可或缺。我现在要实现视野范围的功能,在发现敌人以后,把敌人信息记下来。这就需要一个定制化的动作——判断敌人是否在视野中,这个动作起名为WithInSight。\u003C\u002Fp\u003E\u003Cp\u003E先添加一个WithInSight.cs脚本才能加入到Behavior Designer窗口里,代码如下,已经加上了详细注释:\u003C\u002Fp\u003E\u003Ccode lang=\&csharp\&\u003Epublic class WithinSight : Conditional\n{\n
\u002F\u002F 视野角度\n
public float fieldOfViewA\n
\u002F\u002F 目标物体的Tag\n
public string targetT\n
\u002F\u002F 发现目标时,将目标对象设置到BahaviorTree共享变量里面去\n
public SharedT\n
public SharedVector3 targetP\n
\u002F\u002F 所有指定Tag的物体的数组\n
private Transform[] possibleT\n\n
\u002F\u002F 重载函数,Behavior Designer专用的Awake\n
public override void OnAwake()\n
\u002F\u002F 根据Tag查找到所有物体,全部加入数组\n
var targets = GameObject.FindGameObjectsWithTag(targetTag);\n
possibleTargets = new Transform[targets.Length];\n
for (int i = 0; i & targets.L ++i)\n
possibleTargets[i] = targets[i].\n
\u002F\u002F 重载函数,Behavior Designer专用的Update\n
public override TaskStatus OnUpdate()\n
\u002F\u002F 判断目标是否在视野内,这个返回值TaskStatus很关键,会影响树的执行流程\n
for (int i = 0; i & possibleTargets.L ++i)\n
if (withinSight(possibleTargets[i], fieldOfViewAngle, 10))\n
\u002F\u002F 将目标信息填写到共享变量里面,这样其它Action就可以访问它们了\n
target.Value = possibleTargets[i];\n
targetPos.Value = target.Value.\n
Debug.Log(\&Find Target\& + targetPos.Value);\n
\u002F\u002F 成功则返回 TaskStatus.Success\n
return TaskStatus.S\n
\u002F\u002F 没找到目标就在下一帧继续执行此任务\n
return TaskStatus.R\n
\u002F\u002F 判断物体是否在视野范围内的方法\n
public bool withinSight(Transform targetTransform, float fieldOfViewAngle, float distance)\n
Vector3 direction = targetTransform.position - transform.\n
if (direction.magnitude & distance)\n
return Vector3.Angle(direction, transform.forward) & fieldOfViewA\n
}\n}\n\u003C\u002Fcode\u003E\u003Cp\u003EWithInSight是一种判断条件,而不是实际的行为动作,所以继承了Conditional,这种继承表示了对Behavior的扩展。重点函数是OnAwake和OnUpdate,这个有别于MonoBehavior,是Behavior Designer插件专用的。写好了这段代码之后,再到行为树窗口里去,就多了一个选项:\u003C\u002Fp\u003E\u003Cimg src=\&v2-2abe621cc628d831cb26cb.png\& data-rawwidth=\&684\& data-rawheight=\&398\&\u003E\u003Cp\u003E顺理成章,把该连的线连起来。\u003C\u002Fp\u003E\u003Cimg src=\&v2-29942d98eefb68b0ac7aa1f1a7ebfb34.png\& data-rawwidth=\&199\& data-rawheight=\&272\&\u003E\u003Cp\u003E\u003Cb\u003E现在你大概知道Behavior Designer的基本玩法了,简单来说就是用Decorator(修饰器)和Composites(组合器)搭逻辑框架,然后自定义Conditional(条件)和Action(动作)来实际判断和实际做出行为,仅此而已。\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E现在进行测试,在玩家靠近AI时,应该能触发Debug.Log,如果OK的话,说明你迈出了第一步。\u003C\u002Fp\u003E\u003Cp\u003E这还没完,咱们处理一下代码中的2个shared变量。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E4、关于Shared变量的介绍\u003C\u002Fp\u003E\u003Cp\u003E注意代码中的这两个变量:\u003C\u002Fp\u003E\u003Ccode lang=\&csharp\&\u003E\u002F\u002F 发现目标时,将目标对象设置到BahaviorTree共享变量里面去\n
public SharedT\n
public SharedVector3 targetP\u003C\u002Fcode\u003E\u003Cp\u003ESharedXXXX类型代表这个变量虽然是在这个类中定义的,但是在正确绑定以后,其他Action或者Conditional也可以访问到。简单来说,它们就是专门用在Behavior Designer内部的变量。对这种变量不仅要在代码里声明,还要在Behavior Designer窗口里进行正确设置。\u003C\u002Fp\u003E\u003Cimg src=\&v2-50a8fe1be225a599ef5ec8.png\& data-rawwidth=\&642\& data-rawheight=\&348\&\u003E\u003Cp\u003E之前咱们直接画图了,没有用到这里的四个窗口。介绍一下\u003C\u002Fp\u003E\u003Cp\u003E(1)
Behavior 状态树整体的名称和属性,对咱们的小项目来说默认就行,不用管。\u003C\u002Fp\u003E\u003Cp\u003E(2)
Tasks所有Conditional(条件)和Action(动作)的列表,按照我的开发习惯,较少用到内置的条件和动作。理由是内置动作功能太单一,组合起来树状图会变得极其复杂,还不如用代码清晰。这个问题见仁见智了。\u003C\u002Fp\u003E\u003Cp\u003E(3)
Variables变量窗口,接下来咱们主要介绍这个。\u003C\u002Fp\u003E\u003Cp\u003E(4)
行为树节点的Inspector,是针对某个节点的详细属性。在这里面不仅可以设置参数,还能绑定变量,接下来也要用到。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E5、添加Shared变量\u003C\u002Fp\u003E\u003Cimg src=\&v2-da867be374df291ed9e6fe.png\& data-rawwidth=\&529\& data-rawheight=\&141\&\u003E\u003Cp\u003E只要切换到Variables变量窗口,输入变量名称,选择咱们脚本里定好的类型,然后Add即可。Add之后如下图:\u003C\u002Fp\u003E\u003Cimg src=\&v2-671b42c99ba03d0bfc7f2fb0.png\& data-rawwidth=\&685\& data-rawheight=\&236\&\u003E\u003Cp\u003E已经添加好了,其他参数都不要变,红圈也不要改,因为这个变量的赋值是由脚本负责的。\u003C\u002Fp\u003E\u003Cp\u003E添加好另一个变量TargetPos:\u003C\u002Fp\u003E\u003Cimg src=\&v2-adc4b913864.png\& data-rawwidth=\&534\& data-rawheight=\&260\&\u003E\u003Cp\u003E我们要让AI角色发现敌人时,记住他的transform和位置,以便后续处理,这些变量就和他的大脑记忆一样。所以,要把WithInSight节点和这些变量关联起来。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E6、关联节点和变量\u003C\u002Fp\u003E\u003Cimg src=\&v2-4d8ec2fdc49.png\& data-rawwidth=\&693\& data-rawheight=\&372\&\u003E\u003Cp\u003E点击WithInSight节点,点击Inspector,可以看到这个节点的所有属性。普通的属性就直接设定初始值就ok了,比如第一个属性可视范围是45。重点是对Shared变量进行绑定操作,现在是红色的None。\u003C\u002Fp\u003E\u003Cp\u003E如果这里和我的截图不一致,就点击黑色圆点,切换一下绑定方式。如下图操作:\u003C\u002Fp\u003E\u003Cimg src=\&v2-640ec88bff41dd1cc08697.png\& data-rawwidth=\&689\& data-rawheight=\&182\&\u003E\u003Cp\u003E这样就能把Variables窗口里刚才新建的Target变量,和WithinSight判断中的Target变量彻底联系起来。同理对TargetPos也要做同样操作。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E7、试着加一个动作,进行试验\u003C\u002Fp\u003E\u003Cp\u003E增加一个动作AimAction瞄准动作,实际上就是转向Target的方向即可,如果AI能转向敌人方向,就代表我们的绑定成功了。先按咱们第3步也就是创建WithinSight的方法,创建一个脚本叫AimAction.cs,内容如下:\u003C\u002Fp\u003E\u003Ccode lang=\&csharp\&\u003Epublic class AimAction : Action\n{\n
public SharedT\n\n
\u002F\u002F 是否正在面对入侵者,即已经正确瞄准\n
bool IsFacingTarget()\n
if (target.Value == null)\n
Vector3 v1 = target.Value.position - transform.\n
v1.y = 0;\n
if (Vector3.Angle(transform.forward, v1) & 1)\n
\u002F\u002F 转向入侵者方向,每次只转一点,速度受turnSpeed控制\n
void RotateToTarget()\n
if (target.Value == null)\n
Vector3 v1 = target.Value.position - transform.\n
v1.y = 0;\n
Vector3 cross = Vector3.Cross(transform.forward, v1);\n
float angle = Vector3.Angle(transform.forward, v1);\n
transform.Rotate(cross, Mathf.Min(2, Mathf.Abs(angle)));\n
public override void OnAwake()\n
public override TaskStatus OnUpdate()\n
if (IsFacingTarget())\n
\u002F\u002F 返回值不同对状态树会产生巨大影响,可以对比测试\n
\u002F\u002Freturn TaskStatus.S\n
return TaskStatus.R\n
RotateToTarget();\n
return TaskStatus.R\n
}\n}\n\u003C\u002Fcode\u003E\u003Cp\u003E然后修改行为树的图,添加AimAction和一些联系用的Composite节点,改为下面的形式:\u003C\u002Fp\u003E\u003Cimg src=\&v2-c74b1f0f19b7df9d2680.png\& data-rawwidth=\&384\& data-rawheight=\&287\&\u003E\u003Cp\u003E中间的Sequence代表下面的两个子节点依次执行,UntilSuccess构成一个局部的反复执行逻辑,AI会在左边的子节点重复,直到发现敌人,Until节点中断,执行Aim瞄准动作。\u003C\u002Fp\u003E\u003Cp\u003E别忘了给AimAction节点也绑定两个Shared变量。如果发现像下图这样,和之前说好的不一样,就点击黑色圆点,切换一下绑定方式。点击黑点实际上是切换两种不同的变量使用方式。\u003C\u002Fp\u003E\u003Cimg src=\&v2-e7a97f0ae9bfd8f6496783.png\& data-rawwidth=\&384\& data-rawheight=\&120\&\u003E\u003Cp\u003E现在,如果你操作没错,那么播放游戏,看看敌人是否在发现你之后,就瞄准你:\u003C\u002Fp\u003E\u003Cimg src=\&v2-018d55b6ba3d5dda59d196c.png\& data-rawwidth=\&857\& data-rawheight=\&427\&\u003E\u003Cp\u003E如上图,不仅AI角色能正确瞄准主角,而且在Behavior Designer窗口中,还能实时看到目前逻辑进行的状态,这个是Behavior Designer插件威力最大的功能之一——查看逻辑进展状态,将AI思考过程可视化(符合咱们第四节讲的原则 :D)。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E8、扩展实现所有功能\u003C\u002Fp\u003E\u003Cp\u003E老师领进门,修行在个人。所有基本功能都介绍完毕,至于实际的使用方法需要大家自己分析一下了。\u003C\u002Fp\u003E\u003Cp\u003E我用Behavior Designer重现了咱们第三节讲过的内容,得到了很好的效果,而且很好修改。\u003C\u002Fp\u003E\u003Cimg src=\&v2-84d5f3e4c5860bea87a7.png\& data-rawwidth=\&769\& data-rawheight=\&525\&\u003E\u003Cp\u003E不要被吓着了哦,这是一步一步做了很久的最终效果。而且虽然节点很多,我加上注释之后,其实也就分三大块而已,看起来还是有状态机的影子,不难理解。\u003C\u002Fp\u003E\u003Cimg src=\&v2-bc7ce47bb8af0.png\& data-rawwidth=\&737\& data-rawheight=\&404\&\u003E\u003Cp\u003E如上图,和咱们讲行为树原理时用的概念图基本是一致的。某些Composite前面没讲,下一篇文章会继续深入,当然自己查资料试验才是最好的学习方法。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E3、总结\u003C\u002Fh2\u003E\u003Cp\u003E首先,读者如果做下来的话,建议再回到本文开头,看一下我画的状态机和行为树对比图,加深一下理论印象,而且如果我的概念图不好的话,欢迎提出自己的看法写在评论里。状态机和行为树的问题,属于工程问题,不是科学问题,每个人会找到自己的理解,而且还有可能发现更厉害的抽象方法。\u003C\u002Fp\u003E\u003Cp\u003E对于工程问题来说,就和写代码一样,有很多要点:\u003C\u002Fp\u003E\u003Cp\u003E1、细节多且杂,函数返回值、Composite的使用这些细节的设计决定了成败。\u003C\u002Fp\u003E\u003Cp\u003E2、除非自己动手试验,发现问题、解决问题,否则不可能掌握。\u003C\u002Fp\u003E\u003Cp\u003E所以,我会在之后再次深入讨论Behavior Designer。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E我自己开始写这个例子的时候,光比较Unity不同的行为树插件之间的区别(现在Asset Store里面的同类插件非常多),就花费了两天时间,最终根据资料数量、插件功能确定了Behavior Designer是工程的首选方案。然后翻官方文档和前人总结的博客资料,才慢慢理解了行为树的大致用法。\u003C\u002Fp\u003E\u003Cp\u003E在探索过程中会走很多弯路,现在将我的经验总结成此文,为初学者解决刚开始的那种迷茫的状态会非常有用。\u003C\u002Fp\u003E\u003Cp\u003E行为树的实际使用博大精深,远远不是几篇文章能覆盖到的。毕竟AI是游戏开发中最庞大的系统之一,而行为树又是AI的核心。希望读者们能理解方法,体会乐趣,不要过早陷入技术细节之中。\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E咱们下一节还是继续行为树,下期再见。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E工程地址:\u003Ca href=\&https:\u002F\u002Fgithub.com\u002Fmayao11\u002FPracticalGameAI\u002Ftree\u002Fmaster\u002FAI_Enemy3_Behavior_basic\&\u003Emayao11\u002FPracticalGameAI\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Chr\u003E\u003Cp\u003E对游戏开发感兴趣的同学,欢迎围观我们:【皮皮关游戏开发教育】 ,会定期更新各种教程干货,更有别具一格的线下小班教育。在你学习进步的路上,有皮皮关陪你!~\u003C\u002Fp\u003E\u003Cp\u003E我们的官网地址:\u003Cu\u003E\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Flevelpp.com\u002F\&\u003Ehttp:\u002F\u002Flevelpp.com\u002F\u003C\u002Fa\u003E\u003C\u002Fu\u003E\u003C\u002Fp\u003E\u003Cp\u003E我们的游戏开发技术交流群:\u003C\u002Fp\u003E\u003Cp\u003E我们的微信公众号:皮皮关\u003C\u002Fp\u003E&,&updated&:new Date(&T09:42:16.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:16,&likeCount&:35,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T17:42:16+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\u002Fpic4.zhimg.com\u002Fv2-b339d999bed3eb3e11314_r.jpg&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:16,&likesCount&:35},&&:{&title&:&【Unity】TimeLine&Cinemachine系列教程——动作特写!&,&author&:&chen-hong-song-42&,&content&:&\u003Cp\u003E\u003Cb\u003E前言:\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E上一篇简单的对TimeLine功能有了简单的了解,这一期依然从TimeLine的特性入手,用引擎自身的功能完成我们该篇目的——动作特写。这里会介绍到一款专业的镜头插件Cinemachine,可以很方便的制作镜头跟踪和切换的效果。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E(该篇动图较多,先做流量预警-。-)\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E效果预览:\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-ffb60eababe6a.gif\& data-rawwidth=\&702\& data-rawheight=\&391\& data-thumbnail=\&https:\u002F\u002Fpic3.zhimg.com\u002Fv2-ffb60eababe6a_b.jpg\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E首先先介绍今天的特约嘉宾——Cinemachine\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E先看下他的能力:\u003C\u002Fp\u003E\u003Cp\u003E1.跟踪功能\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-96faecd08bb5ffbb73f5d.gif\& data-rawwidth=\&808\& data-rawheight=\&274\& data-thumbnail=\&https:\u002F\u002Fpic2.zhimg.com\u002Fv2-96faecd08bb5ffbb73f5d_b.jpg\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E2.自由视角\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-626420dcd24cf1a5f5bd6d084cac693c.gif\& data-rawwidth=\&710\& data-rawheight=\&262\& data-thumbnail=\&https:\u002F\u002Fpic1.zhimg.com\u002Fv2-626420dcd24cf1a5f5bd6d084cac693c_b.jpg\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E3.轨道相机\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-9d98d6df5bea2b763377.gif\& data-rawwidth=\&839\& data-rawheight=\&369\& data-thumbnail=\&https:\u002F\u002Fpic4.zhimg.com\u002Fv2-9d98d6df5bea2b763377_b.jpg\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E4.多个目标处理\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-7f8b85fc444cda04fcd6da429a53f57f.gif\& data-rawwidth=\&946\& data-rawheight=\&369\& data-thumbnail=\&https:\u002F\u002Fpic4.zhimg.com\u002Fv2-7f8b85fc444cda04fcd6da429a53f57f_b.jpg\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E当然能力不仅仅是这些,他能很方便解决多个镜头切换之间插值效果,和智能选择镜头机位。这里就先不多细讲了。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E先看一下他的组件\u003C\u002Fb\u003E:\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E创建一个虚相机\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-df094b28e9f0e4c78f90510.png\& data-rawwidth=\&324\& data-rawheight=\&191\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-50163dbe8e7afc8fa21037ddd022cbad.png\& data-rawwidth=\&225\& data-rawheight=\&302\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-1a5bbd3ef5e692fbe2cf41.png\& data-rawwidth=\&439\& data-rawheight=\&423\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u002F*Cinemachine的所有镜头处理都是一个虚拟相机控制游戏中的主摄像机来实现的,意思是整个游戏内不会创建新的摄像机*\u002F\u003C\u002Fp\u003E\u003Cp\u003E参数介绍:\u003C\u002Fp\u003E\u003Cp\u003EPriority:优先级,当有多个摄像机存在时,选用优先级最高的摄像机。\u003C\u002Fp\u003E\u003Cp\u003ELookAt:摄像机观察的物体\u003C\u002Fp\u003E\u003Cp\u003EFollow:摄像机跟随的物体\u003C\u002Fp\u003E\u003Cp\u003ELens:摄像机的常规设置,这里就不多说了。\u003C\u002Fp\u003E\u003Cp\u003EAim:主要是修改摄像机观察物体在镜头内的位置\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-25cd989c5cc.png\& data-rawwidth=\&576\& data-rawheight=\&325\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-1d218c069fa.png\& data-rawwidth=\&415\& data-rawheight=\&271\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E先说基本的:\u003C\u002Fp\u003E\u003Cp\u003EDamping:可以设置水平和竖直方向上的阻尼效果\u003C\u002Fp\u003E\u003Cp\u003EScreenXY:屏幕中心位置,一般都不会修改\u003C\u002Fp\u003E\u003Cp\u003EBiasXY:SoftZone的偏移位置\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E关于DeadZone和SoftZone多说无益直接看修改效果:\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-28f204ddc5195eadcdbacd.gif\& data-rawwidth=\&968\& data-rawheight=\&406\& data-thumbnail=\&https:\u002F\u002Fpic2.zhimg.com\u002Fv2-28f204ddc5195eadcdbacd_b.jpg\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E可以理解摄像机的一个无反应区域DeadZone(转向死间,即无反应区), 摄像机在DeadZone区域外将会发生位移,让目标点始终处于DeadZone中。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-c2fca8bcf2a94fcc44011afc4fe48129.gif\& data-rawwidth=\&888\& data-rawheight=\&310\& data-thumbnail=\&https:\u002F\u002Fpic2.zhimg.com\u002Fv2-c2fca8bcf2a94fcc44011afc4fe48129_b.jpg\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003ESoftZone则是当目标是在这个地区,相机将逐渐重新调整到理想的位置,根据系统的阻尼速度。\u003C\u002Fp\u003E\u003Cp\u003EBody:\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-ef0e7d9be746bcfd8f1ae2b.png\& data-rawwidth=\&333\& data-rawheight=\&141\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003EFollowOffset:跟随位置的偏移,比如我设置跟随物体为A角色,摄像机偏移A角色到他上方2米后方3米的位置\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-dd81eb4ca19b1b82c85a6e.gif\& data-rawwidth=\&741\& data-rawheight=\&302\& data-thumbnail=\&https:\u002F\u002Fpic3.zhimg.com\u002Fv2-dd81eb4ca19b1b82c85a6e_b.jpg\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003EDamping:物体跟随的阻尼效果\u003C\u002Fp\u003E\u003Cp\u003EBinding Mode:\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-4d6b8c0e669ceb3ff098f.png\& data-rawwidth=\&359\& data-rawheight=\&142\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E如果摄像机要根据观察目标的坐标旋转变换做变换,比如旋转,就需要修改BindingMode为Lock To Target\u003C\u002Fp\u003E\u003Cp\u003E修改后,模型旋转会带动摄像机旋转\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-817ad9c94e87c8ddbe5138.gif\& data-rawwidth=\&481\& data-rawheight=\&299\& data-thumbnail=\&https:\u002F\u002Fpic1.zhimg.com\u002Fv2-817ad9c94e87c8ddbe5138_b.jpg\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E最后一个为噪音功能,先不做细讲。可以用于模拟手持摄像机的感觉。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E好了,到这里各位应该对Cinemachine有了初步的了解。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E实战开始~\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E初始镜头创建\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E首先创建一个群组目标的摄像机\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-ef84a922e768f66cf71c.png\& data-rawwidth=\&306\& data-rawheight=\&193\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E创建好后会出现两个物体\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-80e253ee8c82c2cc69d3298eafabeb9e.png\& data-rawwidth=\&231\& data-rawheight=\&42\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E我们在targerGroup选择要出现在摄像机内的两个模型目标\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-ad418c7ebc4d2b93f5b03ed.png\& data-rawwidth=\&342\& data-rawheight=\&297\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E可以修改摄像机主要偏向的权重,这里用平均的就可以。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-665d290a177bcd0e8e34.png\& data-rawwidth=\&322\& data-rawheight=\&520\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E之后将摄像机看向targerGroup物体,调节body偏移位置。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-e0b54d838b54a8df0652.png\& data-rawwidth=\&406\& data-rawheight=\&318\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E场景中能直接看到设置的效果\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-a53aa344e780aef8f23c.png\& data-rawwidth=\&785\& data-rawheight=\&614\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E我们改变了下物体的位置,发现距离过近后摄像机会非常靠前。我们可以修改CM vcam1上的Minmum Distance为2,就可以防止出现摄像机过近的问题。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-1dfbb5e6fbc2bb956afdf5a.png\& data-rawwidth=\&780\& data-rawheight=\&426\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003ETimeLine动画创建\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E不管做任何事情都需要有调理的去做,因此最好能先创建好TrackGroup分个组,然后我们先创建好主角的动画。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-e8ab70293eb52bba54046c8.png\& data-rawwidth=\&278\& data-rawheight=\&191\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E我们将要播放的动画直接拖到动画轨道上就可以,而且还可以处理融合。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-d145af10ba38fdb6c8f42feec8d0cb89.png\& data-rawwidth=\&888\& data-rawheight=\&63\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E但是当角色移动和跳跃如果利用动画本身来做有时候不好调整打击时间。所以我们就要利用到Override功能。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-2cfe0ff4c02df697bab7da.png\& data-rawwidth=\&562\& data-rawheight=\&257\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E它可以很方便的在角色播放动画的时候,再对角色进行动画覆盖,比如角色正在播放跑步动画,我们可以覆盖上移动的动画,实现角色跑向目标。\u003C\u002Fp\u003E\u003Cp\u003EOverride功能的时间轨道双击就成了我们经常用到的Animation编辑器。在这里调节好不同时间角色位置,实现TimeLine播放时的位置匹配。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-26ec4ebd5ef5b2b068a20.png\& data-rawwidth=\&965\& data-rawheight=\&154\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003EAnimation里面的调节也很方便的查看每帧调节的效果\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-209cc135dce816bd9190c3.gif\& data-rawwidth=\&653\& data-rawheight=\&435\& data-thumbnail=\&https:\u002F\u002Fpic4.zhimg.com\u002Fv2-209cc135dce816bd9190c3_b.jpg\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E之后再做一个敌人配合Unity娘的殴打,同样的需要Override动画位移\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-01bca079d709b.png\& data-rawwidth=\&1087\& data-rawheight=\&95\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E目前的效果\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-9e7edecfe88b8b6e6eac31.gif\& data-rawwidth=\&653\& data-rawheight=\&361\& data-thumbnail=\&https:\u002F\u002Fpic2.zhimg.com\u002Fv2-9e7edecfe88b8b6e6eac31_b.jpg\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E这里有个动画知识点需要扩展:\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E很多模型的动画都会有修改骨骼点位置,Unity的Animator有一个选项\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-24e2a77bc0fa28a10eac72de86f385e7.png\& data-rawwidth=\&325\& data-rawheight=\&199\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E勾选后模型的的位置会跟随骨骼根节点的位置移动,但是有时候我们不想让动画直接修改物体的位移变化,比如我们要自定义跳跃的高度和位移距离,这里最好就要屏蔽掉动画的位移。\u003C\u002Fp\u003E\u003Cp\u003E怎么屏蔽呢?将模型动画类型改为人形骨骼。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-03cb21dd480f898aace57.png\& data-rawwidth=\&333\& data-rawheight=\&154\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E在Animations选项中可以看到,Root Transforem XXX的选项,分别对应是否会旋转、高度变化、XZ平面变化。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-68a2d766b.png\& data-rawwidth=\&314\& data-rawheight=\&427\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E我这里屏蔽了升龙拳的前后位移变化,这样我们就可以自己定义动画的距离。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003ETimeLine镜头控制\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E点击Add创建Cinemachine\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-0be0eb2fa74aa28.png\& data-rawwidth=\&302\& data-rawheight=\&208\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E先把我们的默认摄像机放入轨道\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-bcc0c5d9d51.png\& data-rawwidth=\&561\& data-rawheight=\&87\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E创建一个新的摄像机,这个摄像机选好位置后一直看向Unity娘~\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-16d9f1eb79cf0fd0cc49f9.png\& data-rawwidth=\&331\& data-rawheight=\&426\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E同样拖入轨道\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-cee4b5beecc3abac22536c.png\& data-rawwidth=\&810\& data-rawheight=\&83\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003ETimeLine会将摄像机重叠的地方做插值,这样镜头切换就显得非常平滑。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-0c0b5df687b6b5b8e6964f37dafbf387.gif\& data-rawwidth=\&653\& data-rawheight=\&584\& data-thumbnail=\&https:\u002F\u002Fpic4.zhimg.com\u002Fv2-0c0b5df687b6b5b8e6964f37dafbf387_b.jpg\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E特效添加\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E之前找半天没找到特效添加的地方,结果发现TimeLine是用Control Track控制轨道来实现的。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-3e1b8ea4ce8.png\& data-rawwidth=\&263\& data-rawheight=\&194\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E给轨道添加一个Clip来控制资源\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-2e04adaadbc3.png\& data-rawwidth=\&636\& data-rawheight=\&243\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E可以看到Control属性是这样的,先简单介绍一下属性意义\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-39ad9da19da.png\& data-rawwidth=\&330\& data-rawheight=\&442\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003ESource Game Object:用做选择场景内的物体,选择的物体将会在TimeLine运行到该Clips时激活。\u003C\u002Fp\u003E\u003Cp\u003E(如果直接用物体激活来做,不能在编辑模式下看到粒子的变化)\u003C\u002Fp\u003E\u003Cp\u003EPrefab:选择项目的预制体资源,如果选择好预制体后,Source Game Object会修改为Parent Object,代表生成的预制体将会放在哪个场景物体下。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-1a255b0abc8ee4c41dfbb8d.png\& data-rawwidth=\&305\& data-rawheight=\&64\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E加入的特效可以来回拖动看实际效果\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-b9f9f2d046fbd034de1cca.gif\& data-rawwidth=\&691\& data-rawheight=\&514\& data-thumbnail=\&https:\u002F\u002Fpic2.zhimg.com\u002Fv2-b9f9f2d046fbd034de1cca_b.jpg\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E时间缩放\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E动作游戏里面增加打击感经常会用到时间缩放,同时TimeLine里面目前没有找到可以加速播放的地方,所以就需要先手写一个时间缩放功能。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Eusing System.C\nusing System.Collections.G\nusing UnityE\nusing UnityEngine.P\nusing UnityEngine.T\n\npublic class PlayableTime : BasicPlayableBehaviour\n{\n\n
\u002F\u002F\u002F &summary&\n
\u002F\u002F\u002F 曲线\n
\u002F\u002F\u002F &\u002Fsummary&\n
public AnimationCurve mC\n
\u002F\u002F\u002F &summary&\n
\u002F\u002F\u002F 当前时间进度\n
\u002F\u002F\u002F &\u002Fsummary&\n
private float curT\n
\u002F\u002F\u002F &summary&\n
\u002F\u002F\u002F 最大时间\n
\u002F\u002F\u002F &\u002Fsummary&\n
private float maxT\n\n\n\tpublic override void OnGraphStart(Playable playable) {\n\n\t}\n\n\n\tpublic override void OnGraphStop(Playable playable) {\n
Time.timeScale = 1;\n\n
public override void OnBehaviourPlay(Playable playable, FrameData info)\n
maxTime= (float)PlayableExtensions.GetDuration(playable);\n
}\n\n\n\tpublic override void OnBehaviourPause(Playable playable, FrameData info) {\n\n
public override void PrepareFrame(Playable playable, FrameData info)\n
curTime += info.deltaT\n
\u002F\u002F曲线值\n
Time.timeScale = mCurve.Evaluate(curTime\u002F maxTime);\n
}\n}\n\n\n
\u002F\u002F Called each frame while the state is set to Play\n
public override void PrepareFrame(Playable playable, FrameData info)\n
curTime += info.deltaT\n
Time.timeScale = mCurve.Evaluate(curTime\u002F maxTime);\n
}\n}\u003C\u002Fcode\u003E\u003Cp\u003E写好后添加到PlayableTrack ,\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-dc6b421975.png\& data-rawwidth=\&257\& data-rawheight=\&189\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E添加我们的PlayableTime作为Clips,曲线值作为我们的时间缩放值。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-fdeaabb08f81af2af9ae741.png\& data-rawwidth=\&341\& data-rawheight=\&336\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E将我们正在踢到敌人的那一帧时间缩放为0.01倍时间,实现顿帧效果。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-ba41fe834de7ad304b84a.png\& data-rawwidth=\&609\& data-rawheight=\&227\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E实现的时间瞬间静止的感觉\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-2b7fe1ab5a4a802c50fbf38.gif\& data-rawwidth=\&496\& data-rawheight=\&361\& data-thumbnail=\&https:\u002F\u002Fpic1.zhimg.com\u002Fv2-2b7fe1ab5a4a802c50fbf38_b.jpg\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u002F*这里不得不提一下,我们自定义的Clips也能再TimeLine里面实现插值效果,Time.timeScale的效果会在两个Clips之间融合变化。(简直非常方便到炸裂)*\u002F\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-f100c64f91cdb82af44e18ce50407d25.png\& data-rawwidth=\&666\& data-rawheight=\&41\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E总结\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003ETimeLine结合Cinemachine可以实现很棒的影视游戏效果,而且方便在编辑的时候直接看效果,做到来回拖动看细节,同时还能解决片段与片段之间的融合插值效果。调节起来非常的方便,而且自定义的脚本也很容易放入TimeLine中做效果。\u003C\u002Fp\u003E\u003Cp\u003E而如果拿TimeLine做游戏开发还需要脚本动态修改TimeLine的值,不然就算能做一个非常好的游戏效果,不能运用再实际项目中,一切都是空谈。\u003C\u002Fp\u003E\u003Cp\u003E下一篇将会讲解代码控制TimeLine,如何实现动态修改TimeLine的参数,让TimeLine嵌入我们的游戏之中,尽请期待~\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E附上源码:\u003C\u002Fp\u003E\u003Cp\u003E\u003Cu\u003E\u003Ca href=\&https:\u002F\u002Fgithub.com\u002Fchs7FTimeLine\&\u003Ehttps:\u002F\u002Fgithub.com\u002Fchs7FTimeLine\u003C\u002Fa\u003E\u003C\u002Fu\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E对游戏开发感兴趣的同学,欢迎围观我们:【皮皮关游戏开发教育】 ,会定期更新各种教程干货,更有别具一格的线下小班教育。在你学习进步的路上,有皮皮关陪你!~\u003C\u002Fp\u003E\u003Cp\u003E我们的官网地址:\u003Cu\u003E\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Flevelpp.com\u002F\&\u003Ehttp:\u002F\u002Flevelpp.com\u002F\u003C\u002Fa\u003E\u003C\u002Fu\u003E\u003C\u002Fp\u003E\u003Cp\u003E我们的游戏开发技术交流群:\u003C\u002Fp\u003E\u003Cp\u003E我们的微信公众号:皮皮关\u003C\u002Fp\u003E&,&updated&:new Date(&T02:59:41.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:22,&likeCount&:74,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T10:59:41+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\u002Fpic2.zhimg.com\u002Fv2-fbff130f2d7a_r.jpg&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:22,&likesCount&:74},&&:{&title&:&【Unity】TimeLine&Cinemachine系列教程——动态赋值,我要打十个!&,&author&:&chen-hong-song-42&,&content&:&\u003Cp\u003E\u003Cb\u003E前言\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003ETimeLine在上一篇介绍了TimeLine的各个功能特性,已经用TimeLine做了一个固定的特写镜头。但是要用好TimeLine还得去学会如何去掌控它,因此今天主要内容之一是用代码赋值修改TimeLine中轨道的参数(为了能更好的做出效果,几乎快做了一个格斗游戏了,想做动作游戏想的热血沸腾~,最后想着还要出教程,还是停下来老老实实做功能)。\u003C\u002Fp\u003E\u003Cp\u003E前前后后遇到了很多坑点,TimeLine有许多没法力所能及的地方,在这篇文章结尾会将主要的问题写出来,避免大家继续踩坑。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E最终效果\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-51cf6c69ec54eeba79eb7e6d7f393997.gif\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&389\& data-rawheight=\&218\& data-thumbnail=\&https:\u002F\u002Fpic3.zhimg.com\u002Fv2-51cf6c69ec54eeba79eb7e6d7f393997_b.jpg\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003ETrack赋值\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E创建了TimeLine的物体会有一个绑定列表\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-a63b81d5c58b4bd6151b.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&369\& data-rawheight=\&398\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E绑定列表对应我们做TimeLine各个控制轨道\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-fa2a9fa2b5.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&530\& data-rawheight=\&125\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E我们是可以对轨道修改名字的,这个修改后的名字就是Bindings的key值。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-08fbf0ecf9aff8a2c7eb7c6221dfffcd.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&376\& data-rawheight=\&210\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E脚本中可以获取到PlayableDirector类,通过SetGenericBinding函数赋值物体。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E
var mDirector = new PlayableD\n
mDirector.SetGenericBinding(\&轨道名字\&,\&轨道绑定的物体\&);\u003C\u002Fcode\u003E\u003Cp\u003E这一步是比较简单的,完成这一步后,我们可以创建两个动画,在游戏中触发TimeLine后,动态给轨道赋值。比如给攻击动画轨道赋值玩家模型,给受击动画轨道赋值怪物模型。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u002F*有的读者可能会问,我们如果做攻击和被击效果的话可以直接把动作放在角色控制器里面,没必要用TimeLine实现。的确是这样,但是如果游戏中模型动作资源太多,比如战神这款游戏,每一个精英怪都会有一个动作特写,如果一开始全部放在动画控制器中,会造成动画十分混乱。而使用TimeLine,相当于我们动态给模型加载动画,当使用到这个动画的时候再加载它,这样就会让动画控制器非常的干净简洁,也节约了动画占用的内存*\u002F\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003ETimeLineClip赋值\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E接下来我们会动态给轨道内的Clip赋值,比如说Cinemachine的摄像机,和摄像机观察的物体、跟随的物体。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-8fd8df3b07dc7d4aa4cfda65fccfcb63.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&360\& data-rawheight=\&325\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-cb86465ffbe52f7199fefee6e044cf15.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&366\& data-rawheight=\&615\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E获取到Clip的代码我是做了一个字典来在playableAsset.outputs输出信息中储存了所有轨道信息。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E
public void Init()\n
foreach (var at in mDirector.playableAsset.outputs)\n
if (!bindingDict.ContainsKey(at.streamName))\n
bindingDict.Add(at.streamName, at);\n
var CinemachineTrack = bindingDict[\&Cinemachine\&].sourceObject as Cinemachine.Timeline.CinemachineT\n\n
foreach (var clip in CinemachineTrack.GetClips())\n
\u002F\u002FTODO\n
}\u003C\u002Fcode\u003E\u003Cp\u003E而没有用GetGenericBinding函数是因为传入的值是Object类型,目前没找到一个有效传入数据。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-cac33ed1c234e0d5b85bcd50baf8782a.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&652\& data-rawheight=\&93\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E给Clip赋值有几个重要的地方,一是Clip的名字是TimeLineClip的displayName,TimeLineClip.asset是片段资源,\u003Cb\u003E二是赋值的时候得创建ExposedReference类型的结构体,不然直接赋值是没法生效的。\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E
public void Init()\n
foreach (var info in CinemachineTrack.GetClips())\n
if (info.displayName == \&CinemachineShot\&)\n
var cameraInfo = info.asset as Cinemachine.Timeline.CinemachineS\n
var vcam1 = GameObject.Find(\&CM vcam1\&).GetComponent&CinemachineVirtualCameraBase&();\n
var setCam = new ExposedReference&CinemachineVirtualCameraBase&();\n
setCam.defaultValue = vcam1;\n
cameraInfo.VirtualCamera = setC\n
}\u003C\u002Fcode\u003E\u003Cp\u003EClip如果赋值完毕后,要修改Clip的参数,必须解析playableGraph,不然修改的目标并不是实际游戏中的物体。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E
public void Init()\n
var cameraInfo = info.asset as Cinemachine.Timeline.CinemachineS\n
var vcam2 = cameraInfo.VirtualCamera.Resolve(mDirector.playableGraph.GetResolver());\n
vcam2.LookAt = mControl.transform.Find(\&Tran_Chest\&).\n
vcam2.Follow = mControl.\n
}\n\u003C\u002Fcode\u003E\u003Cp\u003E这里有坑点:playableGraph是TimeLine运行时才会创建生存,否则为空。因此我们修改的这些值实际是 TimeLine已经播放的时候修改的。如果TimeLine在OnGraphStart函数中用了的值,我们这里修改后是无效的。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E编写触发脚本\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E做这个之前有一个小插曲,本来我是想用TimeLine的自定义脚本轨道来搞定被抓取后怪物的位置修正的,结果发现不能用TimeLine来做,因为TimeLine的赋值修改会直接对所有用到这个TimeLine资源的物体一起赋值(这个bug目前没有找到解决方法),所以用自定义脚本轨道上处理位置修正,就会保留当前的目标信息,下次抓另一个怪物的时候就会两个物体一起运动(神奇的bug)。\u003C\u002Fp\u003E\u003Cp\u003E所以我直接把抓取的时候位置修正代码和触发TimeLine的代码一起了,代码如下:\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Eusing System.C\nusing System.Collections.G\nusing UnityE\nusing UnityEngine.E\nusing UnityEngine.T\nusing UnityEngine.P\nusing Cinemachine.T\nusing C\n\npublic class KillControl : MonoBehaviour\n{\n
public GameObject mKillI\n
[HideInInspector]\n
public PlayableDirector mD\n
Dictionary&string, PlayableBinding& bindingDict = new Dictionary&string, PlayableBinding&();\n
public CharaControl playerC\n
private CharaControl mC\n
public Vector3 offsetP\n
public Vector3 offsetR\n
public UnityAction OnFinishE\n\n\n
public void Start()\n
mControl = GetComponent&CharaControl&();\n\n
mKillInfo = Instantiate(mKillInfo);\n\n
mKillInfo.transform.SetParent(mControl.transform);\n\n
mDirector = mKillInfo.GetComponent&PlayableDirector&();\n\n
foreach (var at in mDirector.playableAsset.outputs)\n
if (!bindingDict.ContainsKey(at.streamName))\n
bindingDict.Add(at.streamName, at);\n
public void Play()\n
mControl.transform.position = playerControl.transform.position + playerControl.transform.rotation * offsetP\n
mControl.transform.rotation = Quaternion.LookRotation(playerControl.transform.position - mControl.transform.position);\n
mControl.transform.localEulerAngles += offsetR\n\n\n
mKillInfo.gameObject.SetActive(true);\n
mDirector.Play();\n\n
mDirector.SetGenericBinding(bindingDict[\&Player\&].sourceObject, playerControl.mAim);\n
mDirector.SetGenericBinding(bindingDict[\&Enemy\&].sourceObject, mControl.mAim);\n
mDirector.SetGenericBinding(bindingDict[\&Cinemachine\&].sourceObject, Camera.main.GetComponent&Cinemachine.CinemachineBrain&());\n
var CinemachineTrack = bindingDict[\&Cinemachine\&].sourceObject as Cinemachine.Timeline.CinemachineT\n\n
foreach (var info in CinemachineTrack.GetClips())\n
if (info.displayName == \&CinemachineShot\&)\n
var cameraInfo = info.asset as Cinemachine.Timeline.CinemachineS\n
var vcam1 = GameObject.Find(\&CM vcam1\&).GetComponent&CinemachineVirtualCameraBase&();\n
var setCam = new ExposedReference&CinemachineVirtualCameraBase&();\n
setCam.defaultValue = vcam1;\n
cameraInfo.VirtualCamera = setC\n
if (info.displayName == \&CM vcam2\&)\n
var cameraInfo = info.asset as Cinemachine.Timeline.CinemachineS\n
var vcam2 = cameraInfo.VirtualCamera.Resolve(mDirector.playableGraph.GetResolver());\n
vcam2.LookAt = mControl.transform.Find(\&Tran_Chest\&).\n
vcam2.Follow = mControl.\n
private void Update()\n
if (mDirector.gameObject.activeInHierarchy)\n
if (mDirector.state == PlayState.Paused)\n
mDirector.gameObject.SetActive(false);\n
if (OnFinishEvent != null)\n
OnFinishEvent();\n
}\n}\u003C\u002Fcode\u003E\u003Cp\u003E脚本功能很简单,Start里面创建好资源,并做了一个索引字典,Play的时候动态赋值。\u003Cb\u003E唯一要注意的是目前没找到TimeLine的结束回调事件\u003C\u002Fb\u003E,因此只能用判断运行状态来处理结束逻辑。为了精确时间,我将判断函数放在了Update中。\u003C\u002Fp\u003E\u003Cp\u003E将这个脚本挂载到怪物身上,设置好绑定的TimeLine和修正的位置、旋转信息。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-c0ffb2eee2a2.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&369\& data-rawheight=\&401\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E接下来只要我们特殊攻击打倒了怪物,我们就直接播放TimeLine:\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-57f25e97be123b44d9c3fb.gif\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&419\& data-rawheight=\&319\& data-thumbnail=\&https:\u002F\u002Fpic3.zhimg.com\u002Fv2-57f25e97be123b44d9c3fb_b.jpg\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E这里有必要说一下,在编辑状态里面,有时候会发现镜头里面动画位置信息效果不对。这是正常的,因为我们没有去Key位置,TimeLine没有记录位置信息的时候,编辑模式位置就会是一个位置点和方向。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-6b654b54afe.gif\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&547\& data-rawheight=\&531\& data-thumbnail=\&https:\u002F\u002Fpic2.zhimg.com\u002Fv2-6b654b54afe_b.jpg\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E我们可以修改动画的偏移,这样就可以在编辑模式下看到正常效果了\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-82d1cbd5ff5aef0fd59a.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&381\& data-rawheight=\&688\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E攻击判定\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E对于角色如何控制的由于是测试代码,想做了解的同学可以去下载工程源码。这里对如何触发攻击做一个简单讲解。\u003C\u002Fp\u003E\u003Cp\u003E如下,角色攻击的骨骼会绑定攻击盒子(触发器),当攻击动画进入判定攻击时间的时候将盒子激活,在收招的时候盒子判定会关闭。在此之间如果检测到了怪物的受击盒子(怪物会有一个受击判定盒子),就提前关闭攻击触发器,进入TimeLine播放阶段。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-d14cc5c0.gif\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&445\& data-rawheight=\&488\& data-thumbnail=\&https:\u002F\u002Fpic4.zhimg.com\u002Fv2-d14cc5c0_b.jpg\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E附上主要触发代码,根据不同的index索引ID判断身体哪个部位击中了怪物。攻击和被击我都是用一个代码来做判定的,这样方便我们直接可以得到我们打的是谁的被攻击部位。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Eusing System.C\nusing System.Collections.G\nusing UnityE\nusing UnityEngine.E\n\npublic class HitTrigger : MonoBehaviour\n{\n
\u002F\u002F\u002F &summary&\n
\u002F\u002F\u002F 触发器\n
\u002F\u002F\u002F &\u002Fsummary&\n
public Collider mC\n
\u002F\u002F\u002F &summary&\n
\u002F\u002F\u002F 控制该触发器的角色\n
\u002F\u002F\u002F &\u002Fsummary&\n
public CharaControl mP\n
\u002F\u002F\u002F &summary&\n
\u002F\u002F\u002F 碰撞器Id\n
\u002F\u002F\u002F &\u002Fsummary&\n
\u002F\u002F\u002F &summary&\n
\u002F\u002F\u002F 是否激活\n
\u002F\u002F\u002F &\u002Fsummary&\n
public bool isA\n
\u002F\u002F\u002F &summary&\n
\u002F\u002F\u002F 当前碰撞到的物体列表\n
\u002F\u002F\u002F &\u002Fsummary&\n
public List&GameObject& colList = new List&GameObject&();\n
\u002F\u002F\u002F &summary&\n
\u002F\u002F\u002F 碰撞事件\n
\u002F\u002F\u002F &\u002Fsummary&\n
public UnityAction&HitTrigger& TriggerFuncE\n\n\n
public void Start()\n
mCol = gameObject.GetComponent&Collider&();\n
if (mCol.isTrigger)\n
mCol.enabled =\n
public void OnTriggerEnter(Collider other)\n
SetHitTrigger(other);\n
public void OnTriggerStay(Collider other)\n
SetHitTrigger(other);\n
public void SetHitTrigger(Collider other)\n
if (!isActive)\n
var hitObj = other.gameO\n\n
if (!colList.Contains(hitObj))\n
var triggerScr=
hitObj.GetComponent&HitTrigger&();\n\n
if (triggerScr==null||triggerScr.mParent == mParent)\n
colList.Add(hitObj);\n\n
if (TriggerFuncEvent != null)\n
TriggerFuncEvent(triggerScr);\n
public void SetColActive(bool rActive)\n
isActive = rA\n
if (mCol.isTrigger)\n
mCol.enabled = rA\n
if (!isActive)\n
colList.Clear();\n
}\n}\n\u003C\u002Fcode\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003ETimeLine做动画控制的问题\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003EBake Into Pose选项作用:\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E再次提及一个Humanoid动画设置,之前有点模糊的地方,Bake Into Pose如果勾选,这个动画将会只将动画信息放在动画中,不会修改根节点位置。而取消勾选的话,将会将动画位移信息保存到根骨骼中。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-27dcf941ba9d1f1ad3be0bec9de043ae.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&354\& data-rawheight=\&284\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E勾与不勾效果对比:\u003C\u002Fp\u003E\u003Cp\u003E勾选:TimeLine播放完后动画转身\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-f8aa13d26fe.gif\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&295\& data-rawheight=\&276\& data-thumbnail=\&https:\u002F\u002Fpic1.zhimg.com\u002Fv2-f8aa13d26fe_b.jpg\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E取消勾选:播放完毕后,位置信息保存到了根节点上。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-1e0bec5e436873bfbf9d2a.gif\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&295\& data-rawheight=\&276\& data-thumbnail=\&https:\u002F\u002Fpic4.zhimg.com\u002Fv2-1e0bec5e436873bfbf9d2a_b.jpg\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003ETimeLine无法很好处理两个根节点动画融合\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E实际上再非编辑模式下,是正常的,感觉是在编辑模式下,Animtor的App Root Motion选项功能没有激活。如果有这个问题,可以用Override动画修改位置,或者使用动画偏移修改位置\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-e293e4300eec97a7e53499.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&374\& data-rawheight=\&113\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-fc807ff35d4.gif\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&601\& data-rawheight=\&582\& data-thumbnail=\&https:\u002F\u002Fpic4.zhimg.com\u002Fv2-fc807ff35d4_b.jpg\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003ETimeLineClip名字自动修改\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E比如我将未赋值的相机轨道Clip赋值为01\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-d99ae30235fceacf4ae1c7dcb1127351.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&869\& data-rawheight=\&54\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E当拖到层级窗口时,会自动修改名字成CinemachineShot\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-d97e2ccb034d0fbd42812b.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&889\& data-rawheight=\&54\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E这让动态修改Clip的难度直线上线,目前没有找到很好的办法,只能赋值一个不用的初始物体解决。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E总结\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003ETimeLine是Unity的一个很好的功能,但是功能不完美,还是存在许多坑的地方。比如赋值Clips会需要PlayableGraph类,比如自定义脚本赋值问题,动画编辑模式下无法很好查看根节点动画问题。但是总的来说避免了坑点后加功能的时候是非常方便的。\u003C\u002Fp\u003E\u003Cp\u003E有兴趣的同学可以下载工程源码看看:\u003C\u002Fp\u003E\u003Cp\u003E\u003Cu\u003E\u003Ca href=\&https:\u002F\u002Fgithub.com\u002Fchs7FTimeLineBattle\&\u003Ehttps:\u002F\u002Fgithub.com\u002Fchs7FTimeLineBattle\u003C\u002Fa\u003E\u003C\u002Fu\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cu\u003E有朋友吐槽GIT下载下来文件无效,补上网盘下载\u003C\u002Fu\u003E\u003C\u002Fp\u003E\u003Cp\u003E链接:\u003Ca href=\&https:\u002F\u002Fpan.baidu.com\u002Fs\u002F1kVMQ9JD\&\u003Ehttps:\u002F\u002Fpan.baidu.com\u002Fs\u002F1kVMQ9JD\u003C\u002Fa\u003E 密码:cpgy\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E对游戏开发感兴趣的同学,欢迎围观我们:【皮皮关游戏开发教育】 ,会定期更新各种教程干货,更有别具一格的线下小班教育。在你学习进步的路上,有皮皮关陪你!~\u003C\u002Fp\u003E\u003Cp\u003E我们的官网地址:\u003Cu\u003E\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Flevelpp.com\u002F\&\u003Ehttp:\u002F\u002Flevelpp.com\u002F\u003C\u002Fa\u003E\u003C\u002Fu\u003E\u003C\u002Fp\u003E\u003Cp\u003E我们的游戏开发技术交流群:\u003C\u002Fp\u003E\u003Cp\u003E我们的微信公众号:皮皮关\u003C\u002Fp\u003E&,&updated&:new Date(&T10:54:30.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:9,&likeCount&:32,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T18:54:30+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\u002Fpic4.zhimg.com\u002Fv2-0aaa7ec197260baaf358_r.jpg&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:9,&likesCount&:32},&&:{&title&:&给猫看的游戏AI实战(七)行为树和Behavior Designer插件(下篇)&,&author&:&ma-yao-73&,&content&:&\u003Cp\u003E上节课我们已经对行为树和Behavior Designer有了感性和有点理性的认识 :)。但是通过一个例子就掌握这个插件……那是不可能的。所以这节课我们继续,先帮助大家梳理一些基本知识,再提出几个深入的问题,最后继续扩展之前的例子。\u003Cb\u003E这一节将是鸿篇巨制,大家可以慢慢享用。\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E一、重点提示\u003C\u002Fh2\u003E\u003Cp\u003E在讲解之前要说明几个问题:\u003C\u002Fp\u003E\u003Cp\u003E1、行为树是一种逻辑工具,对工具的学习方法肯定是实用优先。\u003C\u002Fp\u003E\u003Cp\u003E特地说这个是因为Behavior Designer提供的功能其实比我们要用的多。作为使用者,务必记住要先把基本功能搞清楚,在初期那些不必要的高级功能只会把我们的思路搞乱而已。设计AI本身已经是很烧脑的工作,不建议使用一些很不直观的修饰器和组合器给自己添乱。而且基本功能已经足够我们组合出非常复杂而强大的行为树了。 :)\u003C\u002Fp\u003E\u003Cp\u003E2、行为树中的节点,会在某一帧中被调用,然后立即得到一个结果:成功Success、失败Failure、运行中Running,只能取三者其一。然后组合器和修饰器会根据返回值进行下一步,这是行为树的基本逻辑。\u003C\u002Fp\u003E\u003Cp\u003E3、节点不是多线程并行的,被调用的节点都必须迅速执行完毕并返回Running、Success或者Failure。所有事件就算是同时发生,也总有先后之分。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E二、组合器的详细介绍\u003C\u002Fh2\u003E\u003Cp\u003E注:本段先略读一遍,然后下一段咱们会做实验,边实验边阅读效果更佳。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003ESequence
串行的AND\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E
Sequence 类似于编程语言中的\&&&\&符号,它从左到右,每帧只执行一个子节点。\u003C\u002Fp\u003E\u003Cp\u003E
1、如果当前子节点返回Running,那么Sequence也返回Running。下一帧继续执行当前这个子节点。\u003C\u002Fp\u003E\u003Cp\u003E
2、如果当前子节点返回失败,那么Sequence节点本身返回失败。\u003C\u002Fp\u003E\u003Cp\u003E
3、如果当前子节点返回成功,如果还有下一个子节点,那么Sequence本身返回Running,下一帧会切换到下一个子节点; 如果所有子节点都完毕了,则Sequence节点返回成功,整个节点结束。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003ESelector
串行的OR\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E Selector与Sequence执行顺序相同,逻辑正巧是“||”的逻辑。它也是从左到右,每帧只执行一个子节点。\u003C\u002Fp\u003E\u003Cp\u003E1、如果当前子节点返回Running,那么Selector也返回Running。下一帧继续执行当前这个子节点。\u003C\u002Fp\u003E\u003Cp\u003E2、如果当前子节点返回失败,那么Selector节点本身返回Running,下一帧执行下一个子节点;如果所有子节点都失败了,就返回失败。\u003C\u002Fp\u003E\u003Cp\u003E3、如果当前子节点返回成功,那么Selector返回成功。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003EParallel
并行的AND\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E
Parallel 从返回值来看它是 “&&” 逻辑。与Sequence的区别是,在每一桢,它都执行所有子节点一次~~。\u003C\u002Fp\u003E\u003Cp\u003E
1、所有子节点都Running,那么Parallel节点也返回Running。\u003C\u002Fp\u003E\u003Cp\u003E
2、有任何一个节点返回失败,那么Parallel立刻结束,返回失败。还处于Running的子节点也会终止(从界面上可以看出,正在Running的被假设为失败)。\u003C\u002Fp\u003E\u003Cp\u003E
3、有任何一个节点返回成功,那么该子节点下一帧就不会被调用了,但是Parallel本身仍然返回Running,直到所有子节点都返回成功,Parallel才返回成功。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003EParallel Selector
并行的OR\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E Parallel Selector 从返回值来看是 “||” 逻辑。它是并行的,每一桢执行所有子节点一次~~。\u003C\u002Fp\u003E\u003Cp\u003E 1、所有子节点都Running,那么Parallel Selector节点也返回Running。\u003C\u002Fp\u003E\u003Cp\u003E 2、有任何一个节点返回失败,那么Parallel Selector 本身返回Running,直到所有子节点都失败了,它才返回失败。\u003C\u002Fp\u003E\u003Cp\u003E 3、有任何一个节点返回成功,Parallel Selector 直接返回成功。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E好的,我们解释了四种最基本的节点,只需它们就足够组成行为树的骨架。下图是Composites全图,我给它们分了组,前面介绍的就是最上面一排基本组。\u003C\u002Fp\u003E\u003Cimg src=\&v2-7aaf04cb33f07.jpg\& data-caption=\&\& data-rawwidth=\&794\& data-rawheight=\&484\&\u003E\u003Cp\u003E其它节点就容易了,我们继续看看:\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003ERandom Sequence
变体的Sequence(串行)\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003ESequence是从左到右串行,Random Sequence 也是串行完全一样,只是它从还没执行过的N个子节点中随机挑选一个执行。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003EPriority Selector, Random Selector
变体的Selector(串行)\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E这二者是Selector的变体,也都是串行。分别是根据优先级挑选、随机挑选、自定义挑选顺序。\u003C\u002Fp\u003E\u003Cp\u003E★
再强调一下,串行情况下,如果有节点还在running,那么肯定先执行running的节点。“挑选”的意思是说,在没有running的节点时,从还没执行过的节点中,根据规则挑出一个。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003ESelector Evaluator, Utility Selector
特殊顺序的Selector\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E这两种类型的特殊之处在于:在每一帧,都要重新计算子节点的优先级或者效用,就算节点正在running,也有可能因为优先级变化而切换节点。它们既不是并行也不是串行。\u003C\u002Fp\u003E\u003Cp\u003EUtility Selector 是一种选择器,它是基于“Utility”也就是“效用”进行选择,用在《模拟人生》这种游戏中会非常有效,就是当你面对吃法、睡觉、上厕所这三件事时,你选效用最大的那一件事去做即可,而且如果有必要可以随时终止当前正在做的事情。\u003C\u002Fp\u003E\u003Cp\u003ESelector Evaluator 涉及到优先级的问题,暂且不表。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E三、组合器和返

我要回帖

更多关于 电商营销活动效果不佳 的文章

 

随机推荐