我有个小项目是这样的,我打包的时候,除了一个默认场景,所有的场景都是通过bundle来实现的,我为了让一个程序包做大限度的发挥作用,不用频繁提交包,就设计了一套流程,在application.js中,我是这样实现的
System.register([], function (_export, _context) {
‘use strict’;
/**
- 打印日志
- @param {string} message
-
@param {any} args
*/
function bootLog() {
if (!DEBUG_BOOT) return;
var a = Array.prototype.slice.call(arguments);
a.unshift(LOG_TAG);
console.log.apply(console, a);
}
/**
- 瀑布流:按序执行异步步骤;上一步 resolve 的值作为下一步的第一个参数。
- @param {Array<function(prev: *): *>} steps 每步可返回 Promise 或同步值
- @param {*} [seed] 第一步收到的 prev,默认 undefined
- @param {string[]|undefined} stepLabels 可选;DEBUG_BOOT 时打印 [瀑布] 序号/标签
-
@returns {Promise<*>} 最后一步的 resolve 值
*/
function runWaterfall(steps, seed, stepLabels) {
var initial = arguments.length > 1 ? seed : undefined;
return steps.reduce(function (promise, step, index) {
return promise.then(function (prev) {
if (DEBUG_BOOT && stepLabels && stepLabels[index]) {
bootLog(’[瀑布]’, String(index + 1) + ‘/’ + steps.length, stepLabels[index]);
}
return Promise.resolve(step(prev));
});
}, Promise.resolve(initial));
}
/**
- 远程 bundle 根:与 bc.json 同目录(由 BC_CONFIG_URL 推导)
/
function resolveBundleBaseUrl() {
var s = String(BC_CONFIG_URL).split(’?’)[0].split(’#’)[0];
var m = s.match(/^(\w+://[^/]+)(/.)?$/);
if (m) {
if (m[2] && m[2].length > 1) {
var path = m[2];
var li = path.lastIndexOf(’/’);
return m[1] + path.slice(0, li + 1);
}
return m[1] + ‘/’;
}
var last = s.lastIndexOf(’/’);
if (last > 6) {
return s.slice(0, last + 1);
}
return s + (s.charAt(s.length - 1) === ‘/’ ? ‘’ : ‘/’);
}
/** 为 true:引擎 DebugMode.INFO、显示 FPS,并输出启动日志;发布请 false */
var DEBUG_BOOT = true;
/** 仅 DEBUG_BOOT 时输出;统一前缀,adb:adb logcat | findstr [qt-remote] */
var LOG_TAG = ‘[qt-remote]’;
/** bc.json 请求超时(毫秒) */
var FETCH_BC_TIMEOUT_MS = 15000;
/** cc.game.init 使用的 settings 路径;打包与包内文件名对齐时改此处 */
var SETTINGS_PATH = ‘src/settings.f39eb.json’;
/** bc.json 请求地址 */
var BC_CONFIG_URL = ‘https://test.dereky.cn/bc.json’;
var cc = null;
return {
setters: [],
execute: function () {
class Application {
constructor() {
/** @see Step 0.1 */
this.settingsPath = SETTINGS_PATH;
this.bcConfigUrl = BC_CONFIG_URL;
this.fallbackSceneName = 'scene';
}
init(engine) {
cc = engine;
cc.game.onPostBaseInitDelegate.add(this.onPostInitBase.bind(this));
cc.game.onPostSubsystemInitDelegate.add(this.onPostSystemInit.bind(this));
}
onPostInitBase() { }
onPostSystemInit() { }
/**
* Step 0:引擎初始化 + 进入自定义引导链
*/
start() {
var debugMode = DEBUG_BOOT ? cc.DebugMode.INFO : cc.DebugMode.ERROR;
return cc.game
.init({
debugMode: debugMode,
settingsPath: this.settingsPath,
overrideSettings: {
launch: { launchScene: '' },
profiling: { showFPS: DEBUG_BOOT },
},
})
.then(
function () {
cc.game.run();
// 启动远程链,按照顺序加载 bc.json、common、main_page
return this.bootstrapRemoteOrFallback();
}.bind(this),
)
.catch(
function (e) {
bootLog('启动失败', e && e.message ? e.message : e);
return this.loadDefaultScene();
}.bind(this),
);
}
/** runWaterfall(bc → common → main);失败冒泡到 start().catch */
bootstrapRemoteOrFallback() {
return runWaterfall(
[
this.bootStep01_fetchBcConfig.bind(this),
this.bootStep02_loadRemoteCommon.bind(this),
this.bootStep03_loadRemoteMainPage.bind(this),
],
undefined,
['拉取bc', '加载公共包', '加载主入口'],
);
}
/** Waterfall 第 1 步:HTTP GET bc.json(超时、_cb、解析 JSON) */
bootStep01_fetchBcConfig() {
// 超时时长
var ms = FETCH_BC_TIMEOUT_MS;
// 超时控制器
var ctrl = typeof AbortController !== 'undefined' ? new AbortController() : null;
// 超时定时器
var t = setTimeout(function () {
if (ctrl) ctrl.abort();
}, ms);
// bc.json 请求地址
var bcUrl = this.bcConfigUrl;
// 请求参数,加上时间避免缓存
var sep = bcUrl.indexOf('?') >= 0 ? '&' : '?';
bcUrl = bcUrl + sep + '_cb=' + Date.now();
// 打印请求地址
bootLog('配置请求地址:', bcUrl);
// 请求 bc.json
return fetch(bcUrl, ctrl ? { signal: ctrl.signal } : {})
// 请求成功
.then(function (r) {
// 清除超时定时器
clearTimeout(t);
// 打印响应状态
bootLog('配置响应状态:', r.status, r.statusText || '');
// 响应不成功则抛出错误
if (!r.ok) {
throw new Error('bc.json 响应异常 HTTP ' + r.status);
}
// 解析 JSON
return r.json();
})
.then(function (cfg) {
bootLog('配置内容:', JSON.stringify(cfg));
return cfg;
})
.catch(function (e) {
clearTimeout(t);
throw e;
});
}
/** Waterfall 第 2 步:按 bc.common_bundle 远程 loadBundle; */
bootStep02_loadRemoteCommon(cfg) {
// 远程 bundle 根
var base = resolveBundleBaseUrl();
// 远程 common 配置
var cb = cfg.common_bundle;
// 远程 common 版本
var cv = cb.bundle_version;
// 打印远程 common 信息
bootLog('加载远程公共包:', cb.bundle_name, '根地址:', base);
// 加载远程 common
return this.loadRemoteBundleOnly(base, cb.bundle_name, cv).then(function () {
return cfg;
});
}
/** Waterfall 第 3 步:resolveBundleBaseUrl → loadBundle(main_page) → loadScene → runScene */
bootStep03_loadRemoteMainPage(cfg) {
// 远程 main_page 配置
var mp = cfg.main_page;
// 远程 bundle 根
var base = resolveBundleBaseUrl();
// 打印远程 main_page 信息
bootLog('主入口包:', mp.bundle_name, '场景:', mp.scene_name);
// 远程 bundle URL
var bundleUrl = base + mp.bundle_name;
// 远程场景名称
var sceneName = mp.scene_name;
// 远程 main_page 版本
var version = mp.bundle_version;
// 打印远程 main_page 信息
bootLog('远程包根地址:', bundleUrl);
bootLog('场景名:', sceneName, '版本号:', version);
// 加载远程 main_page
return this.loadRemoteBundleOnly(base, mp.bundle_name, version).then(function (bundle) {
return new Promise(function (resolve, reject) {
bootLog('加载场景开始:', sceneName);
bundle.loadScene(sceneName, function (err2, scene) {
if (err2) {
bootLog('加载场景失败', sceneName, err2 && err2.message ? err2.message : err2);
reject(err2);
return;
}
bootLog('加载场景成功,进入场景:', sceneName);
cc.director.runScene(scene);
resolve();
});
});
});
}
/** 加载默认场景 */
loadDefaultScene() {
// 默认场景名称
var name = this.fallbackSceneName;
// 加载默认场景
return new Promise(function (resolve) {
// 加载默认场景
cc.assetManager.loadBundle('main', function (err, bundle) {
// 加载失败
if (err) {
bootLog('默认场景:主包加载失败', err);
// 加载默认场景
cc.director.loadScene(name, function () {
// 加载成功
resolve();
});
return;
}
// 加载默认场景
bundle.loadScene(name, function (err2, scene) {
// 加载失败
if (err2) {
bootLog('默认场景:场景加载失败', err2);
cc.director.loadScene(name, function () {
resolve();
});
return;
}
cc.director.runScene(scene);
resolve();
});
});
});
}
/**
* 仅远程 loadBundle、不进场景;第 2 步 common 与第 3 步 main_page 共用,避免复制两套 loadBundle。
*/
loadRemoteBundleOnly(base, bundleName, bundleVersion) {
// 远程 bundle URL
var bundleUrl = base + bundleName;
// 远程 bundle 选项
var opts = {};
// 远程 bundle 版本
opts.version = bundleVersion;
// 打印远程 bundle 信息
bootLog('仅加载远程包:', bundleName, '地址:', bundleUrl, '版本:', opts.version);
// 远程 bundle 文件进度
opts.onFileProgress = function (finished, total, item) {
var u = item && (item.url || item.requestURL || item.id) ? String(item.url || item.requestURL || item.id) : '';
bootLog('远程包下载进度', bundleName, String(finished) + '/' + String(total), u || '(无地址)');
};
// 资产管理器
var am = cc && cc.assetManager;
// 已加载的远程 bundle
var existed = bundleName && am.getBundle && am.getBundle(bundleName);
// 已加载的远程 bundle
if (existed) {
// 已加载的远程 bundle 基础 URL
var b = existed.base != null ? String(existed.base) : '';
// 已加载的远程 bundle 是否为远程
var isRemote = b.indexOf('http://') === 0 || b.indexOf('https://') === 0;
// 已加载的远程 bundle 是否为远程
if (isRemote) {
// 已加载的远程 bundle 释放所有资源
existed.releaseAll();
// 已加载的远程 bundle 移除
am.removeBundle(existed);
}
}
// 加载远程 bundle
return new Promise(function (resolve, reject) {
// 打印加载远程 bundle 信息
bootLog('开始加载远程包:', bundleName);
am.loadBundle(bundleUrl, opts, function (err, bundle) {
if (err) {
bootLog('加载远程包失败', bundleName, err && err.message ? err.message : err);
reject(err);
return;
}
bootLog('加载远程包成功', bundle && bundle.name, bundle && bundle.base);
resolve(bundle);
});
});
}
}
_export('Application', Application);
},
};
});
我有一个名叫common的bundle,在启动游戏的时候,先去远程加载这个bundle,只有在这个bundle加载成功时,才去加载下一个bundle,在这里,是加载一个名叫wether的bundle,然后在这个bundle中,主要的场景里的背景上,写了一个effect,内容如下
// Copyright © 2017-2020 Xiamen Yaji Software Co., Ltd.
// 彩虹水波纹效果 shader
CCEffect %{
techniques:
- passes:
- vert: rainbow-wave-vs:vert
frag: rainbow-wave-fs:frag
depthStencilState:
depthTest: false
depthWrite: false
blendState:
targets:- blend: true
blendSrc: src_alpha
blendDst: one_minus_src_alpha
blendDstAlpha: one_minus_src_alpha
rasterizerState:
cullMode: none
properties:
alphaThreshold: { value: 0.5 }
waveDirX: { value: 1.0, editor: { tooltip: ‘水波传播方向 X(与 waveDirY 组成方向向量;勿全为 0)’ } }
waveDirY: { value: 1.0, editor: { tooltip: ‘水波传播方向 Y’ } }
waveSpeed: { value: 2.0, editor: { tooltip: ‘水波扭曲速度(须 waveAmplitude>0 才能看到 UV扭曲;也会进 wave() 影响颜色「跟波」程度)’ } }
waveScale: { value: 10.0, editor: { tooltip: ‘水波纹密度’ } }
waveAmplitude: { value: 0.1, editor: { tooltip: ‘UV 扭曲强度:水波「鼓起来」靠它,建议 0.02~0.08;0 则只有颜色涟漪、贴图不形变’ } }
rainbowSpeed: { value: 0.5, editor: { tooltip: ‘三色渐变转速:越大换色越快,与时间线性相关(建议 0.05~2)’ } }
paletteWaveInfluence: { value: 0.75, editor: { tooltip: ‘颜色「水波感」:越大越按圆环/定向涟漪变色(推荐 0.6~1);越小越接近整张贴图匀速渐变’ } }
rainbowIntensity: { value: 0.8, editor: { tooltip: ‘叠加颜色强度’ } }
waveColor0: { value: [1.0, 0.25, 0.25, 1.0], editor: { type: color, displayName: ‘水波颜色 1’ } }
waveColor1: { value: [0.25, 1.0, 0.45, 1.0], editor: { type: color, displayName: ‘水波颜色 2’ } }
waveColor2: { value: [0.35, 0.55, 1.0, 1.0], editor: { type: color, displayName: ‘水波颜色 3’ } }
saturation: { value: 1.2, editor: { tooltip: ‘饱和度’ } }
brightness: { value: 1.0, editor: { tooltip: ‘亮度’ } }
}%
- blend: true
- vert: rainbow-wave-vs:vert
CCProgram rainbow-wave-vs %{
precision highp float;
#include <builtin/uniforms/cc-global>
#if USE_LOCAL
#include <builtin/uniforms/cc-local>
#endif
#if SAMPLE_FROM_RT
#include <common/common-define>
#endif
in vec3 a_position;
in vec2 a_texCoord;
in vec4 a_color;
out vec4 color;
out vec2 uv0;
vec4 vert () {
vec4 pos = vec4(a_position, 1);
#if USE_LOCAL
pos = cc_matWorld * pos;
#endif
#if USE_PIXEL_ALIGNMENT
pos = cc_matView * pos;
pos.xyz = floor(pos.xyz);
pos = cc_matProj * pos;
#else
pos = cc_matViewProj * pos;
#endif
uv0 = a_texCoord;
#if SAMPLE_FROM_RT
CC_HANDLE_RT_SAMPLE_FLIP(uv0);
#endif
color = a_color;
return pos;
}
}%
CCProgram rainbow-wave-fs %{
precision highp float;
#include <builtin/internal/embedded-alpha>
#include <builtin/internal/alpha-test>
#include <builtin/uniforms/cc-global>
in vec4 color;
// 与 rainbow-wave-vs 的 out vec2 uv0 始终成对;勿放在 #if USE_TEXTURE 内,否则 USE_TEXTURE=0 时
// Metal/SPIRV 链路的 VS/FS 接口不一致,易出现 Missing entry point / pipeline layout 报错。
in vec2 uv0;
#if USE_TEXTURE
#pragma builtin(local)
layout(set = 2, binding = 12) uniform sampler2D cc_spriteTexture;
#endif
// Constant 内避免 vec2+float 混排(部分 Metal/驱动对 UBO 步进敏感);方向用两个 float,与 plasma_jet 等一致。
uniform Constant {
vec4 waveColor0;
vec4 waveColor1;
vec4 waveColor2;
float waveDirX;
float waveDirY;
float waveSpeed;
float waveScale;
float waveAmplitude;
float rainbowSpeed;
float rainbowIntensity;
float saturation;
float brightness;
float paletteWaveInfluence;
};
// 在三种颜色间循环插值:0→1→2→0,t 为任意实数相位
vec3 paletteThree(float t, vec3 c0, vec3 c1, vec3 c2) {
float x = fract(t) * 3.0;
float seg = floor(x);
float w = fract(x);
if (seg < 0.5) {
return mix(c0, c1, w);
}
if (seg < 1.5) {
return mix(c1, c2, w);
}
return mix(c2, c0, w);
}
// 水波纹函数 - 随机律动版本
float wave(vec2 uv, float time) {
// 使用多个不同频率和相位的正弦波创造随机效果
float wave1 = sin(uv.x * waveScale * 1.0 + time * waveSpeed * 0.8) * 0.5 + 0.5;
float wave2 = sin(uv.y * waveScale * 1.3 + time * waveSpeed * 1.2) * 0.5 + 0.5;
float wave3 = sin((uv.x + uv.y) * waveScale * 0.7 + time * waveSpeed * 0.6) * 0.5 + 0.5;
float wave4 = sin((uv.x - uv.y) * waveScale * 1.1 + time * waveSpeed * 1.4) * 0.5 + 0.5;
float wave5 = sin(uv.x * waveScale * 0.5 + uv.y * waveScale * 0.9 + time * waveSpeed * 0.9) * 0.5 + 0.5;
// 添加噪声效果
float noise1 = sin(uv.x * 20.0 + time * 2.0) * sin(uv.y * 15.0 + time * 1.5) * 0.1;
float noise2 = cos(uv.x * 12.0 + time * 1.8) * cos(uv.y * 18.0 + time * 2.2) * 0.1;
// 混合多个水波纹和噪声
float waveValue = (wave1 + wave2 + wave3 + wave4 + wave5) / 5.0 + noise1 + noise2;
// 添加径向水波纹效果
float distance = length(uv - vec2(0.5, 0.5));
float radialWave = sin(distance * waveScale * 2.0 + time * waveSpeed * 0.7) * 0.3;
// 最终混合
waveValue = (waveValue + radialWave) * 0.6;
return clamp(waveValue, 0.0, 1.0);
}
vec4 frag () {
vec4 o = vec4(1, 1, 1, 1);
#if USE_TEXTURE
float time = cc_time.x;
// 计算水波纹偏移
float waveValue = wave(uv0, time);
// 应用水波纹偏移到UV坐标 - 随机版本
vec2 waveOffset = vec2(
sin(uv0.x * waveScale * 1.2 + time * waveSpeed * 0.9) * waveAmplitude * 0.8 +
cos(uv0.y * waveScale * 0.8 + time * waveSpeed * 1.1) * waveAmplitude * 0.6,
cos(uv0.y * waveScale * 1.1 + time * waveSpeed * 0.7) * waveAmplitude * 0.8 +
sin(uv0.x * waveScale * 0.9 + time * waveSpeed * 1.3) * waveAmplitude * 0.6
);
// 添加径向扭曲(中心点避免 normalize(0))
vec2 center = vec2(0.5, 0.5);
vec2 fromCenter = uv0 - center;
float distance = length(fromCenter);
vec2 radialDir = distance > 1e-4 ? fromCenter / distance : vec2(1.0, 0.0);
vec2 radialOffset = radialDir * sin(distance * waveScale * 3.0 + time * waveSpeed * 0.5) * waveAmplitude * 0.3;
vec2 distortedUV = uv0 + waveOffset + radialOffset;
// 采样纹理
o *= CCSampleWithAlphaSeparated(cc_spriteTexture, distortedUV);
// 颜色相位:同心圆涟漪 + 方向波 → 水波状色带;再与整体时间漂移、wave() 高度场混合
vec2 p = uv0 - center;
vec2 waveDirVec = vec2(waveDirX, waveDirY);
float wLen = length(waveDirVec);
vec2 wNorm = wLen > 1e-4 ? waveDirVec / wLen : vec2(1.0, 0.0);
float ringRipple = sin(distance * waveScale * 6.2831853 - time * waveSpeed * 1.65) * 0.5 + 0.5;
float dirRipple = sin(dot(p, wNorm) * waveScale * 6.2831853 + time * waveSpeed * 1.15) * 0.5 + 0.5;
float ripplePhase = mix(ringRipple, dirRipple, 0.45);
float timeDrift = time * rainbowSpeed;
float palettePhase = mix(timeDrift, ripplePhase * 1.15 + timeDrift * 0.2, clamp(paletteWaveInfluence, 0.0, 1.0))
+ waveValue * paletteWaveInfluence * 0.22;
vec3 paletteColor = paletteThree(
palettePhase,
waveColor0.rgb,
waveColor1.rgb,
waveColor2.rgb
);
vec3 finalColor = paletteColor * rainbowIntensity;
// 应用饱和度和亮度调整
float gray = dot(finalColor, vec3(0.299, 0.587, 0.114));
finalColor = mix(vec3(gray), finalColor, saturation);
finalColor *= brightness;
o.rgb = finalColor;
#endif
o *= color;
ALPHA_TEST(o);
return o;
}
}%
这个效果,在安卓上正常运行,但是在ios上,就报错,效果就没有了,我之前认为可能是不同平台的渲染引擎差异,然后我调试了很久都不行,最后我把这个effect复制出来,创建了一个空项目,并且在把这个effect放到了默认场景下,然后我打包成本地资源,结果打开是可以的,然后我怀疑出现问题的地方可能是远程加载和本地资源有差异,我就把远程的bundle内容和本地的做了一个对比,发现远程的bundle没有问题,也就是说,现在只要是远程的effect,在ios里就不行
我想请大佬帮忙看一下,你们可以随便创建一个3.8.8 的项目,然后构建ios,并使用上面的application.js,里面的资源我暂时不会动,看看这到底是引擎的问题,还是我的问题