Player
So for the Movement in unity or MIU im going to go script by script function by function and you can see which script im talking about by the tittle that comes up.
like here it is "Player.cs"
like here it is "Player.cs"
Player.cs
Veriables
Everything under miscellaneous doesnt really matter that much they just reference the other scripts that make up the player character.
I am usuing the Input Actions or IA that unity has for this project. They are very simular to unreals input actions.
Start just Initializes a bunch of these codes or makes new once so im gonna skip it.
Update how ever does do a few things so lets look at that.
First we store the players input this frame and the delta time.
Then we use the input make a new CameraInput with its look vector being equal to the input.look value.
After that we call a custom function in the camera to update its rotation using the cameraInput.
Here we make a new characterInput struct and fill in with information based of if an input.veriable was pressed this frame or not.
Their are some expections to this, like rotation is just grabed from the camera or JumpSustain is just shaking if was pressed or not.
The one line I want to look closer at is the Run/Crouch inputs, see they are both togglable so they need some extra lines.
This statement is just trying to get what value the crouch should be. We use the "?" operator multiple times to get the answer we want.
the "?" operatror is an if statement for a variable basicly; (statement) ? true : false; if statement is true return the left most value other wise return the right most.
The first ? operator checks if we have crouch toggled or not. The secound ? operator just turns the crouch on or off.
We call the player character to update its inputs and then its body, a function we will see later.
After that we do the weapon inputs but also have a debug key that allows us to Teleport to where a ray hits in the scene.
This is very usefull becouse of how our terrain looks in the game.
LateUpdate is what comes next.
in here we just call a bunch of function inside other scripts that make up the player. Here is where we update the camera, we do this here so that we know that all the movement callculation have been done this frame and now can just move the camera smoothly.
[Header("Miscellaneous")]
[SerializeField] private PlayerCharacter playerCharacter;
[SerializeField] private cutAndParry cutAndParry;
[SerializeField] private PlayerCamera playerCamera;
[SerializeField] private CameraSpring cameraSpring;
[SerializeField] private cameraLean cameraLean;
[Header("Input")]
[SerializeField] private bool toggleCrouchSlide = false;
[SerializeField] private bool toggleRun = false;
private IA_AdvancedPlayer _inputActions;
I am usuing the Input Actions or IA that unity has for this project. They are very simular to unreals input actions.
Start just Initializes a bunch of these codes or makes new once so im gonna skip it.
Update how ever does do a few things so lets look at that.
void Update()
{
var input = _inputActions.player;
var deltaTime = Time.deltaTime;
// get camera input and updates its rotation
var cameraInput = new CameraInput { lookVec = input.look.ReadValue() };
playerCamera.UpdateRotation(cameraInput);
Then we use the input make a new CameraInput with its look vector being equal to the input.look value.
After that we call a custom function in the camera to update its rotation using the cameraInput.
// get character input and update it
var characterInput = new CharacterInput
{
// movement input
Rotation = playerCamera.transform.rotation,
Move = input.WASD.ReadValue(),
Jump = input.jump.WasPressedThisFrame(),
JumpSustain = input.jump.IsPressed(),
Crouch = toggleCrouchSlide ? (input.crouch_slide.WasPressedThisFrame() ? CrouchInput.toggle : CrouchInput.None) : (input.crouch_slide.IsPressed() ? CrouchInput.Crouch : CrouchInput.Uncrouch),
Run = toggleRun ? (input.run.WasPressedThisFrame() ? RunInput.toggle : RunInput.None) : (input.run.IsPressed() ? RunInput.Run : RunInput.Unrun),
Dash = input.dash.WasPressedThisFrame(),
};
var weaponInput = new weaponInput
{
// Gameplay input
Cut = input.cut.WasPressedThisFrame(),
Parry = input.parry.WasPerformedThisFrame(),
Sheath = input.Sheath.WasPerformedThisFrame()
};
Their are some expections to this, like rotation is just grabed from the camera or JumpSustain is just shaking if was pressed or not.
The one line I want to look closer at is the Run/Crouch inputs, see they are both togglable so they need some extra lines.
Crouch = toggleCrouchSlide ? (input.crouch_slide.WasPressedThisFrame() ? CrouchInput.toggle : CrouchInput.None) : (input.crouch_slide.IsPressed() ? CrouchInput.Crouch : CrouchInput.Uncrouch),
the "?" operatror is an if statement for a variable basicly; (statement) ? true : false; if statement is true return the left most value other wise return the right most.
The first ? operator checks if we have crouch toggled or not. The secound ? operator just turns the crouch on or off.
playerCharacter.UpdateInput(characterInput);
playerCharacter.UpdateBody(deltaTime);
cutAndParry.UpdateWeaponInput(weaponInput);
#if UNITY_EDITOR
if (Keyboard.current.tKey.wasPressedThisFrame)
{
var ray = new Ray(playerCamera.transform.position, playerCamera.transform.forward);
if (Physics.Raycast(ray, out var hit))
{
Teleport(hit.point);
}
}
#endif
}
After that we do the weapon inputs but also have a debug key that allows us to Teleport to where a ray hits in the scene.
This is very usefull becouse of how our terrain looks in the game.
LateUpdate is what comes next.
void LateUpdate()
{
var deltaTime = Time.deltaTime;
var cameraTarget = playerCharacter.getCameraTarget();
var state = playerCharacter.GetState();
playerCamera.UpdatePosition(cameraTarget);
cameraSpring.UpdateSpring(deltaTime, cameraTarget.up);
cameraLean.UpdateSpring(deltaTime, state.Stance is Stance.sliding, state.Acceleration, cameraTarget.up);
}
PlayerCharacter.cs
The actual meat of the character movement happens in this script. Think of player.cs as a manger calling all the other scripts as needed.
Let's being with UpdateInput, now this function updates a bunch of veriabels so I wont show all of it but I wanna show a part of it that I really find cool
This looks odd but this is a simple swtich statement but when I was making this I had no idea you could use a switch like this.
Depending on what CrouchInput is it will return true or false but this also allows us to make toggle crouch by just fliping the _requestedCrouch.
the _ => _requestedCrouch looks really werid but is just the defualt case.
UpdateBody from earlier show up here again and it looks like this
This updates the player hight and its camera postion but over time using a lerp.
This create a smooth up and down while crouching.
Now lets look at the BeforeCharacterUpdates function, this on is called before we update the players movement. So this happens before we apply anything like position and rotation.
This is just setting up some states that can be called from heldown buttons, so both runing and crouching comes into play here.
AfterCharacterUpdate does basicly the same so im not going to show it.
So now for the big part * UpdateVelocity *
First we do some set up, we clear Acceleration and set it to 0, we get the grounded movements direaction by calling the function GetDirectionTangentToSurface.
This function Crosses the direction with the characters up and then takes the resulting vector and crosses that with surfaceNormal. Returning this vecotr normalized for us to use.
Here we save a bunch of veriabels for later. Almost all of these have something to do with the _state or _lastState veriables which just a saved copy of the enum that saves the players stance, ie crouching, running, wallruning.
Here we have the start to Slide . First we just do a check to see if we can slide or not.
If we can slide then we set the stance to slide.
Then we check if we are in were in the air. If we were then we get the ground normal by using the KinematicCharacterMotor.
We project our current velocity onto this groundNormal. We do this to get a perallel vector to the plane we sliding on.
if the tangent is great enough we set the currentVelocity to be equal to tangent.normalized * currentVelocity.magnitude. This directs our movementom along the normal of the serfuce we are standing on.
After that we make a float called effectiveSlideStartSpeed. This float will effect the currentVelocity later but we make this temperary verstion so we can change it as needed.
So we run a check to see if our _lastState wasnt grounded and _requestedCrouchInAir is false then we set the effectiveSlideStartSpeed to be 0 and flip the _requestedCrouchInAir to be false. This effectevly tells the later code not to use the effectiveSlideStartSpeed.
After that check we now set up another veriable slideSpeed, is initiale value being equal to either effectiveSlideStartSpeed or currentVelocity.magnitude depending on which is higher in value.
We use the GetDirectionTangentToSurface from before here again. Multiplying ther resulting vector with slideSpeed.
Then we are done with slide.
Let's being with UpdateInput, now this function updates a bunch of veriabels so I wont show all of it but I wanna show a part of it that I really find cool
bool wasReqestingCrouch = _requestedCrouch;
_requestedCrouch = input.Crouch switch
{
CrouchInput.toggle => ! _requestedCrouch,
CrouchInput.None => _requestedCrouch,
CrouchInput.Crouch => true,
CrouchInput.Uncrouch => false,
_ => _requestedCrouch
};
Depending on what CrouchInput is it will return true or false but this also allows us to make toggle crouch by just fliping the _requestedCrouch.
the _ => _requestedCrouch looks really werid but is just the defualt case.
UpdateBody from earlier show up here again and it looks like this
public void UpdateBody(float deltaTime)
{
float currentHeight = motor.Capsule.height;
float normalizedHeight = currentHeight / standingHight;
float cameraTargetHeight = currentHeight * (_state.Stance is Stance.Stand ? standingCameraHeightTarget : crouhCameraHeightTarget);
Vector3 rootTragetScale = new Vector3(1.0f, normalizedHeight, 1.0f);
cameraTarget.localPosition = Vector3.Lerp(
a: cameraTarget.localPosition,
b: new Vector3(0.0f, cameraTargetHeight, 0.0f),
t: 1.0f - Mathf.Exp(-crouchHeightResponse * deltaTime));
root.localScale = Vector3.Lerp(
a: root.localScale,
b: rootTragetScale,
t: 1.0f - Mathf.Exp(-crouchHeightResponse * deltaTime));
}
This create a smooth up and down while crouching.
Now lets look at the BeforeCharacterUpdates function, this on is called before we update the players movement. So this happens before we apply anything like position and rotation.
public void BeforeCharacterUpdate(float deltaTime)
{
_tempState = _state;
if (_requestedRun && _state.Stance is Stance.Stand && !_requestedCrouch)
{
_state.Stance = Stance.Running;
}
if (_requestedCrouch && (_state.Stance is Stance.Stand || _state.Stance is Stance.Running))
{
_state.Stance = Stance.Crouch;
motor.SetCapsuleDimensions(
radius: motor.Capsule.radius,
height: crocuhHight,
yOffset: crocuhHight * 0.5f
);
root.localPosition = new Vector3(0.0f, 0.5f, 0.0f);
}
}
AfterCharacterUpdate does basicly the same so im not going to show it.
So now for the big part * UpdateVelocity *
public void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
{
_state.Acceleration = Vector3.zero;
Vector3 groundedMovement = motor.GetDirectionTangentToSurface(direction: _requestedmovement, surfaceNormal: motor.GroundingStatus.GroundNormal) * _requestedmovement.magnitude;
bool moving = groundedMovement.sqrMagnitude > 0;
if(_state.Stance is Stance.Wallruning) _timeSinceUngrounded = 0.0f;
if (motor.GroundingStatus.IsStableOnGround)
{
_timeSinceUngrounded = 0.0f;
_ungrounedDueToJump = false;
This function Crosses the direction with the characters up and then takes the resulting vector and crosses that with surfaceNormal. Returning this vecotr normalized for us to use.
bool running = _state.Stance is Stance.Running;
var shouldSlideStart = currentVelocity.magnitude > slideStartFreshold;
var crouching = _state.Stance is Stance.Crouch;
var wasStanding = _lastState.Stance is Stance.Stand;
var wasRuning = _lastState.Stance is Stance.Running;
var wasInAir = !_lastState.Grounded;
// start slide
if (moving && crouching && shouldSlideStart && (wasStanding || wasInAir || wasRuning))
{
_state.Stance = Stance.sliding;
if (wasInAir)
{
Vector3 groundNormal = motor.GroundingStatus.GroundNormal;
Vector3 tangent = Vector3.ProjectOnPlane(currentVelocity, groundNormal);
// Keep tangential (momentum along slope), discard into-ground velocity
if (tangent.sqrMagnitude > 0.001f)
currentVelocity = tangent.normalized * currentVelocity.magnitude;
}
float effectiveSlideStartSpeed = slideStartSpeed;
if (!_lastState.Grounded && !_requestedCrouchInAir)
{
effectiveSlideStartSpeed = 0.0f;
_requestedCrouchInAir = false;
}
float slideSpeed = Mathf.Max(effectiveSlideStartSpeed, currentVelocity.magnitude);
currentVelocity = motor.GetDirectionTangentToSurface(
direction: currentVelocity,
surfaceNormal: motor.GroundingStatus.GroundNormal) * slideSpeed;
}
If we can slide then we set the stance to slide.
Then we check if we are in were in the air. If we were then we get the ground normal by using the KinematicCharacterMotor.
We project our current velocity onto this groundNormal. We do this to get a perallel vector to the plane we sliding on.
if the tangent is great enough we set the currentVelocity to be equal to tangent.normalized * currentVelocity.magnitude. This directs our movementom along the normal of the serfuce we are standing on.
After that we make a float called effectiveSlideStartSpeed. This float will effect the currentVelocity later but we make this temperary verstion so we can change it as needed.
So we run a check to see if our _lastState wasnt grounded and _requestedCrouchInAir is false then we set the effectiveSlideStartSpeed to be 0 and flip the _requestedCrouchInAir to be false. This effectevly tells the later code not to use the effectiveSlideStartSpeed.
After that check we now set up another veriable slideSpeed, is initiale value being equal to either effectiveSlideStartSpeed or currentVelocity.magnitude depending on which is higher in value.
We use the GetDirectionTangentToSurface from before here again. Multiplying ther resulting vector with slideSpeed.
Then we are done with slide.
if (_state.Stance is Stance.Stand or Stance.Crouch or Stance.Running)
{
float speed = _state.Stance switch
{
Stance.Stand => walkSpeed,
Stance.Crouch => crouchSpeed,
Stance.Running => runSpeed,
_ => walkSpeed
};
float response = _state.Stance switch
{
Stance.Stand => walkResponse,
Stance.Crouch => crouchResponse,
Stance.Running => runResponse,
_ => walkResponse
};
if (!_requestedDash)
{
Vector3 tragetVelocity = groundedMovement * speed;
Vector3 moveVelocity = Vector3.Lerp(
a: currentVelocity,
b: tragetVelocity,
t: 1.0f - Mathf.Exp(-response * deltaTime)
);
_state.Acceleration = moveVelocity - currentVelocity;
currentVelocity = moveVelocity;
}
}
else
{
currentVelocity -= currentVelocity * (slideFriction * deltaTime);
Vector3 forceOnSlope = Vector3.ProjectOnPlane(
vector: -motor.CharacterUp,
planeNormal: motor.GroundingStatus.GroundNormal
) * slideGravity;
currentVelocity -= forceOnSlope * deltaTime;
float currentSpeed = currentVelocity.magnitude;
Vector3 targetVelocity = groundedMovement * currentVelocity.magnitude;
Vector3 steerVelocity = currentVelocity;
Vector3 steerForce = (targetVelocity - steerVelocity) * slideSteerAcceleration * deltaTime;
steerVelocity += steerForce;
steerVelocity = Vector3.ClampMagnitude(steerVelocity, currentSpeed);
_state.Acceleration = (steerVelocity - currentVelocity) / deltaTime;
currentVelocity = steerVelocity;
if (currentVelocity.magnitude < slideEndSpeed) _state.Stance = Stance.Crouch;
}
}
else if (_state.Stance is not Stance.Wallruning)
// air
_timeSinceUngrounded += deltaTime;
if (_requestedmovement.sqrMagnitude > 0.0f)
{
Vector3 planarMovement = Vector3.ProjectOnPlane(
vector: _requestedmovement,
planeNormal: motor.CharacterUp) * _requestedmovement.magnitude;
Vector3 currentPlanarVelocity = Vector3.ProjectOnPlane(
vector: currentVelocity,
planeNormal: motor.CharacterUp);
Vector3 movementForce = planarMovement * airAcceleration * deltaTime;
if (currentPlanarVelocity.magnitude < airSpeed)
{
Vector3 targetPlanarVelocity = currentPlanarVelocity + movementForce;
targetPlanarVelocity = Vector3.ClampMagnitude(targetPlanarVelocity, airSpeed);
movementForce = targetPlanarVelocity - currentPlanarVelocity;
}
else if (Vector3.Dot(currentPlanarVelocity, movementForce) > 0.0f)
{
Vector3 constrainedMovementForce = Vector3.ProjectOnPlane(
vector: movementForce,
planeNormal: currentPlanarVelocity.normalized
);
movementForce = constrainedMovementForce;
}
if (motor.GroundingStatus.FoundAnyGround)
{
if (Vector3.Dot(movementForce, currentVelocity + movementForce) > 0.0f)
{
Vector3 obstuctionNormal = Vector3.Cross(
motor.CharacterUp,
Vector3.Cross(
motor.CharacterUp,
motor.GroundingStatus.GroundNormal
)
).normalized;
movementForce = Vector3.ProjectOnPlane(movementForce, obstuctionNormal);
}
}
currentVelocity += movementForce;
}
float effectiveGravity = gravity;
float verticalSpeed = Vector3.Dot(currentVelocity, motor.CharacterUp);
if (_requestedSustainJump && verticalSpeed > 0.0f) effectiveGravity *= jumpSustainGravity;
if (dashGravityTimer > 0.0f) effectiveGravity = 0.0f;
if (_state.Stance is Stance.Wallruning) effectiveGravity = 0.0f;
currentVelocity += motor.CharacterUp * effectiveGravity * deltaTime;
}
RaycastHit rightHit;
RaycastHit leftHit;
bool rightWall = Physics.Raycast(transform.position, motor.CharacterRight, out rightHit, wallStartDistance, wall);
bool leftWall = Physics.Raycast(transform.position, -motor.CharacterRight, out leftHit, wallStartDistance, wall);
if (_requestedJump)
{
if (_state.Stance is not Stance.Wallruning)
{
bool grounded = motor.GroundingStatus.IsStableOnGround;
bool canCoyoteJump = _timeSinceUngrounded < coyoteTime && !_ungrounedDueToJump;
if (grounded || canCoyoteJump)
{
_requestedJump = false;
_requestedCrouch = false;
_requestedCrouchInAir = false;
motor.ForceUnground(time: 0.0f);
_ungrounedDueToJump = true;
float currentVerticalSpeed = Vector3.Dot(currentVelocity, motor.CharacterUp);
float targetVerticalSpeed = Mathf.Max(currentVerticalSpeed, jumpSpeed);
currentVelocity += motor.CharacterUp * (targetVerticalSpeed - currentVerticalSpeed);
}
}
else if (_state.Stance is Stance.Wallruning)
{
bool canCoyoteJump = _timeSinceUngrounded < coyoteTime && !_ungrounedDueToJump;
if (canCoyoteJump)
{
_wallrunColdownTimer = wallrunColdownTime;
_requestedJump = false;
_ungrounedDueToJump = true;
Vector3 wallNormal = rightWall ? rightHit.normal : leftHit.normal;
Vector3 combinedDir = motor.CharacterUp + (wallNormal * 0.3f);
currentVelocity += combinedDir * jumpSpeed;
}
}
else
{
bool canJumpLater = _timeSinceJumpRequest < coyoteTime;
_requestedJump = canJumpLater;
_timeSinceJumpRequest += deltaTime;
}
}
if (_requestedDash && currentDashesLeft > 0)
{
if (_state.Stance is Stance.Wallruning)
{
_state.Stance = Stance.Dashing;
_wallrunColdownTimer = wallrunColdownTime;
Vector3 wallNormal = rightWall ? rightHit.normal : leftHit.normal; // normals can at time point towards the wall fuck
Vector3 comebinedDir = (wallNormal * 0.3f) + motor.CharacterForward.normalized;
currentVelocity = new Vector3(0.0f, currentVelocity.y, currentVelocity.z);
currentVelocity += comebinedDir * airDashSpeed;
}
else
{
_state.Stance = Stance.Dashing;
Vector3 groundedForward = motor.GetDirectionTangentToSurface(direction: motor.CharacterForward, surfaceNormal: motor.GroundingStatus.GroundNormal);
motor.ForceUnground(time: 0.0f);
float effectiveDashSpeed = motor.GroundingStatus.IsStableOnGround ? groundDashSpeed : airDashSpeed;
if (groundedMovement.magnitude > 0.0f) currentVelocity += groundedMovement * effectiveDashSpeed;
else currentVelocity += groundedForward * effectiveDashSpeed;
}
currentDashesLeft--;
_requestedDash = false;
}
if (currentDashesLeft == 0) _requestedDash = false;
// wallruning
if (!motor.GroundingStatus.IsStableOnGround && _wallrunColdownTimer < 0.0f && _state.Stance is not Stance.Dashing && !_requestedJump && _isGettingMovementInput && (currentVelocity.sqrMagnitude > wallrunFrechold || _state.Stance is Stance.Running) && (rightWall || leftWall))
{
{// start wallrun
_state.Stance = Stance.Wallruning;
_ungrounedDueToJump = false;
currentVelocity = new Vector3(currentVelocity.x, 0.0f, currentVelocity.z);
}
{// during wallrun
Vector3 wallNormal = rightWall ? rightHit.normal : leftHit.normal;
Vector3 wallForward = Vector3.Cross(wallNormal, motor.CharacterUp);
float chosenStartSpeed = Mathf.Max(wallrunStartSpeed, currentVelocity.magnitude);
float effectiveWallrunSpeed = Mathf.Lerp(
a: chosenStartSpeed,
b: wallrunEndSpeed,
t: 1.0f - Mathf.Exp(-walkResponse * deltaTime));
Vector3 effectiveForward = (motor.CharacterForward - wallForward).magnitude > (motor.CharacterForward - -wallForward).magnitude ? -wallForward : wallForward;
Vector3 tragetVelocity = (effectiveForward + groundedMovement).normalized * effectiveWallrunSpeed;
Vector3 moveVelocity = Vector3.Lerp( // not learping shit huh
a: currentVelocity,
b: tragetVelocity,
t: 1.0f - Mathf.Exp(-wallrunResponse * deltaTime)
);
_state.Acceleration = moveVelocity - currentVelocity;
currentVelocity = moveVelocity;
}
}
else if (_state.Stance is Stance.Wallruning && !rightWall && !leftWall || _state.Stance is Stance.Wallruning && !_isGettingMovementInput)
{
_wallrunColdownTimer = wallrunColdownTime;
_state.Stance = Stance.Stand;
}
}
Ai
core
platform.m_time = std::clamp(platform.m_time += (std::clamp(_delta_time, 0.0f, 1.0f) * time_flow * platform.m_point_travel_time) / platform.diff, 0.0f, 1.0f);
Guns
core
platform.m_time = std::clamp(platform.m_time += (std::clamp(_delta_time, 0.0f, 1.0f) * time_flow * platform.m_point_travel_time) / platform.diff, 0.0f, 1.0f);