Compare commits
38 Commits
3f837d7052
...
main
Author | SHA1 | Date | |
---|---|---|---|
6c25396d4b | |||
0a3a8cb8ab | |||
1a89e4f3b6 | |||
60e1a4ed43 | |||
c7d9f16515 | |||
7e67d09508 | |||
f5f5e2c20b | |||
567db0bd71 | |||
df1d1450e7 | |||
7649a2dc7e | |||
d084e4dba3 | |||
d6db2f3e5f | |||
b0f4106d51 | |||
84b7a09383 | |||
c5c677320b | |||
5f522abcb5 | |||
ab5c442433 | |||
26018facea | |||
407a984300 | |||
08f279e340 | |||
57e025e6aa | |||
d823220e90 | |||
9ef88fb34d | |||
055b3dd7b1 | |||
f6d942c614 | |||
79eb52e282 | |||
d05e382063 | |||
72599b7d00 | |||
c478ee23db | |||
cb44cab4f8 | |||
e05f050e7d | |||
ff44a3436b | |||
3440669227 | |||
be9c443c58 | |||
30e4d544fd | |||
95c03d25d5 | |||
56eaee9d08 | |||
4866226840 |
10
.vscode/settings.json
vendored
@ -1,5 +1,13 @@
|
|||||||
{
|
{
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
"stdbool.h": "c"
|
"*.h": "c",
|
||||||
|
"stdbool.h": "c",
|
||||||
|
"chrono": "c",
|
||||||
|
"cmath": "c",
|
||||||
|
"cstdlib": "c",
|
||||||
|
"format": "c",
|
||||||
|
"ratio": "c",
|
||||||
|
"xlocnum": "c",
|
||||||
|
"xutility": "c"
|
||||||
}
|
}
|
||||||
}
|
}
|
30
MODULE.bazel
@ -1,6 +1,24 @@
|
|||||||
###############################################################################
|
"""
|
||||||
# Bazel now uses Bzlmod by default to manage external dependencies.
|
CrocParty: a tiny platformer.
|
||||||
# Please consider migrating your external dependencies from WORKSPACE to MODULE.bazel.
|
"""
|
||||||
#
|
module(
|
||||||
# For more details, please check https://github.com/bazelbuild/bazel/issues/18958
|
name="crocparty",
|
||||||
###############################################################################
|
version="0.0",
|
||||||
|
)
|
||||||
|
|
||||||
|
bazel_dep(name="rules_python", version="0.31.0")
|
||||||
|
|
||||||
|
# Set up Python
|
||||||
|
python = use_extension("@rules_python//python/extensions:python.bzl", "python")
|
||||||
|
python.toolchain(
|
||||||
|
python_version = "3.10",
|
||||||
|
is_default = True,
|
||||||
|
)
|
||||||
|
|
||||||
|
pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
|
||||||
|
pip.parse(
|
||||||
|
hub_name = "pytools_deps",
|
||||||
|
python_version = "3.10",
|
||||||
|
requirements_lock = "//pytools:requirements.lock",
|
||||||
|
)
|
||||||
|
use_repo(pip, "pytools_deps")
|
1659
MODULE.bazel.lock
generated
22
README.md
@ -1,3 +1,25 @@
|
|||||||
# Croc Party!
|
# Croc Party!
|
||||||
|
|
||||||
A tiny platform game in C/SDL.
|
A tiny platform game in C/SDL.
|
||||||
|
|
||||||
|
# Commands
|
||||||
|
|
||||||
|
## Run (release)
|
||||||
|
|
||||||
|
```
|
||||||
|
$ bazel run -c opt sdl_host.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```
|
||||||
|
$ bazel run sdl_host.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dump requirements
|
||||||
|
|
||||||
|
(TODO: Automate)
|
||||||
|
|
||||||
|
```
|
||||||
|
$ pip freeze -r requirements.txt > requirements.lock
|
||||||
|
```
|
6
device/BUILD
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
cc_library(
|
||||||
|
name = "device",
|
||||||
|
srcs = glob(["*.c"]),
|
||||||
|
hdrs = glob(["*.h"]),
|
||||||
|
visibility = ["//visibility:public"]
|
||||||
|
)
|
7
device/device.c
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#include <stdbool.h>
|
||||||
|
#include "device.h"
|
||||||
|
|
||||||
|
uint8_t device_pixels[DEVICE_H][DEVICE_W];
|
||||||
|
uint32_t device_palette[256];
|
||||||
|
|
||||||
|
bool device_buttons[DEVICE_BUTTON_N];
|
30
device/device.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#ifndef CROCPARTY_DEVICE_H
|
||||||
|
#define CROCPARTY_DEVICE_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
// 256 x 144 is also cool
|
||||||
|
#define DEVICE_W 256
|
||||||
|
#define DEVICE_H 144
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DEVICE_BUTTON_L=0,
|
||||||
|
DEVICE_BUTTON_R=1,
|
||||||
|
DEVICE_BUTTON_U=2,
|
||||||
|
DEVICE_BUTTON_D=3,
|
||||||
|
|
||||||
|
DEVICE_BUTTON_0=4,
|
||||||
|
DEVICE_BUTTON_1=5,
|
||||||
|
DEVICE_BUTTON_2=6,
|
||||||
|
DEVICE_BUTTON_3=7,
|
||||||
|
|
||||||
|
DEVICE_BUTTON_N=8,
|
||||||
|
} DeviceButton;
|
||||||
|
|
||||||
|
extern uint8_t device_pixels[DEVICE_H][DEVICE_W];
|
||||||
|
extern uint32_t device_palette[256];
|
||||||
|
|
||||||
|
extern bool device_buttons[DEVICE_BUTTON_N];
|
||||||
|
|
||||||
|
#endif //CROCPARTY_DEVICE_H
|
36
game/BUILD
@ -1,6 +1,36 @@
|
|||||||
|
load("@bazel_skylib//rules:run_binary.bzl", "run_binary")
|
||||||
|
load("helpers.bzl", "add_sprites")
|
||||||
|
|
||||||
cc_library(
|
cc_library(
|
||||||
name = "game",
|
name = "game",
|
||||||
srcs = glob(["*.c"]),
|
srcs = glob(["*.c"]) + [
|
||||||
hdrs = glob(["*.h"]),
|
"art/game_collectibles.c",
|
||||||
visibility = ["//sdl_host:__pkg__"]
|
"art/game_hud.c",
|
||||||
|
"art/game_npcs.c",
|
||||||
|
"art/game_player.c",
|
||||||
|
"art/game_tiles.c",
|
||||||
|
"map/game_map.c",
|
||||||
|
"map/game_map_entities.c",
|
||||||
|
],
|
||||||
|
hdrs = glob(["*.h", "art/*.h", "map/*.h"]),
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["//device:device", "//sys:sys"]
|
||||||
|
)
|
||||||
|
|
||||||
|
add_sprites(name="game_collectibles", n=24)
|
||||||
|
add_sprites(name="game_hud", n=2)
|
||||||
|
add_sprites(name="game_player", n=96)
|
||||||
|
add_sprites(name="game_npcs", n=64)
|
||||||
|
add_sprites(name="game_tiles", n=120)
|
||||||
|
|
||||||
|
run_binary(
|
||||||
|
name = "game_map",
|
||||||
|
args = [
|
||||||
|
"game_map",
|
||||||
|
"$(location :map/game_map.ldtk)",
|
||||||
|
"$(location :map/game_map.c)",
|
||||||
|
],
|
||||||
|
srcs = [":map/game_map.ldtk"],
|
||||||
|
outs = [":map/game_map.c"],
|
||||||
|
tool = "//pytools:mapdata"
|
||||||
)
|
)
|
7
game/NOTES.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Some notes on the game
|
||||||
|
|
||||||
|
# Position and velocity
|
||||||
|
|
||||||
|
All positions are represented in fixed-point fashion, with a precision of 256.
|
||||||
|
|
||||||
|
For instance, a position or distance of 8 pixels is represented as `0x800`. (2048)
|
BIN
game/art/game_collectibles.aseprite
Normal file
8
game/art/game_collectibles.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#ifndef GAME_COLLECTIBLES_H
|
||||||
|
#define GAME_COLLECTIBLES_H
|
||||||
|
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
extern sys_spritesheet spr_game_collectibles;
|
||||||
|
|
||||||
|
#endif // GAME_COLLECTIBLES_H
|
BIN
game/art/game_collectibles.png
Normal file
After Width: | Height: | Size: 419 B |
BIN
game/art/game_demo_sprites.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
game/art/game_hud.aseprite
Normal file
8
game/art/game_hud.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#ifndef GAME_HUD_H
|
||||||
|
#define GAME_HUD_H
|
||||||
|
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
extern sys_spritesheet spr_game_hud;
|
||||||
|
|
||||||
|
#endif // ART_GAME_HUD_H
|
BIN
game/art/game_hud.png
Normal file
After Width: | Height: | Size: 218 B |
BIN
game/art/game_npcs.aseprite
Normal file
8
game/art/game_npcs.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#ifndef GAME_NPCS_H
|
||||||
|
#define GAME_NPCS_H
|
||||||
|
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
extern sys_spritesheet spr_game_npcs;
|
||||||
|
|
||||||
|
#endif // GAME_NPCS_H
|
BIN
game/art/game_npcs.png
Normal file
After Width: | Height: | Size: 623 B |
BIN
game/art/game_player.aseprite
Normal file
8
game/art/game_player.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#ifndef CROCPARTY_GAME_PLAYER_H
|
||||||
|
#define CROCPARTY_GAME_PLAYER_H
|
||||||
|
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
extern sys_spritesheet spr_game_player;
|
||||||
|
|
||||||
|
#endif // CROCPARTY_GAME_PLAYER_H
|
BIN
game/art/game_player.png
Normal file
After Width: | Height: | Size: 603 B |
BIN
game/art/game_tiles.aseprite
Normal file
8
game/art/game_tiles.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#ifndef CROCPARTY_GAME_TILES_H
|
||||||
|
#define CROCPARTY_GAME_TILES_H
|
||||||
|
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
extern sys_spritesheet spr_game_tiles;
|
||||||
|
|
||||||
|
#endif // CROCPARTY_GAME_TILES_H
|
BIN
game/art/game_tiles.png
Normal file
After Width: | Height: | Size: 729 B |
@ -1,3 +0,0 @@
|
|||||||
#include "device.h"
|
|
||||||
|
|
||||||
uint32_t device_pixels[DEVICE_H][DEVICE_W];
|
|
@ -1,12 +0,0 @@
|
|||||||
#ifndef CROCPARTY_DEVICE_H
|
|
||||||
#define CROCPARTY_DEVICE_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
// 240 x 135 is also cool
|
|
||||||
#define DEVICE_W 128
|
|
||||||
#define DEVICE_H 128
|
|
||||||
|
|
||||||
extern uint32_t device_pixels[DEVICE_H][DEVICE_W];
|
|
||||||
|
|
||||||
#endif //CROCPARTY_DEVICE_H
|
|
60
game/game.c
@ -1,29 +1,57 @@
|
|||||||
#include "device.h"
|
#include "art/game_player.h"
|
||||||
|
#include "art/game_tiles.h"
|
||||||
|
#include "device/device.h"
|
||||||
|
#include "map/game_map.h"
|
||||||
#include "game.h"
|
#include "game.h"
|
||||||
|
#include "sys/sys.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
uint32_t game_frame;
|
uint32_t game_frame;
|
||||||
|
|
||||||
|
const char* game_title() {
|
||||||
|
return "Croc Party!";
|
||||||
|
}
|
||||||
|
|
||||||
void game_init() {
|
void game_init() {
|
||||||
game_frame = 0;
|
sys_init();
|
||||||
|
|
||||||
|
map_game_map_create_entities();
|
||||||
|
|
||||||
|
game_palette_init();
|
||||||
|
game_player_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_destroy() {
|
||||||
|
sys_destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
void game_update() {
|
void game_update() {
|
||||||
|
sys_update();
|
||||||
|
|
||||||
game_frame += 1;
|
game_frame += 1;
|
||||||
|
|
||||||
|
bool allow_input = true;
|
||||||
|
|
||||||
|
game_collectibles_update();
|
||||||
|
game_dialogue_update(&allow_input);
|
||||||
|
game_inflict_update(&allow_input);
|
||||||
|
game_npcs_update(&allow_input);
|
||||||
|
game_player_update(&allow_input);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game_draw() {
|
void game_draw() {
|
||||||
for (int x = 0; x < DEVICE_W; x++) {
|
sys_cls(9);
|
||||||
for (int y = 0; y < DEVICE_H; y++) {
|
|
||||||
uint32_t r = (x * 255)/DEVICE_W;
|
game_player_set_camera();
|
||||||
uint32_t g = (y * 255)/DEVICE_H;
|
sys_map_draw(map_game_map, spr_game_tiles, 0, 0, 0, 0, map_game_map.width, map_game_map.height);
|
||||||
uint32_t b = game_frame & 0x100 ? 0xff - game_frame & 0xff : game_frame & 0xff;
|
|
||||||
if (x % 4 == 2 && y % 4 == 2) {
|
game_collectibles_draw();
|
||||||
r = 255 - r;
|
game_npcs_draw();
|
||||||
g = 255 - g;
|
game_player_draw();
|
||||||
b = 255 - b;
|
|
||||||
}
|
sys_camera_reset();
|
||||||
uint32_t color = r << 24 | g << 16 | b << 8;
|
|
||||||
device_pixels[y][x] = color;
|
game_player_draw_hud();
|
||||||
}
|
game_inflict_draw();
|
||||||
}
|
game_dialogue_draw();
|
||||||
}
|
}
|
||||||
|
12
game/game.h
@ -1,7 +1,19 @@
|
|||||||
#ifndef CROCPARTY_GAME_H
|
#ifndef CROCPARTY_GAME_H
|
||||||
#define CROCPARTY_GAME_H
|
#define CROCPARTY_GAME_H
|
||||||
|
|
||||||
|
#include "game_collectible.h"
|
||||||
|
#include "game_collision.h"
|
||||||
|
#include "game_dialogue.h"
|
||||||
|
#include "game_inflict.h"
|
||||||
|
#include "game_math.h"
|
||||||
|
#include "game_npc.h"
|
||||||
|
#include "game_palette.h"
|
||||||
|
#include "game_player.h"
|
||||||
|
|
||||||
|
const char* game_title();
|
||||||
void game_init();
|
void game_init();
|
||||||
|
void game_destroy();
|
||||||
|
|
||||||
void game_update();
|
void game_update();
|
||||||
void game_draw();
|
void game_draw();
|
||||||
|
|
||||||
|
114
game/game_collectible.c
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include "game.h"
|
||||||
|
#include "art/game_collectibles.h"
|
||||||
|
|
||||||
|
// If this isn't enough, raise the number
|
||||||
|
#define GAME_COLLECTIBLES_N 32
|
||||||
|
|
||||||
|
sys_i32 game_collectible_next = 0;
|
||||||
|
game_collectible game_collectibles[GAME_COLLECTIBLES_N];
|
||||||
|
|
||||||
|
struct {
|
||||||
|
sys_i32 x;
|
||||||
|
sys_i32 y;
|
||||||
|
sys_i32 r;
|
||||||
|
} game_collectible_bubble = {
|
||||||
|
.x = 0,
|
||||||
|
.y = 0,
|
||||||
|
.r = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
sys_i32 game_collectible_wobb_frame = 0;
|
||||||
|
|
||||||
|
void game_collectible_create(sys_i32 x, sys_i32 y, game_collectible_type type) {
|
||||||
|
assert(game_collectible_next < GAME_COLLECTIBLES_N && "too many collectibles");
|
||||||
|
|
||||||
|
sys_i32 id = game_collectible_next++;
|
||||||
|
game_collectibles[id].x = x;
|
||||||
|
game_collectibles[id].y = y;
|
||||||
|
game_collectibles[id].type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
sys_i32 game_collectible_wobb() {
|
||||||
|
return game_wobb3(game_collectible_wobb_frame & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_collectibles_update() {
|
||||||
|
game_collectible_wobb_frame += 1;
|
||||||
|
game_collectible_bubble.r = game_collectible_bubble.r * 7 / 8;
|
||||||
|
if (game_collectible_bubble.r < PIXEL_SZ_MICROPIXEL / 2) { game_collectible_bubble.r = 0; }
|
||||||
|
|
||||||
|
// check collision with player
|
||||||
|
sys_i32 player_x, player_y;
|
||||||
|
game_player_get_center(&player_x, &player_y);
|
||||||
|
|
||||||
|
for (sys_i32 i = 0; i < game_collectible_next; i++) {
|
||||||
|
sys_i32 r;
|
||||||
|
game_collectible* c = &game_collectibles[i];
|
||||||
|
|
||||||
|
if (c->collected) { continue; }
|
||||||
|
|
||||||
|
switch (c->type) {
|
||||||
|
case GAME_COLLECTIBLE_TYPE_CAKE: r = 16; break;
|
||||||
|
default: r = 8; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert to micropixels
|
||||||
|
// add approx radius of player
|
||||||
|
sys_i32 r_distcheck = (r + 5) * PIXEL_SZ_MICROPIXEL;
|
||||||
|
|
||||||
|
int64_t dx = player_x - c->x;
|
||||||
|
int64_t dy = player_y - c->y;
|
||||||
|
|
||||||
|
if (dx * dx + dy * dy <= r_distcheck * r_distcheck) {
|
||||||
|
c->collected = true;
|
||||||
|
// TODO: Spawn effect
|
||||||
|
game_collectible_bubble.x=c->x / PIXEL_SZ_MICROPIXEL;
|
||||||
|
game_collectible_bubble.y=c->y / PIXEL_SZ_MICROPIXEL + game_collectible_wobb();
|
||||||
|
game_collectible_bubble.r=r * PIXEL_SZ_MICROPIXEL;
|
||||||
|
|
||||||
|
switch (c->type) {
|
||||||
|
case GAME_COLLECTIBLE_TYPE_CAKE: game_player_collectibles.n_cake += 16; break;
|
||||||
|
case GAME_COLLECTIBLE_TYPE_MONEY_BIG: game_player_collectibles.n_dollars += 5; break;
|
||||||
|
case GAME_COLLECTIBLE_TYPE_MONEY_SMALL: game_player_collectibles.n_dollars += 1; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_collectibles_draw() {
|
||||||
|
if (game_collectible_bubble.r > 0) {
|
||||||
|
sys_circ_fill(
|
||||||
|
game_collectible_bubble.x,
|
||||||
|
game_collectible_bubble.y,
|
||||||
|
game_collectible_bubble.r / PIXEL_SZ_MICROPIXEL,
|
||||||
|
7
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (sys_i32 i = 0; i < game_collectible_next; i++) {
|
||||||
|
game_collectible* c = &game_collectibles[i];
|
||||||
|
|
||||||
|
if (c->collected) { continue; }
|
||||||
|
|
||||||
|
sys_i32 x = c->x / PIXEL_SZ_MICROPIXEL;
|
||||||
|
sys_i32 y = c->y / PIXEL_SZ_MICROPIXEL + game_collectible_wobb();
|
||||||
|
switch(c->type) {
|
||||||
|
case GAME_COLLECTIBLE_TYPE_CAKE:
|
||||||
|
sys_sprite_draw_ext(
|
||||||
|
spr_game_collectibles,0,x-16,y-16,4,4,false,false);
|
||||||
|
break;
|
||||||
|
case GAME_COLLECTIBLE_TYPE_MONEY_BIG:
|
||||||
|
sys_sprite_draw_ext(
|
||||||
|
spr_game_collectibles,4,x-8,y-8,2,2,false,false);
|
||||||
|
break;
|
||||||
|
case GAME_COLLECTIBLE_TYPE_MONEY_SMALL:
|
||||||
|
sys_sprite_draw_ext(
|
||||||
|
spr_game_collectibles,16,x-8,y-8,2,2,false,false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assert(false&&"invalid collectible");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
25
game/game_collectible.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#ifndef GAME_COLLECTIBLE_H
|
||||||
|
#define GAME_COLLECTIBLE_H
|
||||||
|
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GAME_COLLECTIBLE_TYPE_CAKE,
|
||||||
|
GAME_COLLECTIBLE_TYPE_MONEY_BIG,
|
||||||
|
GAME_COLLECTIBLE_TYPE_MONEY_SMALL,
|
||||||
|
GAME_COLLECTIBLE_TYPE_N
|
||||||
|
} game_collectible_type;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
sys_i32 x;
|
||||||
|
sys_i32 y;
|
||||||
|
game_collectible_type type;
|
||||||
|
bool collected;
|
||||||
|
} game_collectible;
|
||||||
|
|
||||||
|
void game_collectible_create(sys_i32 x, sys_i32 y, game_collectible_type type);
|
||||||
|
|
||||||
|
void game_collectibles_update();
|
||||||
|
void game_collectibles_draw();
|
||||||
|
|
||||||
|
#endif // GAME_COLLECTIBLE_H
|
143
game/game_collision.c
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include "sys/sys.h"
|
||||||
|
#include "game.h"
|
||||||
|
#include "map/game_map.h"
|
||||||
|
|
||||||
|
bool game_collision_is_occlusive(sys_maptile tile) {
|
||||||
|
if (tile >= 0 && tile < 30) { return true; }
|
||||||
|
if (tile >= 60 && tile < 90) { return true; }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// c's modulus can produce a negative result. don't allow that!
|
||||||
|
#define modulus(x, mod) ((x) % (mod) + (mod)) % (mod)
|
||||||
|
#define round_down(x, incr) (x) - modulus(x, incr)
|
||||||
|
#define round_up(x, incr) round_down((x) + (incr) - 1, incr)
|
||||||
|
|
||||||
|
bool game_collision_can_move(game_bbox body, sys_i32 dx, sys_i32 dy) {
|
||||||
|
// First: place the body in space.
|
||||||
|
sys_i32 x = body.x - body.x_origin + dx;
|
||||||
|
sys_i32 y = body.y - body.y_origin + dy;
|
||||||
|
|
||||||
|
// this is extremely similar to the logic in move_to_contact
|
||||||
|
// but has different rounding behavior
|
||||||
|
// (because a body at a fractional position must not actually collide
|
||||||
|
// with fractional pixels)
|
||||||
|
sys_i32 body_x0 = round_down(x, PIXEL_SZ_MICROPIXEL);
|
||||||
|
sys_i32 body_y0 = round_down(y, PIXEL_SZ_MICROPIXEL);
|
||||||
|
sys_i32 body_x1 = body_x0 + body.w;
|
||||||
|
sys_i32 body_y1 = body_y0 + body.h;
|
||||||
|
|
||||||
|
sys_i32 tile_x0 = round_down(body_x0, TILE_SZ_MICROPIXEL);
|
||||||
|
sys_i32 tile_y0 = round_down(body_y0, TILE_SZ_MICROPIXEL);
|
||||||
|
sys_i32 tile_x1 = round_up(body_x1, TILE_SZ_MICROPIXEL);
|
||||||
|
sys_i32 tile_y1 = round_up(body_y1, TILE_SZ_MICROPIXEL);
|
||||||
|
|
||||||
|
// now convert to tile coords instead of pixels
|
||||||
|
tile_x0 /= TILE_SZ_MICROPIXEL;
|
||||||
|
tile_y0 /= TILE_SZ_MICROPIXEL;
|
||||||
|
tile_x1 /= TILE_SZ_MICROPIXEL;
|
||||||
|
tile_y1 /= TILE_SZ_MICROPIXEL;
|
||||||
|
|
||||||
|
for (sys_i32 y = tile_y0; y < tile_y1; y++) {
|
||||||
|
for (sys_i32 x = tile_x0; x < tile_x1; x++) {
|
||||||
|
bool invalid;
|
||||||
|
sys_maptile tile = sys_map_get(map_game_map, x, y, &invalid);
|
||||||
|
if (invalid || game_collision_is_occlusive(tile)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
game_collision game_collision_move_to_contact(
|
||||||
|
game_bbox* body, sys_i32 total_dx, sys_i32 total_dy
|
||||||
|
) {
|
||||||
|
sys_i32 src_x = body->x;
|
||||||
|
sys_i32 src_y = body->y;
|
||||||
|
game_collision collision = {
|
||||||
|
.collided_x=false,
|
||||||
|
.collided_y=false,
|
||||||
|
.distance_x=0,
|
||||||
|
.distance_y=0,
|
||||||
|
};
|
||||||
|
sys_i32 tar_x = body->x + total_dx;
|
||||||
|
sys_i32 tar_y = body->y + total_dy;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
// calculate how much we still need to move
|
||||||
|
int32_t remaining_dx = tar_x - body->x;
|
||||||
|
int32_t remaining_dy = tar_y - body->y;
|
||||||
|
|
||||||
|
// if we don't need to move, stop
|
||||||
|
if (remaining_dx == 0 && remaining_dy == 0) {
|
||||||
|
collision.distance_x = body->x - src_x;
|
||||||
|
collision.distance_y = body->x - src_y;
|
||||||
|
return collision;
|
||||||
|
}
|
||||||
|
|
||||||
|
// figure out where we are
|
||||||
|
sys_i32 body_x0 = body->x - body->x_origin;
|
||||||
|
sys_i32 body_y0 = body->y - body->y_origin;
|
||||||
|
sys_i32 body_x1 = body_x0 + body->w;
|
||||||
|
sys_i32 body_y1 = body_y0 + body->h;
|
||||||
|
|
||||||
|
// figure out what tile we're still in
|
||||||
|
sys_i32 tile_x0 = round_down(body_x0, TILE_SZ_MICROPIXEL);
|
||||||
|
sys_i32 tile_y0 = round_down(body_y0, TILE_SZ_MICROPIXEL);
|
||||||
|
sys_i32 max_overlap = PIXEL_SZ_MICROPIXEL - 1;
|
||||||
|
sys_i32 tile_x1 = round_up(body_x1 - max_overlap, TILE_SZ_MICROPIXEL) + max_overlap;
|
||||||
|
sys_i32 tile_y1 = round_up(body_y1 - max_overlap, TILE_SZ_MICROPIXEL) + max_overlap;
|
||||||
|
|
||||||
|
// but we want the _next_ tile
|
||||||
|
if (tile_x0 == body_x0) { tile_x0 -= TILE_SZ_MICROPIXEL; }
|
||||||
|
if (tile_y0 == body_y0) { tile_y0 -= TILE_SZ_MICROPIXEL; }
|
||||||
|
if (tile_x1 == body_x1) { tile_x1 += TILE_SZ_MICROPIXEL; }
|
||||||
|
if (tile_y1 == body_y1) { tile_y1 += TILE_SZ_MICROPIXEL; }
|
||||||
|
|
||||||
|
// length of an x axis move
|
||||||
|
int step_dx =
|
||||||
|
remaining_dx < 0 ?
|
||||||
|
// line up our left side with the tile to our left
|
||||||
|
// not exceeding dx
|
||||||
|
sys_max_i32(remaining_dx, tile_x0 - body_x0) :
|
||||||
|
remaining_dx > 0 ?
|
||||||
|
// line up our right side with the tile to our right
|
||||||
|
// not exceeding dx
|
||||||
|
sys_min_i32(remaining_dx, tile_x1 - body_x1) :
|
||||||
|
// dx == 0
|
||||||
|
INT32_MAX ;
|
||||||
|
|
||||||
|
int step_dy =
|
||||||
|
remaining_dy < 0 ?
|
||||||
|
// line up our top side with the tile above us
|
||||||
|
// not exceeding dy
|
||||||
|
sys_max_i32(remaining_dy, tile_y0 - body_y0) :
|
||||||
|
remaining_dy > 0 ?
|
||||||
|
// line up our bottom side with the tile below us
|
||||||
|
// not exceeding dy
|
||||||
|
sys_min_i32(remaining_dy, tile_y1 - body_y1) :
|
||||||
|
INT32_MAX;
|
||||||
|
|
||||||
|
// there must be a viable move in some direction
|
||||||
|
assert(step_dx != INT32_MAX || step_dy != INT32_MAX);
|
||||||
|
|
||||||
|
if (sys_abs_i32(step_dx) < sys_abs_i32(step_dy)) {
|
||||||
|
if (game_collision_can_move(*body, step_dx, 0)) {
|
||||||
|
body->x += step_dx; // take the tiny move
|
||||||
|
} else {
|
||||||
|
tar_x = body->x; // stop trying to move
|
||||||
|
collision.collided_x = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (game_collision_can_move(*body, 0, step_dy)) {
|
||||||
|
body->y += step_dy; // take the tiny move
|
||||||
|
} else {
|
||||||
|
tar_y = body->y; // stop trying to move
|
||||||
|
collision.collided_y = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
game/game_collision.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#ifndef GAME_COLLISION_H
|
||||||
|
#define GAME_COLLISION_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
#define TILE_SZ_MICROPIXEL 0x800
|
||||||
|
#define PIXEL_SZ_MICROPIXEL 0x100
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
sys_i32 x_origin;
|
||||||
|
sys_i32 y_origin;
|
||||||
|
sys_i32 x;
|
||||||
|
sys_i32 y;
|
||||||
|
sys_i32 w;
|
||||||
|
sys_i32 h;
|
||||||
|
} game_bbox;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool collided_x;
|
||||||
|
bool collided_y;
|
||||||
|
sys_i32 distance_x;
|
||||||
|
sys_i32 distance_y;
|
||||||
|
} game_collision;
|
||||||
|
|
||||||
|
bool game_collision_is_occlusive(sys_maptile tile);
|
||||||
|
bool game_collision_can_move(game_bbox body, sys_i32 dx, sys_i32 dy);
|
||||||
|
game_collision game_collision_move_to_contact(game_bbox* body, sys_i32 x, sys_i32 y);
|
||||||
|
|
||||||
|
#endif // GAME_COLLISION_H
|
158
game/game_dialogue.c
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "device/device.h"
|
||||||
|
#include "sys/sys.h"
|
||||||
|
#include "game.h"
|
||||||
|
|
||||||
|
#define GAME_DIALOGUE_MAX_N_CHARS 256
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GAME_DIALOGUE_SCREEN_STATE_BOB_IN,
|
||||||
|
GAME_DIALOGUE_SCREEN_STATE_TYPEWRITER,
|
||||||
|
GAME_DIALOGUE_SCREEN_STATE_WAIT,
|
||||||
|
GAME_DIALOGUE_SCREEN_STATE_BOB_OUT,
|
||||||
|
} game_dialogue_screen_state;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool visible;
|
||||||
|
bool modal;
|
||||||
|
const char* dialogue;
|
||||||
|
|
||||||
|
sys_i32 w;
|
||||||
|
sys_i32 h;
|
||||||
|
|
||||||
|
game_dialogue_screen_state state;
|
||||||
|
sys_i32 state_progress;
|
||||||
|
sys_i32 n_chars_shown;
|
||||||
|
sys_i32 n_chars;
|
||||||
|
} game_dialogue_screen;
|
||||||
|
|
||||||
|
uint8_t game_dialogue_frame;
|
||||||
|
|
||||||
|
void game_dialogue_display(const char* text, bool modal) {
|
||||||
|
game_dialogue_screen.visible = true;
|
||||||
|
game_dialogue_screen.modal = modal;
|
||||||
|
game_dialogue_screen.dialogue = text;
|
||||||
|
sys_measure_text(text, &game_dialogue_screen.w, &game_dialogue_screen.h);
|
||||||
|
|
||||||
|
game_dialogue_screen.state = GAME_DIALOGUE_SCREEN_STATE_BOB_IN;
|
||||||
|
game_dialogue_screen.state_progress = 0;
|
||||||
|
game_dialogue_screen.n_chars_shown = 0;
|
||||||
|
game_dialogue_screen.n_chars = strlen(text);
|
||||||
|
assert(game_dialogue_screen.n_chars <= GAME_DIALOGUE_MAX_N_CHARS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_dialogue_update(bool* allow_input) {
|
||||||
|
game_dialogue_frame = ((uint32_t) game_dialogue_frame + 1) & 0xff;
|
||||||
|
|
||||||
|
if (
|
||||||
|
game_dialogue_screen.visible &&
|
||||||
|
game_dialogue_screen.state < GAME_DIALOGUE_SCREEN_STATE_BOB_OUT
|
||||||
|
) {
|
||||||
|
*allow_input = false;
|
||||||
|
|
||||||
|
if (sys_btnp(DEVICE_BUTTON_0, false)) {
|
||||||
|
if (game_dialogue_screen.state < GAME_DIALOGUE_SCREEN_STATE_WAIT) {
|
||||||
|
game_dialogue_screen.state = GAME_DIALOGUE_SCREEN_STATE_WAIT;
|
||||||
|
game_dialogue_screen.n_chars_shown = game_dialogue_screen.n_chars;
|
||||||
|
} else {
|
||||||
|
game_dialogue_screen.state = GAME_DIALOGUE_SCREEN_STATE_BOB_OUT;
|
||||||
|
game_dialogue_screen.state_progress = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (game_dialogue_screen.state) {
|
||||||
|
case GAME_DIALOGUE_SCREEN_STATE_BOB_IN:
|
||||||
|
game_dialogue_screen.state_progress += 25;
|
||||||
|
if (game_dialogue_screen.state_progress >= 0x100) {
|
||||||
|
game_dialogue_screen.state = GAME_DIALOGUE_SCREEN_STATE_TYPEWRITER;
|
||||||
|
game_dialogue_screen.state_progress = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GAME_DIALOGUE_SCREEN_STATE_TYPEWRITER:
|
||||||
|
if (game_dialogue_frame % 4 == 0) {
|
||||||
|
game_dialogue_screen.n_chars_shown = sys_min_i32(
|
||||||
|
game_dialogue_screen.n_chars_shown + 1,
|
||||||
|
game_dialogue_screen.n_chars
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (game_dialogue_screen.n_chars_shown >= game_dialogue_screen.n_chars) {
|
||||||
|
game_dialogue_screen.state = GAME_DIALOGUE_SCREEN_STATE_WAIT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GAME_DIALOGUE_SCREEN_STATE_WAIT:
|
||||||
|
break;
|
||||||
|
case GAME_DIALOGUE_SCREEN_STATE_BOB_OUT:
|
||||||
|
game_dialogue_screen.state_progress += 25;
|
||||||
|
if (game_dialogue_screen.state_progress >= 0x100) {
|
||||||
|
game_dialogue_screen.visible = false;
|
||||||
|
game_dialogue_screen.state_progress = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool game_dialogue_is_busy() {
|
||||||
|
return game_dialogue_screen.visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_dialogue_draw() {
|
||||||
|
if (!game_dialogue_screen.visible) { return; }
|
||||||
|
|
||||||
|
sys_i32 x = DEVICE_W / 2 - game_dialogue_screen.w / 2;
|
||||||
|
sys_i32 y = DEVICE_H - game_dialogue_screen.h - 8;
|
||||||
|
|
||||||
|
if (game_dialogue_screen.modal) {
|
||||||
|
y = DEVICE_H / 2 - game_dialogue_screen.h / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
sys_i32 interp_amt = 0;
|
||||||
|
switch (game_dialogue_screen.state) {
|
||||||
|
case GAME_DIALOGUE_SCREEN_STATE_BOB_IN:
|
||||||
|
interp_amt = 255 - game_dialogue_screen.state_progress;
|
||||||
|
break;
|
||||||
|
case GAME_DIALOGUE_SCREEN_STATE_BOB_OUT:
|
||||||
|
interp_amt = game_dialogue_screen.state_progress;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x0 = x - 4;
|
||||||
|
int y0 = y - 4;
|
||||||
|
int x1 = x + game_dialogue_screen.w + 4;
|
||||||
|
int y1 = y + game_dialogue_screen.h + 4;
|
||||||
|
|
||||||
|
sys_i32 cx = (x0 + x1)/2;
|
||||||
|
sys_i32 cy = (y0 + y1)/2;
|
||||||
|
|
||||||
|
sys_oval_fill(
|
||||||
|
x0 + (cx - x0) * interp_amt / 256,
|
||||||
|
y0 + (cy - y0) * interp_amt / 256,
|
||||||
|
x1 + (cx - x1) * interp_amt / 256,
|
||||||
|
y1 + (cy - y1) * interp_amt / 256,
|
||||||
|
game_dialogue_screen.modal ? 0 : 1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (game_dialogue_screen.state == GAME_DIALOGUE_SCREEN_STATE_BOB_OUT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char chars[GAME_DIALOGUE_MAX_N_CHARS + 1];
|
||||||
|
strcpy(chars, game_dialogue_screen.dialogue);
|
||||||
|
chars[game_dialogue_screen.n_chars_shown] = 0;
|
||||||
|
|
||||||
|
if (game_dialogue_screen.modal) {
|
||||||
|
for (int dy = -2; dy <= 2; dy++) {
|
||||||
|
for (int dx = -2; dx <= 2; dx++) {
|
||||||
|
sys_print(chars, x+dx, y+dy, 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int dy = -1; dy <= 1; dy++) {
|
||||||
|
for (int dx = -1; dx <= 1; dx++) {
|
||||||
|
sys_print(chars, x+dx, y+dy, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sys_print(chars, x, y, 7);
|
||||||
|
}
|
12
game/game_dialogue.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#ifndef GAME_DIALOGUE_H
|
||||||
|
#define GAME_DIALOGUE_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
void game_dialogue_display(const char* text, bool modal);
|
||||||
|
bool game_dialogue_is_busy();
|
||||||
|
|
||||||
|
void game_dialogue_update(bool* allow_input);
|
||||||
|
void game_dialogue_draw();
|
||||||
|
|
||||||
|
#endif // GAME_DIALOGUE_H
|
119
game/game_inflict.c
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
#include "game.h"
|
||||||
|
#include "device/device.h"
|
||||||
|
|
||||||
|
#define GAME_INFLICT_SCREEN_MAX_N_MODALS 4
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GAME_INFLICT_SCREEN_STATE_WAIT_FOR_DIALOGUE,
|
||||||
|
GAME_INFLICT_SCREEN_STATE_CLOSE_EYELIDS,
|
||||||
|
GAME_INFLICT_SCREEN_STATE_PERFORM_MODALS,
|
||||||
|
GAME_INFLICT_SCREEN_STATE_OPEN_EYELIDS,
|
||||||
|
GAME_INFLICT_SCREEN_STATE_INACTIVE
|
||||||
|
} game_inflict_screen_state_t;
|
||||||
|
|
||||||
|
game_inflict_screen_state_t game_inflict_screen_state =
|
||||||
|
GAME_INFLICT_SCREEN_STATE_INACTIVE;
|
||||||
|
|
||||||
|
game_inflict_type game_inflict_screen_type = 0;
|
||||||
|
sys_i32 game_inflict_screen_n_modals = 0;
|
||||||
|
sys_i32 game_inflict_screen_next_modal = 0;
|
||||||
|
sys_i32 game_inflict_screen_progress = 0;
|
||||||
|
|
||||||
|
const char* game_inflict_screen_modals[GAME_INFLICT_SCREEN_MAX_N_MODALS];
|
||||||
|
|
||||||
|
void game_inflict_apply();
|
||||||
|
|
||||||
|
void game_inflict_update(bool* allow_input) {
|
||||||
|
if (game_inflict_screen_state <= GAME_INFLICT_SCREEN_STATE_OPEN_EYELIDS) {
|
||||||
|
*allow_input = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (game_inflict_screen_state) {
|
||||||
|
case GAME_INFLICT_SCREEN_STATE_WAIT_FOR_DIALOGUE:
|
||||||
|
if (!game_dialogue_is_busy()) {
|
||||||
|
game_inflict_screen_state = GAME_INFLICT_SCREEN_STATE_CLOSE_EYELIDS;
|
||||||
|
game_inflict_screen_progress = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GAME_INFLICT_SCREEN_STATE_CLOSE_EYELIDS:
|
||||||
|
game_inflict_screen_progress += 10;
|
||||||
|
if (game_inflict_screen_progress >= 0x100) {
|
||||||
|
game_inflict_screen_state =
|
||||||
|
GAME_INFLICT_SCREEN_STATE_PERFORM_MODALS;
|
||||||
|
game_inflict_screen_progress = 0;
|
||||||
|
game_inflict_apply();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GAME_INFLICT_SCREEN_STATE_PERFORM_MODALS:
|
||||||
|
if (game_dialogue_is_busy()) {
|
||||||
|
// fine, don't do anything
|
||||||
|
} else if (game_inflict_screen_next_modal < game_inflict_screen_n_modals) {
|
||||||
|
game_dialogue_display(game_inflict_screen_modals[
|
||||||
|
game_inflict_screen_next_modal++
|
||||||
|
], true);
|
||||||
|
} else {
|
||||||
|
game_inflict_screen_state =
|
||||||
|
GAME_INFLICT_SCREEN_STATE_OPEN_EYELIDS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GAME_INFLICT_SCREEN_STATE_OPEN_EYELIDS:
|
||||||
|
game_inflict_screen_progress += 10;
|
||||||
|
if (game_inflict_screen_progress >= 0x100) {
|
||||||
|
game_inflict_screen_state =
|
||||||
|
GAME_INFLICT_SCREEN_STATE_INACTIVE;
|
||||||
|
game_inflict_screen_progress = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GAME_INFLICT_SCREEN_STATE_INACTIVE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_inflict_enqueue(int inflict_id) {
|
||||||
|
game_inflict_screen_state = GAME_INFLICT_SCREEN_STATE_WAIT_FOR_DIALOGUE;
|
||||||
|
|
||||||
|
switch (inflict_id) {
|
||||||
|
case GAME_INFLICT_TYPE_CHICKEN:
|
||||||
|
game_inflict_screen_type = GAME_INFLICT_TYPE_CHICKEN;
|
||||||
|
game_inflict_screen_n_modals = 2;
|
||||||
|
game_inflict_screen_next_modal = 0;
|
||||||
|
game_inflict_screen_modals[0] = "For your birthday...";
|
||||||
|
game_inflict_screen_modals[1] = "You're a chicken!";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_inflict_apply() {
|
||||||
|
switch (game_inflict_screen_type) {
|
||||||
|
case GAME_INFLICT_TYPE_CHICKEN:
|
||||||
|
game_player_collectibles.n_cake = 0;
|
||||||
|
game_player_phase = GAME_PLAYER_PHASE_CHICKEN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_inflict_draw_eyelids(uint8_t progress);
|
||||||
|
void game_inflict_draw() {
|
||||||
|
switch (game_inflict_screen_state) {
|
||||||
|
case GAME_INFLICT_SCREEN_STATE_WAIT_FOR_DIALOGUE:
|
||||||
|
// TODO: Screen distortion
|
||||||
|
break;
|
||||||
|
case GAME_INFLICT_SCREEN_STATE_CLOSE_EYELIDS:
|
||||||
|
game_inflict_draw_eyelids(game_inflict_screen_progress & 0xff);
|
||||||
|
break;
|
||||||
|
case GAME_INFLICT_SCREEN_STATE_PERFORM_MODALS:
|
||||||
|
game_inflict_draw_eyelids(255);
|
||||||
|
break;
|
||||||
|
case GAME_INFLICT_SCREEN_STATE_OPEN_EYELIDS:
|
||||||
|
game_inflict_draw_eyelids(255 - game_inflict_screen_progress & 0xff);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_inflict_draw_eyelids(uint8_t progress) {
|
||||||
|
sys_i32 offset = DEVICE_H / 2 -
|
||||||
|
((255 - progress) * (255 - progress) * DEVICE_H/2) / (255 * 255);
|
||||||
|
sys_rect_fill(0, 0, DEVICE_W, offset, 0);
|
||||||
|
sys_rect_fill(0, DEVICE_H - offset, DEVICE_W, DEVICE_H, 0);
|
||||||
|
}
|
19
game/game_inflict.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#ifndef GAME_INFLICT_H
|
||||||
|
#define GAME_INFLICT_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "game.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
// GAME_INFLICT_TYPE_CROC=1,
|
||||||
|
GAME_INFLICT_TYPE_CHICKEN=2,
|
||||||
|
// GAME_INFLICT_TYPE_BAT=3,
|
||||||
|
// GAME_INFLICT_TYPE_ASLEEP=4,
|
||||||
|
} game_inflict_type;
|
||||||
|
|
||||||
|
void game_inflict_update(bool* allow_input);
|
||||||
|
void game_inflict_draw();
|
||||||
|
|
||||||
|
void game_inflict_enqueue(int inflict_id);
|
||||||
|
|
||||||
|
#endif // GAME_INFLICT_H
|
36
game/game_math.c
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#include "game_math.h"
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
// prog runs from 0 to 255
|
||||||
|
|
||||||
|
// except for game_wobb1, all of these are
|
||||||
|
// generated with list(enumerate([round(sin(i/256 * pi * 2)*k) for i in range(256)]))
|
||||||
|
// for k = various values
|
||||||
|
|
||||||
|
sys_i32 game_wobb1(uint8_t prog) {
|
||||||
|
// return 0 or 1 in a roughly sinusoidal pattern
|
||||||
|
if (prog<128) return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sys_i32 game_wobb2(uint8_t prog) {
|
||||||
|
// return -1, 0, 1 in a roughly sinusoidal pattern
|
||||||
|
if (prog<22) return 0;
|
||||||
|
if (prog<107) return 1;
|
||||||
|
if (prog<150) return 0;
|
||||||
|
if (prog<235) return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sys_i32 game_wobb3(uint8_t prog) {
|
||||||
|
// return -2, -1, 0, 1, 2 in a roughly sinusoidal pattern
|
||||||
|
if (prog<11) return 0;
|
||||||
|
if (prog<35) return 1;
|
||||||
|
if (prog<94) return 2;
|
||||||
|
if (prog<118) return 1;
|
||||||
|
if (prog<139) return 0;
|
||||||
|
if (prog<163) return -1;
|
||||||
|
if (prog<222) return -2;
|
||||||
|
if (prog<246) return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
11
game/game_math.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#ifndef GAME_MATH_H
|
||||||
|
#define GAME_MATH_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
sys_i32 game_wobb1(uint8_t prog);
|
||||||
|
sys_i32 game_wobb2(uint8_t prog);
|
||||||
|
sys_i32 game_wobb3(uint8_t prog);
|
||||||
|
|
||||||
|
#endif // GAME_MATH_H
|
171
game/game_npc.c
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include "art/game_hud.h"
|
||||||
|
#include "art/game_npcs.h"
|
||||||
|
#include "device/device.h"
|
||||||
|
#include "sys/sys.h"
|
||||||
|
#include "game.h"
|
||||||
|
|
||||||
|
// If this isn't enough, raise the number
|
||||||
|
#define GAME_NPCS_N 32
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t main;
|
||||||
|
// alt0 and alt1 are ramps
|
||||||
|
uint8_t alt0_0; uint8_t alt0_1;
|
||||||
|
uint8_t alt1_0; uint8_t alt1_1;
|
||||||
|
uint8_t eye_0; uint8_t eye_1;
|
||||||
|
uint8_t pupil;
|
||||||
|
uint8_t adornment;
|
||||||
|
uint8_t shine;
|
||||||
|
} game_npc_palette;
|
||||||
|
|
||||||
|
game_npc_palette game_npc_palettes[5] = {
|
||||||
|
{14, 13, 12, 6, 7, 6, 7, 0, 8, 7},
|
||||||
|
{4, 2, 3, 6, 7, 9, 10, 0, 4, 7},
|
||||||
|
{13, 0, 1, 1, 13, 11, 10, 0, 13, 7},
|
||||||
|
{7, 14, 7, 14, 7, 14, 7, 0, 14, 7},
|
||||||
|
{12, 1, 13, 12, 6, 8, 7, 0, 8, 7}
|
||||||
|
};
|
||||||
|
|
||||||
|
#define GAME_NPC_N_PALETTES (sizeof(game_npc_palettes)/sizeof(game_npc_palettes[0]))
|
||||||
|
|
||||||
|
sys_i32 game_npc_next = 0;
|
||||||
|
game_npc game_npcs[GAME_NPCS_N];
|
||||||
|
|
||||||
|
game_npc* game_npc_create(sys_i32 x, sys_i32 y) {
|
||||||
|
assert(game_npc_next < GAME_NPCS_N && "too many NPCs");
|
||||||
|
|
||||||
|
sys_i32 id = game_npc_next++;
|
||||||
|
game_npc npc = {
|
||||||
|
.x = x,
|
||||||
|
.y = y,
|
||||||
|
|
||||||
|
.sprite_id = 0,
|
||||||
|
.palette_id = 0,
|
||||||
|
.inflict_id = 0,
|
||||||
|
.face_left = false,
|
||||||
|
.flip = false,
|
||||||
|
|
||||||
|
.n_dialogues = 0,
|
||||||
|
.no_cake_dialogue = NULL,
|
||||||
|
/* .dialogues = { doesn't matter }, */
|
||||||
|
|
||||||
|
.present = true,
|
||||||
|
.n_dialogues_seen = 0,
|
||||||
|
.received_cake = false,
|
||||||
|
};
|
||||||
|
game_npcs[id] = npc;
|
||||||
|
return &game_npcs[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_npc_trigger_dialogue(sys_i32 npc_id);
|
||||||
|
void game_npcs_update(bool* allow_input) {
|
||||||
|
if (!*allow_input) { return; }
|
||||||
|
|
||||||
|
// check collision with player
|
||||||
|
sys_i32 player_x, player_y;
|
||||||
|
|
||||||
|
game_player_get_center(&player_x, &player_y);
|
||||||
|
for (sys_i32 i = 0; i < game_npc_next; i++) {
|
||||||
|
game_npc* n = &game_npcs[i];
|
||||||
|
|
||||||
|
if (!n->present) { continue; }
|
||||||
|
|
||||||
|
sys_i32 r_distcheck = 21 * PIXEL_SZ_MICROPIXEL;
|
||||||
|
|
||||||
|
int64_t dx = player_x - n->x;
|
||||||
|
int64_t dy = player_y - n->y;
|
||||||
|
|
||||||
|
if (
|
||||||
|
sys_btnp(DEVICE_BUTTON_0, false) &&
|
||||||
|
dx * dx + dy * dy <= r_distcheck * r_distcheck
|
||||||
|
) {
|
||||||
|
n->face_left = dx < 0;
|
||||||
|
game_npc_trigger_dialogue(i);
|
||||||
|
*allow_input = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_npc_trigger_dialogue(sys_i32 npc_id) {
|
||||||
|
const char* dialogue;
|
||||||
|
|
||||||
|
// TODO: Make it possible for the player to have the cake
|
||||||
|
game_npc* npc = &game_npcs[npc_id];
|
||||||
|
|
||||||
|
if (
|
||||||
|
!npc->received_cake &&
|
||||||
|
game_player_collectibles.n_cake <= 0 &&
|
||||||
|
npc->no_cake_dialogue != NULL
|
||||||
|
) {
|
||||||
|
dialogue = npc->no_cake_dialogue;
|
||||||
|
} else if (npc->n_dialogues_seen < npc->n_dialogues) {
|
||||||
|
dialogue = npc->dialogues[npc->n_dialogues_seen++];
|
||||||
|
} else if (npc->n_dialogues > 0) {
|
||||||
|
dialogue = npc->dialogues[npc->n_dialogues - 1];
|
||||||
|
} else {
|
||||||
|
// nothing to do with this npc
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// give the NPC cake if possible
|
||||||
|
if (!npc->received_cake && game_player_collectibles.n_cake > 0) {
|
||||||
|
npc->received_cake = true;
|
||||||
|
game_player_collectibles.n_cake -= 1;
|
||||||
|
|
||||||
|
if (npc->inflict_id != 0) {
|
||||||
|
game_inflict_enqueue(npc->inflict_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
game_dialogue_display(dialogue, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_npcs_draw() {
|
||||||
|
for (sys_i32 i = 0; i < game_npc_next; i++) {
|
||||||
|
game_npc* n = &game_npcs[i];
|
||||||
|
|
||||||
|
if (!n->present) { continue; }
|
||||||
|
|
||||||
|
game_npc_palette palette =
|
||||||
|
game_npc_palettes[n->palette_id % GAME_NPC_N_PALETTES];
|
||||||
|
|
||||||
|
if (n->received_cake) {
|
||||||
|
sys_sprite_draw_ext(
|
||||||
|
spr_game_hud,
|
||||||
|
0,
|
||||||
|
n->x / PIXEL_SZ_MICROPIXEL - 4,
|
||||||
|
n->y / PIXEL_SZ_MICROPIXEL - 18,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
!n->face_left,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sys_dpal_set(14, palette.main);
|
||||||
|
sys_dpal_set(13, palette.alt0_0);
|
||||||
|
sys_dpal_set(12, palette.alt0_1);
|
||||||
|
sys_dpal_set(6, palette.alt1_0);
|
||||||
|
sys_dpal_set(7, palette.alt1_1);
|
||||||
|
sys_dpal_set(9, palette.eye_0);
|
||||||
|
sys_dpal_set(10, palette.eye_1);
|
||||||
|
sys_dpal_set(1, palette.pupil);
|
||||||
|
sys_dpal_set(8, palette.adornment);
|
||||||
|
sys_dpal_set(11, palette.shine);
|
||||||
|
|
||||||
|
// TODO: Palette
|
||||||
|
sys_sprite_draw_ext(
|
||||||
|
spr_game_npcs,
|
||||||
|
n->sprite_id,
|
||||||
|
n->x / PIXEL_SZ_MICROPIXEL - 8,
|
||||||
|
n->y / PIXEL_SZ_MICROPIXEL - 8,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
n->face_left,
|
||||||
|
n->flip
|
||||||
|
);
|
||||||
|
|
||||||
|
sys_dpal_reset();
|
||||||
|
}
|
||||||
|
}
|
34
game/game_npc.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#ifndef GAME_NPC_H
|
||||||
|
#define GAME_NPC_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
#define GAME_NPC_MAX_N_DIALOGUES 16
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
sys_i32 x;
|
||||||
|
sys_i32 y;
|
||||||
|
|
||||||
|
uint8_t sprite_id;
|
||||||
|
uint8_t palette_id;
|
||||||
|
uint8_t inflict_id;
|
||||||
|
bool face_left;
|
||||||
|
bool flip;
|
||||||
|
|
||||||
|
uint8_t n_dialogues;
|
||||||
|
const char* no_cake_dialogue;
|
||||||
|
const char* dialogues[GAME_NPC_MAX_N_DIALOGUES];
|
||||||
|
|
||||||
|
bool present;
|
||||||
|
uint8_t n_dialogues_seen;
|
||||||
|
bool received_cake;
|
||||||
|
} game_npc;
|
||||||
|
|
||||||
|
// pointer goes to data section and will be valid forever
|
||||||
|
game_npc* game_npc_create(sys_i32 x, sys_i32 y);
|
||||||
|
|
||||||
|
void game_npcs_update(bool* allow_input);
|
||||||
|
void game_npcs_draw();
|
||||||
|
|
||||||
|
#endif // GAME_NPC_H
|
21
game/game_palette.c
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#include "game_palette.h"
|
||||||
|
|
||||||
|
void game_palette_init() {
|
||||||
|
// Pico 8 palette
|
||||||
|
sys_spal_set(0x00, 0x000000ff);
|
||||||
|
sys_spal_set(0x01, 0x1d2b53ff);
|
||||||
|
sys_spal_set(0x02, 0x7e2553ff);
|
||||||
|
sys_spal_set(0x03, 0x008751ff);
|
||||||
|
sys_spal_set(0x04, 0xab5236ff);
|
||||||
|
sys_spal_set(0x05, 0x5f574fff);
|
||||||
|
sys_spal_set(0x06, 0xc2c3c7ff);
|
||||||
|
sys_spal_set(0x07, 0xfff1e8ff);
|
||||||
|
sys_spal_set(0x08, 0xff004dff);
|
||||||
|
sys_spal_set(0x09, 0xffa300ff);
|
||||||
|
sys_spal_set(0x0a, 0xffec27ff);
|
||||||
|
sys_spal_set(0x0b, 0x00e436ff);
|
||||||
|
sys_spal_set(0x0c, 0x29adffff);
|
||||||
|
sys_spal_set(0x0d, 0x83769cff);
|
||||||
|
sys_spal_set(0x0e, 0xff77a8ff);
|
||||||
|
sys_spal_set(0x0f, 0xffccaaff);
|
||||||
|
}
|
6
game/game_palette.h
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#ifndef GAME_PALETTE_H
|
||||||
|
#define GAME_PALETTE_H
|
||||||
|
|
||||||
|
void game_palette_init();
|
||||||
|
|
||||||
|
#endif // GAME_PALETTE_H
|
289
game/game_player.c
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include "art/game_hud.h"
|
||||||
|
#include "art/game_player.h"
|
||||||
|
#include "art/game_tiles.h"
|
||||||
|
#include "device/device.h"
|
||||||
|
#include "map/game_map.h"
|
||||||
|
#include "game.h"
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
game_bbox game_player_bbox = {
|
||||||
|
.x_origin=0x0500,
|
||||||
|
.y_origin=0x0c00,
|
||||||
|
.x=0x9000,
|
||||||
|
.y=0x1000,
|
||||||
|
.w=0x0a00,
|
||||||
|
.h=0x0c00,
|
||||||
|
};
|
||||||
|
bool game_player_grounded = false;
|
||||||
|
sys_i32 game_player_jump_frames = 0;
|
||||||
|
sys_i32 game_player_coyote_frames = 0;
|
||||||
|
sys_i32 game_player_dx = 0;
|
||||||
|
sys_i32 game_player_dy = 0;
|
||||||
|
bool game_player_faceleft = false;
|
||||||
|
|
||||||
|
game_player_collectibles_t game_player_collectibles = {
|
||||||
|
.n_cake=0,
|
||||||
|
.n_dollars=0
|
||||||
|
};
|
||||||
|
|
||||||
|
game_player_phase_t game_player_phase = GAME_PLAYER_PHASE_CROC;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GAME_PLAYER_ANIM_STANDING,
|
||||||
|
GAME_PLAYER_ANIM_WALKING,
|
||||||
|
GAME_PLAYER_ANIM_FLOATING,
|
||||||
|
} game_player_anim;
|
||||||
|
|
||||||
|
game_player_anim game_player_anim_state = GAME_PLAYER_ANIM_STANDING;
|
||||||
|
uint8_t game_player_anim_progress = 0;
|
||||||
|
|
||||||
|
void game_player_anim_transition(game_player_anim anim);
|
||||||
|
void game_player_anim_add_progress(uint8_t amt);
|
||||||
|
|
||||||
|
void game_player_init() {
|
||||||
|
game_player_bbox.x = game_map_player_start_x;
|
||||||
|
game_player_bbox.y = game_map_player_start_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_player_update(bool* allow_input) {
|
||||||
|
sys_i32 dx_max, ddx;
|
||||||
|
switch (game_player_phase) {
|
||||||
|
case GAME_PLAYER_PHASE_CROC:
|
||||||
|
dx_max = 0x140;
|
||||||
|
ddx = 0x50;
|
||||||
|
game_player_bbox.x_origin=0x500;
|
||||||
|
game_player_bbox.w = 0xa00;
|
||||||
|
game_player_bbox.y_origin = game_player_bbox.h = 0xa00; break;
|
||||||
|
break;
|
||||||
|
case GAME_PLAYER_PHASE_CHICKEN:
|
||||||
|
dx_max = 0xd0;
|
||||||
|
ddx = 0x30;
|
||||||
|
game_player_bbox.x_origin=0x300;
|
||||||
|
game_player_bbox.w = 0x600;
|
||||||
|
game_player_bbox.y_origin = game_player_bbox.h = 0x700; break;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
game_collision collision = game_collision_move_to_contact(
|
||||||
|
&game_player_bbox,
|
||||||
|
game_player_dx,
|
||||||
|
game_player_dy
|
||||||
|
);
|
||||||
|
|
||||||
|
if (collision.collided_x) { game_player_dx = 0; }
|
||||||
|
if (collision.collided_y) { game_player_dy = 0; }
|
||||||
|
|
||||||
|
game_player_grounded = !game_collision_can_move(
|
||||||
|
game_player_bbox, 0, 1
|
||||||
|
);
|
||||||
|
|
||||||
|
{
|
||||||
|
sys_i32 fric_numerator = game_player_grounded ? 0x70 : 0x7f;
|
||||||
|
game_player_dx = (
|
||||||
|
game_player_dx * fric_numerator +
|
||||||
|
// round up
|
||||||
|
0x7f * sys_sgn_i32(game_player_dx)
|
||||||
|
) / 0x80;
|
||||||
|
if (sys_abs_i32(game_player_dx) > dx_max) {
|
||||||
|
game_player_dx = sys_sgn_i32(game_player_dx) * dx_max;
|
||||||
|
}
|
||||||
|
|
||||||
|
int wanted_dx = game_player_dx;
|
||||||
|
if (*allow_input && sys_btn(DEVICE_BUTTON_L)) { wanted_dx -= ddx; }
|
||||||
|
if (*allow_input && sys_btn(DEVICE_BUTTON_R)) { wanted_dx += ddx; }
|
||||||
|
|
||||||
|
// allow this if grounded or if it's not a change of direction
|
||||||
|
if (
|
||||||
|
/*
|
||||||
|
game_player_grounded ||
|
||||||
|
(sys_sgn_i32(wanted_dx) == sys_sgn_i32(game_player_dx))
|
||||||
|
*/
|
||||||
|
true
|
||||||
|
) {
|
||||||
|
game_player_dx = wanted_dx;
|
||||||
|
} else {
|
||||||
|
// the smallest possible amount of movement without changing sign
|
||||||
|
game_player_dx = sys_sgn_i32(game_player_dx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game_player_grounded || game_player_coyote_frames > 0) {
|
||||||
|
if (*allow_input && sys_btnp(DEVICE_BUTTON_U, false)) {
|
||||||
|
game_player_dy = -0x200;
|
||||||
|
game_player_jump_frames = 13;
|
||||||
|
game_player_grounded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game_player_dx < 0) { game_player_faceleft = true; }
|
||||||
|
if (game_player_dx > 0) { game_player_faceleft = false; }
|
||||||
|
|
||||||
|
bool wants_to_go_higher = false;
|
||||||
|
if (game_player_grounded) {
|
||||||
|
game_player_coyote_frames = 5;
|
||||||
|
if (game_player_dy > 0) { game_player_dy = 0; }
|
||||||
|
if (sys_abs_i32(game_player_dx) < 0x20) { game_player_dx = 0; }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
game_player_coyote_frames =
|
||||||
|
sys_max_i32(0, game_player_coyote_frames - 1);
|
||||||
|
wants_to_go_higher = sys_btn(DEVICE_BUTTON_U);
|
||||||
|
bool is_going_higher = wants_to_go_higher && game_player_jump_frames > 0;
|
||||||
|
if (game_player_dy >= 0 || !is_going_higher) {
|
||||||
|
game_player_dy += 48;
|
||||||
|
} else {
|
||||||
|
game_player_jump_frames -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deduce animation state
|
||||||
|
if (game_player_grounded) {
|
||||||
|
if (game_player_dx == 0) {
|
||||||
|
game_player_anim_transition(GAME_PLAYER_ANIM_STANDING);
|
||||||
|
} else {
|
||||||
|
game_player_anim_transition(GAME_PLAYER_ANIM_WALKING);
|
||||||
|
game_player_anim_add_progress(
|
||||||
|
sys_min_i32(sys_abs_i32(game_player_dx) / 0x10, 0x20)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (wants_to_go_higher) {
|
||||||
|
game_player_anim_transition(GAME_PLAYER_ANIM_FLOATING);
|
||||||
|
} else {
|
||||||
|
game_player_anim_transition(GAME_PLAYER_ANIM_STANDING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void game_player_get_center(sys_i32* x, sys_i32* y) {
|
||||||
|
*x = game_player_bbox.x;
|
||||||
|
|
||||||
|
// center on me, not on feet
|
||||||
|
*y = game_player_bbox.y - 8 * PIXEL_SZ_MICROPIXEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_player_set_camera() {
|
||||||
|
sys_i32 x = game_player_bbox.x;
|
||||||
|
sys_i32 y = game_player_bbox.y;
|
||||||
|
|
||||||
|
// TODO: Use round_down et al
|
||||||
|
sys_i32 x_pixel = x / PIXEL_SZ_MICROPIXEL;
|
||||||
|
sys_i32 y_pixel = y / PIXEL_SZ_MICROPIXEL;
|
||||||
|
|
||||||
|
sys_i32 x_topcorner = x_pixel - x_pixel % DEVICE_W;
|
||||||
|
sys_i32 y_topcorner = y_pixel - y_pixel % DEVICE_H;
|
||||||
|
|
||||||
|
sys_camera_set(x_topcorner, y_topcorner);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_player_draw() {
|
||||||
|
uint8_t game_player_image;
|
||||||
|
|
||||||
|
uint8_t base = 0;
|
||||||
|
switch (game_player_phase) {
|
||||||
|
default: case GAME_PLAYER_PHASE_CROC: base = 0; break;
|
||||||
|
case GAME_PLAYER_PHASE_CHICKEN: base = 32; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (game_player_anim_state) {
|
||||||
|
case GAME_PLAYER_ANIM_WALKING:
|
||||||
|
game_player_image = base + 2 + (game_player_anim_progress / 0x80) * 2;
|
||||||
|
break;
|
||||||
|
case GAME_PLAYER_ANIM_FLOATING:
|
||||||
|
game_player_image = base + 6;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case GAME_PLAYER_ANIM_STANDING:
|
||||||
|
game_player_image = base + 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sys_sprite_draw_ext(
|
||||||
|
spr_game_player,
|
||||||
|
game_player_image,
|
||||||
|
game_player_bbox.x / 0x100 - 8,
|
||||||
|
game_player_bbox.y / 0x100 - 16,
|
||||||
|
2, 2,
|
||||||
|
game_player_faceleft, false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_player_anim_transition(game_player_anim anim) {
|
||||||
|
if (game_player_anim_state == anim) { return ;}
|
||||||
|
game_player_anim_state = anim;
|
||||||
|
game_player_anim_progress = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_player_anim_add_progress(uint8_t amt) {
|
||||||
|
game_player_anim_progress = (
|
||||||
|
(uint32_t) game_player_anim_progress +
|
||||||
|
(uint32_t) amt
|
||||||
|
) & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void game_player_draw_hud_number(sys_i32 number, sys_i32 x, sys_i32 y);
|
||||||
|
void game_player_draw_hud() {
|
||||||
|
sys_i32 y = 4;
|
||||||
|
if (game_player_collectibles.n_dollars > 0) {
|
||||||
|
sys_sprite_draw_ext(
|
||||||
|
spr_game_hud,
|
||||||
|
1,
|
||||||
|
4, y,
|
||||||
|
1, 1,
|
||||||
|
false, false
|
||||||
|
);
|
||||||
|
game_player_draw_hud_number(game_player_collectibles.n_dollars, 16, y);
|
||||||
|
y += 10;
|
||||||
|
}
|
||||||
|
if (game_player_collectibles.n_cake > 0) {
|
||||||
|
sys_sprite_draw_ext(
|
||||||
|
spr_game_hud,
|
||||||
|
0,
|
||||||
|
4, y - 1,
|
||||||
|
1, 1,
|
||||||
|
false, false
|
||||||
|
);
|
||||||
|
game_player_draw_hud_number(game_player_collectibles.n_cake, 16, y);
|
||||||
|
y += 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_player_draw_hud_number1(sys_i32 number, sys_i32 x, sys_i32 y, uint8_t color) {
|
||||||
|
number = sys_min_i32(sys_max_i32(number, 0), 255);
|
||||||
|
|
||||||
|
char s[2];
|
||||||
|
s[1] = 0;
|
||||||
|
|
||||||
|
if (number >= 100) {
|
||||||
|
sys_i32 top_digit = number / 100;
|
||||||
|
assert(0 <= top_digit && top_digit < 10);
|
||||||
|
s[0] = '0' + top_digit;
|
||||||
|
number = number % 100;
|
||||||
|
sys_print(s, x, y, color);
|
||||||
|
x += 8;
|
||||||
|
}
|
||||||
|
if (number >= 10) {
|
||||||
|
sys_i32 top_digit = number / 10;
|
||||||
|
assert(0 <= top_digit && top_digit < 10);
|
||||||
|
s[0] = '0' + top_digit;
|
||||||
|
number = number % 10;
|
||||||
|
sys_print(s, x, y, color);
|
||||||
|
x += 8;
|
||||||
|
}
|
||||||
|
sys_i32 top_digit = number;
|
||||||
|
assert(0 <= top_digit && top_digit < 10);
|
||||||
|
s[0] = '0' + top_digit;
|
||||||
|
sys_print(s, x, y, color);
|
||||||
|
}
|
||||||
|
void game_player_draw_hud_number(sys_i32 number, sys_i32 x_start, sys_i32 y_start) {
|
||||||
|
for (sys_i32 dy = -1; dy <= 1; dy++) {
|
||||||
|
for (sys_i32 dx = -1; dx <= 1; dx++) {
|
||||||
|
game_player_draw_hud_number1(number, x_start+dx, y_start+dy, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
game_player_draw_hud_number1(number, x_start, y_start, 7);
|
||||||
|
}
|
24
game/game_player.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#ifndef GAME_PLAYER_H
|
||||||
|
#define GAME_PLAYER_H
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
sys_i32 n_cake;
|
||||||
|
sys_i32 n_dollars;
|
||||||
|
} game_player_collectibles_t;
|
||||||
|
extern game_player_collectibles_t game_player_collectibles;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GAME_PLAYER_PHASE_CROC = 0,
|
||||||
|
GAME_PLAYER_PHASE_CHICKEN = 1,
|
||||||
|
} game_player_phase_t;
|
||||||
|
extern game_player_phase_t game_player_phase;
|
||||||
|
|
||||||
|
void game_player_init();
|
||||||
|
void game_player_update(bool* allow_input);
|
||||||
|
void game_player_get_center(sys_i32* x, sys_i32* y);
|
||||||
|
|
||||||
|
void game_player_set_camera();
|
||||||
|
void game_player_draw();
|
||||||
|
void game_player_draw_hud();
|
||||||
|
|
||||||
|
#endif // GAME_PLAYER_H
|
16
game/helpers.bzl
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
load("@bazel_skylib//rules:run_binary.bzl", "run_binary")
|
||||||
|
|
||||||
|
def add_sprites(name, n):
|
||||||
|
run_binary(
|
||||||
|
name = name,
|
||||||
|
args = [
|
||||||
|
name,
|
||||||
|
"{}".format(n), # n sprites
|
||||||
|
"0", # key color
|
||||||
|
"$(location :art/{}.png)".format(name),
|
||||||
|
"$(location :art/{}.c)".format(name)
|
||||||
|
],
|
||||||
|
srcs = [":art/{}.png".format(name)],
|
||||||
|
outs = [":art/{}.c".format(name)],
|
||||||
|
tool = "//pytools:spritesheet",
|
||||||
|
)
|
13
game/map/game_map.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#ifndef CROCPARTY_GAME_MAP_H
|
||||||
|
#define CROCPARTY_GAME_MAP_H
|
||||||
|
|
||||||
|
#include "game/game_collision.h"
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
extern sys_i32 game_map_player_start_x;
|
||||||
|
extern sys_i32 game_map_player_start_y;
|
||||||
|
extern sys_map map_game_map;
|
||||||
|
|
||||||
|
void map_game_map_create_entities();
|
||||||
|
#endif
|
||||||
|
|
7120
game/map/game_map.ldtk
Normal file
86
game/map/game_map_entities.c
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include "sys/sys.h"
|
||||||
|
#include "game/game.h"
|
||||||
|
#include "game_map.h"
|
||||||
|
|
||||||
|
sys_i32 game_map_player_start_x;
|
||||||
|
sys_i32 game_map_player_start_y;
|
||||||
|
|
||||||
|
// called by map_game_map_create_entities
|
||||||
|
void map_game_map_player_spawn_create(sys_i32 x, sys_i32 y) {
|
||||||
|
// center, because the entity has top-left positioning
|
||||||
|
game_map_player_start_x = x * TILE_SZ_MICROPIXEL + PIXEL_SZ_MICROPIXEL * 8;
|
||||||
|
game_map_player_start_y = y * TILE_SZ_MICROPIXEL + PIXEL_SZ_MICROPIXEL * 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_game_map_collectible_cake_create(sys_i32 x, sys_i32 y) {
|
||||||
|
game_collectible_create(
|
||||||
|
(x + 2) * TILE_SZ_MICROPIXEL,
|
||||||
|
(y + 2) * TILE_SZ_MICROPIXEL,
|
||||||
|
GAME_COLLECTIBLE_TYPE_CAKE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_game_map_collectible_money_big_create(sys_i32 x, sys_i32 y) {
|
||||||
|
game_collectible_create(
|
||||||
|
(x + 1) * TILE_SZ_MICROPIXEL,
|
||||||
|
(y + 1) * TILE_SZ_MICROPIXEL,
|
||||||
|
GAME_COLLECTIBLE_TYPE_MONEY_BIG
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_game_map_collectible_money_small_create(sys_i32 x, sys_i32 y) {
|
||||||
|
game_collectible_create(
|
||||||
|
(x + 1) * TILE_SZ_MICROPIXEL,
|
||||||
|
(y + 1) * TILE_SZ_MICROPIXEL,
|
||||||
|
GAME_COLLECTIBLE_TYPE_MONEY_SMALL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
game_npc* game_map_npc_in_progress = NULL;
|
||||||
|
|
||||||
|
void map_game_map_npc_create(sys_i32 x, sys_i32 y) {
|
||||||
|
// center NPC
|
||||||
|
game_map_npc_in_progress = game_npc_create(
|
||||||
|
(x + 1) * TILE_SZ_MICROPIXEL,
|
||||||
|
(y + 1) * TILE_SZ_MICROPIXEL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_game_map_npc_set_sprite_id(sys_i32 id) {
|
||||||
|
assert(game_map_npc_in_progress && "NPC has to be in progress");
|
||||||
|
game_map_npc_in_progress->sprite_id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_game_map_npc_set_palette_id(sys_i32 id) {
|
||||||
|
assert(game_map_npc_in_progress && "NPC has to be in progress");
|
||||||
|
game_map_npc_in_progress->palette_id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_game_map_npc_set_inflict_id(sys_i32 id) {
|
||||||
|
assert(game_map_npc_in_progress && "NPC has to be in progress");
|
||||||
|
game_map_npc_in_progress->inflict_id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_game_map_npc_set_face_left(bool face_left) {
|
||||||
|
assert(game_map_npc_in_progress && "NPC has to be in progress");
|
||||||
|
game_map_npc_in_progress->face_left = face_left;
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_game_map_npc_set_flip(bool flip) {
|
||||||
|
assert(game_map_npc_in_progress && "NPC has to be in progress");
|
||||||
|
game_map_npc_in_progress->flip = flip;
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_game_map_npc_set_no_cake_dialogue(const char* dialogue) {
|
||||||
|
assert(game_map_npc_in_progress && "NPC has to be in progress");
|
||||||
|
game_map_npc_in_progress->no_cake_dialogue = dialogue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_game_map_npc_add_dialogue(const char* dialogue) {
|
||||||
|
assert(game_map_npc_in_progress && "NPC has to be in progress");
|
||||||
|
assert(game_map_npc_in_progress->n_dialogues < GAME_NPC_MAX_N_DIALOGUES &&
|
||||||
|
"NPC can't have too many dialogues");
|
||||||
|
sys_i32 ix = game_map_npc_in_progress->n_dialogues++;
|
||||||
|
game_map_npc_in_progress->dialogues[ix] = dialogue;
|
||||||
|
}
|
1
pytools/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
venv/
|
31
pytools/BUILD
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
load("@pytools_deps//:requirements.bzl", "requirement")
|
||||||
|
|
||||||
|
py_binary(
|
||||||
|
name = "font",
|
||||||
|
srcs = ["font.py", "shared.py"],
|
||||||
|
deps = [
|
||||||
|
requirement("pillow"),
|
||||||
|
requirement("Jinja2"),
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
py_binary(
|
||||||
|
name = "spritesheet",
|
||||||
|
srcs = ["spritesheet.py", "shared.py"],
|
||||||
|
deps = [
|
||||||
|
requirement("pillow"),
|
||||||
|
requirement("Jinja2"),
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
py_binary(
|
||||||
|
name = "mapdata",
|
||||||
|
srcs = ["mapdata.py", "shared.py"],
|
||||||
|
deps = [
|
||||||
|
requirement("pillow"),
|
||||||
|
requirement("Jinja2"),
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
54
pytools/font.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
from typing import Tuple
|
||||||
|
from PIL import Image
|
||||||
|
import sys
|
||||||
|
import shared
|
||||||
|
|
||||||
|
TEMPLATE = """
|
||||||
|
// generated code! be nice!
|
||||||
|
#include "sys/sys.h"
|
||||||
|
sys_glyph font_{{ font_name }}[{{ n_glyphs }}] = { {{- glyphs|join(", ") -}} };
|
||||||
|
""".lstrip()
|
||||||
|
|
||||||
|
|
||||||
|
def main(font_name, n_glyphs, fname_png, fname_c):
|
||||||
|
glyphs = load_glyphs(fname_png)
|
||||||
|
assert(len(glyphs) == n_glyphs), f"must be exactly {n_glyphs} glyphs"
|
||||||
|
|
||||||
|
with open(fname_c, "wt") as output:
|
||||||
|
output.write(
|
||||||
|
shared.templates.from_string(TEMPLATE).render(
|
||||||
|
font_name=font_name,
|
||||||
|
n_glyphs=n_glyphs,
|
||||||
|
glyphs=glyphs,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def load_glyphs(fname_png: str):
|
||||||
|
width, height, data = shared.load_image(fname_png)
|
||||||
|
|
||||||
|
glyphs = []
|
||||||
|
for gy in range(0, height, 8):
|
||||||
|
for gx in range(0, width, 8):
|
||||||
|
glyph = 0
|
||||||
|
for py in range(0, 8):
|
||||||
|
for px in range(0, 8):
|
||||||
|
x = gx + px
|
||||||
|
y = gy + py
|
||||||
|
if pixel_to_monochrome(data[y * width + x]):
|
||||||
|
glyph |= 1 << (py * 8 + px)
|
||||||
|
glyphs.append(glyph)
|
||||||
|
|
||||||
|
return glyphs
|
||||||
|
|
||||||
|
|
||||||
|
def pixel_to_monochrome(rgba: Tuple[int, int, int, int]):
|
||||||
|
if rgba[3] < 128: return False
|
||||||
|
if (rgba[0] + rgba[1] + rgba[2])/3 < 128: return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
assert len(sys.argv) == 5, \
|
||||||
|
"there must be four args (font name, n glyphs, src png, out c)"
|
||||||
|
main(sys.argv[1], int(sys.argv[2]), sys.argv[3], sys.argv[4])
|
162
pytools/mapdata.py
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import shared
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
TEMPLATE = """
|
||||||
|
// generated code! be nice
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
sys_maptile map_{{map_name}}_data[{{width * height}}] = { {{ tiles|join(",") }} };
|
||||||
|
sys_map map_{{map_name}} = {
|
||||||
|
.tiles=map_{{map_name}}_data,
|
||||||
|
.width={{width}},
|
||||||
|
.height={{height}},
|
||||||
|
};
|
||||||
|
|
||||||
|
{% for entity_type in entity_types %}
|
||||||
|
void map_{{map_name}}_{{entity_type}}_create(sys_i32 x, sys_i32 y);
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% for entity_name, field_name, c_type, render_mode in entity_fields %}
|
||||||
|
{% if render_mode == "scalar" %}
|
||||||
|
void map_{{map_name}}_{{entity_name}}_set_{{field_name}}({{c_type}} value);
|
||||||
|
{% elif render_mode == "array" %}
|
||||||
|
void map_{{map_name}}_{{entity_name}}_add_{{field_name}}({{c_type}} value);
|
||||||
|
{% else %}
|
||||||
|
wtf; // {{ render_mode }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
void map_{{map_name}}_create_entities() {
|
||||||
|
{% for entity in entities %}
|
||||||
|
map_{{map_name}}_{{entity.type}}_create({{entity.x}}, {{entity.y}});
|
||||||
|
|
||||||
|
{% for field in entity.fields %}
|
||||||
|
{% if field.renderMode == "scalar" %}
|
||||||
|
map_{{map_name}}_{{entity.type}}_set_{{field.name}}({{field.value|safe}});
|
||||||
|
{% elif field.renderMode == "array" %}
|
||||||
|
{% for s in field.value %}
|
||||||
|
map_{{map_name}}_{{entity.type}}_add_{{field.name}}({{s|safe}});
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
}
|
||||||
|
""".lstrip()
|
||||||
|
|
||||||
|
def main(map_name, fname_ldtk, fname_c):
|
||||||
|
width, height, tiles, entities, entity_types, entity_fields = load_mapdata(fname_ldtk)
|
||||||
|
print(entity_fields)
|
||||||
|
|
||||||
|
with open(fname_c, "wt") as output:
|
||||||
|
output.write(
|
||||||
|
shared.templates.from_string(TEMPLATE).render(
|
||||||
|
map_name=map_name,
|
||||||
|
tiles=tiles,
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
entities=entities,
|
||||||
|
entity_types=entity_types,
|
||||||
|
entity_fields=entity_fields,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def load_mapdata(fname_ldtk):
|
||||||
|
sparse_tiles = {}
|
||||||
|
entities = []
|
||||||
|
entity_types = set()
|
||||||
|
entity_fields = set()
|
||||||
|
|
||||||
|
with open(fname_ldtk, "rt") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
for level in data["levels"]:
|
||||||
|
level_x = level["worldX"] // 8
|
||||||
|
level_y = level["worldY"] // 8
|
||||||
|
for layer in level["layerInstances"]:
|
||||||
|
if layer["__identifier"] == "conceptual":
|
||||||
|
# for right now I use the vague layer to assign tiles
|
||||||
|
for tile in layer["autoLayerTiles"]:
|
||||||
|
x, y = tile["px"]
|
||||||
|
x //= 8
|
||||||
|
y //= 8
|
||||||
|
ix = tile["t"]
|
||||||
|
sparse_tiles[level_x + x, level_y + y] = ix
|
||||||
|
|
||||||
|
if layer["__identifier"] == "entities":
|
||||||
|
for e in layer["entityInstances"]:
|
||||||
|
# TODO: Other fields?
|
||||||
|
entity = {
|
||||||
|
"type": e["__identifier"],
|
||||||
|
"x": e["__worldX"] // 8,
|
||||||
|
"y": e["__worldY"] // 8,
|
||||||
|
"fields": []
|
||||||
|
}
|
||||||
|
for f in e["fieldInstances"]:
|
||||||
|
field = {
|
||||||
|
"name": f["__identifier"],
|
||||||
|
}
|
||||||
|
field["value"], field["renderMode"], rendered_type = format_field_value(f["__type"], f["__value"])
|
||||||
|
entity["fields"].append(field)
|
||||||
|
entity_fields.add((
|
||||||
|
entity["type"],
|
||||||
|
field["name"],
|
||||||
|
rendered_type,
|
||||||
|
field["renderMode"]
|
||||||
|
))
|
||||||
|
|
||||||
|
entities.append(entity)
|
||||||
|
entity_types.add(entity["type"])
|
||||||
|
|
||||||
|
x_min = 0
|
||||||
|
y_min = 0
|
||||||
|
assert not any(x for (x, _) in sparse_tiles if x < x_min), "level can't be left of (0, 0)"
|
||||||
|
assert not any(y for (_, y) in sparse_tiles if y < y_min), "level can't be up from (0, 0)"
|
||||||
|
width = max(x for (x, _) in sparse_tiles) + 1
|
||||||
|
height = max(y for (_, y) in sparse_tiles) + 1
|
||||||
|
dense_tiles = []
|
||||||
|
for y in range(height):
|
||||||
|
for x in range(width):
|
||||||
|
k = (x, y)
|
||||||
|
if k in sparse_tiles:
|
||||||
|
dense_tiles.append(sparse_tiles[k])
|
||||||
|
else:
|
||||||
|
dense_tiles.append(255)
|
||||||
|
|
||||||
|
return width, height, dense_tiles, entities, entity_types, entity_fields
|
||||||
|
|
||||||
|
|
||||||
|
def format_field_value(ty, val):
|
||||||
|
if ty == "Bool":
|
||||||
|
return ("true" if val else "false"), "scalar", "bool"
|
||||||
|
elif ty == "Int":
|
||||||
|
return str(val), "scalar", "sys_i32"
|
||||||
|
elif ty == "String":
|
||||||
|
if val is None:
|
||||||
|
return "NULL", "scalar", "const char*"
|
||||||
|
return json.dumps(wrap(val)), "scalar", "const char*" # this is close enough to being right in C
|
||||||
|
elif ty == "Array<Int>":
|
||||||
|
return [format_field_value("Int", i)[0] for i in val], "array", "sys_i32"
|
||||||
|
elif ty == "Array<String>":
|
||||||
|
return [format_field_value("String", i)[0] for i in val], "array", "const char*"
|
||||||
|
else:
|
||||||
|
assert False, f"unknown type: {ty}"
|
||||||
|
|
||||||
|
|
||||||
|
def wrap(s):
|
||||||
|
return textwrap.fill(s, width=28)
|
||||||
|
|
||||||
|
def annot_xy(lst, w, h):
|
||||||
|
assert len(lst) == w * h
|
||||||
|
for y in range(h):
|
||||||
|
for x in range(w):
|
||||||
|
yield x, y, lst[y * w + x]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
assert len(sys.argv) == 4, \
|
||||||
|
"there must be three args (map name, src ldtk, out c)"
|
||||||
|
main(sys.argv[1], sys.argv[2], sys.argv[3])
|
4
pytools/requirements.lock
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
pillow==10.2.0
|
||||||
|
Jinja2==3.1.3
|
||||||
|
## The following requirements were added by pip freeze:
|
||||||
|
MarkupSafe==2.1.5
|
2
pytools/requirements.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pillow==10.2.0
|
||||||
|
Jinja2==3.1.3
|
17
pytools/shared.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from PIL import Image
|
||||||
|
from jinja2 import Environment, BaseLoader, select_autoescape
|
||||||
|
|
||||||
|
templates = Environment(
|
||||||
|
loader=BaseLoader(),
|
||||||
|
autoescape=select_autoescape(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def load_image(fname):
|
||||||
|
with Image.open(fname) as im:
|
||||||
|
width = im.width
|
||||||
|
height = im.height
|
||||||
|
|
||||||
|
assert width % 8 == 0, "width must be a multiple of 8"
|
||||||
|
assert height % 8 == 0, "height must be a multiple of 8"
|
||||||
|
|
||||||
|
return width, height, list(im.convert("RGBA").getdata())
|
102
pytools/spritesheet.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
from typing import Tuple
|
||||||
|
from PIL import Image
|
||||||
|
import sys
|
||||||
|
import shared
|
||||||
|
|
||||||
|
TEMPLATE = """
|
||||||
|
// generated code! be nice!
|
||||||
|
#include "sys/sys.h"
|
||||||
|
sys_sprite spr_{{spritesheet_name}}_data[{{n_sprites}}] = {
|
||||||
|
{% for sprite in sprites -%}
|
||||||
|
{ .pixels={
|
||||||
|
{% for row in sprite -%}
|
||||||
|
{ {{ row|join(",") }} }{% if not loop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
} }{% if not loop.last %},{% endif %}
|
||||||
|
{%- endfor %}
|
||||||
|
};
|
||||||
|
sys_spritesheet spr_{{spritesheet_name}} = {
|
||||||
|
.sprites=spr_{{spritesheet_name}}_data,
|
||||||
|
.width={{width}},
|
||||||
|
.height={{height}},
|
||||||
|
};
|
||||||
|
""".lstrip()
|
||||||
|
|
||||||
|
|
||||||
|
def main(spritesheet_name, n_sprites, key_color, fname_png, fname_c):
|
||||||
|
sprites, width, height = load_sprites(fname_png, key_color)
|
||||||
|
assert(len(sprites) == n_sprites), f"must be exactly {n_sprites} sprites, not {len(sprites)}"
|
||||||
|
|
||||||
|
with open(fname_c, "wt") as output:
|
||||||
|
output.write(
|
||||||
|
shared.templates.from_string(TEMPLATE).render(
|
||||||
|
spritesheet_name=spritesheet_name,
|
||||||
|
n_sprites=n_sprites,
|
||||||
|
sprites=sprites,
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def load_sprites(fname_png: str, key_color: int):
|
||||||
|
width, height, data = shared.load_image(fname_png)
|
||||||
|
|
||||||
|
sprites = []
|
||||||
|
for gy in range(0, height, 8):
|
||||||
|
for gx in range(0, width, 8):
|
||||||
|
pixels = []
|
||||||
|
for py in range(0, 8):
|
||||||
|
row = []
|
||||||
|
for px in range(0, 8):
|
||||||
|
x = gx + px
|
||||||
|
y = gy + py
|
||||||
|
pal = pixel_to_palette(data[y * width + x], key_color, x, y)
|
||||||
|
row.append(pal)
|
||||||
|
pixels.append(row)
|
||||||
|
sprites.append(pixels)
|
||||||
|
|
||||||
|
return sprites, width//8, height//8
|
||||||
|
|
||||||
|
|
||||||
|
palette_colors = {
|
||||||
|
(0, 0, 0): 0,
|
||||||
|
(29, 43, 83): 1,
|
||||||
|
(126, 37, 83): 2,
|
||||||
|
(0, 135, 81): 3,
|
||||||
|
(171, 82, 54): 4,
|
||||||
|
(95, 87, 79): 5,
|
||||||
|
(194, 195, 199): 6,
|
||||||
|
(255, 241, 232): 7,
|
||||||
|
(255, 0, 77): 8,
|
||||||
|
(255, 163, 0): 9,
|
||||||
|
(255, 236, 39): 10,
|
||||||
|
(0, 228, 54): 11,
|
||||||
|
(41, 173, 255): 12,
|
||||||
|
(131, 118, 156): 13,
|
||||||
|
(255, 119, 168): 14,
|
||||||
|
(255, 204, 170): 15,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def pixel_to_palette(rgba, key_color, x, y):
|
||||||
|
(r, g, b, a) = rgba
|
||||||
|
|
||||||
|
if a < 128:
|
||||||
|
return 255
|
||||||
|
|
||||||
|
rgb = (r, g, b)
|
||||||
|
ix = palette_colors.get(rgb)
|
||||||
|
if ix is None:
|
||||||
|
raise ValueError(f"unrecognized color at ({x}, {y}): {rgb}")
|
||||||
|
|
||||||
|
if ix == key_color:
|
||||||
|
return 255
|
||||||
|
|
||||||
|
return ix
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
assert len(sys.argv) == 6, \
|
||||||
|
"there must be five args (spritesheet name, n sprites, key color, src png, out c)"
|
||||||
|
main(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), sys.argv[4], sys.argv[5])
|
@ -1,5 +1,5 @@
|
|||||||
cc_binary(
|
cc_binary(
|
||||||
name = "sdl_host",
|
name = "sdl_host",
|
||||||
srcs = glob(["*.c", "*.h"]),
|
srcs = glob(["*.c", "*.h"]),
|
||||||
deps = ["//game:game", "//sdl:sdl"],
|
deps = ["//device:device", "//game:game", "//sdl:sdl"],
|
||||||
)
|
)
|
@ -4,11 +4,12 @@
|
|||||||
// don't use sdl's redefinition of main
|
// don't use sdl's redefinition of main
|
||||||
#define SDL_MAIN_HANDLED
|
#define SDL_MAIN_HANDLED
|
||||||
#include "sdl/include/SDL.h"
|
#include "sdl/include/SDL.h"
|
||||||
|
#include "device/device.h"
|
||||||
#include "game/game.h"
|
#include "game/game.h"
|
||||||
#include "game/device.h"
|
|
||||||
|
|
||||||
void sdl_host_suggest_dimensions(uint32_t* window_w, uint32_t* window_h);
|
void sdl_host_suggest_dimensions(uint32_t* window_w, uint32_t* window_h);
|
||||||
void sdl_host_loop();
|
void sdl_host_loop();
|
||||||
|
void sdl_host_handle_keyboard_event(SDL_KeyboardEvent event);
|
||||||
|
|
||||||
SDL_Window* sdl_host_window;
|
SDL_Window* sdl_host_window;
|
||||||
SDL_Renderer* sdl_host_renderer;
|
SDL_Renderer* sdl_host_renderer;
|
||||||
@ -27,7 +28,7 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
// create window
|
// create window
|
||||||
sdl_host_window = SDL_CreateWindow(
|
sdl_host_window = SDL_CreateWindow(
|
||||||
"Croc Party!",
|
game_title(),
|
||||||
SDL_WINDOWPOS_CENTERED,
|
SDL_WINDOWPOS_CENTERED,
|
||||||
SDL_WINDOWPOS_CENTERED,
|
SDL_WINDOWPOS_CENTERED,
|
||||||
(int) window_w, (int) window_h,
|
(int) window_w, (int) window_h,
|
||||||
@ -64,16 +65,16 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
sdl_host_loop();
|
sdl_host_loop();
|
||||||
|
|
||||||
// renderer_cleanup:
|
|
||||||
SDL_DestroyRenderer(sdl_host_renderer);
|
|
||||||
sdl_host_renderer = NULL;
|
|
||||||
renderer_done: ;
|
|
||||||
|
|
||||||
// target_cleanup:
|
// target_cleanup:
|
||||||
SDL_DestroyTexture(sdl_host_target);
|
SDL_DestroyTexture(sdl_host_target);
|
||||||
sdl_host_target = NULL;
|
sdl_host_target = NULL;
|
||||||
target_done: ;
|
target_done: ;
|
||||||
|
|
||||||
|
// renderer_cleanup:
|
||||||
|
SDL_DestroyRenderer(sdl_host_renderer);
|
||||||
|
sdl_host_renderer = NULL;
|
||||||
|
renderer_done: ;
|
||||||
|
|
||||||
// window_cleanup:
|
// window_cleanup:
|
||||||
SDL_DestroyWindow(sdl_host_window);
|
SDL_DestroyWindow(sdl_host_window);
|
||||||
sdl_host_window = NULL;
|
sdl_host_window = NULL;
|
||||||
@ -94,7 +95,7 @@ void sdl_host_suggest_dimensions(uint32_t* window_w, uint32_t* window_h) {
|
|||||||
dm.h = 0;
|
dm.h = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int scalar = 5; scalar >= 1; scalar--) {
|
for (int scalar = 4; scalar >= 1; scalar--) {
|
||||||
uint32_t w = DEVICE_W * scalar;
|
uint32_t w = DEVICE_W * scalar;
|
||||||
uint32_t h = DEVICE_H * scalar;
|
uint32_t h = DEVICE_H * scalar;
|
||||||
if (w <= dm.w && h <= dm.h) {
|
if (w <= dm.w && h <= dm.h) {
|
||||||
@ -120,6 +121,7 @@ void sdl_host_suggest_dimensions(uint32_t* window_w, uint32_t* window_h) {
|
|||||||
|
|
||||||
void sdl_host_loop() {
|
void sdl_host_loop() {
|
||||||
uint32_t ticks_per_frame = 1000/60;
|
uint32_t ticks_per_frame = 1000/60;
|
||||||
|
uint32_t real_pixels[DEVICE_H][DEVICE_W];
|
||||||
|
|
||||||
game_init();
|
game_init();
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -129,13 +131,22 @@ void sdl_host_loop() {
|
|||||||
SDL_Event e;
|
SDL_Event e;
|
||||||
while (SDL_PollEvent(&e)) {
|
while (SDL_PollEvent(&e)) {
|
||||||
if (e.type == SDL_QUIT) { goto quit; }
|
if (e.type == SDL_QUIT) { goto quit; }
|
||||||
|
if (e.type == SDL_KEYDOWN || e.type == SDL_KEYUP) {
|
||||||
|
sdl_host_handle_keyboard_event(e.key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// trigger game logic
|
// trigger game logic
|
||||||
game_update();
|
game_update();
|
||||||
game_draw();
|
game_draw();
|
||||||
|
|
||||||
SDL_UpdateTexture(sdl_host_target, NULL, &device_pixels, sizeof(device_pixels[0]));
|
for (int x = 0; x < DEVICE_W; x++) {
|
||||||
|
for (int y = 0; y < DEVICE_H; y++) {
|
||||||
|
real_pixels[y][x] = device_palette[device_pixels[y][x]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_UpdateTexture(sdl_host_target, NULL, &real_pixels, sizeof(real_pixels[0]));
|
||||||
SDL_RenderClear(sdl_host_renderer);
|
SDL_RenderClear(sdl_host_renderer);
|
||||||
SDL_RenderCopy(sdl_host_renderer, sdl_host_target, NULL, NULL);
|
SDL_RenderCopy(sdl_host_renderer, sdl_host_target, NULL, NULL);
|
||||||
SDL_RenderPresent(sdl_host_renderer);
|
SDL_RenderPresent(sdl_host_renderer);
|
||||||
@ -147,3 +158,32 @@ void sdl_host_loop() {
|
|||||||
}
|
}
|
||||||
quit: ;
|
quit: ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sdl_host_handle_keyboard_event(SDL_KeyboardEvent event) {
|
||||||
|
bool is_down;
|
||||||
|
switch (event.type) {
|
||||||
|
case SDL_KEYDOWN: is_down = true; break;
|
||||||
|
case SDL_KEYUP: is_down = false; break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.repeat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceButton db;
|
||||||
|
|
||||||
|
switch (event.keysym.sym) {
|
||||||
|
case SDLK_LEFT: db = DEVICE_BUTTON_L; break;
|
||||||
|
case SDLK_RIGHT: db = DEVICE_BUTTON_R; break;
|
||||||
|
case SDLK_UP: db = DEVICE_BUTTON_U; break;
|
||||||
|
case SDLK_DOWN: db = DEVICE_BUTTON_D; break;
|
||||||
|
case SDLK_z: db = DEVICE_BUTTON_0; break;
|
||||||
|
case SDLK_x: db = DEVICE_BUTTON_1; break;
|
||||||
|
case SDLK_c: db = DEVICE_BUTTON_2; break;
|
||||||
|
case SDLK_v: db = DEVICE_BUTTON_3; break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
|
||||||
|
device_buttons[db] = is_down;
|
||||||
|
}
|
22
sys/BUILD
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
load("@bazel_skylib//rules:run_binary.bzl", "run_binary")
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "sys",
|
||||||
|
srcs = glob(["*.c"]) + [":fonts/sys_font_small.c"],
|
||||||
|
hdrs = glob(["*.h"]) + [":fonts/sys_font_small.h"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["//device:device"],
|
||||||
|
)
|
||||||
|
|
||||||
|
run_binary(
|
||||||
|
name = "sys_font_small",
|
||||||
|
args = [
|
||||||
|
"sys_font_small",
|
||||||
|
"256",
|
||||||
|
"$(location :fonts/sys_font_small.png)",
|
||||||
|
"$(location :fonts/sys_font_small.c)"
|
||||||
|
],
|
||||||
|
srcs = [":fonts/sys_font_small.png"],
|
||||||
|
outs = [":fonts/sys_font_small.c"],
|
||||||
|
tool = "//pytools:font",
|
||||||
|
)
|
BIN
sys/fonts/sys_font_normal.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
8
sys/fonts/sys_font_small.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#ifndef CROCPARTY_SYS_FONT_SMALL_H
|
||||||
|
#define CROCPARTY_SYS_FONT_SMALL_H
|
||||||
|
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
extern sys_glyph font_sys_font_small[256];
|
||||||
|
|
||||||
|
#endif // CROCPARTY_SYS_FONT_SMALL_H
|
BIN
sys/fonts/sys_font_small.png
Normal file
After Width: | Height: | Size: 33 KiB |
29
sys/sys.c
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "sys.h"
|
||||||
|
|
||||||
|
bool sys_initialized = false;
|
||||||
|
|
||||||
|
void sys_init() {
|
||||||
|
assert(!sys_get_initialized());
|
||||||
|
sys_initialized = true;
|
||||||
|
|
||||||
|
// reset graphics
|
||||||
|
sys_clip_reset();
|
||||||
|
sys_camera_reset();
|
||||||
|
sys_spal_reset();
|
||||||
|
sys_dpal_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_update() {
|
||||||
|
sys_input_update();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sys_get_initialized() {
|
||||||
|
return sys_initialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_destroy() {
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
sys_initialized = false;
|
||||||
|
}
|
28
sys/sys.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#ifndef CROCPARTY_SYS_H
|
||||||
|
#define CROCPARTY_SYS_H
|
||||||
|
|
||||||
|
#include "sys_data.h"
|
||||||
|
#include "sys_graphics.h"
|
||||||
|
#include "fonts/sys_font_small.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize sys.
|
||||||
|
*/
|
||||||
|
void sys_init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update sys -- usually for input state.
|
||||||
|
*/
|
||||||
|
void sys_update();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether sys was initialized.
|
||||||
|
*/
|
||||||
|
bool sys_get_initialized();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy sys.
|
||||||
|
*/
|
||||||
|
void sys_destroy();
|
||||||
|
|
||||||
|
#endif // CROCPARTY_SYS_H
|
20
sys/sys_data.c
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#include "sys_data.h"
|
||||||
|
|
||||||
|
sys_i32 sys_max_i32(sys_i32 x, sys_i32 y) {
|
||||||
|
return x > y ? x : y;
|
||||||
|
}
|
||||||
|
|
||||||
|
sys_i32 sys_min_i32(sys_i32 x, sys_i32 y) {
|
||||||
|
return x < y ? x : y;
|
||||||
|
}
|
||||||
|
|
||||||
|
sys_i32 sys_abs_i32(sys_i32 x) {
|
||||||
|
if (x < 0) { return -x; }
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
sys_i32 sys_sgn_i32(sys_i32 x) {
|
||||||
|
if (x < 0) { return -1; }
|
||||||
|
if (x > 0) { return 1; }
|
||||||
|
return 0;
|
||||||
|
}
|
37
sys/sys_data.h
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#ifndef CROCPARTY_SYS_DATA_H
|
||||||
|
#define CROCPARTY_SYS_DATA_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define SYS_COLOR_TRANSPARENT 255
|
||||||
|
#define SYS_COLOR_N 256
|
||||||
|
#define SYS_SPRITE_H 8
|
||||||
|
#define SYS_SPRITE_W 8
|
||||||
|
|
||||||
|
typedef int32_t sys_i32;
|
||||||
|
typedef uint8_t sys_color;
|
||||||
|
typedef uint8_t sys_maptile;
|
||||||
|
typedef uint32_t sys_screen_color;
|
||||||
|
typedef uint64_t sys_glyph;
|
||||||
|
typedef struct {
|
||||||
|
sys_color pixels[SYS_SPRITE_H][SYS_SPRITE_W];
|
||||||
|
} sys_sprite;
|
||||||
|
typedef struct {
|
||||||
|
sys_sprite* sprites;
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
} sys_spritesheet;
|
||||||
|
typedef struct {
|
||||||
|
sys_maptile* tiles;
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
} sys_map;
|
||||||
|
|
||||||
|
sys_screen_color sys_make_screen_color(uint8_t r, uint8_t g, uint8_t b);
|
||||||
|
|
||||||
|
sys_i32 sys_max_i32(sys_i32 x, sys_i32 y);
|
||||||
|
sys_i32 sys_min_i32(sys_i32 x, sys_i32 y);
|
||||||
|
sys_i32 sys_abs_i32(sys_i32 x);
|
||||||
|
sys_i32 sys_sgn_i32(sys_i32 x);
|
||||||
|
|
||||||
|
#endif // CROCPARTY_SYS_DATA_H
|
447
sys/sys_graphics.c
Normal file
@ -0,0 +1,447 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include "sys.h"
|
||||||
|
#include "device/device.h"
|
||||||
|
|
||||||
|
sys_i32 sys_cam_dx, sys_cam_dy;
|
||||||
|
sys_i32 sys_clip_x0, sys_clip_y0, sys_clip_x1, sys_clip_y1;
|
||||||
|
sys_color sys_dpal[256];
|
||||||
|
|
||||||
|
// primitives (forward declare)
|
||||||
|
void sys_pixel_internal_set(sys_i32 x, sys_i32 y, sys_color c);
|
||||||
|
void sys_scanline_internal_set(
|
||||||
|
sys_i32 x0, sys_i32 x1, sys_i32 y, sys_color c, bool fill
|
||||||
|
);
|
||||||
|
void sys_glyph_internal_draw(sys_i32 x, sys_i32 y, sys_glyph g, sys_color c);
|
||||||
|
void sys_sprite_internal_draw(sys_i32 x, sys_i32 y, sys_sprite s, bool flip_x, bool flip_y);
|
||||||
|
|
||||||
|
// == public ==
|
||||||
|
void sys_clip_set(sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1) {
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
|
sys_clip_set_ext(x0, y0, x1, y1, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_clip_reset() {
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
|
sys_clip_set(0, 0, DEVICE_W, DEVICE_H);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_clip_set_ext(sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, bool clip_previous) {
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
sys_clip_x0 = sys_max_i32(x0, clip_previous ? sys_clip_x0 : 0);
|
||||||
|
sys_clip_y0 = sys_max_i32(y0, clip_previous ? sys_clip_y0 : 0);
|
||||||
|
sys_clip_x1 = sys_min_i32(x1, clip_previous ? sys_clip_x1 : DEVICE_W);
|
||||||
|
sys_clip_y1 = sys_min_i32(y1, clip_previous ? sys_clip_y1 : DEVICE_H);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_pixel_set(sys_i32 x, sys_i32 y, sys_color c) {
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
|
x += sys_cam_dx;
|
||||||
|
y += sys_cam_dy;
|
||||||
|
|
||||||
|
sys_pixel_internal_set(x, y, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
sys_color sys_pixel_get(sys_i32 x, sys_i32 y) {
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
|
x += sys_cam_dx;
|
||||||
|
y += sys_cam_dy;
|
||||||
|
|
||||||
|
if (x < 0 || y < 0 || x >= DEVICE_W || y >= DEVICE_H) {
|
||||||
|
return SYS_COLOR_TRANSPARENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return device_pixels[y][x];
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_measure_text(const char* str, sys_i32* w, sys_i32* h) {
|
||||||
|
sys_i32 x = 0;
|
||||||
|
sys_i32 y = 0;
|
||||||
|
|
||||||
|
sys_i32 max_x = 0;
|
||||||
|
|
||||||
|
for (sys_i32 i = 0;; i++) {
|
||||||
|
max_x = sys_max_i32(x, max_x);
|
||||||
|
uint8_t c = str[i];
|
||||||
|
if (c == 0) { break; }
|
||||||
|
if (c == '\n') { x = 0; y += 8; continue; }
|
||||||
|
if (c == '\r') { x = 0; continue; }
|
||||||
|
x += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
*w = max_x;
|
||||||
|
*h = y + 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_print(const char* str, sys_i32 x, sys_i32 y, sys_color col) {
|
||||||
|
x += sys_cam_dx;
|
||||||
|
y += sys_cam_dy;
|
||||||
|
|
||||||
|
sys_i32 x_orig = x;
|
||||||
|
for (sys_i32 i = 0;; i++) {
|
||||||
|
uint8_t c = str[i];
|
||||||
|
if (c == 0) { break; }
|
||||||
|
if (c == '\n') { x = x_orig; y += 8; continue; }
|
||||||
|
if (c == '\r') { x = x_orig; continue; }
|
||||||
|
sys_glyph_internal_draw(x, y, font_sys_font_small[c], col);
|
||||||
|
x += 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_cls(sys_color c) {
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
|
for (int x = 0; x < DEVICE_W; x++) {
|
||||||
|
for (int y = 0; y < DEVICE_W; y++) {
|
||||||
|
sys_pixel_internal_set(x, y, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_camera_set(sys_i32 x, sys_i32 y) {
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
|
sys_cam_dx = -x;
|
||||||
|
sys_cam_dy = -y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_camera_reset() {
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
|
sys_camera_set(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on bresenham implementations by alois zingl
|
||||||
|
// https://zingl.github.io/bresenham.html
|
||||||
|
void sys_circ_draw(sys_i32 x, sys_i32 y, sys_i32 r, sys_color c) {
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
|
sys_circ_draw_ext(x, y, r, c, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_circ_fill(sys_i32 x, sys_i32 y, sys_i32 r, sys_color c) {
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
|
sys_circ_draw_ext(x, y, r, c, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_circ_draw_ext(
|
||||||
|
sys_i32 x, sys_i32 y, sys_i32 r, sys_color c, bool fill
|
||||||
|
) {
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
|
if (r < 0) { return; }
|
||||||
|
|
||||||
|
sys_oval_draw_ext(x - r, y - r, x + r + 1, y + r + 1, c, fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_oval_draw(
|
||||||
|
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c
|
||||||
|
) {
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
|
sys_oval_draw_ext(x0, y0, x1, y1, c, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_oval_fill(
|
||||||
|
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c
|
||||||
|
) {
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
|
sys_oval_draw_ext(x0, y0, x1, y1, c, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_oval_draw_ext(
|
||||||
|
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c, bool fill
|
||||||
|
) {
|
||||||
|
x0 += sys_cam_dx;
|
||||||
|
y0 += sys_cam_dy;
|
||||||
|
x1 += sys_cam_dx;
|
||||||
|
y1 += sys_cam_dy;
|
||||||
|
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
|
if (x0 == x1 || y0 == y1) { return; }
|
||||||
|
if (x0 > x1) { sys_i32 tmp = x0; x0 = x1; x1 = tmp; }
|
||||||
|
if (y0 > y1) { sys_i32 tmp = y0; y0 = y1; y1 = tmp; }
|
||||||
|
// TODO: Offset by 1px so that x0/y0 is always included?
|
||||||
|
|
||||||
|
// alois' algorithm for this implies the bounds are inclusive
|
||||||
|
x1 -= 1; y1 -= 1;
|
||||||
|
|
||||||
|
int64_t a = x1 - x0;
|
||||||
|
int64_t b = y1 - y0;
|
||||||
|
int64_t b1 = b & 1;
|
||||||
|
|
||||||
|
int64_t dx = 4 * (1 - a) * b * b;
|
||||||
|
int64_t dy = 4 * (b1 + 1) * a * a;
|
||||||
|
int64_t err = dx + dy + b1 * a * a;
|
||||||
|
|
||||||
|
y0 += (b + 1) / 2;
|
||||||
|
y1 = y0 - b1;
|
||||||
|
int64_t aa8 = 8 * a * a;
|
||||||
|
int64_t bb8 = 8 * b * b;
|
||||||
|
|
||||||
|
do {
|
||||||
|
// draw the points at the edge of the line no matter what
|
||||||
|
sys_scanline_internal_set(x0, x1, y0, c, false);
|
||||||
|
sys_scanline_internal_set(x0, x1, y1, c, false);
|
||||||
|
int64_t e2 = 2 * err;
|
||||||
|
if (e2 <= dy) {
|
||||||
|
if (fill) {
|
||||||
|
// draw the whole line
|
||||||
|
sys_scanline_internal_set(x0, x1, y0, c, true);
|
||||||
|
sys_scanline_internal_set(x0, x1, y1, c, true);
|
||||||
|
}
|
||||||
|
y0++; y1--; dy += aa8; err += dy;
|
||||||
|
}
|
||||||
|
if (e2 >= dx || 2 * err > dy) {
|
||||||
|
x0++; x1--; dx += bb8; err += dx;
|
||||||
|
}
|
||||||
|
} while (x0 <= x1);
|
||||||
|
|
||||||
|
while (y0 - y1 < b) {
|
||||||
|
sys_scanline_internal_set(x0 - 1, x1 + 1, y0, c, fill); y0++;
|
||||||
|
sys_scanline_internal_set(x0 - 1, x1 + 1, y1, c, fill); y1--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_line_draw(
|
||||||
|
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c
|
||||||
|
) {
|
||||||
|
x0 += sys_cam_dx;
|
||||||
|
y0 += sys_cam_dy;
|
||||||
|
x1 += sys_cam_dx;
|
||||||
|
y1 += sys_cam_dy;
|
||||||
|
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
|
if (x0 == x1 || y0 == y1) { return; }
|
||||||
|
|
||||||
|
sys_i32 dx = sys_abs_i32(x1 - x0);
|
||||||
|
sys_i32 sx = sys_sgn_i32(x1 - x0);
|
||||||
|
sys_i32 dy = -sys_abs_i32(y1 - y0);
|
||||||
|
sys_i32 sy = sys_sgn_i32(y1 - y0);
|
||||||
|
sys_i32 err = dx + dy;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (x0 == x1 || y0 == y1) { return; }
|
||||||
|
sys_pixel_internal_set(x0, y0, c);
|
||||||
|
sys_i32 e2 = 2 * err;
|
||||||
|
if (e2 >= dy) { err += dy; x0 += sx; }
|
||||||
|
if (e2 <= dx) { err += dx; y0 += sy; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_rect_draw(
|
||||||
|
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c
|
||||||
|
) {
|
||||||
|
sys_rect_draw_ext(x0, y0, x1, y1, c, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_rect_fill(
|
||||||
|
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c
|
||||||
|
) {
|
||||||
|
sys_rect_draw_ext(x0, y0, x1, y1, c, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_rect_draw_ext(
|
||||||
|
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c, bool fill
|
||||||
|
) {
|
||||||
|
x0 += sys_cam_dx;
|
||||||
|
y0 += sys_cam_dy;
|
||||||
|
x1 += sys_cam_dx;
|
||||||
|
y1 += sys_cam_dy;
|
||||||
|
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
|
if (x0 == x1 || y0 == y1) { return; }
|
||||||
|
|
||||||
|
if (x0 == x1 || y0 == y1) { return; }
|
||||||
|
if (x0 > x1) { sys_i32 tmp = x0; x0 = x1; x1 = tmp; }
|
||||||
|
if (y0 > y1) { sys_i32 tmp = y0; y0 = y1; y1 = tmp; }
|
||||||
|
// TODO: Offset by 1px so that x0/y0 is always included?
|
||||||
|
|
||||||
|
for (sys_i32 y = y0; y < y1; y++) {
|
||||||
|
bool whole_line = y == y0 || y == y1;
|
||||||
|
sys_scanline_internal_set(
|
||||||
|
x0, x1, y, c, whole_line || fill
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_spal_set(sys_color c0, sys_screen_color rc1) {
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
|
device_palette[c0] = rc1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_spal_reset() {
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
|
for (int i = 0; i < SYS_COLOR_N; i++) {
|
||||||
|
sys_spal_set(i, 0x000000ff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_dpal_set(sys_color c0, sys_color c1) {
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
|
sys_dpal[c0] = c1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_dpal_reset() {
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
|
for (int i = 0; i < SYS_COLOR_N; i++) {
|
||||||
|
sys_dpal_set(i, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_sprite_draw(
|
||||||
|
sys_spritesheet spritesheet,
|
||||||
|
sys_i32 n,
|
||||||
|
sys_i32 x, sys_i32 y
|
||||||
|
) {
|
||||||
|
sys_sprite_draw_ext(
|
||||||
|
spritesheet,
|
||||||
|
n,
|
||||||
|
x, y,
|
||||||
|
1, 1,
|
||||||
|
false, false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_sprite_draw_ext(
|
||||||
|
sys_spritesheet spritesheet,
|
||||||
|
sys_i32 n,
|
||||||
|
sys_i32 x, sys_i32 y,
|
||||||
|
sys_i32 w, sys_i32 h,
|
||||||
|
bool flip_x, bool flip_y
|
||||||
|
) {
|
||||||
|
x += sys_cam_dx;
|
||||||
|
y += sys_cam_dy;
|
||||||
|
|
||||||
|
// map n to a specific entity on the spritesheet
|
||||||
|
// (this is necessary for w and h)
|
||||||
|
for (int sy = 0; sy < h; sy++) {
|
||||||
|
for (int sx = 0; sx < w; sx++) {
|
||||||
|
sys_i32 sx_adj = flip_x ? w - 1 - sx : sx;
|
||||||
|
sys_i32 sy_adj = flip_y ? h - 1 - sy : sy;
|
||||||
|
sys_i32 tile =
|
||||||
|
n + sx_adj + sy_adj * spritesheet.width;
|
||||||
|
if (tile < 0 || tile >= spritesheet.width * spritesheet.height) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
sys_sprite_internal_draw(
|
||||||
|
x + sx * 8,
|
||||||
|
y + sy * 8,
|
||||||
|
spritesheet.sprites[tile],
|
||||||
|
flip_x,
|
||||||
|
flip_y
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_map_draw(
|
||||||
|
sys_map map,
|
||||||
|
sys_spritesheet spritesheet,
|
||||||
|
sys_i32 sx, sys_i32 sy,
|
||||||
|
sys_i32 tile_x, sys_i32 tile_y,
|
||||||
|
sys_i32 tile_w, sys_i32 tile_h
|
||||||
|
) {
|
||||||
|
// no need to do this, sys_sprite_draw does it
|
||||||
|
// sx += sys_cam_dx;
|
||||||
|
// sy += sys_cam_dy;
|
||||||
|
|
||||||
|
for (sys_i32 ty = 0; ty < tile_h; ty++) {
|
||||||
|
for (sys_i32 tx = 0; tx < tile_w; tx++) {
|
||||||
|
sys_i32 real_tx = tx + tile_x;
|
||||||
|
sys_i32 real_ty = ty + tile_y;
|
||||||
|
if (real_tx < 0 || real_tx >= map.width) { continue; }
|
||||||
|
if (real_ty < 0 || real_ty >= map.height) { continue; }
|
||||||
|
sys_maptile tile = map.tiles[real_tx + map.width * real_ty];
|
||||||
|
sys_sprite_draw(spritesheet, tile, sx + tx * 8, sy + ty * 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sys_maptile sys_map_get(
|
||||||
|
sys_map map,
|
||||||
|
sys_i32 tile_x, sys_i32 tile_y,
|
||||||
|
bool* flag_invalid
|
||||||
|
) {
|
||||||
|
if (flag_invalid != NULL) { *flag_invalid = false; }
|
||||||
|
|
||||||
|
if (
|
||||||
|
tile_x < 0 || tile_y < 0 ||
|
||||||
|
tile_x >= map.width || tile_y >= map.height
|
||||||
|
) {
|
||||||
|
if (flag_invalid != NULL) { *flag_invalid = true; }
|
||||||
|
return 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
return map.tiles[tile_x + map.width * tile_y];
|
||||||
|
}
|
||||||
|
|
||||||
|
// == internal primitives ==
|
||||||
|
void sys_pixel_internal_set(sys_i32 x, sys_i32 y, sys_color c) {
|
||||||
|
sys_color realc = sys_dpal[c];
|
||||||
|
if (realc == SYS_COLOR_TRANSPARENT) { return; }
|
||||||
|
|
||||||
|
if (
|
||||||
|
x < sys_clip_x0 || y < sys_clip_y0 ||
|
||||||
|
x >= sys_clip_x1 || y >= sys_clip_y1
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(x >= 0);
|
||||||
|
assert(y >= 0);
|
||||||
|
assert(x < DEVICE_W);
|
||||||
|
assert(y < DEVICE_H);
|
||||||
|
|
||||||
|
device_pixels[y][x] = realc;
|
||||||
|
}
|
||||||
|
void sys_scanline_internal_set(
|
||||||
|
sys_i32 x0, sys_i32 x1, sys_i32 y, sys_color c,
|
||||||
|
bool fill
|
||||||
|
) {
|
||||||
|
if (fill) {
|
||||||
|
for (sys_i32 x = x0; x <= x1; x++) {
|
||||||
|
sys_pixel_internal_set(x, y, c);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sys_pixel_internal_set(x0, y, c);
|
||||||
|
sys_pixel_internal_set(x1, y, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_glyph_internal_draw(sys_i32 x, sys_i32 y, sys_glyph g, sys_color c) {
|
||||||
|
// iterate through the bits of the glyph, and draw the character
|
||||||
|
// if that bit is set
|
||||||
|
for (int py = 0; py < 8; py++) {
|
||||||
|
for (int px = 0; px < 8; px++) {
|
||||||
|
uint64_t mask = ((uint64_t) 1) << (uint64_t) (py * 8 + px);
|
||||||
|
if ((g & mask) != 0) {
|
||||||
|
sys_pixel_internal_set(x + px, y + py, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_sprite_internal_draw(
|
||||||
|
sys_i32 x, sys_i32 y, sys_sprite s, bool flip_x, bool flip_y
|
||||||
|
) {
|
||||||
|
for (int py = 0; py < 8; py++) {
|
||||||
|
for (int px = 0; px < 8; px++) {
|
||||||
|
sys_i32 px_adj = flip_x ? 7 - px : px;
|
||||||
|
sys_i32 py_adj = flip_y ? 7 - py : py;
|
||||||
|
sys_pixel_internal_set(x + px, y + py, s.pixels[py_adj][px_adj]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
200
sys/sys_graphics.h
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
// Corresponds roughly to section 6.2 of the Pico 8 manual
|
||||||
|
//
|
||||||
|
// However, all ranges are now half-open.
|
||||||
|
//
|
||||||
|
// https://www.lexaloffle.com/dl/docs/pico-8_manual.html
|
||||||
|
//
|
||||||
|
#ifndef CROCPARTY_SYS_GRAPHICS_H
|
||||||
|
#define CROCPARTY_SYS_GRAPHICS_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "sys_data.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the clipping rectangle in pixels.
|
||||||
|
*
|
||||||
|
* All drawing operations will be clipped to this rectangle.
|
||||||
|
*/
|
||||||
|
void sys_clip_set(sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the clipping rectangle.
|
||||||
|
*/
|
||||||
|
void sys_clip_reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the clipping rectangle in pixels.
|
||||||
|
*
|
||||||
|
* This is the same as `sys_clip_set`, but you can pass clip_previous in order
|
||||||
|
* to clip to the previous region.
|
||||||
|
*/
|
||||||
|
void sys_clip_set_ext(
|
||||||
|
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1,
|
||||||
|
bool clip_previous
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the color of the pixel `(x, y)`.
|
||||||
|
*/
|
||||||
|
void sys_pixel_set(
|
||||||
|
sys_i32 x, sys_i32 y,
|
||||||
|
sys_color c
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the color of the pixel `(x, y)`.
|
||||||
|
*
|
||||||
|
* Returns SYS_COLOR_TRANSPARENT if the pixel is out of bounds.
|
||||||
|
*/
|
||||||
|
sys_color sys_pixel_get(
|
||||||
|
sys_i32 x, sys_i32 y
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: SSET/SGET
|
||||||
|
// TODO: FGET/FSET
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output the dimensions of the text (x and y)
|
||||||
|
*/
|
||||||
|
void sys_measure_text(const char* str, sys_i32* x, sys_i32* y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a string `str` in the color `col`
|
||||||
|
*/
|
||||||
|
void sys_print(const char* str, sys_i32 x, sys_i32 y, sys_color col);
|
||||||
|
// TODO: CURSOR? COLOR?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill the entire screen with color `c`.
|
||||||
|
*/
|
||||||
|
void sys_cls(sys_color c);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a screen offset of `(-x, -y)` for all drawing operations.
|
||||||
|
*/
|
||||||
|
void sys_camera_set(sys_i32 x, sys_i32 y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the camera's offset to `(0, 0)`.
|
||||||
|
*/
|
||||||
|
void sys_camera_reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw or fill a circle at `(x, y)` with radius `r`.
|
||||||
|
*
|
||||||
|
* The circle is centered at the center of the pixel, not at the junction
|
||||||
|
* between pixels.
|
||||||
|
*
|
||||||
|
* If r is negative, the circle is not drawn.
|
||||||
|
*
|
||||||
|
* This is a special case of sys_oval_draw_ext.
|
||||||
|
*/
|
||||||
|
void sys_circ_draw(sys_i32 x, sys_i32 y, sys_i32 r, sys_color c);
|
||||||
|
void sys_circ_fill(sys_i32 x, sys_i32 y, sys_i32 r, sys_color c);
|
||||||
|
void sys_circ_draw_ext(sys_i32 x, sys_i32 y, sys_i32 r, sys_color c, bool fill);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw or fill an oval in the rectangle from `(x0, y0)` to `(x1, y1)`
|
||||||
|
*/
|
||||||
|
void sys_oval_draw(sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c);
|
||||||
|
void sys_oval_fill(sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c);
|
||||||
|
void sys_oval_draw_ext(
|
||||||
|
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c, bool fill
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a line from x0 to y0.
|
||||||
|
*
|
||||||
|
* The point `(x1, y1)` is not drawn. (because the line is a half-open interval)
|
||||||
|
*/
|
||||||
|
void sys_line_draw(sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw or fill a rectangle `(x, y, w, h)`
|
||||||
|
*/
|
||||||
|
void sys_rect_draw(sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c);
|
||||||
|
void sys_rect_fill(sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c);
|
||||||
|
void sys_rect_draw_ext(
|
||||||
|
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c, bool fill
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set screen palette.
|
||||||
|
*
|
||||||
|
* After calling this function, virtual color `c0` will be rendered as
|
||||||
|
* real color `rc1`.
|
||||||
|
*/
|
||||||
|
void sys_spal_set(sys_color c0, sys_screen_color rc1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset screen palette.
|
||||||
|
*
|
||||||
|
* After calling this function, all screen colors are (0, 0, 0).
|
||||||
|
*/
|
||||||
|
void sys_spal_reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set draw palette.
|
||||||
|
*
|
||||||
|
* After calling this function, draws using color `c0` will be performed
|
||||||
|
* using color `c1` instead.
|
||||||
|
*
|
||||||
|
* Any color mapped to `SYS_COLOR_TRANSPARENT` will not actually be drawn.
|
||||||
|
*/
|
||||||
|
void sys_dpal_set(sys_color c0, sys_color c1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset draw palette.
|
||||||
|
*
|
||||||
|
* All colors are mapped to themselves. SYS_COLOR_TRANSPARENT is transparent.
|
||||||
|
*/
|
||||||
|
void sys_dpal_reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a given sprite.
|
||||||
|
*
|
||||||
|
* w, h default to 1.
|
||||||
|
* flip_x and flip_y default to false.
|
||||||
|
*/
|
||||||
|
void sys_sprite_draw(
|
||||||
|
sys_spritesheet spritesheet,
|
||||||
|
sys_i32 n,
|
||||||
|
sys_i32 x, sys_i32 y
|
||||||
|
);
|
||||||
|
void sys_sprite_draw_ext(
|
||||||
|
sys_spritesheet spritesheet,
|
||||||
|
sys_i32 n,
|
||||||
|
sys_i32 x, sys_i32 y,
|
||||||
|
sys_i32 w, sys_i32 h,
|
||||||
|
bool flip_x, bool flip_y
|
||||||
|
);
|
||||||
|
// TODO: SSPR
|
||||||
|
// TODO: FILLP?
|
||||||
|
|
||||||
|
void sys_map_draw(
|
||||||
|
// NOTE: not the same order of args
|
||||||
|
// as on pico 8
|
||||||
|
// but: more consistent!
|
||||||
|
sys_map map,
|
||||||
|
sys_spritesheet spritesheet,
|
||||||
|
sys_i32 sx, sys_i32 sy,
|
||||||
|
sys_i32 tile_x, sys_i32 tile_y,
|
||||||
|
sys_i32 tile_w, sys_i32 tile_h
|
||||||
|
// TODO: Layers?
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single map tile, putting whether the coords were invalid into
|
||||||
|
* flag_invalid.
|
||||||
|
*
|
||||||
|
* (flag_invalid may be null)
|
||||||
|
*/
|
||||||
|
sys_maptile sys_map_get(
|
||||||
|
sys_map map,
|
||||||
|
sys_i32 tile_x, sys_i32 tile_y,
|
||||||
|
bool* flag_invalid
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif // CROCPARTY_SYS_GRAPHICS_H
|
37
sys/sys_input.c
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#include "device/device.h"
|
||||||
|
#include "sys/sys.h"
|
||||||
|
#include "sys_input.h"
|
||||||
|
|
||||||
|
sys_i32 sys_input_down_frames[DEVICE_BUTTON_N];
|
||||||
|
|
||||||
|
void sys_input_update() {
|
||||||
|
for (DeviceButton db = 0; db < DEVICE_BUTTON_N; db++) {
|
||||||
|
if (device_buttons[db]) {
|
||||||
|
sys_input_down_frames[db] += 1;
|
||||||
|
} else {
|
||||||
|
sys_input_down_frames[db] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_input_reset() {
|
||||||
|
for (DeviceButton db = 0; db < DEVICE_BUTTON_N; db++) {
|
||||||
|
sys_input_down_frames[db] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sys_btn(DeviceButton button) {
|
||||||
|
if (button >= DEVICE_BUTTON_N) { return false; }
|
||||||
|
return sys_input_down_frames[button] >= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sys_btnp(DeviceButton button, bool repeat) {
|
||||||
|
if (button >= DEVICE_BUTTON_N) { return false; }
|
||||||
|
if (sys_input_down_frames[button] == 1) { return true; }
|
||||||
|
if (!repeat) { return false; }
|
||||||
|
// 31 and every 8 frames after that
|
||||||
|
if (sys_input_down_frames[button] >= 31 && (sys_input_down_frames[button] - 31) % 8 == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
34
sys/sys_input.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#ifndef SYS_INPUT_H
|
||||||
|
#define SYS_INPUT_H
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the state of every button. Each button's btnp state depends
|
||||||
|
* on how long it's been down, and calling this function adds one frame.
|
||||||
|
*
|
||||||
|
* Usually this is called by sys_update(), at the start of frame.
|
||||||
|
*/
|
||||||
|
void sys_input_update();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the input state -- all buttons have been held for 0 frames.
|
||||||
|
*
|
||||||
|
* If the buttons are held next frame, this will lead to a btnp().
|
||||||
|
*
|
||||||
|
* There's rarely any reason for user code to call this.
|
||||||
|
*/
|
||||||
|
void sys_input_reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether a button is down.
|
||||||
|
*/
|
||||||
|
bool sys_btn(DeviceButton button);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether a button was just pressed.
|
||||||
|
*
|
||||||
|
* If `repeat`, then this repeats after 30 frames, returning true every 8
|
||||||
|
* frames after that.
|
||||||
|
*/
|
||||||
|
bool sys_btnp(DeviceButton button, bool repeat);
|
||||||
|
|
||||||
|
#endif // SYS_INPUT_H
|