movable, scrollable map with zoom
* settings.h: Global settings to play around with * fixed.h: Utilites for fixed-point numbers (incomplete but clean) * level.h: Storage format of levels (PROTOTYPE >9000) * map.h: Dynamic format for loaded levels (PROTOTYPE) * render.h: Camera management and rendering (pretty clean)
This commit is contained in:
commit
4a9cbd2506
|
@ -0,0 +1,16 @@
|
|||
# Build files
|
||||
/build-fx
|
||||
/build-cg
|
||||
/*.g1a
|
||||
/*.g3a
|
||||
|
||||
# Python bytecode
|
||||
__pycache__/
|
||||
|
||||
# Common IDE files
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
.vscode
|
||||
|
||||
# Screenshots
|
||||
screens/
|
|
@ -0,0 +1,34 @@
|
|||
# Configure with [fxsdk build-fx] or [fxsdk build-cg], which provide the
|
||||
# toolchain file and module path of the fxSDK
|
||||
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
project(RogueLife)
|
||||
|
||||
include(GenerateG1A)
|
||||
include(GenerateG3A)
|
||||
include(Fxconv)
|
||||
find_package(Gint 2.1 REQUIRED)
|
||||
|
||||
if("${FXSDK_PLATFORM_LONG}" STREQUAL fx9860G)
|
||||
message(FATAL_ERROR "This game does not support the fx-9860G!")
|
||||
endif()
|
||||
|
||||
set(SOURCES
|
||||
src/level.c
|
||||
src/main.c
|
||||
src/map.c
|
||||
src/render.c
|
||||
)
|
||||
set(ASSETS
|
||||
assets-cg/example.png
|
||||
)
|
||||
|
||||
fxconv_declare_assets(${ASSETS} ${ASSETS} WITH_METADATA)
|
||||
|
||||
add_executable(addin ${SOURCES} ${ASSETS})
|
||||
target_compile_options(addin PRIVATE -Wall -Wextra -Os)
|
||||
target_link_libraries(addin Gint::Gint)
|
||||
target_link_options(addin PRIVATE -Wl,-Map=map)
|
||||
|
||||
generate_g3a(TARGET addin OUTPUT "RogueLif.g3a"
|
||||
NAME "RogueLife" ICONS assets-cg/icon-uns.png assets-cg/icon-sel.png)
|
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
|
@ -0,0 +1,3 @@
|
|||
example.png:
|
||||
type: bopti-image
|
||||
name: img_example
|
Binary file not shown.
After Width: | Height: | Size: 8.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.0 KiB |
|
@ -0,0 +1,53 @@
|
|||
//---
|
||||
// fixed: 16:16 fixed-point arithmetic
|
||||
//---
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef int32_t fixed_t;
|
||||
|
||||
/* Standard arithmetic. */
|
||||
|
||||
static inline fixed_t fmul(fixed_t left, fixed_t right)
|
||||
{
|
||||
/* Could be optimized to use 32x32 -> 64 bit product and xtrct */
|
||||
int64_t p = (int64_t)left * (int64_t)right;
|
||||
return (int32_t)(p >> 16);
|
||||
}
|
||||
|
||||
static inline fixed_t fix(int constant)
|
||||
{
|
||||
return constant << 16;
|
||||
}
|
||||
|
||||
static inline fixed_t fixdouble(double constant)
|
||||
{
|
||||
return (fixed_t)(constant * 65536);
|
||||
}
|
||||
|
||||
static inline int ffloor(fixed_t f)
|
||||
{
|
||||
return f >> 16;
|
||||
}
|
||||
|
||||
static inline int fceil(fixed_t f)
|
||||
{
|
||||
return (f + 0xffff) >> 16;
|
||||
}
|
||||
|
||||
static inline int fround(fixed_t f)
|
||||
{
|
||||
return (f + 0x8000) >> 16;
|
||||
}
|
||||
|
||||
static inline float f2float(fixed_t f)
|
||||
{
|
||||
return (float)f / 65536;
|
||||
}
|
||||
|
||||
static inline double f2double(fixed_t f)
|
||||
{
|
||||
return (double)f / 65536;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
#include "level.h"
|
|
@ -0,0 +1,18 @@
|
|||
//---
|
||||
// level: Static level model
|
||||
//
|
||||
// This module provides representations for read-only data structures
|
||||
// describing levels, from which dynamic maps can be built. The only role of
|
||||
// models is that we can import them into the game and then load them into
|
||||
// maps. This is a convenience to abstract away the storage format of levels.
|
||||
//---
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* TODO: DEMO */
|
||||
struct level {
|
||||
int zero;
|
||||
uint16_t colors[];
|
||||
};
|
|
@ -0,0 +1,167 @@
|
|||
#include "map.h"
|
||||
#include "level.h"
|
||||
#include "render.h"
|
||||
|
||||
#include <gint/display.h>
|
||||
#include <gint/keyboard.h>
|
||||
#include <gint/usb.h>
|
||||
#include <gint/usb-ff-bulk.h>
|
||||
#include <gint/kprint.h>
|
||||
#include <gint/cpu.h>
|
||||
#include <gint/timer.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static bool getkey_global_shortcuts(key_event_t e)
|
||||
{
|
||||
if(usb_is_open() && e.key == KEY_OPTN && !e.shift && !e.alpha) {
|
||||
usb_fxlink_screenshot(true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#define _ C_BLACK,
|
||||
#define R C_RED,
|
||||
#define G C_GREEN,
|
||||
#define B C_BLUE,
|
||||
#define Y C_RGB(16,16,16),
|
||||
|
||||
struct level lv_demo = { 0, {
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
_ _ _ _ _ _ _ _ G G G G G G G _ _ _ _ _ _ _ _ _
|
||||
_ _ _ Y Y G G G G G G G G G G G G G G G _ _ _ _
|
||||
_ _ _ Y _ _ _ G G _ _ _ G G G _ _ G _ G _ _ _ _
|
||||
_ _ _ R _ _ _ _ Y Y Y Y Y _ G Y _ G Y G _ _ _ _
|
||||
_ _ R R _ _ Y Y Y _ _ Y _ _ _ Y _ _ Y _ _ _ _ _
|
||||
_ R R R R R Y _ Y Y Y Y Y Y _ Y _ _ Y Y Y B _ _
|
||||
_ R R R R R Y _ _ Y Y Y Y Y Y Y _ _ Y _ _ B _ _
|
||||
_ _ R R R _ Y _ Y Y Y Y Y Y Y Y Y _ B _ _ B B _
|
||||
_ _ R R R _ Y Y Y Y Y Y Y Y Y _ Y B B B _ B B _
|
||||
_ _ R R _ _ _ _ Y Y _ _ Y Y _ _ _ B B B B B B _
|
||||
_ _ _ Y Y Y _ _ _ _ _ _ Y Y Y Y B B B B B B _ _
|
||||
_ _ _ _ Y Y Y Y Y Y Y Y Y _ _ _ _ _ _ _ B B _ _
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
}};
|
||||
|
||||
__attribute__((noreturn))
|
||||
void panic(char const *message)
|
||||
{
|
||||
dclear(C_WHITE);
|
||||
dtext(1, 1, C_BLACK, message);
|
||||
dupdate();
|
||||
getkey();
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
getkey_set_feature_function(getkey_global_shortcuts);
|
||||
kprint_enable_fp();
|
||||
|
||||
usb_interface_t const *interfaces[] = { &usb_ff_bulk, NULL };
|
||||
usb_open(interfaces, GINT_CALL_NULL);
|
||||
|
||||
//---
|
||||
// Load map
|
||||
//---
|
||||
|
||||
struct map demo_map = { .width = 24, .height = 14 };
|
||||
|
||||
struct map *m = &demo_map;
|
||||
m->tiles = malloc(m->width * m->height * sizeof *m->tiles);
|
||||
if(!m->tiles) panic("Bruh go fix your malloc");
|
||||
|
||||
for(int y = 0; y < m->height; y++)
|
||||
for(int x = 0; x < m->width; x++) {
|
||||
struct tile *t = map_tile(m, x, y);
|
||||
uint16_t color = lv_demo.colors[y * m->width + x];
|
||||
|
||||
t->solid = (color == C_BLACK);
|
||||
t->color = color;
|
||||
}
|
||||
|
||||
struct camera camera;
|
||||
struct camera *c = &camera;
|
||||
camera_init(c, m);
|
||||
|
||||
//---
|
||||
// Main loop
|
||||
//---
|
||||
|
||||
volatile int frame_tick = 1;
|
||||
int timer_id = timer_configure(TIMER_ANY, 1000000 / FRAME_RATE,
|
||||
GINT_CALL_SET(&frame_tick));
|
||||
if(timer_id >= 0)
|
||||
timer_start(timer_id);
|
||||
|
||||
/* Lock inputs for (+) and (-) until release */
|
||||
bool lock_plus = false;
|
||||
bool lock_minus = false;
|
||||
bool lock_optn = false;
|
||||
|
||||
while(1) {
|
||||
while(!frame_tick) sleep();
|
||||
|
||||
/* Assume the frame is not late */
|
||||
fixed_t dt = fix(1) / FRAME_RATE;
|
||||
|
||||
dclear(C_BLACK);
|
||||
render_map(m, c);
|
||||
|
||||
// Debugging stuff
|
||||
map_coord_t top, left, bottom, right;
|
||||
camera_screen2map(c, 0, 0, &left, &top);
|
||||
camera_screen2map(c, DWIDTH, DHEIGHT, &right, &bottom);
|
||||
dprint(1, 1, C_WHITE, "Corners, as map: %g..%g %g..%g",
|
||||
f2double(left), f2double(right), f2double(top), f2double(bottom));
|
||||
int x0, y0;
|
||||
camera_map2screen(c, fix(0), fix(0), &x0, &y0);
|
||||
dprint(1, 14, C_WHITE, "Coords of start: %d, %d", x0, y0);
|
||||
// ~
|
||||
|
||||
dupdate();
|
||||
clearevents();
|
||||
|
||||
if(keydown(KEY_MENU))
|
||||
gint_osmenu();
|
||||
if(keydown(KEY_EXIT))
|
||||
break;
|
||||
|
||||
/* Camera speed */
|
||||
fixed_t vx = CAMERA_SPEED_X;
|
||||
fixed_t vy = CAMERA_SPEED_Y;
|
||||
|
||||
/* Camera movement for this frame */
|
||||
if(keydown(KEY_4))
|
||||
camera_move(c, -fmul(dt, vx), 0);
|
||||
if(keydown(KEY_6))
|
||||
camera_move(c, fmul(dt, vx), 0);
|
||||
if(keydown(KEY_8))
|
||||
camera_move(c, 0, -fmul(dt, vy));
|
||||
if(keydown(KEY_2))
|
||||
camera_move(c, 0, fmul(dt, vy));
|
||||
|
||||
if(keydown(KEY_PLUS)) {
|
||||
if(!lock_plus) camera_zoom(c, c->zoom + 1);
|
||||
lock_plus = true;
|
||||
}
|
||||
else lock_plus = false;
|
||||
|
||||
if(keydown(KEY_MINUS)) {
|
||||
if(!lock_minus) camera_zoom(c, c->zoom - 1);
|
||||
lock_minus = true;
|
||||
}
|
||||
else lock_minus = false;
|
||||
|
||||
if(keydown(KEY_OPTN)) {
|
||||
if(!lock_optn && usb_is_open()) usb_fxlink_screenshot(true);
|
||||
lock_optn = true;
|
||||
}
|
||||
else lock_optn = false;
|
||||
}
|
||||
|
||||
timer_stop(timer_id);
|
||||
free(m->tiles);
|
||||
usb_close();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
#include "map.h"
|
||||
|
||||
struct tile *map_tile(struct map *map, int x, int y)
|
||||
{
|
||||
if(x < 0 || x >= map->width || y < 0 || y >= map->height)
|
||||
return NULL;
|
||||
|
||||
return &map->tiles[y * map->width + x];
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
//---
|
||||
// map: Dynamic level models
|
||||
//
|
||||
// This module provides dynamic maps as they are used during games. This
|
||||
// includes objects being activated and their current state, doors being
|
||||
// opened, crates being blown up, you name it. But not moving entities.
|
||||
//---
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fixed.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* Type of map coordinates */
|
||||
typedef fixed_t map_coord_t;
|
||||
|
||||
struct tile {
|
||||
/* TODO: Layers of objects, stuff, dynamic elements, etc? */
|
||||
bool solid;
|
||||
uint16_t color;
|
||||
};
|
||||
|
||||
struct map {
|
||||
/* Dimensions, columns are 0 to width-1, rows are 0 to height-1 */
|
||||
int width, height;
|
||||
/* All tiles */
|
||||
struct tile *tiles;
|
||||
};
|
||||
|
||||
/* Get a pointer to the tile at (x,y) in map; NULL if out of bounds. */
|
||||
struct tile *map_tile(struct map *m, int x, int y);
|
|
@ -0,0 +1,158 @@
|
|||
#include "render.h"
|
||||
#include <gint/display.h>
|
||||
|
||||
//---
|
||||
// Camera management
|
||||
//---
|
||||
|
||||
void camera_init(struct camera *c, struct map *m)
|
||||
{
|
||||
c->zoom = 1;
|
||||
|
||||
c->limits.x_min = fix(-CAMERA_BORDER);
|
||||
c->limits.x_max = fix(m->width + CAMERA_BORDER);
|
||||
c->limits.y_min = fix(-CAMERA_BORDER);
|
||||
c->limits.y_max = fix(m->height + CAMERA_BORDER);
|
||||
|
||||
/* Fullscreen */
|
||||
c->viewport.x_min = 0;
|
||||
c->viewport.x_max = DWIDTH;
|
||||
c->viewport.y_min = 0;
|
||||
c->viewport.y_max = DHEIGHT;
|
||||
|
||||
c->width = fix(c->viewport.x_max - c->viewport.x_min) / TILE_WIDTH;
|
||||
c->height = fix(c->viewport.y_max - c->viewport.y_min) / TILE_HEIGHT;
|
||||
|
||||
c->x = (c->limits.x_min + c->limits.x_max) / 2;
|
||||
c->y = (c->limits.y_min + c->limits.y_max) / 2;
|
||||
}
|
||||
|
||||
void camera_map2screen(struct camera *c, map_coord_t map_x, map_coord_t map_y,
|
||||
int *screen_x, int *screen_y)
|
||||
{
|
||||
/* Start from center point */
|
||||
int sx = DWIDTH / 2;
|
||||
int sy = DHEIGHT / 2;
|
||||
|
||||
map_x -= c->x;
|
||||
map_y -= c->y;
|
||||
|
||||
sx += fround(map_x * TILE_WIDTH * c->zoom);
|
||||
sy += fround(map_y * TILE_HEIGHT * c->zoom);
|
||||
|
||||
if(screen_x) *screen_x = sx;
|
||||
if(screen_y) *screen_y = sy;
|
||||
}
|
||||
|
||||
/* Translate screen coordinates to map coordinates */
|
||||
void camera_screen2map(struct camera *c, int screen_x, int screen_y,
|
||||
map_coord_t *map_x, map_coord_t *map_y)
|
||||
{
|
||||
/* Start from center point */
|
||||
map_coord_t mx = c->x;
|
||||
map_coord_t my = c->y;
|
||||
|
||||
screen_x -= DWIDTH / 2;
|
||||
screen_y -= DHEIGHT / 2;
|
||||
|
||||
mx += fix(screen_x) / TILE_WIDTH / c->zoom;
|
||||
my += fix(screen_y) / TILE_HEIGHT / c->zoom;
|
||||
|
||||
if(map_x) *map_x = mx;
|
||||
if(map_y) *map_y = my;
|
||||
}
|
||||
|
||||
/* Lock the camera at the center if set by settings. */
|
||||
static bool camera_lock(struct camera *c)
|
||||
{
|
||||
if(c->zoom == 1 && CAMERA_LOCK_AT_x1)
|
||||
{
|
||||
c->x = (c->limits.x_min + c->limits.x_max) / 2;
|
||||
c->y = (c->limits.y_min + c->limits.y_max) / 2;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Bound the camera to the map limits */
|
||||
static void camera_bound(struct camera *c)
|
||||
{
|
||||
/* Project viewport onto the map */
|
||||
map_coord_t vx_min, vx_max;
|
||||
map_coord_t vy_min, vy_max;
|
||||
camera_screen2map(c,c->viewport.x_min,c->viewport.y_min,&vx_min,&vy_min);
|
||||
camera_screen2map(c,c->viewport.x_max,c->viewport.y_max,&vx_max,&vy_max);
|
||||
|
||||
/* Bound viewport to map limits */
|
||||
if(vx_min < c->limits.x_min)
|
||||
c->x += (c->limits.x_min - vx_min);
|
||||
else if(vx_max > c->limits.x_max)
|
||||
c->x += (c->limits.x_max - vx_max);
|
||||
if(vy_min < c->limits.y_min)
|
||||
c->y += (c->limits.y_min - vy_min);
|
||||
else if(vy_max > c->limits.y_max)
|
||||
c->y += (c->limits.y_max - vy_max);
|
||||
}
|
||||
|
||||
void camera_move(struct camera *c, map_coord_t dx, map_coord_t dy)
|
||||
{
|
||||
if(camera_lock(c)) return;
|
||||
c->x += dx;
|
||||
c->y += dy;
|
||||
camera_bound(c);
|
||||
}
|
||||
|
||||
void camera_zoom(struct camera *c, int zoom)
|
||||
{
|
||||
if(zoom < ZOOM_MIN) zoom = ZOOM_MIN;
|
||||
if(zoom > ZOOM_MAX) zoom = ZOOM_MAX;
|
||||
|
||||
c->zoom = zoom;
|
||||
if(camera_lock(c)) return;
|
||||
camera_bound(c);
|
||||
}
|
||||
|
||||
//---
|
||||
// Rendering
|
||||
//---
|
||||
|
||||
static uint16_t darker2(uint16_t color)
|
||||
{
|
||||
return (color & 0xf7de) >> 1;
|
||||
}
|
||||
|
||||
static uint16_t darker1(uint16_t color)
|
||||
{
|
||||
int r = (color >> 11);
|
||||
int g = (color >> 6) & 0x1f;
|
||||
int b = color & 0x1f;
|
||||
|
||||
return C_RGB(2*r/3, 2*g/3, 2*b/3);
|
||||
}
|
||||
|
||||
void render_map(struct map *m, struct camera *c)
|
||||
{
|
||||
for(int row = 0; row < m->height; row++)
|
||||
for(int col = 0; col < m->width; col++) {
|
||||
struct tile *t = map_tile(m, col, row);
|
||||
if(!t) continue;
|
||||
|
||||
int x, y;
|
||||
camera_map2screen(c, fix(col), fix(row), &x, &y);
|
||||
|
||||
/* TODO: render_map: use images */
|
||||
uint16_t color_ground = darker1(t->color);
|
||||
uint16_t color_wall = darker2(darker2(t->color));
|
||||
|
||||
int tile_w = c->zoom * TILE_WIDTH;
|
||||
int tile_h = c->zoom * TILE_HEIGHT;
|
||||
int wall_h = c->zoom * WALL_HEIGHT;
|
||||
|
||||
drect(x, y, x+tile_w-1, y+tile_h-1, color_ground);
|
||||
|
||||
struct tile *above = map_tile(m, col, row-1);
|
||||
if(above && !t->solid && above->solid) {
|
||||
drect(x, y-wall_h, x+tile_w-1, y-1, color_wall);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
//---
|
||||
// render: Rendering utilities
|
||||
//---
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "settings.h"
|
||||
#include "map.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
//---
|
||||
// Camera management
|
||||
//---
|
||||
|
||||
struct camera {
|
||||
/* Current zoom level */
|
||||
int zoom;
|
||||
/* Boundaries of the viewable space, in map coordinates */
|
||||
struct {
|
||||
map_coord_t x_min, x_max;
|
||||
map_coord_t y_min, y_max;
|
||||
} limits;
|
||||
/* Viewport on the screen (in pixels) */
|
||||
struct {
|
||||
int16_t x_min, x_max;
|
||||
int16_t y_min, y_max;
|
||||
} viewport;
|
||||
/* Width and height of the viewable area, in map coordinates */
|
||||
map_coord_t width, height;
|
||||
/* Current center point (position of center of screen in the map) */
|
||||
map_coord_t x, y;
|
||||
};
|
||||
|
||||
/* Initialize camera to look at the middle of the map, and set boundaries. */
|
||||
void camera_init(struct camera *c, struct map *m);
|
||||
|
||||
/* Translate map coordinates to pixel coordinates on screen. */
|
||||
void camera_map2screen(struct camera *c, map_coord_t map_x, map_coord_t map_y,
|
||||
int *screen_x, int *screen_y);
|
||||
|
||||
/* Translate screen coordinates to map coordinates. */
|
||||
void camera_screen2map(struct camera *c, int screen_x, int screen_y,
|
||||
map_coord_t *map_x, map_coord_t *map_y);
|
||||
|
||||
/* Move camera by a distance in map coordinates. */
|
||||
void camera_move(struct camera *c, map_coord_t dx, map_coord_t dy);
|
||||
|
||||
/* Set the zoom level of the camera. */
|
||||
void camera_zoom(struct camera *c, int zoom);
|
||||
|
||||
//---
|
||||
// Rendering
|
||||
//---
|
||||
|
||||
/* Render map for the specified camera.
|
||||
TODO: render_map: Honor zoom
|
||||
TODO: render_map: Clip around viewport */
|
||||
void render_map(struct map *m, struct camera *c);
|
|
@ -0,0 +1,27 @@
|
|||
//---
|
||||
// settings: Adjustment variables for the development process
|
||||
//---
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "fixed.h"
|
||||
|
||||
/* Dimensions of tiles on 1:1, in pixels */
|
||||
#define TILE_WIDTH 16
|
||||
#define TILE_HEIGHT 16
|
||||
/* Height of walls on 1:1, in pixels (a fraction of TILE_HEIGHT) */
|
||||
#define WALL_HEIGHT 10
|
||||
/* Minimum and maximum zoom levels */
|
||||
#define ZOOM_MIN 1
|
||||
#define ZOOM_MAX 3
|
||||
/* Number of empty tiles around the map that can be viewed (extra scroll) */
|
||||
#define CAMERA_BORDER 2
|
||||
/* Lock the camera at the center of the map when using zoom x1 */
|
||||
#define CAMERA_LOCK_AT_x1 false
|
||||
/* Camera speed in tiles per second (should be in ratio inversely proportional
|
||||
to TILE_WIDTH and TILE_HEIGHT to keep same speed on both axis on screen) */
|
||||
#define CAMERA_SPEED_X fix(3)
|
||||
#define CAMERA_SPEED_Y fix(3)
|
||||
/* Ideal frame rate (FPS) */
|
||||
#define FRAME_RATE 30
|
Loading…
Reference in New Issue