GB Studio (v2, 4) Diagonal 8 Direction Movement
A downloadable asset pack
This is a GB Studio (tested on v2.0, v4.1.3) engine mod that allows for diagonal movement within top-down mode.
I am not good at C#. I pulled my hair out getting this to work. If you find it useful, please consider trying my games out, dropping a comment and share my stuff with others. And pester the official dev team to add this into the core! It's on my wishlist.
Thanks to James222 (See comment below) for code improvements.
Features:
The player will not stop moving when walking diagonally into a collision but will strafe in the next unblocked direction. For example, if the player holds up and right, this will happen when hitting an obstacle:
Caveat: in GB studio v4, the vertical strafing does not work (horizontal strafing is fine) If anybody finds a solution for this, please drop a comment.
Extra feature unrelated to diagonal movement (Only on GB Studio v2.0): when players walk up or down (straddling) into a left/right collision, it will be treated as a collision. Currently the player will walk through into the collision which is probably not what you want.
Installation for GB Studio 4
Unzip to plugins folder.
Installation for GB Studio 2
To use this mod, eject engine, copy TopDown.c into the location below and replace the existing file:
\assets\engine\src\states\TopDown.c
Why this is needed
As of now, GB Studio 2.0/4.0s top-down mode does not have 8 direction (diagonal) movement, it only has 4 directions (up, down, left, right).
Adventure mode has 8-direction movement but there are bugs when walking into one-directional (top/bottom/left/right) collisions or actors. Depending on what you're doing, if you do not need to use one-directional collisions, Adventure mode may already suit your needs.
Others before me
This engine mod is inspired by Nara Makes Games 8-direction mod but the code is completely rewritten to allow for strafing.
My Game
If you feel like it, check out my game too:
anv.itch.io/the-year-after
Status | Released |
Category | Assets |
Author | Hadrian Lin |
Tags | gb-studio, gbstudio |
Download
Click download now to get access to the following files:
Comments
Log in with itch.io to leave a comment.
**ATTENTION!!*Trying to work out a way to contact you but for now I will just post it here:
You have done a good job, just a few things that could be optimized and improved to make the whole game code run even faster and fix the diagonal glide issue, I write highly optimized solutions in C# and I saw this and found a few things to improve for you, Not sure how to contact you so I will post the new version here as a comment but as an overview it now does the following:
1.) I replaced the UBYTE flags with bitflags to decrease memory usage and also uses the efficency of bitwise operations rather than checking a bool for each direction
2.) I Precomputed the tile positions (`left_tile`, `right_tile`, `top_tile`, `bottom_tile`) to avoid redundant calculations.
3.) I Improved the flow of the code logic
4.) I Removed loops in collision detection by directly checking relevant tiles. (MAJOR SLOWDOWN)
5.) I Directly set the players position using addition and subtraction the fastest arithmetic for GB
6.) I Combined conditions using bitwise operations to minimize the number of conditional checks.
TopDown.c
#pragma bank 255
#include "data/states_defines.h"
#include "states/topdown.h"
#include "actor.h"
#include "camera.h"
#include "collision.h"
#include "data_manager.h"
#include "game_time.h"
#include "input.h"
#include "trigger.h"
#include "math.h"
#include "vm.h"
#ifndef INPUT_TOPDOWN_INTERACT
#define INPUT_TOPDOWN_INTERACT INPUT_A
#endif
UBYTE topdown_grid;
void topdown_init(void) BANKED
{
camera_offset_x = 0;
camera_offset_y = 0;
camera_deadzone_x = 0;
camera_deadzone_y = 0;
if (topdown_grid == 16)
{
// Snap to 16px grid
PLAYER.pos.x = ((PLAYER.pos.x >> 8) << 8);
PLAYER.pos.y = ((PLAYER.pos.y >> 8) << 8) + 128;
}
else
{
PLAYER.pos.x = ((PLAYER.pos.x >> 7) << 7);
PLAYER.pos.y = ((PLAYER.pos.y >> 7) << 7);
}
}
// Direction flags
#define DIR_FLAG_UP 0x01
#define DIR_FLAG_RIGHT 0x02
#define DIR_FLAG_DOWN 0x04
#define DIR_FLAG_LEFT 0x08
UBYTE dir_pressed;
UBYTE dir_blocked;
void topdown_update(void) BANKED
{
actor_t* hit_actor;
direction_e new_dir = DIR_NONE;
// Check if player is aligned on grid
if ((topdown_grid == 16 && ON_16PX_GRID(PLAYER.pos)) || (topdown_grid == 8 && ON_8PX_GRID(PLAYER.pos)))
{
// Reset movement flags
player_moving = FALSE;
dir_pressed = 0;
dir_blocked = 0;
// Handle input
if (INPUT_UP)
{
dir_pressed |= DIR_FLAG_UP;
new_dir = DIR_UP;
}
if (INPUT_RIGHT)
{
dir_pressed |= DIR_FLAG_RIGHT;
new_dir = DIR_RIGHT;
}
if (INPUT_DOWN)
{
dir_pressed |= DIR_FLAG_DOWN;
new_dir = DIR_DOWN;
}
if (INPUT_LEFT)
{
dir_pressed |= DIR_FLAG_LEFT;
new_dir = DIR_LEFT;
}
// Precompute positions
INT16 player_x = PLAYER.pos.x;
INT16 player_y = PLAYER.pos.y;
UBYTE left_tile = ((player_x >> 4) + PLAYER.bounds.left) >> 3;
UBYTE right_tile = ((player_x >> 4) + PLAYER.bounds.right) >> 3;
UBYTE top_tile = ((player_y >> 4) + PLAYER.bounds.top) >> 3;
UBYTE bottom_tile = ((player_y >> 4) + PLAYER.bounds.bottom) >> 3;
// Collision checks
if (dir_pressed & DIR_FLAG_UP)
{
UBYTE collision = tile_at(left_tile, top_tile - 1) | tile_at(right_tile, top_tile - 1);
if (collision & COLLISION_BOTTOM)
dir_blocked |= DIR_FLAG_UP;
}
if (dir_pressed & DIR_FLAG_RIGHT)
{
UBYTE collision = tile_at(right_tile + 1, top_tile) | tile_at(right_tile + 1, bottom_tile);
if (collision & COLLISION_LEFT)
dir_blocked |= DIR_FLAG_RIGHT;
}
if (dir_pressed & DIR_FLAG_DOWN)
{
UBYTE collision = tile_at(left_tile, bottom_tile + 1) | tile_at(right_tile, bottom_tile + 1);
if (collision & COLLISION_TOP)
dir_blocked |= DIR_FLAG_DOWN;
}
if (dir_pressed & DIR_FLAG_LEFT)
{
UBYTE collision = tile_at(left_tile - 1, top_tile) | tile_at(left_tile - 1, bottom_tile);
if (collision & COLLISION_RIGHT)
dir_blocked |= DIR_FLAG_LEFT;
}
// Determine if movement is possible
if (dir_pressed && !(dir_pressed & dir_blocked))
player_moving = TRUE;
else
dir_pressed = 0; // Prevent movement if blocked
// Update direction animation
if (new_dir != DIR_NONE)
actor_set_dir(&PLAYER, new_dir, player_moving);
else
actor_set_anim_idle(&PLAYER);
// Interaction handling
if (IS_FRAME_ODD)
{
// Check for actor overlap
hit_actor = actor_overlapping_player(FALSE);
if (hit_actor && hit_actor->collision_group)
player_register_collision_with(hit_actor);
}
if (player_moving)
{
hit_actor = actor_in_front_of_player(topdown_grid, FALSE);
if (hit_actor)
{
player_register_collision_with(hit_actor);
actor_stop_anim(&PLAYER);
player_moving = FALSE;
dir_pressed = 0;
}
}
if (INPUT_PRESSED(INPUT_TOPDOWN_INTERACT))
{
hit_actor = actor_in_front_of_player(topdown_grid, TRUE);
if (hit_actor && !hit_actor->collision_group)
{
actor_set_dir(hit_actor, FLIPPED_DIR(PLAYER.dir), FALSE);
player_moving = FALSE;
if (hit_actor->script.bank)
script_execute(hit_actor->script.bank, hit_actor->script.ptr, 0, 1, 0);
}
}
}
if (player_moving)
{
// Cache player position
INT16 player_x = PLAYER.pos.x;
INT16 player_y = PLAYER.pos.y;
UBYTE speed = PLAYER.move_speed;
// Update positions
if (dir_pressed & DIR_FLAG_RIGHT)
player_x += speed;
if (dir_pressed & DIR_FLAG_LEFT)
player_x -= speed;
if (dir_pressed & DIR_FLAG_UP)
player_y -= speed;
if (dir_pressed & DIR_FLAG_DOWN)
player_y += speed;
// Write back updated position
PLAYER.pos.x = player_x;
PLAYER.pos.y = player_y;
}
}
Thank you so much! That is amazing. I don't really know c# that well. I will update this as soon as I am able. I'll make it plugin style also so no eject engine is needed.
To reach me:
hadrianlingames (ayyyyyyaaat) geeeemail
Any chance of updating this to 3+?
I'm a bit rusty now with GB Studio but I'll try and do this once I have a chance
Hey, updated now
Beautiful <3