跳轉至內容

PSP 開發/使用者輸入

100% developed
來自 Wikibooks,為開放世界提供開放書籍

教程分析

[編輯 | 編輯原始碼]

本文建立在上一篇文章的基礎上,Hello World 應用。建議您從該文章開始並理解其中的程式碼。本節的具體目標是介紹如何使用 PSP 的使用者輸入(UI) 系統。

UI 系統概述

[編輯 | 編輯原始碼]

PSPSDK 庫提供了硬體和程式之間的介面。此介面查詢硬體並返回結果。然後使用AND運算子(&)和表示按鈕的數字來檢查此結果。由於位對齊的方式,返回的結果可以被視為布林位,因此是 AND 運算子的目的。此邏輯的一個示例是 1010 可以是按鈕 1 被按下,按鈕 2 未被按下,按鈕 3 被按下,按鈕 4 未被按下。PSP 系統有兩種檢查狀態的方法:鎖存方法和狀態方法。

UI 檢查的典型流程是

  1. 初始化取樣
  2. 查詢硬體以獲取操縱桿資訊
  3. 查詢硬體以獲取按鍵資訊
  4. 利用按鍵和操縱桿資訊

本節詳細介紹了所需檔案結構的補充內容。這些更改是強制性的,不應跳過。雖然可以重新命名,但可能會導致頭痛。

資料夾

[編輯 | 編輯原始碼]

在開始之前,從上一篇文章中複製 hello_world 專案資料夾並將其重新命名為 user_input 將節省時間。丟棄目標檔案、EBOOT.PBP 和 elf 檔案是安全的 - 它們目前沒有用。丟棄專案/common 中的目標檔案沒有必要。

更新 Makefile 是第一步。更改專案以適應我們的新環境是一種良好的做法。

  1. 將 TARGET 更改為 user_input
  2. 將 PSP_EBOOT_TITLE 更改為 UserInput
  3. 在 OBJS 下有 main.o 和 ../common/callback.o
  4. 在 main.c 內部,將 PSP_MODULE_INFO() 呼叫中的字串切換為 UserInput
  5. 清空 main.c 中除以下基本模板以外的所有內容

main.c

int main(int argc, char** argv)
{
	// basic init
	setupExitCallback();
	pspDebugScreenInit();

	while (isRunning()) {
		pspDebugScreenSetXY(0, 0);
		// empty
		sceDisplayWaitVblankStart();
	}
	// close out
	sceKernelExitGame();	
	return 0;
}

使用者輸入是常見模組的完美示例,因為另一篇文章已經進行了說明。這兩個檔案將在所有需要透過控制器進行使用者輸入的應用程式中使用。

  1. 導航到 common 資料夾
  2. 建立一個名為 ui.c 和 ui.h 的編譯單元
  3. 在 ui.c 中包含 ui.h
  4. 在 main.c 中包含 ui.h
  5. 轉到專案 Makefile 並將 ../common/ui.o 附加到 OBJS 變數

以下是這些檔案應包含的內容。

ui.c

#include "ui.h"

ui.h

#ifndef UI_H
#define UI_H

#endif

查詢硬體

[編輯 | 編輯原始碼]

接下來的任務是實現方法。任何與控制器相關的內容都需要包含 pspctrl.h。獲取硬體資訊後,下一步是處理資訊,利用 ui 編譯單元。

術語

  • 按鍵保持 - 按鍵按下/釋放每次狀態切換觸發一次,即按鍵按下 - 按鍵抬起
  • 按鍵鎖存 - 按住按鍵會每幀觸發恆定動作,即按鍵按下 - 按鍵按住

可以獲取兩組資訊 - 保持或鎖存。鎖存方法具有保持方法資訊所擁有的按鍵資訊。新增保持方法只會帶來操縱桿和時間戳資訊。本文將演示兩種方法。

按鍵保持

[編輯 | 編輯原始碼]
  1. 在 pspctrl.c 中包含 pspctrl.h
  2. 新增一個變數(結構體)SceCtrlData data
  3. 新增函式 int getJX() 用於操縱桿 X
  4. 新增函式 int getJY() 用於操縱桿 Y
  5. 新增函式 void pollPad() 用於查詢硬體

ui.c

#include <pspctrl.h>

static SceCtrlData data;

int getJX() {

}

int getJY() {

}

void pollPad() {

}

ui.h

int getJX();
int getJY();

void pollPad();

pollPad() 的目的是每幀呼叫一次查詢硬體。在嘗試檢查使用者輸入之前必須查詢硬體,並確保沒有使用舊資料。用於將資料從硬體讀入 SceCtrlData 的函式是 sceCtrlReadBufferPositive(),它接受 SceCtrlData 的記憶體地址和所需的緩衝區讀取量。只需要讀取一次。有兩種不同的硬體輪詢函式:讀取和窺視。sceCtrlPeekBufferPositive() 不會彈出使用者輸入資訊。使用 sceCtrlPeekBufferPositive(),可以在多個編譯單元中分別檢查使用者輸入,每次都輪詢硬體。窺視和讀取函式的一種用途是實現撤消功能,或在遊戲動作中根據按鈕順序進行依賴。

ui.c

void pollPad() {
	sceCtrlReadBufferPositive(&data, 1);
}

SceCtrlData 結構體具有值:int TimeStamp、int Buttons、int Lx 和 int Ly。TimeStamp 用於告知當前讀取的幀,本文未利用它。Buttons 是一個整數,包含任何當前按下的按鍵的位布林值,本文未利用它,因為利用了鎖存方法。Lx 和 Ly 變數分別與 getJX() 和 getJY() 函式配對,因為這些函式公開了操縱桿資訊。LX 和 Ly 是帶符號的 char 長度數字,不保證完全處於中間。中間是 128 X 和 128 Y。

ui.c

int getJX() {
	return data.Lx;
}

int getJY() {
	return data.Ly;
}

按鍵鎖存

[編輯 | 編輯原始碼]

根據程式的不同,按鍵鎖存是必不可少的元件。此方法使用一個名為 SceCtrlLatch 的不同結構體。SceCtrlData 將是 ui 編譯單元的另一個靜態成員,pollLatch() 將從硬體中讀取。SceCtrlLatch 累積地儲存關於按鈕的資訊,作為 SceCtrlData 中的位布林值數字 - 例如,兩個按鈕按下可能看起來像 1100。

ui.c

#include <pspctrl.h>

static SceCtrlLatch latch;

void pollLatch() {

}

ui.h

void pollLatch();

雖然 sceCtrlReadBufferPositive() 用於保持,但 sceCtrlReadLatch() 用於鎖存。鎖存只需要一個引數 - 對鎖存資料結構的引用。鎖存只有幾個成員:uiMake、uiBreak、uiPress 和 uiRelease。

void pollLatch() {
	sceCtrlReadLatch(&latch);
}

為了檢查按鍵是否被按下,需要將位布林值與要檢查的按鍵進行 AND 運算。如果結果為 1,則表示正在檢查的按鍵處於按下狀態,否則為 0,表示處於抬起狀態 - 口語上說,"按鍵是否按下" 為 true,"按鍵是否抬起" 為 false。

觀察 sceCtrlReadBufferPositive()(二進位制)模式

0100 // 位布林值
0001 & // AND 按鍵-0001
0000 // 按鍵-0001 未按下
0101 // 位布林值
0001 & // AND 按鍵-0001
0001 // 按鍵-0001 已按下

需要一個輔助函式來檢查保持方法輸入,因為該結構體在 main.c 中不可見。需要訪問 SceCtrlLatch 成員。在 C 語言中,0 為 false,1 為 true,用於布林檢查。鎖存具有 uiPress 和 uiRelease 欄位。uiPress 是位布林值,表示當前按下的按鍵。uiRelease 是 -1 - uiPress。目前只有 uiPress 相關。該函式請求一個按鍵,並將其與當前按住的按鍵進行 AND 運算,以生成按鍵保持方法。一個合適的函式將是 isKeyHold()。

ui.c

int isKeyHold(int key) {
	return latch.uiPress & key;
}

ui.h

int isKeyHold(int key); // returns 1(true) if key is down

對於閂鎖方法,使用 uiMake 和 uiBreak 欄位。當任意數量的鍵同時按下時,uiMake 將從 0 更新到僅在那一幀按下所有鍵的位布林值。當任意數量的鍵同時釋放時,uiBreak 將從 0 更新到僅在那一幀釋放所有鍵的位布林值。如果鍵 1011 同時按下,uiMake 將為 1011,在下一幀,如果沒有任何其他鍵按下,則為 0000。如果在下一幀僅釋放鍵 0010,uiBreak 將為 0010,並且在下一幀,如果沒有任何其他鍵釋放,則為 0000。如果在下一幀同時釋放鍵 1001,uiBreak 將為 1001。適當的函式將是 isKeyDown() 和 isKeyUp()。

ui.c

int isKeyDown(int key) {
	return (latch.uiMake & key);
}

int isKeyUp(int key) {
	return (latch.uiBreak & key);
}

ui.h

int isKeyDown(int key);
int isKeyUp(int key);

綜合概述

[編輯 | 編輯原始碼]

一個簡單的示例任務是當按下某個鍵時,它將列印一條訊息 - 說明按下的鍵是處於按下狀態,在一幀內是處於釋放狀態,還是在一幀內是處於按下狀態。以下是可檢查的可能鍵的列表。雖然列表並不完全詳盡,但某些鍵只能在核心模式下檢查。強烈建議避免使用核心模式。

  • PSP_CTRL_SELECT
  • PSP_CTRL_START
  • PSP_CTRL_UP
  • PSP_CTRL_RIGHT
  • PSP_CTRL_DOWN
  • PSP_CTRL_LEFT
  • PSP_CTRL_LTRIGGER
  • PSP_CTRL_RTRIGGER
  • PSP_CTRL_TRIANGLE
  • PSP_CTRL_CIRCLE
  • PSP_CTRL_CROSS
  • PSP_CTRL_SQUARE

需要呼叫兩個函式,它們會編輯取樣週期和取樣模式。這兩個函式是 sceCtrlSetSamplingCycle() 和 sceCtrlSetSamplingMode()。在進行任何使用者輸入檢查之前,需要在程式開頭,在主迴圈之前呼叫這兩個函式。sceCtrlSetSamplingCycle() 接收一個 int,通常為 0。sceCtrlSetSamplingMode() 接收一個列舉型別,通常為 PSP_CTRL_MODE_ANALOG。

main.c

	sceCtrlSetSamplingCycle(0);
	sceCtrlSetSamplingMode(PSP_CTRL_MODE_ANALOG);

為了演示這些函式,從硬體中輪詢當前的鍵狀態。列印十字鍵是處於按下狀態還是處於釋放狀態,是否按住圓圈鍵,以及當前的操縱桿軸位置。

main.c

	while (isRunning()) {
		sceDisplayWaitVblankStart();
		pspDebugScreenClear();
		pspDebugScreenSetXY(0, 0);
		pollPad();
		pollLatch();
		if(isKeyDown(PSP_CTRL_CROSS))
			printf("Cross is down!\n");
		if(isKeyUp(PSP_CTRL_CROSS))
			printf("Cross is up!\n");
		if(isKeyHold(PSP_CTRL_CIRCLE))
			printf("Circle is down!\n");
		printf("%d,%d", getJX(), getJY());
	}
TARGET = user_input
OBJS = main.o ../common/callback.o ../common/ui.o

INCDIR = 
CFLAGS = -O2 -G0 -Wall
CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti
ASFLAGS = $(CFLAGS)

LIBDIR =
LIBS = 
LDFLAGS =

EXTRA_TARGETS = EBOOT.PBP
PSP_EBOOT_TITLE = UserInput

PSPSDK=$(shell psp-config --pspsdk-path)
include $(PSPSDK)/lib/build.mak

透過將 Windows 命令列或終端開啟到專案目錄並執行 make EBOOT.PBP 來構建。在 PPSSPP 中啟動 EBOOT.PBP。在輸入成為可能之前,PPSSPP 可能需要將控制器對映配置到鍵盤。

工作示例

[編輯 | 編輯原始碼]


華夏公益教科書