PSP 编程/文本菜单
随意使用或编辑本页的任何部分和/或代码,但请在应得之处给予署名。
这是一个我编写的简单程序,用来展示如何在游戏中使用控件,以及如何编写一个极其简单的菜单,它有详细的注释,但可能存在比我知道的更容易/更快的做事方法,如果您看到可以改进的地方,请随时提出。
要编译这个程序,您需要一些文件(我并没有编写这些文件),您可以从 http://www.psp-programming.com/tutorials/c/lesson04.zip 下载。我相信这些代码是由 Yeldarb 编写的,无论如何,做得很好。
请有人提供公益服务,并以维基格式整理一下,如果您想复制/粘贴,看起来很不错,就像我说的,请给我一些署名。
// First homebrew program written by 42o5H4DYH4Xo24 *NoHellshady00[@]Spamgmail.com
// Completed on January 4th, 2007, 5:55pm
// Extremely basic program to output text on the PSP
// Written using the PSPToolkit & PSPSDK
// Includes
#include <pspkernel.h>
#include <pspdisplay.h>
#include <pspctrl.h>
extern "C" {
#include <graphics.h>
#include <framebuffer.h>
}
// Convenience
// RGB to decimal color conversion macro
#define RGB(r, g, b) ((r)|((g)<<8)|((b)<<16))
// PSP kernel module info
PSP_MODULE_INFO("TextDisplay",0,1,1);
// Standard callback functions
/* Exit callback */
int exit_callback(int arg1, int arg2, void *common) {
sceKernelExitGame();
return 0;
}
/* Callback thread */
int CallbackThread(SceSize args, void *argp) {
int cbid;
cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL);
sceKernelRegisterExitCallback(cbid);
sceKernelSleepThreadCB();
return 0;
}
/* Sets up the callback thread and returns its thread id */
int SetupCallbacks(void) {
int thid = 0;
thid = sceKernelCreateThread("update_thread", CallbackThread, 0x11, 0xFA0, 0, 0);
if(thid >= 0) {
sceKernelStartThread(thid, 0, 0);
}
return thid;
}
// End Callback functs
// Set some global variables and function prototypes
int ColorRed = RGB(255, 0, 0); // Red
int ColorBlue = RGB(0, 0, 255); // Blue
int ColorGreen = RGB(0, 255, 0); // Green
int ColorBlack = RGB(0, 0, 0); // Black
int xMenuop; // Dont change this
int& Menuop = xMenuop; // Change this instead
int startMenu(int a); // Function prototype for printing the menu to the screen with a black background, use int a to set cursor
int startMenuCtrl();
int startMenuCtrlDelay() {
sceKernelDelayThread(1000000/7);
return 0;
}
SceCtrlData pad;
// Main Menu
int startMenu(int a) { //
// Set some local variables
char cursor[5] = "=->"; // What to use as our cursor
int cursorColor = ColorBlue;
int MinMenuop = 1;
int MaxMenuop = 4; // Number of options in our menu
// Check to make sure an invalid option wasnt passed to startMenu()... and makes the list roll over
if (a>MaxMenuop) {
Menuop = MinMenuop;
startMenu(Menuop); // Recursive
}
if (a<MinMenuop) {
Menuop = MaxMenuop;
startMenu(Menuop); // Recursive
}
// Should never execute without a correct value to keep from displaying incorrect cursor positions
// Print the new menu
int cursorY = (Menuop*10)+110;
clearScreen(ColorBlack);
printTextScreen(120, cursorY, cursor, cursorColor);
printTextScreen(150, 120, "Start Game", ColorGreen);
printTextScreen(150, 130, "Load Game", ColorGreen);
printTextScreen(150, 140, "Multiplayer", ColorGreen);
printTextScreen(150, 150, "Password", ColorGreen);
sceDisplayWaitVblankStart();
flipScreen();
return 0;
}
int startMenuCtrl() {
// Start a control loop
while (1) {
sceCtrlReadBufferPositive(&pad, 1);
if (pad.Buttons & PSP_CTRL_DOWN) {
Menuop++;
startMenu(Menuop);
startMenuCtrlDelay();
}
if (pad.Buttons & PSP_CTRL_UP) {
Menuop--;
startMenu(Menuop);
startMenuCtrlDelay();
}
}
}
// Start
int main(void) {
// Init some variables
Menuop = 1;
SetupCallbacks(); // Standard, setup our Callbacks
initGraphics(); // graphics.h does all the initializing for us
printTextScreen(0, 0, "Simple Menu v1.1 by 42o5H4DYH4Xo24", ColorGreen);
sceDisplayWaitVblankStart();
flipScreen();
sceKernelDelayThread(1000000*2);
sceDisplayWaitVblankStart();
clearScreen(ColorBlack);
startMenu(Menuop);
startMenuCtrl();
return 0; // our function main() is supposed to return an int,
// so here it is... for c++ etiquette.
//
// Note: Explicit return in main is good for clarity, as otherwise this will
// return 0 exit code to any caller implicitly. Forming good habits now will
// save you debug headaches later. :)
}
/* Notes
Need to make PSP ftp program to send file from PSP and wait for one in return
and a bash shell to compile the file and resend the eboot
*/
随意评论/编辑此代码或更详细地描述正在发生的事情,只需在应得之处给予署名。
注意:使用递归(例如在 startMenu() 函数中)是导致系统崩溃的非常好的方法。原因是,当调用一个函数时,系统通过将指向该函数的指针压入系统堆栈,然后按相反的顺序(与它们在源代码中出现的顺序相反)压入该函数的任何参数来实现。通常这不会造成问题,因为程序通常不会进行太多连续的函数调用。但考虑一下在 startMenu() 函数的情况下会发生什么:每次光标移出菜单列表的末尾,一个新的函数调用都会被压入堆栈((4 字节函数指针) + (4 字节整数) = 8 字节)。你说这不是问题?让我问问你 - 如果用户只是按住上/下方向键,并让光标在菜单列表中滚动一段时间会发生什么?这 8 个字节将在每四个 startMenu() 调用后添加到堆栈中。因此,过了一会儿,系统堆栈将超出 PSP 上可用内存的大小(在厚机上为 32 MB,在薄机上为 64 MB)。当这种情况发生时,系统将崩溃并挂起。然后,用户必须重置其 PSP 才能恢复。现在,也许您认为这种情况不太可能发生,在这种情况下确实如此,但考虑一下如果您将更多/更大的参数压入堆栈,在类似情况下会发生什么。如果这迫使用户在对固件进行操作时重新启动其 PSP 会发生什么?它会使 PSP 变成砖头。
即使事情没有完全失控,像这样的代码仍然很慢,因为 PSP 的 MIPS32 CPU 使用所谓的寄存器窗口来存储函数调用和参数,而不是像 IA32 架构那样使用正统的堆栈。当寄存器窗口填满时,CPU 将将溢出部分转储到内存中,直到再次需要它为止。这是一个(相对)缓慢的操作,即使您的程序从未完全填满所有可用的 RAM。
关键是递归在逻辑上很优雅,但它是一个非常糟糕的实际解决方案。我建议,当光标移出菜单列表的末尾时,不要递归调用 startMenu() 函数,而是将 startMenu() 函数更改为返回一个无效的菜单位置(即 -1)。然后,在调用 startMenu() 函数的地方,代码应该检查返回值是否无效。如果是,则只需再次调用 startMenu()。例如
// we'll put these in a global struct for now, ideally we'd wrap this whole menu up in a class...
struct main_menu_attributes
{
char cursor[5] = "=->"; // What to use as our cursor
int cursorColor = ColorBlue;
int MinMenuop = 1;
int MaxMenuop = 4; // Number of options in our menu
} mm_attribs; // global variable of type main_menu_attributes
//
// renamed startMenu() to something more appropriate
//
int displayMenu (int argMenuop)
{
// Check to make sure an invalid option wasnt passed to startMenu()... and makes the list roll over
// never execute without a correct value to keep from displaying incorrect cursor positions
// Print the new menu
int cursorY = (Menuop*10)+110; // 110 appears to be the pixel height for the menu so that the cursor appears next to the menu (centered on screen)
// 10 appears to be the pixel height of a character/line of text, so multiplying the Menuop by 10 moves the cursor by
// an entire menu row at a time
clearScreen(ColorBlack);
printTextScreen(120, cursorY, cursor, cursorColor); // print the cursor first
printTextScreen(150, 120, "Start Game", ColorGreen); // print the first menu item's text to the frame buffer
printTextScreen(150, 130, "Load Game", ColorGreen); // print the second menu item's text to the frame buffer
printTextScreen(150, 140, "Multiplayer", ColorGreen); // print the third menu item's text to the frame buffer
printTextScreen(150, 150, "Password", ColorGreen); // print the fourth menu item's text to the frame buffer
sceDisplayWaitVblankStart(); // I have no idea what we're waiting for (if that's what we're doing here)
flipScreen(); // flipScreen() will put the stuff we just wrote onto the screen that the user is looking at
return 0;
}
//
// take input from the user. assuming the rest of the program code has been set up,
// the user can hit the 'HOME' button to leave the program and get back to the XMB
//
int startMenuCtrl ()
{
int command_code; // no need to waste CPU time initializing this since it will not be used until after it is set by calling getMenuInput()
// Start a control loop
while (1)
{
// display the screen
startMenu(Menuop);
// get user input
command_code = getMenuInput ();
// if the input is inappropriate (goes off the menu list) then loop it before displaying again
else if (Menuop < MinMenuop)
{
Menuop = MaxMenuop; // if the user overstepped in the up direction then loop back to the bottom
}
else;
// if the input is appropriate then the menu will be re-displayed as usual
// now we execute depending on what the user chose (if he chose anything at all. if not, then we stay here in this loop and wait)
executeCommand (command_code);
}
}
//
// this function will grab the user input and translate it for us into a simple integer (makes dealing with it easier)
//
int getMenuInput ()
{
int rtrn = 0; // we stick our return value in here
sceCtrlReadBufferPositive(&pad, 1);
if (pad.Buttons & PSP_CTRL_DOWN)
{
rtrn = 1;
}
if (pad.Buttons & PSP_CTRL_UP)
{
rtrn = 2;
}
/*
if (pad.Buttons & /*whatever button you want to use for "OK"*/)
{
// we won't touch Menuop because the user isn't moving the cursor
startMenuCtrlDelay ();
rtrn = /* whatever code you want to use for the button that was pushed */
}
// and so on and so forth for all the button handlers
*/
return rtrn; // the rtrn code we're returning will tell whoever called us what the user's input was
}
//
// moved all the command code into a nice, easy-to-read switch
//
int executeCommand (int command_code)
{
switch (command_code)
{
case 1: // user hit d-pad UP
{
Menuop--; // move the cursor up one
if (Menuop < mm_attribs.MinMenuop)
{
Menuop = mm_attribs.MaxMenuop; // if the user overstepped in the up direction then loop back to the bottom
}
else;
startMenuCtrlDelay (); // wait for a moment so that we don't take input faster than the user can let off of the d-pad
break;
}
case 2: // user hit d-pad DOWN
{
Menuop++; // move the cursor down one
if (Menuop > mm_attribs.MaxMenuop)
{
Menuop = mm_attribs.MinMenuop; // if the user overstepped in the up direction then loop back to the bottom
}
else;
startMenuCtrlDelay (); // wait
break;
}
/*
case /*your command code here*/:
{
// do stuff
break;
}
*/
default: // unhandled input- consider it homework
{
// print a warning message and continue as normal
break;
}
}
}
此代码中还有更多函数,但问题不在于函数调用的绝对数量,而在于函数调用的深度。另外,在 POSIX 系统上,main() 始终需要返回一个整数值。唯一不需要它的是 Windows 和 Visual Studio。