├── base ├── run.js └── typeof2.js ├── lib ├── download.js ├── getLine.js ├── unzip │ ├── index.js │ └── bin │ │ ├── unzip.exe │ │ └── unzip32.dll ├── adaptPath.js ├── createLang.js ├── getLocalLang.js ├── compressFile.js ├── isTsAndJs.js ├── alignString.js ├── getPathInfo.js ├── getPuertsPath.js ├── getFilesPath.js ├── getPuertsWebGLPath.js ├── buildFile.js ├── spack.js ├── getPuerInfo.js ├── createServer.js ├── findPath.js ├── getAllJsPath.js └── createDebugers.js ├── index.js ├── .gitignore ├── Commands ├── build │ ├── startSymbol.js │ ├── swcConfig.js │ ├── ignore.js │ ├── buildRunTime.js │ ├── index.js │ ├── introduceRes.js │ ├── buildForApp.js │ ├── buildForMinigame.js │ ├── buildForBrowser.js │ └── help.js ├── init │ ├── Unity │ │ ├── Assets │ │ │ ├── Resources │ │ │ │ ├── Fonts │ │ │ │ │ ├── Arial.ttf │ │ │ │ │ └── Arial.ttf.meta │ │ │ │ ├── Textures │ │ │ │ │ ├── PuertsLogo.png │ │ │ │ │ └── PuertsLogo.png.meta │ │ │ │ ├── Prefabs │ │ │ │ │ ├── Slider.prefab.meta │ │ │ │ │ └── Slider.prefab │ │ │ │ └── Materials │ │ │ │ │ ├── PuertsMaterial.mat.meta │ │ │ │ │ └── PuertsMaterial.mat │ │ │ ├── Scenes │ │ │ │ ├── App.unity.meta │ │ │ │ └── App.unity │ │ │ ├── CS │ │ │ │ ├── App.cs.meta │ │ │ │ ├── JSLoader.cs.meta │ │ │ │ ├── App.cs │ │ │ │ └── JSLoader.cs │ │ │ └── Editor │ │ │ │ ├── PuertsConfig.cs.meta │ │ │ │ └── PuertsConfig.cs │ │ ├── TS │ │ │ ├── package.json │ │ │ ├── tsconfig.json │ │ │ ├── puer.config.js │ │ │ └── src │ │ │ │ ├── Core │ │ │ │ ├── requestAnimationFrame.ts │ │ │ │ └── Ticker.ts │ │ │ │ ├── DeveloperTools.ts │ │ │ │ └── App.ts │ │ └── .vscode │ │ │ ├── launch.json │ │ │ └── settings.json │ ├── help.js │ └── index.js └── dev │ ├── help.js │ └── index.js ├── temp ├── pull │ ├── index.js │ └── help.js └── make │ ├── index.js │ └── help.js ├── DEVELOP.md ├── package.json ├── LICENSE ├── Bin ├── getAllCommandHelp.js ├── getWorkDir.js ├── helpToString.js ├── getConfig.js ├── index.js └── formatParam.js ├── README.md └── logo.svg /base/run.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/download.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export default ()=>{ 2 | 3 | } -------------------------------------------------------------------------------- /lib/getLine.js: -------------------------------------------------------------------------------- 1 | export default new Array(61).join("—"); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules/ 3 | /.vscode/ 4 | *.lock 5 | *.log -------------------------------------------------------------------------------- /lib/unzip/index.js: -------------------------------------------------------------------------------- 1 | const unzip = (src,out)=>{ 2 | 3 | }; 4 | 5 | export default unzip; -------------------------------------------------------------------------------- /Commands/build/startSymbol.js: -------------------------------------------------------------------------------- 1 | import {sep} from 'path'; 2 | export default `${sep}Resources${sep}`; -------------------------------------------------------------------------------- /lib/adaptPath.js: -------------------------------------------------------------------------------- 1 | export default pathStr => process.platform === 'win32' ? `file://${pathStr}` : pathStr; -------------------------------------------------------------------------------- /lib/unzip/bin/unzip.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbfkcel/puerts_cli/HEAD/lib/unzip/bin/unzip.exe -------------------------------------------------------------------------------- /lib/unzip/bin/unzip32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbfkcel/puerts_cli/HEAD/lib/unzip/bin/unzip32.dll -------------------------------------------------------------------------------- /Commands/init/Unity/Assets/Resources/Fonts/Arial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbfkcel/puerts_cli/HEAD/Commands/init/Unity/Assets/Resources/Fonts/Arial.ttf -------------------------------------------------------------------------------- /Commands/init/Unity/Assets/Resources/Textures/PuertsLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbfkcel/puerts_cli/HEAD/Commands/init/Unity/Assets/Resources/Textures/PuertsLogo.png -------------------------------------------------------------------------------- /lib/createLang.js: -------------------------------------------------------------------------------- 1 | import localLang from "./getLocalLang.js"; 2 | const createLang = obj => { 3 | return key => obj[key][localLang]; 4 | } 5 | 6 | export default createLang; -------------------------------------------------------------------------------- /lib/getLocalLang.js: -------------------------------------------------------------------------------- 1 | const env = process.env; 2 | const language = env.LANG || env.LANGUAGE || env.LC_ALL || env.LC_MESSAGES; 3 | 4 | export default language && language.indexOf('EN') > -1 ? 'en' : 'cn'; -------------------------------------------------------------------------------- /temp/pull/index.js: -------------------------------------------------------------------------------- 1 | import { githubInfo, upmInfo } from '../../lib/getPuerInfo.js'; 2 | 3 | const pullPuerts = async (argObj) => { 4 | console.log(await upmInfo()); 5 | }; 6 | 7 | export default pullPuerts; 8 | -------------------------------------------------------------------------------- /Commands/init/Unity/Assets/Scenes/App.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0a26e98c55a844b589f1e9165cbbcb8a 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Commands/init/Unity/Assets/Resources/Prefabs/Slider.prefab.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1ed254399730f483f8d1e57e4841b996 3 | PrefabImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Commands/init/Unity/Assets/Resources/Materials/PuertsMaterial.mat.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0edd2cce754004149b1e3d30b16a6849 3 | NativeFormatImporter: 4 | externalObjects: {} 5 | mainObjectFileID: 2100000 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /lib/compressFile.js: -------------------------------------------------------------------------------- 1 | 2 | import swc from '@swc/core'; 3 | import fs from 'fs-extra'; 4 | 5 | export default (srcPath,outPath)=>{ 6 | const code = fs.readFileSync(srcPath,'utf-8'); 7 | const miniCode = swc.minifySync(code); 8 | fs.writeFileSync(outPath,miniCode.code); 9 | console.log("文件压缩成功:",srcPath); 10 | } -------------------------------------------------------------------------------- /Commands/init/Unity/Assets/CS/App.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f1079cb4da0dd4540a8968ab625fc25b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Commands/init/Unity/Assets/CS/JSLoader.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: de0d9a868f54741789d14328decc21d3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Commands/init/Unity/Assets/Editor/PuertsConfig.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 29e15fe85f6694342b3312726e68287a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /lib/isTsAndJs.js: -------------------------------------------------------------------------------- 1 | import getPathInfo from './getPathInfo.js'; 2 | 3 | /** 4 | * 判断目录路径是否为 TS或JS 文件 5 | * @param {string} srcPath 输入路径 6 | * @returns Boolean 7 | */ 8 | export default (targetPath)=>{ 9 | const extName = getPathInfo(targetPath).extname; 10 | return /^(jsx|js|ts|es|es6)$/i.test(extName) && !/(\.d\.ts)$/i.test(targetPath); 11 | } -------------------------------------------------------------------------------- /Commands/init/Unity/TS/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.1", 3 | "type": "module", 4 | "scripts": { 5 | "test": "echo 'Hello'" 6 | }, 7 | "dependencies": { 8 | "typeof2": "^1.0.4", 9 | "source-map-support": "^0.5.21", 10 | "chrome-remote-interface": "^0.31.3" 11 | }, 12 | "devDependencies": { 13 | "@types/node": "^18.11.13" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /base/typeof2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Get type 3 | * @param {any} arg Need to get the type of data 4 | * @returns {object} 5 | */ 6 | let t, 7 | type = arg => (t = typeof arg) === "object" ? {}.toString.call(arg).slice(8,-1).toLowerCase() : t, 8 | name = arg => (t = type(arg)) === "function" ? arg.name : t === "undefined" ? undefined : arg.constructor.name; 9 | export default arg => ({type:type(arg),name:name(arg)}); 10 | -------------------------------------------------------------------------------- /Commands/build/swcConfig.js: -------------------------------------------------------------------------------- 1 | export default { 2 | jsc: { 3 | parser: { 4 | syntax: "typescript", 5 | tsx: true 6 | }, 7 | target: "es2015", 8 | loose: false, 9 | minify: { 10 | "compress": false, 11 | "mangle": false 12 | } 13 | }, 14 | module: { 15 | type: "commonjs" 16 | }, 17 | minify: false, 18 | isModule: true 19 | }; -------------------------------------------------------------------------------- /Commands/init/Unity/TS/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "ESNext", 5 | "jsx": "preserve", 6 | "experimentalDecorators": true, 7 | "esModuleInterop":true, 8 | "typeRoots": [ 9 | "./node_modules/@types", 10 | "../Assets/Puerts/Typing", 11 | "../Assets/Gen/Typing" 12 | ] 13 | }, 14 | "inclde":[ 15 | "./src/*.ts" 16 | ] 17 | } -------------------------------------------------------------------------------- /lib/alignString.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 字符串对齐方法 3 | * @param {string} str 字符串 4 | * @param {number} len 对齐长度 5 | * @param {string} align 对齐方式 6 | * @returns string 7 | */ 8 | const alignString = (str,len,align='left') => { 9 | switch (align) { 10 | case 'left': 11 | return str.padEnd(len) 12 | break; 13 | case 'right': 14 | return str.padStart(len); 15 | break; 16 | default: 17 | throw new Error(`Invalid alignment: ${align}`); 18 | break; 19 | }; 20 | }; 21 | 22 | export default alignString; -------------------------------------------------------------------------------- /Commands/init/Unity/Assets/Resources/Fonts/Arial.ttf.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ba6981c61947141e095440956774554b 3 | TrueTypeFontImporter: 4 | externalObjects: {} 5 | serializedVersion: 4 6 | fontSize: 16 7 | forceTextureCase: -2 8 | characterSpacing: 0 9 | characterPadding: 1 10 | includeFontData: 1 11 | fontNames: 12 | - Arial 13 | fallbackFontReferences: [] 14 | customCharacters: 15 | fontRenderingMode: 0 16 | ascentCalculationMode: 1 17 | useLegacyBoundsCalculation: 0 18 | shouldRoundAdvanceValue: 1 19 | userData: 20 | assetBundleName: 21 | assetBundleVariant: 22 | -------------------------------------------------------------------------------- /temp/make/index.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import path from 'node:path'; 3 | import getPathInfo from '../../lib/getPathInfo.js'; 4 | import isTsAndJs from '../../lib/isTsAndJs.js'; 5 | import buildFile from '../../lib/buildFile.js'; 6 | import line from '../../lib/getLine.js'; 7 | import createLang from '../../lib/createLang.js'; 8 | import createDebugers from '../../lib/createDebugers.js'; 9 | import findPath from '../../lib/findPath.js'; 10 | 11 | // https://api.github.com/repos/Kitware/CMake/releases/latest 12 | 13 | 14 | 15 | const dev = async(argObj)=>{ 16 | 17 | }; 18 | 19 | export default dev; -------------------------------------------------------------------------------- /Commands/init/Unity/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Puerts", 9 | "type": "node", 10 | "request": "attach", 11 | "port": 43990 12 | }, 13 | { 14 | "name": "UnityEditor", 15 | "type": "unity", 16 | "path": "${workspaceFolder}/Library/EditorInstance.json", 17 | "request": "launch" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /DEVELOP.md: -------------------------------------------------------------------------------- 1 | # Developer Guide 2 | 3 | 需要对本工具进行二次开发的,不建议使用 `npm` 来安装。 4 | 5 | ## 安装 6 | 7 | - 克隆本项目 `git clone git@github.com:sbfkcel/puerts_cli.git` 8 | - 拉取依赖 9 | - 终端运行 `cd puerts_cli` 10 | - 终端运行 `npm install` 11 | - 挂载全环境变量 12 | - 终端运行 `npm link` 13 | 14 | 现在通过 `prev -v` 则可以输出版本号且正常使用了。 15 | 16 | ## 文件结构 17 | 18 | ```bash 19 | ├── Bin # cli 入口目录 20 | ├── Commands # 命令存放目录,一个目录一个命令 21 | │   ├── build # 命令目录 22 | │   │   ├── help.js # 必须要有,命令帮助及参数配置文件 23 | │   │   └── index.js # 必须要有,命令入口文件,需要向外提供一个方法,方法有一个接收参数对象包含了终端传入的参数、配置、项目信息、当前执行命令所在的路径等 24 | │   ├── dev 25 | │   └── init 26 | ├── index.js 27 | ├── lib 28 | └── package.json 29 | ``` 30 | -------------------------------------------------------------------------------- /Commands/init/Unity/TS/puer.config.js: -------------------------------------------------------------------------------- 1 | import os from 'node:os'; 2 | import path,{ dirname } from 'node:path'; 3 | import { fileURLToPath } from 'node:url'; 4 | 5 | const __dirname = dirname(fileURLToPath(import.meta.url)); 6 | const projectName = path.basename(path.join(__dirname,'..')); 7 | 8 | /** 9 | * puer 脚手架配置 10 | */ 11 | const config = { 12 | // 配置项目 TS 文件目录 13 | tsProjectSrcDir: "./src", 14 | 15 | // 配置项目 TS 编译为 JS 所输出的目录(建议不动) 16 | tsOutputDir: "../Assets/Resources/JS", 17 | 18 | // 配置微信小游戏导出目录 19 | // - 建议与【微信小游戏转换工具】中的【导出路径】保持一至 20 | // - PS:这里为了演示方便,默认输出到桌面与项目名称对应的目录下 21 | minigameOutputDir: path.join(os.homedir(),'Desktop',projectName) 22 | } 23 | export default config; -------------------------------------------------------------------------------- /lib/getPathInfo.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import path from 'node:path'; 3 | 4 | /** 5 | * 获取目标路径信息 6 | * @param {string} targetPath <必选> 目标路径 7 | * @returns object 路径信息,包含其类型、扩展名、目录名 8 | */ 9 | export default (targetPath)=>{ 10 | const result = {}; 11 | let stat; 12 | try { 13 | stat = fs.statSync(targetPath) 14 | } catch (error) {}; 15 | if(stat){ 16 | result.type = stat.isFile() ? 'file' : 17 | stat.isDirectory ? 'dir' : 18 | stat.isSymbolicLink ? 'link' : 19 | stat.isSocket ? 'socket' : 20 | 'other'; 21 | result.isExist = true; 22 | }; 23 | result.extname = path.extname(targetPath).slice(1); 24 | result.dirPath = path.dirname(targetPath); 25 | return result; 26 | } -------------------------------------------------------------------------------- /Commands/build/ignore.js: -------------------------------------------------------------------------------- 1 | 2 | import getPathInfo from "../../lib/getPathInfo.js"; 3 | import startSymbol from "./startSymbol.js"; 4 | const ignore = (item, itemPath) => { 5 | if (item === 'node_modules' || item === 'Editor') { // 所有 node_modules、Editor 可以过滤掉 6 | return true; 7 | }; 8 | if (item.slice(0, 1) === '.') { // 所有以 . 开始的文件过滤掉 9 | return true; 10 | }; 11 | const { type, extname } = getPathInfo(itemPath); 12 | if (type === 'file' && !/^(mjs|cjs|js)$/i.test(extname)) { // 是文件类型,但不是 js 文件的过滤掉 13 | return true; 14 | }; 15 | if (type === 'file' && itemPath.indexOf(startSymbol) < 0) { // 不在 Resources 的目录也过滤掉 16 | return true; 17 | }; 18 | } 19 | 20 | export default ignore; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@puerts/cli", 3 | "version": "0.3.9", 4 | "type": "module", 5 | "author": "sbfkcel@163.com", 6 | "keywords": [ 7 | "puerts", 8 | "puerts cli", 9 | "unity", 10 | "typescript", 11 | "unity js" 12 | ], 13 | "description": "PureTS is a quick scaffold tool, which can be used in the project.", 14 | "engines": { 15 | "node": ">=16.15.0", 16 | "npm": ">=8.5.5" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/sbfkcel/puerts_cli.git" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/sbfkcel/puerts_cli/issues" 24 | }, 25 | "homepage": "https://github.com/sbfkcel/puerts_cli#readme", 26 | "main": "index.js", 27 | "bin": { 28 | "puer": "Bin/index.js" 29 | }, 30 | "dependencies": { 31 | "@swc/core": "^1.3.21", 32 | "axios": "^1.3.4", 33 | "chrome-remote-interface": "^0.31.3", 34 | "fs-extra": "^11.1.0" 35 | }, 36 | "license": "MIT" 37 | } 38 | -------------------------------------------------------------------------------- /lib/getPuertsPath.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import findPath from './findPath.js'; 3 | 4 | /** 5 | * 查找项目中 Puerts 所存放的路径 6 | * @param {string} srcDir unity 项目目录路径 7 | * @return string 查找到的 Puerts 目录路径 8 | */ 9 | export default srcDir=>{ // 通过文件匹配到 puerts 所在的目录 10 | const match1 = (item,itemPath) => { 11 | if( 12 | item === 'GenericDelegate.cs' && // 根据 'GenericDelegate.cs' 文件来定位路径 13 | path.basename(path.dirname(itemPath)) === 'JSType' // 确定文件是在 JSType 目录下 14 | ){ 15 | return true; 16 | }; 17 | }; 18 | const find = findPath(srcDir,[match1],'dir',false,/(^(node_modules)$)|(^\.)/i); 19 | if(find){ 20 | return path.join(find,'..','..','..'); 21 | }else{ 22 | throw new Error('未在您的项目目录中发现 Puerts 目录 -> ',srcDir); 23 | }; 24 | }; -------------------------------------------------------------------------------- /Commands/build/buildRunTime.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import getPuertsWebGLPath from '../../lib/getPuertsWebGLPath.js'; 3 | import spack from '../../lib/spack.js'; 4 | 5 | /** 6 | * 打包 PuertsWebGL PuertsDLLMock 为 puerts-runtime.js 7 | * @param {string} srcDir 项目目录 8 | * @param {string} outputDir 打包结果输出目录 9 | * @param {object} options 编译选项 10 | */ 11 | const buildRunTime = async (srcDir, outputDir, options = {})=>{ 12 | const {param} = options; 13 | const puertsWebGLPath = getPuertsWebGLPath(srcDir); 14 | const config = { 15 | target:'node', 16 | entry: { 17 | "puerts-runtime": path.join(puertsWebGLPath, 'index.ts') 18 | }, 19 | output: { 20 | path: outputDir, 21 | name: '[name].js' 22 | }, 23 | options 24 | }; 25 | await spack(config, code => { 26 | return `(function(global){${code}})(typeof global !== 'undefined' ? global : globalThis || window)`; 27 | }); 28 | } 29 | 30 | export default buildRunTime; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2025 sbfkcel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/getFilesPath.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import getPathInfo from "./getPathInfo.js"; 4 | /** 5 | * 获取指定路径目录下所有文件列表 6 | * @param {string} dirPath <必选> 目录路径 7 | * @param {array} result <可选> 将结果往哪个队列中添加(用于内部循环调用) 8 | * @param {function} ignore [可选] 忽略 9 | * @returns array 文件列表 10 | */ 11 | const getFilesPath = (dirPath,result,ignore)=>{ 12 | result = result || []; 13 | const isDir = fs.statSync(dirPath).isDirectory(); 14 | if(isDir){ 15 | let items = fs.readdirSync(dirPath); 16 | items.forEach(item => { 17 | let itemPath = path.join(dirPath,item); 18 | if(typeof ignore === 'function' && ignore(item,itemPath)){ 19 | return; 20 | }; 21 | switch (getPathInfo(itemPath).type) { 22 | case 'file': 23 | result.push(itemPath); 24 | break; 25 | case 'dir': 26 | getFilesPath(itemPath,result,ignore); 27 | break; 28 | } 29 | }); 30 | }else{ 31 | throw new Error("传入的不是有效的目录路径"); 32 | }; 33 | return result; 34 | } 35 | export default getFilesPath; -------------------------------------------------------------------------------- /lib/getPuertsWebGLPath.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import findPath from './findPath.js'; 3 | import createLang from './createLang.js'; 4 | 5 | const lang = createLang({ 6 | notExist:{ 7 | cn:'未在您的项目目录中发现 PuertsWebGL 目录', 8 | en:'PuertsWebGL directory not found in your project directory' 9 | } 10 | }) 11 | /** 12 | * 查找项目中 PuertsWebGL PuertsDLLMock 所存放的路径 13 | * @param {string} srcDir unity 项目目录路径 14 | * @return string 查找到的 PuertsWebGL 目录路径 15 | */ 16 | export default srcDir=>{ // 通过文件匹配到 puertsWebGL 所在的目录 17 | const match1 = (item,itemPath) => { 18 | if( 19 | item === 'setToInvokeJSArgument.ts' && // 根据 'setToInvokeJSArgument.ts' 文件来定位路径 20 | path.basename(path.dirname(itemPath)) === 'mixins' // 确定文件是在 mixins 目录下 21 | ){ 22 | return true; 23 | }; 24 | }; 25 | const find = findPath(srcDir,[match1],'dir',false,/(^(node_modules)$)|(^\.)/i); 26 | if(find){ 27 | return path.join(find,'..'); 28 | }else{ 29 | throw new Error(`${lang('notExist')} -> ${srcDir}`); 30 | }; 31 | }; -------------------------------------------------------------------------------- /Bin/getAllCommandHelp.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import path,{ dirname } from 'node:path'; 3 | import { fileURLToPath } from 'node:url'; 4 | import alignString from '../lib/alignString.js'; 5 | import getPathInfo from '../lib/getPathInfo.js'; 6 | import createLang from '../lib/createLang.js'; 7 | import adaptPath from '../lib/adaptPath.js'; 8 | 9 | const __dirname = dirname(fileURLToPath(import.meta.url)); 10 | const commandsDir = path.join(__dirname,'..','Commands'); 11 | const commands = fs.readdirSync(commandsDir); 12 | 13 | const lang = createLang({ 14 | commands:{ 15 | cn:'任务列表:', 16 | en:'Command List:' 17 | } 18 | }); 19 | const getAllCommandHelp = async ()=>{ 20 | let result = `${lang('commands')}\n`; 21 | for(let i=0,len=commands.length; i{ 12 | while(path.join(cwd,'..') !== cwd){ // 往上检测,一直检测到根目录为止 13 | if( 14 | getPathInfo(path.join(cwd,'ProjectSettings')).type === 'dir' && 15 | getPathInfo(path.join(cwd,'Packages')).type === 'dir' && 16 | getPathInfo(path.join(cwd,'Library')).type === 'dir' 17 | ){ 18 | return {type:'unity',path:cwd,name:path.basename(cwd)}; 19 | }else if( 20 | getPathInfo(path.join(cwd,'Binaries')).type === 'dir' && 21 | getPathInfo(path.join(cwd,'Config')).type === 'dir' && 22 | getPathInfo(path.join(cwd,'DerivedDataCache')).type === 'dir' && 23 | getPathInfo(path.join(cwd,'Saved')).type === 'dir' 24 | ){ 25 | return {type:'unreal',path:cwd,name:path.basename(cwd)}; 26 | }else{ 27 | cwd = path.join(cwd,'..'); 28 | }; 29 | } 30 | return {msg:lang('msg')}; 31 | } 32 | export default getWorkDir; -------------------------------------------------------------------------------- /Bin/helpToString.js: -------------------------------------------------------------------------------- 1 | import alignString from '../lib/alignString.js'; 2 | import createLang from '../lib/createLang.js'; 3 | const indentation = alignString('',4); // 缩进 4 | 5 | const lang = createLang({ 6 | des:{ 7 | cn:'描述:', 8 | en:'Describe:' 9 | }, 10 | param:{ 11 | cn:'参数:', 12 | en:'Parameter:' 13 | } 14 | }) 15 | 16 | /** 17 | * 将任务帮助对象转为字符串 18 | * @param {object} obj 帮助对象 19 | * @returns string 20 | */ 21 | const helpToString = (obj)=>{ 22 | const commandDesLang = createLang({des:obj.des}); 23 | let result = `${lang('des')}\n`; 24 | result += `${indentation}${commandDesLang('des')}\n`; 25 | result += `${lang('param')}\n`; 26 | const params = obj.params; 27 | for(let key in params){ 28 | const item = params[key]; 29 | const {simple,full} = (()=>{ 30 | const arr = key.split(','); 31 | const simple = arr.length > 1 ? arr[0] : ''; 32 | const full = arr[arr.length - 1]; 33 | return {simple,full}; 34 | })(); 35 | const paramDesLang = createLang({des:item.des}); 36 | result += `${indentation}${alignString(simple+(simple===''?'':','),4)} ${alignString(full,12)} ${paramDesLang('des')} \n` ; 37 | }; 38 | return result; 39 | }; 40 | 41 | export default helpToString; -------------------------------------------------------------------------------- /Commands/init/Unity/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": 3 | { 4 | "**/.DS_Store":true, 5 | "**/.git":true, 6 | "**/.gitmodules":true, 7 | "**/*.booproj":true, 8 | "**/*.pidb":true, 9 | "**/*.suo":true, 10 | "**/*.user":true, 11 | "**/*.userprefs":true, 12 | "**/*.unityproj":true, 13 | "**/*.dll":true, 14 | "**/*.exe":true, 15 | "**/*.pdf":true, 16 | "**/*.mid":true, 17 | "**/*.midi":true, 18 | "**/*.wav":true, 19 | "**/*.gif":true, 20 | "**/*.ico":true, 21 | "**/*.jpg":true, 22 | "**/*.jpeg":true, 23 | "**/*.png":true, 24 | "**/*.psd":true, 25 | "**/*.tga":true, 26 | "**/*.tif":true, 27 | "**/*.tiff":true, 28 | "**/*.3ds":true, 29 | "**/*.3DS":true, 30 | "**/*.fbx":true, 31 | "**/*.FBX":true, 32 | "**/*.lxo":true, 33 | "**/*.LXO":true, 34 | "**/*.ma":true, 35 | "**/*.MA":true, 36 | "**/*.obj":true, 37 | "**/*.OBJ":true, 38 | "**/*.asset":true, 39 | "**/*.cubemap":true, 40 | "**/*.flare":true, 41 | "**/*.mat":true, 42 | "**/*.meta":true, 43 | "**/*.prefab":true, 44 | "**/*.unity":true, 45 | "build/":true, 46 | "Build/":true, 47 | "Library/":true, 48 | "library/":true, 49 | "obj/":true, 50 | "Obj/":true, 51 | "ProjectSettings/":true, 52 | "temp/":true, 53 | "Temp/":true 54 | } 55 | } -------------------------------------------------------------------------------- /Commands/dev/help.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | des:{ // 任务描述 3 | cn:'启动开发模式,监听 TS 修改并自动编译', 4 | en:'Start the development mode, monitor TS modification and compile automatically' 5 | }, 6 | params:{ 7 | '-r,--reload':{ 8 | des:{ 9 | cn:'是否开启热重载,默认:true', 10 | en:'Whether to enable hot reloading, default: true' 11 | }, 12 | default: true, 13 | check: val => { // 检查参数,如果检查到错误要返回一个对象(包含:cn、en) 14 | if(typeof val !== 'boolean'){ 15 | return { 16 | cn:'--reload 传入的值必须为 true、flase', 17 | en:'The value passed in by --reload must be true or false' 18 | } 19 | }; 20 | }, 21 | format: val => { 22 | if(/^true|1$/i.test(val)){ 23 | return true; 24 | }; 25 | if(/^false|0$/i.test(val)){ 26 | return false; 27 | }; 28 | } 29 | }, 30 | '-h,--help':{ 31 | des:{ 32 | cn:'查看 dev 帮助', 33 | en:'View dev help' 34 | }, 35 | check: val => { 36 | return { 37 | cn:'查看帮助不需要参数', 38 | en:'View help does not require parameters' 39 | } 40 | } 41 | } 42 | } 43 | }; 44 | export default config; -------------------------------------------------------------------------------- /Commands/init/help.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | des:{ // 任务描述 3 | cn:'在当前目录初始化一个 puerts 工程项目', 4 | en:'Initialize a puerts project in the current directory' 5 | }, 6 | params:{ 7 | '-f,--force':{ 8 | des:{ 9 | cn:'强制执行,默认:false,(该操作将会覆盖已有文件)', 10 | en:'Mandatory, default: false, (this operation will overwrite existing files)' 11 | }, 12 | default:false, 13 | check: val => { // 检查参数,如果检查到错误要返回一个对象(包含:cn、en) 14 | if(typeof val !== 'boolean'){ 15 | return { 16 | cn:'--force 传入的值必须为 true、flase', 17 | en:'The value passed in by --force must be true or false' 18 | } 19 | }; 20 | }, 21 | format: val => { // 格式化参数 22 | if(/^true|1$/i.test(val)){ 23 | return true; 24 | }; 25 | if(/^false|0$/i.test(val)){ 26 | return false; 27 | }; 28 | } 29 | }, 30 | '-h,--help':{ 31 | des:{ 32 | cn:'查看 init 帮助', 33 | en:'View init help' 34 | }, 35 | check: val => { 36 | return { 37 | cn:'查看帮助不需要参数', 38 | en:'View help does not require parameters' 39 | } 40 | } 41 | } 42 | } 43 | }; 44 | export default config; -------------------------------------------------------------------------------- /Bin/getConfig.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import getPathInfo from '../lib/getPathInfo.js'; 3 | import createLang from '../lib/createLang.js'; 4 | import findPath from '../lib/findPath.js'; 5 | import adaptPath from '../lib/adaptPath.js'; 6 | 7 | const lang = createLang({ 8 | msg:{ 9 | cn:`项目未初始化,请使用 'init' 命令来初始化一个 PuerTs 工程`, 10 | en:`Project not initialized, please use 'init' command to initialize a PuerTs project` 11 | } 12 | }) 13 | const getConfig = async(workDir)=>{ 14 | const configPath = (()=>{ 15 | const dirItems = ['TS','TypeScript','ts','typescript','Puer-Project','puer-project']; // 尝试在这几个目录中寻找 16 | for(let i=0,len=dirItems.length; i{ 16 | const fun = ()=>{ 17 | const currentTime:number = (()=>{ // 当前时间(这里为了V8和Node运行时的兼容,performance 精度更高) 18 | if(obj.performance){ 19 | return obj.performance.now(); 20 | }; 21 | return +new Date - startTime; 22 | })(); 23 | lastTime = lastTime || currentTime; // 防止首次执行无历史时间 24 | const timeCouse:number = currentTime - lastTime; // 距离上一帧时间 25 | const isNext:boolean = intervalTime-timeCouse <= 0 || timeCouse === 0; // 判断是否执行下一帧 26 | if(isNext){ 27 | lastTime = currentTime; 28 | callback(timeCouse,currentTime); 29 | }else{ 30 | callback(); 31 | }; 32 | }; 33 | return _requestAnimationFrame(fun); 34 | }; 35 | const customCancelAnimationFrame = timer => { 36 | _cancelAnimationFrame(timer); 37 | }; 38 | export const requestAnimationFrame = customRequestAnimationFrame; 39 | export const cancelAnimationFrame = customCancelAnimationFrame; -------------------------------------------------------------------------------- /temp/pull/help.js: -------------------------------------------------------------------------------- 1 | const defaultVersion = '2'; 2 | const config = { 3 | des: { // 任务描述 4 | cn: '拉取 Puerts', 5 | en: 'Pull Puerts' 6 | }, 7 | params: { 8 | '-t,--tag': { 9 | des: { 10 | cn: `要拉取的 Puerts 版本号,例如:2.0、2.0.x`, 11 | en: `The Puerts version number to pull, eg: 2.0, 2.0.x` 12 | }, 13 | default: defaultVersion, 14 | check: val => { // 检查参数,如果检查到错误要返回一个对象(包含:cn、en) 15 | const re = /^((\d+\.\d)|(\d+\.\d+\.\d))$/; 16 | if (!re.test(val)) { 17 | return { 18 | cn: `Tag 传入的值必须为 x.x、x.x.x 格式,默认:${defaultVersion}`, 19 | en: `Tag the transmitted value must be in x.x, x.x.x format,Default:${defaultVersion}` 20 | } 21 | }; 22 | }, 23 | format: val => { 24 | return val + ''; 25 | } 26 | }, 27 | '-r,--runtime': { 28 | des: { 29 | cn: '运行时,可选:v8、quickjs,默认:v8', 30 | en: 'Runtime, optional: v8, quickjs, default: v8' 31 | }, 32 | default: 'v8', 33 | check: val => { 34 | if (!(/^(v8|quickjs)$/.test(val))) { 35 | return { 36 | cn: `Runtime 值必须为 v8、quickjs,默认:v8`, 37 | en: `Runtime value must be v8, quickjs, default: v8` 38 | } 39 | } 40 | }, 41 | format: val => { 42 | return val.toLocaleLowerCase(); 43 | } 44 | }, 45 | '-h,--help': { 46 | des: { 47 | cn: '查看 pull 帮助', 48 | en: 'View pull help' 49 | }, 50 | check: val => { 51 | return { 52 | cn: '查看帮助不需要参数', 53 | en: 'View help does not require parameters' 54 | } 55 | } 56 | } 57 | } 58 | }; 59 | export default config; 60 | -------------------------------------------------------------------------------- /Commands/build/index.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import fs from 'fs-extra'; 3 | import { spawnSync } from 'node:child_process'; 4 | import buildForBrowser from "./buildForBrowser.js"; 5 | import buildForMinigame from "./buildForMinigame.js"; 6 | import buildForApp from './buildForApp.js'; 7 | import introduceRes from './introduceRes.js'; 8 | import createServer from '../../lib/createServer.js'; 9 | import createLang from '../../lib/createLang.js'; 10 | 11 | const lang = createLang({ 12 | install:{ 13 | cn:'正在尝试安装默认依赖', 14 | en:'Attempting to install default dependencies' 15 | }, 16 | installFailed:{ 17 | cn:`依赖安装失败,请确保网络连接正常后手动执行 'npm install'`, 18 | en:`Dependent installation failed. Please manually execute 'npm install' after ensuring that the network connection is normal` 19 | } 20 | }); 21 | const build = async(argObj)=>{ 22 | const {param,config} = argObj; 23 | if(param.clear.at(-1)){ 24 | const nodeModulesPath = path.join(config.tsProjectDir,'node_modules'); 25 | fs.removeSync(nodeModulesPath); 26 | try { // 尝试安装依赖并创建 node_modules 目录软链 27 | console.log(lang('install')); 28 | fs.ensureDirSync(path.join(config.tsProjectDir,'node_modules')); // 保证目录是存在的 29 | spawnSync('npm',['install'],{cwd:config.tsProjectDir,shell:true}); 30 | } catch (error) { 31 | console.log(lang('installFailed')); 32 | }; 33 | }; 34 | if(param.target && param.target.at(-1) === 'minigame'){ 35 | const miniGameOutDir = config.minigameOutputDir; 36 | await buildForBrowser(argObj); 37 | console.log("---") 38 | await buildForMinigame(argObj); 39 | introduceRes(miniGameOutDir); // 尝试在index.html、game.js中引入编译结果 40 | if(param.browse && param.browse.at(-1)){ // 有启动预览参数则尝试启用一个静态服务 41 | const port = param.port.at(-1) || 10000; 42 | createServer(path.join(miniGameOutDir,'webgl'),port); 43 | }; 44 | }else{ 45 | buildForApp(argObj); 46 | }; 47 | }; 48 | 49 | export default build; -------------------------------------------------------------------------------- /Commands/init/Unity/TS/src/DeveloperTools.ts: -------------------------------------------------------------------------------- 1 | const System:any = CS.System; 2 | const Puerts:any = CS.Puerts; 3 | const UnityEngine:any = CS.UnityEngine; 4 | 5 | // HotReload 端口号存储 6 | ((puer:any)=>{ 7 | const jsEnvs:any = Puerts.JsEnv.jsEnvs; 8 | const debugPorts = (()=>{ 9 | let result = ''; 10 | for(let i=0,len=jsEnvs.Count; i{ 30 | // 增加 path、fs 模块支持 31 | puer.registerBuildinModule('path', { 32 | dirname(path) { 33 | return System.IO.Path.GetDirectoryName(path); 34 | }, 35 | resolve(dir, url) { 36 | url = url.replace(/\\/g, '/'); 37 | while (url.startsWith('../')) { 38 | dir = System.IO.Path.GetDirectoryName(dir); 39 | url = url.substr(3); 40 | } 41 | return System.IO.Path.Combine(dir, url); 42 | }, 43 | }); 44 | puer.registerBuildinModule('fs', { 45 | existsSync(path) { 46 | return System.IO.File.Exists(path); 47 | }, 48 | readFileSync(path) { 49 | return System.IO.File.ReadAllText(path); 50 | }, 51 | }); 52 | 53 | // 由于目前 puerts 版本引入 node_modules 不支持 import 方式,并且 source-map-support 扩展依赖 fs、path 模块 54 | const sourceMapSupport = require('source-map-support'); 55 | 56 | // 处理文件关系(source-map-support) 依赖上面的文件 57 | sourceMapSupport.install({ 58 | retrieveSourceMap:function(source){ 59 | let mapFile = source+'.map'; 60 | if (System.IO.File.Exists(mapFile)) { 61 | return { 62 | url: source, 63 | map: System.IO.File.ReadAllText(mapFile) 64 | }; 65 | }; 66 | return null; 67 | } 68 | }); 69 | })(global.puer); 70 | -------------------------------------------------------------------------------- /lib/buildFile.js: -------------------------------------------------------------------------------- 1 | import swc from '@swc/core'; 2 | import fs from 'fs-extra'; 3 | import path from 'path'; 4 | import getPathInfo from './getPathInfo.js'; 5 | import createLang from './createLang.js'; 6 | 7 | const lang = createLang({ 8 | 'success':{ 9 | cn:'编译成功:', 10 | en:'Compiled successfully:' 11 | } 12 | }); 13 | export default (srcPath, outPath, options={}) => { 14 | const srcPathInfo = getPathInfo(srcPath); 15 | if(srcPathInfo.type !== 'file'){ 16 | return; 17 | }; 18 | 19 | // 根据参数调整编译参数 20 | const tsTransformOption = Object.assign({ 21 | jsc: { 22 | parser: { 23 | syntax: "typescript", 24 | tsx: true 25 | }, 26 | target: "es2020", 27 | loose: false, 28 | minify: { 29 | compress: false, 30 | mangle: false 31 | }, 32 | preserveAllComments: true 33 | }, 34 | module: { 35 | type: "es6" 36 | }, 37 | minify: false, 38 | isModule: true, 39 | sourceMaps: true 40 | },options); 41 | 42 | const js = swc.transformFileSync(srcPath,tsTransformOption); // 先将TS转换为JS 43 | 44 | // 该正则通过 import、from 关键字来匹配模块路径 45 | const re = /((^import|\nimport|\simport)(\s*)(\(*)('|")(\.|\/)([^'"]*)('|")(\)*))|((( from )('|")(\.|\/))([^'"]*)('|"))/g; 46 | // console.log(js); 47 | js.code = js.code.replace(re,item=>{ // 替换 JS 代码中模块引用,以增加扩展名 48 | const itemPath = (()=>{ 49 | const matchArr = item.match(/('|")([^'"]*)('|")/); 50 | if(matchArr && matchArr[2] !== undefined){ 51 | return matchArr[2]; 52 | }; 53 | })(); 54 | if(path.extname(itemPath) === ""){ // 没有扩展名则添加上扩展或 55 | return `${item.slice(0,-1)}.js${item.slice(-1)}`; 56 | }; 57 | return item; 58 | }); 59 | const dirPath = path.dirname(outPath); 60 | fs.ensureDirSync(dirPath); 61 | if(js.map){ 62 | js.code += `\n//# sourceMappingURL=${path.parse(outPath).base}.map`; 63 | fs.writeFileSync(`${outPath}.map`,js.map); 64 | }; 65 | fs.writeFileSync(outPath,js.code); 66 | console.log(`${lang('success')}${srcPath}`); 67 | } -------------------------------------------------------------------------------- /lib/spack.js: -------------------------------------------------------------------------------- 1 | import swc from '@swc/core'; 2 | import fs from 'fs-extra'; 3 | import path from 'path'; 4 | 5 | const isUserDefinedEntry = (config,name) => { // 用于判断是否为用户定义入口 6 | if (typeof config.entry === 'string') { 7 | return config.entry === name; 8 | }; 9 | if (Array.isArray(config.entry)) { 10 | for (const e of config.entry){ 11 | if (e === name) { 12 | return true; 13 | } 14 | } 15 | return false; 16 | }; 17 | return name in config.entry; 18 | }; 19 | const spack = async(config,pack)=>{ // 打包功能参见 https://github.com/swc-project/cli/blob/master/src/spack/index.ts 20 | const bundle = await swc.bundle(config); 21 | for(let key in bundle){ 22 | let outPath = ''; 23 | if(isUserDefinedEntry(config,key)){ 24 | outPath = path.join(config.output.path, config.output.name.replace('[name]', key)); 25 | }else{ 26 | const ext = path.extname(key); 27 | const base = path.basename(key, ext); 28 | const filename = path.relative(process.cwd(), key); 29 | outPath = path.join(config.output.path,path.dirname(filename),`${base}.js`); 30 | }; 31 | const outDir = path.dirname(outPath); 32 | fs.ensureDirSync(outDir); // 确保目录存在 33 | bundle[key].code = typeof pack === 'function' ? pack(bundle[key].code) : bundle[key].code; 34 | if (bundle[key].map && config.options.sourceMaps) { // 有输出 source map 则同样保存 35 | bundle[key].code += `\n//# sourceMappingURL=${path.basename(outPath)}.map`; 36 | fs.writeFileSync(`${outPath}.map`, bundle[key].map); 37 | }; 38 | fs.writeFileSync(outPath,bundle[key].code); 39 | }; 40 | }; 41 | 42 | // 测试代码 43 | // const config = { 44 | // entry: { 45 | // web: '/Users/fan/Desktop/build_minigame/PuertsDLLMock/output/index.js' 46 | // }, 47 | // output: { 48 | // path: '/Users/fan/Desktop/build_minigame/.temp', 49 | // name: '[name].js' 50 | // } 51 | // }; 52 | // spack(config).then(v => { 53 | // console.log(v); 54 | // }).catch(e => { 55 | // console.log(e); 56 | // }); 57 | 58 | export default spack; -------------------------------------------------------------------------------- /lib/getPuerInfo.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | /** 4 | * 最新版本 https://api.github.com/repos/Tencent/puerts/releases/latest 5 | * 所有版本 https://api.github.com/repos/Tencent/puerts/releases 6 | */ 7 | export const githubInfo = async () => { 8 | const result = {}; 9 | const list = (await axios('https://api.github.com/repos/Tencent/puerts/releases')).data; 10 | for (let i = 0, len = list.length; i < len; i++) { 11 | const item = list[i]; 12 | const itemNameStr = item.name.toLocaleLowerCase(); 13 | const itemName = (() => { 14 | const result = itemNameStr.match(/unity|unreal/); 15 | return result !== null ? result[0] : undefined; 16 | })(); 17 | const itemVer = (() => { 18 | const result = itemNameStr.match(/(\d+\.)+\d+/); 19 | return result !== null ? result[0] : undefined; 20 | })(); 21 | 22 | if (itemName === 'unity' || itemName === 'unreal') { 23 | result[itemName] = result[itemName] || {}; 24 | result[itemName][itemVer] = (() => { 25 | const result = {}; 26 | item.assets.forEach(item => { 27 | const runtime = (() => { 28 | const result = item.name.match(/nodejs|quickjs|v8/i); 29 | return result !== null ? result[0].toLocaleLowerCase() : "v8"; 30 | })(); 31 | if (runtime) { 32 | result[runtime] = item.browser_download_url; 33 | }; 34 | }); 35 | return result; 36 | })(); 37 | }; 38 | }; 39 | return result; 40 | }; 41 | 42 | 43 | /** 44 | * https://package.openupm.cn/包名 45 | * https://package.openupm.cn/-/v1/search?text=关键字 46 | */ 47 | export const upmInfo = async () => { 48 | const result = { unity: {}, unreal: {} }; 49 | const versions = (await axios('http://package.openupm.cn/com.tencent.puerts.core'))?.data?.versions; 50 | if (versions) { 51 | for (const key in versions) { 52 | const item = versions[key]; 53 | const itemVer = (() => { 54 | const result = key.match(/(\d+\.)+\d+/); 55 | return result !== null ? result[0] : undefined; 56 | })(); 57 | result.unity[itemVer] = { 58 | v8: item?.dist.tarball 59 | }; 60 | }; 61 | }; 62 | return result; 63 | }; 64 | -------------------------------------------------------------------------------- /Commands/init/Unity/TS/src/Core/Ticker.ts: -------------------------------------------------------------------------------- 1 | import { requestAnimationFrame,cancelAnimationFrame } from './requestAnimationFrame'; 2 | 3 | namespace Game { 4 | /** 5 | * 轮循环执行器 6 | */ 7 | export class Ticker { 8 | private time:NodeJS.Timeout|number; 9 | private _maxFps:number; 10 | private maxIntervalTime:number; 11 | private isPlay = true; 12 | private static tempTasks:Function[]; 13 | public static tasks:Function[] = []; 14 | constructor(maxFps:number){ 15 | this.maxFps = maxFps; 16 | } 17 | get maxFps(){ 18 | return this._maxFps; 19 | } 20 | set maxFps(val:number){ 21 | this._maxFps = val; 22 | this.maxIntervalTime = 1000 / val; 23 | } 24 | start(){ 25 | this.isPlay = true; 26 | this.time = requestAnimationFrame(this.loop.bind(this),this.maxIntervalTime); 27 | } 28 | stop(){ 29 | this.isPlay = false; 30 | cancelAnimationFrame(this.time); 31 | } 32 | loop(deltaTime:number,performance:number){ 33 | if(!this.isPlay){ 34 | return; 35 | }; 36 | if(deltaTime){ 37 | for(let i=0,len=Ticker.tasks.length; i -1){ // 将新的执行任务临时存放起来,防止当前任务未结束前修改任务列表导致的其它问题 56 | Ticker.tempTasks = [ 57 | ...Ticker.tasks.slice(0,index), 58 | ...Ticker.tasks.slice(index+1) 59 | ]; 60 | }; 61 | } 62 | } 63 | } 64 | 65 | export default Game.Ticker; -------------------------------------------------------------------------------- /lib/createServer.js: -------------------------------------------------------------------------------- 1 | import http from 'node:http'; 2 | import fs from 'node:fs'; 3 | import path from 'node:path'; 4 | import createLang from './createLang.js'; 5 | import line from './getLine.js'; 6 | 7 | const lang = createLang({ 8 | browse:{ 9 | cn:'服务启动成功', 10 | en:'Service started successfully' 11 | } 12 | }); 13 | 14 | const createServer = (dir, port) => { 15 | const server = http.createServer((request, response)=>{ 16 | const filePath = `${dir}${request.url === "/" ? request.url+'index.html' : request.url}`; 17 | // console.log("路径",filePath); 18 | const extname = String(path.extname(filePath)).toLowerCase(); 19 | const mimeTypes = { 20 | '.html': 'text/html', 21 | '.js': 'text/javascript', 22 | '.css': 'text/css', 23 | '.json': 'application/json', 24 | '.png': 'image/png', 25 | '.jpg': 'image/jpg', 26 | '.gif': 'image/gif', 27 | '.svg': 'image/svg+xml', 28 | '.wav': 'audio/wav', 29 | '.mp4': 'video/mp4', 30 | '.woff': 'application/font-woff', 31 | '.ttf': 'application/font-ttf', 32 | '.eot': 'application/vnd.ms-fontobject', 33 | '.otf': 'application/font-otf', 34 | '.wasm': 'application/wasm', 35 | }; 36 | const contentType = mimeTypes[extname] || 'application/octet-stream'; 37 | fs.readFile(filePath, (error, content)=>{ 38 | if (error) { 39 | if (error.code == 'ENOENT') { 40 | fs.readFile('./404.html', (error, content)=>{ 41 | response.writeHead(404, { 42 | 'Content-Type': 'text/html' 43 | }); 44 | response.end(content, 'utf-8'); 45 | }); 46 | } else { 47 | response.writeHead(500); 48 | response.end( 49 | 'Sorry, check with the site admin for error: ' + error.code + ' ..\n', 50 | ); 51 | response.end(); 52 | } 53 | } else { 54 | response.writeHead(200, { 55 | 'Content-Type': contentType 56 | }); 57 | response.end(content, 'utf-8'); 58 | } 59 | }); 60 | }); 61 | 62 | server.listen(port, ()=>{ 63 | console.log(line); 64 | console.log(`${lang('browse')} http://localhost:${port}`); 65 | }); 66 | } 67 | 68 | export default createServer; -------------------------------------------------------------------------------- /Commands/init/Unity/Assets/Resources/Materials/PuertsMaterial.mat: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!21 &2100000 4 | Material: 5 | serializedVersion: 8 6 | m_ObjectHideFlags: 0 7 | m_CorrespondingSourceObject: {fileID: 0} 8 | m_PrefabInstance: {fileID: 0} 9 | m_PrefabAsset: {fileID: 0} 10 | m_Name: PuertsMaterial 11 | m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} 12 | m_ValidKeywords: 13 | - _METALLICGLOSSMAP 14 | m_InvalidKeywords: [] 15 | m_LightmapFlags: 4 16 | m_EnableInstancingVariants: 0 17 | m_DoubleSidedGI: 0 18 | m_CustomRenderQueue: -1 19 | stringTagMap: {} 20 | disabledShaderPasses: [] 21 | m_SavedProperties: 22 | serializedVersion: 3 23 | m_TexEnvs: 24 | - _BumpMap: 25 | m_Texture: {fileID: 0} 26 | m_Scale: {x: 1, y: 1} 27 | m_Offset: {x: 0, y: 0} 28 | - _DetailAlbedoMap: 29 | m_Texture: {fileID: 0} 30 | m_Scale: {x: -1.74, y: 1} 31 | m_Offset: {x: 0, y: 0} 32 | - _DetailMask: 33 | m_Texture: {fileID: 0} 34 | m_Scale: {x: 1, y: 1} 35 | m_Offset: {x: 0, y: 0} 36 | - _DetailNormalMap: 37 | m_Texture: {fileID: 0} 38 | m_Scale: {x: 1, y: 1} 39 | m_Offset: {x: 0, y: 0} 40 | - _EmissionMap: 41 | m_Texture: {fileID: 0} 42 | m_Scale: {x: 1, y: 1} 43 | m_Offset: {x: 0, y: 0} 44 | - _MainTex: 45 | m_Texture: {fileID: 2800000, guid: 64faf8df58e474645b6b6533924b3946, type: 3} 46 | m_Scale: {x: 1, y: 1} 47 | m_Offset: {x: 0, y: 0} 48 | - _MetallicGlossMap: 49 | m_Texture: {fileID: 2800000, guid: 64faf8df58e474645b6b6533924b3946, type: 3} 50 | m_Scale: {x: 1, y: 1} 51 | m_Offset: {x: 0, y: 0} 52 | - _OcclusionMap: 53 | m_Texture: {fileID: 0} 54 | m_Scale: {x: 1, y: 1} 55 | m_Offset: {x: 0, y: 0} 56 | - _ParallaxMap: 57 | m_Texture: {fileID: 0} 58 | m_Scale: {x: 1, y: 1} 59 | m_Offset: {x: 0, y: 0} 60 | m_Ints: [] 61 | m_Floats: 62 | - _BumpScale: 1 63 | - _Cutoff: 0.5 64 | - _DetailNormalMapScale: 1 65 | - _DstBlend: 0 66 | - _GlossMapScale: 1 67 | - _Glossiness: 0.5 68 | - _GlossyReflections: 1 69 | - _Metallic: 0 70 | - _Mode: 0 71 | - _OcclusionStrength: 0.961 72 | - _Parallax: 0.02 73 | - _SmoothnessTextureChannel: 0 74 | - _SpecularHighlights: 1 75 | - _SrcBlend: 1 76 | - _UVSec: 0 77 | - _ZWrite: 1 78 | m_Colors: 79 | - _Color: {r: 1, g: 1, b: 1, a: 1} 80 | - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} 81 | m_BuildTextureStacks: [] 82 | -------------------------------------------------------------------------------- /lib/findPath.js: -------------------------------------------------------------------------------- 1 | import getPathInfo from "./getPathInfo.js"; 2 | import fs from 'fs-extra'; 3 | import typeof2 from '../base/typeof2.js'; 4 | import path from 'path'; 5 | const cloneRegExp = regExp => new RegExp(regExp.source,regExp.flags); 6 | /** 7 | * 8 | * @param {string} srcPath 输入目录 9 | * @param {array} matchArrs 需要查找的文件规则(可多个) 10 | * @param {string} type dir或file,返回文件路径还是目录路径 11 | * @param {boolean} isAll 是否返回全部匹配到的结果 12 | * @param {regExp} ignore 忽略规则 13 | * @return {string} 文件或目录路径 14 | */ 15 | const findPath = (srcPath,matchArrs,type,isAll,ignore)=>{ 16 | isAll = isAll || false; 17 | let result = isAll ? [] : undefined, 18 | eachDir; 19 | type = type || 'dir'; 20 | 21 | // 遍历目录,检查到 22 | (eachDir = (dir) => { 23 | let dirPathInfo = getPathInfo(dir), 24 | temp = false, 25 | dirName = path.basename(dir); 26 | if(dirPathInfo.type === 'dir' && !(ignore && ignore.test(dirName))){ 27 | temp = false; 28 | let dirItems = fs.readdirSync(dir); 29 | for(let i=0,len=dirItems.length; i 之前插入以下代码:', 14 | en:'Please use the applet to compile the result and insert the following code before %s :' 15 | }, 16 | jsTips:{ 17 | cn:'请使用小程序编译结果后在 %s 中插入以下代码:', 18 | en:'Please use the applet to compile the result and insert the following code in %s:' 19 | } 20 | }) 21 | 22 | /** 23 | * 在小程序输出结果中引入编译结果 24 | * @param {string} outDir 小程序工具结果输出目录 25 | */ 26 | const introduceRes = (outDir)=>{ 27 | const htmlPath = path.join(outDir,'webgl','index.html'); 28 | const htmlPathInfo = getPathInfo(htmlPath); 29 | const htmlCode = ``; 30 | if(htmlPathInfo.type === 'file'){ 31 | let htmlStr = fs.readFileSync(htmlPath,'utf-8'); 32 | if(htmlStr.indexOf('puerts-runtime.js') < 0){ 33 | htmlStr = htmlStr.replace(``,` ${htmlCode}\n`); 34 | fs.writeFileSync(htmlPath,htmlStr); 35 | }; 36 | }else{ 37 | console.log(line); 38 | console.log(lang('notExist'),htmlPath); 39 | console.log(lang('htmlTips'),htmlPath); 40 | console.log(htmlCode); 41 | }; 42 | 43 | const gameJsPath = path.join(outDir,'minigame','game.js'); 44 | const gameJsPathInfo = getPathInfo(gameJsPath); 45 | const jsCode = `import "./puerts-runtime";`; 46 | if(gameJsPathInfo.type === 'file'){ 47 | let gameJsStr = fs.readFileSync(gameJsPath,'utf-8'); 48 | if(gameJsStr.indexOf('./puerts-runtime') < 0){ 49 | gameJsStr = (()=>{ 50 | let arr = gameJsStr.split(/\n/); 51 | a:for(let i=0,len=arr.length; i(); // 处理事件要用到(有多少类型添加多少个) 43 | vm.UsingAction(); // 处理事件要用到(有多少类型添加多少个) 44 | vm.UsingAction(); // 处理事件要用到(有多少类型添加多少个) 45 | }; 46 | if(debug){ // 启用调试 47 | vm.WaitDebugger(); 48 | }; 49 | if(entry != null){ 50 | vm.ExecuteModule(entry); 51 | }; 52 | } 53 | void Start(){ 54 | 55 | } 56 | void Update(){ 57 | if(vm != null){ 58 | vm.Tick(); 59 | }; 60 | } 61 | void OnDestroy(){ 62 | if(vm != null){ 63 | vm.Dispose(); 64 | }; 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /Commands/init/Unity/Assets/CS/JSLoader.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using Puerts; 6 | 7 | namespace Game { 8 | public class JSLoader : ILoader, IModuleChecker { 9 | private string root; 10 | 11 | /// 12 | /// 构造方法 13 | /// 14 | /// Js 脚本存放目录 15 | public JSLoader(string root){ 16 | this.root = root; 17 | } 18 | 19 | /// 20 | /// * 接口要求实现 21 | /// 判断文件是否存在 22 | /// 23 | /// 文件路径 24 | /// true/false 25 | public bool FileExists(string filePath){ 26 | // Puerts 需要调用到其目录下的一些 js 文件,这里通通判为存在 27 | if (IsPuertsModule(filePath)) return true; 28 | 29 | #if UNITY_EDITOR 30 | return File.Exists(PathUnified(root, filePath)); 31 | #else 32 | return true; 33 | #endif 34 | } 35 | 36 | /// 37 | /// 定义哪些文件是 Esmodules 38 | /// 39 | /// 文件路径 40 | /// true/false 41 | public bool IsESM(string filepath){ 42 | return filepath.Length < 4 || !filepath.EndsWith(".mjs") || !filepath.EndsWith(".js"); 43 | } 44 | 45 | /// 46 | /// * 接口要求实现 47 | /// 文件内容读取 48 | /// 49 | /// 模块路径 50 | /// 文件完整路径 51 | /// 文本内容 52 | public string ReadFile(string filePath,out string debugPath){ 53 | bool isPuerts = IsPuertsModule(filePath); 54 | debugPath = isPuerts ? GetPuertsModulePath(filePath) : PathUnified(root, filePath); 55 | return File.ReadAllText(debugPath); 56 | } 57 | 58 | /// 59 | /// 获取 Puerts 自带模块路径 60 | /// 61 | /// Puerts 自带模块名称 62 | /// 模块完整路径 63 | private string GetPuertsModulePath(string filePath) { 64 | return PathUnified(Application.dataPath,"Puerts/Runtime/Resources",filePath); 65 | } 66 | 67 | /// 68 | /// 判断模块是否为 Puerts自带模块 69 | /// 70 | /// 模块名称 71 | /// true/false 72 | private bool IsPuertsModule(string filePath){ 73 | return filePath.StartsWith("puerts/"); 74 | } 75 | 76 | /// 77 | /// 纠正路径(Windows下路径斜杠不正确的问题) 78 | /// 79 | /// 80 | /// 纠正之后的路径 81 | private string PathUnified(params string[] args){ 82 | return Path.Combine(args).Replace("\\","/"); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import fs from 'node:fs'; 3 | import path, { dirname } from 'node:path'; 4 | import { fileURLToPath } from 'node:url'; 5 | import formatParam from './formatParam.js'; 6 | import getAllCommandHelp from './getAllCommandHelp.js'; 7 | import getWorkDir from './getWorkDir.js'; 8 | import getConfig from './getConfig.js'; 9 | import createLang from '../lib/createLang.js'; 10 | import getPathInfo from '../lib/getPathInfo.js'; 11 | import adaptPath from '../lib/adaptPath.js'; 12 | 13 | const lang = createLang({ 14 | version:{ 15 | cn:'版本', 16 | en:'Version' 17 | }, 18 | unknownCommand:{ 19 | cn:'未知的命令', 20 | en:'unknown command' 21 | }, 22 | notFunction:{ 23 | cn:'入口必须向外导出一个函数', 24 | en:'The entry must export a function' 25 | } 26 | }); 27 | const __dirname = dirname(fileURLToPath(import.meta.url)); 28 | (async()=>{ 29 | const cwd = process.cwd(); 30 | const command = process.argv[2]; 31 | const packagePath = path.join(__dirname,'..','package.json'); 32 | const packageObj = JSON.parse(fs.readFileSync(packagePath,'utf-8')); 33 | if(command === undefined || /^(-h|--help|-v|--version)$/i.test(command)){ // 未输入命令则返回工具所有帮助信息 34 | console.log(`${lang('version')} ${packageObj.version}\n`); 35 | console.log(await getAllCommandHelp()); 36 | return; 37 | }; 38 | 39 | const param = await formatParam(command,process.argv.slice(3)); // 格式化参数对象 40 | if(typeof param === 'string'){ // 返回字符串则说明参数有误,直接输入帮助信息 41 | console.log(param); 42 | return; 43 | }; 44 | if(param.help){ // 输出命令帮助 45 | console.log(param.help); 46 | return; 47 | }; 48 | 49 | const workObj = getWorkDir(cwd); // 获取当前项目根目录 50 | if(workObj.msg){ // 如果有返回消息,说明有报错,直接输出错误信息 51 | console.log(workObj.msg); 52 | return; 53 | }; 54 | 55 | const config = await getConfig(workObj.path); // 如果获取项目配置返回的是提示,则打印出提示且不再向下执行 56 | const argObj = {param,config,workObj,cwd}; 57 | 58 | const entryPath = path.join(__dirname,'..','Commands',command,'index.js'); // 拿到每个命令入口文件路径 59 | const entryPathInfo = getPathInfo(entryPath); 60 | 61 | if(entryPathInfo.type !== 'file'){ 62 | return; 63 | }; 64 | 65 | if(config.msg && command !== 'init'){ // 项目未初始化,且不是初始化指令则不允许向下执行 66 | console.log(config.msg); 67 | return; 68 | }; 69 | 70 | const entry = (await import(adaptPath(entryPath))).default; // 得到入口方法 71 | if(typeof entry !== 'function'){ 72 | console.log(lang('notFunction')); 73 | return; 74 | }; 75 | entry(argObj); 76 | })() -------------------------------------------------------------------------------- /lib/getAllJsPath.js: -------------------------------------------------------------------------------- 1 | import path,{ dirname } from 'node:path'; 2 | import { fileURLToPath } from 'node:url'; 3 | import getPathInfo from "./getPathInfo.js"; 4 | import getFiles from "./getFilesPath.js"; 5 | import findPath from "./findPath.js"; 6 | 7 | const __dirname = dirname(fileURLToPath(import.meta.url)); 8 | const sep = path.sep; 9 | const ignore = (item,itemPath)=>{ 10 | if(item === 'node_modules'){ // 所有 node_modules 可以过滤掉 11 | return true; 12 | }; 13 | if(item.slice(0,1) === '.'){ // 所有以 . 开始的文件过滤掉 14 | return true; 15 | }; 16 | const {type,extname} = getPathInfo(itemPath); 17 | if(type === 'file' && !/^(mjs|cjs|js)$/i.test(extname)){ // 是文件类型,但不是 js 文件的过滤掉 18 | return true; 19 | }; 20 | if(type === 'file' && itemPath.indexOf(`${sep}Resources${sep}`) < 0){ // 不在 Resources 的目录也过滤掉 21 | return true; 22 | }; 23 | }; 24 | 25 | /** 26 | * 获 Unity 取项目下所有要用到的 js 文件 27 | * @param {string} unityProjectDir unity 项目目录路径 28 | */ 29 | const getAllJsPath = (unityProjectDir)=>{ 30 | const result = {}; 31 | const assetsDir = path.join(unityProjectDir,'Assets'); 32 | const assetsDirInfo = getPathInfo(assetsDir); 33 | const packagesDir = path.join(unityProjectDir,'Packages'); 34 | const packagesDirInfo = getPathInfo(packagesDir); 35 | if(assetsDirInfo.type !== 'dir' || packagesDirInfo.type !== 'dir'){ 36 | throw new Error(`${unityProjectDir} 不是 unity 工程目录`); 37 | }; 38 | const puertsDir = (()=>{ // 获取到项目目录下的 puerts 所在路径 39 | const match1 = (item,itemPath) => { 40 | if( 41 | item === 'GenericDelegate.cs' && // 根据 'GenericDelegate.cs' 文件来定位路径 42 | path.basename(path.dirname(itemPath)) === 'JSType' 43 | ){ 44 | return true; 45 | }; 46 | }; 47 | const find = findPath(unityProjectDir,[match1],'dir',false,/(^(node_modules)$)|(^\.)/i); 48 | if(find){ 49 | return path.join(find,'..','..','..'); 50 | }else{ 51 | throw new Error('未在您的项目目录中发现 PuertsTS 目录'); 52 | }; 53 | })(); 54 | 55 | if(puertsDir){ // puerts 目录下所有 js 文件 56 | result[puertsDir] = getFiles(puertsDir,null,ignore); 57 | }; 58 | 59 | const resourcesDir = path.join(assetsDir,'Resources'); // Resources 目录下所有 js 文件 60 | result[resourcesDir] = getFiles(resourcesDir,null,ignore); 61 | 62 | const dllMockDir = path.join(__dirname,'..','PuertsDLLMock'); // DllMock 目录下所d有 js 文件 (WebGLPostProcessor.cs 中逻辑,看着好像没什么用) 63 | result[dllMockDir] = getFiles(dllMockDir,null,ignore); 64 | 65 | return result; 66 | } 67 | 68 | // console.log("获得的结果",getAllJsPath(`/Users/fan/Desktop/unity`)); 69 | 70 | export default getAllJsPath; -------------------------------------------------------------------------------- /Commands/build/buildForApp.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'node:path'; 3 | import getFilesPath from '../../lib/getFilesPath.js'; 4 | import getPathInfo from '../../lib/getPathInfo.js'; 5 | import isTsAndJs from '../../lib/isTsAndJs.js'; 6 | import buildFile from '../../lib/buildFile.js'; 7 | import line from '../../lib/getLine.js'; 8 | import createLang from '../../lib/createLang.js'; 9 | 10 | const lang = createLang({ 11 | failed:{ 12 | cn:'编译失败:', 13 | en:'Compilation failed' 14 | }, 15 | tip:{ 16 | cn:'共 %d 个文件编译完成', 17 | en:'A total of %d files were compiled' 18 | } 19 | }); 20 | const ignore = (item,itemPath) => { // 忽略 node_modules 和所有 . 开头的目录 21 | if(item === 'node_modules'){ 22 | return true; 23 | }; 24 | if(item[0] === '.'){ 25 | return true; 26 | }; 27 | }; 28 | 29 | /** 30 | * 将项目文件编译为常规应用标准 31 | * @param {object} argObj 参数对象,包含用户所传入的参数、配置信息、工程信息、当前执行命令的路径等 32 | */ 33 | const buildForApp = (argObj)=>{ 34 | const {param,config,workObj,cwd} = argObj; 35 | const srcDir = config.tsProjectSrcDir; 36 | const outDir = config.tsOutputDir; 37 | if(param.clear.at(-1)){ // 清理输出目录 38 | fs.emptyDirSync(outDir); 39 | }; 40 | const packageLinkPath = path.join(outDir,'package.json'); 41 | const packageLinkPathInfo = getPathInfo(packageLinkPath); 42 | const nodeModulesPath = path.join(outDir,'node_modules'); 43 | const nodeModulesPathInfo = getPathInfo(nodeModulesPath); 44 | if(!packageLinkPathInfo.isExist){ // 保证软链存在 45 | fs.createSymlinkSync( 46 | path.join(config.tsProjectDir,'package.json'), 47 | packageLinkPath 48 | ); 49 | }; 50 | if(!nodeModulesPathInfo.isExist){ // 保证软链存在 51 | fs.createSymlinkSync( 52 | path.join(config.tsProjectDir,'node_modules'), 53 | nodeModulesPath 54 | ); 55 | }; 56 | const buildOptions = (()=>{ 57 | const result = {}; 58 | if(param && param.minify !== undefined){ 59 | result.minify = param.minify.at(-1); 60 | }; 61 | if(param && param.sourcemap !== undefined){ 62 | result.sourceMaps = param.sourcemap.at(-1); 63 | }; 64 | return result; 65 | })(); 66 | 67 | let count = 0; 68 | const files = getFilesPath(srcDir,null,ignore); 69 | for(let i=0,len=files.length; i{ 24 | const vmDebugPortsPath = findPath(workPath,[/^\.puertsVmDebugPorts\.txt$/],'file',false); 25 | if(vmDebugPortsPath === undefined){ 26 | return; 27 | }; 28 | const vmDebugPortsPathInfo = getPathInfo(vmDebugPortsPath); 29 | if(vmDebugPortsPathInfo.type === 'file'){ 30 | const vmDebugPortsArr = (()=>{ 31 | const result = []; 32 | const str = fs.readFileSync(vmDebugPortsPath,'utf-8'); 33 | str.split(',').forEach(item => { 34 | result.push(+item); 35 | }); 36 | return result; 37 | })(); 38 | return await createDebugers(vmDebugPortsArr); 39 | }; 40 | }; 41 | 42 | const dev = async(argObj)=>{ 43 | const {config,param,workObj} = argObj; 44 | let debugers = param.reload.at(-1) ? await getDebugers(workObj.path) : null; 45 | const option = {recursive:true}; 46 | const fun = (eventType, fileName)=>{ 47 | const filePath = path.join(config.tsProjectSrcDir,fileName), 48 | filePathInfo = getPathInfo(filePath); 49 | if(filePathInfo.type === 'file' && isTsAndJs(filePath)){ 50 | const outPath = filePath 51 | .replace(config.tsProjectSrcDir,config.tsOutputDir) 52 | .replace(/\.(jsx|js|ts)$/,'.js'); 53 | clearTimeout(timer[filePath]); 54 | timer[filePath] = setTimeout(async()=>{ 55 | try { 56 | buildFile(filePath,outPath); 57 | if(debugers === undefined || debugers.size === 0){ 58 | debugers = await getDebugers(workObj.path); 59 | }; 60 | if(debugers){ 61 | debugers.forEach(async(item)=>{ 62 | try { 63 | if(!item.client){ 64 | await item.init(); 65 | }; 66 | item.update(outPath); 67 | } catch (error) { 68 | console.log(error); 69 | }; 70 | }); 71 | }; 72 | } catch (error) { 73 | console.log(error); 74 | console.log(lang('failed'),filePath); 75 | }; 76 | },200); 77 | }; 78 | }; 79 | fs.watch(config.tsProjectSrcDir,option,fun); 80 | console.log(lang('tip')); 81 | console.log(line); 82 | }; 83 | 84 | export default dev; -------------------------------------------------------------------------------- /temp/make/help.js: -------------------------------------------------------------------------------- 1 | const platformArr = ["win", "osx", "linux", "android", "ios"]; // 支持的目标平台 2 | const archArr = ["auto","x64","ia32","armv7","arm64"]; // 支持的架构 3 | const defaultPlatform = (()=>{ 4 | switch (process.platform) { 5 | case 'win32': 6 | return platformArr[0]; 7 | break; 8 | case 'darwin': 9 | return platformArr[1]; 10 | break; 11 | case 'linux': 12 | return platformArr[2]; 13 | break; 14 | default: 15 | break; 16 | }; 17 | })() 18 | 19 | const config = { 20 | des:{ // 任务描述 21 | cn:'编译 Puerts 2.x+', 22 | en:'Compile Puerts 2.x+' 23 | }, 24 | params:{ 25 | '-p,--platform':{ 26 | des:{ 27 | cn:`平台须为 ${platformArr.join("、")},默认:${defaultPlatform}`, 28 | en:`Platform must be ${platformArr.join("、")}, default: ${defaultPlatform}` 29 | }, 30 | default: defaultPlatform, 31 | check: val => { // 检查参数,如果检查到错误要返回一个对象(包含:cn、en) 32 | if(platformArr.indexOf(val) < 0){ 33 | return { 34 | cn:`Platform 值必须为 ${platformArr.join("、")},默认:${defaultPlatform}`, 35 | en:`Platform value must be ${platformArr.join("、")}` 36 | } 37 | } 38 | }, 39 | format: val => { 40 | return (val+'').toLocaleLowerCase(); 41 | } 42 | }, 43 | '-a,--arch':{ 44 | des:{ 45 | cn:`目标架构,传入的值必须为 ${archArr.join("、")},默认:${archArr[0]}`, 46 | en:`The target architecture` 47 | }, 48 | default:archArr[0], 49 | check: val => { 50 | if(archArr.indexOf(val) < 0){ 51 | return { 52 | cn:`Arch 值必须为 ${archArr.join("、")},默认:${archArr[0]}`, 53 | en:`Arch value must be ${archArr.join("、")},default:${archArr[0]}` 54 | } 55 | } 56 | }, 57 | format: val => { 58 | return (val+'').toLocaleLowerCase(); 59 | }, 60 | }, 61 | '-r,--runtime':{ 62 | des:{ 63 | cn:'运行时,可选:v8、quickjs,默认:v8', 64 | en:'Runtime, optional: v8, quickjs, default: v8' 65 | }, 66 | default:'v8', 67 | check: val => { 68 | if(!(/^(v8|quickjs)$/.test(val))){ 69 | return { 70 | cn:`Runtime 值必须为 v8、quickjs,默认:v8`, 71 | en:`Runtime value must be v8, quickjs, default: v8` 72 | } 73 | } 74 | }, 75 | format: val => { 76 | return val.toLocaleLowerCase(); 77 | } 78 | }, 79 | '-h,--help':{ 80 | des:{ 81 | cn:'查看 make 帮助', 82 | en:'View make help' 83 | }, 84 | check: val => { 85 | return { 86 | cn:'查看帮助不需要参数', 87 | en:'View help does not require parameters' 88 | } 89 | } 90 | } 91 | } 92 | }; 93 | export default config; -------------------------------------------------------------------------------- /lib/createDebugers.js: -------------------------------------------------------------------------------- 1 | import CDP from 'chrome-remote-interface'; 2 | import fs from 'node:fs'; 3 | import crypto from 'node:crypto'; 4 | import createLang from './createLang.js'; 5 | import line from './getLine.js'; 6 | const createHash = str => crypto.createHash('md5').update(str).digest('hex'); 7 | const lang = createLang({ 8 | overload:{ 9 | cn:'重载模块:', 10 | en:'Overload module:' 11 | }, 12 | error:{ 13 | cn:'热重载服务启动失败,请确保游戏已启动。', 14 | en:'The hot reload service failed to start, please make sure the game is started.' 15 | } 16 | }) 17 | const State = { 18 | None:0, 19 | Connecting:1, 20 | Open:2, 21 | Close:3 22 | }; 23 | class HotReLoad { 24 | constructor(port){ 25 | this.client; 26 | this.host = '127.0.0.1'; 27 | this.port = port; 28 | this.debuger; 29 | this.state; 30 | this.pathMap = new Map(); 31 | this.idMap = new Map(); 32 | } 33 | async init(){ 34 | if(this.state === State.Connecting || this.state === State.Open){return;}; 35 | try { 36 | this.state = State.Connecting; 37 | this.client = await CDP({host:this.host,port:this.port,local:true}); 38 | this.client.on('disconnect',async()=>{ 39 | this.close(); 40 | }); 41 | const {Network,Page,Runtime,Debugger} = this.client; 42 | this.debuger = Debugger; 43 | Debugger.on('scriptParsed',(params)=>{ 44 | if (!params || !params.url || !params.scriptId){return;} 45 | const scriptId = params.scriptId; 46 | const filePath = params.url.replace(/\\/g,'/'); 47 | const filePathHash = createHash(filePath); 48 | this.idMap.set(scriptId,filePathHash); 49 | this.pathMap.set(filePathHash,scriptId); 50 | // console.log('写入文件信息',scriptId,filePathHash); 51 | }); 52 | Debugger.on('scriptFailedToParse',(params)=>{}); 53 | await Runtime.enable(); 54 | await Debugger.enable({maxScriptsCacheSize: 10000000}); 55 | this.state = State.Open; 56 | } catch (error) { 57 | // console.log(lang('error')); 58 | // console.log(line); 59 | this.close(); 60 | }; 61 | } 62 | close(){ 63 | if(this.client){ 64 | this.client.close(); 65 | }; 66 | this.client = null; 67 | this.debuger = null; 68 | this.state = State.Close; 69 | } 70 | async update(filePath){ 71 | filePath = filePath.replace(/\\/g,'/'); 72 | if(!this.debuger){return;} 73 | const scriptSource = fs.readFileSync(filePath,'utf-8'); 74 | const scriptId = this.pathMap.get(createHash(filePath)); 75 | if(scriptId === undefined){return;}; 76 | const isExist = await this.debuger.getScriptSource({scriptId}); 77 | if(!isExist || isExist.scriptSource === scriptSource){return;}; // 代码一样则不需要处理 78 | const res = await this.debuger.setScriptSource({scriptId,scriptSource}); // 重载 79 | console.log(`${lang('overload')}${filePath}`); 80 | } 81 | }; 82 | 83 | const createDebugs = async ports => { 84 | let result = new Map(); 85 | for(let i=0,len=ports.length; i{ 35 | const {param,config,workObj} = argObj; 36 | if(config.msg === undefined && param.force.at(-1) === false){ // msg 为 undefined 说明项目初始化过且未带强制执行参数的话则不允许向下执行 37 | console.log(lang('existed')); 38 | return; 39 | }; 40 | const {type} = workObj; 41 | const tplDir = path.join(__dirname,`${type.slice(0,1).toLocaleUpperCase()}${type.slice(1)}`); 42 | const tplFiles = findPath(tplDir,[/^[^\.]/],'file',true,/^\.(?!vscode\b)[^.]+|^node_modules$/); // 除了.vscode之外以.起始的目录、node_modules 都过滤掉 43 | 44 | for(let i=0,len=tplFiles.length; i{ 27 | const result = {}; 28 | if(param && param.minify !== undefined){ 29 | result.minify = param.minify.at(-1); 30 | }; 31 | if(param && param.sourcemap !== undefined){ 32 | result.sourceMaps = param.sourcemap.at(-1); 33 | }; 34 | return result; 35 | })(); 36 | await buildRunTime(srcDir,outputDir,buildOptions); 37 | 38 | const tsProjectSrcDir = config.tsProjectSrcDir; 39 | const tsOutputDir = config.tsOutputDir; 40 | fs.emptyDirSync(tsOutputDir); 41 | buildForApp(argObj); 42 | 43 | const puertsPath = getPuertsPath(srcDir); 44 | outputDir = path.join(outputDir,'puerts_minigame_js_resources'); // 微信小游戏中 require 会在该目录下寻找模块 45 | fs.emptyDirSync(outputDir); 46 | 47 | const resourcesPath = path.join(srcDir, 'Assets', 'Resources'); 48 | const jsList = (()=>{ 49 | const puertsFiles = getFilesPath(puertsPath, null, ignore); // 获取 Puerts 下要打包的文件 50 | const resourcesFiles = getFilesPath(config.tsOutputDir, null, ignore); 51 | return [...puertsFiles,...resourcesFiles]; 52 | })(); 53 | const jsFiles = (()=>{ 54 | const result = {}; 55 | for (let i = 0, len = jsList.length; i < len; i++) { 56 | const item = jsList[i]; 57 | const start = (()=>{ 58 | const index = item.indexOf(startSymbol); 59 | return index > -1 ? index : 0; 60 | })(); 61 | const itemRelativePath = (()=>{ // 得到相对路径 62 | let result; 63 | if(item.indexOf(config.tsOutputDir) > -1){ // 处理项目本身的JS路径 64 | result = item.replace(config.tsOutputDir,''); 65 | }else{ 66 | result = `${item.slice(start + startSymbol.length).split(sep).join('/')}`; // 得到puerts本身的路径 67 | }; 68 | result = result.replace(/\.(mjs|cjs|es6|es)$/i, '.js'); // 处理JS相关扩展名 69 | while(result[0] === path.sep){ 70 | result = result.slice(1) 71 | }; 72 | return result; 73 | })(); 74 | const re = new RegExp(`sourceMappingURL=${path.basename(item)}.map`,'g'); 75 | const code = (()=>{ 76 | let str = fs.readFileSync(item,'utf-8'); 77 | return str.replace(re,`sourceMappingURL=${path.basename(itemRelativePath)}.map`) 78 | })(); 79 | result[itemRelativePath] = code; 80 | const itemMapPath = `${item}.map`; // 如果有 map 文件也一并加入到输出目录 81 | const itemMapPathInfo = getPathInfo(itemMapPath); 82 | if(itemMapPathInfo.type === 'file'){ 83 | result[`${itemRelativePath}.map`] = itemMapPath; 84 | }; 85 | }; 86 | return result; 87 | })(); 88 | 89 | for(let key in jsFiles){ 90 | const outPath = path.join(outputDir,key); 91 | const outDir = path.dirname(outPath); 92 | fs.ensureDirSync(outDir); 93 | fs.writeFileSync(outPath,jsFiles[key]); 94 | }; 95 | }; 96 | 97 | export default buildForMinigame; -------------------------------------------------------------------------------- /Commands/build/buildForBrowser.js: -------------------------------------------------------------------------------- 1 | import path, { sep } from 'node:path'; 2 | import fs from 'fs-extra'; 3 | import swc from '@swc/core'; 4 | import getPuertsPath from '../../lib/getPuertsPath.js'; 5 | import getFilesPath from '../../lib/getFilesPath.js'; 6 | import isTsAndJs from '../../lib/isTsAndJs.js'; 7 | import buildRunTime from './buildRunTime.js'; 8 | import ignore from './ignore.js'; 9 | import startSymbol from './startSymbol.js'; 10 | import swcConfig from './swcConfig.js'; 11 | import getPathInfo from '../../lib/getPathInfo.js'; 12 | 13 | /** 14 | * 将项目文件、Puerts 运行时编译为浏览器支持 15 | * @param {object} argObj 参数对象,包含用户所传入的参数、配置信息、工程信息、当前执行命令的路径等 16 | */ 17 | const buildForBrowser = async function (argObj) { 18 | const {param,config,workObj} = argObj; 19 | const minigameOutputDir = config.minigameOutputDir; 20 | const srcDir = workObj.path; 21 | const outputDir = path.join(minigameOutputDir,'webgl'); 22 | const targetPath = path.join(outputDir, 'puerts_browser_js_resources.js'); 23 | if(param && param.clear && param.clear.at(-1)){ // 删除目标文件 24 | fs.removeSync(targetPath); 25 | fs.removeSync(path.join(outputDir,'puerts-runtime.js')); 26 | }; 27 | 28 | const buildOptions = (()=>{ 29 | const result = {}; 30 | if(param && param.minify !== undefined){ 31 | result.minify = param.minify.at(-1); 32 | }; 33 | if(param && param.sourcemap !== undefined){ 34 | result.sourceMaps = param.sourcemap.at(-1); 35 | }; 36 | return result; 37 | })(); 38 | await buildRunTime(srcDir,outputDir,buildOptions); 39 | 40 | const puertsPath = getPuertsPath(srcDir); 41 | const tsProjectSrcDir = config.tsProjectSrcDir; 42 | const transformConfig = Object.assign(swcConfig,buildOptions); 43 | const jsFiles = await (async() => { 44 | const result = {}; 45 | const puertsFiles = getFilesPath(puertsPath, null, ignore); // 获取 Puerts 下要打包的文件 46 | const resEntrys = (() => { // 得到项目 TS 要打包的文件入口 47 | const result = []; 48 | fs.readdirSync(tsProjectSrcDir).forEach(item => { 49 | let itemPath = path.join(tsProjectSrcDir, item); 50 | if (isTsAndJs(itemPath)) { 51 | result.push(itemPath); 52 | }; 53 | }); 54 | return result; 55 | })(); 56 | 57 | let start = 0; // 处理 puerts 打包逻辑 58 | for (let i = 0, len = puertsFiles.length; i < len; i++) { 59 | const item = puertsFiles[i]; 60 | start = (()=>{ 61 | const index = item.indexOf(startSymbol); 62 | return start === 0 && index > -1 ? index : start; 63 | })(); 64 | const itemPath = `${item.slice(start + startSymbol.length).split(sep).join('/')}`; 65 | const code = swc.transformSync(fs.readFileSync(item, 'utf-8'), transformConfig).code; 66 | result[itemPath] = `(function(exports, require, module, __filename, __dirname) {\n${code}\n})`; 67 | }; 68 | 69 | for (let i = 0, len = resEntrys.length; i < len; i++) { // 根据获取到的入口文件打包项目文件 70 | const item = resEntrys[i]; 71 | const itemOutPath = item.replace(tsProjectSrcDir,config.tsOutputDir) // 得到输出路径 72 | .replace(/\.(jsx|js|ts)$/, '.js'); 73 | 74 | const itemRelativePath = (()=>{ 75 | let result = itemOutPath.replace(`${config.tsOutputDir}`, ''); 76 | while(result[0] === path.sep){ 77 | result = result.slice(1) 78 | }; 79 | return result; 80 | })(); // 根据输出路径得到相对路径 81 | // console.log("相对路径",itemRelativePath,resourcesPath,itemOutPath) 82 | const bundleRes = await swc.bundle((() => { 83 | const config = { entry: {}, target:'node' }; 84 | config.entry[itemRelativePath] = item; 85 | return config 86 | })()); 87 | const code = swc.transformSync(bundleRes[itemRelativePath].code, transformConfig).code; // 由于导出的结果包含在一个对象里,而 es6 的 export 只能用在顶层,所以这里再将其转成 cmd 88 | result[itemRelativePath] = `(function(exports, require, module, __filename, __dirname) {\n${code}\n})` 89 | }; 90 | return result; 91 | })(); 92 | 93 | const resCode = (() => { 94 | let result = ''; 95 | for (let key in jsFiles) { 96 | result += `"${key}":${jsFiles[key]},\n`; 97 | }; 98 | return `window.PUERTS_JS_RESOURCES = {\n${result}\n}`; 99 | })(); 100 | fs.writeFileSync(targetPath, resCode); 101 | }; 102 | 103 | export default buildForBrowser; -------------------------------------------------------------------------------- /Commands/init/Unity/Assets/Scenes/App.unity: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!29 &1 4 | OcclusionCullingSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | m_OcclusionBakeSettings: 8 | smallestOccluder: 5 9 | smallestHole: 0.25 10 | backfaceThreshold: 100 11 | m_SceneGUID: 00000000000000000000000000000000 12 | m_OcclusionCullingData: {fileID: 0} 13 | --- !u!104 &2 14 | RenderSettings: 15 | m_ObjectHideFlags: 0 16 | serializedVersion: 9 17 | m_Fog: 0 18 | m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} 19 | m_FogMode: 3 20 | m_FogDensity: 0.01 21 | m_LinearFogStart: 0 22 | m_LinearFogEnd: 300 23 | m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} 24 | m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} 25 | m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} 26 | m_AmbientIntensity: 1 27 | m_AmbientMode: 0 28 | m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} 29 | m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} 30 | m_HaloStrength: 0.5 31 | m_FlareStrength: 1 32 | m_FlareFadeSpeed: 3 33 | m_HaloTexture: {fileID: 0} 34 | m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} 35 | m_DefaultReflectionMode: 0 36 | m_DefaultReflectionResolution: 128 37 | m_ReflectionBounces: 1 38 | m_ReflectionIntensity: 1 39 | m_CustomReflection: {fileID: 0} 40 | m_Sun: {fileID: 0} 41 | m_IndirectSpecularColor: {r: 0.37311915, g: 0.3807396, b: 0.35872662, a: 1} 42 | m_UseRadianceAmbientProbe: 0 43 | --- !u!157 &3 44 | LightmapSettings: 45 | m_ObjectHideFlags: 0 46 | serializedVersion: 12 47 | m_GIWorkflowMode: 1 48 | m_GISettings: 49 | serializedVersion: 2 50 | m_BounceScale: 1 51 | m_IndirectOutputScale: 1 52 | m_AlbedoBoost: 1 53 | m_EnvironmentLightingMode: 0 54 | m_EnableBakedLightmaps: 1 55 | m_EnableRealtimeLightmaps: 0 56 | m_LightmapEditorSettings: 57 | serializedVersion: 12 58 | m_Resolution: 2 59 | m_BakeResolution: 40 60 | m_AtlasSize: 1024 61 | m_AO: 0 62 | m_AOMaxDistance: 1 63 | m_CompAOExponent: 1 64 | m_CompAOExponentDirect: 0 65 | m_ExtractAmbientOcclusion: 0 66 | m_Padding: 2 67 | m_LightmapParameters: {fileID: 0} 68 | m_LightmapsBakeMode: 1 69 | m_TextureCompression: 1 70 | m_FinalGather: 0 71 | m_FinalGatherFiltering: 1 72 | m_FinalGatherRayCount: 256 73 | m_ReflectionCompression: 2 74 | m_MixedBakeMode: 2 75 | m_BakeBackend: 1 76 | m_PVRSampling: 1 77 | m_PVRDirectSampleCount: 32 78 | m_PVRSampleCount: 512 79 | m_PVRBounces: 2 80 | m_PVREnvironmentSampleCount: 256 81 | m_PVREnvironmentReferencePointCount: 2048 82 | m_PVRFilteringMode: 1 83 | m_PVRDenoiserTypeDirect: 1 84 | m_PVRDenoiserTypeIndirect: 1 85 | m_PVRDenoiserTypeAO: 1 86 | m_PVRFilterTypeDirect: 0 87 | m_PVRFilterTypeIndirect: 0 88 | m_PVRFilterTypeAO: 0 89 | m_PVREnvironmentMIS: 1 90 | m_PVRCulling: 1 91 | m_PVRFilteringGaussRadiusDirect: 1 92 | m_PVRFilteringGaussRadiusIndirect: 5 93 | m_PVRFilteringGaussRadiusAO: 2 94 | m_PVRFilteringAtrousPositionSigmaDirect: 0.5 95 | m_PVRFilteringAtrousPositionSigmaIndirect: 2 96 | m_PVRFilteringAtrousPositionSigmaAO: 1 97 | m_ExportTrainingData: 0 98 | m_TrainingDataDestination: TrainingData 99 | m_LightProbeSampleCountMultiplier: 4 100 | m_LightingDataAsset: {fileID: 112000000, guid: 40e2ddfa22ec64d279f01ea0df3c32d9, type: 2} 101 | m_LightingSettings: {fileID: 0} 102 | --- !u!196 &4 103 | NavMeshSettings: 104 | serializedVersion: 2 105 | m_ObjectHideFlags: 0 106 | m_BuildSettings: 107 | serializedVersion: 2 108 | agentTypeID: 0 109 | agentRadius: 0.5 110 | agentHeight: 2 111 | agentSlope: 45 112 | agentClimb: 0.4 113 | ledgeDropHeight: 0 114 | maxJumpAcrossDistance: 0 115 | minRegionArea: 2 116 | manualCellSize: 0 117 | cellSize: 0.16666667 118 | manualTileSize: 0 119 | tileSize: 256 120 | accuratePlacement: 0 121 | maxJobWorkers: 0 122 | preserveTilesOutsideBounds: 0 123 | debug: 124 | m_Flags: 0 125 | m_NavMeshData: {fileID: 0} 126 | --- !u!1 &418571544 127 | GameObject: 128 | m_ObjectHideFlags: 0 129 | m_CorrespondingSourceObject: {fileID: 0} 130 | m_PrefabInstance: {fileID: 0} 131 | m_PrefabAsset: {fileID: 0} 132 | serializedVersion: 6 133 | m_Component: 134 | - component: {fileID: 418571546} 135 | - component: {fileID: 418571547} 136 | m_Layer: 0 137 | m_Name: Main 138 | m_TagString: Untagged 139 | m_Icon: {fileID: 0} 140 | m_NavMeshLayer: 0 141 | m_StaticEditorFlags: 0 142 | m_IsActive: 1 143 | --- !u!4 &418571546 144 | Transform: 145 | m_ObjectHideFlags: 0 146 | m_CorrespondingSourceObject: {fileID: 0} 147 | m_PrefabInstance: {fileID: 0} 148 | m_PrefabAsset: {fileID: 0} 149 | m_GameObject: {fileID: 418571544} 150 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 151 | m_LocalPosition: {x: 0.06974554, y: 0.03994727, z: 0.83537877} 152 | m_LocalScale: {x: 1, y: 1, z: 1} 153 | m_ConstrainProportionsScale: 0 154 | m_Children: [] 155 | m_Father: {fileID: 0} 156 | m_RootOrder: 0 157 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 158 | --- !u!114 &418571547 159 | MonoBehaviour: 160 | m_ObjectHideFlags: 0 161 | m_CorrespondingSourceObject: {fileID: 0} 162 | m_PrefabInstance: {fileID: 0} 163 | m_PrefabAsset: {fileID: 0} 164 | m_GameObject: {fileID: 418571544} 165 | m_Enabled: 1 166 | m_EditorHideFlags: 0 167 | m_Script: {fileID: 11500000, guid: f1079cb4da0dd4540a8968ab625fc25b, type: 3} 168 | m_Name: 169 | m_EditorClassIdentifier: 170 | entry: App.js 171 | developerTools: DeveloperTools.js 172 | debugPort: 43990 173 | debug: 0 174 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Puerts Cli 2 | 3 | 开箱即用的 PuerTS 脚手架工具,通过本工具可以将 Puerts 快速接入并应用于项目中。 4 | 5 | **功能特点:** 6 | 7 | - 基于 SWC 构建的编译环境,快如闪电 8 | - 无需再手动搭建 Typescript、Webpack 相关编译及打包环境 9 | - 开箱即用的 Souremap、Hot reload、断点调试 等配置集成 10 | - 快速编译 Puerts WebGL 运行时,并能自动完成与Web或微信小游戏项目的对接 11 | - Web、微信小游戏工程拥有编译与预览一体的环境,无需自行再搭建预览环境 12 | 13 | > 现阶段仅支持 Unity 项目,支持 Puerts 2.X的版本正在火速开发中 14 | 15 | 16 | ## 安装 17 | 18 | 确保本地已经安装 [Node.js 16.15.0+](https://nodejs.org/en/) 19 | 20 | ```bash 21 | # 使用npm安装 22 | npm install @puerts/cli -g 23 | 24 | # 使用yarn安装 25 | yarn global add @puerts/cli 26 | 27 | # 输出版本号,检查是否安装成功 28 | puer -v 29 | ``` 30 | 31 | > 注意:以下所有 `puer` 命令都建议以管理员身份运行 32 | 33 | 34 | ## 帮助手册 35 | 36 | Puerts Cli 自带帮助手册,通过添加 `--help` 参数来查看每个命令及相关参数的帮助说明。 37 | 38 | ```bash 39 | # 查看当前可用的命令 40 | puer -h 41 | 42 | # 查看 build 命令使用参数 43 | puer build -h 44 | 45 | # ... 46 | ``` 47 | 48 | ## 新手教程 49 | 50 | - 使用 `Unity 2021+ LTS` 创建一个3D空项目 51 | - 在项目中添加 `Puerts` 52 | - [下载 Puerts](https://github.com/Tencent/puerts/tags)(建议下载 PuerTS_Nodejs_xxx.tgz 版本,因为开发阶段的 Sourcemap、Hotreload 要用到 Node 的环境) 53 | - 解压并将 `Puerts` 目录复制到项目的 `Assets/` 目录下,即:`Assets/Puerts/` 54 | - 注意:由于 `Puerts` 支持多系统多平台,需要手动指定好哪些文件适用于哪些平台,否则可能运行或编译出现异常,具体操作方法如下: 55 | - UnityEditor的资源管理器中找到: `Assets` -> `Puerts` -> `Plugins` -> `xx系统` -> `xx` 直到具体的文件,选中目录下所有文件 56 | - 然后在 `Inspector` 面板勾选对上的平台并点击 `Apply` 按钮(至少保证当前 Editor 环境有选择对,其它如果不知道则全都不选就好,之后等需要编译对应平台的时候再来这里设置) 57 | - 设置完之后,建议关掉 UnityEditor 重开一次 58 | - [下载 PuertsWebGL](https://github.com/zombieyang/puerts_unity_webgl_demo/tags) 并解压 59 | - 在解压目录内找到 `package` 目录并重命名为 `PuertsWebGL` 60 | - 将重命名好的 `PuertsWebGL` 目录复制到项目的 `Assets/` 目录下,即:`Assets/PuertsWebGL/` 61 | - 在项目中添加 `微信小游戏转换工具`(无需支持 Web、微信小游戏可以忽略) 62 | - [下载微信小游戏转换工具Unity插件](https://game.weixin.qq.com/cgi-bin/gamewxagwasmsplitwap/getunityplugininfo?download=1) 63 | - 双击 `minigame.xxx.unitypackage` 将插件导入到项目 64 | - 初始化 `Puerts` 项目 65 | - 终端进入到创建好的 `Unity项目` 目录中 66 | - 终端执行 `puer init` 初始化一个 `Puerts` 项目 67 | - 将项目色彩空间改为 `Gamma`(无需支持 Web、微信小游戏可以忽略) 68 | - UnityEditor菜单 -> `Editor` -> `Project Settings` -> `Player` -> `Other Settings` -> `Color Space` -> `Gamma` 69 | - 允许不安全代码(Blittables 需要用到) 70 | - UnityEditor菜单 -> `Editor` -> `Project Settings` -> `Player` -> `Other Settings` -> `Allow 'unsafe' Code` 71 | - 使用 `Puerts` 生成关联 `Code` 72 | - UnityEditor菜单 -> `PuerTS` -> `Clear Generated Code` 73 | - UnityEditor菜单 -> `PuerTS` -> `Generated Code` 74 | - UnityEditor菜单 -> `PuerTS` -> `Generated index.d.ts (global.CS style)` 75 | - 编译 `Puerts` 项目 76 | - 终端进入到创建好的 `Unity项目` 目录中 77 | - 终端执行 `puer build`,工具会将项目所有的 `TS` 文件编译为 `JS` 78 | - 预览项目 79 | - 使用 `Unity Editor` 打开项目 80 | - 双击 `Assets/Scenes/App.unity` 场景 81 | - 点击 `Play` 即可看到运行效果 82 | - 进入开发模式 83 | - 终端进入到创建好的 `Unity项目` 目录中 84 | - 终端执行 `puer dev`,此时工具会监听项目中的 `TS` 文件是否有修改,如有修改则会实时将其编译为 `JS` 文件 85 | - 默认会启动热重载支持,如果不需要可添加 `--reload false` 参数来关闭 86 | 87 | 88 | ## 编译为Web或微信小游戏工程 89 | 90 | - 导出 Web 或 微信小游戏工程 91 | - 确保 UnityEditor 有安装 Unity-WebGL-Support 92 | - 确保项目已经有安装 `PuertsWebGL`、`微信小游戏转换工具` 93 | - 切换编译平台为 WebGL,并确保添加需要导出的 Scene 94 | - UnityEditor 打开需要导出的场景 95 | - UnityEditor菜单 `File` -> `Build Settings` -> `WebGL` -> `Switch Platform` 96 | - UnityEditor菜单 `File` -> `Build Settings` -> `Add Open Scenes` 97 | - UnityEditor菜单 `微信小游戏` -> `转换小游戏` 在弹出的转换窗口中填写以下信息 98 | - 游戏appid,[在微信公众平台中申请](https://mp.weixin.qq.com/) 99 | - 小游戏项目名称,随便填写 100 | - 游戏资源CDN,填写 `http://localhost:10000`(这也是 cli 工具默认的 Web 项目预览地址,CDN资源也就是这里的资源) 101 | - 导出路径,这里建议与 `puer.config.js` 中的 `minigameOutputDir` 配置项目保持一致 102 | - `puer.config.js` 文件在执行 `puer init` 时会自动创建,文件默认位置:`项目/TS/puer.config.js` 103 | - `minigameOutputDir` 默认为 `桌面/项目目录名称/` 104 | - 点击 `导出WEBGL并转换为小游戏(常用按钮)` 导出项目 105 | - 如果导出遇到错误,请根据 UnityEditor Console 面板中的提示排除故障 106 | - 编译 puerts 运行时并对接 107 | - 终端 `cd Unity项目目录` 108 | - 终端 `puer build --target minigame --browse` 109 | - 脚手架会编译 `puerts运行时` 以及项目中的 `TS` 并自动接好相关入口 110 | - 不出意外终端会输出浏览器预览地址,直接访问即可预览效果 111 | - 如果 `puer.config.js` 中的 `minigameOutputDir` 配置项与 `微信小游戏转换工具` 导出目录不一致则需要根据提示自行手动接入 112 | 113 | > 建议:微信小程序、小游戏技术生态都是基于Web之上的,所以要先保证浏览器上可正常运行,之后再打开小游戏工程来预览调试 114 | 115 | ## 如何断点调试 116 | 117 | - 启动调试模式 118 | - UnityEditor 打开 `Assets/Screnes/App.unity` 场景 119 | - UnityEditor 在 `Hierarchy面板` -> 找到 `Main` 节点,勾选 `Inspector` 面板中的 `Debug` 选项 120 | - 播放场景(此时 UnityEditor 处于卡死等待状态) 121 | - Vscode 连接调试服务 122 | - Vscode 打开该 `Unity项目工程`,并在对应的代码中标记好断点位置 123 | - Vscode 点击左侧 `Side栏` -> `运行和调试` -> `启动调试(Puerts 字样前小三角图标)` 124 | 125 | > Vscode 需要安装 `Debugger for Unity` 扩展 126 | 127 | ## 参与开发 128 | 129 | 参与改进本工具或二次开发本请阅读 [Developer Guide](./DEVELOP.md)。 130 | 131 | 132 | ## 补充说明 133 | 134 | - 建议使用 `PuerTS_Nodejs` 运行时,因为可以直接使用 `Node` 现成的模块 135 | - 注意:引入 node_modules、node 自带模块,请使用 `require` 方法引入 136 | - PuerTS目前未实现完整 `import` 支持,`import` 目前只支持本地模块 137 | - 项目要兼容 `Web`、`微信小游戏`,使用 `Node` 模块需要自行处理好兼容 138 | - 着色器对 WebGL 做兼容支持 139 | - 不建议直接使用引擎自带资源,例如:`Shader.Find("Particles/Standard Unlit")` 140 | - 引入相对模块请以 `./`、`../` 开头,否则编译器可能无法找到模块 141 | 142 | 143 | ## 项目发起者 144 | 145 | Tencent [@zombieyang](https://github.com/zombieyang)、4399 Game [@sbfkcel](https://github.com/sbfkcel) 146 | 147 | 148 | ## 相关项目及资料 149 | 150 | - [Puerts](https://github.com/Tencent/puerts) 151 | - [puerts_unity_demo](https://github.com/chexiongsheng/puerts_unity_demo) 152 | - [puerts_unity_webgl_demo](https://github.com/zombieyang/puerts_unity_webgl_demo) 153 | - [minigame-unity-webgl-transform](https://github.com/wechat-miniprogram/minigame-unity-webgl-transform) 154 | - [微信小游戏开发者文档-快速上手](https://developers.weixin.qq.com/minigame/dev/guide/) 155 | - [Unity WebGL 微信小游戏适配](https://developers.weixin.qq.com/minigame/dev/guide/game-engine/unity-webgl-transform.html) 156 | -------------------------------------------------------------------------------- /Bin/formatParam.js: -------------------------------------------------------------------------------- 1 | import path,{ dirname } from 'node:path'; 2 | import { fileURLToPath } from 'node:url'; 3 | import helpToString from './helpToString.js'; 4 | import createLang from '../lib/createLang.js'; 5 | import getAllCommandHelp from './getAllCommandHelp.js'; 6 | import adaptPath from '../lib/adaptPath.js'; 7 | const __dirname = dirname(fileURLToPath(import.meta.url)); 8 | 9 | /** 10 | * 获取完整的key字符 11 | * @param {string} str 帮助 params key 12 | * @returns 13 | */ 14 | const getFullKey = str => { 15 | const arr = str.split(','); 16 | return arr.length > 1 ? arr[1].slice(2) : arr[0].slice(2); 17 | } 18 | 19 | /** 20 | * 获取与任务帮助参数相对应的 21 | * @param {object} params 任务参数帮助对象 22 | * @param {string} item 任务参数所对象的 key 23 | * @returns object 24 | * - key string 参数完整 key 25 | * - param object 与参数 key 所对应的 param 26 | */ 27 | const getParam = (params,item)=>{ 28 | for(let key in params){ 29 | const arr = key.split(','); 30 | if(arr.indexOf(item) > -1){ 31 | const param = params[key]; 32 | return {key:getFullKey(key),param}; 33 | }; 34 | }; 35 | return {}; 36 | } 37 | 38 | const lang = createLang({ 39 | invalidCommand:{ 40 | cn:'为无效的命令', 41 | en:'is an invalid command' 42 | }, 43 | invalidParam:{ 44 | cn:'为无效的命令参数', 45 | en:'is an invalid command parameter' 46 | } 47 | }) 48 | 49 | /** 50 | * 格式化终端参数 51 | * @param {string} params 任务名称 52 | * @param {array} params 终端数组 53 | * @returns object|string 如果返回字符串则说明参数传入有误 54 | */ 55 | const formatParam = async (command,params)=>{ 56 | const result = {}; 57 | const helpPath = path.join(__dirname,'..','Commands',command,'help.js'); 58 | let helpObj; 59 | try { 60 | helpObj = (await import(adaptPath(helpPath))).default 61 | } catch (error) { 62 | const allCommandHelp = await getAllCommandHelp(); 63 | return `'${command}' ${lang('invalidCommand')}\n\n${allCommandHelp}`; 64 | }; 65 | const helpString = helpToString(helpObj); 66 | let key,param,msg,checkRes; 67 | for(let i=0,len=params.length; i{ // 处理 key 方法 71 | let itemVal; 72 | item = (()=>{ // 参考到参数和值合并传入的类型,例如:--target=pc 73 | const index = item.indexOf('='); 74 | if(index > -1){ 75 | itemVal = item.slice(index+1); 76 | return item.slice(0,index); 77 | }; 78 | return item; 79 | })(); 80 | const paramObj = getParam(helpObj.params,item); 81 | param = paramObj.param; 82 | if( 83 | itemVal === undefined && // 说明值不是合并传入 84 | param && // 说明有传入参数 85 | typeof param.default === 'boolean' && // 说明默认缺省值有设置,且为 boolean 类型 86 | (params[i+1] === undefined || params[i+1].slice(0,1) === '-') // 说明下个一参数不是值类型 87 | ){ 88 | itemVal = true; 89 | }; 90 | key = paramObj.key; 91 | if(param === undefined){ // 参数错误,给出提示消息 92 | msg = `'${item}' ${lang('invalidParam')}\n\n`; 93 | msg += helpString; 94 | return msg; 95 | }; 96 | result[key] = key === 'help' ? helpString : result[key] || []; // 如果用户输入是帮助参数,则返回帮助信息即可 97 | if(itemVal !== undefined){ // 校验与参数合并传入的值 98 | return handleVal(itemVal); 99 | }; 100 | }; 101 | const handleVal = item => { // 处理值方法 102 | if(param === undefined){ // 没有参数则说明参选传入异常 103 | return `'${item}' ${lang('invalidParam')}\n\n${helpString}`; 104 | }; 105 | const val = typeof param.format === 'function' ? param.format(item) : item; 106 | const checkVal = typeof param.check === 'function' ? param.check(val) : null; 107 | if(checkVal){ 108 | const checkValMsg = createLang({msg:checkVal}); 109 | msg = `${checkValMsg('msg')}\n\n`; 110 | msg += helpString; 111 | return msg; 112 | }; 113 | if(result[key].push){ 114 | result[key].push(val); 115 | }; 116 | }; 117 | if(isKey){ // 校验 key 的合法性 118 | checkRes = handleKey(); 119 | if(checkRes){ 120 | return checkRes; 121 | }; 122 | }else{ // 校验值的合法性 123 | checkRes = handleVal(item); 124 | if(checkRes){ 125 | return checkRes; 126 | }; 127 | }; 128 | }; 129 | 130 | for(const key in helpObj.params){ // 遍历设置缺省默认值 131 | const param = helpObj.params[key]; 132 | const fullKey = getFullKey(key); 133 | if(( 134 | result[fullKey] === undefined || result[fullKey].length === 0) && 135 | param.default !== undefined 136 | ){ 137 | result[fullKey] = [param.default]; 138 | }; 139 | }; 140 | return result; 141 | } 142 | 143 | export default formatParam; -------------------------------------------------------------------------------- /Commands/build/help.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | des:{ // 任务描述 3 | cn:'编译当前项目 TS 为 JS ', 4 | en:'Compile current project TS to JS' 5 | }, 6 | params:{ 7 | '-t,--target':{ 8 | des:{ 9 | cn:`目标平台,可选值:pc、minigame,默认为:pc`, 10 | en:`Target platform, optional values: pc, minigame, default: pc` 11 | }, 12 | default: 'pc', 13 | check: val => { // 检查参数,如果检查到错误要返回一个对象(包含:cn、en) 14 | if(!/^(pc|minigame)$/i.test(val)){ 15 | return { 16 | cn:'目标平台只能是:pc、minigame', 17 | en:'The target platform can only be: pc, minigame' 18 | } 19 | }; 20 | }, 21 | format: val => { // 格式化参数 22 | return val.toLocaleLowerCase(); 23 | } 24 | }, 25 | '-c,--clear':{ 26 | des:{ 27 | cn:`编译之前是否清理目录,默认:false`, 28 | en:`Whether to clean up the directory before compiling, default: false` 29 | }, 30 | default: false, 31 | check: val => { // 检查参数,如果检查到错误要返回一个对象(包含:cn、en) 32 | if(typeof val !== 'boolean'){ 33 | return { 34 | cn:'--clear 传入的值必须为 true、flase', 35 | en:'The value passed in by --clear must be true or false' 36 | } 37 | }; 38 | }, 39 | format: val => { 40 | if(/^true|1$/i.test(val)){ 41 | return true; 42 | }; 43 | if(/^false|0$/i.test(val)){ 44 | return false; 45 | }; 46 | } 47 | }, 48 | '-s,--sourcemap':{ 49 | des:{ 50 | cn:`是否启用 Sourcemap,默认:true`, 51 | en:`Whether to enable Sourcemap, default: true` 52 | }, 53 | default: true, 54 | check: val => { // 检查参数,如果检查到错误要返回一个对象(包含:cn、en) 55 | if(typeof val !== 'boolean'){ 56 | return { 57 | cn:'--sourcemap 传入的值必须为 true、flase', 58 | en:'The value passed in by --sourcemap must be true or false' 59 | } 60 | }; 61 | }, 62 | format: val => { 63 | if(/^true|1$/i.test(val)){ 64 | return true; 65 | }; 66 | if(/^false|0$/i.test(val)){ 67 | return false; 68 | }; 69 | } 70 | }, 71 | '-m,--minify':{ 72 | des:{ 73 | cn:`是否启用 Minify,默认:false`, 74 | en:`Whether to enable Minify, default: false` 75 | }, 76 | default: false, 77 | check: val => { // 检查参数,如果检查到错误要返回一个对象(包含:cn、en) 78 | if(typeof val !== 'boolean'){ 79 | return { 80 | cn:'--minify 传入的值必须为 true、flase', 81 | en:'The value passed in by --minify must be true or false' 82 | } 83 | }; 84 | }, 85 | format: val => { 86 | if(/^true|1$/i.test(val)){ 87 | return true; 88 | }; 89 | if(/^false|0$/i.test(val)){ 90 | return false; 91 | }; 92 | } 93 | }, 94 | '-b,--browse':{ 95 | des:{ 96 | cn:`是否启用 http Server,默认:false`, 97 | en:`Whether to enable http Server, default: false` 98 | }, 99 | default: false, 100 | check: val => { // 检查参数,如果检查到错误要返回一个对象(包含:cn、en) 101 | if(typeof val !== 'boolean'){ 102 | return { 103 | cn:'--browse 传入的值必须为 true、flase', 104 | en:'The value passed in by --browse must be true or false' 105 | } 106 | }; 107 | }, 108 | format: val => { 109 | if(/^true|1$/i.test(val)){ 110 | return true; 111 | }; 112 | if(/^false|0$/i.test(val)){ 113 | return false; 114 | }; 115 | } 116 | }, 117 | '-p,--port':{ 118 | des:{ 119 | cn:`指定预览服务端口号,默认:10000`, 120 | en:`Specify the preview service port number, default: 10000` 121 | }, 122 | default: 10000, 123 | check: val => { // 检查参数,如果检查到错误要返回一个对象(包含:cn、en) 124 | if(typeof val !== 'number' || isNaN(val) || val < 1 || val > 65535){ 125 | return { 126 | cn:'--port 传入的值必须为一个可用的有效端口号,默认:10000', 127 | en:'--port The value passed in must be a valid port number that is available, default:10000' 128 | } 129 | }; 130 | }, 131 | format: val => { 132 | return +val; 133 | } 134 | }, 135 | '-h,--help':{ 136 | des:{ 137 | cn:'查看 build 帮助', 138 | en:'View build help' 139 | }, 140 | check: val => { 141 | return { 142 | cn:'查看帮助不需要参数', 143 | en:'View help does not require parameters' 144 | } 145 | } 146 | } 147 | } 148 | }; 149 | export default config; -------------------------------------------------------------------------------- /Commands/init/Unity/Assets/Editor/PuertsConfig.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// 官方说明 https://github.com/Tencent/puerts/blob/master/doc/unity/manual.md 3 | /// 4 | using System.Collections.Generic; 5 | using Puerts; 6 | using System; 7 | using UnityEngine; 8 | 9 | /// 10 | /// 配置类 11 | /// ! 配置类必须打 [Configure] 标签 12 | /// ! 必须放在Editor目录 13 | /// 14 | [Configure] 15 | public class PuertsConfig { 16 | 17 | /// 18 | /// 在 Js/Ts 调用时可以找到该类 19 | /// * 会生成一个静态类(wrap),在 Js 调用时将直接静态调用加快速度,否则通过反射调用 20 | /// * 会生成到 Assets/Gen/Typing/csharp/index.d.ts ,以在 Ts 中引用 21 | /// ! 须放在 [Configure] 标记过的类里 22 | /// 23 | /// 24 | [Binding] 25 | static IEnumerable Bindings { 26 | get { 27 | var types = new List(); 28 | var namespaces = new HashSet(); 29 | namespaces.Add("Game"); 30 | namespaces.Add("tiny"); 31 | namespaces.Add("tiny.utils"); 32 | Dictionary> ignored = new Dictionary>(); 33 | var ignored_classes = new HashSet(); 34 | 35 | // 忽略 tiny.EditorUtils 类 36 | ignored_classes = new HashSet(); 37 | ignored_classes.Add("EditorUtils"); 38 | ignored.Add("tiny", ignored_classes); 39 | 40 | // TODO:在此处添加要忽略绑定的类型 41 | Dictionary> registered = new Dictionary>(); 42 | foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { 43 | var name = assembly.GetName().Name; 44 | foreach (var type in assembly.GetTypes()) { 45 | if (!type.IsPublic) continue; 46 | if (type.Name.Contains("<") || type.Name.Contains("*")) continue; // 忽略泛型,指针类型 47 | if (type.Namespace == null || type.Name == null) continue; // 这是啥玩意? 48 | bool accept = namespaces.Contains(type.Namespace); 49 | if (accept && ignored.ContainsKey(type.Namespace) && ignored[type.Namespace].Contains(type.Name)) continue; 50 | if (accept) { 51 | types.Add(type); 52 | if (!registered.ContainsKey(type.Namespace)) { 53 | var classes = new HashSet(); 54 | classes.Add(type.Name); 55 | registered.Add(type.Namespace, classes); 56 | } else { 57 | registered[type.Namespace].Add((type.Name)); 58 | } 59 | } 60 | } 61 | } 62 | 63 | // 绑定 Unity常用类型 64 | types.Add(typeof(UnityEngine.Light)); 65 | types.Add(typeof(UnityEngine.Debug)); 66 | types.Add(typeof(UnityEngine.Vector2)); 67 | types.Add(typeof(UnityEngine.Vector3)); 68 | types.Add(typeof(UnityEngine.Vector4)); 69 | types.Add(typeof(UnityEngine.Quaternion)); 70 | types.Add(typeof(UnityEngine.RenderMode)); 71 | types.Add(typeof(UnityEngine.Canvas)); 72 | types.Add(typeof(UnityEngine.CanvasRenderer)); 73 | types.Add(typeof(UnityEngine.UI.Text)); 74 | types.Add(typeof(UnityEngine.UI.CanvasScaler)); 75 | types.Add(typeof(UnityEngine.UI.GraphicRaycaster)); 76 | 77 | types.Add(typeof(UnityEngine.UI.Slider)); 78 | types.Add(typeof(UnityEngine.UI.Slider.SliderEvent)); 79 | types.Add(typeof(UnityEngine.Events.UnityEvent)); 80 | types.Add(typeof(UnityEngine.EventSystems.EventSystem)); 81 | types.Add(typeof(UnityEngine.EventSystems.StandaloneInputModule)); 82 | 83 | types.Add(typeof(UnityEngine.MeshFilter)); 84 | types.Add(typeof(UnityEngine.Mesh)); 85 | types.Add(typeof(UnityEngine.Shader)); 86 | types.Add(typeof(UnityEngine.Renderer)); 87 | types.Add(typeof(UnityEngine.MeshRenderer)); 88 | types.Add(typeof(UnityEngine.Texture)); 89 | types.Add(typeof(UnityEngine.CameraClearFlags)); 90 | types.Add(typeof(UnityEngine.PrimitiveType)); 91 | types.Add(typeof(UnityEngine.Material)); 92 | types.Add(typeof(UnityEngine.ParticleSystemRenderer)); 93 | types.Add(typeof(UnityEngine.Resources)); 94 | types.Add(typeof(UnityEngine.Color)); 95 | types.Add(typeof(UnityEngine.Rect)); 96 | types.Add(typeof(UnityEngine.Bounds)); 97 | types.Add(typeof(UnityEngine.Ray)); 98 | types.Add(typeof(UnityEngine.RaycastHit)); 99 | types.Add(typeof(UnityEngine.Matrix4x4)); 100 | 101 | types.Add(typeof(UnityEngine.Time)); 102 | types.Add(typeof(UnityEngine.Transform)); 103 | types.Add(typeof(UnityEngine.RectTransform)); 104 | types.Add(typeof(UnityEngine.Object)); 105 | types.Add(typeof(UnityEngine.GameObject)); 106 | types.Add(typeof(UnityEngine.Component)); 107 | types.Add(typeof(UnityEngine.Behaviour)); 108 | types.Add(typeof(UnityEngine.MonoBehaviour)); 109 | types.Add(typeof(UnityEngine.AudioClip)); 110 | types.Add(typeof(UnityEngine.ParticleSystem.MainModule)); 111 | types.Add(typeof(UnityEngine.AnimationClip)); 112 | types.Add(typeof(UnityEngine.Animator)); 113 | types.Add(typeof(UnityEngine.AnimationCurve)); 114 | types.Add(typeof(UnityEngine.AndroidJNI)); 115 | types.Add(typeof(UnityEngine.AndroidJNIHelper)); 116 | types.Add(typeof(UnityEngine.Collider)); 117 | types.Add(typeof(UnityEngine.Collision)); 118 | types.Add(typeof(UnityEngine.Rigidbody)); 119 | types.Add(typeof(UnityEngine.Screen)); 120 | types.Add(typeof(UnityEngine.Texture)); 121 | types.Add(typeof(UnityEngine.TextAsset)); 122 | types.Add(typeof(UnityEngine.SystemInfo)); 123 | types.Add(typeof(UnityEngine.Input)); 124 | types.Add(typeof(UnityEngine.Mathf)); 125 | 126 | types.Add(typeof(UnityEngine.Camera)); 127 | types.Add(typeof(UnityEngine.Camera.RenderRequest)); 128 | types.Add(typeof(UnityEngine.ParticleSystem)); 129 | // types.Add(typeof(UnityEngine.AudioSource)); 130 | types.Add(typeof(UnityEngine.AudioListener)); 131 | types.Add(typeof(UnityEngine.Physics)); 132 | types.Add(typeof(UnityEngine.SceneManagement.Scene)); 133 | types.Add(typeof(UnityEngine.Networking.IMultipartFormSection)); 134 | types.Add(typeof(UnityEngine.Networking.UnityWebRequest)); 135 | return types; 136 | } 137 | } 138 | /// 139 | /// 对定义的 Blittable 值类型通过内存拷贝传递,可避免值类型传递产生的GC,需要开启unsafe编译选项 140 | /// ! 只能用在属性上 141 | /// ! 需要开启 unsafe 编译选项 142 | /// ! 须放在 [Configure] 标记过的类里 143 | /// 144 | /// 145 | [BlittableCopy] 146 | static IEnumerable Blittables { 147 | get { 148 | return new List() { 149 | typeof(Vector2), 150 | typeof(Vector3), 151 | typeof(Vector4), 152 | typeof(Quaternion), 153 | typeof(Color), 154 | typeof(Rect), 155 | typeof(Bounds), 156 | typeof(Ray), 157 | typeof(RaycastHit), 158 | typeof(Matrix4x4) 159 | }; 160 | } 161 | } 162 | 163 | /// 164 | /// 过滤函数 165 | /// ! 只能用在函数上 166 | /// ! 须放在 [Configure] 标记过的类里 167 | /// 168 | /// 169 | /// 170 | [Filter] 171 | static bool FilterMethods(System.Reflection.MemberInfo memberInfo){ 172 | string sig = memberInfo.ToString(); 173 | 174 | if (memberInfo.ReflectedType.FullName == "UnityEngine.MonoBehaviour" && memberInfo.Name == "runInEditMode") return true; 175 | if (memberInfo.ReflectedType.FullName == "UnityEngine.Input" && memberInfo.Name == "IsJoystickPreconfigured") return true; 176 | if (memberInfo.ReflectedType.FullName == "UnityEngine.Texture" && memberInfo.Name == "imageContentsHash") return true; 177 | if (memberInfo.ReflectedType.FullName == "UnityEngine.MeshRenderer" && memberInfo.Name == "stitchLightmapSeams") return true; 178 | if (memberInfo.ReflectedType.FullName == "UnityEngine.MeshRenderer" && memberInfo.Name == "receiveGI") return true; 179 | if (memberInfo.ReflectedType.FullName == "UnityEngine.MeshRenderer" && memberInfo.Name == "scaleInLightmap") return true; 180 | if (memberInfo.ReflectedType.FullName == "UnityEngine.ParticleSystemRenderer" && memberInfo.Name == "supportsMeshInstancing") return true; 181 | if (memberInfo.ReflectedType.FullName == "UnityEngine.UI.Text" && memberInfo.Name == "OnRebuildRequested") return true; 182 | if (memberInfo.ReflectedType.FullName == "UnityEngine.Light" && memberInfo.Name == "SetLightDirty") return true; 183 | if (memberInfo.ReflectedType.FullName == "UnityEngine.Light" && memberInfo.Name == "shadowRadius") return true; 184 | if (memberInfo.ReflectedType.FullName == "UnityEngine.Light" && memberInfo.Name == "shadowAngle") return true; 185 | if (memberInfo.ReflectedType.FullName == "UnityEngine.Light" && memberInfo.Name == "areaSize") return true; 186 | if (memberInfo.ReflectedType.FullName == "UnityEngine.Light" && memberInfo.Name == "lightmapBakeType") return true; 187 | if (memberInfo.ReflectedType.FullName == "UnityEngine.CanvasRenderer" && memberInfo.Name == "OnRequestRebuild") return true; 188 | if (memberInfo.ReflectedType.FullName == "UnityEngine.CanvasRenderer" && memberInfo.Name == "onRequestRebuild") return true; 189 | // if (memberInfo.Name == "OnRequestRebuild") return true; 190 | // TODO: 添加要忽略导出的类成员 191 | 192 | return sig.Contains("*"); 193 | } 194 | } -------------------------------------------------------------------------------- /Commands/init/Unity/Assets/Resources/Prefabs/Slider.prefab: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!1 &1231850180608324340 4 | GameObject: 5 | m_ObjectHideFlags: 0 6 | m_CorrespondingSourceObject: {fileID: 0} 7 | m_PrefabInstance: {fileID: 0} 8 | m_PrefabAsset: {fileID: 0} 9 | serializedVersion: 6 10 | m_Component: 11 | - component: {fileID: 1898337743207582101} 12 | m_Layer: 5 13 | m_Name: Handle Slide Area 14 | m_TagString: Untagged 15 | m_Icon: {fileID: 0} 16 | m_NavMeshLayer: 0 17 | m_StaticEditorFlags: 0 18 | m_IsActive: 1 19 | --- !u!224 &1898337743207582101 20 | RectTransform: 21 | m_ObjectHideFlags: 0 22 | m_CorrespondingSourceObject: {fileID: 0} 23 | m_PrefabInstance: {fileID: 0} 24 | m_PrefabAsset: {fileID: 0} 25 | m_GameObject: {fileID: 1231850180608324340} 26 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 27 | m_LocalPosition: {x: 0, y: 0, z: 0} 28 | m_LocalScale: {x: 1, y: 1, z: 1} 29 | m_ConstrainProportionsScale: 0 30 | m_Children: 31 | - {fileID: 4399685348025787094} 32 | m_Father: {fileID: 6537489691012012305} 33 | m_RootOrder: 2 34 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 35 | m_AnchorMin: {x: 0, y: 0} 36 | m_AnchorMax: {x: 1, y: 1} 37 | m_AnchoredPosition: {x: 0, y: 0} 38 | m_SizeDelta: {x: -20, y: 0} 39 | m_Pivot: {x: 0.5, y: 0.5} 40 | --- !u!1 &1802333083905496803 41 | GameObject: 42 | m_ObjectHideFlags: 0 43 | m_CorrespondingSourceObject: {fileID: 0} 44 | m_PrefabInstance: {fileID: 0} 45 | m_PrefabAsset: {fileID: 0} 46 | serializedVersion: 6 47 | m_Component: 48 | - component: {fileID: 6537489691012012305} 49 | - component: {fileID: 2588691311829537688} 50 | m_Layer: 5 51 | m_Name: Slider 52 | m_TagString: Untagged 53 | m_Icon: {fileID: 0} 54 | m_NavMeshLayer: 0 55 | m_StaticEditorFlags: 0 56 | m_IsActive: 1 57 | --- !u!224 &6537489691012012305 58 | RectTransform: 59 | m_ObjectHideFlags: 0 60 | m_CorrespondingSourceObject: {fileID: 0} 61 | m_PrefabInstance: {fileID: 0} 62 | m_PrefabAsset: {fileID: 0} 63 | m_GameObject: {fileID: 1802333083905496803} 64 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 65 | m_LocalPosition: {x: 0, y: 0, z: 0} 66 | m_LocalScale: {x: 1, y: 1, z: 1} 67 | m_ConstrainProportionsScale: 0 68 | m_Children: 69 | - {fileID: 5589667107664287527} 70 | - {fileID: 3815194246150075767} 71 | - {fileID: 1898337743207582101} 72 | m_Father: {fileID: 0} 73 | m_RootOrder: 0 74 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 75 | m_AnchorMin: {x: 0.5, y: 0} 76 | m_AnchorMax: {x: 0.5, y: 0} 77 | m_AnchoredPosition: {x: 0, y: 100} 78 | m_SizeDelta: {x: 160, y: 20} 79 | m_Pivot: {x: 0.5, y: 0.5} 80 | --- !u!114 &2588691311829537688 81 | MonoBehaviour: 82 | m_ObjectHideFlags: 0 83 | m_CorrespondingSourceObject: {fileID: 0} 84 | m_PrefabInstance: {fileID: 0} 85 | m_PrefabAsset: {fileID: 0} 86 | m_GameObject: {fileID: 1802333083905496803} 87 | m_Enabled: 1 88 | m_EditorHideFlags: 0 89 | m_Script: {fileID: 11500000, guid: 67db9e8f0e2ae9c40bc1e2b64352a6b4, type: 3} 90 | m_Name: 91 | m_EditorClassIdentifier: 92 | m_Navigation: 93 | m_Mode: 3 94 | m_WrapAround: 0 95 | m_SelectOnUp: {fileID: 0} 96 | m_SelectOnDown: {fileID: 0} 97 | m_SelectOnLeft: {fileID: 0} 98 | m_SelectOnRight: {fileID: 0} 99 | m_Transition: 1 100 | m_Colors: 101 | m_NormalColor: {r: 1, g: 1, b: 1, a: 1} 102 | m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} 103 | m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} 104 | m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} 105 | m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} 106 | m_ColorMultiplier: 1 107 | m_FadeDuration: 0.1 108 | m_SpriteState: 109 | m_HighlightedSprite: {fileID: 0} 110 | m_PressedSprite: {fileID: 0} 111 | m_SelectedSprite: {fileID: 0} 112 | m_DisabledSprite: {fileID: 0} 113 | m_AnimationTriggers: 114 | m_NormalTrigger: Normal 115 | m_HighlightedTrigger: Highlighted 116 | m_PressedTrigger: Pressed 117 | m_SelectedTrigger: Selected 118 | m_DisabledTrigger: Disabled 119 | m_Interactable: 1 120 | m_TargetGraphic: {fileID: 3234787197712074057} 121 | m_FillRect: {fileID: 2442678018381804529} 122 | m_HandleRect: {fileID: 4399685348025787094} 123 | m_Direction: 0 124 | m_MinValue: 0 125 | m_MaxValue: 1 126 | m_WholeNumbers: 0 127 | m_Value: 0 128 | m_OnValueChanged: 129 | m_PersistentCalls: 130 | m_Calls: [] 131 | --- !u!1 &3293076831770716673 132 | GameObject: 133 | m_ObjectHideFlags: 0 134 | m_CorrespondingSourceObject: {fileID: 0} 135 | m_PrefabInstance: {fileID: 0} 136 | m_PrefabAsset: {fileID: 0} 137 | serializedVersion: 6 138 | m_Component: 139 | - component: {fileID: 3815194246150075767} 140 | m_Layer: 5 141 | m_Name: Fill Area 142 | m_TagString: Untagged 143 | m_Icon: {fileID: 0} 144 | m_NavMeshLayer: 0 145 | m_StaticEditorFlags: 0 146 | m_IsActive: 1 147 | --- !u!224 &3815194246150075767 148 | RectTransform: 149 | m_ObjectHideFlags: 0 150 | m_CorrespondingSourceObject: {fileID: 0} 151 | m_PrefabInstance: {fileID: 0} 152 | m_PrefabAsset: {fileID: 0} 153 | m_GameObject: {fileID: 3293076831770716673} 154 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 155 | m_LocalPosition: {x: 0, y: 0, z: 0} 156 | m_LocalScale: {x: 1, y: 1, z: 1} 157 | m_ConstrainProportionsScale: 0 158 | m_Children: 159 | - {fileID: 2442678018381804529} 160 | m_Father: {fileID: 6537489691012012305} 161 | m_RootOrder: 1 162 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 163 | m_AnchorMin: {x: 0, y: 0.25} 164 | m_AnchorMax: {x: 1, y: 0.75} 165 | m_AnchoredPosition: {x: -5, y: 0} 166 | m_SizeDelta: {x: -20, y: 0} 167 | m_Pivot: {x: 0.5, y: 0.5} 168 | --- !u!1 &5276228020299191658 169 | GameObject: 170 | m_ObjectHideFlags: 0 171 | m_CorrespondingSourceObject: {fileID: 0} 172 | m_PrefabInstance: {fileID: 0} 173 | m_PrefabAsset: {fileID: 0} 174 | serializedVersion: 6 175 | m_Component: 176 | - component: {fileID: 2442678018381804529} 177 | - component: {fileID: 5517108881046890422} 178 | - component: {fileID: 8183201761782904221} 179 | m_Layer: 5 180 | m_Name: Fill 181 | m_TagString: Untagged 182 | m_Icon: {fileID: 0} 183 | m_NavMeshLayer: 0 184 | m_StaticEditorFlags: 0 185 | m_IsActive: 1 186 | --- !u!224 &2442678018381804529 187 | RectTransform: 188 | m_ObjectHideFlags: 0 189 | m_CorrespondingSourceObject: {fileID: 0} 190 | m_PrefabInstance: {fileID: 0} 191 | m_PrefabAsset: {fileID: 0} 192 | m_GameObject: {fileID: 5276228020299191658} 193 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 194 | m_LocalPosition: {x: 0, y: 0, z: 0} 195 | m_LocalScale: {x: 1, y: 1, z: 1} 196 | m_ConstrainProportionsScale: 0 197 | m_Children: [] 198 | m_Father: {fileID: 3815194246150075767} 199 | m_RootOrder: 0 200 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 201 | m_AnchorMin: {x: 0, y: 0} 202 | m_AnchorMax: {x: 0, y: 0} 203 | m_AnchoredPosition: {x: 0, y: 0} 204 | m_SizeDelta: {x: 10, y: 0} 205 | m_Pivot: {x: 0.5, y: 0.5} 206 | --- !u!222 &5517108881046890422 207 | CanvasRenderer: 208 | m_ObjectHideFlags: 0 209 | m_CorrespondingSourceObject: {fileID: 0} 210 | m_PrefabInstance: {fileID: 0} 211 | m_PrefabAsset: {fileID: 0} 212 | m_GameObject: {fileID: 5276228020299191658} 213 | m_CullTransparentMesh: 1 214 | --- !u!114 &8183201761782904221 215 | MonoBehaviour: 216 | m_ObjectHideFlags: 0 217 | m_CorrespondingSourceObject: {fileID: 0} 218 | m_PrefabInstance: {fileID: 0} 219 | m_PrefabAsset: {fileID: 0} 220 | m_GameObject: {fileID: 5276228020299191658} 221 | m_Enabled: 1 222 | m_EditorHideFlags: 0 223 | m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} 224 | m_Name: 225 | m_EditorClassIdentifier: 226 | m_Material: {fileID: 0} 227 | m_Color: {r: 1, g: 1, b: 1, a: 1} 228 | m_RaycastTarget: 1 229 | m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} 230 | m_Maskable: 1 231 | m_OnCullStateChanged: 232 | m_PersistentCalls: 233 | m_Calls: [] 234 | m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} 235 | m_Type: 1 236 | m_PreserveAspect: 0 237 | m_FillCenter: 1 238 | m_FillMethod: 4 239 | m_FillAmount: 1 240 | m_FillClockwise: 1 241 | m_FillOrigin: 0 242 | m_UseSpriteMesh: 0 243 | m_PixelsPerUnitMultiplier: 1 244 | --- !u!1 &5555562365397644940 245 | GameObject: 246 | m_ObjectHideFlags: 0 247 | m_CorrespondingSourceObject: {fileID: 0} 248 | m_PrefabInstance: {fileID: 0} 249 | m_PrefabAsset: {fileID: 0} 250 | serializedVersion: 6 251 | m_Component: 252 | - component: {fileID: 4399685348025787094} 253 | - component: {fileID: 8330528840467417470} 254 | - component: {fileID: 3234787197712074057} 255 | m_Layer: 5 256 | m_Name: Handle 257 | m_TagString: Untagged 258 | m_Icon: {fileID: 0} 259 | m_NavMeshLayer: 0 260 | m_StaticEditorFlags: 0 261 | m_IsActive: 1 262 | --- !u!224 &4399685348025787094 263 | RectTransform: 264 | m_ObjectHideFlags: 0 265 | m_CorrespondingSourceObject: {fileID: 0} 266 | m_PrefabInstance: {fileID: 0} 267 | m_PrefabAsset: {fileID: 0} 268 | m_GameObject: {fileID: 5555562365397644940} 269 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 270 | m_LocalPosition: {x: 0, y: 0, z: 0} 271 | m_LocalScale: {x: 1, y: 1, z: 1} 272 | m_ConstrainProportionsScale: 0 273 | m_Children: [] 274 | m_Father: {fileID: 1898337743207582101} 275 | m_RootOrder: 0 276 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 277 | m_AnchorMin: {x: 0, y: 0} 278 | m_AnchorMax: {x: 0, y: 0} 279 | m_AnchoredPosition: {x: 0, y: 0} 280 | m_SizeDelta: {x: 20, y: 0} 281 | m_Pivot: {x: 0.5, y: 0.5} 282 | --- !u!222 &8330528840467417470 283 | CanvasRenderer: 284 | m_ObjectHideFlags: 0 285 | m_CorrespondingSourceObject: {fileID: 0} 286 | m_PrefabInstance: {fileID: 0} 287 | m_PrefabAsset: {fileID: 0} 288 | m_GameObject: {fileID: 5555562365397644940} 289 | m_CullTransparentMesh: 1 290 | --- !u!114 &3234787197712074057 291 | MonoBehaviour: 292 | m_ObjectHideFlags: 0 293 | m_CorrespondingSourceObject: {fileID: 0} 294 | m_PrefabInstance: {fileID: 0} 295 | m_PrefabAsset: {fileID: 0} 296 | m_GameObject: {fileID: 5555562365397644940} 297 | m_Enabled: 1 298 | m_EditorHideFlags: 0 299 | m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} 300 | m_Name: 301 | m_EditorClassIdentifier: 302 | m_Material: {fileID: 0} 303 | m_Color: {r: 1, g: 1, b: 1, a: 1} 304 | m_RaycastTarget: 1 305 | m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} 306 | m_Maskable: 1 307 | m_OnCullStateChanged: 308 | m_PersistentCalls: 309 | m_Calls: [] 310 | m_Sprite: {fileID: 10913, guid: 0000000000000000f000000000000000, type: 0} 311 | m_Type: 0 312 | m_PreserveAspect: 0 313 | m_FillCenter: 1 314 | m_FillMethod: 4 315 | m_FillAmount: 1 316 | m_FillClockwise: 1 317 | m_FillOrigin: 0 318 | m_UseSpriteMesh: 0 319 | m_PixelsPerUnitMultiplier: 1 320 | --- !u!1 &5634413007027189761 321 | GameObject: 322 | m_ObjectHideFlags: 0 323 | m_CorrespondingSourceObject: {fileID: 0} 324 | m_PrefabInstance: {fileID: 0} 325 | m_PrefabAsset: {fileID: 0} 326 | serializedVersion: 6 327 | m_Component: 328 | - component: {fileID: 5589667107664287527} 329 | - component: {fileID: 4094056889019221543} 330 | - component: {fileID: 9084789808685572664} 331 | m_Layer: 5 332 | m_Name: Background 333 | m_TagString: Untagged 334 | m_Icon: {fileID: 0} 335 | m_NavMeshLayer: 0 336 | m_StaticEditorFlags: 0 337 | m_IsActive: 1 338 | --- !u!224 &5589667107664287527 339 | RectTransform: 340 | m_ObjectHideFlags: 0 341 | m_CorrespondingSourceObject: {fileID: 0} 342 | m_PrefabInstance: {fileID: 0} 343 | m_PrefabAsset: {fileID: 0} 344 | m_GameObject: {fileID: 5634413007027189761} 345 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 346 | m_LocalPosition: {x: 0, y: 0, z: 0} 347 | m_LocalScale: {x: 1, y: 1, z: 1} 348 | m_ConstrainProportionsScale: 0 349 | m_Children: [] 350 | m_Father: {fileID: 6537489691012012305} 351 | m_RootOrder: 0 352 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 353 | m_AnchorMin: {x: 0, y: 0.25} 354 | m_AnchorMax: {x: 1, y: 0.75} 355 | m_AnchoredPosition: {x: 0, y: 0} 356 | m_SizeDelta: {x: 0, y: 0} 357 | m_Pivot: {x: 0.5, y: 0.5} 358 | --- !u!222 &4094056889019221543 359 | CanvasRenderer: 360 | m_ObjectHideFlags: 0 361 | m_CorrespondingSourceObject: {fileID: 0} 362 | m_PrefabInstance: {fileID: 0} 363 | m_PrefabAsset: {fileID: 0} 364 | m_GameObject: {fileID: 5634413007027189761} 365 | m_CullTransparentMesh: 1 366 | --- !u!114 &9084789808685572664 367 | MonoBehaviour: 368 | m_ObjectHideFlags: 0 369 | m_CorrespondingSourceObject: {fileID: 0} 370 | m_PrefabInstance: {fileID: 0} 371 | m_PrefabAsset: {fileID: 0} 372 | m_GameObject: {fileID: 5634413007027189761} 373 | m_Enabled: 1 374 | m_EditorHideFlags: 0 375 | m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} 376 | m_Name: 377 | m_EditorClassIdentifier: 378 | m_Material: {fileID: 0} 379 | m_Color: {r: 1, g: 1, b: 1, a: 1} 380 | m_RaycastTarget: 1 381 | m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} 382 | m_Maskable: 1 383 | m_OnCullStateChanged: 384 | m_PersistentCalls: 385 | m_Calls: [] 386 | m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0} 387 | m_Type: 1 388 | m_PreserveAspect: 0 389 | m_FillCenter: 1 390 | m_FillMethod: 4 391 | m_FillAmount: 1 392 | m_FillClockwise: 1 393 | m_FillOrigin: 0 394 | m_UseSpriteMesh: 0 395 | m_PixelsPerUnitMultiplier: 1 396 | -------------------------------------------------------------------------------- /Commands/init/Unity/TS/src/App.ts: -------------------------------------------------------------------------------- 1 | import ue = CS.UnityEngine; // 是用来协助进行类型检查和声明的,在运行时是完全不存在 2 | import Ticker from './Core/Ticker'; 3 | const { GameObject, Time, Vector2, Vector3, RenderMode, Camera, Canvas, CanvasRenderer, MeshFilter, Mesh, 4 | MeshRenderer, Light, LightType, FontStyle, Quaternion, UI, CameraClearFlags, Resources, EventSystems, 5 | RectTransform, TextAnchor, LightmapBakeType} = CS.UnityEngine; 6 | const {CanvasScaler,Slider} = CS.UnityEngine.UI; 7 | const {$typeof} = puer; 8 | 9 | namespace Game { 10 | interface GameOption { 11 | /** 每秒最大渲染帧数限制 */ 12 | maxFps?:number, 13 | /**是否为线上*/ 14 | online?:boolean 15 | } 16 | interface GameObj { 17 | object:ue.GameObject, 18 | components?:{ 19 | camera?:ue.Camera, 20 | canvas?:ue.Canvas, 21 | canvaslScale?:ue.UI.CanvasScaler, 22 | graphicRaycaster?:ue.UI.GraphicRaycaster 23 | eventSystem?:ue.EventSystems.EventSystem, 24 | standaloneInputModule?:ue.EventSystems.StandaloneInputModule, 25 | text?:ue.UI.Text, 26 | rectTransform?:ue.RectTransform, 27 | slider?:ue.UI.Slider 28 | } 29 | } 30 | interface RandomizeMatrix { 31 | position:[number,number,number], 32 | scale:[number,number,number], 33 | rotaion:[number,number,number] 34 | } 35 | export class App{ 36 | private _maxFps:number; // 最大帧 37 | private ticker:Ticker; // 轮循执行器 38 | public static mainCamera:ue.GameObject; // 主相机 39 | public static uiCamera:ue.GameObject; // Ui相机 40 | public static uiCanvas:ue.GameObject; // Ui画布 41 | public static eventSystem:ue.GameObject; // Ui事件系统 42 | public static stage:ue.GameObject; // 舞台节点 43 | 44 | private cubeList:ue.GameObject[] = []; // 保存 Cube 列表 45 | private cubeCont:number = 100; // 记录 Cube 数量 46 | private speed:number = 1; // 记录 Cube 速度 47 | 48 | /** 49 | * 构造方法 50 | * @param option 游戏启动参数 51 | */ 52 | constructor(option?:GameOption){ 53 | if(option){ 54 | for(let key in option){ 55 | this[key] = option[key]; 56 | }; 57 | }; 58 | if(!this.maxFps){ 59 | this.maxFps = 60; 60 | }; 61 | if(!this.maxFps){ 62 | this.maxFps = 60; 63 | }; 64 | this.ticker = new Ticker(this.maxFps); 65 | } 66 | 67 | /** 68 | * 应用启动入口 69 | */ 70 | async init():Promise{ 71 | this.ticker.start(); // 启动轮循执行器 72 | 73 | const mainCameraObj = this.createMainCamera(); 74 | App.mainCamera = mainCameraObj.object; // 创建主相机 75 | 76 | const uiCameraObj = this.createUiCamera(); 77 | App.uiCamera = uiCameraObj.object; // 创建UI相机 78 | 79 | const canvasObj = this.createCanas(uiCameraObj.components.camera); 80 | App.uiCanvas = canvasObj.object; // 创建UI画布 81 | 82 | const eventSystemObj = this.createEventSystem(); 83 | App.eventSystem = eventSystemObj.object; // 创建UI事件系统 84 | 85 | App.stage = new GameObject("Stage"); // 创建舞台,内容全在舞台上 86 | 87 | const speedTextObj = this.createUiText('TextSpeed'); // 速度文本 88 | const countTextObj = this.createUiText('TextCount'); // 数量文本 89 | countTextObj.components.rectTransform.anchoredPosition = new Vector2(0,-40); // 调整显示位置稍微向下偏移一点 90 | 91 | const timers = {}; // 用于保存计时器,防抖之用 92 | const updateSpeed = (val:number)=>{ // 更新速度方法 93 | this.speed = val; 94 | speedTextObj.components.text.text = `Speed:${val.toFixed(3)}` 95 | }; 96 | updateSpeed(this.speed); // 速度首次初始化 97 | 98 | const updateCount = (val:number)=>{ // 更新数量方法 99 | val = ~~val; // 取整 100 | if(val === this.cubeList.length){return;}; // 值与当前列表的数量一样则无需处理 101 | this.cubeCont = val; // 记录最新数量 102 | countTextObj.components.text.text = `Count:${this.cubeCont}`; // 更新提示文字 103 | clearTimeout(timers['updateCount']); // 防抖处理 104 | timers['updateCount'] = setTimeout(() => { // 防抖处理 105 | this.cubeList = this.createCubeList(); 106 | }, 200); 107 | }; 108 | updateCount(this.cubeCont); // 数量首次初始化 109 | 110 | const speedSlideObj = this.createUiSlider('SliderSpeed',updateSpeed); // 创建调节速度的Slide 111 | speedSlideObj.components.slider.minValue = this.speed; // 最小值为默认初始值 112 | speedSlideObj.components.slider.maxValue = this.speed + 100 - this.speed; // 最大值100 113 | 114 | const countSlideObj = this.createUiSlider('SliderCount',updateCount); // 创建调节数量的Slide 115 | countSlideObj.components.rectTransform.anchoredPosition = new Vector2(0,80); // 调整显示位置稍微向下偏移一点 116 | countSlideObj.components.slider.minValue = this.cubeCont; // 最小值为默认初始值 117 | countSlideObj.components.slider.maxValue = this.cubeCont + 20000 - this.cubeCont; // 最大值3000 118 | 119 | const update = (deltaTime:number,performance:number)=>{ // 更新方法(当热重载开启时,修改该方法即时生效) 120 | for(let i=0,len=this.cubeList.length; i):GameObj{ 161 | const object:ue.GameObject = GameObject.Instantiate(Resources.Load("Prefabs/Slider")) as ue.GameObject; 162 | object.name = name; 163 | const rectTransform:ue.RectTransform = object.GetComponent($typeof(RectTransform)) as ue.RectTransform; 164 | const slider:ue.UI.Slider = object.GetComponent($typeof(Slider)) as ue.UI.Slider; 165 | slider.minValue = 1; 166 | slider.maxValue = 10; 167 | // slider.wholeNumbers = true; 168 | slider.onValueChanged.AddListener(call); // 为 UI 添加事件绑定 169 | rectTransform.transform.localPosition = new Vector3(0,160,0); 170 | rectTransform.transform.localScale = new Vector3(2,2,2); 171 | rectTransform.sizeDelta = new Vector2(120,20); 172 | object.transform.SetParent(App.uiCanvas.transform,false); 173 | return {object,components:{slider,rectTransform}}; 174 | } 175 | 176 | /** 177 | * 创建随机矩阵 178 | * @returns RandomizeMatrix 179 | */ 180 | createRandomizeMatrix():RandomizeMatrix{ 181 | const position:RandomizeMatrix['position'] = [ 182 | Math.random() * 20 - 10, 183 | Math.random() * 20 - 10, 184 | Math.random() * 20 - 10, 185 | ]; 186 | const scaleVal = Math.random() * 1; 187 | const scale:RandomizeMatrix['scale'] = [scaleVal,scaleVal,scaleVal]; 188 | const rotaion:RandomizeMatrix['rotaion'] = [ 189 | Math.random() * 90 * Math.PI, 190 | Math.random() * 90 * Math.PI, 191 | Math.random() * 90 * Math.PI 192 | ]; 193 | return {position,scale,rotaion}; 194 | } 195 | 196 | /** 197 | * 创建Cube列表 198 | * @returns ue.GameObject[] 199 | */ 200 | createCubeList():ue.GameObject[]{ 201 | for(let i=0,len=this.cubeList.length; i{ 307 | const app = new Game.App({maxFps:60,online:false}); 308 | app.init(); 309 | })() 310 | export default Game.App; -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 79 |
80 |

Puerts Cli

81 |
82 |
83 |
84 |
85 | --------------------------------------------------------------------------------