The player is always above the platform in the air. How to make the player to be perfectly on the platform top?

4 weeks ago 26
ARTICLE AD BOX

player is a bit above the platform the wooden deck.

Both the player and the wooden deck have colliders.

both colliders

The player has the character controller component for the collider.

the player inspector settings.

The wooden deck has a box collider.

deck inspector settings

This script is attached to the player:

using UnityEngine; [RequireComponent(typeof(CharacterController))] [RequireComponent(typeof(Animator))] public class ModernCharacterController : MonoBehaviour { [Header("Movement Settings")] public float walkSpeed = 4f; public float runSpeed = 8f; [Header("Vertical (Screen Up/Down) Movement")] public float verticalMoveSpeed = 3f; [Header("Jump Settings")] public float jumpHeight = 2f; public float gravity = -9.81f; [Header("Animation Smoothing")] [Tooltip("How fast the Speed parameter ramps up (higher = snappier)")] public float speedSmoothRate = 10f; [Header("2.5D Settings")] public bool lockZAxis = true; private CharacterController controller; private Animator animator; private float lockedZPosition; private Vector3 velocity; private bool isGrounded; private float currentAnimSpeed; // smoothed value sent to animator void Start() { controller = GetComponent<CharacterController>(); animator = GetComponent<Animator>(); lockedZPosition = transform.position.z; // Face right (positive X) for side-scroller transform.rotation = Quaternion.Euler(0, 90, 0); // Auto-place player on ground SnapToGround(); } void SnapToGround() { // Temporarily disable the CharacterController so we can set position directly controller.enabled = false; RaycastHit hit; Vector3 rayOrigin = transform.position + Vector3.up * 10f; if (Physics.Raycast(rayOrigin, Vector3.down, out hit, 50f)) { transform.position = new Vector3(transform.position.x, hit.point.y, transform.position.z); Debug.Log($"Snapped to ground at Y: {hit.point.y} (hit: {hit.collider.name})"); } controller.enabled = true; } void Update() { HandleMovement(); HandleAnimator(); } void HandleMovement() { // Ground check isGrounded = controller.isGrounded; if (isGrounded && velocity.y < 0) { velocity.y = -2f; } // Raw input for instant response float moveXRaw = Input.GetAxisRaw("Horizontal"); float moveYRaw = Input.GetAxisRaw("Vertical"); // Determine if running bool isRunning = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); float currentMoveSpeed = isRunning ? runSpeed : walkSpeed; // Build movement vector Vector3 move = new Vector3(moveXRaw * currentMoveSpeed, 0f, 0f); // Optional vertical screen movement (W/S) Vector3 verticalMove = new Vector3(0f, moveYRaw * verticalMoveSpeed, 0f); // Face horizontal direction if (moveXRaw > 0.1f) transform.rotation = Quaternion.Euler(0, 90, 0); else if (moveXRaw < -0.1f) transform.rotation = Quaternion.Euler(0, -90, 0); // Apply horizontal + vertical movement controller.Move((move + verticalMove) * Time.deltaTime); // Jump if (Input.GetButtonDown("Jump") && isGrounded) { velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity); animator.SetTrigger("Jump"); } // Apply gravity velocity.y += gravity * Time.deltaTime; controller.Move(velocity * Time.deltaTime); // Lock Z axis if (lockZAxis) { float zDrift = transform.position.z - lockedZPosition; if (Mathf.Abs(zDrift) > 0.001f) { controller.Move(new Vector3(0f, 0f, -zDrift)); } } } void HandleAnimator() { float moveXRaw = Mathf.Abs(Input.GetAxisRaw("Horizontal")); bool isRunning = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); // Target speed for blend tree: 0 = idle, 0.5 = walk, 1 = run float targetAnimSpeed = 0f; if (moveXRaw > 0.1f) { targetAnimSpeed = isRunning ? 1f : 0.5f; } // Smooth it so animation transitions look natural currentAnimSpeed = Mathf.MoveTowards(currentAnimSpeed, targetAnimSpeed, speedSmoothRate * Time.deltaTime); animator.SetFloat("Speed", currentAnimSpeed); animator.SetBool("IsGrounded", isGrounded); } }

And this script is attached to the empty gameobject DockManager:

using System.Collections.Generic; using UnityEngine; public class DockManager : MonoBehaviour { public GameObject dockPrefab; public int sideViewSegments = 6; public int thirdPersonSegments = 24; public Transform player; [Header("Segment Settings")] [Tooltip("Leave 0 to auto-calculate from prefab")] public float segmentLengthOverride = 0f; [Header("Auto-calculated")] [SerializeField] private float segmentLength; private List<GameObject> segments = new List<GameObject>(); private float lastSpawnX; private int currentSegmentCount; void Start() { if (segmentLengthOverride > 0f) { segmentLength = segmentLengthOverride; } else { Renderer rend = dockPrefab.GetComponentInChildren<Renderer>(); if (rend != null) { GameObject temp = Instantiate(dockPrefab, Vector3.zero, dockPrefab.transform.rotation); Renderer[] allRenderers = temp.GetComponentsInChildren<Renderer>(); Bounds combinedBounds = allRenderers[0].bounds; for (int i = 1; i < allRenderers.Length; i++) { combinedBounds.Encapsulate(allRenderers[i].bounds); } segmentLength = combinedBounds.size.x; Debug.Log($"Calculated segment length: {segmentLength}"); Destroy(temp); } } currentSegmentCount = sideViewSegments; lastSpawnX = 0f; for (int i = 0; i < currentSegmentCount; i++) { SpawnSegment(false); } } void Update() { if (segments.Count == 0) return; if (player.position.x > segments[0].transform.position.x + segmentLength) { RecycleSegment(); } } // Call this from SimpleCameraFollow when switching views public void SetViewMode(bool isThirdPerson) { int targetCount = isThirdPerson ? thirdPersonSegments : sideViewSegments; if (targetCount > currentSegmentCount) { // Add more segments ahead int toAdd = targetCount - currentSegmentCount; for (int i = 0; i < toAdd; i++) { SpawnSegment(true); } } else if (targetCount < currentSegmentCount) { // Remove excess segments from the back (farthest ahead) int toRemove = currentSegmentCount - targetCount; for (int i = 0; i < toRemove; i++) { if (segments.Count > targetCount) { GameObject last = segments[segments.Count - 1]; segments.RemoveAt(segments.Count - 1); Destroy(last); lastSpawnX -= segmentLength; } } } currentSegmentCount = targetCount; } void SpawnSegment(bool assignColor) { GameObject segment = Instantiate( dockPrefab, new Vector3(lastSpawnX, 0, 0), dockPrefab.transform.rotation ); segments.Add(segment); if (assignColor) SetRandomColor(segment); lastSpawnX += segmentLength; } void RecycleSegment() { GameObject first = segments[0]; segments.RemoveAt(0); first.transform.position = new Vector3(lastSpawnX, 0, 0); SetRandomColor(first); segments.Add(first); lastSpawnX += segmentLength; } void SetRandomColor(GameObject segment) { Renderer renderer = segment.GetComponent<Renderer>(); if (renderer == null) return; MaterialPropertyBlock block = new MaterialPropertyBlock(); renderer.GetPropertyBlock(block); Color randomColor = Random.ColorHSV(0f, 1f, 0.6f, 1f, 0.6f, 1f); block.SetColor("_BaseColor", randomColor); block.SetColor("_Color", randomColor); renderer.SetPropertyBlock(block); } }
Read Entire Article