using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace Game.Player.Character.Movers.Custom
{
[RequireComponent(typeof(BoxCollider2D))]
public class CharacterMover2D : MonoBehaviour
{
[SerializeField] private int _maxVerticalRays = 3;
[SerializeField] private int _maxHorizontalRays = 3;
[SerializeField] private float _skinWidth = 0.1f;
[SerializeField] private LayerMask _collisionMask;
public CollisionData Collisions;
private BoxCollider2D _collider;
private ColliderStats _colliderStats;
private void Awake()
{
_collider = GetComponent<BoxCollider2D>();
_colliderStats = CalculateColliderStats();
}
public void Move(Vector2 velocity)
{
MoveHorizontal(velocity.x);
MoveVertical(velocity.y);
}
private void MoveHorizontal(float velX)
{
Collisions.Left = false;
Collisions.Right = false;
if (Mathf.Abs(velX) < 0.0001f) return;
float xDist = Mathf.Abs(velX);
int xDir = (int) Mathf.Sign(velX);
RayHitData hits = CastHorizontalRays(xDist, xDir);
if (hits.Nearest.HasValue)
{
transform.position
= transform.position + new Vector3((hits.Nearest.Value.distance - _skinWidth) * xDir, 0, 0);
}
else
transform.position = transform.position + new Vector3(xDist * xDir, 0, 0);
}
private void MoveVertical(float velY)
{
Collisions.Below = false;
Collisions.Above = false;
float yDist = Mathf.Abs(velY) + _skinWidth;
int yDir = (int) Mathf.Sign(velY);
RayHitData hits = CastVerticalRays(yDist, yDir);
if (hits.Nearest.HasValue)
{
transform.position = transform.position + new Vector3(0, (hits.Nearest.Value.distance - _skinWidth) * yDir, 0);
}
else
{
transform.position = transform.position + new Vector3(0, yDist * yDir, 0);
}
}
private RayHitData CastVerticalRays(float yDist, int yDir)
{
float raySegmentSep = _collider.size.x / (_maxVerticalRays - 1);
float colliderLeft = transform.position.x + _colliderStats.LeftDistance;
float colliderBottom = transform.position.y + _colliderStats.BottomDistance;
float colliderTop = transform.position.y + _colliderStats.TopDistance;
float colliderYOrigin = (yDir == 1) ? colliderTop + _skinWidth : colliderBottom - _skinWidth;
var hits = new List<RaycastHit2D>();
for (int i = 0; i < _maxVerticalRays; i++)
{
Vector2 rayOrigin = new Vector2(colliderLeft + (i * raySegmentSep), colliderYOrigin);
RaycastHit2D hitInfo = Physics2D.Raycast(rayOrigin, Vector2.up * yDir, yDist, _collisionMask);
Debug.DrawLine(rayOrigin, rayOrigin + Vector2.up * (yDist * yDir), Color.red, 0.16f);
if (hitInfo.collider != null)
{
hits.Add(hitInfo);
}
}
RaycastHit2D? nearest = null;
if (hits.Count > 0)
{
foreach (RaycastHit2D hit in hits)
{
if (!nearest.HasValue || hit.distance < nearest.Value.distance) nearest = hit;
}
if (yDir == 1) Collisions.Above = true;
else if (yDir == -1) Collisions.Below = true;
}
return new RayHitData
{
Hits = hits,
Nearest = nearest
};
}
private RayHitData CastHorizontalRays(float xDist, int xDir)
{
float raySegmentSep = _collider.size.x / (_maxHorizontalRays - 1);
float colliderRight = transform.position.x + _colliderStats.RightDistance;
float colliderLeft = transform.position.x + _colliderStats.LeftDistance;
float colliderTop = transform.position.y + _colliderStats.TopDistance;
float colliderXOrigin = (xDir == 1) ? colliderRight + _skinWidth : colliderLeft - _skinWidth;
var hits = new List<RaycastHit2D>();
for (int i = 0; i < _maxVerticalRays; i++)
{
Vector2 rayOrigin = new Vector2(colliderXOrigin, colliderTop - (i * raySegmentSep));
RaycastHit2D hitInfo = Physics2D.Raycast(rayOrigin, Vector2.right * xDir, xDist, _collisionMask);
Debug.DrawLine(rayOrigin, rayOrigin + Vector2.right * (xDist * xDir), Color.red, 0.16f);
if (hitInfo.collider != null)
{
hits.Add(hitInfo);
}
}
RaycastHit2D? nearest = null;
if (hits.Count > 0)
{
foreach (RaycastHit2D hit in hits)
{
if (!nearest.HasValue || hit.distance < nearest.Value.distance) nearest = hit;
}
if (xDir == 1) Collisions.Right = true;
else if (xDir == -1) Collisions.Left = true;
}
return new RayHitData
{
Hits = hits,
Nearest = nearest
};
}
/// <summary>
/// This should be called anytime the colliders' size changes.
/// </summary>
private ColliderStats CalculateColliderStats()
{
ColliderStats newStats;
newStats.RightDistance = _collider.bounds.size.x / 2;
newStats.LeftDistance = -newStats.RightDistance;
newStats.TopDistance = _collider.bounds.size.y / 2;
newStats.BottomDistance = -newStats.TopDistance;
return newStats;
}
}
internal struct RayHitData
{
public RaycastHit2D? Nearest;
public List<RaycastHit2D> Hits;
}
public struct CollisionData
{
public bool Right;
public bool Left;
public bool Below;
public bool Above;
}
public struct ColliderStats
{
public float RightDistance;
public float LeftDistance;
public float TopDistance;
public float BottomDistance;
}
}
Could be buggy. Doesnt do slope stuff either, but maybe a good base for someone wanting to do a Raycast 2D controller
Be the first to comment
You can use [html][/html], [css][/css], [php][/php] and more to embed the code. Urls are automatically hyperlinked. Line breaks and paragraphs are automatically generated.