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

非网格类Factorio游戏建筑系统:菜单驱动类实例化与职责划分问询

针对Factorio类游戏建筑系统职责划分的优化建议

首先,你当前Game.BuyBuilding(Building building)的设计确实存在职责混淆和逻辑漏洞——先创建建筑再校验资源的顺序是反的,不仅会造成不必要的对象实例化浪费,还把本该属于Game的"构建决策"逻辑提前泄露到了UI或其他调用方。下面是这类模拟建造游戏中通用的职责划分实践,结合你的代码给出具体优化方案:


1. 明确三类核心模块的职责边界

  • UI类:只负责用户交互与展示:
    • 遍历BuildInstruction.BuildInstructions的分类树,渲染菜单
    • 捕获用户的选择(比如选中了Stockpile的BuildInstruction)、输入(比如点击的世界坐标、选中的两个连接点)
    • 将用户的"建造请求"(包含BuildInstruction和必要参数)传递给Game,不做任何业务逻辑校验,也不创建Building/Connection对象
  • Game类:负责业务规则校验与流程控制:
    • 校验玩家的资金、建造条件(比如是否有足够空间、是否满足前置科技)
    • 若校验通过,调用BuildInstruction创建建筑/连接对象,再委托World处理实际的世界添加逻辑
    • 处理建造成功/失败的反馈(比如扣钱、给UI返回提示)
  • World类:负责世界状态的维护:
    • 管理所有实体的位置、碰撞、更新逻辑
    • 提供添加/移除实体的原子操作(比如AddWorldObject)

2. 重构BuildInstruction与Game的交互逻辑

第一步:给BuildInstruction补充必要的元数据

让BuildInstruction携带建造所需的核心信息(比如成本),方便Game做校验:

public abstract class BuildInstruction {
    // 新增:建造该建筑/连接所需的资金
    public int Cost { get; protected set; }
    public string Name { get; protected set; }

    // 保留原有的分类树初始化逻辑...
}

public class BuildingBuildInstruction : BuildInstruction {
    Func<PointF, Building> BuildFunction;
    // 构造时传入成本与名称
    public BuildingBuildInstruction(string name, int cost, Func<PointF, Building> buildFunction) {
        Name = name;
        Cost = cost;
        BuildFunction = buildFunction;
    }
    public Building Build(PointF worldPos) => BuildFunction(worldPos);
}

public class ConnectionBuildInstruction : BuildInstruction {
    Func<IConnectable, IConnectable, Connection> BuildFunction;
    public ConnectionBuildInstruction(string name, int cost, Func<IConnectable, IConnectable, Connection> buildFunction) {
        Name = name;
        Cost = cost;
        BuildFunction = buildFunction;
    }
    public Connection Build(IConnectable start, IConnectable end) => BuildFunction(start, end);
}

第二步:修改Game类的方法,接收BuildInstruction而非已构建的对象

让Game主导从校验到构建的完整流程:

public class Game {
    World World = new World();
    public int PlayerMoney { get; private set; } = 1000; // 示例玩家资金

    // 处理建筑建造请求
    public bool BuyBuilding(BuildingBuildInstruction instruction, PointF worldPos) {
        // 1. 先校验资金
        if (PlayerMoney < instruction.Cost) {
            // 通知UI:资金不足
            return false;
        }

        // 2. 校验世界建造条件(比如是否有碰撞)
        var tempBuilding = instruction.Build(worldPos);
        if (!World.CanPlaceObject(tempBuilding)) {
            // 通知UI:无法放置(比如空间不足)
            return false;
        }

        // 3. 所有校验通过,执行建造流程
        PlayerMoney -= instruction.Cost;
        if (ConstructBuilding(tempBuilding)) {
            // 通知UI:建造成功
            return true;
        } else {
            // 异常情况:回滚资金
            PlayerMoney += instruction.Cost;
            return false;
        }
    }

    // 处理连接建造请求(比如电线、管道)
    public bool BuyConnection(ConnectionBuildInstruction instruction, IConnectable start, IConnectable end) {
        if (PlayerMoney < instruction.Cost) {
            return false;
        }

        var connection = instruction.Build(start, end);
        if (World.AddWorldObject(connection)) {
            PlayerMoney -= instruction.Cost;
            return true;
        }
        return false;
    }

    bool ConstructBuilding(Building building) {
        return World.AddWorldObject(new ConstructionSite(building));
    }
}

第三步:UI类的调用逻辑示例

UI只需要传递用户选择的BuildInstruction和输入参数:

public class BuildMenuUI {
    Game _game;

    // 用户选中了Stockpile的BuildInstruction,点击了世界坐标
    public void OnBuildingSelected(BuildingBuildInstruction instruction, PointF clickPos) {
        bool success = _game.BuyBuilding(instruction, clickPos);
        if (success) {
            ShowMessage($"开始建造{instruction.Name}");
        } else {
            ShowMessage("建造失败:资金不足或无法放置");
        }
    }

    // 用户选中了Powerline的BuildInstruction,选中了两个连接点
    public void OnConnectionSelected(ConnectionBuildInstruction instruction, IConnectable start, IConnectable end) {
        bool success = _game.BuyConnection(instruction, start, end);
        // 处理反馈...
    }
}

3. 额外的优化建议

  • 拆分ConstructionSite逻辑:可以把"建造中"的状态管理放到Game或一个单独的ConstructionManager类中,World只负责存储实体,不处理建造进度逻辑,保持职责单一。
  • 抽象建造校验逻辑:如果未来有更多建造条件(比如科技解锁、电力需求),可以给BuildInstruction添加CanBuild(Game game, World world, params object[] args)抽象方法,让每个具体的BuildInstruction自己实现校验逻辑,Game只做统一调用。
  • 缓存临时对象:如果担心tempBuilding的创建开销,可以在BuildInstruction中添加Preview(PointF worldPos)方法,返回一个轻量的预览对象用于碰撞检测,避免创建完整的Building实例。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:29:07