So my problem is that I want my objects to randomly move within a certain round range, with a certain speed.
Right now it moves very fast and multiplying it by speed doesn't work.
Here's my code:
using UnityEngine;
using System.Collections;
public class RandomTargetMovement : MonoBehaviour {
public float radius = 20.0f;
void Update(){
transform.position = Random.insideUnitCircle * radius;
}
}
That's because you're making a new random number in every update. This is bad for several reasons.
But in this particular instance, not only it is bad, it simply doesn't work. That's because update is called every time a frame is rendered and that means you will always have jerky motion, no matter how you set your speed. For that, you should use deltaTime.
I assume what you want is for the object to move to a point, then start moving towards a new random point. Here is a not-so-elegant solution:
using UnityEngine;
using System.Collections;
public class TestSample : MonoBehaviour {
public float radius = 40.0f;
public float speed = 5.0f;
// The point we are going around in circles
private Vector2 basestartpoint;
// Destination of our current move
private Vector2 destination;
// Start of our current move
private Vector2 start;
// Current move's progress
private float progress = 0.0f;
// Use this for initialization
void Start () {
start = transform.localPosition;
basestartpoint = transform.localPosition;
progress = 0.0f;
PickNewRandomDestination();
}
// Update is called once per frame
void Update () {
bool reached = false;
// Update our progress to our destination
progress += speed * Time.deltaTime;
// Check for the case when we overshoot or reach our destination
if (progress >= 1.0f)
{
progress = 1.0f;
reached = true;
}
// Update out position based on our start postion, destination and progress.
transform.localPosition = (destination * progress) + start * (1 - progress);
// If we have reached the destination, set it as the new start and pick a new random point. Reset the progress
if (reached)
{
start = destination;
PickNewRandomDestination();
progress = 0.0f;
}
}
void PickNewRandomDestination()
{
// We add basestartpoint to the mix so that is doesn't go around a circle in the middle of the scene.
destination = Random.insideUnitCircle * radius + basestartpoint;
}
}
Hope this helps.
Related
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RotateAroundTarget : MonoBehaviour
{
public Transform target;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
transform.RotateAround(target.transform.position, new Vector3(0,1,0), 20 * Time.deltaTime);
transform.position = new Vector3(0,Random.Range(0,5f),0);
}
}
the RotateAround was working fine but once i added the Random.Range part the object is no just jumping very fast.
i guess i need somehow to add a delay between the random pickups ? but not sure how to make it smooth so the object will rotate around the target and from time to time randomly will change the height smooth up and down.
and how to add an option to change the rotation around the radius around the target?
tried the code above but it's still far away from what I wanted.
I tried using InvokeRepeating but then the transform is not rotating around anymore.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RotateAroundTarget : MonoBehaviour
{
public Transform target;
// Start is called before the first frame update
void Start()
{
InvokeRepeating("ChangePosition", 0, 2);
}
// Update is called once per frame
void Update()
{
transform.RotateAround(target.transform.position, new Vector3(0,1,0), 20 * Time.deltaTime);
}
private void ChangePosition()
{
transform.position = new Vector3(0, Random.Range(0, 5f), 0);
}
}
There are a couple issues with your code. First, setting the position with:
transform.position = new Vector3(0,Random.Range(0,5f),0);
will completely overwrite any X/Z change caused by the prior RotateAround. That's why your rotation stopped as soon as you introduced this code. To set the position without losing the X/Z values, you could use:
transform.position = new Vector3(
transform.position.x,
Random.Range(0,5f),
transform.position.z);
So now, the rotation works again. But the height changes are still erratic because your code is setting a new random height every frame. What can we do instead?
Let's try constraining the maximum height change to a certain speed, and only set a new height target when the previous one has been reached. So your code might look like:
public class RotateAroundTarget : MonoBehaviour
{
public Transform target;
public float verticalSpeed = 20;
public float radius = 10;
private float targetHeight;
// Start is called before the first frame update
void Start()
{
// Initialize with a height to start moving toward
targetHeight = GetNewTargetHeight();
// If we start at a given distance away from the target, then this
// will become our radius when we RotateAround it
transform.position = new Vector3(0, 0, radius);
}
// Update is called once per frame
void Update()
{
transform.RotateAround(target.transform.position,
new Vector3(0,1,0), 20 * Time.deltaTime);
// Just like your rotation rate, we scale this based on game time
float maxHeightChange = verticalSpeed * Time.deltaTime;
float targetHeightDelta = targetHeight - transform.position.y;
if (maxHeightChange >= Mathf.Abs(targetHeightDelta)){
// If the current height is close enough to the target, we can move it
// there and then generate a new height to go towards
transform.position = new Vector3(
transform.position.x,
targetHeight,
transform.position.z);
targetHeight = GetNewTargetHeight();
}
else{
// Otherwise, limit the magnitude of the height change by the speed and
// apply it to the current height
float allowedHeightChange = Mathf.Clamp(
targetHeightDelta,
-maxHeightChange,
maxHeightChange);
transform.position = new Vector3(
transform.position.x,
transform.position.y + allowedHeightChange,
transform.position.z);
}
}
private float GetNewTargetHeight()
{
return Random.Range(0,5f);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MoveNavi : MonoBehaviour
{
public Transform destinationTransform
public float speed;
public float distanceToStop;
public float lerpTime;
public static bool naviChildOfHand = false;
private GameObject rig_f_middle;
private bool changeNaviChild = false;
// Start is called before the first frame update
void Start()
{
rig_f_middle = GameObject.Find("rig_f_middle.02.R");
}
// Update is called once per frame
void Update()
{
if (IKControl.startMovingNAVI == true)
{
var v = rig_f_middle.transform.position - transform.position;
if (v.magnitude < 0.001f)
{
//this.transform.parent = rig_f_middle.transform;
//this.enabled = false;
naviChildOfHand = true;
changeNaviChild = true;
return;
}
Vector3 moveDir = v.normalized;
transform.position += moveDir * speed * Time.deltaTime;
}
if(changeNaviChild == true)
{
this.transform.position = Mathf.Lerp(this.transform.position,
destinationTransform.position, lerpTime * Time.deltaTime);
}
}
}
Instead just changing the object child to another parent with this line :
this.transform.parent = rig_f_middle.transform;
I have duplicated the object in two places this transform when the game start it's child of a parent and destinationTransform is another copy of the transform object and I want to use the Mathf Lerp to smooth switch from the transform to the destinationTransform.
Here is a screenshot :
I marked with red circle the destination name NAVI Destination it's the same object as the NAVI Original.
And I want to use Lerp to smooth change between the original to the destination so the slowly the original will become disable and the destination will become enabled somehow.
The main goal is to make a smooth transition changing the NAVI as child from one parent to another parent.
What you'll want to do is have references to the origin transform, and the destination transform, then Lerp between those two positions. Once you've reached the destination, then parent this transform to the destination transform.
You can use the Vector3.Lerp to lerp between two vectors.
Like this (written but untested):
public class MoveNavi : MonoBehaviour
{
public enum TransitionState
{
None,
MovingTowards,
Transferring
}
public Transform destinationTransform;
public float speed;
public float distanceToStop;
public float lerpTime;
public static bool naviChildOfHand = false;
private GameObject rig_f_middle;
private bool changeNaviChild = false;
private Transform originTransform;
private float timer;
private TransitionState state = TransitionState.MovingTowards;
void Start ( )
{
rig_f_middle = GameObject.Find ( "rig_f_middle.02.R" );
}
void Update ( )
{
switch ( state )
{
case TransitionState.MovingTowards:
var v = rig_f_middle.transform.position - transform.position;
if ( v.magnitude < 0.001f )
{
state = TransitionState.Transferring;
originTransform = rig_f_middle.transform;
timer = 0;
return;
}
Vector3 moveDir = v.normalized;
transform.position += moveDir * speed * Time.deltaTime;
break;
case TransitionState.Transferring:
timer += Time.deltaTime;
this.transform.position = Vector3.Lerp ( originTransform.position, destinationTransform.position, timer );
if ( timer >= 1.0f )
{
this.transform.parent = destinationTransform;
state = TransitionState.None;
this.enabled = false;
return;
}
break;
default:
this.enabled = false;
return;
}
}
}
That should get you most, if not all, the way towards a solution.
You probably wouldn't use Mathf.Lerp which is for interpolating float values but rather Vector3.Lerp which interpolates two Vector3 values.
I wouldn't use Update for this and poll check some state but rather a Coroutine which is way easier to maintain and control. It is also more efficient than having a most of the time "useless" Update running that is only used in one single case.
When using Lerp there are mainly two use cases
Stabelizing e.g. a user Input in order to avoid jittering. That is the use case you used. Here you would every frame interpolate the current position against the next input destination position, getting slower while getting closer to the target
Move smooth from one position to another. This is the use case you actually want. Here you wouldn't use a "fixed" factor but rather one growing from 0 to 1 and use a fixed startPosition and endPosition.
So personally I would rather use something general like
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ParentTransition : MonoBehaviour
{
// Avergae move speed in Unity units per second
[SerializeField] float MoveSpeed = 1f;
// flag for preventing concurrent routines
private bool isMoving;
private IEnumerator MoveToNewParentRoutine(Transform newParent, Action whenDone)
{
// prevent multiple concurrent routines
if(isMoving) yield break;
isMoving = true;
// Get current parent and position
var currentParent = transform.parent;
var currentPosition = transform.position;
// Get target position
var targetPosition = newParent.position;
// Get the current distance to the target in order to calculate
// the duration of the animation
var distance = Vector3.Distance(currentPosition, targetPosition);
// get the duration of the transition depending on the distance and move speed
var targetDuration = distance / speed;
var timePassed = 0f;
while(timePassed < targetDuration)
{
// This grows linear from 0 to 1
var factor = timePassed / targetDuration;
// optionally add some ease-in and ease-out
factor = Mathf.SmoothStep(0, 1, factor);
this.transform.position = Vector3.Lerp(currentPosition, targetPosition, factor);
// increase by the time since last frame
timePassed += Time.deltaTime;
// "Pause" the routine here, render this frame and
// continue from here in the next frame
yield return null;
}
// to be sure set the target hard
transform.position = targetPosition;
// Now transit to the new parent
transform.parent = newParent;
naviChildOfHand = true;
// Give this routine free for a next transition (if needed)
isMoving = false;
// Action to execute when the routine is finished
whenDone?.Invoke();
}
public void MoveToNewParent()
{
if (!isMoving)
{
StartCoroutine(MoveToNewParentRoutine(rig_f_middle));
}
}
}
Personally I would remove the Update completely and rather add a
And then rather call this method from somewhere else whenever needed like e.g.
parentTransitionReference.MoveToNewParent(someTransform);
Using this you could use the same component for any transition between parent objects in general.
Then in your specific use case you would probably attach it to your navi and use it like e.g.
public class MoveNavi : MonoBehaviour
{
[SeriaizeField] ParentTransition parentTransition;
public Transform destinationTransform
public static bool naviChildOfHand = false;
[SerializeField] private GameObject rig_f_middle;
bool isAlreadyMoving;
// Start is called before the first frame update
void Start()
{
if(!rig_f_middle) rig_f_middle = GameObject.Find("rig_f_middle.02.R");
if(!parentTransition)
{
if(!TryGetComponent<ParentTransition>(out parentTransition)
{
parentTransition = gameObject.AddComponent<ParentTransition>();
}
}
}
// Update is called once per frame
void Update()
{
if (!isAlreadyMoving && IKControl.startMovingNAVI)
{
isAlreadyMoving = true;
parentTransition.MoveToNewParent(rig_f_middle, () =>
{
parentTransition.MoveToNewParent(destinationTransform, () =>
{
naviChildOfHand = true;
});
});
}
}
}
When the game starts, a random waypoint is selected from an array. The camera should then rotate to face the selected random waypoint and start moving towards it.
Once the camera has reached the waypoint, it should wait 3 seconds before rotating to face and move towards the next random waypoint.
The problem I have is in Start(). The camera does not rotate to face the first waypoint before it starts moving towards it. Instead, it moves towards the first waypoint backwards. Then, when it reaches the waypoint, it waits 3 seconds to rotate and move towards the next waypoint.
It's working fine except that the camera does not rotate to face the first selected random waypoint. It's moving to it backward without first rotating to face it.
Here's my code:
The waypoints script
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Waypoints : MonoBehaviour
{
public GameObject[] waypoints;
public GameObject player;
public float speed = 5;
public float WPradius = 1;
public LookAtCamera lookAtCam;
private int current = 0;
private bool rot = false;
public void Init()
{
waypoints = GameObject.FindGameObjectsWithTag("Target");
if(waypoints.Length > 0)
{
StartCoroutine(RotateFacingTarget(waypoints[UnityEngine.Random.Range(0, waypoints.Length)].transform));
}
}
void Update()
{
if (waypoints.Length > 0)
{
if (Vector3.Distance(waypoints[current].transform.position, transform.position) < WPradius)
{
current = UnityEngine.Random.Range(0, waypoints.Length);
rot = false;
StartCoroutine(RotateFacingTarget(waypoints[current].transform));
if (current >= waypoints.Length)
{
current = 0;
}
}
if (rot)
transform.position = Vector3.MoveTowards(transform.position, waypoints[current].transform.position, Time.deltaTime * speed);
}
}
IEnumerator RotateFacingTarget(Transform target)
{
yield return new WaitForSeconds(3);
lookAtCam.target = target;
rot = true;
}
}
The look at camera script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LookAtCamera : MonoBehaviour
{
//values that will be set in the Inspector
public Transform target;
public float RotationSpeed;
//values for internal use
private Quaternion _lookRotation;
private Vector3 _direction;
// Update is called once per frame
void Update()
{
//find the vector pointing from our position to the target
if (target)
{
_direction = (target.position - transform.position).normalized;
//create the rotation we need to be in to look at the target
_lookRotation = Quaternion.LookRotation(_direction);
//rotate us over time according to speed until we are in the required rotation
transform.rotation = Quaternion.Slerp(transform.rotation, _lookRotation, Time.deltaTime * RotationSpeed);
}
}
}
How can I fix this?
Let's assume that Waypoints.Init() is being called and your waypoints variable has an array of 3.
Waypoints.Init() starts a coroutine
Your coroutine waits 3 seconds
After 3 seconds, you set your camera target which Slerps to face the position
Update on its first frame says waypoints.Length > 0 == true
It's not close to its target, and rot is false, so it does not move
Now, you're waiting 3 seconds, not rotating, and not moving.
Your coroutine's 3 second wait time is up and starts the rotation toward your target
rot is now true at the start of your rotation, so your Update method starts moving toward the target as well
It would seem that your logic is off in how the order of operations works. If it needs to act as you describe, I suggest that you operate on the target differently.
I've implemented the following using an enum:
Waypoints
public class Waypoints : MonoBehaviour
{
private GameObject[] waypoints;
private Transform currentWaypoint;
private enum CameraState
{
StartRotating,
Rotating,
Moving,
Waiting
}
private CameraState cameraState;
public GameObject player;
public float speed = 5;
public float WPradius = 1;
public LookAtCamera lookAtCam;
private int current = 0;
private bool isCameraRotating = false;
void Start()
{
cameraState = CameraState.StartRotating;
}
void Update()
{
switch (cameraState)
{
// This state is used as a trigger to set the camera target and start rotation
case CameraState.StartRotating:
{
// Sanity check in case the waypoint array was set to length == 0 between states
if (waypoints.Length == 0)
break;
// Tell the camera to start rotating
currentWaypoint = waypoints[UnityEngine.Random.Range(0, waypoints.Length)].transform;
lookAtCam.target = currentWaypoint;
cameraState = CameraState.Rotating;
break;
}
// This state only needs to detect when the camera has completed rotation to start movement
case CameraState.Rotating:
{
if (lookAtCam.IsFinishedRotating)
cameraState = CameraState.StartMoving;
break;
}
case CameraState.Moving:
{
// Move
transform.position = Vector3.MoveTowards(transform.position, currentWaypoint.position, Time.deltaTime * speed);
// Check for the Waiting state
if (Vector3.Distance(currentWaypoint.position, transform.position) < WPradius)
{
// Set to waiting state
cameraState = CameraState.Waiting;
// Call the coroutine to wait once and not in CameraState.Waiting
// Coroutine will set the next state
StartCoroutine(WaitForTimer(3));
}
break;
}
case CameraState.Waiting:
// Do nothing. Timer has already started
break;
}
}
IEnumerator WaitForTimer(float timer)
{
yield return new WaitForSeconds(timer);
cameraState = CameraState.StartRotating;
}
public void RefreshWaypoints()
{
waypoints = GameObject.FindGameObjectsWithTag("Target");
}
}
LookAtCamera
public class LookAtCamera : MonoBehaviour
{
// Values that will be set in the Inspector
public Transform target;
public float RotationSpeed;
private float timer = 0.0f;
public bool IsRotationFinished
{
get { return timer > 0.99f; }
}
// Update is called once per frame
void Update()
{
if (target != null && timer < 0.99f)
{
// Rotate us over time according to speed until we are in the required rotation
transform.rotation = Quaternion.Slerp(transform.rotation,
Quaternion.LookRotation((target.position - transform.position).normalized),
timer);
timer += Time.deltaTime * RotationSpeed;
}
}
}
I'm wondering how to smoothly zoom in and smoothly zoom out on button press in Unity3d using c#. I've got zooming part down already, but not sure how to make a transition of zooming in and out smooth. As an example, I'd like it to zoom in as smooth as it is in ARMA or DayZ game.
Here's my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class zoomIN : MonoBehaviour {
public Camera cam;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if (Input.GetMouseButton (1)) {
cam.fieldOfView = 20;
}
if (Input.GetMouseButtonUp (1)) {
cam.fieldOfView = 60;
}
}
}
I'd appreciate any help!
Thanks and Merry Xmas!
Use coroutine to do this. You can use it to enable the speed or duration of the zooming. Perform a Mathf.Lerp between cam.fieldOfView and the destination( 20 or 60) depending on if the key is pressed or released.
Note: You must change Input.GetMouseButton to Input.GetMouseButtonDown otherwise your first if statement will be running every frame while the right mouse button is held down. I think you want to be true once only.
public Camera cam;
Coroutine zoomCoroutine;
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(1))
{
//Stop old coroutine
if (zoomCoroutine != null)
StopCoroutine(zoomCoroutine);
//Start new coroutine and zoom within 1 second
zoomCoroutine = StartCoroutine(lerpFieldOfView(cam, 20, 1f));
}
if (Input.GetMouseButtonUp(1))
{
//Stop old coroutine
if (zoomCoroutine != null)
StopCoroutine(zoomCoroutine);
//Start new coroutine and zoom within 1 second
zoomCoroutine = StartCoroutine(lerpFieldOfView(cam, 60, 1f));
}
}
IEnumerator lerpFieldOfView(Camera targetCamera, float toFOV, float duration)
{
float counter = 0;
float fromFOV = targetCamera.fieldOfView;
while (counter < duration)
{
counter += Time.deltaTime;
float fOVTime = counter / duration;
Debug.Log(fOVTime);
//Change FOV
targetCamera.fieldOfView = Mathf.Lerp(fromFOV, toFOV, fOVTime);
//Wait for a frame
yield return null;
}
}
A simple way to get your smooth zoom animation is by performing the zoom operation over multiple frames. So instead of changing the fieldOfView from 20 to 60 right away, increase the fieldOfView with 5 every frame until you reach your target of 60. (To lengthen the animation you can of course take a smaller number than 5.) So based on the mouse input you can keep a state _zoomedIn and based on that state and on the current fieldOfView you can determine whether you still need to add or substract to the value. Which gives you something like the following code: (not tested)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class zoomIN : MonoBehaviour {
public Camera cam;
private bool _zoomedIn = false;
private int _zoomedInTarget = 60;
private int _zoomedOutTarget = 20;
// Update is called once per frame
void Update () {
if (Input.GetMouseButtonDown (1))
_zoomedIn = true;
if (Input.GetMouseButtonUp (1)) {
_zoomedIn = false;
}
if (_zoomedIn) {
if (cam.fieldOfView < _zoomedInTarget)
cam.fieldOfView += 5;
} else {
if (cam.fieldOfView > _zoomedOutTarget)
cam.fieldOfView -= 5;
}
}
Recently I'm making a chess game which has animation when the chess move. I used two method, RotateTowards and Translate(). RotateTowards() only run in Update() function. Here is my code:
using UnityEngine;
using System.Collections;
public class OnTouch : MonoBehaviour {
public GameObject cube;
public GameObject sphere;
int clicked;
Quaternion lastRot;
// Use this for initialization
void Start () {
clicked = 0;
}
// Update is called once per frame
void Update () {
if(clicked == 1)
{
var rotation = Quaternion.LookRotation(cube.transform.position - transform.position);
print(rotation);
rotation.x = 0;
rotation.z = 0;
cube.transform.rotation = Quaternion.RotateTowards(cube.transform.rotation, rotation, 100 * Time.deltaTime);
if(cube.transform.rotation = ???? <- No Idea){
clicked = 0;
}
}
}
void OnMouseDown()
{
print("clicked");
clicked = 1;
}
}
I attach that code to all chess tile. So, after the cube stop rotating, I tried to click another tile. But, the previous RotateTowards() method keep running, so it ran both. I've try to make IF logic, but I have no idea for
if(cube.transform.rotation == ??? <-- No idea){
clicked = 0;
}
Any solution? Thank you!
it will never reaches your final rotation, it will get very close though so you can check if the angle between your destination rotation and current rotation is smaller than a small degree then you can say it reached.
float DestAngle = 1.0f; //here we take 1 degree as the near enough angle
float angle = Quaternion.Angle(transform.rotation, target.rotation);//we calculate the angle between current rotaion and destination
if (Mathf.Abs (angle) <= DestAngle ) { //we reached }
if (cube.transform.rotation == rotation)