Collision correction pushing player in the wrong direction - c#

I'm currently working on a 2D game using an ECS with a character that's able to move left and right and make a small jump. I'm using a velocity component to control the player's movement, which is updated like this during every game tick:
private const float MaximumVerticalVelocity = 15f;
private const float VerticalAcceleration = 1f;
private const float MaximumHorizontalVelocity = 10f;
private const float HorizontalAcceleration = 1f;
private void move(GameTime gameTime, int entityID) {
float speed = HorizontalAcceleration;
var hitbox = (RectangleF) _colliderMapper.Get(entityID).Bounds;
var keyInputs = _inputMapper.Get(entityID);
var velocity = _velocityMapper.Get(entityID);
var position = _positionMapper.Get(entityID);
updateVelocity(velocity, 0, VerticalAcceleration);
if (Keyboard.GetState().IsKeyDown(keyInputs[PlayerAction.Jump]) &&
hitbox.Bottom < Game.Main.MapHeight && hitbox.Bottom > 0 && (
!Game.Main.TileIsBlank(position.X, hitbox.Bottom) ||
!Game.Main.TileIsBlank(hitbox.Right - 1, hitbox.Bottom)
)) {
velocity.DirY = -11f;
}
if (Keyboard.GetState().IsKeyDown(keyInputs[PlayerAction.Sprint])) {
speed *= 2;
}
bool leftDown = Keyboard.GetState().IsKeyDown(keyInputs[PlayerAction.Left]);
bool rightDown = Keyboard.GetState().IsKeyDown(keyInputs[PlayerAction.Right]);
if (leftDown && !(rightDown && velocity.DirX <= 0)) {
updateVelocity(velocity, -speed, 0);
}
else if (!leftDown && velocity.DirX <= 0) {
updateVelocity(velocity, speed, 0, 0, MaximumVerticalVelocity);
}
if (rightDown && !(leftDown && velocity.DirX >= 0)) {
updateVelocity(velocity, speed, 0);
}
else if (!rightDown && velocity.DirX >= 0) {
updateVelocity(velocity, -speed, 0, 0, MaximumVerticalVelocity);
}
if (!leftDown && !rightDown && velocity.DirX != 0) {
if (velocity.DirX < 0) {
updateVelocity(velocity, speed, 0, 0, MaximumVerticalVelocity);
}
else {
updateVelocity(velocity, -speed, 0, 0, MaximumVerticalVelocity);
}
}
position.X += velocity.DirX;
position.Y += velocity.DirY;
}
private void updateVelocity(Velocity velocity, float x, float y, float xLimit, float yLimit) {
if ((x >= 0 && velocity.DirX + x < xLimit) || (x < 0 && velocity.DirX + x > -xLimit)) {
velocity.DirX += x;
}
else {
if (x >= 0) {
velocity.DirX = xLimit;
}
else {
velocity.DirX = -xLimit;
}
}
if ((y >= 0 && velocity.DirY + y < yLimit) || (y < 0 && velocity.DirY + y > -yLimit)) {
velocity.DirY += y;
}
else {
if (y >= 0) {
velocity.DirY = yLimit;
}
else {
velocity.DirY = -yLimit;
}
}
}
private void updateVelocity(Velocity velocity, float x, float y) =>
updateVelocity(velocity, x, y, MaximumHorizontalVelocity, MaximumVerticalVelocity);
The player and every tile are in a collision system provided by the framework (MonoGame.Extended). All of them have rectangular hitboxes. This is the current code I'm using to resolve collisions when the player collides with a tile:
private void onCollision(int entityID, object sender, CollisionEventArgs args) {
if (args.Other is StaticCollider) {
var velocity = _velocityMapper.Get(entityID);
var position = _positionMapper.Get(entityID);
var collider = _colliderMapper.Get(entityID);
var intersection = collider.RectBounds.Intersection((RectangleF) args.Other.Bounds);
var otherBounds = (RectangleF) args.Other.Bounds;
if (intersection.Height > intersection.Width) {
if (collider.RectBounds.X < otherBounds.Position.X) {
position.X -= intersection.Width;
}
else {
position.X += intersection.Width;
}
velocity.DirX = 0;
}
else {
if (collider.RectBounds.Y < otherBounds.Y) {
position.Y -= intersection.Height;
}
else {
position.Y += intersection.Height;
}
velocity.DirY = 0;
}
collider.RectBounds.X = position.X;
collider.RectBounds.Y = position.Y;
}
}
The issue is that when the player jumps and lands on the tile in such a way that the width of the intersection is shorter than the height, the player is pushed sideways rather than upwards. (shown here and here) What do I do in this situation?

The line of code:
if (intersection.Height > intersection.Width)
Only works if the rectangles are:
Square: Check
Same size: Problem here.
Done properly his gives the following four collision zones, formed from the blue diagonals:
This is the actual test you are performing (not to scale):
The reason it moves to the right is the order the collision checks occur.
Solution
Since the aspect ratio is the same, width / height = 1, for both objects:
// ...
var adj = (float)collider.RectBounds.Width / otherBounds.Height;
if (intersection.Height * adj > intersection.Width) {
// ...
If they are not the same aspect ratio: Add a second adj2 variable:
// ...
var adj = (float)collider.RectBounds.Width / otherBounds.Height;
var adj2 = (float)otherBounds.Height / collider.RectBounds.Width;
if (intersection.Height * adj > intersection.Width * adj2) {
// ...
I will say that your programming style/approach/methodology, ECS, does not scale beyond small games.
In C# put the methods inside of the classes they will be used in or in a base class and derive classes from it, if they are not related use an interface.
Mappers are not needed, the just take up space and time being on the heap.
Function calls are expensive:
If a multiple calls to the same function, Keyboard.GetState() returns the same value store the value in a local variable.
Minimize the number of casts and dots, .: like args.Other.Bounds.
The parameter CollisionEventArgs args needs to be simplified to: Rectanglef otherBounds

Related

I need help on the math for detecting the "side" of collision on a rotating sprite

So I am making a 2D space shmup that handles combat in a naval way. So you shoot out the broadsides of the ship, your shields and hull are divided into 4 sections: Forward, Starboard, Port, and Rear. I am not the greatest with math, but I managed to find a script that detects the side of my polygon collider that was hit by say a collision or projectile. That all works great.
The problem is my sprite rotates to steer in 2D space. So when I collide with something say for example with the nose of my ship, if my ship's nose is up then the collision is detected properly. But if the ship is rotated and the nose is now on the left and I collide with something on the nose, the script will detect the nose collision as a port side collision instead. Could somebody help me with correcting the math to account for my ship's rotation?
Collision2DExtension.cs
using UnityEngine;
namespace PixelsoftGames
{
public static class Collision2DExtensions
{
public static Collision2DSideType GetContactSide(Vector2 max, Vector2 center, Vector2 contact)
{
Collision2DSideType side = Collision2DSideType.None;
float diagonalAngle = Mathf.Atan2(max.y - center.y, max.x - center.x) * 180 / Mathf.PI;
float contactAngle = Mathf.Atan2(contact.y - center.y, contact.x - center.x) * 180 / Mathf.PI;
if (contactAngle < 0)
{
contactAngle = 360 + contactAngle;
}
if (diagonalAngle < 0)
{
diagonalAngle = 360 + diagonalAngle;
}
if (
((contactAngle >= 360 - diagonalAngle) && (contactAngle <= 360)) ||
((contactAngle <= diagonalAngle) && (contactAngle >= 0))
)
{
side = Collision2DSideType.Starboard;
}
else if (
((contactAngle >= 180 - diagonalAngle) && (contactAngle <= 180)) ||
((contactAngle >= 180) && (contactAngle <= 180 + diagonalAngle))
)
{
side = Collision2DSideType.Port;
}
else if (
((contactAngle >= diagonalAngle) && (contactAngle <= 90)) ||
((contactAngle >= 90) && (contactAngle <= 180 - diagonalAngle))
)
{
side = Collision2DSideType.Forward;
}
else if (
((contactAngle >= 180 + diagonalAngle) && (contactAngle <= 270)) ||
((contactAngle >= 270) && (contactAngle <= 360 - diagonalAngle))
)
{
side = Collision2DSideType.Rear;
}
return side.Opposite();
}
static bool ranOnce = false;
public static Collision2DSideType GetContactSide(this Collision2D collision)
{
Vector2 max = collision.collider.bounds.max;
Vector2 center = collision.collider.bounds.center;
Vector2 contact = collision.GetContact(0).point;
if (!ranOnce)
{
ranOnce = true;
Debug.Log("Max: " + max);
Debug.Log("Center: " + center);
Debug.Log("Contact: " + contact);
}
return GetContactSide(max, center, contact);
}
}
}
Collision2DSideTypeExtensions.cs
namespace PixelsoftGames
{
public static class Collision2DSideTypeExtensions
{
public static Collision2DSideType Opposite(this Collision2DSideType sideType)
{
Collision2DSideType opposite;
if (sideType == Collision2DSideType.Port)
{
opposite = Collision2DSideType.Starboard;
}
else if (sideType == Collision2DSideType.Starboard)
{
opposite = Collision2DSideType.Port;
}
else if (sideType == Collision2DSideType.Forward)
{
opposite = Collision2DSideType.Rear;
}
else if (sideType == Collision2DSideType.Rear)
{
opposite = Collision2DSideType.Forward;
}
else
{
opposite = Collision2DSideType.None;
}
return opposite;
}
}
}
Collision2DSideType
public enum Collision2DSideType { None, Port, Starboard, Forward, Rear }
In the method GetContactSide you never get the rotation of your sprite, it is like your sprite angle is always 0
One solution for this is to add as a parameter the angle of your sprite to the method and add that angle to the the condition to determine wich side of the sprite it is
It can look like that :
public static class Collision2DExtensions
{
public static Collision2DSideType GetContactSide(Vector2 max, Vector2 center, Vector2 contact, float angle)
{
...
if (
((contactAngle >= (360 - diagonalAngle) + angle) && (contactAngle <= 360 + angle)) ||
((contactAngle <= diagonalAngle + angle) && (contactAngle >= 0 + angle))
)
...
`
I think you should do that for each of these conditions

Change animation based on mouse screen position in Unity 2D

I am developing an Isometric 2D game in Unity, using C# scripts. The character will be able to run in 8 different orientations.
I am trying to trigger a running animation depending on the mouse position.
My script is working fine but I don't think is the best way to face this problem.
First of all, I have an enum with the possible orientations:
public enum Orientations {N,NE,E,SE,S,SW,W,NW,NONE}
I wrote a method that returns an Orientations value based in a movement. This is because I want to trigger an animation based on the movement, so the Character will always be looking at the direction of the movement:
public static Orientations GetOrientation(Vector2 movement)
{
if (movement.x == 0 && movement.y == 1)
{
return Orientations.N;
}
else if (movement.x == 1 && movement.y == 0)
{
return Orientations.E;
}
else if (movement.x == 0 && movement.y == -1)
{
return Orientations.S;
}
else if (movement.x == -1 && movement.y == 0)
{
return Orientations.W;
}
else if (movement.x == -1 && movement.y == 1)
{
return Orientations.NW;
}
else if (movement.x == 1 && movement.y == 1)
{
return Orientations.NE;
}
else if (movement.x == -1 && movement.y == -1)
{
return Orientations.SW;
}
else if (movement.x == 1 && movement.y == -1)
{
return Orientations.SE;
}
return Orientations.NONE;
}
Next, I get the mouse angle between the character and the screen.
public static float GetMousePosition(Transform transform)
{
float cameraDistance = Camera.main.transform.position.y - transform.position.y;
Vector3 mousePosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, cameraDistance));
float angleRadius = Mathf.Atan2(mousePosition.y - transform.position.y, mousePosition.x - transform.position.x);
float angle = (180 / Mathf.PI) * angleRadius;
angle = (angle < 0) ? angle + 360 : angle;
return angle;
}
Then, I transform the angle in a Vector2, so I am able to switch between triggering animations by the character movement and mouse position:
public static Vector2 AngleToVectorDirection(Transform transform)
{
Vector2 direction = new Vector2(0,0);
float angle = GetMousePosition(transform);
if(angle >= 67.5 && angle < 112.5)
{
direction = new Vector2(0,1);
}
else if (angle >= 112.5 && angle < 157.5)
{
direction = new Vector2(-1,1);
}
else if (angle >= 157.5 && angle < 202.5)
{
direction = new Vector2(-1, 0);
}
else if (angle >= 202.5 && angle < 247.5)
{
direction = new Vector2(-1, -1);
}
else if (angle >= 247.5 && angle < 292.5)
{
direction = new Vector2(0, -1);
}
else if (angle >= 292.5 && angle < 337.5)
{
direction = new Vector2(1, -1);
}
else if (angle >= 337.5 || angle < 22.5)
{
direction = new Vector2(1, 0);
}
else if (angle >= 22.5 && angle < 67.5)
{
direction = new Vector2(1, 1);
}
return direction;
}
To finish, I return the Orientation as I mentioned:
public static Orientations GetOrientationByMovement(Transform transform, Vector2 movement)
{
Vector2 orientation;
if (!Input.GetButton("Fire1"))
{
orientation = movement;
}
else
{
orientation = AngleToVectorDirection(transform);
}
return GetOrientation(orientation);
}
This Orientation is received by an AnimationController script that triggers the animation.
I can not simply rotate the character, or flip sprite, or something like that because it is animation based.
Here you are doing the work twice. So in an optimisation point of view that not ideal but in a design point of view that can be good to separate the works. The question is where does the movement parameter come from in the GetOrientationByMovement method? can you use instead the orientation enum? if yes then that simplifies your code greatly!
You end up with :
public static Orientations AngleToVectorDirection(Transform transform)
{
float angle = GetMousePosition(transform);
if(angle >= 67.5 && angle < 112.5)
{
return Orientations.N;
}
else if (angle >= 112.5 && angle < 157.5)
{
return Orientations.NW;
}
else if (angle >= 157.5 && angle < 202.5)
{
return Orientations.W;
}
else if (angle >= 202.5 && angle < 247.5)
{
return Orientations.SW;
}
else if (angle >= 247.5 && angle < 292.5)
{
return Orientations.S;
}
else if (angle >= 292.5 && angle < 337.5)
{
return Orientations.SE;
}
else if (angle >= 337.5 || angle < 22.5)
{
return Orientations.E;
}
else if (angle >= 22.5 && angle < 67.5)
{
return Orientations.NE;
}
}
Also try to keep consistency in your code. if you use return value in the if statement do that for both function, in you use it outside do it everywhere.
Note that in your implementation of the AngleToVectorDirection you don't need to create a new vector in the beginning since you cover all the angle that can be.
Answering my own question:
With the just released unity's 3.8f1 patch, I've found in them demo project a way to trigger animations in a really simple way.
I am just using the code you can find in the official site:
https://blogs.unity3d.com/2019/03/18/isometric-2d-environments-with-tilemap/?_ga=2.120446600.1010886114.1552829987-288556513.1552829987
They use a IsometricCharacterRenderer script where use Animator.Play() passing as parameter a value of a string[], based on the player movement.
public static readonly string[] staticDirections = { "Static N", "Static NW", "Static W", "Static SW", "Static S", "Static SE", "Static E", "Static NE" };
public static readonly string[] runDirections = { "Run N", "Run NW", "Run W", "Run SW", "Run S", "Run SE", "Run E", "Run NE" };
public void SetDirection(Vector2 direction)
{
//use the Run states by default
string[] directionArray = null;
//measure the magnitude of the input.
if (direction.magnitude < .01f)
{
//if we are basically standing still, we'll use the Static states
//we won't be able to calculate a direction if the user isn't pressing one, anyway!
directionArray = staticDirections;
}
else
{
//we can calculate which direction we are going in
//use DirectionToIndex to get the index of the slice from the direction vector
//save the answer to lastDirection
directionArray = runDirections;
lastDirection = DirectionToIndex(direction, 8);
}
//tell the animator to play the requested state
animator.Play(directionArray[lastDirection]);
}
And to get the direction index, they convert the movement in an angle, just how I did, but in a smart way.
public static int DirectionToIndex(Vector2 dir, int sliceCount)
{
//get the normalized direction
Vector2 normDir = dir.normalized;
//calculate how many degrees one slice is
float step = 360f / sliceCount;
//calculate how many degress half a slice is.
//we need this to offset the pie, so that the North (UP) slice is aligned in the center
float halfstep = step / 2;
//get the angle from -180 to 180 of the direction vector relative to the Up vector.
//this will return the angle between dir and North.
float angle = Vector2.SignedAngle(Vector2.up, normDir);
//add the halfslice offset
angle += halfstep;
//if angle is negative, then let's make it positive by adding 360 to wrap it around.
if (angle < 0)
{
angle += 360;
}
//calculate the amount of steps required to reach this angle
float stepCount = angle / step;
//round it, and we have the answer!
return Mathf.FloorToInt(stepCount);
}

Line of Sight with Ray in XNA/Monogame

I'm trying to create a Line of Sight method for an enemy class. However, it always returns false, no matter how close the player is to the enemy or whether the ray passes through any blocks to get to the player.
public virtual bool PlayerInLOS()
{
Vector3 middleOfPlayer = new Vector3(Level.Player.Position.X, Level.Player.Position.Y - Level.Player.BoundingRectangle.Height / 2, 0);
Vector3 middleOfEnemy = new Vector3(Position.X, Position.Y - localBounds.Height / 2, 0);
Vector3 direction = middleOfPlayer - middleOfEnemy;
float distanceToPlayer = Vector3.Distance(middleOfEnemy, middleOfPlayer);
if (direction != Vector3.Zero)
direction.Normalize();
Ray lineOfSight = new Ray(middleOfEnemy, direction);
float? lineToPlayer = lineOfSight.Intersects(Level.Player.BoundingBox);
foreach (BoundingBox box in Level.boundingBoxes)
{
float? distanceToIntersect = lineOfSight.Intersects(box);
if (distanceToIntersect == null)
continue;
else if (distanceToIntersect < visionLength && distanceToIntersect < distanceToPlayer && distanceToIntersect != null)
return false;
}
// Never gets to this part because it always returns before it exits the for loop
if (lineToPlayer < visionLength)
return true;
else return false;
}
Any ideas? Thanks.
I ended up fixing the problem by implementing an entirely different solution: checking every Vector2 along the distance between the enemy and the player. Works perfectly.
public bool CanSeePlayer()
{
Vector2 middleOfPlayer = new Vector2(Level.Player.Position.X, Level.Player.Position.Y - Level.Player.BoundingRectangle.Height / 2);
Vector2 middleOfEnemy = new Vector2(Position.X, Position.Y - localBounds.Height / 2);
Vector2 direction = middleOfPlayer - middleOfEnemy;
float distanceToPlayer = Vector2.Distance(middleOfEnemy, middleOfPlayer);
if (visionLength > distanceToPlayer) // If the enemy can see farther than the player's distance,
{
if (direction != Vector2.Zero)
direction.Normalize();
for (int y = 0; y < Level.tiles.GetLength(1); ++y) // loop through every tile,
{
for (int x = 0; x < Level.tiles.GetLength(0); ++x)
{
if (Level.GetCollision(x, y) != TileCollision.Passable) // and if the block is solid,
{
Vector2 currentPos = middleOfEnemy;
float lengthOfLine = 0.0f;
Rectangle tileRect = new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height);
while (lengthOfLine < distanceToPlayer + 1.0f) // check every point along the line
{
currentPos += direction;
if (tileRect.Contains(currentPos)) // to see if the tile contains it.
{
return false;
}
lengthOfLine = Vector2.Distance(middleOfEnemy, currentPos);
}
}
}
}
// If every tile does not contain a single point along the line from the enemy to the player,
return true;
}
return false;
}
If you need to check if enemy is within angle of sight, and in some distance you could try this code.
public static bool InLOS(float AngleDistance, float PositionDistance, Vector2 PositionA, Vector2 PositionB, float AngleB)
{
float AngleBetween = (float)Math.Atan2((PositionA.Y - PositionB.Y), (PositionA.X - PositionB.X));
if ((AngleBetween <= (AngleB + (AngleDistance / 2f / 100f))) && (AngleBetween >= (AngleB - (AngleDistance / 2f / 100f))) && (Vector2.Distance(PositionA, PositionB) <= PositionDistance)) return true;
else return false;
}
credits: https://gamedev.stackexchange.com/questions/26813/xna-2d-line-of-sight-check

2D Character falls off the map in Unity3D

I am currently working on a project for one of the courses i am taking.
I am making a 2D game in unity3D, and i have a small problem, every time i run the game my character keeps on falling through the map, even though i have added a rigidbody2D and a boxCollider2D to both my character and the foreground. The code is attached, it is in C# and it is a bit long. Thank you so much in advance ..
enter code here
using System;
using UnityEngine;
using System.Collections;
public class CharacterController2D : MonoBehaviour
{
private const float SkinWidth = .02f;
private const int TotalHorizontalRays = 8;
private const int TotalVerticalRays = 4;
private static readonly float SlopeLimitTanget = Mathf.Tan (75f * Mathf.Deg2Rad);
public LayerMask PlatformMask;
public ControllerParameters2D DefaultParameters;
public ControllerState2D State { get; private set; }
public Vector2 Velocity { get { return _velocity; }}
public bool HandleCollisions { get; set; }
//Return overrideparamteres if it is not null, if it is null it will return DefaultParameters
public ControllerParameters2D Parameters { get { return _overrideParameters ?? DefaultParameters; } }
public GameObject StandingOn { get; private set;}
public Vector3 PlatformVelocity { get; private set;}
public bool CanJump
{
get
{
if(Parameters.JumpRestrictions == ControllerParameters2D.JumpBehavior.CanJumpAnywhere)
return _jumpIn <= 0;
if(Parameters.JumpRestrictions == ControllerParameters2D.JumpBehavior.CanJumpOnGround)
return State.IsGrounded;
return false;
}
}
private Vector2 _velocity;
private Transform _transform;
private Vector3 _localScale;
private BoxCollider2D _boxCollider;
private ControllerParameters2D _overrideParameters;
private float _jumpIn;
private GameObject _lastStandingOn;
private Vector3
_activeGlobalPlatformPoint,
_activeLocalPlatformPoint;
private Vector3
_raycastTopLeft,
_raycastBottomRight,
_raycastBottomLeft;
private float _verticalDistanceBetweenRays,
_horizonatalDistanceBetweenRays;
public void Awake()
{
HandleCollisions = true;
State = new ControllerState2D();
_transform = transform;
_localScale = transform.localScale;
_boxCollider = GetComponent <BoxCollider2D>();
// Absolute Value
var colliderWidth = _boxCollider.size.x * Mathf.Abs(transform.localScale.x) - (2 * SkinWidth);
_horizonatalDistanceBetweenRays = colliderWidth / (TotalVerticalRays - 1);
var colliderHeight = _boxCollider.size.y * Mathf.Abs( transform.localScale.y ) - (2 * SkinWidth);
_verticalDistanceBetweenRays = colliderHeight / (TotalHorizontalRays - 1);
}
public void AddForce(Vector2 force)
{
_velocity = force;
}
public void SetForce(Vector2 force)
{
_velocity += force;
}
public void SetHorizontalForce(float x)
{
_velocity.x = x;
}
public void SetVerticalForce(float y)
{
_velocity.y = y;
}
public void Jump()
{
AddForce(new Vector2(0, Parameters.JumpMagnitude));
_jumpIn = Parameters.JumpFrequency;
}
public void LateUpdate()
{
_jumpIn -= Time.deltaTime;
//We force the player to go up or down based on the gravity
_velocity.y += Parameters.Gravity * Time.deltaTime;
//Move the characther per his velocity scaled by time
Move (Velocity * Time.deltaTime);
}
// Ensures the player doesn't fall off the map or move through the wall
private void Move(Vector2 deltaMovement)
{
var wasGrounded = State.IsCollidingBelow;
State.Reset();
if(HandleCollisions)
{
HandlePlatforms();
CalculateRayOrigins();
if(deltaMovement.y < 0 && wasGrounded)
HandleVerticalSlope(ref deltaMovement);
if(Mathf.Abs(deltaMovement.x) > .001f)
MoveHorizontally(ref deltaMovement);
MoveVertically(ref deltaMovement);
CorrectHorizontalPlacement(ref deltaMovement, true);
CorrectHorizontalPlacement(ref deltaMovement, false);
}
_transform.Translate(deltaMovement, Space.World);
if (Time.deltaTime > 0)
_velocity = deltaMovement / Time.deltaTime;
_velocity.x = Mathf.Min (_velocity.x, Parameters.MaxVelocity.x);
_velocity.y = Mathf.Min (_velocity.y, Parameters.MaxVelocity.y);
if(State.IsMovingUpSlope)
_velocity.y = 0;
//Standing on the platform
if(StandingOn != null)
{
_activeGlobalPlatformPoint = transform.position;
_activeLocalPlatformPoint = StandingOn.transform.InverseTransformPoint(transform.position);
Debug.DrawLine(transform.position, _activeGlobalPlatformPoint);
Debug.DrawLine(transform.position, _activeLocalPlatformPoint + StandingOn.transform.position);
if(_lastStandingOn != StandingOn)
{
//If the last thing we are standing on is not null, send a message to leave it
if(_lastStandingOn != null)
_lastStandingOn.SendMessage("ControllerExist2D", this, SendMessageOptions.DontRequireReceiver);
//Inform what we are standing on that we have entered
StandingOn.SendMessage("ControllerEnter2D", this, SendMessageOptions.DontRequireReceiver);
_lastStandingOn = StandingOn;
}
//Invoke the platform that we are standing on it
else if (StandingOn != null)
StandingOn.SendMessage("ControllerStay2D", this, SendMessageOptions.DontRequireReceiver);
}
else if (_lastStandingOn != null)
{
_lastStandingOn.SendMessage("ControllerExit2D", this, SendMessageOptions.DontRequireReceiver);
_lastStandingOn = null;
}
}
private void HandlePlatforms()
{
//Calculate the velocity of the platform
if(StandingOn != null)
{
var newGlobalPlatformPoint = StandingOn.transform.TransformPoint(_activeLocalPlatformPoint);
var moveDistance = newGlobalPlatformPoint - _activeGlobalPlatformPoint;
//Sticks the player on the platform, wherever the platform teleport the players stays on it
if(moveDistance != Vector3.zero)
transform.Translate(moveDistance, Space.World);
PlatformVelocity = (newGlobalPlatformPoint - _activeGlobalPlatformPoint) / Time.deltaTime;
}
else
PlatformVelocity = Vector3.zero;
StandingOn = null;
}
private void CorrectHorizontalPlacement(ref Vector2 deltaMovement, bool isRight)
{
var halfwidth = (_boxCollider.size.x * _localScale.x) / 2f;
var rayOrigin = isRight ? _raycastBottomRight : _raycastBottomLeft;
if(isRight)
rayOrigin.x -= (halfwidth - SkinWidth);
else
rayOrigin.x += (halfwidth - SkinWidth);
var rayDirection = isRight ? Vector2.right : -Vector2.right;
var offset = 0f;
for(var i = 1; i <= TotalHorizontalRays - 1; i++)
{
var rayVector = new Vector2(deltaMovement.x + rayOrigin.x, deltaMovement.y + rayOrigin.y + (i * _verticalDistanceBetweenRays));
Debug.DrawRay(rayVector, rayDirection * halfwidth, isRight ? Color.cyan : Color.magenta);
var raycastHit = Physics2D.Raycast(rayVector, rayDirection, halfwidth, PlatformMask);
if(!raycastHit)
continue;
offset = isRight ? ((raycastHit.point.x - _transform.position.x) - halfwidth) : (halfwidth - (_transform.position.x - raycastHit.point.x));
}
deltaMovement.x += offset;
}
private void CalculateRayOrigins()
{
var size = new Vector2 (_boxCollider.size.x * Mathf.Abs (_localScale.x), _boxCollider.size.y * Mathf.Abs (_localScale.y)) / 2;
var center = new Vector2(_boxCollider.center.x * _localScale.x, _boxCollider.center.y * _localScale.y);
//Location of the player, then we add the box collider to it relative to the center of the player
_raycastTopLeft = _transform.position + new Vector3 (center.x - size.x + SkinWidth, center.y + size.y - SkinWidth);
_raycastBottomRight = _transform.position + new Vector3 (center.x + size.x - SkinWidth, center.y - size.y + SkinWidth); //Going right
_raycastBottomLeft = _transform.position + new Vector3 (center.x - size.x + SkinWidth, center.y - size.y + SkinWidth); //Going left and down-up
}
//Cast rays to the left or to the right depending on the player's movement
//Determining how far the player can go either to the left, or to the right
private void MoveHorizontally(ref Vector2 deltaMovement)
{
var isGoingRight = deltaMovement.x > 0;
//The distance between the starting point and the final destination
var rayDistance = Mathf.Abs (deltaMovement.x) + SkinWidth;
//Where is the player going? right or left
var rayDirection = isGoingRight ? Vector2.right : -Vector2.right;
//Right? we start from bottom right. Left? we start fro, bottom left
var rayOrigin = isGoingRight ? _raycastBottomRight : _raycastBottomLeft;
//Determines how many rays we want to shoot out to the left or to the right
for(var i = 0; i < TotalHorizontalRays; i++)
{
var rayVector = new Vector2(rayOrigin.x, rayOrigin.y + (i * _verticalDistanceBetweenRays));
//Visual representation about the rays
Debug.DrawRay(rayVector, rayDirection * rayDistance, Color.red);
//Checks if the player hit something or not
var rayCastHit = Physics2D.Raycast(rayVector, rayOrigin, rayDistance, PlatformMask);
if(!rayCastHit) //If there was a raycast then do something, otherwise continue to loop
continue;
//We return true if we are on a horizotnal slope, and check if we are going right or left or hit something while going up
if(i == 0 && HandleHorizontalSlope(ref deltaMovement, Vector2.Angle(rayCastHit.normal, Vector2.up), isGoingRight))
break;
//If we hit something then we can only go that far forward
deltaMovement.x = rayCastHit.point.x - rayVector.x;
rayDistance = Mathf.Abs(deltaMovement.x);
if(isGoingRight)
{
//If we are going right, then we have to substract the skinwidth
deltaMovement.x -= SkinWidth;
State.IsCollidingRight = true;
}
else
{
//The oppoiste of the if statement, if we are going left, we add the skinwidth
deltaMovement.x += SkinWidth;
State.IsCollidingLeft = true;
}
//Handles error collision, if the player hits something and go through it
if(rayDistance < SkinWidth + .0001f)
break;
}
}
private void MoveVertically(ref Vector2 deltaMovement)
{
//Check to see if going up or down
var isGoingUp = deltaMovement.y > 0;
var rayDistance = Mathf.Abs (deltaMovement.y) + SkinWidth;
var rayDirection = isGoingUp ? Vector2.up : -Vector2.up;
var rayOrigin = isGoingUp ? _raycastTopLeft : _raycastBottomLeft;
rayOrigin.x += deltaMovement.x;
var standingOnDistance = float.MaxValue;
for(var Count = 0; Count < TotalVerticalRays; Count++)
{
var rayVector = new Vector2(rayOrigin.x + (Count * _horizonatalDistanceBetweenRays), rayOrigin.y);
Debug.DrawRay(rayVector, rayDirection * rayDistance, Color.red);
var raycastHit = Physics2D.Raycast(rayVector, rayDirection, rayDistance, PlatformMask);
//If the player hit nothing then keep going.
if(raycastHit)
{
continue;
}
if(!isGoingUp)
{
var verticalDistanceToHit = _transform.position.y - raycastHit.point.y;
if(verticalDistanceToHit < standingOnDistance)
{
standingOnDistance = verticalDistanceToHit;
//Platform we are standing on
StandingOn = raycastHit.collider.gameObject;
}
}
//Determine the furthest distance we can move down or up without hitting anything
deltaMovement.y = raycastHit.point.y - rayVector.y;
rayDistance = Mathf.Abs(deltaMovement.y);
if(isGoingUp)
{
deltaMovement.y -= SkinWidth;
State.IsCollidingAbove = true;
}
else
{
deltaMovement.y += SkinWidth;
State.IsCollidingBelow = true;
}
if(!isGoingUp && deltaMovement.y > .0001f)
{
State.IsMovingUpSlope = true;
}
if(rayDistance < SkinWidth + .0001f)
{
break;
}
}
}
private void HandleVerticalSlope(ref Vector2 deltaMovement)
{
//Give us the center of the vertical rays;
var center = (_raycastBottomLeft.x + _raycastBottomRight.x) / 2;
var direction = -Vector2.up;
var slopeDistance = SlopeLimitTanget * (_raycastBottomRight.x - center);
var slopeRayVector = new Vector2 (center, _raycastBottomLeft.y);
Debug.DrawRay(slopeRayVector, direction * slopeDistance, Color.yellow);
var raycastHit = Physics2D.Raycast (slopeRayVector, direction, slopeDistance, PlatformMask);
if (!raycastHit)
return;
// ReSharper disable CompareOfFloatsByEqualityOperator
var isMovingDownSlope = Mathf.Sign (raycastHit.normal.x) == Mathf.Sign (deltaMovement.x);
if(!isMovingDownSlope)
return;
var angle = Vector2.Angle (raycastHit.normal, Vector2.up);
if(Mathf.Abs(angle) < .0001f)
return; //Which means there we are not on a slope, we are on something else
State.IsMovingDownSlope = true;
State.SlopeAngle = angle;
deltaMovement.y = raycastHit.point.y - slopeRayVector.y;
}
private bool HandleHorizontalSlope(ref Vector2 deltaMovement, float angle, bool isGoingRight)
{
//We do not want to move to an angle of 90
if(Mathf.RoundToInt(angle) == 90)
return false;
if(angle > Parameters.SlopeLimit)
{
deltaMovement.x = 0;
return true;
}
if(deltaMovement.y > .07f)
return true;
deltaMovement.x += isGoingRight ? -SkinWidth : SkinWidth;
deltaMovement.y = Mathf.Abs (Mathf.Tan (angle * Mathf.Deg2Rad) * deltaMovement.x);
State.IsMovingUpSlope = true;
State.IsCollidingBelow = true;
return true;
}
public void OnTriggerEnter2D(Collider2D other)
{
var parameters = other.gameObject.GetComponent<ControllerPhysicsVolume2D>();
if(parameters == null)
return;
_overrideParameters = parameters.Parameters;
}
public void OnTriggerExit2D(Collider2D other)
{
var parameters = other.gameObject.GetComponent<ControllerPhysicsVolume2D>();
if(parameters == null)
return;
_overrideParameters = null;
}
}
As #Terrance said you really don't need to write your own code logic for collision detection.
Secondly I noticed OnTriggerEnter2D and OnTriggerExit2D methods in your code, that also points out to one thing that all your boxCollider2d has isTrigger option checked. Therefore, I suggest you to uncheck isTrigger option on both your player and ground as TRIGGER will never stop two objects from crossing each other (you need to have both objects have 'isTrigger' unchecked on their colliders if you don't want them to pass through each other). And use method OnCollisionEnter2D and OnCollisionExit2D to detect the collision.
What is difference between Trigger and a Collider:
Taking real world example Colliders are tangible object e.g you yourself and the floor you are standing on both are solid and tangible.
While trigger is intangible, example of triggers can be the walkthrough security doors; these doors are hollow from inside allowing any person to pass without any hindrance, but if you wear any metal object on yourself and pass through that "Hollow Area" between the door, the door will TRIGGER an alarm. therefore with respect to the game world you can say that the door has a trigger at it hollow area.

Rotate angle to target angle via shortest side

Hi and thanks for reading.
I need to change this void I wrote so that it can work with negative angles.
The goal of this function is to rotate an ANGLE towards the DIRECTION by adding INCREMENT to either clockwise or counterclockwise (+ or -).
However the problem as I said is that it does not work with numbers less than 0 or greater than 360 (2pi). I need to be able to use negative angles as well.
I tried several stuff but couldn't get it to work for a while. Can anyone lend me a hand? I'll be grateful. :D
public void ToDirection(float Increment, float Direction)
{
if (CurrentAngle != Direction)
{
float ClockwiseDifference;
float CounterClockwiseDifference;
//Clockwise
if (Direction < CurrentAngle)
{
ClockwiseDifference = CurrentAngle - Direction;
}
else
{
ClockwiseDifference = Constants.Rotation_360 - (Direction - CurrentAngle);
}
//CounterClockwise
if (Direction > CurrentAngle)
{
CounterClockwiseDifference = Direction - CurrentAngle;
}
else
{
CounterClockwiseDifference = Constants.Rotation_360 - (CurrentAngle - Direction);
}
float CurrentFaceSpeed = Increment;
if (ClockwiseDifference == CounterClockwiseDifference)
{
if (Globals.Randomizer.Next(0, 2) == 0)
{
if (ClockwiseDifference < CurrentFaceSpeed)
{
CurrentAngle = Direction;
}
else
{
CurrentAngle -= CurrentFaceSpeed;
}
}
else
{
if (CounterClockwiseDifference < CurrentFaceSpeed)
{
CurrentAngle = Direction;
}
else
{
CurrentAngle += CurrentFaceSpeed;
}
}
}
else if (ClockwiseDifference < CounterClockwiseDifference)
{
if (ClockwiseDifference < CurrentFaceSpeed)
{
CurrentAngle = Direction;
}
else
{
CurrentAngle -= CurrentFaceSpeed;
}
}
else
{
if (CounterClockwiseDifference < CurrentFaceSpeed)
{
CurrentAngle = Direction;
}
else
{
CurrentAngle += CurrentFaceSpeed;
}
}
}
if (CurrentAngle >= Constants.Rotation_360)
{
CurrentAngle -= Constants.Rotation_360;
}
else if (CurrentAngle < 0)
{
CurrentAngle += Constants.Rotation_360;
}
}
Simply unwrap the angle. Then you'll always have angles from 0 to 360. Unwrap the starting angle and the target angle, then perform your turn. Here's a working example (the only method you really need is UnwrapAngle()).
internal class Program {
private static object UnwrapAngle(double angle) {
if (angle >= 0) {
var tempAngle = angle % 360;
return tempAngle == 360 ? 0 : tempAngle;
}
else
return 360 - (-1 * angle) % 360;
}
private static void TestUnwrap(double angle, double expected) {
Console.WriteLine(String.Format("{0} unwrapped = {1}, expected {2}", angle, UnwrapAngle(angle), expected));
}
private static void Main(string[] args) {
TestUnwrap(0, 0);
TestUnwrap(360, 0);
TestUnwrap(180, 180);
TestUnwrap(360 + 180, 180);
TestUnwrap(-270, 90);
TestUnwrap(-270 - 720, 90);
TestUnwrap(-725, 355);
Console.ReadLine();
}
}
This answer seems to cover the topic quite well:
Work out whether to turn clockwise or anticlockwise from two angles
RE: angles that are less than 0 or greater than 360. Basically, -10 is the same as 350. 720 is the same as 360. So if you translate the incoming angle so that it lies between 0 and 360, all your problems are solved (presuming your code works for values between 0 & 360 as you suggest).
This is something I've done myself (in a different language before):
var wantDir;
var currDir;
var directiondiff;
var maxTurn;
// want - this is your target direction \\
wantDir = argument0;
// max turn - this is the max number of degrees to turn \\
maxTurn = argument1;
// current - this is your current direction \\
currDir = direction;
if (wantDir >= (currDir + 180))
{
currDir += 360;
}
else
{
if (wantDir < (currDir - 180))
{
wantDir += 360;
}
}
directiondiff = wantDir - currDir;
if (directiondiff < -maxTurn)
{
directiondiff = -maxTurn
}
if (directiondiff > maxTurn)
{
directiondiff = maxTurn
}
// return the resultant directional change \\
return directiondiff

Categories