Sliding with collision using OBB - c#

In my C# 3D game:
I have a OBB (orientated bounding box) for my wall. My player is another tiny obb. I have good collision detection use SAT (separating axis theorem). When the collision occurs I have the old position of the player (where no collision occurred) and the position change the player experienced. To calculate the new position I use: Position = OldPosition + PositionChange; I also have a function which detects when I am colliding with any Wall in my world, It will return null if there is no collision and it will return an Wall if there is a collision. A Wall is a class I have added which has a model, OBB, and position. If (IntersectsAnyWall() == null) //no collision
That works well however I don't have sliding working. By sliding I mean when the player walks into a wall at an angle he slides along it instead of just stopping. Splitting the movement up into X and Z components and then testing both of them separately creates a jittering effect which is not what I want.
Can anyone give me a suggestion on how to approach sliding with OBB collision?

The wall can be modelled to stop only movements in the wall's normal's direction. In order to allow sliding, you have to find the wall's normal at the collision site. Furthermore, it would be a good idea to calculate, how deep the collision has taken place. Then all you have to do is subtract the "unpassable" part:
Position = OldPosition + PositionChange
if(IntersectsAnyWall() != null)
{
var normal = //wall's normal at collision site
var collisionDepth = ...;
Position += collisionDepth * normal;
}
If you can't calculate the collision depth, you may also subtract the entire part along the normal:
Position = OldPosition + PositionChange
if(IntersectsAnyWall() != null)
{
var normal = //wall's normal at collision site
var partAlongNormal = Vector2.Dot(PositionChange, -normal);
Position += partAlongNormal * normal;
}

Related

Unity calculate how much object moved on a given axis

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.

Trouble creating particle at a raycasts end/hit point for a moving object

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;
}
}

Detecting overlapping polygon2D colliders in Unity

I'm trying to program an isometric building placement script. Each building has a PolygonCollider2D component on it with a trigger. When placing a new building, I'm trying to check if the PolygonCollider2D of the placed building overlaps with anything else (to check if placement is valid). My code is as follows:
Adjust the points of the new placed building collider based on the mouse position
Vector2[] points = polygonCollider2D.points;
points.SetValue(polygonCollider2D.points[0] + (Vector2)mousePosition, 0);
points.SetValue(polygonCollider2D.points[1] + (Vector2)mousePosition, 1);
points.SetValue(polygonCollider2D.points[2] + (Vector2)mousePosition, 2);
points.SetValue(polygonCollider2D.points[3] + (Vector2)mousePosition, 3);
polygonCollider2D.points = points;
Set up contact filter:
ContactFilter2D contactFilter2D = new ContactFilter2D();
contactFilter2D.useTriggers = true;
contactFilter2D.SetLayerMask(polygonCollider2D.gameObject.layer);
Check for collisions
List<Collider2D> list = new List<Collider2D>();
Debug.Log(polygonCollider2D.OverlapCollider(contactFilter2D, list));
However if there is already a building there, it still does not register an overlap.
What am I missing / doing wrong ?
Thanks so much for the help!
Your code which sets the polygon collider points seems to be the issue here. If that code runs multiple times, it will repeatedly offset the collider further and further away from its original position. You probably don't want to be changing the actual collider; usually you should move object which has the collider. So you would replace those lines with something like this:
gameObject.transform.position = mousePosition;

Reflection of velocity no longer works after converting to 3D

I've been making a game based on Dragon Quest Heroes Rocket Slime's tank battle system for a while. First thing I need to get down is it's movement system where the player can stretch and sling themselves in a direction. When they hit a wall they should reflect off it realistically (eg: hitting it diagonally will reflect them diagonally)
My game was in 2D for the most part but figuring out pseudo-3d collision in a 2D space among other issues made it nearly impossible to continue so I moved it to fully 3d.
Everything worked well until I tried bouncing off a wall. If my player hits a wall going up, he goes down and vice versa. However, if my player hits a wall going left it tries to 'reflect' him left when it should reflect him right (and vice versa for hitting it while going right).
The way 'velocity' works in my game is that when stretching a Vector2 called pVelocity goes up based on how much they stretched. When they let go a 'endPos' is created based on currentPosition + pVelocity. The player will then move via Vector3.MoveTowards to that endPos at a constant speed. When hitting a wall I do this:
if (hit && foundHit.transform!=transform && curState != state.wallHit && foundHit.transform.tag!="Object")
{
//we've hit a wall while blasting
//now we make the player 'squish' up against the wall and bounce off!
Vector3 reflectedVelocity = Vector3.Reflect(new Vector3(pVelocity.x,0,pVelocity.y), foundHit.normal);
//playerAnimator.SetFloat("VelocityX", reflectedVelocity.x);
//playerAnimator.SetFloat("VelocityY", reflectedVelocity.y);
curState = state.wallHit;
playerSound.Play();
transform.position = foundHit.point;
playerAnimator.Play("Squish");
StartCoroutine(wallBounce(reflectedVelocity));
}
And in wallBounce, I do this:
IEnumerator wallBounce(Vector3 hitVelocity)
{
playerAnimator.enabled = true;
yield return new WaitForSeconds(.5f);
playerAnimator.Play("Walk blend tree");
pVelocity = new Vector2(hitVelocity.x,hitVelocity.z);
endPos = transform.position + (-transform.forward * pVelocity.magnitude);
curState = state.blasting;
Vector3 diff = endPos - transform.position;
diff.Normalize();
float rot_z = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0f, rot_z - 90, 0f);
}
When slinging, the player is always facing in the direction they're going so I assume that the issue is somewhere inside wallBounce when I create the new endPos but I'm not really sure how to fix it.
Seems the issue was that my player wasn't looking in the direction of movement when hitting left or right. Adding this in the part before I call MoveTowards worked.
transform.LookAt(endPos);

How can I rotate a vector (centripetal force)?

I'm trying to implement centripetal force in a programming language.
I saw some videos teaching the theory. But I dont know how to apply that in a programming language.
If I understand I have to apply centripetal force ac = v²/r to the velocity vector. But I dont know exactly how to proceed.
I have two game objects, one depicting Earth, other depicting Moon. What I wanted is to translate the moon around earth and using a button to "cut/cancel" the centripetal force in order to the moon get out to the earth's orbit.
I have no clue how to start that.
All I know is to rotate like this:
velocity.x = Mathf.Cos(Time.time) * earth_moon_radius;
velocity.z = Mathf.Sin(Time.time) * earth_moon_radius;
moon.transform.position = velocity;
But how to apply centripetal force as described above?
If you just want to have the moon rotating around earth and some trigger to release the moon, it's easier to use rotation around a center instead of forces. Given the following GameObject hierarchy:
Center (MoonRotator attached)
-- Moon
-- Earth
public class MoonRotator : MonoBehaviour
{
public bool cancelCentripetalForce = false;
public Vector3 angularVelocity = new Vector3 (0f, 0f, 100f);
public GameObject moon;
void Update () {
if (cancelCentripetalForce) {
Vector3 radius = moon.transform.position - transform.position;
Vector3 angularVelocityRadians = Mathf.Deg2Rad * angularVelocity;
moon.rigidbody.velocity = Vector3.Cross (angularVelocityRadians, radius);
moon.transform.parent = null;
Destroy (this);
} else {
Vector3 rot = transform.rotation.eulerAngles + angularVelocity * Time.deltaTime;
transform.rotation = Quaternion.Euler (rot);
}
}
}
If cancelCentripetalForce is set true Moon stops travelling around earth but proceeds with its current tangential velocity. This is given as:
v = ω × r
Earth has localPosition (0, 0, 0) and Moon is in this example located in the x-y plane rotating around the z axis.
If you want to cancel the force, add an opposing force vector that is based on your object's linear velocity and current direction.
So I have an object pointing straight along the Z axis. The object's 'forward' vector is 0, 0, 1. Do 1 - Math.abs(forward.x), same for y and z to get 1, 1, 0 when you're pointing forward along the Z axis. You want the direction you're pointing in to be 0'd so that the inertia from that direction is not damped in any way. Now you can apply a cancellation force in any direction that you are NOT pointed in.
This means that if your object is moving in any direction in world space that it's not facing in you can easily apply a cancellation force that is multiplied by the object's linear velocity to get circular motion that uses forces instead of directly setting velocity.
Another way you can do it is solve for V by manually setting it, then find radius using V=RW. You should know V and R or W. Now you can find the force necessary to keep the orbit stable around a point, rather than adding inertia from every frame.

Categories