Preface and Acknowledgements
Placeholder: Preface notes and acknowledgements for the project.
Unity Game Engine Basics
Placeholder: Introduction to Unity and setting up the project.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : PhysObject
{
// Start is called before the first frame update
new void Start()
{
base.Start();
}
// Update is called once per frame
new void Update()
{
base.Update();
transform.rotation = physManager.mainCamera.transform.rotation;
}
new void FixedUpdate()
{
base.FixedUpdate();
}
}
Platformer Game Design Challenges
Placeholder: Common challenges in designing platformer gameplay.
Basic Physics and Movement
Placeholder: Core movement, gravity, and collision handling.
public void ApplyGravity()
{
//There is no gravity during a dash
if (dashing || justReleasedNovaNode) { return; }
//Instantiate the gravity magnitude variable
float gravityMagnitude = gravityCoef * physManager.gravityMagnitude;
//Set the max fall speed that gravity can apply
float maxGravityFall = maxFallSpeed;
if (wallSliding) { maxGravityFall = maxWallFallSpeed; }
//Increase the force of gravity if upward speed exceed a certain threshold
Vector3 verticalVelocityVector = Vector3.ProjectOnPlane(rb.velocity, Right);
//Only increase the gravity if the velocity is in the upwards direction
if (Vector3.Dot(verticalVelocityVector, Up) > 0f)
{
float verticalSpeed = verticalVelocityVector.magnitude;
//Only increase the gravity if the speed exceeds a threshold
if (verticalSpeed >= extraGravityJumpSpeedThreshold)
{
//if the speed is faster than the max lerp window then just set the gravity coef to the max
if (verticalSpeed >= extraGravityJumpSpeedMax) { gravityMagnitude *= extraFrictionMaxCoef; }
else
{
//If the speed is in the lerp window then lerp and add
float gravityTValue = (verticalSpeed - extraGravityJumpSpeedThreshold) / (extraGravityJumpSpeedMax - extraGravityJumpSpeedThreshold);
gravityMagnitude *= Mathf.Lerp(extraGravityMinCoef, extraGravityMaxCoef, gravityTValue);
}
}
}
ApplyForceVectorClampedToPlaneMaxSpeed(Down, Right, maxGravityFall, gravityMagnitude * Time.deltaTime, false);
}
Key Trigonometric Concepts
Placeholder: Applying trigonometry to movement and physics.
public void ApplyForceVectorClampedToPlaneMaxSpeed(Vector3 forceDirection, Vector3 planeNormal, float maxSpeed, float forceMagnitude, bool negateAntagonistic = true, float antagonisticBonusCoef = 1f)
{
//Get the Vector of just the force relative to the camera and the plane normal
Vector3 projectedVector = Vector3.ProjectOnPlane(rb.velocity, planeNormal);
//float projectedVectorMagnitude = projectedVector.magnitude;
//Determine rather or not the current movement is in the positive direction from the perspective of the camera and the force normal
bool currentVectorIsPositive = true;
float projectedDotProduct = Vector3.Dot(projectedVector, forceDirection);
if (projectedDotProduct < -physManager.dotProductReverseThreshold){ currentVectorIsPositive = false; }
//Determine if the input is reversing the current movement
bool reversing = true;
if (((forceMagnitude > 0) && (currentVectorIsPositive)) || ((forceMagnitude < 0) && (!currentVectorIsPositive))){ reversing = false; }
//Determine the change in the velocity
float velocityDelta = forceMagnitude;
if (reversing) { velocityDelta *= antagonisticBonusCoef; }
//Determine if the new velocity will exceed the max movement speed
Vector3 clampedForce = forceDirection * velocityDelta;
//Reduce the force based on existing antagonistic forces
if (negateAntagonistic) { clampedForce = ApplyForceAgainstAntagonisticForces(clampedForce); }
//Dewtermine what the new velocity would be if the force was actually applied
Vector3 newVelocity = rb.velocity + clampedForce;
//Determine what the new velocity vector would be in terms of the given plane normal
Vector3 newProjectedVector = Vector3.ProjectOnPlane(newVelocity, planeNormal);
float newProjectedVectorMagnitude = newProjectedVector.magnitude;
//If the new velocity will exceed the max movement speed then clamp it so that it will only go the max speed
if (newProjectedVectorMagnitude >= maxSpeed)
{
//If the phys object is already moving faster than the max speed in the opposite direction of the force
//Then do not clamp or adjust the force
//Only adjust the force if it is congruent with the current movement
if (Vector3.Dot(newProjectedVector, clampedForce) > 0)
{
//Get the vector of the potential max speed
Vector3 goalProjectedVelocity = forceDirection * maxSpeed;
if (velocityDelta < 0) { goalProjectedVelocity = -goalProjectedVelocity; }
//Simply subtract to get a potential new force
Vector3 potentiallyNewClampedForce = goalProjectedVelocity - projectedVector;
//Ensure that the clamping did not reverse the direction of the original force
if (Vector3.Dot(potentiallyNewClampedForce, clampedForce) <= 0)
{
//Reduce the force to nothing if it has to reverse its direction in order to meet the max speed
clampedForce = new Vector3(0, 0, 0);
}
else
{
//Use the new clamped force if the force direction was not reversed
clampedForce = potentiallyNewClampedForce;
}
}
}
//Actually Apply The Force
rb.AddForce(clampedForce, ForceMode.VelocityChange);
//Debug.DrawLine(transform.position, transform.position + (clampedForce*3), Color.white);
}
Applying Perspective to Inputs and Physics
Placeholder: Using perspective shifts to affect gameplay mechanics.
public void CheckLeftRight()
{
if ((dashing)||(usingGravityGun)) { return; }
float movementMagnitude = inputManager.leftRight.value;
if (Mathf.Abs(movementMagnitude) < inputManager.movementInputMin) { movementMagnitude = 0; }
//Simulate movement inputs during wall jumps to make them more consistent
if ((wallJumping)&&(simulateMovementDuringWallJumps))
{
if (wallJumpedToTheRight)
{
if (((inputManager.inputDirection == Direction.Up) || (inputManager.inputDirection == Direction.RightUp))
&& (inputManager.leftRight.value > -overideSimInputThreshold))
{
movementMagnitude = 1;
}
}
else
{
if (((inputManager.inputDirection == Direction.Up) || (inputManager.inputDirection == Direction.LeftUp))
&& (inputManager.leftRight.value < overideSimInputThreshold))
{
movementMagnitude = -1;
}
}
}
if (movementMagnitude != 0)
{
if (normalizeMovementInput)
{
if (movementMagnitude > 0) { movementMagnitude = 1; }
else { movementMagnitude = -1; }
}
MoveLeftRight(movementMagnitude);
}
}
Coyote Time
Placeholder: Adding a buffer window to make jumps more forgiving.
public void InititateReverseCoyoteTime()
{
inReverseCoyoteTime = true;
currentReverseCoyoteTime = 0f;
}
public void EndReverseCoyoteTime()
{
inReverseCoyoteTime = false;
currentReverseCoyoteTime = 0f;
}
public void TickReverseCoyoteTime()
{
if (!inReverseCoyoteTime) { return; }
if (currentReverseCoyoteTime >= reverseCoyoteTime)
{
EndReverseCoyoteTime();
return;
}
currentReverseCoyoteTime += Time.deltaTime;
}
Wall Jumping
Placeholder: Implementing wall detection and jump mechanics.
public void CheckJump()
{
if (dashing)
{
if (jumping) { EndJump(); }
if (inputManager.jump.pressed) { jumpAfterDash = true; }
return;
}
//Check if a jump is ending
if ((!inputManager.jump.held) && (jumping)) { EndJump(); return; }
//Prioritize jumping off of the ground
//Check if a jump off the ground is occuring
if ((inputManager.jump.pressed || inReverseCoyoteTime) && (canJump) && (!jumping)) { Jump(); return; }
//Check if a jump is currently occuring
//Prioritize holding a jump after prioritzing a jump starting
if ((inputManager.jump.held) && (jumping))
{
if (wallJumping) { HoldWallJump(); return; }
else { HoldJump(); return; }
}
//If no other jump is occuring, check for a wall jump
if ((inputManager.jump.pressed || inReverseCoyoteTime) && (!wallJumping))
{
//Determine which wall to jump off of... if you are near a wall
bool wallJumpLeftAvailable = false;
bool wallJumpRightAvailable = false;
if (nearWallLeft && !leftWallJumpOnCooldown) { wallJumpLeftAvailable = true; }
if (nearWallRight && !rightWallJumpOnCooldown) { wallJumpRightAvailable = true; }
//If you are within range to jump off of either the left wall or the right wall then determine which wall to choose
if ((wallJumpLeftAvailable) && (wallJumpRightAvailable))
{
//If you are equally close to both walls, do a tie break
//A tie break occilates between choosing the left wall and the right wall
if (collisionDistanceRightWall == collisionDistanceLeftWall)
{
if (tieBreakRightWall) { JumpOffWall(true); return; }
else { JumpOffWall(false); return; }
}
//If closer to the left wall then jump off left wall
if (collisionDistanceLeftWall < collisionDistanceRightWall) { JumpOffWall(false); return; }
else { JumpOffWall(true); return; } //If closer to the right wall then jump off right wall
}
//If just near the left wall
if (wallJumpLeftAvailable) { JumpOffWall(false); return; }
//If just near the right wall
if (wallJumpRightAvailable) { JumpOffWall(true); return; }
//If not near the walls, check for coyote times
//If coyote time is occcuring for both sides simultaneously, then defer to tie break
wallJumpLeftAvailable = false;
wallJumpRightAvailable = false;
if (canWallJumpOffLeftWall && !leftWallJumpOnCooldown) { wallJumpLeftAvailable = true; }
if (canWallJumpOffRightWall && !rightWallJumpOnCooldown) { wallJumpRightAvailable = true; }
if ((wallJumpLeftAvailable)&&(wallJumpRightAvailable))
{
if (tieBreakRightWall) { JumpOffWall(true); return; }
else { JumpOffWall(false); return; }
}
//If still in coyote time for left wall
if (wallJumpLeftAvailable) { JumpOffWall(false); return; }
//If still in coyote time for right wall
if (wallJumpRightAvailable) { JumpOffWall(true); return; }
}
//Initiate reverse coyote time if no other jumps are available
if (inputManager.jump.pressed) { InititateReverseCoyoteTime(); }
}
Dashing
Placeholder: Creating a dash mechanic with movement bursts and cooldowns.
public void Dash()
{
//Get the vector of the dash force
Vector3 dashForce = inputManager.UpDownLeftRight(true);
//If there is no input direction then default the dash to the direction that the player is facing
if ((dashForce.x == 0) && (dashForce.y == 0))
{
if (facing == Direction.Left) { dashForce = Left; }
else { dashForce = Right; }
}
previousDashVector = dashForce;
//Multiply the normalized vector by the dash force
dashForce *= initialDashForce;
//Adjust the x values of the dash force
//The x values are adjusted because the dash force is set relative to gravity, with no gravity in the x direction a different value must be used
//Project the dash force onto the x plane via the up vector
Vector3 dashForceXOnly = Vector3.ProjectOnPlane(dashForce, Up);
//Multiply that vector by the dash bonus coeficient
Vector3 dashXBonusVector = (dashForceXOnly * dashXBonusCoef) - dashForceXOnly;
//Add that new vector with the x bonus to the original dash force
dashForce += dashXBonusVector;
Vector3 dashForceNormalized = dashForce.normalized;
//Adjust the dash if it is in the downwards direction
float downwardsMagnitude = Vector3.Dot(dashForceNormalized, Down);
if (downwardsMagnitude > 0.15f)
{
if (downwardsMagnitude < 0.8f) { dashForce *= dashDownDiagonalBonusCoef; }
else { dashForce *= dashDownBonusCoef; }
}
InitiateDash();
//dashSaveVelocity = rb.velocity;
rb.velocity = new Vector3(0, 0, 0);
//Debug.Log(dashForce);
rb.AddForce(dashForce, ForceMode.VelocityChange);
}
public void InitiateDash()
{
dashing = true;
jumpAfterDash = false;
canDash = false;
inDashBonus = true;
currentDashTime = 0f;
}
public void EndDash()
{
InitiateDashCooldown();
InitiateDashBonusTime();
dashing = false;
//rb.velocity = dashSaveVelocity;
rb.velocity = rb.velocity * postDashSpeedCoef;
currentDashTime = 0f;
if (jumpAfterDash){ inputManager.jump.SpoofInput(1,true);}
jumpAfterDash = false;
}
public void TickDash()
{
if (!dashing) { return; }
if (currentDashTime >= dashTime)
{
EndDash();
}
currentDashTime += Time.deltaTime;
}
public void InitiateDashCooldown()
{
dashOnCooldown = true;
currentDashCooldown = 0f;
}
public void EndDashCooldown()
{
dashOnCooldown = false;
currentDashCooldown = 0f;
}
public void TickDashCooldown()
{
if (!dashOnCooldown) { return; }
if (currentDashCooldown >= dashCooldown)
{
EndDashCooldown();
}
currentDashCooldown += Time.deltaTime;
}
public void InitiateDashBonusTime()
{
inDashBonus = true;
currentDashBonusTime = 0f;
}
public void EndDashBonusTime()
{
inDashBonus = false;
currentDashBonusTime = 0f;
}
public void TickDashBonusTime()
{
if (!inDashBonus) { return; }
if (currentDashBonusTime >= dashMaxSpeedBonusOvertime)
{
EndDashBonusTime();
}
currentDashBonusTime += Time.deltaTime;
}
Grappling Hook
Placeholder: Designing and coding a grappling hook ability.
public void GetNearestGravityNode()
{
nearestGravityNode = physManager.GetNearestNode(transform.position);
}
public bool TryInitiateGravityGunUse()
{
if (usingGravityGun) { return true; }
if (nearestGravityNode == null) { return false; }
Debug.Log("Getting New Node");
Vector3 vectorToGravityNode = nearestGravityNode.transform.position - transform.position;
float distance = vectorToGravityNode.magnitude;
if (distance > nearestGravityNode.range)
{
return false;
}
else
{
grappledGravityNode = nearestGravityNode;
goalDistanceFromGravityNode = distance;
enteredNovaNodeThisFrame = true;
return true;
}
}
public void ApplyGravityNodeEffects()
{
if ((!usingGravityGun) || (grappledGravityNode == null)) { return; }
justReleasedNovaNode = false;
Vector3 gravityNodeToPlayer = transform.position - grappledGravityNode.transform.position;
float distanceToPlayer = gravityNodeToPlayer.magnitude;
//Check if out of range of the gravity gun
if (distanceToPlayer > grappledGravityNode.range + novaNodeReleaseDistanceBuffer)
{
EndGravityGunUse();
return;
}
//Get the goal Velocity vector for the swing
Vector3 goalVelocity = Vector3.ProjectOnPlane(rb.velocity, gravityNodeToPlayer);
float goalVelocityMagnitude = goalVelocity.magnitude;
//Initialize the swing if it started this frame
if (enteredNovaNodeThisFrame)
{
float previousVelocityMagnitude = rb.velocity.magnitude;
novaNodeEnterVelocityMagnitude = previousVelocityMagnitude;
float magnitudeDif = previousVelocityMagnitude - goalVelocityMagnitude;
if (magnitudeDif > 0)
{
goalVelocityMagnitude += magnitudeDif * novaNodeEnterKeepSpeedCoef;
}
enteredNovaNodeThisFrame = false;
}
//Add bonus swing based on movement inputs
Vector3 movementInputVector = inputManager.UpDownLeftRight(false, true);
float movementAcceleration = (novaNodeBaseAcceleration + (novaNodeEnterVelocityMagnitude * novaNodeEnterSpeedBonusAccelerationCoef)) * Time.deltaTime;
float inputToGoalVeloDot = Vector3.Dot(movementInputVector, goalVelocity);
if (inputToGoalVeloDot > 0) //Accelerate if analog movement is towards the goal velocity
{
goalVelocityMagnitude += movementAcceleration;
}
else if (inputToGoalVeloDot < 0)//Otherwise Deccelerate
{
goalVelocityMagnitude -= movementAcceleration;
}
//Recreate the velocity vector
goalVelocity = goalVelocity.normalized * goalVelocityMagnitude;
Vector3 grapplingForceVector = goalVelocity - rb.velocity;
rb.AddForce(grapplingForceVector, ForceMode.VelocityChange);
//Adjust for swinging in or out
if (distanceToPlayer != goalDistanceFromGravityNode)
{
Vector3 targetLocation = (gravityNodeToPlayer.normalized * goalDistanceFromGravityNode) + grappledGravityNode.transform.position;
Vector3 adjustmentForce = targetLocation - transform.position;
float trueDistanceFromTargetLocation = Mathf.Abs(distanceToPlayer - goalDistanceFromGravityNode);
if ( trueDistanceFromTargetLocation > 0.1f)
{
adjustmentForce *= (trueDistanceFromTargetLocation + 1f) * 2 * (rb.velocity.magnitude * Time.deltaTime);
}
rb.AddForce(adjustmentForce, ForceMode.VelocityChange);
}
}
public void InitiateNovaNodeNoAntagonisticCooldown()
{
justReleasedNovaNode = true;
currentNovaNodeNoAntagonisticTime = 0f;
}
public void TickNovaNodeNoAntagonisticCooldown()
{
if (!justReleasedNovaNode) { return; }
//Debug.DrawLine(transform.position, transform.position + rb.velocity, Color.white);
if (currentNovaNodeNoAntagonisticTime > novaNodeReleaseNoAntagonisticTime)
{
EndNovaNodeNoAntagonisticCooldown();
}
currentNovaNodeNoAntagonisticTime += Time.deltaTime;
}
public void EndNovaNodeNoAntagonisticCooldown()
{
justReleasedNovaNode = false;
currentNovaNodeNoAntagonisticTime = 0f;
}
public void EndGravityGunUse()
{
enteredNovaNodeThisFrame = false;
usingGravityGun = false;
InitiateNovaNodeNoAntagonisticCooldown();
}
public void DrawGravityGunLine()
{
lineRenderer.enabled = true;
if (grappledGravityNode != null)
{
Vector3[] newVerticies = new Vector3[2];
newVerticies[0] = transform.position;
newVerticies[1] = grappledGravityNode.transform.position;
lineRenderer.SetPositions(newVerticies);
lineRenderer.endWidth = 0.18f;
lineRenderer.startWidth = 0.11f;
}
}
public void HideGravityGunLine()
{
lineRenderer.enabled = false;
}
public void CheckGravityGunJump()
{
//If you have upward momentum, allow a jump
if (Vector3.Dot(rb.velocity, Up) > 0f)
{
NeutralizeCoyoteTime();
canJump = true;
StartCoyoteTime();
}
}
Play Testing and Refining
Placeholder: Iterating based on testing and player feedback.
public void NegateMomentumInGivenDirection(Vector3 negationDirection)
{
//Check if any momentum is even going in the negation Direction
if (Vector3.Dot(negationDirection, rb.velocity) > 0)
{
Vector3 newVelocity = Vector3.ProjectOnPlane(rb.velocity, negationDirection);
rb.velocity = newVelocity;
}
}
public void NegateDownwardMomentum()
{
NegateMomentumInGivenDirection(Down);
}
public Vector3 GetAntagonisticForceVectorClampedToPlane(Vector3 forceDirection, Vector3 planeNormal, float antagonisticForceMagnitude, bool noRelatedInput = false, float noRelatedInputBonusCoef = 1f, bool uniDirectional = false)
{
//Get the Vector of just the movement relative to the camera and the plane normal
Vector3 projectedVector = Vector3.ProjectOnPlane(rb.velocity, planeNormal);
//Determine rather or not the current movement is in the positive direction from the perspective of the camera and the force normal
bool currentVectorIsPositive = true;
float projectedDotProduct = Vector3.Dot(projectedVector, forceDirection);
if (projectedDotProduct < -physManager.dotProductReverseThreshold) { currentVectorIsPositive = false; }
//If the antagonistic force is only allowed to slow in a single direction then exit the function if the movement is in the opposite direction
if ((uniDirectional) && (currentVectorIsPositive)) { Debug.Log("MovementInOppositeDirection"); return new Vector3(0,0,0); }
//Determine the change in the velocity
float velocityDelta = antagonisticForceMagnitude;
if (noRelatedInput) { velocityDelta *= noRelatedInputBonusCoef; }
//Determine if the new velocity will exceed the max movement speed
Vector3 clampedForce = forceDirection * velocityDelta;
//Reverse the force of the antagonistic vector if the current direction is positive
if (currentVectorIsPositive) { clampedForce = -clampedForce; }
Vector3 newVelocity = rb.velocity + clampedForce;
Vector3 newProjectedVector = Vector3.ProjectOnPlane(newVelocity, planeNormal);
//Determine rather or not the new movement is in the positive direction from the perspective of the camera and the force normal
bool newVectorIsPositive = true;
float newProjectedDotProduct = Vector3.Dot(newProjectedVector, forceDirection);
if (newProjectedDotProduct < -physManager.dotProductReverseThreshold){ newVectorIsPositive = false; }
//Determine if the antagonistic force would change the direction of the object.
//This should not be allowed since antagonistic forces would not change directions of forces, merely slow them
if (currentVectorIsPositive != newVectorIsPositive)
{
//Debug.DrawLine(transform.position, transform.position - (projectedVector), Color.black, 0.2f);
//Debug.DrawLine(transform.position + new Vector3(0,0.5f,0), transform.position + new Vector3(0, 0.5f, 0) + (clampedForce), Color.red);
return -projectedVector;
}
//Actually Apply The Force
//Debug.DrawLine(transform.position, transform.position + (clampedForce*3), Color.red);
return clampedForce;
}
public void ApplyAntagonisticForceVectorClampedToPlane(Vector3 forceDirection, Vector3 planeNormal, float antagonisticForceMagnitude, bool noRelatedInput = false, float noRelatedInputBonusCoef = 1f, bool uniDirectional = false)
{
//Get the Vector of just the movement relative to the camera and the plane normal
Vector3 projectedVector = Vector3.ProjectOnPlane(rb.velocity, planeNormal);
//Determine rather or not the current movement is in the positive direction from the perspective of the camera and the force normal
bool currentVectorIsPositive = true;
float projectedDotProduct = Vector3.Dot(projectedVector, forceDirection);
if (projectedDotProduct < -physManager.dotProductReverseThreshold){ currentVectorIsPositive = false; }
//If the antagonistic force is only allowed to slow in a single direction then exit the function if the movement is in the opposite direction
if ((uniDirectional) && (currentVectorIsPositive)) { Debug.Log("MovementInOppositeDirection"); return; }
//Determine the change in the velocity
float velocityDelta = antagonisticForceMagnitude;
if (noRelatedInput) { velocityDelta *= noRelatedInputBonusCoef; }
//Determine if the new velocity will exceed the max movement speed
Vector3 clampedForce = forceDirection * velocityDelta;
//Reverse the force of the antagonistic vector if the current direction is positive
if (currentVectorIsPositive) { clampedForce = -clampedForce; }
Vector3 newVelocity = rb.velocity + clampedForce;
Vector3 newProjectedVector = Vector3.ProjectOnPlane(newVelocity, planeNormal);
//Determine rather or not the new movement is in the positive direction from the perspective of the camera and the force normal
bool newVectorIsPositive = true;
float newProjectedDotProduct = Vector3.Dot(newProjectedVector, forceDirection);
if (newProjectedDotProduct < -physManager.dotProductReverseThreshold){ newVectorIsPositive = false; }
//Determine if the antagonistic force would change the direction of the object.
//This should not be allowed since antagonistic forces would not change directions of forces, merely slow them
if (currentVectorIsPositive != newVectorIsPositive)
{
rb.AddForce(-projectedVector, ForceMode.VelocityChange);
return;
}
//Actually Apply The Force
rb.AddForce(clampedForce, ForceMode.VelocityChange);
}
public void ApplyForceVectorClampedToPlaneMaxSpeed(Vector3 forceDirection, Vector3 planeNormal, float maxSpeed, float forceMagnitude, bool negateAntagonistic = true, float antagonisticBonusCoef = 1f)
{
//Get the Vector of just the force relative to the camera and the plane normal
Vector3 projectedVector = Vector3.ProjectOnPlane(rb.velocity, planeNormal);
//float projectedVectorMagnitude = projectedVector.magnitude;
//Determine rather or not the current movement is in the positive direction from the perspective of the camera and the force normal
bool currentVectorIsPositive = true;
float projectedDotProduct = Vector3.Dot(projectedVector, forceDirection);
if (projectedDotProduct < -physManager.dotProductReverseThreshold){ currentVectorIsPositive = false; }
//Determine if the input is reversing the current movement
bool reversing = true;
if (((forceMagnitude > 0) && (currentVectorIsPositive)) || ((forceMagnitude < 0) && (!currentVectorIsPositive))){ reversing = false; }
//Determine the change in the velocity
float velocityDelta = forceMagnitude;
if (reversing) { velocityDelta *= antagonisticBonusCoef; }
//Determine if the new velocity will exceed the max movement speed
Vector3 clampedForce = forceDirection * velocityDelta;
//Reduce the force based on existing antagonistic forces
if (negateAntagonistic) { clampedForce = ApplyForceAgainstAntagonisticForces(clampedForce); }
//Dewtermine what the new velocity would be if the force was actually applied
Vector3 newVelocity = rb.velocity + clampedForce;
//Determine what the new velocity vector would be in terms of the given plane normal
Vector3 newProjectedVector = Vector3.ProjectOnPlane(newVelocity, planeNormal);
float newProjectedVectorMagnitude = newProjectedVector.magnitude;
//If the new velocity will exceed the max movement speed then clamp it so that it will only go the max speed
if (newProjectedVectorMagnitude >= maxSpeed)
{
//If the phys object is already moving faster than the max speed in the opposite direction of the force
//Then do not clamp or adjust the force
//Only adjust the force if it is congruent with the current movement
if (Vector3.Dot(newProjectedVector, clampedForce) > 0)
{
//Get the vector of the potential max speed
Vector3 goalProjectedVelocity = forceDirection * maxSpeed;
if (velocityDelta < 0) { goalProjectedVelocity = -goalProjectedVelocity; }
//Simply subtract to get a potential new force
Vector3 potentiallyNewClampedForce = goalProjectedVelocity - projectedVector;
//Ensure that the clamping did not reverse the direction of the original force
if (Vector3.Dot(potentiallyNewClampedForce, clampedForce) <= 0)
{
//Reduce the force to nothing if it has to reverse its direction in order to meet the max speed
clampedForce = new Vector3(0, 0, 0);
}
else
{
//Use the new clamped force if the force direction was not reversed
clampedForce = potentiallyNewClampedForce;
}
}
}
//Actually Apply The Force
rb.AddForce(clampedForce, ForceMode.VelocityChange);
//Debug.DrawLine(transform.position, transform.position + (clampedForce*3), Color.white);
}
On the Horizon
Placeholder: Planned features, polish, and future improvements.