Skip to content

Instantly share code, notes, and snippets.

@qwe321qwe321qwe321
Created August 5, 2019 09:07
Show Gist options
  • Save qwe321qwe321qwe321/1c08b08cccc3fb8a1ef3ecba4c580181 to your computer and use it in GitHub Desktop.
Save qwe321qwe321qwe321/1c08b08cccc3fb8a1ef3ecba4c580181 to your computer and use it in GitHub Desktop.
A sticky platformer controller from the game PROJECT1ON in GMTKJam2019. https://itch.io/jam/gmtk-2019/rate/463690
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Clipping;
using VFX;
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(StickyAnimationController))]
public class StickyController : MonoBehaviour {
private static StickyControoler s_Instance;
public static StickyControoler Instance {
get {
if (s_Instance) {
return s_Instance;
}
return null;
}
}
[Header("Misc")]
public bool canControl = true;
public bool usePhysics = true;
[Header("Basic")]
public float gravity;
public float moveSpeed;
public float airMoveSpeed;
public float colliderTestMinDist = 0.01f;
[Header("Dash")]
public float dashSpeed = 20f;
public float dashTime = 0.1f;
public GhostEffectPool ghostEffector;
public CreateCircularSpectrum audioSpectrum;
[Header("Grab")]
public float grabRadiusFactor = 0.1f;
public LayerMask grabLayerMask;
private StickyAnimationController m_animationController;
private Rigidbody2D m_Rigidbody;
private Collider2D m_Collider2D;
private Coroutine dashCorutine;
[SerializeField]
private bool isDashing;
[SerializeField]
private bool isSticked;
[SerializeField]
private bool canDash;
public bool IsDead { get; private set; }
public bool IsGoal { get; private set; }
private float inputX;
private float inputY;
private Vector2 inputDirection;
private Vector2 dashDirection;
private Vector2 colliderSize;
private float colliderRadius;
private float gravityVelocity;
[SerializeField]
private Vector2 moveVector;
private void Awake()
{
if (s_Instance != null) {
Destroy(this.gameObject);
return;
}
s_Instance = this;
m_Rigidbody = GetComponent<Rigidbody2D>();
m_Collider2D = GetComponent<Collider2D>();
m_animationController = GetComponent<StickyAnimationController>();
gravityVelocity = 0;
dashDirection = Vector2.zero;
moveVector = Vector2.zero;
colliderSize = m_Collider2D.bounds.size;
colliderRadius = Mathf.Min(colliderSize.x, colliderSize.y) / 2;
if (ghostEffector != null) {
ghostEffector.enabled = false;
}
}
private void FixedUpdate()
{
if (!usePhysics) { return; }
if (IsDead || IsGoal) { return; }
// Consume moveVector.
Vector3 nextPos = transform.position + (Vector3)moveVector;
moveVector = Vector3.zero;
// Grab.
var overlaps = Physics2D.OverlapCircleAll(nextPos, colliderRadius * grabRadiusFactor, grabLayerMask);
if (overlaps != null && overlaps.Length > 0)
{
float minDist = float.PositiveInfinity;
Vector3 nearestPos = transform.position;
Vector3 nearestDir = Vector3.zero;
Collider2D nearestCollider = null;
foreach(var overlap in overlaps)
{
float dist;
Vector3 dir;
Vector2 pos = Grab(nextPos, overlap, out dist, out dir);
if (dist < minDist)
{
minDist = dist;
nearestPos = pos;
nearestDir = dir;
nearestCollider = overlap;
}
}
// Trigger Collider.
if(nearestCollider != null)
{
ClipObject clipObject = nearestCollider.gameObject.GetComponent<ClipObject>();
if(clipObject != null && clipObject.IsClipped)
{
StepOnClipObjAttr(clipObject.objAttr, nearestDir.normalized);
}
}
isSticked = true;
canDash = true;
// Stick position
if (nearestPos != transform.position) {
nextPos = nearestPos;
debug_NearestPos = nearestPos;
//transform.position = nearestPos;
}
} else {
isSticked = false;
}
m_Rigidbody.MovePosition(nextPos);
}
private void Update()
{
if (!usePhysics) { return; }
if (IsDead || IsGoal) { return; }
if (canControl) {
inputX = (Input.GetKey(KeyCode.D) ? 1 : 0) - (Input.GetKey(KeyCode.A) ? 1 : 0);
inputY = (Input.GetKey(KeyCode.W) ? 1 : 0) - (Input.GetKey(KeyCode.S) ? 1 : 0);
inputDirection = new Vector2(inputX, inputY).normalized;
// Dash Trigger
if (canDash && Input.GetKeyDown(KeyCode.Space) && (inputX != 0 || inputY != 0)) {
canDash = false;
isSticked = false;
dashDirection = inputDirection;
if (dashCorutine != null) { StopCoroutine(dashCorutine); }
dashCorutine = StartCoroutine(DashTimer(dashTime));
AudioMaster.Instance.PlaySound(SFX.Dash);
// VFX
BloomEffector.Bloom(0.5f);
CameraShaker.Shake(0.2f, 0.1f);
audioSpectrum.HypeTime(0.2f);
gravityVelocity = 0; // Reset
}
}
// Movement
if (isDashing) { // Dashing movement.
moveVector += dashDirection * dashSpeed * Time.deltaTime;
} else {
if (isSticked) { // Grounded movement.
moveVector += inputDirection * moveSpeed * Time.deltaTime;
} else { // Airbone movement.
moveVector += inputDirection * airMoveSpeed * Time.deltaTime;
}
// Gravity
if (!isSticked) {
gravityVelocity += gravity * Time.deltaTime;
} else { // Sticked without gravity.
gravityVelocity = 0;
}
moveVector += Vector2.down * gravityVelocity * Time.deltaTime;
}
}
public void ReleaseControl(bool keepOriginalVel) {
canControl = false;
if (!keepOriginalVel) {
inputDirection = Vector2.zero;
}
}
public void GainControl() {
canControl = true;
}
public void DisablePhysics(bool stopControl) {
if (stopControl) {
ReleaseControl(false);
}
usePhysics = false;
}
public void EnablePhysics() {
usePhysics = true;
}
IEnumerator DashTimer(float duration)
{
ghostEffector.enabled = true;
isDashing = true;
yield return new WaitForSeconds(duration);
isDashing = false;
ghostEffector.enabled = false;
canDash = false;
dashCorutine = null;
}
Vector3 PointOnLine(Vector3 p0, Vector3 p1, Vector3 p)
{
float l2 = Vector3.Distance(p0, p1);
if (l2 == 0.0f)
return p0;
Vector3 vec01 = p1 - p0;
float t = Mathf.Clamp01(Vector3.Dot(p - p0, vec01) / Vector3.Dot(vec01, vec01));
Vector3 projection = p0 + t * vec01;
return projection;
}
float DistanceToLine(Vector3 p0, Vector3 p1, Vector3 p)
{
Vector3 proj = PointOnLine(p0, p1, p);
return (proj - p).magnitude;
}
private Vector2 Grab(Vector3 position, Collider2D collider, out float minDistance, out Vector3 dir)
{
MeshFilter mf = collider.gameObject.GetComponent<MeshFilter>();
minDistance = float.PositiveInfinity;
dir = Vector3.zero;
if (mf != null) {
int intersectionCount = 0;
Vector3 nearestPoint = Vector3.zero;
Vector3 localPos = collider.gameObject.transform.InverseTransformPoint(position);
Mesh mesh = mf.mesh;
Vector3 last_p = mesh.vertices[mesh.vertices.Length - 2];
for (int i = 0; i < mesh.vertices.Length-1; i++) {
Vector3 p0 = last_p;
Vector3 p1 = mesh.vertices[i];
Vector3 p2 = PointOnLine(p0, p1, localPos);
float d0 = (p2 - localPos).magnitude;
if (d0 < minDistance) {
minDistance = d0;
nearestPoint = p2;
dir = (localPos - p2).normalized * (colliderRadius + colliderTestMinDist);
}
if ((p0.x - p1.x) != 0) {
float m = (p0.y - p1.y) / (p0.x - p1.x);
float b = p0.y - m * p0.x;
if ((p0.x <= localPos.x && p1.x > localPos.x || p1.x < localPos.x && p0.x >= localPos.x) && ((localPos.x * m + b) > localPos.y)) {
intersectionCount += 1;
}
}
last_p = p1;
}
if((intersectionCount & 1) > 0) {
dir *= -1;
}
return collider.gameObject.transform.TransformPoint(nearestPoint + dir);
}
return Vector2.zero;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment