Organizations

  • 游戏窗口比例 这里列出常规的游戏当中用到的像素比例, 方便开发的时候套用屏幕比例加载. 这里列取出些外部网站设置标准比例参考: QQ游戏大厅第三方比例 COCOS手机比例 常规手机比例 Gamer比例 因为现在宽屏2K/4K显示器的普及, 屏幕能够设置的比例也越来越高, 这里提供下比较主流的PC比例: 1280 x 720 1366 x 768 1440 x 810 1600 x 900 1920 x 1080 以上就是比较标准的常规PC比例, 而移动端竖屏则大部分采用以下比例: 640 x 1136, 兼容最好 750 x 1334 1242 x 2208 1125 x 2436, 支持全面屏 基本上设计采用以上参考即可, 这里提供 JSON 配置文件方便本地读取加载, 首先是 PC 配置: [ { "w": 1280, "h": 720 }, { "w": 1366, "h": 768 }, { "w": 1440, "h": 810 }, { "w": 1600, "h": 900 }, { "w": 1920, "h": 1080 } ] 而手机竖屏端配置则如下:
    架构 Created Sun, 16 Jun 2024 15:46:32 +0800
  • Godot版本Web部署 需要注意目前版本号: Godot4.2.2.stable, 听说后续版本会优化修复 这里采用了简单空项目用来检查显示鼠标的项目, 打出来的包大小为 wasm 单个文件大小竟然达到 47.0 MB(开启调试导出) | 34.0MB(不导出调试)! 或许你接触到日常游戏就会感觉到这个容量没什么, 但是可要知道这是 Web浏览器 受限于 Web 的权限导致资源大部分都是直接访问加载. 可以想象打开网页每次需要下载 48|34 MB 资源才能进游戏黑屏加载, 移动端网络环境特别复杂3G/4G/宽带网等情况 在没有分包加载情况, wasm 和 worker.js 加载及其恐怖且伴随兼容性问题, 目前已知某些浏览器是无法支持: 微信H5内核浏览器 部分Chromium打包浏览器 部分FireFox浏览器 之前安全性问题导致 SharedArrayBuffer 被禁用, 知道后续修改规范要求强制 https 同源访问才放出. 这里提供测试的 Godot 测试地址方便查看: 测试地址, 点击左边连接可以测试访问(服务器在国外可能速度比较慢) 这里可以看出其加载的异常状态: 这恐怖的包体是必须预先下载总合计 48|34 MB, 服务器哪怕开启大量的资源压缩技术( cf/gzip/br 等, 但是本身 wasm 也是压缩过了 ) 也没办法避免庞大胞体, 这也是不得不放弃开发 Godot 的 Web 版本原因, 其他还有大量国内浏览器带来的兼容问题. 官方GitHub也有人讨论 这个问题 本地测试脚本 Godot 导出项目之后需要在本地测试样例项目是否运行成功, 这里依靠 Python 挂载服务( serve.py 文件 ):
    Godot Created Tue, 04 Jun 2024 18:34:49 +0800
  • 字符串转int32 这里主要场景就是编写 proto 的时候客户端和服务端需要对齐协议 id(int); 客户端常规来说会推送 int32 值, 然后服务端拿着该值匹配本地 proto 文件尝试将后续字节数据转化成对应格式. 所以需要简单的 Map<Int,Proto> 做映射处理, 从而方便调用出字符串所属的对象. 也可以直接传递个字符串标识如 Player.Login 给服务端, 但是每次传递协议都带一大段字符串还要做安全解析处理起来更加复杂 这里利用 Java 编写简单的字符串转 int32 方法, 并且测试下碰撞: public class StringToInt32 { public static void main(String[] args) { // 测试两者的碰撞 int total = 100000; // 十万随机字符碰撞 // 随机构建字符串测试碰撞 Random random = new Random(); List<String> exists = new ArrayList<>(); String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; for (int i = 0; i < total; i++) { int len = 3 + random.
    技巧 Created Fri, 31 May 2024 14:01:13 +0800
  • AStar寻路 这种是游戏服务端当中最常见的碰撞检测游戏寻路方式, 日常AI机器人也十分依赖寻路策略. 静态可以采用 AStar, 而动态则需要 Dstar 的 Dijkstra 处理运算寻路, 移动端游戏尽可能采用静态寻路处理 游戏客户端大部分实现这些插件让其更加易用( Unity的NavMesh ), 但是服务端则缺失这部分功能也就没办法在服务端模拟玩家行走路线从而同步. AStar寻路实际上是采用 网格(Gird) 的寻路计算, 具体原理就是先把整个地图分解成 Width * Height 多个格子, 然后通过网格周边连线来处理出最短路径, 同时支持标识障碍物绕过计算, 这种过程就是将 平面栅格化. 大部分寻路都是基于这种方式, 只是按照不同算法评估周边格子从而采样路径处理. 注: 在空间寻路当中必须要具有 方向 和 距离 才能形成正确的寻路路径(向量|矢量) 寻路方式按照不同方式计算寻路: 曼哈顿估价法(Manhattan Heuristic) 几何估价法(Euclidean Heuristic) 对角线估价法(Diagonal Heuristic) 对应三种寻路方式图示处理: 曼哈顿估价法 基于 四向移动(十字形位移) 的计算, 也就是只有 上下左右 方向, 这种四向计算性能消耗相对比较小但是只支持四向寻路: ---d为计算的相邻正方体网格间移动成本, 也就是从起始坐标格子位移到周边格子路径 ---node节点是网格起始的点坐标, goal节点则是最后的终点坐标 ---@param node { x = 0,y = 0 } 起始点 ---@param goal { x = 0,y = 0 } 最终点 ---@param d number function ManhattanHeuristic(node, goal, d) local dx = math.
    游戏数学 Created Thu, 09 May 2024 23:27:24 +0800
  • 协议响应设计 无论是 Web|Protbuf 服务端响应给客户端都基本需要对协议进行关联响应, 比如 Web 响应给数据(内部的 status 用于客户端索引错误处理): // 客户端请求转发给内部让其转发到指定proto对象 { // 标识请求的请求控制器 "proto": 10059, "args": { "username": "meteorcat", "password": "meteorcat", } } // 服务端响应给客户端响应协议 { "status": 10037, "message": "failed by username", "data": {} } 而 GoogleProtobuf 也虽然不需要带上 status, 但是需要在客户端|服务端采用 KEY-VALUE 方式做哈希索引: --- 这里做类似Lua挂载查询 --- 常规都是以哈希表方式让其客户端|服务端启动时候加载 protocols = {} protocols[10023] = LoginRequestProto.New() protocols[10024] = LoginResponseProto.New() --- 后续直接调用其内部句柄对象解析协议即可 所以协议关联是很重要的, 否则后续请求协议数据多起来特别庞大也不好管理, 所以需要全局的协议管理器方便注入处理; 但是注册协议时候最大问题就是协议分类, 功能分类譬如 玩家分类(User-Login|Register)|公告分类(Notice-List|Readed), 这种时候就需要对协议做分类方便类目管理, 这推荐采用整除法做协议管理分组: --- 正规的协议采用 `xxxyyy` 格式的 int 类型 --- 比如设定用户信息类 100, 而他分类当中可以 获取信息(1)/修改昵称(2) --- 剩下设定场景游戏类 200, 而其分类当中可以 加入房间(1)/离开房间(2) local user_group = 100 local user_get_info = 1 local user_change_nickname = 2 -- 打包成int local user_mask = user_group * 1000 -- 转换成 100,000 local user_get_info_proto = user_mask + user_get_info -- 打包成 100,001, 后三位用于识别子分类 local user_change_nickname_proto = user_mask + user_change_nickname -- 打包成 100,002 -- 解包出类型和具体协议 local dec_mask = tonumber(user_get_info_proto / 1000) -- 整除掉标识, 获取到分类 100 local dec_id = user_get_info_proto - (dec_mask * 1000) -- 提取出的具体协议值 1 这种方法直接利用简单的加减乘除处理, 在性能上面占据优势且跨平台处理也方便移植, 所以包装协议可以直接处理好封装成哈希表处理.
    架构 Created Thu, 09 May 2024 18:46:37 +0800
  • 最简单的游戏寻路实现 这种寻路方法是之前在游戏应用到相对简单的游戏寻路方法, 通过客户端设计编辑 (X1, Y1) - (X2, Y2) 多段线段导出JSON格式: // A点->B点的线段 [ // 起点 = A { x: 1, y: 4 }, // 终点 = B { x: 8, y: 5, } ] // C点->E点的线段 [ // 起点 = C { x: 3, y: 7 }, // 终点 = B { x: 6, y: 7, } ] 两点之间就能确定线段, 那么就可以转化成数学问题: 确定玩家的终点(x1,y1), 转化成 坐标系中求终点到多条线段的最小距离 数学运算, 反推出终点到哪条线段距离最短 现在已经确定好路径线段, 之后通过玩家起点(x2,y2)转化成 坐标系中求玩家点到选中线段的最小距离, 推出玩家启动选中路径最短距离 到这里已经确定好几段路径: 玩家终点应该走哪条预设路径最短线路: <point(x1,y1) -> line(x1,y1)>, 玩家起点到路径的最短线路: <point(x2,y2) -> line(x2,y2) > 最终线路组合: <point(x2,y2),玩家起点> -> <line(x2,y2),玩家距离最短距离> -> 走到端点 -> <point(x1,y2),路径端点到终点> 那么最后得出的路径就连接接起来就是这样:
    游戏数学 Created Sun, 05 May 2024 00:12:28 +0800
  • 状态同步 网络游戏的同步策略是作为服务端的必修课, 并且在其中接触到以下两种同步方式: 状态同步: 相对来说宽泛的同步条件, 对于数据同步精度不需要超高进度级别情况 帧同步: 适合少数人竞技短时局势的对局游戏, 要求对局所有人都有高精度高频率数据交换 帧同步在 FPS|格斗|MOBA 中都有更好的表现, 游戏选手操作能够达到媲美高精度电脑级别, 所以对于数据同步要求极高 帧同步由于高频率高精度的同步请求导致性能消耗极高所以没办法持久常年运行, 帧同步只接收客户端推送操作来让服务端演算, 帧同步的游戏更像是将游戏当中客户端负责部分移交给到服务端, 所以在操作|响应方面体验更加贴近客户端体验. 但是帧同步带来更大的知识点: 定点数, 内|外插值补偿帧, 同步步骤记录 目前就我接触到有以下方案来处理服务端状态同步: 自己从头编写 物理碰撞|寻路算法 在服务端处理方式使得客户端和服务端计算结果一致, 客户端负责提交操作让服务端一起计算结果保存在服务器. 依靠游戏服务端自带的 DedicatedServer, 在服务端上挂载业务游戏自带的场景服务器, 用户提交的数据依靠自己编写服务端构建重新包装推送到游戏自带服务器. 纯客户端实现的服务端, 基本比较知名的就是 Unity3D 的 ET(基于C#) 的集成开发服务端, 内部算法数据直接延用游戏引擎内部的方法 目前主流游戏开发引擎都自带 DedicatedServer 用来方便服务端挂载, Unity3d|UE|Godot 都支持挂载 从头实现在服务端实现客户端所需的 物理碰撞|寻路算法 之类对于服务端功底要求很高, 从数学到程序方面都要去从头开始设计; 而如果对游戏实时性要求不是那么高的话, 可以优先选用 状态同步 来构建, 所有这里着重讲解的就是 状态同步. 帧同步 涉及的知识点太过高深, 我至今都没办法说已经入门, 更别说深入了解构建样例. 状态同步 首先如果要做状态同步要准备构建出场景地图, 场景地图一般是为 2D(x,y)|3D(x,y,z) + block(碰撞不允许进入地区) + path(场景路线,玩家寻路直接检索路线最短点移动过去执行路线移动); 这里场景地图甚至需要客户端编辑器做好地图编辑器工具方便导出给服务端使用, 后续还有物理碰撞需要在服务端的 Update 做更新同步处理, 让服务端也可以模拟物理碰撞从而同步客户端的操作.
    Godot Created Thu, 02 May 2024 01:59:24 +0800
  • 自签证书内网穿透 这里是基于内网的自签名证书对外开放服务功能, 主要流程: Linux 定时自动 生成自签证书生成放置于 Nginx 特定目录, 建议每日|每月自动更新证书数据 在生成证书的同时挂载自签证书提供对外服务, 所有服务都必须经由自签证书访问 在生成证书的同时写入到公钥和证书数据到 Redis 之中保存 对外挂起单独 Web 服务提供登录服务用于统一登录授权 用户登录认证之后服务器返回 地址+端口+证书+公钥进行 从而保存到本地挂起 Web 通过自签证书访问服务 用户从登录多个返回授权服务列表可以直接访问到内部自签名服务 具体的请求时序图如下: 这种访问方式可以在外网防止中间人窃取访问数据, 从而保证内部服务的安全可靠性; 这里采用 Python|Bash 脚本处理都行, 另外还需要知道怎么 构建自签名证书. 上面的构建自签证书需要手动输入必要的信息, 这里采用连贯命令直接单行全部编写( 先测试脚本: /etc/nginx/auto.cer.sh ): #!/bin/bash # 注意: 这个脚本最后采用 root 方式管理, 因为要Nginx重载配置 if [ $UID -ne 0 ]; then echo "require permissions, please run as root" exit 1 fi # 证书有效天数, 最好采用动态生成不断周期更新 CER_DAY=60 # 构建的动态端口起始值 CER_INDEX=2 # 转发的内网服务地址 CER_PROXY="http://127.0.0.1:3000" # 证书输出路径, 这里默认在 Nginx 上构建出来, 注意必须先创建好目录 # mkdir -p /etc/nginx/auto_ssl CER_PATH="/etc/nginx/auto_ssl" # Nginx 放置的配置加载目录, 注意必须要创建好目录 # mkdir -p /etc/nginx/auto.
    部署 Created Wed, 24 Apr 2024 21:18:35 +0800
  • 数据库设计小技巧 对于常规的表结构推荐预留以下字段: create_uid: 创建后台管理员ID update_uid: 更新数据管理员ID delete_uid: 删除数据管理员ID create_time: 数据创建时间 update_time: 数据更新时间 delete_time: 删除删除时间 这几个是设计表当中最好追加的字段, 因为常常需要溯源和软删除处理, 这里举几个常用样例: 需求A: 用户创建的数据要求自己进行可见和修改, 用户只能修改自己所有的那部分数据. 需求B: 数据只允许被 软删除 而不能直接 硬删除, 会导致丢失关联的数据库数据. 需求C: 订单数据表需要按照最后玩家支付时间倒叙排序查看( 订单流程分为: 创建订单, 等待回调, 支付完成, 发货完成 ) 这里目前工作当中最常见的场景, 里面都需要充分调度到内部的条件数据: # 只允许查询|修改到自己创建的数据 SELECT * FROM `order_info` WHERE `create_uid` = owner_uid # 去除查询出来的已经被标识删除的数据 SELECT * FROM `order_info` WHERE `delete_uid` <= 0 这几个字段在功能表上推荐先追加上, 因为本身都是数值类型所以相对占用也不是那么大.
    架构 Created Thu, 18 Apr 2024 18:14:13 +0800
  • 空间坐标测距检索 当时项目提出的具体项目需求, 后续实现之后感觉挺有趣的就把其单独剔除出来总结起来; 具体需求就是商家在我们自己商户平台 注册并绑定当前地址, 然后用户需要在平台检索最近的注册商家. 这里实现方式其实也十分简单, 只需要在注册的时候使用 MySQL|MariaDB 的 Geometry 类型保存位置经纬度. 注意: MySQL版本必须在 5.7 之后才能支持该类型. Geometry 类型是专门用于空间存储的类型, 具体支持以下细分类型: Point(110.3 44.0): 点, 保存坐标点位置, 也就是位置标识经纬度 LineString(80.07 23.45, 99.23 34.56): 线, 两个点联系的直线, 用于标识两点直线 Polygon((93.30 35.301 90.332 30.341)): 面, 多点连接采用的多边形面 MultiPoint: 多点, 用于多个点记录, 一般是复杂的多点位置记录 MultiLineString: 多线, 用于多条路线记录, 一般是复杂的多路线记录 MultiPolygon: 多面, 用于多个面对象, 一般是复杂的多面记录 GeometryCollection: 集合, 这是很高级的经纬度混合集合, 实际上我个人很少去用到 回归需求上面, 实际上要求客户端|移动端在商户平台需要对接第三方SDK平台来提交坐标系, 这里以 百度地图开放平台SDK 做样例; 假设安卓客户端已经接入 百度安卓定位SDK, 如下官方文档说明: /** * 实现定位回调 */ public class MyLocationListener extends BDAbstractLocationListener { @Override public void onReceiveLocation(BDLocation location) { //此处的BDLocation为定位结果信息类,通过它的各种get方法可获取定位相关的全部结果 //以下只列举部分获取经纬度相关(常用)的结果信息 //更多结果信息获取说明,请参照类参考中BDLocation类中的说明 //获取纬度信息 - 关键参数 double latitude = location.
    架构 Created Wed, 17 Apr 2024 19:55:51 +0800