How to detect when a cube is directly above another cube? - c#

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!")
}

Related

Is there a way to create a curve when launching a gameobject to the player?

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.

How to make camera relative movement

I'm learning unity and c#, and want to make my movement to be camera relative movement instead of world relative movement. How do I do that?
I'm learning unity and c#, my unity version is 2018.3.12f1. I would be happy for help.
just to let know, instead of moving the cam I'm rotating the player.
void Update()
{
float AxisY = Player.transform.eulerAngles.y;
/* Movement starts here */
Vector3 Movement = new Vector3 (Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { //running code
Player.transform.position += Movement * running_speed * Time.deltaTime;
} else {
Player.transform.position += Movement * speed * Time.deltaTime;
}
/*Movement ends here */
/* Rotation controller starts here */
Quaternion target = Quaternion.Euler(Player.transform.eulerAngles.x, Player.transform.eulerAngles.y, Player.transform.eulerAngles.z);
/*if (Player.transform.eulerAngles.x != 0 || Player.transform.eulerAngles.z != 0 || Player.transform.eulerAngles.y != 0) {
Player.transform.rotation = Quaternion.Euler(0,0,0);
}*/
if (Input.GetKey(KeyCode.E))
{
Debug.Log("E got pressed");
//float AxisYPositive = Player.transform.eulerAngles.y;
AxisY = AxisY+1;
Player.transform.rotation = Quaternion.Euler(0, AxisY, 0);
} else if (Input.GetKey(KeyCode.Q))
{
Debug.Log("Q got pressed");
//float AxisYNegetive = Player.transform.eulerAngles.y;
AxisY=AxisY-1;
Player.transform.rotation = Quaternion.Euler(0, AxisY, 0);
}
}
}
The player's movement is world relative, how to make the movement camera relative?
If you want to make the movements relative to the gameObject, call the method Transform.Rotate() on the transform of the gameObject you want to rotate rather than modifying its Quaternion directly. Just make sure the final argument is set to Space.Self.
if (Input.GetKey(KeyCode.E))
{
Debug.Log("E got pressed");
//float AxisYPositive = Player.transform.eulerAngles.y;
AxisY = AxisY+1;
Player.transform.Rotate(Quaternion.Euler(0, AxisY, 0), Space.Self);
}
In general you don't want to directly mess with objects transform.rotation, at least not unless you at least somewhat understand quaternions (I don't!).
I can see a few issues with your code, but the common thread seems to be that you don't really understand how transforms work. Specifically, you might want to look into World/Local space.
The usual way to control a player goes roughly like this:
void DoMovement(Transform player)
{
//If you move first your controls might feel 'drifty', especially at low FPS.
Turn(player);
Move(player);
}
void Turn(Transform player)
{
float yaw = Input.GetAxis("Yaw") * time.deltaTime; //Aka turn left/right
player.Rotate(0, yaw, 0, Space.Self);
// Space.Self is the default value, but I put it here for clarity.
//That means the player will rotate relative to themselves,
//...instead of relative to the world-axis, like in your code.
}
You didn't ask about movement, but as-is your character will always move relative to the world. The below should make it move relative to the camera.
Transform _cameraTransform; //Assumes this is set druing Start()
void Move(Transform player)
{
var forwardMove = _cameraTransform.Forward; //Get whatever direction is 'forward' for camera
forwardMove.Y = 0; //Don't want movement up and down.
forwardMove = forwardMove.normalized; //Normalize sets the 'power' of the vector to 1.
//If you set Y to 0 and don't normalize you'll go slower when camera looks down
//...than when camera is flat along the plane
player.position += forwardMove * Input.GetAxis("Vertical") * time.deltaTime;
//Here you could do the same for strafe/side to side movement.
//Would be same as above, but using the transform.right and Horizontal axis
}
Now, I'm making some assumptions here since you haven't specified what kind of game it is and what kind of controls you want. I'm assuming you have a character running around on a mostly flat plane (no aircraft/spaceship controls), and that the camera is attached to the player. This might not not actually be the case.
In any case I advice you to check out the tutorials, especially the Roll-a-Ball tutorial which I have found is good for beginners to get a grasp on basic players controls that are not just world-relative. The other tutorials, too, are pretty good if you think they're interesting.
Aside from the official Unity tuts a ton of decent to amazing tutorials out there, including video tutorials, so for something like this you could just search for <game type> tutorial and pick whatever seems good to you. While getting started I advice you to avoid the shortest videos, as you will likely benefit greatly from explanation that only fits in longer videos. Of course, that doesn't mean you should pick the longest videos either.
In case someone needs to move an object and don't care about colliders, you can use transform.Translate and assign to his second parameter relativeTo your camera (or any transform) to automatically calculate the translation relative to the object assigned.

Building a rotatable lift in Unity

I got a "cube" in Unity. This cube has a Trigger, when moving into it, the object gets grabbed into the air. This is working as a lift, you can see an example what I mean here. It is a small scene taken from Spyro 2.
https://youtu.be/f8wWMa4N5mE?t=643
The code I use is really small for now
private float liftSpeed = 10; // the speed, the object is flying up
private void OnTriggerStay(Collider col)
{
Rigidbody objectRigid = col.gameObject.GetComponent<Rigidbody>(); // get the rigidbody from the object in the trigger
if (objectRigid != null) // does it have a rigidbody?
objectRigid.velocity = new Vector3(objectRigid.velocity.x, liftSpeed, objectRigid.velocity.z); // make it fly in the air
}
So I have a lift, that totally works fine. But I when I rotate my lift I want it to work aswell.
Some examples (my gamne is 3D)
So how can I make my lift work for all "rotations"?
You can use transform.up to get the up direction of your lift, and then multiply by the lift speed.
objectRigid.velocity = transform.up * liftSpeed;
transform.up changes depending on how the object is rotated, so if your lift is rotated to the left, then the lift will carry objects to the left.
You can use the transforms RotateAround Method for a rotation like in the video clip.
Transform t = col.gameObject.GetComponent<Transform>();
transform.RotateAround(Vector3.zero, Vector3.up, 20 * Time.deltaTime);
The given snipet lets the object rotate around it's axis, which is pointing up.
Make the player child of the lift OnTriggerEnter.
Move the lift as you do
You can rotate the lift using RotateAround function. The player should also rotate along with the lift, as it's a child of the lift now.
make player's parent to null when it's out of lift trigger using OnTriggerExit
This is my solution to the problem.
The lift and the EndPoint will be Empty GameObjects. If you want the lift has a platform or a base you can add a GameObject as child of the lift with the shape you prefer.
You need to attach a Collider to the lift and set it as trigger.
Then you add the following script to the lift Empty GameObject
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MoveLift : MonoBehaviour {
public Transform end;
float speed = 4f;
bool liftActivated = false;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if(liftActivated)
{
float step = speed * Time.deltaTime;
//To move up the lift
transform.position = Vector3.MoveTowards(transform.position, end.position, step);
//To spin the lift
transform.RotateAround(Vector3.up, 2 * Time.deltaTime);
//To stop spining the lift when it reaches the end of the path
if(transform.position.Equals(end.position))
liftActivated = false;
}
}
void OnTriggerEnter(Collider other){
liftActivated = true;
other.gameObject.transform.parent = this.transform;
other.gameObject.GetComponent<Rigidbody>().isKinematic=true;
}
}
Then you should decide if once the lift reaches its destination it is the player who has to move out of the platform, or if you unparent it and let it fall down as the end of the trip

How to reverse gravity immediately?

I'm making a 2D game. I have a Cube Object falling down from the top of the screen, and I have pipes which the cube should pass by reversing gravity.
Well my object is falling down from the top of the screen. I tap on the screen to reverse gravity but it's not going immediately up: it takes time to change the gravity orientation. When i tap the screen my object continues falling and then goes up. My movement is forming the shape of a U when I tap the screen. The same thing happens when it goes up: I tap it to go down and in that case my movement forms the shape of a ∩.
What I want to achieve is that when I tap the screen my object's movement has instant response.
In addition, I want some sort of attenuation, damping, or smoothing.
I've tried these examples without success:
http://docs.unity3d.com/ScriptReference/Vector2.Lerp.html
http://docs.unity3d.com/Documentation/ScriptReference/Vector3.SmoothDamp.html
This is my code:
public class PlayerControls : MonoBehaviour
{
public GameObject playerObject = null;
Rigidbody2D player;
public float moveSpeed;
public float up;
Vector2 targetVelocity;
void Start()
{
player = playerObject.GetComponent<Rigidbody2D>();
// Set the initial target velocity y component to the desired up velocity.
targetVelocity.y = up;
}
void Update()
{
for (int i = 0; i < Input.touchCount; i++)
{
Touch touch = Input.GetTouch(i);
if (touch.phase == TouchPhase.Ended && touch.tapCount == 1)
{
// Flip the target velocity y component.
targetVelocity.y = -targetVelocity.y;
}
}
}
void FixedUpdate()
{
// Ensure if moveSpeed changes, the target velocity does too.
targetVelocity.x = moveSpeed;
// Change the player's velocity to be closer to the target.
player.velocity = Vector2.Lerp(player.velocity, targetVelocity, 0.01f);
}
}
This script is attached to the falling cube.
When affected by a constant force (in your code, gravity), an object will move in a parabola. You're observing a parabola: that U shape. To avoid the parabola and get instant response (a V shape), don't use forces. Replace the gravityScale code with something like:
Vector2 velocity = player.velocity;
velocity.y = -velocity.y;
player.velocity = velocity;
This inverts the velocity's y component, making the player move upwards.
You also mentioned you want to smooth this out. I suggest adding another variable: the target velocity.
To implement this, add Vector2 targetVelocity to your component. Then, during each FixedUpdate you can interpolate from the current velocity towards the target velocity to gradually change speed. Replace this line in your current code:
player.velocity = new Vector2(moveSpeed, player.velocity.y);
With this:
player.velocity = Vector2.Lerp(player.velocity, targetVelocity, 0.01f);
This will slowly change the velocity to be the same as the target velocity, smoothing out the movement. 0.01f is the place between the old velocity and the new velocity that the player's velocity is set to. Choose a larger number to interpolate more quickly, and a smaller number to change direction more slowly.
Then, instead of changing velocity when the screen is tapped, change targetVelocity. Make sure that the x component of targetVelocity is equal to moveSpeed, or the object won't move horizontally at all!
Combining these two changes, the Start and FixedUpdate methods should look similar to this:
void Start()
{
player = playerObject.GetComponent<Rigidbody2D>();
// Set the initial target velocity y component to the desired up velocity.
targetVelocity.y = up;
}
void Update()
{
for (int i = 0; i < Input.touchCount; i++)
{
Touch touch = Input.GetTouch(i);
if (touch.phase == TouchPhase.Ended && touch.tapCount == 1)
{
// Flip the target velocity y component.
targetVelocity.y = -targetVelocity.y;
}
}
}
void FixedUpdate()
{
// Ensure if moveSpeed changes, the target velocity does too.
targetVelocity.x = moveSpeed;
// Change the player's velocity to be closer to the target.
player.velocity = Vector2.Lerp(player.velocity, targetVelocity, 0.01f);
}
Note that you shouldn't use Input in FixedUpdate, because inputs are updated every frame and FixedUpdate may run multiple times per frame. This could cause the input to be read twice, especially if the frame rate gets slower and the fixed update is more likely to need to run multiple times per frame.

How do I make character play another animation when moving left?

I have script which makes character (gameobject) move right.
How do I make so when he reaches right border, and character starts moving left with playing another animation (moving left)?
Here is my script of character's moving right and left
void MovementRight()
{
_character.transform.Translate (Vector2.right 100f Time.deltaTime);
}
void MovementLeft()
{
_character.transform.Translate (Vector3.left 100f Time.deltaTime);
}
I would suggest you handle animations with Animator, and make the horizontal animation to be handled by a float variable in Animator, (so when it is -1 animate walking left, and 1 animate walking right).
So you will need to make 3 scripts.
//This will handle the animator getting the speed of the character from MovingHandler
AnimationHandler.cs
//This will actually move the character based on input handler
MovingHandler.cs
//Here you will hear to inputs
InputHandler.cs
And to make suere the character won't move outside the screen you will need to get the screenbounds on MovingHandler and make a check before moving the character, if it is in the same spot in x as the bounds of the screen you will make a return; also if it is going to the left and the character is <= than left bound then return; and the same on the right bound >= then return;
Something like this:
Transform charTransform;
float leftHorizontalBound;
float rightHorizontalBound;
void Start()
{
charTransform = this.transform;
leftHorizontalBound = camera.ViewportToWorldPoint (new Vector3 (0,0, camera.nearClipPlane)).x;
rightHorizontalBound= camera.ViewportToWorldPoint (new Vector3 (1,0, camera.nearClipPlane)).x;
}
void Update()
{
if(charTransform.position.x <= leftHorizontalBound)
{
charTransform.position = new vector2(leftHorizontalBound + 0.1f);
return;
}
if(charTransform.position.x >= rightHorizontalBound)
{
charTransform.position = new vector2(rightHorizontalBound - 0.1f);
return;
}
//MAKE HERE YOUR MOVEMENT BASED ON INPUT.
}
Dont use that code just use the idea, since i didn't test it, i just code it here.
I hope it helped.

Categories