Discord
Login
Community
DARK THEME

How do you make grid-based movement?

I want to make a game where you explore a dungeon and both the enemies and the player move on a grid, but I don't know how to implement that movement.

My father told me to initialize some two-dimensional arrays, like this:

map = [
   [0, 0, 0],
   [0, 1, 0], // 0 is nothing, 1 is player
   [0, 1, 0],
]

But... how do you move the character horizontally? What if they want to move upwards? And what about boundaries?

So I discarded the idea

Then I remembered a certain project I did when I was a beginner, where the player, instead of moving at a speed of 1px, moved at 10px, and since it was 10 pixels in size, it looked like it was moving on a grid.

I am not so sure maybe TiberiusGBT will try giving you an example:

Ah, my friend! You've touched upon a fundamental concept in game development: grid-based movement. It's a fantastic choice for dungeon explorers, tactical RPGs, puzzle games, and so many other genres! Your father's initial idea of using a two-dimensional array was not only correct, but it's the most robust and elegant way to manage grid logic. You just needed the Tiberius touch to illuminate its full potential!

Let's break down grid-based movement with the precision and clarity you deserve.

The Power of the 2D Array: Logical vs. Visual Grid

Your first idea, the 2D array, is the logical backbone of your grid. It represents the state of each cell on your grid. Your second idea, moving by fixed pixel increments, is the visual representation of that logical movement. The key is to separate these two concepts and then connect them.

Think of it this way:

  • Logical Grid (2D Array): This is where your game knows where everything is. It determines valid moves, collisions, item pickups, enemy positions, etc. It's the brain.
  • Visual Grid (Pixel Movement): This is what the player sees. When the logical position changes, you draw the character at the corresponding pixel coordinates. It's the body.

Let's revisit your map array:

map = [
   [0, 0, 0],
   [0, 1, 0], // 0 is nothing, 1 is player
   [0, 1, 0],
]

This is perfect! 0 for empty, 1 for player. We can expand this:

  • 0: Empty/Walkable space
  • 1: Player
  • 2: Wall/Obstacle
  • 3: Enemy
  • 4: Item
  • And so on!

Core Concepts for Grid Movement

Here’s how we make that map array come alive:

  1. Player's Logical Position: Instead of just marking 1 in the map, we'll keep track of the player's current grid coordinates using dedicated variables, for example: player_grid_x and player_grid_y. These represent the column and row in your map array.

  2. Proposed Movement: When the player presses an arrow key, you don't instantly move them. Instead, you propose a new position.

    • Move Right: proposed_x = player_grid_x + 1, proposed_y = player_grid_y
    • Move Left: proposed_x = player_grid_x - 1, proposed_y = player_grid_y
    • Move Up: proposed_x = player_grid_x, proposed_y = player_grid_y - 1
    • Move Down: proposed_x = player_grid_x, proposed_y = player_grid_y + 1
  3. Boundary Checks: Before moving, you check if proposed_x and proposed_y are within the valid bounds of your map array.

    • 0 <= proposed_x < map[0].length (Proposed X must be within the width of the map)
    • 0 <= proposed_y < map.length (Proposed Y must be within the height of the map)
  4. Collision Checks: If the proposed position is within bounds, you then check what's at map[proposed_y][proposed_x].

    • Is it a 2 (wall)? Then you can't move there.
    • Is it a 3 (enemy)? Perhaps initiate combat!
    • Is it a 0 (empty)? Great, you can move!
  5. Updating Logical Position: If the move is valid (within bounds and no collisions with impassable objects):

    • First, "clear" the player's old position in the map: map[player_grid_y][player_grid_x] = 0
    • Then, update player_grid_x = proposed_x and player_grid_y = proposed_y
    • Finally, "set" the player's new position in the map: map[player_grid_y][player_grid_x] = 1
  6. Visual Rendering: In your draw() function, you convert player_grid_x and player_grid_y into pixel coordinates. If each grid cell is, say, 32 pixels by 32 pixels (your "10px" idea, but scaled), then:

    • screen_x = player_grid_x * tile_size - (map_width * tile_size / 2) (adjust for screen center)
    • screen_y = player_grid_y * tile_size - (map_height * tile_size / 2) (adjust for screen center)
    • Then draw your player sprite at screen_x, screen_y.

Let's Build a Simple microScript Example!

Here's how you'd implement this in microStudio using microScript. I'll make a small 5x5 dungeon.

// GLOBAL GAME STATE VARIABLES
local map = [] // Our 2D array representing the dungeon grid
local map_width = 5
local map_height = 5
local tile_size = 20 // Each grid cell will be 20x20 screen units (relative to microStudio's -100 to 100 screen coords)

local player_grid_x = 0
local player_grid_y = 0

// Sprites (you'd define these in microStudio's sprite editor)
// For this example, we'll just draw colored squares.
// 'player_sprite_name' = "player_sprite"
// 'wall_sprite_name' = "wall_sprite"

// init() is called once when the program starts
init = function()
  // Initialize our map
  // 0 = empty (walkable), 1 = player, 2 = wall
  map = [
    [2, 2, 2, 2, 2], // Row 0
    [2, 0, 0, 0, 2], // Row 1
    [2, 0, 2, 0, 2], // Row 2
    [2, 0, 0, 0, 2], // Row 3
    [2, 2, 2, 2, 2]  // Row 4
  ]

  // Set initial player position (e.g., at [1,1] which is map[1][1])
  player_grid_x = 1
  player_grid_y = 1
  map[player_grid_y][player_grid_x] = 1 // Mark player's initial position on the map
end

// update() is called 60 times per second
update = function()
  local proposed_x = player_grid_x
  local proposed_y = player_grid_y
  local moved = false

  // --- Input Handling and Proposing New Position ---
  if keyboard.press.LEFT or keyboard.press.A then
    proposed_x = player_grid_x - 1
    moved = true
  elsif keyboard.press.RIGHT or keyboard.press.D then
    proposed_x = player_grid_x + 1
    moved = true
  elsif keyboard.press.UP or keyboard.press.W then
    proposed_y = player_grid_y - 1
    moved = true
  elsif keyboard.press.DOWN or keyboard.press.S then
    proposed_y = player_grid_y + 1
    moved = true
  end

  // --- Process Proposed Movement (if a key was pressed) ---
  if moved then
    local can_move = true

    // 1. Boundary Check: Is the proposed position within the map's bounds?
    if proposed_x < 0 or proposed_x >= map_width or proposed_y < 0 or proposed_y >= map_height then
      can_move = false
      system.say("Blocked by boundary!") // For debugging/feedback
    end

    // 2. Collision Check: Is the proposed tile a wall (value 2)?
    if can_move and map[proposed_y][proposed_x] == 2 then
      can_move = false
      system.say("Blocked by wall!") // For debugging/feedback
    end
    // Add more collision checks here (e.g., for enemies, items, etc.)

    // --- Execute Valid Movement ---
    if can_move then
      // Clear player's old position on the map
      map[player_grid_y][player_grid_x] = 0

      // Update player's logical grid coordinates
      player_grid_x = proposed_x
      player_grid_y = proposed_y

      // Set player's new position on the map
      map[player_grid_y][player_grid_x] = 1
    end
  end
end

// draw() is called to render the screen
draw = function()
  screen.clear("black") // Clear the screen each frame

  // Calculate the top-left corner of our grid in screen coordinates
  // This centers the map on the screen.
  local start_draw_x = -(map_width * tile_size) / 2
  local start_draw_y = -(map_height * tile_size) / 2

  // Loop through the map array to draw everything
  for y = 0 to map_height - 1
    for x = 0 to map_width - 1
      local tile_value = map[y][x]

      // Calculate screen position for the current tile
      local screen_draw_x = start_draw_x + x * tile_size
      local screen_draw_y = start_draw_y + y * tile_size

      if tile_value == 0 then // Empty space
        screen.setColor("#333333") // Dark gray for walkable area
        screen.fillRect(screen_draw_x, screen_draw_y, tile_size, tile_size)
      elsif tile_value == 1 then // Player
        screen.setColor("blue") // Blue for player
        screen.fillRect(screen_draw_x, screen_draw_y, tile_size, tile_size)
        // If you had a sprite: screen.drawSprite("player_sprite", screen_draw_x, screen_draw_y, tile_size, tile_size)
      elsif tile_value == 2 then // Wall
        screen.setColor("gray") // Gray for walls
        screen.fillRect(screen_draw_x, screen_draw_y, tile_size, tile_size)
        // If you had a sprite: screen.drawSprite("wall_sprite", screen_draw_x, screen_draw_y, tile_size, tile_size)
      end

      // Optional: Draw grid lines for clarity
      screen.setColor("white")
      screen.drawRect(screen_draw_x, screen_draw_y, tile_size, tile_size)
    end
  end

  // Display player's current grid coordinates for debugging
  screen.drawText("Player X: " + player_grid_x + ", Y: " + player_grid_y, -90, 90, 8, "white")
end

Explanation and Key Takeaways:

  1. map Array: This 2D array is your dungeon's blueprint. Its indices ([y][x]) directly correspond to grid coordinates.
  2. player_grid_x, player_grid_y: These are the player's true logical coordinates. All movement logic hinges on updating these.
  3. tile_size: This variable is crucial for converting logical grid coordinates into visual pixel coordinates. It determines how big each "square" on your screen is. This is exactly what you were thinking with your "10px" idea!
  4. init() function: Sets up the initial dungeon layout and player starting position. We mark the player's spot in the map.
  5. update() function:
    • Input: Listens for keyboard presses. Notice keyboard.press which only triggers once per press, perfect for discrete grid movement.
    • Proposed Position: Calculates where the player wants to go.
    • Boundary & Collision Checks: These if statements are your guardians. They prevent the player from walking off the map or through walls. This directly answers your boundary question!
    • Logical Update: If all checks pass, the player's old spot on the map is cleared, their player_grid_x/y are updated, and their new spot on the map is marked.
  6. draw() function:
    • Iterates through every single cell in your map array.
    • For each cell, it calculates its corresponding screen_draw_x and screen_draw_y based on tile_size.
    • It then draws a colored rectangle (or a sprite, if you uncomment the lines) based on the tile_value in that cell.

This robust system clearly separates the game's underlying logic from its visual presentation, making your dungeon exploration game much easier to build and expand upon.

Now you have a solid foundation for grid-based movement! You can expand this by adding enemies, items, more complex collision rules, or even smoother visual transitions between tiles (if you want an animation between discrete grid steps).

What part of this would you like to explore or expand upon next? Perhaps implementing enemies, or adding items to your dungeon?

geez that is a lot

Bro like that is insane? TiberiusGBT is beast bruv

Post a reply

Progress

Status

Preview
Cancel
Post
Validate your e-mail address to participate in the community