531 lines
18 KiB
C#
531 lines
18 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)
|
|
{
|
|
TraceInfo traceInfo = new TraceInfo();
|
|
|
|
Vector3 delta = end - start;
|
|
float maxDistance = delta.Length;
|
|
Vector3 direction = delta.Normalized;
|
|
|
|
bool collided = false;
|
|
var capsuleCollider = Actor.GetChild<CapsuleCollider>();
|
|
var boxCollider = Actor.GetChild<BoxCollider>();
|
|
var meshCollider = Actor.GetChild<MeshCollider>();
|
|
PhysicsColliderActor colliderActor = null;
|
|
if (capsuleCollider && capsuleCollider.IsActive)
|
|
{
|
|
colliderActor = capsuleCollider;
|
|
collided = Physics.CapsuleCastAll(start,
|
|
capsuleCollider.Radius, capsuleCollider.Height,
|
|
direction, out traceInfo.hitInfos, capsuleCollider.Orientation, maxDistance,
|
|
uint.MaxValue,
|
|
false);
|
|
}
|
|
else if (meshCollider && meshCollider.IsActive)
|
|
{
|
|
colliderActor = meshCollider;
|
|
collided = Physics.ConvexCastAll(start,
|
|
meshCollider.CollisionData,
|
|
direction, out traceInfo.hitInfos, capsuleCollider.Orientation, maxDistance,
|
|
uint.MaxValue,
|
|
false);
|
|
}
|
|
else if (boxCollider && boxCollider.IsActive)
|
|
{
|
|
colliderActor = boxCollider;
|
|
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();
|
|
closest.Distance = float.MaxValue;
|
|
foreach (var hitInfo in traceInfo.hitInfos)
|
|
{
|
|
if (hitInfo.Collider == colliderActor)
|
|
continue;
|
|
|
|
hitInfosFiltered.Add(hitInfo);
|
|
|
|
if (hitInfo.Distance < closest.Distance)
|
|
closest = hitInfo;
|
|
}
|
|
|
|
traceInfo.hitInfos = hitInfosFiltered.ToArray();
|
|
|
|
if (closest.Distance > 0f)
|
|
{
|
|
traceInfo.fraction = closest.Distance / maxDistance;
|
|
traceInfo.hitNormal = closest.Normal;
|
|
traceInfo.hitPosition = closest.Point;
|
|
traceInfo.endPosition = start + (delta * traceInfo.fraction);
|
|
|
|
if (traceInfo.fraction == 0f && maxDistance > 0f)
|
|
traceInfo.startSolid = true;
|
|
}
|
|
else
|
|
{
|
|
traceInfo.startSolid = true;
|
|
traceInfo.fraction = 0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
traceInfo.hitInfos = new RayCastHit[0];
|
|
traceInfo.fraction = 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);
|
|
if (cylinderShape)
|
|
{
|
|
DebugDraw.DrawWireBox(capsuleCollider.Box, Color.GreenYellow * 0.8f);
|
|
}
|
|
|
|
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>();
|
|
|
|
for (int bump = 0; bump < 4; bump++)
|
|
{
|
|
Vector3 startPos = position;
|
|
Vector3 endPos = position + (velocity * timeleft);
|
|
float distt = (endPos - startPos).Length;
|
|
|
|
TraceInfo trace = TracePlayer(startPos, endPos);
|
|
// TODO: handle portals here
|
|
|
|
float fraction = trace.fraction;
|
|
Vector3 hitNormal = trace.hitNormal;
|
|
|
|
if (trace.startSolid)
|
|
{
|
|
velocity = Vector3.Zero;
|
|
break;
|
|
}
|
|
|
|
if (fraction > 0f)
|
|
{
|
|
position = trace.endPosition;
|
|
hitNormals.Clear(); // this is present in some forks, not in Q3
|
|
}
|
|
|
|
if (fraction >= 1f)
|
|
break;
|
|
|
|
timeleft *= 1.0f - fraction;
|
|
|
|
// this doesn't seem to do anything, we never have any hitNormals stored here
|
|
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);
|
|
if (hitNormals.Count != 1)
|
|
Console.Print("hitNormals: " + hitNormals.Count);
|
|
|
|
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;
|
|
}
|
|
|
|
// push off slightly away from the walls to not get stuck
|
|
position += normalMargin.Normalized * 0.031f;
|
|
|
|
if (plane == hitNormals.Count)
|
|
{
|
|
if (hitNormals.Count == 2)
|
|
{
|
|
Vector3 dir = Vector3.Cross(hitNormals[0], hitNormals[1]);
|
|
//dir.Normalize();
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
//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 bool cylinderShape = true;
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|