如何在ncurses中实现游戏窗口与暂停窗口的切换?
NCurses自定义窗口实现游戏暂停功能的问题
我用C语言开发游戏,未使用stdscr,而是自定义了名为basewin的窗口。需求如下:
- 按下P键时,打开
pausewin窗口并暂停basewin中的游戏 - 按下R键时,关闭
pausewin并恢复游戏,且basewin需保持暂停前的状态
我原本认为用窗口实现该需求有以下优势:
- 代码更简洁
- 窗口是内存空间指针,可轻松恢复
basewin - 能作为通用游戏暂停方案,避免退出
pausewin后使用printw出现问题
但该思路未成功实现,我编写了简化版测试代码(如下),但在标记位置出现问题。尝试过用tempwin但仅生效一次,也试过overwrite,似乎并非正确解决方法。
#include <ncurses.h> #include <stdlib.h> #include <unistd.h> #include <stdlib.h> int main() { /* INIT NCURSES */ initscr(); curs_set(0); noecho(); /* INIT PAUSE WIN */ WINDOW *pausewin = newwin(LINES/3, COLS/3,LINES/3, COLS/3); keypad(pausewin,TRUE); int cMax_pause, lMax_pause; getmaxyx(pausewin,lMax_pause, cMax_pause); box(pausewin,0,0); mvwprintw(pausewin,1,cMax_pause/2 -5,"PAUSE WIN"); mvwprintw(pausewin,lMax_pause/2, cMax_pause/3-4, "[R]ESUME"); mvwprintw(pausewin,lMax_pause/2, 2*cMax_pause/3-3, "[Q]UIT"); /* INIT BASE WIN */ WINDOW *basewin = newwin(LINES, COLS/2, 0, COLS/4); keypad(basewin,TRUE); int lMax, cMax; getmaxyx(basewin, lMax, cMax); box(basewin,0,0); mvwprintw(basewin,0,2, "[P]AUSE"); mvwprintw(basewin,1,cMax/2 -4,"BASE WIN"); /* INIT VAR */ bool loop = true; bool pause = false; int ch,chp; /* INIT VARS PLAYER */ int pline = lMax/2; int pcol = cMax/2; char pch[2] ; pch[0] = 'M'; /* AFFICHAGE BASE WIN : PRE-LOOP */ wrefresh(basewin); /* "GAME" LOOP */ while(loop){ ch = wgetch(basewin); switch(ch){ case 'p': /* On met le jeu en pause */ case 'P': pause = true; break; case KEY_LEFT: /* Sinon deplacement */ pcol -= 1; break; case KEY_RIGHT: pcol += 1; break; case KEY_UP: pline -= 1; break; case KEY_DOWN: pline += 1; break; default: break; } if (pause){ wrefresh(pausewin); // HERE IS THE PLACE I HAVE MY PROBLEM while(pause){ chp = wgetch(pausewin); switch (chp){ case 'q': case 'Q': pause = false; loop = false; break; case 'r': case 'R': pause = false; wrefresh(basewin); // HERE TOO break; default: break; } } } else{ mvwprintw(basewin,pline,pcol,pch); } wrefresh(basewin); } delwin(pausewin); delwin(basewin); endwin(); return EXIT_SUCCESS; }
问题根源与解决方法
核心问题
当前代码的问题在于:
- 打开
pausewin时,没有保存basewin的当前画面状态,导致pausewin覆盖basewin区域后,恢复时无法还原之前的游戏画面 - 恢复时直接调用
wrefresh(basewin),但ncurses的窗口刷新机制不会自动处理被其他窗口覆盖的内容,需要手动还原状态
修正方案
使用copywin创建临时窗口保存basewin的状态,暂停时显示pausewin,恢复时用临时窗口的内容覆盖basewin后再刷新。
修改后的完整代码:
#include <ncurses.h> #include <stdlib.h> #include <unistd.h> #include <stdbool.h> // 补充bool类型头文件 int main() { /* INIT NCURSES */ initscr(); curs_set(0); noecho(); cbreak(); // 添加cbreak模式,确保按键立即响应 /* INIT PAUSE WIN */ WINDOW *pausewin = newwin(LINES/3, COLS/3, LINES/3, COLS/3); keypad(pausewin, TRUE); int cMax_pause, lMax_pause; getmaxyx(pausewin, lMax_pause, cMax_pause); box(pausewin, 0, 0); mvwprintw(pausewin, 1, cMax_pause/2 - 5, "PAUSE WIN"); mvwprintw(pausewin, lMax_pause/2, cMax_pause/3 - 4, "[R]ESUME"); mvwprintw(pausewin, lMax_pause/2, 2*cMax_pause/3 - 3, "[Q]UIT"); wrefresh(pausewin); // 预先绘制暂停窗口,避免第一次显示时闪烁 wclear(pausewin); // 清空暂停窗口的缓冲区,避免干扰后续显示 /* INIT BASE WIN */ WINDOW *basewin = newwin(LINES, COLS/2, 0, COLS/4); keypad(basewin, TRUE); int lMax, cMax; getmaxyx(basewin, lMax, cMax); box(basewin, 0, 0); mvwprintw(basewin, 0, 2, "[P]AUSE"); mvwprintw(basewin, 1, cMax/2 - 4, "BASE WIN"); /* 用于保存basewin状态的临时窗口 */ WINDOW *tempwin = newwin(LINES, COLS/2, 0, COLS/4); /* INIT VAR */ bool loop = true; bool pause = false; int ch, chp; /* INIT VARS PLAYER */ int pline = lMax/2; int pcol = cMax/2; char pch[2] = {'M', '\0'}; // 修正字符串初始化 /* AFFICHAGE BASE WIN : PRE-LOOP */ mvwprintw(basewin, pline, pcol, pch); wrefresh(basewin); /* "GAME" LOOP */ while(loop){ ch = wgetch(basewin); switch(ch){ case 'p': case 'P': pause = true; // 保存basewin当前状态到tempwin copywin(basewin, tempwin, 0, 0, 0, 0, lMax-1, cMax-1, FALSE); // 显示暂停窗口 box(pausewin, 0, 0); mvwprintw(pausewin, 1, cMax_pause/2 - 5, "PAUSE WIN"); mvwprintw(pausewin, lMax_pause/2, cMax_pause/3 - 4, "[R]ESUME"); mvwprintw(pausewin, lMax_pause/2, 2*cMax_pause/3 - 3, "[Q]UIT"); wrefresh(pausewin); break; case KEY_LEFT: pcol = (pcol > 1) ? pcol - 1 : 1; // 防止越界 break; case KEY_RIGHT: pcol = (pcol < cMax-2) ? pcol + 1 : cMax-2; // 防止越界 break; case KEY_UP: pline = (pline > 1) ? pline - 1 : 1; // 防止越界 break; case KEY_DOWN: pline = (pline < lMax-2) ? pline + 1 : lMax-2; // 防止越界 break; default: break; } if (pause){ while(pause){ chp = wgetch(pausewin); switch (chp){ case 'q': case 'Q': pause = false; loop = false; break; case 'r': case 'R': pause = false; // 用tempwin的内容还原basewin copywin(tempwin, basewin, 0, 0, 0, 0, lMax-1, cMax-1, FALSE); wrefresh(basewin); break; default: break; } } } else { wclear(basewin); // 清空窗口重绘,避免残留旧的玩家位置 box(basewin, 0, 0); mvwprintw(basewin, 0, 2, "[P]AUSE"); mvwprintw(basewin, 1, cMax/2 - 4, "BASE WIN"); mvwprintw(basewin, pline, pcol, pch); wrefresh(basewin); } } delwin(tempwin); delwin(pausewin); delwin(basewin); endwin(); return EXIT_SUCCESS; }
关键修改点说明
- 添加
stdbool.h头文件,修复bool类型未定义问题 - 创建
tempwin临时窗口,用于保存basewin的状态 - 按下P键时,用
copywin将basewin的当前内容复制到tempwin,然后显示pausewin - 按下R键时,用
copywin将tempwin的内容复制回basewin,再刷新恢复画面 - 优化玩家移动的边界判断,防止越界
- 游戏循环中重绘
basewin的边框和固定文本,避免移动时丢失界面元素
内容的提问来源于stack exchange,提问作者rm-star
相关产品推荐
相关产品推荐

