Files
GoakeFlax/Source/Game/PlayerMovement.cs
2021-04-30 07:27:41 +03:00

826 lines
29 KiB
C#

using System.Collections.Generic;
using FlaxEngine;
using Cabrito;
using System.Diagnostics;
using System.Threading.Tasks;
using FlaxEditor.Utilities;
namespace Game
{
public struct TraceInfo
{
public RayCastHit[] hitInfos;
public bool startSolid;
// closest hit
public float fraction;
public Vector3 endPosition;
public Vector3 hitNormal;
public Vector3 hitPosition;
// furthest hit
//public float maxFraction;
//public Vector3 maxHitNormal;
//public Vector3 maxEndPosition;
}
public class PlayerMovement : Script
{
[Limit(0, 9000), Tooltip("Base Movement speed")]
public float MoveSpeed { get; set; } = 320;
private float viewPitch;
private float viewYaw;
private float viewRoll;
private InputEvent onExit = new InputEvent("Exit");
Actor rootActor;
public override void OnAwake()
{
base.OnAwake();
onExit.Triggered += () =>
{
if (Console.IsSafeToQuit)
Engine.RequestExit();
};
rootActor = Actor.GetChild(0);
}
public override void OnDestroy()
{
base.OnDestroy();
onExit.Dispose();
}
public override void OnStart()
{
var initialEulerAngles = Actor.Orientation.EulerAngles;
viewPitch = initialEulerAngles.X;
viewYaw = initialEulerAngles.Y;
viewRoll = initialEulerAngles.Z;
}
private TraceInfo TracePlayer(Vector3 start, Vector3 end)
{
bool collided = false;
TraceInfo traceInfo = new TraceInfo();
var capsuleCollider = Actor.GetChild<CapsuleCollider>();
var boxCollider = Actor.GetChild<BoxCollider>();
PhysicsColliderActor colliderActor = null;
Vector3 delta = end - start;
float maxDistance = delta.Length;
Vector3 direction = delta.Normalized;
if (capsuleCollider && capsuleCollider.IsActive)
{
colliderActor = capsuleCollider;
//start.Y -= capsuleCollider.Height / 2;
//start.Y -= capsuleCollider.Radius / 2;
/*collided = Physics.BoxCastAll(start,
new Vector3(capsuleCollider.Radius,
(capsuleCollider.Height + capsuleCollider.Radius) / 2,
capsuleCollider.Radius),
delta.Normalized, out hitInfos, Quaternion.Identity, delta.Length, uint.MaxValue,
false);*/
/*traceInfo.startSolid = Physics.OverlapCapsule(start, capsuleCollider.Radius, capsuleCollider.Height,
out Collider[] results, capsuleCollider.Orientation, uint.MaxValue, false);
if (traceInfo.startSolid)
{
foreach (Collider collider in results)
{
if (collider == colliderActor)
continue;
colliderActor = colliderActor;
}
}
else*/
{
collided = Physics.CapsuleCastAll(start,
capsuleCollider.Radius, capsuleCollider.Height,
direction, out traceInfo.hitInfos, capsuleCollider.Orientation, maxDistance,
uint.MaxValue,
false);
}
}
else if (boxCollider && boxCollider.IsActive)
{
colliderActor = boxCollider;
//start.Y += boxCollider.Size.Y / 2;
/*traceInfo.startSolid = Physics.CheckBox(start, boxCollider.OrientedBox.Extents, boxCollider.Orientation,
uint.MaxValue, false);
if (!traceInfo.startSolid)*/
{
collided = Physics.BoxCastAll(start,
boxCollider.OrientedBox.Extents,
direction, out traceInfo.hitInfos, boxCollider.Orientation, maxDistance, uint.MaxValue,
false);
}
}
if (collided)
{
List<RayCastHit> hitInfosFiltered = new List<RayCastHit>();
RayCastHit closest = new RayCastHit();
RayCastHit furthest = new RayCastHit();
closest.Distance = float.MaxValue;
furthest.Distance = float.MinValue;
foreach (var hitInfo in traceInfo.hitInfos)
{
if (hitInfo.Collider == colliderActor)
continue;
hitInfosFiltered.Add(hitInfo);
if (hitInfo.Distance < closest.Distance)
closest = hitInfo;
if (hitInfo.Distance > furthest.Distance)
furthest = hitInfo;
}
traceInfo.hitInfos = hitInfosFiltered.ToArray();
//if (!closest.Normal.IsZero)
if (closest.Distance > 0f)
{
//traceInfo.fraction = (closest.Point - start).Length / maxDistance;
//traceInfo.maxFraction = (furthest.Point - start).Length / maxDistance;
/*if (traceLength != 0f)
{
traceInfo.fraction = closest.Distance / traceLength;
traceInfo.maxFraction = furthest.Distance / traceLength;
}
else
{
traceInfo.fraction = 0f;
traceInfo.maxFraction = 0f;
}*/
// convoluted shit incoming...
// trace a ray back from the hitpoints to calculate the actual fraction travelled...
Physics.RayCastAll(closest.Point, -direction, out RayCastHit[] rayHitInfos, maxDistance * 2f,
uint.MaxValue, true);
float altFraction = 0f;
float oldFraction = closest.Distance / maxDistance;
foreach (var hitInfo in rayHitInfos)
{
if (hitInfo.Collider != colliderActor)
continue;
traceInfo.fraction = hitInfo.Distance / maxDistance;
altFraction = (closest.Point - hitInfo.Point).Length / maxDistance;
break;
}
traceInfo.hitNormal = closest.Normal;
traceInfo.hitPosition = closest.Point;
traceInfo.endPosition = start + (direction * traceInfo.fraction);
//traceInfo.maxHitNormal = furthest.Normal;
//traceInfo.hitPosition = furthest.Point;
//traceInfo.maxEndPosition = start + ((end - start).Normalized * traceInfo.maxFraction);
if (traceInfo.fraction == 0f && maxDistance > 0f)
traceInfo.startSolid = true;
}
else
{
traceInfo.startSolid = true;
traceInfo.fraction = 0f;
}
/*traceInfo.hitInfos = hitInfosFiltered.ToArray();
float travel = (end - start).Length;
if (travel != 0f)
{
traceInfo.fraction = closest.Distance / travel;
traceInfo.maxFraction = furthest.Distance / travel;
}
else
{
traceInfo.fraction = 0f;
traceInfo.maxFraction = 0f;
}
traceInfo.hitNormal = closest.Normal;
traceInfo.hitPosition = closest.Point;
traceInfo.endPosition = start + ((end - start).Normalized * traceInfo.fraction);
traceInfo.maxHitNormal = furthest.Normal;
//traceInfo.hitPosition = furthest.Point;
traceInfo.maxEndPosition = start + ((end - start).Normalized * traceInfo.maxFraction);
if (traceInfo.fraction == 0f && travel > 0f)
traceInfo.startSolid = true;*/
}
else
{
traceInfo.hitInfos = new RayCastHit[0];
traceInfo.fraction = traceInfo.startSolid ? 0f : 1f;
traceInfo.endPosition = end;
}
return traceInfo;
}
public override void OnDebugDraw()
{
base.OnDebugDraw();
var capsuleCollider = Actor.GetChild<CapsuleCollider>();
var boxCollider = Actor.GetChild<BoxCollider>();
var rigidBody = Actor.As<RigidBody>();
if (capsuleCollider && capsuleCollider.IsActive)
{
Quaternion rotation = capsuleCollider.LocalOrientation * Quaternion.Euler(0f, 90f, 0f);
DebugDraw.DrawWireTube(rigidBody.Position, rotation, capsuleCollider.Radius, capsuleCollider.Height, Color.GreenYellow * 0.8f);
}
else if (boxCollider && boxCollider.IsActive)
{
DebugDraw.DrawWireBox(boxCollider.OrientedBox.GetBoundingBox(), Color.GreenYellow * 0.8f);
}
}
private void SlideMove(ref Vector3 position, bool stepUp, ref Vector3 velocity, bool asdf = false)
{
if (velocity.IsZero)
return;
Vector3 originalPosition = position;
Vector3 originalVelocity = velocity;
float timeleft = Time.DeltaTime;
List<Vector3> hitNormals = new List<Vector3>();
// margin in fte: 0.0312 away from the wall
Vector3 traceOffset = Vector3.Zero;
for (int bump = 0; bump < 4; bump++)
{
Vector3 startPos = position;
Vector3 endPos = position + (velocity * timeleft);
TraceInfo trace = TracePlayer(startPos + traceOffset, endPos);
// TODO: handle portals here
float fraction = trace.fraction;
Vector3 hitNormal = trace.hitNormal;
Vector3 hitEndPosition = trace.endPosition;
/*if (bump > 0 && trace.startSolid)
{
fraction = trace.maxFraction;
hitNormal = trace.maxHitNormal;
hitEndPosition = trace.maxEndPosition;
}*/
if (fraction > 0f)
{
position = hitEndPosition;
hitNormals.Clear();
/*if (position.Z > 368f)
{
position = position;
TracePlayer(startPos + traceOffset, endPos);
}*/
}
else if (trace.startSolid)
{
velocity = Vector3.Zero;
break;
}
if (fraction >= 1f)
break;
timeleft -= timeleft * fraction;
bool hitPreviousNormal = false;
foreach (Vector3 normal in hitNormals)
{
if (Vector3.Dot(hitNormal, normal) > 0.99)
{
// nudge away from the same wall we hit earlier and try again
velocity += hitNormal;
hitPreviousNormal = true;
break;
}
}
if (hitPreviousNormal)
continue;
hitNormals.Add(hitNormal);
/*
bool stuck = true;
foreach (Vector3 normal in hitNormals)
{
// clip velocity
Vector3 velocityProjected = Vector3.ProjectOnPlane(velocity, normal);
float backoff = Vector3.Dot(velocity, normal) * 1f;
velocity -= normal * backoff;
//velocity = velocityProjected;
bool parallel = true;
foreach (Vector3 otherNormal in hitNormals)
{
if (otherNormal == normal)
continue;
if (Vector3.Dot(velocity, otherNormal) < 0f)
{
parallel = false;
break;
}
}
if (parallel)
{
// moving away from all touched walls
stuck = false;
break;
}
}
if (stuck)
{
if (hitNormals.Count == 2)
{
Vector3 dir = Vector3.Cross(hitNormals[0], hitNormals[1]);
float dist = Vector3.Dot(dir, velocity);
velocity = dist * dir;
}
else
{
velocity = Vector3.Zero;
break;
}
}
else
{
// nudge very slightly away from the wall to avoid getting stuck
//position += trace.hitNormal * 0.01f;
}
*/
int plane;
Vector3 normalMargin = Vector3.Zero;
for (plane = 0; plane < hitNormals.Count; plane++)
{
Vector3 normal = hitNormals[plane];
// clip velocity
velocity = velocity - normal * Vector3.Dot(velocity, normal);
//velocity = Vector3.ProjectOnPlane(velocity, normal);
//traceOffset = normal * 1f;
normalMargin += normal;
//position += normal * 0.031f;
int plane2;
for (plane2 = 0; plane2 < hitNormals.Count; plane2++)
{
if (plane == plane2)
continue;
if (Vector3.Dot(velocity, hitNormals[plane2]) < 0f)
break;
}
if (plane2 == hitNormals.Count)
break;
}
position += normalMargin.Normalized * 0.031f;
if (plane == hitNormals.Count)
{
if (hitNormals.Count == 2)
{
Vector3 dir = Vector3.Cross(hitNormals[0], hitNormals[1]);
float dist = Vector3.Dot(dir, velocity);
velocity = dist * dir;
}
else
{
velocity = Vector3.Zero;
break;
}
}
else
{
// nudge very slightly away from the wall to avoid getting stuck
//position += trace.hitNormal * 0.01f;
//velocity += trace.hitNormal * 0.01f;
}
// prevents bouncing against the wall
if (/*velocity.Length > 0f && */Vector3.Dot(velocity, originalVelocity) <= 0f)
{
velocity = Vector3.Zero;
break;
}
}
if (!asdf)
{
/*var finalTrace = TracePlayer(position, position);
if (finalTrace.startSolid)
{
position = originalPosition;
velocity = originalVelocity;
SlideMove(ref position, false, ref velocity, true);
}*/
}
}
private bool SlideMove2(ref Vector3 position, bool stepUp, ref Vector3 velocity)
{
// PM_SlideMove
/*
endpos = pos + velocity
for i=0, i<4, i++
trace(pos, endpos)
if trace.dist >= 1
break;
blocked =
blocked_floor => trace.plane.normal[2] >= MIN_STEP_NORMAL
blocked_step => trace.plane.normal[2] == 0
blocked_other => .
*/
if (velocity.IsZero)
return false;
Vector3 originalVelocity = velocity;
Vector3 startPos = position;
//float distanceLeft = (velocity * Time.DeltaTime).Length;
float timeLeft = Time.DeltaTime;
bool anyCollisions = false;
for (int i = 0; i < 3; i++)
{
Vector3 normal = Vector3.Zero;
//float distance = distanceLeft;
Vector3 direction = velocity.Normalized;
//Vector3 endPos = startPos + (direction * distanceLeft);
Vector3 endPos = startPos + (velocity * timeLeft);
float travel = (endPos - startPos).Length;
//if (endPos == startPos)
// break;
TraceInfo trace = TracePlayer(startPos, endPos);
bool collided = trace.hitInfos.Length > 0;
float fraction = 1.0f;
float distance = 0f;
if (trace.startSolid)
{
velocity = Vector3.Zero;
anyCollisions = true;
break;
}
if (collided)
{
collided = false;
if (trace.hitInfos.Length > 1)
Console.Print("fixme more traces!");
foreach (var hitInfo in trace.hitInfos)
{
//if (hitInfo.Distance < minDistanceFromFloor)
// continue;
//if (hitInfo.Distance > distance)
// Console.Print("hit distance is greater: " + hitInfo.Distance.ToString() + " > " + distance.ToString());
fraction = hitInfo.Distance / travel;
collided = true;
//if (hitInfo.Distance <= distance)
{
normal = hitInfo.Normal;
distance = hitInfo.Distance;
}
break;
//Console.Print("collided " + hitInfo.Distance.ToString());
//break;
}
}
if (collided)
{
// move to contact position, continue from here
startPos += direction * distance; // what if this points to different direction?
//distanceLeft -= distance;
timeLeft *= fraction;
//Console.Print("timeleft: " + timeLeft.ToString() + ", frac:" + fraction.ToString());
//velocity = Vector3.ProjectOnPlane(velocity, normal);
Vector3 velocityProjected = Vector3.ProjectOnPlane(velocity, normal);
float backoff = Vector3.Dot(velocity, normal) * 1f;
velocity -= normal * backoff;
velocity = velocityProjected;
//Console.Print((velocityProjected-velocity).Length.ToString());
// prevents bouncing against the wall
if (velocity.Length > 0f && Vector3.Dot(velocity, originalVelocity) <= 0f)
{
velocity = Vector3.Zero;
//distanceLeft = 0f;
timeLeft = 0f;
}
}
else
{
//distanceLeft = 0f;
timeLeft = 0f;
startPos = endPos;
}
anyCollisions |= collided;
if (timeLeft <= 0f)//if (distanceLeft <= 0f)
break;
}
position = startPos;
return anyCollisions;
}
//Vector3 wishVelocity = Vector3.Zero;
Vector3 gravityVelocity = Vector3.Zero;
Vector3 currentVelocity = Vector3.Zero;
private bool onGround = false;
private const float friction = 4f;
private const float stopspeed = 100f;
private const float accelerationGround = 10f;
private const float jumpVelocity = 270f;
private const float maxAirSpeed = 320f;
private const float maxAirStrafeSpeed = 30f; //Q2+
private const float airAcceleration = 0.4f * 0f; //Q2+
private const float airStopAcceleration = 2.5f * 0f; //Q2+
private const float airStrafeAcceleration = 70f * 0f; //CPM?
private const float strafeAcceleration = 10f; //QW
private const float airControl = 0f; //CPM
private bool jumped = false;
private Vector3 safePosition;
public override void OnFixedUpdate()
{
var rigidBody = Actor.As<RigidBody>();
var rootTrans = rootActor.Transform;
var inputDirection = new Vector3(InputManager.GetAxis("Horizontal"), 0.0f, InputManager.GetAxis("Vertical"));
var moveDirection = rootTrans.TransformDirection(inputDirection);
Vector3 wishVelocity = Vector3.Zero;
if (!inputDirection.IsZero)
wishVelocity = moveDirection.Normalized * MoveSpeed;
// categorize position
onGround = true;
//TraceInfo traceGround = TracePlayer(rigidBody.Position, rigidBody.Position + Physics.Gravity.Normalized);
/*if (!traceGround.startSolid && traceGround.fraction < 1f &&
-Vector3.Dot(Physics.Gravity.Normalized, traceGround.hitNormal) < 0.7f)
{
// slope
// clip velocity
Vector3 bounce = Physics.Gravity.Normalized;
//Vector3 velocityProjected = Vector3.ProjectOnPlane(velocity, normal);
float backoff = Vector3.Dot(bounce, traceGround.hitNormal) * 2f;
bounce -= traceGround.hitNormal * backoff;
//velocity = velocityProjected;
Vector3 point = (rigidBody.Position + Physics.Gravity.Normalized) +
(1f - traceGround.fraction) * bounce;
// retrace
traceGround = TracePlayer(rigidBody.Position, rigidBody.Position + point,
false);
}
if (!traceGround.startSolid && (traceGround.fraction >= 1f ||
-Vector3.Dot(Physics.Gravity.Normalized, traceGround.hitNormal) < 0.7f))
{
// falling or sliding down a slope
onGround = false;
}
else
{
onGround = !traceGround.startSolid;
}
if (onGround)
{
// snap to ground
if (!traceGround.startSolid)
{
Vector3 newPos = rigidBody.Position;
if (traceGround.fraction < 1f)
{
//newPos += -Physics.Gravity.Normalized * traceGround.fraction;
}
rigidBody.Position = newPos;
}
}*/
/*if (traceGround.startSolid)
{
Console.Print("stuk: ");
rigidBody.Position = safePosition;
traceGround = TracePlayer(rigidBody.Position, rigidBody.Position + Physics.Gravity.Normalized,
false);
//onGround = true;
//currentVelocity.Y = 0f;
}
if (!traceGround.startSolid)
{
foreach (var hitInfo in traceGround.hitInfos)
{
var dot = Vector3.Dot(Physics.Gravity.Normalized, hitInfo.Normal);
if (-dot >= 0.7) //~45deg slope
{
//Console.Print("d: " + hitInfo.Distance);
Vector3 newPos = rigidBody.Position;
if (hitInfo.Distance > 0f)
newPos += Physics.Gravity.Normalized * (hitInfo.Distance - 0.01f);
else
newPos += hitInfo.Normal * 0.1f;
rigidBody.Position = newPos;
onGround = true;
currentVelocity.Y = 0f;
break;
//if (currentVelocity.Length > 0.01f)
// Console.Print("groundvel: " + currentVelocity.ToString());
//currentVelocity.Y = 0.0f;
}
}
}*/
// jump
if (onGround)
{
if (!jumped && InputManager.GetAction("Jump"))
{
//jumped = true;
currentVelocity += Vector3.Up * jumpVelocity;
onGround = false;
}
else if (jumped) // jump released
jumped = false;
}
// ground friction
if (onGround)
{
float currentSpeed = currentVelocity.Length;
float control = currentSpeed < stopspeed ? stopspeed : currentSpeed;
var drop = control * friction * Time.DeltaTime;
float newspeed = currentSpeed - drop;
if (newspeed < 0)
newspeed = 0;
if (currentSpeed < 0.0001f)
currentVelocity *= 0;
else
currentVelocity *= newspeed / currentSpeed;
}
//bool stepUp = false;
if (onGround) // ground acceleration
{
ApplyAcceleration(wishVelocity.Normalized, wishVelocity.Length, float.MaxValue, accelerationGround);
}
else // air acceleration
{
var wishspeed = wishVelocity.Length;
if (wishspeed > maxAirSpeed)
wishspeed = maxAirSpeed;
if (strafeAcceleration != 0f)
ApplyAcceleration(wishVelocity.Normalized, wishspeed, maxAirStrafeSpeed, strafeAcceleration);
//stepUp = true;
}
if (!onGround)
{
//currentVelocity += Physics.Gravity * Time.DeltaTime;
//Console.Print("grav");
}
//else
// Console.Print("Yv: " + currentVelocity.Y);
Vector3 newPosition = rigidBody.Position;
SlideMove(ref newPosition, false, ref currentVelocity);
safePosition = rigidBody.Position;
rigidBody.Position = newPosition;
//if (currentVelocity.Length > 0.0f)
// rigidBody.Position += currentVelocity * Time.DeltaTime;
rigidBody.LinearVelocity = Vector3.Zero;
//if (currentVelocity.Length > 0.01f)
// Console.Print("vel: " + currentVelocity.ToString());
}
void ApplyAcceleration(Vector3 wishDir, float wishspeed, float maxWishspeed, float acceleration)
{
float wishspeedOrig = wishspeed;
if (wishspeed > maxWishspeed)
wishspeed = maxWishspeed;
float currentSpeed = Vector3.Dot(currentVelocity, wishDir);
float addSpeed = wishspeed - currentSpeed;
if (addSpeed <= 0f)
return;
float accelSpeed = acceleration * wishspeedOrig * Time.DeltaTime;
if (accelSpeed > addSpeed)
accelSpeed = addSpeed;
currentVelocity += accelSpeed * wishDir;
}
public override void OnUpdate()
{
float xAxis = InputManager.GetAxisRaw("Mouse X");
float yAxis = InputManager.GetAxisRaw("Mouse Y");
if (xAxis != 0.0f || yAxis != 0.0f)
{
var camera = rootActor.GetChild<Camera>();
viewPitch += yAxis;
viewYaw += xAxis;
viewPitch = Mathf.Clamp(viewPitch, -90.0f, 90.0f);
// root orientation must be set first
rootActor.Orientation = Quaternion.Euler(0, viewYaw, 0);
camera.Orientation = Quaternion.Euler(viewPitch, viewYaw, viewRoll);
}
}
}
}