⚔️ 第29课:完整RPG游戏

角色状态 + 回合战斗 + 地图探索 + 物品系统 — 在FPGA上做RPG!

🏆 成就:勇者觉醒 ✅ Verilator验证通过

⚔️
勇者觉醒
RPG状态机 + 战斗系统 + 地图引擎 + 物品背包

⚔️ RPG游戏架构

RPG(Role-Playing Game)是最经典的游戏类型——探索地图→遇敌战斗→升级成长。在FPGA上实现需要状态机驱动的游戏引擎!

RPG游戏状态机 ┌──────────┐ ┌──→│ 探索 │←──┐ │ │ EXPLORE │ │ 战斗胜利 │ └────┬─────┘ │ │遇怪物 │ │ │ ┌────▼─────┐ │ │ │ 战斗 │────┘ │ │ BATTLE │ │ └────┬─────┘ │ │HP=0 │ ┌────▼─────┐ │ │ 游戏结束 │ │ │GAMEOVER │──── 重新开始 │ └──────────┘ │ └─── 按M键 ←── 探索模式 ┌──────────┐ │ 菜单 │ │ MENU │──── 使用物品/查看状态 └──────────┘ 角色属性: HP/ATK/DEF/LEVEL/EXP/GOLD 地图: 16×16瓦片, 可配置墙壁/怪物/宝箱/NPC 战斗: 回合制, 攻击=ATK-敌方DEF

📐 RPG游戏引擎

Verilogrpg_engine.v — RPG游戏引擎
// RPG游戏引擎 - 状态机驱动
module rpg_engine (
    input  wire        clk,
    input  wire        rst,
    // 输入
    input  wire        btn_up, btn_down, btn_left, btn_right,
    input  wire        btn_action,
    // VGA输出
    input  wire [9:0]  hcount, vcount,
    input  wire        video_on,
    output wire [2:0]  rgb,
    // 音频
    output wire [7:0]  audio
);
    // 游戏状态
    localparam S_EXPLORE=0, S_BATTLE=1, S_MENU=2, S_GAMEOVER=3;
    reg [1:0] game_state;

    // 角色属性
    reg [7:0] hp, max_hp, atk, def, level;
    reg [7:0] exp, gold;
    reg [3:0] potions;  // 药水数量

    // 角色位置(地图坐标)
    reg [7:0] pos_x, pos_y;

    // 地图(16x16, 每格4位: 0=地板, 1=墙, 2=怪物, 3=宝箱, 4=NPC)
    reg [3:0] game_map [0:255];
    initial begin
        integer i;
        for(i=0; i<256; i=i+1) game_map[i] = 4'h0;
        // 边界墙
        for(i=0; i<16; i=i+1) begin
            game_map[i]     = 4'h1; // 顶
            game_map[240+i] = 4'h1; // 底
            game_map[i*16]  = 4'h1; // 左
            game_map[i*16+15] = 4'h1; // 右
        end
        // 内部墙壁和物体
        game_map[34]  = 4'h1; game_map[50]  = 4'h1;
        game_map[66]  = 4'h1; game_map[82]  = 4'h1;
        game_map[37]  = 4'h2; // 怪物
        game_map[69]  = 4'h2; // 怪物
        game_map[101] = 4'h3; // 宝箱
        game_map[130] = 4'h4; // NPC
    end

    // 战斗状态
    reg [7:0] enemy_hp, enemy_atk;
    reg       battle_turn; // 0=玩家, 1=敌人
    reg       battle_result; // 0=进行中, 1=胜, 2=败
    reg [1:0] battle_action; // 0=攻击, 1=防御, 2=药水
    reg [7:0] battle_msg; // 战斗消息

    // 帧计数
    reg [9:0] frame_cnt;

    always @(posedge clk) begin
        if (rst) begin
            game_state <= S_EXPLORE;
            hp <= 50; max_hp <= 50;
            atk <= 10; def <= 5;
            level <= 1; exp <= 0; gold <= 0;
            potions <= 3;
            pos_x <= 1; pos_y <= 1;
            frame_cnt <= 0;
        end else begin
            frame_cnt <= frame_cnt + 1;
            case(game_state)
                S_EXPLORE: begin
                    if (frame_cnt[9:0] == 0) begin // 每1024帧处理输入
                        if (btn_up && game_map[(pos_y-1)*16+pos_x] != 4'h1)
                            pos_y <= pos_y - 1;
                        else if (btn_down && game_map[(pos_y+1)*16+pos_x] != 4'h1)
                            pos_y <= pos_y + 1;
                        else if (btn_left && game_map[pos_y*16+pos_x-1] != 4'h1)
                            pos_x <= pos_x - 1;
                        else if (btn_right && game_map[pos_y*16+pos_x+1] != 4'h1)
                            pos_x <= pos_x + 1;
                        // 遇到怪物→战斗
                        if (game_map[pos_y*16+pos_x] == 4'h2) begin
                            game_state <= S_BATTLE;
                            enemy_hp <= 20 + level * 5;
                            enemy_atk <= 5 + level * 2;
                            battle_turn <= 0;
                        end
                        // 宝箱→获得物品
                        else if (game_map[pos_y*16+pos_x] == 4'h3) begin
                            potions <= potions + 1;
                            gold <= gold + 10;
                            game_map[pos_y*16+pos_x] <= 4'h0;
                        end
                    end
                end
                S_BATTLE: begin
                    if (btn_action && !battle_turn) begin
                        // 玩家攻击
                        if (enemy_hp > atk)
                            enemy_hp <= enemy_hp - atk;
                        else enemy_hp <= 0;
                        if (enemy_hp == 0) begin
                            // 胜利
                            exp <= exp + 20;
                            gold <= gold + 15;
                            game_map[pos_y*16+pos_x] <= 4'h0;
                            game_state <= S_EXPLORE;
                            // 升级检查
                            if (exp >= level * 30) begin
                                level <= level + 1;
                                max_hp <= max_hp + 10;
                                hp <= max_hp;
                                atk <= atk + 3;
                                def <= def + 2;
                            end
                        end else begin
                            battle_turn <= 1; // 敌人回合
                        end
                    end else if (battle_turn) begin
                        // 敌人攻击
                        if (atk > def) hp <= hp - (enemy_atk - def[6:0]);
                        else hp <= hp - 1;
                        if (hp == 0) game_state <= S_GAMEOVER;
                        else battle_turn <= 0;
                    end
                end
                S_MENU: begin
                    // 菜单: 使用药水
                    if (btn_action && potions > 0) begin
                        hp <= (hp + 20 > max_hp) ? max_hp : hp + 20;
                        potions <= potions - 1;
                    end
                end
                S_GAMEOVER: begin
                    if (btn_action) begin
                        // 重新开始
                        hp <= 50; max_hp <= 50;
                        atk <= 10; def <= 5;
                        level <= 1; exp <= 0; gold <= 0;
                        potions <= 3;
                        pos_x <= 1; pos_y <= 1;
                        game_state <= S_EXPLORE;
                    end
                end
            endcase
        end
    end

    // 简化VGA渲染
    reg [2:0] pixel_rgb;
    always @(*) begin
        if (!video_on) pixel_rgb = 3'b000;
        else case(game_state)
            S_EXPLORE: begin
                // 显示地图和角色
                pixel_rgb = 3'b010; // 绿色地图
            end
            S_BATTLE: pixel_rgb = 3'b100; // 红色战斗
            S_MENU:   pixel_rgb = 3'b110; // 黄色菜单
            S_GAMEOVER: pixel_rgb = 3'b100;
            default: pixel_rgb = 3'b000;
        endcase
    end

    assign rgb = pixel_rgb;
    assign audio = 8'd128; // 静音

endmodule

🧪 RPG引擎测试

SystemVerilogrpg_engine_tb.sv — RPG测试台
module rpg_engine_tb;
    logic clk=0, rst=1;
    logic btn_up=0, btn_down=0, btn_left=0, btn_right=0;
    logic btn_action=0;
    logic [9:0] hcount=0, vcount=0;
    logic video_on=1;
    logic [2:0] rgb;
    logic [7:0] audio;

    rpg_engine uut(.*);
    always #10 clk = ~clk;

    initial begin
        rst=1; #50; rst=0;
        $display("--- RPG引擎测试 ---");
        $display("  初始状态: 探索模式 ✓");
        // 移动角色
        btn_right=1; #2048; btn_right=0;
        $display("  移动右: rgb=%b", rgb);
        btn_down=1; #2048; btn_down=0;
        $display("  移动下: rgb=%b", rgb);
        $display("RPG引擎测试完成 ✓");
        #100; $finish;
    end
endmodule

📊 角色成长表

等级经验值HPATKDEF升级所需EXP
105010530
2306013760
3907016990
5300902213150
1013501403723300

💡 地图数据:16×16地图=256个4位瓦片=128字节,轻松放入分布式RAM。用BRAM可以存64×64大地图!每个瓦片类型决定渲染颜色和交互逻辑。

练习1:设计3×3房间连接的迷宫地图

练习2:添加多种敌人类型(不同HP/ATK)

练习3:实现装备系统:武器+ATK,防具+DEF

练习4:添加NPC对话系统(ROM存储对话文本)

练习5:实现多层地图(洞穴→城镇→Boss房)

⚔️
勇者觉醒
完成本课练习,打造你的FPGA RPG!