掌握 XGE 的场景(Scene)栈机制,实现菜单→游戏→暂停等多界面切换,理解生命周期回调。
游戏通常有多个"界面":主菜单、游戏关卡、暂停菜单、设置页面。XGE 用场景栈管理这些界面——栈顶是当前活跃场景,只有栈顶场景接收 Update 和 Draw。
每个场景是一个 xge_scene_t 结构体,包含生命周期回调(进入/离开/暂停/恢复)和每帧回调(事件/更新/绘制)。
struct xge_scene_t {
void* pUser; // 用户数据(场景私有状态)
xge_scene_lifecycle_proc onEnter; // 进入场景
xge_scene_lifecycle_proc onLeave; // 离开场景
xge_scene_lifecycle_proc onPause; // 被压栈(暂停)
xge_scene_lifecycle_proc onResume; // 从栈中恢复
xge_scene_event_proc onEvent; // 事件处理
xge_scene_update_proc onUpdate; // 逻辑更新 (fDelta)
xge_scene_draw_proc onDraw; // 绘制
xge_scene_lifecycle_proc onFree; // 释放资源
int bEntered; // 是否已进入
int bPaused; // 是否暂停
};| 函数 | 说明 |
|---|---|
int xgeSceneSet(xge_scene pScene) | 替换整个栈,设为唯一场景 |
int xgeScenePush(xge_scene pScene) | 压入新场景(当前场景暂停) |
int xgeScenePop(void) | 弹出栈顶(恢复下一层) |
int xgeSceneReplace(xge_scene pScene) | 替换栈顶(不改变栈深度) |
xge_scene xgeSceneCurrent(void) | 获取栈顶场景 |
int xgeSceneCount(void) | 获取栈中场景数量 |
int xgeSceneDispatchEvent(const xge_event_t* pEvent) | 向栈顶场景分发事件 |
int xgeSceneUpdateStrategySet(int iMode, float fFixedStep, int iMaxUpdates) | 设置更新策略 |
void xgeSceneUpdateStrategyGet(int* pMode, float* pFixedStep, int* pMaxUpdates) | 获取更新策略 |
gcc -O2 -I. -o ch20.exe ch20_main.c build\xge.lib -lm -lws2_32 -liphlpapi -lgdi32 -luser32 -lshell32 -lopengl32 -lole32 -lwinmm -lavrt
.\ch20.exe
static int menuEnter(xge_scene s) {
xgeLogWrite(XGE_LOG_INFO, "Menu entered");
return XGE_OK;
}
static int menuUpdate(xge_scene s, float fDelta) {
// 检测按键 → 进入游戏
if (xgeKeyPressed(XGE_KEY_ENTER)) {
xgeScenePush(&g_tGameScene); // 压入游戏场景
}
return XGE_OK;
}
static int menuDraw(xge_scene s) {
xgeClear(0x081114FF);
// xgeTextDraw(300, 250, "PRESS ENTER TO START", ...);
return XGE_OK;
}
static xge_scene_t g_tMenuScene = {
.onEnter = menuEnter,
.onUpdate = menuUpdate,
.onDraw = menuDraw,
};int main(void) {
xge_desc_t d = {0};
d.iWidth = 800; d.iHeight = 600;
xgeInit(&d);
// 设置初始场景(清空栈,压入菜单)
xgeSceneSet(&g_tMenuScene);
// 引擎自动调用栈顶场景的 onUpdate / onDraw
xgeRun(NULL, NULL); // 场景模式下回调可为 NULL
xgeUnit();
return 0;
}// 游戏中按 ESC → 压入暂停场景
static int gameUpdate(xge_scene s, float fDelta) {
if (xgeKeyPressed(XGE_KEY_ESCAPE)) {
xgeScenePush(&g_tPauseScene);
// 游戏场景自动收到 onPause
}
// ... 游戏逻辑 ...
return XGE_OK;
}
// 暂停场景中按 Resume → 弹出
static int pauseUpdate(xge_scene s, float fDelta) {
if (xgeKeyPressed(XGE_KEY_ESCAPE)) {
xgeScenePop();
// 游戏场景自动收到 onResume
}
return XGE_OK;
}// Push 新场景:
// 旧栈顶.onPause() → 新场景.onEnter()
//
// Pop 栈顶:
// 栈顶.onLeave() → 栈顶.onFree() → 新栈顶.onResume()
//
// Replace 栈顶:
// 旧栈顶.onLeave() → 旧栈顶.onFree() → 新场景.onEnter()
//
// Set(清栈):
// 所有场景依次 onLeave + onFree → 新场景.onEnter()将以下代码保存为 ch20_main.c,编译后运行即可体验场景栈切换(Enter 进入游戏,ESC 返回菜单):
#include "xge.h"
static xge_scene_t g_tMenuScene;
static xge_scene_t g_tGameScene;
/* --- 菜单场景 --- */
static int menuUpdate(xge_scene s, float fDelta) {
(void)s; (void)fDelta;
if (xgeKeyPressed(XGE_KEY_ENTER))
xgeScenePush(&g_tGameScene);
return XGE_OK;
}
static int menuDraw(xge_scene s) {
(void)s;
xgeClear(0x081114FF);
xgeShapeRectFill(250, 250, 300, 100, 0x1A2A30FF);
xgeShapeCircleFill(400, 300, 30, 0x4FD8C2FF);
return XGE_OK;
}
/* --- 游戏场景 --- */
static int gameUpdate(xge_scene s, float fDelta) {
(void)s; (void)fDelta;
if (xgeKeyPressed(XGE_KEY_ESCAPE))
xgeScenePop();
return XGE_OK;
}
static int gameDraw(xge_scene s) {
(void)s;
xgeClear(0x14100AFF);
xgeShapeCircleFill(400, 300, 60, 0xFFB454FF);
return XGE_OK;
}
int main(void) {
// 初始化场景结构体
g_tMenuScene.onUpdate = menuUpdate;
g_tMenuScene.onDraw = menuDraw;
g_tGameScene.onUpdate = gameUpdate;
g_tGameScene.onDraw = gameDraw;
xge_desc_t d = {0};
d.iWidth = 800;
d.iHeight = 600;
d.sTitle = "ch20 - Scene Stack";
d.iFlags = XGE_INIT_VSYNC;
xgeInit(&d);
xgeSceneSet(&g_tMenuScene); // 设置初始场景
xgeRun(NULL, NULL); // 场景模式下回调可为 NULL
xgeUnit();
return 0;
}xgeSceneSet(&g_tMenuScene) 清空场景栈并压入菜单场景作为初始界面。xgeScenePush 压入游戏场景时,菜单场景自动收到 onPause。xgeScenePop 弹出游戏场景后,菜单场景自动收到 onResume 并重新活跃。xgeRun(NULL, NULL):场景模式下引擎自动调用栈顶场景的 onUpdate/onDraw,无需额外帧回调。xge_scene 是指针类型,指向你的 xge_scene_t 实例。通常用全局/静态变量。xgeSceneUpdateStrategySet 可设置固定步长更新(适合物理模拟)。