【Laya】LocalStorage 本地存储

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;
    }
}

注意事项

  1. 容量限制:浏览器 LocalStorage 通常限制约 5MB,超出会抛出异常
  2. 同步操作:存储/读取是同步的,大量数据可能阻塞主线程
  3. 隐私模式:浏览器隐私/无痕模式下可能无法使用
  4. 字符串存储 :setItem/getItem 只能存储字符串,数值需自行转换
  5. 跨域隔离:不同域名/端口/协议的存储相互独立
  6. 数据安全:存储的数据明文可见,敏感信息需加密
  7. 兼容性:部分浏览器在禁用 Cookie 时也会禁用 LocalStorage

相关文档

相关推荐
魔士于安3 天前
unity 丑陋哥布林 哥布林有humannoid 动画没 带动画
unity·游戏引擎·材质·贴图·模型
淡海水4 天前
13-04-面试-源码级深度追问链
数据结构·unity·面试·c#·游戏引擎·源码·il2cpp
小小数媒成员4 天前
UGUI 性能优化(详细,全,深入)
开发语言·unity·游戏引擎
右耳朵猫AI5 天前
Web前端周刊2026W38 | React 19.3 发布、StyleX 深潜、jsdom 30.1 提速
前端·javascript·react.js·typescript·node.js
溪语流沙5 天前
Django + Vue电商项目第001讲:开篇|注册登录加增删改查,那不是电商
redis·python·mysql·docker·typescript·django·vue
goujunwe5 天前
GEO 评估方法:如何自测你的内容,会不会被 AI 引用
搜索引擎·网络安全·游戏引擎·全文检索·中文分词
掰头战士6 天前
多重影分身!恨不得把一个agent掰成两个? 还真能干!
typescript·llm·agent
shmily麻瓜小菜鸡6 天前
TDD(测试驱动开发)详解
开发语言·javascript·typescript·node.js·ecmascript
林川~016 天前
Unity 常用 API 与核心类全解:从生命周期到协程,附性能避坑清单(Unity 6 适配)
unity·游戏引擎·常用api
sensen_kiss6 天前
CPT306 Coursework 1 个人游戏作业——坦克大战
unity·游戏程序·游戏策划