basic smooth controls

This commit is contained in:
Lephenixnoir 2021-06-04 15:14:12 +02:00
parent a31f56478a
commit 1e9afa5550
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
13 changed files with 365 additions and 205 deletions

View File

@ -16,6 +16,7 @@ endif()
set(SOURCES
src/entities.c
src/game.c
src/geometry.c
src/level.c
src/main.c

View File

@ -1,26 +1,61 @@
#include "entities.h"
void entity_move(entity_t *e, fixed_t dx, fixed_t dy)
fpoint_t entity_pos(entity_t const *e)
{
e->x += dx;
e->y += dy;
return (fpoint_t){ e->movement.x, e->movement.y };
}
fixed_t entity_distance2(entity_t const *e1, entity_t const *e2)
static void update_pos(entity_movement_t *m, fixed_t dt)
{
fixed_t dx = e2->x - e1->x;
fixed_t dy = e2->y - e1->y;
return fmul(dx, dx) + fmul(dy, dy);
m->x += fmul(m->vx, dt);
m->y += fmul(m->vy, dt);
}
fpoint_t entity_facing(entity_t const *e)
entity_movement_t entity_still(entity_t *e, fixed_t dt)
{
if(e->type != ENTITY_AGENT) return (fpoint_t){ 0, 0 };
entity_movement_t next = e->movement;
int f = e->agent.facing;
return (fpoint_t){
.x = ((f == RIGHT) - (f == LEFT)) * fix(1),
.y = ((f == DOWN) - (f == UP)) * fix(1),
};
/* Decrease speed along a quadratic curve */
next.vx >>= 1;
next.vy >>= 1;
next.accel_duration = 0;
if(next.vx >= -1 && next.vx <= 1) next.vx = 0;
if(next.vy >= -1 && next.vy <= 1) next.vy = 0;
update_pos(&next, dt);
return next;
}
entity_movement_t entity_move(entity_t *e, int direction, fixed_t dt)
{
entity_movement_params_t const *params = e->movement_params;
entity_movement_t next = e->movement;
next.facing = direction;
/* If direction has changed, reset speed to 0 */
if(next.facing != direction) {
next.vx = 0;
next.vy = 0;
next.accel_duration = 0;
}
/* Apply the acceleration curve */
next.accel_duration += dt;
if(next.accel_duration > params->accel_time)
next.accel_duration = params->accel_time;
fixed_t v = 0;
if(params->accel_time > 0) {
fixed_t progress = feasein(fdiv(next.accel_duration,params->accel_time));
v = fmul(params->max_speed - params->min_speed, progress) + params->min_speed;
}
fpoint_t dir = fdir(next.facing);
next.vx = fmul(dir.x, v);
next.vy = fmul(dir.y, v);
update_pos(&next, dt);
return next;
}

View File

@ -6,42 +6,66 @@
#include "geometry.h"
/* Entity types */
enum {
ENTITY_AGENT,
ENTITY_PARTICLE,
};
//---
// Agents
//---
typedef struct {
/* Entity type */
uint16_t type;
/* Speed limi */
fixed_t max_speed;
/* Minimum speed granted immediately when starting a movement */
fixed_t min_speed;
/* Duration to reach max speed (the curve might be smooth) */
fixed_t accel_time;
/* Duration to reach min speed */
fixed_t decel_time;
/* Coordinates of the entity's hitbox center on the map */
} entity_movement_params_t;
typedef struct {
/* Current position */
fixed_t x, y;
/* Current speed */
fixed_t vx, vy;
/* Time spent accelerating */
fixed_t accel_duration;
/* Current facing */
uint8_t facing;
union {
/* ENTITY_AGENT */
struct {
/* Entity's hitbox */
shape_t hitbox;
/* Facing */
uint8_t facing;
} agent;
} entity_movement_t;
/* ENTITY_PARTICLE */
struct {
fixed_t z;
fixed_t vx, vy, vz;
} part;
};
/* Alive agents with hitboxes and movement control */
typedef struct {
/* Hitbox, centered around (movement.x, movement.y) */
shape_t hitbox;
/* Current cinematic situation */
entity_movement_t movement;
/* Cinematic parameters */
entity_movement_params_t const *movement_params;
} entity_t;
/* Move an entity in the map space, assumes no collisions. */
void entity_move(entity_t *e, fixed_t dx, fixed_t dy);
/* Entity position */
fpoint_t entity_pos(entity_t const *e);
/* Distance quared between two entities */
fixed_t entity_distance2(entity_t const *e1, entity_t const *e2);
/* Movement if entity stays still */
entity_movement_t entity_still(entity_t *e, fixed_t dt);
/* Movement after a normal move (collisions might prevent it) */
entity_movement_t entity_move(entity_t *e, int direction, fixed_t dt);
/* Position and movement after a dash (collisions might prevent it) */
entity_movement_t entity_dash(entity_t *e, int direction, fixed_t dt);
/* Unit vector in the direction the agent is facing */
/* Unit vector in the direction the entity is facing */
fpoint_t entity_facing(entity_t const *e);
//---
// Particles
//---
/* Particles with no interaction but visual effects */
typedef struct {
/* Location and speed */
fixed_t x, y;
fixed_t vx, vy;
} particle_t;

View File

@ -15,11 +15,18 @@ typedef fixed_t map_coord_t;
static inline fixed_t fmul(fixed_t left, fixed_t right)
{
/* Could be optimized to use 32x32 -> 64 bit product and xtrct */
/* Generally optimized by the compiler to use dmuls.l and xtrct */
int64_t p = (int64_t)left * (int64_t)right;
return (int32_t)(p >> 16);
}
static inline fixed_t fdiv(fixed_t left, fixed_t right)
{
/* Pretty slow */
uint64_t d = (uint64_t)left << 16;
return d / right;
}
static inline fixed_t fix(int constant)
{
return constant << 16;
@ -54,3 +61,19 @@ static inline double f2double(fixed_t f)
{
return (double)f / 65536;
}
static inline fixed_t feasein(fixed_t x)
{
return fmul(x, x);
}
static inline fixed_t fease(fixed_t x)
{
if(x <= fix(1) / 2) {
return 2 * fmul(x, x);
}
else {
x = fix(1) - x;
return fix(1) - 2 * fmul(x, x);
}
}

74
src/game.c Normal file
View File

@ -0,0 +1,74 @@
#include "game.h"
#include <stdlib.h>
bool game_load(game_t *g, level_t *level)
{
game_unload(g);
map_t *m = &g->map;
m->width = level->width;
m->height = level->height;
m->tiles = malloc(m->width * m->height * sizeof *m->tiles);
if(!m->tiles) {
game_unload(g);
return false;
}
for(int y = 0; y < m->height; y++)
for(int x = 0; x < m->width; x++) {
struct tile *t = map_tile(m, x, y);
t->base = level->tiles[level->width * y + x].base;
t->decor = level->tiles[level->width * y + x].decor;
t->solid = (t->base == 0) || (t->base >= 16);
}
camera_init(&g->camera, m);
g->entities = NULL;
g->entity_count = 0;
return true;
}
void game_unload(game_t *g)
{
g->map.width = 0;
g->map.height = 0;
free(g->map.tiles);
for(int i = 0; i < g->entity_count; i++)
free(g->entities[i]);
free(g->entities);
}
void game_add_entity(game_t *g, entity_t *entity)
{
size_t new_size = (g->entity_count + 1) * sizeof *g->entities;
entity_t **new_entities = realloc(g->entities, new_size);
if(!new_entities) return;
g->entities = new_entities;
g->entities[g->entity_count] = entity;
g->entity_count++;
}
bool game_shape_collides(game_t *g, entity_t const *e, shape_t const *hitbox)
{
/* Collision against map */
if(map_entity_collides(&g->map, hitbox))
return true;
/* Collision against entities */
for(int i = 0; i < g->entity_count; i++) {
entity_t *other = g->entities[i];
if(other == e) continue;
shape_t other_hitbox = other->hitbox;
shape_translate(&other_hitbox, entity_pos(other));
if(shape_collide(hitbox, &other_hitbox))
return true;
}
return false;
}

36
src/game.h Normal file
View File

@ -0,0 +1,36 @@
//---
// game: Static and dynamic information relating to an unfolding game
//---
#pragma once
#include "map.h"
#include "entities.h"
#include "render.h"
#include "level.h"
typedef struct game {
/* The map's coordinate system is the primary coordinate system in all of
this game's code */
map_t map;
/* User's camera */
camera_t camera;
/* List of entities */
entity_t **entities;
int entity_count;
/* Player; this must be one of the entities loaded in the game */
entity_t *player;
} game_t;
/* Allocate resources to load a level. */
bool game_load(game_t *g, level_t *level);
/* Free resources allocated for the level. */
void game_unload(game_t *g);
/* Add an entity to the game. */
void game_add_entity(game_t *g, entity_t *e);
/* Check whether an entity would collide if it had the given hitbox. */
bool game_shape_collides(game_t *g, entity_t const *e, shape_t const *hitbox);

View File

@ -65,6 +65,23 @@ ipoint_t point_f2i(fpoint_t);
irect_t rect_f2i(frect_t);
icircle_t circle_f2i(fcircle_t);
/* Distance-squared between two points */
static inline fixed_t dist2(fpoint_t p1, fpoint_t p2)
{
fixed_t dx = p2.x - p1.x;
fixed_t dy = p2.y - p1.y;
return fmul(dx, dx) + fmul(dy, dy);
}
/* Unit vector in the specified direction */
static inline fpoint_t fdir(int dir)
{
return (fpoint_t){
.x = fix((dir == RIGHT) - (dir == LEFT)),
.y = fix((dir == DOWN) - (dir == UP)),
};
}
//---
// General shapes
//---

View File

@ -3,6 +3,7 @@
#include "render.h"
#include "geometry.h"
#include "entities.h"
#include "game.h"
#include <gint/display.h>
#include <gint/keyboard.h>
@ -24,12 +25,6 @@ static bool getkey_global_shortcuts(key_event_t e)
return false;
}
#define _ C_BLACK,
#define R C_RED,
#define G C_GREEN,
#define B C_BLUE,
#define Y C_RGB(16,16,16),
extern level_t lv_demo;
int main(void)
@ -42,33 +37,29 @@ int main(void)
usb_interface_t const *interfaces[] = { &usb_ff_bulk, NULL };
usb_open(interfaces, GINT_CALL_NULL);
//---
// Load map
//---
game_t game = { 0 };
map_t *m = &game.map;
camera_t *c = &game.camera;
struct map *m = malloc(sizeof *m);
m->width = lv_demo.width;
m->height = lv_demo.height;
m->entities = NULL;
m->entity_count = 0;
m->tiles = malloc(m->width * m->height * sizeof *m->tiles);
for(int y = 0; y < m->height; y++)
for(int x = 0; x < m->width; x++) {
struct tile *t = map_tile(m, x, y);
t->base = lv_demo.tiles[lv_demo.width * y + x].base;
t->decor = lv_demo.tiles[lv_demo.width * y + x].decor;
t->solid = (t->base == 0) || (t->base >= 16);
}
struct camera camera;
struct camera *c = &camera;
camera_init(c, m);
game_load(&game, &lv_demo);
//---
// Load entities on the map
// Load random entities on the map
//---
entity_movement_params_t emp_none = {
.min_speed = fix(0),
.max_speed = fix(0),
.accel_time = fix(0),
.decel_time = fix(0),
};
entity_movement_params_t emp_player = {
.min_speed = PLAYER_SPEED / 4,
.max_speed = PLAYER_SPEED,
.accel_time = fix(1) / 4,
.decel_time = fix(1) / 4,
};
for(int i = 0; i < 10; i++) {
entity_t *e = malloc(sizeof *e);
if(!e) continue;
@ -81,30 +72,34 @@ int main(void)
struct tile *t = map_tile(m, x, y);
if(t && !t->solid) break;
}
e->type = ENTITY_AGENT;
e->x = fix(x) + fix(1) / 2;
e->y = fix(y) + fix(1) / 2;
e->movement.x = fix(x) + fix(1) / 2;
e->movement.y = fix(y) + fix(1) / 2;
e->movement.vx = 0;
e->movement.vy = 0;
e->movement.facing = rand() % 4;
e->movement.accel_duration = 0;
e->movement_params = &emp_none;
if(rand() % 2) {
e->agent.hitbox = (shape_t){ SHAPE_RECT,
.rect = { .l=-fix(3)/8, .r=fix(3)/8, .t=-fix(3)/8, .b=fix(3)/8 },
e->hitbox = (shape_t){ SHAPE_RECT,
.rect = { .l=-fix(1)/4, .r=fix(1)/4, .t=-fix(1)/4, .b=fix(1)/4 },
.color = C_GREEN,
};
e->agent.facing = rand() % 4;
}
else {
e->agent.hitbox = (shape_t){ SHAPE_CIRCLE,
.circle = { .x=0, .y=0, .r=fix(3)/8 },
e->hitbox = (shape_t){ SHAPE_CIRCLE,
.circle = { .x=0, .y=0, .r=fix(1)/4 },
.color = C_GREEN,
};
e->agent.facing = rand() % 4;
}
map_add_entity(m, e);
game_add_entity(&game, e);
}
entity_t *player = m->entities[5];
player->agent.hitbox.color = C_BLUE;
entity_t *player = game.entities[5];
game.player = player;
player->hitbox.color = C_BLUE;
player->movement_params = &emp_player;
//---
// Main loop
@ -132,7 +127,7 @@ int main(void)
time_render = prof_exec({
dclear(C_RGB(2,1,1));
render_map(m, c, player);
render_game(&game);
dprint(1, 1, C_WHITE, "Frame: %.3j ms", time_render);
dupdate();
});
@ -176,52 +171,44 @@ int main(void)
}
else lock_optn = false;
/* Player speed */
vx = PLAYER_SPEED_X;
vy = PLAYER_SPEED_Y;
/* Player movement */
fixed_t dx = 0;
fixed_t dy = 0;
int new_facing = player->agent.facing;
int dir = -1;
if(keydown(KEY_UP)) dir = UP;
if(keydown(KEY_DOWN)) dir = DOWN;
if(keydown(KEY_LEFT)) dir = LEFT;
if(keydown(KEY_RIGHT)) dir = RIGHT;
if(keydown(KEY_UP)) {
dy -= fmul(dt, vy);
new_facing = UP;
}
if(keydown(KEY_DOWN)) {
dy += fmul(dt, vy);
new_facing = DOWN;
}
if(keydown(KEY_LEFT)) {
dx -= fmul(dt, vx);
new_facing = LEFT;
}
if(keydown(KEY_RIGHT)) {
dx += fmul(dt, vx);
new_facing = RIGHT;
}
entity_movement_t next = (dir >= 0)
? entity_move(player, dir, dt)
: entity_still(player, dt);
entity_move(player, dx, dy);
if(map_entity_collides(m, player))
entity_move(player, -dx, -dy);
shape_t player_hitbox = player->hitbox;
shape_translate(&player_hitbox, (fpoint_t){ next.x, next.y });
player->agent.facing = new_facing;
if(!game_shape_collides(&game, player, &player_hitbox)) {
player->movement = next;
}
else {
player->movement.facing = next.facing;
player->movement.vx = fix(0);
player->movement.vy = fix(0);
player->movement.accel_duration = fix(0);
}
/* Attack */
if(keydown(KEY_SHIFT) && !lock_shift) {
//~ Attacks
fixed_t reach2 = fmul(ATTACK_REACH, ATTACK_REACH);
fixed_t angle_cos2 = fmul(ATTACK_ANGLE_COS, ATTACK_ANGLE_COS);
fpoint_t dir = entity_facing(player);
fpoint_t dir = fdir(player->movement.facing);
for(int i = 0; i < m->entity_count; i++) {
entity_t *e = m->entities[i];
if(e->type != ENTITY_AGENT) continue;
for(int i = 0; i < game.entity_count; i++) {
entity_t *e = game.entities[i];
if(e == player) continue;
/* Distance to player */
fixed_t dx = e->x - player->x;
fixed_t dy = e->y - player->y;
fixed_t dx = e->movement.x - player->movement.x;
fixed_t dy = e->movement.y - player->movement.y;
fixed_t dist2 = fmul(dx, dx) + fmul(dy, dy);
if(dist2 > reach2) continue;
@ -231,7 +218,7 @@ int main(void)
continue;
/* Inflict damage */
e->agent.hitbox.color = C_RED;
e->hitbox.color = C_RED;
}
//~
}
@ -239,7 +226,6 @@ int main(void)
}
timer_stop(timer_id);
map_destroy(m);
prof_quit();
usb_close();
return 1;

View File

@ -12,7 +12,7 @@ void tile_shape(struct tile const *tile, shape_t *s)
s->rect.b = fix(1)/2;
}
struct tile *map_tile(struct map *m, int x, int y)
struct tile *map_tile(map_t const *m, int x, int y)
{
if(x < 0 || x >= m->width || y < 0 || y >= m->height)
return NULL;
@ -20,28 +20,10 @@ struct tile *map_tile(struct map *m, int x, int y)
return &m->tiles[y * m->width + x];
}
void map_add_entity(struct map *m, entity_t *e)
bool map_entity_collides(map_t *m, shape_t const *hitbox)
{
size_t new_size = (m->entity_count + 1) * sizeof *m->entities;
entity_t **new_entities = realloc(m->entities, new_size);
if(!new_entities) return;
m->entities = new_entities;
m->entities[m->entity_count] = e;
m->entity_count++;
}
bool map_entity_collides(struct map *m, entity_t *e)
{
shape_t entity_hitbox;
shape_t tile_hitbox;
if(e->type == ENTITY_PARTICLE) return false;
/* Determine the entity hitbox */
entity_hitbox = e->agent.hitbox;
shape_translate(&entity_hitbox, (fpoint_t){ e->x, e->y });
/* Collisions against walls and static objects */
for(int y = 0; y < m->height; y++)
for(int x = 0; x < m->width; x++) {
@ -52,32 +34,9 @@ bool map_entity_collides(struct map *m, entity_t *e)
fpoint_t center = { fix(x) + fix(1)/2, fix(y) + fix(1)/2 };
shape_translate(&tile_hitbox, center);
if(shape_collide(&entity_hitbox, &tile_hitbox))
return true;
}
/* Collision against entities */
for(int i = 0; i < m->entity_count; i++) {
entity_t *other = m->entities[i];
if(other == e || other->type == ENTITY_PARTICLE) continue;
shape_t other_hitbox = other->agent.hitbox;
shape_translate(&other_hitbox, (fpoint_t){ other->x, other->y });
if(shape_collide(&entity_hitbox, &other_hitbox))
if(shape_collide(hitbox, &tile_hitbox))
return true;
}
return false;
}
void map_destroy(struct map *m)
{
free(m->tiles);
for(int i = 0; i < m->entity_count; i++)
free(m->entities[i]);
free(m->entities);
free(m);
}

View File

@ -40,24 +40,16 @@ void tile_shape(struct tile const *tile, shape_t *s);
// Map grid, tiles location in space, and entities
//---
struct map {
typedef struct {
/* Dimensions, columns are 0 to width-1, rows are 0 to height-1 */
int width, height;
/* All tiles */
struct tile *tiles;
/* Entities on the map */
entity_t **entities;
int entity_count;
};
} map_t;
/* 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);
struct tile *map_tile(map_t const *m, int x, int y);
/* Add an entity to the map. */
void map_add_entity(struct map *m, entity_t *e);
/* Check whether an entity collides with the map. */
bool map_entity_collides(struct map *m, entity_t *e);
/* Free resources associated with the map. */
void map_destroy(struct map *m);
/* Check whether a hitbox collides with the map. */
bool map_entity_collides(map_t *m, shape_t const *hitbox);

View File

@ -1,11 +1,13 @@
#include "render.h"
#include "game.h"
#include <gint/display.h>
//---
// Camera management
//---
void camera_init(struct camera *c, struct map *m)
void camera_init(camera_t *c, map_t const *m)
{
c->zoom = 1;
@ -27,7 +29,7 @@ void camera_init(struct camera *c, struct map *m)
c->y = (c->limits.y_min + c->limits.y_max) / 2;
}
ipoint_t camera_map2screen(struct camera *c, fpoint_t p)
ipoint_t camera_map2screen(camera_t const *c, fpoint_t p)
{
return (ipoint_t){
.x = DWIDTH / 2 + fround((p.x - c->x) * TILE_WIDTH * c->zoom),
@ -35,7 +37,7 @@ ipoint_t camera_map2screen(struct camera *c, fpoint_t p)
};
}
/* Translate screen coordinates to map coordinates */
fpoint_t camera_screen2map(struct camera *c, ipoint_t p)
fpoint_t camera_screen2map(camera_t const *c, ipoint_t p)
{
return (fpoint_t){
.x = c->x + fix(p.x - DWIDTH / 2) / TILE_WIDTH / c->zoom,
@ -44,7 +46,7 @@ fpoint_t camera_screen2map(struct camera *c, ipoint_t p)
}
/* Lock the camera at the center if set by settings. */
static bool camera_lock(struct camera *c)
static bool camera_lock(camera_t *c)
{
if(c->zoom == 1 && CAMERA_LOCK_AT_x1)
{
@ -56,7 +58,7 @@ static bool camera_lock(struct camera *c)
}
/* Bound the camera to the map limits */
static void camera_bound(struct camera *c)
static void camera_bound(camera_t *c)
{
/* Project top left and bottom-right corners of viewport onto the map */
ipoint_t v_tl = { c->viewport.x_min, c->viewport.y_min };
@ -76,7 +78,7 @@ static void camera_bound(struct camera *c)
c->y += (c->limits.y_max - m_br.y);
}
void camera_move(struct camera *c, map_coord_t dx, map_coord_t dy)
void camera_move(camera_t *c, map_coord_t dx, map_coord_t dy)
{
if(camera_lock(c)) return;
c->x += dx;
@ -84,7 +86,7 @@ void camera_move(struct camera *c, map_coord_t dx, map_coord_t dy)
camera_bound(c);
}
void camera_zoom(struct camera *c, int zoom)
void camera_zoom(camera_t *c, int zoom)
{
if(zoom < ZOOM_MIN) zoom = ZOOM_MIN;
if(zoom > ZOOM_MAX) zoom = ZOOM_MAX;
@ -94,7 +96,7 @@ void camera_zoom(struct camera *c, int zoom)
camera_bound(c);
}
fixed_t camera_ppu(struct camera *c)
fixed_t camera_ppu(camera_t const *c)
{
/* Since this assumes isotropic space we can use TILE_WIDTH or TILE_HEIGHT
indifferently (they're assumed equal) */
@ -107,11 +109,10 @@ fixed_t camera_ppu(struct camera *c)
/* TODO: render_map(): Split into render_game() which takes the game
structure as input (including the player) */
void render_map(struct map *m, struct camera *c, entity_t *player)
void render_map(map_t const *m, camera_t const *c)
{
extern bopti_image_t img_tileset_base;
extern bopti_image_t img_tileset_decor;
extern bopti_image_t img_spritesheet;
/* Render floor and walls */
for(int row = 0; row < m->height; row++)
@ -131,16 +132,24 @@ void render_map(struct map *m, struct camera *c, entity_t *player)
TILE_WIDTH * (t->decor % 16), TILE_HEIGHT * (t->decor / 16),
TILE_WIDTH, TILE_HEIGHT, DIMAGE_NONE);
}
}
void render_game(game_t const *g)
{
extern bopti_image_t img_spritesheet;
camera_t const *c = &g->camera;
entity_t const *player = g->player;
render_map(&g->map, &g->camera);
/* Render entities */
for(int i = 0; i < m->entity_count; i++) {
entity_t *e = m->entities[i];
if(e->type == ENTITY_PARTICLE) continue;
for(int i = 0; i < g->entity_count; i++) {
entity_t *e = g->entities[i];
ipoint_t p = camera_map2screen(c, (fpoint_t){ e->x, e->y });
ipoint_t p = camera_map2screen(c, entity_pos(e));
/* Translate entity hitbox to the map coordinate system */
shape_t s = e->agent.hitbox;
shape_t s = e->hitbox;
shape_scale(&s, camera_ppu(c));
shape_translate(&s, point_i2f(p));
shape_draw(&s);
@ -151,7 +160,7 @@ void render_map(struct map *m, struct camera *c, entity_t *player)
}
/* Render player */
ipoint_t pp = camera_map2screen(c, (fpoint_t){ player->x, player->y });
ipoint_t pp = camera_map2screen(c, entity_pos(player));
dsubimage(pp.x-8, pp.y-8, &img_spritesheet,
16 * player->agent.facing, 0, 16, 16, DIMAGE_NONE);
16 * player->movement.facing, 0, 16, 16, DIMAGE_NONE);
}

View File

@ -13,7 +13,7 @@
// Camera management
//---
struct camera {
typedef struct {
/* Current zoom level */
int zoom;
/* Boundaries of the viewable space, in map coordinates */
@ -30,25 +30,26 @@ struct camera {
map_coord_t width, height;
/* Current center point (position of center of screen in the map) */
map_coord_t x, y;
};
} camera_t;
/* Initialize camera to look at the middle of the map, and set boundaries. */
void camera_init(struct camera *c, struct map *m);
void camera_init(camera_t *c, map_t const *m);
/* Translate map coordinates to pixel coordinates on screen. */
ipoint_t camera_map2screen(struct camera *c, fpoint_t map_point);
ipoint_t camera_map2screen(camera_t const *c, fpoint_t map_point);
/* Translate screen coordinates to map coordinates. */
fpoint_t camera_screen2map(struct camera *c, ipoint_t screen_point);
fpoint_t camera_screen2map(camera_t const *c, ipoint_t screen_point);
/* Move camera by a distance in map coordinates. */
void camera_move(struct camera *c, map_coord_t dx, map_coord_t dy);
void camera_move(camera_t *c, map_coord_t dx, map_coord_t dy);
/* Set the zoom level of the camera. */
void camera_zoom(struct camera *c, int zoom);
void camera_zoom(camera_t *c, int zoom);
/* Number of pixels per unit of map. Assumes isotropic space. */
fixed_t camera_ppu(struct camera *c);
fixed_t camera_ppu(camera_t const *c);
//---
// Rendering
@ -57,4 +58,8 @@ fixed_t camera_ppu(struct camera *c);
/* 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, entity_t *player);
void render_map(map_t const *m, camera_t const *c);
/* Render game full-screen. */
struct game;
void render_game(struct game const *g);

View File

@ -26,8 +26,7 @@
/* Ideal frame rate (FPS) */
#define FRAME_RATE 30
/* Player speed in tiles per second */
#define PLAYER_SPEED_X fix(3)
#define PLAYER_SPEED_Y fix(3)
#define PLAYER_SPEED fix(4)
/* Player reach for attacks, in tiles */
#define ATTACK_REACH fix(1)
/* Width of the cone where attacks reach, in cosine */