MeteorCat / 游戏建筑系统构建

Created Wed, 03 Apr 2024 20:40:56 +0800 Modified Wed, 29 Oct 2025 23:24:59 +0800

建筑系统构建

网络游戏当中比较常见建筑功能经过大量版本迭代, 移动端为了节约性能消耗从地图区块拖动放置演变到固定建筑解锁; 这里先从客户端说明怎么去设计两种系统, 基本上只需要掌握这两种方式就能应对:

  • PixelBuild: 允许玩家拖动建筑在平面上建成单位, 只要在平面不冲突就允许灵活构建单位
  • UnlockBuild: 平面上已经预设好建筑单位, 只需要按照条件激活该建筑进行工作

两者首先是必须要生成平面, 这里采用 Godot 来编写样例( Unity|UE 实际上思路也差不多 ); 现在构建最简单的生成 矿场(Gold)农场(Farm), 具体自定义策划作用:

  • Gold: 建成矿场之后 每5s 会提取游戏的金币资源到个人资源
  • Farm: 建成农场之后 每5s 会提取游戏的体力资源到个人资源

注意: 初始的资源收获周期是允许策划自定义的, 这里可以先考虑写死做成初版.

首先是像素点构建出建筑, 具体最后完成结果类似如下( PixelBuild ):

PixelBuild

之后第二种建筑解锁方式就类似如下, 直接满足条件解锁激活建筑即可( UnlockBuild ):

UnlockBuild

注意: 之后内容要求具有一定服务端设计经验才能理解, 主体还是做状态同步到服务器共享.

如果想要达到满足玩家自由交互性, 最好采用 像素构建 让玩家在指定区域内选择构建建筑; 而如果只是简单想做个建筑解锁出资源点让玩家定时上线 收菜 获取收益的话, 直接采用 解锁激活 来激活建筑.

解锁建筑

这种建筑解锁方式目前大部分另外简称为 家园系统, 主要就是提供给玩家升级解锁产出游戏资源来维持游戏内部的经济系统; 以比较知名的二次元品类 “明日方舟” 为例, 初版内部就是家园解锁建筑从而生产出游戏内部货币 “龙门币” 等, 这种简单家园系统不需要太多客户端逻辑, 可能只需要 玩家等级提升|游戏货币解锁|游戏任务解锁 然后服务端协议统治下就能处理.

这方式只需要服务端去自行检查是否满足条件通知客户端解锁即可, 客户端所需要做的就是接收到协议获取多少秒之后可以领取的道具就行了

这里展示具体的触发流程图:

Build

如果没有强灵活构建需求, 尽可能采用这种建筑解锁方式可以规避大量问题.

像素构建

这种建筑构建更类似于 红警|星际|魔兽争霸 之类的 RTS 构建风格, 通过点击面板的建筑在 2D 平面上创建放置建筑之后激活, 其中主要注意点就以下这些:

  • 第三方资源, 比如地图区块有些空气墙和 “金矿|气矿” 占位, 那些区域不允许构建建筑在其之上
  • 自身建筑单位, 自己建筑抢占了指定宽高地块, 其他重新建筑不允许在其之上构建
  • 构建平面限制, 必须要设定在指定宽高像素平面上才允许构建建筑, 避免玩家构建到平面之外

大概的设计游戏界面就如下:

Create

实际上计算公式也十分简单, 一般坐标系都是从左上角首个点 (0,0) 做默认坐标起始点; 而判断建筑位置是否在 ‘大地图(2D平面建筑层)’ 的 1280 * 720 平面坐标重合即可.

这里采用 Godot 类似 Python 脚本编写逻辑, 整体上逻辑能够比较清晰看出来

# 关键函数, 判断对比 active 坐标系是否和 place 坐标系重合
func in_square_2d(place_pos:Vector2,place_size:Vector2,active_pos:Vector2,active_size:Vector2)->bool:
    
    ###  计算平面
    var place_min_x = int(place_pos.x) # 平面左上最小X起始位置: 0(以当前屏幕计算, 如果不是以原点就是其偏差值)
    var place_min_y = int(place_pos.y) # 平面左上最小Y起始位置: 0(同上)
    var place_max_x = place_min_x + int(place_size.x) - 1 # 计算出平台具体覆盖宽X范围, 内部已经含位置偏移量
    var place_max_y = place_min_y + int(place_size.y) - 1 # 计算出平台具体覆盖高Y范围, 同上
    
    ### 计算建筑
    var build_min_x = int(active_pos.x) # 建筑最小X起始
    var build_min_y = int(active_pos.y) # 建筑最小Y起始
    var build_max_x = build_min_x + active_size.x - 1 # 计算出建筑覆盖的X范围
    var build_max_y = build_min_y + active_size.y - 1 # 计算出建筑覆盖的Y范围
    
    # 这里可以仔细思考为什么会有 -1 偏差?
    
    ### 计算最小是否越界
    if build_min_x < place_min_x or build_max_y < place_min_y:
        return false
    
    
    ### 求是否相交
    # 获取到了左上右下坐标相关参数
    var min_x = maxi(place_min_x,build_min_x) # 求出X起始
    var min_y = maxi(place_min_y,build_min_y) # 求出Y起始
    var max_x = mini(place_max_x,build_max_x) # 请求X终点
    var max_y = mini(place_max_y,build_max_y) # 请求Y终点
    # 计算相交与否
    if min_x > max_x or min_y > max_y:
        return false
    else:
        return true

最后计算实现出构建建筑的效果如下:

Building

这里面的逻辑客户端和服务端可以通用, 客户端负责界面效果触发( 底色显示红色表示无法建筑 ), 之后点击触发推送给服务端 build_id(建筑ID)/x(建筑X轴坐标)/y(建筑Y轴坐标), 由服务器来最后判断生成成功.

这就是为什么如果赶时间开发建议采用 ‘解锁建筑’ 方案, 上面那个方案需要前后端联调才能最终确认.

测试样例工程: