This moving platform script is for moving a platform with a player on it in mind. So that the player moves with platform and can freely move on it without trouble. That works because in the player script the velocity of the platform gets added to the velocity of the player. The platform is based on velocity movement because i had to get the velocity of the platform to make it work with the player.
Here below is a video of what is happening:
The problem
The problem is that everytime the platform switches direction the velocity of the platform changes in the opposite direction and the player should adjust with that in sync. But it does not. Rather, it goes a little to the right or left because the velocity change has a delay ( I assume that that is the problem, but could be something else) . I’ve tried multiple things, asigning the transform of the player to the transform of the collision while changing direction, does not work. Also making the player a child of the platform while changing direction, does not work. (I could have implemented them wrong but im not sure). I’ve added dampening to the movement of the platform but that does not work either, only minimizes it. That’s why I ask for help for how to fix this. Here’s a video below to show it.

Movingplatform
using System.Collections;
using System.Collections.Generic;
using TarodevController;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class WaypointFollower : MonoBehaviour
{
public Transform posA, posB;
public float speed;
Vector3 targetPos;
private Vector3 velocity;
public float smoothTime;
PlayerController PlayerController;
Rigidbody2D rb;
Vector3 moveDirection;
Rigidbody2D playerRb;
//bool PlayerOnPlatform;
//Transform PlayerCollision;
//Transform Player;
//GameObject PLAYER;
//bool direction;
private void Awake()
{
PlayerController = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerController>();
rb = GetComponent<Rigidbody2D>();
playerRb = GameObject.FindGameObjectWithTag("Player").GetComponent<Rigidbody2D>();
//Player = GameObject.FindGameObjectWithTag("Player").GetComponent<Transform>();
}
private void Start()
{
targetPos = posB.position;
DirectionCalculate();
}
private void Update()
{
if (Vector2.Distance(transform.position, posA.position) < 0.1f)
{
targetPos = posB.position;
DirectionCalculate();
}
if (Vector2.Distance(transform.position, posB.position) < 0.1f )
{
targetPos = posA.position;
DirectionCalculate();
}
}
private void FixedUpdate()
{
Vector3 targetVelocity = moveDirection * speed;
rb.velocity = Vector3.SmoothDamp(rb.velocity, targetVelocity, ref velocity, smoothTime);
}
void DirectionCalculate()
{
moveDirection = (targetPos - transform.position).normalized;
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.CompareTag("Player"))
{
PlayerController.isOnPlatform = true;
//PlayerOnPlatform = true;
PlayerController.platformRb = rb;
playerRb.gravityScale = 0;
//PLAYER = collision.gameObject;
}
}
private void OnTriggerStay2D(Collider2D collision)
{
if (collision.CompareTag("Player"))
{
//PlayerCollision = collision.transform;
}
}
private void OnTriggerExit2D(Collider2D collision)
{
if (collision.CompareTag("Player"))
{
//PlayerOnPlatform = false;
PlayerController.isOnPlatform = false;
playerRb.gravityScale = 0;
}
}
}
PLAYERCONTROLLER
using System;
using System.Runtime.InteropServices;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Events;
using static UnityEngine.Networking.UnityWebRequest;
namespace TarodevController
{
[RequireComponent(typeof(Rigidbody2D), typeof(Collider2D))]
public class PlayerController : MonoBehaviour, IPlayerController
{
[SerializeField] private ScriptableStats _stats;
private Rigidbody2D _rb;
private CapsuleCollider2D _col;
private FrameInput _frameInput;
private Vector2 _frameVelocity;
private bool _cachedQueryStartInColliders;
private bool _ceilingHit;
private bool _isSliding;
private bool _CannotFlip;
public bool isOnPlatform;
public Rigidbody2D platformRb;
private bool _canSlide;
private SpriteRenderer flip;
private float _slideVelocity;
#region Interface
public Vector2 FrameInput => _frameInput.Move;
public event Action<bool, float> GroundedChanged;
public event Action Jumped;
public event Action <bool> Slided;
public event Action<bool> Crouched;
#endregion
private float _time;
private void Awake()
{
_CrouchSpeed = _stats.MaxSpeed;
_rb = GetComponent<Rigidbody2D>();
_col = GetComponent<CapsuleCollider2D>();
_cachedQueryStartInColliders = Physics2D.queriesStartInColliders;
_canSlide = true;
_isSliding = false;
flip = gameObject.GetComponent<SpriteRenderer>();
}
private void Update()
{
_time += Time.deltaTime;
GatherInput();
}
private void GatherInput()
{
_frameInput = new FrameInput
{
JumpDown = Input.GetButtonDown("Jump") || Input.GetKeyDown(KeyCode.C),
JumpHeld = Input.GetButton("Jump") || Input.GetKey(KeyCode.C),
Move = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"))
};
if (_stats.SnapInput)
{
_frameInput.Move.x = Mathf.Abs(_frameInput.Move.x) < _stats.HorizontalDeadZoneThreshold ? 0 : Mathf.Sign(_frameInput.Move.x);
_frameInput.Move.y = Mathf.Abs(_frameInput.Move.y) < _stats.VerticalDeadZoneThreshold ? 0 : Mathf.Sign(_frameInput.Move.y);
}
if (_frameInput.JumpDown)
{
_jumpToConsume = true;
_timeJumpWasPressed = _time;
}
}
private void FixedUpdate()
{
CheckCollisions();
HandleJump();
HandleDirection();
HandleCrouch();
HandleSlide();
ApplyMovement();
HandleGravity();
HandleFlip();
}
private void HandleFlip()
{
if (_frameVelocity.x < 0)
{
flip.flipX = true;
}
else if (_frameVelocity.x > 0)
{
flip.flipX = false;
}
}
private void HandleCrouch()
{
if (_isSliding)
{
// If sliding, do nothing
return;
}
if ((_grounded && ceilingHitCheck()) || (_frameInput.Move.y < 0 && _grounded))
{
Crouched?.Invoke(true);
_CrouchSpeed = _stats.MaxSpeed / 2;
GetComponent<CapsuleCollider2D>().size = new Vector2(0.47f, 0.95f);
GetComponent<CapsuleCollider2D>().offset = new Vector2(-0.09f, -0.52f);
}
else if (!_ceilingHit) // Add this condition to check if not currently hitting the ceiling
{
_CrouchSpeed = _stats.MaxSpeed;
GetComponent<CapsuleCollider2D>().offset = new Vector2(-0.09f, -0.05f);
GetComponent<CapsuleCollider2D>().size = new Vector2(0.47f, 1.9f);
Crouched?.Invoke(false);
}
}
private void HandleSlide()
{
// Check if the player is grounded, moving horizontally past a certain speed, holding down 's', and allowed to slide
if (_grounded && Input.GetAxisRaw("Vertical") < 0 && Mathf.Abs(_frameVelocity.x) >= _stats.StartSlideSpeed && _canSlide)
{
GetComponent<CapsuleCollider2D>().size = new Vector2(0.47f, 0.95f);
GetComponent<CapsuleCollider2D>().offset = new Vector2(-0.09f, -0.52f);
// Set sliding to true
_isSliding = true;
// Set canSlide to false
_canSlide = false;
// Preserve the current horizontal velocity (this will be your initial slide velocity)
_slideVelocity = _frameVelocity.x;
_CrouchSpeed = _stats.MaxSpeed / 2;
Slided?.Invoke(true);
}
// Check if the slide button is released or the player stops holding down 's'
if (Input.GetAxisRaw("Vertical") >= 0 && _canSlide == false)
{
// Set sliding to false
_isSliding = false;
// Set canSlide to true
_canSlide = true;
Slided?.Invoke(false);
}
if (_isSliding && _canSlide == false)
{
// Gradually decrease the slide velocity using a slide deceleration factor
_slideVelocity = Mathf.MoveTowards(_slideVelocity, 0, _stats.SlideDeceleration * Time.fixedDeltaTime);
// Assign the slide velocity to the frame velocity's x component
_frameVelocity.x = _slideVelocity;
// If the slide velocity reaches zero, stop sliding
if ((Math.Abs(_slideVelocity) <= _CrouchSpeed) && (_stats.MinSlideSpeedIsCrouch) && _canSlide == false)
{
_isSliding = false;
Slided?.Invoke(false);
}
else if ((Math.Abs(_slideVelocity) <= _stats.MinSlideSpeed) && (!_stats.MinSlideSpeedIsCrouch) && _canSlide == false)
{
_isSliding = false;
Slided?.Invoke(false);
}
}
}
bool ceilingHitCheck()
{
RaycastHit2D[] results = new RaycastHit2D[10];
int numHits = Physics2D.CapsuleCastNonAlloc(_col.bounds.center, _col.size, _col.direction, 0, Vector2.up, results, _stats.CeilingDistance, ~_stats.PlayerLayer);
bool hit = false;
foreach (var result in results)
{
if (result.collider != null && !result.collider.isTrigger)
{
hit = true;
break;
}
}
return hit;
}
#region Collisions
private float _frameLeftGrounded = float.MinValue;
private bool _grounded;
private void CheckCollisions()
{
Physics2D.queriesStartInColliders = false;
// Ground and Ceiling
bool groundHit = Physics2D.CapsuleCast(_col.bounds.center, _col.size, _col.direction, 0, Vector2.down, _stats.GrounderDistance, ~_stats.PlayerLayer);
bool ceilingHit = ceilingHitCheck();
Debug.Log($"Ground Hit: {groundHit}, Ceiling Hit: {ceilingHit}");
// Hit a Ceiling
if (ceilingHit)
{
_frameVelocity.y = Mathf.Min(0, _frameVelocity.y);
}
// Landed on the Ground
if (!_grounded && groundHit)
{
_grounded = true;
_coyoteUsable = true;
_bufferedJumpUsable = true;
_endedJumpEarly = false;
GroundedChanged?.Invoke(true, Mathf.Abs(_frameVelocity.y));
}
// Left the Ground
else if (_grounded && !groundHit)
{
_grounded = false;
_frameLeftGrounded = _time;
GroundedChanged?.Invoke(false, 0);
}
Physics2D.queriesStartInColliders = _cachedQueryStartInColliders;
}
#endregion
#region Jumping
private bool _jumpToConsume;
private bool _bufferedJumpUsable;
private bool _endedJumpEarly;
private bool _coyoteUsable;
private float _timeJumpWasPressed;
private float _CrouchSpeed;
private bool HasBufferedJump => _bufferedJumpUsable && _time < _timeJumpWasPressed + _stats.JumpBuffer;
private bool CanUseCoyote => _coyoteUsable && !_grounded && _time < _frameLeftGrounded + _stats.CoyoteTime;
private void HandleJump()
{
if (!_endedJumpEarly && !_grounded && !_frameInput.JumpHeld && _rb.velocity.y > 0) _endedJumpEarly = true;
if (!_jumpToConsume && !HasBufferedJump) return;
if (_grounded || CanUseCoyote) ExecuteJump();
_jumpToConsume = false;
}
private void ExecuteJump()
{
_endedJumpEarly = false;
_timeJumpWasPressed = 0;
_bufferedJumpUsable = false;
_coyoteUsable = false;
_frameVelocity.y = _stats.JumpPower;
Jumped?.Invoke();
}
#endregion
#region Horizontal
private void HandleDirection()
{
if (!_isSliding)
{
if (_frameInput.Move.x == 0)
{
var deceleration = _grounded ? _stats.GroundDeceleration : _stats.AirDeceleration;
_frameVelocity.x = Mathf.MoveTowards(_frameVelocity.x, 0, deceleration * Time.fixedDeltaTime);
}
else
{
_frameVelocity.x = Mathf.MoveTowards(_frameVelocity.x, _frameInput.Move.x * _CrouchSpeed, _stats.Acceleration * Time.fixedDeltaTime);
}
}
else if (_isSliding && _slideVelocity == 0 && Input.GetAxisRaw("Vertical") < 0)
{
// If the slide has ended and the player is still holding down 's', transition to crouching
HandleCrouch();
}
}
#endregion
#region Gravity
private void HandleGravity()
{
if (_grounded && _frameVelocity.y <= 0f)
{
_frameVelocity.y = _stats.GroundingForce;
}
else
{
var inAirGravity = _stats.FallAcceleration;
if (_endedJumpEarly && _frameVelocity.y > 0) inAirGravity *= _stats.JumpEndEarlyGravityModifier;
_frameVelocity.y = Mathf.MoveTowards(_frameVelocity.y, -_stats.MaxFallSpeed, inAirGravity * Time.fixedDeltaTime);
}
}
#endregion
private void ApplyMovement()
{
if (isOnPlatform)
{
_rb.velocity = new Vector2(Mathf.Clamp((_frameVelocity.x + (platformRb.velocity.x + (platformRb.velocity.x * 0.02f ))), -_stats.MaxSpeed, _stats.MaxSpeed), _frameVelocity.y);
}
else
{
_rb.velocity = new Vector2(_frameVelocity.x, _frameVelocity.y);
}
}
#if UNITY_EDITOR
private void OnValidate()
{
if (_stats == null) Debug.LogWarning("Please assign a ScriptableStats asset to the Player Controller's Stats slot", this);
}
#endif
}
public struct FrameInput
{
public bool JumpDown;
public bool JumpHeld;
public Vector2 Move;
}
public interface IPlayerController
{
public event Action<bool, float> GroundedChanged;
public event Action Jumped;
public event Action<bool> Crouched;
public event Action<bool> Slided;
public Vector2 FrameInput { get; }
}
}