【LWJGL官方教程】輸入處理

原文:https://github.com/SilverTiger/lwjgl3-tutorial/wiki/Input
譯註:並沒有逐字逐句翻譯一切,只翻譯了自己覺得有用的部分。另外此翻譯僅供參考,請一切以原文爲準。代碼例子文件鏈接什麼的都請去原鏈接查找。括號裏的內容一般也是譯註,供理解參考用。總目錄傳送門:http://blog.csdn.net/zoharwolf/article/details/49472857

LWJGL3裏用GLFW處理輸入很容易。本教程就看看怎樣處理來自鍵盤、鼠標、手柄、搖桿的輸入。

Processing events 處理事件

我們開始前你得先清楚,LWJGL3裏需要調用glfwPollEvents()來獲取已經接收到的事件。
另一種方法是調用glfwWaitEvents(),它會一直線程休眠等待,直到收到一個事件,但是期間也可以調用glfwPostEmptyEvent()來喚醒線程。如果你想用GLFW做一個文字冒險遊戲的話,這個很好用。但是一般的實時遊戲不應該這樣空等待事件,所以我們還是應該在每幀輪詢(poll)事件。(也就是用glfwPollEvents()更正確)
另外你可能會用glfwSetInputMode(window, mode, value),mode是GLFW_CURSOR, GLFW_STICKY_KEYS or GLFW_STICKY_MOUSE_BUTTONS其中之一,如果是GLFW_STICKY_KEYS和GLFW_STICKY_MOUSE_BUTTONS的話,那value應該是GLFW_TRUE 或 GLFW_FALSE。如果是With GLFW_CURSOR,那value可以是GLFW_CURSOR_NORMAL, GLFW_CURSOR_HIDDEN 或GLFW_CURSOR_DISABLED。

Keyboard input 鍵盤輸入

Key input 按鍵輸入

對於鍵盤輸入,我們用 glfwGetKey(window, key)輪詢按鍵狀態,window就是窗口句柄,key是一個GLFW_KEY_<鍵名>這樣的常量,比如GLFW_KEY_W 或 GLFW_KEY_SPACE,你也可以通過獲取GLFW_KEY_UNKNOWN來處理一些特殊的按鍵比如E-mail鍵。通過調用glfwGetKey,你會得到鍵狀態,看它是GLFW_PRESS還是GLFW_RELEASE。

int state = glfwGetKey(window, GLFW_KEY_UP);
if (state == GLFW_PRESS) {
    moveUp();
}

但是輪詢有一個問題,如果在輪詢結束前按鍵就鬆開了,那麼就可能會錯過一個按過的鍵,有一個簡單的解決辦法,你可以用glfwSetInputMode(window, mode, value)來設置粘滯鍵。
如果我們設GLFW_STICKY_KEYS 爲GLFW_TRUE,就算提前放開,鍵狀態也將一直保持GLFW_PRESS直到你輪詢到此鍵。如果你想要知道某個鍵是否曾被按過,這很有用。但是如果想知道按鍵順序,那就沒什麼用了。

glfwSetInputMode(window, GLFW_STICKY_KEYS, GLFW_TRUE);

大部分情況,輪詢輸入已經夠了,但是推薦使用回調函數。
當一個物理按鍵被按下、鬆開、重複按時,按鍵回調函數將被調用,我們可以簡單地通過glfwSetKeyCallback(window, cbfun)來設置一個按鍵回調函數。
一般我們應該放一個函數指針在裏面,但是JAVA並沒有那種東西,不過LWJGL爲此提供了一個GLFWKeyCallback 類。需要對此回調函數建一個強引用,這樣它就不會被垃圾回收掉,所以得放一個private GLFWKeyCallback keyCallback在你的類中。

glfwSetKeyCallback(window, keyCallback = new GLFWKeyCallback() {

    @Overridepublic void invoke(long window, int key, int scancode, int action, int mods) {
        /* Do something */
    }
}

如果你用的是JAVA8,你可以用匿名函數表達式,因爲GLFWKeyCallback也提供了一個單獨的抽象方法

glfwSetKeyCallback(window, keyCallback = GLFWKeyCallback((window, key, scancode, action, mods) -> {
    /* Do something */
}));

你用哪個版本的都可以,現在來看看參數們。前兩個很明確,window是窗口句柄,key是那些GLFW_KEY_<鍵名>。
scancode是系統指定的按鍵掃描碼,僅在用GLFW_KEY_UNKNOWN時纔有用。
鍵狀態存儲在action裏,它將是GLFW_PRESS, GLFW_RELEASE or GLFW_REPEAT中的一個,mods是一個用來保存修飾鍵的位字段,它可能會包含GLFW_MOD_SHIFT, GLFW_MOD_CONTROL, GLFW_MOD_ALT and GLFW_MOD_SUPER
例如,你想要看是否ctrl+alt+F被按下,只需要像以下代碼這樣:

int ctrlAlt = GLFW_MOD_ALT | GLFW_MOD_CONTROL;

if ((mods & ctrlAlt) == ctrlAlt && key == GLFW_KEY_F && action == GLFW_PRESS) {
    System.out.println("Control + Alt + F was pressed!");
}

Text input 文本輸入

另一個你可能想要實現的,就是比如在文字冒險遊戲裏的那種文本輸入功能。
這個你只能用回調函數來實現了,沒有方法可以輪詢文本輸入。這裏我們使用GLFWCharCallback。

glfwSetCharCallback(window, charCallback = new GLFWCharCallback() {

    @Overridepublic void invoke(long window, int codepoint) {
        /* Do something */
    }
}

// Lambda expression for Java 8
glfwSetCharCallback(window, charCallback = GLFWCharCallback((window, codepoint) -> {
    /* Do something */
}));

用此回調函數,你可以收到一個輸入的每個字符的unicode字符碼,只需要把它們轉換成String,這很簡單。

System.out.println("Char typed: " + String.valueOf(Character.toChars(codepoint)));

// You could also do it with the String constructorSystem.out.println("Char typed: " + new String(Character.toChars(codepoint)));

如果你也想知道哪個修飾鍵被使用,你可以用GLFWCharModsCallback,跟字符回調函數相似,它會接收修飾鍵。

glfwSetCharModsCallback(window, charModsCallback = new GLFWCharModsCallback() {

    @Overridepublic void invoke(long window, int codepoint, int mods) {
        /* Do something */
    }
}

// Lambda expression for Java 8
glfwSetCharModsCallback(window, charModsCallback = GLFWCharModsCallback((window, codepoint, mods) -> {
    /* Do something */
}));

Clipboard input 剪貼板輸入

GLFW裏你甚至可以使用剪貼板,只需要調用 glfwGetClipboardString(window),設置剪貼板就和拿到剪貼板一樣容易。

String clipboard = glfwGetClipboardString(window);
glfwSetClipboardString(window, "Some new String");

Mouse input 鼠標輸入

Cursor modes 指針模式

跟鍵盤相似,我們也有兩種方法處理鼠標輸入。但那之前我們先看看不同的鼠標模式。
可以用glfwSetInputMode(window, GLFW_CURSOR, value)設置三種不同的鼠標模式。

  • GLFW_CURSOR_NORMAL 默認的模式
  • GLFW_CURSOR_HIDDEN 使鼠標在窗口上時不可見
  • GLFW_CURSOR_DISABLED 使鼠標只能在窗口上並且隱藏,當你希望用鼠標控制攝像機鏡頭時,這功能很有用。

另外,指針模式也可以使用粘滯鍵,它跟鍵盤輸入部分的粘滯鍵相似。

glfwSetInputMode(window, GLFW_STICKY_MOUSE_BUTTONS, GLFW_TRUE);

Button input 按鍵輸入

輪詢鼠標按鍵輸入幾乎和輪詢鍵盤輸入相同,調用glfwGetMouseButton(window, button),button值應是GLFW_MOUSE_BUTTON_<數字>, 範圍是從GLFW_MOUSE_BUTTON_1到GLFW_MOUSE_BUTTON_8或GLFW_MOUSE_BUTTON_LAST,後面倆是一樣的,另外還有左中右鍵。
跟鍵盤相似,狀態有GLFW_PRESS 和GLFW_RELEASE。

int state = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT);
if (state == GLFW_PRESS) {
    showContextMenu();
}

你應該已經知道了,輪詢時會漏掉按過的鍵,所以仍然要用回調函數。
看鼠標回調函數,它幾乎和鍵盤的完全一致,最大的不同在於,它沒有scancode參數,並且action只有GLFW_PRESS 和GLFW_RELEASE兩種,沒有重複按鍵,其他和鍵盤迴調函數都一樣。

glfwSetMouseButtonCallback(window, mouseButtonCallback = new GLFWMouseButtonCallback() {

    @Overridepublic void invoke(long window, int button, int action, int mods) {
        /* Do something */
    }
}

// Lambda expression for Java 8
glfwSetMouseButtonCallback(window, mouseButtonCallback = GLFWMouseButtonCallback((window, button, action, mods) -> {
    /* Do something */
}));

Cursor position 指針位置

當然,如果你做RTS遊戲之類的,你可能也想知道指針的位置。這個也可以靠輪詢或回調函數實現。
輪詢指針位置調用glfwGetCursorPos(window, xpos, ypos),此函數什麼也不會返回,因爲它會將屏幕座標值設置在xpos和ypos裏。一般這裏要用兩個引用參數(譯註:大概是指類似C++裏的按引用傳遞參數,在JAVA里程序員並不能顯示地標識這個),但是LWJGL我們用Buffer來代替。可以用ByteBuffer或DoubleBuffer,x值將在0和framebufferWidth之間,y值將在0和framebufferHeight之間,原點是屏幕左上角。

DoubleBuffer xpos = BufferUtils.createDoubleBuffer(1);
DoubleBuffer ypos = BufferUtils.createDoubleBuffer(1);
glfwGetCursorPos(window, xpos, ypos);

System.out.println("CursorPos: " + xpos.get() + "," + ypos.get());

如果你不想建個Buffer,你也可以用回調函數,只需要創建一個GLFWCursorPosCallback並將它分配給窗口。

glfwSetCursorPosCallback(window, cursorPosCallback = new GLFWCursorPosCallback() {

    @Overridepublic void invoke(long window, double xpos, double ypos) {
        /* Do something */
    }
}

// Lambda expression for Java 8
glfwSetCursorPosCallback(window, cursorPosCallback = GLFWCursorPosCallback((window, xpos, ypos) -> {
    /* Do something */
}));

Cursor enter events 指針進入事件

有時你想在指針進入或離開窗口時收到通知,在LWJGL和GLFW裏,可以用指針進入回調函數實現。你知道怎樣創建回調函數了,讓我們開始吧。

glfwSetCursorEnterCallback(window, cursorEnterCallback = new GLFWCursorEnterCallback() {

    @Overridepublic void invoke(long window, int entered) {
        if (entered == GLFW_TRUE) {
            /* Mouse entered */
        } else {
            /* Mouse left */
        }
    }
}

// Lambda expression for Java 8
glfwSetCursorEnterCallback(window, cursorEnterCallback = GLFWCursorEnterCallback((window, entered) -> {
    if (entered == GLFW_TRUE) {
        /* Mouse entered */
    } else {
        /* Mouse left */
    }
}));

entered值不是布爾而是整型,但是它只能有兩個值,進入窗口時是GLFW_TRUE ,離開窗口時是GLFW_FALSE

Scroll input 滾輪輸入

通過滾輪迴調函數可以獲取滾輪輸入,跟其他回調函數一樣,要有強引用,防被垃圾回收。

glfwSetScrollCallback(window, scrollCallback = new GLFWScrollCallback() {

    @Overridepublic void invoke(long window, double xoffset, double yoffset) {
        /* Do something */
    }
}

// Lambda expression for Java 8
glfwSetScrollCallback(window, scrollCallback = GLFWScrollCallback((window, xoffset, yoffset) -> {
    /* Do something */
}));

兩個offset是一樣的,如果你向上滾滾輪,將得到一個大於0的yoffset,向下滾就是小於0的。
注意,這可能對不同的鼠標來說略有區別。(MACBOOK的鼠標?)

Path dropping 拖拽路徑

另一個有趣的東西就是拖拽路徑回調函數,當你將一或多個文件拖拽到窗口上時,它會被調用。

glfwSetDropCallback(window, dropCallback = new GLFWDropCallback() {

    @Overridepublic void invoke(long window, int count, long names) {
        /* Do something */
    }
}
// Lambda expression for Java 8
glfwSetDropCallback(window, dropCallback = GLFWDropCallback((window, count, names) -> {
    /* Do something */
}));

前倆參數不言而喻,window是句柄,count值告訴你拖進窗口的有多少路徑。names值是一個指針指向一個數組,數組內是拖進來的文件UTF-8編碼的路徑名,如果你想要得到路徑名,你要用Callbacks類。

String[] pathNames = Callbacks.dropCallbackNamesString(count, names);
for (int i = 0; i < count; i ++) {
    System.out.println(pathNames[i]);
}

你僅能在GLFWDropCallback回調函數中使用此方法。

Cursor objects 指針對象

一些程序有它們自己的指針,用LWGJL和GLFW你也可以實現,你可以創建一些標準指針或者根據圖片建一些自定義的指針。
創建標準指針很容易,只需要用glfwCreateStandardCursor(shape),shape可以是以下值:

  • GLFW_ARROW_CURSOR: 通常的指針。跟默認指針相似。
  • GLFW_IBEAM_CURSOR: 文本輸入時的指針。
  • GLFW_CROSSHAIR_CURSOR: 十字準星型指針。
  • GLFW_HAND_CURSOR: 手型指針。
  • GLFW_HRESIZE_CURSOR: 橫向縮放指針。
  • GLFW_VRESIZE_CURSOR: 縱向縮放指針。

創建指針後還要設置它,如果你想,可以讓不同的窗口有不同的指針。

long cursor = glfwCreateStandardCursor(GLFW_CROSSHAIR_CURSOR);
glfwSetCursor(window, cursor);

但是你可能想創建一個自定義指針,爲此你得用ImageIO讀取圖片並轉換成ByteBuffer,然後用GLFW創建。我們在創建紋理的時候已經做過了

InputStream in = new FileInputStream("cursor.png");
BufferedImage image = ImageIO.read(in);

int width = image.getWidth();
int height = image.getHeight();

int[] pixels = new int[width * height];
image.getRGB(0, 0, width, height, pixels, 0, width);

ByteBuffer buffer = BufferUtils.createByteBuffer(width * height * 4);
for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
        int pixel = pixels[y * imageWidth + x];
        /* Red component */
        buffer.put((byte) ((pixel >> 16) & 0xFF));
        /* Green component */
        buffer.put((byte) ((pixel >> 8) & 0xFF));
        /* Blue component */
        buffer.put((byte) (pixel & 0xFF));
        /* Alpha component */
        buffer.put((byte) ((pixel >> 24) & 0xFF));
    }
}
buffer.flip();

注意這次你圖片數據是RGBA格式,原點在左上角。現在爲了用圖片數據,我們需要用GLFWimage類分配內存。之後我們就可以創建GLFW指針了。

ByteBuffer glfwImage = GLFWimage.malloc(width, height, buffer);

long cursor = glfwCreateCursor(glfwImage, xhot, yhot);
glfwSetCursor(window, cursor);

xhot和yhot兩個值指定了指針熱點,窗口就是用此點來跟蹤指針的座標的。
如果你想用默認指針,只需要把它設成NULL。

glfwSetCursor(window, NULL);

程序結束時,你不再需要指針,應該銷燬它。

glfwDestroyCursor(cursor);

Controller input 手柄輸入

手柄沒有回調函數,所以只能輪詢。在GLFW裏,它們被稱爲搖桿(joystick),但是也可以用那些方法處理手柄(gamepad)。

用手柄之前,先應該檢查它有沒有插上,需要調用glfwJoystickPresent(joy),joy值應該是GLFW_JOYSTICK_<數字>格式,從GLFW_JOYSTICK_1到GLFW_JOYSTICK_LAST或GLFW_JOYSTICK_16。如果插入了手柄,此方法將返回GLFW_TRUE,否則返回GLFW_FALSE。

int present = glfwJoystickPresent(GLFW_JOYSTICK_1);
if (present == GLFW_TRUE) {
    System.out.println("Controller 1 is present!);}

Button states 按鍵狀態

現在假如手柄已插入,我們可以用glfwGetJoystickButtons(joy)取按鍵狀態,它將得到手柄上每個鍵的狀態,GLFW_PRESS或GLFW_RELEASE。用LWJGL,它將返回給你一個ByteBuffer,你可以從中查出每個鍵的狀態,但是對於不同的手柄,按鍵ID可能不同。

ByteBuffer buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1);

int buttonID = 1;
while (buttons.hasRemaining()) {
    int state = buttons.get();
    if (state == GLFW_PRESS) {
        System.out.println("Button " + buttonID + " is pressed!");
    }
    buttonID++;
}

Axis states 軸狀態

想取到搖桿軸值,用glfwGetJoystickAxes(joy),它將得到每個軸的狀態,狀態值是FloatBuffer的形式,範圍在-1.0和1.0之間。通常你並不會得到0,而是得到一個很小的浮點數,比如-1.5258789E-5,也就是-0.0000015258789,有時你就算按到底也得不到1.0。因此,用手柄時,你應該使用一個闕值,而不是簡單的值相等判斷。

FloatBuffer axes = glfwGetJoystickAxes(GLFW_JOYSTICK_1);

int axisID = 1;
while (axes.hasRemaining()) {
    float state = axes.get();
    if (state < -0.95f || state > 0.95f) {
        System.out.println("Axis " + axisID + " is at full-range!");
    } else if (state < -0.5f || state > 0.5f) {
        System.out.println("Axis " + axisID + " is at mid-range!");
    }
    axisID++;
}

Controller name 手柄名稱

如果想獲取手柄名稱,要用glfwGetJoystickName(joy)

String name = glfwGetJoystickName(GLFW_JOYSTICK_1);
System.out.println("Controller name is " + name + "!");

本教程的最後一課,將要講遊戲邏輯。

發佈了13 篇原創文章 · 獲贊 9 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章