Counting a full rotation with a circular drive - c#

I've been raking my brain for the last day on how to calculate a full rotation with how to count a full rotation of an object along the X axis thats using the base circular drive from SteamVR.
I thought a simple 3d cube, with the mesh turned off in the the path of the rotation with collision code on it would be a barebone way of doing it, but it doesn't even seem to be registering the detection when the object hits the placed cubes, and i know its not because of me being stupid, as its recycled code from a working part of the project.
Below i have a small piece of code that basically detects when the object has reached the end of the rotation, and then increments the Count by one.
My main problem is that sometimes it manages to clock more than once, and if you can find the right spot, you can just keep it there and it'll keep on adding the count up by. Im wondering how i can stop it and increment only by one, until another full rotation has been made?
EDIT: to be more clear in case there is any confusion, Once the angle is clocked in between 359 and 360, i want it to increment once, whereas currently if you get the angle to sit anywhere in between 359-360 it will carry on adding one to the rotation count, despite no full rotation having been made, so im trying to figure out how to make my code only increment once, and once it does increment once it resets the position to zero, so therefore no more Increments can happen. It's a crank mechanism in VR, along the X axis.
Any help is appreciated, Thanks!
float Test;
float RotationCount = 0;
// Start is called before the first frame update
void Start()
{
// Test = transform.localRotation.eulerAngles.x;
}
// Update is called once per frame
void Update()
{
if (Test > 359 && Test < 360)
{
Debug.Log("Clocked");
count();
}
else
{
// Debug.Log("Nope");
}
if (Test == 0)
{
Debug.Log("Yes");
}
Test = transform.localRotation.eulerAngles.x;
}
void count()
{
RotationCount++;
}

sometimes it manages to clock more than once, and if you can find the right spot, you can just keep it there and it'll keep on adding the count up by.
well in
if (Test > 359 && Test < 360)
{
Debug.Log("Clocked");
count();
}
what happens if your angle is e.g. 359.5?
It is very difficult to just take a current rottaion and know whether it was turned more or less than a certain angle.
I'ld rather store the last rotation, compare it to the current one and add the difference to a variable. Than if the variable exceeds 360 a full rotation was done. Since I also don't like to calculate anything with Quaternion and eulerAngles, way simplier is to use the methods provided by Vector3.
For the local rotation around X (== transform.right) I would use the angle between the current and the last transfrm.up vector.
Something like
public class RotationCheck : MonoBehaviour
{
public int RotationCount;
public float rotatedAroundX;
public Vector3 lastUp;
public UnityEvent On3TimesRotated;
private void Awake()
{
rotatedAroundX = 0;
// initialize
lastUp = transform.up;
}
private void Update()
{
var rotationDifference = Vector3.SignedAngle(transform.up, lastUp, transform.right);
rotatedAroundX += rotationDifference;
if (rotatedAroundX >= 360.0f)
{
Debug.Log("One positive rotation done", this);
RotationCount++;
rotatedAroundX -= 360.0f;
}
else if (rotatedAroundX <= -360.0f)
{
Debug.Log("One negative rotation done", this);
RotationCount--;
rotatedAroundX += 360.0f;
}
// update last rotation
lastUp = transform.up;
// check for fire the event
if (RotationCount >= 3)
{
On3TimesRotated?.Invoke();
RotationCount = 0;
}
}
}
You can use a UnityEvent to get the same thing the Button uses for onClick so you can reference callbacks there via the inspector.
BUT if you don't care about the single rotations but actually only wnat the final RotationCount >= 3 I would actually use
private void Update()
{
var rotationDifference = Vector3.SignedAngle(transform.up, lastUp, transform.right);
rotatedAroundX += rotationDifference;
RotationCount = Mathf.RoundToInt(rotatedAroundX / 360.0f);
// update last rotation
lastUp = transform.up;
// check for fire the event
if (RotationCount >= 3)
{
On3TimesRotated?.Invoke();
RotationCount = 0;
rotatedAroundX = 0;
}
}
which directly reduces the value by one if rotated under the 360 mark instead of waiting for a full negative rotation
*as you can see instead of reaching 3 the RotationCount is reset to 0. This is where the On3TimesRotated event is/would get fired.

Related

Unity, how can I make it so a prefab follows a path?

I'm making a small game in Unity and part of it it's that glass balls spawn every few seconds and they follow a path composed of targets, and the prefab has these targets specified:
Prefab picture
And here is the code:
public class move_to_target : MonoBehaviour
{
public GameObject[] target;
public float speed;
int current = 0;
float radius_target = 1;
// Update is called once per frame
void Update()
{
if (Vector3.Distance(target[current].transform.position, transform.position) < radius_target)
{
current = Random.Range(0, target.Length);
if (current >= target.Length)
{
current = 0;
}
}
transform.position = Vector3.MoveTowards(transform.position, target[current].transform.position, Time.deltaTime * speed);
}
}
So, whenever the ball spawns, it should go for the 1st target, then 2nd and finally the 3rd, but when I load the game, all the balls go to the horizon without stopping.
The problem seems to solve itself when I put the ball/fairy prefab in the scene and load the targets in the scene instead of the prefabs but that isn't the ideal solution.
How can I make it so the balls go to the targets in the scene?
Don't have enough rep to comment but this looks like it should work, what do you see if you Debug.Log(target[current].transform.position)?
If you don't need the target game object references, you could store Vector3[]'s instead.
A couple of things: You're randomizing your target every single update frame, so there is actually no progression from one target to the next in any kind of order. Is that intentional?
Also, you're moving the ball transform only when its distance is less than (<) a certain value. It seems more likely that you should be moving the ball as long as its distance is greater than (>) that value. Are you trying to go from a distance of greater than 1, to a distance of less than / equal to one?
If that's the case, maybe something like (untested):
public List<Transform> target = new List<Transform>();
public float speed;
int current = 0;
float radius_target = 1;
void Update()
{
if (Vector3.Distance(target[current].transform.position, transform.position) > radius_target)
{
transform.position = Vector3.MoveTowards(transform.position, target[current].transform.position, Time.deltaTime * speed);
} else {
current++;
if (current == target.Count)
{
current = 0;
}
}
}
But by this method the ball will seek to return to target[0] after target[2] (or whatever is the last one) and keep going forever in a loop. If you want to stop after reaching the last target you'd want to only execute this whole block if the current target < target.Count without returning to zero as it currently does.
And finally, your radius threshold of 1 could either work or not work, depending on how these targets are set up in the scene. This whole approach only works if they are all more than one unit (or radius_target units) away from each other.

How do I create an object to move between two positions in unity, using C#, and why is my code not working?

I have this piece of code right here that should make the block object move between the startPos and endPos objects, but something is wrong about it and I don't know what.
void FixedUpdate()
{
if (block.transform.position == startPos.transform.position)
{
check = false;
}
if(block.transform.position == endPos.transform.position)
{
check = true;
}
if (check == false)
{
block.transform.position = Vector3.Lerp(block.transform.position, endPos.transform.position, .03f);
}
if (check == true)
{
block.transform.position = Vector3.Lerp(block.transform.position, startPos.transform.position, .03f);
}
}
At some point the block will reach endPos, and then on its way back to startPos it will stop, because the functions will be executed simultaneously. But how is this possible because my if's right there should not allow this to happen?
In general you should always be using
Update &rightarrow; called every frame
instead of
FixedUpdate &rightarrow; called in certain realtime intervals
except you are dealing with Physics somehow (which doesn't seem to be the case here). Also see the Update and FixedUpdate Tutorial
The issue with Vector3.Lerp is that it doesn't behave as you expect.
I guess you like that it starts fast and then becomes "smooth" ... but actually this might be your problem.
It never reaches the target position really. It just gets closer and closer and slower and slower ...
... until at some moment the == with a precision of 0.00001f eventually becomes true.
So it might seem that it stopped but actually it might still be moving just really really slow.
For the following two alternatives you have to decide a bit what you want to control:
Option: The speed
If you want to have a linear velocity for the object you should rather use
// adjust via the Inspector
[SerializeField] private float moveSpeedInUnityUnitPerSecond = 1f;
// you should use Update here in general
void Update()
{
if (block.transform.position == startPos.transform.position)
{
check = false;
}
// always use else in cases where only on condition can be
// true at the same time anyway
else if(block.transform.position == endPos.transform.position)
{
check = true;
}
block.transform.position = Vector3.MoveTowards(block.transform.position, check ? startPos.transform.position : endPos.transform.position, Time.deltaTime * moveSpeed);
}
Option: The duration
If you rather want a smooth movement but control the duration it takes to reach the target you should use a Lerp but with a factor depending on the time like
// adjust via the Inspector
[SerializeField] private float moveDurationInSeconds = 1f;
private float passedTime;
// you should use Update here in general
void Update()
{
// prevent overshooting
passedTime += Mathf.Min(moveDurationInSeconds - passedTime, Time.deltaTime);
if(passedTime >= moveDurationInSeconds)
{
check = !check;
passedTime = 0;
}
var lerpFactor = passedTime / moveDurationInSeconds;
// and now add ease-in and ease-out
var smoothedLerpFactor = Mathf.SmoothStep(0, 1, lerpFactor);
var fromPosition = check ? endPos.transform.position : startPos.transform.position;
var toPosition = check ? startPos.transform.position : endPos.transform.position;
block.transform.position = Vector3.Lerp(fromPosition, toPosition, smoothedLerpFactor);
}
For this you could also use a Coroutine which usually is a bit easier to interpret and maintain:
// adjust via the Inspector
[SerializeField] private float moveDurationInSeconds = 1f;
// yes you see correctly one can directly use the Start
// as a Coroutine
private IEnumerator Start()
{
var fromPosition = startPos.transform.position;
var toPosition = endPos.transform.position;
// looks strange but as long as you yield somewhere inside
// the loop it simply means repeat the sequence forever
// just like the Update method
while(true)
{
var passedTime = 0f;
while(passedTime < moveDurationInSeconds)
{
var lerpFactor = passedTime / moveDurationInSeconds;
// and now add ease-in and ease-out
var smoothedLerpFactor = Mathf.SmoothStep(0, 1, lerpFactor);
block.transform.position = Vector3.Lerp(fromPosition, toPosition, smoothedLerpFactor);
passedTime += Mathf.Min(moveDurationInSeconds - passedTime, Time.deltaTime);
// reads like: "pause" here, render this frame and continue
// from here in the next frame
yield return null;
}
// once reached flip the positions
var temp = fromPosition;
fromPosition = toPosition;
toPosition = temp;
}
}
in both cases you could still add more flexibility and instead of simply using the moveDurationInSeconds use
var fixedDuration = moveDurationInSeconds * Vector3.Distance(fromPosition, toPosition);
this way the movement takes shorter if the positions are closer together and longer if they are further apart. This comes pretty close to the Lerp you used before regarding to the smoothness of motion but you can control very good how long the movement will take.
The third argument of Vector3.Lerp is a percentage of the distance between the first two arguments. When you pass in 0.03f, you're getting 3% closer to your end position but never actually getting exactly there (You can prove this by logging the block's position and the target's position and you'll see they're never perfectly equal).
Instead of checking if your block's position with the == operator (which works to an accuracy of 0.00001f) to the target position, you could simply check if it's close enough with Vector3.Distance:
if(Vector3.Distance(block.transform.position, endPos.transform.position) < 0.1f) {
You can make your "close enough threshold" (0.1f in the example) a named variable for easy adjustment until it feels right.
For starters, Update() loops every frame. FixedUpdate() depends on the number of frames per second set in the time setting. So, put your code into Void Update() instead.
If you refer to Vector3.MoveTowards documentation, you may find a much better approach to your problem.
The script below should do the trick. The target variable should be set to the position of your end point. Speed is how fast your object should move.
Inside Update(), the step variable is used to determine how far the object should move based off its speed and the time elapsed since it last moved. Finally, the last line changes the position of the object and records the new position.
public Transform target;
public float speed;
void Update() {
float step = speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, target.position, step);
}

How can I make GameObjects translate across the screen smoothly in Unity?

Basically, I have a game where tickets appear at the top of the screen. When a ticket is completed, it gets destroyed, and if there's a gap between the remaining tickets, the tickets slide down to fill in the gap. I actually have it working just fine, but they kind of just jump to the positions and it doesn't look very pretty.
How can I move the objects to their target position smoothly? I've tried messing with Lerp and Smoothdamp, but both of those seem to be producing strange results (tickets go all over the place. Here's the relevant code I'm working with:
public void SlideRail()
{
ticketList.RemoveAll(x => x == null);
int count = ticketList.Count;
for (int i = destroyedIndex; i < ticketList.Count; i++)
{
if (ticketList[i] != null)
{
ticketList[i].transform.Translate(Vector3.left * 35);
ticketList[i].GetComponent<Ticket>().index--;
}
}
indexer = railPosition.IndexOf(ticketList.Last().GetComponent<Ticket>().item_pos);
up = false;
}
DestroyedIndex is the index of the object that was destroyed (so that we only move the objects after it in the list)
Up is a flag that activates SlideRail(), when its set to false the method ends and the next method continues.
SlideRail is then called in the Update() method:
void Update()
{
if (up && ticketList.Count > 2)
SlideRail();
if (Time.time > nextTicket)
{
nextTicket = Time.time + timeBetweenTickets;
StartCoroutine(WaitToPrint());
CreateTicket();
UpdatePosition();
}
}
I feel like I should be able to do this using Translate, but I must be missing something.
With movement like this you should usually include Time.deltaTime (as a factor in translate) to bind the movement to the frame rate. [Edit: Translate this sentence to framerate-independant, that's what I meant.]
Now, to the actual point, it looks like you do the movement in just one frame and thus you get this jump (you always set up to false at the end of SlideRail).
A possible way of doing this is to specify a target position upon trigger (task complete, need to push over) and than keep calling ticketList[i].transform.position = Vector3.MoveTowards(ticketList[i].transform.position, targetPosition, speed * Time.deltaTime); for each object (framed by a "reached target position" check, e.g. using if(Vector3.Distance(ticketList[i].transform.position, targetPosition) > someThreshold). You can add an else to this to directly set the position when in threshold range (make the threshold small so it's not visible, e.g. 0.1f), though you usually only need this for non-linear functions like lerp which slow down at the end and can stall. [Edit: Because of floating point imprecision it's actually mostly always a good idea to use a threshold.]
Using iTween, it can be simplified into a one line call:
iTween.MoveTo(someGameObject, iTween.Hash("y", targetY,
"islocal", true,"easetype", iTween.EaseType.spring,
"time", 0.2f));
I've wrote this piece of code for myself before, maybe it helps you.
using System;
using System.Collections;
using UnityEngine;
public class MoveController : MonoBehaviour
{
public float moveTime;
public AnimationCurve moveSpeedCurve;
public AnimationCurve movePositionCurve;
public void StartMoving(Vector2 destination, Action action = null)
{
StartCoroutine(Move(destination, action));
}
IEnumerator Move(Vector3 destination, Action action = null)
{
float currentTime = 0;
float perc = 0;
Vector3 currentPos = transform.localPosition;
while (perc != 1)
{
currentTime += Time.deltaTime;
if (currentTime > moveTime)
{
currentTime = moveTime;
}
perc = moveSpeedCurve.Evaluate(currentTime / moveTime);
transform.localPosition = Vector2.LerpUnclamped(currentPos, destination, movePositionCurve.Evaluate(perc));
yield return null;
}
if (action != null)
action();
}
}

How can I get the camera to pause on 3 objects on the x axis?

I am a new student working on a class project. I have 1 script attached to the camera in my only scene. I want the camera to pause over the 1st object, scroll to the 2nd object and pause then scroll to the 3rd object and pause then end. Putting this code in the UPDATE, the camera never stops. Here in the START, it hesitates around 15 sec and then it goes right to the last object, then the function stops. Note the delay set for 10 seconds. I tried putting the code in a function and calling the function from START… but no good. What am I doing wrong? HELP ME OB1....
One more thing... Is START the best place to play sound?
using UnityEngine;
using System.Collections;
// I want the camera to pause over the 1st object, scroll to the 2nd object and pause
// then scroll to the 3rd object and pause then end. Putting this code in the UPDATE
// the camera never stops. Here in the START, it hesitates around 15 sec and then it
// goes right to the last object, then the function stops. Note the delay set for 10
// seconds.
public class CameraControl : MonoBehaviour
{
public float speed; // How fast to move the camera
public int moves; // How many moves to make
public float MyWait; // How long to pause over object
// Use this for initialization
void Start()
{
StartCoroutine(MyDelay());
for (int y = 1; y <= 2; y++) // go to the next two objects
{
for (int i = 1; i <= moves; i++) // Move the camera to the next position
{
Camera.main.transform.Translate(new Vector3(1.0f, 0.0f, 0.0f) * speed * Time.deltaTime);
Debug.LogFormat("moves = {0} ", i);
}
StartCoroutine(MyDelay());
}
}
IEnumerator MyDelay()
{
yield return new WaitForSeconds(10.0f);
}
}
Try placing this code on your camera and place all the game objects you'd like the camera to move to in the Objects list. If you'd like the camera to be a little further back so it can see the object, create a new Vector3 instead of simply giving the exact position and then give that new Vector3 the x, y and z of the iterating object and then add distance to which ever axis you'd like for the camera to be distanced from the object.
public float MyWait = 5; // How long to pause over object
public float speed = 5f; // How fast to move the camera
public List<GameObject> Objects; //List of each object for the camera to go to
void Start()
{
StartCoroutine(MoveToObject(0));
}
IEnumerator MoveToObject(int iteratingObject)
{
//Wait for however many seconds
yield return new WaitForSeconds(MyWait);
bool atDestination = false;
//Move the camera until at destination
while (!atDestination)
{
yield return new WaitForFixedUpdate();
transform.position = Vector3.MoveTowards(transform.position, Objects[iteratingObject].transform.position, Time.deltaTime * speed);
if (transform.position == Objects[iteratingObject].transform.position)
atDestination = true;
}
//Continue iterating until moved over all objects in list
if(iteratingObject != Objects.Count - 1)
StartCoroutine(MoveToObject(iteratingObject + 1));
}
I think you're going to need to put some code in the Update function for this to work smoothly.
Time.deltaTime will only really make sense in an Update function, using it here and trying to do everything in the Start function won't work. Also setting the Translate transform will instantly set the position to the given value. Look up linear interpolation (lerp).
I would suggest you have a member that you use to track the current state, i.e. which object you're looking at, but an Enum of states might be easier to read.
Then you can keep a member for how long you've been in that state, which you can increase in the Update.
Then within the Update you can check whether it is time to change state or update your moving camera.
Good luck!

Xna adding gravity to a 2d sprite

I am trying to simulate gravity in my first xna 2d game. I have the following
//Used for Jumping
double elapsedAirTime = 0.0;
double maxAirTime = 8.35;
//End Jumping
So I am trying to move the sprite up by a certain amount while the elapsedAirTime < maxAirTime
However, there is some issue where my code only seems to move the sprite up once and not multiple times during this segment of time. here is the code in my "player.cs" class, or the update method of the class.
if (newState.IsKeyDown(Keys.Space))
{
if(oldState.IsKeyUp(Keys.Space))
{
//if we are standing or jumping then change the velocity
if (playerState == PlayerStates.Standing)
{
playerState = PlayerStates.Jumping;
this.position.Y -= (float)(30.0 + ((1.2)*elapsedAirTime)*elapsedAirTime);
}
}
}
//if we are jumping give it some time
if (playerState == PlayerStates.Jumping)
{
if ((elapsedAirTime < maxAirTime) && position.Y < 3)
{
this.position.Y -= (float)(30.0 + ((1.2) * elapsedAirTime)*elapsedAirTime);
elapsedAirTime += gameTime.ElapsedGameTime.TotalSeconds;
}
//otherwise time to fall
else
{
playerState = PlayerStates.Falling;
}
}
//add gravity to falling objects
if (playerState == PlayerStates.Falling || playerState == PlayerStates.Standing)
{
//if we are above the ground
if (this.position.Y < windowBot - 110)
{
//chnage state to falling
playerState = PlayerStates.Falling;
this.position.Y += 3.0f + ((float)(gameTime.ElapsedGameTime.TotalSeconds));
}
else
{
playerState = PlayerStates.Standing;
elapsedAirTime = 0.0f;
}
}
Any help is much appreciated, please and thank you!
To give your sprite the feel of gravity, you should add velocity and acceleration to your Sprite class. Then, create an Update method for the Sprite, and have acceleration be added to your velocity every update, and velocity added to position every update. Position should not be based on the amount of elapsed air time. You can set the acceleration to a constant gravitational value, and then add to the velocity of the Sprite whenever you jump. This will create a flowing parabolic jump that looks nice. If you want to include timing, you can pass the GameTime into the Sprite's Update method, and use it as a modifier on the velocity. Here is an example Update method:
void Update(GameTime gt)
{
int updateTime = gt.ElapsedGameTime.TotalMilliseconds - oldgt.ElapsedGameTime.TotalMilliseconds;
float timeScalar = updateTime / AVG_FRAME_TIME;
this.velocity += this.acceleration * timeScalar;
this.position += this.velocity;
oldgt = gt;
}
If you use timing, this method is a little complicated. You have to keep track of how much time the update took, then divide it by the average amount of time an update or frame should take to get the amount you should adjust your velocity by. Without timing, the method is very simple:
void Update()
{
this.velocity += this.acceleration;
this.position += this.velocity;
}
I would suggest using the simpler method until you understand exactly how timing works and why you need to implement it.
It looks like this line is at fault:
this.position.Y -= (float)(30.0 + ((1.2) * elapsedAirTime)*elapsedAirTime);
I think you will find that this updates the sprites position quicker than you imagine, the sprite will move 330 pixels up the screen in 10 updates (assuming Game.IsFixedTimeStep == true) that is 1 tenth of a second realtime
It is likely that this is just updating so quickly that you don't get a change to see it rise before the && position.Y < 3 condition kicks in and changes the playerState.
It looks like you are trying to say - jump at a rate of x pixels per second for upto 8.5 seconds so long as space is held.
What you need for that is to change the calculation to this.position.y -= (float) (30 * gameTime.ElapsedGameTime.TotalSeconds), this will give a very liner movement to the jump action but it will mean that the sprite jumps at exactly 30 pixels per second.
If Game.IsFixedTimeStep == true - which is the default - the update gets called 60 times per second so gameTime.ElapsedGameTime.TotalSeconds is going to be about 0.1 every update. If something happens to cause an update to skip (rendering issues for example) then update will get delayed and gameTime.ElapsedGameTime.TotalSeconds may be 0.3 (the 2 updates skipped) but the formular still works out the correct jump rate.

Categories

Resources