未验证 提交 ac166283 编写于 作者: K kun 提交者: GitHub

Merge branch 'main' into feature/update-doc

......@@ -6,6 +6,11 @@ Removed - 删除功能/接口
Fixed - 修复问题
Others - 其他
-->
## 2022-10-20
### Added
* 高性能模式HTTP网络切换为小游戏接口
* 适配插件版本升级到1.0.97
## 2022-10-12
### Added
* 微信压缩纹理工具支持ASTC文件读取,并支持不同的块大小(需Unity2021版本)
......
此差异已折叠。
fileFormatVersion: 2
guid: 6cc335c875b7242b2b937937d262f304
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
此差异已折叠。
fileFormatVersion: 2
guid: 61476b21b30644ba3b6656f5633e7b3c
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using WeChatWASM;
public class AudioManager : MonoBehaviour
{
// cdn路径音频最多支持10个同时在线播放,先下载后的音频(needDownload)最多支持32个同时播放,先初始化10个
private static int DEFAULT_AUDIO_COUNT = 10;
// 创建音频队列
private static Queue<WXInnerAudioContext> audioPool = new Queue<WXInnerAudioContext>();
// 当前场景需要预下载的音频列表
private static string[] audioList = {
"https://res.wx.qq.com/wechatgame/product/webpack/userupload/20220901/211827/CallMeTeenTop.mp3",
"https://res.wx.qq.com/wechatgame/product/webpack/userupload/20220815/105451/1.mp3",
"https://res.wx.qq.com/wechatgame/product/webpack/userupload/20220901/211846/Night-n.mp3",
"https://res.wx.qq.com/wechatgame/product/webpack/userupload/20220901/211854/Origin-o.mp3",
"https://res.wx.qq.com/wechatgame/product/webpack/userupload/20220901/211905/Seoul-Night-SN.mp3",
};
// 正在播放的音频对象列表
private static List<WXInnerAudioContext> audioPlayArray = new List<WXInnerAudioContext>();
private bool isDestroyed = false;
// 初始化
public void Start()
{
// 创建音频对象池,创建时设置属性需要下载
for (var i = 0; i < DEFAULT_AUDIO_COUNT; i++)
{
addAudio();
};
// 批量下载音频文件
downloadAudio();
// 先下载后播放第3个音频
playAfterDownload(2);
// 立即播放(但不会缓存到本地)第1个音频
playRightNow(0);
}
private WXInnerAudioContext getAudio()
{
if (this.isDestroyed) {
return null;
}
if (audioPool.Count == 0)
{
addAudio();
}
var audio = audioPool.Dequeue();
audioPlayArray.Add(audio);
return audio;
}
private void removeAudio(WXInnerAudioContext audio)
{
if (audioPlayArray.Contains(audio))
{
audio.OffCanplay();
audioPlayArray.Remove(audio);
audioPool.Enqueue(audio);
}
}
private WXInnerAudioContext addAudio()
{
var audio = WX.CreateInnerAudioContext(new InnerAudioContextParam() { needDownload = true });
// 自动播放停止
audio.OnEnded(() =>
{
removeAudio(audio);
});
// 加载出错
audio.OnError(() =>
{
removeAudio(audio);
});
// 手动停止
audio.OnStop(() =>
{
removeAudio(audio);
});
audioPool.Enqueue(audio);
return audio;
}
private void downloadAudio()
{
// 预下载音频
WX.PreDownloadAudios(audioList, (int res) =>
{
if (res == 0)
{
// 下载成功
// 下载后播放第2个音频
playAfterDownload(1);
}
else
{
// 下载失败
}
});
}
public void playAfterDownload(int index)
{
var audioIndex = getAudio();
if (audioIndex == null) {
return;
}
// 如果没有下文修改needDownload为false的函数,理论上创建的所有音频都是true,可以省去这一条
audioIndex.needDownload = true;
// 对于已经设置了needDownload为true的audio,设置src后就会开始下载对应的音频文件
// 如果该文件已经下载过,并且配置了缓存本地,就不会重复下载
// 如果该文件没有下载过,等同于先调用WX.PreDownloadAudios下载后再播放
audioIndex.src = audioList[index];
// 在可以播放时播放
audioIndex.OnCanplay(() =>
{
audioIndex.Play();
});
}
public void playRightNow(int index)
{
// 如果是需要在当前场景立刻播放的音频,则不设置needDownload,音频会边下边播
// 但是再次使用该音频时会因为没有下载而需要再次下载,并不推荐这样使用
var audioPlayRightNow = getAudio();
if (audioPlayRightNow == null) {
return;
}
// 修改src会触发下载,所以设置needDownload属性要在修改src之前
audioPlayRightNow.needDownload = false;
// 如果当前音频已经下载过,并且配置了缓存本地,就算设置needDownload为false也不会重复下载
audioPlayRightNow.src = audioList[index];
// 在可以播放时播放
audioPlayRightNow.OnCanplay(() =>
{
audioPlayRightNow.Play();
});
}
public void stopAllAudio()
{
audioPlayArray.ForEach(audio => {
audio.OffCanplay();
audio.Stop();
});
}
public void playRandom()
{
var index = new System.Random().Next(0, audioList.Length);
Debug.Log("Play:" + index);
Debug.Log("PlayAudioLength:" + audioPlayArray.Count);
playRightNow(index);
}
private void OnDestroy()
{
this.isDestroyed = true;
this.stopAllAudio();
}
}
\ No newline at end of file
fileFormatVersion: 2
guid: 425996276dd1f4577a5c2b9ca9030287
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using WeChatWASM;
using UnityEngine.SceneManagement;
public class Common : MonoBehaviour
{
public void ChangeScene(int index) {
SceneManager.LoadScene(index);
}
}
fileFormatVersion: 2
guid: 73f40247276ff4a379739ff58f4b7798
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
......@@ -11,7 +11,8 @@ public class Demo : MonoBehaviour
public Text txtUserInfo;
public WXFileSystemManager fs = new WXFileSystemManager();
public WeChatWASM.WXEnv env = new WXEnv();
// Start is called before the first frame update
private WXUserInfoButton infoButton;
void Start()
{
WX.InitSDK((code) =>
......@@ -40,12 +41,14 @@ public class Demo : MonoBehaviour
// 首次获取会弹出用户授权窗口, 可通过右上角-设置-权限管理用户的授权记录
var canvasWith = (int)(systemInfo.screenWidth * systemInfo.pixelRatio);
var canvasHeight = (int)(systemInfo.screenHeight * systemInfo.pixelRatio);
var button = WX.CreateUserInfoButton(0, canvasHeight * 2 / 3, canvasWith, canvasHeight / 3, "zh_CN", false);
button.OnTap((userInfoButonRet) =>
var buttonHeight = (int)(canvasWith / 1080f * 300f);
infoButton = WX.CreateUserInfoButton(0, canvasHeight - buttonHeight, canvasWith, buttonHeight, "zh_CN", false);
infoButton.OnTap((userInfoButonRet) =>
{
Debug.Log(JsonUtility.ToJson(userInfoButonRet.userInfo));
txtUserInfo.text = $"nickName:{userInfoButonRet.userInfo.nickName}, avartar:{userInfoButonRet.userInfo.avatarUrl}";
});
Debug.Log("infoButton Created");
});
}
......@@ -216,4 +219,14 @@ public class Demo : MonoBehaviour
{
}
private void OnDestroy()
{
Debug.Log("OnDestroy");
if (infoButton != null)
{
infoButton.Destroy();
Debug.Log("infoButton Destroy");
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using WeChatWASM;
public class Recorder : MonoBehaviour
{
private WXRecorderManager recorderManager;
private WXUploadTask uploadTask;
private string tempFilePath;
void Start()
{
Debug.Log("RecorderManager");
recorderManager = WX.GetRecorderManager();
recorderManager.OnStart(() =>
{
Debug.Log("recorder onStart");
});
recorderManager.OnPause(() =>
{
Debug.Log("recorder onPause");
});
recorderManager.OnResume(() =>
{
Debug.Log("recorder onResume");
});
recorderManager.OnStop((res) =>
{
Debug.Log("recorder onStop");
Debug.Log(res.tempFilePath);
Debug.Log(res.duration);
Debug.Log(res.fileSize);
tempFilePath = res.tempFilePath;
});
recorderManager.OnFrameRecorded((res) =>
{
Debug.Log("recorder onFrameRecorded");
Debug.Log(res.frameBuffer.Length);
Debug.Log(res.isLastFrame);
});
recorderManager.OnInterruptionBegin(() =>
{
Debug.Log("recorder onInterruptionBegin");
});
recorderManager.OnInterruptionEnd(() =>
{
Debug.Log("recorder onInterruptionEnd");
});
}
public void recorderStart()
{
var options = new RecorderManagerStartOption()
{
duration = 10000,
sampleRate = 44100,
numberOfChannels = 1,
encodeBitRate = 192000,
format = "aac",
frameSize = 50
};
recorderManager.Start(options);
}
public void recorderStop()
{
recorderManager.Stop();
}
public void recorderPause()
{
recorderManager.Pause();
}
public void recorderResume()
{
recorderManager.Resume();
}
public void recorderPlay()
{
// 播放临时路径里的音频,此处不可设置needDownload,因为已经在本地文件了
var audioPlayRightNow = WX.CreateInnerAudioContext(new InnerAudioContextParam() {});
audioPlayRightNow.src = tempFilePath;
audioPlayRightNow.OnPlay(() =>
{
Debug.Log("recorder audio OnPlay");
});
audioPlayRightNow.OnCanplay(() =>
{
Debug.Log("recorder audio OnCanplay");
audioPlayRightNow.Play();
});
audioPlayRightNow.OnError(() =>
{
Debug.Log("recorder audio OnError");
});
}
public void uploadFile()
{
uploadTask = WX.UploadFile(new UploadFileOption()
{
url = "xxxxxxxx", // 开发者自己的后台地址
filePath = tempFilePath,
name = "test",
timeout = 10000,
success = (successResult) =>
{
Debug.Log("successResult");
Debug.Log(JsonUtility.ToJson(successResult));
},
fail = (failResult) =>
{
Debug.Log("failResult");
Debug.Log(JsonUtility.ToJson(failResult));
},
complete = (completeResult) =>
{
Debug.Log("completeResult");
Debug.Log(JsonUtility.ToJson(completeResult));
}
});
uploadTask.OnHeadersReceived((data) =>
{
Debug.Log("onHeadersReceived");
Debug.Log(JsonUtility.ToJson(data.header));
});
uploadTask.OnProgressUpdate((data) =>
{
Debug.Log("onProgressUpdate");
Debug.Log(data.progress);
Debug.Log(data.totalBytesSent);
Debug.Log(data.totalBytesExpectedToSend);
});
}
}
fileFormatVersion: 2
guid: fa563f7b01aef4a578b12a5ea6fe42e2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
......@@ -760,7 +760,6 @@ namespace WeChatWASM
allMemory += stObject.cpuMemory;
}
//http://km.oa.com/articles/show/208879?kmref=search&from_page=1&no=1
public void getCacheDetail() {
allMemory = 0;
UnityEngine.Object[] objList = FindObjectsOfType(typeof(UnityEngine.Object));
......
......@@ -760,7 +760,6 @@ namespace WeChatWASM
allMemory += stObject.cpuMemory;
}
//http://km.oa.com/articles/show/208879?kmref=search&from_page=1&no=1
public void getCacheDetail() {
allMemory = 0;
UnityEngine.Object[] objList = FindObjectsOfType(typeof(UnityEngine.Object));
......
# 为何需要音频优化
1. Unity2021版本之前的音频不支持压缩音频,在引擎CPU侧解压会消耗大量内存
2. iOS高性能模式下,Unity WebGL默认使用WebAudio会消耗游戏进程大量内存(特别是时长较大的音频)
我们建议将Unity Audio替换成SDK中的音频API控制播放。如果工作量太大,也建议替换长音频(如BGM)。
1. Unity2021 版本之前的音频不支持压缩音频,在引擎 CPU 侧解压会消耗大量内存
2. iOS 高性能模式下,Unity WebGL 默认使用 WebAudio 会消耗游戏进程大量内存(特别是时长较大的音频)
3. 减少音频重复下载,音频保存本地可以反复使用
我们建议将 Unity Audio 替换成 SDK 中的音频 API 控制播放。如果工作量太大,也建议替换长音频(如 BGM)。
## API 使用
## API使用
### 接口使用
代码如下,可参考[微信开发者文档](https://developers.weixin.qq.com/minigame/dev/api/media/audio/InnerAudioContext.html),其中src为音频地址,可填本地路径如 xx.wav,运行时会自动和配置的音频地址前缀(默认为DATA_CDN/Assets)做拼接得到最终线上地址。
* 使用方法一:
为了减少用户等待时间和避免音频延迟,
对于重要的音频可以等待`onCanplay`事件之后再调用`Play`播放。onCanplay事件是表示,当前音频已经可以播了,可以直接掉用`Play`,不会有延迟。而对于没那么重要的音频,允许部分延迟的,可以不等待onCanplay事件,而直接调用`Play`方法,这样音频就会边加载边播放,效果取决于用户网络,可能会稍微有点延迟。如果希望等音频全部加载了再播放,以及便于后续复用,避免后续延迟的话,可以在初始化是加上needDownload = true,
```
// 第一次使用
var music = WX.CreateInnerAudioContext(new InnerAudioContextParam()
{
src = "Audio/0.wav",
needDownload = true
});
music.OnCanplay(() =>
{
music.Play();
});
// 后续需要需要同时创建多个音频实例时,可以直接Play
var music2 = WX.CreateInnerAudioContext(new InnerAudioContextParam()
{
src = "Audio/0.wav"
});
music2.Play();
```
* 使用方法二:
如果想直接将用到的音频先完全下载避免延迟,用到的时候直接使用,则可以如下调用:
```
//需要预下载列表
string[] a = { "Audio/0.wav", "Audio/1.wav" };
WX.PreDownloadAudios(a, (int res) =>
{
//下载完成后回调
Debug.Log("Downloaded" + res);
if (res == 0)
{
//可以直接调用,无网络延迟
var music0 = WX.CreateInnerAudioContext(new InnerAudioContextParam()
{
src = "Audio/0.wav"
});
music0.Play();
var music1 = WX.CreateInnerAudioContext(new InnerAudioContextParam()
{
src = "Audio/1.wav"
});
music1.Play();
}
});
参考[微信开发者文档](https://developers.weixin.qq.com/minigame/dev/api/media/audio/InnerAudioContext.html)
其中src为音频地址,可填本地路径如 xx.wav,运行时会自动和配置的音频地址前缀(默认为DATA_CDN/Assets)做拼接得到最终线上地址。
```c#
// 使用方法一:创建音频对象可以在初始化是加上needDownload = true,音频会先下载到本地,然后再播放
// 保存本地后,同样的路径不会重复下载,再次使用时无需下载
var audio1 = WX.CreateInnerAudioContext(new InnerAudioContextParam()
{
src = "Audio/0.wav",
needDownload = true
});
// 在可以播放时播放
audio1.OnCanplay(() =>
{
audio1.Play();
});
// 使用方法二:创建音频对象,不设置下载,音频在准备完成后会立即开始播放,并且边下边播
// 这样下载的缓存并不会保存到本地,再次使用时依然会重复下载,推荐频率少或者一次性使用的音频这样处理
var audio2 = WX.CreateInnerAudioContext(new InnerAudioContextParam()
{
src = "Audio/1.wav",
});
// 在可以播放时播放
audio2.OnCanplay(() =>
{
audio2.Play();
});
// 使用方法三:先提前创建音频对象,批量下载音频文件,在下载完成后可以直接修改音频对象的src并播放
string[] a = { "Audio/0.wav", "Audio/1.wav", "Audio/2.wav" };
var audio3 = WX.CreateInnerAudioContext(new InnerAudioContextParam(){ needDownload = true });
WX.PreDownloadAudios(a, (int res) =>
{
// 下载完成后回调
if (res == 0)
{
audio3.src = "Audio/2.wav"
audio3.OnCanplay(() =>
{
audio3.Play();
});
}
});
// 停止音频播放
audio.OffCanplay();
audio.Stop();
```
`注意` WX.CreateInnerAudioContext 返回的音频对象是可以复用的,就是可以多次调用Play方法播放,但是如果需要多个音频同时播放就要创建多个音频对象了。
## 参考示例
音频一般最多只能同时存在10个,所以必须要开发者自己控制音频对象池重复使用,可以参考以下示例:
[音频示例](https://github.com/wechat-miniprogram/minigame-unity-webgl-transform/blob/main/Demo/API/Assets/Scripts/AudioManager.cs)
## 示例补充说明
- 示例只是作为参考,可以不按照示例,以开发者文档为准
- WX.CreateInnerAudioContext 返回的音频对象是可以复用的,可以多次调用 Play 方法播放,但是如果需要多个音频同时播放就要创建多个音频对象。
- 其中音频 src 为音频地址,可填本地路径如 Audios/xx.wav,运行时会自动和配置的音频地址前缀(默认为 DATA_CDN/Assets)做拼接得到最终线上地址。
- 音频需要等待`onCanplay`事件之后再调用`Play`播放。onCanplay 事件是表示,当前音频已经可以播了,
## 保存文件到本地
在使用 WX.CreateInnerAudioContext 时设置了 needDownload 属性或者使用 WX.PreDownloadAudios 下载音频时,会自动把符合[自动缓存规则](https://github.com/wechat-miniprogram/minigame-unity-webgl-transform/blob/main/Design/FileCache.md#%E4%BA%8C%E5%93%AA%E4%BA%9B%E8%B5%84%E6%BA%90%E4%BC%9A%E8%87%AA%E5%8A%A8%E7%BC%93%E5%AD%98)的文件保存到本地,下次启动游戏时可以无需下载
## 导出设置
勾选使用微信音频API,并填上"游戏资源CDN",比如填写的地址为https://wx.qq.com/data/ ,而API的src地址为 Audio/Chill_1.wav,则最终请求路径会自动拼接前缀DATA_CDN/Assets(其中Assets路径可通过MiniGameConfig.asset更改), 即https://wx.qq.com/data/Assets/Audio/Chill_1.wav。
## 将音频文件上传CDN
游戏内的音频会被导出到导出目录的Assets文件夹下,需要将其上传至您的CDN,保证用户能访问到
勾选使用微信音频 API,并填上"游戏资源 CDN",比如填写的地址为https://wx.qq.com/data/ ,而 API 的 src 地址为 Audio/Chill_1.wav,则最终请求路径会自动拼接前缀 DATA_CDN/Assets(其中 Assets 路径可通过 MiniGameConfig.asset 更改), 即https://wx.qq.com/data/Assets/Audio/Chill_1.wav。
## 将音频文件上传 CDN
游戏内的音频会被导出到导出目录的 Assets 文件夹下,需要将其上传至您的 CDN,保证用户能访问到
![avatar](../image/assets2.png)
## 注意事项
- 使用微信音频API后音频无需再监听onAudioInterruptionBegin,onAudioInterruptionEnd事件,插件会自动处理。
- 音频未使用needDownload=true或PreDownloadAudios的情况下,音频文件是不还被缓存的,多次创建并播放同样音频会造成多次下载。需要游戏逻辑自己负责缓存音频对象(就是WX.CreateInnerAudioContext的对象需要自己缓存)。
- 使用微信音频 API 后音频无需再监听 onAudioInterruptionBegin,onAudioInterruptionEnd 事件,插件会自动处理。
- 音频未使用 needDownload=true 或 PreDownloadAudios 的情况下,音频文件是不还被缓存的,多次创建并播放同样音频会造成多次下载。需要游戏逻辑自己负责缓存音频对象(就是 WX.CreateInnerAudioContext 的对象需要自己缓存)。
此差异已折叠。
......@@ -56,7 +56,7 @@ AssetBundle和Addressables资源目录。
### 资源服务器注意事项
1. 针对txt文件进行开启Brotli或gzip压缩,**首资源包有非常高的压缩率**
2. 小游戏资源下载并发数为10,超过时底层自动排队
3. 单个请求文件最大**不超过100MB,超时默认为60s**
3. 单个请求文件最大**不超过100MB,超时默认为60s**(理论最大值,实际游戏永远不要让单个文件这么大,考虑到玩家平均下载带宽,建议单文件2~5MB以内)
4. 网络安全域名、跨域、SSL等问题请参考文档[网络通信适配](UsingNetworking.md)
## 资源更新说明
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
......@@ -11,28 +11,14 @@ Unity的BuildTarget支持WebGL平台,WebGL导出包是基于WebAssembly技术
## 二、接入流程
接入流程主要包含以下几个环节:
<img src='../image/summary2.png'/>
接入流程请参考文档[Unity WebGL微信小游戏转换指南](Guide.md)
> 注:图中每个环节的人力时间为预计,具体时间因具体项目不同。
## 三、参考资料
1. https://emscripten.org/
2. https://docs.unity3d.com/Manual/webgl-gettingstarted.html
3. https://forum.unity.com/forums/webgl.84/
4. http://webassembly.org.cn/getting-started/developers-guide/
5. https://developer.mozilla.org/zh-CN/docs/WebAssembly/C_to_wasm
### 评估反馈
我们为接入游戏提供了评估表,问卷内容是本转换方案最重要的影响因素。当根据实际项目填写之后与我们联系后一起评估可行性和技术风险点,详细内容请参考[方案评估](Evaluation.md)
### 工具转换
我们提供了**Unity转换插件**帮助开发者将项目自动导出为微信小游戏包,随后即可使用微信开发者工具或Android/iOS真机进行预览。
关于转换工具的使用请参考文档:
* [使用转换工具导出微信小游戏](Transform.md)
### 平台能力接入
微信小游戏平台提供众多开放能力,但目前是以JavaScript API形式提供。
为了降低开发者进行平台能力对接的门槛,我们提供了平台能力C# SDK,因此依然可以熟悉的C#接口进行平台能力使用。
关于平台能力C# SDK请参考文档:
* [WX SDK平台能力适配](WX_SDK.md)
### 调优体验
转换工具能帮助开发者快速转换原有Unity项目,然而对于优质的小游戏我们还需要更多的体验调优以取得良好的上线表现。
关于调优请参考文档:
- [性能优化总览](PerfOptimization.md)
- [性能评估标准](PerfMeasure.md)
\ No newline at end of file
此差异已折叠。
此差异已折叠。
此差异已折叠。
image/guide/guide1.png

358.9 KB | W: | H:

image/guide/guide1.png

262.9 KB | W: | H:

image/guide/guide1.png
image/guide/guide1.png
image/guide/guide1.png
image/guide/guide1.png
  • 2-up
  • Swipe
  • Onion skin
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册