ue4怎么从控制ue4导出摄像机画面转换到控制角色

张瀚荣:如何用UE4制作3D动作游戏
张瀚荣:如何用UE4制作3D动作游戏
GameLook报道/ 6月5日,2015年第三期GameLook开放日‧虚幻引擎专场活动在上海正式举行,此次活动由Epic
Games与GameLook联合主办。
动作游戏凭借爽快的打击感和强烈的操作感一直受到玩家的喜爱,但一些高品质的3D动作游戏设计其实颇有难度,国内市场上常见的大多是横版2D游戏。本次活动上,Megafun的创始人张瀚荣来分享了利用UE4引擎制作3D横版动作游戏的心得。
张瀚荣从策划的角度分析动作游戏的制作流程,“先制作2D草图,通过这些草图检验动作的合理性,然后转化为3D模型,最后把这些动作变成招式。”随后张瀚荣着重介绍了UE4中通知系统的强大功能,“游戏中输入的判定、声音、音效、特效、伤害、传递给敌人的状态、取消机制、摄像机的抖动、定帧等所有事情都可以通过通知来完成”。
“伤害区域判定是难点”张瀚荣介绍了伤害判定设计的方法,“通过在人物身前编辑各式碰撞盒子,并通过Notify
state控制这些盒子的开关以及变换”此外张瀚荣还分享了动作游戏中缓冲和取消机制的设计,“一个是简单的连招Combos,第二个是角色的特定能力包括角色的跳,还有刚才看到绳子的勾爪,把自己可以勾回去的那种,还有还有一些技能,然后设计这三者的总体逻辑”。
演讲最后Megafun的程序员具体演示了Notify
state的使用方法并介绍了其优点,包括保证两个函数成对,方便设计者进行管理。
以下是演讲实录:
张瀚荣:大家好!我们公司正在使用虚幻4引擎在做一款3D的横版动作类游戏,目前代号“TTW计划”,团队在深圳。目前计划的平台只有PC和主机两个,手游并没有考虑太多。然后也计划在7月底China
Joy发布一款试玩版。
首先看一段视频,因为我们的游戏太大了,有二十多个G,所以视频也是我花了一个通宵时间剪辑的,还有不少Bug的时候,比较粗糙,大家可以关注我们下一个月的版本。
先制作2D草图、再转化为3D模型
这次分享主要是我们如何使用虚幻4引擎来做一个动作类的游戏。经典的动作游戏有很多,比如说战神系列,鬼泣系列,还有国产的雨血等,都是在动作打击感上做得比较好的游戏。但很多人在做动作游戏的时候都会觉得它特别难做,摸不着它的门道。
动作游戏以我一个做策划的角度来讲主要分为三个方面:视觉、听觉、触觉,触觉就是连击操作的手感。我们的工作流程是这样的,首先制作2D的动作草图,通过这些草图的设计就可以看出动作合理还是不合理。然后根据这些草图在UE4里面做成3D模型的动作,并通过UE4中自带的Cascade这样一个工具来做特效。最后把这些动作变成一个招式,会在Montage中配置事件,这部分的事件非常多,非常烦琐,并在Animation
Sequence中配置特效与音效。
这是一个简单的制作流程图,就是把工作任务拆分到不同的人员身上,而这一切的主题就是
“通知”。无论是Montage还是Animation
Sequence中都会用到UE4里面独特的设定,叫Notifies,在下面这个位置。
功能强大的“通知”系统 控制连招判定
插入招式特效
通知是干什么用?通知是动画在特定的位置、特定的区域来做特定的事情。特定的事情包括哪些呢?包括输入的判定,声音、音效、特效、伤害、传递给敌人的状态、取消机制、摄像机的抖动、定帧等所有事情都可以通过通知来完成。
手感的来源其实第一个就是连招输入的判定,当你一招打出去要连下一招的时候,判定的时间是否合理的。对于手残党,像我这种来讲,我就会要求一个比较长的一个判定时间。但是对于高手来说,它可能更希望硬核一点。
缓冲我们的手感分为两个通知,包括一些即时响应和缓冲机制。缓冲机制是指获取玩家输入后,等待动画播放到某一帧后才跳转到下一段连招。即时响应就是获取玩家输入后,马上跳转到下一段连招。
那么我们在里面在里面我们是如何配置的呢?可以看到下面这个叫Attack Anim Notify
State是我们制作的缓冲机制的判定,然后下面Stop Delay
Notify代表不接受按键输入。也就是说在这一长度帧数里面,动作是允许跳转到下一个招式,但是最早的两帧内已经开始接收判定输入了。
特效很简单,就是在特定的某一帧做动作的特效,包括一些挥刀的音效。声音格式是WAV(16bit)的。
伤害区域判定是难点
编辑各式碰撞盒子
还有一个比较重要的是伤害区域的判定,这是动作游戏里面最难也是最核心的。这张图是引用《街霸4》里面框的艺术截图,他们分好多种的框。而我们实际需要的游戏框并没有这么复杂。
我们只需要在特定帧段判定攻击,过了这个帧段就不判定攻击了。通过自动调节判定框大小,调节判定框数量,来输出攻击所传递给每个受击者的状态。比如说这招打出去是需要传递它是浮空的状态,击倒的状态还是普通僵直的状态。
这是之前做的Slnow的配制表,我们通过Slnow的配制表来配置这一系列伤害框里面的一些信息,这个Startswing就是伤害的通知。然后在UE4的编辑器调节这些碰撞盒子的属性,所做盒子的位置,就是人物身前的位置。
我们也可以用通知系统去实现给角色自身添加一系列的状况,比如说在特定的某一帧给角色增加一个霸体状态,也可以无视一切碰撞检测,相当于无敌,蓄力、飞行、延迟出招等一些状态都可以通过通知实现。我们这里Charge,就是一个蓄力的通知,表示人物在蓄力需要重复播的一段动画。
缓冲和取消的机制的设定
动作游戏中也有缓冲和取消机制。缓冲是指这一招是否可以取消?相信大家玩过DNF它有一个后跳是强制取消所有招式和状态的,就是动作游戏的招式,你一招是否能取消?是马上取消还是要等到这一招出完又没有收招可以被取消,还是你的招式被特定类型的招式取消呢?这一系列设定非常核心非常细,但是都可以通过通知完成。
我们游戏中招式分三种,一个是简单的连招Combos,第二个是角色的特定能力包括角色的跳,还有刚才看到绳子的勾爪,把自己可以勾回去的那种,还有还有一些技能。总体逻辑是连招自身不能被打断,但是技能可以打断你的连招,能力也可以打断连招,技能是不能被任何东西打断,能力是可以无视帧数,在出招没开始的时候就打断当前的招式。
我们这里有一个Skilljumping
notify,就是技能的取消机制,连招的取消机制刚才也说过,在这两个区域之内是可以取消的。
刚才我讲的通知是出于策划的角度怎样配置这一系列的事件,接下来程序员会讲通知里面怎样运作的。
Begin,End,Tick和GetNotifyName四种方法重写Notify
Megafun程序员:大家好!我来跟大家讲一下如何编写通知的逻辑。在UE4,特别是做动作游戏的时候,角色挥一刀过去,在特定的某一帧需要执行一系列的操作,而这一系列的操作会有很多的功能。我们现在通过一个实例来分析一下,我们怎样去编写。
比如说一刀挥到这边来,此时伤害的碰撞格应该是进行开启,而在开启的过程中比方说他打到那边去,就需要关闭盒子。我们通过一个Notify
state,来控制碰撞盒的开启、关闭以及变换。
具体操作很简单就是点击Open Full Blueprint
Editor,打开NotifyState编辑界面。它有4个方法可以被重写,分别是Begin,End,Tick和GetNotifyName。在Begin的时候,就说明我接受到这个事件,所产生的事件的。End也是同理。Tick则是Begin和End之间Tick的事件,它有一个参数Deltatime。那Getnotifynoame就是我想看一下现在哪个Notify在起作用,但是我们一般不会用。
详细逻辑是在Begin的时候,在人物身上挂很多的碰撞盒,Begin时将盒子开启。在Tick,我们有一条Curve曲线,根据传入的曲线有一些变化值来调整碰撞盒的状态,可能是位置、大小也有可能旋转。在End的时候找到碰撞盒,然后将它关闭就可以了。其实代码比较麻烦,但是实际上伪代码就这么一点点,非常简单的一个信息。
配置是这样的,有一个Begin,中间就会产生Tick事件,然后我们会用一个用于编写动作游戏中碰撞盒的开启和关闭的逻辑。我们看到这里有三张图,这里一个测试盒来进行这样的演示,可以看到这是在第一帧,发现第一帧非常小,然后到那边是一个Tick,然后逐渐变大,这是我们当时实现的碰撞盒,这就是碰撞盒所对应的一个物体。
state的好处:保证成对,方便管理
为什么我们用Notifystate,而不是用两个Notify+Player的Tick函数?首先是说我们有Begin,我们就要有End,叫成对调用。不可能一刀砍下去,这个盒子开了,或者在这个期间我被怪打了,我要没有调用到这个End的这个盒子就会一直开着,那样就出事了。
所以我们需要保持它是成对调用。在Notifystate,通过动画打断的一个事件,保证它调用Notifystate的End的函数,可以保证与Begin的成对调用。此外就是Begin、End、Tick他们三个是在同一个BP里面方便我们进行管理。所以建议使用Notifystate实现在动作游戏中玩家碰撞盒的开启、关闭。
我你们采用的是基于碰撞盒的武器攻击点,其实我也研究过,但是我想过另外一种方案,基于线性射线检测的方案。例如手里的任何一把长柄武器,我在武器上定两个点,每一帧进行检测,一旦产生碰撞就认为攻击成功。
张瀚荣:之前有考虑过通过一些,比如说直接在武器上绑定一个盒子,或者是通过一个特殊的方法去做一些检测,但是考虑到因为我们的动作非常快,每一次出招时间非常短。在这个过程中如果在武器上挂一个盒子会非常短,可能只有一帧,但是我希望我们的打击帧数在3帧以上的判定。
另外一点是我们的特效会做得比它的招式动作本身要夸张。比如说我一个角色实际上没有位移,刚才视频里面有一个镜头,就是向前这么大的圆弧的时候,没有任何角色位移,角色还是在原地,但是一瞬间就回来了。这种情况下只能通过在人物前方摆放盒子来实现。
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。1292人阅读
UE4学习(14)
最近也是刚开始自学UE4,一开始使用UE4的时候,感觉它和UNITY差距还是比较大的。在没有任何编码的基础上,我居然能操作一个角色在场景里面跑来跑去,很是让我不解。为了研究它的这一部分代码是怎么实现的,我看了UE4官网上的教程----3rd Person Game with Blueprint。如果有其他人和我一样也是刚接触UE4并且想了解这一块的。推荐你们可以去看看官网上的教程。
官网的英文版地址如下:/latest/INT/Videos/ & &选择相应的教程就可以观看了。但是该英文版的视频要求翻墙。如果没有翻墙的话可以上Youku去看
优酷的英文版地址如下:/i/UMzE2NDk2OTIw
在这里额外多说一些,由于UE4在国内的资料不像UNITY那么齐全。所以如果需要查询资料的话最好还是翻个墙。另外,Youtube上有实时翻译功能,由于英文版是没有字幕的,所以如果能将口语翻译成英文字幕,那么看起来也是轻松非常多。由于文档教程不可能一步步都说的清楚,所以推荐大家还是可以先去学习学习官网的资料,如果有地方有疑惑的话来这里看看我是怎么解决的也可以。废话不多,下面开始进入正式的项目学习。
项目目标:创建一个第三人称视角,视野中有一个Actor,该Actor可以实现到处走动,跳跃,在待机,走动或者跳跃的过程中可以进行攻击实现连续出拳。
主要涉及到的模块:动画混合(Animation Blend),状态机(State Machine),动画蒙太奇(Animation Montage),还有一些蓝图(Blueprint)的知识。
主要步骤如下:
1.新建项目并导入资源。
2.设置响应事件(前后走,左右走,镜头上下左右移动,跳跃,攻击)
3.创建混合动画(Idle_walk_run)
4.创建人物的Animation_BP,设置好几个要进行的动作,并设定好彼此之间的转换关系,同时,编写事件图表
5.创建人物Character_BP,将响应事件和设定好的事件连接起来。到这一步位置就可以让人物在场景中跑来跑去并跳跃了,但是还不能一边移动一边攻击。
6.创建AnimationMontage_BP文件,实现动画的混合。
一 &首先是创建项目并导入资源,这个不浪费时间多说了,直接导入就是了。
二 &设定响应事件
人物前后移动:MoveForward
人物左右移动:MoveRight
镜头左右旋转:Turn
镜头上下旋转:LookUp
跳跃:Jump
效果图如下,这一块没什么好说的,就不要浪费时间了。
当我们设定好了这些输入事件以后,我们就可以在角色的蓝图里面获得该事件啦。把按钮和事件绑定好了以后,那么我们的人物的基础移动也就完成了。
三 & 创建混合动画
在内容管理器中 右键-&动画-&混合空间1D(因为决定该动作融合的条件只有一个速度,所以用1D的就足够了)。文件命名为Idle_Walk_Run,然后双击打开。首先勾选屏幕正中间的这个按钮并添加X坐标轴标签为Speed
将右边相应的动画文件拖动到左边的这个窗口中,就会生成那个小圈圈,将小圈圈在轴上移动,就可以决定在什么时候进行动作的混合。可以用鼠标在上面滑动,观察预览窗口的显示,这样子一个动作的混合就做完啦,是不是很简单?!
四 &创建人物Animation_BP
在内容管理器中 右键-&动画-&动画蓝图,命名为Aniamtion_BP,然后双击打开。新建一个状态机节点,并将该节点和最终动画姿势连接起来。
进入之后的窗体应该是这样的:
由于我们后续创建的MyCharacter_BP会是继承于UE4中自带的Character父类的,他已经拥有了跳跃,游泳,移动等功能,所以我们不必关心他是具体怎么跳跃的,我们只要能触发的跳跃行为就可以了。跳跃的行为会由其他蓝图来实现,这里我们要做的是动作的切换条件。新建一个变量IsInAir。该变量会在其他地方被设置好。然后传递到这里面来。
Idle_Walk_Run-&JumpStart:当我们的人物开始离开地面了,就播放起跳动作。
JumpStart-&JumpLoop:如果起跳动作快结束了,那么开始进入循环播放跳跃过程中的动作
JumpLoop-&JumpEnd:和起跳开始类似,如果人物快要落到地面了,IsInAir = false,那么开始播放落地动作
JumpEnd-&Idle_Walk_Run:落地动作结束后开始播放待机动作。
到这一步为止,我们已经创建了人物的状态机,其中包含了一个混合动作,3个跳跃动作。并设定好了他们之间的关系,那么接下来我们就要去设定什么按键对应什么样的事件,这样的事件怎么最终体现在对人物的操作上。那么接下来我们要去编写蓝图中的事件图表了,在这里只用关注蓝字上面的内容就可以了。
效果图如下:
到这一步为止,我们已经能获取人物身上的状态,从而来为我们状态机中需要的变量赋值,当我们为这些变量赋值以后,那么状态机获得了这些参数的值,就可以做不同的动作了。那么还剩下一部,如何将按键和响应绑定起来呢?也就是说,我们现在知道了人物一旦走动起来,那么他就会有移动的动作,跳起来就会播放跳跃的几个动作,可是他们是怎么通过键盘去触发走动,跳跃这些行为的呢。那么接下来我们就要完成这一件事情了。
五 &创建人物Character_BP,并编写其蓝图
内容管理器-&右键-&蓝图类 选择继承于Character,在蓝图编辑器中指定该Character的Mesh为我们第一步中导入来的资源,添加好SpringArm和CameraComponent等组件并设置好,类似组件的设置的不会再这里面讲,因为那些东西太多太繁琐,还是API文档靠谱,所以请自行查阅API文档。
这一步应该没有太多好说的,理解了每个节点的作用就可以了。
到这一步为止,我们应该就已经创建了一个可以自由到处跑动的角色了,他还可以进行跳跃。鼠标可以进行镜头的旋转。需要将项目设置中的默认GameMode修改成我们创建项目的GameMode,同时将DefaultPawn改成我们创建的Character_BP,那么游戏开始的时候创建的救会是我们需要的那个角色了。下一步我们要实现动作上的混合,需要能边走边出拳,这个需要再下一篇文章再详细说,因为其篇幅也是比较多的,操作也比较麻烦。
我们可以对上述的所有操作进行一下总结:
1.我们需要创建一个角色,该角色继承于Character类,他有基础的一些功能,但是并不满足我们的要求,我们在其蓝图中去捕获我们设定好的键盘输入事件,并将其进行一定的计算或者转换,然后作为参数输入到其他的组件中,这些组件获取了这些参数以后,就会执行相应的操作。比如MoveMentComponent获得了方向的参数以后就会朝某个方向进行移动,CharacterController获得了角度以后就会对相机进行旋转。
2.我们需要创建一个动画蓝图,并将Character_BP中的动画蓝图指定为它,该动画蓝图包含了一个状态机,状态机中定义了若干个动作,以及动作之间切换的条件。动画蓝图的事件图表中,会每帧的去检测人物当前的状态,然后根据该状态去设定状态机中的条件,当状态机中捕获到了这一信息,那么就会进行相应的动作,比如播放动作等。
以跳跃为例子
我们设定了Space键对应跳跃这一个行为。那么在角色的蓝图中,当我们按下Space键时,就会触发人物的跳跃事件(因为我们继承于Character),跳跃将会使人物离开地面。那么在角色的动画蓝图中,我们每帧都会去检测人物是不是离开了地面,如果离开了地方那么就去设置IsInAir参数。所以当我们点击了Space按钮,会导致IsInAir参数为true,那么这个时候状态机就会发现满足了IsInAir为true的这一个条件,那么他就开始播放这一个动作了。这就是这一整套的一个逻辑。
最后再说两句
这篇教程其实严格的来说也不叫做教程,因为如果你一路顺着做下来的话估计肯定是做不出来的。以为里面有很多参数的设置无法一一说明,写博文只是为了记录一个大概的思路,帮助大家理解这一整台状态机和角色之间是怎么进行协作的。如果想要做出项目来,我还是强烈建议去看UE4的官网视频,同时还是推一下GOOGLE的搜索,谁用谁知道。翻个墙很简单,付费也就100+一年。快速的学习东西远比这100来块重要的太多了。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:8523次
排名:千里之外
原创:27篇
评论:13条
(3)(6)(1)(5)(5)(4)(4)如何使用角色控制器实现角色方向旋转_百度知道[UE4]自定义摄影机跟随Character一起旋转的设置项 - 移动开发当前位置:& &&&[UE4]自定义摄影机跟随Character一起旋转的设置项[UE4]自定义摄影机跟随Character一起旋转的设置项&&网友分享于:&&浏览:0次[UE4]自定义摄像机跟随Character一起旋转的设置项
在Character蓝图编辑器中选中自己创建的摄像机,然后勾选“Use Pawn Control Rotation”
12345678910
12345678910
12345678910 上一篇:下一篇:文章评论相关解决方案 1234567891011 Copyright & &&版权所有  最近在Unity上要写一个东东,功能差不多就是在Unity编辑器上的旋转,移动这些,在手机上也能比较容易操作最好,原来用Axiom3D写过一个类似的,有许多位置并不好用,刚好在研究UE4的源码,在模型操作上,很多位置都解决了,其实大家可以对比下,在UE4与Unity中,UE4的如移动和旋转都要正确和好用。
  如下是根据UE4中简单移植过来的效果图,差不多已经够用,UE4相关源码主要在EditorViewportClient与UnrealWidget。
  介绍一下这个组件主要功能。
  1. 模型本地空间与世界空间二种模式。
  2. 根据情况动态生成操作模型,如在移动模型时,选择的轴变色,旋转时,视角与模型的方向产生不同模型。
  3. 移动根据鼠标平面映射到对应移动平面,点保存上轴上距离不变(作为对比,可以看到Unity上长距离移动,鼠标位置在轴上的位置位移会不断拉大)。
  4. 不管模型与摄像机的距离,旋转与移动操作都是合适的大小。
  5. 旋转方向的确定,简单说,就是在旋转时,如果用鼠标移动来确定旋转的方向,这个问题看似简单,我以前就没搞出来。
  6. 在移动本台,我们需要更方便的操作,所以在移动平台会有些操作,如更容易选中,生成的模型会更大等。
  最后,有一些,如箭头模型,选择旋转与移动轴的算法以前考虑过,就没用UE4本身的,如果感觉有问题,自己去移植UE4的。其中旋转因为移动平台易用性,就设定了一个值,如在我这设定的是10,就是每次只旋转10度。
  简单分析一下,UE4里相关思路。
  其中移动的算法思路非常赞,比如我们要移动X轴,那么我们对应在法线为Y或是法线为Z轴上的平面都可以,通过摄像机的方向与这二个平面的夹角,在这如果摄像机的方向与法线Y平面的角大于与法线Z平面的角,那么我们选择法线Y平面的面做映射面,而Z向量作偏向轴方向,什么意思了,我们鼠标是在二维面上移动的,但是对应的只在X轴上移动,那么我们在法线Y平面上的映射向量需要去掉在Z向量上偏向量的影响。如下是移动的主要代码,每步我加了注释,其中一些比较常用如投影,向量减向量在某向量上的投影的意义要记清,当初我也是看到这,就一下想通这个算法的思路了。  
/// &summary&
/// FWidget::GetAbsoluteTranslationDelta
/// 算法思想,如果移动X轴,选取以Y轴或是Z轴为法线并过模型上的面,鼠标移动映射在这个面上。
/// 其中,如果选择Y轴面,要去掉Z轴上运动值,参看NormalToRemove
/// &/summary&
/// &param name="inParams"&&/param&
/// &returns&&/returns&
public Vector3 GetAbsoluteTranslationDelta(AbsoluteMovementParams inParams)
//鼠标移动的位置 对应的面,请看GetAxisPlaneNormalAndMask方法
Plane movementPlane = new Plane(inParams.PlaneNormal, inParams.Position);
//估算鼠标点击在模型上的位置(点击射线方向)
Vector3 eyeVector = inParams.EyePos + inParams.PixelDir * (inParams.Position - inParams.EyePos).
//模型的世界位置
Vector3 requestedPositon = inParams.P
//点击方向与面的夹角
float dotPlaneNormal = Vector3.Dot(inParams.PixelDir, inParams.PlaneNormal);
//摄像机方向与面的夹角不为90度
if (Mathf.Abs(dotPlaneNormal) & 0.00001)
//摄像机到点击位置 与 面的交点 ,把requestedPositon映射到面上位置
requestedPositon = LinePlaneIntersection(inParams.EyePos, eyeVector, movementPlane);
//拖动的增量(都在movementPlane上,二点相差)
var deltaPosition = requestedPositon - inParams.P
//保存最开始点击下去得到的偏移
Vector3 offset = GetAbsoluteTranslationInitialOffset(requestedPositon, inParams.Position);
//去掉最开始本身的偏移
deltaPosition -= initialO
//.Log("delta:" + deltaPosition);
//去掉deltaPosition到NormalToRemove上投影 outDrag与NormalToRemove 互相垂直,outDrag+NormalToRemove = deltaPosition
float movementAxis = Vector3.Dot(deltaPosition, inParams.NormalToRemove);
Vector3 outDrag = deltaPosition - inParams.NormalToRemove * movementA
//Debug.Log("outDrag:" + outDrag);
//get the distance from the original position to the new proposed position
//Vector3 deltaFromStart = inParams.Position + outDrag - initialP
//模型到摄像机方向
Vector3 eyeToNewPosition = inParams.Position + outDrag - inParams.EyeP
//模型到摄像机方向与摄像机方向 夹角大于90度
float behindDot = Vector3.Dot(eyeToNewPosition, inParams.CameraDir);
if (behindDot &= 0)
outDrag = Vector3.
return outD
FWidget::GetAbsoluteTranslationDelta
  移动的算法差不多就是这样,其中如何生成移动模型就不拉出来,后面会给出源代码,大家自己去找。至于如何找到移动模型对应的X,Y,Z轴,或是全部移动,算法以前写过,求得二射线相隔最近的二点,然后根据二点的长度判断是否认为相交,在代码文件上的GetAxisType,具体大家去看。
  旋转时,我们根据摄像机到模型的向量分别计算对应的XYZ轴上正负向量,再分别生成如X轴上对应YZ平面的90度弧形,顺便我们得到每个对应平面在对应屏幕上的方向,这样我们在屏幕上移动就能正确的对应模型应该的旋转方向,列出其中相关代码,更详细的解释请看函数对应的注释。  
#region 渲染旋转
public void Render_Rotate()
if (currentAxis == AxisType.None)
Render_RotateArc();
Render_RotateAll();
//旋转模式下,生成三个面的旋转模型
public void Render_RotateArc()
Vector3 toWidget = ((this.transform.position - Camera.main.transform.position)).
Vector3 XAxis = coordSystem * Vector3.
Vector3 YAxis = coordSystem * Vector3.
Vector3 ZAxis = coordSystem * Vector3.
//画对应的旋转的90度面
var redMesh = DrawRotationArc(AxisType.X, this.transform.position, ZAxis, YAxis, 0, Mathf.PI / 2.0f, toWidget, Color.red, ref xAxisDir);
var greenMesh = DrawRotationArc(AxisType.Y, this.transform.position, XAxis, ZAxis, 0, Mathf.PI / 2.0f, toWidget, Color.green, ref yAxisDir);
var blueMesh = DrawRotationArc(AxisType.Z, this.transform.position, XAxis, YAxis, 0, Mathf.PI / 2.0f, toWidget, Color.blue, ref zAxisDir);
//分别合并面与线,合成一个SubMesh时,要求MeshTopology与材质一样
var faceMesh = CombineMesh(true, redMesh.FaceMesh, greenMesh.FaceMesh, blueMesh.FaceMesh);
//var lineMesh = CombineMesh(true, redMesh.LineMesh, greenMesh.LineMesh, blueMesh.LineMesh);
float x = Mathf.Sign(Vector3.Dot(toWidget, bLocation ? axisTransform.right : Vector3.right));
float y = Mathf.Sign(Vector3.Dot(toWidget, bLocation ? axisTransform.up : Vector3.up));
float z = Mathf.Sign(Vector3.Dot(toWidget, bLocation ? axisTransform.forward : Vector3.forward));
var redLineMesh = CreateLine(Vector3.zero, -XAxis * innerRadius * x, Color.red);
var greenLineMesh = CreateLine(Vector3.zero, -YAxis * innerRadius * y, Color.green);
var blueLineMesh = CreateLine(Vector3.zero, -ZAxis * innerRadius * z, Color.blue);
var lineMesh = CombineMesh(true, redLineMesh, greenLineMesh, blueLineMesh);
//合并面与线,分别对应一个SubMesh,可以用不同MeshTopology与材质
meshFilter.mesh = CombineMesh(false, faceMesh, lineMesh);
//给每个SubMesh对应材质
meshRender.sharedMaterials = new Material[2] { faceMat, lineMat };
/// &summary&
/// FWidget::DrawRotationArc 渲染选择某个轴后的对应模型,360度的面
/// &/summary&
public void Render_RotateAll()
Vector3 toWidget = (this.transform.position - Camera.main.transform.position).
Vector3 XAxis = coordSystem * Vector3.
// Quaternion.Inverse(coordSystem) *
Vector3 YAxis = coordSystem * Vector3.
Vector3 ZAxis = coordSystem * Vector3.
float adjustDeltaRotation = bLocation ? -totalDeltaRotation : totalDeltaR
float absRotation = Mathf.Abs(totalDeltaRotation) % 360.0f;
float angleRadians = absRotation * Mathf.Deg2R
float startAngle = adjustDeltaRotation & 0.0f ? -angleRadians : 0.0f;
float filledAngle = angleR
LineFaceMesh meshRotation = null;
LineFaceMesh meshAll = null;
//画对应的旋转的90度面
if (currentAxis == AxisType.X)
meshRotation = DrawRotationArc(AxisType.X, this.transform.position, ZAxis, YAxis, startAngle, startAngle + filledAngle, toWidget, Color.red);
meshAll = DrawRotationArc(AxisType.X, this.transform.position, ZAxis, YAxis, startAngle + filledAngle, startAngle + 2.0f * Mathf.PI, toWidget, Color.yellow);
else if (currentAxis == AxisType.Y)
meshRotation = DrawRotationArc(AxisType.Y, this.transform.position, XAxis, ZAxis, startAngle, startAngle + filledAngle, toWidget, Color.green);
meshAll = DrawRotationArc(AxisType.Y, this.transform.position, XAxis, ZAxis, startAngle + filledAngle, startAngle + 2.0f * Mathf.PI, toWidget, Color.yellow);
else if (currentAxis == AxisType.Z)
meshRotation = DrawRotationArc(AxisType.Z, this.transform.position, XAxis, YAxis, startAngle, startAngle + filledAngle, toWidget, Color.blue);
meshAll = DrawRotationArc(AxisType.Z, this.transform.position, XAxis, YAxis, startAngle + filledAngle, startAngle + 2.0f * Mathf.PI, toWidget, Color.yellow);
meshFilter.mesh = CombineMesh(false, meshRotation.FaceMesh, meshAll.FaceMesh);
//给每个SubMesh对应材质
meshRender.sharedMaterials = new Material[2] { lineMat, faceMat };
public LineFaceMesh DrawRotationArc(AxisType type, Vector3 inLocation, Vector3 axis0, Vector3 axis1, float inStartAngle, float inEndAngle, Vector3 toWidget, Color32 color)
Vector2 outAxis = new Vector2();
return DrawRotationArc(type, inLocation, axis0, axis1, inStartAngle, inEndAngle, toWidget, color, ref outAxis);
///X轴上,我们渲染YZ平面,先确定在摄像机-&模型在Y轴与Z轴上的方向,再确定这个平面对应在屏幕上的方向
public LineFaceMesh DrawRotationArc(AxisType type, Vector3 inLocation, Vector3 axis0, Vector3 axis1, float inStartAngle, float inEndAngle, Vector3 toWidget, Color32 color, ref Vector2 outAxisDir)
//确定采用轴的正向还是反向
bool bMirrorAxis0 = Vector3.Dot(axis0, toWidget) &= 0.0f;
bool bMirrorAxis1 = Vector3.Dot(axis1, toWidget) &= 0.0f;
Vector3 renderAxis0 = bMirrorAxis0 ? axis0 : -axis0;
Vector3 renderAxis1 = bMirrorAxis1 ? axis1 : -axis1;
//画90度弧形
var mesh = DrawThickArc(renderAxis0, renderAxis1, inStartAngle, inEndAngle, toWidget, color);
//确定屏幕上对应方向
float direction = (bMirrorAxis0 ^ bMirrorAxis1) ? -1.0f : 1.0f;
var axisSceen0 = ScreenToPixel(this.transform.position + renderAxis0 * 64);
var axisSceen1 = ScreenToPixel(this.transform.position + renderAxis1 * 64);
outAxisDir = ((axisSceen1 - axisSceen0) * direction).
//世界点转成屏幕对应的像素位置
public Vector2 ScreenToPixel(Vector3 pos)
Vector4 loc =
loc.w = 1;
//MVP 后的位置,其值在 DX/OpenGL 范围各不相同
Vector4 mvpLoc = Camera.main.projectionMatrix * Camera.main.worldToCameraMatrix *
//四维数据转到三维,简单来说,X,Y,Z限定范围到DX/OpenGL所定义的包围圈中
float InvW = 1.0f / mvpLoc.w;
//这里是在DX下的范围,由[-1,1]映射到[0,1]中
var x = (0.5f + mvpLoc.x * 0.5f * InvW) * Camera.main.pixelW
var y = (0.5f - mvpLoc.y * 0.5f * InvW) * Camera.main.pixelH
return new Vector2(x, y);
/// &summary&
/// 动态生成以axis0和axis1组成的平面,以axis0为0度,画从inStartAngle到inEndAngle弧形
/// &/summary&
public LineFaceMesh DrawThickArc(Vector3 axis0, Vector3 axis1, float inStartAngle, float inEndAngle, Vector3 toWidget, Color32 color)
LineFaceMesh lineFace = new LineFaceMesh();
Mesh mesh = lineFace.FaceM
//Mesh lineMesh = lineFace.LineM
int numPoints = (int)(circleSide * (inEndAngle - inStartAngle) / (Mathf.PI / 2.0f)) + 1;
Vector3 zAxis = Vector3.Cross(axis0, axis1);
Vector3[] posArray = new Vector3[2 * numPoints + 2];
Color32[] colorArray = new Color32[2 * numPoints + 2];
Vector2[] uvArray = new Vector2[2 * numPoints + 2];
//Vector3[] linePosArray = new Vector3[4 * numPoints + 4];
int index = 0;
Vector3 lastVertex = Vector3.
for (int radiusIndex = 0; radiusIndex & 2; ++radiusIndex)
float radius = (radiusIndex == 0) ? outerRadius : innerR
float tcRadius = radius / (float)innerR
for (int vectexIndex = 0; vectexIndex &= numP vectexIndex++)
float percent = vectexIndex / (float)numP
float angle = Mathf.Lerp(inStartAngle, inEndAngle, percent);
float angleDeg = angle * Mathf.Rad2D
Vector3 vertexDir = Quaternion.AngleAxis(angleDeg, zAxis) * axis0;
vertexDir.Normalize();
float tcAngle = percent * Mathf.PI / 2;
Vector2 tc = new Vector2(tcRadius * Mathf.Cos(angle), tcRadius * Mathf.Sin(angle));
Vector3 vertexPos = vertexDir *
posArray[index] = vertexP
uvArray[index] =
colorArray[index] =
lastVertex = vertexP
mesh.vertices = posA
mesh.uv = uvA
mesh.colors32 = colorA
int innerStart = numPoints + 1;
int[] triArray = new int[3 * 2 * numPoints];
index = 0;
for (int vertexIndex = 0; vertexIndex & numP vertexIndex++)
triArray[index++] = vertexI
triArray[index++] = vertexIndex + 1;
triArray[index++] = vertexIndex + innerS
triArray[index++] = vertexIndex + 1;
triArray[index++] = vertexIndex + innerStart + 1;
triArray[index++] = vertexIndex + innerS
mesh.triangles = triA
lineFace.LineMesh = CreateLine(Vector3.zero, zAxis * innerRadius, color);
return lineF
//创建一个线段
public Mesh CreateLine(Vector3 start, Vector3 end, Color32 color)
Mesh mesh = new Mesh();
mesh.vertices = new Vector3[2] { start, end };
mesh.uv = new Vector2[2] { Vector2.zero, Vector2.zero };
mesh.colors32 = new Color32[2] { color, color };
mesh.SetIndices(new int[] { 0, 1 }, MeshTopology.Lines, 0);
//bineMeshes 需要已经正确的subMesh indices,而这里的mesh的indices都是从0开始,自己写个
public Mesh CombineMesh(bool mergeSubMeshes, params Mesh[] meshs)
List&Vector3& vectors = new List&Vector3&();
List&Vector2& uvs = new List&Vector2&();
List&Color32& colors = new List&Color32&();
List&int& startIndexs = new List&int&();
int start = 0;
int indexCount = 0;
bool bUV = true;
bool bColor = true;
foreach (var mesh in meshs)
vectors.AddRange(mesh.vertices);
uvs.AddRange(mesh.uv);
if (mesh.uv.Length == 0)
bUV = false;
colors.AddRange(mesh.colors32);
if (mesh.colors32.Length == 0)
bColor = false;
startIndexs.Add(start);
start += mesh.vertexC
indexCount += mesh.GetIndices(0).L
var combineMesh = new Mesh();
combineMesh.SetVertices(vectors);
combineMesh.SetUVs(0, uvs);
if (bColor)
combineMesh.SetColors(colors);
combineMesh.subMeshCount = mergeSubMeshes ? 1 : meshs.L
int[] allIndices = new int[indexCount];
int autoIndex = 0;
for (int i = 0; i & meshs.L i++)
var indices = meshs[i].GetIndices(0);
int count = indices.L
int[] tris = new int[count];
for (int j = 0; j & j++)
allIndices[autoIndex++] = indices[j] + startIndexs[i];
tris[j] = indices[j] + startIndexs[i];
if (!mergeSubMeshes)
combineMesh.SetIndices(tris, meshs[i].GetTopology(0), i);
if (mergeSubMeshes)
combineMesh.SetIndices(allIndices, meshs[0].GetTopology(0), 0);
return combineM
#endregion
  因为UE4中有RHI,所以只管放入相应Rendering Command,下面会自动合并,优化,而Unity因为高度集成,相反在写这些代码时比较麻烦,如上,我本意在场景里定义一个空的模型,加上我这个脚本后就能实现相应旋转,移动的功能,不引入别的任何内容,也不生成子GameObject,所以动态生成对应的MeshFilter与MeshRenderer要考虑如下需求。
  1. 只有一个MeshFilter与MeshRender,这样我们可能要自己组装多个SubMesh.
  2. 每个轴用不同的颜色表示,并且每轴需要二种绘制方式,三角面,线条。
  3. 我们要优化渲染,需要最少的Material能完成就用最少的Material,以及最少的SubMesh.
  4. 渲染需要,深度测试通过,但是不要写入深度缓存中,不受灯光影响。
  5. 层次显示需要,面要透明,而线不需要透明。
  一般来说,每个面用不同颜色表示,在Unity中就需要不同的Material,或运行时设置Material的变量,这样每个面就不能合并显示,我们需要能利用模型本身颜色的Shader,并且要满足上面第四点,通过Unity官方提供的Unity5Shader这个项目,我们找到GUI/Text Shader,满足上面的条件,这样,生成三个轴对应的面模型时,使用颜色数据,就能合并成一个SubMesh,使用一个Material渲染,我们知道,同一个SubMesh,不可能出现一个画三角面,一个线,这样我们最少有二个SubMesh。大家对照下Render_RotateArc这个方法,结合ComBineMesh这个方法,可能有的同学会问,Unity不是本身就提供了bineMeshs,使用这个合并不就OK了,bineMeshs这个方法需要本身的SubMesh对应的Indices里索引已经是全局数据的索引才可以用的,什么意思了,我们这边生成的三个Mesh,其indices里的数据都是针对本身的vectices的索引,用CombineMeshs合并后,后面的Mesh对应的索引就错了。
  上面的这部分UE4与Unity代码几乎完全不同,需要大家自己修改成自己所需要的。
  选择旋转轴的算法没用UE4的,用的一种非常简单的方法,大致思路,找到射线与圆的二个交点,把交点转到模型空间中,查看交点的x,y,z的值,那个值接近0,就是那个轴,想具体理解可见我前文&&,里面也有求得移动轴的算法。&
  最后,说一个简单的东东,原来我一直没搞出来,不管模型与摄像机的距离,旋转与移动操作都是合适的大小,我原来求出来的值,要么就是在距离少时,显示不对,要么就是在距离远时,显示不对,而UE4给出一个简单的式子,如下面代码。
Vector4 aposition = axisTransform.
aposition.w = 1;
float w = (Camera.main.projectionMatrix * Camera.main.worldToCameraMatrix * aposition).w;
widgetScale = w * (4.0f / Camera.main.pixelWidth / Camera.main.projectionMatrix[0, 0]);
widgetScale
  代码完整链接 ,就一个文件,在Unity场景中,根节点下建立一个GameObject,把这个脚本放上面去就行,对应UI如设置 世界/本地,旋转,移动都有相应API调用。
阅读(...) 评论()}

我要回帖

更多关于 ue4摄像机 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信