跳转到内容

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。


[编辑 | 编辑源代码]
华夏公益教科书