/* * 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,fx,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. * f flashes, s shakes, t changes the tone, d fades out and in, p turns a glow picture on and off. the box stays clear of all of them */ #include #include #include "konprogram.h" #include "kon2d.h" #include "konstage.h" #include "kontextbox.h" #include "konfx.h" #if !defined(KON_MODULE_2D) || !defined(KON_MODULE_STAGE) || !defined(KON_MODULE_TEXTBOX) || !defined(KON_MODULE_FX) || !defined(KON_MODULE_FONT) #error "build with: ./build.sh -d -m 2d,stage,textbox,fx,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_screenfx_t fx; kon_image *back[2], *block[SPRITES], *glow; int who[SPRITES]; int beat, background; int toned, faded, glowing, glowSprite; } 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; } static kon_image *makeGlow(int size) { kon_image *image = kon_createFramebuffer(size, size); int x, y; if (!image) return NULL; for (y = 0; y < size; y++) { for (x = 0; x < size; x++) { float dx = ((float)x + 0.5f) / (float)size * 2.0f - 1.0f, dy = ((float)y + 0.5f) / (float)size * 2.0f - 1.0f; float d = sqrtf(dx * dx + dy * dy), a = d < 1.0f ? (1.0f - d) * (1.0f - d) : 0.0f; image->data[y * size + x] = ((uint32_t)(a * 255.0f) << 24) | (COL_SAND & 0x00FFFFFFu); } } 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_F) kon_screenfxFlash(&state->fx, 0xC0FFFFFFu, 0.4f); else if (ev->key == KON_KEY_S) kon_screenfxShake(&state->fx, 5.0f, 2.0f, 12.0f, 0.5f); else if (ev->key == KON_KEY_T) { state->toned = !state->toned; kon_screenfxTone(&state->fx, state->toned ? 30 : 0, state->toned ? 10 : 0, state->toned ? -20 : 0, state->toned ? 80 : 0, 0.8f, KON_EASE_INOUT); } else if (ev->key == KON_KEY_D) { state->faded = !state->faded; kon_screenfxFade(&state->fx, COL_BACKGROUND, state->faded ? 1.0f : 0.0f, 0.8f, KON_EASE_LINEAR); } else if (ev->key == KON_KEY_P) { state->glowing = !state->glowing; kon_stageFade(&state->stage, state->glowSprite, state->glowing ? 0.9f : 0.0f, 0.6f, KON_EASE_LINEAR); } 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; kon_stageUpdate(&state->stage, (float)dt); kon_screenfxUpdate(&state->fx, (float)dt); kon_stageRotate(&state->stage, state->glowSprite, (float)program->time * 0.3f, 0.0f, KON_EASE_LINEAR); 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_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[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_screenfxInit(&state->fx); state->glow = makeGlow(160); if (!state->glow) return 0; state->glowSprite = kon_stageAdd(&state->stage, state->glow, NULL, KON_LAYER_BACK); state->sprites[state->glowSprite].blend = KON_BLEND_ADD; state->sprites[state->glowSprite].anchorX = state->sprites[state->glowSprite].anchorY = 0.5f; state->sprites[state->glowSprite].z = 10; kon_stageMove(&state->stage, state->glowSprite, VIEW_WIDTH * 0.5f, 60.0f, 0.0f, KON_EASE_LINEAR); 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]); kon_freeFramebuffer(state->glow); } 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); }