creator项目打包成facebook playable game广告


creator项目打包成facebook playable game广告

  1. 首先十分感谢chongshengzhujue提供的开源工具

https://github.com/chongshengzhujue/playableFBCompile

  1. 为了适配我们的项目,做了一些修改
  • compile.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#!/usr/bin/python
# -*- coding: UTF-8 -*-

from xml.dom.minidom import parse
import xml.dom.minidom
import json
import os
import time
import sys
import codecs
import cgi
import HTMLParser
import re
import base64

if sys.getdefaultencoding() != 'utf-8':
reload(sys)
sys.setdefaultencoding('utf-8')

RootDir = os.getcwd()
print(RootDir)

htmlPath = RootDir + '/build/web-mobile/index.html'
settingScrPath = RootDir + '/build/web-mobile/src/settings.js'
mainScrPath = RootDir + '/build/web-mobile/main.js'
engineScrPath = RootDir + '/build/web-mobile/cocos2d-js-min.js'
projectScrPath = RootDir + '/build/web-mobile/src/project.js'

resPath = RootDir + '/build/web-mobile/res'

settingMatchKey = '{#settings}'
mainMatchKey = '{#main}'
engineMatchKey = '{#cocosengine}'
projectMatchKey = '{#project}'
resMapMatchKey = '{#resMap}'

addScriptPathList = [settingScrPath, mainScrPath, engineScrPath, projectScrPath]

fileByteList = ['.png', '.jpg', '.mp3', '.ogg']

base64PreList = {
'.png' : 'data:image/png;base64,',
'.jpg' : 'data:image/jpeg;base64,',
'.mp3' : '',
'.ogg' : ''
}

def read_in_chunks(filePath, chunk_size=1024*1024):
"""
Lazy function (generator) to read a file piece by piece.
Default chunk size: 1M
You can set your own chunk size
"""
extName = os.path.splitext(filePath)[1]
if extName in fileByteList:
file_object = open(filePath, 'rb')
base64Str = base64.b64encode(file_object.read())
preName = base64PreList[extName]
if preName != None:
base64Str = preName + base64Str
return base64Str
elif extName == '':
return None

file_object = open(filePath)
return file_object.read()

def writeToPath(path, data):
with open(path,'w') as f: # 如果filename不存在会自动创建, 'w'表示写数据,写之前会清空文件中的原有数据!
f.write(data)

def getResMap(jsonObj, path):
fileList = os.listdir(path)
for fileName in fileList:
absPath = path + '/' + fileName
if (os.path.isdir(absPath)):
getResMap(jsonObj, absPath)
elif (os.path.isfile(absPath)):
dataStr = read_in_chunks(absPath)
if dataStr != None:
absPath = 'res' + absPath.replace(resPath, '')
jsonObj[absPath] = dataStr
print(absPath)

def getResMapScript():
jsonObj = {}
getResMap(jsonObj, resPath)
resStr = unicode("window.resMap = ") + json.dumps(jsonObj)
return resStr

def start():
htmlStr = read_in_chunks(htmlPath)

settingsStr = read_in_chunks(settingScrPath)
htmlStr = htmlStr.replace(settingMatchKey, settingsStr, 1)

projectStr = read_in_chunks(projectScrPath)
htmlStr = htmlStr.replace(projectMatchKey, projectStr, 1)

mainStr = read_in_chunks(mainScrPath)
htmlStr = htmlStr.replace(mainMatchKey, mainStr, 1)

engineStr = read_in_chunks(engineScrPath)
htmlStr = htmlStr.replace(engineMatchKey, engineStr, 1)

resStr = getResMapScript()
htmlStr = htmlStr.replace(resMapMatchKey, resStr, 1)

writeToPath(htmlPath, htmlStr)



if __name__ == '__main__':
start()
  • index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>

<title>XXXX</title>

<!--http://www.html5rocks.com/en/mobile/mobifying/-->
<meta name="viewport"
content="width=device-width,user-scalable=no,initial-scale=1, minimum-scale=1,maximum-scale=1"/>

<!--https://developer.apple.com/library/safari/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html-->
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
<meta name="format-detection" content="telephone=no"/>

<!-- force webkit on 360 -->
<meta name="renderer" content="webkit"/>
<meta name="force-rendering" content="webkit"/>
<!-- force edge on IE -->
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="msapplication-tap-highlight" content="no"/>

<!-- force full screen on some browser -->
<meta name="full-screen" content="yes"/>
<meta name="x5-fullscreen" content="true"/>
<meta name="360-fullscreen" content="true"/>

<!-- force screen orientation on some browser -->
<meta name="screen-orientation" content=""/>
<meta name="x5-orientation" content=""/>

<!--fix fireball/issues/3568 -->
<!--<meta name="browsermode" content="application">-->
<meta name="x5-page-mode" content="app"/>

<!--<link rel="apple-touch-icon" href=".png" />-->
<!--<link rel="apple-touch-icon-precomposed" href=".png" />-->

<style>
html {
-ms-touch-action: none;
}

body, canvas, div {
display: block;
outline: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);

-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
-khtml-user-select: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}

/* Remove spin of input type number */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
/* display: none; <- Crashes Chrome on hover */
-webkit-appearance: none;
margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
}

body {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 0;
border: 0;
margin: 0;

cursor: default;
color: #888;
background-color: #333;

text-align: center;
font-family: Helvetica, Verdana, Arial, sans-serif;

display: flex;
flex-direction: column;
}

#Cocos2dGameContainer {
position: absolute;
margin: 0;
overflow: hidden;
left: 0px;
top: 0px;

display: -webkit-box;
-webkit-box-orient: horizontal;
-webkit-box-align: center;
-webkit-box-pack: center;
}

canvas {
background-color: rgba(0, 0, 0, 0);
}

a:link, a:visited {
color: #666;
}

a:active, a:hover {
color: #666;
}

p.header {
font-size: small;
}

p.footer {
font-size: x-small;
}

#splash {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #171717 url(./splash.png) no-repeat center;
background-size: 40%;
}

.progress-bar {
background-color: #1a1a1a;
position: absolute;
left: 25%;
top: 80%;
height: 15px;
padding: 5px;
width: 50%;
/*margin: 0 -175px; */
border-radius: 5px;
box-shadow: 0 1px 5px #000 inset, 0 1px 0 #444;
}

.progress-bar span {
display: block;
height: 100%;
border-radius: 3px;
box-shadow: 0 1px 0 rgba(255, 255, 255, .5) inset;
transition: width .4s ease-in-out;
background-color: #34c2e3;
}

.stripes span {
background-size: 30px 30px;
background-image: linear-gradient(135deg, rgba(255, 255, 255, .15) 25%, transparent 25%,
transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%,
transparent 75%, transparent);

animation: animate-stripes 1s linear infinite;
}

@keyframes animate-stripes {
0% {background-position: 0 0;} 100% {background-position: 60px 0;}
}

</style>

</head>
<body>
<canvas id="GameCanvas" oncontextmenu="event.preventDefault()" tabindex="0"></canvas>
<div id="splash">
<div class="progress-bar stripes">
<span style="width: 0%"></span>
</div>
</div>

<script type="text/javascript" charset="utf-8">
{#resMap}
</script>

<script type="text/javascript" charset="utf-8">
{#settings}
</script>

<script type="text/javascript" charset="utf-8">
{#cocosengine}
</script>

<script type="text/javascript" charset="utf-8">
{#project}
</script>

<script type="text/javascript" charset="utf-8">
{#main}
</script>
</body>
</html>
  • main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
(function () {

function boot () {

var settings = window._CCSettings;
window._CCSettings = undefined;

if ( !settings.debug ) {
var uuids = settings.uuids;

var rawAssets = settings.rawAssets;
var assetTypes = settings.assetTypes;
var realRawAssets = settings.rawAssets = {};
for (var mount in rawAssets) {
var entries = rawAssets[mount];
var realEntries = realRawAssets[mount] = {};
for (var id in entries) {
var entry = entries[id];
var type = entry[1];
// retrieve minified raw asset
if (typeof type === 'number') {
entry[1] = assetTypes[type];
}
// retrieve uuid
realEntries[uuids[id] || id] = entry;
}
}

var scenes = settings.scenes;
for (var i = 0; i < scenes.length; ++i) {
var scene = scenes[i];
if (typeof scene.uuid === 'number') {
scene.uuid = uuids[scene.uuid];
}
}

var packedAssets = settings.packedAssets;
for (var packId in packedAssets) {
var packedIds = packedAssets[packId];
for (var j = 0; j < packedIds.length; ++j) {
if (typeof packedIds[j] === 'number') {
packedIds[j] = uuids[packedIds[j]];
}
}
}
}

// init engine
var canvas;

if (cc.sys.isBrowser) {
canvas = document.getElementById('GameCanvas');
}

function setLoadingDisplay () {
// Loading splash scene
var splash = document.getElementById('splash');
var progressBar = splash.querySelector('.progress-bar span');
cc.loader.onProgress = function (completedCount, totalCount, item) {
var percent = 100 * completedCount / totalCount;
if (progressBar) {
progressBar.style.width = percent.toFixed(2) + '%';
}
};
splash.style.display = 'block';
progressBar.style.width = '0%';

cc.director.once(cc.Director.EVENT_AFTER_SCENE_LAUNCH, function () {
splash.style.display = 'none';
});
}

var onStart = function () {
cc.view.enableRetina(true);
cc.view.resizeWithBrowserSize(true);

if (!false && !false) {
// UC browser on many android devices have performance issue with retina display
if (cc.sys.os !== cc.sys.OS_ANDROID || cc.sys.browserType !== cc.sys.BROWSER_TYPE_UC) {
cc.view.enableRetina(true);
}
if (cc.sys.isBrowser) {
setLoadingDisplay();
}

if (cc.sys.isMobile) {
if (settings.orientation === 'landscape') {
cc.view.setOrientation(cc.macro.ORIENTATION_LANDSCAPE);
}
else if (settings.orientation === 'portrait') {
cc.view.setOrientation(cc.macro.ORIENTATION_PORTRAIT);
}
cc.view.enableAutoFullScreen([
cc.sys.BROWSER_TYPE_BAIDU,
cc.sys.BROWSER_TYPE_WECHAT,
cc.sys.BROWSER_TYPE_MOBILE_QQ,
cc.sys.BROWSER_TYPE_MIUI,
].indexOf(cc.sys.browserType) < 0);
}

// Limit downloading max concurrent task to 2,
// more tasks simultaneously may cause performance draw back on some android system / brwosers.
// You can adjust the number based on your own test result, you have to set it before any loading process to take effect.
if (cc.sys.isBrowser && cc.sys.os === cc.sys.OS_ANDROID) {
cc.macro.DOWNLOAD_MAX_CONCURRENT = 2;
}
}

// init assets
cc.AssetLibrary.init({
libraryPath: 'res/import',
rawAssetsBase: 'res/raw-',
rawAssets: settings.rawAssets,
packedAssets: settings.packedAssets,
md5AssetsMap: settings.md5AssetsMap
});

var launchScene = settings.launchScene;

// load scene
cc.director.loadScene(launchScene, null,
function () {
if (cc.sys.isBrowser) {
// show canvas
canvas.style.visibility = '';
var div = document.getElementById('GameDiv');
if (div) {
div.style.backgroundImage = '';
}
}
cc.loader.onProgress = null;
console.log('Success to load scene: ' + launchScene);
}
);
};

// jsList
// var jsList = settings.jsList;

// var bundledScript = settings.debug ? 'src/project.dev.js' : 'src/project.js';
// if (jsList) {
// jsList = jsList.map(function (x) {
// return 'src/' + x;
// });
// jsList.push(bundledScript);
// }
// else {
// jsList = [bundledScript];
// }
var jsList = [];

var option = {
//width: width,
//height: height,
id: 'GameCanvas',
scenes: settings.scenes,
//debugMode: settings.debug ? cc.DebugMode.INFO : cc.DebugMode.ERROR,
showFPS: (!false && !false) && settings.debug,
frameRate: 60,
jsList: jsList,
groupList: settings.groupList,
collisionMatrix: settings.collisionMatrix,
renderMode: 0
}

cc.game.run(option, onStart);
}





if (window.document) {

var __audioSupport = cc.sys.__audioSupport;
var formatSupport = __audioSupport.format;
var context = __audioSupport.context;

// 转二进制Blob
function base64toBlob(base64, type) {
// 将base64转为Unicode规则编码
var bstr = atob(base64, type),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n) // 转换编码后才可以使用charCodeAt 找到Unicode编码
}
return new Blob([u8arr], {
type: type,
})
}

// 转二进制
function base64toArray(base64) {
// 将base64转为Unicode规则编码
var bstr = atob(base64),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n) // 转换编码后才可以使用charCodeAt 找到Unicode编码
}

return u8arr;
}

// 加载domaudio
function loadDomAudio(item, callback) {
var dom = document.createElement('audio');

dom.muted = true;
dom.muted = false;

var data = window.resMap[item.url.split("?")[0]];
data = base64toBlob(data, "audio/mpeg");

if (window.URL) {
dom.src = window.URL.createObjectURL(data);
} else {
dom.src = data;
}


var clearEvent = function () {
clearTimeout(timer);
dom.removeEventListener("canplaythrough", success, false);
dom.removeEventListener("error", failure, false);
if(__audioSupport.USE_LOADER_EVENT)
dom.removeEventListener(__audioSupport.USE_LOADER_EVENT, success, false);
};
var timer = setTimeout(function () {
if (dom.readyState === 0)
failure();
else
success();
}, 8000);
var success = function () {
clearEvent();
item.element = dom;
callback(null, item.url);
};
var failure = function () {
clearEvent();
var message = 'load audio failure - ' + item.url;
//cc.log(message);
callback(message, item.url);
};
dom.addEventListener("canplaythrough", success, false);
dom.addEventListener("error", failure, false);
if(__audioSupport.USE_LOADER_EVENT)
dom.addEventListener(__audioSupport.USE_LOADER_EVENT, success, false);
}

// 加载webaudio
function loadWebAudio(item, callback) {
if (!context) callback(new Error('Audio Downloader: no web audio context.'));

var data = window.resMap[item.url];
data = base64toArray(data);

if (data) {
context["decodeAudioData"](data.buffer, function(buffer){
//success
item.buffer = buffer;
callback(null, buffer);
}, function(){
//error
callback('decode error - ' + item.id, null);
});
} else {
callback('request error - ' + item.id, null);
}
}

// web加载数据方法
var arrayBufferHandler = (item, callback, isCrossOrigin, img) => {
var index = item.url.lastIndexOf(".");
var strtype=item.url.substr(index + 1, 4);
strtype=strtype.toLowerCase();
var data = window.resMap[item.url];

var img = new Image();
function loadCallback () {
img.removeEventListener('load', loadCallback);
img.removeEventListener('error', errorCallback);

callback(null, img);
}
function errorCallback () {
img.removeEventListener('load', loadCallback);
img.removeEventListener('error', errorCallback);

// Retry without crossOrigin mark if crossOrigin loading fails
// Do not retry if protocol is https, even if the image is loaded, cross origin image isn't renderable.

callback(new Error('Load image (' + url + ') failed'));
}

img.addEventListener('load', loadCallback);
img.addEventListener('error', errorCallback);
img.src = data;
};

// web加载数据方法
var jsonBufferHandler = (item, callback) => {
var str = window.resMap[item.url];
callback(null, str);
};

// web加载音频
var audioBufferHandler = (item, callback) => {
if (formatSupport.length === 0) {
return new Error('Audio Downloader: audio not supported on this browser!');
}

item.content = item.url;

// If WebAudio is not supported, load using DOM mode
if (!__audioSupport.WEB_AUDIO || (item.urlParam && item.urlParam['useDom'])) {
loadDomAudio(item, callback);
}
else {
loadWebAudio(item, callback);
}
}

// 添加加载函数
cc.loader.addDownloadHandlers({
png: arrayBufferHandler,
jpg: arrayBufferHandler,
jpeg: arrayBufferHandler,
});

cc.loader.addDownloadHandlers({
json: jsonBufferHandler,
});

cc.loader.addDownloadHandlers({
mp3: audioBufferHandler,
ogg: audioBufferHandler,
wav: audioBufferHandler,
m4a: audioBufferHandler
})

var splash = document.getElementById('splash');
splash.style.display = 'block';

boot();
}

})();
  • FbPlayableAd.onCTAClick();加在游戏结算界面里的play more按钮里

For any CTA, the playable code should call the Javascript function FbPlayableAd.onCTAClick() whenever an action should open the store. For example, “this.downloadBtn.onClick(function(){FbPlayableAd.onCTAClick()});. Facebook has code that will handle navigation to the right app store page for this app