Home > Net >  How to detect when a cube is directly above another cube?
How to detect when a cube is directly above another cube?

Time:12-08

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.

level setup screenshot

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.

CodePudding user response:

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

CodePudding user response:

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

  • Related