Controlling two characters simultaneously on a grid
I'm trying to create a grid based puzzle game where you control two characters with one set of input, but each character is on their own little maze. So essentially you have to guide your two characters that move simultaneously through their own maze, to their goal own goal. So I've actually got a prototype of the game working, but I encountered some serious bugs so I decided to rework it and see if I can fix it piece by piece. My problem right now is that they're tiles on the floor that the characters can slide over. So if the one on the left slides over a tile it'll skip a space for example, and the one on the right will only move its normal one space.
My problem is that while the object on the left is still moving, the object on the right can move freely - which is not what I want! If either object is moving I want to make sure that neither object can accept input. I've tried a few ways, with more or less success depending, but the closest I can get is that you can only move if they're both not moving, but if you happen to press an input just as the sliding block reaches the end then the other block goes off sync... Does anyone know how I can do this?
I have this script on both the moving objects, and it moves nicely and slides, but it just needs to be limited:
[Header("Test Variables")]
public float moveSpeed1 = 4f;
public Transform movePoint1;
public LayerMask whatStopsMovement1;
public static bool isSliding = false;
public LayerMask slidingBlocks;
// Start is called before the first frame update
void Start()
{
movePoint1.parent = null;
}
// Update is called once per frame
void Update()
{
transform.position = Vector3.MoveTowards(transform.position, movePoint1.position, moveSpeed1 * Time.deltaTime);
if (Vector3.Distance(transform.position, movePoint1.position) == 0f)
{
Movement();
}
}
public void Movement()
{
if (Input.GetKeyDown(KeyCode.LeftArrow) || Input.GetKeyDown(KeyCode.RightArrow))
{
if (Input.GetKeyDown(KeyCode.LeftArrow))
{
MovementHorizontal(-1f);
}
if (Input.GetKeyDown(KeyCode.RightArrow))
{
MovementHorizontal(1f);
}
}
else if (Input.GetKeyDown(KeyCode.UpArrow) || Input.GetKeyDown(KeyCode.DownArrow))
{
if (Input.GetKeyDown(KeyCode.UpArrow))
{
MovementVertical(1f);
}
if (Input.GetKeyDown(KeyCode.DownArrow))
{
MovementVertical(-1f);
}
}
}
public void MovementHorizontal(float moveAmount)
{
if (!Physics2D.OverlapPoint(movePoint1.position + new Vector3(moveAmount, 0f, 0f), whatStopsMovement1))
{
movePoint1.position += new Vector3(moveAmount, 0f, 0f);
}
SlidingBlocksHorizontal(moveAmount, moveAmount);
}
public void MovementVertical(float moveAmount)
{
if (!Physics2D.OverlapPoint(movePoint1.position + new Vector3(0f, moveAmount, 0f), whatStopsMovement1))
{
movePoint1.position += new Vector3(0f, moveAmount, 0f);
}
SlidingBlocksVertical(moveAmount, moveAmount);
}
public void SlidingBlocksVertical(float moveAmount, float adjustmentValue)
{
if (Physics2D.OverlapPoint(transform.position + new Vector3(0f, moveAmount, 0f), slidingBlocks))
{
while (Physics2D.OverlapPoint(movePoint1.position, slidingBlocks))
{
movePoint1.position += new Vector3(0f, moveAmount, 0f);
}
isSliding = true;
}
}
public void SlidingBlocksHorizontal(float moveAmount, float adjustmentValue)
{
if (Physics2D.OverlapPoint(transform.position + new Vector3(moveAmount, 0f, 0f), slidingBlocks))
{
while (Physics2D.OverlapPoint(movePoint1.position, slidingBlocks))
{
movePoint1.position += new Vector3(moveAmount, moveAmount 0f);
}
isSliding = true;
}
}
Answer by muckenhoupt · Oct 15, 2020 at 12:24 AM
So, the problem with checking to see if they're moving is a race condition. On a given frame, it'll run the Update method of both your guys, and you have no control over the ordering. So in a single frame it can look at one guy and say "This guy is moving, so we'll ignore input, and also he's at the end of his move so he should stop", and then look at the other guy and say "No one's moving, so we'll accept input."
What I'd do is coordinate them with a third object -- let's call it "observer". Give it a FixedUpdate method -- that way, it won't run between the Update methods of the two guys in a single frame. In the observer's FixedUpdate method, have it check all the guys to see if they're moving (or if there's anything else that should block input). If there is, set a flag. Then the Update method of the guys can check the flag to see if they're allowed to act on input during the current frame.
Alternately, you could get rid of the input handling in the guys entirely and make the observer solely responsible for looking at the input, deciding whether to act on it, and then iterating over the guys and setting them in motion.
Either way, the solution is to centralize the decision of whether to act on input, so that the two guys can't decide different things.
Come to think of it, you don't really need a third object as long as you make the decision in FixedUpdate and act on it in Update, or vice versa. As long as you have one phase where you decide whether to act on input or not, and another phase where you start and stop movement or otherwise make changes that could affect that decision, and there's no overlap between those two phases in a single frame, you're golden.
That makes sense and worked perfectly, thanks! Sometimes you just don't think of these thing!