MackeyK24 Posted August 30, 2017 Share Posted August 30, 2017 Root Motion or In Place... What type of animations are we supposed (or can we) use just in place or can we use root motion... If using root motion how do keep collider in sync and how to deal with the player moving forward instead in addition to user input ??? Quote Link to comment Share on other sites More sharing options...
GameMonetize Posted August 30, 2017 Share Posted August 30, 2017 Can you help me understand what root motion does? Quote Link to comment Share on other sites More sharing options...
MackeyK24 Posted August 31, 2017 Author Share Posted August 31, 2017 15 hours ago, Deltakosh said: Can you help me understand what root motion does? https://docs.unity3d.com/Manual/RootMotion.html Basically they allow the translation on the 'Root Bone' ... So if your animation moves the root bone position like a walking animation (instead of doing the walking animation while staying at position zero)... they allow that... they must some how use that forward motion each frame... If you 'UNCHECK' apply root motion... they keep the mesh at position zero at runtime giving you an in place animation even if the animation has root bone motion... My little light weight 'Apply Root Motion' detect for false... if that off... I zero the X and Z RIGHT BEFORE I compute the translation matrix for each bone... that way I don't have to constantly check each frame to zero the X and Y positions... I just do that at runtime (I may event put an options to not actually zero but offset by the original x and z ... that we just take off whatever the animation put on from starting position so the X and Z might not actually be 0 but will be the start position ... maybe is started at z 0.1 or something like that... anyways thats seems to be working so far) In C# code in the loop that goes thru each bone... I compute the matrix for each frame of the animations building keys from the cupped matrix and stick on that bone.animation ... Since I am fixing and offsetting the X and Z during the actual Animation Frame Sampling... They just going the scene already on the bone with no runtime code need to fix/offset to remove and root bone motion... But as you stated before ... all this could be avoided if you use IN-PLACE animations... but they are harder to find ALL in-place animation for a complete character setup BTW... Here is how I do that in C# before it ever gets serialized into the scene: AnimationMode.BeginSampling(); for (var i = 0; i < clipFrameCount; i++) { Matrix4x4 local; clip.SampleAnimation(source, i * frameTime); if (applyRootMotion == false && transform == skinnedMesh.rootBone) { float positionX = transform.localPosition.x; float positionY = transform.localPosition.y; float positionZ = transform.localPosition.z; if (animationState != null) { positionY = Mathf.Clamp(positionY, 0.0f, animationState.clampRootPositionY); if (animationState.zeroRootPositionsXZ) { positionX = 0.0f; positionZ = 0.0f; } } local = Matrix4x4.TRS(new Vector3(positionX, positionY, positionZ), transform.localRotation, transform.localScale); } else { local = Matrix4x4.TRS(transform.localPosition, transform.localRotation, transform.localScale); } float[] matrix = new[] { local[0, 0], local[1, 0], local[2, 0], local[3, 0], local[0, 1], local[1, 1], local[2, 1], local[3, 1], local[0, 2], local[1, 2], local[2, 2], local[3, 2], local[0, 3], local[1, 3], local[2, 3], local[3, 3] }; var key = new BabylonAnimationKey { frame = (i + frameOffest), values = matrix }; keys.Add(key); } AnimationMode.EndSampling(); Quote Link to comment Share on other sites More sharing options...
adam Posted August 31, 2017 Share Posted August 31, 2017 I recommend not moving the root bone. Move the mesh instead. GameMonetize 1 Quote Link to comment Share on other sites More sharing options...
GameMonetize Posted August 31, 2017 Share Posted August 31, 2017 https://www.babylonjs-playground.com/#1BZJVJ#110 Quote Link to comment Share on other sites More sharing options...
MackeyK24 Posted September 1, 2017 Author Share Posted September 1, 2017 14 hours ago, Deltakosh said: https://www.babylonjs-playground.com/#1BZJVJ#110 Im sorry @Deltakosh ... but I don't get what your trying to show here... the movement in the z direction by adding 0.05 Does this anyway have to do with how unity is applying root motion to the animation... basically I think the motion from the animation is supposed to drive the actual movement of the character... I think that what Root Motion actually does. Quote Link to comment Share on other sites More sharing options...
GameMonetize Posted September 1, 2017 Share Posted September 1, 2017 So it could really be different. I'm not sure yet how this could work Quote Link to comment Share on other sites More sharing options...
MackeyK24 Posted September 2, 2017 Author Share Posted September 2, 2017 On 8/31/2017 at 6:21 AM, Deltakosh said: https://www.babylonjs-playground.com/#1BZJVJ#110 Yo @Deltakosh I think that might work... It looks like you just take "Runspeed" to the Z and use turning for the Root Motion (I guess just rotation) and just always go forward on Z or backwards if negative value on Z Take a look at this: https://docs.unity3d.com/Manual/ScriptingRootMotion.html Quote Link to comment Share on other sites More sharing options...
GameMonetize Posted September 4, 2017 Share Posted September 4, 2017 Ok I understand. Root motion is kind of animation for the mesh. The bones will deform the mesh and the root motion animation will move / rotate the mesh itself. If this is correct then we just need to export mesh root motion animations alongside bones animations right? Quote Link to comment Share on other sites More sharing options...
MackeyK24 Posted September 4, 2017 Author Share Posted September 4, 2017 2 hours ago, Deltakosh said: Ok I understand. Root motion is kind of animation for the mesh. The bones will deform the mesh and the root motion animation will move / rotate the mesh itself. If this is correct then we just need to export mesh root motion animations alongside bones animations right? Yo thanks @Deltakosh That is exactly what I am doing now... During the encoding of the transformation matrix of each bone, I do the following: 1... Check of is root bone transform, if so encode the position and rotation of the root transform to babylon animation key and REMOVE/RESET (via animator clip options) existing position and rotation values for that bone in the matrix calculations... So we will end up the the full animation (baked or zero out root bone translation with a copy of the real translation for the root bone store in another set of animation key) Take a look a the c# Code, this code 'SAMPLES' each animation clip frame for each bone: AnimationMode.BeginSampling(); for (var i = 0; i < clipFrameCount; i++) { Matrix4x4 local; int frameIndex = (i + frameOffest); clip.SampleAnimation(source, i * frameTime); if (transform == skinnedMesh.rootBone) { float positionX = transform.localPosition.x; float positionY = transform.localPosition.y; float positionZ = transform.localPosition.z; float rotationX = transform.localRotation.eulerAngles.x * (float)Math.PI / 180; float rotationY = transform.localRotation.eulerAngles.y * (float)Math.PI / 180; float rotationZ = transform.localRotation.eulerAngles.z * (float)Math.PI / 180; Quaternion rotationQT = transform.localRotation; if (encodeRootMotions) { // Build Transform Root Motion Keys if (cycled == false) { pxkeys.Add(new BabylonAnimationKey { frame = frameIndex, values = new[] { positionX } }); pykeys.Add(new BabylonAnimationKey { frame = frameIndex, values = new[] { positionY } }); pzkeys.Add(new BabylonAnimationKey { frame = frameIndex, values = new[] { positionZ } }); rxkeys.Add(new BabylonAnimationKey { frame = frameIndex, values = new[] { rotationX } }); rykeys.Add(new BabylonAnimationKey { frame = frameIndex, values = new[] { rotationY } }); rzkeys.Add(new BabylonAnimationKey { frame = frameIndex, values = new[] { rotationZ } }); } } // Bake Root Motion Translations if (settings.loopBlendOrientation || settings.keepOriginalOrientation) { if (settings.keepOriginalOrientation) { rotationQT = Quaternion.Euler(originalRX, originalRY, originalRZ); } else { rotationQT = Quaternion.Euler(rotationQT.eulerAngles.x, settings.orientationOffsetY, rotationQT.eulerAngles.z); } } if (settings.loopBlendPositionY || settings.keepOriginalPositionY) { if (settings.heightFromFeet) { positionY = Mathf.Clamp(positionY, 0.0f, settings.level + clampFeetPositionY); } else { positionY = originalPY + clampFeetPositionY; } } if (zeroRootPositionsXZ) { positionX = 0.0f; positionZ = 0.0f; } else { if (settings.loopBlendPositionXZ || settings.keepOriginalPositionXZ) { if (settings.keepOriginalPositionXZ) { positionX = originalPX; positionZ = originalPZ; } else { positionX = 0.0f; positionZ = 0.0f; } } } local = Matrix4x4.TRS(new Vector3(positionX, positionY, positionZ), rotationQT, transform.localScale); } else { local = Matrix4x4.TRS(transform.localPosition, transform.localRotation, transform.localScale); } float[] matrix = new[] { local[0, 0], local[1, 0], local[2, 0], local[3, 0], local[0, 1], local[1, 1], local[2, 1], local[3, 1], local[0, 2], local[1, 2], local[2, 2], local[3, 2], local[0, 3], local[1, 3], local[2, 3], local[3, 3] }; var key = new BabylonAnimationKey { frame = frameIndex, values = matrix }; keys.Add(key); } AnimationMode.EndSampling(); frameOffest += clipFrameCount; totalFrameCount += clipFrameCount; The new Root animation keys encoding like this: // // Format Root Motion Keys // string property = "none"; if (pxkeys.Count > 0) { property = "metadata.state.animPosition.x"; anims.Add(new BabylonAnimation { dataType = (int)BabylonAnimation.DataType.Float, name = property + " animation", keys = pxkeys.ToArray(), framePerSecond = frameRate, enableBlending = false, blendingSpeed = 0.0f, loopBehavior = (animationState != null) ? (int)animationState.loopBehavior : (int)BabylonAnimation.LoopBehavior.Relative, property = property }); } property = "none"; if (pykeys.Count > 0) { property = "metadata.state.animPosition.y"; anims.Add(new BabylonAnimation { dataType = (int)BabylonAnimation.DataType.Float, name = property + " animation", keys = pykeys.ToArray(), framePerSecond = frameRate, enableBlending = false, blendingSpeed = 0.0f, loopBehavior = (animationState != null) ? (int)animationState.loopBehavior : (int)BabylonAnimation.LoopBehavior.Relative, property = property }); } property = "none"; if (pzkeys.Count > 0) { property = "metadata.state.animPosition.z"; anims.Add(new BabylonAnimation { dataType = (int)BabylonAnimation.DataType.Float, name = property + " animation", keys = pzkeys.ToArray(), framePerSecond = frameRate, enableBlending = false, blendingSpeed = 0.0f, loopBehavior = (animationState != null) ? (int)animationState.loopBehavior : (int)BabylonAnimation.LoopBehavior.Relative, property = property }); } property = "none"; if (rxkeys.Count > 0) { property = "metadata.state.animRotation.x"; anims.Add(new BabylonAnimation { dataType = (int)BabylonAnimation.DataType.Float, name = property + " animation", keys = rxkeys.ToArray(), framePerSecond = frameRate, enableBlending = false, blendingSpeed = 0.0f, loopBehavior = (animationState != null) ? (int)animationState.loopBehavior : (int)BabylonAnimation.LoopBehavior.Relative, property = property }); } property = "none"; if (rykeys.Count > 0) { property = "metadata.state.animRotation.y"; anims.Add(new BabylonAnimation { dataType = (int)BabylonAnimation.DataType.Float, name = property + " animation", keys = rykeys.ToArray(), framePerSecond = frameRate, enableBlending = false, blendingSpeed = 0.0f, loopBehavior = (animationState != null) ? (int)animationState.loopBehavior : (int)BabylonAnimation.LoopBehavior.Relative, property = property }); } property = "none"; if (rzkeys.Count > 0) { property = "metadata.state.animRotation.z"; anims.Add(new BabylonAnimation { dataType = (int)BabylonAnimation.DataType.Float, name = property + " animation", keys = rzkeys.ToArray(), framePerSecond = frameRate, enableBlending = false, blendingSpeed = 0.0f, loopBehavior = (animationState != null) ? (int)animationState.loopBehavior : (int)BabylonAnimation.LoopBehavior.Relative, property = property }); } // // Cache Babylon Animation Keys // if (anims.Count > 0) { List<BabylonAnimation> sourceAnimiamtions = null; if (SceneBuilder.AnimationCurveKeys.ContainsKey(sourceId)) { sourceAnimiamtions = SceneBuilder.AnimationCurveKeys[sourceId]; } else { sourceAnimiamtions = new List<BabylonAnimation>(); SceneBuilder.AnimationCurveKeys.Add(sourceId, sourceAnimiamtions); } foreach (var anim in anims) { sourceAnimiamtions.Add(anim); } } This works great so far... Then on the client side 1... I use these to function to update the current root motion for the animPosition keys frames encoded for for the root transform/bone private updateRootMotion():void { if (this.owned.metadata != null && this.owned.metadata.state != null) { if (this.owned.metadata.state.animPosition != null) { this._rootPosition = this.owned.metadata.state.animPosition; this._rootPosition.subtractToRef(this._lastPosition, this._deltaPosition); this._lastPosition.x = this._rootPosition.x; this._lastPosition.y = this._rootPosition.y; this._lastPosition.z = this._rootPosition.z; } if (this.owned.metadata.state.animRotation != null) { this._rootRotation = this.owned.metadata.state.animRotation; this._rootRotation.subtractToRef(this._lastRotation, this._deltaRotation); this._lastRotation.x = this._rootRotation.x; this._lastRotation.y = this._rootRotation.y; this._lastRotation.z = this._rootRotation.z; } } } private resetRootMotion():void { this._lastPosition.x = 0.0; this._lastPosition.y = 0.0; this._lastPosition.z = 0.0; this._lastRotation.x = 0.0; this._lastRotation.y = 0.0; this._lastRotation.z = 0.0; } The update root motion gets called every frame... I use a 'LastPosition' holder to calculate the DELTA changes of the position and rotation and expose as a few function on the new animator component... So can at anytime call: animation.getRootPosition() - Get the original rootPosition vector recorded into the animations animation.getRootRotation() - Same as above but for rotations animation.getDeltaPosition() - The calculated delta changes from the last frame animation.getDeltaRotation() - Same as above but for rotations I can then use regular movement code to move the 'Container' game object ... not the actual skinned mesh transform but its parent. So in a TestPlayer.ts controller that is attach to the parent object... I get a reference to the 'Animator Component' ... Just like you would do in Unity: var mannequinn:BABYLON.AbstractMesh = this.getChildMesh("Mannequinn"); if (mannequinn != null) { this._animator = this.manager.findSceneComponent("BABYLON.AnimationState", mannequinn); if (this._animator != null) { // Note All loaded and ready } else { console.log("Failed to locate animator for mannequinn: " + mannequinn.name); } } else { console.log("Failed to locate mannequin mesh: " + mannequinn.name); } I can then make call it its public functions like, getDeltaPosition() and use that delta to move the player with velocity: if (this._animator != null) { var deltaPosition:BABYLON.Vector3 = this._animator.getDeltaPosition(); var deltaRotation:BABYLON.Vector3 = this._animator.getDeltaRotation(); if (this._character != null) { var delta:number = this.manager.deltaTime; //var ang:number = (deltaRotation.y * (this.rotateSpeed * 0.5)) / delta; //this.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,-ang,0)); //this.mesh.rotation.y += -ang; this._inputVelocity.x = (deltaPosition.x * this.moveSpeed) / delta; this._inputVelocity.y = 0.0; this._inputVelocity.z = (deltaPosition.z * this.moveSpeed) / delta; var jumped:boolean = (this.manager.getKeyInput(this.keyboardJump) || this.manager.getButtonInput(this.buttonJump)); if (jumped === true) this._inputVelocity.y = this.jumpForce; this._character.move(this._inputVelocity, jumped); } } Dud everything so far is working great... EXCEPT FOR MY LACK OF 3D-Game stuff... For example ... I am TOTALLY shitty at rotation and quaternions and smoothly rotating.... I don't know that part is always so 'Fuzzy' for me... Especially compared to ALL the other stuff I can do BOTH in Babylon Typscript and Unity C# (as you can tell by the toolkit itself)... But rotations ALWAYS fuck me up So I have my current rotation code remarked in the example about.... What I need is to SMOOTHLY rotate the parent game object with the Root Bone rotation delta.... With the code I am use above, trying to rotate with angular velocity as well as regular mesh.rotation... it SANPS around too fast and looks funny.: //var ang:number = (deltaRotation.y * (this.rotateSpeed * 0.5)) / delta; //this.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,-ang,0)); //this.mesh.rotation.y += -ang; One last part is when switching from walk to run... you can see a 'Hard Jump' because I also don't really now HOW to use enableBlending and BlendingSpeed... I red the docs ... but I still can't get the desired smoothness in between animation clip changes But that is my progress so far... I am going to make a video so you can actually see what I am talking about and see the animation system action.... Maybe you can then tell me what I need to do to go further Quote Link to comment Share on other sites More sharing options...
MrVR Posted May 21, 2018 Share Posted May 21, 2018 NO root motion With root motion Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.