[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,功能完成!

参考

最後,这是专案连结


<<:  Day03基本架构(HTML)

>>:  IOS、Python自学心得30天 Day-9 模组训练改善-1

[区块链&DAPP介绍 Day10] Solidity 教学 - units and globally available variables-1

今日来介绍一些单位跟全域变数相关的东西 Ether Units 在任何数字後面加上 wei、gwei...

[Day6] 呼吸灯制作

1.前言 今天要介绍LED,并用LED灯制作出呼吸灯的效果,但是可能会有一些人会想说,那我使用昨天所...

C# 入门笔记04(继承)

物件导向 这单元主要是让大家了解物件导向的基本实作 物件导向有三大特性: 封装 继承 多型 封装 类...

如何在Windows 10中从磁碟机中删除锁定图标

Windows 10的档案总管中磁碟机上的锁定图标表示该磁碟机已使用BitLocker加密。Wind...

Day-5 现代电视游玩怀旧主机可接受的最低标准、最终方案 S 端子

S 端子(或称 S-Video)的 S 呢、指的是 Separate、也就是分离的意思。它的原理就只...