Search Unity

Simple Object 2D Movement

Discussion in 'Getting Started' started by CreedGameStudios, Sep 23, 2016.

  1. CreedGameStudios

    CreedGameStudios

    Joined:
    Aug 26, 2016
    Posts:
    44
    I'm now at the point where animation is needed, the tile(s) will have to go from a,b to x,y (usually just a vertical drop). However, I'm not seeing a specific function in Unity to say 'Move from here to there' in tutorials and such. Assuming there isn't (though it would probably be a welcome addition), it seems an approach like the Rougelike Tutorial uses is the best approach.

    1) Is this correct? Or is there a simpler way to process that movement?

    2) Rather than tracking a bunch of objects, could something like the 'Smooth Movement' function in Rougelike be added to each of the Prefabs for the tiles, so the script can just say "Hey, you! Go over there now" and the tile essentially handles its own movement?

    Any other pointers or suggestions (the simpler the better!) are also appreciated!
     
  2. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    There are third-party "tweening" (interpolation) libraries like DOTween that allow you to say "move from here to there" if you really want to.

    However, sooner or later you're going to need some other behaviors for those objects, so I suggest you should start now! Create a "MoveTo" script, with code that looks something like this:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class MoveTo : MonoBehaviour {
    4.     public Vector3 target;
    5.     public float speed = 10f;
    6.  
    7.     void Update() {
    8.         transform.position = Vector3.MoveTowards(transform.position, target, speed * Time.deltaTime);
    9.     }
    10. }
    Now you can attach this script to anything that needs to move sometimes, and just set the target and speed to make it smoothly move wherever you want.
     
  3. CreedGameStudios

    CreedGameStudios

    Joined:
    Aug 26, 2016
    Posts:
    44
    Okay, great, that makes sense and it's simple.

    One more thing, though, when I see scripts like that, they usually have something to say 'if you're really close to the target, just move directly TO the target.' (So they don't linger at 1.987 instead of 2, I get that part) Is that necessary, especially if I want to call the transform.position of an object when I click on it? Or is that just being very specific?
     
    Last edited: Sep 23, 2016
  4. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    No, that's not necessary. MoveTowards already does that.
     
  5. CreedGameStudios

    CreedGameStudios

    Joined:
    Aug 26, 2016
    Posts:
    44
    Probably a silly question, but I keep expecting to see a constructor in that code. However, my guess is that instead I'll just set someTile.target to a Vector for the new location, and someTile.speed if desired. Is that right?

    (Apologies, but once I'm 100% on something, I fully 'get it' and can adapt it going forward.)
     
  6. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    That's right. (And in fact, you can't use constructors with MonoBehaviour subclasses.)
     
  7. CreedGameStudios

    CreedGameStudios

    Joined:
    Aug 26, 2016
    Posts:
    44
    Okay good. But I think there might be an issue if the object isn't Instantiated yet, maybe? To test this, I wrote (in a KeyUp triggered function):
    GameObject.Find("0-0").target = new Vector3(9,9,0);

    Tiles are named for their coordinates, and there will always be something at 0-0 to test with. But I'm getting an error when saving the script:
    Type `UnityEngine.GameObject' does not contain a definition for `target' and no extension method `target' of type `UnityEngine.GameObject' could be found (are you missing a using directive or an assembly reference?)

    Is my wording off here, or do I need to GetComponent every time I reference an Object to move it? Or is there something else at work here...
     
  8. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    When coding, you always have to think about the types of things.

    GameObject.Find returns a GameObject. If you follow that link you can look at all the methods and properties that GameObject has. Sure enough, there is no 'target' property here, thus the compiler is quite correct.

    So back up. What are you trying to set the target of? Oh right, the MoveTo component. So yes, you need to use GetComponent<MoveTo>() to get a reference to that.

    Of course you only need to call GetComponent<MoveTo>() every time if you're not storing that reference somewhere handy for future use. But I wouldn't worry about it too much... compared to GameObject.Find, which in general is really slow, GetComponent is trivial.
     
    CreedGameStudios likes this.
  9. CreedGameStudios

    CreedGameStudios

    Joined:
    Aug 26, 2016
    Posts:
    44
    Over the course of even one level, as tiles are destroyed and new ones created (possibly in the hundreds), that's a lot of components to get. But if the code to animate a tile's movement is that simple, it seems easy to have the script identify an object and use the MoveTo function to transport it. I'll give it a try...
     
  10. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    No, that's a lot (perhaps too many) GameObjects to .Find. Seriously. That is something to watch out for. (GetComponent is insignificant compared to that.)

    (But first, get your game working by whatever means possible. Worry about optimizing performance later.)
     
  11. CreedGameStudios

    CreedGameStudios

    Joined:
    Aug 26, 2016
    Posts:
    44
    Thanks. Ugh, head meets desk again... I tried to implement this in the part of the script where, after matched tiles are removed, remaining tiles are lowered. This function already tests fine, just without animation, so hopefully I would now be able to see them fall.

    Code (CSharp):
    1. float stp = 50f * Time.deltaTime;
    2. GameObject targetGo = GameObject.Find(oldname);
    3. Vector3 oldLoc = new Vector3(x - cpos, cyy - rpos, 0);   //rpos/cpos shift the grid visually to the center
    4. Vector3 newLoc = new Vector3(x - cpos, y - rpos, 0);
    5. targetGo.transform.position = Vector3.MoveTowards(newLoc, oldLoc, stp);
    First, I thought had it backwards by having the new location first, but when I swap them,the tiles fly UP not down. Odd.

    Second, I'm missing something here, as one of two things happen when I try to adjust the value of stp:
    1) The tiles immediately appear at the new location without any visual movement
    2) The tiles move to the wrong location, stopping short of their target.

    I don't understand why, I thought the third parameter in the MoveTowards just set the speed of the movement. But it's literally the ONLY thing I'm altering that causes the change between the two results.
     
  12. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Yes, you're missing the big picture here. I will try to help, but I think you would be best served by going back and working through some of the tutorials under the "Learn" link at the top of this page. There are fundamental concepts about how Unity works that you haven't grasped, and this is going to cause you no end of grief until you get them.

    Take another look at the script I gave you. Notice that it's doing all its work in the Update method. This gets called on every frame, and its job is to move things just a little bit. And then on the next frame, it gets called again and moves things again. This is how animation happens.

    Now study the docs for MoveTowards, including the sample code given there, as well as how I used it in my script. You will find that the first parameter is the current value of the thing you're moving. Definitely not the new location. The second parameter is what you're moving it towards (certainly not the old location!), and the third parameter is how much you want to move it by. And you expect it to return a value moved just a little bit from the the first parameter towards the second. So calling this once, in (as you say) the part of the script where remaining tiles are lowered, is just wrong. It has to be called on every frame.

    So, throw out your code above, and instead GetComponent<MoveTo>() and set the .target of that. This script is responsible for moving things, and it does so with code in Update that moves a little bit on every frame. Where your code is, you just need to trigger this movement by setting a new target.
     
    CreedGameStudios likes this.
  13. CreedGameStudios

    CreedGameStudios

    Joined:
    Aug 26, 2016
    Posts:
    44
    Oh for pete's sake.. yeah, same as the sample for MoveTowards, in the Update function. Not sure why I thought it could be moved out of an Update function, but it makes sense now.

    One last thing. When I tried it with your MoveTo sample attached to all the prefabs, an odd thing happened. All the tiles flew to the center (0, 0 coordinates on screen) of the grid. Whenever matches were made (which happens in a numerical array), new tiles also flew to the center. Is that because 'target' doesn't have a value to start? I can easily assign that value when tiles are created, if that's needed. (They're currently placed with a Vector 3 based on their grid coordinate, with an offset to keep the grid centered in the display.)
     
  14. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    No, Vector3 (just like int or float) always has a value. If you don't give it a different one, that value is (0,0,0).

    So, yeah, that's where these things would MoveTo if you don't tell them otherwise.

    Yep, that's a good solution.

    Another solution would be to make MoveTo assume that objects start where they should be, by assigning the current transform.position to the target in Start. The problem with this is, if you ever want to create an object at runtime and then make it immediately start moving somewhere, that will be hard to accomplish.
     
    CreedGameStudios likes this.
  15. CreedGameStudios

    CreedGameStudios

    Joined:
    Aug 26, 2016
    Posts:
    44
    Funny, while walking the dog I thought 'Couldn't you just assign the current transform.position in Start?' Good point to remember about that method, though.

    And we have movement! Thanks again! Not just for the patient explanation, but for explaining the how/why a bit. For me at least, it helps a lot with understanding the concepts for future use in other applications of it.
     
    JoeStrout likes this.
  16. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,144
    Alternatively you can use the Awake() method as a replacement for the constructor though it's primarily intended for advanced initialization as well as situations where you need the entire scene loaded (like the various forms of GameObject.Find).

    By the way there is an entry in the docs that shows the order the various event methods (Awake(), Start(), Update(), etc) are called as well as a brief explanation for each (their own docs pages give a more in-depth explanation).

    https://docs.unity3d.com/Manual/ExecutionOrder.html
     
    Last edited: Sep 25, 2016
    Kiwasi and JoeStrout like this.
  17. Deleted User

    Deleted User

    Guest

    I only skimmed this but any time you're bringing many things into and out of existence in Unity you're going to want to write one function that instantiates them all, stuffs them in a List, but disables them as it does. Then you're going to yoink them back out and activate them, and probably likewise recycle them to where they came from.

    This is called object pooling, sounds like you should be aware of that. At this point in my own development I suspect most "involved" Unity games include an object pool of some kind. Right now I just make pools for different kinds of things.


    So, nobody posted the reason these are like this, and this is a really core fundamental. Essentially, those people are using Linear Interpolation straight-up "wrong" and getting away with it for pragmatic reasons. Since I have learned how to properly lerp on a curve I now resent people who do this in a small, silly way - because the only reason I never do that is that it bugs me.

    I would explain, but I need to sign off; and frankly this person does it better:

    https://chicounity3d.wordpress.com/2014/05/23/how-to-lerp-like-a-pro/