游戏开发中的二进制应用

在游戏开发中,二进制操作是一个非常重要的技术点,它可以帮助我们高效地存储和处理各种游戏数据。本文将探讨几个常见的应用场景并使用Node.js来演示具体实现。

1. 使用位运算存储游戏状态

在游戏中,我们经常需要记录玩家的各种状态,比如:

  • 是否完成某个任务
  • 是否解锁某个成就
  • 是否获得某个道具

使用二进制的位运算可以非常高效地存储这些布尔值信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 使用一个32位整数存储32个不同的状态
let playerState = 0;

// 设置状态的函数
function setState(state, position) {
return state | (1 << position);
}

// 检查状态的函数
function checkState(state, position) {
return (state & (1 << position)) !== 0;
}

// 清除状态的函数
function clearState(state, position) {
return state & ~(1 << position);
}

// 示例使用
let state = 0;
// 设置第3个任务完成
state = setState(state, 2); // position从0开始计数
console.log(checkState(state, 2)); // 输出: true

2. 合并服务器ID和玩家ID

在大型多服务器游戏中,我们经常需要生成全局唯一的玩家ID。一种常见的方式是将服务器ID和玩家ID组合在一起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class PlayerIdGenerator {
static combineIds(serverId, playerId) {
// 使用位移操作合并ID
// 假设serverId使用16位,playerId使用48位
return BigInt(serverId) << 48n | BigInt(playerId);
}

static extractIds(combinedId) {
// 从组合ID中提取服务器ID和玩家ID
const serverId = Number(combinedId >> 48n);
const playerId = Number(combinedId & ((1n << 48n) - 1n));
return { serverId, playerId };
}
}

// 示例使用
const combinedId = PlayerIdGenerator.combineIds(1, 12345);
console.log(PlayerIdGenerator.extractIds(combinedId));
// 输出: { serverId: 1, playerId: 12345 }

3. 关卡进度存储

对于关卡进度的存储,我们可以使用位图(bitmap)的方式来高效存储:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class LevelProgress {
constructor() {
this.progress = 0n; // 使用BigInt可以存储更多关卡
}

// 标记关卡完成
completeLevel(levelNumber) {
this.progress |= 1n << BigInt(levelNumber - 1);
}

// 检查关卡是否完成
isLevelCompleted(levelNumber) {
return (this.progress & (1n << BigInt(levelNumber - 1))) !== 0n;
}

// 获取已完成的关卡数量
getCompletedLevelsCount() {
let count = 0;
let temp = this.progress;
while (temp > 0n) {
if (temp & 1n) count++;
temp >>= 1n;
}
return count;
}
}

// 示例使用
const progress = new LevelProgress();
progress.completeLevel(1);
progress.completeLevel(3);
progress.completeLevel(5);

console.log(progress.isLevelCompleted(1)); // true
console.log(progress.isLevelCompleted(2)); // false
console.log(progress.getCompletedLevelsCount()); // 3

4. 常见位操作模式

在游戏开发中,有一些标准的位操作模式经常被使用:

4.1 获取特定位的值

1
2
3
4
5
6
7
8
9
// 获取第n位的值(从右往左,从0开始计数)
function getBit(num, n) {
return (num >> n) & 1;
}

// 示例
const number = 12; // 二进制:1100
console.log(getBit(number, 2)); // 输出:1
console.log(getBit(number, 0)); // 输出:0

4.2 设置特定位的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 设置第n位为1
function setBit(num, n) {
return num | (1 << n);
}

// 设置第n位为0
function clearBit(num, n) {
return num & ~(1 << n);
}

// 示例
let number = 12; // 二进制:1100
number = setBit(number, 1); // 变成:1110
number = clearBit(number, 2); // 变成:1010

4.3 获取最右边的1的位置

1
2
3
4
5
6
7
8
// 获取最右边的1的位置
function getLowestSetBit(num) {
return num & -num;
}

// 示例
const number = 12; // 二进制:1100
console.log(getLowestSetBit(number)); // 输出:4 (二进制:0100)

4.4 位掩码操作

1
2
3
4
5
6
7
8
9
// 使用掩码提取特定位
function extractBits(num, position, length) {
const mask = ((1 << length) - 1) << position;
return (num & mask) >> position;
}

// 示例:从一个32位数中提取中间8位
const number = 0x12345678;
const middle8Bits = extractBits(number, 12, 8);

4.5 检查是否是2的幂

1
2
3
4
5
6
7
8
// 检查一个数是否是2的幂
function isPowerOfTwo(num) {
return num > 0 && (num & (num - 1)) === 0;
}

// 示例
console.log(isPowerOfTwo(16)); // true
console.log(isPowerOfTwo(18)); // false

4.6 计算二进制中1的个数

1
2
3
4
5
6
7
8
9
10
11
12
// 计算二进制表示中1的个数
function countOnes(num) {
let count = 0;
while (num) {
num &= (num - 1); // 清除最右边的1
count++;
}
return count;
}

// 示例
console.log(countOnes(14)); // 输出:3 (14的二进制是1110)

4.7 交换两个数

1
2
3
4
5
6
7
8
9
10
11
// 使用异或交换两个数
function swapNumbers(a, b) {
a = a ^ b;
b = a ^ b;
a = a ^ b;
return [a, b];
}

// 示例
let [x, y] = swapNumbers(5, 10);
console.log(x, y); // 输出:10 5

这些位操作模式在游戏开发中经常用于:

  1. 状态标志的管理
  2. 权限控制
  3. 数据压缩
  4. 高效的数学计算
  5. 资源标记

使用这些位操作时要注意:

  1. 操作符优先级
  2. 有符号数和无符号数的区别
  3. JavaScript中数字的位数限制
  4. 代码可读性,建议添加适当的注释说明位操作的目的

性能优势

使用二进制操作的主要优势:

  1. 存储空间效率高:一个32位整数可以存储32个布尔值
  2. 运算速度快:位运算是CPU的基本操作,执行效率很高
  3. 网络传输量小:相比传输多个独立的布尔值,传输一个整数更节省带宽

注意事项

  1. JavaScript中的数字是64位浮点数,进行位运算时会转换为32位整数
  2. 如果需要处理更大的数值,可以使用BigInt
  3. 在进行位运算时要注意运算符的优先级
  4. 代码的可读性可能会降低,建议添加适当的注释

JavaScript中的数字精度和BigInt

5.1 JavaScript数字的限制

JavaScript中的Number类型使用IEEE 754双精度浮点数格式,这意味着:

  • 总共64位:1位符号位 + 11位指数位 + 52位尾数位
  • 能够精确表示的整数范围是:±2^53 - 1(即±9007199254740991)
  • 超过这个范围的整数运算可能会丢失精度

5.2 48位整数的使用场景

使用48位整数是一个比较安全的选择,原因如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 48位整数的最大值
const MAX_SAFE_48BIT = Math.pow(2, 48) - 1; // 281474976710655

// 示例:合并服务器ID和玩家ID
function combineIds(serverId, playerId) {
// 使用16位存储serverId,32位存储playerId
return (serverId << 32) | playerId;
}

// 这样的设计是安全的,因为:
// - serverId 最大可以到 65535 (16位)
// - playerId 最大可以到 4294967295 (32位)
// - 总共48位,不会超过JavaScript安全整数范围

5.3 使用BigInt的场景

但在以下情况下,建议使用BigInt:

  1. 需要处理超过48位的整数:
1
2
3
4
// 使用BigInt处理大数
const bigPlayerId = 9007199254740991n; // 最大安全整数
const serverBits = 16n;
const combinedId = (BigInt(serverId) << serverBits) | bigPlayerId;
  1. 需要进行精确的位操作:
1
2
3
4
5
6
// BigInt保证位操作的精确性
function extractBitsWithBigInt(num, start, length) {
const bigNum = BigInt(num);
const mask = (1n << BigInt(length)) - 1n;
return (bigNum >> BigInt(start)) & mask;
}
  1. 处理时间戳和唯一ID:
1
2
3
4
5
6
7
// 使用BigInt组合时间戳和序列号
function generateUniqueId(timestamp, sequence) {
const timestampBits = 42n; // 可以表示到2109年
const sequenceBits = 22n; // 每毫秒4百万个序列号

return (BigInt(timestamp) << sequenceBits) | BigInt(sequence);
}

5.4 选择建议

  1. 如果确定数值范围在48位以内:

    • 使用普通Number类型
    • 性能更好
    • 与其他JavaScript API兼容性更好
  2. 如果可能超过48位或需要精确计算:

    • 使用BigInt
    • 保证计算精度
    • 但要注意:
      • BigInt运算相对较慢
      • 不能与普通数字直接混合运算
      • 某些API可能不支持BigInt
  3. 在游戏开发中的最佳实践:

    • 对于游戏内普通ID(如物品ID、玩家ID等):使用48位以内的普通数字
    • 对于需要长期存储或特殊计算的大数:使用BigInt
    • 在设计之初就要考虑ID的范围和增长速度

MongoDB中存储BigInt

在MongoDB中存储大整数有几种方案,每种方案都有其适用场景:

6.1 使用Long类型

MongoDB原生支持64位整数(Long类型),这是最直接的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Mongoose Schema 定义
const mongoose = require('mongoose');

const playerSchema = new mongoose.Schema({
playerId: {
type: mongoose.Schema.Types.Long, // 使用Long类型
required: true
},
// ... 其他字段
});

// 使用方式
const Player = mongoose.model('Player', playerSchema);

// 存储数据
const player = new Player({
playerId: mongoose.Types.Long.fromString("9223372036854775807") // 最大64位整数
});

6.2 使用String类型存储

当需要处理超过64位的整数时,可以使用字符串存储:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const userSchema = new mongoose.Schema({
userId: {
type: String,
validate: {
validator: function(v) {
// 验证是否为有效的数字字符串
return /^\d+$/.test(v);
},
message: props => `${props.value} 不是有效的数字字符串!`
},
required: true
}
});

// 使用BigInt时的转换
const user = new User({
userId: BigInt("123456789123456789").toString()
});

6.3 使用自定义SchemaType

可以创建自定义SchemaType来处理BigInt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 自定义BigInt SchemaType
const BigIntSchemaType = function(key, options) {
mongoose.SchemaType.call(this, key, options, 'BigIntSchema');
};

BigIntSchemaType.prototype = Object.create(mongoose.SchemaType.prototype);

// 定义转换方法
BigIntSchemaType.prototype.cast = function(val) {
if (val === null) return val;
if (val === undefined) return val;

try {
return BigInt(val.toString());
} catch (error) {
throw new Error(`无法将 ${val} 转换为 BigInt`);
}
};

// 注册自定义类型
mongoose.Schema.Types.BigInt = BigIntSchemaType;

// 使用自定义类型
const schema = new mongoose.Schema({
bigIntField: {
type: mongoose.Schema.Types.BigInt
}
});

6.4 使用复合字段

对于特别大的数字,可以将其拆分存储:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const idSchema = new mongoose.Schema({
high: {
type: Number,
required: true
},
low: {
type: Number,
required: true
}
});

// 使用方法
function storeBigInt(bigIntValue) {
const high = Number(bigIntValue >> 32n);
const low = Number(bigIntValue & 0xFFFFFFFFn);
return { high, low };
}

function retrieveBigInt(record) {
return (BigInt(record.high) << 32n) | BigInt(record.low);
}

// 示例
const record = storeBigInt(123456789123456789n);
const originalValue = retrieveBigInt(record);

6.5 最佳实践建议

  1. 对于64位以内的整数:

    • 使用MongoDB的Long类型
    • 性能最好,空间效率高
  2. 对于超过64位的整数:

    • 如果不需要进行数值计算,使用String类型
    • 如果需要计算,使用自定义SchemaType或复合字段
  3. 查询优化:

1
2
3
4
5
6
7
8
// 创建索引时注意类型
playerSchema.index({ playerId: 1 }); // 对于Long类型
userSchema.index({ userId: 1 }); // 对于String类型

// 查询时确保使用正确的类型
await Player.find({
playerId: mongoose.Types.Long.fromString("123456789")
});
  1. 注意事项:
    • 确保前端传输大数时使用字符串
    • 考虑数据库索引的影响
    • 注意序列化和反序列化时的类型转换
    • 考虑到MongoDB的查询性能

总结

二进制操作在游戏开发中有着广泛的应用,它不仅可以提高程序的性能,还能节省存储空间和网络带宽。在实际开发中,我们需要根据具体场景选择合适的实现方式,在性能和可维护性之间找到平衡点。

7. 使用异或进行简单加密

异或操作在游戏中常用于简单的数据加密,特别是在密码游戏或存档加密中。异或加密的特点是:

  • 同样的密钥,第一次异或加密,第二次异或解密
  • 计算速度快,实现简单
  • 适合轻量级的数据保护

7.1 基本异或加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 简单的异或加密/解密函数
function xorEncrypt(data, key) {
// 确保data和key都是数字
let result = data ^ key;
return result;
}

// 示例:加密游戏密码
const originalPassword = 12345;
const secretKey = 54321;
const encrypted = xorEncrypt(originalPassword, secretKey);
const decrypted = xorEncrypt(encrypted, secretKey);

console.log('原始密码:', originalPassword);
console.log('加密后:', encrypted);
console.log('解密后:', decrypted);

7.2 字符串异或加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 字符串异或加密
function xorEncryptString(str, key) {
let result = '';
for (let i = 0; i < str.length; i++) {
// 使用字符的ASCII码进行异或
const charCode = str.charCodeAt(i) ^ key;
result += String.fromCharCode(charCode);
}
return result;
}

// 示例:加密游戏存档数据
const saveData = "LEVEL5_SCORE1000";
const key = 127; // 密钥(0-255之间)
const encryptedData = xorEncryptString(saveData, key);
const decryptedData = xorEncryptString(encryptedData, key);

console.log('原始数据:', saveData);
console.log('加密后:', encryptedData);
console.log('解密后:', decryptedData);

7.3 多重异或加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 使用多个密钥进行异或加密
function multiKeyXorEncrypt(data, keys) {
let result = data;
for (const key of keys) {
result ^= key;
}
return result;
}

// 示例:使用多个密钥加密游戏数据
const gameScore = 1000000;
const encryptionKeys = [123, 456, 789];
const encryptedScore = multiKeyXorEncrypt(gameScore, encryptionKeys);
const decryptedScore = multiKeyXorEncrypt(encryptedScore, encryptionKeys);

7.4 带随机密钥的异或加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 生成随机密钥并进行异或加密
function randomKeyXorEncrypt(data) {
const key = Math.floor(Math.random() * 0xFFFFFFFF); // 32位随机数
const encrypted = data ^ key;
return {
encrypted,
key
};
}

// 使用保存的密钥解密
function xorDecrypt(encrypted, key) {
return encrypted ^ key;
}

// 示例:游戏存档加密
const playerScore = 9999;
const { encrypted, key } = randomKeyXorEncrypt(playerScore);
const decrypted = xorDecrypt(encrypted, key);

// 存储格式:同时保存加密数据和密钥
const saveFormat = {
data: encrypted,
key: key
};

7.5 实际应用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 游戏密码系统
class GamePasswordSystem {
constructor(baseKey) {
this.baseKey = baseKey;
}

// 生成关卡密码
generateLevelPassword(level, score, lives) {
// 将游戏数据打包成一个数字
const gameData = (level << 20) | (score << 8) | lives;
// 添加简单的校验和
const checksum = (level + score + lives) & 0xFF;
// 组合数据
const fullData = (gameData << 8) | checksum;
// 加密
return this.encrypt(fullData);
}

// 验证并解析密码
validatePassword(password) {
try {
// 解密
const decrypted = this.decrypt(password);
// 提取校验和
const checksum = decrypted & 0xFF;
const gameData = decrypted >> 8;
// 解析数据
const level = (gameData >> 20) & 0xFFF;
const score = (gameData >> 8) & 0xFFF;
const lives = gameData & 0xFF;
// 验证校验和
if (checksum !== ((level + score + lives) & 0xFF)) {
return null;
}
return { level, score, lives };
} catch {
return null;
}
}

encrypt(data) {
return data ^ this.baseKey;
}

decrypt(data) {
return data ^ this.baseKey;
}
}

// 使用示例
const passwordSystem = new GamePasswordSystem(0xABCD1234);

// 生成密码
const password = passwordSystem.generateLevelPassword(
5, // 第5关
1000, // 分数1000
3 // 3条命
);

// 验证密码
const gameState = passwordSystem.validatePassword(password);
if (gameState) {
console.log('密码有效:', gameState);
} else {
console.log('密码无效');
}

7.6 注意事项

  1. 安全性考虑:

    • 异或加密是一种非常基础的加密方式,不适用于需要高安全性的场景
    • 如果密钥泄露,数据很容易被解密
    • 适合用于简单的游戏数据保护,不适合用于敏感信息
  2. 实现建议:

    • 可以组合使用多个密钥
    • 添加校验和机制来验证数据完整性
    • 考虑使用时间戳或随机数增加加密强度
    • 对于重要数据,建议使用专业的加密算法
  3. 性能优化:

    • 异或操作非常快,适合实时加解密
    • 对于大量数据,可以考虑使用TypedArray提高性能
    • 可以缓存常用的密钥组合

8. 游戏开发中的其他二进制应用

8.1 碰撞检测优化

使用位掩码来优化碰撞检测是一种常见的做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 定义碰撞层级
const CollisionLayers = {
NONE: 0, // 0000
PLAYER: 1 << 0, // 0001
ENEMY: 1 << 1, // 0010
BULLET: 1 << 2, // 0100
WALL: 1 << 3 // 1000
};

class GameObject {
constructor() {
this.layer = CollisionLayers.NONE;
this.collidesWith = CollisionLayers.NONE;
}

// 设置可以与哪些层级发生碰撞
setCollisionMask(layers) {
this.collidesWith = layers;
}

// 检查是否需要进行碰撞检测
shouldCheckCollision(otherObject) {
return (this.layer & otherObject.collidesWith) !== 0 ||
(otherObject.layer & this.collidesWith) !== 0;
}
}

// 使用示例
const player = new GameObject();
player.layer = CollisionLayers.PLAYER;
player.setCollisionMask(CollisionLayers.ENEMY | CollisionLayers.WALL);

const enemy = new GameObject();
enemy.layer = CollisionLayers.ENEMY;
enemy.setCollisionMask(CollisionLayers.PLAYER | CollisionLayers.BULLET);

8.2 技能系统

使用位运算管理技能状态和效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 技能效果定义
const SkillEffects = {
NONE: 0,
STUN: 1 << 0, // 眩晕
SILENCE: 1 << 1, // 沉默
POISON: 1 << 2, // 中毒
INVINCIBLE: 1 << 3, // 无敌
STEALTH: 1 << 4 // 隐身
};

class Character {
constructor() {
this.effects = SkillEffects.NONE;
}

setProperties(props) {
this.effects = props;
}

update() {
if (this.effects & SkillEffects.INVINCIBLE) {
this.applyInvincible();
}
if (this.effects & SkillEffects.STEALTH) {
this.updateAlpha();
}
// ... 其他更新逻辑
}
}

8.3 地图系统

使用位图表示地图数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class TileMap {
constructor(width, height) {
this.width = width;
this.height = height;
// 使用Uint8Array存储地图数据
this.data = new Uint8Array(width * height);
}

// 设置瓦片属性
setTileProperties(x, y, properties) {
const index = y * this.width + x;
this.data[index] = properties;
}

// 检查瓦片是否可行走
isWalkable(x, y) {
const properties = this.getTileProperties(x, y);
return (properties & TileProperties.BLOCKED) === 0;
}
}

const TileProperties = {
EMPTY: 0,
BLOCKED: 1 << 0, // 不可通过
WATER: 1 << 1, // 水域
DAMAGE: 1 << 2, // 伤害区域
TELEPORT: 1 << 3, // 传送点
TREASURE: 1 << 4 // 宝藏
};

8.4 输入系统

使用位运算处理按键组合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class InputManager {
constructor() {
this.currentKeys = 0;
this.previousKeys = 0;
}

// 按键定义
static Keys = {
UP: 1 << 0,
DOWN: 1 << 1,
LEFT: 1 << 2,
RIGHT: 1 << 3,
JUMP: 1 << 4,
ATTACK: 1 << 5
};

// 更新按键状态
setKey(key, pressed) {
if (pressed) {
this.currentKeys |= key;
} else {
this.currentKeys &= ~key;
}
}

// 检查按键组合
isComboPressed(combo) {
return (this.currentKeys & combo) === combo;
}

// 检查是否刚刚按下
isKeyJustPressed(key) {
return (this.currentKeys & key) !== 0 && (this.previousKeys & key) === 0;
}

update() {
this.previousKeys = this.currentKeys;
}
}

8.5 粒子系统

使用位运算管理粒子属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Particle {
constructor() {
this.properties = 0;
}

static Properties = {
GRAVITY: 1 << 0, // 受重力影响
COLLISION: 1 << 1, // 开启碰撞
FADE: 1 << 2, // 渐隐效果
BOUNCE: 1 << 3, // 反弹效果
TRAIL: 1 << 4 // 留下轨迹
};

setProperties(props) {
this.properties = props;
}

update() {
if (this.properties & Particle.Properties.GRAVITY) {
this.applyGravity();
}
if (this.properties & Particle.Properties.FADE) {
this.updateAlpha();
}
// ... 其他更新逻辑
}
}

8.6 存档系统

使用位运算压缩游戏进度数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class GameProgress {
constructor() {
this.unlockedLevels = 0;
this.collectedItems = 0;
this.completedQuests = 0;
}

// 解锁关卡
unlockLevel(levelId) {
this.unlockedLevels |= (1 << levelId);
}

// 收集物品
collectItem(itemId) {
this.collectedItems |= (1 << itemId);
}

// 完成任务
completeQuest(questId) {
this.completedQuests |= (1 << questId);
}

// 获取完成度百分比
getCompletionPercentage() {
const totalBits = 32; // 假设使用32位整数
const count = (num) => {
let bits = 0;
while (num) {
bits += num & 1;
num >>= 1;
}
return bits;
};

const collected = count(this.collectedItems);
const completed = count(this.completedQuests);
const unlocked = count(this.unlockedLevels);

return ((collected + completed + unlocked) / (totalBits * 3)) * 100;
}

// 导出存档数据
exportSave() {
return {
levels: this.unlockedLevels,
items: this.collectedItems,
quests: this.completedQuests
};
}
}