由于creator是针对 android、ios、web、小游戏等平台的引擎,所以游戏的加载过程肯定是要能适应各个环境的启动的。对于web和小游戏这种需要远程下载资源的肯定是要做好启动流程的设计这对于游戏的启动速度效果是有很重要效果的。
在指定启动流程的时候首先要提到的是creator bundle模式的一个特点,就是bundle如果选择了小游戏的 subpackage 模式就不能让资源是 remoteassets 了,如果设置成了 remoteasset 那你的代码就只能是在主包里的,这里就涉及到一个问题,就是我们游戏引擎的代码打包出来就已经接近了4Mb,如果我们的业务代码不分包就发布不了超出了小游戏的限制。所以我们设计的时候业务代码肯定是最好做成分包。另外跨bundle包引用代码必须要先加载bundle包,否则就会有问题(如果你目前没问题,那你打包个包就有问题了,因为直接运行是没问题的,别问我为什么知道的,因为我踩坑了。)
所以针对上面的特性,我的建议是我们同一个模块的代码和资源一定要分不同的目录管理。
然后单独一个bundle用于启动整个游戏,这个bundle 尽量的精简,只要一个背景图片,一个加载脚本就足够了。然后再这个加载脚本将游戏必要的资源bundle包,代码bundle包都加载好,然后再加载登录场景中的启动prefab, 在prefab上绑定一个启动的脚本。这么做主要是跨包引用代码会造成报错,因为我的启动脚本不能依赖其它的代码,只能通过动态加载prefab避免这个问题。
如下是我的启动bundle包
import * as cc from 'cc';
import { BaseLoader } from './BaseLoader';
const { ccclass, property } = cc._decorator;
enum ENV {
DEBUG,
INNER_TEST,
OUT_TEST,
RELEASE,
}
@ccclass('Lauch')
export class Lauch extends cc.Component {
@property(cc.ProgressBar)
private m_loadingBar : cc.ProgressBar = null!;
@property(cc.Label)
private m_lblProcess : cc.Label = null!;
@property({
type : cc.Enum(ENV),
displayName : "GameEnv",
tooltip : "游戏发布环境",
})
private m_gameEnv : number = ENV.RELEASE;
updateProcess(percent : number){
this.m_lblProcess.string = `游戏加载中:${percent}%`
this.m_loadingBar.progress = percent/100;
}
start () {
let resload = new BaseLoader();
this.updateProcess(0);
cc.setDisplayStats(this.m_gameEnv != ENV.RELEASE);
BaseLoader.loadBundleArray(["modules"], ()=>{
resload.loadPrefab("modules#world/GameWorld", ( err, prefab : cc.Prefab)=>{
let node = cc.instantiate(prefab);
let GameWorld : any = node.getComponent("GameWorld");
GameWorld.m_loadingBar = this.m_loadingBar;
GameWorld.m_lblProcess = this.m_lblProcess;
GameWorld.m_gameEnv = this.m_gameEnv;
cc.game.addPersistRootNode(node);
})
}, ( percent : number)=>{
this.updateProcess(Math.floor(percent*100));
})
}
}
// Learn TypeScript:
// - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
// - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
// - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.html
import * as cc from 'cc';
import { ENV, setCurEnv} from "../base/config/Env"
import { SceneMgr } from '../base/core/SceneMgr';
import { Network } from '../base/net/Network';
import { log} from "../base/log/log"
import { utils } from '../base/utils/utils';
import { platform } from '../base/platform/platform';
import { EventDispatcher, EventType, ListenerFunc, EventListener } from '../base/frame/EventDispatcher';
import { ResLoader, SoundMgr } from '../base/core';
import { BundleAsset } from '../../world/BaseLoader';
import { ConfigService } from '../common/src/ConfigService';
const { ccclass, property } = cc._decorator;
export const enum GlobalEvent{
UPDATE_FINISH,
UPDATE_PROCESS,
UPDATE_NEED_UPDATE,
UPDATE_ERROR,
INIT_UPDATE_MANIFEST,
}
@ccclass('GameWorld')
export class GameWorld extends cc.Component {
private static s_instance : GameWorld | null = null;
public static getInstance() : GameWorld{
if(this.s_instance == null){
this.s_instance = new GameWorld();
}
return this.s_instance;
}
private m_audioSource : cc.AudioSource | null = null;
private m_eventDispatcher : EventDispatcher = new EventDispatcher();
public m_gameEnv : ENV = null!;
@property({
displayName : "是否开启广告",
})
private m_openAd : boolean = true;
@property({
displayName: "广告跳过提示",
visible(){
let obj : any = this;
return !obj.m_openAd;
},
})
private m_filterAdTips : string = "广告尚未接入,当前直接跳过广告!";
public m_loadingBar : cc.ProgressBar = null!;
public m_lblProcess : cc.Label = null!;
public m_globalResLoader : ResLoader = new ResLoader();
getAudioSource() : cc.AudioSource | null
{
return this.m_audioSource;
}
getGlobalResLoader() : ResLoader{
return this.m_globalResLoader;
}
getCurEnv() : ENV{
return this.m_gameEnv;
}
getIsOpenAd():boolean{
return this.m_openAd;
}
getFilterAdTips():string{
return this.m_filterAdTips;
}
initResourceSize(){
let framesize = cc.view.getFrameSize();
let mysize = cc.size(1334, 750);
let ftmp = framesize.width/framesize.height;
let rtmp = mysize.width/mysize.height;
let resolutionSize = cc.size(1334, 750);
if( ftmp > rtmp ){
resolutionSize.height = mysize.height;
resolutionSize.width = resolutionSize.height * framesize.width/framesize.height;
}else{
resolutionSize.width = mysize.width;
resolutionSize.height = resolutionSize.width * framesize.height/framesize.width;
}
cc.view.setDesignResolutionSize(resolutionSize.width, resolutionSize.height, cc.ResolutionPolicy.SHOW_ALL);
}
onLoad()
{
this.initResourceSize();
this.m_audioSource = this.getComponent(cc.AudioSource);
cc.assert(this.m_audioSource, "must add Component cc.AudioSource in GameWorld Node");
console.log("GameWorld cur env", this.m_gameEnv);
setCurEnv(this.m_gameEnv);
if(GameWorld.s_instance == null)
{
GameWorld.s_instance = this;
}
else
{
console.error("Gameworld is repeat load!");
}
cc.game.on(cc.Game.EVENT_HIDE, ()=>{
//游戏切后台
console.log("EVENT_GAME_HIDE")
SoundMgr.getInstance().pauseMusic();
})
cc.game.on(cc.Game.EVENT_SHOW, ()=>{
//游戏切前台
console.log("EVENT_GAME_SHOW");
this.scheduleOnce(()=>{
SoundMgr.getInstance().resumeMusic();
}, 0.1)
})
}
public addListenerOnce(event : EventType, owner : Object, handler : ListenerFunc,) : EventListener {
return this.m_eventDispatcher.addListenerOnce(event, owner, handler);
}
public addListener( event : EventType, owner : Object, handler : ListenerFunc, count : number = -1, order : number = 0) : EventListener {
return this.m_eventDispatcher.addListener(event, owner, handler, count, order);
}
public pauseListenerByOwner( owner : Object, event ?: EventType){
return this.m_eventDispatcher.pauseListenerByOwner(owner, event);
}
public resumeOwner(owner : Object, event ?: EventType){
return this.m_eventDispatcher.resumeOwner(owner, event);
}
public removeListenerByOwner( owner : Object, event ?: EventType){
return this.m_eventDispatcher.removeListenerByOwner(owner, event);
}
public removeListenerByEvent( event : EventType){
return this.m_eventDispatcher.removeListenerByEvent(event);
}
public removeListener( event : EventType, owner : Object, handler : ListenerFunc){
return this.m_eventDispatcher.removeListener(event, owner, handler);
}
public dispatch( event : EventType, ...datas : any[]) {
return this.m_eventDispatcher.dispatch(event, ...datas);
}
public addListenerAll(owner : Object, func : ListenerFunc){
return this.m_eventDispatcher.addListenerAll(owner, func);
}
public removeListenerAll(owner : Object, func : ListenerFunc){
return this.m_eventDispatcher.removeListenerAll(owner, func);
}
public initUpdateManifest( doneCallback : ()=>void){
if( cc.sys.isNative ){
this.getGlobalResLoader().LoadAsset("project", ( err, asset : cc.Asset )=>{
console.log("initUpdateManifest result project", );
this.dispatch(GlobalEvent.INIT_UPDATE_MANIFEST, asset);
doneCallback()
})
}else{
doneCallback();
}
}
start () {
let global : any = globalThis;
global.utils = utils;
global.log = log;
global.SoundMgr = SoundMgr;
console.log(utils.parseGetParams());
log.d("gameWorld start", cc.sys);
log.d("os", cc.sys.os, cc.sys.isBrowser);
log.d("platform", cc.sys.platform);
platform.getInstance().init()
this.initUpdateManifest(()=>{
Network.getInstance().init(()=>{
this.launchGame();
})
})
}
update(dt: number){
Network.getInstance().update(dt);
}
restartGame(){
platform.getInstance().restart();
}
launchGame(){
this.m_loadingBar.progress = 0;
this.m_lblProcess.string = `加载bundle资源中 0%`
ResLoader.loadBundleArray(["modules", "common"], ( err : Error | null, bundles : Map<string, BundleAsset> | null )=>{
if(bundles){
bundles.forEach(( bundle )=>{
bundle.addRef(); //全局报不需要释放
})
}
this.m_loadingBar.progress = 0;
this.m_lblProcess.string = `加载配置表资源中 0%`
ConfigService.getInstance().loadAllConfigs(()=>{
this.m_loadingBar.progress = 0;
this.m_lblProcess.string = `加载场景中 0%`
SceneMgr.getInstance().loadRunScene("login#loginScene", ()=>{
}, ( percent : number)=>{
this.m_loadingBar.progress = percent;
this.m_lblProcess.string = `加载场景中 ${Math.floor(percent*1000)/10}%`
})
}, ( percent : number)=>{
this.m_loadingBar.progress = percent;
this.m_lblProcess.string = `加载配置表资源中 ${Math.floor(percent*1000)/10}%`
})
})
}
}