2D transform.position using Mathf.Sin or lerp Functions???

Hello! Here’s a video of what I am working on!

I am making a space shooter, as we can see in video! I want my first enemies to simple move side to side. I thought I could add Mathf.Sin() somwhere in the transform.position function during update, but my sprites stay stationary, there’s not continual side to side movement. It’s what we see in video. Here is my script. I am notorious for providing too little detail, please let me know if you need more because I WILL provide it happily!

:slight_smile:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class squidController2 : MonoBehaviour
{
    public float xPos;
    // Start is called before the first frame update
    void Start()
    {
        xPos = transform.position.x;
    }

    // Update is called once per frame
    void Update()
    {
        transform.position = new Vector3(Mathf.Sin(xPos), transform.position.y, 0.0f);//I THOUGHT THIS WOULD MOVE THE SPRITE SIDE TO SIDE 2 UNITS
    }
}

hello
Base that sin on their world y. Not local y.
Imagine squids are moving on the conveyor belt.
You have your cumulative background position, and their own location on this, this is their world y.

Imagine this image was your level, rotated by 90 degrees

So you want your x motion to be what is y on this image

1 Like

Don’t forget you also have to transform this position into radians, to have it nice like in this picture
You can just multiply Pi with it.

so it should be something like this

float sineMotion = Mathf.Sin((playerGlobalPos.y + enemyScreenPos.y) * Mathf.PI * correctiveFactor);
enemyPos.x = Screen.width * (sineMotion + .5f); // to get it from -1 .. +1 to a true position on screen

Here I’m using some playerGlobalPos as a metric for how far ahead your player has gone into, in screen space. And we can offset this by enemyScreenPos, to find its global position as well. So in turn playerGlobalPos.y + enemyScreenPos.y should compute where exactly the squid is lying on this conveyor belt. You then multiply this by PI to stretch it a bit, just to illustrate a point, but in fact, you can skip this altogether, and use some manual factor instead, tweaked until the motion resembles something decent.

You can expose that factor as a [SerializeField] public float sineFactor = 1f;
and then tweak it from the inspector.

1 Like

Thank you Orion! You are awesome help!

1 Like

I made a few mistakes there, it should be Screen.width * (sineMotion + 1f) / 2f
but hopefully you understood the gist of it

as sine returns from -1 … +1
you want to get 0 … Screen.width

so sineMotion + 1f
gets you 0 … 2

you then / 2
to get 0 … 1

and then * Screen.width
for 0 … Screen.width

a negligibly faster (but more beginner-unfriendly way) would be

(Screen.width >> 1) * (sineMotion + 1f)

because Screen.width is an int and the result is effectively the same as Screen.width / 2

Of course, the result will end up in screen units (pixels), and you want it in world units (which do not have to correspond with your screen), so you either work with something else instead of Screen.width (you can just fix it to a known value), or you have some conversion ratio, for example SCREEN_TO_WORLD and then multiply with that.

I don’t know your actual setup, and all of this assumes your left edge aligns with the Y axis in Unity (in 2D setup). If your center line aligns with the Y axis, then your sine output is already good, you just need to multiply its output without adding 1, and so on.

1 Like

I’ve updated the last post, hopefully you got it right

1 Like

In any case, from your sine situation alone, I hope you can see the benefit of aligning your world so that the Y axis runs through the middle. It just simplifies things along the way.

1 Like

My first attempt to incorporate it was a mess! It pulls it far negative on my x axis. I don’t think I followed the instructions well. I am going to attempt this again. I never know if I put quite enough info up here.

CODE ON SQUID

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class squidController2 : MonoBehaviour
{
    public float enemyPosX;
    [SerializeField] public float correctiveFactor = 1f;
    public Transform playerGlobalPos;
    public float playerGlobalPosY;
    public Transform enemyScreenPos;
    public float enemyScreenPosY;

    // Start is called before the first frame update
    void Start()
    {
        playerGlobalPosY = playerGlobalPos.transform.position.y;
        enemyScreenPosY = enemyScreenPos.transform.position.y;
        enemyPosX = transform.position.x;
    }

    // Update is called once per frame
    void Update()
    {

       
        float sineMotion = Mathf.Sin((playerGlobalPosY + enemyScreenPosY) * Mathf.PI * correctiveFactor);
        enemyPosX = Screen.width * (sineMotion + .5f); // to get it from -1 .. +1 to a true position on screen
        transform.position = new Vector3(enemyPosX, transform.position.y, 0.0f);
    }
}

I will post more details and a second try, because you added more info before I tried anything further than my latest post.

I’m working on getting it in there! I’ll be sure to reply. I will be pulled away soon for a quick time though!

You’re really supposed to keep only relevant info inside SquidController, namely either its screen position, or its world position. If you have both that’s not really useful. Also you don’t need player position in there.

Maybe this is why people have difficulties understanding my code, but you’re really supposed to have a good grip of organizing things, so that you can grab 'em where you need them. But that’s really outside of my scope (because I can’t see the rest of the project), I can’t tell everything here, from how to organize hierarchy, to how many scripts you need, where to attach them and all.

Basically, try to keep things separated, to begin with. You want your squid to not know anything about your player, and vice versa. What I said back at the beginning is strictly how to obtain good values from the Sin function, and why it doesn’t behave well, not how your script should look exactly.

1 Like

Try for starters to have a system in which every single entity, that runs on this background, rely strictly on world coordinates, saved in their transform.localPosition. That way, you don’t need no variables to track this down.

Make a long background strip for all these entities to spawn on.

Then apply the inverted logic on the camera, and instead of moving the camera, parent everything into one single object (except the camera), and move that the other way. Internally, each entity will have its relative position to the “world”, but you don’t move the camera anywhere, at least from your own global perspective.

This is a good setup for a shmup. It lets you keep things centered around the world origin, while keeping relevant positional data consistent throughout.

It also lets you to make Squid move in a sinewave fashion, by letting it simply read its own position in space, and transform its x coordinate based on that.

public class SquidController : MonoBehaviour {

  public float screenWidthInUnits = 1f; // will show up in inspector
  public float sineFactor = 1f; // will show up in inspector

  void Update() {
    var pos = transform.localPosition;
    pos = new Vector2(screenWidthInUnits * Mathf.Sin(pos.y * sineFactor), pos.y);
    transform.localPosition = pos;
  }

}

If your camera is directly on the Y axis (meaning its X coordinate is 0), then that’s all there is to it. However the squid should also move up or down, or else it won’t move anywhere.

If you don’t want to move it down or up, along the track, then you need to animate something else through time.

public class SquidController : MonoBehaviour {

  public float screenWidthInUnits = 1f; // will show up in inspector
  public float sineFactor = 1f; // will show up in inspector
  [Range(0f, 1f)] public float timeFactor = 1f; // will show up in inspector

  private float time = 0f; // won't show up in inspector

  void Update() {
    var pos = transform.localPosition;
    pos = new Vector2(screenWidthInUnits * Mathf.Sin(time * sineFactor), pos.y);
    transform.localPosition = pos;

    time += Time.deltaTime * timeFactor; // time has to increase with each frame
  }

}

In this case, we use the passage of time as the rolling factor for the sine to be animated.

1 Like

Wow Orion! You’re the man! I think my problem is experience related, my experience is mostly limited to my video tutorial addiction. I have probably 7 or so projects of different type I work on. You’re very articulate and sensible in explanation. But sometimes I run into things I think are slightly outside my current understanding, I am building my knowledge though! Such as I tried, in a different project, to clamp the mouse input with old input system And mathf.clamp, but it seemed like my mathf function was completely ignored. I’m definitely gaining traction daily, so I’ll understand soon! I’ll be looking at this carefully today, got to run someone somewhere now. But, the kettle is ready to heat up for tea right when I’m back!

I love the suggestion of childing world to object and adding motion to the world, my motion is indeed on camera and ship

I have a background strip that is stationary, and I used rect transform to resize and move my game objects. I selected all of them and duplicated them to make the work twice as long, I added a galaxy over the seam.

My ship and Camera have separate movement/controller scripts. I have a controller on the squid.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class cameraController : MonoBehaviour
{
    public float yPosition;
    // Start is called before the first frame update
    void Start()
    {
       
    }

    // Update is called once per frame
    void Update()
    {
        yPosition += 0.05f;
        if (yPosition < 51.0f)
        {
            transform.position = new Vector3(0.0f/*touchPosition.x transform.position.x*/, yPosition, 0.0f);
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class touchPC : MonoBehaviour
{
    //public GameObject bullet;
    //Vector2 bulletPos;
    //public float fireRate = 0.5f;
    //public Transform firePoint;
    //public int maxHealth = 100;
    //public int currentHealth;
    //public HealthBar healthBar;

    public float yPosition;
    public float xPosition;

    void Awake()
    {
        //currentHealth = maxHealth;
        //healthBar.SetMaxHealth(maxHealth);
    }
   
    void Start()
    {
        yPosition = transform.position.y;
    }

    // Update is called once per frame
    void Update()
    {
        yPosition += 0.05f;

        if (Input.touchCount > 0)
        {
            Touch touch = Input.GetTouch(0);
            Vector3 touchPosition = Camera.main.ScreenToWorldPoint(touch.position);
            touchPosition.z = 0f;
           
            xPosition = Mathf.Clamp(touchPosition.x * 0.75f, -2.0f, 2.0f);
           
            //fire();
            //Debug.Log("Fire!");
        }
        if (yPosition < 47.0f)
        {
            transform.position = new Vector3(xPosition/*touchPosition.x transform.position.x*/, yPosition, 0.0f);
        }
        /*if (Input.touchCount > 1)
        {
            TakeDamage(20);
        }*/
        //healthBar.SetHealth(currentHealth);
    }

    void fire()
    {
        //bulletPos = transform.position;
        //Instantiate(bullet, bulletPos, Quaternion.identity);
    }

    void TakeDamage(int damage)
    {
        //currentHealth -= damage;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class squidController2 : MonoBehaviour
{
    public float enemyPosX;
    [SerializeField] public float correctiveFactor = 1f;
    public Transform playerGlobalPos;
    public float playerGlobalPosY;
    public Transform enemyScreenPos;
    public float enemyScreenPosY;

    // Start is called before the first frame update
    void Start()
    {
        playerGlobalPosY = playerGlobalPos.transform.position.y;
        enemyScreenPosY = enemyScreenPos.transform.position.y;
        enemyPosX = transform.position.x;
    }

    // Update is called once per frame
    void Update()
    {

       
        float sineMotion = Mathf.Sin((playerGlobalPosY + enemyScreenPosY) * Mathf.PI * correctiveFactor);
        enemyPosX = Screen.width * (sineMotion + .5f); // to get it from -1 .. +1 to a true position on screen
        transform.position = new Vector3(enemyPosX, transform.position.y, 0.0f);
    }
}

I know it’s risky to download files on the internet and everything, and I also don’t know the recommended way to share project files. Anyhow, I uploaded a .zip of the project files to drop box.

https://www.dropbox.com/s/cs2wezrzj5rytkl/spaceShooter0.0.2.zip?dl=0

And here’s what I meant originally. If you have the world move like this, then the Squids global position will truly move along with it, even though it appears to be standing still relative to background.

To get this global position, you use position, as opposed to localPosition, which is the actual position of the Squid relative to its parent. This is perhaps a confusing choice of words, on Unity’s part, but it is what it is. You’ll get used to it.

Now, let’s use this global position to influence sine motion.

public class SquidController : MonoBehaviour {

  public float screenWidthInUnits = 1f; // will show up in inspector
  public float sineFactor = 1f; // will show up in inspector

  void Update() {
    var pos = transform.localPosition;
    pos = new Vector2(screenWidthInUnits * Mathf.Sin(transform.position.y * sineFactor), pos.y); // notice the difference
    transform.localPosition = pos;
  }

}

Additionally, in this example, you can make the squid look at the player i.e. but now you need to figure out how to get the player’s position.

In this case the simplest approach would be to expose such globally interesting information through some singleton object. Singleton means it is guaranteed to have just one instance, so that you don’t mistakenly copy its data and make a mess.

public class PublicInfo : MonoBehaviour {

  static private PublicInfo _instance;
  static public PublicInfo Instance => _instance;

  public GameObject player; // we'll use this to hook up the player in the inspector

  void Awake() { // when this object awakens it stores a reference to self to a static field above
    _instance = this;
  }

}

Now you attach this script to some empty top level gameobject, called Game for example.
Drag n drop Player from your hieararchy onto the eponymous field in the inspector of this script.

You can now access this field by calling PublicInfo.Instance.player from any other script.
And now, you can do this.

public class SquidController : MonoBehaviour {

  public float screenWidthInUnits = 1f; // will show up in inspector
  public float sineFactor = 1f; // will show up in inspector

  void Update() {
    // sine wave motion
    var pos = transform.localPosition;
    pos = new Vector2(screenWidthInUnits * Mathf.Sin(transform.position.y * sineFactor), pos.y);
    transform.localPosition = pos;

    // look at the player logic
    var playerPos = PublicInfo.Instance.player.transform.localPosition;
    var diffPos = playerPos - pos;
    var angle = Math.Atan2(diffPos.y, diffPos.x); // in radians

    // but Euler function accepts degrees so you need to multiply angle with this constant;
    transform.localRotation = Quaternion.Euler(0f, 0f, angle * Mathf.Rad2Deg);
  }

}

The reason why Euler looks like this (0f, 0f, something) is because the rotation is happening on the Z axis, because you’re working on the XY plane.

edit: made a mistake in how PublicInfo is accessed!

1 Like

I am moving so quickly at the moment, I will have more time later. All this next info is simply next attempt, I am continuing my Work in Progress on following the advice and fixing the issue! Thank you so much! File included for fun or whatever! :slight_smile:

https://www.dropbox.com/s/cs2wezrzj5rytkl/spaceShooter0.0.2.zip?dl=0

edited same dropbox link I guess

//ADDED THIS AS NEW EMPTY


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PublicInfo : MonoBehaviour
{

    static private PublicInfo _instance;
    static public PublicInfo Instance => _instance;

    public GameObject player; // we'll use this to hook up the player in the inspector

    void Awake()
    { // when this object awakens it stores a reference to self to a static field above
        _instance = this;
    }

}
//ALTERED SQUIDCONTROLLER

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class squidController2 : MonoBehaviour
{
    /*public float enemyPosX;
    [SerializeField] public float correctiveFactor = 1f;
    public Transform playerGlobalPos;
    public float playerGlobalPosY;
    public Transform enemyScreenPos;
    public float enemyScreenPosY;

    // Start is called before the first frame update
    void Start()
    {
        playerGlobalPosY = playerGlobalPos.transform.position.y;
        enemyScreenPosY = enemyScreenPos.transform.position.y;
        enemyPosX = transform.position.x;
    }

    // Update is called once per frame
    void Update()
    {

       
        float sineMotion = Mathf.Sin((playerGlobalPosY + enemyScreenPosY) * Mathf.PI * correctiveFactor);
        enemyPosX = Screen.width * (sineMotion + .5f); // to get it from -1 .. +1 to a true position on screen
        transform.position = new Vector3(enemyPosX, transform.position.y, 0.0f);
    }*/
    public float screenWidthInUnits = 1f; // will show up in inspector
    public float sineFactor = 1f; // will show up in inspector

    void Update()
    {
        var pos = transform.localPosition;
        pos = new Vector2(screenWidthInUnits * Mathf.Sin(transform.position.y * sineFactor), pos.y); // notice the difference
        transform.localPosition = pos;
    }
}

It works! Thank you OrionS.!

I will post a follow-up, as I now need to examine what is really going on here, and what code I have in my game that is now useless and should be removed or altered!

1 Like