I am making a 3D Planet roaming game. The Planet itself is 3D, but every object in the scene will be 2D. As the player walks around the planet, their rotation relative north changes, and thus, if objects are placed statically in the scene, sometimes objects will appear upside down. I basically want every object to be tangential to the sphere, while still maintaining its "uprightness".
Vector3 pos = transform.position;
Vector3 planetPos = Planet.transform.position;
Vector3 direction = (planetPos - pos).normalized; // Calculate Tangent
transform.rotation = Quaternion.Euler(direction);
transform.Rotate(new Vector3(90, 0, 0));
This code here rotates the object relative to the planet, but it doesnt maintain is orientation relative to the player.
How can I get the other objects to not appear upside down?
Spherical Billboarding
You could find the direction that the object was relative to the world, and make the object look at that.
public GameObject[] objs;
public Vector3 worldPos;
void Update()
{
foreach (GameObject obj in objs)
{
obj.transform.rotation = Quaternion.LookRotation(obj.transform.position - worldPos);
}
}
This disables physics, so you could add more:
public GameObject[] objs;
public Vector3 worldPos;
public gravityForce;
Vector3 gravity;
Quaternion look;
void Update()
{
foreach (GameObject obj in objs)
{
look = Quaternion.LookRotation(obj.transform.position - worldPos);
ChangeGravity(look, obj);
}
}
Rigidbody rb;
void ChangeGravity(Quaternion looking, GameObject obj)
{
gravity = new Vector3(looking.x, looking.y, looking.z);
gravity.Normalize();
gravity *= -gravityForce;
rb = obj.GetComponent<Rigidbody>();
rb.useGravity = false;
rb.AddForce(gravity);
}
These were untested, but the first one is much more likely to work.
Related
I would like to develop a 3d game. An "air hockey" simulator. I would like to control the paddle with the mouse but at the same time, I would like to use the physics engine to handle collisions with the puck and the borders of the table.
I tried the following:
If I use a Kinematic paddle I lose the property to handle the physics by the engine.
If I use static object the same.
If I use a dynamic rigidbody I am not able to control smoothly the paddle.
What is the best way to handle this scenario?
Thank you.
What makes a air-puck feel good is the sliding effect it has. Ofcourse it doesn't continue forever, but still feels nice.
Here is what you can do:
Create an Paddle & Puck
Create two physics materials for both.
Decrease the friction on the material that both feels slidey, the puck a little more than the paddle.
For both:
Freeze the x & z rotation
Freeze the y position
Now the part that makes the paddle use physics & RigidBody correctly. Create a new script for moving the paddle:
public class PaddleMovement : MonoBehaviour
{
private RigidBody rb;
public float speed = 5;
public float minDist = 0;
public float maxDist = 5;
public LayerMask layers;
void Start()
{
rb = GetComponent<RigidBody>();
}
void Update()
{
// Paddle will only move if we hold down the mouse button
paddleGrabed = Input.GetInput(KeyCode.Mouse0);
}
void FixedUpdate()
{
if (paddleGrabed)
{
HandleMovement();
}
}
void HandleMovement()
{
Ray ray = Camera.main.ScreenToWorldPoint(Input.MousePosition);
RaycstHit hit;
if (Physics.Raycast(ray, out hit, 100f, layers))
{
// Calculate the slow effect as paddle comes close to the point;
float dist = Vector3.Distance
(
new Vector3(transform.position.x, 0 transform.position.z),
new Vector3(hit.point.x, 0, hit.point.z)
);
dist = Mathf.Clamp(dist, minDist, maxDist);
var slowEffect = dist / maxDist;
// Now move move the rigid body appropriately
var dir = new Vector3(hit.point.x, 0, hit.point.z) -new Vector3(transform.position.x, 0 transform.position.z);
dir.Normalize();
rb.MovePosition(transform.position + dir * slowEffect * speed * Time.deltaTime);
}
}
}
That should move the Paddle to a certain position with a bit of a lag and sliding effect.
I'm trying to get an object to rotate smoothly on the z axis when it comes into contact with a collider. However it just seems to snap really quickly and I can't find a way to smooth this out.
I've split the rotation out into 2 scenarios - one for when the player enters the collider (which needs to be smooth), and one for when the player is already in the collider (which needs to be snappy). this is so that when the player enters the objects gravity (collider) he'll rotate smoothly before falling to the ground. However, if the player is already within the objects gravitational pull, its snappy so they can run on the ground (a circle) without any wierd floating or rolling occuring.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GravitationalBody : MonoBehaviour
{
[Range(0, 0.3f)] [SerializeField] private float m_OrientationSmoothing = 0.3f; // How much to smooth out the movement
void OnTriggerEnter2D(Collider2D obj) {
// Define direction of gravity
Vector3 gravityDirection = transform.position - obj.transform.position;
// apply gravity to all objects within range
obj.GetComponent<Rigidbody2D>().AddForce(gravityDirection);
orientObjects(obj, gravityDirection, true);
}
void OnTriggerStay2D(Collider2D obj)
{
// Define direction of gravity
Vector3 gravityDirection = transform.position - obj.transform.position;
// apply gravity to all objects within range
obj.GetComponent<Rigidbody2D>().AddForce(gravityDirection);
orientObjects(obj, gravityDirection, false);
}
// Control players Z axis rotation to point toward center of planets
void orientObjects(Collider2D obj, Vector3 gravityDirection, bool lerp)
{
float orientation = 90 + Mathf.Atan2(gravityDirection.y, gravityDirection.x) * Mathf.Rad2Deg;
if (lerp)
{
Quaternion q = Quaternion.AngleAxis(orientation, Vector3.forward);
obj.transform.rotation = Quaternion.Lerp(obj.transform.rotation, q, m_OrientationSmoothing * Time.deltaTime);
}
else obj.transform.rotation = Quaternion.Euler(0f, 0f, orientation);
}
}
I don't know if it will help but if you want a smooth rotation look at this code, I'm sure you can find inspiration here:
[SerializeField] float _degreesPerSecond = 30f;
[SerializeField] Vector3 _axis = Vector3.forward;
void Update ()
{
transform.Rotate( _axis.normalized * _degreesPerSecond * Time.deltaTime );
}
I'm attempting to make the ability to slide in an FPS game I'm working on and I've tried rotating the player object but when tested, it does nothing. Pressing the slide key I have assigned does nothing but rotate the player object in the Y and Z axis (I'm trying to rotate it on the X axis)
Here's the code I'm using (only relevant parts have been kept in):
public class SlideMovementScript : MonoBehaviour
{
public Rigidbody rig;
public CapsuleCollider capcol;
public GameObject player;
public float originalHeight;
public float reducedHeight;
public float slideSpeed = 10f;
// Start is called before the first frame update
void Start()
{
capcol = GetComponent<CapsuleCollider>();
rig = GetComponent<Rigidbody>();
originalHeight = capcol.height;
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.LeftControl) && Input.GetKey(KeyCode.W))
Sliding();
else if (Input.GetKeyUp(KeyCode.LeftControl))
GoUp();
}
private void Sliding()
{
capcol.height = reducedHeight;
player.transform.localRotation = Quaternion.Euler(1.599f, 0f, 0f);
rig.AddForce(transform.forward * slideSpeed, ForceMode.VelocityChange);
}
private void GoUp()
{
GetComponent<CapsuleCollider>().height = originalHeight;
player.transform.localRotation = Quaternion.Euler(0f, 0f, 0f);
}
}
I've gotten ZERO error messages from this but I haven't managed to find the problem
Another problem I've also had is when you start sliding, it just instantly rotates the Player object to X 1.599 Y 0 Z 0. I want it to rotate to where the player is facing but I haven't found a way to do it, even using transform.foward or other references won't work.
The Explanation (skip to script if you want) (read notes)
I reccomend using Quaternion's methods. You can use Quaternion.Slerp which can smooth out the rotation towards the player.
Slerp means spherical linear interpolation
Basically, Slerp is a 3 argumented method. the first argument is the current rotation, the second argument is the target rotation, and the third is at which point between those two you want to output.
More simply, Lerp is linear interpolation. Lerps arguments are the same, except it handles floats and not Quaternions. If you Mathf.Lerp(0,1,1); you will get 1, because 1 is all the ay to the second argument. If you do Mathf(0, 3, 0.5); you will get 1.5 because 1.5 is halfway (0.5) between them both.
You can imagine slerp like that, if you give rotation (90, 0, 0) and alerp it to (0, 0, 0) by 0.5 you will get (45, 0, 0).
On this example, I slerp from the current rotation, to the target rotation, by delta time. This way, as time goes on, it slightly increases the current rotation to be the target.
I can create the target as 1.599 rotated on the x.
To make this relative Make a parent game object to the player containing the mesh and rigidbody and maybe the collider (depends if you want the collider upright). I will call this "slidePlayer" slidePlayer must be a child of the player gameObject
Notes
Make sure to have slidePlayer to a gameObject that is a child of player, but must have the player mesh or sliding won't be visible.
Change rot to the target rotation.
Change speed to speed to rotate to the target rotation.
Here is your script I changed:
public class SlideMovementScript : MonoBehaviour
{
public Rigidbody rig;
public CapsuleCollider capcol;
public GameObject player;
public GameObject slidePlayer;
public float originalHeight;
public float reducedHeight;
public float slideSpeed = 10f;
// Start is called before the first frame update
void Start()
{
capcol = GetComponent<CapsuleCollider>();
rig = GetComponent<Rigidbody>();
originalHeight = capcol.height;
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.LeftControl) && Input.GetKey(KeyCode.W))
Sliding();
else if (Input.GetKeyUp(KeyCode.LeftControl))
GoUp();
}
private void Sliding()
{
capcol.height = reducedHeight;
Quaternion rot = Quaternion.Euler(1.599f, 0f, 0f);
float speed = 1f;
Quaternion curRot = slidePlayer.transform.localRotation;
slidePlayer.transform.localRotation = Quaternion.Slerp(curRot, rot, Time.deltaTime * speed);
rig.AddForce(transform.forward * slideSpeed, ForceMode.VelocityChange);
}
private void GoUp()
{
GetComponent<CapsuleCollider>().height = originalHeight;
Quaternion rot = Quaternion.Euler(0f, 0f, 0f);
float speed = 3f;
Quaternion curRot = slidePlayer.transform.localRotation;
slidePlayer.transform.localRotation = Quaternion.Slerp(curRot, rot, Time.deltaTime * speed);
}
}
Let me know if there are any problems in coments. Thanks.
I have a SMALL a problem. In Unity 3D 2020 Beta, I've put a player with a sphere collider on it and some cubes (walls) with box colliders. I've added a player controller script to the player object.
I've put the camera above the plane where the player and the walls are on, and I've made that the player should rotate to face the mouse position. I used rigidbody.AddForce for movement in a FixedUpdate function.
The player controller script is attached below:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[Header("Keys")]
public KeyCode forward;
public KeyCode backward;
public KeyCode left;
public KeyCode right;
public KeyCode fire;
[Header("Health")]
public int hitpoints = 3;
[Header("Movement")]
public float speed;
public float turningSpeed;
[Header("Shooting")]
public GameObject bulletPrefab;
public Transform bulletSpawner;
public float bulletSpeed;
public float reloadTime;
private float currentReload;
private Rigidbody rb;
private Quaternion targetRotation;
void Start()
{
rb = GetComponent<Rigidbody>();
currentReload = reloadTime;
}
void LateUpdate()
{
if (hitpoints == 0)
Die();
// Rotation
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
targetRotation = Quaternion.LookRotation(hit.point - transform.position);
Debug.DrawLine(transform.position, hit.point, Color.white);
}
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, turningSpeed * Time.deltaTime);
transform.eulerAngles = new Vector3(0, transform.rotation.eulerAngles.y, 0);
currentReload += Time.deltaTime;
// Shooting
if (Input.GetKeyDown(fire) && currentReload >= reloadTime)
{
currentReload = 0f;
GameObject bulletGO = Instantiate(bulletPrefab, bulletSpawner.position, transform.rotation);
bulletGO.transform.position = bulletSpawner.position;
Bullet bulletScript = bulletGO.GetComponent<Bullet>();
bulletScript.speed = bulletSpeed;
Destroy(bulletGO, 5f);
}
}
void FixedUpdate()
{
// Movement
if (Input.GetKey(forward))
{
rb.AddForce(Vector3.forward * speed, ForceMode.Force);
}
if (Input.GetKey(backward))
{
rb.AddForce(-Vector3.forward * speed, ForceMode.Force);
}
if (Input.GetKey(left))
{
rb.AddForce(Vector3.left * speed, ForceMode.Force);
}
if (Input.GetKey(right))
{
rb.AddForce(Vector3.right * speed, ForceMode.Force);
}
//transform.position = new Vector3(transform.position.x, 10, transform.position.z);
// ON RIGIDBODY I HAVE CONSTRAINS:
// POSITION: Y (thats why I commented the line above)
// ROTATION: X, Z (topdown -> so I want only rotation on Y)
}
private void Die()
{
Destroy(gameObject);
}
}
But the problem is when the player hits very hard a wall, the sphere collider starts shaking and the player does not look at the mouse position exactly (it is somewhere 10 degrees away most of the times - it depends on how hard do I hit the walls).
I can record if it helps. If you want any information, feel free to ask! Any help will be appreciated! :)
The problem here seems to be you're directly altering the transform of your object despite having a Rigidbody component. Generally you should avoid altering a transform directly when you have a Rigidbody attached, especially a non-kinematic one, as by attaching one you are signalling that the object is to be controlled by the physics simulation.
Solutions I would explore:
If you don't need a rigidbody, don't use one
If you can avoid altering the transform directly, then do not do so. You can rotate objects by applying torque and the likes
Try setting your object to kinematic if you don't need collisions to affect the rigidbody's physics
Manually set the torque and velocity of your object to 0 each fixed update
I'm creating an Archery, Platformer, Shooting game named "ArcheryRun" with a small team. We have a powerbar (shown in orange) which increases as you hold down the "Left Mouse Button". However since its a UI element it is static in a position.
I would like it to appear above the player when they change their z-axis as Ive done with the Arrow Image by using a 2D Sprite object. However I can't seem how to change the z-axis of a UI Image relative to the player or use a Game Object which allows a fill option.
Any help is appreciated, thanks :)
Change Orange Powerbar to Follow Player
If we understand your question, this should work:
This method gives you screen canvas position from world position:
public static Vector3 GetScreenPositionFromWorldPosition(Vector3 targetPosition)
{
Vector3 screenPos;
screenPos = Camera.main.WorldToScreenPoint(targetPosition);
return new Vector3 (screenPos.x, screenPos.y, 0f);
}
Then you need a script on top of power bar, lets say like this:
public class PowerBar : MonoBehaviour
{
public Transform target;
RectTransform _rectTransform;
void Awake ()
{
_rectTransform = GetComponent<RectTransform> ();
}
public void updatePosition()
{
Vector3 pos = YourStaticClassWith.GetScreenPositionFromWorldPosition (target.position);
Vector2 rpos = pos;
_rectTransform.anchoredPosition3D = rpos + Vector2.up * 100f; // 100f is value of how many pixels above the target that UI element should apear
}
void LateUpdate()
{
updatePosition ();
}
}
The target is basicly transform of your player object.
The 100f value represents pixels above the target.