Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Grid-Based Character Movement

Discussion in 'Scripting' started by ATLAS-INTERACTIVE, Oct 27, 2015.

  1. ATLAS-INTERACTIVE

    ATLAS-INTERACTIVE

    Joined:
    Mar 3, 2014
    Posts:
    1,421
    I am looking to implement a grid-based movement scheme to my game, with a movement tile size of 3 (each tile is 3x3), I wrote some code a long time ago for another project but can't find it anywhere and can't remember for the life of me how to do it.

    I have managed to create a grid-based movement system (which needs work with smoothness), but I am having a lot of trouble with rotating the character 90 degrees depending on the key pressed.

    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3.  
    4. public class PlayerMovement : MonoBehaviour {
    5.  
    6.     public float gridSize = 3.0f;
    7.  
    8.     private Vector3 pos;
    9.     private Transform tr;
    10.     private Quaternion rot;
    11.  
    12.     private float speed = 5.0f;
    13.     private float degree;
    14.     private float angle;
    15.  
    16.     [Header("Movement Keys")]
    17.     public KeyCode forwardKey = KeyCode.W;
    18.     public KeyCode leftKey = KeyCode.A;
    19.     public KeyCode backKey = KeyCode.S;
    20.     public KeyCode rightKey = KeyCode.D;
    21.     [Space(1)]
    22.     public KeyCode turnLeftKey = KeyCode.Q;
    23.     public KeyCode turnRightKey = KeyCode.E;
    24.  
    25.     void Start()
    26.     {
    27.         pos = transform.position;
    28.         tr = transform;
    29.         rot = transform.rotation;
    30.     }
    31.  
    32.     void Update()
    33.     {
    34.         if (Input.GetKey(forwardKey) && tr.position == pos)
    35.         {
    36.             pos += Vector3.forward * gridSize;
    37.         }
    38.  
    39.         else if (Input.GetKey(leftKey) && tr.position == pos)
    40.         {
    41.             pos += Vector3.left * gridSize;
    42.         }
    43.  
    44.         else if (Input.GetKey(backKey) && tr.position == pos)
    45.         {
    46.             pos += Vector3.back * gridSize;
    47.         }
    48.  
    49.         else if (Input.GetKey(rightKey) && tr.position == pos)
    50.         {
    51.             pos += Vector3.right * gridSize;
    52.         }
    53.  
    54.         else if (Input.GetKey(turnLeftKey) && tr.position == pos)
    55.         {
    56.             degree += 90f;
    57.         }
    58.  
    59.         else if (Input.GetKey(turnRightKey) && tr.position == pos)
    60.         {
    61.             degree -= 90f;
    62.         }
    63.  
    64.         transform.position = Vector3.MoveTowards(transform.position, pos, Time.deltaTime * speed);
    65.         angle = Mathf.MoveTowardsAngle(transform.rotation.y, degree, Time.deltaTime * 20f);
    66.         transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.Euler(0, degree, 0), Time.deltaTime * 20f);
    67.     }
    68. }
    69.  
     
    Last edited: Oct 27, 2015
  2. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,744
    What's it doing instead?
     
  3. ATLAS-INTERACTIVE

    ATLAS-INTERACTIVE

    Joined:
    Mar 3, 2014
    Posts:
    1,421
    The rotation on Q and E is dodgy at best, it rotates slowly (should be going at the same speed as movement), it gets it wrong (sometimes Q will rotate right instead of left, or go for 180 degrees instead of 90), and when I press or hold the rotation keys, the screen jitters and "vibrates" as if it is trying to fight itself.
     
  4. jmjd

    jmjd

    Joined:
    Nov 14, 2012
    Posts:
    48
    So I'm inferring a bit about how you want the controls to be, but from your code so far, it seems that you only want to be able to rotate in 90 degree increments, and you can only rotate when you're not moving.

    So, right now the issue is that when you try to rotate, the code is checking every frame if the turn keys are down, and since it's very hard to only hold a key down for a single frame, you are getting very unpredictable results.

    So, much like you are already checking to see if you have arrived at the current desired position before moving again, we want to do the same with rotation.

    So we're going to add this check to your turn code:
    Code (CSharp):
    1. Mathf.Approximately(degree, transform.rotation.eulerAngles.y) == true
    I'm using the Mathf.Approximately() function here, because comparing floats with == isn't really a good idea.

    Now there's going to be a small issue here, transform.rotation.eulerAngles.y is going to return a value between 0 and 360 degrees. Even if you rotate your player -90 degrees, it will convert that to 270 degrees. So when we are updating the degree value, we are going to want to do something similar, so that we don't compare -90 with 270. So let's change these lines:
    Code (CSharp):
    1. degree += 90f;
    2. ...
    3. degree -= 90f;
    to
    Code (CSharp):
    1. degree = Mathf.Repeat(degree + 90f, 360f);
    2. ...
    3. degree = Mathf.Repeat(degree - 90f, 360f);

    So here's the entire script. I added a rotateSpeed variable, and changed speed to moveSpeed, and made them public so I could change the values in the inspector. So change that to your liking.
    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3.  
    4. public class TempPlayerMovement : MonoBehaviour
    5. {
    6.     public float gridSize = 3.0f;
    7.     public float moveSpeed = 5.0f;
    8.     public float rotateSpeed = 360.0f;
    9.  
    10.     private Vector3 pos;
    11.     private Transform tr;
    12.     private Quaternion rot;
    13.  
    14.     private float degree;
    15.     private float angle;
    16.  
    17.     [Header("Movement Keys")]
    18.     public KeyCode forwardKey = KeyCode.W;
    19.     public KeyCode leftKey = KeyCode.A;
    20.     public KeyCode backKey = KeyCode.S;
    21.     public KeyCode rightKey = KeyCode.D;
    22.     [Space(1)]
    23.     public KeyCode turnLeftKey = KeyCode.Q;
    24.     public KeyCode turnRightKey = KeyCode.E;
    25.  
    26.     void Start()
    27.     {
    28.         pos = transform.position;
    29.         tr = transform;
    30.         rot = transform.rotation;
    31.     }
    32.  
    33.     void Update()
    34.     {
    35.         if(Input.GetKey(forwardKey) && tr.position == pos)
    36.         {
    37.             pos += Vector3.forward * gridSize;
    38.         }
    39.  
    40.         else if(Input.GetKey(leftKey) && tr.position == pos)
    41.         {
    42.             pos += Vector3.left * gridSize;
    43.         }
    44.  
    45.         else if(Input.GetKey(backKey) && tr.position == pos)
    46.         {
    47.             pos += Vector3.back * gridSize;
    48.         }
    49.  
    50.         else if(Input.GetKey(rightKey) && tr.position == pos)
    51.         {
    52.             pos += Vector3.right * gridSize;
    53.         }
    54.  
    55.         else if(Input.GetKey(turnLeftKey) && tr.position == pos && Mathf.Approximately(degree, transform.rotation.eulerAngles.y) == true)
    56.         {
    57.             degree = Mathf.Repeat(degree - 90f, 360f);
    58.         }
    59.  
    60.         else if(Input.GetKey(turnRightKey) && tr.position == pos && Mathf.Approximately(degree, transform.rotation.eulerAngles.y) == true)
    61.         {
    62.             degree = Mathf.Repeat(degree + 90f, 360f);
    63.         }
    64.  
    65.         transform.position = Vector3.MoveTowards(transform.position, pos, Time.deltaTime * moveSpeed);
    66.         angle = Mathf.MoveTowardsAngle(transform.rotation.y, degree, Time.deltaTime * 20f);
    67.         transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.Euler(0, degree, 0), Time.deltaTime * rotateSpeed);
    68.     }
    69. }
    70.  
     
  5. RockoDyne

    RockoDyne

    Joined:
    Apr 10, 2014
    Posts:
    2,234
    One thing you might want to think about is having the directions be put into an array so the directions you can go are set in stone. Have an index for the current direction, and then make sure when you translate you are always rotating by the current direction to specify the direction you are going to.

    Rotating slowly is probably because the last parameter in Quat.RotateTowards is max degrees, so it's probably only moving a degree at a time. As for vibrating and fighting itself is probably because it is, since there is nothing locking input for rotations other than that little position check and not a rotation check.
     
  6. ATLAS-INTERACTIVE

    ATLAS-INTERACTIVE

    Joined:
    Mar 3, 2014
    Posts:
    1,421
    The rotation on this works much better, thanks.

    The only real other issue I had was the forward remains as the global forward once rotated, so forward is no longer forward from the players perspective.

    This is something I have no idea on how to fix as switching it to use localPosition did nothing.
     
  7. jmjd

    jmjd

    Joined:
    Nov 14, 2012
    Posts:
    48
    If you want to move relative to the player's rotation, then instead of using absolute values like Vector3.forward, you'll want to use the player's forward. So the position updates will look something like this:
    Code (CSharp):
    1. pos += transform.forward * gridSize;

    But this will introduce problems, if the player is rotating and presses the key to move. So, instead of having all the position and rotation checks with each key press, let's move them into one condition that we'll call 'canMove'.
    Code (CSharp):
    1.         bool canMove = (tr.position == pos) && Mathf.Approximately(degree, transform.rotation.eulerAngles.y);

    Now, before we check for any key presses, check if we can move. Here's the updated script:
    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3.  
    4. public class TempPlayerMovement : MonoBehaviour
    5. {
    6.     public float gridSize = 3.0f;
    7.     public float moveSpeed = 5.0f;
    8.     public float rotateSpeed = 360.0f;
    9.  
    10.     private Vector3 pos;
    11.     private Transform tr;
    12.     private Quaternion rot;
    13.  
    14.     private float degree;
    15.     private float angle;
    16.  
    17.     [Header("Movement Keys")]
    18.     public KeyCode forwardKey = KeyCode.W;
    19.     public KeyCode leftKey = KeyCode.A;
    20.     public KeyCode backKey = KeyCode.S;
    21.     public KeyCode rightKey = KeyCode.D;
    22.     [Space(1)]
    23.     public KeyCode turnLeftKey = KeyCode.Q;
    24.     public KeyCode turnRightKey = KeyCode.E;
    25.  
    26.     void Start()
    27.     {
    28.         pos = transform.position;
    29.         tr = transform;
    30.         rot = transform.rotation;
    31.     }
    32.  
    33.     void Update()
    34.     {
    35.         bool canMove = (tr.position == pos) && Mathf.Approximately(degree, transform.rotation.eulerAngles.y);
    36.         if(canMove)
    37.         {
    38.             if(Input.GetKey(forwardKey))
    39.             {
    40.                 pos += transform.forward * gridSize;
    41.             }
    42.  
    43.             else if(Input.GetKey(leftKey))
    44.             {
    45.                 pos += -transform.right * gridSize;
    46.             }
    47.  
    48.             else if(Input.GetKey(backKey))
    49.             {
    50.                 pos += -transform.forward * gridSize;
    51.             }
    52.  
    53.             else if(Input.GetKey(rightKey))
    54.             {
    55.                 pos += transform.right * gridSize;
    56.             }
    57.  
    58.             else if(Input.GetKey(turnLeftKey))
    59.             {
    60.                 degree = Mathf.Repeat(degree - 90f, 360f);
    61.             }
    62.  
    63.             else if(Input.GetKey(turnRightKey))
    64.             {
    65.                 degree = Mathf.Repeat(degree + 90f, 360f);
    66.             }
    67.         }
    68.  
    69.         transform.position = Vector3.MoveTowards(transform.position, pos, Time.deltaTime * moveSpeed);
    70.         angle = Mathf.MoveTowardsAngle(transform.rotation.y, degree, Time.deltaTime * 20f);
    71.         transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.Euler(0, degree, 0), Time.deltaTime * rotateSpeed);
    72.     }
    73. }
    74.  
     
    Last edited: Oct 27, 2015
    UltiGamer835 likes this.
  8. UltiGamer835

    UltiGamer835

    Joined:
    Jun 5, 2013
    Posts:
    8
    I'd just like to say that you're WONDERFUL at explaining things, if you could please take a look at my question I would highly appreciate it. As for this question, the solution if so far out of my league in terms of coding that any advice I have to offer would probably destroy everything.
     
  9. ATLAS-INTERACTIVE

    ATLAS-INTERACTIVE

    Joined:
    Mar 3, 2014
    Posts:
    1,421
    Sorry to bother you all again, but I just thought of a problem with this, how do I calculate collision for walls and non-traversable areas?
     
  10. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    So for true grid based movement you can pretty much abandon all of the transform.forward stuff. It's easier to simply represent your level in an array, and have the player simply Lerp between positions as needed.

    Think MVC. Your game doesn't run in Unity, it runs on the data structure you build.

    Here are some things I've done for my grid based game:

    Set up an enum with the various directions. Add extention methods to the enum for TurnLeft and TurnRight. Add explicit conversions to an appropriate vector for each direction.

    Then set up an array for your map. At each array position store info about the passability of each grid location. Checking for collisions is as straight forward as checking if the next location is passable.

    Pathfinding becomes trivial when everything is already in a grid too.
     
  11. ATLAS-INTERACTIVE

    ATLAS-INTERACTIVE

    Joined:
    Mar 3, 2014
    Posts:
    1,421
    Sorry for the extremely delayed response, we had an emergency with one of our assets deleting another asset on import.

    I have set up a script placed on all floor tiles, aptly named FloorTiles, which I will be using as a way to detect which areas can be traversed, somehow.

    This does not help me with things like doors and gates though, which will have a toggle-able state.

    As I am using a grid-based movement, and physics is not being used at all for movement, I cannot use anything like colliders as they simply pass through them.
     
  12. SteveJ

    SteveJ

    Joined:
    Mar 26, 2010
    Posts:
    3,066
    Assuming you're creating a dungeon-crawler type game?
     
  13. ATLAS-INTERACTIVE

    ATLAS-INTERACTIVE

    Joined:
    Mar 3, 2014
    Posts:
    1,421
    Yes I am, that is why I thought a grid-based and physics-free movement system would be best.
     
  14. SteveJ

    SteveJ

    Joined:
    Mar 26, 2010
    Posts:
    3,066
    I was going to suggest you might find something in this old thread, but all of the code in there is complete crap.

    http://forum.unity3d.com/threads/first-person-grid-based-movement.46754/

    You absolutely still want to use colliders for your doors, walls, etc. That really has nothing to do with the other elements of "physics" in general.

    Place your environment (walls, door, etc) into a layer of their own. When you test for movement possibilities, raycast against that layer to determine if anything is in your way. This is sweet for things like pitfalls etc as you can toggle the layer on them when they're passable vs impassable. You can do the same with doors (toggle the layer) or simply leave them on the obstacle layer as by nature they'll be out of the raycast when open (i.e. up in the roof, inside a wall, etc).
     
  15. ATLAS-INTERACTIVE

    ATLAS-INTERACTIVE

    Joined:
    Mar 3, 2014
    Posts:
    1,421
    Doesn't using a raycast require me to use colliders though, and use quite a lot of computing power?
     
  16. rayhankhalid

    rayhankhalid

    Joined:
    Jun 28, 2022
    Posts:
    1
    if u mind can u teach me how to make a script of rotating rectangle object while moving
     
  17. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,797
    Please don't pick random threads from 2015 to necro-post. That's not a useful or successful life strategy.

    Instead, go do tutorials. LOTS of tutorials by the sounds of our question.

    Tutorials and example code are great, but keep this in mind to maximize your success and minimize your frustration:

    How to do tutorials properly, two (2) simple steps to success:

    Step 1. Follow the tutorial and do every single step of the tutorial 100% precisely the way it is shown. Even the slightest deviation (even a single character!) generally ends in disaster. That's how software engineering works. Every step must be taken, every single letter must be spelled, capitalized, punctuated and spaced (or not spaced) properly, literally NOTHING can be omitted or skipped.

    Fortunately this is the easiest part to get right: Be a robot. Don't make any mistakes.
    BE PERFECT IN EVERYTHING YOU DO HERE!!

    If you get any errors, learn how to read the error code and fix your error. Google is your friend here. Do NOT continue until you fix your error. Your error will probably be somewhere near the parenthesis numbers (line and character position) in the file. It is almost CERTAINLY your typo causing the error, so look again and fix it.

    Step 2. Go back and work through every part of the tutorial again, and this time explain it to your doggie. See how I am doing that in my avatar picture? If you have no dog, explain it to your house plant. If you are unable to explain any part of it, STOP. DO NOT PROCEED. Now go learn how that part works. Read the documentation on the functions involved. Go back to the tutorial and try to figure out WHY they did that. This is the part that takes a LOT of time when you are new. It might take days or weeks to work through a single 5-minute tutorial. Stick with it. You will learn.

    Step 2 is the part everybody seems to miss. Without Step 2 you are simply a code-typing monkey and outside of the specific tutorial you did, you will be completely lost. If you want to learn, you MUST do Step 2.

    Of course, all this presupposes no errors in the tutorial. For certain tutorial makers (like Unity, Brackeys, Imphenzia, Sebastian Lague) this is usually the case. For some other less-well-known content creators, this is less true. Read the comments on the video: did anyone have issues like you did? If there's an error, you will NEVER be the first guy to find it.

    Beyond that, Step 3, 4, 5 and 6 become easy because you already understand!

    Finally, when you have errors...

    Remember: NOBODY here memorizes error codes. That's not a thing. The error code is absolutely the least useful part of the error. It serves no purpose at all. Forget the error code. Put it out of your mind.

    The complete error message contains everything you need to know to fix the error yourself.

    The important parts of the error message are:

    - the description of the error itself (google this; you are NEVER the first one!)
    - the file it occurred in (critical!)
    - the line number and character position (the two numbers in parentheses)
    - also possibly useful is the stack trace (all the lines of text in the lower console window)

    Always start with the FIRST error in the console window, as sometimes that error causes or compounds some or all of the subsequent errors. Often the error will be immediately prior to the indicated line, so make sure to check there as well.

    All of that information is in the actual error message and you must pay attention to it. Learn how to identify it instantly so you don't have to stop your progress and fiddle around with the forum.

    Imphenzia: How Did I Learn To Make Games: