1
\$\begingroup\$

I have this little game, where my player is moving around a simple level, using a grid based movement script (See code below). My problem is that I have tried to implement a 2D raycast, which has to detect if the next 'tile' is a movable tile (not a wall). However all the examples I have found on the internet hasn't been something I could make work. So please help me with how I can detect if the player is allowed to move, before he moves.

I am using Unity 5.

using System.Collections;
using UnityEngine;

public class GridMove : MonoBehaviour {
    public float moveSpeed = 2f;

    private float gridSize = 1f;
    private enum Orientation {
        Horizontal,
        Vertical
    };
    private Orientation gridOrientation = Orientation.Vertical;
    private bool allowDiagonals = false;
    private bool correctDiagonalSpeed = true;
    private Vector2 input;
    private bool isMoving = false;
    private Vector3 startPosition;
    private Vector3 endPosition;
    private float t;
    private float factor;
    private Animator animator;

    // Use this for initialization
    void Start () {
        animator = GetComponent<Animator>();
    }

    public void Update() {
        if (!isMoving) {
            input = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
            if (!allowDiagonals) {
                if (Mathf.Abs(input.x) > Mathf.Abs(input.y)) {
                    input.y = 0;
                } else {
                    input.x = 0;
                }
            }

            if (input != Vector2.zero) {
                StartCoroutine(Move(transform));
                animator.SetBool("isWalking", true);
            }
            else {
                animator.SetBool("isWalking", false);
            }
        }
    }

    public IEnumerator Move(Transform transform) {
        isMoving = true;
        startPosition = transform.position;
        t = 0;

        animator.SetFloat("x", input.x);
        animator.SetFloat("y", input.y);

        if(gridOrientation == Orientation.Horizontal) {
            endPosition = new Vector3(startPosition.x + System.Math.Sign(input.x) * gridSize,
                                      startPosition.y, startPosition.z + System.Math.Sign(input.y) * gridSize);
        } else {
            endPosition = new Vector3(startPosition.x + System.Math.Sign(input.x) * gridSize,
                                      startPosition.y + System.Math.Sign(input.y) * gridSize, startPosition.z);
        }

        if(allowDiagonals && correctDiagonalSpeed && input.x != 0 && input.y != 0) {
            factor = 0.7071f;
        } else {
            factor = 1f;
        }

        while (t < 1f) {
            t += Time.deltaTime * (moveSpeed/gridSize) * factor;
            transform.position = Vector3.Lerp(startPosition, endPosition, t);
            yield return null;
        }

        isMoving = false;
        yield return 0;
    }
}
\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

If you're using a grid, you shouldn't be using Unity's physics for collision detection - use the grid. Get your position on the grid then do something like this:

public bool GridCast (Coordinate Position, Coordinate Direction, int Length)
{
    for (int i = 0; i < Length; i++)
    {
        Coordinate Check = Position + Direction * Length;
        if (GridNodes[Check.x,Check.y].Occupied)
        {
            //Collision
            return true;
        }
    }
    return false;
}

To check if you can walk forward 4 tiles, do something like this:

if (GridPhysics.GridCast(MyCoordinate, new Coordinate (0,1), 4))
{
   //Something's in the way
}
else{
   //Nothing's in the way; proceed with walking
}

If you want to find out how many tiles you can walk forward IF something is in the way, add an out parameter called Point that represents the point of collision.

Coordinate Check = Position + Direction * Length;
if (GridNodes[Check.x,Check.y].Occupied)
{
    //Collision
    *Point = Check*
    return true;
}

Then just move up to that point.

Move (Point - MyCoordinate);

To answer the title question, you can create a wall by simply accessing the GridNodes array and marking the nodes the wall occupies and detect collision with the GridCast method.

For (int i = Wall.MyCoordinate.x; i < Wall.Width + Wall.MyCoordinate.x; i++)
{
    for (int j = Wall.MyCoordinate.y; j < Wall.Height + Wall.MyCoordinate.y; j++)
    {
        GridNodes[i,j].Occupied = true;
    }
}

Note that this GridCast implementation won't work accurately for diagonals but it will be uber fast, especially if you flatten the GridNodes array.


Here's an example of something else you can do with a grid map: https://www.youtube.com/watch?v=MEd6XV2Pecw. I call it influence map in this overview but the concepts remain the same - map your position to a 2D point on the grid's plane then do whatever you need to with the point and the information stored in the grid.

Pathfinding, fog of war (would be really cool for a grid game), and AI Bots can all be implemented with an influence map. I'm 99.99% sure MineCraft uses a flattened 3D array to store its blocky information. Starcraft uses a grid for pathfinding, building, and attack searching (I think). Anyways, you get the point. Grids are cheap, effective ways of storing and interacting with information.

\$\endgroup\$
5
  • \$\begingroup\$ mhh.. I am not quite sure what you mean, I haven't made my level in an 2D array. The script I provided is attached to my player object, and it then moves 1 world unit (which is the same as a tile). How can I make use of a 2D array, for my level instead? Should I use the 2D array to generate the level also? \$\endgroup\$ Commented Apr 27, 2015 at 7:49
  • \$\begingroup\$ Oh, I see. This method might take a bit more work effort but it'll benefit you A LOT in the long run since grids can be used for many things. Create scripts called GripdMap, GridNode, GridAgent, and GridPhysics. Generate the 2D array of GridNodes in GridMap. GridNode should include a boolean called 'Occupied' but you can add other things as well. Attach 'GridAgent' on your player and use that to interact with the grid. Basically, GridAgent converts your world position into 2 integers on the XZ plane (just round it off). GridPhysics is for specialized things like gridcasting. \$\endgroup\$
    – JPtheK9
    Commented Apr 27, 2015 at 17:02
  • \$\begingroup\$ Note: GridMap can be a static class for better performance and GridNode can't inherit from MonoBehaviour or you're gonna get some huge overhead. GridPhysics can be static as well (like Unity's Physics class). GridAgent should be the only class inheriting from MB. \$\endgroup\$
    – JPtheK9
    Commented Apr 27, 2015 at 17:04
  • \$\begingroup\$ Thank you... I have tried coding my own grid manager, which has some tile objects witht he corresponding information I have to use. It seems to work, so therefore I am accepting your answer. Thanks for your help :) \$\endgroup\$ Commented May 3, 2015 at 11:18
  • \$\begingroup\$ Happy to help. If you need any more information or ideas, feel free to ask and I'll do my best :) \$\endgroup\$
    – JPtheK9
    Commented May 4, 2015 at 1:56
2
\$\begingroup\$

Using a 2D tile array for your world/level generation and representation will definitely simplify things. For example you could internally represent your world in a grid of tiles and take it from there :

using System;
using System.Collections.Generic;
using System.Text;

namespace Tiles {
    public class Vector2 {
        public int x; public int y;
        public Vector2(int x, int y) {
            this.x = x; this.y = y;
        }
    }

    public enum TileType {
        None,
        Grass,
        Dirt,
        Stone,
        Water,
        Air,
    }

    public class Tile {
        public Vector2 position;
        public TileType tileType;

        public Tile(int x, int y, TileType tileType) {
            this.position = new Vector2(x, y);
            this.tileType = tileType;
        }
    }

    public class World {
        public Tile[,] tiles;
        public int sizeX; public int sizeY;

        public World(int sizeX, int sizeY) {
            this.sizeX = sizeX;
            this.sizeY = sizeY;
            int tileCount;

            // build the world
            tiles = new Tile[sizeX, sizeY];

            Random rnd = new Random();
            tileCount = Enum.GetValues(typeof(TileType)).Length;
            // construct grid of tiles
            for (int x = 0; x < sizeX; x++) {
                for (int y = 0; y < sizeX; y++) {
                    tiles[x, y] = new Tile(x, y, (TileType)rnd.Next(tileCount));
                }
            }
        }
    }

    class Program {
        static void Main(string[] args) {
            World world = new World(20, 20);
            for (int y = 0; y < world.sizeY; y++) {
                for (int x = 0; x < world.sizeX; x++) {
                    Console.Write((int)world.tiles[x, y].tileType + " ");
                }
                Console.WriteLine();
            }
        }
    }
}

Once thats set up you can then interrogate a tile in your move path to see if its walkable based on the terrain type, or if it's blocked by an child obstacle etc.

\$\endgroup\$
2
  • \$\begingroup\$ Great explanation and example code for the grid! \$\endgroup\$
    – JPtheK9
    Commented Apr 27, 2015 at 17:05
  • \$\begingroup\$ Cheers @JPtheK9 \$\endgroup\$
    – DanoThom
    Commented Apr 28, 2015 at 8:20

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .