記得在初學的時候,任何教材都會說"把功能包進函式(function)內,以便日後不用在寫這麼長一串"之類的。在這之後就沿生出了一堆命名規則與實作上的SPEC,方便在跟別人進行協作時,更快知道別人在寫甚麼,雖然這個2D Platformer全程只會有我一人製作,但既使只有一人,但訂下規範也是極其重要的。
今天要來說明(實作),"我"會怎麼規劃命名以及文件的擺放。
以下出在這個專案平常本人在寫的時候,會用的格式
由於C語言沒有命名空間(namespace),防止相同名稱的函數撞車,常會看到這樣的命名glfwXXXX
、SDL_XXXX
用模組的名稱作為變數或函式的前綴,達到命名空間的功能。
通常我會有一個習慣,不一定是最好的。將一段功能拆到函式內時,儘量只會將功能拆一到兩層,避免層層的call stack,避免閱讀或debug時,要一層層的往下找。
我把昨天寫的範例切到iron_window.h
和iron_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;
}
昨天在使用範例的時候,看到有一段:
...
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離開遊戲的功能,透關官方的文件,除了寫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,功能完成!