I have a scene that is as follows:
I have the player object, which is a Z locked cube that I move around with a script, using various AddForce calls. The important method in there is this one:
public void SetDirections(Vector3 down)
{
down.Normalize();
this.down = down;
forward = Vector3.Cross(down, new Vector3(0, 0, 1));
}
Other code relating to rotation in the same script is this:
void Turning ()
{
// Irányitó gombok hatására a Taxi elforduljon 180fokot
Vector3 lookDirection = forward;
// balra forditás
if (inputh > 0)
{
faceLeft = false;
}
// jobbra forditás
if (inputh < 0)
{
faceLeft = true;
}
if (faceLeft)
{
lookDirection *= -1;
}
transform.LookAt(transform.position + lookDirection, new Vector3(0, 1, 0));
}
I have this bit on another object in the scene:
public class RotateGravity : MonoBehaviour
{
public GameObject Taxi;
public float MinLimit, MaxLimit;
private TaxiController tControl;
private Rigidbody tRigidbody;
private Transform tTransform;
void Start()
{
tControl = Taxi.GetComponent<TaxiController>();
tRigidbody = Taxi.GetComponent<Rigidbody>();
tTransform = Taxi.GetComponent<Transform>();
}
void OnTriggerStay()
{
Vector3 originalDown = tControl.Down;
Vector3 newDown = tTransform.position - gameObject.transform.position;
if (Vector3.SignedAngle(newDown, new Vector3(1, 0, 0), new Vector3(0, 0, 1)) > MinLimit &&
Vector3.SignedAngle(newDown, new Vector3(1, 0, 0), new Vector3(0, 0, 1)) < MaxLimit)
{
newDown.Normalize();
tControl.SetDirections(newDown);
float angle = Vector3.SignedAngle(originalDown, newDown, new Vector3(0, 0, 1));
tRigidbody.velocity = Quaternion.AngleAxis(angle, new Vector3(0, 0, 1)) * tRigidbody.velocity;
}
Debug.Log("Stay: " + newDown);
}
void OnTriggerExit()
{
float angle = Vector3.Angle(tTransform.position - gameObject.transform.position, new Vector3(1, 0, 0));
Vector3 newDown;
float newAngle;
if (Mathf.Abs(angle - MinLimit) < Mathf.Abs(angle - MaxLimit))
{
newDown = Quaternion.AngleAxis(MinLimit, new Vector3(0, 0, -1)) * new Vector3(1, 0, 0);
newAngle = MinLimit;
}
else
{
newDown = Quaternion.AngleAxis(MaxLimit, new Vector3(0, 0, -1)) * new Vector3(1, 0, 0);
newAngle = MaxLimit;
}
Debug.Log("Exit: " + newDown + " " + newAngle);
tControl.SetDirections(newDown);
tRigidbody.velocity = Quaternion.AngleAxis(newAngle, new Vector3(0, 0, 1)) * tRigidbody.velocity;
}
}
Basically the idea is that the gravity direction changes as the player moves along the object, so just by moving right it would revolve around the object between the MinLimit and MaxLimit angles. Which it does. At the end I want to fix the direction to these limits and this is where a weird thing happens. In my example I have the MinLimit at 0 and the MaxLimit at 90. Meaning that the player enters horizontally from the left, and gravity changes from (0, -1, 0) to (1, 0, 0). Except, at the end the player object rotates 90 degrees around its forward vector.
Can somebody explain where I'm going wrong? I hate rotations in Unity :(
In the video below the red sphere is just an indicator under the player object to show better its facing.
https://www.youtube.com/watch?v=z4p9UQb7inA
Okay, I was not setting the up vector correctly in the Turning() method, it pointed in the positive Y direction, so when my forward direction ended up being the same it didn't know where to rotate the object on that axis. I'm dumb. I set the up vector to point to -1 * down, and boom, it worked.
Related
I have a gravity thing and when they touch the ceiling I want the player to flip, my code doesn't work and I wonder if there is a better way to do this. I'd also like to know how to make the player not rotate to those exact coordinates and just where the player is already facing.
IEnumerator GravitySwitch()
{
if (Input.GetMouseButtonDown(1) && grounded)
{
Physics.gravity = new Vector3(0, 10.0f, 0);
yield return new WaitForSeconds(0.2f);
if (grounded)
transform.localEulerAngles = new Vector3(0, 0, 180);
}
if (Input.GetMouseButtonDown(0) && grounded)
{
Physics.gravity = new Vector3(0, -10.0f, 0);
yield return new WaitForSeconds(0.2f);
if (grounded)
transform.localEulerAngles = new Vector3(0, 0, 0);
}
}
You could multiply the scale of your player's scale vector by:
new Vector3(1, -1, 1);
can try
transform.Rotate(0.0f, 0.0f, 180.0f, Space.Self);
https://docs.unity3d.com/ScriptReference/Transform.Rotate.html
so i don't know if i explained it properly in the title, but what i'm trying to do is by clicking Q or E rotate object around the Y axis, but at the same time i'm raycasting to the ground to check the point rotation to face it in to the right direction.
To clarify this, here is an example:
I'm trying to place the fireplace, i'm on the hill. To prevent the fireplace go into the terrain, i need to rotate it so that i would "stick" to the terrain by it's bottom, but then i want to rotate it around the Y a couple of degrees..
Here is the code that i've got:
using UnityEngine;
public class Foobar : MonoBehaviour
{
Transform cam;
Transform prefabAsTransform;
Vector3 currentPos;
float addToY = 0.5f;
int mask;
void Start()
{
cam = Camera.main.transform;
prefabAsTransform = transform;
mask = LayerMask.GetMask("Default");
}
void Update()
{
RaycastHit hit;
if (Physics.Raycast(cam.position, cam.forward, out hit, Mathf.Infinity, mask))
{
currentPos = new Vector3(hit.point.x, hit.point.y + addToY, hit.point.z);
prefabAsTransform.position = currentPos;
prefabAsTransform.transform.up = hit.normal;
}
if (prefabAsTransform != null)
{
if (Input.GetKeyDown(KeyCode.Q))
{
if (Input.GetKey(KeyCode.LeftControl))
{
prefabAsTransform.Rotate(new Vector3(0, 1, 0));
}
else
{
prefabAsTransform.Rotate(new Vector3(0, 10, 0));
}
}
if (Input.GetKeyDown(KeyCode.E))
{
if (Input.GetKey(KeyCode.LeftControl))
{
prefabAsTransform.Rotate(new Vector3(0, -1, 0));
}
else
{
prefabAsTransform.Rotate(new Vector3(0, -10, 0));
}
}
}
}
}
The problem here is that it's facing the terrain without any issues, but i'm unable to rotate it around Y by myself. I assume that's due to the fact, that i'm constantly updating the rotation, but i have no idea how to change that.
Yes, it's because you are setting the rotation every frame with prefabAsTransform.transform.up = hit.normal; and not taking the current forward direction into account.
Instead, you should use prefabAsTransform.rotation = LookRotation(newForward, newUp); But, how can you use the current forward to determine newForward? You can use cross products for that. This worked for me:
void Update()
{
RaycastHit hit;
if (Physics.Raycast(cam.position, cam.forward, out hit, Mathf.Infinity, mask))
{
currentPos = new Vector3(hit.point.x, hit.point.y + addToY, hit.point.z);
prefabAsTransform.position = currentPos;
Vector3 newUp = hit.normal;
Vector3 oldForward = prefabAsTransform.forward;
Vector3 newRight = Vector3.Cross(newUp, oldForward);
Vector3 newForward = Vector3.Cross(newRight, newUp);
prefabAsTransform.rotation = Quaternion.LookRotation(newForward, newUp);
}
if (prefabAsTransform != null)
{
if (Input.GetKeyDown(KeyCode.Q))
{
if (Input.GetKey(KeyCode.LeftControl))
{
prefabAsTransform.Rotate(new Vector3(0, 1, 0));
}
else
{
prefabAsTransform.Rotate(new Vector3(0, 10, 0));
}
}
if (Input.GetKeyDown(KeyCode.E))
{
if (Input.GetKey(KeyCode.LeftControl))
{
prefabAsTransform.Rotate(new Vector3(0, -1, 0));
}
else
{
prefabAsTransform.Rotate(new Vector3(0, -10, 0));
}
}
}
}
protected override void Update(GameTime gameTime)
{
int dt = gameTime.ElapsedGameTime.Milliseconds;
base.Update(gameTime);
player.storedPos = player.position;
Vector3 storedAcc = acceleration;
acceleration = new Vector3(0, 0, 0);
if (Keyboard.GetState().IsKeyDown(Keys.Left)) player.rotation.Y += 0.1f;
if (Keyboard.GetState().IsKeyDown(Keys.Right)) player.rotation.Y -= 0.1f;
player.velocity *= 0.9f; // friction
if (Keyboard.GetState().IsKeyDown(Keys.Up))
{
acceleration.X = (float)Math.Sin(player.rotation.Y) * 0.001f;
acceleration.Z = (float)Math.Cos(player.rotation.Y) * 0.001f;
}
// camera follow
gamecam.position = new Vector3(50, 50, 50) + player.position;
gamecam.target = player.position;
MovePlayer(dt);
foreach (basicCuboid WallSegment in walls)
{
if (player.hitBox.Intersects(WallSegment.collisionbox))
{
ElasticCollision(WallSegment);
}
}
if (player.hitBox.Intersects(door.collisionbox))
{
ElasticCollision(door);
}
if (player.hitBox.Intersects(TriggerBoxRockFall) && !rockFalling)
{
rockFalling = true;
rock.velocity = new Vector3(0, 0.2f, 0);
}
if (rockFalling)
{
Vector3 gravity = new Vector3(0, -0.01f, 0);
}
So this is the code I have so far, I am unsure about how to make the rock actually fall though. What am I missing or have I typed something in wrong? I need the rock to fall upon the player colliding with a trigger box below the rock.
It is not clear from the code provided, but you only want the code for player.hitBox.Intersects(TriggerBoxRockFall) to trigger when you are below it.
Change the code:
if (player.hitBox.Intersects(TriggerBoxRockFall) && !rockFalling)
{
rockFalling = true;
rock.velocity = new Vector3(0, 0.2f, 0);
}
to:
if (player.Velocity.Y < 0 && player.hitBox.Intersects(TriggerBoxRockFall) && !rockFalling)
{
rockFalling = true;
rock.velocity = new Vector3(0, 0.2f, 0);
}
As a side note, move as much of the code out of Game1 as possible. The gravity of the rock should be done in the Rock, not Game1.
for example:
if (player.Velocity.Y < 0 && player.hitBox.Intersects(TriggerBoxRockFall) && !rockFalling)
{
rockFalling = true;
rock.velocity = new Vector3(0, 0.2f, 0);
rock.GravityApplies = true;
}
I have small game I wrote in which i have object which moving forward and turning left and right. when user press A object goes left and rotate to the left if user pres D it goes left and rotate left, i what to set rotation to 0 after user letting key up
bool slowbtn = Input.GetKey("s");
bool right = Input.GetKey("d");
if (right == true)
{
rd.AddForce(sideForce * Time.deltaTime, 0, 0,ForceMode.VelocityChange);
rd.transform.eulerAngles = new Vector3(0, 10 , 0);
}
if (Input.GetKey("a"))
{
rd.AddForce(-sideForce * Time.deltaTime, 0, 0, ForceMode.VelocityChange);
rd.transform.eulerAngles = new Vector3(0, -10 , 0);
}
if i wan to set rotation back to 0 when user release key i am using this
if (Input.GetButtonUp("a"))
{
rd.transform.eulerAngles = new Vector3(0, 0, 0);
}
if (Input.GetButtonUp("d"))
{
rd.transform.eulerAngles = new Vector3(0, 0, 0);
}
but it doesn't work I don't understand why and also it brakes my previous code , so object if not moving forward
Unity's Input.GetButtonUp and Input.GetButtonDown handle "virtual" buttons, which you set up in Input Settings. Input.GetKeyUp/Input.GetKeyDown are about keys on your keyboard. So, you should choose GetKey or GetButton but not both at the same time.
As I see, you want your object rotates during all the time user is pressing a key. I suggest you to use addition "state" property in your class:
private State state = State.IDLE;
private enum State {
LEFT, RIGHT, IDLE
};
Update your code:
if (Input.GetKeyDown(KeyCode.D)) {
state = State.RIGHT;
}
if (Input.GetKeyDown(KeyCode.A)) {
state = State.LEFT;
}
if (Input.GetKeyUp(KeyCode.D) || Input.GetKeyUp(KeyCode.A)) {
state = State.IDLE;
}
switch (state) {
case State.LEFT:
rd.AddForce(sideForce * Time.deltaTime, 0, 0, ForceMode.VelocityChange);
rd.transform.eulerAngles = new Vector3(0, 10, 0);
break;
case State.RIGHT:
rd.AddForce(-sideForce * Time.deltaTime, 0, 0, ForceMode.VelocityChange);
rd.transform.eulerAngles = new Vector3(0, -10 , 0);
break;
case State.IDLE:
rd.transform.eulerAngles = Vector3.zero;
break;
}
Several recommedations:
Operate with physics in FixedUpdate() method instead of Update()
Use Unity's KeyCode enumeration for keyboard keys
Keep your code clean
Develop an algorithm first and then convert that algorithm to a code
When I replace your
Input.GetButtonUp("a")
with
Input.GetKeyUp("a")
this works totally fine.
Did you try to debug this code for yourself? Because it was quite simple to figure out that the Input.GetButtonUp(…) was not called by setting a breakpoint in this line.
Btw. I would consider writing your input code like this:
if (Input.GetKey("d"))
{
rd.AddForce(sideForce * Time.deltaTime, 0, 0, ForceMode.VelocityChange);
rd.transform.eulerAngles = new Vector3(0, 10, 0);
}
else if (Input.GetKey("a"))
{
rd.AddForce(-sideForce * Time.deltaTime, 0, 0, ForceMode.VelocityChange);
rd.transform.eulerAngles = new Vector3(0, -10, 0);
}
else
{
rd.transform.eulerAngles = new Vector3(0, 0, 0);
}
I am using Unity 3D engine to make a 2D game. I want to create endlessly repeating terrain. I got it to repeat seamlessly, except for the fact that sometimes two terrains will spawn at once. It happens randomly and without any good reason. Any help?
This is my terrain respawn code:
using UnityEngine;
using System.Collections;
using PlayerPrefs = GamePlayerPrefs;
public class SelfTerrainSpawn : MonoBehaviour {
// terrain references
public GameObject first;
public GameObject second;
public GameObject third;
public GameObject fourth;
public GameObject fifth;
public GameObject sixth;
public GameObject seventh;
public float spawnXPos = 0.0f;
public float respawnCoordinate = 30.4f;
public float respawnTriggerCoordinate = -21.7f;
public bool canSpawn = true;
public bool starting = true;
public float random;
void Start() {
this.gameObject.transform.position = new Vector2 (0.0f, 31.4f);
this.gameObject.transform.eulerAngles = new Vector3 (0, 0, 90.0f);
}
void Update()
{
// if the camera is farther than the number last position minus 16 terrain is spawned
// a lesser number may make the terrain 'pop' into the scene too early
// showing the player the terrain spawning which would be unwanted
if (this.gameObject.transform.position.y <= respawnTriggerCoordinate && canSpawn)
{
// turn off spawning until ready to spawn again
random = Random.Range(0,18);
SpawnTerrain (random);
canSpawn = false;
}
if (this.gameObject.transform.position.y <= respawnTriggerCoordinate - 10) {
Destroy (this.gameObject);
}
}
void SpawnTerrain(float rand) {
if ((rand == 0)) {
Instantiate (first, new Vector3 (respawnCoordinate, spawnXPos, 0), Quaternion.Euler (0, 0, 0));
}
if ((rand >= 1) && (rand <= 4)) {
Instantiate (second, new Vector3 (respawnCoordinate, spawnXPos, 0), Quaternion.Euler (0, 0, 0));
}
if ((rand >= 5) && (rand <= 8)) {
Instantiate (third, new Vector3 (respawnCoordinate, spawnXPos, 0), Quaternion.Euler (0, 0, 0));
}
if ((rand >= 9) && (rand <= 10)) {
Instantiate (fourth, new Vector3 (respawnCoordinate, spawnXPos, 0), Quaternion.Euler (0, 0, 0));
}
if ((rand >= 10) && (rand <= 13)) {
Instantiate (fifth, new Vector3 (respawnCoordinate, spawnXPos, 0), Quaternion.Euler (0, 0, 0));
}
if ((rand >= 13) && (rand <= 15)) {
Instantiate (sixth, new Vector3 (respawnCoordinate, spawnXPos, 0), Quaternion.Euler (0, 0, 0));
}
if ((rand >= 15) && (rand <= 18)) {
Instantiate (seventh, new Vector3 (respawnCoordinate, spawnXPos, 0), Quaternion.Euler (0, 0, 0));
}
}
}
This script is attached to the first terrain piece, inside of the game:
Click here to see an image of the game screen
It is also attached to the other two prefabs. Each of these prefabs also have a script that moves them down the screen slowly. Basically, when one's top reaches the top of the camera, another is supposed to spawn above the camera. Then it moves down and repeats. See that I used the canSpawn variable to make sure that it only spawns once. However, at random, two terrains will spawn on top of each other. Can anybody give me a solution to this problem?
When rand is 10, 13, or 15, you'll generate two terrains because your range checks overlap for those values.