This weekend I fixed a bug where the player character (Mark) could keep on moving after they passed through a door. The game would generate a new room and force them to move to the new door location – but there was a few moments of unrestricted out of bounds movement before Mark is teleported to the new room.
You can see a demonstration of this above. It just looks weird.
In order to fix this I implemented a new behaviour Boolean flag to my base movement class isForceMoving
. When an object which inherits from the base movement class starts a forced movement, the Bool is set to true, and when the forced movement is over, its set to false.
The moving object can check this Boolean in the Update()
function, and in the case of the player object – if its true, ignore all movement commands from the user are ignored! Easy!
However as it happened, this created some very weird and unpredictable additional movement bugs into the game.
If you tried to issue movement commands whilst the player object couldn’t move, once it was able to move the character would randomly rubber-band about!
The reason for this was when the ForceMove()
was releasing its lock on isForceMovement
the Ridged Body 2D component of the object had not been updated with the new position yet. Apparently this takes a couple of milliseconds. This meant that any issued movement commands within a small time window were being sent from its original starting position, not the one after the forced movement.
I needed to implement a WaitUntil()
in the ForceMove()
subroutine, which waits until the position has been correctly set to the new requested position before releasing the isForceMoving
lock.
This is acceptable as ForceMove()
is designed to run within a coroutine, so won’t block processing while its waiting, and always ensures that any move commands are being issued from the starting position after the ForceMove()
.
public IEnumerator ForceMove(Vector2 new_position)
{
yield return new WaitUntil(() => !isMoving);
isForceMoving = true;
isMoving = true;
if (rb2D != null) {
rb2D.velocity = Vector2.zero;
rb2D.MovePosition(new_position);
rb2D.velocity = Vector2.zero;
yield return new WaitUntil(() => rb2D.position == new_position);
} else {
this.transform.position = new_position;
yield return new WaitUntil(() => this.transform.position == new Vector3(new_position.x, new_position.y));
}
movement_history[current_movement_history] = new_position;
current_movement_history++;
if (current_movement_history == movement_history_size)
{
current_movement_history = 0;
}
isMoving = false;
isForceMoving = false;
}
And you can see the result below! No more weird movements out of bounds, and no more rubber-banding of the character to weird positions.