I have just started working on another project where one of the game mechanics is the ability to relocate a drone to the position of your mouse cursor.
Here is my code below where I have several problems(will specify)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class lightDrone : MonoBehaviour
{
Vector3 newPosition;
void Start()
{
newPosition = transform.position + (0,0,10); //***Problem A***
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit))
{
newPosition = hit.point;
transform.position = newPosition; // ***Problem B***
}
}
}
}
Problem A - I am trying to have the new position = wherever the cursorhas clicked + 10 on the y axis so the "Drone" is seemed to be flying and not in the ground. This isn't achieving anything and is just giving me compiling errors.
I want the exact position to be (cursor.x, (public int (y)), cursor.z) but I have a very vague idea to do it.
Problem B -
Currently when I click my mouse, the object moves to the cursor but it seems to be just teleporting instantly. I want it to move at a certain speed and I think I need a public float to do so and change transform.position to translate.position but this again doesn't work.
Thank you in advance for answering my questions, I am trying to learn these new mechanics and how to code them. :)
Problem A
If you want your new position to be where ever the cursor clicked + 10 on the y axis it makes no sense to put any of your code in Start(). That's for initialization only. It runs just once at the beginning of the scene. Just add the 10 to your newPosition in the Update() method.
Problem B
Setting transform.position to a certain value will make your transform instantly move to that position. If you want to do it over time you need to move your transform a few increments at a time. Since this introduces that "over time" element, you need to use a method that takes place over time, so you need coroutines, asyncs or you can use just the update method if you're a tiny bit crafty.
Also you need the ray to intersect and hit something. Like a flat plane, the ground, terrain, anything with a collider. If it doesn't hit anywhere then you won't have a new place to move to.
Code
using UnityEngine;
public class LightDrone : MonoBehaviour
{
public float speed = 60.0f;
// Our destination needs to be remembered outside a single iteration of
// Update. So we put it outside of the method in order to remember it
// across multiple frames.
private Vector3 currentDestination;
// We need to check if we're at the destination yet so we know when to stop.
private bool notAtDestinationYet;
// When we're closer to the destination than this tolerance, we decide that
// we have arrived there.
private float tolerance = 0.1f;
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit))
{
var newPosition = hit.point;
// You can add whatever height you want here. I added
// just 2 instead of 10.
currentDestination = newPosition + new Vector3(0, 2.0f, 0);
notAtDestinationYet = true;
}
}
if (notAtDestinationYet)
{
// Use a bit of vector math to get the direction from our current
// position to the destination. The direction is a normalized Vector
// that we can just multiply with our speed to go in that direction
// at that specific speed!
var heading = currentDestination - transform.position;
var distance = heading.magnitude;
var direction = heading / distance;
// Check if we've arrived at our destination.
if (distance < tolerance)
{
notAtDestinationYet = false;
}
else
{
// If the remaining distance between us and the destination is
// smaller than our speed, then we'll go further than necessary!
// This is called overshoot. So we need to make our speed
// smaller than the remaining distance.
// We also multiply by deltaTime to account for the variable
// amount of time that has passed since the last Update() call.
// Without multiplying with the amount of time that has passed
// our object's speed will increase or decrease when the
// framerate changes! We really don't want that.
float currentSpeed = Mathf.Clamp(speed * Time.deltaTime,
Mathf.Epsilon, distance);
transform.position += direction * currentSpeed;
}
}
}
}
Related
I've been told that Rigidbody.MoveRotation is the best way in Unity 3D to rotate the player between fixed positions while still detecting hits. However, while I can move smoothly from fixed position to position with:
if (Vector3.Distance(player.position, targetPos) > 0.0455f) //FIXES JITTER
{
var direction = targetPos - rb.transform.position;
rb.MovePosition(transform.position + direction.normalized * playerSpeed * Time.fixedDeltaTime);
}
I can't find out how to rotate smoothly between fixed positions. I can rotate to the angle I want instantly using Rigidbody.MoveRotation(Vector3 target);, but I can't seem to find a way to do the above as a rotation.
Note: Vector3.Distance is the only thing stopping jitter. Has anyone got any ideas?
First of all MoveRotation doesn't take a Vector3 but rather a Quaternion.
Then in general your jitter might come from overshooting - you might be moving further than the distance between your player and target actually is.
You can avoid that bit by using Vector3.MoveTowards which prevents any overshooting of the target position like e.g.
Rigidbody rb;
float playerSpeed;
Vector3 targetPos;
// in general ONLY g through the Rigidbody as soon as dealing wit Physics
// do NOT go through transform at all
var currentPosition = rb.position;
// This moves with linear speed towards the target WITHOUT overshooting
// Note: It is recommended to always use "Time.deltaTime". It is correct also during "FixedUpdate"
var newPosition = Vector3.MoveTowards(currentPosition, targetPos, playerSpeed * Time.deltaTime);
rb.MovePosition(newPosition);
// [optionally]
// Note: Vector3 == Vector3 uses approximation with a precision of 1e-5
if(rb.position == targetPos)
{
Debug.Log("Arrived at target!");
}
Then you can simply apply this same concept also to rotation by going through the equivalent Quaternion.RotateTowards basically just the same approach
Rigidbody rb;
float anglePerSecond;
Quaternion targetRotation;
var currentRotation = rb.rotation;
var newRotation = Quaternion.RotateTowards(currentRotation, targetRotation, anglePerSecond * Time.deltaTime);
rb.MoveRotation(newRotation);
// [optionally]
// tests whether dot product is close to 1
if(rb.rotation == targetRotation)
{
Debug.Log("Arrived at rotation!");
}
You can go one step further and use a tweeting library to tween between rotations.
DOTween
With that you can call it like this:
rigidbody.DoRotate(target, 1f) to rotate to target in 1 second.
Or even add callbacks.
rigidbody.DoRotate(target, 1f).OnComplete(//any method or lambda you want)
If at some point you want to cancel the tween yuou can save it on a variable and then call tween.Kill();
So, you want to animate the rotation value over time until it reaches a certain value.
Inside the Update method, you can use the Lerp method to keep rotating the object to a point, but you will never really reach this point if you use Lerp. It will keep rotating forever (always closer to the point).
You can use the following:
private bool rotating = true;
public void Update()
{
if (rotating)
{
Vector3 to = new Vector3(20, 20, 20);
if (Vector3.Distance(transform.eulerAngles, to) > 0.01f)
{
transform.eulerAngles = Vector3.Lerp(transform.rotation.eulerAngles, to, Time.deltaTime);
}
else
{
transform.eulerAngles = to;
rotating = false;
}
}
}
So, if the distance between the current object angle and the desired angle is greater than 0.01f, it jumps right to the desired position and stop executing the Lerp method.
I am moving a rigidbody using rb.AddForce(force,ForceMode.Impulse) where force is the target position the rigidbody have to reach.
Now the speed it goes directly depends on the distance it has to cover.
Let's say the time taken to reach the target position is 3sec. I need the rigidbody to cover the same target pos in 5sec.
I dont want to change the timescale as it affects my gameflow
On Changing the velocity of rigidbody it fails to reach the target position
Some basic physics/math:
velocity = change-in-position / travel-time
force = mass * change-in-velocity / acceleration-time
For ease, we're going to call change-in-position as distance, and change-in-velocity/acceleration-time as acceleration
Now, since the acceleration-time component is effectively zero because you're using Impulse, we're going to remove it from the equation (in math terms, we set it at '1')
force = mass * change-in-velocity
Assuming your object starts at zero velocity, we can simplify change-in-velocity to just velocity
force = mass * velocity
force = mass * distance / travel-time
To bring that back into Unity code:
var mass = rb.mass;
var distance = destination.position - transform.position;
var travelTime = 5f; // seconds
var force = mass * distance / travelTime;
rb.AddForce(force, ForceMode.Impulse);
Note that this assumes a frictionless transfer and constant velocity.
If you ignore gravity, this code solves the problem, here I changed the drag according to weight and distance, it may be a little bit away from the destination at the end, the reason should be higher drag friction.
public void ForceToTarget(Transform target, float time = 1f)
{
var rb = GetComponent<Rigidbody>();
var vector = target.position - transform.position;
var distance = vector.magnitude;
rb.drag = distance/time;
rb.AddForce(vector*rb.mass*distance/time, ForceMode.Impulse);
}
If you want precise control over your speed, then stop using ForceMode.Impulse because other physics effects like drag will make your answers wrong. Instead, just set the speed yourself. You can do this with a Coroutine to control timing and ForceMode.VelocityChange to control the speed. Basically, just look at where you are, where the target is, how much time is left, and apply the speed directly.
private bool canMove = true;
public void MoveTo(Vector3 targetPosition, float targetTime)
{
if(canMove)
{
StartCoroutine(MoveToCoroutine(targetPosition,targetTime));
}
}
private IEnumerator MoveToCoroutine(Vector3 targetPosition, float time)
{
canMove = false;
while(time > 0)
{
var positionDelta = transform.position - targetPosition;
var targetSpeed = positionDelta / time;
var speedDelta = targetSpeed - rb.velocity;
rb.AddForce(speedDelta , ForceMode.VelocityChange);
yield return null;
time -= Time.deltaTime;
}
// Bring the object to a stop before fully releasing the coroutine
rb.AddForce(-rb.velocity, ForceMode.VelocityChange);
canMove = true;
}
I wrote this here into the text editor, no IDE and haven't tested it, but I'm pretty sure this'll do what you want.
Assuming you're using the target position as-is then larger vectors will cause larger force to be applied than smaller vectors. Similarly, if using a direction vector as-is then as the rb gets closer to the target the magnitute of the vector gets smaller and thus less force is applied.
To get a constant speed use the direction to the target and Normalise it instead. Regardless of the distance the direction vector will always have a magnitude of 1 so you can multiply it by any value to accurately control the speed of the object:
Rigidbody rb;
public Transform target;
public float dist;
public float speed = 2f; // or whatever
public float targetDistance = 40f; // or whatever
private void Start()
{
rb = GetComponent<Rigidbody>();
StartCoroutine("IMove");
}
IEnumerator IMove()
{
dist = Vector3.Distance(transform.position, target.position);
while (dist > targetDistance)
{
dist = Vector3.Distance(transform.position, target.position);
rb.AddForce(Vector3.Normalize(target.position - transform.position) * speed, ForceMode.Impulse);
yield return new WaitForFixedUpdate();
}
}
Without getting too much into the physics and maths, if you want it to travel slower but the same distance you need to reduce the gravity on it and the initial force.
Note in this example I am assuming the weight is 1 to make the calculation a bit easier for force.
public class TrevelSpeedAdjusted
{
public float speedFactor = 1;
void FixedUpdate()
{
// Reduce the gravity on the object
rb.AddForce(-Physics.gravity * rigidbody.mass * (1 - speedFactor));
}
public float AddAdjustedForce(Vector3 force, ForceMode forceMode)
{
rb.AddForce(force * speedFactor, forceMode);
}
}
So you can try DoTween package to do this pretty easily and its very convenient to use a package instead of using Unity's inbuilt system.
With doTween use this:
DOMove(Vector3 to, float duration, bool snapping) condition to tween your physics Gameobject to a given target position in the duration you require.
Here's documentation you can refer to if you want: http://dotween.demigiant.com/documentation.php
Let me give you an example:
Install the doTween Package. http://dotween.demigiant.com/download
Import it to unity.
Go to your script where you want to achieve the functionality you mentioned on your question and add this header "using DG.Tweening".
Now get access of your RigidBody.
For Example Lets say: I have a cube gameobject with rigidbidy and this script attached.
The Cube Initial Position is at 0,0,0.
And I want it to move to 5,5,5 in 3 seconds or 5 seconds as per your questions request. And lets say I want this to happen when I click SpaceBar on keyboard.
So I would simply do.
Rigidbody rb;
void Start()
{
rb= GetComponent<Rigibody>();
}
void Update()
{
if(Input.GetButtonDown(Keycode.Space))
{
MoveCube(new Vector3(5,5,5),5);
}
}
void MoveCube(Vector3 inTargetPosition , float durationToReachTheTarget)
{
//What this line does is first take in the target position you want your physics object to reach as it first parameter, Then takes in the duration in which you want it to reach there.
rb.DoMove(inTargetPosition,durationToReachTheTarget);
}
This should help you. But remember this is only if you okay with adding an extra package. Personally this package is very good and I would recommend you this.
I'm trying to add dashing to the player in a Diablo-esque game I'm working on, and it "works" where you can dash towards your cursor.
if (isDashing)
{
Ray ray = cam.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 100, movementMask))
{
motor.MoveToPoint(hit.point);
motor.speed = 150;
}
}
I get the mouse position, then move to that position with enormous velocity. MoveToPoint() function uses NavMeshAgent's SetDestination(point) function and this again "works" but the problem is naturally there isn't a limit to how far you can dash. You can have the mouse way over at the end of the screen and the character will still dash there.
I figured I should be using a Vector3 to define the limits of the dash, but I can't quite nail it down. I get the direction below and then apply the magnitude, but it does not work how I imagined it would.
if (Physics.Raycast(ray, out hit, 100, movementMask))
{
float mag = (hit.point - transform.position).magnitude;
if (mag < dashLength)
{
Vector3 dir = (hit.point - transform.position).normalized;
Vector3 newVector = dir * (dashLength - mag);
motor.MoveToPoint(newVector);
}
else
motor.MoveToPoint(hit.point);
motor.agent.speed = 150;
}
Any help would be appreciated
The main issue here is that you are treating a movement vector like a position. You want to start a the current transform.position and ADD the movement vector you calculated.
Further instead of extracting and checking the magnitude you can simply use Vector3.ClampMagnitude to keep the direction but make sure that the magnitude is not greater than your dashLength like e.g.
if (Physics.Raycast(ray, out hit, 100, movementMask))
{
// Calculate the difference only once
var movement = hit.point - transform.position;
// Make sure that delta has a maximum magnitude of dashLength
movement = Vector3.ClampMagnitude(delta, dashLength);
// MoveToPoint expects a position not only the movement vector
// so you start at your current position and add the movement
var newPosition = transform.position + movement;
motor.MoveToPoint(newPosition);
motor.agent.speed = 150;
}
I have this tracking project that I have worked on for a long time. Basically, the User moves a cube to trace the movements of a sphere which moves randomly. Currently, I am working with the User movements.
The issue is that the device that the user utilizes to move in all directions is a mouse in combination with the arrow keys. This is because the mouse handles two dimensions (x-y), while the arrow keys handle two dimensions (x-z). However, I would like to make it possible for me to just use the mouse.
As such, my professor suggested that I use the shift key to switch between x-y and x-z movements. However, I am confused as to how to go about this.
The code below represents what I have at this moment and what I have tried in regards to the shift key movements.
Could someone please help me solve this issue, or is there a better way to go about this?
Thank you!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(MeshCollider))]
public class UserController : MonoBehaviour {
public int speed = 20;
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
// get input data from keyboard or controller
float moveHorizontal = Input.GetAxis("Horizontal");
float moveVertical = Input.GetAxis("Vertical");
// update player position based on input
Vector3 position = transform.position;
position.x += moveHorizontal * speed * Time.deltaTime;
position.z += moveVertical * speed * Time.deltaTime;
transform.position = position;
}
void OnMouseDrag()
{
while(Input.GetMouseButton(0))
{
int shiftCalled = 0;
//3D Drag, courtesy of Unity Forums
if(Input.GetKey(KeyCode.LeftShift))
{
float distance_to_screen = Camera.main.WorldToScreenPoint(gameObject.transform.position).z;
transform.position = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, distance_to_screen));
}
//Plane Drag, courtesy of Unity Forums
if(Input.GetKey(KeyCode.RightShift))
{
float distance_to_screen = Camera.main.WorldToScreenPoint(gameObject.transform.position).z;
Vector3 pos_move = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, distance_to_screen));
transform.position = new Vector3(pos_move.x, transform.position.y, pos_move.z);
}
}
}
}
Here's what I would do -
Imagine a ray going from the position of the mouse on your screen towards a 2D plane that stretches on the X and Z angles. The intersection of said ray with the plane would be the X,Z coordinated that you wish your object to be dragged on.
Now imagine the same ray intersecting with a different plane that stretches on the X and Y coordinates. This will generate the other type of movement you are looking for.
By holding the SHIFT button you will essentially be swapping these two planes, which should generate the result you are looking for.
You can generate both planes in code using -
xy_Plane = new Plane(Vector3.forward, m_DistanceFromCamera);
xz_Plane = new Plane(Vector3.up, m_DistanceFromCamera);
Generate the rays using -
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
//Initialise the enter variable
float enter = 0.0f;
if (xy_Plane.Raycast(ray, out enter))
Vector3 hitPoint = ray.GetPoint(enter);
and check which plane should be used with -
if(Input.GetKey(KeyCode.Shift))
Now you only need to move your object based on the hitPoint you received.
Hope this helps.
I have a ball which rotates around the point 0,0,0 in the Z-axis. When the space button is pressed, the ball has to go inside the large circle. Now my code looks like this. When you press space, the ball does not behave as they should. I want to know how to make a balloon down exactly down
that's how the ball should behave ->
behavior image
my code:
void Update () {
if (Input.GetKeyDown (KeyCode.Space)) {
transform.position = new Vector3 (transform.position.x - 1, transform.position.y - 1, 0);
} else {
transform.RotateAround(new Vector3(0,0,0), new Vector3(0,0,1), 2);
}
}
Your code to 'jump' the orbit doesn't do what you want because Transform.RotateAround modifies both the rotation and the position of the object's transform.
So jumping to (position - 1,1,0) in the world is going to return wildly different results every time.
What you want to do instead is calculate the (Vector) direction from the object to the centre of orbit (the difference), then scale that down to how far you want it to move, then apply it to the position.
private Vector3 _orbitPos = Vector3.zero;
private float _orbitAngle = 2f;
private float _distanceToJump = 2f;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
var difference = (_orbitPos - transform.position).normalized * _distanceToJump;
transform.Translate(difference);
}
transform.RotateAround(_orbitPos, Vector3.forward, _orbitAngle);
}
This will move the object to be orbiting 2 units closer when space is pressed immediately.
If you wanted to have a smooth transition instead of a jump, look into using Mathf.Lerp, Vector3.Lerp and the routines involved.