- Home /
What's the best way to simulate input for physics-driven, CPU-controlled actors?
I'm building a game in the style of Super Monkey Ball. Both the enemy and the player use the same mesh etc and the only difference is color. Every actor should be driven by AddRelativeforces.
For my player, it's easy - I capture Input.GetAxis and feed this to a move vector, then apply forces proportionally. No problem there.
But I'm at a loss on how to move my CPU-controlled enemies. If i manually apply forces, the move vector does not smoothly increase in magnitude over time like it does with my physical player: this is because Input.GetAxis returns a -1...1 value proportional to the 'strength' of the joystick move (this is simulated by unity when using a keyboard).
I was thinking Lerping to a certain value but I'm not sure how to do it... any help would be appreciated.
Answer by Tomer-Barkan · Oct 01, 2015 at 01:37 PM
The correct Object Oriented design in my opinion, would be to have the "Actor" class, that will be used for both player and enemies. This actor class will be attached to the mesh object, and will contain an API to control this actor. In your example, it should have a method called "AddRelativeForces(Vector2 force)", that will apply the relevant forces on the actor's rigidbody:
public class Actor : MonoBehaviour {
protected void AddRelativeForces(Vector2 force) {
GetComponent<RigidBody2D>().AddForce(force);
}
}
Then, you will have two different controllers. One for the player, and one for the enemy. They will both make use of the actor's API, but each will make it's own decision of when to apply the force. The player will make the decision according to the keyboard input, and the enemy will make the decision according to your AI algorithm.
public class PlayerControl : MonoBehaviour {
public Actor actor;
void Update() {
Vector2 force = new Vector2(Input.GetAxis("playerX"), 0); // set force depending on controls
force = force * Time.deltaTime; // multiply by deltaTime to keep force independent of framerate
actor.AddRelativeForces(force);
}
}
And the enemy:
public class EnemyControl : MonoBehaviour {
public Actor actor;
void Update() {
Vector2 force = new Vector2(GetAiForceDirection(), 0); // set force depending on AI decision
force = force * Time.deltaTime; // multiply by deltaTime to keep force independent of framerate
actor.AddRelativeForces(force);
}
float GetAiForceDirection() {
// Implement your AI algorithm here
}
}
Thank you, that is useful.
The part I'm struggling with however is being able to send the input as a smooth increase from 0 to 1 at the exact same speed Unity does it when it simulates an analog joystick with the keyboard.
In other words, in your code when you apply Vector2 as a force you do so immediately, whereby if it moved the joystick in the same direction I would get a gradual increase in applying the force.
Hmm, not sure how unity calculates it's gravity/sensitivty... but if you use a joystick, then it's simply 0..1, linearly, where 0 is when the stick is centered, and 1 when it's all the way to the side (or 0..-1 for the other side).
If you use a button, it might be easier to make the input "snap", which means it will be either -1, 0 or 1. Then you can do your own formula for smoothing, and use the same formula for the AI.
A simple smoothing formula would be:
Vector2 currentSmoothValue = Vector2.zero;
void Update() {
Vector2 playerInputDirection = new Vector2(Input.GetAxis("horizontal"), Input.GetAxis("vertical"));
currentSmoothValue = Vector2.$$anonymous$$oveTowards(currentSmoothValue, playerInputDirection, Time.deltaTime * smoothSpeed);
actor.AddRelativeForce(currentSmoothValue * Time.deltaTime);
}