Zeid Tisnes
Zeid Tisnes

Reputation: 408

How to create a camera that combines both forced movement and smooth player following

Context

So I'm making a clone of Crossy Roads where the camera follows the player. It does some linear interpolation (Lerp) after moving, and the camera starts moving away from the player in the positive direction (x-axis until camera reaches to a certain range where player is not visible enough). Things I have tried is by flagging it, but I think I'm doing it wrong.

Problem

I have done my camera movements accordingly, but I am having an issue where the conditions are not properly met. I'm get the offset camera after not moving, but it does not the Lerp, and vice-versa. I want both to happen after a certain condition after the game starts. When the player moves, the camera follows it in Lerp. However, once the player is "Idle", its still Lerping. I want the camera to continue by itself and at the same time focus at the player's object.

Example

Camera with Lerp, but not moving away from the player

Camera Lerps after moving, but it doesn't move away from the player

Camera moving away, but not following player with lerp

enter image description here

Code

CameraController.cs

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

/*
** Camera following player (Smoothing and angle): https://youtu.be/4HpC--2iowE
** Maximo: https://www.youtube.com/watch?v=bXNFxQpp2qk&ab_channel=iHeartGameDev
** Moving character relative to camera: https://forum.unity.com/threads/moving-character-relative-to-camera.383086/
** Camera follow v2: https://youtu.be/Jpqt2gRHXtc?list=PLq_nO-RwB516fNlRBce0GbtJSfysAjOgU
*/

public class CameraController : MonoBehaviour
{
    public GameObject player;
    public PlayerControl playerControlScript;
    private Vector3 newCameraPos;
    public bool stillIdle;


    void Start()
    {
        stillIdle = false; 
        PlayerControl playerControlScript = GetComponent<PlayerControl>();
    }

    void LateUpdate()
    {
        player = GameObject.FindGameObjectWithTag("Player");
        if (playerControlScript.GetfirstInput()) //True
        {
            stillIdle = true;
            newCameraPos = Vector3.Lerp(transform.position, playerControlScript.transform.position, Time.deltaTime);
            transform.position = new Vector3(newCameraPos.x, 1, newCameraPos.z);
        }
        if (stillIdle)
            transform.position = new Vector3(transform.position.x + 0.69f * Time.deltaTime, transform.position.y, transform.position.z); //Moving camera away effect
    }
}

PlayerControl.cs

public class PlayerControl : MonoBehaviour
{
    bool firstInput;
    Vector3 startPos;
    Vector3 endPos;
    public bool GetfirstInput() //I was learning how to have a Get function while my member was private from another script file
    {
        return firstInput;
    }
    void Update()
    {
         if (Input.GetButtonDown("up") || Input.GetButtonDown("left") || Input.GetButtonDown("right") || Input.GetButtonDown("down"))
        {
            //if game starts
            {
                //Other variables being initialized here
                firstInput = true;
            }
        }
        
    }
}

Hierarchy/Inspector

Main Camera

enter image description here

Player Object

enter image description here

Some help would be appreciate it. I feel I have been staring at this problem and I bet it is something minimal and small from just thinking it.

Let me know if you need clarifications. I'm happy to edit and answer them for everyone

Upvotes: 1

Views: 223

Answers (1)

Willard Peng
Willard Peng

Reputation: 344

  1. If player in your game does not change, you don't have to find the player reference in each LateUpdate().
  2. I notice that once stillIdle is set true, it never goes back to false, is this your intention to do that?
  3. You call Lerp when playerControlScript.GetfirstInput() is true, so maybe we need to look at its implementation. Maybe it turns true in some conditions you do not intend it to.

Maybe Try this

public class PlayerControl : MonoBehaviour
{

    private bool _hasFireFirstInput = false;
    private bool _isIdle = true;

    public bool IsIdle => _isIdle;
    public bool HasFireFirstInput => _hasFireFirstInput;

    private void Update()
    {
        if (Input.GetButton("Horizontal") || Input.GetButton("Vertical"))
        {
            _hasFireFirstInput = true;
            _isIdle = false;


            Vector3 pos = transform.position;
            pos = new Vector3(pos.x, pos.y, pos.z + .2f * Time.deltaTime);
            transform.position = pos;
        }
        else
        {
            _isIdle = true;
        }
    }
}

I use Input.GetButton() rather than Input.GetButtonDown(), since the later only return true at the frame the button is pressed, meaning that if i long-press the button, it will return false after the next frame.

public class CameraController : MonoBehaviour
{
    [SerializeField] PlayerControl _playerControlScript;
    [SerializeField] Vector3 _offset;
    [SerializeField] float _lerpSpeed;

    bool _iskeepLerping = false;
    float _lerpVal = 0f;

    private void LateUpdate()
    {
        if (!_playerControlScript.HasFireFirstInput)
        {
            return;
        }

        if (_playerControlScript.IsIdle)
        {
            MoveAway();
        }
        else
        {
            Lerp();
        }
    }

    private void MoveAway()
    {
        _iskeepLerping = false;
        transform.position = new Vector3(transform.position.x + 0.69f * Time.deltaTime, transform.position.y, transform.position.z);
    }

    private void Lerp()
    {
        if (!_iskeepLerping)
        {
            _lerpVal = 0f;
        }
        
        Vector3 newCameraPos = Vector3.Lerp(transform.position, _playerControlScript.transform.position + _offset, _lerpVal);
        transform.position = newCameraPos;
        _lerpVal += _lerpSpeed * Time.deltaTime;
        _iskeepLerping = true;
    }
}

enter image description here

Upvotes: 1

Related Questions