Unity, Follow a rolling ball and be able to rotate around it - c#

I want to be able to follow a rolling ball (which I can do by itself) and I want to be able to rotate around the ball while holding right click (which I can do by itself as well) but when I try to combine these 2 I get unwanted results.
In the code below you can see that if I just wanted to rotate around the ball my "RotateAround" takes care of that no problem and if I just wanted to follow the ball wherever it goes with the camera being the same distance away and angle then my _offset takes care of that. When I try to combine the 2 to have it follow the ball and be able to rotate the camera (with right click) at the same time the camera just stops following the ball.
I figured since you can follow the ball with the _offset by itself which I found on the Unity tutorials that when the position is changed with RotateAround that _offset would take care of keeping the camera in the new position. This script by the way is on my Camera GameObject.
void Start()
{
_offset = CamFollowStartSpot.position - Ball.transform.position;
}
void LateUpdate()
{
if (Input.GetMouseButton(1))
{
_transform.RotateAround(Ball.transform.position, Vector3.up, 20 * Time.deltaTime);
_offset = _transform.position - Ball.transform.position;
}
_transform.position = Ball.transform.position + _offset;
}

I figured out what was wrong, it was when I was setting my position at the end. The code below needed to be called before the IF statement to check for my mouse click input.
_transform.position = Ball.transform.position + _offset;
The correct way is in the code below.
void LateUpdate()
{
_transform.position = Ball.transform.position + _offset;
if (Input.GetMouseButton(1))
{
_transform.RotateAround(Ball.transform.position, Vector3.up, 20 * Time.deltaTime);
_offset = _transform.position - Ball.transform.position;
}
}

Related

How to detect when a cube is directly above another cube?

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

Zoom In & Zoom Out in Unity

So, Hey guys. Im new to Unity and Im trying a very simple code that allows the player to Zoom-in & Zoom-Out. Here's the code of it:
[Header("Vertical Zooming")]
[SerializeField] private float zoomSpeed;
private void Update()
{
VerticalZooming();
}
private void VerticalZooming()
{
Vector3 initialPosition = new Vector3();
// bool canMove = false;
if (Input.GetMouseButtonDown(0))
{
initialPosition = mCamRef.ScreenToViewportPoint(Input.mousePosition);
Debug.Log(initialPosition.y);
}
if (Input.GetMouseButton(0))
{
Vector3 newPosition = mCamRef.ScreenToViewportPoint(Input.mousePosition);
Debug.Log("newPosition" + newPosition.y);
if (newPosition.y < initialPosition.y)
{
Zoom((zoomSpeed * Time.deltaTime) * -1f);
}
if (newPosition.y > initialPosition.y)
{
Zoom(zoomSpeed * Time.deltaTime);
}
intialPosition = newPosition;
}
}
private void Zoom(float inZoomSpeed)
{
transform.Translate(mCamRef.transform.position.z * inZoomSpeed * transform.forward);
}
So, if you see the code above. On mousebuttondown we note down the initial position and on holding the mouse button we keep track of new position constantly.. What i want is pretty simple. With reference to my initial mouse click im checking if my new position is greater in y is yes i need to zoom out. If new position is lower than initial position in y-axis. Them, i need to zoom in. Which I did, But for some reason its not working.
Two problems here:
Though if there is no change to newposition i.e., even if both my newposition variable and initial position variable are in same spot my camera moves(Zoom-out in my case) which shouldn't happen. It should only happen if my newPosition.y variable is greater that that of initialposition.y. I'm not able to figure out why this is happening. I tried using bool conditions to do this, but failed.
If i play test this in my mobile.. Its only going on one direction. in my case its only zooming out continously though my zoom-in if-condition meets. But when playtesting in unity game window. The zoom in works when my mouse is out of the game-screen. Because its only working outside the game screen.This is the reason its not working in mobile as there is no space outside mobile display. Which i dont know why this is happening or dont know how to overcome this.
Let me know why! Im stuck at it for a long time and not getting any idea. Thanks <3
If Any doubt in question let me know. Sorry for a huge explanation. Just wanted to be very precise on the question.
I have seen people suggesting zoom in zoom out through using FOV. But i wanna do it using translate or move the camera itself.
So your problem here is on taking count on the screenToViewPort point.
Instead just try this..
if (Input.GetMouseButtonDown(0))
{
mInitialPosition = Input.mousePosition.y;
}
if (Input.GetMouseButton(0))
{
mChangedPosition = Input.mousePosition.y;
if (mChangedPosition == mInitialPosition)
{
return;
}
if (mChangedPosition < mInitialPosition)
{
Zoom((mZoomSpeed * -1f) * Time.deltaTime);
}
if (mChangedPosition > mInitialPosition)
{
Zoom(mZoomSpeed * Time.deltaTime);
}
}
private void Zoom(float inZoomSpeed)
{
transform.Translate(mCamRef.transform.position.z * inZoomSpeed * transform.forward);
}
Not sure how ScreenToViewport point works... But I guess this code should do the magic for you... Here I'm just straight away taking the mouse.y value on click and hold and did the same functions as you did and also telling it not to do anything when both the y values are same and return the function... So meaning that you already had everything in your code itself... Have a nice day

How to create a 180 degree camera orbit over X seconds in Unity?

I have a camera that orbits a point on the screen. Clicking and dragging your mouse left and right will orbit around the focus point and releasing the mouse will leave the camera at that angle.
I'm trying to create a function that will orbit the camera 180 degrees, smoothly. So for example, if the camera is behind the "player" and this function gets called, the camera will orbit to the front of the player over the course of X seconds and stop in the exact opposite position of where it started.
Here's the code I'm currently using for camera rotation based on mouse click and drag:
Quaternion camTurnAngle =
Quaternion.AngleAxis(Input.GetAxis("Mouse X") * rotationSpeed, Vector3.up);
this.cameraOffset = camTurnAngle * this.cameraOffset;
Vector3 newPos = this.currentFocusPoint + this.cameraOffset;
transform.position = Vector3.Slerp(transform.position, newPos, smoothFactor);
transform.LookAt(this.currentFocusPoint);
I am trying to replace this mouse-drag based camera rotate with a UI button (aka, a single function call) that will fully orbit the camera 180 degrees over a short period of time, and then it will rotate back if pressed again. Here's an MS paint drawing just in case my explanation is confusing:
I'm not sure how do to something like this. It sounds similar to using Vector3.Slerp, but I cannot find a solution that works. I am so far from a solution that I don't really have any sensible code to post. I'll just say that I've tried two methods so far:
1) I've tried using transform.RotateAround, where angle I pass in is rotateSpeed * Time.deltaTime. My issue is I don't know how to check for an end condition, and without one my camera will rotate forever.
2) I've tried messing with Quaternion.Slerp, but the results are seeing are not what I expected.
How can I achieve a smooth 180 degree camera orbit, that takes a predetermined amount of time to complete?
This is a textbook use for a coroutine. Basically, start a coroutine that keeps track of how much time is left in the orbit, then either updates cameraOffset based on the time that's left or Time.deltaTime, whatever is shorter.
private IEnumerator RotateCam(float rotateDuration)
{
float timeLeft = rotateDuration;
// possibly - disable control of camera here
while (timeLeft > 0)
{
yield return null;
float elapsedRotationTime = Mathf.Min(timeLeft, Time.deltaTime);
float angleThisFrame = 180f * elapsedRotationTime / rotateDuration;
Quaternion camTurnAngle = Quaternion.AngleAxis(angleThisFrame, Vector3.up);
this.cameraOffset = camTurnAngle * this.cameraOffset;
transform.position = this.currentFocusPoint + this.cameraOffset;
transform.LookAt(this.currentFocusPoint);
timeLeft -= Time.deltaTime;
}
// possibly - re-enable control of camera here
}
Then, when you want to begin the rotation:
// private Coroutine rotateCoroutine
this.rotateCoroutine = StartCoroutine(RotateCam(2f));

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.

Rigidbody Mobs fall through ground while walking

I have a really odd issue, I created custom MOB AI in unity for my game (it has procedural generated voxel world so agents didn't work well). I use rigidbody on the mobs which I move around.
But I have this issue where mobs go inside the floor while moving (doesn't happen when standing) and when they stand, they teleport back up!. It's not animation, I disabled animations and it still happens.
here is how I move them:
private void WalkToTarget()
{
if (goal != Vector3.zero)
{
if (goal.y <= 5)
{
currentstatus = MOBSTATUS.STANDING;
return;
}
var distance = Vector3.Distance(VoxelPlayPlayer.instance.transform.position, gameObject.transform.position);
if (distance < 15)
{
goal = VoxelPlayPlayer.instance.transform.position;
goal.y = VoxelPlayEnvironment.instance.GetHeight(goal);
}
if (distance < 5)
{
this.currentstatus = MOBSTATUS.ATTACKING;
}
//MOVEMENT HAPPENS HERE
Vector3 direction = (goal - mobcontroller.transform.position).normalized*2f;
if(mobcontroller.collisionFlags!=CollisionFlags.CollidedBelow)
direction+= Vector3.down;
mobcontroller.Move(direction * Time.fixedDeltaTime);
RotateTowards(direction);
}
}
Edit:
All code: https://pastebin.com/khCmfKGi
Part of your problem is that you are using CollisionFlags incorrectly.
Instead of this:
if(mobcontroller.collisionFlags!=CollisionFlags.CollidedBelow)
You need to do this
if(mobcontroller.collisionFlags & CollisionFlags.CollidedBelow)
Because you are trying to check if the mob is at least colliding below, not if the mob is only colliding below.
Even then, CharacterController.Move should not move you through colliders on its own.
I suspect that RotateTowards(direction) might be rotating the boundaries of mob's collider through the ground in some cases. To prevent that, I recommend creating a lookDirection that keeps the character rotation flat when you do your RotateTowards:
Vector3 direction = (goal - mobcontroller.transform.position).normalized*2f;
if(mobcontroller.collisionFlags & CollisionFlags.CollidedBelow)
direction+= Vector3.down;
mobcontroller.Move(direction * Time.fixedDeltaTime);
Vector3 lookDirection = (goal - mobController.transform.position);
lookDirection.y = mobController.transform.y;
RotateTowards(lookDirection);
This problem happened when using Rigidbody and CharacterController on the mob. Removing Rigidbody from the mob solved this problem.

Categories