Facebook 小游戏加载远程图片
问题背景
Facebook Instant Game中,大部分的资源是直接打到游戏包里的,但是,在某些情况下,我们想要加载远程图片来更新游戏内容,比如更新关卡,或者远程更新交叉推广的游戏图标等。
示例
像这个游戏中,交叉推广的游戏就从服务器远程加载的。
游戏测试地址:https://fb.gg/play/twistglidecolor
问题
如果直接使用Cocos Creator的cc.loader.load来加载远程服务器上的图片,则会出现2种错误:
- 跨域资源访问的问题 (CORS = Cross-Origin Resource Sharing)
关于 CORS: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
- 内容安全策略 (CSP = Content Security Policy)
如果在服务器端开放了跨域访问,还可能遇到这个问题,因为facebook只允许从特定域名下加载图片。
关于CSP: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
解决方法
进过一番调查,发现facebook instant game支持用blob读取图片。
https://stackoverflow.com/questions/49122173/instant-games-content-security-policy?rq=1
那么,方法也就明确了。
第一步,服务端开放跨域访问
以Nginx举例,可以在配置文件中添加以下设置:
# https://enable-cors.org/server_nginx.html
#
# Wide-open CORS config for nginx
#
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
#
# Custom headers and headers various browsers *should* be OK with but aren't
#
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
#
# Tell client that this pre-flight info is valid for 20 days
#
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
}
第二步,使用blob协议来读取图片数据
有两种方式可以读取blob数据
- 使用XMLHttpRequest接口
// 该段代码来自 stackoverflow,未测试
function loadXHR(url) {
return new Promise(function(resolve, reject) {
try {
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.responseType = "blob";
xhr.onerror = function() {reject("Network error.")};
xhr.onload = function() {
if (xhr.status === 200) {resolve(xhr.response)}
else {reject("Loading error:" + xhr.statusText)}
};
xhr.send();
}
catch(err) {reject(err.message)}
});
}
- 使用fetch 接口
关于fetch: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
// 自用代码
async loadIcon(iconUrl:string, gameId:string){
console.info("===> loading icon: ", iconUrl);
try{
let resp = await fetch(iconUrl, {
method: 'GET',
mode: 'cors',
// cache: 'default',
});
let blobObj = await resp.blob();
if(blobObj){
console.info("===> image blob: ", blobObj);
// TODO:显示blob格式的图片
}else{
console.warn("===> loading blob failed");
}
}catch(e){
console.warn("===> something wrong: ", e);
}
}
第三步,显示blob格式的图片
Cocos Creator的参考代码
function showBlobImage(blobObj){
let img = new Image();
img.src = URL.createObjectURL(blobObj);
img.onload = function(){
var texture = new cc.Texture2D();
texture.initWithElement(img);
texture.handleLoadedTexture();
var newframe = new cc.SpriteFrame(texture);
if(newframe){
if(self.sprite){
self.sprite.spriteFrame = newframe;
self.node.width = 100;
self.node.height = 100;
console.info("===> update icon success!");
}
}else{
console.warn("===> create sp failed");
}
}
}
其他解决方法
- 将图片保存在facebook允许的域名下
这个理论上可行的,网上也有人是这么做的,但是我没有去测试。
- 使用base64或者其他自定义编码来传图片数据,然后显示。
这也是个可行的方法,但是有几个明显的问题:
1). 数据大,传输效率低
2). base64解码的效率低
3). 需要服务端将图片转换为base64编码
是否会违反facebook规定?
不会。
1). fetch是facebook支持的api
https://developers.facebook.com/docs/games/instant-games/faq/
2). 从stackoverflow上的回复来看,facebook允许blob协议来传输图片。
https://stackoverflow.com/questions/49122173/instant-games-content-security-policy?rq=1