Improved cutscene system.

This commit is contained in:
2025-10-29 07:31:17 -05:00
parent cf1dcd3637
commit a48fa50a3e
19 changed files with 313 additions and 38 deletions

View File

@@ -15,6 +15,7 @@ target_sources(microrpg PRIVATE
) )
# Subdirs # Subdirs
add_subdirectory(cutscene)
add_subdirectory(entity) add_subdirectory(entity)
add_subdirectory(scene) add_subdirectory(scene)
add_subdirectory(platform) add_subdirectory(platform)

11
src/cutscene/CMakeLists.txt Executable file
View File

@@ -0,0 +1,11 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(microrpg PRIVATE
cutscene.c
cutsceneitem.c
cutscenemode.c
)

49
src/cutscene/cutscene.c Normal file
View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "cutscene.h"
#include "game.h"
void cutsceneInit() {
memset(&GAME.cutsceneSystem, 0, sizeof(GAME.cutsceneSystem));
}
void cutsceneStart(const cutscene_t *cutscene) {
GAME.cutsceneSystem.cutscene = cutscene;
GAME.cutsceneSystem.mode = CUTSCENE_MODE_INITIAL;
GAME.cutsceneSystem.currentItem = 0xFF;// Set to 0xFF so start wraps.
cutsceneNext();
}
void cutsceneTick() {
if(GAME.cutsceneSystem.cutscene == NULL) return;
const cutsceneitem_t *item = (
&GAME.cutsceneSystem.cutscene->items[GAME.cutsceneSystem.currentItem]
);
cutsceneItemTick(item, &GAME.cutsceneSystem.data);
}
void cutsceneNext() {
if(GAME.cutsceneSystem.cutscene == NULL) return;
GAME.cutsceneSystem.currentItem++;
// End of the cutscene?
if(GAME.cutsceneSystem.currentItem >= GAME.cutsceneSystem.cutscene->itemCount) {
GAME.cutsceneSystem.cutscene = NULL;
GAME.cutsceneSystem.currentItem = 0;
GAME.cutsceneSystem.mode = CUTSCENE_MODE_NONE;
return;
}
// Start item.
const cutsceneitem_t *item = (
&GAME.cutsceneSystem.cutscene->items[GAME.cutsceneSystem.currentItem]
);
cutsceneItemStart(item, &GAME.cutsceneSystem.data);
}

View File

@@ -6,22 +6,41 @@
*/ */
#pragma once #pragma once
#include "microrpg.h" #include "cutsceneitem.h"
#include "cutscenemode.h"
#define CUTSCENE_ITEM_NULL 0
#define CUTSCENE_ITEM_TEXTBOX 1
#define CUTSCENE_ITEM_CALLBACK 2
typedef struct { typedef struct {
uint8_t type; const cutsceneitem_t *items;
union {
const char *textbox;
void (*callback)(void);// TODO: Bring entity data across.
};
} cutsceneitem_t;
typedef struct {
cutsceneitem_t *items;
uint8_t itemCount; uint8_t itemCount;
} cutscene_t; } cutscene_t;
typedef struct {
const cutscene_t *cutscene;
uint8_t currentItem;
// Data (used by the current item).
cutsceneitemdata_t data;
cutscenemode_t mode;
} cutscenesystem_t;
/**
* Initialize the cutscene system.
*/
void cutsceneInit();
/**
* Start a cutscene.
*
* @param cutscene Pointer to the cutscene to start.
*/
void cutsceneStart(const cutscene_t *cutscene);
/**
* Advance to the next item in the cutscene.
*/
void cutsceneNext();
/**
* Update the cutscene system for one tick.
*/
void cutsceneTick();

View File

@@ -0,0 +1,11 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "microrpg.h"
typedef void (*cutscenecallback_t)(void);

View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "cutsceneitem.h"
#include "cutscene.h"
void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
switch(item->type) {
case CUTSCENE_ITEM_TEXT:
break;
case CUTSCENE_ITEM_WAIT:
data->wait = item->wait;
break;
case CUTSCENE_ITEM_CALLBACK:
if(item->callback != NULL) item->callback();
break;
default:
break;
}
}
void cutsceneItemTick(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
switch(item->type) {
case CUTSCENE_ITEM_TEXT:
break;
case CUTSCENE_ITEM_CALLBACK:
break;
case CUTSCENE_ITEM_WAIT:
data->wait--;
if(data->wait <= 0) {
// Wait is over, proceed to next item.
cutsceneNext();
}
break;
default:
break;
}
}

View File

@@ -0,0 +1,47 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "cutscenewait.h"
#include "cutscenecallback.h"
#include "cutscenetext.h"
#define CUTSCENE_ITEM_NULL 0
#define CUTSCENE_ITEM_TEXT 1
#define CUTSCENE_ITEM_CALLBACK 2
#define CUTSCENE_ITEM_WAIT 3
typedef struct cutsceneitem_s {
uint8_t type;
// Arguments/Data that will be used when this item is invoked.
union {
cutscenetext_t text;
cutscenecallback_t callback;
cutscenewait_t wait;
};
} cutsceneitem_t;
typedef union {
cutscenewaitdata_t wait;
} cutsceneitemdata_t;
/**
* Start the given cutscene item.
*
* @param item The cutscene item to start.
* @param data The cutscene item data storage.
*/
void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data);
/**
* Tick the given cutscene item (one frame).
*
* @param item The cutscene item to tick.
* @param data The cutscene item data storage.
*/
void cutsceneItemTick(const cutsceneitem_t *item, cutsceneitemdata_t *data);

View File

@@ -0,0 +1,20 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "cutscenemode.h"
#include "game.h"
bool_t cutsceneModeIsInputAllowed() {
switch(GAME.cutsceneSystem.mode) {
case CUTSCENE_MODE_FULL_FREEZE:
case CUTSCENE_MODE_INPUT_FREEZE:
return false;
default:
return true;
}
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "microrpg.h"
// Mode 0, Assume no cutscene is active.
#define CUTSCENE_MODE_NONE 0
// Mode 1, assume that the cutscene is controlling all gameplay (full freeze).
#define CUTSCENE_MODE_FULL_FREEZE 1
// Mode 2, assume that the cutscene is simply stopping regular player input.
#define CUTSCENE_MODE_INPUT_FREEZE 2
// Mode 3, assume that the cutscene is not affecting gameplay directly.
#define CUTSCENE_MODE_GAMEPLAY 3
// Default mode for all cutscenes.
#define CUTSCENE_MODE_INITIAL CUTSCENE_MODE_INPUT_FREEZE
typedef uint8_t cutscenemode_t;
/**
* Check if input is allowed in the current cutscene mode.
*
* @return true if input is allowed, false otherwise.
*/
bool_t cutsceneModeIsInputAllowed();

View File

@@ -0,0 +1,11 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "microrpg.h"
typedef const char *cutscenetext_t;

View File

@@ -0,0 +1,12 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "microrpg.h"
typedef uint16_t cutscenewait_t;
typedef uint16_t cutscenewaitdata_t;

View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "cutscene/cutscene.h"
static void testCutsceneTest() {
}
static const cutsceneitem_t TEST_CUTSCENE_ITEMS[] = {
{ .type = CUTSCENE_ITEM_WAIT, .wait = 1 * GAME_TIME_TICKS_PER_SECOND }
};
static const cutscene_t TEST_CUTSCENE = {
.items = TEST_CUTSCENE_ITEMS,
.itemCount = sizeof(TEST_CUTSCENE_ITEMS) / sizeof(cutsceneitem_t)
};

View File

@@ -1,17 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "cutscene.h"
static void testCutsceneTest() {
abort();
}
cutsceneitem_t TEST_CUTSCENE_ITEMS[] = {
{ .type = CUTSCENE_ITEM_CALLBACK, .callback = testCutsceneTest }
};

View File

@@ -31,7 +31,7 @@ void entityTick(entity_t *entity) {
} }
// Let entities move (if they do) // Let entities move (if they do)
if(entity->type == ENTITY_TYPE_PLAYER) { if(entity->type == ENTITY_TYPE_PLAYER && cutsceneModeIsInputAllowed()) {
playerTickInput(entity); playerTickInput(entity);
} }
} }

View File

@@ -9,5 +9,6 @@
#include "entity.h" #include "entity.h"
void signInteract(entity_t *entity) { void signInteract(entity_t *entity) {
abort(); if(entity->sign.cutscene == NULL) return;
cutsceneStart(entity->sign.cutscene);
} }

View File

@@ -6,12 +6,12 @@
*/ */
#pragma once #pragma once
#include "microrpg.h" #include "cutscene/cutscene.h"
typedef struct entity_s entity_t; typedef struct entity_s entity_t;
typedef struct sign_s { typedef struct sign_s {
void *nothing; const cutscene_t *cutscene;
} sign_t; } sign_t;
/** /**

View File

@@ -6,22 +6,28 @@
*/ */
#include "game.h" #include "game.h"
#include "cutscene/cutscene.h"
#include "cutscene/scene/testcutscene.h"
game_t GAME; game_t GAME;
void gameInit() { void gameInit() {
memset(&GAME, 0, sizeof(GAME)); memset(&GAME, 0, sizeof(GAME));
cutsceneInit();
entityInit(&GAME.player, ENTITY_TYPE_PLAYER); entityInit(&GAME.player, ENTITY_TYPE_PLAYER);
entityInit(&GAME.overworld.map.entities[0], ENTITY_TYPE_SIGN); entityInit(&GAME.overworld.map.entities[0], ENTITY_TYPE_SIGN);
GAME.overworld.map.entities[0].position.x = 5; GAME.overworld.map.entities[0].position.x = 5;
GAME.overworld.map.entities[0].position.y = 5; GAME.overworld.map.entities[0].position.y = 5;
GAME.overworld.map.entities[0].sign.cutscene = &TEST_CUTSCENE;
GAME.scene = SCENE_OVERWORLD; GAME.scene = SCENE_OVERWORLD;
} }
void gameTick() { void gameTick() {
gameTimeTick(&GAME.time); gameTimeTick(&GAME.time);
cutsceneTick();
sceneTick(); sceneTick();
} }

View File

@@ -9,6 +9,7 @@
#include "entity/entity.h" #include "entity/entity.h"
#include "world/map.h" #include "world/map.h"
#include "scene/scene.h" #include "scene/scene.h"
#include "cutscene/cutscene.h"
#include "gametime.h" #include "gametime.h"
#define GAME_SCENE_INITIAL 0 #define GAME_SCENE_INITIAL 0
@@ -18,6 +19,7 @@ typedef struct game_s {
scene_t scene; scene_t scene;
entity_t player; entity_t player;
gametime_t time; gametime_t time;
cutscenesystem_t cutsceneSystem;
union { union {
struct { struct {

View File

@@ -72,7 +72,7 @@ uint8_t platformUpdate() {
// Update input state. // Update input state.
PLATFORM_RAYLIB.inputPrevious = PLATFORM_RAYLIB.inputCurrent; PLATFORM_RAYLIB.inputPrevious = PLATFORM_RAYLIB.inputCurrent;
PLATFORM_RAYLIB.inputCurrent = 0; PLATFORM_RAYLIB.inputCurrent = 0;
rlkeyboardmap_t *map = RL_KEYBOARD_MAP; const rlkeyboardmap_t *map = RL_KEYBOARD_MAP;
do { do {
if(IsKeyDown(map->raylibKey)) { if(IsKeyDown(map->raylibKey)) {
PLATFORM_RAYLIB.inputCurrent |= map->action; PLATFORM_RAYLIB.inputCurrent |= map->action;