Laya.URL 路径管理使用指南

简介

Laya.URL 是 LayaAir 引擎提供的 URL 路径格式化和版本管理类。引擎加载资源时会自动调用 formatURL 函数格式化 URL 路径。

适用场景

  • 设置资源基础路径(CDN/远程服务器)
  • 资源版本管理(缓存更新)
  • URL 路径映射和重定向
  • 小游戏平台资源扩展名转换

工作原理

原始URL → formatURL → basePath拼接 → 版本号插入 → urlMapping映射 → customFormat处理 → 最终URL

核心优势

优势 说明
统一管理 集中管理所有资源路径
版本控制 自动添加版本号,解决缓存问题
环境切换 轻松切换开发/生产环境
路径映射 灵活映射资源到不同位置

目录


API 参考

静态属性

属性 类型 说明
basePath string 基础路径,默认为当前网页路径
basePaths Record<string, string> 扩展基础路径映射表,如 {"aa/": "http://abc.com/"}
version Record<string, string> URL 版本映射表,格式化后生成 文件名-版本号.扩展名
urlMapping Record<string, string> URL 映射表,将 URL 映射到不同路径
customFormat (url: string) => string 自定义格式化函数
rootPath string ~/ 开头的 URL 路径映射(不推荐,应使用 basePaths

实例属性

属性 类型 说明
url string 格式化后的地址(只读)
path string 地址的文件夹路径,不包括文件名(只读)

静态方法

方法 参数 返回值 说明
formatURL(url: string, base?: string) URL地址, 基础路径(可选) string 格式化 URL,合并 basePath 并处理版本
normalize(url: string) URL地址 string 格式化相对路径,处理 ...
join(base: string, path: string) 基础路径, 相对路径 string 组合相对路径并格式化
getPath(url: string) URL地址 string 获取文件夹路径(不包括文件名,末尾有 /
getFileName(url: string) URL地址 string 获取文件名
getURLVerion(url: string) URL地址 string 获取 URL 版本字符
overrideExtension(originalExts: string[], targetExt: string, miniGameOnly?: boolean) 原扩展名数组, 目标扩展名, 是否仅小游戏 void 覆盖文件扩展名
initMiniGameExtensionOverrides() void 初始化小游戏文件扩展名覆盖

基础用法

1. 设置基础路径

设置所有资源的公共基础路径:

// 设置 CDN 基础路径
Laya.URL.basePath = "https://cdn.example.com/assets/";

// 之后加载资源时会自动拼接
Laya.loader.load("images/player.png");  // 实际加载: https://cdn.example.com/assets/images/player.png

2. URL 版本管理

为资源添加版本号,解决缓存问题。由于 version 是只读属性,推荐使用 customFormat

// 使用 customFormat 实现版本管理
const versionMap: Record<string, string> = {
    "images/player.png": "v1.0.0",
    "data/config.json": "v2.3.1"
};

Laya.URL.customFormat = (url: string): string => {
    const version = versionMap[url];
    if (version) {
        // 插入版本号:images/player.png → images/player-v1.0.0.png
        const dotIndex = url.lastIndexOf(".");
        if (dotIndex > 0) {
            return url.substring(0, dotIndex) + "-" + version + url.substring(dotIndex);
        }
    }
    return url;
};

// 格式化后的 URL
// images/player.png → images/player-v1.0.0.png
// data/config.json → data/config-v2.3.1.json

3. 使用 URL 实例

创建 URL 实例获取格式化后的地址:

const url = new Laya.URL("resources/images/bg.jpg");

console.log(url.url);  // "resources/images/bg.jpg" (格式化后的地址)
console.log(url.path); // "resources/images/" (文件夹路径)

4. 路径拼接

组合多个路径段:

// 拼接路径
const fullPath = Laya.URL.join("resources", "images/player.png");
// 结果: "resources/images/player.png"

5. 规范化路径

处理包含 ... 的路径:

// 规范化路径
const normalized = Laya.URL.normalize("a/b/../c/./d.png");
// 结果: "a/c/d.png"

实用示例

示例1: 环境路径配置

根据不同环境配置资源路径:

@regClass()
export class ResourceConfig {
    /**
     * 初始化资源路径配置
     */
    public static init(): void {
        // 根据环境变量判断
        const isDev = import.meta.env.DEV;

        if (isDev) {
            // 开发环境:使用本地资源
            Laya.URL.basePath = "h5/";
        } else {
            // 生产环境:使用 CDN
            Laya.URL.basePath = "https://cdn.example.com/game-assets/v1.0/";
        }

        console.log("资源基础路径:", Laya.URL.basePath);
    }

    /**
     * 切换到 CDN 备份路径
     */
    public static switchToBackupCDN(): void {
        Laya.URL.basePath = "https://backup-cdn.example.com/game-assets/";
        console.log("已切换到备份 CDN");
    }
}

// 游戏启动时初始化
ResourceConfig.init();

示例2: 资源版本管理器

自动化资源版本管理,使用 customFormat 实现:

@regClass()
export class VersionManager {
    private static readonly VERSION_KEY = "resource_version";
    private static versionMap: Record<string, string> = {};

    /**
     * 初始化版本管理器
     */
    public static init(): void {
        // 加载保存的版本信息
        this.loadVersion();

        // 设置自定义格式化
        const originalFormat = Laya.URL.customFormat;
        Laya.URL.customFormat = (url: string): string => {
            // 应用版本号
            const version = this.versionMap[url];
            if (version) {
                const dotIndex = url.lastIndexOf(".");
                if (dotIndex > 0) {
                    url = url.substring(0, dotIndex) + "-" + version + url.substring(dotIndex);
                }
            }

            // 调用原始格式化
            return originalFormat ? originalFormat(url) : url;
        };
    }

    /**
     * 设置资源版本
     */
    public static setVersion(versionMap: Record<string, string>): void {
        this.versionMap = {
            ...this.versionMap,
            ...versionMap
        };

        // 保存版本信息到本地
        Laya.LocalStorage.setJSON(this.VERSION_KEY, this.versionMap);
    }

    /**
     * 加载保存的版本信息
     */
    public static loadVersion(): void {
        const saved = Laya.LocalStorage.getJSON(this.VERSION_KEY);
        if (saved) {
            this.versionMap = saved;
            console.log("已加载资源版本:", saved);
        }
    }

    /**
     * 清除版本缓存(用于开发调试)
     */
    public static clearVersion(): void {
        this.versionMap = {};
        Laya.LocalStorage.setItem(this.VERSION_KEY, "");
        console.log("已清除资源版本缓存");
    }

    /**
     * 更新单个资源版本
     */
    public static updateResourceVersion(path: string, version: string): void {
        this.versionMap[path] = version;
        Laya.LocalStorage.setJSON(this.VERSION_KEY, this.versionMap);
    }
}

// 使用示例
VersionManager.init();
VersionManager.setVersion({
    "assets/atlas/game.atlas": "20241201",
    "assets/sounds/bgm.mp3": "v2.0"
});

示例3: 多路径映射管理

管理不同类型的资源路径:

@regClass()
export class PathManager {
    /**
     * 初始化多路径映射
     */
    public static init(): void {
        // 图片资源使用本地路径
        Laya.URL.basePaths["images/"] = "assets/images/";

        // 音频资源使用 CDN
        Laya.URL.basePaths["audio/"] = "https://cdn.example.com/audio/";

        // 3D 资源使用单独的服务器
        Laya.URL.basePaths["models/"] = "https://models.example.com/";

        // 远程配置文件
        Laya.URL.basePaths["config/"] = "https://config.example.com/";
    }

    /**
     * 动态添加路径映射
     */
    public static addPath(alias: string, realPath: string): void {
        Laya.URL.basePaths[alias] = realPath;
    }

    /**
     * 移除路径映射
     */
    public static removePath(alias: string): void {
        delete Laya.URL.basePaths[alias];
    }
}

// 初始化路径管理器
PathManager.init();

// 之后加载资源时会自动映射
// "images/player.png" → "assets/images/player.png"
// "audio/bgm.mp3" → "https://cdn.example.com/audio/bgm.mp3"

示例4: 自定义 URL 格式化

实现自定义的 URL 格式化逻辑:

@regClass()
export class URLFormatter {
    /**
     * 初始化自定义格式化
     */
    public static init(): void {
        // 设置自定义格式化函数
        Laya.URL.customFormat = (url: string): string => {
            // 添加时间戳防止缓存(仅开发环境)
            if (import.meta.env.DEV) {
                const separator = url.includes("?") ? "&" : "?";
                return `${url}${separator}t=${Date.now()}`;
            }

            // 生产环境:压缩图片路径
            if (url.endsWith(".png")) {
                return url.replace(".png", ".webp");
            }

            return url;
        };
    }

    /**
     * 设置语言路径前缀
     */
    public static setLanguagePrefix(lang: string): void {
        const originalFormat = Laya.URL.customFormat;

        Laya.URL.customFormat = (url: string): string => {
            // 为 i18n 资源添加语言前缀
            if (url.startsWith("i18n/")) {
                url = url.replace("i18n/", `i18n/${lang}/`);
            }

            // 调用原始格式化
            return originalFormat ? originalFormat(url) : url;
        };
    }
}

// 初始化
URLFormatter.init();

示例5: 完整的资源管理器

综合管理资源路径、版本和映射:

@regClass()
export class ResourceManager {
    private static initialized = false;
    private static versionMap: Record<string, string> = {};

    /**
     * 初始化资源管理器
     */
    public static async init(): Promise<void> {
        if (this.initialized) return;

        // 1. 设置基础路径
        this.setupBasePath();

        // 2. 设置路径映射
        this.setupPathMapping();

        // 3. 加载版本信息
        this.loadVersions();

        // 4. 设置自定义格式化
        this.setupCustomFormat();

        this.initialized = true;
        console.log("资源管理器初始化完成");
    }

    /**
     * 设置基础路径
     */
    private static setupBasePath(): void {
        const env = this.getEnvironment();

        switch (env) {
            case "development":
                Laya.URL.basePath = "h5/";
                break;
            case "staging":
                Laya.URL.basePath = "https://staging-cdn.example.com/";
                break;
            case "production":
                Laya.URL.basePath = "https://cdn.example.com/";
                break;
        }
    }

    /**
     * 设置路径映射
     */
    private static setupPathMapping(): void {
        Laya.URL.basePaths = {
            "assets/": "assets/",
            "res/": "resources/",
            "ui/": "assets/ui/",
            "audio/": "assets/audio/",
            "scenes/": "assets/scenes/",
            "prefabs/": "assets/prefabs/"
        };
    }

    /**
     * 加载版本信息
     */
    private static loadVersions(): void {
        // 从本地加载或从服务器获取
        const localVersions = Laya.LocalStorage.getJSON("resource_versions");

        if (localVersions) {
            this.versionMap = localVersions;
        }
    }

    /**
     * 设置自定义格式化
     */
    private static setupCustomFormat(): void {
        Laya.URL.customFormat = (url: string): string => {
            // 应用版本号
            const version = this.versionMap[url];
            if (version) {
                const dotIndex = url.lastIndexOf(".");
                if (dotIndex > 0) {
                    url = url.substring(0, dotIndex) + "-" + version + url.substring(dotIndex);
                }
            }

            // 处理空格
            url = url.replace(/\s/g, "%20");

            // 处理中文字符
            url = encodeURI(url);

            return url;
        };
    }

    /**
     * 获取当前环境
     */
    private static getEnvironment(): string {
        // 根据域名或配置判断环境
        const hostname = window.location.hostname;

        if (hostname === "localhost" || hostname === "127.0.0.1") {
            return "development";
        } else if (hostname.includes("staging")) {
            return "staging";
        } else {
            return "production";
        }
    }

    /**
     * 格式化 URL(供外部使用)
     */
    public static formatURL(url: string): string {
        return Laya.URL.formatURL(url);
    }

    /**
     * 获取资源路径
     */
    public static getPath(url: string): string {
        return Laya.URL.getPath(url);
    }
}

// 使用示例
await ResourceManager.init();

// 加载资源时会自动应用所有配置
Laya.loader.load("ui/main.json");
Laya.loader.load("audio/bgm.mp3");

高级技巧

1. URL 映射表

使用 urlMapping 进行资源重定向:

// 设置 URL 映射
Laya.URL.urlMapping = {
    "old-assets/player.png": "new-assets/sprites/player.png",
    "old-ui/main.json": "new-ui/main-v2.json"
};

// 旧路径会自动映射到新路径
Laya.loader.load("old-assets/player.png");  // 实际加载: new-assets/sprites/player.png

2. 小游戏扩展名处理

小游戏平台可能需要特定的文件扩展名:

// 微信小游戏:将 .png 转换为 .jpg
Laya.URL.overrideExtension([".png"], ".jpg", true);

// 小游戏平台初始化
Laya.URL.initMiniGameExtensionOverrides();

3. 路径分析工具

@regClass()
export class PathAnalyzer {
    /**
     * 解析 URL 各部分
     */
    public static analyze(url: string): {
        fullPath: string;
        path: string;
        fileName: string;
        extension: string;
        version: string;
    } {
        const path = Laya.URL.getPath(url);
        const fileName = Laya.URL.getFileName(url);
        const version = Laya.URL.getURLVerion(url);
        const extension = fileName.substring(fileName.lastIndexOf("."));

        return {
            fullPath: Laya.URL.formatURL(url),
            path,
            fileName,
            extension,
            version
        };
    }

    /**
     * 检查是否为远程资源
     */
    public static isRemote(url: string): boolean {
        return url.startsWith("http://") || url.startsWith("https://");
    }

    /**
     * 检查是否为绝对路径
     */
    public static isAbsolute(url: string): boolean {
        return url.startsWith("/") || this.isRemote(url);
    }
}

// 使用
const info = PathAnalyzer.analyze("images/player.png");
console.log(info);
// {
//   fullPath: "https://cdn.example.com/images/player.png",
//   path: "images/",
//   fileName: "player.png",
//   extension: ".png",
//   version: ""
// }

4. 动态 CDN 切换

根据网络状况动态切换 CDN:

@regClass()
export class CDNSwitcher {
    private static cdnList = [
        "https://cdn1.example.com/",
        "https://cdn2.example.com/",
        "https://backup.example.com/"
    ];
    private static currentIndex = 0;

    /**
     * 测试 CDN 可用性
     */
    public static async testCDN(cdnUrl: string): Promise<boolean> {
        try {
            const response = await fetch(`${cdnUrl}health.json`, {
                method: "HEAD",
                cache: "no-cache"
            });
            return response.ok;
        } catch {
            return false;
        }
    }

    /**
     * 选择最快的 CDN
     */
    public static async selectBestCDN(): Promise<void> {
        for (let i = 0; i < this.cdnList.length; i++) {
            const isAvailable = await this.testCDN(this.cdnList[i]);
            if (isAvailable) {
                this.currentIndex = i;
                Laya.URL.basePath = this.cdnList[i];
                console.log("已切换到 CDN:", this.cdnList[i]);
                return;
            }
        }

        // 所有 CDN 都不可用,使用本地资源
        Laya.URL.basePath = "h5/";
        console.warn("所有 CDN 不可用,使用本地资源");
    }

    /**
     * 切换到下一个 CDN
     */
    public static switchCDN(): void {
        this.currentIndex = (this.currentIndex + 1) % this.cdnList.length;
        Laya.URL.basePath = this.cdnList[this.currentIndex];
        console.log("已切换到 CDN:", this.cdnList[this.currentIndex]);
    }
}

5. 资源预加载路径

@regClass()
export class PreloadPathConfig {
    /**
     * 生成预加载资源列表
     */
    public static getPreloadList(): string[] {
        const basePath = Laya.URL.basePath;

        return [
            // 核心 UI
            Laya.URL.formatURL("ui/loading.json"),

            // 必要图集
            Laya.URL.formatURL("assets/atlas/common.atlas"),

            // 启动音频
            Laya.URL.formatURL("audio/bgm.mp3"),

            // 配置文件
            Laya.URL.formatURL("config/game.json")
        ];
    }

    /**
     * 预加载所有资源
     */
    public static preload(): Promise<void> {
        const resources = this.getPreloadList();

        return new Promise((resolve, reject) => {
            Laya.loader.load(resources, {
                // 完成回调
                complete: () => {
                    console.log("预加载完成");
                    resolve();
                },
                // 进度回调
                progress: (progress: number) => {
                    console.log(`预加载进度: ${Math.floor(progress * 100)}%`);
                },
                // 错误回调
                error: (err: any) => {
                    console.error("预加载失败:", err);
                    reject(err);
                }
            } as any);
        });
    }
}

最佳实践

1. 环境分离

// ✅ 正确:根据环境配置
const config = {
    dev: { basePath: "h5/", cdn: "" },
    prod: { basePath: "", cdn: "https://cdn.example.com/" }
};

const current = config[import.meta.env.MODE];
Laya.URL.basePath = current.basePath;

// ❌ 错误:硬编码路径
Laya.URL.basePath = "https://cdn.example.com/";

2. 版本号命名规范

规范 示例 说明
语义化版本 v1.0.0 适合正式发布版本
日期版本 20241201 适合每日构建
哈希版本 a3f2c1 适合 CI/CD 自动构建
// ✅ 正确:使用 customFormat 实现有意义的版本号
const versionMap: Record<string, string> = {
    "game.atlas": "v1.2.0",
    "config.json": "20241201"
};

Laya.URL.customFormat = (url: string): string => {
    const version = versionMap[url];
    if (version) {
        const dotIndex = url.lastIndexOf(".");
        if (dotIndex > 0) {
            return url.substring(0, dotIndex) + "-" + version + url.substring(dotIndex);
        }
    }
    return url;
};

// ❌ 错误:尝试赋值给只读属性
// Laya.URL.version = {  // Error: Cannot assign to 'version' because it is read-only
//     "game.atlas": "v1.2.0"
// };

3. 路径常量管理

// ✅ 正确:使用常量管理路径
export class ResourcePaths {
    public static readonly UI = "ui/";
    public static readonly IMAGES = "assets/images/";
    public static readonly AUDIO = "assets/audio/";
    public static readonly SCENES = "assets/scenes/";
}

// 使用
Laya.loader.load(ResourcePaths.UI + "main.json");

4. 避免频繁修改 basePath

// ✅ 正确:使用 basePaths 处理多路径
Laya.URL.basePaths = {
    "images/": "local/images/",
    "audio/": "https://cdn.example.com/audio/"
};

// ❌ 错误:频繁修改 basePath
Laya.URL.basePath = "local/images/";
// ... 加载图片
Laya.URL.basePath = "https://cdn.example.com/audio/";
// ... 加载音频

5. 调试 URL 格式化

// ✅ 正确:调试时打印格式化结果
const originalFormatURL = Laya.URL.formatURL;
Laya.URL.formatURL = function(url: string, base?: string): string {
    const result = originalFormatURL.call(this, url, base);
    console.log(`[URL] ${url}${result}`);
    return result;
};

注意事项

  1. basePath 自动拼接:引擎加载资源时自动调用 formatURL,无需手动拼接
  2. 版本号格式:版本号会插入到文件名和扩展名之间,如 file-v1.0.0.png
  3. 路径映射优先级basePaths > basePath,具体路径映射优先于通用基础路径
  4. 自定义格式化customFormat 会覆盖默认格式化逻辑,谨慎使用
  5. 小游戏限制:小游戏平台对资源路径有特殊要求,使用 initMiniGameExtensionOverrides 适配
  6. CDN 缓存:版本号更新后,需要确保 CDN 已部署对应版本资源
  7. 相对路径:使用 ./../ 时,推荐用 normalize 规范化

相关文档

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐