I'm currently making a small platformer 3D game, but unfortunately I can't make the player to rotate properly when it is riding the platform, the thing here is that I don't want to make the player child of the platform, so far I've managed to make him move smoothly along with the platform, but the rotation is still going nowhere, here is the code I'm using for the rotation:
player.transform.rotation *= platform.rotation;
and here is the effect I got:
Rotation Error
not very nice :(
I guess the solution is something simple, some formula, but unfortunately I'm not very good with math :( So, thank you guys, I hope you can help me.
I'll show you a simple script example which makes a cube rotate by input while reacting to the rotation of the platform on which it stands:
using UnityEngine;
public class CubeRotation : MonoBehaviour {
public GameObject Platform;
Quaternion PreviousPlatformRotation;
public float rotationSpeed = 50;
private void Start() {
PreviousPlatformRotation = Platform.transform.rotation;
}
private void Update() {
//Rotate the cube by input
if (Input.GetKey(KeyCode.A)) {
transform.Rotate(Vector3.up, Time.deltaTime * rotationSpeed);
}
if (Input.GetKey(KeyCode.D)) {
transform.Rotate(Vector3.up, -Time.deltaTime * rotationSpeed);
}
//Adjust rotation due to platform rotating
if (Platform.transform.rotation != PreviousPlatformRotation) {
var platformRotatedBy = Platform.transform.rotation * Quaternion.Inverse(PreviousPlatformRotation);
transform.rotation *= platformRotatedBy;
PreviousPlatformRotation = Platform.transform.rotation;
}
}
}
The logic of the adjustment to the platform rotation is this:
Get at start the rotation quaternion of the platform (in your case, get it when the cube object climbs on the platform)
With A and D rotate the cube normally around the local Y axis.
Afterwards check if the platform's rotation has changed, if yes:
3.a Get how much the platform rotated since the previous frame, with the operation Actual rotation * Inverse(Previous Rotation); this operation it's akin to a difference between two quaternions
3.b Add that quaternion to the cube's rotation with the *= operator
3.c Set the platform's previous rotation value to the new one.
That's pretty much it.
Related
I'm relatively new to coding, having just started about a week ago, and I can't seem to figure out or find anything relating to look relative movement using a third person Cinemachine freelook camera.
What I'm trying to accomplish is a camera similar to Risk of Rain 2, or maybe Smite where you can orbit the player and look at the front of them while they're idle, but once you start moving the player rotates towards the direction the camera is looking, and stays facing that direction no matter how they're moving.
I have a basic scene set up with a Cinemachine freelook camera following a cube, but my problem is whenever I turn the camera then move, my character doesn't rotate and instead starts moving in the direction it's facing instead of where the camera's looking, then only starts rotating if I'm pressing a movement key, so the rotation gets disjointed from where the camera's looking, if that makes sense. I kind of frankensteined some code from Royal Skies' movement tutorial and someone else's rotation tutorial for a top-down game.
This is the code for a script applied to my player character;
public float speed = 4.5f;
public Vector3 deltaMove;
public float turnspeed = 500;
void Update()
{
deltaMove = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical")) * speed * Time.deltaTime; //movement code
transform.Translate(deltaMove);
float horizontal = Input.GetAxis("Mouse X");
if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.D) == true) //if wasd pressed, rotate
{
transform.Rotate(horizontal * turnspeed * Vector3.up * Time.deltaTime, Space.World); //the rotation code
}
}
I'd be willing to do anything to be honest, I've been trying to figure this out for a bit now.
The Issue
I'm making a 2D Unity game where the main weapon of your character is a fireball gun. The idea is that a fireball will shoot out of the player's hand at the same angle the player's hand is pointing. I have 3 issues:
When I shoot the fireball, since the fireball is a RididBody, it pushes the player. This is because I've made the centre of the player's arm (the same place where the fireball shoots from) the point at which the arm rotates around the player (what is meant to be the shoulder);
To instantiate the fireball prefab on the arm, the only way I know how to do it is by using a piece of code which requires the arm to be a RigidBody. This means that the arm is affected by gravity and falls off the player on start unless I freeze the arm's y-axis movement, which means that when the player jumps, while the arm does not fall, it floats at the same y-position as where it started while moving along the x-axis; and
When the fireball is shot, the angle from which it is propelled after being shot is not the same angle as the angle of the player's arm.
Instantiating the Fireball
if (Input.GetKeyDown(KeyCode.Space))
{
pew.Play();
var fireballTransform = Instantiate(fireballPrefab); //creates a new shot sprite
fireballTransform.position = new Vector3(transform.position.x + horizMultiplier, transform.position.y, transform.position.z);
fireballTransform.rotation = orientation;
fireballTransform.transform.Rotate(0, 0, transform.rotation.z);
}
if (Input.GetKeyDown(KeyCode.D)) // moves right
{
orientation = 0;
horizMultiplier = 0.08F;
}
if (Input.GetKeyDown(KeyCode.A)) // moves left
{
orientation = 180;
horizMultiplier = -0.08F;
}
This piece of code is located within the script applied to the player's arm. The movement of the arm works fine and the problem seems to be either within this piece of code or the code for my fireball (which I will put next). A few definitions:
pew is a sound effect played when the fireball is shot;
horizMultiplier is the distance from the arm's centre which I would like the fireball to instantiate (also dependant of if the player) is facing left or right); and
orientation is which direction the player is facing (left or right). The fireball is then instantiated facing that same direction.
Fireball Script
public Vector2 speed = new Vector2(); // x and y forces respectively
private Rigidbody2D rb; // shorthand
private float rotation;
void Start()
{
rb = GetComponent<Rigidbody2D>(); // shorthand
rotation = rb.rotation;
if (rotation == 0)
{
rb.AddForce(Vector3.right * speed.x); // propels right
}
if (rotation == 180)
{
rb.AddForce(Vector3.left * speed.x); // propels left
}
}
I believe this code is explanatory enough with comments (if not please comment and I'll address any question). I believe an issue could also be in this piece of code because of the lines: rb.AddForce(Vector3.right * speed.x); and rb.AddForce(Vector3.left * speed.x); as these add directional forces to the object. I don't know is this is objective direction (right or left no matter what direction the object the force is being applied to is facing) or if it's right or left in terms of the object-- say if an object was rotated 90 degrees clockwise and that object had a force applied so that it moves right making the object move downwards.
What I'm expecting to happen is the player's arm will turn so that when a fireball is fired it is fired in the direction the arm is facing. The arms turning mechanics are fine, it's just trying to properly instantiate the fireball. Can anyone help with any of the issues I've laid out?
How I would go about this:
Add an empty transform as child of the arm and move it to where the fireball should spawn. Also make sure the rotation of it is such that its forward vector (the local z-axis) points to where the fireball should go. To see this, you need to set "Local" left of the play button.
Spawn the fireball at the transform's position with its rotation.
Ignore collision between the arm's/player's collider and the fireball's collider with https://docs.unity3d.com/ScriptReference/Physics.IgnoreCollision.html. If necessary, you can enable the collision between the two colliders again after 1s or so.
Set the fireball rigidbody's velocity to speed * rigidbody.forward.
If you need help with the code, please post it so I can see what's going on.
Edit:
The arm definitely doesn't require a Rigidbody.
You can just use Instantiate(prefab, position, rotation) as shorthand.
Is this for a 2D game?
Also, I'm going to sleep now but I'll gladly try to help tomorrow.
Your Issues
1) You should be masking the fireball's layer not to collide with your player's layer.
You can find more info about this here: https://docs.unity3d.com/Manual/LayerBasedCollision.html
(note: make sure you're on the Physics2D panel, and not the Physics one, as that's for 3D)
2) There is a setting called gravityScale in RigidBody2D. You should set this to 0 if you don't want gravity to be affecting your object. More info: https://docs.unity3d.com/Manual/class-Rigidbody2D.html
Fireball Instantiate
if (Input.GetKeyDown(KeyCode.Space))
{
pew.Play();
var position = hand.transform.position + hand.transform.forward * horizMultiplier;
var rotation = hand.transform.rotation;
var fireball = Instantiate<Fireball>(fireballPrefab, position, rotation);
fireball.StartMoving();
}
Fireball Script
private Rigidbody2D rb; // shorthand
private float rotation;
public float Speed;
public void StartMoving()
{
rb = GetComponent<Rigidbody2D>(); // shorthand
rb.velocity = transform.forward * Speed;
}
void OnTriggerEnter(....) { .... }
I have a first person rigidbody capsule that rotates so that he will always be upright against the gravity direction. I want to rotate my player to the side so that the player camera will not rotate vertically.
My code is,
void Update() {
FixOrientation();
}
void FixOrientation()
{
if (trans.up != -GetGravityDirection())
{
Quaternion targetRotation = Quaternion.FromToRotation(trans.up, -GetGravityDirection()) * trans.localRotation;
trans.localRotation = Quaternion.RotateTowards(trans.localRotation, targetRotation, 5f);
}
}
The result is,
In the image above, I changed the gravity direction to point to the ceiling.
This code only rotates the player at the global x-axis no matter where he is facing which means when i'm facing global forward or backward, the player will rotate vertically the camera. What I want is for it to rotate on the side(local z axis).
Unity already has a method for exactly that: Transform.Rotate has an overload taking an angle and a rotation axis.
It might look like
// rotation speed in degrees per second
public float RotationSpeed;
void Update()
{
FixOrientation();
}
void FixOrientation()
{
if (transform.up != -GetGravityDirection())
{
// Get the current angle between the up axis and your negative gravity vector
var difference = Vector3.Angle(transform.up, -GetGravityDirection());
// This simply assures you don't overshoot and rotate more than required
// to avoid a back-forward loop
// also use Time.deltaTime for a frame-independent rotation speed
var maxStep = Mathf.Min(difference, RotationSpeed * Time.deltaTime);
// you only want ot rotate around local Z axis
// Space.Self makes sure you use the local axis
transform.Rotate(0, 0, maxStep, Space.Self);
}
}
A Sidenote:
Just in general be careful with the direct comparison of two Vectors
trans.up != -GetGravityDirection()
uses an approximation of 0.00001. In your case that should be fine anyway but for comparing you should rather use
Vector3.Angle(vector1, vector2) > threshold
to define a wider or stronger threshold
I'm building a top down shooter and so I have my camera above my player and the map. Here's the code I've written in the player controller script for movement:
public class playerMovement : MonoBehaviour {
public float speed;
private Camera mainCamera;
void Start () {
mainCamera = FindObjectOfType<Camera>();
}
// Update is called once per frame
void Update () {
// player movement
transform.Translate(speed * Input.GetAxis("Horizontal") * Time.deltaTime, 0f, speed * Input.GetAxis("Vertical") * Time.deltaTime);
// Camera Ray casting
Ray cameraRay = mainCamera.ScreenPointToRay(Input.mousePosition);
Plane groundPlane = new Plane(Vector3.up, Vector3.zero);
float rayLength;
if (groundPlane.Raycast(cameraRay, out rayLength)) {
Vector3 look = cameraRay.GetPoint(rayLength);
Debug.DrawLine(cameraRay.origin, look, Color.red);
transform.LookAt(new Vector3(look.x, transform.position.y, look.z));
}
}
}
I want to be able to move the player using the WASD keys and also rotate following the direction on where the mouse is, however I don't want the rotation of the player to change the direction of the keys, I need the player to move forwards if the W key is pressed no matter which way the player is facing.
However for some reason my code makes the player move forwards depending on which way it is facing which I don't want.
How can I fix this?
The problem is that your transform.Translate call is in "self" space. Forward, backward, left, right are all relative to the direction the transform is facing. That is why your player is moving relative to the facing direction.
If you want to translate relative to "global" or "world" space, you have to add an additional parameter.
// player movement
transform.Translate(speed * Input.GetAxis("Horizontal") * Time.deltaTime,
0f,
speed * Input.GetAxis("Vertical") * Time.deltaTime,
Space.World);
Note the Space.World parameter at the end, to set the world coordinate system.
You can find more in the Unity docs here: https://docs.unity3d.com/ScriptReference/Transform.Translate.html
You need to look at the difference between local and global coordinate systems.
Right now your WASD keys are moving the player character according to global coordinates, and you want the WASD movement to be dependant on the player's orientation so you need to use a local coordinate system.
http://wiki.unity3d.com/index.php?title=Converting_Between_Coordinate_Systems
I'm working on a UI menu for a game, and I dragged in a GameObject to serve as a representation of what will be placed down if you select it. When I attach a script for its rotation and run the game, it tilts downward with the tilt of the camera, even though there is no script linking the camera and the object.
Canvas is in ScreenSpace - Camera mode.
Rotation Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BuildList : MonoBehaviour {
//Floats
public float speed = 10f;
void Update ()
{
transform.eulerAngles = new Vector3 (-90f, transform.eulerAngles.y, transform.eulerAngles.z + (speed * Time.deltaTime));
}
}
The x-rotation in the inspector is the one that is rotating. When I rotate the camera downwards, the value the x-rotation in the inspector gets is -155, while the x-rotation of this object should be fixed at -90.
This may or not solve your problem, but I have a tip:
Don't ever use transform.eulerAngles for iterative update. It could cause Gimbal Lock. Use Rotate if possible.
I suggest replace the code with this:
void Update ()
{
// Note: the axis to rotate may different than what you expect. experiment with it
transform.Rotate(Vector3.forward * (speed * Time.deltaTime), Space.World);
}
And set the object rotation at (-90,0,0) before the game begin.
Thanks for willnode pushing me in the right direction, I found code in unity Documentation and modified it so it only rotates the z-axis of the object.
https://docs.unity3d.com/ScriptReference/Transform.Rotate.html
transform.Rotate(0, 0, Time.deltaTime * speed);