Cocos使用精灵组件显示相机内容
1. 为什么使用精灵渲染
在游戏引擎中,游戏场景内除
webview
和video
外所有的节点都是渲染在Canvas
上,这导致了webview
和video
只能存在于所有节点的最上层或最下层,而这种层级关系会出现节点事件无法正常监听或者webview
和video
被遮挡,为解决这个问题可以通过将影像渲染在精灵组件上。
2. 如何实现
2.1 添加视频标签
可以直接用代码创建,也可以在构建后添加到
index.html
文件中;
typescript
// 创建一个新的视频元素
let video = document.createElement("video");
// 设置视频元素的 ID
video.setAttribute('id', this._player_container_id);
// 设置视频预加载属性为自动
video.setAttribute('preload', 'auto');
// 将视频元素隐藏
video.setAttribute('hidden', 'hidden');
// 设置视频元素的样式,宽高都为0,以隐藏视频
video.setAttribute('style', 'width: 0px; height: 0px;')
// 将视频元素添加到文档的主体中
document.body.appendChild(video);
2.2 逻辑实现
原理是将视频内容根据自己设置的固定帧绘制在画布,再将纹理转换成精灵帧显示在精灵组件上;(适用于相机采集、直播采集、视频文件播放等)
typescript
private _texture: cc.Texture2D; // 用于存储纹理
private _canvas: HTMLCanvasElement; // HTML画布元素
private _canvasCtx: CanvasRenderingContext2D; // 画布的2D上下文
private _sprite: cc.Sprite; // 精灵组件
private spriteFrameCache: cc.SpriteFrame[] = []; // 精灵帧缓存数组
private index = 0; // 当前使用的缓存索引
private _video; // 视频元素
private lastUpdateTime = -1; // 上一次更新时间
private _Timer = 0; // 定时器
init() {
// 创建画布并设置尺寸
let canvas: HTMLCanvasElement = document.createElement('canvas');
canvas.width = this.node.width; // 设置画布宽度
canvas.height = this.node.height; // 设置画布高度
this._canvas = canvas; // 保存画布引用
this._canvasCtx = canvas.getContext('2d'); // 获取2D上下文
this._sprite = this.getComponent(cc.Sprite); // 获取精灵组件
this._texture = new cc.Texture2D(); // 创建新的纹理对象
// 初始化两个精灵帧并存入缓存
for (let i = 0; i < 2; i++) {
this.spriteFrameCache.push(new cc.SpriteFrame()); // 创建精灵帧并加入缓存
}
}
private async updateTexture(): Promise<void> {
// 如果视频未定义,返回
if (this._video == undefined) return;
// 如果视频未暂停且当前时间与最后更新时间不同,进行更新
if (!this._video.paused && this._video.currentTime !== this.lastUpdateTime) {
this.lastUpdateTime = this._video.currentTime; // 更新最后更新时间
this._Timer = 0; // 重置计时器
} else if (this._Timer < 10) {
this._Timer += 1 / 25; // 增加计时器
} else {
this.unschedule(this.updateTexture); // 取消调度
this.clearSprite(); // 清空精灵
this._Timer = 0; // 重置计时器
console.log('updateTexture fail'); // 打印失败日志
return; // 返回
}
// 在画布上绘制视频内容
this._canvasCtx.drawImage(this._video, 0, 0, this.node.width, this.node.height);
this._texture.initWithElement(this._canvas); // 用画布元素初始化纹理
let spriteFrame = this.spriteFrameCache[this.index]; // 获取当前索引的精灵帧
spriteFrame.setTexture(this._texture); // 设置精灵帧的纹理
this._sprite.spriteFrame = spriteFrame; // 更新精灵的显示帧
this.index = this.index ^ 1; // 切换索引(0 和 1 之间切换)
}
bind(cb): void {
// 获取本地视频元素
this._video = document.querySelector("#local_video").children[0];
// 请求用户媒体(音频和视频)
navigator.mediaDevices
.getUserMedia({
audio: true,
video: true
})
.then((stream) => {
this.handleSuccess(stream); // 成功时处理流
this._video.play(); // 播放视频
cb(); // 调用回调
})
.catch(this.handleError); // 处理错误
}
handleSuccess(stream) {
this._video.srcObject = stream; // 将流设置为视频播放器的源对象
}
handleError(e) {
console.log("绑定失败:"); // 输出错误信息
console.log(e); // 输出具体错误
}
clearSprite() {
this._sprite.spriteFrame = null; // 清空精灵的显示帧
}
/**调用测试 **/
test() {
this.bind(() => { // 绑定视频流并在完成后执行回调
this.unschedule(this.updateTexture); // 取消之前的调度
this.schedule(this.updateTexture, 1 / 25, cc.macro.REPEAT_FOREVER); // 调度更新纹理的方法
this.init(); // 初始化设置
});
}