跳轉到內容

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。


[編輯 | 編輯原始碼]
華夏公益教科書