Laya.LocalStorage 本地存储使用指南
简介
Laya.LocalStorage 是 LayaAir 引擎提供的本地存储管理类,用于实现数据的持久化存储。基于浏览器 LocalStorage API 封装,支持字符串和 JSON 对象的存储。
适用场景:
- 游戏存档、进度保存
- 玩家设置、配置项存储
- 离线数据缓存
- 用户偏好记录
工作原理:
数据 → 序列化 → 浏览器 LocalStorage → 持久保存 → 下次启动读取
核心优势:
| 优势 | 说明 |
|---|---|
| 数据持久化 | 浏览器关闭后数据仍保留 |
| 容量充足 | 通常约 5MB 存储空间 |
| API 简洁 | 字符串和 JSON 对象分别处理 |
| 跨会话 | 同一域名下数据共享 |
目录
API 参考
属性
| 属性 | 类型 | 说明 |
|---|---|---|
count |
number |
获取本地存储中存储的项目数量(只读) |
字符串存储
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
setItem(key: string, value: string) |
键名, 键值 | void |
存储字符串类型的键值对 |
getItem(key: string) |
键名 | `string | null` |
JSON 存储
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
setJSON(key: string, value: any) |
键名, 任意值 | void |
存储 Object 类型(自动序列化为 JSON) |
getJSON(key: string) |
键名 | any |
获取指定键名的 Object 值(自动反序列化) |
数据管理
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
removeItem(key: string) |
键名 | void |
删除指定键名的数据 |
clear() |
无 | void |
清除所有本地存储的数据 |
基础用法
1. 存储和读取字符串
最基础的存储方式:
typescript
// 存储数据
Laya.LocalStorage.setItem("username", "player1");
Laya.LocalStorage.setItem("level", "10");
Laya.LocalStorage.setItem("music", "0.8");
// 读取数据
let name = Laya.LocalStorage.getItem("username"); // "player1"
let level = Laya.LocalStorage.getItem("level"); // "10"
let music = Laya.LocalStorage.getItem("music"); // "0.8"
// 读取不存在的键返回 null
let unknown = Laya.LocalStorage.getItem("unknown"); // null
2. 存储和读取 JSON 对象
存储复杂对象时使用 setJSON/getJSON:
typescript
// 存储 JSON 对象
Laya.LocalStorage.setJSON("playerData", {
level: 25,
score: 15000,
inventory: ["sword", "shield", "potion"],
stats: {
hp: 100,
mp: 50,
attack: 35
}
});
// 读取 JSON 对象
let playerData = Laya.LocalStorage.getJSON("playerData");
console.log(playerData.level); // 25
console.log(playerData.inventory); // ["sword", "shield", "potion"]
console.log(playerData.stats.hp); // 100
3. 删除单个数据
typescript
// 删除指定键的数据
Laya.LocalStorage.removeItem("username");
// 验证删除
console.log(Laya.LocalStorage.getItem("username")); // null
4. 清空所有数据
typescript
// 清空所有存储
Laya.LocalStorage.clear();
// 检查数量
console.log(Laya.LocalStorage.count); // 0
5. 检查存储数量
typescript
// 存储一些数据
Laya.LocalStorage.setItem("key1", "value1");
Laya.LocalStorage.setItem("key2", "value2");
Laya.LocalStorage.setItem("key3", "value3");
// 获取存储数量
console.log(Laya.LocalStorage.count); // 3
实用示例
示例1: 游戏存档系统
完整的存档管理器,支持保存/读取/删除存档:
typescript
@regClass()
export class SaveManager {
private static readonly SAVE_KEY = "game_save_data";
private static readonly MAX_SAVES = 3;
/**
* 保存当前游戏进度
*/
public static save(slot: number = 1): boolean {
if (slot < 1 || slot > this.MAX_SAVES) {
console.error("存档槽位无效:", slot);
return false;
}
const saveData = {
slot: slot,
timestamp: Date.now(),
version: "1.0.0",
player: {
level: GameManager.playerLevel,
exp: GameManager.playerExp,
gold: GameManager.playerGold,
hp: GameManager.playerHp,
maxHp: GameManager.playerMaxHp
},
scene: GameManager.currentScene,
quests: GameManager.quests.map(q => ({
id: q.id,
status: q.status,
progress: q.progress
})),
settings: {
musicVolume: AudioManager.musicVolume,
soundVolume: AudioManager.soundVolume
}
};
const key = `${this.SAVE_KEY}_${slot}`;
Laya.LocalStorage.setJSON(key, saveData);
console.log(`存档已保存到槽位 ${slot}`);
return true;
}
/**
* 加载指定槽位的存档
*/
public static load(slot: number = 1): boolean {
if (slot < 1 || slot > this.MAX_SAVES) {
console.error("存档槽位无效:", slot);
return false;
}
const key = `${this.SAVE_KEY}_${slot}`;
const saveData = Laya.LocalStorage.getJSON(key);
if (!saveData) {
console.log(`槽位 ${slot} 没有存档`);
return false;
}
// 恢复游戏数据
GameManager.playerLevel = saveData.player.level;
GameManager.playerExp = saveData.player.exp;
GameManager.playerGold = saveData.player.gold;
GameManager.playerHp = saveData.player.hp;
GameManager.playerMaxHp = saveData.player.maxHp;
GameManager.currentScene = saveData.scene;
// 恢复任务
GameManager.loadQuests(saveData.quests);
// 恢复设置
AudioManager.musicVolume = saveData.settings.musicVolume;
AudioManager.soundVolume = saveData.settings.soundVolume;
console.log(`存档已加载,保存时间:${new Date(saveData.timestamp).toLocaleString()}`);
return true;
}
/**
* 检查指定槽位是否有存档
*/
public static hasSave(slot: number = 1): boolean {
const key = `${this.SAVE_KEY}_${slot}`;
return Laya.LocalStorage.getItem(key) !== null;
}
/**
* 获取存档信息(用于显示存档列表)
*/
public static getSaveInfo(slot: number = 1): any | null {
const key = `${this.SAVE_KEY}_${slot}`;
const saveData = Laya.LocalStorage.getJSON(key);
if (!saveData) return null;
return {
slot: slot,
timestamp: saveData.timestamp,
level: saveData.player.level,
scene: saveData.scene
};
}
/**
* 删除指定槽位的存档
*/
public static deleteSave(slot: number = 1): void {
const key = `${this.SAVE_KEY}_${slot}`;
Laya.LocalStorage.removeItem(key);
console.log(`槽位 ${slot} 的存档已删除`);
}
/**
* 获取所有存档信息
*/
public static getAllSaves(): any[] {
const saves = [];
for (let i = 1; i <= this.MAX_SAVES; i++) {
const info = this.getSaveInfo(i);
if (info) {
saves.push(info);
}
}
return saves;
}
}
// 使用示例
// 保存游戏
SaveManager.save(1);
// 检查是否有存档
if (SaveManager.hasSave(1)) {
// 加载存档
SaveManager.load(1);
}
// 获取所有存档信息
const saves = SaveManager.getAllSaves();
console.log(saves);
示例2: 游戏设置管理
设置数据的持久化存储:
typescript
@regClass()
export class GameSettings {
private static readonly SETTINGS_KEY = "game_settings";
// 默认设置
public static readonly DEFAULTS = {
musicVolume: 0.8,
soundVolume: 1.0,
enableVibration: true,
enableParticles: true,
language: "zh-CN",
quality: "high",
autoSave: true,
showFps: false
};
// 当前设置
static settings = { ...this.DEFAULTS };
/**
* 加载设置
*/
public static load(): void {
const saved = Laya.LocalStorage.getJSON(this.SETTINGS_KEY);
if (saved) {
// 合并保存的设置和默认设置(处理新增设置项)
this.settings = { ...this.DEFAULTS, ...saved };
}
console.log("设置已加载:", this.settings);
}
/**
* 保存设置
*/
public static save(): void {
Laya.LocalStorage.setJSON(this.SETTINGS_KEY, this.settings);
console.log("设置已保存");
}
/**
* 更新单个设置并保存
*/
public static update<K extends keyof typeof GameSettings.settings>(
key: K,
value: typeof GameSettings.settings[K]
): void {
this.settings[key] = value;
this.save();
}
/**
* 获取设置值
*/
public static get<K extends keyof typeof GameSettings.settings>(
key: K
): typeof GameSettings.settings[K] {
return this.settings[key];
}
/**
* 重置为默认设置
*/
public static reset(): void {
this.settings = { ...this.DEFAULTS };
this.save();
console.log("设置已重置");
}
/**
* 应用设置到游戏
*/
public static apply(): void {
AudioManager.musicVolume = this.settings.musicVolume;
AudioManager.soundVolume = this.settings.soundVolume;
Laya.Stat.show = this.settings.showFps;
}
}
// 游戏启动时加载设置
GameSettings.load();
GameSettings.apply();
// 修改设置
GameSettings.update("musicVolume", 0.5);
GameSettings.update("showFps", true);
示例3: 玩家数据统计
记录玩家统计数据:
typescript
@regClass()
export class PlayerStats {
private static readonly STATS_KEY = "player_stats";
static stats = {
totalPlayTime: 0, // 总游戏时长(秒)
totalGamesPlayed: 0, // 游戏次数
totalScore: 0, // 总得分
highestScore: 0, // 最高分
totalKills: 0, // 总击杀数
totalDeaths: 0, // 总死亡数
achievements: [] as string[], // 成就列表
firstPlayTime: 0, // 首次游戏时间
lastPlayTime: 0 // 最后游戏时间
};
/**
* 加载统计数据
*/
public static load(): void {
const saved = Laya.LocalStorage.getJSON(this.STATS_KEY);
if (saved) {
this.stats = { ...this.stats, ...saved };
} else {
// 首次游戏,记录时间
this.stats.firstPlayTime = Date.now();
}
this.stats.lastPlayTime = Date.now();
}
/**
* 保存统计数据
*/
public static save(): void {
Laya.LocalStorage.setJSON(this.STATS_KEY, this.stats);
}
/**
* 记录游戏结束
*/
public static recordGameEnd(score: number, kills: number, deaths: number): void {
this.stats.totalGamesPlayed++;
this.stats.totalScore += score;
this.stats.totalKills += kills;
this.stats.totalDeaths += deaths;
if (score > this.stats.highestScore) {
this.stats.highestScore = score;
}
this.save();
}
/**
* 解锁成就
*/
public static unlockAchievement(achievementId: string): void {
if (!this.stats.achievements.includes(achievementId)) {
this.stats.achievements.push(achievementId);
this.save();
console.log("成就解锁:", achievementId);
}
}
/**
* 获取统计信息
*/
public static getStats(): typeof PlayerStats.stats {
return { ...this.stats };
}
/**
* 计算平均得分
*/
public static getAverageScore(): number {
if (this.stats.totalGamesPlayed === 0) return 0;
return Math.floor(this.stats.totalScore / this.stats.totalGamesPlayed);
}
/**
* 计算 KDA
*/
public static getKDA(): number {
if (this.stats.totalDeaths === 0) return this.stats.totalKills;
return parseFloat((this.stats.totalKills / this.stats.totalDeaths).toFixed(2));
}
}
示例4: 离线缓存系统
缓存游戏资源数据:
typescript
@regClass()
export class CacheManager {
private static readonly CACHE_PREFIX = "cache_";
private static readonly CACHE_META = "cache_meta";
private static readonly DEFAULT_EXPIRE = 24 * 60 * 60 * 1000; // 24小时
/**
* 存储缓存数据(带过期时间)
*/
public static set(key: string, data: any, expireMs: number = this.DEFAULT_EXPIRE): void {
const cacheKey = this.CACHE_PREFIX + key;
const expireTime = Date.now() + expireMs;
const cacheData = {
data: data,
expire: expireTime
};
Laya.LocalStorage.setJSON(cacheKey, cacheData);
this.updateMeta(key, expireTime);
}
/**
* 获取缓存数据
*/
public static get(key: string): any | null {
const cacheKey = this.CACHE_PREFIX + key;
const cacheData = Laya.LocalStorage.getJSON(cacheKey);
if (!cacheData) {
return null;
}
// 检查是否过期
if (Date.now() > cacheData.expire) {
this.remove(key);
return null;
}
return cacheData.data;
}
/**
* 删除缓存
*/
public static remove(key: string): void {
const cacheKey = this.CACHE_PREFIX + key;
Laya.LocalStorage.removeItem(cacheKey);
}
/**
* 清理所有过期缓存
*/
public static cleanExpired(): void {
const meta = Laya.LocalStorage.getJSON(this.CACHE_META) || {};
const now = Date.now();
let cleaned = 0;
for (const key in meta) {
if (meta[key] < now) {
this.remove(key);
delete meta[key];
cleaned++;
}
}
if (cleaned > 0) {
Laya.LocalStorage.setJSON(this.CACHE_META, meta);
console.log(`清理了 ${cleaned} 个过期缓存`);
}
}
/**
* 清空所有缓存
*/
public static clear(): void {
const meta = Laya.LocalStorage.getJSON(this.CACHE_META) || {};
for (const key in meta) {
Laya.LocalStorage.removeItem(this.CACHE_PREFIX + key);
}
Laya.LocalStorage.removeItem(this.CACHE_META);
}
/**
* 更新缓存元数据
*/
private static updateMeta(key: string, expireTime: number): void {
const meta = Laya.LocalStorage.getJSON(this.CACHE_META) || {};
meta[key] = expireTime;
Laya.LocalStorage.setJSON(this.CACHE_META, meta);
}
}
// 使用示例
// 缓存服务器数据,1小时过期
CacheManager.set("player_info", serverData, 60 * 60 * 1000);
// 获取缓存
const data = CacheManager.get("player_info");
// 定期清理过期缓存
setInterval(() => {
CacheManager.cleanExpired();
}, 60 * 60 * 1000); // 每小时清理一次
示例5: 数据版本迁移
处理数据结构升级:
typescript
@regClass()
export class MigrationManager {
private static readonly VERSION_KEY = "data_version";
private static readonly CURRENT_VERSION = 3;
/**
* 执行数据迁移
*/
public static migrate(): void {
const savedVersion = parseInt(Laya.LocalStorage.getItem(this.VERSION_KEY) || "0");
if (savedVersion >= this.CURRENT_VERSION) {
console.log("数据已是最新版本");
return;
}
console.log(`开始数据迁移:${savedVersion} → ${this.CURRENT_VERSION}`);
// 依次执行迁移
for (let v = savedVersion + 1; v <= this.CURRENT_VERSION; v++) {
this.runMigration(v);
}
// 更新版本号
Laya.LocalStorage.setItem(this.VERSION_KEY, this.CURRENT_VERSION.toString());
console.log("数据迁移完成");
}
/**
* 执行指定版本的迁移
*/
private static runMigration(version: number): void {
console.log(`执行迁移 v${version}`);
switch (version) {
case 1:
this.migrateToV1();
break;
case 2:
this.migrateToV2();
break;
case 3:
this.migrateToV3();
break;
}
}
/**
* 迁移到版本1:重命名键名
*/
private static migrateToV1(): void {
const oldData = Laya.LocalStorage.getJSON("player");
if (oldData) {
Laya.LocalStorage.setJSON("player_data", oldData);
Laya.LocalStorage.removeItem("player");
}
}
/**
* 迁移到版本2:添加新字段
*/
private static migrateToV2(): void {
const settings = Laya.LocalStorage.getJSON("game_settings");
if (settings && !settings.quality) {
settings.quality = "high";
Laya.LocalStorage.setJSON("game_settings", settings);
}
}
/**
* 迁移到版本3:重构数据结构
*/
private static migrateToV3(): void {
const oldSaves = Laya.LocalStorage.getJSON("game_save_data");
if (oldSaves) {
const newSaves = {
slot: 1,
timestamp: oldSaves.timestamp || Date.now(),
player: {
level: oldSaves.level || 1,
exp: oldSaves.exp || 0
}
};
Laya.LocalStorage.setJSON("game_save_data_1", newSaves);
Laya.LocalStorage.removeItem("game_save_data");
}
}
}
// 游戏启动时执行迁移
MigrationManager.migrate();
高级技巧
1. 数据加密存储
敏感数据加密后存储:
typescript
@regClass()
export class SecureStorage {
// 简单的 XOR 加密(生产环境应使用更安全的加密方式)
private static encrypt(data: string, key: string = "layaair"): string {
let result = "";
for (let i = 0; i < data.length; i++) {
result += String.fromCharCode(
data.charCodeAt(i) ^ key.charCodeAt(i % key.length)
);
}
return btoa(result); // Base64 编码
}
private static decrypt(data: string, key: string = "layaair"): string {
const decoded = atob(data); // Base64 解码
let result = "";
for (let i = 0; i < decoded.length; i++) {
result += String.fromCharCode(
decoded.charCodeAt(i) ^ key.charCodeAt(i % key.length)
);
}
return result;
}
public static setItem(key: string, value: string): void {
const encrypted = this.encrypt(value);
Laya.LocalStorage.setItem("sec_" + key, encrypted);
}
public static getItem(key: string): string | null {
const encrypted = Laya.LocalStorage.getItem("sec_" + key);
if (!encrypted) return null;
return this.decrypt(encrypted);
}
}
// 使用
SecureStorage.setItem("token", "player_secret_token");
const token = SecureStorage.getItem("token");
2. 数据压缩存储
使用 JSON 简化减少存储空间:
typescript
@regClass()
export class CompressedStorage {
/**
* 存储时只保留必要字段
*/
public static setPlayerData(data: any): void {
const compressed = {
l: data.level, // level -> l
e: data.exp, // exp -> e
g: data.gold, // gold -> g
i: data.inventory // inventory -> i
};
Laya.LocalStorage.setJSON("p", compressed);
}
/**
* 读取时恢复完整字段名
*/
public static getPlayerData(): any | null {
const compressed = Laya.LocalStorage.getJSON("p");
if (!compressed) return null;
return {
level: compressed.l,
exp: compressed.e,
gold: compressed.g,
inventory: compressed.i
};
}
}
3. 使用常量定义键名
避免字符串拼写错误:
typescript
export class StorageKeys {
// 存档相关
public static readonly SAVE_DATA = "game_save_data";
public static readonly AUTO_SAVE = "auto_save";
// 设置相关
public static readonly SETTINGS = "game_settings";
public static readonly AUDIO_SETTINGS = "audio_settings";
// 玩家相关
public static readonly PLAYER_DATA = "player_data";
public static readonly PLAYER_STATS = "player_stats";
// 缓存相关
public static readonly CACHE_PREFIX = "cache_";
}
// 使用
Laya.LocalStorage.setJSON(StorageKeys.SAVE_DATA, saveData);
Laya.LocalStorage.setItem(StorageKeys.SETTINGS, JSON.stringify(settings));
4. 存储空间检测
检测存储空间是否充足:
typescript
@regClass()
export class StorageChecker {
/**
* 检查存储空间
*/
public static checkSpace(): { used: number; remaining: number; percent: number } {
let used = 0;
for (let i = 0; i < Laya.LocalStorage.count; i++) {
const key = localStorage.key(i);
if (key) {
used += localStorage.getItem(key)?.length || 0;
}
}
const total = 5 * 1024 * 1024; // 约 5MB
const remaining = total - used;
const percent = Math.floor((used / total) * 100);
return { used, remaining, percent };
}
/**
* 检查是否能存储数据
*/
public static canStore(data: any): boolean {
const json = JSON.stringify(data);
const space = this.checkSpace();
return json.length < space.remaining;
}
/**
* 显示存储信息
*/
public static logInfo(): void {
const space = this.checkSpace();
console.log(`存储使用情况: ${space.percent}%`);
console.log(`已使用: ${(space.used / 1024).toFixed(2)} KB`);
console.log(`剩余: ${(space.remaining / 1024).toFixed(2)} KB`);
}
}
5. 批量操作
typescript
@regClass()
export class BatchStorage {
/**
* 批量存储
*/
public static setItems(items: Record<string, string>): void {
for (const key in items) {
Laya.LocalStorage.setItem(key, items[key]);
}
}
/**
* 批量获取
*/
public static getItems(keys: string[]): Record<string, string | null> {
const result: Record<string, string | null> = {};
for (const key of keys) {
result[key] = Laya.LocalStorage.getItem(key);
}
return result;
}
/**
* 批量删除
*/
public static removeItems(keys: string[]): void {
for (const key of keys) {
Laya.LocalStorage.removeItem(key);
}
}
/**
* 按前缀删除
*/
public static removeByPrefix(prefix: string): void {
const keys: string[] = [];
for (let i = 0; i < Laya.LocalStorage.count; i++) {
const key = localStorage.key(i);
if (key && key.startsWith(prefix)) {
keys.push(key);
}
}
this.removeItems(keys);
}
}
// 使用
BatchStorage.setItems({
"key1": "value1",
"key2": "value2",
"key3": "value3"
});
BatchStorage.removeByPrefix("cache_");
最佳实践
1. 合理规划数据结构
| 建议 | 说明 |
|---|---|
| 使用 JSON 存储复杂对象 | 用 setJSON/getJSON 处理对象 |
| 键名使用前缀分类 | 如 save_, settings_, cache_ |
| 避免存储大文件 | 图片、音频等不应存入 LocalStorage |
| 定期清理过期数据 | 实现缓存过期机制 |
2. 错误处理
typescript
// ✅ 正确:添加错误处理
public static safeSetJSON(key: string, value: any): boolean {
try {
Laya.LocalStorage.setJSON(key, value);
return true;
} catch (e) {
console.error("存储失败:", e);
// 可能是空间不足或隐私模式
return false;
}
}
public static safeGetJSON<T>(key: string): T | null {
try {
return Laya.LocalStorage.getJSON(key) as T;
} catch (e) {
console.error("读取失败:", e);
return null;
}
}
3. 版本控制
typescript
// ✅ 正确:为数据添加版本信息
interface SaveData {
version: string;
timestamp: number;
player: PlayerData;
}
const saveData: SaveData = {
version: "1.0.0",
timestamp: Date.now(),
player: { ... }
};
4. 数据验证
typescript
// ✅ 正确:验证读取的数据
public static loadPlayerData(): PlayerData | null {
const data = Laya.LocalStorage.getJSON("player_data");
// 验证数据完整性
if (!data || typeof data !== "object") return null;
if (!data.level || typeof data.level !== "number") return null;
if (!data.inventory || !Array.isArray(data.inventory)) return null;
return data;
}
5. 默认值处理
typescript
// ✅ 正确:提供默认值
export class GameSettings {
private static readonly DEFAULTS = {
musicVolume: 0.8,
soundVolume: 1.0
};
public static getMusicVolume(): number {
const saved = Laya.LocalStorage.getItem("musicVolume");
return saved ? parseFloat(saved) : this.DEFAULTS.musicVolume;
}
}
注意事项
- 容量限制:浏览器 LocalStorage 通常限制约 5MB,超出会抛出异常
- 同步操作:存储/读取是同步的,大量数据可能阻塞主线程
- 隐私模式:浏览器隐私/无痕模式下可能无法使用
- 字符串存储 :
setItem/getItem只能存储字符串,数值需自行转换 - 跨域隔离:不同域名/端口/协议的存储相互独立
- 数据安全:存储的数据明文可见,敏感信息需加密
- 兼容性:部分浏览器在禁用 Cookie 时也会禁用 LocalStorage