News › Forums › RAIN › General Discussion and Troubleshooting › Custom RAINMotor
Tagged: custom motor
This topic contains 13 replies, has 2 voices, and was last updated by Other-Jeff 1 month, 2 weeks ago.
-
AuthorPosts
-
November 20, 2022 at 11:09 am #39528
Hi,
I’m writign a custom motor to attach RAIn to our movement system, which is based around setting a direction to move in.
What i am getting is kind of odd. It appears to teleport to the target or at least close to it on the first move. After that
movement is fine.Is there any condition under which the RAIN code would be setting the position of the actor directly in its transform?
Thanks. Code below in case you can see something else wrong…
using RAIN.Motion; using RAIN.Serialization; using UnityEngine; [RAINSerializableClass] public class UAMotor : RAINMotor { public AICharacterController controller; public override void ApplyMotionTransforms() { //NOP } /// <summary> /// Turn towards FaceTarget /// </summary> /// <returns></returns> public override bool Face() { if (IsFacing(FaceTarget.Orientation)) { return true; } else { controller.targetRotation = Quaternion.LookRotation(FaceTarget.Position - controller.transform.position, new Vector3(0, 1, 0)); return false; } } public override bool IsAt(Vector3 aPosition) { if ((controller.transform.position - aPosition).magnitude <= 0.1) { return true; } else { return false; } } public override bool IsAt(MoveLookTarget aTarget) { return IsAt(aTarget.Position); } public override bool IsFacing(Vector3 aPosition) { Vector3 facingVector = aPosition - controller.transform.position; float angle = Vector3.Angle(facingVector, controller.transform.forward); if (Mathf.Abs(angle) <= 0.1f) { return true; } else { return false; } } public override bool IsFacing(MoveLookTarget aTarget) { return IsFacing(aTarget.Position); } public override bool Move() { if (IsAtMoveTarget) { controller.inputMoveDirectionLocal = Vector3.zero; return true; } else { controller.inputMoveDirectionLocal = (MoveTarget.Position - controller.transform.position); return false; } } public override void Stop() { controller.inputMoveDirectionLocal = Vector3.zero; controller.targetRotation = controller.transform.rotation; } public override void UpdateMotionTransforms() { //throw new NotImplementedException(); } }
November 20, 2022 at 11:55 am #39529Additional note.
It appears to be a frame stutter just after I start moving? Our logic keeps a constant speed of motion ergo the jump.
But why would RAIN cause a stutter just afetr starting a move? This is my behavior code.
November 20, 2022 at 12:12 pm #39530Okay, new behavior. Its not hitching and its getting where its supposed to… but its leaping over obstacles!
Thats cool, but not what I intended.
How do I tell it to keep its feet on the ground?
November 20, 2022 at 6:32 pm #39535I don’t see anything in your code that indicates it would add any jumping or similar, but a lot of your work is being done by that AICharacterController class that might be doing the jumping for you.
On another note, you probably want to add this to your custom motor:
public override void UpdateMotionTransforms() { AI.Kinematic.ParentTransform = Matrix4x4.identity; AI.Kinematic.Position = AI.Body.transform.position; AI.Kinematic.Orientation = AI.Body.transform.rotation.eulerAngles; }
You don’t have to apply the details from our Kinematic (what normally goes in ApplyMotionTransforms) but likely you want RAIN to know about your position so that if you do something like move or face with your behavior tree it knows where you are coming from (in case you want to use the navigation mesh for instance).
November 24, 2022 at 12:56 pm #39558Thanks, I figured that one out.
I’m having an odd effect now however where its trying to go directly to the end point of the path. In your architecture, does the Navigator call the motor, or does the motor have to explicitly call the navigator to get a path?
November 24, 2022 at 11:50 pm #39563The motor needs to call the Navigator to get a path. You can also work directly with the RAIN Navigation Mesh (GetPathFinder or CreatePathFinder is the call I think) to get a path finder, and subsequently a path. You can also work with a Unity Nav Mesh Agent, or call their static functions to generate paths, if you are using a Unity navigation mesh.
Here’s what the BasicMotor does in it’s move call:
public override bool Move() { // If we are at our target, we are done if (IsAt(MoveTarget)) return true; TurnAngle = 0f; AngleToTarget = 0f; // The cached move target keeps us from using up extra allocations _cachedTarget = AI.Navigator.GetNextPathWaypoint(MoveTarget, _cachedTarget); // After allowing the navigator to run, if we are done pathfinding // and we don't allow off graph movement, and we are at the last point in the path // then we are done if (!AllowOffGraphMovement && !AI.Navigator.IsPathfinding) { if (AI.Navigator.CurrentPath == null || !AI.Navigator.CurrentPath.IsValid) return true; if (!AI.Navigator.CurrentPath.IsPartial && IsAt(AI.Navigator.CurrentPath.GetWaypointPosition(AI.Navigator.CurrentPath.WaypointCount - 1))) return true; } // If we have a valid move target from our navigator, attempt to head towards it if (_cachedTarget.IsValid) { // Copy the position over so we can keep track of our last known good position GoodTarget.VectorTarget = _cachedTarget.Position; // Update our angle to target Vector3 tTurn = RAIN.Utility.MathUtils.GetLookAtAngles(AI.Kinematic.Position, _cachedTarget.Position, AI.Kinematic.Orientation); AngleToTarget = RAIN.Utility.MathUtils.WrapAngle(tTurn.y - AI.Kinematic.Orientation.y); SimpleSteering.DoDirectMovement(AI, _cachedTarget.Position, CloseEnoughDistance, CloseEnoughAngle, FaceBeforeMoveAngle, Allow3DMovement, Allow3DRotation); } // We don't have a valid move target from our navigator, and we don't allow off graph movement, so head to our last known good target else if (!AllowOffGraphMovement) { // Update our angle to target Vector3 tTurn = RAIN.Utility.MathUtils.GetLookAtAngles(AI.Kinematic.Position, GoodTarget.Position, AI.Kinematic.Orientation); AngleToTarget = RAIN.Utility.MathUtils.WrapAngle(tTurn.y - AI.Kinematic.Orientation.y); SimpleSteering.DoDirectMovement(AI, GoodTarget.Position, Mathf.Min(0.001f, CloseEnoughDistance), CloseEnoughAngle, FaceBeforeMoveAngle, Allow3DMovement, Allow3DRotation); } // We don't have a valid move target from our navigator, but we allow off graph movement, so head straight to our origina move target else { // Update our angle to target Vector3 tTurn = RAIN.Utility.MathUtils.GetLookAtAngles(AI.Kinematic.Position, MoveTarget.Position, AI.Kinematic.Orientation); AngleToTarget = RAIN.Utility.MathUtils.WrapAngle(tTurn.y - AI.Kinematic.Orientation.y); SimpleSteering.DoDirectMovement(AI, MoveTarget.Position, Mathf.Min(0.001f, CloseEnoughDistance), CloseEnoughAngle, FaceBeforeMoveAngle, Allow3DMovement, Allow3DRotation); } // And update our turn angle if we have angular velocity if (AI.Kinematic.Rotation.sqrMagnitude > 0f) TurnAngle = AngleToTarget; // We return false because we are still moving towards our target return false; }
Ours is a little more complex as we’re trying to handle all the possibilities, but perhaps that can point you at the right calls to make.
November 24, 2022 at 11:52 pm #39564Sorry that was the Mecanim Motor and not the Basic Motor, although they will soon be one in the same.
December 1, 2022 at 8:56 am #39586Thanks Sigil, just wanted to make sure I was understanding the structure. All good for now!
December 7, 2022 at 10:38 am #39622I’m very close now. I see the path and the actor moves to the first border between the mesh node its in and the next mesh node, but then it sticks there, like itsg etting set back there after every move from the on.
Do I need to do something to tell it to advance to the next target?
Code below:
using System; using RAIN.Core; using RAIN.Motion; using RAIN.Navigation.Pathfinding; using RAIN.Serialization; using UnityEngine; [RAINSerializableClass] public class UAMotor : RAINMotor { public AICharacterController controller; private RAINPath path; private float TurnAngle; private float AngleToTarget; private MoveLookTarget _cachedTarget; private MoveLookTarget GoodTarget = new MoveLookTarget(); public override void AIInit() { base.AIInit(); Allow3DMovement = false; } public override bool Allow3DMovement { get { return false; } set { //nop } } public override void ApplyMotionTransforms() { //NOP } /// <summary> /// Turn towards FaceTarget /// </summary> /// <returns></returns> public override bool Face() { if (IsFacing(FaceTarget.Orientation)) { return true; } else { controller.targetRotation = Quaternion.LookRotation(FaceTarget.Position - controller.transform.position, new Vector3(0, 1, 0)); return false; } } public override bool IsAt(Vector3 aPosition) { if ((controller.transform.position - aPosition).magnitude <= 0.1) { return true; } else { return false; } } public override bool IsAt(MoveLookTarget aTarget) { return IsAt(aTarget.Position); } public override bool IsFacing(Vector3 aPosition) { Vector3 facingVector = aPosition - controller.transform.position; float angle = Vector3.Angle(facingVector, controller.transform.forward); if (Mathf.Abs(angle) <= 0.1f) { return true; } else { return false; } } public override bool IsFacing(MoveLookTarget aTarget) { return IsFacing(aTarget.Position); } public override bool Move() { // If we are at our target, we are done if (IsAt(MoveTarget)) return true; TurnAngle = 0f; AngleToTarget = 0f; // The cached move target keeps us from using up extra allocations _cachedTarget = AI.Navigator.GetNextPathWaypoint(MoveTarget, Allow3DMovement, AllowOffGraphMovement, _cachedTarget); // After allowing the navigator to run, if we are done pathfinding // and we don't allow off graph movement, and we are at the last point in the path // then we are done if (!AllowOffGraphMovement && !AI.Navigator.IsPathfinding) { if (AI.Navigator.CurrentPath == null || !AI.Navigator.CurrentPath.IsValid) return true; if (!AI.Navigator.CurrentPath.IsPartial && IsAt(AI.Navigator.CurrentPath.GetWaypointPosition(AI.Navigator.CurrentPath.WaypointCount - 1))) return true; } // If we have a valid move target from our navigator, attempt to head towards it if (_cachedTarget.IsValid) { // Copy the position over so we can keep track of our last known good position GoodTarget.VectorTarget = _cachedTarget.Position; // Update our angle to target Vector3 tTurn = RAIN.Utility.MathUtils.GetLookAtAngles(AI.Kinematic.Position, _cachedTarget.Position, AI.Kinematic.Orientation); AngleToTarget = RAIN.Utility.MathUtils.WrapAngle(tTurn.y - AI.Kinematic.Orientation.y); DoDirectMovement(AI, _cachedTarget.Position, CloseEnoughDistance, CloseEnoughAngle, FaceBeforeMoveAngle, Allow3DMovement, Allow3DRotation); } // We don't have a valid move target from our navigator, and we don't allow off graph movement, so head to our last known good target else if (!AllowOffGraphMovement) { // Update our angle to target Vector3 tTurn = RAIN.Utility.MathUtils.GetLookAtAngles(AI.Kinematic.Position, GoodTarget.Position, AI.Kinematic.Orientation); AngleToTarget = RAIN.Utility.MathUtils.WrapAngle(tTurn.y - AI.Kinematic.Orientation.y); DoDirectMovement(AI, GoodTarget.Position, Mathf.Min(0.001f, CloseEnoughDistance), CloseEnoughAngle, FaceBeforeMoveAngle, Allow3DMovement, Allow3DRotation); } // We don't have a valid move target from our navigator, but we allow off graph movement, so head straight to our origina move target else { // Update our angle to target Vector3 tTurn = RAIN.Utility.MathUtils.GetLookAtAngles(AI.Kinematic.Position, MoveTarget.Position, AI.Kinematic.Orientation); AngleToTarget = RAIN.Utility.MathUtils.WrapAngle(tTurn.y - AI.Kinematic.Orientation.y); DoDirectMovement(AI, MoveTarget.Position, Mathf.Min(0.001f, CloseEnoughDistance), CloseEnoughAngle, FaceBeforeMoveAngle, Allow3DMovement, Allow3DRotation); } // And update our turn angle if we have angular velocity if (AI.Kinematic.Rotation.sqrMagnitude > 0f) TurnAngle = AngleToTarget; // We return false because we are still moving towards our target return false; } private void DoDirectMovement(AI aI, Vector3 position, float closeEnoughDistance, float closeEnoughAngle, float faceBeforeMoveAngle, bool allow3DMovement, bool allow3DRotation) { Vector3 direction = (position - AI.Body.transform.position).normalized; controller.inputMoveDirectionLocal = AI.Body.transform.InverseTransformDirection(direction); } public override void Stop() { controller.inputMoveDirectionLocal = Vector3.zero; controller.targetRotation = controller.transform.rotation; } public override void UpdateMotionTransforms() { AI.Kinematic.ParentTransform = Matrix4x4.identity; AI.Kinematic.Position = AI.Body.transform.position; AI.Kinematic.Orientation = AI.Body.transform.rotation.eulerAngles; } }
December 8, 2022 at 10:53 am #39637The AI.Navigator.GetNextPathWaypoint(MoveTarget, Allow3DMovement, AllowOffGraphMovement, _cachedTarget) call should automatically move on to the next path point once it thinks it got there. See if doing this improves your results (I think the defaults aren’t set right now):
... public override void AIInit() { base.AIInit(); Allow3DMovement = false; Allow3DRotation = false; AllowOffGraphMovement = true; CloseEnoughDistance = 0.1f; CloseEnoughAngle = 0.1f; } ...
If CloseEnoughDistance was zero it’d explain why the navigator never moved past the first point, as the chances of getting *exactly* on the path point is fairly slim.
December 8, 2022 at 11:48 am #39640Closer… he gets to the first waypoint and stops, but does not proceed to next waypoint?
December 9, 2022 at 10:46 am #39645Got it all working, thanks!
December 9, 2022 at 11:03 am #39647What ended up being the cause?
December 10, 2022 at 9:46 am #39658Two things. The first was that someone put a second large collider on my monster for no apparent reason and it was hanging up.
Secondarily, I had some issues with getting the angle of facing right.
But its all working now
New questions though on actions… but Ill post on a nwe thread.
-
AuthorPosts
You must be logged in to reply to this topic.