First, I'd like to apologize because this is a very basic and repetitive question, but I am completely new to game development.
I understand that I can find a distance between two objects using:
float dist = Vector3.Distance(other.position, transform.position);
However, how can I find the distance between a point of one object to other object?
For instance let's say my object is this sphere
Now, how can I return an array that says that there are no objects to the left (null), in the front there is an object at 1, and to the right there is an object at 0.5?
Thank you for your patience and understanding
I'm not exactly sure what you wan't to achieve...
If you wan't to get potential objects at 0.5, 1, 1.5, etc. on lets say the Z Axis you probably would want to do this with raycasting.
If you wish to check for any objects returning the direction dependant to the Z Axis (0.5, 0.856, 1.45, etc) in contrast, you probably would either
use a scaled sphere collider and add the colliding object with the OnCollisionEnter Callback to the array/List
iterate through every object of the scene and check it's relative pos
Use Raycast Probes procedurally (using a density float and Raycasting every density offset on the Z Axis and checking if the ray hit anything ...)
...
Totally dependant on your case of use, and whether you use 2D or 3D.
Seneral
EDIT: Here's what you would want in 2D to get a wall withing range maxRange on the layer optionalWallLayer in direction y 1 (up in unity 2D)
float maxRange = 10.0f;
RaycastHit hit;
Vector3 dir = transform.TransformDirection(new Vector3 (0, 1, 0));
if (Physics.Raycast(transform.position, dir, maxRange, out hit, LayerMask optionalWallLayer))
{
Debug.Log ("Wall infront of this object in Range" + hit.distance);
// hit contains every information you need about the hit
// Look into the docs for more information on these
}
This would likely go into the Update Function of your Sphere's MonoBehaviour.
The option with the Sphere collider is useful if you are not sure if you are going to hit your obstacles. If these are small, you would likely want to add the said Sphere Collider as a component to your sphere, scale it up to your maximum distance, and add a MonoBehaviour script to the sphere, which would contain something like this:
public List<Transform> allObjectsInRange; // GameObjects to enclose into calculations, basically all which ever entered the sphere collider.
public List<float> relatedDistances;
void Update () {
// basic loop to iterate through each object in range to update it's distance in the Lists IF YOU NEED...
for (int cnt = 0; cnt < allObjectsInRange.Count; cnt++) {
relatedDistances[cnt] = Vector2.Distance (transform.position, allObjectsInRange[cnt].position);
}
}
// Add new entered Colliders (Walls, entities, .. all Objects with a collider on the same layer) to the watched ones
void OnCollisionEnter (Collision col) {
allObjectsInRange.Add (col.collider.transform);
relatedDistances.Add (Vector2.Distance (transform.position, col.collider.transform));
}
// And remove them if they are no longer in reasonable range
void OnCollisionExit (Collision col) {
if (allObjectsInRange.Contains (col.collider.transform)) {
relatedDistances.RemoveAt (allObjectsInRange.IndexOf (col.collider.transform));
allObjectsInRange.Remove (col.collider.transform);
}
}
That should do it, either of the options, based on your exact case, again:) Note both are pseudo-codes... Hope it helps!
Related
I am using the Unity Engine with C#.
I have a 1x1 cube which moves forward on a grid of 49, 1x1 cubes (screenshot below) - when I press the start button on the controller.
The movement code for the cube is below.
void MovePlayerCube()
{
transform.Translate(direction * moveSpeed * Time.deltaTime);
}
When this cube passes over a cube with an arrow on it, the cube will change direction to where the arrow is pointing (staying on the same Y axis).
I need to detect the exact point at which the cube is directly over the cube with the arrow on it, and run the 'change direction' code at that point.
I'm currently using Vector3.Distance to check if the X and Z coordinates of the 2 cubes are close enough together (if they are less than 0.03f in distance), I can't check if they are equal due to floating point imprecision.
However this is really ineffective as half the time this code doesn't register for probably the same reason, and if I increase the 0.03f to a point where it never misses it becomes really noticeable that the cube isn't aligned with the grid anymore.
There has to be a proper solution to this and hopefully I've clarified the situation enough?
Any advice is appreciated.
You are moving your cube via
transform.Translate(direction * moveSpeed * Time.deltaTime);
which will never be exact an might overshoot your positions.
=> I would rather implement a coroutine for moving the cube exactly one field at a time, ensuring that after each iteration it fully aligns with the grid and run your checks once in that moment.
It doesn't even have to match exactly then, you only need to check if you are somewhere hitting a cube below you.
So something like e.g.
private Vector3Int direction = Vector3Int.left;
private IEnumerator MoveRoutine()
{
// depends on your needs if this runs just forever or certain steps
// or has some exit condition
while(true)
{
// calculate the next position
// optional round it to int => 1x1 grid ensured on arrival
// again depends a bit on your needs
var nextPosition = Vector3Int.RoundToInt(transform.position) + direction;
// move until reaching the target position
// Vector3 == Vector3 uses a precision of 1e-5
while(transform.position != nextPosition)
{
transform.position = Vector3.MoveTowards(transform.position, nextPosition, moveSpeed * Time.deltaTime);
yield return null;
}
// set target position in one frame just to be sure
transform.position = nextPosition;
// run your check here ONCE and adjust direction
}
}
start this routine only ONCE via
StartCoroutine(MoveRoutine());
or if you have certain exit conditions at least only run one routine at a time.
A Corouine is basically just a temporary Update routine with a little bit different writing => of course you could implement the same in Update as well if you prefer that
private Vector3Int direction = Vector3Int.left;
private Vector3 nextPosition;
private void Start()
{
nextPosition = transform.position;
}
private void Update()
{
if(transform.position != nextPosition)
{
transform.position = Vector3.MoveTowards(transform.position, nextPosition, moveSpeed * Time.deltaTime);
}
else
{
transform.position = nextPosition;
// run your check here ONCE and adjust direction
// then set next position
nextPosition = Vector3Int.RoundToInt(transform.position) + direction;
}
}
Then regarding the check you can have a simple raycast since you only run it in a specific moment:
if(Physics.Raycast(transform.position, Vector3.down, out var hit))
{
direction = Vector3Int.RountToInt(hit.transform.forward);
}
assuming of course your targets have colliders attached, your moved cube position (pivot) is above those colliders (assumed it from your image) and your targets forward actually points int the desired new diretcion
I would do it this way. First I would split the ability of certain objects to be "moving with certain speed" and "moving in a certain direction", this can be done with C# interfaces. Why? Because then your "arrow" cube could affect not only your current moving cube, but anything that implements the interfaces (maybe in the future you'll have some enemy cube, and it will also be affected by the arrow modifier).
IMovingSpeed.cs
public interface IMovementSpeed
{
float MovementSpeed{ get; set; }
}
IMovementDirection3D.cs
public interface IMovementDirection3D
{
Vector3 MovementDirection { get; set; }
}
Then you implement the logic of your cube that moves automatically in a certain direction. Put this component on your player cube.
public class MovingStraight: MonoBehaviour, IMovementSpeed, IMovementDirection3D
{
private float _movementSpeed;
Vector3 MovementSpeed
{
get { return _movementSpeed; }
set { _movementSpeed = value; }
}
private Vector3 _movementDirection;
Vector3 MovementDirection
{
get { return _movementDirection; }
set { _movementDirection= value; }
}
void Update()
{
// use MovementSpeed and MovementDirection to advance the object position
}
}
Now to implement how the arrow cube modifies other objects, I would attach a collision (trigger) component for both moving cube and the arrow cube.
In the component of the arrow cube, you can implement an action when something enters this trigger zone, in our case we check if this object "has direction that we can change", and if so, we change the direction, and make sure that the object will be aligned, by forcing the arrow cube's position and the other object's position to be the same on the grid.
public class DirectionModifier: MonoBehaviour
{
private Vector3 _newDirection;
private void OnTriggerEnter(Collider collider)
{
IMovementDirection3D objectWithDirection = collider as IMovementDirection3D ;
if (objectWithDirection !=null)
{
objectWithDirection.MovementDirection = _newDirection;
// to make sure the object will continue moving exactly
// from where the arrow cube is
collider.transform.position.x = transform.position.x;
collider.transform.position.y = transform.position.y;
}
}
}
If you made your trigger zones too large, however, then the moving cube will "jump" abruptly when it enters the arrow cube's trigger zone. You can fix it by either starting a coroutine as other answers suggested, or you could make the trigger zones pretty small, so that the jump is not noticeable (just make sure not to make them too small, or they may not intersect each other)
You could then similarly create many other modifying blocks, that would change speed or something
I think that it is enough for you to check if the X and Z coordinates are equal, since the movement occurs only along them
Example
if(_player.transfom.position.x == _gameSquare.transfom.position.x && _player.transfom.position.z == _gameSquare.transfom.position.z)
{
Debag.Log("Rotate!")
}
I am working on a VR project in Unity (2020.3.40f), and need to add the option to move an object on its axis based on the controller's (the user's hand) movement.
Currently I store the controller's position when it grabs the object, and continuously calculate the distance the controller has moved from the initial position.
But it is inaccurate, because the controller might have moved in a direction that shouldn't affect the object's position.
For example:
I have this blue lever that the user has to pull. I want to know how much the controller has moved along the green axis, so I can move the lever accordingly.
If the user moves their hand upwards, it shouldn't affect the lever (but in my current implementation, I use Vector3.Distance so the lever moves anyway).
My code:
private void OnTriggerEnter(Collider other)
{
controller = other.GetComponentInParent<IController>();
if (controller == null || controller.IsOccupied)
{
return;
}
controller.IsOccupied = true;
controllerStartPosition = controller.GetPosition();
}
private void Update()
{
if (controller == null) return;
Vector3 currentControllerPosition = controller.GetPosition();
float distance = Vector3.Distance(currentControllerPosition, controllerStartPosition);
transform.Translate(0, 0, distance * sensitivity); // The object always moves along its forward axis.
}
I assume that I need to project the controller's position on the object's forward axis and calculate the distance of that, but I have very basic knowledge in vectors maths so I am not sure about that.
So my question is, What are the calculations that I should do to get the correct distance?
As mentioned what you want to do is Vector3.Project your given hand movement onto the desired target axis direction and only move about this delta.
Something like
private void Update()
{
Vetcor3 currentControllerPosition = controller.GetPosition();
// the total vector in world space your hand has moved since start
Vector3 delta = currentControllerPosition - controllerStartPosition;
// the delta projected onto this objects forward vector in world space
// you can of course adjust the vector but from your usage this seems the desired one
Vector3 projectedDelta = Vector3.Project(delta, transform.forward);
// finally moving only about that projected vector in world space
transform.position += projectedDelta * sensitivity;
}
what you are currently doing is;
you are calculating the distance in every axis which the movment on every axis will change the outcome. What you need is when calculating the distance only pass in the parameters in the desired axis for example:
float distance = currentControllerPosition.x - controllerStartPosition.x;
this will give you the diffrence between the x axis of these points.
for example if it was at 5 and it moved to 8 this will return you 3 regardless the movement on the other axis.
Preamble:
I have a 3D side scroller style game in which the player flies along avoiding stuff, you know, side-scrollery things.
I'd like to add an effect (particle system) to the player when they get close (within a preset dangerZone, say 1.6 units (meters)) to the terrain, like a dusty dragging cloud under them sort of thing. I'm familiar with the particle system and raycasts but I don't know how to marry the concepts together to achieve what I'm after. The terrain undulates randomly and is not a flat surface, if that helps.
I'd also be hoping to make the particle system 'grow' the closer the player gets to the terrain if that makes sense. There is also speed to consider, so the closer to the ground and faster the player is should have an effect on the particle system.
My Thoughts:
I already have a score multiplier that uses a raycast to check the player's position from the ground/terrain and increases the closer they get.
void Update() {
force = speed *2;
RaycastHit hit;
Ray downRay = new Ray(transform.position, -Vector3.up);
if (Physics.Raycast(downRay, out hit, dangerZone)) {
var distanceToGround = hit.distance;
float hazardMultiplier = Mathf.Round( (transform.position.y - distanceToGround)*100 ) /100;
if (hit.collider.tag == "Terrain") {
playerData.scoreMulitplier = hazardMultiplier;
}
}
else {
playerData.scoreMulitplier = playerData.baseScoreMulitplier;
}
}
I'm thinking I can use the raycast I already have to instantiate a particle system on the terrain/at the raycast hit point but I'm not sure how exactly to go about this.
Any help is appreciated and thanks in advance.
You're on the right track. A couple things first before we dive into the solution:
Raycasts are calculated on the fixed frame (physics, FixedUpdate), not the visual frame (Update). While you may invoke a Raycast during Update, it won't be calculated until the next FixedFrame anyways. I'd recommend moving this to FixedUpdate to reduce the chance of doubled logic (2 simultaneous raycasts) or skipped logic (no raycasts).
You can set your particle system's scaling mode to the hierarchy, and scale using the transform. Alternatively, you can set the startSize of the particleSystem's main attributes.. Since you want to change the size of the particleSystem to change fairly frequently, I would recommend changing the scaling mode to hierarchy and just modifying the transform of the object it is attached to.
[SerializeField]
ParticleSystem ps;
[SerializeField]
float dangerZone = 1.6f;
[SerializeField]
float maxParticleSize = 2.0f; //How much you want particles to scale up based on closeness to terrain
void FixedUpdate() {
RaycastHit hit;
Ray downRay = new Ray(transform.position, -Vector3.up);
//Consider adding a layerMask parameter here that only interacts with the "terrain" layer. This will save you physics computing power, and remove the need for tag checking every frame (which is not efficient).
if (Physics.Raycast(downRay, out hit, dangerZone)) {
var distanceToGround = hit.distance;
float hazardMultiplier = Mathf.Round( (transform.position.y - distanceToGround)*100 ) /100; //This doesn't make too much sense to me, so you may want to revisit this. Since this isn't scoped for this question we can ignore it.
//If you use a layerMask, then you wouldn't ever need to check
if (hit.collider.tag == "Terrain") {
playerData.scoreMulitplier = hazardMultiplier;
float particleScale = 1 + (dangerZone - hit.distance) / dangerZone;
ps.transform.localScale = (particleScale, particleScale, particleScale);
if(ps.isStopped)
ps.Play();
} else //In scenarios where the raycast hits a non-terrain target, you'll need to turn off particles. If you use a layermask this wouldn't be needed.
{
if(ps.isPlaying)
ps.Stop();
}
}
else {
if(ps.isPlaying)
ps.Stop();
playerData.scoreMulitplier = playerData.baseScoreMulitplier;
}
}
This is what I have tried so far:
I create a raycast and if it hits an object on layer 8 (the layer in which objects need to be launched to the player), I call the SlerpToHand() function.
private void Update()
{
if(Physics.Raycast(transform.position, transform.forward * raycastLength, out hit))
{
if(hit.collider.gameObject.layer == 8)
{
// Launch object to player
SlerpToHand(hit.collider.transform);
}
}
}
Inside of SlerpToHand(), I set the object's position to Vector3.Slerp(), that vector being created from values in the hit object.
private void SlerpToHand(Transform hitObj)
{
Vector3 hitObjVector = new Vector3(hitObj.transform.position.x, hitObj.transform.position.y, hitObj.transform.position.z);
hitObj.position = Vector3.Slerp(hitObjVector, transform.position, speed);
}
But the result of this is all wrong, the object just gets teleported to the player's hands. Is Vector3.Slerp() not a good way to curve an object to the player? For context I am trying to recreate Half-Life: Alyx's grabbity gloves. There is still some work to do with the hand gestures but I am just trying to get the object curve down. Help is much appreciated, let me know if more info is needed.
See unity docs:
public static Vector3 Slerp(Vector3 a, Vector3 b, float t);
Here, t is a normalized position between two input values. It means, if t = 0, result will be exactly first value. If t = 1, result will be exactly second value. If t = 0.5, result will be the middle between two values.
So, usually, you need to call Slerp every Update, step by step increasing t from 0 to 1. For this, usually Time.deltaTime used (which equals the time between updates). For speed control, multiply your speed by Time.deltaTime.
Update()
{
if (t < 1)
{
t += Time.deltaTime * speed;
hitObj.position = Vector3.Slerp(startPosition, endPosition, t);
}
}
...and in this case, for start moving, you just need to set t = 0. Probably, you have to implement your own logic here, but this should show the idea.
In addition:
Slerp used to interpolate between vector directions, for positions use Lerp.
Consider use DOTween plugin - its free and powerful for such cases.
I'm new to Unity2D (Unity 5.0.2f1) and have been searching for a solution which I'm sure is staring me in the face!
I have a game object (essentially a road) like below (DirtTrack1):
I have a spawner which spawns GameObjects (vehicles). I want to spawn those vehicles over this road.
I have tried the following code to do this, essentially trying to spawn the vehicle within the Y-axis area of the road by getting the bottom Y co-ordinate of the road and the top Y co-ordinate, so I get the min and max vertical positions of where I can place the vehicle:
void FixedUpdate() {
// Repeat spawning after the period spawn
// route has finished.
if (!_inSpawningIteration)
StartCoroutine (SpawnVehiclePeriodically());
}
IEnumerator SpawnVehiclePeriodically()
{
// First, get the height of the vehicle and road.
float vehicleHeightHalf = vehiclePreFab.GetComponent<SpriteRenderer>().bounds.size.y / 2f;
float roadHeightHalf = roadObject.GetComponent<SpriteRenderer>().bounds.size.y / 2f;
float roadTopY = roadObject.transform.position.y + roadHeightHalf;
float roadBottomY = roadObject.transform.position.y - roadHeightHalf;
// Next, ensure that maxY is within bounds of this farm vehicle.
roadMaxY = roadTopY - vehicleHeightHalf;
roadMinY = roadBottomY + vehicleHeightHalf;
// Set the position and spawn.
Vector3 newPosition = new Vector3 (Const_RoadItemsPositionX, randomY, 0f);
GameObject vehicle = (GameObject)GameObject.Instantiate (vehiclePreFab, newPosition, Quaternion.identity);
}
This does spawn randomly but most times it is always not within the road itself. It is either part on the road or at the outside edge of it.
I can't figure out what I'm doing wrong here but I'm sure it is something very simple!
Tick the kinematic check of your vehicle, physics may be moving it out of the road if you don't do that.
You are using localPosition. From documentation:
Position of the transform relative to the parent transform.
If the transform has no parent, it is the same as Transform.position.
Looking at your scene, your road has a parent object and the relative position you are getting might be messing up with the spawn position of cars.