Skip to content

Instantly share code, notes, and snippets.

@drusepth
Created July 17, 2022 08:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save drusepth/4ec93172f67e3421d7c65a96986fa80d to your computer and use it in GitHub Desktop.
Save drusepth/4ec93172f67e3421d7c65a96986fa80d to your computer and use it in GitHub Desktop.
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class HoverboardController : MonoBehaviour
{
public float multiplier; // How much force to exert on the ground to keep afloat
// This would be super cool to lerp off hoverboard battery level
// Potential game loop: find batteries?
// Design: we could also put the battery bar [|||||] on the back of the hoverboard
// These two attributes would be great to manipulate THPS3-style by introducing multiple boards
// that are better/worse at turning, speed, etc
public float move_force = 500;
public float acceleration_torque = 150;
public float turn_torque = 150;
public float lean_torque = 500;
public float jump_force = 5000f;
// TODO use these instead (charge up jump force by crouching)
/*
public float minimum_jump_force = 50000f;
public float crouch_jump_force_increase = 10000f;
public float maximum_jump_force = 80000f;
*/
public float float_height = 1f; // Distance to ground to be "grounded"
public float angular_righting_force = 1f; // TODO implement this for leveling out in the air
private Rigidbody body;
public Transform[] anchors = new Transform[4];
private RaycastHit[] hits = new RaycastHit[4];
private Camera main_camera;
private Transform default_position;
void Start()
{
body = GetComponent<Rigidbody>();
main_camera = Camera.main;
default_position = transform;
}
void Update()
{
HandleJumpInput();
}
void FixedUpdate()
{
// Apply an upward force from each of the board's 4 corners to keep it "floating"
for (int i = 0; i < anchors.Length; i++)
ApplyHoverForceAtPoint(anchors[i], hits[i]);
HandleForwardBackwardMovement();
HandleTurningMovement();
// When we're in the air, we should slowly right ourselves back to "level"
if (IsFullyAirborn())
transform.rotation = Quaternion.Lerp(transform.rotation, default_position.rotation, Time.time * angular_righting_force);
}
void HandleForwardBackwardMovement()
{
float forward_force = Input.GetAxis("Vertical");
// For rad cinematic jumps we switch our "forward" to the camera's "forward" instead of the
// player's "forward" when we're fully in the air, which allows for more airtime and better
// framing with the character-following camera.
if (IsFullyAirborn() && main_camera.transform.position.y > transform.position.y)
body.AddForce(forward_force * move_force * main_camera.transform.forward);
else
body.AddForce(forward_force * move_force * transform.forward);
// If we're on the ground, we also tilt the front/back of the board to signal acceleration/deceleration
if (IsFullyGrounded())
body.AddTorque(forward_force * acceleration_torque * main_camera.transform.right);
}
void HandleTurningMovement()
{
float turning_force = Input.GetAxis("Horizontal");
body.AddTorque(turning_force * turn_torque * transform.up);
// TODO: we should increase the turn_torque when we're in the air for tricks
if (IsFullyGrounded())
body.AddTorque(turning_force * lean_torque * -transform.forward);
}
void HandleJumpInput()
{
if (Input.GetKeyUp(KeyCode.Space) && IsFullyGrounded())
body.AddForce(jump_force * transform.up);
}
void ApplyHoverForceAtPoint(Transform anchor, RaycastHit hit)
{
// If this anchor is grounded, apply an upward force to float
if (IsGrounded(anchor, out hit))
{
float force = Mathf.Abs(1 / (hit.point.y - anchor.position.y));
body.AddForceAtPosition(transform.up * force * multiplier, anchor.position, ForceMode.Acceleration);
}
}
bool IsGrounded(Transform anchor) => Physics.Raycast(anchor.position, -anchor.up, float_height);
bool IsGrounded(Transform anchor, out RaycastHit hit) => Physics.Raycast(anchor.position, -anchor.up, out hit, float_height);
bool IsFullyGrounded()
{
bool fully_grounded = true;
for (int i = 0; i < 4; i++)
if (!IsGrounded(anchors[i]))
return false;
return fully_grounded;
}
bool IsFullyAirborn()
{
bool fully_airborn = true;
for (int i = 0; i < 4; i++)
if (IsGrounded(anchors[i]))
return false;
return fully_airborn;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment