├── .gitignore ├── src └── framework │ ├── platform │ ├── dev │ │ ├── index.ts │ │ ├── page │ │ │ ├── iphonex.html │ │ │ └── debug-panel.html │ │ ├── recorder.dev.ts │ │ └── platform.dev.ts │ ├── wechat │ │ ├── index.ts │ │ ├── storage.wechat.ts │ │ ├── wx.webaudio.d.ts │ │ ├── downloader.wechat.ts │ │ ├── websocket.wechat.ts │ │ ├── xhr.wechat.ts │ │ ├── wx-patch.d.ts │ │ └── fs.wechat.ts │ ├── kuaishou │ │ ├── index.ts │ │ ├── storage.kuaishou.ts │ │ ├── downloader.kuaishou.ts │ │ ├── websocket.kuaishou.ts │ │ ├── recorder.kuaishou.ts │ │ ├── xhr.kuaishou.ts │ │ ├── fs.kuaishou.ts │ │ └── platform.kuaishou.ts │ ├── bytedance │ │ ├── index.ts │ │ ├── storage.bytedance.ts │ │ ├── downloader.bytedance.ts │ │ ├── websocket.bytedance.ts │ │ ├── recorder.bytedance.ts │ │ ├── xhr.bytedance.ts │ │ └── fs.bytedance.ts │ ├── utils │ │ ├── index.ts │ │ └── SubPackageLoader.ts │ ├── web │ │ ├── downloader.web.ts │ │ ├── websocket.web.ts │ │ ├── canvas.ts │ │ ├── platform.web.basic.ts │ │ ├── fs.web.ts │ │ └── platform.web.ts │ └── platform.common.ts │ ├── @types │ ├── files.d.ts │ ├── http.d.ts │ ├── websocket.d.ts │ ├── sdk.d.ts │ ├── IFileDownloader.d.ts │ ├── recorder.d.ts │ ├── fs.d.ts │ ├── env.d.ts │ ├── global.d.ts │ └── EventEmitter.d.ts │ ├── tiny │ ├── core │ │ ├── TinyObject.ts │ │ ├── HashObject.ts │ │ ├── global.ts │ │ ├── EventEmitter.ts │ │ └── async-operation │ │ │ └── AsyncOperation.ts │ └── utils │ │ ├── promise.ts │ │ ├── StateMachine.basic.ts │ │ ├── fs │ │ ├── FileUtils.ts │ │ ├── fs.xhr.ts │ │ └── fs.sizelimited.ts │ │ ├── deferred.ts │ │ ├── FileDownloader.xhr.ts │ │ ├── format.ts │ │ ├── path.ts │ │ ├── http.xhr.ts │ │ └── index.ts │ ├── webapi │ └── xhr │ │ ├── url.ts │ │ ├── webapi.xhr.d.ts │ │ └── xhr.common.ts │ └── common │ ├── exception.ts │ └── constants.ts ├── README.md ├── tsconfig.json ├── LICENSE ├── package.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | yarn.lock 4 | -------------------------------------------------------------------------------- /src/framework/platform/dev/index.ts: -------------------------------------------------------------------------------- 1 | import '../web'; 2 | import './platform.dev'; 3 | -------------------------------------------------------------------------------- /src/framework/platform/wechat/index.ts: -------------------------------------------------------------------------------- 1 | import './xhr.wechat'; 2 | import './platform.wechat'; 3 | -------------------------------------------------------------------------------- /src/framework/platform/kuaishou/index.ts: -------------------------------------------------------------------------------- 1 | import './xhr.kuaishou'; 2 | import './platform.kuaishou'; 3 | -------------------------------------------------------------------------------- /src/framework/platform/bytedance/index.ts: -------------------------------------------------------------------------------- 1 | import './xhr.bytedance'; 2 | import './platform.bytedance'; 3 | -------------------------------------------------------------------------------- /src/framework/@types/files.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.txt'; 2 | declare module '*.yaml'; 3 | declare module '*.html'; 4 | -------------------------------------------------------------------------------- /src/framework/tiny/core/TinyObject.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from './EventEmitter'; 2 | import { HashObject } from './HashObject'; 3 | 4 | export class TinyObject extends HashObject { 5 | /** 事件派发器 */ 6 | readonly event = new EventEmitter(); 7 | 8 | /** 执行清理/销毁操作,执行该操作之后不应再继续使用该对象 */ 9 | dispose() { 10 | this.event.dispose(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/framework/webapi/xhr/url.ts: -------------------------------------------------------------------------------- 1 | export interface IURL { 2 | url: string; 3 | hostname?: string; 4 | path?: string; 5 | port?: number; 6 | protocal?: string; 7 | } 8 | import parse from 'url-parse'; 9 | export function parseUrl(url: string): IURL { 10 | const ret = parse(url); 11 | Object.assign(ret, { url }); 12 | return ret as unknown as IURL; 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/framework/platform/wechat/storage.wechat.ts: -------------------------------------------------------------------------------- 1 | 2 | export class WechatLocalStorage implements LocalStorage { 3 | getItem(key: string): string { 4 | return wx.getStorageSync(key); 5 | } 6 | 7 | setItem(key: string, value: string): void { 8 | wx.setStorageSync(key, value); 9 | } 10 | 11 | clear() { 12 | wx.clearStorageSync(); 13 | } 14 | 15 | removeItem(key: string) { 16 | wx.removeStorageSync(key); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/framework/platform/bytedance/storage.bytedance.ts: -------------------------------------------------------------------------------- 1 | 2 | export class BytedanceLocalStorage implements LocalStorage { 3 | getItem(key: string): string { 4 | return tt.getStorageSync(key); 5 | } 6 | 7 | setItem(key: string, value: string): void { 8 | tt.setStorageSync(key, value); 9 | } 10 | 11 | clear() { 12 | tt.clearStorageSync(); 13 | } 14 | 15 | removeItem(key: string) { 16 | tt.removeStorageSync(key); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/framework/platform/kuaishou/storage.kuaishou.ts: -------------------------------------------------------------------------------- 1 | 2 | export class KuaishouLocalStorage implements LocalStorage { 3 | getItem(key: string): string { 4 | return ks.getStorageSync(key); 5 | } 6 | 7 | setItem(key: string, value: string): void { 8 | ks.setStorageSync(key, value); 9 | } 10 | 11 | clear() { 12 | ks.clearStorageSync(); 13 | } 14 | 15 | removeItem(key: string) { 16 | ks.removeStorageSync(key); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/framework/platform/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { globalGet, globalSet } from 'framework/tiny/core/global'; 2 | 3 | export function DefinePlatfrom(type: new () => Platform) { 4 | globalSet('getPlatform', function getPlatform() { 5 | let platform: Platform = globalGet('platform'); 6 | if (platform) return platform; 7 | platform = new type; 8 | globalSet("platform", platform); 9 | console.log('[platform]', '当前平台', platform); 10 | return platform; 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/framework/@types/http.d.ts: -------------------------------------------------------------------------------- 1 | type HttpRequestMethod = 'GET' | 'POST' | 'DELETE' | 'PUT'; 2 | 3 | declare interface http { 4 | /** 发起 HTTP 请求 */ 5 | fetch(method: HttpRequestMethod, url: string, body?: BodyInit | Record, headers?: Record, responseType?: string): Promise; 6 | /** 发起 POST 请求 */ 7 | post(url: string | string, data?: BodyInit | Record, headers?: Record, responseType?: string): Promise; 8 | /** 发起 GET 请求 */ 9 | get(url: string | string, params?: Record, headers?: Record, responseType?: string): Promise; 10 | } 11 | -------------------------------------------------------------------------------- /src/framework/@types/websocket.d.ts: -------------------------------------------------------------------------------- 1 | declare interface TinyWebSocket { 2 | 3 | /** 断开连接时被调用 */ 4 | onclose: (event: { code: number; reason: string; }) => void; 5 | /** 连接错误时被调用 */ 6 | onerror: (event: { code: number; message: string }) => void; 7 | /** 收到数据时被调用 */ 8 | onmessage: (data: string | ArrayBuffer) => void; 9 | /** 建立连接后被调用 */ 10 | onopen: () => void; 11 | /** 断开连接 */ 12 | close(code?: number, reason?: string): Promise; 13 | /** 发送数据 */ 14 | send(data: string | ArrayBufferLike): Promise; 15 | } 16 | 17 | declare type TinyWebSocketClass = new (url: string, protocols?: string | string[]) => TinyWebSocket; 18 | -------------------------------------------------------------------------------- /src/framework/tiny/core/HashObject.ts: -------------------------------------------------------------------------------- 1 | export type Nullable = T | null; 2 | 3 | /** 4 | * 框架内所有对象的基类,为对象实例提供唯一的hashCode值。 5 | */ 6 | export interface IHashObject { 7 | /** 8 | * 返回此对象唯一的哈希值,用于唯一确定一个对象。hashCode为大于等于1的整数。 9 | */ 10 | readonly hashCode: number; 11 | } 12 | 13 | /** 14 | * 哈希计数 15 | */ 16 | let $hashCount: number = 0; 17 | 18 | /** 生成新的 HashCode */ 19 | export function GenerateObjectHashCode() { 20 | return ++$hashCount; 21 | } 22 | 23 | export class HashObject implements IHashObject { 24 | private $hashCode = GenerateObjectHashCode(); 25 | get hashCode() { 26 | return this.$hashCode; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/framework/platform/wechat/wx.webaudio.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace wx { 2 | interface WebAudioContext { 3 | /** 获取当前上下文的时间戳。*/ 4 | currentTime: number; 5 | /** 当前上下文的最终目标节点,一般是音频渲染设备 */ 6 | destination: AudioDestinationNode; 7 | 8 | /** 异步解码一段资源为AudioBuffer。 */ 9 | decodeAudioData(data: ArrayBuffer, success: (buffer: AudioBuffer) => void, fail: (err: string) => void); 10 | 11 | /** 创建一个BufferSourceNode实例,通过AudioBuffer对象来播放音频数据 */ 12 | createBufferSource(): AudioBufferSourceNode; 13 | 14 | /** 创建一个GainNode */ 15 | createGain(): GainNode; 16 | } 17 | /** 创建 WebAudio 上下文。 */ 18 | function createWebAudioContext(): WebAudioContext; 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # platform 2 | 游戏运行时的平台抽象层,实现统一的接口,方便游戏开发调用平台能力不必关心底层平台的差异。与游戏引擎解耦,方便在不同引擎中使用。 3 | 4 | 已在多个小游戏项目中被使用、在数千万台设备中运行过,稳定可靠。 5 | 6 | ### 提供的主要功能 7 | - 文件系统,包括读写文件、创建目录、删除文件等 8 | - 适配小游戏写入空间大小受限的环境 9 | - 只读文件系统,支持读取小游戏包内文件,自动触发分包加载 10 | - 网络: 提供统一的网络请求功能,支持 http、https、websocket 11 | - 数据存储: 提供统一的数据存储功能 12 | - 启动参数,获取启动游戏时的参数,如启动场景、买量归因参数、自定义启动参数等 13 | - 用户信息授权与获取(平台登陆等操作) 14 | - 分享(图片、文本、录屏) 15 | - 系统弹窗、提示框 16 | - 设备振动 17 | - 加速度计 18 | - 键盘与输入法 19 | - 设备信息、屏幕信息(异形屏幕与安全区检测) 20 | - [更多功能查看接口文档](src/framework/@types/platform.d.ts) 21 | 22 | ### 支持的平台 23 | - Web 浏览器 24 | - 微信小游戏 25 | - 抖音小游戏(字节系小游戏) 26 | - 快手小游戏 27 | - QQ 小游戏 28 | - VIVO 小游戏 29 | - OPPO 小游戏 30 | -------------------------------------------------------------------------------- /src/framework/common/exception.ts: -------------------------------------------------------------------------------- 1 | class Exception extends Error { 2 | constructor(message?: string, readonly code = 0) { 3 | super(message); 4 | } 5 | } 6 | 7 | export class NotImplementedException extends Exception { 8 | constructor(message: string = 'Not impemented', readonly code = 0) { 9 | super(message, code); 10 | } 11 | } 12 | 13 | export class NotSupportedException extends Exception { 14 | constructor(message: string = 'Not supported', readonly code = 0) { 15 | super(message, code); 16 | } 17 | } 18 | 19 | export class OperateFailedException extends Exception { 20 | constructor(message: string = 'opertation failed', readonly code = 0) { 21 | super(message, code); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/framework/tiny/utils/promise.ts: -------------------------------------------------------------------------------- 1 | import 'finally-polyfill'; 2 | const actions = {} as Record & { refcount: number } >; 3 | 4 | /** 5 | * 避免异步请求在执行过程中被多次执行,如果第一次执行没有完成,后续的请求将直接返回正在执行的操作 6 | * @param id 用于识别相同操作行为的ID 7 | * @param action 真正执行操作的行为 8 | */ 9 | export function avoidRepeat(id: string | number, action: ()=> Promise) { 10 | let a = actions[id]; 11 | if (!a) { 12 | a = action() as Promise & { refcount: number; }; 13 | a.refcount = 1; 14 | actions[id] = a; 15 | } else { 16 | a.refcount += 1; 17 | } 18 | a.finally(() => { 19 | a.refcount -= 1; 20 | if (a.refcount <= 0) { 21 | delete actions[id]; 22 | } 23 | }); 24 | return a as Promise; 25 | } 26 | -------------------------------------------------------------------------------- /src/framework/@types/sdk.d.ts: -------------------------------------------------------------------------------- 1 | declare interface ISDKClass { 2 | /** SDK 的类型 */ 3 | readonly type: string; 4 | /** 启动游戏时被创建 */ 5 | new(options?: Record): ISDK; 6 | } 7 | 8 | /** SDK 接口 */ 9 | declare interface ISDK { 10 | /** SDK 名称 */ 11 | readonly name?: string; 12 | 13 | /** 游戏启动时被调用, 需要及时 reslove 否则将阻止游戏启动流程 */ 14 | initialize?(): Promise; 15 | 16 | /** 游戏结束时被调用 */ 17 | finalize?(): void; 18 | 19 | /** 游戏开始时调用 */ 20 | start?(): void; 21 | 22 | /** 上报事件 */ 23 | sendEvent?(event: string, values?: Record): Promise; 24 | 25 | /** 用户登陆成功时被调用 */ 26 | onUserLogin?(user: User): void; 27 | 28 | /** 应用进入前台时调用 */ 29 | onShow?(): void; 30 | 31 | /** 应用进入后台是调用 */ 32 | onHide?(): void; 33 | } 34 | -------------------------------------------------------------------------------- /src/framework/platform/web/downloader.web.ts: -------------------------------------------------------------------------------- 1 | import { XHRFileDownloader } from 'framework/tiny/utils/FileDownloader.xhr'; 2 | import { path } from 'framework/tiny/utils/path'; 3 | 4 | export class WebFileDownloader extends XHRFileDownloader { 5 | constructor(readonly fs: IFilesSystem) { 6 | super(); 7 | } 8 | 9 | protected async saveResponse(request: XMLHttpRequest, task: IDownloadTask, onProgress?: (current: number, max: number) => void): Promise { 10 | if (onProgress) onProgress(0, 1); 11 | const type = typeof request.response === 'string' ? 'utf8' : 'binary'; 12 | await this.fs.mkdir(path.dirname(task.file), true); 13 | await this.fs.writeFile(task.file, request.response, type as unknown as 'binary'); 14 | if (onProgress) onProgress(1, 1); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/framework/@types/IFileDownloader.d.ts: -------------------------------------------------------------------------------- 1 | declare interface IDownloadTask { 2 | /** 取消下载 */ 3 | abort(); 4 | /** 下载进度事件 */ 5 | onprogress?: (current: number, max: number) => void; 6 | /** 下载完成事件 */ 7 | onload?: () => void; 8 | /** 下载出错事件 */ 9 | onerror?: (error: any) => void; 10 | /** 错误信息 */ 11 | error?: any; 12 | /** 请求的 url */ 13 | readonly url: string; 14 | /** 文件保存的实际路径,注意可能与传入的路径不一致,如添加用户可写路径等操作,需要以返回值的路径为准 */ 15 | readonly file: string; 16 | } 17 | 18 | declare interface IDownloadFileOptions { 19 | /** 存储类型 */ 20 | type: 'text' | 'arraybuffer'; 21 | /** 请求 header */ 22 | headers?: Record; 23 | /** 超时时间 单位 ms */ 24 | timeout?: number; 25 | } 26 | 27 | declare interface IFileDownloader { 28 | load(url: string, file: string, options?: IDownloadFileOptions): IDownloadTask; 29 | } 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "ESNext", 5 | "jsx": "preserve", 6 | "lib": [ 7 | "DOM", 8 | "ES2020" 9 | ], 10 | "allowJs": true, 11 | "sourceMap": true, 12 | "inlineSources": true, 13 | "moduleResolution": "node", 14 | "baseUrl": "src", 15 | "typeRoots": [ 16 | "node_modules/@types", 17 | "Assets/Scripts/gen/puerts/Typing/csharp", 18 | "src/types" 19 | ], 20 | "skipLibCheck": true, 21 | "noImplicitAny": true, 22 | "experimentalDecorators": true, 23 | "allowSyntheticDefaultImports": true 24 | }, 25 | "exclude": [ 26 | "**/*.js", 27 | "docs/", 28 | "node_modules/", 29 | "tiny_modules/", 30 | "src/framework/tiny/utils/hash/js-md5.js", 31 | "Assets/**/*.mjs", 32 | "Library/**/*", 33 | "UI/plugins/tiny/**/*" 34 | ] 35 | } -------------------------------------------------------------------------------- /src/framework/platform/dev/page/iphonex.html: -------------------------------------------------------------------------------- 1 | 15 |