khmer2040
khmer2040

Reputation: 157

Limit object to screen edges along x axis - Unity3d

I am really new to Unity3d. I'm creating a gallery shooting game and I have a gun object that only moves along the x axis. I want to limit the movement just to the edge of the screen. Once it hits the edge of the screen, I have it hard coded to move back into the screen a few pixels. But it looks rough since it flickers when it hits the edge of the screen. I've tried setting the transforms.position.x to equal the transform.position.x once it hits the edge of the screen but the gun object will continue to move off screen.

Currently this is the code:

using UnityEngine;
using System.Collections;

public class PlayerMovement : MonoBehaviour 
{
    public float maxStrafeSpeed = 0.01f; // max move speed, recommend a very low float, will move too fast if too high
    public float maxMouseSpeed = 3.0f;  // max speed the gun will rotate to where the mouse is pointing
    private float h; // horizontal axis

    // Use this for initialization
    void Start () 
    {

    }

    // Update is called once per frame
    void Update () 
    {
        PlayerMovementStrafing ();
        PlayerMovementMouseAim ();
    }

    void PlayerMovementStrafing()
    {
        // get input
        h = Input.GetAxis ("Horizontal") * maxStrafeSpeed;

        Vector3 pos = Camera.main.WorldToViewportPoint(transform.position);

        if (pos.x < 0.0f) 
        {
            transform.position = new Vector3(-0.6f, transform.position.y, transform.position.z);
            Debug.Log("Left edge of view");
        }
        else if (pos.x > 1.0f)
        {
            transform.position = new Vector3(0.6f, transform.position.y, transform.position.z);
            Debug.Log("Right edge of view");
        }

        this.transform.Translate (new Vector3 (h, 0.0f, 0.0f));

    }

    void PlayerMovementMouseAim ()
    {
        transform.Rotate(Vector3(Input.GetAxis("Mouse Y"), Input.GetAxis("Mouse X"), 0) * (Time.deltaTime * maxMouseSpeed));
    }
}

Upvotes: 0

Views: 3929

Answers (1)

Selali Adobor
Selali Adobor

Reputation: 2157

Your logic is pretty much there, you just made a subtle error in when you're moving.

The main issue is you're clamping before you move:

void PlayerMovementStrafing()
{
   // Getting Input
    h = ... 

    //Getting the viewport location for clamping
    Camera.main.Worl...

    //Clamping the position
    if (pos.x < 0.0f) ...   
    else if (pos.x > 1.0f) ... 

    // Changes the position after we just clamped (so each turn clamping is one frame behind)
    this.transform.Translate (new Vector3 (h, 0.0f, 0.0f)); 
}

But aside from that, you should apply clamping code like this in LateUpdate. Update should work if your game is very simple, but from the docs, "LateUpdate is called after all Update functions have been called.". The docs for order of execution also show that LateUpdate is called after animations and physics each frame, and is the the last event before rendering.

Being the last event before rendering makes LateUpdate is what make it so ideal here, I defined a method that gets called there, and shrunk the strafing method.

I also used ViewportToWorldPoint to clamp the value instead of using a hardcoded value. With the hardcoded value, if the camera isn't in the exact location that you have it in now the clamping will fail (even if it's moved in the editor).

If the camera can never move and you're worried about performance, both ___to___ calls can be moved to Start and their values cached but it's very likely to be a premature optimization at this point (and open you up to subtle bugs):

I also added a call to Time.delta in the strafing method. Currently if the game isn't running at exactly 60fps, the movement will vary in speed, because Update is only called once per frame. In other word your movement speed is X units per frame. Multiplying by Time.delta (the time since the last frame) changes that to X units per second.

That means even if the FPS vary (e.g. on a slow machine), your movement speed won't. It also means you'll need to increase your speed values since Time.delta will usually be less than 1.

As a "rule", if you're in Update and you're making a call that will result in movement over time multiply by Time.deltaTime (so it includes the Translate call [you already had them for the Rotate call]). Note that in FixedUpdate you can still multiply by Time.deltaTime (which returns Time.fixedDeltaTime in that method, but you don't have to, since it's not affected by FPS.

void PlayerMovementStrafing()
{
    // get input
    h = Input.GetAxis ("Horizontal") * maxStrafeSpeed;    
    transform.Translate(new Vector3 (h, 0.0f, 0.0f) * Time.delta); //Multiply by delta time in 
}

void LateUpdate()
{
    PlayerMovementClamping();
}

void PlayerMovementClamping()
{
    var viewpointCoord = Camera.main.WorldToViewportPoint(transform.position);

    if (viewpointCoord.x < 0.0f)
    {
        Debug.Log("Left edge of view");
        viewpointCoord.x = 0.0f;
        transform.position = Camera.main.ViewportToWorldPoint(viewpointCoord);
    }
    else if (viewpointCoord.x > 1.0f)
    {
        Debug.Log("Right edge of view");
        viewpointCoord.x = 1.0f;
        transform.position = Camera.main.ViewportToWorldPoint(viewpointCoord);
    }
}

And a small note, if you aren't using the blocks for anything more than debugging you can shorten the clamping code with Mathf.Clamp01:

void PlayerMovementClamping()
{
    var viewpointCoord = Camera.main.WorldToViewportPoint(transform.position);
    viewpointCoord.x = Mathf.Clamp01(viewpointCoord.x);
    transform.position = Camera.main.ViewportToWorldPoint(viewpointCoord);
}

Upvotes: 1

Related Questions