diff options
| author | KonAki <konaki@anokonaki.org> | 2026-10-13 00:01:11 +0800 |
|---|---|---|
| committer | KonAki <konaki@anokonaki.org> | 2026-10-13 00:01:11 +0800 |
| commit | c3e0f2d163d5dc0b2a9e19b72d85c1611ef95270 (patch) | |
| tree | c568f94aa4bfe1226c6d81fd96a67dd080c4bdff | |
| parent | 3896db0ea50d1e91c29638d6db81a9b66b0c1ba8 (diff) | |
add script example: konscript driving stage and text box
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | README.md | 1 | ||||
| -rw-r--r-- | examples/script.c | 316 |
3 files changed, 318 insertions, 1 deletions
@@ -22,7 +22,7 @@ PACKFLAGS ?= BIN = $(OBJDIR)/$(PROGRAM)$(EXE) PACKER = $(OBJDIR)/konpack$(EXE) MODELER = $(OBJDIR)/konmodel$(EXE) -EXAMPLES = modelview sound ui platform dialog corridor hills sectorview +EXAMPLES = modelview sound ui platform dialog script corridor hills sectorview EXAMPLEBINS = $(EXAMPLES:%=$(OBJDIR)/%$(EXE)) PACKFILE = $(OBJDIR)/data.kpk CORE = $(OBJDIR)/engine.o $(OBJDIR)/konwinlib.o $(OBJDIR)/konsofren.o \ @@ -136,6 +136,7 @@ The examples are their own little programs, in the [examples](examples) folder. - [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 +- [script.c](examples/script.c) is a konscript script driving the stage and text box, with a menu, a hidden option and a variable ```sh ./build.sh -d -m audio,mic,debug obj/sound diff --git a/examples/script.c b/examples/script.c new file mode 100644 index 0000000..53ca718 --- /dev/null +++ b/examples/script.c @@ -0,0 +1,316 @@ +/* + * script: a konscript script driving the stage and the text box. the script is the list near the top, the rest + * is the program doing what the script asks for + * + * build: ./build.sh -d -m 2d,stage,textbox,script,fx,font obj/script + * run: obj/script + * + * space, enter or a click goes on, up and down or the mouse pick in the menu + */ + +#include <stdio.h> + +#include "konprogram.h" +#include "kon2d.h" +#include "konstage.h" +#include "kontextbox.h" +#include "konfx.h" +#include "konscript.h" + +#if !defined(KON_MODULE_2D) || !defined(KON_MODULE_STAGE) || !defined(KON_MODULE_TEXTBOX) || !defined(KON_MODULE_SCRIPT) || !defined(KON_MODULE_FX) || !defined(KON_MODULE_FONT) +#error "build with: ./build.sh -d -m 2d,stage,textbox,script,fx,font obj/script" +#endif + +#define VIEW_WIDTH 320 +#define VIEW_HEIGHT 180 + +#define COL_BACKGROUND 0xFF1B1826u +#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, PEOPLE }; +enum { H_ENTER, H_LEAVE, H_FLASH }; /* our own commands, the script only knows their numbers */ +enum { SW_EXCUSED }; /* switches */ +enum { V_EXCUSES }; /* variables */ + +/*** the script ***/ + +static const kon_cmd_t script[] = { + /* 0 */ KON_HOST(H_ENTER, ADA, KON_SLOT_LEFT, 0, NULL), + /* 1 */ KON_SAY(ADA, "Morning."), + /* 2 */ KON_HOST(H_ENTER, BO, KON_SLOT_FAR_RIGHT, 0, NULL), + /* 3 */ KON_SAY(BO, "You're late. Again."), + /* 4 */ KON_MENU(3), + /* 5 */ KON_OPTION("Sorry.", 9, -1), + /* 6 */ KON_OPTION("There was traffic.", 12, -1), + /* 7 */ KON_OPTION("About the key...", 17, SW_EXCUSED), + /* 8 */ KON_END(), + /* 9 */ KON_SAY(BO, "Fine. Just don't make it a habit."), + /* 10 */ KON_VAR(V_EXCUSES, KON_SET, 0), + /* 11 */ KON_JUMP(20), + /* 12 */ KON_SAY(BO, "There is always traffic."), + /* 13 */ KON_SAY(ADA, "There was a lot of it today."), + /* 14 */ KON_VAR(V_EXCUSES, KON_ADD, 1), + /* 15 */ KON_SWITCH(SW_EXCUSED, 1), + /* 16 */ KON_JUMP(20), + /* 17 */ KON_SAY(BO, "What about it?"), + /* 18 */ KON_SAY(ADA, "Never mind."), + /* 19 */ KON_JUMP(20), + /* 20 */ KON_IFVAR(V_EXCUSES, KON_IS_LT, 2, 23), + /* 21 */ KON_SAY(BO, "Anyway. Again."), + /* 22 */ KON_JUMP(4), + /* 23 */ KON_SAY(BO, "That's the second excuse. I'm going."), + /* 24 */ KON_HOST(H_FLASH, 0, 0, 0, NULL), + /* 25 */ KON_HOST(H_LEAVE, BO, 0, 0, NULL), + /* 26 */ KON_HOST(H_LEAVE, ADA, 0, 0, NULL), + /* 27 */ KON_WAIT(800), + /* 28 */ KON_END() +}; + +/*** the program ***/ + +typedef struct state { + kon_stage_t stage; + kon_stageSprite_t sprites[PEOPLE]; + kon_textbox_t box, menu; + kon_screenfx_t fx; + kon_image *back, *block[PEOPLE]; + int who[PEOPLE]; + kon_script_t vm; + uint8_t switches[8]; + int32_t vars[8]; + int shown; +} state_t; + +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; +} + +static int host(kon_script_t *vm, const kon_cmd_t *cmd, void *user) +{ + state_t *state = user; + + (void)vm; + switch (cmd->a) { + case H_ENTER: { + int who = cmd->b, i = kon_stageAdd(&state->stage, state->block[who], NULL, KON_LAYER_BACK); + + if (i < 0) break; + state->who[who] = i; + kon_stageMove(&state->stage, i, cmd->c == KON_SLOT_LEFT ? -30.0f : VIEW_WIDTH + 30.0f, 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, (kon_slot_t)cmd->c, 0.6f, KON_EASE_OUT); + break; + } + case H_LEAVE: + if (state->who[cmd->b] >= 0) kon_stageHide(&state->stage, state->who[cmd->b], 0.6f); + break; + case H_FLASH: + kon_screenfxFlash(&state->fx, 0xC0FFFFFFu, 0.4f); + break; + } + return 0; +} + +static void restart(state_t *state) +{ + int i; + + for (i = 0; i < PEOPLE; i++) { + if (state->who[i] >= 0) kon_stageRemove(&state->stage, state->who[i]); + state->who[i] = -1; + } + for (i = 0; i < 8; i++) state->switches[i] = 0, state->vars[i] = 0; + kon_scriptStart(&state->vm, 0); + state->shown = 0; +} + +static void showMenu(state_t *state) +{ + const char *labels[KON_SCRIPT_MAX_OPTIONS]; + int i, n = kon_scriptOptionCount(&state->vm); + + if (n > KON_TEXTBOX_CHOICES) n = KON_TEXTBOX_CHOICES; + for (i = 0; i < n; i++) labels[i] = kon_scriptOption(&state->vm, i); + kon_textboxChoices(&state->menu, labels, n); + 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); +} + +static void action(state_t *state) +{ + if (state->menu.state == KON_TEXTBOX_CHOOSING) { + kon_scriptChoose(&state->vm, kon_textboxConfirm(&state->menu)); + state->shown = 0; + } else { + kon_textboxAdvance(&state->box); + } +} + +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_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_screenfxUpdate(&state->fx, (float)dt); + kon_textboxUpdate(&state->box, (float)dt); + kon_textboxUpdate(&state->menu, (float)dt); + kon_scriptUpdate(&state->vm, (float)dt); + + switch (kon_scriptRun(&state->vm)) { + case KON_SCRIPT_SAY: + if (!state->shown) { + const kon_cmd_t *cmd = kon_scriptCurrent(&state->vm); + + kon_textboxSay(&state->box, cmd->a == ADA ? "Ada" : "Bo", cmd->text); + state->shown = 1; + } else if (state->box.state == KON_TEXTBOX_EMPTY) { + kon_scriptResume(&state->vm); + state->shown = 0; + } + break; + case KON_SCRIPT_MENU: + if (!state->shown) { + showMenu(state); + state->shown = 1; + } + break; + case KON_SCRIPT_DONE: + case KON_SCRIPT_ERROR: + if (!kon_stageBusy(&state->stage)) restart(state); + break; + default: + break; + } +} + +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_screenfxApply(&state->fx, game); + 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 = makeGradient(0xFF2E2848u, COL_BACKGROUND); + 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 || !state->block[ADA] || !state->block[BO]) return 0; + + kon_stageInit(&state->stage, state->sprites, PEOPLE, VIEW_WIDTH, VIEW_HEIGHT); + kon_stageBackground(&state->stage, state->back, KON_FIT_STRETCH, 0.0f); + kon_screenfxInit(&state->fx); + + 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; + + kon_scriptInit(&state->vm, script, (int)(sizeof script / sizeof script[0]), state->switches, 8, state->vars, 8); + state->vm.host = host; + state->vm.user = state; + state->who[ADA] = state->who[BO] = -1; + kon_scriptStart(&state->vm, 0); + return 1; +} + +static void shutdown(kon_program_t *program, void *user) +{ + state_t *state = user; + int i; + + (void)program; + kon_freeFramebuffer(state->back); + for (i = 0; i < PEOPLE; i++) kon_freeFramebuffer(state->block[i]); +} + +int main(void) +{ + state_t state = {0}; + kon_programConfig_t config = {0}; + + config.title = "script"; + 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); +} |
