I am trying to recreate a "GTA-Style" third person camera in Unity. I created code that rotates the player in the direction the camera is pointing. However, the camera is a child of the player, so when I rotate the player, I also rotate the camera. So I have to reset the camera's Y rotation. which in "turn" causes a frame or two to have the camera looking in a different direction.
Hierarchy of Player:
Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ThirdPersonCamera : MonoBehaviour
{
public float rotationSpeed = 2f;
public Transform target, player;
float mouseX;
float mouseY;
float h;
float v;
// Start is called before the first frame update
void Start()
{
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
void LateUpdate()
{
h = Input.GetAxis("Horizontal");
v = Input.GetAxis("Vertical");
mouseX += Input.GetAxis("Mouse X") * rotationSpeed;
mouseY -= Input.GetAxis("Mouse Y") * rotationSpeed;
mouseY = Mathf.Clamp(mouseY, -35, 60);
CamControl();
}
void CamControl()
{
transform.LookAt(target);
if (h != 0 || v != 0)
{
target.rotation = Quaternion.Euler(mouseY, mouseX, 0);
player.rotation = Quaternion.Euler(0, mouseX, 0);
}
else
{
target.rotation = Quaternion.Euler(mouseY, mouseX, 0);
while (h != 0 || v != 0)
{
target.rotation = Quaternion.Euler(target.rotation.x, 0, target.rotation.z);
player.rotation = Quaternion.Euler(player.rotation.x, player.rotation.y - target.rotation.y + 180, player.rotation.z);
}
}
}
}
How this script works:
How I have it set up on Main Camera. (Notice hierarchy setup)
Note: The "Player" and "Target" variables have the same name as the GameObjects assigned to them.
If there is no input on Horizontal and Vertical axes, the camera will rotate around the player like normal.
If there is input on the Horizontal and Vertical axes, the player will face the direction the camera is facing. Then it has to reset the camera's Y rotation.
What I have tried:
Using slerp to smooth out the camera angles.
Note: I have TRIED slerping, I'm not sure that I did it correctly.
Unchilding the Target (Camera)
Result: This just made the camera not follow the player, but it shows that my code to turn the player is somewhat functional.
Setting the Player (in script) to the graphics.
Result: This gave me an odd view of the player, and made the player rotate up and down as well.
Sorry for the long and complicated post!
If your code to rotate the player and the camera works when unchilding the target from the player, then don't child the target of camera in the player.
Instead, make the target follow the player.
For instance:
void CamControl()
{
target.position = player.position;
transform.LookAt(target);
...
}
You can then do a smooth follow using a SmoothDamp for example, instead of copying directly the player position.
Moreover, if your player is controlled by physics (using the rigidbody for the movement), you should update your camera in the fixed update.
Related
I'm new to Unity 3D and I've started to study and learn player and camera mechanics these past few weeks.Although, I have a simple character controller system with a Cinemachine free look cam following the player, I need help incorporating a camera relative player movement mechanic into my project. I therefore need help incorporating such a mechanism into my system. I've added my PlayerController Code below
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[SerializeField] public float _playerSpeed;
[SerializeField] public Rigidbody rb;
[SerializeField] private float _jumpforce = 100 ;
[SerializeField] private float fallMultiplier;
[SerializeField] private float lowJumpMultiplier;
public bool isGrounded;
public bool jumpReady;
public float jumpcoolDownTimer = 1.5f;
public float jumpcoolDownCurrent = 0.0f;
[SerializeField] private float sensi;
private float rotX;
private float rotY;
private Vector3 rotate;
int isJumpingHash;
int isFallingHash;
int isLandingHash;
public Animator animator;
Vector3 vel;
// Start is called before the first frame update
void Start()
{
jumpcoolDownCurrent = jumpcoolDownTimer;
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
animator = GetComponent<Animator>();
//Converting string values to hash values to save memory
isJumpingHash = Animator.StringToHash("isJumping");
isFallingHash = Animator.StringToHash("isFalling");
isLandingHash = Animator.StringToHash("isGrounded");
var cam = Camera.main;
}
// Update is called once per frame
void Update()
{
Move();
Jump();
}
private void OnCollisionEnter(Collision collision){
if(collision.gameObject.tag == "Surface"){
isGrounded = true;
}
}
private void Move()
{
vel = new Vector3(Input.GetAxis("Horizontal"),0,Input.GetAxis("Vertical")) * _playerSpeed;
if(Input.GetKey(KeyCode.LeftShift) && Input.GetKey(KeyCode.W)){
transform.position += transform.forward * Time.deltaTime * _playerSpeed;
}
}
private void Jump(){
vel.y = rb.velocity.y;
rb.velocity = vel;
//Jump Cooldown timer
if(jumpcoolDownCurrent >= jumpcoolDownTimer){
jumpReady = true;
}
else{
jumpcoolDownCurrent += Time.deltaTime;
jumpReady = false;
}
bool jump = animator.GetBool(isJumpingHash);
bool fall = animator.GetBool(isFallingHash);
bool land = animator.GetBool(isLandingHash);
//Jump
if(Input.GetKeyDown(KeyCode.Space) && isGrounded && jumpReady){
rb.AddForce(Vector3.up * _jumpforce, ForceMode.Impulse);
isGrounded = false;
jumpcoolDownCurrent = 0.0f;
animator.SetBool(isJumpingHash, true);
jump = true;
}
//Fall
if((rb.velocity.y <= 0 && !isGrounded)){
rb.velocity += Vector3.up * Physics.gravity.y * (fallMultiplier - 1) * Time.deltaTime;
animator.SetBool(isFallingHash, true);
fall = true;
}
//Land
if(isGrounded && fall){
fall = false;
animator.SetBool(isFallingHash, false);
animator.SetBool(isLandingHash, true);
}
//Back to 2d movement
if(isGrounded && rb.velocity.y <= 0){
animator.SetBool(isLandingHash, false);
land = false;
animator.SetBool(isJumpingHash, false);
jump = false;
animator.SetBool(isFallingHash, false);
fall = false;
}
}
}
I've referred to several different YouTube tutorials and and also surfed different forums to find a solution, but to no avail. But I did notice that all these users were using the Quaternion Rotation mechanics and cam transforms. Upon attempting to incorporate these codes into my project, the camera continuously rotates as I try to rotate the camera in one particular direction, or the A and D (strafe left and right) animations weren't functioning properly.
P.S -> For those of you who don't know, camera relative moment is the mechanic that most third person games use now, where the player while in movement, turns along the rotation and the camera and runs in the direction the camera is facing. Good examples of that would be God of War (2018), Batman Arkham Series etc.
I highly recommend that you don't use CineMachine, I have never seen anyone able to use it successfully and it is very simple to make your own camera controller after you study for a few days (or pay for someone to make one for you).
To make Camera Relative Movement in a platformer game similar to God of War, you should look into transform.Rotation, EulerAngles and Quaternions (Penny De Byl has good online classes and books on dealing with Quaternions, they're Vector4 and very difficult to wrap your head around)
Then, throughout your game, add a bunch of walls with a trigger collider attached that rotate the camera to 1 angle or another when the player crosses them.
You might even want to make the camera a child of the player object then it will always have the perfect angle and move when the player rotates.
If you ask a more specific question later, then a more specific answer can be given.
Using Camera.main.transform.forward you can get the forward direction relative to the camera. You can use Camera.main.transform.right to get the right direction. If you set the y coordinate of these to zero, you can use these to define the direction you travel in. You would then need to normalize it, and multiply it by your input.
So, here is what I would do:
get forward direction for camera
scale it so that the vertical component is zero
normalize it so that you get a normalized directional vector
multiply by your vertical movement to get your vector for movement forward and back
do the same for right and left movement
add the 2 parts together
multiply by speed to get your velocity
Like this:
vel = (Vector3.Scale(Camera.main.transform.forward, new Vector3(1, 0, 1)).normalized * Input.GetAxis("Vertical"))
+ (Vector3.Scale(Camera.main.transform.right, new Vector3(1, 0, 1)).normalized * Input.GetAxis("Horizontal")))
* playerSpeed;
Also, while it is not relevant to your issue at the moment, it is best to normalize your input vector. So do something like: Vector3 inputDirection = new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"), 0f).normalized. Otherwise, diagonal movement ends up being much faster than movement in one direction.
The target is a simple 3d cube.
This screenshot showing the turret in the hierarchy and the script attached to it and the target.
The script is attached to the Turret child :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RotateTurret : MonoBehaviour
{
[SerializeField]
private float turnRateRadians = 2 * Mathf.PI;
[SerializeField]
private Transform turretTop; // the gun part that rotates
[SerializeField]
private Transform bulletSpawnPoint;
//private Enemy target;
public GameObject target;
void Update()
{
TargetEnemy();
}
void TargetEnemy()
{
if (target != null)
{
Vector3 targetDir = target.transform.position - transform.position;
// Rotating in 2D Plane...
targetDir.y = 0.0f;
targetDir = targetDir.normalized;
Vector3 currentDir = turretTop.forward;
currentDir = Vector3.RotateTowards(currentDir, targetDir, turnRateRadians * Time.deltaTime, 1.0f);
Quaternion qDir = new Quaternion();
qDir.SetLookRotation(currentDir, Vector3.up);
turretTop.rotation = qDir;
}
}
}
When running the game the cube is not moving the turret is not moving. Only if I'm moving the target cube in the scene view window drag it then the turret rotate but also the turret is a bit behind never facing the target cube.
What I need it to do is when running the game the cube will start make circles nonstop around the turret with random speed and the target cube also should move up down with random height between min/max height.
And the turret should rotate facing the target according to the target height and speed.
Make an empty gameobject same position with turret, then make target child of that empty object and place it to where you want to rotate it, then rotate empty parent object with that sciprt
private int _minY, _maxY;
private int _targetHeight;
private const float Tolerance = 0.1f;
private void Start()
{
_minY = -10;
_maxY = 10;
_targetHeight = Random.Range(_minY, _maxY);
}
private void FixedUpdate()
{
var randomSpeed = Random.Range(2, 4);
transform.Rotate(0,randomSpeed,0);
if(Math.Abs(transform.position.y - _targetHeight) < Tolerance)
_targetHeight = Random.Range(_minY, _maxY);
transform.position = Vector3.MoveTowards(transform.position, new Vector3(transform.position.x, _targetHeight, transform.position.z), randomSpeed/10f);
}
I am currently using a Taxi model from as an enemy with AI that will drive to different waypoints. Whenever the car moves to a waypoint it auto rotates 90 degrees to the right, right away but keeps moving from waypoint to waypoint.
How do I fix a NavMeshAgent that auto turns 90 degrees when moving to a waypoint? The commented out code fixes the auto rotate but does not rotate enough when moving to the waypoint upon setDestination.
The uncommented code first rotates 90 degrees and then rotates a little after each waypoint (from the 90 degree position). (Unity API script from Vector3.RotateTowards)
_agent.UpdateRotation = false , stops initial rotation but I then have to control rotation manually (which I am having a hard time with)
private void Start()
{
_agent = GetComponent<NavMeshAgent>();
// Almost works doesn't rotate enough
//_agent.updateRotation = false;
_isStopped = false;
}
private void Update()
{
//Almost works, doesn't rotate enough
//transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.Euler(0, (_angleToRotate) * 8, 0), 1f);
//Rotates but turns 90 degrees first
Vector3 targetDirection = _wayPoints[_currentWayPoint].transform.position - transform.position;
float singleStep = _speed * Time.deltaTime;
Vector3 newDirection = Vector3.RotateTowards(transform.forward, targetDirection, singleStep, 0.0f);
Debug.DrawRay(transform.position, newDirection, Color.red);
transform.rotation = Quaternion.LookRotation(newDirection);
switch (_currentState)
{
case AIState.NonAlert:
//Debug.Log("Not Alert...");
if (_isStopped == true)
{
return;
}
else
{
if (_wayPoints.Count > 0)
{
_agent.SetDestination(_wayPoints[_currentWayPoint].transform.position);
//Gets distance between two Vector3s
float distanceToWayPoint = Vector3.Distance(_wayPoints[_currentWayPoint].transform.position, transform.position);
if (distanceToWayPoint < 1.0f)
{
_currentWayPoint++;
//Almost works, doesnt rotate enough
//_angleToRotate = Vector3.SignedAngle(transform.position, _wayPoints[_currentWayPoint].transform.position, Vector3.up);
}
}
}
This is due to the fact that the taxi model itself has an 90° offset.
You can change that with creating a prefab for the taxi model with an empty gameobject as parent and give the taxi the needed 90° offset on the prefab. After that you use the parent object for movement and rotation
Or
Change the direction of the model in blender or any other 3d modeling tool
This is for a 2D platform game.
I don't want the camera to move up the Y axis when the player jumps. I only want it to move when the player to the upper part of the screen so it can scroll up to vertical platforms and ladders.
Does anybody know what to enter in the code and the Unity editor so that can be done?
Here's the code I have so far in the camera script.
public class CameraControl : MonoBehaviour {
public GameObject target;
public float followAhead;
public float smoothing;
private Vector3 targetPosition;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
targetPosition = new Vector3 (target.transform.position.x, transform.position.y, transform.position.z);
if (target.transform.localScale.x > 0f) {
targetPosition = new Vector3 (targetPosition.x + followAhead, targetPosition.y, targetPosition.z);
} else {
targetPosition = new Vector3 (targetPosition.x - followAhead, targetPosition.y, targetPosition.z);
}
transform.position = Vector3.Lerp (transform.position, targetPosition, smoothing * Time.deltaTime);
}
}
I guess you have a bool tied to the jump which triggers the jumping animation.
So, in the Update() of the camera you can do something like this:
void Update() {
// Update camera X position
if (isPlayerJumping) return;
// Update camera Y position
}
This way, you update the Y position of the camera only if the player isn't jumping, while still updating the X position in all cases (even while jumping).
I used This code for my MainCamera for following the player in my 2d game in Unity5 :
using UnityEngine;
using System.Collections;
public class CameraFollow : MonoBehaviour {
public float dampTime = 0.15f;
private Vector3 velocity = Vector3.zero;
public Transform target;
// Update is called once per frame
void Update ()
{
if (target)
{
Vector3 point = GetComponent<Camera>().WorldToViewportPoint(target.position);
Vector3 delta = target.position - GetComponent<Camera>().ViewportToWorldPoint(new Vector3(0.5f, 0.5f, point.z)); //(new Vector3(0.5, 0.5, point.z));
Vector3 destination = transform.position + delta;
transform.position = Vector3.SmoothDamp(transform.position, destination, ref velocity, dampTime);
}
}
}
It work fine But player is in middle of screen allways . i wan player be in down of screen and my sprite for show Earth of my game will stick below the camera . i mean better in following pictures:
What I Want :
The Result :
You can add a vertical offset to the calculation. Just adding it to destination should do that I think.
Vector3 destination = ...
destination.y += someOffset;
transform.position = Vector3.SmoothDamp(...);
Otherwise you could also add an empty gameobject to the player gameobject and use that as your target.
One thing that you might need to consider is the resolution.