'The most efficient way to track allowed object movement in Unity 2D for puzzle making

Okay so I've rolled this around in my brain, and while I would probably know what code to use if I could figure it out, I just can't decide what the best way to implement this is.

Basically, say I have a vector grid. This grid could have any number of blockaded areas that the item I am pushing around can't go to. The pushing of the object is done by an interact button using the "new" inputsystem package. I could have a dynamic rigidbody and let it be pushed around that way, but I want to use the interact button I have, in part because the first puzzle of this type is used in a tutorial to teach the player the game's commands.

Without knowing where the blockades are going to be, I'm not sure how to tell where the object can move at any given time. My current thought is use colliders, and keep them far enough away that there isn't an actual collision since I don't think I can place them perfectly enough to get collisions at the right times, and throw out raycasts at a short range to detect these colliders. (This gives me more "give" when placing the colliders.)

Does this sound right? Is there a more efficient way to do it? Even if it requires more complex code, I am interested in feedback because I want to learn "good coding" and how to do things in the best, most efficient way possible, rather than spaghetti coding my way through it.



Solution 1:[1]

The most efficient way? Forget Colliders. Basic old-school game dev skill: Working with arrays. Create a 2D array of boolean values, or integers set to 0 or 1 representing OPEN or CLOSED.

using UnityEngine;

public class TestObstacleGrid : MonoBehaviour
{
    const int GRID_WIDTH = 8; //if you change this, you'll need to match the size of the grid, below.
    int[,] grid = {
        //               -> +x
        {0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0},
        {0,0,0,1,1,1,1,1},
        {0,0,0,1,0,1,1,1},
        {0,0,0,1,0,1,1,1},
        {0,0,0,0,0,0,0,0},
        {1,1,0,0,0,0,0,0},
        {1,1,0,0,0,0,0,0}
        //|
        //V
        //+z
        
        };
    
    const int OPEN = 0;
    const int CLOSED = 1;
    
    
    
    public Vector3 playerPos = Vector3.zero; //give player a start position via inspector.

    //You need to drag/drop something like a Cube GameObject into here, via the inspector.
    public GameObject playerGameObject; 
    Vector3 playerOffset = new Vector3(0.5f, 0.5f, 0.5f);
    Texture2D tex;

    void Start()
    {
        tex = new Texture2D(8, 8, TextureFormat.ARGB32, false);
        tex.filterMode = FilterMode.Point; //makes texture crisp, not blurry
        
        //assumes you are using a plane, not a quad. 
        this.transform.position = new Vector3(GRID_WIDTH / 2, 0, GRID_WIDTH / 2); //shift by half
        this.transform.localScale = new Vector3(-GRID_WIDTH / 10f, 1f, -GRID_WIDTH / 10f); //Unity Planes are 10 units wide.
        
        MeshRenderer renderer = this.GetComponent<MeshRenderer>(); 
        renderer.material.mainTexture = tex;
        
        playerGameObject.transform.position = playerPos;
        
        //Only needs to be called once in Start() or Awake(), if grid doesn't change.
        RenderGridToTexture(); 
    }

    void Update()
    {
        Vector3 motion = Vector3.zero;

        if (Input.GetKeyDown(KeyCode.W))
            motion += Vector3.forward;
        if (Input.GetKeyDown(KeyCode.S))
            motion += Vector3.back;
        if (Input.GetKeyDown(KeyCode.A))
            motion += Vector3.left;
        if (Input.GetKeyDown(KeyCode.D))
            motion += Vector3.right;
        
        Vector3 proposedPos = playerPos + motion;
        //prevent player leaving the grid.
        proposedPos.x = proposedPos.x < 0 ? 0 : proposedPos.x;
        proposedPos.x = proposedPos.x > GRID_WIDTH - 1 ? GRID_WIDTH - 1 : proposedPos.x;
        proposedPos.z = proposedPos.z < 0 ? 0 : proposedPos.z;
        proposedPos.z = proposedPos.z > GRID_WIDTH - 1 ? GRID_WIDTH - 1 : proposedPos.z;
        
        if (grid[(int)proposedPos.z, (int)proposedPos.x] == CLOSED)
        {
            proposedPos = playerPos; //reset the proposed to the current position (stay there).
            
            Debug.Log("Invalid attempted move from " + playerPos +
                                              " to " + proposedPos);
        }
        else //OPEN
            playerPos = proposedPos;
        
        playerGameObject.transform.position = playerPos + playerOffset; //offset shifts the cube so it looks right.
        
        //Uncomment this if the grid can change during play.
        //RenderGridToTexture(); 
    }
        
    void RenderGridToTexture()
    {
        for (int z = 0; z < GRID_WIDTH; z++)
            for (int x = 0; x < GRID_WIDTH; x++)
                tex.SetPixel(x, z, 
                    grid[z, x] == 1 ? Color.red : Color.black );
        
        tex.Apply();    
    }
}

Create an empty GameObject, and attach to this script along with a MeshRenderer and MeshFilter; for the MeshFilter, give it a Plane mesh. Create a Cube GameObject and drag it onto the script's playerGameObject field in the inspector. The script will do the rest.

Using the keys, your player moves one full square at a time, but you'll want smooth movement in the end. That's fine; this is only a basis for your game. Later on, when you want smooth movement, you can figure it out using Mathf.Lerp() with coroutines to move your player smoothly from one space to another (as well as the object your player is pushing).

You'll need to figure out pushing - the principle is the same as already used here to check for collisions: Look at the grid and see if a non-zero value is there. Then look in front of the object to see if there is an obstacle. Use a different number in the grid, like 2, to represent a pushable object.

That's the best I can do in short form, hope it's enough. Adios and good luck.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1