Skip to content

Narwhal Simulator 6: Blocks the Right Way

Hi everyone! I’ve been very hard at work the past few days on Narwhal Simulator. I’ve been working in making a block system, a system which handles the health and destruction of blocks. 

I’ll get all the minor changes out of the way first. I upgraded the renderer to the Universal Render Pipeline. This doesn’t mean much for the game right now, but it means later we can have 2D lights if we want. I also sorted some of the stuff that was laying around in the Assets folder into specific folders, so the project is more organized now. 

Now let’s get into the fun (or boring if you don’t like programming) stuff. This post is rather long and rather technical. If you don’t like this kind of post, let me know and I’ll do something different for Friday. For the tiles, I decided to use the Unity tilemap system.

How does the Unity tilemap system work? You must first make a base object that all your tilemaps will be parented to. It has a grid on it that controls the position of the tiles, the size of the tiles, and the “cell swizzle”. The cell swizzle isn’t very important and I just left it at the default, but it sounds super funny. It controls the order of the coordinate system the cells use, so kind of like rotating the grid without rotating the actual tiles, if you were interested. Then, you have to make a child object with a tilemap and a tilemap renderer on it. This is your actual tilemap, and you can have multiple if you want. You can also add a tilemap collider to have it collide. If you do intend to have a collider, I would recommend putting a composite collider on the grid object and checking the “used by composite” option on the tilemap collider. Usually, the tilemap collider would generate a separate square for each tile, which is sort of inefficient and also means that your objects can sort of snag onto the middles between the tiles. The composite collider fixes both of these issues.

The tilemap system uses Tile objects to represent what is in each cell, or space. Each tile only stores a bit of data, including what type of object it is, and a few other things. This is very efficient because you don’t store stuff that doesn’t change. For example, every ice tile has a reference to the same ice Tile object, which stores the information about the sprite, colour, and collider type. This means that the tiles are stored very efficiently and I can make very large maps without the game lagging too much.

There are 2 types of custom data I need to store outside of the tilemap:

  • Data about block types. This includes things like the block’s name and maximum health. These are shared between all blocks of the same type.
  • Data about specific blocks. This includes things like the block’s current health. This data isn’t shared between blocks and each block has its own data.

A block’s maximum health is something that must be stored on a block type to block type basis, since each type of block has a different amount of maximum health, but every block of that type has the same maximum health. This isn’t too hard to implement. To do this, I created a dictionary with Sprite as the key and a custom struct called BlockType. This stores data about each type of block. It’s not ideal, because since the key of the dictionary is the tile’s Sprite, you can’t easily get data about a certain block type by name. Here’s an example of how I get data about the maximum health of a block:

blockTypesDict[t.sprite].maxHealth // t is a Tile

This is good for now and it might be good for the rest of development, but I expect I’ll have to change it at some point.

The second type of data, data about specific blocks, is harder to implement. Each block has its own health value since when the player damages one block, we don’t want all the other blocks of the same type to be damaged too. This isn’t too hard to implement by itself, we can just make a dictionary with the key being something that identifies a certain block instead of a certain type of block. The location of the block works for this since I doubt blocks will be moving around. So I can make a dictionary with the key as a Vector2Int (an x and y coordinate) and the value as whatever I need. At some point, this will need to be a struct, but for now, since the only type of data, each block needs is health, a float. So right now, this is how I can get the health of a specific block.

dynamicTileData[pos] // pos is a Vector2Int, 2 ints that represent x and y coordinates

This isn’t everything though. Every block needs to have a health whenever it gets damaged, otherwise, the game won’t know whether the block will be destroyed or not. However, there is no point in storing the health value of every single block in the tilemap, since most of them are full health and many of them will likely never be damaged anyway. So, whenever the player damages a block, the game checks if the health dictionary (I called this dynamicTileData) has the block in it and if it doesn’t, to add the block to the dictionary with the default health. If the block is destroyed, we can remove it again. This is probably the most memory-efficient way to do this.

I made break textures that indicate how much health the block has left. I did this with a second tilemap on top of the first one. This wasn’t too hard and I don’t really have anything to say about it, so here’s a picture. 

I think that wraps up everything I want to say about the block tilemaps. If you want to look at the block logic further, you can visit the GitHub repository. The Scripts/Blocks folder holds all the block scripts. The BlockManager script is attached to the Grid GameObject. It holds all the data about blocks and block types, and it handles blocks being damaged and destroyed. There is also the BlockType script, which holds the BlockType struct.

I added some new types of blocks. I added the Ice block, which was here before but now has a name and some friends :). I added the Stone block, a harder block than the Ice block. I also added the Gold block. The Gold block’s health is in the middle of Ice and Stone. Gold doesn’t serve much of a purpose at the moment, but in the future when score is implemented, it will be very valuable. I also added WarningTape, a block that doesn’t have a health or a BlockType. It serves as a visible border at the edge of the playable space. Just bashing into the side of the screen was a bit lame. I also added the Bedrock block, a block that will be stronger than stone and serve as a border of sorts for the first area, hopefully being strong enough to deter players from leaving the first area before they are strong enough. I haven’t put them in the game yet, but I probably will have it by Friday. Here’s what the blocks look like side by side, as well as the new break textures, the textures that show the player an approximation of how much health the block has left.

I said I would make the player look less bad by today, but I haven’t done that yet. I will do that for Friday, I promise.

Unity’s tilemap system has a very nice brush, which lets me paint with blocks. Here’s my masterpiece. I’ll print you an exclusive signed copy for only $599.99, a bargain for such a beautiful piece of art.

That’s all! Next time, I will be working on making the player look better and less rectangular. I also plan to add a score system, with currency called Narwhal Coins. See you Friday!

Links recap:

GitHub: https://github.com/AgentD1/NarwhalSimulator

Trello: https://trello.com/b/Nd7rNvWU/narwhal-simulator

Leave a Reply