How to use Tilemap to create enemies (or terrain) and implement single tile damage determination

This article is used to record the problems encountered in a Game Jam and related experience methods.

Enemy requirements in the game:

The enemy is composed of blocks. If all the blocks are destroyed, the enemy will be defeated.

Planning needs:

The blocks that make up the enemy are implemented by Tilemap instead of grid adsorption. (The requirements are similar to implementing Tilemap to create destructible terrain)

Problem analysis:

Only one Tilemap collider2D collision body is mounted on an enemy. How to determine the damage of all individual blocks.

Why use Tilemap to make enemies?

Because the enemies are made up of small blocks, and some enemies are made up of hundreds of blocks, it is inefficient to manually splice them one by one. So if you use Tilemap, you can quickly create a complete enemy.

What is so special about this requirement?

In general, the damage determination is to mount a separate collision body for each object, and determine the damage through collision detection. But Tilmap’s All tiles share a collider. In this case, if damage is directly applied to the enemy through collision detection, all tiles will suffer the same damage, which is obviously inconsistent. Required(The requirement is for individual tiles to undergo separate damage determinations).

Solution ideas

The condition for defeating the enemy is that the enemy is defeated when all the blocks it consists of are destroyed. So you should first get the total number oftiles (totalTiles) that make up the current enemy. Note that the blocks we want to obtain must be blocks with colliders, so that all tiles that are not enemy components can be filtered out.

private void Start()
    {
        //initialization
        destroyBody = GetComponent<Tilemap>();
        tileHealth = new Dictionary<Vector3Int, int>();
        totalTiles = 0;

        foreach (var pos in destroyBody.cellBounds.allPositionsWithin)
        {
            if (destroyBody.HasTile(pos) & amp; & amp; destroyBody.GetColliderType(pos) != Tile.ColliderType.None)
            {
                tileHealth.Add(pos, singleBodyHp);
                totalTiles + + ;
            }
        }
    }

There is a small pit at this location. There may be some transparent textures in the resources transferred from the art. Such places are parts that cannot be discovered by players in the game. The collision body needs to be changed to None to prevent the level from getting stuck.

What can be imagined is that although there is only one collider, the detected object is always a complete enemy, but the contact point generated by collision detection is unique to a single tile, so it can pass collision detection To obtain the contact point, because the tilemap itself has a coordinate system different from the world coordinates, it is necessary to convert the contact point coordinates into grid coordinates through WorldToCell to provide accurate position information for subsequent elimination of individual tiles.

In the Start function, we use desroyBody.cellBounds.allPositionsWithin to store the current position information of all tiles together with the health value into the dictionary. When calling the function that applies damage, pass in the position information and damage value. If the health value of the current position tile is 0, then eliminate the single tile through the SetTile method, and then move it from the dictionary Divide and reduce the total number of remaining tiles by one.

The summary is that we do not directly use collision detection to determine damage, but obtain the collision point indirectly through collision detection, and then lock a single tile through the collision point. Just operate the tiles.

This problem is not a difficult one, but I found it quite interesting when I encountered it for the first time, so I made this brief experience record.

The specific source code is as follows:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;

public class DestroyBody : MonoBehaviour
{
    //Draw the enemy's Tilemap
    private Tilemap destroyBody;
    //Use a dictionary to store the coordinates and blood volume of a single tile
    private Dictionary tileHealth;
    //Single tile blood volume
    private int singleBodyHp;
    //Particle effects after breaking tiles
    public GameObject boomEffect;
    //The total number of tiles that make up the enemy
    private int totalTiles;
    //In order to expand the damage determination range, add offsets in the X and Y directions
    public float offsetX;
    public float offsetY;

    private void Awake()
    {
        singleBodyHp = gameObject.transform.parent.GetComponent().singleHp;
    }

    private void Start()
    {
        //initialization
        destroyBody = GetComponent<Tilemap>();
        tileHealth = new Dictionary<Vector3Int, int>();
        totalTiles = 0;

        foreach (var pos in destroyBody.cellBounds.allPositionsWithin)
        {
            if (destroyBody.HasTile(pos) & amp; & amp; destroyBody.GetColliderType(pos) != Tile.ColliderType.None)
            {
                tileHealth.Add(pos, singleBodyHp);
                totalTiles + + ;
            }
        }
    }

    private void Update()
    {
        //If the total number of tiles is 0, it is determined that the enemy has been destroyed
        if (totalTiles == 0)
        {
            Destroy(gameObject.transform.parent.gameObject);
        }
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.CompareTag("PlayerBullet"))
        {
            //Get the damage value
            int damage = collision.gameObject.GetComponent().damage;
            //Get the collision point
            Vector3 hitPos = collision.contacts[0].point;

            //Generate 8 additional points to expand the damage determination range
            Vector3Int[] tilePositions = new Vector3Int[]
            {
                destroyBody.WorldToCell(hitPos),
                destroyBody.WorldToCell(hitPos + new Vector3(offsetX, 0f, 0f)),
                destroyBody.WorldToCell(hitPos - new Vector3(offsetX, 0f, 0f)),
                destroyBody.WorldToCell(hitPos + new Vector3(0f, offsetY, 0f)),
                destroyBody.WorldToCell(hitPos - new Vector3(0f, offsetY, 0f)),
                destroyBody.WorldToCell(hitPos + new Vector3(offsetX, offsetY, 0f)),
                destroyBody.WorldToCell(hitPos + new Vector3(offsetX, -offsetY, 0f)),
                destroyBody.WorldToCell(hitPos - new Vector3(offsetX, -offsetY, 0f)),
                destroyBody.WorldToCell(hitPos - new Vector3(offsetX, offsetY, 0f))
            };

            //Call the hit function
            foreach (Vector3Int tilePos in tilePositions)
            {
                TakeDamage(damage, tilePos, hitPos);
            }
        }
    }

    /// 
    ///Receive damage
    /// 
    /// 
    /// 
    /// 
    public void TakeDamage(int damage, Vector3Int tilePos, Vector3 boomEffectPos)
    {
        //Whether there is a tile at tilePos in the current tilemap
        if (destroyBody.HasTile(tilePos))
        {
            if (tileHealth.ContainsKey(tilePos))
            {
                tileHealth[tilePos] -= damage;
                //The blood volume of the tile at the current position is less than 0, and there are tiles
                if (tileHealth[tilePos] <= 0 & amp; & amp; destroyBody.GetTile(tilePos) != null)
                {
                    //Remove the tile at this location
                    destroyBody.SetTile(tilePos, null);
                    //The total number of tiles is reduced by one
                    totalTiles--;
                    AudioManager.Instance.PlaySound(AudioName.Sound_EnemyDead);
                    //Play the crushing effect
                    GameObject effect = Instantiate(boomEffect, boomEffectPos, Quaternion.identity);
                    Destroy(effect, 1.5f);
                    //remove from dictionary
                    tileHealth.Remove(tilePos);
                }
            }
        }
    }
}

The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. Algorithm skill tree Home page Overview 57053 people are learning the system