cocos3D 1.1.0适配魅族小游戏

【本文参与征文活动】

前言

1.本教程适用了cocos3D 1.1.0版本发布到魅族小游戏平台。其他诸如1.0.4版本的引擎版本没有做过实验。所以不能保证其他版本是OK的。

2.在正式开始适配我们的魅族小游戏之前,建议先详细的阅读魅族的官方文档。链接如下
https://shimo.im/docs/enni3mhvNyo5fZOm/read
这样子打起包来才能得心应手。

3.因为魅族小游戏目前只对cocos2D做了适配和验证,3D还没有来的做,所以问题真的是多多。幸好有个魅族官方的大佬可以咨询问题,不然这个魅族的包我肯定是打不出来的。所以如果按照本教程还不能适配,可以通过某些渠道去联系一下魅族官方的技术大佬。说不定他可以帮助你。

让安装包可以跑起来

1.魅族小游戏的核心思想就利用构建面板,构建出来一个web-mobile的文件夹(可以在网页打开)。然后利用这个文件夹和魅族提供的转换工具生成一个rpk。放到手机上去运行。关于这部分,前言里的魅族的文档写的很清楚了。所以在这里不再赘述

2.打开我们构建出来的web-mobile的文件夹。可以看到以下几个文件

(1)打开main.js文件夹。
搜索 “boot.prepare.engine = function() {” 这句话
然后将这个函数里的内容全部注释掉,如下

这里是用js代码生成了一个html的script标签用来加载cocos3d-min.js文件,但是这个标签魅族小游戏里貌似是不支持的。会解析错误。所以我们注释掉它。

在onStart函数里添加两句代码。方便我们做平台判断。

(2)打开index.html文件。搜索“”
在这句话前加上 “”。最后结果如下图

(3)打开cocos3d-js.min.js文件。这个文件贼长,电脑会卡一会。
将最开始的几个字符从

System.register([],(function(e)......

修改成

System.register('cc',[],(function(e)......

(4)在代码里不要使用cc.game,.setFrameRate()函数来修改游戏的fps。会导致游戏画面抽搐。如果项目里有这些代码。建议改成如下的形式

if(cc.sys.platform != cc.sys.MEIZU_GAME){
    cc.game.setFrameRate();
}

这里要注意的是。Cocos3D里并没有cc.sys.MEIZU_GAME这个变量。但是这里可以正常运行是因为在步骤(1)里我们加入了这2句代码。同样的道理,在适配oppo,vivo这些平台的时候,都可以这样子操作。方便我们在游戏里添加不同的平台。

修改完上述三个文件之后。我们就可以用这个文件夹压缩成.zip。然后使用魅族的工具去生成rpk了。

2.音频问题

(1)音频异常原因
我们生成的rpk应该可以在魅族手机上跑起来了。但是在播放音乐的时候会非常不正常。根据我的测试结果发现如果在当前的场景树上有任何一个节点,而这个节点上的组件引用了任何形式的音频。都会引发异常。
如下图的两种形式


第一种形式是使用AudioSource组件
第二种是在脚本里使用属性修饰器,然后拖拽个音频来绑定

@property(AudioClip)
public clips: AudioClip = null;

都是不行的。
假设当前场景里有10个音频被引用了,那么当你点击魅族手机的屏幕的时候,这个10个音频都会被自动播放一遍。cocos-3d.min.js里有一个分发触摸的代码,会直接对所有的音频都播放一遍。这简直是见了鬼了。当然这个问题是在有在魅族小游戏有的。其他平台不会有问题。

(2).音频异常解决方式
所以在这里,我们需要修改一下音频的播放方式。在这之前,我播放音频的办法一直都是在场景上创建一个节点audio。在这个节点上挂载一个AudioCtrl.ts(自己写的文件)。AudioCtrl.ts代码如下

import { _decorator, Component, Node, AudioClip, CCString } from "cc";
import { LocalData } from "./LocalData";
const { ccclass, property } = _decorator;


@ccclass("AudioCtrl")
export class AudioCtrl extends Component {

@property(CCString)
public sceneName: string = '';

@property([AudioClip])
public clips: Array<AudioClip> = [];

playOneShot(clipIndex: any, vol = 1.0) {
  if (this.clips[clipIndex]) {
       this.clips[clipIndex].playOneShot(vol);
  }
}

play(clipIndex: any, loop = false, vol = 1.0) {
     if (this.clips[clipIndex]) {
     if (loop == true) {
          this.clips[clipIndex].setVolume(vol);
          this.clips[clipIndex].setLoop(loop);
          this.clips[clipIndex].play();
     }
     else {
         this.clips[clipIndex].playOneShot(vol)
     }
   }
}

getPlayState(clipIndex) {
    if (this.clips[clipIndex]) {
         return this.clips[clipIndex].state;
     }
     return null;
}

stop(clipIndex) {
      if (this.clips[clipIndex] && this.clips[clipIndex].state == AudioClip.PlayingState.PLAYING) {
             this.clips[clipIndex].stop();
      }
}
}

AudioCtrl.ts里有一个音频数组,当前场景需要播放的音效,我都拖拽到了这个数组里,然后需要播放的时候。我会使用如下的代码播放

  let audio:AudioCtrl = cc.find(‘audio’).getComponent(AudioCtrl);
  audio.play(1);
  audio.stop(1);

现在我们要换一个形式了。
1.将audio这个节点做成一个预制件,放到resources下
2.在场景初始化开始的时候。控制场景的脚本里使用如下代码去动态加载预制件,然后生成节点放到我们的场景上。

if (cc.sys.platform != cc.sys.MEIZU_GAME) {
       cc.loader.loadRes('prefabs/startSceneAudio/audio', Prefab, (err, prefab) => {
       if (err) return cc.log(err);
       if (cc.isValid(this.node) == false) return;
       let node = cc.instantiate(prefab);
       node.parent = cc.director.getScene();
      })
}

注意,这里必须使用cc.loder.loadRes去加载预制件。如果你使用如下代码去创建节点

@property
audioPrefab:Prefab = null;

onLoad(){
    instantiate(this.audioPrefab).parnet = cc.direct.getScene();
}

即将这个预制件拖拽到场景上的某个脚本的属性列表上也不行,因为这也会满足音频异常的原因。

接着当我们需要播放某个音效的时候,我们可以写个一个类来封装这个行为。

//为魅族平台专门服务的
export let AudioHelper = {

innerAudios: {},



play(nodeName: string , clipIndex: any, loop = false, vol = 1.0) {


if (cc.sys.platform != cc.sys.MEIZU_GAME) {
let node: Node = cc.find(nodeName) as Node;
if (node == null) {
return cc.log('audioHelper node not found:' + nodeName);
}
let audioCtrl = node.getComponent(AudioCtrl);
audioCtrl.play(clipIndex, loop, vol);
}
else {
let audioName = this.getInnerAudioUrl(nodeName, clipIndex);
let innerAudio = this.innerAudios[audioName];
if (innerAudio == null) {
//使用魅族平台播放音频的接口
innerAudio = qg.createInnerAudioContext();
innerAudio.src = audioName;
innerAudio.loop = loop;
innerAudio.volume = vol;
innerAudio.autoplay = true;
this.innerAudios[audioName] = innerAudio;
}
else {
innerAudio.play();
}
}
},

stop(nodeName: string , clipIndex: number) {
if (cc.sys.platform != cc.sys.MEIZU_GAME) {
let node: Node = cc.find(nodeName);
if (node == null) {
return cc.log('audioHelper node not found:' + nodeName);
}
let audioCtrl: AudioCtrl = node.getComponent(AudioCtrl);
audioCtrl.stop(clipIndex);
}
else {
//todo 使用 createInnerAudio来停止声音
let audioName = this.getInnerAudioUrl(nodeName, clipIndex);
if (this.innerAudios[audioName] && this.innerAudios[audioName].paused == false) {
this.innerAudios[audioName].stop();
}
}
},

getPlayState(nodeName: string, clipEnum: any, clipIndex: number) {
if (cc.sys.platform != cc.sys.MEIZU_GAME) {
let node: Node = cc.find(nodeName);
if (node == null) {
return cc.log('audioHelper node not found:' + nodeName);
}
let audioCtrl: AudioCtrl = node.getComponent(AudioCtrl);
return audioCtrl.getPlayState(clipIndex);
}
else {
let audioName = this.getInnerAudioUrl(nodeName, clipIndex)
if (this.innerAudios[audioName]) {
if (this.innerAudios[audioName].paused) {
return AudioClip.PlayingState.STOPPED;
}
else {
return AudioClip.PlayingState.PLAYING;
}
}
}
},

getInnerAudioUrl(nodeName, clipIndex) {
//这里是根据播放音频的序号来转化成对于的名字
return this.gameSceneAudioNodeClipNames[clipIndex];
},

gameSceneAudioNodeClipNames: [
'res/audio/button_normal.mp3', //0
'res/audio/button_back.mp3', //1
'res/audio/gold_increment.mp3', //2
'res/audio/win.mp3', //3
'res/audio/fail.mp3', //4
'res/audio/hurt.mp3', //5
'res/audio/golddrop.mp3', //6
'res/audio/hurt_enemy.mp3', //7
'res/audio/explose.mp3', //8
'res/audio/aerocraft.mp3', //9
'res/audio/aerocraft_gun.mp3', //10
'res/audio/rock_raise.mp3', //11
'res/audio/rock_fail.mp3', //12
'res/audio/nuclear_explosion.mp3', //13
'res/audio/giftBox.mp3', //14
'res/audio/dead.mp3', //15
'res/audio/sekiro_dange.mp3', //16
'res/audio/warning.mp3', //17
'res/audio/enemyBullet.mp3', //18
'res/audio/elite_smile.mp3', //19
'res/audio/getCollection.mp3', //20
'res/audio/walk.mp3', //21
'res/audio/fireworks.mp3', //21
],
}

核心思想是当前是魅族平台的话,那么就调用魅族原生的音频接口,其他的就还用引擎提供的音频播放方式就好了。我们看到上边的代码里有很多的mp3文件。我们不要忘记把这些音频放到我们构建出来的web-moblie的相应的位置里哦。

结尾

在上述的教程里我们修改了四次地方。分别是
res/audio 文件夹
cocos3d-js.min.js
main.js
index.html
当然每次当你重新构建web-mobile的时候,这四个文件都会被重置到最初的状态哦。所以记得修改好,手机上能跑后提交到版本库里。然后每次构建完后从版本库里还原哦。

6赞

3D 第一篇征文?感谢参与和分享!

1赞

我按照你这个方式播放音频没有声音啊

有实现使用CDN吗

没有,魅族小游戏的包体限制很大,16M好像。所以不需要把res放到服务器上

Cocos3D 1.2.0的目录已经和1.1完全不一样了。这种时候有没有什么指导方案呢。按照魅族石墨文档的流程操作也不行