2D platformer player intersecting floors and walls

Hi, I’m a bit of a newbie to Unity (and to making games), but I’m trying to build a retro-style 2D platform game along the lines of Super Mario Bros. I’m using 2D raycasting rather than physics to achieve that classic feel, but I’m having a problem where my player character is intersecting the floors and walls that I’ve set up. They stop his movement, but he sits a little inside them.

I’m using 16-bit-style pixel graphics, so I’m trying to keep the positions of all assets integers rather than floats.

I’m not sure what I’m doing wrong so I’m including my whole character controller code. I’d really appreciate any help from anyone.

Thanks very much!

using UnityEngine;
using System.Collections;

public class PlayerController : MonoBehaviour {

	public int positionX, positionY;
	public int speed;
	public LayerMask playerLayerMask;

	private float moveHorizontal, moveVertical;
	private Animator animator;
	private RaycastHit2D hitFront1, hitRear1, hitBottom1, hitTop1;
	private int distXToMove, distYToMove;
	private bool jump;
	private int jumpCount;
	private bool jumping;
	private int fallAccel;
	private int fallAccelMax;

	void Start()
	{
		animator = this.GetComponent<Animator> ();
		jumping = false;
		fallAccelMax = 10;
		fallAccel = 0;
		jumpCount = 15;
	}


	void Update ()
	{
		GetPosition ();			// Get player position.

		GetPlayerInput ();		// Get input from the player.

		PerformPhysics ();

		AnimateSprite ();		// Choose correct animation.

		CollisionDetection ();	// Detect collisions with objects.

		TranslatePlayer ();		// Move the player.
	}


	void GetPosition()
	{
		positionX = (int)transform.position.x;	// Store initial position.
		positionY = (int)transform.position.y;	//
	}


	void GetPlayerInput ()
	{
		moveHorizontal = (int)Input.GetAxis ("Horizontal");	// Get direction.

		distXToMove = (int)(moveHorizontal * speed);

		if (!jumping)
		{
			jump = Input.GetKeyDown ("joystick button 1");
			if (jump)
				jumping = true;
		}
	}


	void PerformPhysics()
	{
		if (!hitBottom1 && fallAccel < fallAccelMax && !jumping)
			fallAccel++;
		else if (hitBottom1)
			fallAccel = 0;

		distYToMove = (int)(-fallAccel);

		if (jumping)
		{
			if (jumpCount > 0)
			{
				distYToMove = jumpCount;
				jumpCount--;
			}
			else
			{
				jumpCount = 15;
				jumping = false;
			}
		}
	}


	void AnimateSprite()
	{
		if (moveHorizontal != 0)
		{
			// Play run animation if moving horizontally.
			animator.SetInteger ("PixelMeState", 1);

			// Reflect player in X if moving left.
			if (moveHorizontal < 0)
				transform.localScale = new Vector2(-1, 1);
			else if (moveHorizontal > 0)	
				transform.localScale = new Vector2(1, 1);
		}
		// Play idle animation by default.
		else
			animator.SetInteger ("PixelMeState", 0);
	}
	

	void CollisionDetection()
	{
	// Check for collision from RHS of character.
	hitFront1 = Physics2D.Raycast(transform.position + new Vector3 (5, 12, 0), 
	            new Vector3 (distXToMove, 0, 0), distXToMove, playerLayerMask);
	// Draw line to show ray from RHS of character.
	Debug.DrawRay (transform.position + new Vector3 (5, 12, 0), 
	               new Vector3 (distXToMove, 0, 0), Color.green);

	// Check for collision from LHS of character.
	hitRear1 = Physics2D.Raycast(transform.position + new Vector3 (-6, 12, 0), 
	               new Vector3 (distXToMove, 0, 0), distXToMove, playerLayerMask);
	// Draw line to show ray from LHS of character.
	Debug.DrawRay (transform.position + new Vector3 (-6, 12, 0), 
	               new Vector3 (distXToMove, 0, 0), Color.red);

	// Check for collision from bottom of character.
	hitBottom1 = Physics2D.Raycast(transform.position, new Vector3 (0, distYToMove, 0), 
	             distYToMove, playerLayerMask);
	// Draw line to show ray from bottom of character.
	Debug.DrawRay (transform.position, new Vector3 (0, distYToMove, 0), Color.grey);

	// Check for collision from top of character.
	hitTop1 = Physics2D.Raycast(transform.position + new Vector3 (0, 24, 0),
	          new Vector3 (0, distYToMove, 0), distYToMove, playerLayerMask);
	// Draw line to show ray from top of character.
	Debug.DrawRay (transform.position + new Vector3 (0, 24, 0), 
	               new Vector3 (0, distYToMove, 0), Color.cyan);
	}


	void TranslatePlayer()
	{
		// If wall will be reached this frame, and the player isn't already there,
		// move the remaining distance to the wall.
		if (hitFront1 && hitFront1.collider.tag == "Wall" && distXToMove > 0)
			distXToMove = (int)hitFront1.distance;

		// If wall will be reached this frame, and the player isn't already there,
		// move the remaining distance to the wall.
		if (hitRear1 && hitRear1.collider.tag == "Wall" && distXToMove < 0)
			distXToMove = (int)hitRear1.distance;

		// If platform will be reached this frame, and the player isn't already there,
		// move the remaining distance to the platform.
		if (hitBottom1 && hitBottom1.collider.tag == "Platform" && distYToMove < 0)
			distYToMove = (int)hitBottom1.distance;	

		// If platform will be reached this frame, and the player isn't already there,
		// move the remaining distance to the platform.
		if (hitTop1 && hitTop1.collider.tag == "Ceiling" && distYToMove > 0)
			distYToMove = (int)hitTop1.distance;


		positionX += distXToMove;		// Increment position
		positionY += distYToMove;		//

		// Set new player position.
		transform.position = new Vector2 (positionX, positionY);
	}

Have you adjusted the colliders properly

I made a simple setup, from left to right:

  1. 2 cubes, 1x1x1, boxcollider, upper one plus rigidbody
  2. 1 cube, 1x2x1
  3. 2 quads, renderer without material, boxcollider2d, upper one plus rigidbody2d

As one can immediately see that 3D physics and 2D physics are completely different on how they are handled. The cubes overlap, the quads even have gaps. You can also see, that all three setups have different heights in total, adding up of course when there’s more colliders.

The 2D colliders use some kind of skin width as the charactercontroller does, but its width is neither accessible nor documented.

On a jam I found increasing the size of a sprite via scale by 0.03 was quite good, but in total I think you’re pretty much out of luck with Unity and pixel precision while using pyhsics.

I managed to solve the problem. Well it was actually a number of problems. Firstly I had set up all the graphic elements to have pivots at their bases rather than their centres. However the ray casting was occurring relative to the centre of the player character. So first I changed all the pivots to the object centres and tweaked the ray casting.

This didn’t solve my problem though. However after a lot of hair-tearing, I realised that there were a couple of minus signs in the wrong places in my collision detection code. Fixing these solved my problem and now the character detects collisions with pixel-perfect accuracy.

Thanks for the help! :slight_smile: