local movement on rigidbody sphere with the mouse and keyboard - c#

So i have completed the Roll a ball tutorial from Unity that is a little game that uses a sphere with a rigidbody applied to it with some basic movement script found here.
What i want now is to take it a step further and introduce a somewhat more advanced movement script which also takes mouse input into play.
What i am trying to achieve is so to add force based on the local axis, so if i i move the mouse left the ball turns and the force is added in that direction. Let me show what code i have come up with (added to a simple sphere with rigidbody applied):
using UnityEngine;
using System.Collections;
public class playerController : MonoBehaviour {
public float turnSpeed = 2.0f;
public float moveSpeed = 250.0f;
void FixedUpdate() {
float h = turnSpeed * Input.GetAxis("Mouse X");
transform.Rotate(0, h, 0);
float moveHorizontal = Input.GetAxis("Horizontal");
float moveVertical = Input.GetAxis("Vertical");
Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
rigidbody.AddForce(movement * moveSpeed * Time.deltaTime);
}
}
Ok so what is happening is that the ball is turning when i move the mouse, and the ball is rolling if i use the arrowkeys, but what i'm not managing to figure out after some trial and error is to get the ball moving in the direction it is turning.
How would you approach this particular scenario? Any help is as always much appreciated guys.

I managed to get around this and sharing for others to see.
I reference to the camera GameObject and use the camera.transform on the keys. Guess it is really basic but still.
public GameObject Camera;
public float moveSpeed = 0.0f;
if (Input.GetKey ("right") || Input.GetKey ("d")) {
rigidbody.AddForce( Camera.transform.right * moveSpeed * Time.deltaTime);
}
if (Input.GetKey ("left") || Input.GetKey ("a")) {
rigidbody.AddForce( -Camera.transform.right * moveSpeed * Time.deltaTime);
}
if (Input.GetKey ("up") || Input.GetKey ("w")) {
rigidbody.AddForce( Camera.transform.forward * moveSpeed * Time.deltaTime);
}
if (Input.GetKey ("down") || Input.GetKey ("s")) {
rigidbody.AddForce( -Camera.transform.forward * moveSpeed * Time.deltaTime);
}

Something Like this should do the trick::
if (Input.GetKey ("up") || Input.GetKey ("w")) {
rigidbody.AddForce( Camera.transform.forward * moveSpeed * Time.deltaTime);
rigidbody.AddRelativeTorque(vector3.right * speed)
//Note that rotation happens around the axis, so when moving (forward orback you will rotate on the vector3.right/left) and when moving( Right/left you will use the vector3.forward/back)
}

Related

Unity 3D Jumping issue

I've been following along with a 3D shooter tutorial (pretty good so far) but I've hit a snag when it comes to my framerate and jumping. The framerate on my PC is just not consistent and consequently the jump height varies constantly and sometimes the character doesn't jump at all. I know handling jumping in Update (rather than FixedUpdate) can cause issues regarding framerates but the tutorial insists that using Time.deltaTime should resolve that. Any ideas on what I should do to try and keep my jumps consistent?
//Jumping
public float jumpHeight = 10f;
public Transform ground;
private bool readyToJump;
public LayerMask groundLayer;
public float groundDistance = 0.5f;
// Update is called once per frame
private void Update()
{
Jump();
PlayerMovement();
CameraMovement();
Shoot();
}
void PlayerMovement()
{
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
Vector3 movement = x * transform.right + z * transform.forward;
myController.Move(movement * speed * Time.deltaTime);
//9.8 meters/second^2
velocity.y += Physics.gravity.y * Mathf.Pow(Time.deltaTime, 2) * gravityModifier;
if (myController.isGrounded)
{
velocity.y = Physics.gravity.y * Time.deltaTime;
}
myController.Move(velocity);
}
private void CameraMovement()
{
float mouseX = Input.GetAxisRaw("Mouse X") * mouseSensitivity * Time.deltaTime;
float mouseY = Input.GetAxisRaw("Mouse Y") * mouseSensitivity * Time.deltaTime;
cameraVerticalRotation -= mouseY;
cameraVerticalRotation = Mathf.Clamp(cameraVerticalRotation, minVertCameraAngle, maxVertCameraAngle);
transform.Rotate(Vector3.up * mouseX);
myHead.localRotation = Quaternion.Euler(cameraVerticalRotation, 0f, 0f);
}
void Jump()
{
readyToJump = Physics.OverlapSphere(ground.position, groundDistance, groundLayer).Length > 0;
if (Input.GetButtonDown("Jump") && readyToJump)
{
velocity.y = Mathf.Sqrt(jumpHeight * -2f * Physics.gravity.y) * Time.deltaTime;
}
myController.Move(velocity);
}
Handling the acceleration yourself is a bad idea, especially in Update rather than FixedUpdate. You should know that, in real life, the velocity changes smoothly with the acceleration against time. If you draw a curve, it is a straight slope. However, in computer, if you just calculate the velocity frame by frame, the curve will be looked like stairs.
You may use the physics engine in Unity and add an instant velocity to the character when it jumps. Just let Unity handle the acceleration.
First, you need to add a Rigidbody component to it. Then, you may modify your code to:
private Rigidbody rb;
void Start()
{
rb = GetComponent<Rigidbody>();
}
void Jump()
{
readyToJump = Physics.OverlapSphere(ground.position, groundDistance, groundLayer).Length > 0;
if (Input.GetButtonDown("Jump") && readyToJump)
{
rb.velocity = Vector3.up * jumpHeight * 2f / -Physics.gravity.y * gravityModifier;
}
}
Don't forget to remove the code in PlayerMovement on handling falling after you have Use Gravity in the Rigidbody component.
Edit
If you are using CharacterController, you can only handle the acceleration yourself. You may just move the logic to FixedUpdate to solve the various height problem, or make a more accurate simulation. For example, use this to handle jumping:
velocity.y = jumpHeight * 2f / -Physics.gravity.y * gravityModifier;
and use this to handle falling:
myController.Move(velocity * Time.deltaTime + Physics.gravity * Mathf.Pow(Time.deltaTime, 2) / 2f * gravityModifier);
// Here you should check whether it will fall through the ground.
// If no, use this line to update the velocity.
velocity.y += Physics.gravity.y * gravityModifier * Time.deltaTime;
// Otherwise, update the position to make it on ground and set velocity.y to 0.

How to make the player move according to the camera motion

Now I'm a beginner game developer and I'm working on a game so I have set the camera motion that when you move the mouse the player rotates however I want to make the WASD keys work to the direction the player is rotated. Basically when I rotate the camera to the left the W key still moves the player on the Z dimension. How can I fix this?
Here's my player rotation code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraMovement : MonoBehaviour
{
public float speedH = 2.0f;
public float speedV = 2.0f;
private float yaw = 0.0f;
private float pitch = 0.0f;
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
yaw += speedH * Input.GetAxis("Mouse X");
pitch -= speedV * Input.GetAxis("Mouse Y");
transform.eulerAngles = new Vector3(pitch, yaw, 0.0f);
}
}
Moving in the direction the character is facing is a simple work for Transform.forward and Transform.right because you need to move the player based on local position and rotation and not the world one.
For example, you could have
void Move (float h, float v)
{
movement.Set (h, 0f, v);
movement = movement.normalized * speed * Time.deltaTime;
playerRigidbody.MovePosition (transform.position + (transform.forward * movement.z) + (transform.right * movement.x));
}
The Rigidbody is what you need to move it, if you want an object that is always moving in the direction is facing you could easily do a
transform.position += transform.forward * Time.deltaTime * movementSpeed;
In this case, to go back you should subtract instead of add.
But use my first suggestion for your player :D
EDIT
In your update you can add
Move(yaw, pitch);

Can't Jump in FPS

I am following an FPS tutorial here. Unfortunately, at the end when Brackeys tests out the code, I cannot jump. Here is my player movement script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
public CharacterController controller;
public float speed = 10f;
public float gravity = -10f;
public float jumpHeight = 5f;
public Transform groundCheck;
public float groundDistance = 0.4f;
public LayerMask groundMask;
Vector3 velocity;
bool isGrounded;
void Update()
{
isGrounded = Physics.CheckSphere(groundCheck.position, groundDistance, groundMask);
if (isGrounded && velocity.y < 0)
{
velocity.y = -2f;
}
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
Vector3 move = transform.right * x + transform.forward * z;
controller.Move(move * speed * Time.deltaTime);
if (Input.GetButton("Jump") && isGrounded)
{
velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
}
velocity.y += gravity * Time.deltaTime;
controller.Move(velocity * Time.deltaTime);
}
}
Here is the screenshot for the code component in the unity editor:
Please help me!
Also, if you need any clarification, do not hesitate to ask!
I have some suggestions for your problem but I am not 100% sure that they will work.
Check that that the layer Ground is attached to the gameobject that is your platform. If you don't know how to do this, check this doc on layers: https://docs.unity3d.com/Manual/Layers.html
Another idea is that of the groundcheck. Make sure the ground check is just below the player.
My final suggestion is that of the key bindings. Make sure that Jump is assigned to your desired button.
I believe these will work. If there are still any problems, comment down below.
I haven't tried the code myself yet, but if you increase the Jumpheight to something like 300 the player should jump up.
This is because your formula:
velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
Ends up being smaller than the gravity , so you don't have enough force to jump up.
No need for ground check from Brackeys's FPS code since the character controller has its public isGrounded property.

How to Rotate a GameObject?

I'm watching unity tutorials and in the control code of the ship I would like to make it rotate on its axis, that is, it can rotate 360 ​​degrees continuously while I press the right key for example.
playerController:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class Boundary
{
public float xMin, xMax, zMin, zMax;
}
public class PlayerController : MonoBehaviour {
[Header("Movement")]
public float speed;
public float tilt;
public Boundary boundary;
private Rigidbody rig;
[Header("Shooting")]
public GameObject shot;
public Transform shotSpawn;
public float fireRate;
private float nextFire;
void Awake () {
rig = GetComponent<Rigidbody>();
}
void Update () {
if (Input.GetButton ("Fire1") && Time.time > nextFire) {
nextFire = Time.time + fireRate;
Instantiate (shot, shotSpawn.position, Quaternion.identity);
}
}
void FixedUpdate () {
float moveHorizontal = Input.GetAxis ("Horizontal");
float moveVertical = Input.GetAxis ("Vertical");
Vector3 movement = new Vector3(moveHorizontal, 0f, moveVertical);
rig.velocity = movement * speed;
rig.position = new Vector3 (Mathf.Clamp (rig.position.x, boundary.xMin, boundary.xMax), 0f, Mathf.Clamp (rig.position.z, boundary.zMin, boundary.zMax));
rig.rotation = Quaternion.Euler (0f, 0f, rig.velocity.x * -tilt);
}
}
How can I edit it to do what I want?
example:
You can use Transform.Rotate()
Your code would look like this, with the example you provided:
void FixedUpdate () {
float moveHorizontal = Input.GetAxis ("Horizontal");
float moveVertical = Input.GetAxis ("Vertical");
Vector3 movement = new Vector3(moveHorizontal, 0f, moveVertical);
rig.velocity = movement * speed;
rig.position = new Vector3 (Mathf.Clamp (rig.position.x, boundary.xMin, boundary.xMax), 0f, Mathf.Clamp (rig.position.z, boundary.zMin, boundary.zMax));
if(moveHorizontal > 0){ //if your "right key" is pressed
// Rotate the object around its local X axis at 1 degree per second
transform.Rotate(Vector3.right * Time.deltaTime);
}
}
For faster Rotation you can simply multiply the Vector3.right with some value.
To Rotate around another Axis, use other Vector directions, like Vector3.up.
For example:
transform.Rotate(Vector3.Up * moveHorizontal * Time.deltaTime);
transform.Rotate(Vector3.Forward * moveHorizontal * Time.deltaTime);
When you multiply the Vector3.Right with your moveHorizontal it should also work when you press the "left key" and that should result in rotating in the other direction.
transform.Rotate(Vector3.right * moveHorizontal * Time.deltaTime);
Notes:
That only works if your PlayerController is attached to your ship gameobject. If it is not attached you have to use the transform of your ship of course.
World Space vs Local Space
When you click on your object in the scene view, you should see the transform and the 3 arrows (red, green, blue) pointing on different directions (the 3 axis). When you use the method with the parameters provided above, you are using these arrows as rotation axis.
You can also rotate arround the WorldSpace axis.
transform.Rotate(Vector3.up, Time.deltaTime, Space.World);
When to use transform.Rotate?
When you change position or rotation of a transform, using the transforms
methods, it will be applied at the end of the frame. These changes ignore physics.
-> Use transforms methods if you don't care about collisions
When to use rigidbody.MoveRotation?
When you change rigidbody.position or rigidbody.MoveRotation using rigidbodies methods, it will be applied at the end of the next physics step. These changes care about physics (collisions and stuff)
-> Use rigidbodies methods if you care about collisions
Thanks to Helium for that hint.
The third possibility:
Instead of directly calling transform.Rotate or rigidbody.MoveRotation, you can also rotate your Object, using an animation, which changes the transforms rotation.
Example of Transform.Rotate()
You can clearly see, that the collision checks are ignored on that object while it's rotating through the ground. (I packed that gif into a spoiler to reduce noise. You'll need to hover over it, if you want to see it)
This will be the speed and axis of your rotation.
public Vector3 eulerAngleVelocity = new Vector3(0f,0f,1000f);
Since you want to rotate a rigidbody so use MoveRotation
Quaternion deltaRotation = Quaternion.Euler(-moveHorizontal * eulerAngleVelocity * Time.deltaTime);
rig.MoveRotation(rig.rotation * deltaRotation);
Final code will look like this
// NEW CODE BEGIN------
public Vector3 eulerAngleVelocity = new Vector3(0f,0f,1000f);
// NEW CODE END------
void FixedUpdate () {
float moveHorizontal = Input.GetAxis ("Horizontal");
float moveVertical = Input.GetAxis ("Vertical");
// NEW CODE BEGIN------
Vector3 movement = new Vector3(0f, 0f, moveVertical); // Notice I have removed moveHorizontal, this will make sure your gameobject doesnt go left and right. We will use move horizontal for rotating the gameobject.
// NEW CODE END------
rig.velocity = movement * speed;
rig.position = new Vector3 (Mathf.Clamp (rig.position.x, boundary.xMin, boundary.xMax), 0f, Mathf.Clamp (rig.position.z, boundary.zMin, boundary.zMax));
// NEW CODE BEGIN------
Quaternion deltaRotation = Quaternion.Euler(-moveHorizontal * eulerAngleVelocity * Time.deltaTime);
rig.MoveRotation(rig.rotation * deltaRotation);
// NEW CODE END------
}
You can play with eulerAngleVelocity to get the desired speed. Hope this helps. ;)

Unity bouncing when colliding with object

I have made a script with movement, that finally works as i want it to, except one thing... I want it to be a first person game, but the way the movement works now, is on the global axis, which means that W is always torwards one specific direction, no matter what direction my camera is turning... How do i fix this? i want the movement to stay how it is, but with the W key to always be forward depending on the way the camera or player is looking.
Please let me know how my script would look edited, or atleast what part i have to change.
I would also like to add that i would love to be able to do a wall jump, but i am not sure how to add that behavior.
Here is my movement script:
public class MovementScript : MonoBehaviour {
public float speed;
public float jumpforce;
public float gravity = 25;
private Vector3 moveVector;
private Vector3 lastMove;
private float verticalVelocity;
private CharacterController controller;
// Use this for initialization
void Start () {
controller = GetComponent<CharacterController> ();
//Låser og gemmer musen
Cursor.lockState = CursorLockMode.Locked;
}
// Update is called once per frame
void Update () {
//Låser musen op
if (Input.GetKeyDown ("escape"))
Cursor.lockState = CursorLockMode.None;
moveVector = Vector3.zero;
moveVector.x = Input.GetAxis("Horizontal");
moveVector.z = Input.GetAxis("Vertical");
if (controller.isGrounded) {
verticalVelocity = -1;
if (Input.GetButton("Jump")) {
verticalVelocity = jumpforce;
}
} else {
verticalVelocity -= gravity * Time.deltaTime;
moveVector = lastMove;
}
moveVector.y = 0;
moveVector.Normalize ();
moveVector *= speed;
moveVector.y = verticalVelocity;
controller.Move (moveVector * Time.deltaTime);
lastMove = moveVector;
}
}
You need to go from local to world space:
for example when you want to move on the x-axis regarding the player's orientation, the local vector is (1, 0, 0), which you get from your input axis, but the CharacterController need a world based direction (see the doc of the Move function)
A more complex move function taking absolute movement deltas.
To get this, use Transform.TransformDirection like this
worldMove = transform.TransformDirection(moveVector);
controller.Move (worldMove * Time.deltaTime);
EDIT regarding your issue with the controller moving a bit after releasing the input:
That's because GetAxis gives you a smoothed value. Replace GetAxis by GetAxisRaw and it should work
Modify the final moving code to
controller.Move(moveVector.z * transform.forward * Time.deltaTime);
controller.Move(moveVector.x * transform.right * Time.deltaTime);
controller.Move(moveVector.y * transform.up * Time.deltaTime);
Alternatively, suppose your character is only rotated about the Y axis, you can look into rotating your moveVector by your character's Y rotation so moveVector's forward points parallel to your chracter's forward.
I found a good explaination of rotating a vector here: https://answers.unity.com/questions/46770/rotate-a-vector3-direction.html
Rotating a vector:
moveVector = Quaternion.Euler(0, transform.eulerAngles.y, 0) * moveVector;

Categories