天天看點

HaaS EDU場景式應用學習 - 飛機大戰實驗介紹涉及知識點開發環境準備遊戲設定遊戲實作實作效果開發者支援

實驗介紹

飛機大戰作為一款經典的街機遊戲,是很多人的童年回憶。我們的 HaaS EDU K1

開發闆專門設計了街機樣式的按鍵排列,很适合我們做這類遊戲的開發。

HaaS EDU場景式應用學習 - 飛機大戰實驗介紹涉及知識點開發環境準備遊戲設定遊戲實作實作效果開發者支援

涉及知識點

OLED繪圖
按鍵事件
           

開發環境準備

硬體

開發用電腦一台
HAAS EDU K1 開發闆一塊
USB2TypeC 資料線一根
           

軟體

AliOS

Things開發環境搭建

開發環境的搭建請參考 @ref HaaS_EDU_K1_Quick_Start (搭建開發環境章節),其中詳細的介紹了AliOS Things 3.3的IDE內建開發環境的搭建流程。
           

HaaS EDU

K1 DEMO 代碼下載下傳

HaaS EDU K1 DEMO 的代碼下載下傳請參考 @ref HaaS_EDU_K1_Quick_Start (建立工程章節),其中,
選擇解決方案: 基于教育開發闆的示例
選擇開發闆: haaseduk1 board configure
           

代碼編譯、燒錄

參考 @ref HaaS_EDU_K1_Quick_Start (3.1 編譯工程章節),點選 ✅ 即可完成編譯固件。
參考 @ref HaaS_EDU_K1_Quick_Start (3.2 燒錄鏡像章節),點選 "⚡️" 即可完成燒錄固件。
           

遊戲設定

不同于規則簡單的貪吃蛇,在飛機大戰這類遊戲中,往往需要對遊戲中出現的每個對象進行數值、行為的設定。在開發遊戲前期,梳理好這些設定也有助于我們更清晰地進行開發。有時,優秀的設定也是吸引玩家的重要因素。

角色設定

HaaS EDU場景式應用學習 - 飛機大戰實驗介紹涉及知識點開發環境準備遊戲設定遊戲實作實作效果開發者支援
HaaS EDU場景式應用學習 - 飛機大戰實驗介紹涉及知識點開發環境準備遊戲設定遊戲實作實作效果開發者支援
HaaS EDU場景式應用學習 - 飛機大戰實驗介紹涉及知識點開發環境準備遊戲設定遊戲實作實作效果開發者支援
HaaS EDU場景式應用學習 - 飛機大戰實驗介紹涉及知識點開發環境準備遊戲設定遊戲實作實作效果開發者支援

行為設定

- 在本遊戲中,玩家将控制阿克琉斯級戰艦,在持續不斷的敵機中通過閃避或攻擊開辟出自己的路。
- 玩家可以通過 HaaS EDU K1 的四個按鍵控制 阿克琉斯級戰艦 進行前後左右運動。
- 在遊戲進行過程中,玩家的戰艦會不斷發射炮彈。被炮彈攻擊的敵方戰艦會損失響應的裝甲。
- 若玩家戰艦被敵方戰艦撞擊,雙方均會損失裝甲。
- 玩家有三次緊急修複戰艦的機會。
           

遊戲實作

遊戲流程

在開始之前,我們先使用一個簡單的流程,幫助大家了解本遊戲的重新整理機制。這個大循化即遊戲重新整理所需要的所有流程。

// 遊戲中所有對象的更新判定由大循環維護
void aircraftBattle_task()
{
    while (1)
    {
        OLED_Clear();        // 清理螢幕資料
        global_update();    // 重新整理全局對象 如更新對象的貼圖狀态 發射子彈 撞擊判斷 等
        global_draw();        // 繪制重新整理完後的所有對象
        OLED_Refresh_GRAM();// 将繪制結果顯示在螢幕上
        aos_msleep(40);        // 40ms 為一個遊戲周期
    }
}
           
HaaS EDU場景式應用學習 - 飛機大戰實驗介紹涉及知識點開發環境準備遊戲設定遊戲實作實作效果開發者支援

貼圖實作

對于每個對象,我們希望能夠将其定位到遊戲地圖上的每一點,而不是單純使用貼圖函數。是以,每個對象有一個“控制坐标”,而我們相對這個“控制坐标”計算出貼圖坐标。這樣,如果一個對象需要變換不同尺寸的貼圖,我們可以更友善地計算出它的貼圖坐标。

如圖,紅色為該對象的控制坐标,藍色為該貼圖的貼圖坐标。

HaaS EDU場景式應用學習 - 飛機大戰實驗介紹涉及知識點開發環境準備遊戲設定遊戲實作實作效果開發者支援
HaaS EDU場景式應用學習 - 飛機大戰實驗介紹涉及知識點開發環境準備遊戲設定遊戲實作實作效果開發者支援
typedef struct
{
    map_t *map;                // 貼圖
    int cur_x; 
    int cur_y;                // 飛行物對象的控制坐标
} dfo_t;                      // 飛行物對象
           
/*
    -> x
    ____________________
    |         |    icon|
 |  |       of_y       |
 \/ |         |        |
  y |--of_x--cp        |
    |__________________|
*/

typedef struct
{
    icon_t *icon;    // 貼圖對象
    int offset_x;
    int offset_y;    // 相對于控制坐标的偏移
} map_t; // 貼圖
           
注意⚠️,在開發過程中,我們使用的是豎屏模式,坐标系是以豎屏做處理。是以,在繪圖時,我們需要做坐标系的轉換。
           
HaaS EDU場景式應用學習 - 飛機大戰實驗介紹涉及知識點開發環境準備遊戲設定遊戲實作實作效果開發者支援
void draw_dfo(dfo_t *dfo)
{
    map_t *cur_map = get_cur_map(dfo);    // 擷取目前對象的貼圖
    
    // 計算對象邊界
    int top = dfo->cur_y + cur_map->offset_y;    
    int bottom = dfo->cur_y + cur_map->offset_y + cur_map->icon->width;
    int left = dfo->cur_x + cur_map->offset_x;
    int right = dfo->cur_x + cur_map->offset_x + cur_map->icon->height;
    
    // 若對象超出螢幕 則不繪制
    if (top > 132 || bottom < 0 || left > 64 || right < 0)
        return;

    // 繪制坐标轉換後的貼圖對象
    OLED_Icon_Draw(
        dfo->cur_y + cur_map->offset_y,
        64 - (dfo->cur_x + cur_map->offset_x + cur_map->icon->height),
        cur_map->icon,
        2);
}
           
這樣,就可以實作在OLED上繪制我們設定的戰艦圖檔了。
           
HaaS EDU場景式應用學習 - 飛機大戰實驗介紹涉及知識點開發環境準備遊戲設定遊戲實作實作效果開發者支援

移動戰艦

接下來,我們要實作的是根據使用者的按鍵輸入來移動戰艦的貼圖。在此之前,我們需要對

dfo_t 結構體進行更多的補充。我們額外定義一個 speed

屬性,用于定義在使用者每次操作時移動一定的距離。

注意,這裡的前後左右均是在遊戲坐标系中。

typedef struct
{
    // 艦船坐标
    int cur_x; // 運動
    int cur_y;
    // 艦船速度
    uint8_t speed;      // 絕對固定
    // 艦船貼圖
    map_t *map;
} dfo_t;                   // Dentified Flying Object
           
typedef enum
{
    UP,
    LEFT,
    RIGHT,
    DOWN
} my_craft_dir_e_t;

void move_MyCraft(dfo_t *my_craft, my_craft_dir_e_t dir)
{
    // 擷取艦船目前的貼圖對象
    map_t *cur_map = get_cur_map(my_craft);
    // 計算貼圖邊界
    int top = my_craft->cur_y + cur_map->offset_y;
    int bottom = my_craft->cur_y + cur_map->offset_y + cur_map->icon->width;
    int left = my_craft->cur_x + cur_map->offset_x;
    int right = my_craft->cur_x + cur_map->offset_x + cur_map->icon->height;
    // 判斷方向
    switch (dir)
    {
    case UP:
        // 如果這次移動不會超過地圖邊界 則移動
        if (!(top - my_craft->speed < 0))
            my_craft->cur_y -= my_craft->speed;
        break;
    case DOWN:
        if (!(bottom + my_craft->speed > 132))
            my_craft->cur_y += my_craft->speed;
        break;
    case LEFT:
        if (!(left - my_craft->speed < 0))
            my_craft->cur_x -= my_craft->speed;
        break;
    case RIGHT:
        if (!(right + my_craft->speed > 64))
            my_craft->cur_x += my_craft->speed;
        break;
    default:
        break;
    }
}
           

将按鍵回調函數關聯至移動艦船函數。注意,這裡的前後左右均是在遊戲坐标系中。

void aircraftBattle_key_handel(key_code_t key_code)
{
    switch (key_code)
    {
    case EDK_KEY_4:
        move_MyCraft(my_craft, LEFT);
        break;
    case EDK_KEY_1:
        move_MyCraft(my_craft, UP);
        break;
    case EDK_KEY_3:
        move_MyCraft(my_craft, DOWN);
        break;
    case EDK_KEY_2:
        move_MyCraft(my_craft, RIGHT);
        break;
    default:
        break;
    }
}
           
HaaS EDU場景式應用學習 - 飛機大戰實驗介紹涉及知識點開發環境準備遊戲設定遊戲實作實作效果開發者支援

加一點特效

作為一個注重細節,精益求精的開發者,我們希望給我們的艦船加上一些特效。而這需要艦船對象不斷改變重繪自己的貼圖。為了這個功能,我們額外建立了一個新的結構體用于管理“動畫”。

typedef struct
{
    map_t **act_seq_maps;             // 貼圖指針數組 該動畫的所有貼圖(例如爆炸動作包含3幀)
    uint8_t act_seq_len;              // 貼圖指針數組長度
    uint8_t act_seq_index;            // 用于索引幀
    
    uint8_t act_seq_interval;         // 幀間延遲
    uint8_t act_seq_interval_cnt;     // 用于延遲計數
    
    uint8_t act_is_destory;            // 用于标記該動畫是否是毀滅動畫 若是則不再重複
} act_seq_t;
           

同時,每個艦船對象新增了一系列屬性 act_seq_type,

用于顯示目前的貼圖狀态。例如,當 act_seq_type = 0

時,表示艦船處于正常狀态,每隔 act_seq_interval

個周期切換顯示一次貼圖,即第一行的三幀貼圖。當 act_seq_type = 1

時,表示艦船處于爆炸狀态,每隔 act_seq_interval

個周期切換顯示一次貼圖,即第二行的三幀貼圖。

目前 act_seq_type

的含義由每個艦船對象自己定義和維護。也可以歸納成統一的枚舉量,這一步讀者可以自行完成。

HaaS EDU場景式應用學習 - 飛機大戰實驗介紹涉及知識點開發環境準備遊戲設定遊戲實作實作效果開發者支援
typedef struct
{
    int cur_x;
    int cur_y;
    uint8_t speed;

    act_seq_t **act_seq_list; // 動畫數組 包含了多個動作序列
    uint8_t act_seq_list_len; // 動畫數組長度
    uint8_t act_seq_type;
} dfo_t; 
           
// 正常動作序列    
act_seq_t *achilles_normal_act = (act_seq_t *)malloc(sizeof(act_seq_t));
achilles_normal_act->act_seq_maps = achilles_normal_maplist;
achilles_normal_act->act_seq_len = 3;        // 該動作序列包含3幀圖檔
achilles_normal_act->act_seq_interval = 10;    // 該動畫幀間延遲10周期
achilles_normal_act->act_is_destory = 0;    // 該動畫不是毀滅動畫 即一直重複
// 毀滅動作序列    
act_seq_t *achilles_destory_act = (act_seq_t *)malloc(sizeof(act_seq_t));
achilles_destory_act->act_seq_maps = achilles_destory_maplist;
achilles_destory_act->act_seq_len = 3;
achilles_destory_act->act_seq_interval = 4;    // 該動畫幀間延遲4周期
achilles_destory_act->act_is_destory = 1;
// 動作序列數組
act_seq_t **achilles_act_seq_list = (act_seq_t **)malloc(sizeof(act_seq_t *) * achilles->act_seq_list_len);
achilles_act_seq_list[0] = achilles_normal_act;
achilles_act_seq_list[1] = achilles_destory_act;
// 将艦船對象屬性指向該動作序列數組
achilles->act_seq_list = achilles_act_seq_list;
achilles->act_seq_type = 0;
           
定義完成後,我們需要在遊戲的每一次循環中,更新戰艦狀态和貼圖。
           
void craft_update_act(dfo_t *craft)
{
    act_seq_t *cur_act_seq = craft->act_seq_list[craft->act_seq_type];
    if (cur_act_seq->act_seq_interval == 0)
        return;    // 若目前戰艦無動作序列,則不進行更新
    ++(cur_act_seq->act_seq_interval_cnt);
    if (cur_act_seq->act_seq_interval_cnt >= cur_act_seq->act_seq_interval)
    {
        cur_act_seq->act_seq_interval_cnt = 0;
        ++(cur_act_seq->act_seq_index);    // 切換貼圖
        if (cur_act_seq->act_seq_index >= cur_act_seq->act_seq_len)
        {
            cur_act_seq->act_seq_index = 0;
            if (cur_act_seq->act_is_destory == 1)
            {
                // 在這裡處理毀滅的艦船
            }
        }
    }
}
           

這樣,我們就為戰艦添加了噴氣的特效。

HaaS EDU場景式應用學習 - 飛機大戰實驗介紹涉及知識點開發環境準備遊戲設定遊戲實作實作效果開發者支援

移動敵機

移動敵機的方式更簡單。隻需要将其向下移動即可。實作方式如下。

void move_enemy(dfo_t *craft)
{
    map_t *cur_map = get_cur_map(craft);
    craft->cur_y += craft->speed;
    int top = craft->cur_y + cur_map->offset_y;
    if (top > 132)                                        // 當敵機飛過螢幕下方
        reload_dfo(craft, AUTO_RELOAD, AUTO_RELOAD);     // 重載敵機
}
           

重載敵機

在飛機大戰中,會有持續不斷的敵機生成,并且敵機的出現順序和位置都随機。為了實作這種效果,我們采用的方式是維護一個敵機數組,當敵機飛過螢幕下方或是被擊落後,我們會回收敵機并重新加載,将其重新顯示在螢幕上。

void reload_dfo(dfo_t *craft, int pos_x, int pos_y)
{
    craft->cur_x = craft->pos_x;
    craft->cur_y = craft->pos_y;
    
    if (pos_x == AUTO_RELOAD)    // 如果指定重載坐标為自動重載
    {
        uint16_t height = get_cur_map(craft)->icon->width;
        craft->cur_x = random() % (64 - height) + height / 2;    // 則随機生成一個坐标,且保證對象顯示在地圖内
    }
    if (pos_y == AUTO_RELOAD)
    {
        uint16_t width = get_cur_map(craft)->icon->height;
        craft->cur_y = -(random() % 1000) - width / 2;
    }
}
           

這樣,就能夠實作源源不斷的敵機了。

HaaS EDU場景式應用學習 - 飛機大戰實驗介紹涉及知識點開發環境準備遊戲設定遊戲實作實作效果開發者支援

發射子彈

對于子彈而言,它和戰艦的屬性非常相似,是以我們在現有的艦船對象 dfo_t

上稍加改動即可。

typedef enum
{
    Achilles,     // 阿克琉斯級
    Venture,      // 沖鋒者級
    Ares,         // 阿瑞斯級 戰神級
    TiTan,        // 泰坦級
    Bullet,        // 子彈
} dfo_model_e_t;    // 飛行物型号

typedef struct
{
    int offset_x;
    int offset_y;    // 炮台的相對位置
} arms_t;            // 武裝結構體

typedef struct
{
    dfo_model_e_t model;    // 型号
    
    // 運動相關
    int start_x;             // 飛行物的起始位置 用于計算飛行距離
    int start_y;

    int cur_x;                 // 飛行物的目前位置
    int cur_y;

    uint8_t speed;          // 飛行物的運動速度
    unsigned int range;     // 射程

    // 顯示相關
    act_seq_t **act_seq_list;     // 動畫數組
    uint8_t act_seq_list_len;     // 動畫數組長度
    uint8_t act_seq_type;         // 動畫狀态

    // 攻擊相關
    arms_t **arms_list;    // 武器裝備數組
    uint8_t arms_list_len; // 武器數組長度
} dfo_t;
           

那麼,目前 dfo_t

結構體不僅僅可以用于艦船,也可以用于定義子彈。接下來,我們為艦船定義炮台和子彈。

dfo_t *create_achilles()    // 定義阿克琉斯級戰艦
{
    // 貼圖等其他定義

    achilles->damage = 8;        // 定義撞擊傷害
    achilles->full_life = 10;    // 定義完整裝甲值
    achilles->cur_life = 10;    // 初始化裝甲值

    achilles->arms_list_len = 2;    // 設定炮台數為2
    achilles->arms_list = achilles_arms_list;    // 定義炮台數組

    return achilles;
}

dfo_t *create_bullet()
{
    // 貼圖等其他定義    

    bullet->damage = 1;            // 定義射擊傷害
    bullet->full_life = 1;        // 定義完整裝甲值
    bullet->cur_life = 0;        // 初始化子彈時 預設不激活

    bullet->start_x = -100;        // 初始化子彈時 将其移出螢幕外不做處理
    bullet->start_y = -100;
    bullet->cur_x = -100;
    bullet->cur_y = -100;

    return bullet;
}
           

為了生成持續不斷的子彈,我們也采用重載的方式去生成子彈。

// 檢索未被激活的子彈
dfo_t *get_deactived_bullet()
{
    for (int i = 0; i < MAX_BULLET; i++)
    {
        if (bullet_group[i]->cur_life <= 0)
            return bullet_group[i];
    }
    return NULL;
}

// 觸發艦船射擊子彈
void shut_craft(dfo_t *craft)
{
    if (craft->arms_list == NULL || craft->arms_list_len == 0)
        return;

    // 從每個炮台重載子彈
    for (int i = 0; i < craft->arms_list_len; i++)
    {
        dfo_t *bullet = get_deactived_bullet();
        if (bullet == NULL)
            return;
        reload_dfo(bullet, craft->cur_x + craft->arms_list[i]->offset_x, craft->cur_y + craft->arms_list[i]->offset_y);
    }
}

// 在每一次重新整理時移動所有子彈
void move_bullet(dfo_t *bullet)
{
    if (bullet->cur_life <= 0)
        return;
    map_t *cur_map = get_cur_map(bullet);
    bullet->cur_y -= bullet->speed;
    int bottom = bullet->cur_y + cur_map->offset_y + cur_map->icon->width;
    if (bottom < 0 || (bullet->start_y - bullet->cur_y) > bullet->range)
    {
        bullet->cur_life = 0;    // 對超出射程的子彈 取消激活
        bullet->cur_x = -100;
    }
}
           
HaaS EDU場景式應用學習 - 飛機大戰實驗介紹涉及知識點開發環境準備遊戲設定遊戲實作實作效果開發者支援

撞擊判定

在這一步,我們将會實作對于所有對象的撞擊判定,并對對象的屬性做出對應的處理。簡單而言,撞擊判定隻需要檢查兩個對象是否有像素點的重疊即可。

// 判斷兩個dfo對象 bullet craft 是否發生撞擊
int hit_check(dfo_t *bullet, dfo_t *craft)
{
    if (craft->cur_y <= 0 || craft->cur_x <= 0)
        return 0;
    if (craft->cur_life <= 0)
        return 0;
    if (bullet->cur_life <= 0)
        return 0;
    act_seq_t *cur_act_seq = bullet->act_seq_list[bullet->act_seq_type];
    map_t *cur_map = cur_act_seq->act_seq_maps[cur_act_seq->act_seq_index];

    for (int bullet_bit_x = 0; bullet_bit_x < (cur_map->icon->height); bullet_bit_x++)
    {
        for (int bullet_bit_y = 0; bullet_bit_y < (cur_map->icon->width); bullet_bit_y++)
        {
            uint8_t bit = (cur_map->icon->p_icon_mask == NULL) ? cur_map->icon->p_icon_data[bullet_bit_x / 8 + bullet_bit_y] & (0x01 << bullet_bit_x % 8) : cur_map->icon->p_icon_mask[bullet_bit_x / 8 + bullet_bit_y] & (0x01 << bullet_bit_x % 8);
            if (bit == 0)
                continue;

            int bit_cur_x = bullet->cur_x + cur_map->offset_x + cur_map->icon->height - bullet_bit_x;
            int bit_cur_y = bullet->cur_y + cur_map->offset_y + bullet_bit_y;

            act_seq_t *cur_craft_act_seq = craft->act_seq_list[craft->act_seq_type];
            map_t *cur_craft_map = cur_craft_act_seq->act_seq_maps[cur_craft_act_seq->act_seq_index];

            for (int craft_bit_x = 0; craft_bit_x < (cur_craft_map->icon->height); craft_bit_x++)
            {
                for (int craft_bit_y = 0; craft_bit_y < (cur_craft_map->icon->width); craft_bit_y++)
                {
                    uint8_t craft_bit = (cur_craft_map->icon->p_icon_mask == NULL) ? cur_craft_map->icon->p_icon_data[craft_bit_x / 8 + craft_bit_y] & (0x01 << craft_bit_x % 8) : cur_craft_map->icon->p_icon_mask[craft_bit_x / 8 + craft_bit_y] & (0x01 << craft_bit_x % 8);
                    if (craft_bit == 0)
                        continue;
                    // 找到有效點對應的絕對坐标
                    int craft_bit_cur_x = craft->cur_x + cur_craft_map->offset_x + cur_craft_map->icon->height - craft_bit_x;
                    int craft_bit_cur_y = craft->cur_y + cur_craft_map->offset_y + craft_bit_y;
                    // 開始周遊所有可撞擊對象
                    if (craft_bit_cur_x == bit_cur_x && craft_bit_cur_y == bit_cur_y)
                    {
                        return 1;
                    }
                }
            }
        }
    }
    return 0;
}
           

全局撞擊判定,判斷地圖上所有存活對象的撞擊情況。

void global_hit_check(void)
{
    // 子彈撞擊檢測
    for (int j = 0; j < MAX_BULLET; j++)
    {
        dfo_t *bullet = bullet_group[j];
        if (bullet->cur_life <= 0)
            continue;

        for (int i = 0; i < MAX_L_CRAFT + MAX_M_CRAFT + MAX_S_CRAFT; i++)
        {
            dfo_t *craft = enemy_crafts[i];
            if (craft->cur_life <= 0)
                continue;

            if (hit_check(bullet, craft))
            {
                craft->cur_life -= bullet->damage;
                bullet->cur_life = 0;
                bullet->cur_x = -100;
                if (craft->cur_life <= 0)
                {
                    destory(craft);
                }
                continue;
            }
        }
    }

    // 我方飛機撞擊檢測
    for (int i = 0; i < MAX_L_CRAFT + MAX_M_CRAFT + MAX_S_CRAFT; i++)
    {
        dfo_t *craft = enemy_crafts[i];
        if (craft->cur_life <= 0)
            continue;

        if (hit_check(my_craft, craft))
        {
            craft->cur_life -= my_craft->damage;
            my_craft->cur_life -= craft->damage;
            // 如果艦船裝甲損毀 則摧毀艦船 将其動畫狀态置為毀滅動畫
            if (craft->cur_life <= 0)
            {
                craft->act_seq_type = 1;
                craft->cur_life = 0;
            }
            if (my_craft->cur_life <= 0)
            {
                my_craft->act_seq_type = 1;
                my_craft->cur_life = 0;
                g_chance--;
            }
            continue;
        }
    }
}
           

全局重新整理

void global_update(void)
{
    for (int i = 0; i < MAX_L_CRAFT + MAX_M_CRAFT + MAX_S_CRAFT; i++)
    {
        craft_update_act(enemy_crafts[i]);    // 更新所有敵機貼圖狀态
        move_enemy(enemy_crafts[i]);        // 自動移動所有敵機
    }
    for (int i = 0; i < MAX_BULLET; i++)
    {
        move_bullet(bullet_group[i]);        // 自動移動所有激活的子彈
    }
    craft_update_act(my_craft);                // 更新玩家艦船狀态
    shut_craft(my_craft);                    // 觸發玩家艦船射擊
    global_hit_check();                        // 全局撞擊判定
}
           

實作效果

接下來請欣賞筆者的操作。

HaaS EDU場景式應用學習 - 飛機大戰實驗介紹涉及知識點開發環境準備遊戲設定遊戲實作實作效果開發者支援

開發者支援

HaaS官方:

https://haas.iot.aliyun.com/

HaaS技術社群:

https://blog.csdn.net/HaaSTech

開發者釘釘群和公衆号見下圖,開發者釘釘群每天都有技術支援同學值班。

HaaS EDU場景式應用學習 - 飛機大戰實驗介紹涉及知識點開發環境準備遊戲設定遊戲實作實作效果開發者支援