BitMap in Godot – Complete Guide

Bitmaps are one of those fundamental concepts that bridge across various fields in computer science, from image processing to gaming. Understanding how to manipulate a matrix of boolean values not only helps in optimizing data storage but also opens up a world of possibilities in algorithm implementation. Let’s dive into the BitMap class in Godot 4 to see how it can enhance your game development journey.

What Is BitMap?

BitMap in Godot 4 is, essentially, a boolean matrix. It’s a two-dimensional array that stores boolean values — think of it as an endless checkerboard where each square can either be occupied (true) or empty (false). Each element in this array, or each ‘bit’, consumes only one bit of memory. Therefore, it is an incredibly memory-efficient way to store binary data.

What Is It For?

The BitMap class provides a practical way to represent a binary matrix using natural cartesian coordinates. This means that you can efficiently handle large amounts of binary data for various purposes. It can be used for mask-based collisions, procedural generation, pathfinding algorithms, and much more. Its functions allow you to translate, resize, and manipulate the data in ways that are crucial for dynamic game mechanics.

Why Should I Learn It?

Learning how to use the BitMap class is invaluable for Godot developers. With its utility in managing space and optimizing processes, BitMap is an essential tool in the game developer’s toolkit. Knowing how to leverage this Godot feature will not only make your games run more efficiently but also open up creative avenues for implementing complex game logic. Let’s start exploring the potential of BitMap through coding examples and see how it can bolster your game development skills.

CTA Small Image
FREE COURSES AT ZENVA
LEARN GAME DEVELOPMENT, PYTHON AND MORE
ACCESS FOR FREE
AVAILABLE FOR A LIMITED TIME ONLY

Creating and Accessing a BitMap

Before using a BitMap, you need to create an instance of the BitMap class and then you’ll be able to manipulate it according to your needs. Here’s how you can create a BitMap and set or get the value of a bit:

var bitmap = BitMap.new()

# Set the size of the BitMap to 10x10.
bitmap.create(Size2(10, 10))

# Set the bit at position (5, 5) to true.
bitmap.set_bit(Vector2(5, 5), true)

# Get the value of the bit at position (5, 5).
var bit_value = bitmap.get_bit(Vector2(5, 5))
print(bit_value) # Prints: True

This simple example demonstrates the initialization of a BitMap, setting a bit, and then accessing it.

Manipulating Multiple Bits

It’s often necessary to change multiple bits at once. Below, you’ll find examples of how to fill a BitMap with a single value, or how to copy one BitMap to another.

# Fill the entire BitMap with `true`.
bitmap.fill(true)

# Create another BitMap.
var copy_bitmap = BitMap.new()
copy_bitmap.create(Size2(10, 10))

# Copy the contents of 'bitmap' to 'copy_bitmap'.
copy_bitmap.copy_from(bitmap)

Filling and copying BitMaps is essential when you are setting up an initial state or when you need to duplicate your BitMap data.

Manipulating BitMap Regions

When developing games, you may need to modify specific regions within a BitMap. Below are examples illustrating how to work with rectangular regions inside a BitMap.

# Set a rectangular region to false.
bitmap.set_rect(Rect2(Vector2(2, 2), Size2(4, 4)), false)

# Get a bit at position (3, 3) after setting rectangle.
var bit_3_3 = bitmap.get_bit(Vector2(3, 3))
print(bit_3_3) # Should be false as it's within the set rectangle.

Setting rectangular regions are particularly useful in scenarios like fog of war, patching terrain, or creating spaces within a level grid.

Advanced Operations: Bitwise Logic

For more complex scenarios, you might want to perform operations that involve two BitMaps. This can be achieved through bitwise logic operations such as AND, OR, and XOR, among others.

# Assume 'bitmap' and 'copy_bitmap' are already defined and created as per previous examples.

# Perform an AND operation between 'bitmap' and 'copy_bitmap'.
var and_bitmap = bitmap.op_and(copy_bitmap)

# Perform an OR operation between 'bitmap' and 'copy_bitmap'.
var or_bitmap = bitmap.op_or(copy_bitmap)

# Perform an XOR operation between 'bitmap' and 'copy_bitmap'.
var xor_bitmap = bitmap.op_xor(copy_bitmap)

These operations are similar to truth tables in logic and can be incredibly powerful for creating complex gameplay interactions like combining masks for collision systems or generating unique procedural patterns.

Optimization by Locking BitMaps

When working on larger BitMaps or executing multiple operations in sequence, it’s advisable to lock the BitMap before performing these operations to optimize performance.

# Lock the BitMap to perform multiple operations.
bitmap.lock()

# Set various bits while the BitMap is locked.
for i in range(10):
    bitmap.set_bit(Vector2(i, i), true)

# Unlock the BitMap to commit the changes.
bitmap.unlock()

Locking a BitMap is similar to batching operations, which can reduce the overhead and improve the performance of your game.

In this part of the tutorial, we have learned how to create and manipulate BitMaps using basic and advanced functionalities provided by Godot’s BitMap class. In the next part, we will explore further use cases and see how BitMap operations can be applied to actual game development scenarios.Taking what we have already discussed, let’s expand our examples to showcase other practical applications of the BitMap class in game development with Godot 4.

Using BitMaps for Terrain Generation

One common application is using BitMaps for terrain generation. By toggling bits, you can create a simple heightmap or a collision map for your terrain.

# Assuming we have a 2D array 'height_data' for terrain heights.

# Create a BitMap for terrain collision.
var terrain_collision = BitMap.new()
terrain_collision.create(Size2(height_data.size().x, height_data.size().y))

# Iterate over height data to set collision where the height is above a certain threshold.
for x in range(height_data.size().x):
    for y in range(height_data.size().y):
        if height_data[x][y] > TERRAIN_COLLISION_THRESHOLD:
            terrain_collision.set_bit(Vector2(x, y), true)

This way, bits that correlate with higher terrain points can be set to true, defining where a character can and can’t move.

Creating a Pathfinding Grid

BitMap can also be used to represent grids in pathfinding algorithms, such as A* (A-Star). You can designate walkable and non-walkable areas with true or false values.

# Create BitMap for walkable areas.
var walkable_areas = BitMap.new()
walkable_areas.create(Size2(map_size.x, map_size.y))

# Set walkable and non-walkable areas based on some condition.
for x in range(map_size.x):
    for y in range(map_size.y):
        walkable_areas.set_bit(Vector2(x, y), not is_wall(x, y))

Non-walkable areas could be walls or obstacles, where the `is_wall` function determines if a given tile coordinate is a wall.

Line Drawing on a BitMap

Additionally, you could use BitMaps to draw lines or patterns, which can be useful for visual effects or custom generation logic.

# Draw a line from (0,0) to (9,9) on our BitMap.
for i in range(10):
    bitmap.set_bit(Vector2(i, i), true)

This simple line-drawing algorithm can be adapted for more complex shapes or patterns.

Masking Operations

Another application might be masking operations. This involves creating a mask BitMap and using it to selectively hide or reveal parts of another.

# Create a mask BitMap.
var mask = BitMap.new()
mask.create(Size2(map_size.x, map_size.y))
mask.fill(true) # Start with everything visible.

# Create a shape on the mask where visibility will be blocked.
for x in range(3, 7):
    for y in range(3, 7):
        mask.set_bit(Vector2(x, y), false)

# Assume 'bitmap' is a BitMap that represents visibility.

# Use the AND operation to apply the mask.
var visibility_after_mask = bitmap.op_and(mask)

Through bitwise logic, you can simulate a variety of effects such as fog of war, light exposure, or even status effects in your game.

Updating BitMap Over Time

Games often require dynamic updates to their systems. For example, as your entities move across a grid, the BitMap that represents walkable paths would need updating.

# Entity moves from position `old_pos` to `new_pos`.

# Free up the old position.
walkable_areas.set_bit(old_pos, true)

# Mark the new position as occupied.
walkable_areas.set_bit(new_pos, false)

This example maintains an accurate walkability BitMap as entities move around.

Exporting and Importing BitMap Data

Finally, BitMap data may need to be exported for save systems, or imported when loading levels. Here’s an example of how to save and load BitMap data as a PoolByteArray.

# Save BitMap data.
var saved_bitmap_data = bitmap.get_data()

# Assuming you have a save and load system that can handle PoolByteArray...

# Load BitMap data.
bitmap.set_data(saved_bitmap_data)

Storing binary data efficiently is critical for game performance, especially for save systems or network transmission.

Each of these examples gives a glance into how critical and versatile the BitMap class is for various aspects of game development with Godot 4. Through BitMap, we can handle data with incredible efficiency and speed, paving the way for more advanced gameplay mechanics and dynamic environments.BitMaps can be immensely useful in representing a level’s structure. For level designers wrangling with tile-based games, having the ability to toggle collision or interaction areas can be crucial. Here’s an example of setting up collision areas for a tile-based game:

# Initialize a BitMap for collision.
var collision_map = BitMap.new()
collision_map.create(level_size)

# Loop through your level's tiles, setting non-walkable tiles to true.
for x in range(level_size.x):
    for y in range(level_size.y):
        if level_data[x][y] == TILE_WALL:
            collision_map.set_bit(Vector2(x, y), true)

The resulting BitMap allows us to efficiently check for collisions with zero overhead for empty spaces.

Next, consider an example where we need to count the true bits in a given BitMap. This can be useful in scenarios like scoring systems in puzzle games, or keeping track of resources on a map:

# Use the count_true_bits_in_region method to count bits set to true in a defined region.
var true_bits_count = bitmap.count_true_bits_in_region(Rect2(Vector2(0, 0), Size2(10, 10)))
print("There are ", true_bits_count, " true bits in the region.")

Moving on, imagine implementing a feature like ‘paint’ ability in a game, where the player can paint certain areas on a grid. Here’s how you might toggle a BitMap based on player input:

# Handle player painting action.
func paint_tiles(paint_position, paint_size):
    var rect = Rect2(paint_position - paint_size / 2, paint_size)
    for x in rect.position.x to rect.end.x:
        for y in rect.position.y to rect.end.y:
            paintable_area.set_bit(Vector2(x, y), true)

Now consider a real-time strategy(RTS) game where areas of the map become visible as they’re explored by the player. This could be accomplished by selectively turning on bits in the visibility map:

# Update visibility BitMap as units explore the map.
func update_visibility(unit_position, visibility_range):
    for x in -visibility_range to visibility_range:
        for y in -visibility_range to visibility_range:
            var pos = unit_position + Vector2(x, y)
            # Check if it's within the map bounds before setting.
            if is_within_map_bounds(pos):
                visibility_map.set_bit(pos, true)

Finally, consider using BitMaps to perform bulk actions. Instead of checking each individual cell for a change, use a BitMap to store modified cells and update them all at once:

# Assume some game event causes multiple updates to a BitMap.
func bulk_update(event_positions):
    var update_map = BitMap.new()
    update_map.create(level_size)
    
    for event_position in event_positions:
        update_map.set_bit(event_position, true)
    
    # Later, apply all updates at once.
    apply_bulk_updates(update_map)

func apply_bulk_updates(update_map):
    for x in range(level_size.x):
        for y in range(level_size.y):
            if update_map.get_bit(Vector2(x, y)):
                # Perform necessary update for the position.
                update_position(Vector2(x, y))

In this code, the `apply_bulk_updates()` function simulates an efficient way to batch process changes, enhancing the game’s performance instead of performing these updates in the main gameplay loop.

BitMaps are incredible tools for handling discrete data efficiently. Through the different applications showcased above, we see they not only play a significant role in coding games but also in ensuring they run smoothly. Game development often involves managing a plethora of boolean states, and with BitMaps, Godot provides a robust way to handle this complexity with ease and efficiency. Whether you are creating a simple puzzle game or a complex RTS, mastering BitMap will significantly bolster your development arsenal.

Continue Your Game Development Journey

Ready to take your Godot skills to the next level? Regardless of whether you’re just starting out or looking to sharpen your game development talents, Zenva’s Godot Game Development Mini-Degree is an excellent next step. Dive into a comprehensive curriculum where you’ll learn how to build cross-platform games, refine your GDScript coding abilities, manage gameplay control flows, and tackle player-versus-enemy combat systems across various game genres.

At Zenva, we understand that a robust education involves a wide array of topics and skill levels. That’s why, in addition to the focused Mini-Degree, we provide a broad collection of Godot courses to suit every interest. From honing specific technical competencies to exploring the breadth of what Godot 4 has to offer, our project-based lessons and flexible learning options are designed to help you stay ahead in the ever-evolving field of game development. Start your adventure with us today, and bring your game ideas to life!

Conclusion

Congratulations on taking this enlightening journey into the world of BitMaps with Godot 4! You now have the knowledge to harness this powerful class to optimize your games and tackle various challenges in creative and efficient ways. Remember, this is only a glimpse of what you can achieve, and learning is an adventure that never truly ends. At Zenva, we are committed to supporting you throughout your game development journey with high-quality, practical education that translates into real-world skills.

Keep honing your craft, and if you’re eager to continue growing your expertise, check out our Godot Game Development Mini-Degree. Expand your understanding, build amazing games, and join a community of developers who share your passion. With every new skill you master, you’re one step closer to bringing your dream projects to life. Start your next chapter with us, and let’s create something incredible together!

FREE COURSES
Python Blog Image

FINAL DAYS: Unlock coding courses in Unity, Godot, Unreal, Python and more.