Hi! I’m a student at the moment, but most of my work has been written until now. I’m developing a 2D platformer using Unity (on some really awful machines) and after almost a month stuck on this particular thing I have finally decided to come to real people for help. Last time I did I was called stupid for being new to this, and patronized unnecessarily - that was before I went into this course in September. It actually turns out I was missing something my PC couldn’t actually handle, so I can only access any of my stuff while in college, 4 days a week, and I was able to do the exact thing I was doing before successfully. However that’s not my current problem.
Please take it easy on me - I’m a student in a kinda amateur test course and this stuff isn’t easy to find online. I’m also autistic and might need some specification on what you mean by some things or where to find something you need to know.
I’m attempting to make a semi-interactive environment with objects like benches, trashcans and lampposts that the player can jump onto.
If you’ve ever played Night in The Woods, something like that. Most of the environment is like a bunch of hidden platforms that can be jumped on top of and jumped off of at any given time. In my case, its a park bench for now, just while I figure out the script.
I’ve mostly got this down. I’m using a PlatformEffector2D on the platform, and the collider is used by effector. The current problem is, when I attempt to walk past the bench, as soon as I walk past the center of the bench, I am forced on top of it. Not to mention as well, when I press the down arrow it disables the effector like I’ve coded it to. The only way it’s currently somehow working how I want is i’ve set it to wait until about 2 seconds after pressing left or right keys before the effector is re-enabled, which isn’t helpful if you’re not out of the bounds.
I’m also struggling with jump buffering as its settings haven’t been changed at all but it’s still buffering the jump differently than it did about a week ago. It used to buffer the jump if I pressed jump JUST before I landed, then it’d do a small jump. Now, any time i’m in the air and I press Jump after double jumping, it does an insanely high-velocity jump/bounce, instead of doing it if i pressed just before landing. It used to work fine but the player script hasn’t changed for a while, so I have no idea.
I’ve been spending about 2 weeks on just trying to get this to work, but it isn’t. Can someone please help me?
Here’s my PlayerController script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerController : MonoBehaviour
{
// Exposed Fields
[Header("Run Properties")]
[SerializeField] private float maxRunSpeed = 5.0f;
[SerializeField] private float groundAcceleration = 4.0f;
[SerializeField] private float airAcceleration = 2.0f;
[SerializeField] private float runForceMultiplier = 1.0f;
[SerializeField] private float frictionFactor = 0.5f;
[Header("Jump Properties")]
[SerializeField] private float jumpPower = 15.0f;
[SerializeField] public int maxJumps = 1;
[SerializeField] int jumpsRemaining;
[SerializeField] private float coyoteTimeDuration = 0.1f; //Coyote time duration
private float coyoteTimeCounter; // Timer for Coyote Time
[SerializeField] private float jumpBufferTime = 0.2f; // Buffer window
private float jumpBufferCounter = 0f;
[Header("Enviroment Checks")]
[SerializeField] private Vector2 groundCheckSize;
[SerializeField] private LayerMask groundLayers;
// Component References
private PlayerInput playerInput;
private Rigidbody2D rb2d;
private Animator anim;
// Private Fields
private bool _isOnGround;
private Vector2 _movementInput;
//audio management
public AudioSource audioSource;
public AudioClip jumpSound;
// Unity Callback Methods
private void Awake()
{
//Link references to components
playerInput = GetComponent<PlayerInput>();
rb2d = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
}
private void Update()
{
UpdateDirection();
UpdateAnimations();
_isOnGround = CheckIfOnGround();
_movementInput = playerInput.actions["Movement"].ReadValue<Vector2>();
// Reset jumps and handle coyote time and buffer
if (_isOnGround)
{
jumpsRemaining = maxJumps;
if (jumpBufferCounter > 0)
{
Jump();
jumpBufferCounter = 0f; // clear buffer
}
}
else
{
// Decrease coyote time counter when in the air
if (coyoteTimeCounter > 0)
coyoteTimeCounter -= Time.deltaTime;
}
// Local variables for running
float targetSpeed, speedDifference, acceleration, runForce;
// Calculate run force
targetSpeed = _movementInput.x * maxRunSpeed;
speedDifference = targetSpeed - rb2d.velocity.x;
acceleration = _isOnGround ? groundAcceleration : airAcceleration;
runForce = Mathf.Pow(Mathf.Abs(speedDifference) * acceleration, runForceMultiplier) * Mathf.Sign(speedDifference);
// Apply run force to player
rb2d.AddForce(runForce * Vector2.right);
// Handle friction when the player is still (no movement input)
if (_isOnGround && Mathf.Approximately(_movementInput.x, 0))
{
float amount = Mathf.Min(Mathf.Abs(rb2d.velocity.x), Mathf.Abs(frictionFactor));
amount *= Mathf.Sign(rb2d.velocity.x);
rb2d.AddForce(Vector2.right * -amount, ForceMode2D.Impulse);
}
// Handle sliding on slopes: If the slope is too steep, stop the player's horizontal velocity
if (_isOnGround && Mathf.Abs(rb2d.velocity.x) > 0.1f)
{
RaycastHit2D hit = Physics2D.Raycast(transform.position, Vector2.down, groundCheckSize.y / 2 + 0.1f, groundLayers);
if (hit.collider != null)
{
float slopeAngle = Vector2.Angle(hit.normal, Vector2.up);
if (slopeAngle > 45f)
{
// Completely stop horizontal velocity on steep slopes
rb2d.velocity = new Vector2(0f, rb2d.velocity.y);
}
}
}
}
private void OnDrawGizmos()
{
Gizmos.color = _isOnGround ? Color.green : Color.red;
Gizmos.DrawWireCube(transform.position, new Vector3(groundCheckSize.x, groundCheckSize.y, 0.1f));
}
private void OnEnable()
{
playerInput.actions["Jump"].started += OnJumpPressed;
playerInput.actions["Jump"].canceled += OnJumpReleased;
}
private void OnDisable()
{
playerInput.actions["Jump"].started -= OnJumpPressed;
playerInput.actions["Jump"].canceled -= OnJumpReleased;
}
// Input Event Callback Methods
private void OnJumpPressed(InputAction.CallbackContext context)
{
if (_isOnGround || jumpsRemaining > 0)
{
if (_isOnGround)
{
jumpsRemaining = maxJumps - 1;
Jump(); //Only call Jump is the player is on the ground
coyoteTimeCounter = 0; // reset coyote time after jumping
}
else
{
jumpsRemaining--;
Jump();
}
}
else
{
jumpBufferCounter = jumpBufferTime;
}
}
private void Jump()
{
rb2d.velocity = new Vector2(rb2d.velocity.x, jumpPower);
coyoteTimeCounter = coyoteTimeDuration;
}
private void OnJumpReleased(InputAction.CallbackContext context)
{
if (rb2d.velocity.y > 0)
{
rb2d.velocity = new Vector2(rb2d.velocity.x, rb2d.velocity.y / 2);
}
}
// Class Methods
private bool CheckIfOnGround()
{
RaycastHit2D hit = Physics2D.Raycast(transform.position, Vector2.down, groundCheckSize.y / 2 + 0.1f, groundLayers);
if (hit.collider != null)
{
float slopeAngle = Vector2.Angle(hit.normal, Vector2.up);
if (slopeAngle < 45f)
{
return true;
}
else
{
rb2d.velocity = new Vector2(rb2d.velocity.x * 0.9f, rb2d.velocity.y);
return true;
}
}
return false;
}
private void UpdateAnimations()
{
anim.SetBool("OnGround", _isOnGround);
anim.SetFloat("RunSpeed", Mathf.Abs(rb2d.velocity.x));
}
private void UpdateDirection()
{
var scale = transform.localScale;
if (!Mathf.Approximately(_movementInput.x, 0)) scale.x = Mathf.Sign(_movementInput.x);
transform.localScale = scale;
}
Platform script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class OneWayPlatform : MonoBehaviour
{
[SerializeField] public PlatformEffector2D platformEffector;
[SerializeField] public BoxCollider2D playerCollider;
[SerializeField] public float distanceThreshold = 3f;
private Transform player;
private Transform oneWayPlatform;
public bool hasFallen = false;
private bool isWaiting = false;
private void Start()
{
player = GameObject.FindWithTag("Player").transform;
oneWayPlatform = transform;
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.DownArrow))
{
FallThroughPlatform();
hasFallen = true;
}
if (Input.GetKeyUp(KeyCode.UpArrow) || (Input.GetKey(KeyCode.LeftArrow) || Input.GetKey(KeyCode.RightArrow)))
{
if (!isWaiting)
{
StartCoroutine(Wait());
}
}
}
private void FallThroughPlatform()
{
platformEffector.surfaceArc = 0;
hasFallen = false;
}
IEnumerator Wait()
{
isWaiting = true;
yield return new WaitForSeconds(0.5f);
platformEffector.surfaceArc = 90;
isWaiting = false;
}
}
Thanks to anybody willing to help <3