I’m developing a platform jumping game.
In this game, I use tilemap to build the level. And each tile triggers different effect when player stand on those tile. The problem is when I walk from one tile to another type of tile, the method OnCollisionEnter2D isn’t called, however, When I jump to those tile, that method is called.
And the following is some details for the game.
class PlayerController is attached to player, which is derived from KinematicObject. KinematicObject handling the lower logic of physics world, and leave those game logic like state machine to PlayerController. and I use Rigidbody2D.Cast to detect whether or not player is collide with some tile then do the corresponding things, like stop moving when grounded and bounce back when hit tile.
And TileCollisionEffect.cs is attached to the tilemap object, for triggering effects.
here is some code snippet of KinematicObject.cs and TileCollisionEffect.cs
KinematicObject.cs
protected virtual void FixedUpdate()
{
if (velocity.y < 0)
velocity += gravityModifier * Physics2D.gravity * Time.deltaTime;
else
velocity += Physics2D.gravity * Time.deltaTime;
IsGrounded = false;
var deltaPosition = velocity * Time.deltaTime;
var moveAlongGround = new Vector2(groundNormal.y, -groundNormal.x);
var move = moveAlongGround * deltaPosition.x;
PerformMovement(move, false);
move = Vector2.up * deltaPosition.y;
PerformMovement(move, true);
}
void PerformMovement(Vector2 move, bool yMovement)
{
var distance = move.magnitude;
if (distance > minMoveDistance)
{
var count = body.Cast(move, contactFilter, hitBuffer, distance + shellRadius);
for (var i = 0; i < count; i++)
{
var currentNormal = hitBuffer[i].normal;
if (currentNormal.y > minGroundNormalY) //minGroundNormalY = 0.65
{
IsGrounded = true;
if (yMovement)
{
groundNormal = currentNormal;
currentNormal.x = 0;
}
}
if (IsGrounded)
{
var projection = Vector2.Dot(velocity, currentNormal);
if (projection < 0)
{
velocity = velocity - projection * currentNormal; // zero velocity when hit the ground
}
}
else
{
var projection=Vector2.Dot(velocity,currentNormal);
velocity = velocity - 2*projection*currentNormal; //bounce back if hit tilemap when flying
}
var modifiedDistance = hitBuffer[i].distance - shellRadius;
distance = modifiedDistance < distance ? modifiedDistance : distance;
}
}
body.position = body.position + move.normalized * distance;
}
TileCollisionEffect.cs
public class TileCollisionEffect : MonoBehaviour
{
[System.Serializable]
public class CollisionEvent : UnityEvent<PlayerController> { }
[System.Serializable]
public struct TileEffect {
public TileBase tile;
public CollisionEvent effect;
}
public TileEffect[] effects;
Dictionary<TileBase, CollisionEvent> _effectMap;
private void OnEnable() {
if (_effectMap != null)
return;
_effectMap = new Dictionary<TileBase, CollisionEvent>(effects.Length);
foreach (var entry in effects)
_effectMap.Add(entry.tile, entry.effect);
idx = 0;
}
private void OnCollisionEnter2D(Collision2D collision) {
var points=new ContactPoint2D[100];
var collider = collision.collider;
int contact_point_num = collider.GetContacts(points);
Debug.Log("TileCollisionEffect.OnCollisionEnter()"
+" contact.normal = "+points[0].normal
+" contact.points.Count = "+contact_point_num
);
if(points[0].normal.y < 0 ) // if player is not hit from above, then do nothing
{
return;
}
var map = GetComponent<Tilemap>();
var grid = map.layoutGrid;
var tileWorldPos = points[1].point;
Vector3 localPosition = grid.transform.InverseTransformPoint(tileWorldPos);
localPosition = localPosition - (Vector3)points[1].normal*0.5f;
Vector3Int cell = grid.LocalToCell(localPosition);
var tile = map.GetTile(cell);
if(tile == null)
{
Debug.Log("TileCollisionEffect.OnCollisionEnter() tile is null");
return;
}
var playerController = collider.gameObject.GetComponent<PlayerController>();
if (_effectMap.TryGetValue(tile, out CollisionEvent effect) && effect != null)
{
effect.Invoke(playerController);
}
}
public void Orange(PlayerController pc) {
pc.jump_coef_w =2;
Debug.Log("Orange");
}
public void Green(PlayerController pc) {
if(pc == null)
{
Debug.Log("TileCollisionEffect.Orange() pc is null");
}
pc.jump_coef_h =1.5f;
Debug.Log("Green");
}
public void Alice(PlayerController pc) {
pc.isStandonIce = true;
Debug.Log("Alice");
}
}