├── .eslintrc.js
├── .gitignore
├── .idea
├── inspectionProfiles
│ └── Project_Default.xml
├── jsLibraryMappings.xml
├── jsLinters
│ └── eslint.xml
└── vcs.xml
├── README.md
├── doc
├── c25d1182.png
└── hehe.gif
├── game
├── README.md
└── index.html
├── icon
└── icon.png
├── package.json
├── src
├── core.js
├── editor.js
├── main.js
├── profile.js
├── server.js
└── window.js
└── static
└── 404.html
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // http://eslint.org/docs/user-guide/configuring
2 | module.exports = {
3 | // 将 ESLint 限制到一个特定的项目,在配置文件里设置 "root": true。ESLint 一旦发现配置文件中有 "root": true,它就会停止在父级目录中寻找。
4 | root: true,
5 | // 检测ES6代码
6 | parser: 'babel-eslint',
7 | parserOptions: {
8 | sourceType: 'module'
9 | },
10 | //
11 | env: {
12 | browser: true,
13 | },
14 | // 消除no-undef影响
15 | globals: {
16 | _: true
17 | },
18 | 'rules': {
19 | // 使用单引号
20 | 'quotes': [1, 'single'],
21 | 'prefer-promise-reject-errors': 0,
22 | 'space-unary-ops': 0,
23 | 'no-unused-expressions': 0,
24 | 'no-useless-return': 0,
25 | 'standard/no-callback-literal': 0,
26 | 'import/first': 0,
27 | 'import/export': 0,
28 | 'no-mixed-operators': 0,
29 | 'no-use-before-define': 0,
30 | // 允许使用分号
31 | 'semi': [0, 'never'],
32 | // 允许使用==
33 | 'eqeqeq': 0,
34 | // 缩进使用不做限制
35 | 'indent': 0,
36 | // 允许使用tab
37 | 'no-tabs': 0,
38 | // 函数圆括号之前的空格
39 | // 'space-before-function-paren': [2, "always"],
40 | // 不要求块内空格填充格式
41 | 'padded-blocks': 0,
42 | // 不限制变量一起声明
43 | 'one-var': 0,
44 | // debugger使用
45 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
46 | // 开发模式允许使用console
47 | 'no-console': 0,
48 | // 条件语句中复制操作符需要用圆括号括起来
49 | 'no-cond-assign': [2, 'except-parens'],
50 | // 允许使用条件表达式使用常量
51 | 'no-constant-condition': 0,
52 | // 单行可忽略大括号,多行不可忽略
53 | 'curly': [2, 'multi-line'],
54 | // 不允许使用var变量
55 | 'no-var': 2,
56 | // 不允许出现多个空格
57 | 'no-multi-spaces': ["error", {ignoreEOLComments: true}],
58 | 'camelcase': 0,
59 | // 对象字面量的键值空格风格
60 | 'key-spacing': 2,
61 | // if语句包含一个return语句, else就多余
62 | 'no-else-return': 2,
63 | // 建议将经常出现的数字提取为变量
64 | 'no-magic-numbers': [0, {ignoreArrayIndexes: true}],
65 | // 不允许重复声明变量
66 | 'no-redeclare': [2, {builtinGlobals: true}],
67 | // 立即执行函数风格
68 | 'wrap-iife': [2, 'inside'],
69 | // 不允许圆括号中出现空格
70 | 'space-in-parens': [2, 'never'],
71 | // 确保运算符周围有空格
72 | 'space-infix-ops': 2,
73 | // 强制点号与属性同一行
74 | 'dot-location': [2, 'property'],
75 | // 强制单行代码使用空格
76 | 'block-spacing': [2, 'always'],
77 | // 约束for-in使用hasOwnProperty判断
78 | 'guard-for-in': 0,
79 | // 采用one true brace style大括号风格
80 | 'brace-style': [2, '1tbs', {'allowSingleLine': true}],
81 | // 统一逗号周围空格风格
82 | 'comma-spacing': [2, {'before': false, 'after': true}],
83 | // 禁止出现多个空行
84 | 'no-multiple-empty-lines': [2, {'max': 1, 'maxEOF': 2}],
85 | // 允许箭头函数不使用圆括号
86 | 'arrow-parens': 0,
87 | // 规范generator函数的使用
88 | 'generator-star-spacing': [2, {'before': false, 'after': true}],
89 | // 要求在块级
90 | // 'lines-around-comment': [2, {
91 | // 'beforeBlockComment': true,
92 | // 'afterBlockComment': false,
93 | // 'beforeLineComment': true,
94 | // 'afterLineComment': false
95 | // }]
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | .idea
3 | package-lock.json
4 | splash.png
5 | style-desktop.css
6 | style-mobile.css
7 | game/res
8 | game/src
9 | game/subpackages
10 | node_modules
11 | .DS_Store
12 | cocos2d-js-min.js
13 | main.js
14 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/jsLibraryMappings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/jsLinters/eslint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CocosCreatorForDeskApp
2 | 如果你的游戏时使用cocos creator开发制作的,并且希望能够发布为桌面应用,那么该解决方案一定能帮助到您!
3 |
4 | 该解决方案,基于Electron,轻松将cocos creator发布到任何桌面,使用该解决方案的好处是,再也不用去踩发布原生桌面应用的坑了!
5 |
6 | 为此,您需要注意,相比原生桌面应用,该解决方案最终游戏包体会增加50~70M不等,如果您特别在意游戏安装包体积,那么,请慎重考虑!
7 |
8 |
9 |
10 |
11 | ## 使用说明
12 | - 安装依赖`npm install`
13 | - 运行: `npm run app`
14 |
15 | ## 打包说明
16 |
17 | ### 打包前的准备工作
18 |
19 | - 使用CocosCreator构建出一份web版本的游戏
20 | - 将构建的web游戏拷贝到game目录,game目录文件示例
21 | 
22 |
23 | 本项目采用的是`electron-builder`打包
24 |
25 | - 全局安装`electron-builder`依赖: `npm install -g electron-builder`
26 | - 打包:
27 | - win: `npm run pack-win`
28 | - mac: `npm run pack-mac`
29 |
30 |
31 | ## 后续开发功能
32 | - 请查看相关issues
33 | - 欢迎追加issues
34 |
35 | 欢迎加入CocosCreator插件交流群(224756137)
36 |
37 | 一起学习,一起进步!
38 |
39 | 
40 |
--------------------------------------------------------------------------------
/doc/c25d1182.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidys/CocosCreatorForDeskApp/955c0c2f2f3366e6fa6db2abba01d70fe07b17a0/doc/c25d1182.png
--------------------------------------------------------------------------------
/doc/hehe.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidys/CocosCreatorForDeskApp/955c0c2f2f3366e6fa6db2abba01d70fe07b17a0/doc/hehe.gif
--------------------------------------------------------------------------------
/game/README.md:
--------------------------------------------------------------------------------
1 | # 说明
2 | 请把你的游戏放在这个game目录
3 |
--------------------------------------------------------------------------------
/game/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Cocos Creator | examplecases
7 |
8 |
9 |
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 |
50 |
51 |
52 |
53 |
54 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/icon/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidys/CocosCreatorForDeskApp/955c0c2f2f3366e6fa6db2abba01d70fe07b17a0/icon/icon.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cocoscreatorfordeskapp",
3 | "version": "1.0.0",
4 | "description": "使用cocos creator开发游戏,打包桌面应用解决方案",
5 | "main": "src/main.js",
6 | "scripts": {
7 | "app": "electron .",
8 | "pack-win": "electron-builder --win --x64",
9 | "pack-mac": "electron-builder --mac"
10 | },
11 | "build": {
12 | "appId": "com.my.game",
13 | "productName": "Game",
14 | "mac": {
15 | "target": [
16 | "dmg"
17 | ]
18 | },
19 | "win": {
20 | "asar": true,
21 | "asarUnpack": "static",
22 | "icon": "icon/icon.png",
23 | "target": [
24 | "zip"
25 | ]
26 | }
27 | },
28 | "repository": {
29 | "type": "git",
30 | "url": "git+https://github.com/tidys/CocosCreatorForDeskApp.git"
31 | },
32 | "author": "",
33 | "license": "ISC",
34 | "bugs": {
35 | "url": "https://github.com/tidys/CocosCreatorForDeskApp/issues"
36 | },
37 | "homepage": "https://github.com/tidys/CocosCreatorForDeskApp#readme",
38 | "dependencies": {
39 | "@types/fs-extra": "^8.0.0",
40 | "@types/node": "^12.6.3",
41 | "chokidar": "^3.0.2",
42 | "express": "^4.17.1",
43 | "fs-extra": "^8.1.0",
44 | "json-format": "^1.0.1"
45 | },
46 | "devDependencies": {
47 | "babel-eslint": "^10.0.2",
48 | "electron": "^5.0.6",
49 | "eslint-plugin-vue": "^5.2.3"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/core.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidys/CocosCreatorForDeskApp/955c0c2f2f3366e6fa6db2abba01d70fe07b17a0/src/core.js
--------------------------------------------------------------------------------
/src/editor.js:
--------------------------------------------------------------------------------
1 | const Fs = require('fs');
2 | const Path = require('path');
3 | const Electron = require('electron');
4 |
5 | module.exports = {
6 | _getUserDateDir() {
7 | let dir = null;
8 | if (Electron.remote && Electron.remote.app) {
9 | dir = Electron.remote.app.getPath('userData');
10 | } else if (Electron && Electron.app) {
11 | dir = Electron.app.getPath('userData');
12 | }
13 | return dir;
14 | },
15 |
16 | getAppConfigDefaultPath(file) {
17 | let dir = this._getUserDateDir();
18 | let savePath = Path.join(dir, file || 'default.json');
19 |
20 | if (!Fs.existsSync(savePath)) {
21 | Fs.writeFileSync(savePath, '{}');
22 | }
23 | // console.log( savePath );
24 | return savePath;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | global.Editor = require('./editor.js');
2 | const Window = require('./window.js');
3 | const Server = require('./server.js');
4 | const {app} = require('electron');
5 | app.on('ready', function () {
6 | Server.initServer(function (url) {
7 | Window.createMainWindow(url);
8 | });
9 | });
10 | app.on('window-all-closed', function () {
11 | if (process.platform !== 'darwin') {
12 | app.quit();
13 | }
14 | });
15 |
--------------------------------------------------------------------------------
/src/profile.js:
--------------------------------------------------------------------------------
1 | const Fs = require('fs');
2 | const Path = require('path');
3 | const JsonFormat = require('json-format');
4 |
5 |
6 | class Profile {
7 | constructor(file) {
8 | this.data = null;
9 | this.file = file;
10 | this.vaild = this.load(file);
11 | }
12 |
13 | load(file) {
14 | if (!Fs.existsSync(file)) {
15 | return false;
16 | }
17 |
18 | let data = Fs.readFileSync(file, 'utf-8');
19 | try {
20 | this.data = JSON.parse(data);
21 | } catch (e) {
22 | this.data = null;
23 | return false;
24 | }
25 | return true;
26 | }
27 |
28 | saveItem(key, value) {
29 | if (!this.vaild) {
30 | console.log('save failed');
31 | return;
32 | }
33 | this.data[key] = value;
34 | Fs.writeFileSync(this.file, JSON.stringify(this.data));
35 | }
36 |
37 | // 保存object类型的数据
38 | saveWithObjectData(data) {
39 | if (!this.vaild) {
40 | console.log('save failed');
41 | return;
42 | }
43 | for (let key in data) {
44 | this.data[key] = data[key];
45 | }
46 |
47 | // Fs.writeFileSync( this.file, JSON.stringify( this.data ) );
48 | Fs.writeFileSync(this.file, JsonFormat(this.data));
49 | }
50 |
51 | // 保存array类型的数据
52 | saveWithArrayData(data) {
53 | if (!this.vaild) {
54 | console.log('save failed');
55 | return;
56 | }
57 | this.data = data;
58 |
59 | // Fs.writeFileSync( this.file, JSON.stringify( this.data ) );
60 | Fs.writeFileSync(this.file, JsonFormat(this.data));
61 | }
62 | }
63 |
64 | module.exports = Profile;
65 |
--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------
1 | const Express = require('express');
2 | const Path = require('path');
3 | const Fs = require('fs');
4 | module.exports = {
5 | _isInit: false,
6 | server: null,
7 | url: null,
8 | _launchCB: null,
9 | initServer(launchCB) {
10 | if (this._isInit === false) {
11 | this._isInit = true;
12 | this._launchCB = launchCB;
13 | this.probe(4101, this._onFindPort.bind(this));
14 | }
15 |
16 | },
17 | _startServer(port) {
18 | let App = Express();
19 | App.use(Express.static(Path.join(__dirname, '../game')));
20 |
21 | // App.get('/', function (req, res) {
22 | // let file = Path.join(__dirname, '../game/index.html');
23 | // res.sendFile(file);
24 | // });
25 |
26 | let server = App.listen(port, 'localhost', function () {
27 | let host = server.address().address;
28 | let port = server.address().port;
29 | this.url = `http://${host}:${port}`;
30 | console.log(`预览服务访问地址 ${this.url}`);
31 | this._launchCB && this._launchCB(this.url);
32 | }.bind(this))
33 | this.server = server;
34 | },
35 |
36 | _onFindPort(bl, port) {
37 | if (bl) {
38 | console.log('端口被占用:' + port);
39 | this.probe(port + 1, this._onFindPort.bind(this));
40 | } else {
41 | this._startServer(port);
42 | }
43 | },
44 | probe(port, callback) {
45 | let net = require('net');
46 |
47 | // tcp使用端口0表示系统分配端口
48 | let server = net.createServer().listen(port);
49 | server.on('listening', function () {
50 | server.once('close', function () {
51 | callback(false, port);
52 | });
53 | server.close();
54 | });
55 | server.on('error', function (err) {
56 | callback(true, port);
57 | });
58 | },
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/src/window.js:
--------------------------------------------------------------------------------
1 | const Electron = require('electron');
2 | const BrowserWindow = Electron.BrowserWindow;
3 | const Profile = require('./profile.js');
4 | const Chokidar = require('chokidar');
5 | const Path = require('path');
6 | const Fs = require('fs');
7 |
8 | module.exports = {
9 | createMainWindow: function (url) {
10 | let win = this._createWindow({
11 | title: '游戏窗口',
12 | url: url,
13 | devtools: true,
14 | profile: 'main-window.json',
15 | autoReload: true,
16 | watchDir: Path.join(__dirname),
17 | closeCallBack: function () {
18 |
19 | }
20 | })
21 | },
22 | _createWindow(options) {
23 | if (!options.profile) {
24 | return;
25 | }
26 | let profilePath = Editor.getAppConfigDefaultPath(options.profile);
27 | let profile = new Profile(profilePath);
28 | let width = 850;
29 | let height = 680;
30 | let screenSize = Electron.screen.getPrimaryDisplay().size;
31 | let x = screenSize.width / 2 - width / 2;
32 | let y = screenSize.height / 2 - height / 2;
33 | if (profile.data) {
34 | width = profile.data.width || width;
35 | height = profile.data.height || height;
36 | x = profile.data.x || x;
37 | y = profile.data.y || y;
38 | }
39 | let win = new BrowserWindow({
40 | width: width,
41 | height: height,
42 | x: x,
43 | y: y,
44 | title: options.title || '',
45 | parent: options.parent || null,
46 | modal: !!options.modal,
47 | resizable: true,
48 | webPreferences: {
49 | webSecurity: false,
50 | nodeIntegration: true, // 允许在渲染进程使用node特性
51 | },
52 | backgroundColor: '#ffffff'
53 | });
54 | if (options.html) {
55 | let indexFile = options.html;
56 | if (!Fs.existsSync(indexFile)) {
57 | indexFile = Path.join(__dirname, '../static/404.html');
58 | }
59 | win.loadFile(indexFile);
60 | } else if (options.url) {
61 | win.loadURL(options.url);
62 | } else {
63 | return;
64 | }
65 | win.show();
66 | if (options.devtools) {
67 | win.openDevTools();
68 | }
69 | win.on('resize', function (win) {
70 | let size = win.sender.getSize();
71 | profile.saveWithObjectData({
72 | width: size[0],
73 | height: size[1]
74 | });
75 | });
76 | win.on('close', (event) => {
77 | let window = event.sender;
78 | let pos = win.getPosition();
79 | profile.saveWithObjectData({
80 | x: pos[0],
81 | y: pos[1]
82 | });
83 |
84 | options.closeCallBack && options.closeCallBack();
85 | });
86 | if (options.autoReload) {
87 | this.watchReload(win, options.watchDir);
88 | }
89 | },
90 | watchReload(window, dir) {
91 | let watcher = Chokidar.watch(dir);
92 | let timer = null;
93 | watcher.on('all', function (event, file, state) {
94 | if (event === 'change') {
95 | console.log(`${event}: ${file}`);
96 | clearTimeout(timer);
97 | timer = setTimeout(function () {
98 | if (window && !window.isDestroyed()) {
99 | window.reload();
100 | }
101 | }, 1000);
102 | }
103 | });
104 | },
105 | }
106 |
--------------------------------------------------------------------------------
/static/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 | 404!
9 |
10 |
11 |
--------------------------------------------------------------------------------