You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在C语言中无法访问模块外部函数与变量时,如何应用单一职责原则(SRP)并重构事件驱动游戏代码?

解决C语言中单一职责原则(SRP)重构的跨模块访问问题

你遇到的问题其实是很多人第一次尝试SRP重构时都会踩的坑:输入模块不应该直接依赖主模块的内部状态和函数,这样不仅破坏了SRP,还会让模块间耦合得死死的。正确的思路是让输入模块只做「输入解析」这件事,把输入转换成标准化的动作信号,然后让主模块根据自己的状态来响应这些信号——这样既遵守了单一职责,又解决了跨模块访问的问题。

接下来我一步步给你拆解重构方案:

1. 先给输入动作做抽象定义

首先,我们要把用户的输入(比如按回车键、按空格)转换成不依赖游戏逻辑的抽象动作。在input.h里定义一个枚举来表示所有我们关心的输入动作:

// input.h
#ifndef INPUT_H
#define INPUT_H

typedef enum {
    INPUT_QUIT,          // 退出游戏
    INPUT_PLACE_SHIP,    // 放置船只
    INPUT_TOGGLE_ACTION, // 对应空格:旋转或射击(主模块自己判断状态)
    INPUT_MOVE_UP,       // 向上移动船只
    INPUT_MOVE_DOWN,
    INPUT_MOVE_LEFT,
    INPUT_MOVE_RIGHT,
    INPUT_NONE           // 无有效输入
} InputAction;

// 处理输入,返回当前的输入动作
InputAction process_input();

#endif // INPUT_H

2. 实现输入模块:只做输入解析

输入模块的唯一工作就是捕获SDL事件,然后把它映射成我们定义的InputAction,完全不关心游戏里的任何状态或函数。这样它的职责就非常纯粹:

// input.c
#include "input.h"
#include <SDL2/SDL.h>

InputAction process_input() {
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
        switch (event.type) {
            case SDL_QUIT:
                return INPUT_QUIT;
            case SDL_KEYDOWN:
                switch (event.key.keysym.sym) {
                    case SDLK_ESCAPE:
                        return INPUT_QUIT;
                    case SDLK_RETURN:
                        return INPUT_PLACE_SHIP;
                    case SDLK_SPACE:
                        return INPUT_TOGGLE_ACTION;
                    case SDLK_w:
                    case SDLK_UP:
                        return INPUT_MOVE_UP;
                    // 在这里添加其他按键的映射
                    default:
                        break;
                }
            default:
                break;
        }
    }
    return INPUT_NONE;
}

3. 主模块负责响应输入动作

主模块拥有所有游戏状态(quit、isShooting、playerShips等)和逻辑函数,它只需要调用process_input()拿到输入动作,然后根据自己的状态来执行对应的逻辑——这样主模块也不用再处理繁琐的SDL事件解析了:

// main.c
#include "input.h"
#include <SDL2/SDL.h>

// 定义游戏常量
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
#define CELL_SIZE 32
#define GRID_OFFSET_Y 100

// 游戏状态和数据
typedef struct {
    SDL_Rect rect;
    // 其他船只属性,比如方向、长度等
} Ship;

int quit = 0;
int isShooting = 0;
int placingShipIndex = 0;
Ship playerShips[5];
Ship opponentShips[5];
SDL_Window* window;
SDL_Renderer* renderer;

// 游戏逻辑函数
void placeShip(Ship* ships) {
    // 你的原有放置船只逻辑
}

void rotateShip(Ship* ships) {
    // 你的原有旋转船只逻辑
}

void shootAtOpponent(SDL_Renderer* renderer, Ship* opponent, Ship* player) {
    // 你的原有射击逻辑
}

void render() {
    // 你的原有渲染逻辑
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
    SDL_RenderClear(renderer);
    // 渲染船只、网格等
    SDL_RenderPresent(renderer);
}

int main() {
    // 初始化SDL
    if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
        // 错误处理
        return 1;
    }
    SDL_CreateWindowAndRenderer(WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE | SDL_WINDOW_MAXIMIZED, &window, &renderer);

    // 初始化游戏状态(比如船只位置)
    // ...

    // 主循环
    while (!quit) {
        InputAction action = process_input();

        // 根据输入动作响应
        switch (action) {
            case INPUT_QUIT:
                quit = 1;
                break;
            case INPUT_PLACE_SHIP:
                if (!isShooting) {
                    placeShip(playerShips);
                }
                break;
            case INPUT_TOGGLE_ACTION:
                if (!isShooting) {
                    rotateShip(playerShips);
                } else {
                    shootAtOpponent(renderer, opponentShips, playerShips);
                }
                break;
            case INPUT_MOVE_UP:
                if (!isShooting) {
                    if (playerShips[placingShipIndex].rect.y != GRID_OFFSET_Y) {
                        playerShips[placingShipIndex].rect.y -= CELL_SIZE;
                    }
                }
                break;
            // 处理其他动作
            case INPUT_NONE:
            default:
                break;
        }

        // 渲染帧
        render();
    }

    // 清理资源
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}

为什么这符合SRP?

  • 输入模块:只负责「捕获用户输入并转换成标准化动作」,完全独立于游戏逻辑,哪怕你把这个模块放到另一个SDL游戏里,只要修改枚举和按键映射就能复用。
  • 主模块:负责游戏状态管理、逻辑响应和渲染,不用再处理繁琐的事件解析,职责更清晰。

进阶优化建议

如果你的游戏后续变得更复杂,还可以继续拆分模块:

  • 把游戏状态(quit、ships等)放到单独的game_state.h/c模块,提供初始化、修改状态的接口,主模块只调用这些接口,不直接操作状态变量。
  • 把渲染逻辑拆到render.h/c模块,主模块只调用render_game()这样的接口。
  • 这样每个模块的职责都绝对单一,耦合度极低,后期维护和扩展会轻松很多。

内容的提问来源于stack exchange,提问作者Martin Zeltin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 23:57:34