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。