iT邦幫忙

2021 iThome 鐵人賽

DAY 2
0
自我挑戰組

翻車機率極高的2D平台遊戲(2D Platformer)製作系列 第 2

[Day2] 命名規則與組織寫好的功能

今日目標

  • 模組化視窗生成的功能
  • 點擊ESC離開視窗

規則與SPEC

記得在初學的時候,任何教材都會說"把功能包進函式(function)內,以便日後不用在寫這麼長一串"之類的。在這之後就沿生出了一堆命名規則與實作上的SPEC,方便在跟別人進行協作時,更快知道別人在寫甚麼,雖然這個2D Platformer全程只會有我一人製作,但既使只有一人,但訂下規範也是極其重要的。

今天要來說明(實作),"我"會怎麼規劃命名以及文件的擺放。

以下出在這個專案平常本人在寫的時候,會用的格式

  • 變數: my_variable
  • 常數 或 唯一變數: CONST_VARIABLE
  • 類別: StructType
  • 列舉: EnumType
  • 函式(function)、巨集函式(Marcro Function): MyFunction
  • 文件命名: iron_file_name.h、iron_file_name.c

由於C語言沒有命名空間(namespace),防止相同名稱的函數撞車,常會看到這樣的命名glfwXXXXSDL_XXXX用模組的名稱作為變數或函式的前綴,達到命名空間的功能。

拆分視窗生成的功能

通常我會有一個習慣,不一定是最好的。將一段功能拆到函式內時,儘量只會將功能拆一到兩層,避免層層的call stack,避免閱讀或debug時,要一層層的往下找。

我把昨天寫的範例切到iron_window.hiron_window.c裡面,並且將一些公用的類別與enum放在iron_types.h裡面

iron_types.h

#ifndef _IRON_TYPES_H_
#define _IRON_TYPES_H_

typedef enum ResultType {
    RES_SUCCESS                     = 0,

    RES_ERROR_CREATE_WINDOW         = 1,
} ResT;

#endif // _IRON_TYPES_H_

iron_window.h

#ifndef _IRON_WINDOW_H_
#define _IRON_WINDOW_H_

#include "iron_types.h"

// [iron_window] create window
ResT CreateWindow(int w, int h, const char* title);

// [iron_window] release window
void ReleaseWindow(void);

// [iron_window] check is window should be close or not
int IsWindowRunning(void);

// [iron_window] close window
void CloseWindow(void);

// [iron_window] first state in game loop, refresh the scene
void StartScene();

// [iron_window] last state in game loop, store scene data in this frame
void EndScene();

#endif // _IRON_WINDOW_H_

iron_window.c

#include "iron_window.h"

#include <stdio.h>

#include "GLFW/glfw3.h"

// static struct: 
// -- only show in this module, can be seen as 'private' variable.
static struct {
    GLFWwindow* wnd;
} WINDOW;

// ---------------------------------- //
//      Functions implementaion       //
// ---------------------------------- //

// [iron_window] create window
ResT CreateWindow(int w, int h, const char* title) {
    if (!glfwInit()) {
        printf("Failed to init glfw.");
        return RES_ERROR_CREATE_WINDOW;
    }

    WINDOW.wnd = glfwCreateWindow(w, h, title, NULL, NULL);
    if (WINDOW.wnd == NULL) {
        printf("Failed to create glfw window.");
        return RES_ERROR_CREATE_WINDOW;
    }

    glfwMakeContextCurrent(WINDOW.wnd);

    glfwSetKeyCallback(WINDOW.wnd, WindowKeyCallback);      // handle key input

    return RES_SUCCESS;
}

// [iron_window] release window
void ReleaseWindow() {
    if (WINDOW.wnd != NULL) {
        glfwDestroyWindow(WINDOW.wnd);
    }
    glfwTerminate();
}

// [iron_window] check is window should be close or not
int IsWindowRunning(void) {
    return !glfwWindowShouldClose(WINDOW.wnd);
}

// [iron_window] close window
void CloseWindow(void) {
    glfwSetWindowShouldClose(WINDOW.wnd, 1);
}

// [iron_window] first state in game loop, refresh the scene
void StartScene() {
}

// [iron_window] last state in game loop, store scene data in this frame
void EndScene() {
    glfwSwapBuffers(WINDOW.wnd);
    glfwPollEvents();
}

最後在main(iron_main.c)文件裡面,改寫成這樣:

#include "iron.h"

#define WND_W 800
#define WND_H 600

int main() {

    // Init the window
    ResT res = CreateWindow(WND_W, WND_H, "Little Iron");
    if (res == RES_ERROR_CREATE_WINDOW) {
        ReleaseWindow();
        return RES_ERROR_CREATE_WINDOW;
    }

    // Game loop
    while (IsWindowRunning()) {
        StartScene();


        EndScene();
    }

    ReleaseWindow();

    return 0;
}

小補充: GLFW的Context Object

昨天在使用範例的時候,看到有一段:

...
glfwMakeContextCurrent(window);
...

蠻多專案都會看到所謂context一詞,好在,官方文件就有直接寫:

Context objects
A window object encapsulates both a top-level window and an OpenGL or OpenGL ES context. It is created with glfwCreateWindow and destroyed with glfwDestroyWindow or glfwTerminate. See Window creation for more information.

看來是封裝各平台與不同版本OpenGL的物件。

按下ESC離開

最後,我想要加上一個簡單的點擊esc離開遊戲的功能,透關官方的文件,除了寫Game Loop寫

if Press(KEY_ESC)
    CloseWindow()

之類的,GLFW有提供所有輸入裝置的管理callback


// iron_wiondow.c

static void WindowKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) {
    // NOTE: close window by esacpe key
    if (action == GLFW_PRESS) {
        if (key == GLFW_KEY_ESCAPE) {
            CloseWindow();
        }
    }
}

// 在`CreateWindow`下方新增...

....
glfwSetKeyCallback(WINDOW.wnd, WindowKeyCallback);      // handle key input
....

然後,編譯,建置,開啟執行檔,點擊ESC,功能完成!

參考

最後,這是專案連結


上一篇
[Day1] 專案建置與視窗生成
下一篇
[Day3] 驅動OpenGL
系列文
翻車機率極高的2D平台遊戲(2D Platformer)製作33

尚未有邦友留言

立即登入留言