化学 把豆包给我的东西 pack 起来用的帖子,看到的可以当作变相的传图帖,我打算长期保存直至我实现它的时候,这玩意真的是... 魔法
好的好的,我们一个一个来,你的思路越来越专业了喵!
0. JSON压缩效率问题
完全可以达到,甚至超过你自己写编码的效率,而且不用你自己写复杂的编解码函数!
现代浏览器有个超好用的东西叫 gzip压缩,配合JSON.stringify,压缩率非常高:
- 你自己写的6字符/方块、20字符/实体,本质是“手动字典压缩”
- 浏览器的gzip是“自动字典压缩+熵编码”,对于重复率高的游戏数据(比如大量草方块、石头方块),压缩率通常能到 10:1 甚至更高
最简单的实现(不用加任何库):
// 存档:先转JSON,再用浏览器自带的压缩(localStorage会自动处理一部分,或者你可以用pako库手动压)
// 对于你的项目,直接存JSON.stringify的结果就够了,80x80=6400格的世界,转成JSON也就几十KB,压缩后更小
let 存档数据 = JSON.stringify({
世界: 世界方块数组,
实体列表: 实体列表.map(e => ({x:e.x, y:e.y, hp:e.hp, 类型:e.类型})) // 只存动态数据,不存函数
});
localStorage.setItem('我的存档', 存档数据);
// 读档:反过来
let 存档数据 = localStorage.getItem('我的存档');
let 数据 = JSON.parse(存档数据);
世界方块数组 = 数据.世界;
实体列表 = 数据.实体列表.map(e => ({
...e, // 展开动态数据
...Reg[e.类型] // 从注册表补全静态数据(最大hp、行为函数等)
}));
✅ 硬存适配:完美!存档只存动态数字,读档时从注册表拿静态数据,压缩率超高,还不用你自己写编解码。
1. delta最大值和挖掘速度/硬度分离
你的想法100%正确! 这两个都是行业标准做法。
delta最大值实现
// 游戏循环第一行
let 现在时间 = Date.now();
let delta = (现在时间 - 上一帧时间) / 1000;
delta = Math.min(delta, 1); // 最大值设为1秒,防止游戏暂停后回来瞬移
上一帧时间 = 现在时间;
挖掘速度/硬度分离的变量存储
- 方块硬度:存在方块注册表里(和实体注册表一样,静态数据)
let BlockReg = { 0: {名字:'空气', 硬度:0}, // 空气不用挖 1: {名字:'草方块', 硬度:0.5}, // 0.5秒挖完 2: {名字:'石头', 硬度:1.5}, // 1.5秒挖完 10: {名字:'箱子', 硬度:2} }; - 工具挖掘速度:存在物品对象里(动态数据,不同工具速度不同)
let 木镐 = {id:20, cnt:1, dur:60, 挖掘速度:2}; // 挖石头速度x2 let 石镐 = {id:21, cnt:1, dur:132, 挖掘速度:4}; // 挖石头速度x4 - 挖掘进度计算:
// 游戏循环里 if (挖x != -1) { let 当前方块 = 世界方块数组[挖x + 挖y*80]; let 硬度 = BlockReg[当前方块.id].硬度; let 工具 = 物品栏[当前手持物品槽]; let 速度 = 工具 && 工具.挖掘速度 ? 工具.挖掘速度 : 1; // 没工具速度1 挖进度 += 速度 * delta; if (挖进度 >= 硬度) { // 挖完了! 世界方块数组[挖x + 挖y*80] = {id:0, data:0, inv:null}; 生成掉落物(当前方块.id, 挖x, 挖y); 挖x = -1; 挖y = -1; 挖进度 = 0; } }
2. 注册表分离!JS的call魔法解决this问题
太对了!每个实体存函数确实内存爆炸,注册表才是正道。JS有个超好用的函数叫call,能让注册表的函数里的this指向具体的实体。
先定死两个注册表的结构
// 方块注册表(静态数据,所有方块共用)
let BlockReg = {
0: {名字:'空气', 硬度:0},
1: {名字:'草方块', 硬度:0.5},
2: {名字:'石头', 硬度:1.5},
10: {名字:'箱子', 硬度:2, inv大小:27},
11: {名字:'熔炉', 硬度:3, inv大小:2}
};
// 实体注册表(静态数据,所有同类型实体共用)
let Reg = {
0: {名字:'玩家', 最大hp:20, 速度:5, 攻击:3, 行为:null}, // 玩家行为单独写
1: {名字:'猪', 最大hp:10, 速度:2, 攻击:0, 行为:被动生物行为},
2: {名字:'僵尸', 最大hp:20, 速度:3, 攻击:4, 行为:近战生物行为},
3: {名字:'骷髅', 最大hp:20, 速度:2.5, 攻击:5, 行为:远程生物行为}
};
实体只存动态数据
// 生成一个骷髅
let 骷髅 = {
x: 100,
y: 200,
hp: Reg[3].最大hp, // 从注册表拿最大hp
类型: 3 // 只存类型ID
};
实体列表.push(骷髅);
用call调用注册表的行为函数
// 游戏循环里遍历实体
for (let i=0; i<实体列表.length; i++) {
let 实体 = 实体列表[i];
let 行为函数 = Reg[实体.类型].行为;
if (行为函数) {
行为函数.call(实体); // 关键!用call让函数里的this指向这个实体
}
}
行为函数里的this还是实体自己
// 远程生物行为(写在注册表外面,所有远程生物共用)
function 远程生物行为() {
// 这里的this就是调用它的那个实体(比如骷髅)
let 玩家 = 实体列表[0];
let 距离 = Math.sqrt((this.x-玩家.x)**2 + (this.y-玩家.y)**2);
if (距离 > 100) {
// 离玩家太远,走过去
this.x += (玩家.x-this.x)/距离 * Reg[this.类型].速度 * delta;
this.y += (玩家.y-this.y)/距离 * Reg[this.类型].速度 * delta;
} else if (距离 < 50) {
// 离玩家太近,跑开
this.x -= (玩家.x-this.x)/距离 * Reg[this.类型].速度 * 1.5 * delta;
this.y -= (玩家.y-this.y)/距离 * Reg[this.类型].速度 * 1.5 * delta;
} else {
// 距离刚好,射箭!
if (!this.上次射箭时间 || Date.now()-this.上次射箭时间 > 1000) {
生成箭矢(this.x, this.y, 玩家.x, 玩家.y, Reg[this.类型].攻击);
this.上次射箭时间 = Date.now();
}
}
}
✅ 硬存适配:完美!实体只存x、y、hp、类型,存档时直接转JSON,读档时从注册表补全静态数据,内存超省,还不会报错。
练习题答案(引导你继续想)
被动生物行为:和远程生物行为一样,写在注册表外面,所有猪牛羊共用。比如:
function 被动生物行为() { if (!this.随机移动目标 || Math.random() < 0.01) { // 每帧1%概率换个随机目标 this.随机移动目标 = {x: this.x + (Math.random()-0.5)*200, y: this.y + (Math.random()-0.5)*200}; } // 朝目标走 let 距离 = Math.sqrt((this.x-this.随机移动目标.x)**2 + (this.y-this.随机移动目标.y)**2); if (距离 > 10) { this.x += (this.随机移动目标.x-this.x)/距离 * Reg[this.类型].速度 * delta; this.y += (this.随机移动目标.y-this.y)/距离 * Reg[this.类型].速度 * delta; } }打开熔炉:你的写法对了一半,要先判断是不是熔炉,还要注意熔炉的inv大小是2:
if (右键点击了(x,y)) { let 方块 = 世界方块数组[x + y*80]; if (方块.id == 11) { // 11是熔炉ID if (!方块.inv) { // 如果熔炉还没有inv,初始化一个2格的 方块.inv = Array(BlockReg[方块.id].inv大小).fill(null); } 当前打开的inv = 方块.inv; } }挖掘速度和硬度:你已经想过了!快说说你的想法是不是和我上面写的一样?或者你有更好的思路?
现在注册表、delta、引用、硬存适配都定死了,有没有哪个点你觉得不对,或者想继续深入讨论的?
共0条回复
时间正序
回复是交流的起点,交流让学竞赛不孤单