Skip to content

Instantly share code, notes, and snippets.

@Hegemege
Created September 22, 2025 11:28
Show Gist options
  • Save Hegemege/6eab1445fba03bd2481238b542ed39b2 to your computer and use it in GitHub Desktop.
Save Hegemege/6eab1445fba03bd2481238b542ed39b2 to your computer and use it in GitHub Desktop.
using System;
using UnityEngine;
using System.Collections;
using System.Net;
#if (UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN)
using XInputDotNetPure;
#endif
public class PlayerPhysicsScript : MonoBehaviour {
// Physics
// Movement speed in units per second
public float movementSpeed;
public float maxVerticalSpeed;
public float maxHorizontalSpeed;
public float horizontalInputThreshold;
public float minBounceVelocity;
// Gravity in acceleration speed
public float gravity;
// Jump variables
public float jumpSpeed;
public float jumpCooldownTimerMax;
private float jumpCooldownTimer;
private bool canJump;
private bool jumped;
// Dampening and friction
public float dampening;
public float bounceDampening;
public float friction;
// Last horizontal movement direction
[HideInInspector]
public int lastHorizontalDirection;
// A vector variable other gameobjects can use to add impulse-like force to the physics objects
[HideInInspector]
public Vector3 impulseForce;
// Physics properties
private Vector3 velocity;
private Vector3 lastUnstuckPosition;
// Private helpers
private BackgroundCollisionScript bgCollisionScript;
private PlayerBehaviourScript behaviourScript;
private Animator animator;
// Audio effect event flags
private bool startedMoving;
private bool stoppedMoving;
private X360ControllerManagerScript x360Script;
#if (UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN)
public PlayerIndex playerIndex;
#endif
// Property for accessing the collision point of the player
private Vector3 CollisionPoint
{
get { return transform.position + collisionOffset; }
set { transform.position = value - collisionOffset; }
}
private Vector3 collisionOffset;
// Which input axes to use (Horizontal_P1 etc)
// Get it from the general behaviour script
private string ControlSchemePostfix
{
get
{
return behaviourScript.ControlSchemePostfix;
}
}
private string ControlDevicePostfix
{
get
{
return behaviourScript.ControlDevicePostfix;
}
}
// Controls properties
private float HorizontalInput
{
get
{
if (ControlDevicePostfix == "kb")
{
return Input.GetAxis("Horizontal_" + ControlSchemePostfix + "_" + ControlDevicePostfix);
}
else
{
#if (UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN)
return x360Script.HorizontalLeftInput(playerIndex);
#endif
}
return 0f;
}
}
private float JumpInput
{
get
{
if (ControlDevicePostfix == "kb")
{
return Input.GetAxis("Jump_" + ControlSchemePostfix + "_" + ControlDevicePostfix);
}
else
{
#if (UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN)
return x360Script.LeftShoulder(playerIndex);
#endif
}
return 0f;
}
}
void Awake()
{
x360Script = GameObject.Find("X360ControllerManager").GetComponent<X360ControllerManagerScript>();
}
public void Spawn()
{
bgCollisionScript = GameObject.Find("Background").GetComponent<BackgroundCollisionScript>();
behaviourScript = GetComponent<PlayerBehaviourScript>();
animator = transform.Find("Sprite").GetComponent<Animator>();
collisionOffset = transform.Find("CollisionPoint").transform.position - transform.position;
Respawn();
}
public void Respawn()
{
// Reset physics values
velocity = new Vector3(0, 0);
lastHorizontalDirection = 1;
impulseForce = new Vector3(0, 0);
jumpCooldownTimer = 0f;
jumped = false;
canJump = false;
lastUnstuckPosition = transform.position;
}
void FixedUpdate()
{
var dt = Time.fixedDeltaTime;
if (behaviourScript.isDead)
{
return;
}
// Movement physics will be calculated in two passes: horizontal and vertical
// This is so that sliding along edges will be smoother
// If player is already stuck, dont calculate physics, instead move them back to a known unstuck location
var wasStuck = bgCollisionScript.IsSolidWorld(CollisionPoint);
wasStuck = wasStuck || !bgCollisionScript.IsInBounds(CollisionPoint);
if (wasStuck)
{
CollisionPoint = lastUnstuckPosition;
}
else
{
lastUnstuckPosition = CollisionPoint;
}
// Horizontal input and gravity
float dx = HorizontalInput * movementSpeed * dt;
float dy = -gravity * dt;
// Jump action
if (jumped)
{
dy += jumpSpeed * dt;
}
if (velocity.x > horizontalInputThreshold)
{
if (dx > 0)
{
dx = 0f;
}
}
else if (velocity.x < -horizontalInputThreshold)
{
if (dx < 0)
{
dx = 0f;
}
}
// Impulse forces
dx += impulseForce.x * dt;
dy += impulseForce.y * dt;
impulseForce = new Vector3(0, 0);
// Apply velocity in steps (x and y separately), and test for collision
var stuckXResult = bgCollisionScript.IsSolidWorld(CollisionPoint, velocity + new Vector3(dx, 0f));
var stuckYResult = bgCollisionScript.IsSolidWorld(CollisionPoint, velocity + new Vector3(0f, dy));
var isStuckBoundsX = !bgCollisionScript.IsInBounds(CollisionPoint + velocity + new Vector3(dx, 0f));
var isStuckBoundsY = !bgCollisionScript.IsInBounds(CollisionPoint + velocity + new Vector3(0f, dy));
var isStuckX = stuckXResult.x != -100f || isStuckBoundsX;
var isStuckY = stuckYResult.x != -100f || isStuckBoundsY;
if (!isStuckX)
{
velocity.x += dx;
}
if (!isStuckY)
{
velocity.y += dy;
}
// Friction
if (isStuckX) //apply friction to velocity.y
{
velocity.y *= friction;
}
if (isStuckY) //apply friction to velocity.x
{
velocity.x *= friction;
}
// Sliding up a slope, while holding input key and dx is 0
if (isStuckX && HorizontalInput != 0f)
{
var nudge = 0.01f;
var boostedDx = dx > 0f ? dx + nudge : dx - nudge; // Give dx a little nudge to better climb slopes from stand-still
var absDx = Mathf.Abs(boostedDx);
// Check if there is free space at dx, absDx (diagonal up)
// If there is, move there!
var stuckDiagResult = bgCollisionScript.IsSolidWorld(CollisionPoint, velocity + new Vector3(boostedDx, absDx));
var isStuckDiagonal = stuckDiagResult.x != -100f ||
!bgCollisionScript.IsInBounds(CollisionPoint + velocity + new Vector3(boostedDx, absDx));
if (!isStuckDiagonal)
{
velocity.x = boostedDx;
velocity.y = absDx;
}
}
// Enable jumping if y-wise we are stuck
if (isStuckY)
{
// If was hit by terrain, use the raycast points for normal. Otherwise, get at position + velocity
Vector3 normal;
if (!isStuckBoundsY)
{
normal = bgCollisionScript.GetNormalWorld(new Vector2(stuckYResult.z, stuckYResult.w));
}
else
{
normal = bgCollisionScript.GetNormalWorld(CollisionPoint + velocity);
}
// If the surface is facing up even somewhat, enable jumping
if (Vector3.Dot(normal, new Vector3(0, 1, 0)) > 0f)
{
canJump = true;
if (!jumped)
{
animator.SetTrigger("playerLand");
}
}
}
else // otherwise disable it (air jumps are possible otherwise)
{
canJump = false;
}
// Clamp velocity to min/max range
var clampedDx = Mathf.Clamp(velocity.x, -maxHorizontalSpeed, maxHorizontalSpeed);
var clampedDy = Mathf.Clamp(velocity.y, -maxVerticalSpeed, maxVerticalSpeed);
velocity = new Vector3(clampedDx, clampedDy);
// Dampening
velocity *= dampening;
var stuckResult = bgCollisionScript.IsSolidWorld(CollisionPoint, velocity);
// Test if the velocity would put us into terrain
var isStuck = stuckResult.x != -100f;
if (isStuck)
{
var normal = bgCollisionScript.GetNormalWorld(new Vector2(stuckResult.z, stuckResult.w));
var projectedVelocity = Vector3.Dot(normal, velocity);
// If the velocity along the normal is not high enough, mitigate velocity
if (Mathf.Abs(projectedVelocity) < minBounceVelocity)
{
velocity = new Vector3(0f, 0f);
}
else
{
// Bounce from the surface, use surface normal
velocity -= normal * projectedVelocity * 2;
// Bounce loses some energy
velocity *= bounceDampening;
}
}
// Test for being stuck again, in case the bounce puts us into terrain
var finalStuckResult = bgCollisionScript.IsSolidWorld(CollisionPoint, velocity);
isStuck = finalStuckResult.x != -100f ||
!bgCollisionScript.IsInBounds(CollisionPoint + velocity);
if (isStuck)
{
velocity *= 0f;
canJump = true;
}
// Apply velocity
transform.Translate(velocity);
// Reset jumping
jumped = false;
}
void Update()
{
var dt = Time.deltaTime;
if (behaviourScript.isDead)
{
return;
}
jumpCooldownTimer += dt;
// If holding jump and cooldown is gone
if (JumpInput > 0 && jumpCooldownTimer > jumpCooldownTimerMax)
{
if (canJump)
{
animator.SetTrigger("playerJump");
Fabric.EventManager.Instance.PostEvent("Player_Jump");
jumpCooldownTimer = 0f;
jumped = true;
canJump = false;
}
}
if (HorizontalInput != 0.0f)
{
stoppedMoving = false;
if (!startedMoving)
{
startedMoving = true;
Fabric.EventManager.Instance.PostEvent("Player_Move");
}
animator.SetTrigger("playerMove");
if (HorizontalInput > 0)
{
lastHorizontalDirection = 1;
}
else
{
lastHorizontalDirection = -1;
}
}
else
{
startedMoving = false;
if (!stoppedMoving)
{
stoppedMoving = true;
Fabric.EventManager.Instance.PostEvent("Player_Stop");
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment