diff options
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | README.md | 3 | ||||
| -rwxr-xr-x | build.sh | 6 | ||||
| -rw-r--r-- | examples/dialog.c | 260 |
4 files changed, 268 insertions, 3 deletions
@@ -22,7 +22,7 @@ PACKFLAGS ?= BIN = $(OBJDIR)/$(PROGRAM)$(EXE) PACKER = $(OBJDIR)/konpack$(EXE) MODELER = $(OBJDIR)/konmodel$(EXE) -EXAMPLES = modelview sound ui platform corridor hills sectorview +EXAMPLES = modelview sound ui platform dialog corridor hills sectorview EXAMPLEBINS = $(EXAMPLES:%=$(OBJDIR)/%$(EXE)) PACKFILE = $(OBJDIR)/data.kpk CORE = $(OBJDIR)/engine.o $(OBJDIR)/konwinlib.o $(OBJDIR)/konsofren.o \ @@ -32,6 +32,8 @@ The engine modules are a `.h` and a `.c` pair, they depend on each other a bit, | toml | kontoml | | tile | tilemaps and sprite sheets | | 2d | what any 2D game needs: a scrolling camera, frame animation, drawing frames of a sprite sheet | +| stage | sprites on a stage for visual novels and cutscenes: slots, layers above or below your text box, fades, moves, a background that stretches, covers or fits (adds 2d) | +| textbox | a text box for any game: wrapped pages, typewriter, name plate, a face inside, menus, a panel that can be a colour, an image or a nine patch (adds 2d, font) | | platformer | a box that runs and jumps through a tilemap, with coyote time and jump buffering (adds tile) | | rpg | top-down games on tilemaps: actors that walk a tile at a time, events on tiles (talk, touch, step), directions (adds tile) | | 3d | first person kit: mouse look body, walls from a tilemap, editable hill terrain (adds tile) | @@ -130,6 +132,7 @@ The examples are their own little programs, in the [examples](examples) folder. - [modelview.c](examples/modelview.c) shows a model and plays and blends its animations - [sound.c](examples/sound.c) is an audio and mic playground, everything is made in code so no files needed - [ui.c](examples/ui.c) is a character card maker made with the ui module +- [dialog.c](examples/dialog.c) is a small scene with sprites, a text box and a menu, built from the stage and textbox modules ```sh ./build.sh -d -m audio,mic,debug obj/sound @@ -12,7 +12,7 @@ developer=0 usage() { echo "usage: build.sh [-d] [-D] [-p platform] [-m module,...] [-o objdir] [target ...]" - echo "modules: font toml tile pack ui debug assets image audio mic model 2d platformer rpg 3d sector world" + echo "modules: font toml tile pack ui debug assets image audio mic model 2d stage textbox platformer rpg 3d sector world" echo "platforms: native linux bsd windows" echo "targets: all run clean tools pack examples" exit 1 @@ -77,7 +77,7 @@ addobj() for m in $modules; do DEFS="$DEFS -DKON_MODULE_$(echo "$m" | tr 'a-z' 'A-Z')" case $m in - font|toml|tile|pack|ui|debug|2d|platformer|rpg|3d|sector|world) addobj "kon$m" ;; + font|toml|tile|pack|ui|debug|2d|stage|textbox|platformer|rpg|3d|sector|world) addobj "kon$m" ;; assets) addobj konassets ;; model) addobj konmodel; addobj konassets ;; image) addobj stb_image ;; @@ -107,6 +107,8 @@ case " $modules " in *" debug "*) need font; need assets ;; esac case " $modules " in *" ui "*) need font ;; esac case " $modules " in *" world "*) need 3d; need sector; need tile ;; esac case " $modules " in *" platformer "*|*" rpg "*) need tile ;; esac +case " $modules " in *" stage "*) need 2d ;; esac +case " $modules " in *" textbox "*) need 2d; need font ;; esac case " $modules " in *" 3d "*) need tile ;; esac case " $modules " in *" model "*) need assets ;; esac case " $DEFS " in *" -DKON_MODULE_ASSETS "*) need pack ;; esac diff --git a/examples/dialog.c b/examples/dialog.c new file mode 100644 index 0000000..51fe369 --- /dev/null +++ b/examples/dialog.c @@ -0,0 +1,260 @@ +/* + * dialog: a little scene with sprites on a stage, a text box in front of them and a menu of buttons in the middle + * + * build: ./build.sh -d -m 2d,stage,textbox,font obj/dialog + * run: obj/dialog + * + * space, enter or a click goes on, up and down or the mouse pick in the menu, l puts Bo in front of the text box and back again + */ + +#include <stdio.h> + +#include "konprogram.h" +#include "kon2d.h" +#include "konstage.h" +#include "kontextbox.h" + +#if !defined(KON_MODULE_2D) || !defined(KON_MODULE_STAGE) || !defined(KON_MODULE_TEXTBOX) || !defined(KON_MODULE_FONT) +#error "build with: ./build.sh -d -m 2d,stage,textbox,font obj/dialog" +#endif + +#define VIEW_WIDTH 320 +#define VIEW_HEIGHT 180 + +#define COL_BACKGROUND 0xFF1B1826u +#define COL_PANEL 0xFF26223Au +#define COL_PINK 0xFFF2A7C3u +#define COL_PINK_DARK 0xFFB5738Fu +#define COL_TEAL 0xFF8FD3C8u +#define COL_TEAL_DARK 0xFF5A9A90u +#define COL_SAND 0xFFF3DDA0u + +enum { ADA, BO, SPRITES }; + +typedef struct state { + kon_stage_t stage; + kon_stageSprite_t sprites[4]; + kon_textbox_t box, menu; + kon_image *back[2], *block[SPRITES]; + int who[SPRITES]; + int beat, background; +} state_t; + +/*** pictures ***/ + +static kon_image *makeBlock(int width, int height, uint32_t fill, uint32_t edge) +{ + kon_image *image = kon_createFramebuffer(width, height); + + if (!image) return NULL; + kon_clearFramebuffer(image, edge); + kon_fillRectangle(image, 3, 3, width - 6, height - 6, fill); + return image; +} + +static kon_image *makeGradient(uint32_t top, uint32_t bottom) +{ + kon_image *image = kon_createFramebuffer(2, VIEW_HEIGHT); + int x, y; + + if (!image) return NULL; + for (y = 0; y < VIEW_HEIGHT; y++) { + uint32_t c = 0xFF000000u; + int shift; + + for (shift = 0; shift < 24; shift += 8) { + int a = (int)((top >> shift) & 0xFF), b = (int)((bottom >> shift) & 0xFF); + + c |= (uint32_t)(a + (b - a) * y / (VIEW_HEIGHT - 1)) << shift; + } + for (x = 0; x < 2; x++) image->data[y * 2 + x] = c; + } + return image; +} + +/*** the scene ***/ + +static void say(state_t *state, int who, const char *text) +{ + kon_textboxSay(&state->box, who == ADA ? "Ada" : "Bo", text); + kon_stageTween(&state->stage, state->who[ADA], KON_PROP_BRIGHTNESS, who == ADA ? 1.0f : 0.55f, 0.2f, KON_EASE_LINEAR); + if (state->who[BO] >= 0) kon_stageTween(&state->stage, state->who[BO], KON_PROP_BRIGHTNESS, who == BO ? 1.0f : 0.55f, 0.2f, KON_EASE_LINEAR); +} + +static void enter(state_t *state, int who, kon_slot_t slot, float fromX, kon_layer_t layer) +{ + int i = kon_stageAdd(&state->stage, state->block[who], NULL, layer); + + if (i < 0) return; + state->who[who] = i; + kon_stageMove(&state->stage, i, fromX, VIEW_HEIGHT, 0.0f, KON_EASE_LINEAR); + kon_stageFade(&state->stage, i, 0.0f, 0.0f, KON_EASE_LINEAR); + kon_stageFade(&state->stage, i, 1.0f, 0.5f, KON_EASE_OUT); + kon_stagePlace(&state->stage, i, slot, 0.6f, KON_EASE_OUT); +} + +static void beat(state_t *state) +{ + static const char *labels[3] = {"swap the background", "move Ada to the middle", "leave"}; + + switch (state->beat++) { + case 0: + kon_stageBackground(&state->stage, state->back[0], KON_FIT_STRETCH, 0.0f); + state->background = 0; + enter(state, ADA, KON_SLOT_LEFT, -30.0f, KON_LAYER_BACK); + state->who[BO] = -1; + say(state, ADA, "Hello. The box is see through, and we both stand behind it."); + break; + case 1: + enter(state, BO, KON_SLOT_FAR_RIGHT, VIEW_WIDTH + 30.0f, KON_LAYER_BACK); + say(state, BO, "Press l and I come out in front of it. Press it again to go back."); + break; + case 2: + say(state, ADA, "The letters arrive one at a time, and a message that does not fit goes on a second page. Press the action key to hurry a page up."); + break; + case 3: + kon_textboxChoices(&state->menu, labels, 3); + kon_textboxFitChoices(&state->menu, 170); + kon_textboxCenter(&state->menu, VIEW_WIDTH, 0, 1, 0); + kon_textboxMove(&state->menu, kon_tweenValue(&state->menu.x), (float)((int)kon_tweenValue(&state->box.y) - state->menu.height) / 2.0f, 0.0f, KON_EASE_LINEAR); + break; + case 4: + say(state, BO, "Goodbye."); + break; + case 5: + kon_stageHide(&state->stage, state->who[ADA], 0.6f); + kon_stageHide(&state->stage, state->who[BO], 0.6f); + break; + default: + state->beat = 0; + break; + } +} + +static void choose(state_t *state, int index) +{ + state->beat = 3; + if (index == 0) { + state->background = !state->background; + kon_stageBackground(&state->stage, state->back[state->background], KON_FIT_STRETCH, 1.0f); + say(state, ADA, "A one second cross fade."); + } else if (index == 1) { + kon_stagePlace(&state->stage, state->who[ADA], KON_SLOT_CENTER, 0.8f, KON_EASE_INOUT); + say(state, ADA, "Eased at both ends."); + } else { + state->beat = 4; + say(state, BO, "Already?"); + } +} + +static void action(state_t *state) +{ + if (state->menu.state == KON_TEXTBOX_CHOOSING) choose(state, kon_textboxConfirm(&state->menu)); + else kon_textboxAdvance(&state->box); +} + +/*** program ***/ + +static int event(kon_program_t *program, const kon_event_t *ev, void *user) +{ + state_t *state = user; + + if (ev->type == KON_EVENT_KEY_DOWN) { + if (ev->key == KON_KEY_SPACE || ev->key == KON_KEY_ENTER) action(state); + else if (ev->key == KON_KEY_L && state->who[BO] >= 0) kon_stageLayer(&state->stage, state->who[BO], state->sprites[state->who[BO]].layer == KON_LAYER_BACK ? KON_LAYER_FRONT : KON_LAYER_BACK); + else if (ev->key == KON_KEY_UP) kon_textboxSelect(&state->menu, -1); + else if (ev->key == KON_KEY_DOWN) kon_textboxSelect(&state->menu, 1); + else return 0; + } else if (ev->type == KON_EVENT_MOUSE_MOVE) { + int x, y; + + if (kon_windowToGame(program->display, ev->mouseX, ev->mouseY, &x, &y)) kon_textboxHighlight(&state->menu, kon_textboxChoiceAt(&state->menu, x, y)); + } else if (ev->type == KON_EVENT_MOUSE_DOWN && ev->mouseButton == KON_MOUSE_LEFT) { + int x, y; + + if (state->menu.state == KON_TEXTBOX_CHOOSING) { + if (kon_windowToGame(program->display, ev->mouseX, ev->mouseY, &x, &y) && kon_textboxChoiceAt(&state->menu, x, y) >= 0) action(state); + } else { + action(state); + } + } else { + return 0; + } + return 1; +} + +static void update(kon_program_t *program, double dt, void *user) +{ + state_t *state = user; + + (void)program; + kon_stageUpdate(&state->stage, (float)dt); + kon_textboxUpdate(&state->box, (float)dt); + kon_textboxUpdate(&state->menu, (float)dt); + if (state->box.state == KON_TEXTBOX_EMPTY && state->menu.state == KON_TEXTBOX_EMPTY && !kon_stageBusy(&state->stage)) beat(state); +} + +static void draw(kon_program_t *program, kon_framebuffer_t *game, void *user) +{ + state_t *state = user; + + (void)program; + kon_clearFramebuffer(game, COL_BACKGROUND); + kon_stageDrawBackground(&state->stage, game); + kon_stageDraw(&state->stage, game, KON_LAYER_BACK); + kon_textboxDraw(&state->box, game); + kon_stageDraw(&state->stage, game, KON_LAYER_FRONT); + kon_textboxDraw(&state->menu, game); +} + +static int init(kon_program_t *program, void *user) +{ + state_t *state = user; + + state->back[0] = makeGradient(0xFF2E2848u, COL_BACKGROUND); + state->back[1] = makeGradient(0xFF2A3A44u, 0xFF1B1826u); + state->block[ADA] = makeBlock(64, 120, COL_PINK, COL_PINK_DARK); + state->block[BO] = makeBlock(64, 132, COL_TEAL, COL_TEAL_DARK); + if (!state->back[0] || !state->back[1] || !state->block[ADA] || !state->block[BO]) return 0; + + kon_stageInit(&state->stage, state->sprites, 4, VIEW_WIDTH, VIEW_HEIGHT); + state->who[ADA] = state->who[BO] = -1; + + kon_textboxInit(&state->box, program->font, 12, 118, VIEW_WIDTH - 24, 52); + state->box.panel.fill = 0xA026223Au; + state->box.plate.fill = 0xE0332E4Eu; + kon_textboxInit(&state->menu, program->font, 0, 0, 170, 40); + state->menu.choiceMode = KON_CHOICES_BUTTONS; + state->menu.choiceAlign = KON_ALIGN_CENTER; + state->menu.buttonSelected.fill = COL_SAND; + return 1; +} + +static void shutdown(kon_program_t *program, void *user) +{ + state_t *state = user; + int i; + + (void)program; + for (i = 0; i < 2; i++) kon_freeFramebuffer(state->back[i]); + for (i = 0; i < SPRITES; i++) kon_freeFramebuffer(state->block[i]); +} + +int main(void) +{ + state_t state = {0}; + kon_programConfig_t config = {0}; + + config.title = "dialog"; + config.gameWidth = VIEW_WIDTH; + config.gameHeight = VIEW_HEIGHT; + config.windowWidth = 1280; + config.windowHeight = 720; + config.user = &state; + config.init = init; + config.event = event; + config.update = update; + config.draw = draw; + config.shutdown = shutdown; + return kon_programRun(&config); +} |
