Unity3D Position and Rotation matching on Waypoints - c#

I have a simple waypoint system. It uses a transform of arrays that act as the bucket what holds the waypoint values.
I use this waypoint system to move a camera throughout a scene by moving towards one point to another. The scene is not big - so everything is close to each other.
The camera moves from one position to another by button click/press. This works fine.
void Start()
{
//Sets the Camera to the first point
Camera = GameObject.Find("Main Camera");
Camera.transform.position = patrolPoints[0].position
currentPoint = 0;
}
//Fixed Update seems to work better for LookAt
void FixedUpdate()
{
//Looks at initial Target
Camera.transform.LookAt(TargetPoints[0]);
if (click == true)
{
Camera.transform.position = Vector3.MoveTowards(Camera.transform.position, patrolPoints[currentPoint].position, moveSpeed * Time.deltaTime);
//Camera.transform.rotation = Quaternion.Slerp(Camera.transform.rotation, patrolPoints[currentPoint].transform.rotation, Time.deltaTime);
Camera.transform.LookAt(TargetPoints[currentPoint]);
}
}
public void onNextClick()
{
if (currentPoint >= patrolPoints.Length)
{
currentPoint = 0;
}
if (Camera.transform.position == patrolPoints[currentPoint].position)
{
currentPoint++;
click = true;
}
}
I am having difficulty with one aspect of the transform that I haven't talked about yet. That is the rotation.
I have used lookAt for setting up the target of the first look at point and that works. However when it runs to the next target in the look at array - the change is sudden.
I have tried an Slerp in the shot as well - and this works when the waypoint has been placed in the appropriate rotation value - and the value Slerps from one position to the next. However, the timing isn't quite aligning up yet. Some position transitions get there quicker - meaning the rotation is trying to get caught up / while others are lagging behind.
I have tried getting the distance between the current waypoint and the next waypoint and treating that as an overall percentage in the update relative to the current position of the camera - I believe this could help work out how far the rotation should be orientated given the position update.
However, I am somewhat lost on it. Many examples suggest looking at iTween - and I wouldn't imagine this would work - however, I want to get into the math a bit.
Can anyone put me in the right direction?

Looks like the Lerp for Position and a Slerp for Rotation done the trick.
MoveTowards wasn't playing ball with a Slerp on rotation - so I believe the timings weren't aligning.
if (click == true)
{
Camera.transform.position = Vector3.MoveTowards(Camera.transform.position, patrolPoints[currentPoint].position, moveSpeed * Time.deltaTime);
Camera.transform.rotation = Quaternion.Lerp(Camera.transform.rotation, patrolPoints[currentPoint].rotation, moveSpeed * Time.deltaTime);
I've been led to believe the lerp values work like a percentage of such - so the same input value works for it.
Finally I used a range on the distance between current position and update on the click - this helped speed up the button click.
if (Vector3.Distance(Camera.transform.position, patrolPoints[currentPoint].position) < PositionThreshold)
{
currentPoint++;
click = true;
}
Thank you for your time.

Related

Why is this transform.position reset between frames

So, my issue is as follows:
I'm developing a very simple 2D game as I'm quite new to Unity. Basically, there is a knight, and there is a troll. I'm developing my custom AI for the troll and got stumped at the very beginning. I tried to make the troll sprite correctly rotate and walk towards the player (in Update()), but then it turned out that the troll game object doesn't move as much as a millionth of a unit (although it rotates correctly)!
Now, what is special about my problem is the following: it's not about using localPosition instead of position (the troll has no parents – pun intended), it's not about referencing the transform in the script via "[SerializeField]", it's not about working with static objects or messed-up prefab coordinates or any of the other issues I've found in my desperate 10-hour search on the Internet. No, the issue is that I HAVE the troll transform in my code, I modify it successfully, but then it fails to be applied to the in-game troll object. Let that sink in – I am correctly modifying the correct transform (checked multiple times via debugging) but at the beginning of the next frame, all changes get magically reverted and the transform doesn't move the slightest.
I've made almost twenty modifications to my code in order to try and make it work (including separating stuff in different files as was suggested in a particular forum topic) but it just doesn't, and I can't think of anything else. Any help is very, very much appreciated.
public class EnemyController : MonoBehaviour
{
[SerializeField]
private Animator animator;
[SerialiseField]
private float walkingSpeed;
[SerializeField]
[Header("The aggro range of the enemy in Unity units.")]
private float aggroRange;
private float dir;
private Transform playerTransform;
void Update()
{
if (Aggro(playerTransform, aggroRange))
{
//WALKING:
WalkTowards(playerTransform, walkingSpeed);
}
}
private void WalkTowards(Transform targetTransform, float walkingSpeed)
{
animator.SetBool("IsRunning", false);
dir = 0.0f;
//Get the direction (left or right)
if (targetTransform.position.x < transform.position.x) //Left
{
dir = -1.0f;
//Flip the sprite to the left
transform.rotation = Quaternion.Euler(0.0f, 180.0f, 0.0f);
}
else //Right
{
dir = 1.0f;
//Flip the sprite to the right
transform.rotation = Quaternion.Euler(0.0f, 0.0f, 0.0f);
}
transform.position = new Vector3(
transform.position.x + (walkingSpeed * dir * Time.deltaTime),
transform.position.y,
transform.position.z
);
Debug.Log(
string.Format("Troll 1 position:\nx: {0:#.##}", transform.position.x) +
string.Format(" y: {0:#.##}", transform.position.y) +
string.Format(" z: {0:#.##}", transform.position.z)
);
}
}
(NOTE: Aggro() is a custom method that determines when the enemy aggros; not included in the code as it is irrelevant to the issue)
So basically, transform.position gets changed correctly in the script and stays that way until the end of the method, but at the beginning of the next frame – i.e. when WalkTowards() gets called again, the transform.position remains the same as it was at the beginning of the last frame – as if no calculations or changes were ever made.
#Ruzihm,
You were right!!! Turns out the Animator component of the troll was causing the issue – "Apply root motion" was off, I turned it on and the position gets correctly updated now. Thank you ever so much for the help!
Example:

Gyroscope with compass help needed

I need to have a game object point north AND I want to combine this with gyro.attitude input. I have tried, unsuccessfully, to do this in one step. That is, I couldn't make any gyro script, which I found on the net, work with the additional requirement of always pointing north. Trust me, I have tried every script I could find on the subject. I deduced that it's impossible and probably was stupid to think it could be done; at least not this way (i.e. all-in-one). I guess you could say I surmised that you can't do two things at once. Then I thought possibly I could get the same effect by breaking-up the duties. That is, a game object that always points north via the Y axis. Great, got that done like this:
_parentDummyRotationObject.transform.rotation = Quaternion.Slerp(_parentDummyRotationObject.transform.rotation, Quaternion.Euler(0, 360 - Input.compass.trueHeading, 0), Time.deltaTime * 5f);
And with the game object pointing north on the Y, I wanted to add the second game-object, a camera in this case, with rotation using gyro input on the X and Z axis. The reason I have to eliminate the Y axes on the camera is because I get double rotation. With two things rotating at once (i.e. camera and game-object), a 180 degree rotation yielded 360 in the scene. Remember I need the game object to always point north (IRL) based on the device compass. If my device is pointing towards the East, then my game-object would be rotated 90 degrees in the unity scene as it points (rotation) towards the north.
I have read a lot about gyro camera controllers and one thing I see mentioned a lot is you shouldn't try to do this (limit it) on just 1 or 2 axis, when using Quaternions it's impossible when you don't know what you're doing, which I clearly do not.
I have tried all 3 solutions from this solved question: Unity - Gyroscope - Rotation Around One Axis Only and each has failed to rotate my camera on 1 axis to satisfy my rotational needs. Figured I'd try getting 1 axis working before muddying the waters with the 2nd axis. BTW, my requirements are simply that the camera should only rotate on 1 axis (in any orientation) based on the X axis of my device. If I could solve for X, then I thought it'd be great to get Z gyro input to control the camera as well. So far I cannot get the camera controlled on just 1 axis (X). Anyway, here are my findings...
The first solution, which used Input.gyro.rotationRateUnbiased, was totally inaccurate. That is, if I rotated my device around a few times and then put my phone/device down on my desk, the camera would be in a different rotation/location each time. There was no consistency. Here's my code for the first attempt/solution:
<code>
private void Update()
{
Vector3 previousEulerAngles = transform.eulerAngles;
Vector3 gyroInput = Input.gyro.rotationRateUnbiased;
Vector3 targetEulerAngles = previousEulerAngles + gyroInput * Time.deltaTime * Mathf.Rad2Deg;
targetEulerAngles.y = 0.0f;
targetEulerAngles.z = 0.0f;
transform.eulerAngles = targetEulerAngles;
}
</code>
The second solution was very consistent in that I could rotate my device around and then put it down on the desk and the unity camera always ended up in the same location/rotation/state so-to-speak. The problem I had was the camera would rotate on the one axis (X in this case), but it did so when I rotated my device on either the y or x axis. Either type of rotation/movement of my phone caused the unity camera to move on the X. I don't understand why the y rotation of my phone caused the camera to rotate on X. Here is my code for solution #2:
private void Start()
{
Input.gyro.enabled = true;
startEulerAngles = transform.eulerAngles;
startGyroAttitudeToEuler = Input.gyro.attitude.eulerAngles;
}
private void Update()
{
Vector3 deltaEulerAngles = Input.gyro.attitude.eulerAngles - startGyroAttitudeToEuler;
deltaEulerAngles.y = 0.0f;
deltaEulerAngles.z = 0.0f;
transform.eulerAngles = startEulerAngles - deltaEulerAngles;
}
The 3rd solution: I wasn't sure how to complete this last solution, so it never really worked. With the 2 axis zeroed-out, the camera just flipped from facing left to right and back, or top to bottom and back; depending on which axis were commented out. If none of the axis were commented-out (like the original solution) the camera would gyro around on all axis. Here's my code for attempt #3:
private void Start()
{
_upVec = Vector3.zero;
Input.gyro.enabled = true;
startEulerAngles = transform.eulerAngles;
}
private void Update()
{
Vector3 gyroEuler = Input.gyro.attitude.eulerAngles;
phoneDummy.transform.eulerAngles = new Vector3(-1.0f * gyroEuler.x, -1.0f * gyroEuler.y, gyroEuler.z);
_upVec = phoneDummy.transform.InverseTransformDirection(-1f * Vector3.forward);
_upVec.z = 0;
// _upVec.x = 0;
_upVec.y = 0;
transform.LookAt(_upVec);
// transform.eulerAngles = _upVec;
}
Originally I thought it was my skills, but after spending a month on this I'm beginning to think that this is impossible to do. But that just can't be. I know it's a lot to absorb, but it's such a simple concept.
Any ideas?
EDIT: Thought I'd add my hierarchy:
CameraRotator (parent with script) -> MainCamera (child)
CompassRotator (parent) -> Compass (child with script which rotates parent)
I'd do this in the following way:
Camara with default 0, 0, 0 rotation
Screenshot
Object placed at the center of the default position of the camera.
Script for the Camera:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
Camera m_MainCamera;
// Start is called before the first frame update
void Start()
{
// Disable the sleep timeout during gameplay.
// You can re-enable the timeout when menu screens are displayed as necessary.
Screen.sleepTimeout = SleepTimeout.NeverSleep;
// Enable the gyroscope.
if (SystemInfo.supportsGyroscope)
{
Input.gyro.enabled = true;
}
m_MainCamera = Camera.main;
m_MainCamera.enabled = true;
}
// Update is called once per frame
void Update()
{
if (m_MainCamera.enabled)
{
// First - Grab the Gyro's orientation.
Quaternion tAttitude = Input.gyro.attitude;
// The Device uses a 'left-hand' orientation, we need to transform it to 'right-hand'
Quaternion tGyro = new Quaternion(tAttitude.x, tAttitude.y, -tAttitude.z, -tAttitude.w);
// the gyro attitude is tilted towards the floor and upside-down reletive to what we want in unity.
// First Rotate the orientation up 90deg on the X Axis, then 180Deg on the Z to flip it right-side up.
Quaternion tRotation = Quaternion.Euler(-90f, 0, 0) * tGyro;
tRotation = Quaternion.Euler(0, 0, 180f) * tRotation;
// You can now apply this rotation to any unity camera!
m_MainCamera.transform.localRotation = tRotation;
}
}
}
With this script my Object always face SOUTH no matter what.
If you want the object to face NORTH you just have to turn the view 180º on the Y axis as a last rotation:
Quaternion tRotation = Quaternion.Euler(-90f, 0, 0) * tGyro;
tRotation = Quaternion.Euler(0, 0, 180f) * tRotation;
//Face NORTH:
tRotation = Quaternion.Euler(0,180f, 0) * tRotation;
Hope this might help ;)

Show object in front of the player always

I have stuck in this simple problem but unable to understand that why i am unable to control it.
I have these line of code which is displaying my canvas object in front of my player(camRotationToWatch object name in code) at certain rotation of the player.
if (camRotationToWatch.transform.localEulerAngles.x >= navigationCanvasXMinmumLimit && camRotationToWatch.transform.localEulerAngles.x <= navigationCanvasXMaximumLimit)
{
if (!navCanvasHasDisplay)
{
navigationCanvas.SetActive(true);
//Debug.Log(camRotationToWatch.transform.forward);
Vector3 navCanvas = camRotationToWatch.transform.position + camRotationToWatch.transform.forward * navCanvasDisplayDistanceFromCam;
navCanvas = new Vector3(navCanvas.x, 2f, navCanvas.z);
navigationCanvas.transform.position = new Vector3(navCanvas.x, navCanvas.y, navCanvas.z);
navigationCanvas.transform.rotation = camRotationToWatch.transform.rotation;
navCanvasHasDisplay = true;
}
}
else
{
//navigationCanvas.SetActive(false);
if (locationPanel.activeSelf == false && infoPanel.activeSelf == false) {
navigationCanvas.SetActive(false);
navCanvasHasDisplay = false;
}
}
This code is actually work fine when camRotationToWatch object rotate from down to up and Canvas show at correct position but as I try to to rotate camRotationToWatch from up to down it display(active) Canvas at very top position. How can I restrict canvas to show at same position (No matter player rotate from up to down or down to up) but display on front of the player object?
Kinda hard trying to figure out what exactly you want to do. But this did what I think you where trying to do
public GameObject follow; // The object you want to rotate around
public float distance = 2; // Distance to keep from object
private void Update() {
Vector3 forward = follow.transform.forward;
forward.y = 0; // This will result in Vector3.Zero if looking straight up or down. Carefull
transform.position = follow.transform.position + forward * distance;
transform.rotation = Quaternion.LookRotation(forward, Vector3.up);
}
I believe your "unexpected behavior" is due to the use of euler angles since they are not always entirely predictable. Try using Quaternions or Vector3.Angle() when possible.
If you want to limit the angle (say... if looking down or up more than 45° disable the object) you could do the following:
if (Vector3.Angle(forward, follow.transform.forward) > maxAngle) { ... }
This probably isn't a complete answer but something that might help. This line:
Vector3 navCanvas = camRotationToWatch.transform.position + camRotationToWatch.transform.forward * navCanvasDisplayDistanceFromCam;
You are creating a position at a fixed distance from camRotationToWatch. But if that object is looking up or down, that position is not horizontally at navCanvasDisplayDistanceFromCam. If it's looking straight up, then this position is in fact directly above.
So when you do this to set a fixed vertical height:
navCanvas = new Vector3(navCanvas.x, 2f, navCanvas.z);
you aren't getting the distance from camRotationToWatch that you think you are.
Instead of using camRotationToWatch.transform.forward, create a vector from it and zero out the Y component, and normalize before using it to offset the position. (You will need to watch out for zero length vectors with that though).
Whether that fixes your problem or not, it's too hard to guess but it should help improve your results some.
EDIT: Here is an example of how you can avoid the issue with the canvas being too close:
Vector3 camForward = camRotationToWatch.transform.forward;
camForward.y = 0;
if (camForward.magnitude == 0)
{
//TODO: you'll need to decide how to deal with a straight up or straight down vector
}
camForward.Normalize();
//Note: now you have a vector lying on the horizontal plane, pointing in
//the direction of camRotationToWatch
Vector3 navCanvas = camRotationToWatch.transform.position + camForward *
navCanvasDisplayDistanceFromCam;
//Note: if you want the canvas to be at the player's height (or some offset from),
//use the player's y
navCanvas = new Vector3(navCanvas.x, Player.transform.y, navCanvas.z);
navigationCanvas.transform.position = navCanvas;
Again, this might not fix all your issues but will help to ensure your canvas lies at a set distance horizontally from the Player and will also compensate for the player's up and down motion.

Change position of current gameObject in C# on condition

I have been searching and racking my brains for a while now to try and get this code working, to no avail. Hopefully you guys can help.
I have a simple set up with a cube that moves position every frame. I need the cube to go to an x position when it reaches a different location.
Example: Cube starts at position 0, moves forward in the x axis until it gets to position 15, then reverts back to 0 and stops.
Vector3 startingPosition;
void Start ()
{
startingPosition = gameObject.transform.position;
}
void Update ()
{
if (gameObject.transform.position.x == 15) {
gameObject.transform.position = startingPosition;
} else {
float translation = Time.deltaTime * 2;
transform.Translate (0, 0, translation);
transform.Translate (Vector3.forward * translation);
}
}
}
Currently the cube continuously moves (no stopping point), it's x position having no affect on the positioning.
Change your == to >= and see if that makes a difference. My guess is that position.x is never exactly equal to 15, either due to floating-point precision errors or due to your translation logic skipping over 15 from one frame to the next.
I have had an issue with a animation not working correctly and feel like I have scoured the internet for days trying to get it to work, so this was very helpful. In my situation, the animations' GameObject was at a position of y=1 and should have moved to y=9, but would not update unless I clicked the transition in the animator. I changed the code from x == 15 to y <= 9 and Vector3.forward to Vector3.up and it works perfectly now. Hope this might help someone else with the same issue. using Unity v 2017.1.2

Reset model rotation when input stops

I have the model representing the player's ship gradually leaning when the player strafes. For instance, here's the code that leans the ship right:
In Update() of the Game class:
if (ship.rightTurnProgress < 1 && (currentKeyState.IsKeyDown(Keys.D)))
{
ship.rightTurnProgress += (float)gameTime.ElapsedGameTime.TotalSeconds * 30;
}
In Update() of the Ship class:
if (currentKeyState.IsKeyDown(Keys.D))
{
Velocity += Vector3.Right * VelocityScale * 10.0f;
RotationMatrix = Matrix.CreateRotationX(MathHelper.PiOver2) *
Matrix.CreateRotationY(0.4f * rightTurnProgress);
}
This is what I'm attempting to do to make it ease back out of the lean when it stops strafing:
In Update() of the Game class:
if (ship.rightTurnProgress > 0 && currentKeyState.IsKeyUp(Keys.D))
{
ship.rightTurnProgress -= (float)gameTime.ElapsedGameTime.TotalSeconds * 30;
}
In Update() of the Ship class:
if (currentKeyState.IsKeyUp(Keys.D) && rightTurnProgress > 0)
{
RotationMatrix = Matrix.CreateRotationX(MathHelper.PiOver2) *
Matrix.CreateRotationY(-0.4f * rightTurnProgress);
}
Since easing into the lean works no problem, I thought easing out of the lean would be a simple matter of reversing the process. However, it tends to not go all the way back to the default position after a long strafe. If you tap the key, it snaps all the way back to the full lean of the -opposite- direction. This isn't what I expected at all. What am I missing here?
I suggest you represent the rotation of you ship as a quaternion. That way you can use an interpolation function such as slerp. Simply have a second quaternion that represents you targeted lean angle and the ship will smoothly rotate until it achieves the targeted angle.
Here's a good tutorial on quaternions. If you want to avoid quaternions use MathHelper.Lerp to smoothly transition from the current value to the target.
if (currentKeyState.IsKeyDown(Keys.D))
{
ship.TurnProgress = MathHelper.Lerp(ship.TurnProgress, 1, somefloat * timeDelta);
}
else if (currentKeyState.IsKeyDown(Keys.a))
{
ship.TurnProgress = MathHelper.Lerp(ship.TurnProgress, -1, somefloat * timeDelta);
}
else (currentKeyState.IsKeyDown(Keys.D))
{
ship.TurnProgress = MathHelper.Lerp(ship.TurnProgress, 0, somefloat * timeDelta);
}
Edit: Also there is a GameDev stack overflow so check it out if you have more questions.
Unless you know how long the turn is or you have some kind of acceleration vector you will have to wait until the turn is stopped before returning the sprite angle to neutral, then what happens when the player turns left before the sprite has reached its neutral position? I assume that when you turn right using RightTurnProgress you also have a LeftTurnProgress I suggest you combine them into one variable to keep it smooth and avoid the snapping effect you are getting.
You are creating an 'absolute' rotation matrix so you don't need to flip the sign to -0.4f. Why not just have a variable called ship.lean and calculate the rotation matrix every update. Then you just need logic to ease ship.lean between -1 (left lean) and 1 (right lean) or 0 for no lean.

Categories

Resources