I have just started working with Unity to simply learn how it works. I've been trying to get grid-based movement and collision with a tilemap working, and it's mostly there, but there's one bit that's not quite right and I'm unsure as to why.
Currently, the player sprite is moved in a grid-based fashion. When it approaches an obstacle, the movement script uses Physics.OverlapCircle
, filtering on the collision layer, to determine whether the player can advance to the next grid cell. This works, save for the fact that it appears to be using the entire bounds of the sprite for collision detection, whereas I want to allow the top portion of the sprite (i.e. the head) to overlap and actually detect collision with the lower portion (the body).
Here is a screenshot of my editor, with the player selected. You can see that the player appears to be covering a 2x2 area of the grid (I think this has to do with the size of the sprite not matching the tilemap). The player sprite is 32x32 with Pixels Per Unit
defined as 32, whereas the tilemap has a Pixels Per Unit
defined as 16.
Here is my movement script, as well. The method at the bottom of the file, CanMoveToTargetPosition
covers the overlap logic.
using System.Collections.Generic;
using UnityEngine;
public class Movement : MonoBehaviour
{
private const float movementSpeed = 5f;
public Transform movementTarget;
public LayerMask collisionLayer;
public Animator animator;
// Start is called before the first frame update
void Start()
{
movementTarget.parent = null;
}
// Update is called once per frame
void Update()
{
MovePlayer();
if (ShouldCheckMovement())
{
CheckInputForMovement();
}
}
private void MovePlayer()
{
transform.position = Vector3.MoveTowards(transform.position, movementTarget.position, movementSpeed * Time.deltaTime);
}
private bool ShouldCheckMovement() => Vector3.Distance(transform.position, movementTarget.position) <= .05f;
private void CheckInputForMovement()
{
float horizontal = Input.GetAxisRaw("Horizontal");
float vertical = Input.GetAxisRaw("Vertical");
float horizontalPositive = Mathf.Abs(horizontal);
float verticalPositive = Mathf.Abs(vertical);
var velocity = Vector3.zero;
if (horizontalPositive == 1f)
{
velocity = new Vector3(horizontal, 0f);
}
else if (verticalPositive == 1f)
{
velocity = new Vector3(0f, vertical);
}
var targetPosition = movementTarget.position + velocity;
if (!CanMoveToTargetPosition(targetPosition))
{
movementTarget.position = targetPosition;
}
animator.SetFloat("Horizontal", horizontal);
animator.SetFloat("Vertical", vertical);
float speed = Mathf.Clamp((horizontalPositive + verticalPositive) / 2f, 0f, 1f);
animator.SetFloat("Speed", speed);
}
private bool CanMoveToTargetPosition(Vector2 targetPosition) =>
Physics2D.OverlapCircle(targetPosition, .2f, collisionLayer);
}
The tilemap has an attached TilemapCollider2D
component. I've tried including a RigidBody2D
with a CompositeCollider2D
, but this actually makes the result worse - the player collides with some parts of the collision layer, but can walk through others.
I've tried using a BoxCollider2D
to allow me to define the actual collision area on the player, but I don't seem to be able to then get the overlap collision working with the tilemap.
Here is a view of my collision layer with the tilemap collision areas defined:
Finally, you can see a recording of my issue as a GIF or MP4.
I'm sure this is more to do with my lack of understanding of how the physics overlap, or colliders work in Unity, but I'm struggling to find any resources to help with this specific issue. So, any links for further reading would be appreciated! I did manage to get non-grid-based movement working with collision detection by just using a Rigidbody2D
and BoxCollider2D
on the player, and TilemapCollider2D
, RigidBody2D
and CompositeCollider2D
on the tilemap. But I would like to retain the grid-based movement.
Any help would be appreciated! Thanks.