转自:http://angryant.com/2014/03/07/Moving-in-Unity/ ,详细描述了物体在unity中移动的几种方式,并且给出了代码描述,对加深对Unity理解很有帮助,谢谢AngryAnt 。我用我粗陋的英文水平进行简单的翻译,望谅解。
Introduction
Moving something around on the screen in Unity is really not that hard. The point of this post is therefore not to introduce you to how this is accomplished, but rather to point out where you’re doing it wrong™.
Generally Unity moves objects using one of three systems:
- Direct transform manipulation.
- Physics.
- Navigation.
In the end, movement is measured on transform updates. I make the distinction from the point of view of where you provide the input. Technically you could argue that animation should be up there as well, but I choose to lump that in with transform manipulation, since disabling the application of movement by the animation system has no other side effects.
As a side-note, since 4.3 it is possible to partially or completely disable having the animation system update the transform hierarchy.
在unity中移动物体并不是一件非常困难的事情,这篇文章不在于介绍移动的具体细节,而主要为了支持在使用过程中可能出现的错误。
通常Unity中移动物体采用下面几种方式:
- 直接使用transform
- 物理
- 导航
移动通常以更新变换来判断,这篇文章主要是通过对不同的输入进行来区分。从技术角度而言,你可能争辩需要加入“动画”,而我选择把动画和移动操作汇总在一起,毕竟移出动画系统对移动系统并还没影响。注意的是:从Unity4.3版本开始,Unity可能在慢慢的将动画对移动的更新影响去掉。
Transform
So long as the goal is simply to move an object and nothing else, modifying the values of the transform component, or using its various useful methods to do so, is all you need.
This includes directly animating the transform via the animation window or by enabling animation root motion.
However chances are that your situation is more complex than this and you would do well to read on.
如果只是简单的移动物体,可以通过直接修改transform组件值完成或者其他方法,包括直接使用动画通过动画窗口或者使能动作。
Physics
When your object in any way needs to affect and/or be affected by the physics simulation, you need to make some extra considerations. Simply slapping on a collider and calling it a day will ruin your next.
To properly participate as a dynamic part of the physics simulation, an object needs to have a rigidbody component attached somewhere in its transform hierarchy.
The physics system considers separate transform hierarchies as separate objects, so one (and only one) rigidbody component will mark the rest of its hierarchy as dynamic.
Multiple rigidbodies in the same hierarchy leads to undetermined behaviour (read: weirdnessness) – the only exception being if those rigidbodies are connected by a joint – thus making their behaviour again well defined.
当你的物体需要被物理影响或者影响其他物体,你需要额外的思考。为了实现物理系统中的动态部分,你需要在物体层中添加Rigiboy组件,物理系统将移动层组作为一个单独的物体,所以只要有一个物体添加了刚体组件,就将会使这个物体层级中剩下的部分标注为动态,其实就是说Rigidoby即使作为子物体,也会收到物理的影响,这种影响也会对父类和子类产生影响。一个物体层级中如果有多个Rigibody可能会产生离奇的效果,除非是不同的刚体通过连接组合在一起,就比如人的不同身体部位。
Kinematic
“But I don’t need gravity or forces or all that other nonsense!” – be cool, that is what the “kinematic” switch is for. This basically tells the physics simulation that your object is dynamic, but you will take care of all its movement.
Kinematic rigidbodies will not be affected by forces or collisions, but will collide with non-kinematic rigidbodies, sending collision events and pushing them out of the way (assuming there are colliders present somewhere in the transform hierarchy of the object).
“So why add the rigidbody in the first place? Things work just fine without!” – if you have Unity pro, I would direct your attention at the profiler as you move about – if not, take my word for it that it is not a joyous sight.
Any object (unique transform hierarchy) with no rigidbody present is treated by the physics simulation as static. For optimal performance, all static colliders are baked into a static collision geometry, securing optimal performance when doing collision checks.
However every time one static collider (note that this has nothing to do with the static flags on the GameObject – just the presence of absence of a rigidbody on the object in question) is moved, the whole static collision geometry is marked dirty and regenerated.
This is not a terribly costly operation, so moving pieces of level geometry from one position to another from time to time is fine. However moving a character of one or more static colliders around every frame will cost you.
Note that while moving by directly modifying the transform of a kinematic rigidbody is just fine, you will get better results for rapid movement by using the MovePosition and MoveRotation rigidbody functions.
The former will effectively “teleport” the physical object – fine for short distances and minor rotations, but less so for longer moves. MoveRotation and -Position effectively “drags” the object from A to B.
如果不需要重力或者力的影响,你可以通过kinematic进行切换。Kinematic rigidbody 不会受到力和碰撞的影响,但是和non-kinematic rigidbody碰撞的时候仍然能够产生碰撞事件并且将碰撞物推出,并且可以通过tranform进行变换。
”为什么要第一时间考虑rigibody?“,即使没有它事情也一样工作的完好---那你最好在Unity profiler 中观察一下移动的性能。
没有Rigibody的碰撞体在物理系统中被作为静态来对待,从优化系统方面来考虑,所有的静态碰撞体将该被烘焙为静态碰撞几何体从而保证最优的碰撞检测。
当移动静态碰撞体的时候,系统中所有的静态碰撞体都会被重新生成,所以进行大量静态碰撞体的移动将会是一件非常耗费性能的事情。
如果有这方面的需求你可以使用Kinematic rigidbody 通过transform进行移动,或者使用rigidbody类的MoveRotation 、MovePosition 方法。Kinematic rigidbody 方法适合短距离移动或者小的旋转效率很好,在长距离的时候稍差;MoveRotation and -Position 在“拖到”物体从A到B 非常高效。
Kine-not-so-matic
“Ok, so maybe it would pretty useful if my character could walk into walls, get pushed by others and that sort of thing…” No problem. Disable the kinematic flag and start moving via the rigidbody component. If you wanted proper forces and all that, I’m sure you’re already all over the AddForce function and all that.
However if you still want strict control – just with a touch of presence – you should look at directly setting the velocity property of the rigidbody.
Given sideways and forward movement input, forming a velocity vector is easy. By setting the velocity of the rigidbody you add that information to the physics simulation as well as tell it to update the position of the object smoothly.
That includes pushes from other rigidbodies or pushbacks from static geometry. However directly setting the velocity will override any directional change and so momentum will remain unchanged.
Therefore consider factoring in some acceleration when building your target velocity vector – for a more natural look after your character is pushed or makes an abrupt change of direction.
可以使用力来进行控制,不过想要精确控制的话,你可以考虑直接设置velocity 属性(不要在物理中每个步骤都去设置,但是可以可用在紧急改变状态的时候使用)。通过设置velocity就相当于告诉物理系统去平缓的更新物体位置。但是这种方式将会推翻所有方向的改变并且动能保持不变,这种方式合适的应用就是快速方向改变的场合,比如汽车的加速。
Navigation
Parallel to the physics simulation you find the navigation runtime. Similarly to how the rendered world model is defined by static and dynamic geometry and the physics world model is defined by static colliders and rigidbodies, the navigation world model is defined by interconnected navigation mesh and dynamic obstacles.
While the static physics geometry defines areas of no access, navigation mesh defines areas which are navigable. This information is used for finding a valid path from point A to B, but more importantly it is used to constrain characters and inform them of their surroundings.
The physics simulation can be used for this as well and traditionally is. However the data covered by the physics system is vastly more complex and its ability to define traversable space is a side effect of its ability to define non-traversable space.
This is where you end up spending way too much time blocking off sections of scenery and later testing that there are indeed no holes in that. Navmeshes on the other hand define a surface on which characters of a given height and with a given radius can move.
就像渲染中模型可以被分为静态和动态,物理系统中的模型能够分为静态碰撞体和刚体,导航世界中的模型可以为被分为连接体和动态阻挡。Unity中的导航能够和物理模拟并行执行,静态物体几何体定义不可通行面积,导航网格定义可以通行的区域,这些信息在寻路中都可以用到,不过更重要的是角色本身的限制和他们周围的信息。Navmeshes 从另一方面来的定义了一个角色在设定高度和半径的情况下可以行走的表面。 (中间翻译出来的不理解)
NavMeshAgent
Similarly to the rigidbody component, the NavMeshAgent component wires an object to the navigation runtime. In stead of the single kinematic switch, however, the NavMeshAgent has separate updatePosition and updateRotation toggles.
To get things going, you can either set a path by one of the many accessors for that or directly set the velocity. Assuming that the NavMeshAgent is configured to update the position, this will start smoothly moving the object like with the rigidbody – only this time constrained by the navigation meshes rather than collision geometry.
In addition to pathfinding and staying on the navigation mesh, the navigation runtime will also attempt to have the various NavMeshAgents avoid one-another by adjusting velocity based on the position and velocity of nearby dynamic obstacles and NavMeshAgents.
Avoidance can be completely tweaked though – so that one NavMeshAgent can ignore it completely or itself be ignored or weigh different NavMeshAgents differently.
与rigidbody 组件相似,NavMeshAgent 组件在运行时进行导航,NavMeshAgent已经将updatePosition 和updateRotation 分开。
为了使物体行走,你可以设置路径或者直接设置速度向量。如果NavMeshAgent允许更新位置,物体将会平滑的移动物体,这个时候物体受navigation meshes 约束,而不是碰撞几何;除了寻路和呆在导航网格上之外,在导航过程中,代理将会尝试将不同的NavMeshAgent具有不同的速度,根据不同动态物体和NavMeshAgent之间的距离和速度,进而避开另外一个。规避行为确实寻在,比如在多路赛车并行自动运行,你会发现加速度比较快的车辆,如果前面有较慢的车,他们会自动转弯,而不会撞击。
Direct control
“Pathfinding? I just need to move this character around based on input.” Sure, fine – that is where you just go set the velocity of the NavMeshAgent rather than trying to set a path or destination for it.
Like with the rigidbody, this starts moving the NavMeshAgent smoothly and tells the runtime about its current velocity – giving other agents a chance to avoid. Note that even if you do not let the NavMeshAgent directly control the position of your character, you should still feed the velocity back to it – in order to keep the avoidance runtime up to date.
如果是围绕输入移动角色,可以通过设置NavMeshAgent的速度,而不是重新设置新的path或者新的路径。重新设置速度后,NavMeshAgent 将会慢慢的移动过去并且告诉Runtime当前的位置(为了让其他Agent躲避)。即使你不让NavMeshAgent 直接控制物体的位置,你也应该把速度重新设置回来,为了保持运行时的躲避行为。
添加:前几天,在做多轨道变换的时候发现如果重新寻路之后,速度会降下来,所以可以采用的解决方案就是重新寻路后把速度变量重新设置就好了。
Animation
“WAT! You did not count this as one of the three ways of moving stuff!” – nop, quite right. However root motion is awesome, so let’s briefly touch on how we tie that to the other systems for much greatness.
While most of what the animation system does is not of too much concern for your movement logic, one very useful feature is. By analysing animations on import, the surface movement relevant for those animations is calculated and later blended as the animations are blended.
By default the animation system will, with root motion enabled, move an animated object around by directly updating the transform position based on the root motion of the currently playing animation blend. Animation nicely synchronised with world movement.
I’ll root my own motion, thank you
However while that looks mighty cool, it really isn’t very considerate of your carefully crafted physics simulation or your neatly marked navigation runtime. Luckily it does give an in by allowing you to override the actual application of the root motion.
This is accomplished by, on the same game object as your animator component, attaching a script implementing the method OnAnimatorMove. Once that method is defined, the animation system will no longer directly apply root motion and in stead call this implementation post-evaluation.
In the implementation of the OnAnimatorMove callback, you could then update a target velocity vector by simply dividing animator.deltaPosition by Time.deltaTime and similarly rotation. And once we have desired movement in the form of a velocity vector, plenty of the earlier described scenarios become relevant.
Most interactive niceness
“Great, I’ll take one of each!” Sure, no problem. Well… It’s not exactly straight forward, but it is indeed possible to combine all of these things to get something that is responsive, embedded in your simulations and looks great.
The chain of data goes a little something like this:
- Player controlled characters:
- Feed input to the animator, resulting in nicely blended animations and root motion.
- Generate a target velocity in OnAnimatorMove.
- Set velocity of NavMeshAgent (configured to not update position or rotation).
- Set velocity and rotation of non-kinematic rigidbody (properly constrained on rotation so it doesn’t tip over).
- Map transform position to navigation mesh via NavMesh.SamplePosition.
- Non-player characters or indirectly controlled player characters:
- Set destination or path of NavMeshAgent.
- Feed desiredVelocity of NavMeshAgent to animator.
- Repeat as for player controlled characters from 2.
What this setup does not give you is responsiveness to being bumped into. However velocity-wise this is not something your movement implementation should handle directly unless you are ok with breaking that nice root motion setup you just established.
In stead I would recommend using queries on the surrounding physics and navigation environment to inform the animation state machine of special conditions like “player wants to go full speed, but there’s a wall two units from here” and handle slowing down, stopping and similar in there where the result will look good.
One simple trick is to do a navigation raycast along the forward vector of the moving transform for some amount of look-ahead distance and if a navigation mesh edge is hit and do a physics raycast further forward from a point slightly elevated from that edge hit point.
With that setup you can very simply gather information about where the character is headed – if into a steep wall or perhaps to a ledge or obstacle which could be leapt.
Unity Hacks
Since apparently I lumped just about everything into that project, unsurprisingly the Unity Hacks project has some work on this form of wired up movement. Particularly the Mover component attempts to create a system-agnostic movement interface as well as some simple movement on networked setups not covered here.
While not complete or in any way a final answer, I hope that with the information provided here, it turns out useful for you.