diff --git a/assets/Script/Engine.meta b/assets/Script/Engine.meta new file mode 100644 index 0000000..44c9151 --- /dev/null +++ b/assets/Script/Engine.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "89004b78-f84d-4134-8e8c-f51c5c3800df", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine.meta b/assets/Script/Engine/CatanEngine.meta new file mode 100644 index 0000000..8e6402c --- /dev/null +++ b/assets/Script/Engine/CatanEngine.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "a69fe64f-177f-4e4b-83f0-1f418203d85f", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CSharp.meta b/assets/Script/Engine/CatanEngine/CSharp.meta new file mode 100644 index 0000000..fc0867e --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CSharp.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "f9edb32f-c4ab-4e5d-8270-71fa609e1db7", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CSharp/String.ts b/assets/Script/Engine/CatanEngine/CSharp/String.ts new file mode 100644 index 0000000..ad83796 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CSharp/String.ts @@ -0,0 +1,16 @@ +interface StringConstructor { + IsNullOrEmpty: (value: string) => boolean; + Format: (format: string, ...args: any[]) => string; +} + +String.IsNullOrEmpty = function (value: string): boolean { + return value === undefined || value === null || value.trim() === ''; +}; + +String.Format = function (format: string, ...args: any[]): string { + return format.replace(/{(\d+)}/g, (match, index) => { + let value = args[index]; + if (value === null || value === undefined) return ''; + return '' + value; + }); +} diff --git a/assets/Script/Engine/CatanEngine/CSharp/String.ts.meta b/assets/Script/Engine/CatanEngine/CSharp/String.ts.meta new file mode 100644 index 0000000..2a0b168 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CSharp/String.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "0c3d1ca6-bdaf-4a00-b209-6ef460802cdc", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CSharp/System.meta b/assets/Script/Engine/CatanEngine/CSharp/System.meta new file mode 100644 index 0000000..3cd87f6 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CSharp/System.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "01b35dee-e6e0-4a6e-a73c-3b49c37f1c5f", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CSharp/System/Action.ts b/assets/Script/Engine/CatanEngine/CSharp/System/Action.ts new file mode 100644 index 0000000..e681833 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CSharp/System/Action.ts @@ -0,0 +1,125 @@ +/** + * 回呼函數: fnname (arg: TArg): void + */ +interface ActionCallback { + (arg: TArg): void; +} + +interface Struct { + callback: ActionCallback; + target: any; + once?: boolean; +} + +export class Action { + private _queue: Struct[] = []; + + /** + * 監聽事件 + * @param callback 回呼函數: fnname (arg: TArg): void + * @param bindTarget 回呼時this綁定的對象 + */ + AddCallback(callback: ActionCallback, bindTarget?: any) { + let q = > { + callback: callback, + target: bindTarget + }; + this._queue.push(q); + } + + /** + * 監聽事件 (一次性) + * @param callback 回呼函數: fnname (arg: TArg): void + * @param bindTarget 回呼時this綁定的對象 + */ + AddCallbackOnce(callback: ActionCallback, bindTarget?: any) { + let q = > { + callback: callback, + target: bindTarget, + once: true + }; + this._queue.push(q); + } + + /** + * 移除事件 + * @param callback + */ + RemoveByCallback(callback: ActionCallback) { + let index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback || q.callback === callback) { + q.callback = undefined; + this._queue.splice(index, 1); + } + } + } + } + + /** + * 移除事件 + * @param bindTarget 回呼時this綁定的對象 + */ + RemoveByBindTarget(bindTarget: any) { + let index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback || q.target === bindTarget) { + q.callback = undefined; + this._queue.splice(index, 1); + } + } + } + } + + /** + * 移除全部事件 + */ + RemoveAllCallbacks() { + this._queue.forEach(q => q.callback = undefined); + this._queue.length = 0; + } + + /** + * 發送事件 + * @param arg 參數 + */ + DispatchCallback(arg: TArg) { + let index = this._queue.length; + if (index > 0) { + let cleanRemoved = false; + this._queue.slice().forEach(q => { + if (!q.callback) { + cleanRemoved = true; + return; + } + + if (q.target) { + q.callback.call(q.target, arg); + } else { + q.callback(arg); + } + + if (q.once) { + q.callback = undefined; + cleanRemoved = true; + } + }); + + if (cleanRemoved) { + index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback) { + this._queue.splice(index, 1); + } + } + } + } + } + } +} diff --git a/assets/Script/Engine/CatanEngine/CSharp/System/Action.ts.meta b/assets/Script/Engine/CatanEngine/CSharp/System/Action.ts.meta new file mode 100644 index 0000000..9d69dd8 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CSharp/System/Action.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "ea9bf762-40a7-4bab-b949-8d5b3d4289e2", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CSharp/System/ActionExample.ts b/assets/Script/Engine/CatanEngine/CSharp/System/ActionExample.ts new file mode 100644 index 0000000..90196d5 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CSharp/System/ActionExample.ts @@ -0,0 +1,85 @@ +import { Action } from "./Action"; +import { ActionWithType } from "./ActionWithType"; +import { ActionWithType2 } from "./ActionWithType2"; + +const {ccclass, property} = cc._decorator; + +enum CustomType { + Ex1, Ex2 +} + +class CustomEvent extends ActionWithType {} +class CustomEvent2 extends ActionWithType2 {} + +@ccclass +export default class NewClass extends cc.Component { + callback: Action = new Action(); + customCallback: CustomEvent = new CustomEvent(); + customCallback2: CustomEvent2 = new CustomEvent2(); + + private num: number = 0; + + start () { + this.callback.AddCallback(this.CB, this); + this.callback.AddCallbackOnce(this.OnceCB, this); + + this.customCallback.AddCallback(CustomType.Ex1, this.CBType, this); + this.customCallback.AddCallbackOnce(CustomType.Ex2, this.OnceCBType, this); + + this.customCallback2.AddCallback(CustomType.Ex2, this.CBTypeAllin1, this); + this.customCallback2.AddCallbackOnce(CustomType.Ex1, this.CBTypeAllin1, this); + } + + DispatchClick() { + this.num++; + + this.callback.DispatchCallback(this.num); + + this.customCallback.DispatchCallback(CustomType.Ex1, this.num); + this.customCallback.DispatchCallback(CustomType.Ex2, this.num); + + this.customCallback2.DispatchCallback(CustomType.Ex1, this.num); + this.customCallback2.DispatchCallback(CustomType.Ex2, this.num); + } + + RemoveEventClick() { + this.callback.RemoveByCallback(this.CB); + // this.callback.RemoveByCallback(this.OnceCB); + // this.callback.RemoveByBindTarget(this); + // this.callback.RemoveAll(); + + // this.callbackWithType.RemoveByCallback(this.CBType); + // this.callbackWithType.RemoveByCallback(this.OnceCBType); + this.customCallback.RemoveByType(CustomType.Ex1); + // this.callbackWithType.RemoveByType(CustomType.Ex2); + // this.callbackWithType.RemoveByBindTarget(this); + // this.callbackWithType.RemoveAll(); + } + + OnceCB(x: number) { + cc.log(`OnceCB [${this.num}]`); + } + + CB(x: number) { + cc.log(`CB [${this.num}]`); + } + + OnceCBType(x: number) { + cc.log(`OnceCBType [${this.num}]`); + } + + CBType(x: number) { + cc.log(`CBType [${this.num}]`); + } + + CBTypeAllin1(type: CustomType,x: number) { + // switch (type) { + // case CustomType.Ex1: + // break; + // case CustomType.Ex2: + // break; + // } + + cc.log(`CBTypeAllin1 [${CustomType[type]}][${this.num}]`); + } +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CSharp/System/ActionExample.ts.meta b/assets/Script/Engine/CatanEngine/CSharp/System/ActionExample.ts.meta new file mode 100644 index 0000000..d8e0f22 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CSharp/System/ActionExample.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "cc645b73-6192-414d-a5bc-4220c24e322d", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CSharp/System/ActionWithType.ts b/assets/Script/Engine/CatanEngine/CSharp/System/ActionWithType.ts new file mode 100644 index 0000000..1a078ee --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CSharp/System/ActionWithType.ts @@ -0,0 +1,166 @@ +/** + * 回呼函數: fnname (arg: TArg): void + */ +interface ActionCallback { + (arg: TArg): void; +} + +interface Struct { + callback: ActionCallback; + target: any; + type: TType; + once?: boolean; +} + +export class ActionWithType { + private _queue: Struct[] = []; + + /** + * 監聽事件 + * @param callback 回呼函數: fnname (arg: TArg): void + * @param bindTarget 回呼時this綁定的對象 + */ + AddCallback(type: TType, callback: ActionCallback, bindTarget?: any) { + let q = > { + callback: callback, + target: bindTarget, + type: type + }; + this._queue.push(q); + } + + /** + * 監聽事件 (一次性) + * @param callback 回呼函數: fnname (arg: TArg): void + * @param bindTarget 回呼時this綁定的對象 + */ + AddCallbackOnce(type: TType, callback: ActionCallback, bindTarget?: any) { + let q = > { + callback: callback, + target: bindTarget, + type: type, + once: true + }; + this._queue.push(q); + } + + /** + * 移除事件 + * @param callback + */ + RemoveByCallback(callback: ActionCallback) { + let index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback || q.callback === callback) { + q.callback = undefined; + this._queue.splice(index, 1); + } + } + } + } + + /** + * 移除事件 + * @param bindTarget 回呼時this綁定的對象 + */ + RemoveByBindTarget(bindTarget: any) { + let index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback || q.target === bindTarget) { + q.callback = undefined; + this._queue.splice(index, 1); + } + } + } + } + + /** + * 移除事件 + * @param type 事件類型 + */ + RemoveByType(type: TType) { + let index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback || q.type === type) { + q.callback = undefined; + this._queue.splice(index, 1); + } + } + } + } + + /** + * 移除事件 + * @param type 事件類型 + * @param callback + */ + RemoveCallback(type:TType, callback: ActionCallback) { + let index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback || (q.type === type && q.callback === callback)) { + q.callback = undefined; + this._queue.splice(index, 1); + } + } + } + } + + /** + * 移除全部事件 + */ + RemoveAllCallbacks() { + this._queue.forEach(q => q.callback = undefined); + this._queue.length = 0; + } + + /** + * 發送事件 + * @param type 事件類型 + * @param arg 參數 + */ + DispatchCallback(type: TType, arg: TArg) { + let index = this._queue.length; + if (index > 0) { + let cleanRemoved = false; + this._queue.slice().forEach(q => { + if (!q.callback) + { + cleanRemoved = true; + return; + } + if (q.type !== type) return; + + if (q.target) { + q.callback.call(q.target, arg); + } else { + q.callback(arg); + } + + if (q.once) { + q.callback = undefined; + cleanRemoved = true; + } + }); + + if (cleanRemoved) { + index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback) { + this._queue.splice(index, 1); + } + } + } + } + } + } +} diff --git a/assets/Script/Engine/CatanEngine/CSharp/System/ActionWithType.ts.meta b/assets/Script/Engine/CatanEngine/CSharp/System/ActionWithType.ts.meta new file mode 100644 index 0000000..f57922f --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CSharp/System/ActionWithType.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "61d770ec-24e2-425b-b66b-2b03e192e45b", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CSharp/System/ActionWithType2.ts b/assets/Script/Engine/CatanEngine/CSharp/System/ActionWithType2.ts new file mode 100644 index 0000000..33e8c75 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CSharp/System/ActionWithType2.ts @@ -0,0 +1,166 @@ +/** + * 回呼函數: fnname (type: TType, arg: TArg): void + */ +interface ActionCallback { + (type: TType, arg: TArg): void; +} + +interface Struct { + callback: ActionCallback; + target: any; + type: TType; + once?: boolean; +} + +export class ActionWithType2 { + private _queue: Struct[] = []; + + /** + * 監聽事件 + * @param callback 回呼函數: fnname (type: TType, arg: TArg): void + * @param bindTarget 回呼時this綁定的對象 + */ + AddCallback(type: TType, callback: ActionCallback, bindTarget?: any) { + let q = > { + callback: callback, + target: bindTarget, + type: type + }; + this._queue.push(q); + } + + /** + * 監聽事件 (一次性) + * @param callback 回呼函數: fnname (type: TType, arg: TArg): void + * @param bindTarget 回呼時this綁定的對象 + */ + AddCallbackOnce(type: TType, callback: ActionCallback, bindTarget?: any) { + let q = > { + callback: callback, + target: bindTarget, + type: type, + once: true + }; + this._queue.push(q); + } + + /** + * 移除事件 + * @param callback + */ + RemoveByCallback(callback: ActionCallback) { + let index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback || q.callback === callback) { + q.callback = undefined; + this._queue.splice(index, 1); + } + } + } + } + + /** + * 移除事件 + * @param bindTarget 回呼時this綁定的對象 + */ + RemoveByBindTarget(bindTarget: any) { + let index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback || q.target === bindTarget) { + q.callback = undefined; + this._queue.splice(index, 1); + } + } + } + } + + /** + * 移除事件 + * @param type 事件類型 + */ + RemoveByType(type: TType) { + let index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback || q.type === type) { + q.callback = undefined; + this._queue.splice(index, 1); + } + } + } + } + + /** + * 移除事件 + * @param type 事件類型 + * @param callback + */ + RemoveCallback(type:TType, callback: ActionCallback) { + let index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback || (q.type === type && q.callback === callback)) { + q.callback = undefined; + this._queue.splice(index, 1); + } + } + } + } + + /** + * 移除全部事件 + */ + RemoveAllCallbacks() { + this._queue.forEach(q => q.callback = undefined); + this._queue.length = 0; + } + + /** + * 發送事件 + * @param type 事件類型 + * @param arg 參數 + */ + DispatchCallback(type: TType, arg: TArg) { + let index = this._queue.length; + if (index > 0) { + let cleanRemoved = false; + this._queue.slice().forEach(q => { + if (!q.callback) + { + cleanRemoved = true; + return; + } + if (q.type !== type) return; + + if (q.target) { + q.callback.call(q.target, type, arg); + } else { + q.callback(type, arg); + } + + if (q.once) { + q.callback = undefined; + cleanRemoved = true; + } + }); + + if (cleanRemoved) { + index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback) { + this._queue.splice(index, 1); + } + } + } + } + } + } +} diff --git a/assets/Script/Engine/CatanEngine/CSharp/System/ActionWithType2.ts.meta b/assets/Script/Engine/CatanEngine/CSharp/System/ActionWithType2.ts.meta new file mode 100644 index 0000000..788201f --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CSharp/System/ActionWithType2.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "ed703ebd-efd4-4ec9-9b84-de748ef8f9e8", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CSharp/System/Text.meta b/assets/Script/Engine/CatanEngine/CSharp/System/Text.meta new file mode 100644 index 0000000..dccbd5c --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CSharp/System/Text.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "09d69d12-a6d1-4bb1-bcfe-faa811632467", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CSharp/System/Text/Encoding.ts b/assets/Script/Engine/CatanEngine/CSharp/System/Text/Encoding.ts new file mode 100644 index 0000000..bf63568 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CSharp/System/Text/Encoding.ts @@ -0,0 +1,86 @@ +export module Encoding.UTF8 { + + export function GetBytes(str: string) { + let len = str.length, resPos = -1; + let resArr = new Uint8Array(len * 3); + for (let point = 0, nextcode = 0, i = 0; i !== len;) { + point = str.charCodeAt(i), i += 1; + if (point >= 0xD800 && point <= 0xDBFF) { + if (i === len) { + resArr[resPos += 1] = 0xef; + resArr[resPos += 1] = 0xbf; + resArr[resPos += 1] = 0xbd; + break; + } + + nextcode = str.charCodeAt(i); + if (nextcode >= 0xDC00 && nextcode <= 0xDFFF) { + point = (point - 0xD800) * 0x400 + nextcode - 0xDC00 + 0x10000; + i += 1; + if (point > 0xffff) { + resArr[resPos += 1] = (0x1e << 3) | (point >>> 18); + resArr[resPos += 1] = (0x2 << 6) | ((point >>> 12) & 0x3f); + resArr[resPos += 1] = (0x2 << 6) | ((point >>> 6) & 0x3f); + resArr[resPos += 1] = (0x2 << 6) | (point & 0x3f); + continue; + } + } else { + resArr[resPos += 1] = 0xef; + resArr[resPos += 1] = 0xbf; + resArr[resPos += 1] = 0xbd; + continue; + } + } + if (point <= 0x007f) { + resArr[resPos += 1] = (0x0 << 7) | point; + } else if (point <= 0x07ff) { + resArr[resPos += 1] = (0x6 << 5) | (point >>> 6); + resArr[resPos += 1] = (0x2 << 6) | (point & 0x3f); + } else { + resArr[resPos += 1] = (0xe << 4) | (point >>> 12); + resArr[resPos += 1] = (0x2 << 6) | ((point >>> 6) & 0x3f); + resArr[resPos += 1] = (0x2 << 6) | (point & 0x3f); + } + } + return resArr.subarray(0, resPos + 1); + } + + export function GetString(array: Uint8Array) { + let str = ""; + let i = 0, len = array.length; + while (i < len) { + let c = array[i++]; + switch (c >> 4) { + case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: + str += String.fromCharCode(c); + break; + case 12: case 13: + str += String.fromCharCode(((c & 0x1F) << 6) | (array[i++] & 0x3F)); + break; + case 14: + str += String.fromCharCode(((c & 0x0F) << 12) | ((array[i++] & 0x3F) << 6) | ((array[i++] & 0x3F) << 0)); + break; + } + } + return str; + } + + export function b64EncodeUnicode(str) { + return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) { + return String.fromCharCode('0x' + p1); + })); + } + export function b64DecodeUnicode(str) { + return decodeURIComponent(atob(str).split('').map(function (c) { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }).join('')); + } + export function isBase64(str) { + if (str === '' || str.trim() === '') { return false; } + try { + return btoa(atob(str)) == str; + } catch (err) { + return false; + } + } +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CSharp/System/Text/Encoding.ts.meta b/assets/Script/Engine/CatanEngine/CSharp/System/Text/Encoding.ts.meta new file mode 100644 index 0000000..5dd8375 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CSharp/System/Text/Encoding.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "43bf5724-e939-4189-b981-c32ef694e5a5", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2.meta b/assets/Script/Engine/CatanEngine/CoroutineV2.meta new file mode 100644 index 0000000..70b43dd --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "9f510f2b-83d8-4097-8683-32d6134323fb", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/CancellationTokenSource.ts b/assets/Script/Engine/CatanEngine/CoroutineV2/CancellationTokenSource.ts new file mode 100644 index 0000000..6b264b9 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/CancellationTokenSource.ts @@ -0,0 +1,43 @@ +const CANCEL = Symbol(); + +export interface CancellationToken { + readonly IsCancellationRequested: boolean; + ThrowIfCancellationRequested(): void; +} + +export class CancellationTokenSource { + readonly Token: CancellationToken; + + constructor() { + this.Token = new CancellationTokenImpl(); + } + + Cancel() { + this.Token[CANCEL](); + } +} + +export class TaskCancelledException extends Error { + constructor() { + super("Task Cancelled"); + Reflect.setPrototypeOf(this, TaskCancelledException.prototype); + } +} + +class CancellationTokenImpl implements CancellationToken { + IsCancellationRequested: boolean; + + constructor() { + this.IsCancellationRequested = false; + } + + ThrowIfCancellationRequested() { + if (this.IsCancellationRequested) { + throw new TaskCancelledException(); + } + } + + [CANCEL]() { + this.IsCancellationRequested = true; + } +} diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/CancellationTokenSource.ts.meta b/assets/Script/Engine/CatanEngine/CoroutineV2/CancellationTokenSource.ts.meta new file mode 100644 index 0000000..4b00594 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/CancellationTokenSource.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "9a414131-91a8-4d02-9921-9d1ee01764c3", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/Core.meta b/assets/Script/Engine/CatanEngine/CoroutineV2/Core.meta new file mode 100644 index 0000000..58192bc --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/Core.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "fbfe97a8-24ca-4f67-b049-323652c7194b", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/Core/ActionEnumerator.ts b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/ActionEnumerator.ts new file mode 100644 index 0000000..ddd1dae --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/ActionEnumerator.ts @@ -0,0 +1,17 @@ +import { BaseEnumerator } from "./BaseEnumerator"; + +export class ActionEnumerator extends BaseEnumerator { + private _action: Function; + + constructor(action: Function) { + super(); + this._action = action; + } + + next(value?: any): IteratorResult { + if (this._action) { + this._action(); + } + return { done: true, value: undefined }; + } +} diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/Core/ActionEnumerator.ts.meta b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/ActionEnumerator.ts.meta new file mode 100644 index 0000000..8894866 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/ActionEnumerator.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "3cf9e5c3-520f-48a9-8821-9be76d519765", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/Core/BaseEnumerator.ts b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/BaseEnumerator.ts new file mode 100644 index 0000000..f690291 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/BaseEnumerator.ts @@ -0,0 +1,87 @@ +import { IEnumeratorV2, IEnumeratorV2Started } from "../IEnumeratorV2"; +import { CoroutineExecutor } from "./CoroutineExecutor"; + +export abstract class BaseEnumerator implements IEnumeratorV2 { + public nextEnumerator: BaseEnumerator; + + abstract next(value?: any): IteratorResult; + + Start(target?: any): IEnumeratorV2Started { + let executor = LazyLoad.EnumeratorExecutor(this, target); + CoroutineExecutor.instance.StartCoroutine(executor); + return executor; + } + + Then(iterator: Iterator): IEnumeratorV2 { + if (!iterator) return this; + + if (iterator instanceof BaseEnumerator) { + BaseEnumerator.getLastEnumerator(this).nextEnumerator = iterator; + return this; + } else { + let enumerator = LazyLoad.SingleEnumerator(iterator); + BaseEnumerator.getLastEnumerator(this).nextEnumerator = enumerator; + return this; + } + } + + ThenSerial(...iterators: Iterator[]): IEnumeratorV2 { + let last = BaseEnumerator.getLastEnumerator(this); + for (let iterator of iterators) { + if (iterator instanceof BaseEnumerator) { + last.nextEnumerator = iterator; + } else { + let enumerator = LazyLoad.SingleEnumerator(iterator); + last.nextEnumerator = enumerator; + } + last = last.nextEnumerator; + } + return this; + } + + ThenParallel(...iterators: Iterator[]): IEnumeratorV2 { + return this.Then(LazyLoad.ParallelEnumerator(...iterators)); + } + + ThenAction(action: Function, delaySeconds?:number): IEnumeratorV2 { + if (delaySeconds > 0) { + return this.ThenSerial(LazyLoad.WaitTimeEnumerator(delaySeconds), LazyLoad.ActionEnumerator(action)); + } else { + return this.Then(LazyLoad.ActionEnumerator(action)); + } + } + + ThenWaitTime(seconds: number): IEnumeratorV2 { + return this.Then(LazyLoad.WaitTimeEnumerator(seconds)); + } + + static getLastEnumerator(enumerator: BaseEnumerator): BaseEnumerator { + let next = enumerator; + while (next.nextEnumerator) { + next = next.nextEnumerator; + } + return next; + } +} + +module LazyLoad { + export function EnumeratorExecutor(enumerator: BaseEnumerator, target: any) { + return new (require("./EnumeratorExecutor") as typeof import("./EnumeratorExecutor")).EnumeratorExecutor(enumerator, target); + } + + export function SingleEnumerator(iterator: Iterator) { + return new (require("./SingleEnumerator") as typeof import("./SingleEnumerator")).SingleEnumerator(iterator); + } + + export function ParallelEnumerator(...iterators: Iterator[]) { + return new (require("./ParallelEnumerator") as typeof import("./ParallelEnumerator")).ParallelEnumerator(iterators); + } + + export function WaitTimeEnumerator(seconds: number) { + return new (require("./WaitTimeEnumerator") as typeof import("./WaitTimeEnumerator")).WaitTimeEnumerator(seconds); + } + + export function ActionEnumerator(action: Function) { + return new (require("./ActionEnumerator") as typeof import("./ActionEnumerator")).ActionEnumerator(action); + } +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/Core/BaseEnumerator.ts.meta b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/BaseEnumerator.ts.meta new file mode 100644 index 0000000..16fea9a --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/BaseEnumerator.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "4084537c-c7e8-4d47-b283-39be77ef9685", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/Core/CoroutineExecutor.ts b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/CoroutineExecutor.ts new file mode 100644 index 0000000..685b89f --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/CoroutineExecutor.ts @@ -0,0 +1,95 @@ +import { EnumeratorExecutor } from "./EnumeratorExecutor"; + +export class CoroutineExecutor { + private static _instance: CoroutineExecutor; + static get instance() { + return CoroutineExecutor._instance = CoroutineExecutor._instance || new CoroutineExecutor(); + } + + private _executors: EnumeratorExecutor[] = []; + private _nextExecutors: EnumeratorExecutor[] = []; + private _isRunning: boolean = false; + private _cleanRemoved: boolean = false; + private _scheduler: cc.Scheduler; + + constructor() { + this._scheduler = cc.director.getScheduler(); + this._scheduler.enableForTarget(this); + this._scheduler.scheduleUpdate(this, 0, true); + } + + StartCoroutine(executor: EnumeratorExecutor) { + executor.next(0); + //TODO: 這邊要考量next後馬上接BaseEnumerator/Iterator的情形 + + if (!this._isRunning) { + this._executors.push(executor); + + if (this._scheduler.isTargetPaused(this)) { + this._scheduler.resumeTarget(this); + } + } else { + this._nextExecutors.push(executor); + } + } + + StopCoroutineBy(target: any) { + if (!target) return; + + for (let r of this._executors) { + if (target === r.target) { + r.Stop(); + } + } + + for (let r of this._nextExecutors) { + if (target === r.target) { + r.Stop(); + } + } + } + + update(delta: number) { + if (this._nextExecutors.length) { + this._executors.push(...this._nextExecutors); + this._nextExecutors.length = 0; + } + + if (this._cleanRemoved) { + // 移除[doneFlag=true]的協程 + let index = this._executors.length; + while (index--) { + let r = this._executors[index]; + if (r.doneFlag) { + this._executors.splice(index, 1); + } + } + this._cleanRemoved = false; + } + + if (this._executors.length == 0) { + if (CC_DEBUG) { + cc.log("[CoroutineV2] All coroutines done"); + } + this._scheduler.pauseTarget(this); + return; + } + + this._isRunning = true; + + // 執行協程 + for (let r of this._executors) { + if (r.doneFlag || r.pauseFlag || r.childFlag) + { + if (r.doneFlag) { + this._cleanRemoved = true; + } + continue; + } + + r.next(delta); + } + + this._isRunning = false; + } +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/Core/CoroutineExecutor.ts.meta b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/CoroutineExecutor.ts.meta new file mode 100644 index 0000000..4a00e69 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/CoroutineExecutor.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "f25b1e42-90d8-4fc0-9925-6e7e92296d57", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/Core/EnumeratorExecutor.ts b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/EnumeratorExecutor.ts new file mode 100644 index 0000000..2506711 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/EnumeratorExecutor.ts @@ -0,0 +1,169 @@ +import { IEnumeratorV2Started } from "../IEnumeratorV2"; +import { BaseEnumerator } from "./BaseEnumerator"; +import { SingleEnumerator } from "./SingleEnumerator"; + +export class EnumeratorExecutor implements IEnumeratorV2Started { + public Current: any; + + public target: any; + public pauseFlag: boolean; + public doneFlag: boolean; + public childFlag: boolean; + public asyncFlag: boolean; + public error: any; + + private _executor: EnumeratorExecutor; + private _enumerator: BaseEnumerator; + + constructor(enumerator: BaseEnumerator, target: any) { + this.target = target; + this._enumerator = enumerator; + } + + next(delta?: any): IteratorResult { + if (this._executor && this._executor.doneFlag) { + this._executor = null; + } + + if (this.doneFlag || (!this._enumerator && !this._executor)) { + this.doneFlag = true; + return { done: true, value: undefined }; + } + + if (this.asyncFlag || this.pauseFlag) return { done: false, value: undefined }; + + let result: IteratorResult; + + if (this._executor) { + result = this._executor.next(delta); + this.Current = this._executor.Current; + if (this._executor.doneFlag) { + this._executor = null; + } else { + result.done = false; + return result; + } + } + + if (!this._enumerator) { + this.doneFlag = true; + return { done: true, value: undefined }; + } + + try { + result = this._enumerator.next(delta); + let value = result.value; + let done = result.done; + + if (value) { + // Iterator + if (typeof value[Symbol.iterator] === 'function') { + value = new SingleEnumerator(>value); + } + + if (value instanceof BaseEnumerator) { + if (!done) { + BaseEnumerator.getLastEnumerator(value).nextEnumerator = this._enumerator; + } + this._enumerator = value; + result = this._enumerator.next(delta); + value = result.value; + done = result.done; + + if (value) { + // Iterator again + if (typeof value[Symbol.iterator] === 'function') { + value = new SingleEnumerator(>value); + } + + if (value instanceof BaseEnumerator) { + if (!done) { + BaseEnumerator.getLastEnumerator(value).nextEnumerator = this._enumerator; + } + this._enumerator = value; + result.done = false; + done = false; + } + } + } + + if (value instanceof EnumeratorExecutor) { + if (done) { + this._enumerator = this._enumerator.nextEnumerator; + } + value.childFlag = true; + result.done = false; + done = false; + this._executor = value; + } else if (Promise.resolve(value) === value) { + this.asyncFlag = true; + result.done = false; + done = false; + (>value) + .then(v => { + this.asyncFlag = false; + this.Current = v; + if (done) { + this._enumerator = this._enumerator.nextEnumerator; + } + }) + .catch(e => { + this.asyncFlag = false; + this.doneFlag = true; + this._enumerator = null; + this.error = e; + if (e instanceof Error) { + cc.error(e.stack); + } else { + cc.error(`Error: ${JSON.stringify(e)}`); + } + }); + } + + this.Current = value; + } + + if (done) { + this._enumerator = this._enumerator.nextEnumerator; + if (this._enumerator) { + result.done = false; + } + } + } + catch (e) + { + this.doneFlag = true; + this.error = e; + if (e instanceof Error) { + cc.error(e.stack); + } else { + cc.error(`Error: ${JSON.stringify(e)}`); + } + result = { done: true, value: e }; + } + + return result; + } + + Stop(): void { + this.doneFlag = true; + if (this._executor) { + this._executor.Stop(); + } + } + + Pause(): void { + this.pauseFlag = true; + if (this._executor) { + this._executor.Pause(); + } + } + + Resume(): void { + this.pauseFlag = false; + if (this._executor) { + this._executor.Resume(); + } + } + +} diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/Core/EnumeratorExecutor.ts.meta b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/EnumeratorExecutor.ts.meta new file mode 100644 index 0000000..4566fa5 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/EnumeratorExecutor.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "91cb70ed-e6f9-4ce0-b7c5-1720087b3bd7", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/Core/ParallelEnumerator.ts b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/ParallelEnumerator.ts new file mode 100644 index 0000000..5aa146e --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/ParallelEnumerator.ts @@ -0,0 +1,46 @@ +import { BaseEnumerator } from "./BaseEnumerator"; +import { EnumeratorExecutor } from "./EnumeratorExecutor"; +import { SingleEnumerator } from "./SingleEnumerator"; + +export class ParallelEnumerator extends BaseEnumerator { + private _executors: EnumeratorExecutor[] = []; + + constructor(iterators: Iterator[]) { + super(); + if (iterators && iterators.length) { + for (let iterator of iterators) { + if (iterator instanceof BaseEnumerator) { + this._executors.push(new EnumeratorExecutor(iterator, null)); + } else { + this._executors.push(new EnumeratorExecutor(new SingleEnumerator(iterator), null)); + } + } + } + } + + next(value?: any): IteratorResult { + if (this._executors.length) { + // 先移除[doneFlag=true]協程 + let index = this._executors.length; + while (index--) { + let r = this._executors[index]; + if (r.doneFlag) { + this._executors.splice(index, 1); + } + } + + if (this._executors.length == 0) { + return { done: true, value: undefined }; + } + + // 執行協程 + for (let r of this._executors) { + r.next(value); + } + + return { done: false, value: undefined }; + } + + return { done: true, value: undefined }; + } +} diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/Core/ParallelEnumerator.ts.meta b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/ParallelEnumerator.ts.meta new file mode 100644 index 0000000..3b9faf8 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/ParallelEnumerator.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "017ebc9a-5152-4f94-bbaf-e3b914e87b41", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/Core/SingleEnumerator.ts b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/SingleEnumerator.ts new file mode 100644 index 0000000..10eae34 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/SingleEnumerator.ts @@ -0,0 +1,18 @@ +import { BaseEnumerator } from "./BaseEnumerator"; + +export class SingleEnumerator extends BaseEnumerator { + private _iterator: Iterator; + + constructor(iterator: Iterator) { + super(); + this._iterator = iterator; + } + + next(value?: any): IteratorResult { + if (!this._iterator) { + return { done: true, value: undefined }; + } + + return this._iterator.next(value); + } +} diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/Core/SingleEnumerator.ts.meta b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/SingleEnumerator.ts.meta new file mode 100644 index 0000000..4a9da9f --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/SingleEnumerator.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "c439d019-2da8-48b8-a65b-bff928d0fda8", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/Core/WaitTimeEnumerator.ts b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/WaitTimeEnumerator.ts new file mode 100644 index 0000000..04d7b2e --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/WaitTimeEnumerator.ts @@ -0,0 +1,21 @@ +import { BaseEnumerator } from "./BaseEnumerator"; + +export class WaitTimeEnumerator extends BaseEnumerator { + private _seconds: number; + + constructor(seconds: number) { + super(); + this._seconds = seconds; + } + + next(value?: any): IteratorResult { + let delta = value as number; + this._seconds -= delta; + + if (this._seconds <= 0) { + return { done: true, value: 0 }; + } else { + return { done: false, value: this._seconds }; + } + } +} diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/Core/WaitTimeEnumerator.ts.meta b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/WaitTimeEnumerator.ts.meta new file mode 100644 index 0000000..11e0155 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/Core/WaitTimeEnumerator.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "a3038e6f-1bb4-4aff-a686-b69209df3592", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/CoroutineExample.ts b/assets/Script/Engine/CatanEngine/CoroutineV2/CoroutineExample.ts new file mode 100644 index 0000000..ef9e978 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/CoroutineExample.ts @@ -0,0 +1,199 @@ +import { CoroutineV2 } from "./CoroutineV2"; +import { IEnumeratorV2Started } from "./IEnumeratorV2"; + +const {ccclass, property} = cc._decorator; + +class A { + private numbers: number[] = [1, 2]; + private index = 0; + + [Symbol.iterator](): IterableIterator { + return this; + } + + next(value?: any): IteratorResult { + if (this.index < this.numbers.length) { + let value = this.numbers[this.index++]; + cc.log(`A=> ${value}`); + return { + done: false, + value: value + }; + } + + return { done: true, value: undefined }; + } +} + +@ccclass +export default class CoroutineExample extends cc.Component { + private _routine: IEnumeratorV2Started; + private _obj: Object = { "a": true }; + private _obj2: Object = { "b": true }; + + private _num: number = 3; + + button1Clicked() { + // this._routine = CoroutineV2 + // .Parallel(this.Coroutine1(1, 3), this.Coroutine1(4, 6)) + // .ThenWaitTime(2) + // .Then(this.Coroutine1(7, 9)) + // .ThenWaitTime(2) + // .ThenAction(() => cc.log("action callback 1")) + // .ThenWaitTime(2) + // .ThenAction(this.actionCallback) + // //.Start(this); + // .Start(this); + // this._routine = CoroutineV2.Single(this.FunA()).Start(this); + + this._routine = CoroutineV2.Single(this.Test1_1()).Start(this); + // this._routine = CoroutineV2.Single(this.Test2_1()).Start(this); + } + + *Test1_1() { + yield null; + yield *this.Test1_2(); + // CoroutineV2.Single(this.Test1_3()).Start(this); + yield this.Test1_3(); + } + + *Test1_2() { + yield null; + } + + *Test1_3() { + yield this.Test1_3_1(); + yield CoroutineV2.Single(this.Test1_4()).Start(this._obj); + // yield CoroutineV2.Single(this.Test1_4()); //.Start(this); + // yield *this.Test1_4(); + cc.log("main wait 3"); + yield CoroutineV2.WaitTime(2); + cc.log("done"); + } + + *Test1_3_1() { + yield this.Test1_3_2(); + yield CoroutineV2.WaitTime(1); + cc.log("Test1_3_1.1"); + yield CoroutineV2.WaitTime(1); + cc.log("Test1_3_1.2"); + } + + *Test1_3_2() { + yield this.Test1_3_3(); + yield CoroutineV2.WaitTime(1); + cc.log("Test1_3_2.1"); + yield CoroutineV2.WaitTime(1); + cc.log("Test1_3_2.2"); + yield CoroutineV2.WaitTime(1); + cc.log("Test1_3_2.3"); + } + + *Test1_3_3() { + yield CoroutineV2.WaitTime(1); + cc.log("Test1_3_3.1"); + yield CoroutineV2.WaitTime(1); + cc.log("Test1_3_3.2"); + yield CoroutineV2.WaitTime(1); + cc.log("Test1_3_3.3"); + } + + *Test1_4() { + this._num++; + cc.log(`WaitTime2 ${this._num}`); + yield CoroutineV2.WaitTime(2).Start(this._obj2); + this._num++; + cc.log(`WaitTime2 ${this._num}`); + yield CoroutineV2.WaitTime(2).Start(this._obj2); + this._num++; + cc.log(`WaitTime2 ${this._num}`); + } + + *Test2_1() { + cc.log("111"); + CoroutineV2.Single(this.Test2_2()).Start(this); + cc.log("333"); + } + + *Test2_2() { + cc.log("222"); + return; + } + + button2Clicked() { + // this._routine && this._routine.Stop(); + if (this._obj2) { + CoroutineV2.StopCoroutinesBy(this._obj2); + this._obj2 = null; + return; + } + if (this._obj) { + CoroutineV2.StopCoroutinesBy(this._obj); + this._obj = null; + return; + } + + CoroutineV2.StopCoroutinesBy(this); + + } + + button3Clicked() { + // CoroutineV2.StopCoroutinesBy(this); + this._routine && this._routine.Pause(); + } + + button4Clicked() { + // CoroutineV2.StopCoroutinesBy(this); + this._routine && this._routine.Resume(); + } + + *Coroutine1(start:number, end: number) + { + for (let i = start; i <= end; i++) { + // yield CoroutineV2.WaitTime(1).Start(); // Start()可以省略, 會由外層啟動 + // yield CoroutineV2.WaitTime(1).Start(this); // target也可以省略, 由外層的target控制 + + yield CoroutineV2.WaitTime(1).Start(); + cc.log(`C1 => ${i}`); + + // 嵌套 + yield CoroutineV2 + .WaitTime(1) + .ThenParallel( + // 再嵌套 + CoroutineV2.Action(() => cc.log("start parallel")), + this.Coroutine2(10, 2), + this.Coroutine2(20, 2), + new A()) + .ThenAction(() => cc.log("end parallel")) + .Start(); + + // Promise + yield this.loadItemAsync("settings.json"); + } + } + + *Coroutine2(num: number, repeat: number) + { + for (let i = 0; i < repeat; i++) { + //yield CoroutineV2.WaitTime(2); + yield 0; + cc.log(`C2: ${num}`); + // yield CoroutineV2.WaitTime(1); + } + } + + actionCallback() { + cc.log("action callback 2"); + } + + loadItemAsync(id: string): Promise<{id: string}> { + return new Promise((resolve) => { + cc.log('loading item start:', id); + setTimeout(() => { + resolve({ id: id }); + cc.log('loading item done:', id); + }, 3000); + }); + } +} diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/CoroutineExample.ts.meta b/assets/Script/Engine/CatanEngine/CoroutineV2/CoroutineExample.ts.meta new file mode 100644 index 0000000..34ad818 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/CoroutineExample.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "dfd32c11-76f6-4e38-9272-1d7966d1ef3c", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/CoroutineV2.ts b/assets/Script/Engine/CatanEngine/CoroutineV2/CoroutineV2.ts new file mode 100644 index 0000000..5606b57 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/CoroutineV2.ts @@ -0,0 +1,75 @@ +import { IEnumeratorV2, IEnumeratorV2Started } from "./IEnumeratorV2"; +import { BaseEnumerator } from "./Core/BaseEnumerator"; +import { SingleEnumerator } from "./Core/SingleEnumerator"; +import { ParallelEnumerator } from "./Core/ParallelEnumerator"; +import { WaitTimeEnumerator } from "./Core/WaitTimeEnumerator"; +import { ActionEnumerator } from "./Core/ActionEnumerator"; +import { CoroutineExecutor } from "./Core/CoroutineExecutor"; + +export module CoroutineV2 { + /** + * 啟動一般協程 + */ + export function StartCoroutine(iterator: Iterator, target?: any): IEnumeratorV2Started { + return Single(iterator).Start(target); + } + + /** + * 依據IEnumeratorV2.Start(target)綁定的目標, 來停止協程 + * @param target + */ + export function StopCoroutinesBy(target: any) { + CoroutineExecutor.instance.StopCoroutineBy(target); + } + + /** + * 單一協程 + */ + export function Single(iterator: Iterator): IEnumeratorV2 { + if (iterator instanceof BaseEnumerator) { + return iterator; + } else { + return new SingleEnumerator(iterator); + } + } + + /** + * 平行協程 + */ + export function Parallel(...iterators: Iterator[]): IEnumeratorV2 { + return new ParallelEnumerator(iterators); + } + + /** + * 序列協程 + */ + export function Serial(...iterators: Iterator[]): IEnumeratorV2 { + let [iterator, ...others] = iterators; + if (iterator instanceof BaseEnumerator) { + return iterator.ThenSerial(...others); + } else { + return new SingleEnumerator(iterator).ThenSerial(...others); + } + } + + /** + * 執行方法協程 + * @param action 方法 + * @param delaySeconds 延遲秒數 + */ + export function Action(action: Function, delaySeconds?: number): IEnumeratorV2 { + if (delaySeconds > 0) { + return new WaitTimeEnumerator(delaySeconds).Then(new ActionEnumerator(action)); + } else { + return new ActionEnumerator(action); + } + } + + /** + * 等待時間協程 + * @param seconds 秒數 + */ + export function WaitTime(seconds: number): IEnumeratorV2 { + return new WaitTimeEnumerator(seconds); + } +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/CoroutineV2.ts.meta b/assets/Script/Engine/CatanEngine/CoroutineV2/CoroutineV2.ts.meta new file mode 100644 index 0000000..6899064 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/CoroutineV2.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "fc38e505-bd37-44c3-9e0a-fd463bb88c51", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/IEnumeratorV2.ts b/assets/Script/Engine/CatanEngine/CoroutineV2/IEnumeratorV2.ts new file mode 100644 index 0000000..c684c4d --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/IEnumeratorV2.ts @@ -0,0 +1,16 @@ +export interface IEnumeratorV2 extends Iterator { + Start(target?: any): IEnumeratorV2Started; + Then(iterator: Iterator): IEnumeratorV2; + ThenSerial(...iterators: Iterator[]): IEnumeratorV2; + ThenParallel(...iterators: Iterator[]): IEnumeratorV2; + ThenAction(action: Function, delaySeconds?: number): IEnumeratorV2; + ThenWaitTime(seconds: number): IEnumeratorV2; +} + +export interface IEnumeratorV2Started { + readonly Current: any; + + Pause(): void; + Resume(): void; + Stop(): void; +} diff --git a/assets/Script/Engine/CatanEngine/CoroutineV2/IEnumeratorV2.ts.meta b/assets/Script/Engine/CatanEngine/CoroutineV2/IEnumeratorV2.ts.meta new file mode 100644 index 0000000..c19bee1 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/CoroutineV2/IEnumeratorV2.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "df3ab07d-3d2b-4552-b454-29b95223ea85", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/NetManagerV2.meta b/assets/Script/Engine/CatanEngine/NetManagerV2.meta new file mode 100644 index 0000000..70c9a06 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NetManagerV2.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "6f870efd-e869-4415-9cf2-138ab667cd5d", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/NetManagerV2/Core.meta b/assets/Script/Engine/CatanEngine/NetManagerV2/Core.meta new file mode 100644 index 0000000..7e229ac --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NetManagerV2/Core.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "5e6c027f-ce4b-47fa-968c-f3bb6059ad81", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/NetManagerV2/Core/INetConnector.ts b/assets/Script/Engine/CatanEngine/NetManagerV2/Core/INetConnector.ts new file mode 100644 index 0000000..a9d801b --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NetManagerV2/Core/INetConnector.ts @@ -0,0 +1,13 @@ +import { Action } from "../../CSharp/System/Action"; +import { INetRequest } from "./INetRequest"; +import { INetResponse } from "./INetResponse"; + +export interface INetConnector { + readonly OnDataReceived: Action>; + readonly OnDisconnected: Action; + readonly IsConnected: boolean; + + SendAsync(req: INetRequest): Iterator; + Send(req: INetRequest); + Logout(); +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/NetManagerV2/Core/INetConnector.ts.meta b/assets/Script/Engine/CatanEngine/NetManagerV2/Core/INetConnector.ts.meta new file mode 100644 index 0000000..8f7c43c --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NetManagerV2/Core/INetConnector.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "f97991b5-0da6-4220-ab29-13c8f8f7e405", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/NetManagerV2/Core/INetRequest.ts b/assets/Script/Engine/CatanEngine/NetManagerV2/Core/INetRequest.ts new file mode 100644 index 0000000..a49f8f9 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NetManagerV2/Core/INetRequest.ts @@ -0,0 +1,12 @@ +import { INetResponse } from "./INetResponse"; + +export interface INetRequest { + readonly Method: string; + readonly MethodBack: string; + + Data: TRequest; + Result: INetResponse; + + SendAsync(): Iterator; + Send(); +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/NetManagerV2/Core/INetRequest.ts.meta b/assets/Script/Engine/CatanEngine/NetManagerV2/Core/INetRequest.ts.meta new file mode 100644 index 0000000..af38d71 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NetManagerV2/Core/INetRequest.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "339fcf27-bdb9-4b8f-ae18-dd54c9500145", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/NetManagerV2/Core/INetResponse.ts b/assets/Script/Engine/CatanEngine/NetManagerV2/Core/INetResponse.ts new file mode 100644 index 0000000..502b4cf --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NetManagerV2/Core/INetResponse.ts @@ -0,0 +1,6 @@ +export interface INetResponse { + readonly Method: string; + readonly Status: number; + readonly Data: TResponse; + readonly IsValid: boolean; +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/NetManagerV2/Core/INetResponse.ts.meta b/assets/Script/Engine/CatanEngine/NetManagerV2/Core/INetResponse.ts.meta new file mode 100644 index 0000000..cf97e1f --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NetManagerV2/Core/INetResponse.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "c4cb0cd4-b98c-4f8e-b1e6-ac3b51281b28", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/NetManagerV2/Examples.meta b/assets/Script/Engine/CatanEngine/NetManagerV2/Examples.meta new file mode 100644 index 0000000..a8363dc --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NetManagerV2/Examples.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "94e55972-723c-4dab-9ebc-870bd5043fca", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/NetManagerV2/Examples/NetTester.ts b/assets/Script/Engine/CatanEngine/NetManagerV2/Examples/NetTester.ts new file mode 100644 index 0000000..92331b1 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NetManagerV2/Examples/NetTester.ts @@ -0,0 +1,69 @@ +import { CoroutineV2 } from "../../CoroutineV2/CoroutineV2"; +import { INetResponse } from "../Core/INetResponse"; +import { NetConnector } from "../NetConnector"; +import { NetManager } from "../NetManager"; +import { Slot1_SpinRequestExample } from "./Slot1_SpinRequestExample"; + +const {ccclass, property} = cc._decorator; + +@ccclass +export default class NetTester extends cc.Component { + + onConnectClicked() { + CoroutineV2.StartCoroutine(this.ConnectAsync()); + } + + *ConnectAsync() { + if (!NetManager.HasInit) { + let conn = new NetConnector("192.168.7.165", 9005); + conn.OnDataReceived.AddCallback(this.OnNetDataReceived, this); + conn.OnDisconnected.AddCallback(this.OnNetDisconnected, this); + conn.OnLoadUIMask.AddCallback(this.OnLoadUIMask, this); + + NetManager.Initialize(conn); + } + + cc.log("連線中..."); + yield NetManager.ConnectAsync(); // 同個connector要再次連線, 可以不用叫CasinoNetManager.Initialize(), 但要先叫CasinoNetManager.Disconnect() + cc.log(`連線狀態: ${NetManager.IsConnected}`); + } + + onDisconnectClicked() { + cc.log("中斷連線中..."); + NetManager.Disconnect(); // 中斷連線 + } + + onSendMessageClicked1() { + cc.log("發送訊息(不使用協程)"); + let req = new Slot1_SpinRequestExample(401); + req.Send(); + // CasinoNetManager.Send(req); + } + + onSendMessageClicked2() { + CoroutineV2.StartCoroutine(this.SendAsync()); + } + + *SendAsync() { + cc.log("發送訊息中(使用協程)..."); + let req = new Slot1_SpinRequestExample(399); + yield req.SendAsync(); + // yield CasinoNetManager.SendAsync(req); + + let resp = req.Result; + cc.log(`發送協程完畢, Server回應: ${resp.Method}(${JSON.stringify(resp.Data)}), 狀態: ${resp.Status}`); + // cc.log(`使用介面資料: ${resp.Data.slot}`); + } + + private OnNetDisconnected() { + cc.log("[事件] 收到連線中斷事件"); + } + + private OnNetDataReceived(resp: INetResponse) { + cc.log(`[事件] 收到server呼叫: ${resp.Method}(${JSON.stringify(resp.Data)}), 狀態: ${resp.Status}`); + } + + private OnLoadUIMask(value: boolean) { + cc.log(`[事件] LoadUIMask: ${value}`); + } +} diff --git a/assets/Script/Engine/CatanEngine/NetManagerV2/Examples/NetTester.ts.meta b/assets/Script/Engine/CatanEngine/NetManagerV2/Examples/NetTester.ts.meta new file mode 100644 index 0000000..baf7502 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NetManagerV2/Examples/NetTester.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "0cb7df7a-d0e7-4ce1-832e-4583cf3385e5", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/NetManagerV2/Examples/Slot1_SpinRequestExample.ts b/assets/Script/Engine/CatanEngine/NetManagerV2/Examples/Slot1_SpinRequestExample.ts new file mode 100644 index 0000000..60e9bac --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NetManagerV2/Examples/Slot1_SpinRequestExample.ts @@ -0,0 +1,37 @@ +import { NetRequest } from "../NetRequest"; + +// 送給server的結構 +interface Request { + pay: number; +} + +// server回應的結構 +interface Response { + pay: [[number, number]]; + /**拉霸結果 */ + slot: number[]; + get: any[]; +} + +// class Account_CreateRequest extends CasinoRequest { // 也可以是基本類或any, 但不建議用any, 使用介面ts才會有提示 +export class Slot1_SpinRequestExample extends NetRequest { + get Method(): string { + return "slot1.spin"; + } + + // MethodBack預設回傳Method, 不一樣才需要覆寫 + // get MethodBack(): string { + // return "slot1.freespin"; + // } + + constructor(totalBet: number) { + super(); + + // 原本的SingleValue拿掉, 統一使用Data來存送出結構 + + // this.Data = 2; + this.Data = { + pay: totalBet, + }; + } +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/NetManagerV2/Examples/Slot1_SpinRequestExample.ts.meta b/assets/Script/Engine/CatanEngine/NetManagerV2/Examples/Slot1_SpinRequestExample.ts.meta new file mode 100644 index 0000000..f832224 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NetManagerV2/Examples/Slot1_SpinRequestExample.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "1af9e6af-3dc3-4d02-8b24-481adc07932a", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/NetManagerV2/NetConfig.ts b/assets/Script/Engine/CatanEngine/NetManagerV2/NetConfig.ts new file mode 100644 index 0000000..f367907 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NetManagerV2/NetConfig.ts @@ -0,0 +1,4 @@ +export default class NetConfig { + /**是否顯示RPC接送JSON的LOG */ + public static ShowServerLog: boolean = true; +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/NetManagerV2/NetConfig.ts.meta b/assets/Script/Engine/CatanEngine/NetManagerV2/NetConfig.ts.meta new file mode 100644 index 0000000..f928ea3 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NetManagerV2/NetConfig.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "c7f5f6a9-94fd-4f5f-9f0a-545cd14edca9", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/NetManagerV2/NetConnector.ts b/assets/Script/Engine/CatanEngine/NetManagerV2/NetConnector.ts new file mode 100644 index 0000000..83fe940 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NetManagerV2/NetConnector.ts @@ -0,0 +1,259 @@ +import { BaseEnumerator } from "../CoroutineV2/Core/BaseEnumerator"; +import { Action } from "../CSharp/System/Action"; +import { Encoding } from "../CSharp/System/Text/Encoding"; +import { INetRequest } from "./Core/INetRequest"; +import { INetResponse } from "./Core/INetResponse"; +import NetConfig from "./NetConfig"; + +export class NetConnector { + readonly OnDataReceived: Action> = new Action>(); + readonly OnDisconnected: Action = new Action(); + readonly OnLoadUIMask: Action = new Action(); + + get IsConnected() { + return this._ws && this._ws.readyState === WebSocket.OPEN; + } + + private _host: string; + private _ws: WebSocket; + private _waitings: WsRequestEnumerator[] = []; + + constructor(host: string, port: number/*, ip: string*/) { + let checkHttp: string = ""; + let index: number = host.indexOf("https://"); + if (index != -1) { + checkHttp = "https"; + host = host.replace("https://", ""); + } else { + checkHttp = window.location.href.substring(0, 5); + host = host.replace("http://", ""); + } + if (CC_DEBUG) { + cc.log("[事件]checkHttp=", checkHttp, host, port); + } + if (checkHttp != "https") { + //this._host = `ws://${host}:${port}/?ip=${ip}`; + this._host = `ws://${host}:${port}` + } + else { + //this._host = `wss://${host}:${port}/?ip=${ip}`; + this._host = `wss://${host}:${port}`; + } + } + + ConnectAsync() { + if (this._ws) { + throw new Error("請先執行CasinoNetManager.Disconnect()中斷連線"); + } + if (cc.sys.isNative && cc.sys.os == cc.sys.OS_ANDROID && this._host.indexOf("wss") !== -1) { + let cacert = cc.url.raw('resources/cacert.cer'); + if (cc.loader.md5Pipe) { + cacert = cc.loader.md5Pipe.transformURL(cacert) + } + //@ts-ignore + this._ws = new WebSocket(this._host, null, cacert) + } else { + //@ts-ignore + this._ws = new WebSocket(this._host); + } + + this._ws.binaryType = 'arraybuffer'; + this._ws.onopen = this.OnWebSocketOpen.bind(this); + this._ws.onmessage = this.OnWebSocketMessage.bind(this); + this._ws.onclose = this.OnWebSocketClose.bind(this); + + return new WsConnectEnumerator(this._ws); + } + + Send(req: INetRequest) { + if (!this.IsConnected) return; + + let json = [req.Method]; + if (req.Data != null && req.Data != undefined && req.Data != NaN) { + json[1] = req.Data; + } + + if (CC_DEBUG && NetConfig.ShowServerLog) { + if (req.Data != null && req.Data != undefined && req.Data != NaN) { + cc.log(`[RPC] 傳送server資料: ${req.Method}(${JSON.stringify(req.Data)})`); + } else { + cc.log(`[RPC] 傳送server資料: ${req.Method}()`); + } + } + + let str = JSON.stringify(json); + if (str.length > 65535) { + throw new Error('要傳的資料太大囉'); + } + + let strary = Encoding.UTF8.GetBytes(str); + let buffer = new Uint8Array(4 + strary.byteLength); + let u16ary = new Uint16Array(buffer.buffer, 0, 3); + u16ary[0] = strary.byteLength; + buffer[3] = 0x01; + buffer.set(strary, 4); + + this._ws.send(buffer); + } + + SendAsync(req: INetRequest, mask: boolean) { + let iterator = new WsRequestEnumerator(req); + if (!this.IsConnected) { + iterator.SetResponse(ErrorResponse); + } else { + this._waitings.push(iterator); + if (mask) { + this.OnLoadUIMask.DispatchCallback(true); + } + this.Send(req); + } + return iterator; + }; + + Disconnect() { + this.WebSocketEnded(); + } + + private WebSocketEnded() { + if (!this._ws) return; + + this._ws.close(); + this._ws.onopen = null; + this._ws.onmessage = null; + this._ws.onclose = () => { }; + this._ws = null; + + this.CleanWaitings(); + this.OnDisconnected.DispatchCallback(); + } + + private CleanWaitings() { + for (let w of this._waitings) { + w.SetResponse(ErrorResponse); + this.OnLoadUIMask.DispatchCallback(false); + } + this._waitings.length = 0; + } + + private OnWebSocketOpen(e: Event) { + if (CC_DEBUG) { + cc.log(`[RPC] ${this._host} Connected.`); + } + } + + private OnWebSocketMessage(e: MessageEvent) { + if (e.data instanceof ArrayBuffer) { + this.ParseRpcMessage(e.data); + } else if (e.data instanceof Blob) { + let reader = new FileReader(); + reader.onload = (e) => { this.ParseRpcMessage(reader.result); reader.onload = null; } + reader.readAsArrayBuffer(e.data); + } else { + throw new Error(`未知的OnWebSocketMessage(e.data)類型: ${e.data}`); + } + } + + private ParseRpcMessage(buffer: ArrayBuffer) { + let startIndex = 0, byteLength = buffer.byteLength; + while (startIndex + 4 < byteLength) { + let strlen = new DataView(buffer, startIndex, 3).getUint16(0, true); + let str = Encoding.UTF8.GetString(new Uint8Array(buffer, startIndex + 4, strlen)); + startIndex += strlen + 4; + + try { + let json = JSON.parse(str); + let method = json[0]; + let status = json[1][0]; + let data = json[1][1]; + + let resp = >{ + Method: method, + Status: status, + Data: data, + IsValid: method && status === 0 + }; + + if (CC_DEBUG && NetConfig.ShowServerLog) { + if (data) { + cc.log(`[RPC] 收到server呼叫:(${resp.Status}): ${resp.Method}(${JSON.stringify(resp.Data)})`); + } else { + cc.log(`[RPC] 收到server呼叫:(${resp.Status}): ${resp.Method}()`); + } + } + + let dispatch = true; + for (let i = 0, len = this._waitings.length; i < len; i++) { + let w = this._waitings[i]; + if (w.MethodBack === resp.Method) { + dispatch = false; + this._waitings.splice(i, 1); + w.SetResponse(resp); + this.OnLoadUIMask.DispatchCallback(false); + break; + } + } + + if (dispatch) { + this.OnDataReceived.DispatchCallback(resp); + } + } + catch + { + throw new Error(`[RPC] 無法解析Server回應: ${str}`); + } + } + } + + private OnWebSocketClose(e: CloseEvent) { + this.WebSocketEnded(); + } +} + +const ErrorResponse: INetResponse = { + Status: -1, + Method: "", + Data: {}, + IsValid: false, +}; + +class WsConnectEnumerator extends BaseEnumerator { + private _ws: WebSocket; + + constructor(ws: WebSocket) { + super(); + this._ws = ws; + } + + next(value?: any): IteratorResult { + return { + done: this._ws.readyState === WebSocket.OPEN || this._ws.readyState === WebSocket.CLOSED, + value: undefined + }; + } +} + +class WsRequestEnumerator extends BaseEnumerator { + readonly MethodBack: string; + + private _req: INetRequest; + private _done: boolean = false; + + constructor(req: INetRequest) { + super(); + + this._req = req; + this.MethodBack = req.MethodBack; + } + + SetResponse(resp: INetResponse) { + this._req.Result = resp; + this._done = true; + } + + next(value?: any): IteratorResult { + return { + done: this._done, + value: undefined + }; + } +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/NetManagerV2/NetConnector.ts.meta b/assets/Script/Engine/CatanEngine/NetManagerV2/NetConnector.ts.meta new file mode 100644 index 0000000..1e0f762 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NetManagerV2/NetConnector.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "221e1688-cc40-450d-9248-464978540a85", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/NetManagerV2/NetManager.ts b/assets/Script/Engine/CatanEngine/NetManagerV2/NetManager.ts new file mode 100644 index 0000000..b3250de --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NetManagerV2/NetManager.ts @@ -0,0 +1,50 @@ +import { INetRequest } from "./Core/INetRequest"; +import { NetConnector } from "./NetConnector"; + +export class NetManager { + static get IsConnected() { return this._connector && this._connector.IsConnected; } + static get HasInit() { return this._connector != null; } + + private static _connector: NetConnector; + + static Initialize(connector: NetConnector) { + this._connector = connector; + } + + static ConnectAsync() { + this.CheckConnector(); + return this._connector.ConnectAsync(); + } + + /** + * 斷線 + */ + static Disconnect() { + this.CheckConnector(); + this._connector.Disconnect(); + } + + /** + * 傳送資料給Server, 不等待回應 + * @param req + */ + static Send(req: INetRequest) { + this.CheckConnector(); + this._connector.Send(req); + } + + /** + * 傳送資料給Server, 並等待回應 + * @param req + */ + static SendAsync(req: INetRequest,mask:boolean) { + this.CheckConnector(); + return this._connector.SendAsync(req,mask); + } + + private static CheckConnector() + { + if (!this._connector) throw new Error("請先呼叫CasinoNetManager.Initialize()初始化connector"); + } + +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/NetManagerV2/NetManager.ts.meta b/assets/Script/Engine/CatanEngine/NetManagerV2/NetManager.ts.meta new file mode 100644 index 0000000..09fb6f6 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NetManagerV2/NetManager.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "7c3e375d-3672-42e7-8a45-dd5ecf9d5fe8", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/NetManagerV2/NetRequest.ts b/assets/Script/Engine/CatanEngine/NetManagerV2/NetRequest.ts new file mode 100644 index 0000000..157f605 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NetManagerV2/NetRequest.ts @@ -0,0 +1,21 @@ +import { INetRequest } from "./Core/INetRequest"; +import { NetManager } from "./NetManager"; + +export abstract class NetRequest implements INetRequest { + abstract get Method(): string; + + get MethodBack(): string { + return this.Method; + } + + Data: TResquest; + Result: import("./Core/INetResponse").INetResponse; + + SendAsync(mask: boolean = false): Iterator { + return NetManager.SendAsync(this, mask); + } + + Send() { + NetManager.Send(this); + } +} diff --git a/assets/Script/Engine/CatanEngine/NetManagerV2/NetRequest.ts.meta b/assets/Script/Engine/CatanEngine/NetManagerV2/NetRequest.ts.meta new file mode 100644 index 0000000..487585e --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NetManagerV2/NetRequest.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "36534597-4273-48e8-bbeb-8dde4857d26f", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/NoSleep.ts b/assets/Script/Engine/CatanEngine/NoSleep.ts new file mode 100644 index 0000000..b51ebc9 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NoSleep.ts @@ -0,0 +1,81 @@ +export class NoSleep { + static oldIOS: boolean = typeof navigator !== 'undefined' && parseFloat(('' + (/CPU.*OS ([0-9_]{3,4})[0-9_]{0,1}|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0, ''])[1]).replace('undefined', '3_2').replace('_', '.').replace('_', '')) < 10 && !window["MSStream"]; + static webm: string = 'data:video/webm;base64,GkXfo0AgQoaBAUL3gQFC8oEEQvOBCEKCQAR3ZWJtQoeBAkKFgQIYU4BnQI0VSalmQCgq17FAAw9CQE2AQAZ3aGFtbXlXQUAGd2hhbW15RIlACECPQAAAAAAAFlSua0AxrkAu14EBY8WBAZyBACK1nEADdW5khkAFVl9WUDglhohAA1ZQOIOBAeBABrCBCLqBCB9DtnVAIueBAKNAHIEAAIAwAQCdASoIAAgAAUAmJaQAA3AA/vz0AAA='; + static mp4: string = 'data:video/mp4;base64,AAAAIGZ0eXBtcDQyAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAACKBtZGF0AAAC8wYF///v3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE0MiByMjQ3OSBkZDc5YTYxIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxNCAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTEgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MToweDExMSBtZT1oZXggc3VibWU9MiBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0wIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MCA4eDhkY3Q9MCBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0wIHRocmVhZHM9NiBsb29rYWhlYWRfdGhyZWFkcz0xIHNsaWNlZF90aHJlYWRzPTAgbnI9MCBkZWNpbWF0ZT0xIGludGVybGFjZWQ9MCBibHVyYXlfY29tcGF0PTAgY29uc3RyYWluZWRfaW50cmE9MCBiZnJhbWVzPTMgYl9weXJhbWlkPTIgYl9hZGFwdD0xIGJfYmlhcz0wIGRpcmVjdD0xIHdlaWdodGI9MSBvcGVuX2dvcD0wIHdlaWdodHA9MSBrZXlpbnQ9MzAwIGtleWludF9taW49MzAgc2NlbmVjdXQ9NDAgaW50cmFfcmVmcmVzaD0wIHJjX2xvb2thaGVhZD0xMCByYz1jcmYgbWJ0cmVlPTEgY3JmPTIwLjAgcWNvbXA9MC42MCBxcG1pbj0wIHFwbWF4PTY5IHFwc3RlcD00IHZidl9tYXhyYXRlPTIwMDAwIHZidl9idWZzaXplPTI1MDAwIGNyZl9tYXg9MC4wIG5hbF9ocmQ9bm9uZSBmaWxsZXI9MCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAOWWIhAA3//p+C7v8tDDSTjf97w55i3SbRPO4ZY+hkjD5hbkAkL3zpJ6h/LR1CAABzgB1kqqzUorlhQAAAAxBmiQYhn/+qZYADLgAAAAJQZ5CQhX/AAj5IQADQGgcIQADQGgcAAAACQGeYUQn/wALKCEAA0BoHAAAAAkBnmNEJ/8ACykhAANAaBwhAANAaBwAAAANQZpoNExDP/6plgAMuSEAA0BoHAAAAAtBnoZFESwr/wAI+SEAA0BoHCEAA0BoHAAAAAkBnqVEJ/8ACykhAANAaBwAAAAJAZ6nRCf/AAsoIQADQGgcIQADQGgcAAAADUGarDRMQz/+qZYADLghAANAaBwAAAALQZ7KRRUsK/8ACPkhAANAaBwAAAAJAZ7pRCf/AAsoIQADQGgcIQADQGgcAAAACQGe60Qn/wALKCEAA0BoHAAAAA1BmvA0TEM//qmWAAy5IQADQGgcIQADQGgcAAAAC0GfDkUVLCv/AAj5IQADQGgcAAAACQGfLUQn/wALKSEAA0BoHCEAA0BoHAAAAAkBny9EJ/8ACyghAANAaBwAAAANQZs0NExDP/6plgAMuCEAA0BoHAAAAAtBn1JFFSwr/wAI+SEAA0BoHCEAA0BoHAAAAAkBn3FEJ/8ACyghAANAaBwAAAAJAZ9zRCf/AAsoIQADQGgcIQADQGgcAAAADUGbeDRMQz/+qZYADLkhAANAaBwAAAALQZ+WRRUsK/8ACPghAANAaBwhAANAaBwAAAAJAZ+1RCf/AAspIQADQGgcAAAACQGft0Qn/wALKSEAA0BoHCEAA0BoHAAAAA1Bm7w0TEM//qmWAAy4IQADQGgcAAAAC0Gf2kUVLCv/AAj5IQADQGgcAAAACQGf+UQn/wALKCEAA0BoHCEAA0BoHAAAAAkBn/tEJ/8ACykhAANAaBwAAAANQZvgNExDP/6plgAMuSEAA0BoHCEAA0BoHAAAAAtBnh5FFSwr/wAI+CEAA0BoHAAAAAkBnj1EJ/8ACyghAANAaBwhAANAaBwAAAAJAZ4/RCf/AAspIQADQGgcAAAADUGaJDRMQz/+qZYADLghAANAaBwAAAALQZ5CRRUsK/8ACPkhAANAaBwhAANAaBwAAAAJAZ5hRCf/AAsoIQADQGgcAAAACQGeY0Qn/wALKSEAA0BoHCEAA0BoHAAAAA1Bmmg0TEM//qmWAAy5IQADQGgcAAAAC0GehkUVLCv/AAj5IQADQGgcIQADQGgcAAAACQGepUQn/wALKSEAA0BoHAAAAAkBnqdEJ/8ACyghAANAaBwAAAANQZqsNExDP/6plgAMuCEAA0BoHCEAA0BoHAAAAAtBnspFFSwr/wAI+SEAA0BoHAAAAAkBnulEJ/8ACyghAANAaBwhAANAaBwAAAAJAZ7rRCf/AAsoIQADQGgcAAAADUGa8DRMQz/+qZYADLkhAANAaBwhAANAaBwAAAALQZ8ORRUsK/8ACPkhAANAaBwAAAAJAZ8tRCf/AAspIQADQGgcIQADQGgcAAAACQGfL0Qn/wALKCEAA0BoHAAAAA1BmzQ0TEM//qmWAAy4IQADQGgcAAAAC0GfUkUVLCv/AAj5IQADQGgcIQADQGgcAAAACQGfcUQn/wALKCEAA0BoHAAAAAkBn3NEJ/8ACyghAANAaBwhAANAaBwAAAANQZt4NExC//6plgAMuSEAA0BoHAAAAAtBn5ZFFSwr/wAI+CEAA0BoHCEAA0BoHAAAAAkBn7VEJ/8ACykhAANAaBwAAAAJAZ+3RCf/AAspIQADQGgcAAAADUGbuzRMQn/+nhAAYsAhAANAaBwhAANAaBwAAAAJQZ/aQhP/AAspIQADQGgcAAAACQGf+UQn/wALKCEAA0BoHCEAA0BoHCEAA0BoHCEAA0BoHCEAA0BoHCEAA0BoHAAACiFtb292AAAAbG12aGQAAAAA1YCCX9WAgl8AAAPoAAAH/AABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAGGlvZHMAAAAAEICAgAcAT////v7/AAAF+XRyYWsAAABcdGtoZAAAAAPVgIJf1YCCXwAAAAEAAAAAAAAH0AAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAygAAAMoAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAB9AAABdwAAEAAAAABXFtZGlhAAAAIG1kaGQAAAAA1YCCX9WAgl8AAV+QAAK/IFXEAAAAAAAtaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSGFuZGxlcgAAAAUcbWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAAE3HN0YmwAAACYc3RzZAAAAAAAAAABAAAAiGF2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAygDKAEgAAABIAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY//8AAAAyYXZjQwFNQCj/4QAbZ01AKOyho3ySTUBAQFAAAAMAEAAr8gDxgxlgAQAEaO+G8gAAABhzdHRzAAAAAAAAAAEAAAA8AAALuAAAABRzdHNzAAAAAAAAAAEAAAABAAAB8GN0dHMAAAAAAAAAPAAAAAEAABdwAAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAAC7gAAAAAQAAF3AAAAABAAAAAAAAABxzdHNjAAAAAAAAAAEAAAABAAAAAQAAAAEAAAEEc3RzegAAAAAAAAAAAAAAPAAAAzQAAAAQAAAADQAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAANAAAADQAAAQBzdGNvAAAAAAAAADwAAAAwAAADZAAAA3QAAAONAAADoAAAA7kAAAPQAAAD6wAAA/4AAAQXAAAELgAABEMAAARcAAAEbwAABIwAAAShAAAEugAABM0AAATkAAAE/wAABRIAAAUrAAAFQgAABV0AAAVwAAAFiQAABaAAAAW1AAAFzgAABeEAAAX+AAAGEwAABiwAAAY/AAAGVgAABnEAAAaEAAAGnQAABrQAAAbPAAAG4gAABvUAAAcSAAAHJwAAB0AAAAdTAAAHcAAAB4UAAAeeAAAHsQAAB8gAAAfjAAAH9gAACA8AAAgmAAAIQQAACFQAAAhnAAAIhAAACJcAAAMsdHJhawAAAFx0a2hkAAAAA9WAgl/VgIJfAAAAAgAAAAAAAAf8AAAAAAAAAAAAAAABAQAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAACsm1kaWEAAAAgbWRoZAAAAADVgIJf1YCCXwAArEQAAWAAVcQAAAAAACdoZGxyAAAAAAAAAABzb3VuAAAAAAAAAAAAAAAAU3RlcmVvAAAAAmNtaW5mAAAAEHNtaGQAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAidzdGJsAAAAZ3N0c2QAAAAAAAAAAQAAAFdtcDRhAAAAAAAAAAEAAAAAAAAAAAACABAAAAAArEQAAAAAADNlc2RzAAAAAAOAgIAiAAIABICAgBRAFQAAAAADDUAAAAAABYCAgAISEAaAgIABAgAAABhzdHRzAAAAAAAAAAEAAABYAAAEAAAAABxzdHNjAAAAAAAAAAEAAAABAAAAAQAAAAEAAAAUc3RzegAAAAAAAAAGAAAAWAAAAXBzdGNvAAAAAAAAAFgAAAOBAAADhwAAA5oAAAOtAAADswAAA8oAAAPfAAAD5QAAA/gAAAQLAAAEEQAABCgAAAQ9AAAEUAAABFYAAARpAAAEgAAABIYAAASbAAAErgAABLQAAATHAAAE3gAABPMAAAT5AAAFDAAABR8AAAUlAAAFPAAABVEAAAVXAAAFagAABX0AAAWDAAAFmgAABa8AAAXCAAAFyAAABdsAAAXyAAAF+AAABg0AAAYgAAAGJgAABjkAAAZQAAAGZQAABmsAAAZ+AAAGkQAABpcAAAauAAAGwwAABskAAAbcAAAG7wAABwYAAAcMAAAHIQAABzQAAAc6AAAHTQAAB2QAAAdqAAAHfwAAB5IAAAeYAAAHqwAAB8IAAAfXAAAH3QAAB/AAAAgDAAAICQAACCAAAAg1AAAIOwAACE4AAAhhAAAIeAAACH4AAAiRAAAIpAAACKoAAAiwAAAItgAACLwAAAjCAAAAFnVkdGEAAAAObmFtZVN0ZXJlbwAAAHB1ZHRhAAAAaG1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAAO2lsc3QAAAAzqXRvbwAAACtkYXRhAAAAAQAAAABIYW5kQnJha2UgMC4xMC4yIDIwMTUwNjExMDA='; + + static hasInit: boolean = false; + static noSleepTimer: number; + static noSleepVideo: HTMLVideoElement; + static isStart: boolean; + + public static init() { + if (this.hasInit) return; + + if (!this.oldIOS) { + let noSleepVideo = document.createElement('video'); + + noSleepVideo.setAttribute('muted', ''); + noSleepVideo.setAttribute('title', 'Intro'); + noSleepVideo.setAttribute('playsinline', ''); + + this.addSourceToVideo(noSleepVideo, 'webm', this.webm); + this.addSourceToVideo(noSleepVideo, 'mp4', this.mp4); + + noSleepVideo.addEventListener('loadedmetadata', function () { + if (noSleepVideo.duration <= 1) { + noSleepVideo.setAttribute('loop', ''); + } else { + noSleepVideo.addEventListener('timeupdate', function () { + if (noSleepVideo.currentTime > 0.5) { + noSleepVideo.currentTime = Math.random(); + } + }); + } + }); + + document.body.appendChild(noSleepVideo); + this.noSleepVideo = noSleepVideo; + } + + this.hasInit = true; + this.isStart = false; + } + + public static start() { + if (!this.isStart) { + cc.log("nosleep start"); + if (this.oldIOS) { + this.stop(); + this.noSleepTimer = window.setInterval(() => { + if (!document.hidden) { + window.location.href = window.location.href.split('#')[0]; + window.setTimeout(window.stop, 0); + } + }, 15000); + } else { + this.noSleepVideo.play(); + } + this.isStart = true; + } + } + + public static stop() { + if (this.oldIOS) { + if (this.noSleepTimer) { + window.clearInterval(this.noSleepTimer); + this.noSleepTimer = 0; + } + } else { + this.noSleepVideo.pause(); + } + this.isStart = false; + } + + static addSourceToVideo(element: HTMLVideoElement, type: string, dataURI: string) { + let source = document.createElement('source'); + source.src = dataURI; + source.type = 'video/' + type; + element.appendChild(source); + } + +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/NoSleep.ts.meta b/assets/Script/Engine/CatanEngine/NoSleep.ts.meta new file mode 100644 index 0000000..a6e423d --- /dev/null +++ b/assets/Script/Engine/CatanEngine/NoSleep.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "90f2152c-2c37-4c7c-b3a3-04c8aee53c34", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/TableV3/Core.meta b/assets/Script/Engine/CatanEngine/TableV3/Core.meta new file mode 100644 index 0000000..3e78f3d --- /dev/null +++ b/assets/Script/Engine/CatanEngine/TableV3/Core.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "3d4ae989-9f9b-429a-a331-191a8cd8193d", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/TableV3/Core/ITableJson.ts b/assets/Script/Engine/CatanEngine/TableV3/Core/ITableJson.ts new file mode 100644 index 0000000..4a80f78 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/TableV3/Core/ITableJson.ts @@ -0,0 +1,4 @@ +export interface ITableJson { + cols: string[], + rows: any[], +} diff --git a/assets/Script/Engine/CatanEngine/TableV3/Core/ITableJson.ts.meta b/assets/Script/Engine/CatanEngine/TableV3/Core/ITableJson.ts.meta new file mode 100644 index 0000000..4d73fc1 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/TableV3/Core/ITableJson.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "c5f8c44b-0b24-4f57-b229-4c3ad9301236", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/TableV3/Core/ITableRow.ts b/assets/Script/Engine/CatanEngine/TableV3/Core/ITableRow.ts new file mode 100644 index 0000000..bdc2290 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/TableV3/Core/ITableRow.ts @@ -0,0 +1,10 @@ +export interface ITableRow { + Id: number; +} + +/** + * 表沒有欄位 + */ +export class WithoutRow implements ITableRow { + Id: number; +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/TableV3/Core/ITableRow.ts.meta b/assets/Script/Engine/CatanEngine/TableV3/Core/ITableRow.ts.meta new file mode 100644 index 0000000..f90feaf --- /dev/null +++ b/assets/Script/Engine/CatanEngine/TableV3/Core/ITableRow.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "104d86f0-0cb9-4cd1-a305-44ea90ee3d7f", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/TableV3/Core/TableBase.ts b/assets/Script/Engine/CatanEngine/TableV3/Core/TableBase.ts new file mode 100644 index 0000000..b26163f --- /dev/null +++ b/assets/Script/Engine/CatanEngine/TableV3/Core/TableBase.ts @@ -0,0 +1,21 @@ +import { ITableRow } from "./ITableRow"; + +export abstract class TableBase extends Array { + constructor() { + super(); + Object.setPrototypeOf(this, new.target.prototype); + } + + /**欄位數量 */ + public get Count(): number { return this.length; } + /**取得全部鍵值 */ + public get Keys(): string[] { return Object.keys(this); } + /**取得全部欄位值 */ + public get Rows(): Array { return Object["values"](this); } + // public get Rows(): Array { return this; } + + /**是否包含該Id值的欄位 */ + public ContainsRow(id: number): boolean { + return id in this; + } +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/TableV3/Core/TableBase.ts.meta b/assets/Script/Engine/CatanEngine/TableV3/Core/TableBase.ts.meta new file mode 100644 index 0000000..a9c5bd3 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/TableV3/Core/TableBase.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "e4f18713-244f-4375-b77a-c26bf197cd3f", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/TableV3/Examples.meta b/assets/Script/Engine/CatanEngine/TableV3/Examples.meta new file mode 100644 index 0000000..7549167 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/TableV3/Examples.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "4a176b88-26e0-42ae-8acc-42ab2e942ace", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/TableV3/Examples/CSSettingsV3Example.ts b/assets/Script/Engine/CatanEngine/TableV3/Examples/CSSettingsV3Example.ts new file mode 100644 index 0000000..95fa797 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/TableV3/Examples/CSSettingsV3Example.ts @@ -0,0 +1,13 @@ +import { TableManager } from "../TableManager"; +import { StringExampleTableRow, StringTableExample } from "./Tables/StringTableExample"; + +const { ccclass } = cc._decorator; + +@ccclass +export default class CSSettingsV3Example { + + private static _stringExample: StringTableExample; + /** 共用_字串表#string.xlsx */ + public static get StringExample(): StringTableExample { return this._stringExample = this._stringExample || TableManager.InitTable("#string", StringTableExample, StringExampleTableRow); } + +} diff --git a/assets/Script/Engine/CatanEngine/TableV3/Examples/CSSettingsV3Example.ts.meta b/assets/Script/Engine/CatanEngine/TableV3/Examples/CSSettingsV3Example.ts.meta new file mode 100644 index 0000000..fb517d6 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/TableV3/Examples/CSSettingsV3Example.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "04f57003-d6a1-4fee-adf8-69994db08f05", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/TableV3/Examples/TableUseExample.ts b/assets/Script/Engine/CatanEngine/TableV3/Examples/TableUseExample.ts new file mode 100644 index 0000000..3977b2c --- /dev/null +++ b/assets/Script/Engine/CatanEngine/TableV3/Examples/TableUseExample.ts @@ -0,0 +1,82 @@ +import CSSettingsV3Example from "./CSSettingsV3Example"; +import { StringExampleTable } from "./Tables/StringTableExample"; + + +const { ccclass, property } = cc._decorator; + +@ccclass +export default class TableUseExample extends cc.Component { + + start() { + + //#region StringExample表 + cc.log("----------------#stringExample"); + cc.log(CSSettingsV3Example.StringExample instanceof StringExampleTable); // true + cc.log(Array.isArray(CSSettingsV3Example.StringExample)); // true, 所以Array相關的方法都可以拿來操作 + + cc.log(CSSettingsV3Example.StringExample.length); + cc.log(CSSettingsV3Example.StringExample.Count); // 跟length一樣 + + cc.log(CSSettingsV3Example.StringExample.ContainsRow(11)); // 是否包含id=11的Row + cc.log(11 in CSSettingsV3Example.StringExample); // 同上 + + cc.log(CSSettingsV3Example.StringExample[1].MsgZnCh); + cc.log(CSSettingsV3Example.StringExample[1]["MsgZnCh"]); // 同上 + cc.log(CSSettingsV3Example["StringExample"][1]["MsgZnCh"]); // 同上 + + cc.log("----------------"); + for (let row of CSSettingsV3Example.StringExample) { + if (row) { // 如果Row沒有連號, 那有可能取到undefined值, 要先判斷, 不想判斷就用 CSSettings.StringExample.Rows + cc.log(row.Id, row.MsgZnCh); + } + } + + cc.log("----------------"); + for (let id of CSSettingsV3Example.StringExample.Keys) { + cc.log(id); // 只會列出有值的id, undefined會跳過 + } + + cc.log("----------------"); + for (let row of CSSettingsV3Example.StringExample.Rows) { + cc.log(row.Id, row.MsgZnCh); // 只會列出有值的Row, undefined會跳過 + } + //#endregion + + //#region StringExample表 #StringFilter表 + cc.log("----------------#stringExample#string_filter"); + //cc.log(CSSettings.StringExample.StringFilter instanceof StringFilterTable); // true + cc.log(Array.isArray(CSSettingsV3Example.StringExample.StringFilter)); // true, 所以Array相關的方法都可以拿來操作 + + cc.log(CSSettingsV3Example.StringExample.StringFilter.length); + cc.log(CSSettingsV3Example.StringExample.StringFilter.Count); // 跟length一樣 + + cc.log(CSSettingsV3Example.StringExample.StringFilter.ContainsRow(11)); // 是否包含id=11的Row + cc.log(11 in CSSettingsV3Example.StringExample.StringFilter); // 同上 + + cc.log(CSSettingsV3Example.StringExample.StringFilter[1].FilterWord); + cc.log(CSSettingsV3Example.StringExample.StringFilter[1]["FilterWord"]); // 同上 + cc.log(CSSettingsV3Example["StringExample"]["StringFilter"][1]["FilterWord"]); // 同上 + + cc.log("----------------"); + for (let row of CSSettingsV3Example.StringExample.StringFilter) { + if (row) { // 如果Row沒有連號, 那有可能取到undefined值, 要先判斷, 不想判斷就用 CSSettings.StringExample.StringFilter.Rows + cc.log(row.Id, row.FilterWord); + } + } + + cc.log("----------------"); + for (let id of CSSettingsV3Example.StringExample.StringFilter.Keys) { + cc.log(id); // 只會列出有值的id, undefined會跳過 + } + + cc.log("----------------"); + for (let row of CSSettingsV3Example.StringExample.StringFilter.Rows) { + cc.log(row.Id, row.FilterWord); // 只會列出有值的Row, undefined會跳過 + } + //#endregion + + cc.log("----------------"); + //CSSettingsV3.ResetTables(); // 重置表 + + } +} diff --git a/assets/Script/Engine/CatanEngine/TableV3/Examples/TableUseExample.ts.meta b/assets/Script/Engine/CatanEngine/TableV3/Examples/TableUseExample.ts.meta new file mode 100644 index 0000000..f5192f0 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/TableV3/Examples/TableUseExample.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "27d36ad6-da65-4673-abdb-4635a1a3d3a8", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/TableV3/Examples/Tables.meta b/assets/Script/Engine/CatanEngine/TableV3/Examples/Tables.meta new file mode 100644 index 0000000..72e8c87 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/TableV3/Examples/Tables.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "e65b6243-578c-4cea-ac46-1dfd4d455017", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/TableV3/Examples/Tables/StringTableExample.ts b/assets/Script/Engine/CatanEngine/TableV3/Examples/Tables/StringTableExample.ts new file mode 100644 index 0000000..c13e845 --- /dev/null +++ b/assets/Script/Engine/CatanEngine/TableV3/Examples/Tables/StringTableExample.ts @@ -0,0 +1,45 @@ +import { ITableRow } from "../../Core/ITableRow"; +import { TableBase } from "../../Core/TableBase"; +import { TableManager } from "../../TableManager"; + +/** + * 共用_字串表#string.xlsx + * ##程式碼由工具產生, 在此做的修改都將被覆蓋## + */ +export class StringTableExample extends TableBase { + private _stringFilter: StringFilterTable; + /** 共用_字串表#string.xlsx > #string_filter */ + public get StringFilter(): StringFilterTable { return this._stringFilter = this._stringFilter || TableManager.InitTable("#string#string_filter", StringFilterTable, StringFilterTableRow); } +} + +/** + * #string + */ +export class StringExampleTable extends TableBase {} + +export class StringExampleTableRow implements ITableRow { + /** 編號 */ + Id: number; + /** 英文訊息 */ + MsgEn: string; + /** 繁體中文訊息 */ + MsgZnTw: string; + /** 簡體中文讯息 */ + MsgZnCh: string; + /** 越南文讯息 */ + MsgVi: string; + /** 泰文讯息 */ + MsgTh: string; +} + +/** + * #string_filter + */ +export class StringFilterTable extends TableBase {} + +export class StringFilterTableRow implements ITableRow { + /** 編號 */ + Id: number; + /** 過濾字串 */ + FilterWord: string; +} diff --git a/assets/Script/Engine/CatanEngine/TableV3/Examples/Tables/StringTableExample.ts.meta b/assets/Script/Engine/CatanEngine/TableV3/Examples/Tables/StringTableExample.ts.meta new file mode 100644 index 0000000..acb3e1d --- /dev/null +++ b/assets/Script/Engine/CatanEngine/TableV3/Examples/Tables/StringTableExample.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "c4bea919-96cd-40ee-a5f7-d9327414b1b2", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/TableV3/TableManager.ts b/assets/Script/Engine/CatanEngine/TableV3/TableManager.ts new file mode 100644 index 0000000..331de1d --- /dev/null +++ b/assets/Script/Engine/CatanEngine/TableV3/TableManager.ts @@ -0,0 +1,48 @@ +import { ITableJson } from "./Core/ITableJson"; +import { ITableRow } from "./Core/ITableRow"; + +export class TableManager { + private static _tableJsons: { [key: string]: ITableJson } = {}; + + public static AddJsonAssets(jsonAssets: cc.JsonAsset[]) { + if (!jsonAssets) return; + let newAssets: cc.JsonAsset[] = jsonAssets.concat(); + for (let jsonAsset of newAssets) { + this.AddJsonAsset(jsonAsset); + } + } + + public static AddJsonAsset(jsonAsset: cc.JsonAsset) { + if (!jsonAsset) { + return; + } + for (let tableName in jsonAsset.json) { + console.log(`TableV3 [${tableName}] json loaded`); + this._tableJsons[tableName] = jsonAsset.json[tableName]; + } + } + + public static GetTable(name: string): ITableJson { + return this._tableJsons[name]; + } + + public static InitTable>(name: string, tableType: { new(): T }, rowType: { new(): ITableRow }): T { + let json = this._tableJsons[name]; + if (!json) { + throw new Error(`TableV3 [${name}] 尚未載入json檔`); + } + let table = new tableType(); + let cols = json.cols; + let colLength = cols.length; + let rows = json.rows; + for (let r of rows) { + let trow = new rowType(); + for (let i = 0; i < colLength; i++) { + trow[cols[i]] = r[i]; + } + table[trow.Id] = trow; + } + //cc.log(`TableV3 [${name}] init done`); + return table; + } +} \ No newline at end of file diff --git a/assets/Script/Engine/CatanEngine/TableV3/TableManager.ts.meta b/assets/Script/Engine/CatanEngine/TableV3/TableManager.ts.meta new file mode 100644 index 0000000..14ecbca --- /dev/null +++ b/assets/Script/Engine/CatanEngine/TableV3/TableManager.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "f3bcfb76-6225-4757-a039-9018806ef54e", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Animation.meta b/assets/Script/Engine/Component/Animation.meta new file mode 100644 index 0000000..14bdbcb --- /dev/null +++ b/assets/Script/Engine/Component/Animation.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "96902cc4-f71a-4909-b87d-f709fd0d5681", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Animation/AnimationAutoPlay.ts b/assets/Script/Engine/Component/Animation/AnimationAutoPlay.ts new file mode 100644 index 0000000..396d0c6 --- /dev/null +++ b/assets/Script/Engine/Component/Animation/AnimationAutoPlay.ts @@ -0,0 +1,16 @@ + +const { ccclass, requireComponent, menu } = cc._decorator; + +@ccclass +@menu("Plug-in/Animation/AnimationAutoPlay") +@requireComponent(cc.Animation) +export default class AnimationAutoPlay extends cc.Component { + + onEnable() { + let anim = this.getComponent(cc.Animation); + if (anim != null) { + let animationState = anim.play(); + anim.sample(animationState.name); + } + } +} diff --git a/assets/Script/Engine/Component/Animation/AnimationAutoPlay.ts.meta b/assets/Script/Engine/Component/Animation/AnimationAutoPlay.ts.meta new file mode 100644 index 0000000..a83b306 --- /dev/null +++ b/assets/Script/Engine/Component/Animation/AnimationAutoPlay.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "c51bb156-b283-4e24-a738-317650150b9d", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Animation/AnimationRandomPlay.ts b/assets/Script/Engine/Component/Animation/AnimationRandomPlay.ts new file mode 100644 index 0000000..b0c90ed --- /dev/null +++ b/assets/Script/Engine/Component/Animation/AnimationRandomPlay.ts @@ -0,0 +1,153 @@ +const { ccclass, property, requireComponent, menu } = cc._decorator; + +@ccclass("State_AnimationRandomPlay") +export class State_AnimationRandomPlay { + @property({ displayName: "最少時間", type: cc.Float }) + public mintime: number = 0; + @property({ displayName: "最多時間", type: cc.Float }) + public maxtime: number = 0; + @property({ displayName: "權重", type: cc.Integer }) + public weight: number = 0; + @property({ displayName: "動畫", type: cc.AnimationClip }) + public clip: cc.AnimationClip = null; +} + +@ccclass +@menu("Plug-in/Animation/AnimationRandomPlay") +@requireComponent(cc.Animation) +/** 可以根據權重決定多久後隨機播放甚麼動畫 */ +export class AnimationRandomPlay extends cc.Component { + //#region public 外調參數 + + @property({ type: State_AnimationRandomPlay }) + public states: State_AnimationRandomPlay[] = []; + + //#endregion + + //#region public 屬性 + + public nowPlayName: string = ""; + public nextPlayName: string = ""; + public nextPlayTime: number = null; + + //#endregion + + //#region private 屬性 + + private _animation: cc.Animation = null; + private _weightAll: number[] = []; + private _weightAllNum: number = 0; + + //#endregion + + //#region get set + + get animation(): cc.Animation { + if (this._animation == null) { + this._animation = this.node.getComponent(cc.Animation); + } + return this._animation; + } + + //#endregion + + //#region Lifecycle + + onLoad(): void { + let self: this = this; + let weight: number = 0; + for (let i: number = 0; i < this.states.length; i++) { + weight += this.states[i].weight; + this._weightAll.push(weight); + this._weightAllNum += this.states[i].weight; + } + + // 一般動畫 + this.animation.on("finished", () => { + self.GetNextAnim(); + }, this); + + // 不一般動畫 (X + // Loop動畫 + this.animation.on("lastframe", () => { + self.animation.setCurrentTime(0); + self.animation.stop(); + self.GetNextAnim(); + }, this); + } + + onEnable(): void { + this.GetNextAnim(); + } + + onDisable(): void { + this.nextPlayName = ""; + this.nextPlayTime = null; + this.animation.setCurrentTime(0); + this.animation.stop(); + } + + onDestroy(): void { + this.animation.targetOff(this); + // let self: this = this; + // this.animation.off("finished", () => { + // self.GetNextAnim(); + // }, this); + // this.animation.off("lastframe", () => { + // self.animation.setCurrentTime(0); + // self.animation.stop(); + // self.GetNextAnim(); + // }, this); + } + + update(dt: number): void { + let time: number = Date.now(); + if (this.nextPlayTime && time >= this.nextPlayTime) { + this.nowPlayName = this.nextPlayName; + if (this.animation.getAnimationState(this.nextPlayName)) { + this.animation.play(this.nextPlayName); + } else { + console.error(`this node(${this.node.name}) not has animation(${this.nextPlayName})`); + this.animation.addClip(this.GetClip_From_states(this.nextPlayName)); + if (this.animation.getAnimationState(this.nextPlayName)) { + console.warn(`this node(${this.node.name}) add animation(${this.nextPlayName})`); + this.animation.play(this.nextPlayName); + } + } + this.nextPlayName = ""; + this.nextPlayTime = null; + } + } + + //#endregion + + //#region Custom Function + + /** 取得下一隻動畫的時間&名稱 */ + GetNextAnim(): void { + let random: number = Math.floor(Math.random() * this._weightAllNum) + 1; + for (let i: number = 0; i < this._weightAll.length; i++) { + if (random <= this._weightAll[i]) { + let time: number = Math.floor(Math.random() * (this.states[i].maxtime - this.states[i].mintime + 1)) + this.states[i].mintime; + this.nextPlayTime = Date.now() + (time * 1000); + this.nextPlayName = this.states[i].clip.name; + // if (CC_DEBUG) { + // let date: Date = new Date(this.nextPlayTime); + // console.log(`nextWaitTime: ${time}, nextPlayTime: ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}, nextPlayName: ${this.nextPlayName}`); + // } + break; + } + } + } + + /** 取得下一隻動畫的時間&名稱 */ + GetClip_From_states(name: string): cc.AnimationClip { + for (let i: number = 0; i < this.states.length; i++) { + if (this.states[i].clip.name === name) { + return this.states[i].clip; + } + } + } + + //#endregion +} diff --git a/assets/Script/Engine/Component/Animation/AnimationRandomPlay.ts.meta b/assets/Script/Engine/Component/Animation/AnimationRandomPlay.ts.meta new file mode 100644 index 0000000..f6751a5 --- /dev/null +++ b/assets/Script/Engine/Component/Animation/AnimationRandomPlay.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "66e6675c-c922-4952-9eb1-dc55aece8dc3", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Animation/Animator.ts b/assets/Script/Engine/Component/Animation/Animator.ts new file mode 100644 index 0000000..506024d --- /dev/null +++ b/assets/Script/Engine/Component/Animation/Animator.ts @@ -0,0 +1,163 @@ +import { CoroutineV2 } from "../../CatanEngine/CoroutineV2/CoroutineV2"; +import { RandomEx } from "../../Utils/Number/RandomEx"; + +const { ccclass, property, requireComponent, menu } = cc._decorator; + +@ccclass("State") +export class State { + @property() + public name: string = ""; + @property({ type: cc.AnimationClip }) + public clip: cc.AnimationClip = null; + @property() + public transitionTo: string = ""; +} +@ccclass +@menu("Plug-in/Animation/Animator") +@requireComponent(cc.Animation) +export class Animator extends cc.Component { + + @property({ displayName: "Default State" }) + public defaultState: string = ""; + + @property({ type: State }) + public states: State[] = []; + public nowPlayName: string = ""; + _animation: cc.Animation = null; + + /** 動畫速度 */ + private _speed: number = 1; + get animation(): cc.Animation { + if (this._animation == null) { + this._animation = this.node.getComponent(cc.Animation); + } + return this._animation; + } + + onLoad(): void { + if (CC_DEV) { + let animationClip: cc.AnimationClip[] = this.animation.getClips(); + for (let s of this.states) { + let state: State = null; + for (let i: number = 0; i < animationClip.length; i++) { + const clip: cc.AnimationClip = animationClip[i]; + if (s.clip != null && s.clip.name === clip.name) { + state = s; + break; + } + } + if (state === null) { + console.error(`node: ${this.node.name}, anim: ${s.clip?.name}, 動畫沒有掛在Animation上面`); + } + } + } + } + + onEnable(): void { + this.stopState(); + if (this.defaultState !== "") { + this.playState(this.defaultState); + } + } + onDisable(): void { + this.stopState(); + } + + /** + * runStateAndWait(動作機只會接一次動畫) + * @param name 動畫State的名稱 + * @param callback callback 沒有transitionTo才會觸發 + */ + public *runStateAndWait(name: string, callback: Function = null): any { + if (name === "") { + return; + } + this.animation.stop(); + this.nowPlayName = name; + let state: State = null; + for (let s of this.states) { + if (s.name === name && s.clip != null && s.clip.isValid) { + state = s; + break; + } + } + if (state == null) { + return; + } + let animationState: cc.AnimationState = this.animation.play(state.clip.name); + animationState.speed = this.animation.currentClip.speed * this._speed; + this.animation.sample(animationState.name); + if (animationState.duration) { + yield CoroutineV2.WaitTime(animationState.duration); + } + if (callback && !state.transitionTo) { + callback(); + } + yield* this.runStateAndWait(state.transitionTo, callback); + } + + /** playState(動作機只會接一次動畫) */ + public playState(name: string, callback: Function = null): void { + if (!this.node?.activeInHierarchy) { + cc.warn(`Animator error name: ${this.node.name}, activeInHierarchy: ${this.node.activeInHierarchy}`); + } + CoroutineV2.Single(this.runStateAndWait(name, callback)).Start(this); + } + + /** playState(隨機) */ + public playRandomState(callback: Function = null): void { + let random: number = RandomEx.GetInt(0, this.states.length); + let state: State = this.states[random]; + this.playState(state.name, callback); + } + + public stopState(): void { + CoroutineV2.StopCoroutinesBy(this); + this.nowPlayName = ""; + } + + /** + * 設定動畫速率(原有動畫的Speed在乘上倍率) + * @param speed 速率 + */ + public SetSpeed(speed: number): void { + this._speed = speed; + } + + public getAnimTime(name: string, isGetNext: boolean = false): number { + for (let s of this.states) { + if (s.name === name && s.clip != null) { + let time: number = s.clip.duration / this._speed; + if (isGetNext && s.transitionTo !== "") { + time += this.getAnimTime(s.transitionTo, true); + } + return time; + } + } + return null; + } + + /** + * 暫停在某時間 + * @param name Animator設定的動畫名稱 + * @param time 要停的時間點 + * @example + * this._anim.GotoTimeAndPause(name, 0); + */ + public GotoTimeAndPause(name: string, time: number = 0): void { + let clipName: string = null; + for (let s of this.states) { + if (s.name === name && s.clip != null) { + clipName = s.clip.name; + } + } + if (!clipName) { + cc.error(`GotoFrameAndPause get clip error: ${name}`); + return; + } + this.animation.play(clipName); + this.animation.stop(clipName); + this.animation.setCurrentTime(time); + this.animation.sample(clipName); + } +} diff --git a/assets/Script/Engine/Component/Animation/Animator.ts.meta b/assets/Script/Engine/Component/Animation/Animator.ts.meta new file mode 100644 index 0000000..18141b7 --- /dev/null +++ b/assets/Script/Engine/Component/Animation/Animator.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "e0690d25-55e6-4fb2-9932-2231d0125e60", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Animation/SPAnimator.ts b/assets/Script/Engine/Component/Animation/SPAnimator.ts new file mode 100644 index 0000000..7c5baa5 --- /dev/null +++ b/assets/Script/Engine/Component/Animation/SPAnimator.ts @@ -0,0 +1,184 @@ +import { CoroutineV2 } from "../../CatanEngine/CoroutineV2/CoroutineV2"; + +const { ccclass, property, requireComponent, menu, executeInEditMode } = cc._decorator; + +// /** Clipname */ +// export enum Clipname { +// None, +// } + +@ccclass("SPState") +export class SPState { + @property() + public name: string = ""; + public clip: cc.AnimationClip = null; + @property({ displayName: "Spine動畫名稱", tooltip: "Spine動畫名稱" }) + public clipname: string = ""; + // @property({ displayName: "clipname1", type: cc.Enum(Clipname) }) + // public clipname1: Clipname = Clipname.None; + @property() + public isloop: boolean = false; + @property() + public transitionTo: string = ""; +} +@ccclass +// @executeInEditMode +@menu("Plug-in/Animation/SPAnimator") +@requireComponent(sp.Skeleton) +export class SPAnimator extends cc.Component { + + @property({ displayName: "Default State" }) + public defaultState: string = ""; + + @property({ type: SPState }) + public states: SPState[] = []; + public nowPlayName: string = ""; + private _isInit: boolean = false; + _animation: sp.Skeleton = null; + + /** 動畫速度 */ + private _speed: number = 1; + get animation(): sp.Skeleton { + if (this._animation == null) { + this._animation = this.node.getComponent(sp.Skeleton); + } + return this._animation; + } + + protected onLoad(): void { + if (this._isInit) { + return; + } + if (!this.node.activeInHierarchy) { + cc.error(`node: ${this.node.name}, activeInHierarchy: ${this.node.activeInHierarchy}, 動畫沒有打開無法初始化`); + return; + } + let animationClip: cc.AnimationClip[] = this.animation["skeletonData"]["_skeletonCache"].animations; + // if (CC_EDITOR) { + // for (let i: number = 0; i < animationClip.length; i++) { + // const clip: cc.AnimationClip = animationClip[i]; + // Clipname[clip.name] = i; + // Clipname[i.toString()] = clip.name; + // cc.log(`[${i}] ${clip.name}`); + // } + // return; + // } + for (let s of this.states) { + let state: SPState = null; + for (let i: number = 0; i < animationClip.length; i++) { + const clip: cc.AnimationClip = animationClip[i]; + if (s.clipname === clip.name && s.clipname != null) { + s.clip = clip; + state = s; + break; + } + } + if (CC_DEV) { + if (state === null) { + console.error(`node: ${this.node.name}, anim: ${s.clipname}, 動畫沒有掛在Animation上面`); + } + } + } + this._isInit = true; + } + + protected onEnable(): void { + this.stopState(); + if (this.defaultState !== "") { + this.playState(this.defaultState); + } + } + protected onDisable(): void { + this.stopState(); + } + + /** + * runStateAndWait(動作機只會接一次動畫) + * @param name 動畫State的名稱 + * @param callback callback 沒有transitionTo才會觸發 + */ + public *runStateAndWait(name: string, callback: Function = null): any { + if (!this._isInit) { + this.onLoad(); + } + if (name === "") { + return; + } + this.animation.setToSetupPose(); + let lastPlayName: string = ""; + for (let s of this.states) { + if (s.name === this.nowPlayName && s.clipname != null) { + lastPlayName = s.clipname; + break; + } + } + this.nowPlayName = name; + let state: SPState = null; + for (let s of this.states) { + if (s.name === name && s.clipname != null) { + state = s; + break; + } + } + if (state == null) { + return; + } + // let animationState: cc.AnimationState = this.animation.play(state.clipname); + if (lastPlayName) { + this.animation.setMix(lastPlayName, state.clipname, 0.5); + } + this.animation.setAnimation(0, state.clipname, state.isloop); + // let animationState: sp.spine.TrackEntry = this.animation.setAnimation(0, state.clipname, state.isloop); + // animationState.speed = this.animation.currentClip.speed * this._speed; + // this.animation.sample(animationState.name); + if (state.clip.duration) { + yield CoroutineV2.WaitTime(state.clip.duration); + } + if (callback && !state.transitionTo) { + callback(); + } + yield* this.runStateAndWait(state.transitionTo, callback); + } + + /** playState(動作機只會接一次動畫) */ + public playState(name: string, callback: Function = null): void { + if (!this.node.activeInHierarchy) { + cc.warn(`SPAnimator error name: ${this.node.name}, activeInHierarchy: ${this.node.activeInHierarchy}`); + } + CoroutineV2.Single(this.runStateAndWait(name, callback)).Start(this); + } + + // /** playState(隨機) */ + // public playRandomState(callback: Function = null): void { + // let random: number = RandomEx.GetInt(0, this.states.length); + // let state: SPState = this.states[random]; + // this.playState(state.name, callback); + // } + + public stopState(): void { + CoroutineV2.StopCoroutinesBy(this); + this.nowPlayName = ""; + this.animation.clearTracks(); + } + + // /** + // * 設定動畫速率(原有動畫的Speed在乘上倍率) + // * @param speed 速率 + // */ + // public SetSpeed(speed: number): void { + // this._speed = speed; + // } + + public getAnimTime(name: string, isGetNext: boolean = false): number { + for (let s of this.states) { + if (s.name === name && s.clipname != null) { + let time: number = s.clip.duration / this._speed; + if (isGetNext && s.transitionTo !== "") { + time += this.getAnimTime(s.transitionTo, true); + } + return time; + } + } + return null; + } +} diff --git a/assets/Script/Engine/Component/Animation/SPAnimator.ts.meta b/assets/Script/Engine/Component/Animation/SPAnimator.ts.meta new file mode 100644 index 0000000..2b0d3a2 --- /dev/null +++ b/assets/Script/Engine/Component/Animation/SPAnimator.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "636825f6-4e9a-4b5b-991d-8bc1afd3a1ca", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Animation/SkeletonExt.js b/assets/Script/Engine/Component/Animation/SkeletonExt.js new file mode 100644 index 0000000..6373de3 --- /dev/null +++ b/assets/Script/Engine/Component/Animation/SkeletonExt.js @@ -0,0 +1,48 @@ + +cc.game.once(cc.game.EVENT_ENGINE_INITED, function () { + + cc.js.mixin(sp.Skeleton.prototype, { + update(dt) { + // if (CC_EDITOR) return; + + if (CC_EDITOR) { + cc.engine._animatingInEditMode = 1; + cc.engine.animatingInEditMode = 1; + } + + if (this.paused) return; + + dt *= this.timeScale * sp.timeScale; + + if (this.isAnimationCached()) { + + // Cache mode and has animation queue. + if (this._isAniComplete) { + if (this._animationQueue.length === 0 && !this._headAniInfo) { + let frameCache = this._frameCache; + if (frameCache && frameCache.isInvalid()) { + frameCache.updateToFrame(); + let frames = frameCache.frames; + this._curFrame = frames[frames.length - 1]; + } + return; + } + if (!this._headAniInfo) { + this._headAniInfo = this._animationQueue.shift(); + } + this._accTime += dt; + if (this._accTime > this._headAniInfo.delay) { + let aniInfo = this._headAniInfo; + this._headAniInfo = null; + this.setAnimation(0, aniInfo.animationName, aniInfo.loop); + } + return; + } + + this._updateCache(dt); + } else { + this._updateRealtime(dt); + } + } + }); +}); diff --git a/assets/Script/Engine/Component/Animation/SkeletonExt.js.meta b/assets/Script/Engine/Component/Animation/SkeletonExt.js.meta new file mode 100644 index 0000000..d8928dd --- /dev/null +++ b/assets/Script/Engine/Component/Animation/SkeletonExt.js.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "9d832050-308c-4cd9-87c6-d8542ea9c3f3", + "importer": "javascript", + "isPlugin": true, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": true, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Button.meta b/assets/Script/Engine/Component/Button.meta new file mode 100644 index 0000000..a3a5478 --- /dev/null +++ b/assets/Script/Engine/Component/Button.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "f5250e44-01c0-4660-8c8c-ca3e9d5c9def", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Button/BlockDoubleClickButton.ts b/assets/Script/Engine/Component/Button/BlockDoubleClickButton.ts new file mode 100644 index 0000000..e6b6353 --- /dev/null +++ b/assets/Script/Engine/Component/Button/BlockDoubleClickButton.ts @@ -0,0 +1,27 @@ + +const { ccclass, requireComponent, menu, property } = cc._decorator; + +@ccclass +@menu("Plug-in/Button/BlockDoubleClickButton") +@requireComponent(cc.Button) +export default class BlockDoubleClickButton extends cc.Component { + //#region Lifecycle + + protected onEnable(): void { + this.node.on("click", this.OnClickNode, this); + } + + protected onDisable(): void { + this.node.off("click", this.OnClickNode, this); + } + + //#endregion + + //#region Event + + public OnClickNode(event: cc.Button, customEventData: any): void { + this.getComponent(cc.Button).interactable = false; + } + + //#endregion +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Button/BlockDoubleClickButton.ts.meta b/assets/Script/Engine/Component/Button/BlockDoubleClickButton.ts.meta new file mode 100644 index 0000000..592e4b7 --- /dev/null +++ b/assets/Script/Engine/Component/Button/BlockDoubleClickButton.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "b9536e4d-70cc-4d90-ac7d-4af5134f9cc7", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Button/ButtonClickCD.ts b/assets/Script/Engine/Component/Button/ButtonClickCD.ts new file mode 100644 index 0000000..00758fc --- /dev/null +++ b/assets/Script/Engine/Component/Button/ButtonClickCD.ts @@ -0,0 +1,81 @@ +// import CSSettingsV3 from "../../../FormTable/CSSettingsV3"; + +const { ccclass, requireComponent, menu, property } = cc._decorator; + +/** 有冷卻功能的按鈕 */ +@ccclass +@menu("Plug-in/Button/ButtonClickCD") +@requireComponent(cc.Button) +export default class ButtonClickCD extends cc.Component { + //#region property + + @property() + public CDTime: number = 3; + + //#endregion + + //#region public + + public Msg: string; + + //#endregion + + //#region private + + private _nowCDTime: number = 0; + + //#endregion + + //#region Lifecycle + + protected onLoad(): void { + this.loadMsg(); + } + + private async loadMsg(): Promise { + let CSSettingsV3: any = (await (import("../../../FormTable/CSSettingsV3"))).default; + this.Msg = CSSettingsV3.prototype.CommonString(1514); + } + + protected update(dt: number): void { + if (this._nowCDTime > 0) { + this._nowCDTime -= dt; + if (this._nowCDTime <= 0) { + this._nowCDTime = 0; + this.getComponent(cc.Button).interactable = true; + } + } + } + + protected onEnable(): void { + this.node.on("click", this._onClick, this); + this.node.on(cc.Node.EventType.TOUCH_START, this._onTouchStart, this); + } + + protected onDisable(): void { + this.node.off("click", this._onClick, this); + this.node.off(cc.Node.EventType.TOUCH_START, this._onTouchStart, this); + } + + //#endregion + + //#region Custom + + private _onClick(event: cc.Button, customEventData: any): void { + // if (this._nowCDTime > 0) { + // CSMessage.CreateYesMsg(String.Format(this.Msg, this._nowCDTime.toFixed(0))); + // return; + // } + this.getComponent(cc.Button).interactable = false; + this._nowCDTime = this.CDTime; + } + + private async _onTouchStart(event: cc.Event.EventTouch): Promise { + if (this._nowCDTime > 0) { + let CSMessage: any = (await (import("../../../Common/Message/CSMessage"))).default; + CSMessage.CreateYesMsg(String.Format(this.Msg, this._nowCDTime.toFixed(0))); + } + } + + //#endregion +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Button/ButtonClickCD.ts.meta b/assets/Script/Engine/Component/Button/ButtonClickCD.ts.meta new file mode 100644 index 0000000..dd9ad43 --- /dev/null +++ b/assets/Script/Engine/Component/Button/ButtonClickCD.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "c9dc0a70-6d91-4d02-8ceb-8be96cc33225", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Button/HoldButton.ts b/assets/Script/Engine/Component/Button/HoldButton.ts new file mode 100644 index 0000000..0f987d8 --- /dev/null +++ b/assets/Script/Engine/Component/Button/HoldButton.ts @@ -0,0 +1,143 @@ + +const { ccclass, requireComponent, menu, property } = cc._decorator; + +@ccclass +@menu("Plug-in/Button/HoldButton") +@requireComponent(cc.Button) +export default class HoldButton extends cc.Component { + //#region public + + @property() + public MaxTime: number = 2; + + /** 是否HoldLine */ + @property({ displayName: "是否有HoldLine", tooltip: "是否HoldLine" }) + public IsHaveHoldLine: boolean = false; + + @property({ type: cc.Node, visible(): boolean { return this.IsHaveHoldLine; } }) + public HoldLine: cc.Node = null; + + @property({ type: cc.Sprite, visible(): boolean { return this.IsHaveHoldLine; } }) + public ProgressBG: cc.Sprite = null; + + @property({ type: cc.Sprite, visible(): boolean { return this.IsHaveHoldLine; } }) + public ProgressLine: cc.Sprite = null; + + @property({ type: [cc.Component.EventHandler] }) + public OnInvoke: cc.Component.EventHandler[] = []; + + //#endregion + + //#region private + + private _isOnInvoke: boolean = false; + + private _m_isMouseDown: boolean = false; + + private _m_pressDeltaTime: number = 0; + + //#endregion + + //#region Lifecycle + + protected start(): void { + if (this.HoldLine != null) { + this.HoldLine.active = false; + } + } + + protected update(dt: number): void { + if (this._m_isMouseDown) { + this._checkHoldAutoStart(dt); + } else { + if (this.IsHaveHoldLine) { + this.HoldLine.active = false; + } + } + } + + protected onEnable(): void { + this.node.on(cc.Node.EventType.TOUCH_START, this._onTouchStart, this); + this.node.on(cc.Node.EventType.TOUCH_END, this._onTouchEnd, this); + this.node.on(cc.Node.EventType.TOUCH_CANCEL, this._onTouchCancel, this); + } + + protected onDisable(): void { + this.node.off(cc.Node.EventType.TOUCH_START, this._onTouchStart, this); + this.node.off(cc.Node.EventType.TOUCH_END, this._onTouchEnd, this); + this.node.off(cc.Node.EventType.TOUCH_CANCEL, this._onTouchCancel, this); + } + + //#endregion + + //#region Custom + + private _checkHoldAutoStart(deltaTime: number): void { + this._m_pressDeltaTime += deltaTime; + + if (this.IsHaveHoldLine) { + // 蓄能條顯示特效 + this.ProgressLine.fillRange = this._m_pressDeltaTime; + } + + if (this._m_pressDeltaTime > this.MaxTime) { + this.node.pauseSystemEvents(true); + + this._isOnInvoke = true; + this._m_isMouseDown = false; + if (this.IsHaveHoldLine) { + this.HoldLine.active = false; + } + this._m_pressDeltaTime = 0; + if (this.OnInvoke != null) { + this.OnInvoke.forEach((eventHandler: cc.Component.EventHandler) => { + if (eventHandler) { + if (eventHandler.target === "Callback" && eventHandler.component === "Callback" && eventHandler.handler) { + (eventHandler.handler)(); + } else { + eventHandler.emit([this.node.getComponent(cc.Button)]); + } + } + }); + } + } + } + + //#endregion + + //#region EventT + + private _onTouchStart(event: cc.Event.EventTouch): void { + if (this._m_isMouseDown) { + return; + } + this._m_isMouseDown = true; + if (this.IsHaveHoldLine) { + this.HoldLine.active = true; + } + } + + private _onTouchEnd(event: cc.Event.EventTouch): void { + this.node.resumeSystemEvents(true); + this._m_isMouseDown = false; + this._m_pressDeltaTime = 0; + if (this.IsHaveHoldLine) { + this.HoldLine.active = false; + } + this._isOnInvoke = false; + this._checkHoldAutoStart(0); + } + + private _onTouchCancel(event: cc.Event.EventTouch): void { + this.node.resumeSystemEvents(true); + this._m_isMouseDown = false; + this._m_pressDeltaTime = 0; + if (this.IsHaveHoldLine) { + this.HoldLine.active = false; + } + this._isOnInvoke = false; + this._checkHoldAutoStart(0); + } + + //#endregion +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Button/HoldButton.ts.meta b/assets/Script/Engine/Component/Button/HoldButton.ts.meta new file mode 100644 index 0000000..a892656 --- /dev/null +++ b/assets/Script/Engine/Component/Button/HoldButton.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "1472bab3-e5de-4d9a-a761-ccf2787ad36f", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Button/LongTouchComponent.ts b/assets/Script/Engine/Component/Button/LongTouchComponent.ts new file mode 100644 index 0000000..2920eac --- /dev/null +++ b/assets/Script/Engine/Component/Button/LongTouchComponent.ts @@ -0,0 +1,90 @@ +const { ccclass, property } = cc._decorator; + +@ccclass +export default class LongTouchComponent extends cc.Component { + @property({ + tooltip: "触摸回调间隔(秒)。假如为0.1,那么1秒内会回调10次 ${longTouchEvents} 事件数组" + }) + touchInterval: number = 0.1; + + @property({ + type: [cc.Component.EventHandler], + tooltip: "回调事件数组,每间隔 ${toucheInterval}s 回调一次" + }) + longTouchEvents: cc.Component.EventHandler[] = []; + + /** + * 触摸计数器,用于统计本次长按的回调次数 + */ + private _touchCounter: number = 0; + + /** + * 标记当前是否在触摸这个节点 + */ + private _isTouching: boolean = false; + + onEnable() { + this.node.on(cc.Node.EventType.TOUCH_START, this._onTouchStart, this); + this.node.on(cc.Node.EventType.TOUCH_END, this._onTouchEnd, this); + this.node.on(cc.Node.EventType.TOUCH_CANCEL, this._onTouchCancel, this); + } + + onDisable() { + this.node.off(cc.Node.EventType.TOUCH_START, this._onTouchStart, this); + this.node.off(cc.Node.EventType.TOUCH_END, this._onTouchEnd, this); + this.node.off(cc.Node.EventType.TOUCH_CANCEL, this._onTouchCancel, this); + } + + private _onTouchStart(event: cc.Event.EventTouch) { + if (this._isTouching) { + return; + } + + if (this.node.getBoundingBoxToWorld().contains(event.getLocation())) { + this._isTouching = true; + } else { + this._isTouching = false; + } + + if (this._isTouching) { + // 第一次触摸立即回调一次 + this.publishOneTouch(); + + // 然后开启计时器,计算后续的长按相当于触摸了多少次 + this.schedule(this._touchCounterCallback, this.touchInterval); + } + } + + private _onTouchEnd(event: cc.Event.EventTouch) { + this._isTouching = false; + this._touchCounter = 0; + this.unschedule(this._touchCounterCallback); + } + + private _onTouchCancel(event: cc.Event.EventTouch) { + this._isTouching = false; + this._touchCounter = 0; + this.unschedule(this._touchCounterCallback); + } + + private _touchCounterCallback() { + if (this._isTouching) { + this.publishOneTouch(); + } else { + this.unschedule(this._touchCounterCallback); + } + } + + /** + * 通知出去:被点击/触摸了一次,长按时,会连续多次回调这个方法 + */ + private publishOneTouch() { + if (!this._isTouching) { + return; + } + this._touchCounter++; + this.longTouchEvents.forEach((eventHandler: cc.Component.EventHandler) => { + eventHandler.emit([this._touchCounter]); + }); + } +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Button/LongTouchComponent.ts.meta b/assets/Script/Engine/Component/Button/LongTouchComponent.ts.meta new file mode 100644 index 0000000..02497e2 --- /dev/null +++ b/assets/Script/Engine/Component/Button/LongTouchComponent.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "29b183d5-ec75-4a1b-b99e-88b2c99f796f", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Editbox.meta b/assets/Script/Engine/Component/Editbox.meta new file mode 100644 index 0000000..eb368ec --- /dev/null +++ b/assets/Script/Engine/Component/Editbox.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "53ba29d0-c2b9-43bc-9098-4c49e095da34", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Editbox/EditboxEx.ts b/assets/Script/Engine/Component/Editbox/EditboxEx.ts new file mode 100644 index 0000000..256e0b2 --- /dev/null +++ b/assets/Script/Engine/Component/Editbox/EditboxEx.ts @@ -0,0 +1,44 @@ + +const { ccclass, menu, requireComponent } = cc._decorator; + +@ccclass +// @menu("Plug-in/EditBox/EditboxEx") +@requireComponent(cc.EditBox) +export default class EditboxEx extends cc.Component { + + //#region private + + private _worldPos: cc.Vec2 = null; + + //#endregion + + //#region Lifecycle + + protected onLoad(): void { + if (CC_DEV || cc.sys.isNative) { + let editbox: cc.EditBox = this.node.getComponent(cc.EditBox); + this._worldPos = this.node.GetWorldPosition(); + editbox.node.on("editing-did-began", this._onEditDidBegan, this); + editbox.node.on("editing-did-ended", this._onEditDidEnded, this); + } + } + + //#endregion + + //#region EventListener + + private _onEditDidBegan(editbox: cc.EditBox, customEventData: string): void { + let winSizeHeight: number = cc.winSize.height; + let nodeSizeHeight: number = this.node.height / 2; + let targetHeight: number = winSizeHeight - nodeSizeHeight; + let worldPos: cc.Vec2 = cc.v2(this.node.GetWorldPosition().x, targetHeight); + this.node.SetWorldPosition(worldPos); + } + + // 假设这个回调是给 editingDidEnded 事件的 + private _onEditDidEnded(editbox: cc.EditBox, customEventData: string): void { + this.node.SetWorldPosition(this._worldPos); + } + + //#endregion +} diff --git a/assets/Script/Engine/Component/Editbox/EditboxEx.ts.meta b/assets/Script/Engine/Component/Editbox/EditboxEx.ts.meta new file mode 100644 index 0000000..ee79ca3 --- /dev/null +++ b/assets/Script/Engine/Component/Editbox/EditboxEx.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "1e6b0467-4516-4f10-a876-473c8894ff17", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Label.meta b/assets/Script/Engine/Component/Label.meta new file mode 100644 index 0000000..b888aab --- /dev/null +++ b/assets/Script/Engine/Component/Label.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "dff82bd3-7f62-4573-9fe7-02f301602ae9", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Label/LabelOtherSetting.ts b/assets/Script/Engine/Component/Label/LabelOtherSetting.ts new file mode 100644 index 0000000..bb5e14e --- /dev/null +++ b/assets/Script/Engine/Component/Label/LabelOtherSetting.ts @@ -0,0 +1,14 @@ +const {ccclass, property} = cc._decorator; + +@ccclass +export class LabelOtherSetting extends cc.Component { + @property + public bold:boolean = false; + @property + public underline:boolean = false; + + onEnable(){ + (this.getComponent(cc.Label))._enableBold(this.bold); + (this.getComponent(cc.Label))._enableUnderline(this.underline); + } +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Label/LabelOtherSetting.ts.meta b/assets/Script/Engine/Component/Label/LabelOtherSetting.ts.meta new file mode 100644 index 0000000..ab23325 --- /dev/null +++ b/assets/Script/Engine/Component/Label/LabelOtherSetting.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "8444b16e-19e2-45f3-b155-0bbde521118f", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Node.meta b/assets/Script/Engine/Component/Node.meta new file mode 100644 index 0000000..3746977 --- /dev/null +++ b/assets/Script/Engine/Component/Node.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "8ddc4917-8b1c-4b4e-abfe-9a7b37d363dc", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Node/DropItem.ts b/assets/Script/Engine/Component/Node/DropItem.ts new file mode 100644 index 0000000..065c328 --- /dev/null +++ b/assets/Script/Engine/Component/Node/DropItem.ts @@ -0,0 +1,58 @@ +const { ccclass, property } = cc._decorator; + +/** 物品拖動 */ +@ccclass +export default class DragItem extends cc.Component { + //#region Lifecycle + + protected onLoad(): void { + // 獲取小節點 + let carNode: cc.Node = this.node; + // 新增變數判斷使用者當前滑鼠是不是處於按下狀態 + let mouseDown: boolean = false; + // 當使用者點選的時候記錄滑鼠點選狀態 + carNode.on(cc.Node.EventType.MOUSE_DOWN, (event) => { + mouseDown = true; + }); + // 只有當使用者滑鼠按下才能拖拽 + carNode.on(cc.Node.EventType.MOUSE_MOVE, (event) => { + if (!mouseDown) { return; } + // 獲取滑鼠距離上一次點的資訊 + let delta: any = event.getDelta(); + let canvasNode: cc.Node = cc.Canvas.instance.node; + // 增加限定條件 + let minX: number = -canvasNode.width / 2 + carNode.width / 2; + let maxX: number = canvasNode.width / 2 - carNode.width / 2; + let minY: number = -canvasNode.height / 2 + carNode.height / 2; + let maxY: number = canvasNode.height / 2 - carNode.height / 2; + let moveX: number = carNode.x + delta.x; + let moveY: number = carNode.y + delta.y; + // 控制移動範圍 + if (moveX < minX) { + moveX = minX; + } else if (moveX > maxX) { + moveX = maxX; + } + if (moveY < minY) { + moveY = minY; + } else if (moveY > maxY) { + moveY = maxY; + } + // 移動小車節點 + carNode.x = moveX; + carNode.y = moveY; + }); + // 當滑鼠抬起的時候恢復狀態 + carNode.on(cc.Node.EventType.MOUSE_UP, (event) => { + mouseDown = false; + }); + carNode.on(cc.Node.EventType.TOUCH_END, (event) => { + mouseDown = false; + }); + carNode.on(cc.Node.EventType.TOUCH_CANCEL, (event) => { + mouseDown = false; + }); + } + + //#endregion +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Node/DropItem.ts.meta b/assets/Script/Engine/Component/Node/DropItem.ts.meta new file mode 100644 index 0000000..074cb6b --- /dev/null +++ b/assets/Script/Engine/Component/Node/DropItem.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "af74a109-396e-4192-bcac-70e8b050f9e6", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/ParticleSystem.meta b/assets/Script/Engine/Component/ParticleSystem.meta new file mode 100644 index 0000000..85c1c17 --- /dev/null +++ b/assets/Script/Engine/Component/ParticleSystem.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "5e5b6a48-a903-46ac-8feb-4f515f6171fa", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/ParticleSystem/ParticleSystemAutoPlay.ts b/assets/Script/Engine/Component/ParticleSystem/ParticleSystemAutoPlay.ts new file mode 100644 index 0000000..092ebfa --- /dev/null +++ b/assets/Script/Engine/Component/ParticleSystem/ParticleSystemAutoPlay.ts @@ -0,0 +1,14 @@ + +const { ccclass } = cc._decorator; + +@ccclass +export default class ParticleSystemAutoPlay extends cc.Component { + + onEnable() { + let sys = this.getComponent(cc.ParticleSystem); + if (sys != null) { + sys.stopSystem(); + sys.resetSystem(); + } + } +} diff --git a/assets/Script/Engine/Component/ParticleSystem/ParticleSystemAutoPlay.ts.meta b/assets/Script/Engine/Component/ParticleSystem/ParticleSystemAutoPlay.ts.meta new file mode 100644 index 0000000..8e90833 --- /dev/null +++ b/assets/Script/Engine/Component/ParticleSystem/ParticleSystemAutoPlay.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "69f69903-43ad-484d-b1ae-1055b246ad61", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Sprite.meta b/assets/Script/Engine/Component/Sprite.meta new file mode 100644 index 0000000..ec1c2c6 --- /dev/null +++ b/assets/Script/Engine/Component/Sprite.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "37e07a73-638f-4f2f-918a-b70472d54085", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/Sprite/ShaderGray.ts b/assets/Script/Engine/Component/Sprite/ShaderGray.ts new file mode 100644 index 0000000..f96986d --- /dev/null +++ b/assets/Script/Engine/Component/Sprite/ShaderGray.ts @@ -0,0 +1,9 @@ +const { ccclass, requireComponent } = cc._decorator; + +@ccclass +@requireComponent(cc.Sprite) +export default class ShaderGray extends cc.Component { + onLoad() { + this.getComponent(cc.Sprite).setMaterial(0, cc.Material.getBuiltinMaterial('2d-gray-sprite')); + } +} diff --git a/assets/Script/Engine/Component/Sprite/ShaderGray.ts.meta b/assets/Script/Engine/Component/Sprite/ShaderGray.ts.meta new file mode 100644 index 0000000..0db02aa --- /dev/null +++ b/assets/Script/Engine/Component/Sprite/ShaderGray.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "3167aa46-5e25-4c15-ae71-d566568d72ad", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/UIPanel.meta b/assets/Script/Engine/Component/UIPanel.meta new file mode 100644 index 0000000..ac1c413 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "36c89e1d-52ff-4f8c-9063-40f3c5500591", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/UIPanel/ScreenResize.ts b/assets/Script/Engine/Component/UIPanel/ScreenResize.ts new file mode 100644 index 0000000..46992e8 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/ScreenResize.ts @@ -0,0 +1,188 @@ +import { CoroutineV2 } from "../../CatanEngine/CoroutineV2/CoroutineV2"; +import { UIManager } from "./UIManager"; + +/**畫面自適應(換場景手動呼叫) */ +export default class ScreenResize { + private static _instance: ScreenResize = null; + public static get Instance(): ScreenResize { return this._instance; } + /**直橫式的製作尺寸 */ + public static readonly CanvasSize: cc.Vec2[] = [cc.v2(1422, 800), cc.v2(800, 1422)]; + /**是否直式機台 */ + public static IsPortrait: number = 0; + /**固定橫直判斷(null=通用.0=固定橫.1=固定直) */ + public static PL: number = null; + + constructor() { + cc.log("creat ScreenResize"); + ScreenResize._instance = this; + ScreenResize._instance.CallManual(); + } + + public AddEven(): void { + ScreenResize._instance.AddResizeEvent(); + } + + /**手動呼叫 */ + public CallManual(): void { + ScreenResize.Instance.GameResize("inGameResize"); + } + + public AddResizeEvent() { + this.GameResize("inGameResize"); + window.onresize = () => { + this.GameResize("window.onresize"); + }; + cc.view.setResizeCallback(() => { + this.GameResize("cc.view.setResizeCallback"); + }); + } + + public GameResize(resizeType: string) { + if (ScreenResize.PL == null) { + //自適應 + ScreenResize.IsPortrait = this._isPortraitMode(); + } else { + //固定直橫 + ScreenResize.IsPortrait = ScreenResize.PL; + } + cc.log("resizeType:" + resizeType); + cc.log("ScreenResize.IsPortrait:" + ScreenResize.IsPortrait); + if (cc.sys.isBrowser) { + //網頁版的修正顯示範圍判斷 + this._browserAutoScreenSetting(resizeType); + } else { + this._appAutoScreenSetting(); + } + this._alignWithScreen(); + CoroutineV2.Single(this._delayChangeDir()).Start(); + this._alignWithGameUI(); + } + private _browserAutoScreenSetting(resizeType: string) { + let frameSize = cc.view.getFrameSize(); + if (ScreenResize.IsPortrait) { + cc.view.setOrientation(cc.macro.ORIENTATION_PORTRAIT); + if (resizeType === "inGameResize") { + //只需要進遊戲設定一次,避免第一次進入場景時萬一跟設計分辨率不同會導致世界座標跑掉(cocos engine底層沒處理好) + if (frameSize.width > frameSize.height) { + cc.view.setFrameSize(frameSize.height, frameSize.width) + } else { + cc.view.setFrameSize(frameSize.width, frameSize.height) + } + } + } else { + cc.view.setOrientation(cc.macro.ORIENTATION_LANDSCAPE); + if (resizeType === "inGameResize") { + //只需要進遊戲設定一次,避免第一次進入場景時萬一跟設計分辨率不同會導致世界座標跑掉(cocos engine底層沒處理好) + if (frameSize.height > frameSize.width) { + cc.view.setFrameSize(frameSize.height, frameSize.width) + } else { + cc.view.setFrameSize(frameSize.width, frameSize.height) + } + } + } + cc.view.setDesignResolutionSize(ScreenResize.CanvasSize[ScreenResize.IsPortrait].x, ScreenResize.CanvasSize[ScreenResize.IsPortrait].y, new cc.ResolutionPolicy(cc.ContainerStrategy["PROPORTION_TO_FRAME"], cc.ContentStrategy["NO_BORDER"])); + if (document.getElementById("GameCanvas")["width"] % 2 != 0) { + document.getElementById("GameCanvas")["width"] -= 1; + } + if (document.getElementById("GameCanvas")["height"] % 2 != 0) { + document.getElementById("GameCanvas")["height"] -= 1; + } + document.body.style.width = "100%"; + document.body.style.height = "100%"; + } + + private _appAutoScreenSetting() { + cc.view.setDesignResolutionSize(ScreenResize.CanvasSize[ScreenResize.IsPortrait].x, ScreenResize.CanvasSize[ScreenResize.IsPortrait].y, cc.ResolutionPolicy.SHOW_ALL); + } + + /**舊版COCOS引擎內的座標對齊 */ + private _alignWithScreen(): void { + var designSize, nodeSize; + if (CC_EDITOR) { + nodeSize = designSize = cc["engine"]["getDesignResolutionSize"](); + cc.Canvas.instance.node.setPosition(designSize.width * 0.5, designSize.height * 0.5); + } + else { + var canvasSize = nodeSize = cc.visibleRect; + designSize = cc.view.getDesignResolutionSize(); + var clipTopRight = !cc.Canvas.instance.fitHeight && !cc.Canvas.instance.fitWidth; + var offsetX = 0; + var offsetY = 0; + if (clipTopRight) { + // offset the canvas to make it in the center of screen + offsetX = (designSize.width - canvasSize.width) * 0.5; + offsetY = (designSize.height - canvasSize.height) * 0.5; + } + cc.Canvas.instance.node.setPosition(canvasSize.width * 0.5 + offsetX, canvasSize.height * 0.5 + offsetY); + } + cc.Canvas.instance.node.width = nodeSize.width; + cc.Canvas.instance.node.height = nodeSize.height; + } + private *_delayChangeDir() { + yield CoroutineV2.WaitTime(0.2); + this._alignWithScreen(); + this._alignWithGameUI(); + } + private _alignWithGameUI() { + UIManager.DireEvent.DispatchCallback([]); + } + private _isPortraitMode(): number { + let sw = window.screen.width; + let sh = window.screen.height; + let _Width = sw < sh ? sw : sh; + let _Height = sw >= sh ? sw : sh; + if (cc.sys.isBrowser) { + //網頁版的顯示範圍判斷 + let w = document.documentElement.clientWidth; + let h = document.documentElement.clientHeight; + let w2 = window.innerWidth; + let h2 = window.innerHeight; + let containerW = Number.parseInt(document.getElementById("Cocos2dGameContainer").style.width) + Number.parseInt(document.getElementById("Cocos2dGameContainer").style.paddingRight) + Number.parseInt(document.getElementById("Cocos2dGameContainer").style.paddingLeft); + let containerH = Number.parseInt(document.getElementById("Cocos2dGameContainer").style.height) + Number.parseInt(document.getElementById("Cocos2dGameContainer").style.paddingTop) + Number.parseInt(document.getElementById("Cocos2dGameContainer").style.paddingBottom); + let rotate = Number.parseInt(document.getElementById("Cocos2dGameContainer").style.transform.replace("rotate(", "").replace("deg)", "")); + /*cc.log( + "w:" + w + ",h:" + h + + "\n,w2:" + w2 + ",h2:" + h2 + + "\n,sw:" + sw + ",sh:" + sh + + "\n,_Width:" + _Width + ",_Height:" + _Height + + "\n,frameW:" + cc.view.getFrameSize().width + ",frameH:" + cc.view.getFrameSize().height + + "\n,containerW:" + containerW + ",containerH:" + containerH + ",rotate:" + rotate + + "\n,canvasW:" + cc.game.canvas.width + ",canvasrH:" + cc.game.canvas.height + );*/ + if (w == _Width) { + return 1; + } else if (w == _Height) { + if (!CC_DEV) { + return 0; + } else { + if (containerW >= containerH) { + return rotate == 0 ? 0 : 1; + } else { + return rotate == 0 ? 1 : 0; + } + } + } else if (w == w2) { + if (containerW >= containerH) { + return rotate == 0 ? 0 : 1; + } else { + return rotate == 0 ? 1 : 0; + } + } else { + if (containerW >= containerH) { + return rotate == 0 ? 0 : 1; + } else { + return rotate == 0 ? 1 : 0; + } + } + } else { + if (sw == _Width) { + return 1; + } else if (sw == _Height) { + return 0; + } else { + cc.log("XXXXXXXXXXXXXXXXXX"); + return 1; + } + } + } +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/UIPanel/ScreenResize.ts.meta b/assets/Script/Engine/Component/UIPanel/ScreenResize.ts.meta new file mode 100644 index 0000000..24bedfb --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/ScreenResize.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "187716a0-d35c-4b06-80fb-48b799e7fe9e", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI.meta b/assets/Script/Engine/Component/UIPanel/SwitchShowUI.meta new file mode 100644 index 0000000..48c3534 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "f63b1b10-07ba-49bc-aa9f-a0ea4c652076", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchActiveGroup.ts b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchActiveGroup.ts new file mode 100644 index 0000000..8937eb7 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchActiveGroup.ts @@ -0,0 +1,27 @@ +import ScreenResize from "../ScreenResize"; + +const { ccclass, property } = cc._decorator; + +@ccclass("SwitchActive") +export class SwitchActive { + @property({ type: cc.Node }) + public UI: cc.Node = null; + @property({ type: cc.Boolean/*, visible: function (this: ImageGroup) { return this.Reset; } */ }) + public show: boolean[] = []; + + public SetObjActive(obj: cc.Node, show: boolean): void { + obj.active = show; + } +} +@ccclass +export default class SwitchActiveGroup extends cc.Component { + @property({ displayName: "縮放scale群組", type: SwitchActive }) + public ScaleGroups: SwitchActive[] = []; + public Run(): void { + if (this.ScaleGroups != null && this.ScaleGroups.length) { + for (let group of this.ScaleGroups) { + group.SetObjActive(group.UI, group.show[ScreenResize.IsPortrait]); + } + } + } +} diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchActiveGroup.ts.meta b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchActiveGroup.ts.meta new file mode 100644 index 0000000..c977b5e --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchActiveGroup.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "a96cf057-9e35-4146-89df-5f0f4819fb6c", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchActiveGroupExtra.ts b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchActiveGroupExtra.ts new file mode 100644 index 0000000..ce782c4 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchActiveGroupExtra.ts @@ -0,0 +1,32 @@ +import ScreenResize from "../ScreenResize"; +import { UIManager } from "../UIManager"; + +const { ccclass, property } = cc._decorator; + +@ccclass("SwitchActiveObj") +export class SwitchActiveObj { + @property({ type: cc.Node }) + public UI: cc.Node = null; + @property({ type: cc.Boolean/*, visible: function (this: ImageGroup) { return this.Reset; } */ }) + public show: boolean[] = []; + + public SetObjActive(obj: cc.Node, show: boolean): void { + obj.active = show; + } +} +@ccclass +export default class SwitchActiveGroupExtra extends cc.Component { + @property({ displayName: "縮放scale群組", type: SwitchActiveObj }) + public ScaleGroups: SwitchActiveObj[] = []; + public Run(): void { + if (this.ScaleGroups != null && this.ScaleGroups.length) { + for (let group of this.ScaleGroups) { + group.SetObjActive(group.UI, group.show[ScreenResize.IsPortrait]); + } + } + } + onLoad() { + UIManager.DireEvent.AddCallback(this.Run, this); + this.Run(); + } +} diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchActiveGroupExtra.ts.meta b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchActiveGroupExtra.ts.meta new file mode 100644 index 0000000..a1edd09 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchActiveGroupExtra.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "9d9e9007-973c-4926-b5cf-aacceae74665", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchImgGroup.ts b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchImgGroup.ts new file mode 100644 index 0000000..7b08c11 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchImgGroup.ts @@ -0,0 +1,30 @@ +import ScreenResize from "../ScreenResize"; + +const { ccclass, property } = cc._decorator; +@ccclass("SwitchImgSourceGroup") +export class SwitchImgSourceGroup { + @property({ type: cc.Node }) + public Sprite: cc.Sprite = null; + @property({ type: cc.SpriteFrame/*, visible: function (this: ImageGroup) { return this.Reset; } */ }) + public SourceImg: cc.SpriteFrame[] = []; + + public SetImg(obj: cc.Sprite, switchObj: cc.SpriteFrame): void { + if (obj == null || switchObj == null) { + return; + } + obj.getComponent(cc.Sprite).spriteFrame = switchObj; + } +} + +@ccclass +export default class SwitchImgGroup extends cc.Component { + @property({ displayName: "換UI群組", type: SwitchImgSourceGroup }) + public ImgGroups: SwitchImgSourceGroup[] = []; + public Run(): void { + if (this.ImgGroups != null && this.ImgGroups.length) { + for (let group of this.ImgGroups) { + group.SetImg(group.Sprite, group.SourceImg[ScreenResize.IsPortrait]); + } + } + } +} diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchImgGroup.ts.meta b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchImgGroup.ts.meta new file mode 100644 index 0000000..80c68ef --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchImgGroup.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "56151188-edd5-4384-8a84-50a84456a6c3", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchImgGroupExtra.ts b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchImgGroupExtra.ts new file mode 100644 index 0000000..9c306e5 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchImgGroupExtra.ts @@ -0,0 +1,35 @@ +import ScreenResize from "../ScreenResize"; +import { UIManager } from "../UIManager"; + +const { ccclass, property } = cc._decorator; +@ccclass("SwitchImgSource") +export class SwitchImgSource { + @property({ type: cc.Node }) + public Sprite: cc.Sprite = null; + @property({ type: cc.SpriteFrame/*, visible: function (this: ImageGroup) { return this.Reset; } */ }) + public SourceImg: cc.SpriteFrame[] = []; + + public SetImg(obj: cc.Sprite, switchObj: cc.SpriteFrame): void { + if (obj == null || switchObj == null) { + return; + } + obj.getComponent(cc.Sprite).spriteFrame = switchObj; + } +} + +@ccclass +export default class SwitchImgGroupExtra extends cc.Component { + @property({ displayName: "換UI群組", type: SwitchImgSource }) + public ImgGroups: SwitchImgSource[] = []; + public Run(): void { + if (this.ImgGroups != null && this.ImgGroups.length) { + for (let group of this.ImgGroups) { + group.SetImg(group.Sprite, group.SourceImg[ScreenResize.IsPortrait]); + } + } + } + onLoad() { + UIManager.DireEvent.AddCallback(this.Run, this); + this.Run(); + } +} diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchImgGroupExtra.ts.meta b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchImgGroupExtra.ts.meta new file mode 100644 index 0000000..a06b42e --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchImgGroupExtra.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "208b15b7-a199-4b86-a538-3d1d18695552", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchPositionGroup.ts b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchPositionGroup.ts new file mode 100644 index 0000000..e76a394 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchPositionGroup.ts @@ -0,0 +1,31 @@ +import ScreenResize from "../ScreenResize"; + +const { ccclass, property } = cc._decorator; +@ccclass("SwitchPosition") +export class SwitchPosition { + @property({ type: cc.Node }) + public UI: cc.Node = null; + @property({ type: cc.Vec2/*, visible: function (this: ImageGroup) { return this.Reset; } */ }) + public Pos: cc.Vec2[] = []; + + public SetPos(obj: cc.Node, posNum: cc.Vec2): void { + obj.setPosition(posNum); + } +} +@ccclass +export default class SwitchPositionGroup extends cc.Component { + @property({ displayName: "改變座標群組", type: SwitchPosition }) + public PosGroups: SwitchPosition[] = []; + public Run(): void { + if (this.PosGroups != null && this.PosGroups.length) { + for (let group of this.PosGroups) { + if (!group.UI || !group.Pos[ScreenResize.IsPortrait]) { + cc.error("沒有設定節點或座標.name=" + this.node.name); + continue; + } + group.SetPos(group.UI, group.Pos[ScreenResize.IsPortrait]); + } + } + } + +} diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchPositionGroup.ts.meta b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchPositionGroup.ts.meta new file mode 100644 index 0000000..75fdda5 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchPositionGroup.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "2083e1d2-be99-4173-b425-cdc9da21cbb3", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchPositionGroupExtra.ts b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchPositionGroupExtra.ts new file mode 100644 index 0000000..b43b4ec --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchPositionGroupExtra.ts @@ -0,0 +1,33 @@ +import ScreenResize from "../ScreenResize"; +import { UIManager } from "../UIManager"; + +const { ccclass, property } = cc._decorator; +@ccclass("SwitchPositionObj") +export class SwitchPositionObj { + @property({ type: cc.Node }) + public UI: cc.Node = null; + @property({ type: cc.Vec2/*, visible: function (this: ImageGroup) { return this.Reset; } */ }) + public Pos: cc.Vec2[] = []; + + public SetPos(obj: cc.Node, posNum: cc.Vec2): void { + obj.setPosition(posNum); + } +} +@ccclass +export default class SwitchPositionGroupExtra extends cc.Component { + @property({ displayName: "改變座標群組", type: SwitchPositionObj }) + public PosGroups: SwitchPositionObj[] = []; + public Run(param: any[] = null): void { + if (this.PosGroups != null && this.PosGroups.length) { + for (let group of this.PosGroups) { + cc.log("橫直轉換:" + group.UI.name + ":" + group.Pos[ScreenResize.IsPortrait]); + group.SetPos(group.UI, group.Pos[ScreenResize.IsPortrait]); + } + } + } + onLoad() { + UIManager.DireEvent.AddCallback(this.Run, this); + this.Run(); + } + +} diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchPositionGroupExtra.ts.meta b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchPositionGroupExtra.ts.meta new file mode 100644 index 0000000..9ea4434 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchPositionGroupExtra.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "512c7a23-3a86-4d84-abc6-9553be08da10", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchRotationGroup.ts b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchRotationGroup.ts new file mode 100644 index 0000000..8ad8824 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchRotationGroup.ts @@ -0,0 +1,27 @@ +import ScreenResize from "../ScreenResize"; + +const { ccclass, property } = cc._decorator; + +@ccclass("SwitchRotation") +export class SwitchRotation { + @property({ type: cc.Node }) + public UI: cc.Node = null; + @property({ type: cc.Float/*, visible: function (this: ImageGroup) { return this.Reset; } */ }) + public rotaitonNum: number[] = []; + + public SetObjRotation(obj: cc.Node, r: number): void { + obj.angle = -r; + } +} +@ccclass +export default class SwitchRotationGroup extends cc.Component { + @property({ displayName: "設定rotation群組", type: SwitchRotation }) + public ScaleGroups: SwitchRotation[] = []; + public Run(): void { + if (this.ScaleGroups != null && this.ScaleGroups.length) { + for (let group of this.ScaleGroups) { + group.SetObjRotation(group.UI, group.rotaitonNum[ScreenResize.IsPortrait]); + } + } + } +} diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchRotationGroup.ts.meta b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchRotationGroup.ts.meta new file mode 100644 index 0000000..e0bdd40 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchRotationGroup.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "e53fa0f6-ddad-41e1-b0d2-df151a2f3ff0", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchScaleGroup.ts b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchScaleGroup.ts new file mode 100644 index 0000000..aa68aae --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchScaleGroup.ts @@ -0,0 +1,27 @@ +import ScreenResize from "../ScreenResize"; + +const { ccclass, property } = cc._decorator; + +@ccclass("SwitchSize") +export class SwitchScale { + @property({ type: cc.Node }) + public UI: cc.Node = null; + @property({ type: cc.Float/*, visible: function (this: ImageGroup) { return this.Reset; } */ }) + public ScaleNum: number[] = []; + + public SetObjScale(obj: cc.Node, scaleNum: number): void { + obj.setScale(scaleNum); + } +} +@ccclass +export default class SwitchScaleGroup extends cc.Component { + @property({ displayName: "縮放scale群組", type: SwitchScale }) + public ScaleGroups: SwitchScale[] = []; + public Run(): void { + if (this.ScaleGroups != null && this.ScaleGroups.length) { + for (let group of this.ScaleGroups) { + group.SetObjScale(group.UI, group.ScaleNum[ScreenResize.IsPortrait]); + } + } + } +} diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchScaleGroup.ts.meta b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchScaleGroup.ts.meta new file mode 100644 index 0000000..b5d78e0 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchScaleGroup.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "4e2f6235-b5a6-4c44-9ea5-4779b97f1630", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchScaleGroupExtra.ts b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchScaleGroupExtra.ts new file mode 100644 index 0000000..92bf1a2 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchScaleGroupExtra.ts @@ -0,0 +1,32 @@ +import ScreenResize from "../ScreenResize"; +import { UIManager } from "../UIManager"; + +const { ccclass, property } = cc._decorator; + +@ccclass("SwitchSizeObj") +export class SwitchScaleObj { + @property({ type: cc.Node }) + public UI: cc.Node = null; + @property({ type: cc.Float/*, visible: function (this: ImageGroup) { return this.Reset; } */ }) + public ScaleNum: number[] = []; + + public SetObjScale(obj: cc.Node, scaleNum: number): void { + obj.setScale(scaleNum); + } +} +@ccclass +export default class SwitchScaleGroupExtra extends cc.Component { + @property({ displayName: "縮放scale群組", type: SwitchScaleObj }) + public ScaleGroups: SwitchScaleObj[] = []; + public Run(): void { + if (this.ScaleGroups != null && this.ScaleGroups.length) { + for (let group of this.ScaleGroups) { + group.SetObjScale(group.UI, group.ScaleNum[ScreenResize.IsPortrait]); + } + } + } + onLoad() { + UIManager.DireEvent.AddCallback(this.Run, this); + this.Run(); + } +} diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchScaleGroupExtra.ts.meta b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchScaleGroupExtra.ts.meta new file mode 100644 index 0000000..f0e250a --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchScaleGroupExtra.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "065d43a0-b2c1-4a41-8e84-8a0f3d3817f2", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchSoueceGroup.ts b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchSoueceGroup.ts new file mode 100644 index 0000000..7c0b725 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchSoueceGroup.ts @@ -0,0 +1,34 @@ +import ScreenResize from "../ScreenResize"; + +const { ccclass, property } = cc._decorator; +@ccclass("SwitchUIGroup") +export class SwitchUIGroup { + @property({ type: cc.Node }) + public UI: cc.Node = null; + @property({ type: cc.Integer/*, visible: function (this: ImageGroup) { return this.Reset; } */ }) + public SourceID: number[] = []; + + public Set(obj: cc.Node, switchObj: cc.Node): void { + if (obj == null || switchObj == null) { + return; + } + obj.removeAllChildren(); + obj.ExAddChild(switchObj); + } +} + +@ccclass +export default class SwitchSoueceGroup extends cc.Component { + @property({ displayName: "換UI群組", type: SwitchUIGroup }) + public UIGroups: SwitchUIGroup[] = []; + public Run(switchObg: cc.Node): void { + if (this.UIGroups != null && this.UIGroups.length) { + for (let group of this.UIGroups) { + let child: cc.Node[] = switchObg.getChildByName(group.SourceID[ScreenResize.IsPortrait].toString()).children; + for (let i: number = 0; i < child.length; i++) { + group.Set(group.UI, child[i]); + } + } + } + } +} diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchSoueceGroup.ts.meta b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchSoueceGroup.ts.meta new file mode 100644 index 0000000..ee27060 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchSoueceGroup.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "45ea513f-8665-4d40-8b51-3a67ab35f327", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchWHGroup.ts b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchWHGroup.ts new file mode 100644 index 0000000..5678f46 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchWHGroup.ts @@ -0,0 +1,26 @@ +import ScreenResize from "../ScreenResize"; + +const { ccclass, property } = cc._decorator; +@ccclass("SwitchWH") +export class SwitchWH { + @property({ type: cc.Node }) + public UI: cc.Node = null; + @property({ type: cc.Vec2/*, visible: function (this: ImageGroup) { return this.Reset; } */ }) + public WH: cc.Vec2[] = []; + + public SetWH(obj: cc.Node, WHNum: cc.Vec2): void { + obj.SetSizeDelta(WHNum); + } +} +@ccclass +export default class SwitchWHGroup extends cc.Component { + @property({ displayName: "改變寬高群組", type: SwitchWH }) + public WHGroups: SwitchWH[] = []; + public Run(): void { + if (this.WHGroups != null && this.WHGroups.length) { + for (let group of this.WHGroups) { + group.SetWH(group.UI, group.WH[ScreenResize.IsPortrait]); + } + } + } +} diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchWHGroup.ts.meta b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchWHGroup.ts.meta new file mode 100644 index 0000000..9ccdef1 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchWHGroup.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "5cfb2592-7ada-4b09-81dc-0dc14dc94aa5", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchWHGroupExtra.ts b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchWHGroupExtra.ts new file mode 100644 index 0000000..e7f8127 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchWHGroupExtra.ts @@ -0,0 +1,31 @@ +import ScreenResize from "../ScreenResize"; +import { UIManager } from "../UIManager"; + +const { ccclass, property } = cc._decorator; +@ccclass("SwitchWHObj") +export class SwitchWHObj { + @property({ type: cc.Node }) + public UI: cc.Node = null; + @property({ type: cc.Vec2/*, visible: function (this: ImageGroup) { return this.Reset; } */ }) + public WH: cc.Vec2[] = []; + + public SetWH(obj: cc.Node, WHNum: cc.Vec2): void { + obj.SetSizeDelta(WHNum); + } +} +@ccclass +export default class SwitchWHGroupExtra extends cc.Component { + @property({ displayName: "改變寬高群組", type: SwitchWHObj }) + public WHGroups: SwitchWHObj[] = []; + public Run(): void { + if (this.WHGroups != null && this.WHGroups.length) { + for (let group of this.WHGroups) { + group.SetWH(group.UI, group.WH[ScreenResize.IsPortrait]); + } + } + } + onLoad() { + UIManager.DireEvent.AddCallback(this.Run, this); + this.Run(); + } +} diff --git a/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchWHGroupExtra.ts.meta b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchWHGroupExtra.ts.meta new file mode 100644 index 0000000..e270c71 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/SwitchShowUI/SwitchWHGroupExtra.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "b9806317-d4bd-4aac-be3e-a16b0a3159f9", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/UIPanel/UIManager.ts b/assets/Script/Engine/Component/UIPanel/UIManager.ts new file mode 100644 index 0000000..6a09050 --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/UIManager.ts @@ -0,0 +1,8 @@ +import { Action } from "../../CatanEngine/CSharp/System/Action"; + +export class UIManager { + public static readonly ScreenScale = 0.58; + /**橫直切換監聽 */ + public static readonly DireEvent: Action = new Action(); + +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/UIPanel/UIManager.ts.meta b/assets/Script/Engine/Component/UIPanel/UIManager.ts.meta new file mode 100644 index 0000000..ba46edd --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/UIManager.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "277e84bb-8d5d-4e7c-8dd1-06d752d7bba1", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Component/UIPanel/UIPanel.ts b/assets/Script/Engine/Component/UIPanel/UIPanel.ts new file mode 100644 index 0000000..312fc6b --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/UIPanel.ts @@ -0,0 +1,182 @@ +import { CoroutineV2 } from "../../CatanEngine/CoroutineV2/CoroutineV2"; +import ScreenResize from "./ScreenResize"; +import SwitchActiveGroup from "./SwitchShowUI/SwitchActiveGroup"; +import SwitchImgGroup from "./SwitchShowUI/SwitchImgGroup"; +import SwitchPositionGroup from "./SwitchShowUI/SwitchPositionGroup"; +import SwitchRotationGroup from "./SwitchShowUI/SwitchRotationGroup"; +import SwitchScaleGroup from "./SwitchShowUI/SwitchScaleGroup"; +import SwitchWHGroup from "./SwitchShowUI/SwitchWHGroup"; +import { UIManager } from "./UIManager"; + +const { ccclass } = cc._decorator; + +@ccclass +export default class UIPanel extends cc.Component { + + /**[基底UIPanel]等待跳出旗標*/ + private _isWaiting: boolean = false; + + /** + * [基底UIPanel]初始化實做(UI每次創建時呼叫, 只會呼叫一次) + * @param initData + */ + protected ImplementInitial(...initData: any[]): void { + } + + /** + * [基底UIPanel]顯示準備實做(UI顯示前呼叫) + * @param param + */ + protected *ImplementReadyShow(...param: any[]): IterableIterator { + } + + /** + * [基底UIPanel]顯示實做(UI顯示呼叫) + * @param param + */ + protected *ImplementShow(): IterableIterator { + } + + /** + * [基底UIPanel]隱藏(實做) + * @param param + */ + protected *ImplementHide(...param: any[]): IterableIterator { + } + + /** + * [基底UIPanel]移除實做 + */ + protected ImplementDestroy() { + } + + /** + * [基底UIPanel]直橫版切換 + * @param param + */ + public ChangeDire(param: any[] = null): void { + if (!cc.isValid(this)) { + return; + } + cc.log("ChangeDire:" + this.name); + if (this.getComponent(SwitchScaleGroup)) { + this.getComponent(SwitchScaleGroup).Run(); + } + if (this.getComponent(SwitchPositionGroup)) { + this.getComponent(SwitchPositionGroup).Run(); + } + if (this.getComponent(SwitchWHGroup)) { + this.getComponent(SwitchWHGroup).Run(); + } + if (this.getComponent(SwitchImgGroup)) { + this.getComponent(SwitchImgGroup).Run(); + } + if (this.getComponent(SwitchActiveGroup)) { + this.getComponent(SwitchActiveGroup).Run(); + } + if (this.getComponent(SwitchRotationGroup)) { + this.getComponent(SwitchRotationGroup).Run(); + } + this._uiMaskHandler(); + + } + private _uiMaskHandler(): void { + let mask: cc.Node = this.node.getChildByName("Mask"); + if (mask && this.node.parent.name == "ElementContent") { + let size: cc.Vec2 = ScreenResize.CanvasSize[ScreenResize.IsPortrait]; + mask.SetSizeDelta(cc.v2(size.x / UIManager.ScreenScale, size.y / UIManager.ScreenScale)); + } + } + //======================================================================================= + /** + * [禁止複寫]創UI + * @param source + * @param parent + * @param data + */ + public static CreateUI(source: cc.Prefab, parent: cc.Node, ...data: any[]): UIPanel { + let node = parent.ExAddChild(source); + let script = node.getComponent(UIPanel); + script.Initial(...data); + return script; + } + + /** + * [禁止複寫]初始化 + * @param initData + */ + public Initial(...initData: any[]): void { + UIManager.DireEvent.RemoveByBindTarget(this); + UIManager.DireEvent.AddCallback(this.ChangeDire, this); + this.node.active = false; + this._uiMaskHandler(); + this.ImplementInitial(...initData); + } + + /** + * [禁止複寫]顯示UI + * @param param + */ + public *Show(...param: any[]): IterableIterator { + yield* this.ImplementReadyShow(...param); + this.node.active = true; + yield* this.ImplementShow(); + } + + /** + * [禁止複寫]隱藏UI + * @param param + */ + public *Hide(...param: any[]): IterableIterator { + if (this._isWaiting) { + cc.warn(this.node.name, "_isWaiting = true無法關閉"); + } + else { + yield* this.ImplementHide(...param); + if (this && this.node) { + this.node.active = false; + } + } + } + + /** + * [禁止複寫]等待UI + * @param showData + * @param hideData + */ + public *Wait(showData: any[] = [], hideData: any[] = []): IterableIterator { + yield* this.Show(...showData); + this._isWaiting = true; + while (this._isWaiting) { + yield null; + } + yield* this.Hide(...hideData); + } + + /** + * [禁止複寫]關閉UI + * @param param + */ + public Close(...param: any[]): void { + if (this._isWaiting) { + this._isWaiting = false; + } + else { + CoroutineV2.Single(this.Hide(...param)).Start(); + } + } + + //======================================================================================= + onLoad() { + UIManager.DireEvent.RemoveByBindTarget(this); + UIManager.DireEvent.AddCallback(this.ChangeDire, this); + } + //COCOS引擎內建生命週期 + //onLoad->start->update->lateUpdate->onDestroy->onEnable->onDisable + onDestroy() { + UIManager.DireEvent.RemoveByBindTarget(this); + this.ImplementDestroy(); + } + + //======================================================================================= +} diff --git a/assets/Script/Engine/Component/UIPanel/UIPanel.ts.meta b/assets/Script/Engine/Component/UIPanel/UIPanel.ts.meta new file mode 100644 index 0000000..1ec395b --- /dev/null +++ b/assets/Script/Engine/Component/UIPanel/UIPanel.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "066bf037-e621-482f-a93f-fc1e59e90cfb", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Data/LocalStorageData.ts b/assets/Script/Engine/Data/LocalStorageData.ts new file mode 100644 index 0000000..f390a3e --- /dev/null +++ b/assets/Script/Engine/Data/LocalStorageData.ts @@ -0,0 +1,51 @@ + +export default class LocalStorageData { + private static _instance: LocalStorageData = null; + public static get Instance(): LocalStorageData { + return LocalStorageData._instance; + } + + constructor() { + LocalStorageData._instance = this; + } + + // ======================================================================================= + public get CompileVersion(): string { return cc.sys.localStorage.getItem("CompileVersion"); } + public set CompileVersion(value: string) { cc.sys.localStorage.setItem("CompileVersion", value.toString()); } + public get RemoteVerList(): string { return cc.sys.localStorage.getItem("RemoteVerList"); } + public set RemoteVerList(value: string) { cc.sys.localStorage.setItem("RemoteVerList", value); } + public get LocalVerList(): string { return cc.sys.localStorage.getItem("LocalVerList"); } + public set LocalVerList(value: string) { cc.sys.localStorage.setItem("LocalVerList", value); } + public get ComboDeviceID(): string { return cc.sys.localStorage.getItem("ComboDeviceID") || ""; } + public set ComboDeviceID(value: string) { cc.sys.localStorage.setItem("ComboDeviceID", value); } + public get BundleUrl(): string { return cc.sys.localStorage.getItem("BundleUrl"); } + public set BundleUrl(value: string) { cc.sys.localStorage.setItem("BundleUrl", value); } + public get Language(): string { return cc.sys.localStorage.getItem("language"); } + public set Language(value: string) { cc.sys.localStorage.setItem("language", value); } + public get MusicType(): string { return cc.sys.localStorage.getItem("MusicType"); } + public set MusicType(value: string) { cc.sys.localStorage.setItem("MusicType", value); } + public get SoundType(): string { return cc.sys.localStorage.getItem("SoundType"); } + public set SoundType(value: string) { cc.sys.localStorage.setItem("SoundType", value); } + public get LvUpNotifyType(): boolean { return JSON.parse(cc.sys.localStorage.getItem("LvUpNotifyType")); } + public set LvUpNotifyType(value: boolean) { cc.sys.localStorage.setItem("LvUpNotifyType", JSON.stringify(value)); } + public get WinNotifyType(): boolean { return JSON.parse(cc.sys.localStorage.getItem("WinNotifyType")); } + public set WinNotifyType(value: boolean) { cc.sys.localStorage.setItem("WinNotifyType", JSON.stringify(value)); } + public get DownloadList_Preview(): string { return cc.sys.localStorage.getItem("DownloadList_Preview"); } + public set DownloadList_Preview(value: string) { cc.sys.localStorage.setItem("DownloadList_Preview", value); } + + /** + * key: id + * value: 是否開過卡 + */ + public get BingoCardInfo(): Map { return cc.sys.localStorage.getItem("BingoCardInfo") ? new Map(JSON.parse(cc.sys.localStorage.getItem("BingoCardInfo"))) : new Map(); } + public set BingoCardInfo(value: Map) { cc.sys.localStorage.setItem("BingoCardInfo", JSON.stringify(Array.from(value.entries()))); } + + /** + * key: id + * value: 是否開過卡 + */ + public get FiveCardInfo(): Map { return cc.sys.localStorage.getItem("FiveCardInfo") ? new Map(JSON.parse(cc.sys.localStorage.getItem("FiveCardInfo"))) : new Map(); } + public set FiveCardInfo(value: Map) { cc.sys.localStorage.setItem("FiveCardInfo", JSON.stringify(Array.from(value.entries()))); } + + // ======================================================================================= +} diff --git a/assets/Script/Engine/Data/LocalStorageData.ts.meta b/assets/Script/Engine/Data/LocalStorageData.ts.meta new file mode 100644 index 0000000..8bf9148 --- /dev/null +++ b/assets/Script/Engine/Data/LocalStorageData.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "64763dbc-3640-450c-91e6-23a149ecc675", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/HUD/AssetBundleMamager.ts b/assets/Script/Engine/HUD/AssetBundleMamager.ts new file mode 100644 index 0000000..7d826bf --- /dev/null +++ b/assets/Script/Engine/HUD/AssetBundleMamager.ts @@ -0,0 +1,456 @@ +import BusinessTypeSetting from "../../_BusinessTypeSetting/BusinessTypeSetting"; +import LocalStorageData from "../Data/LocalStorageData"; +import Enum_Loading from "../HUDV2/Enum_Loading"; +import HUDM from "./HUDM"; + +export default class AssetBundleMamager { + //#region static 屬性 + + private static _instance: AssetBundleMamager = null; + public static get Instance(): AssetBundleMamager { return AssetBundleMamager._instance; } + + //#endregion + + //#region public 屬性 + + public HUGroup: Map = new Map(); + + /** 本地VerList */ + public LocalVerList: Enum_Loading.VerListObj = null; + + /** 遠端VerList */ + public RemoteVerList: Enum_Loading.VerListObj = null; + + public DownloadList_Preview: Object = {}; + + /** IsChangeBundleUrl */ + public IsChangeBundleUrl: boolean = false; + + //#endregion + + //#region Lifecycle + + constructor() { + AssetBundleMamager._instance = this; + CC_PREVIEW && this._initdownloadList_Preview(); + } + + //#endregion + + //#region Custom Function + + /** + * 取得Bundle + * @param {string} BundleName Bundle名稱 + * @param {string} Version 版號 + * @return {cc.AssetManager.Bundle} Bundle + */ + public *GetBundle(BundleName: string, Version: string = ""): IterableIterator { + let bundle: cc.AssetManager.Bundle = cc.assetManager.getBundle(BundleName); + if (bundle) { + return bundle; + } + + // options是可选参数,引擎会根据保留字段 进行对应的操作,这里添加了version和onFileProgress,可用来记录热更资源版本和下载进度 + let options: any = null; + + let BundleUrl: string = BundleName; + if (cc.sys.isNative && !this.LocalVerList[BundleName].UseLocal) { + BundleUrl = `${(jsb.fileUtils ? jsb.fileUtils.getWritablePath() : "/")}Bundle/${BundleName}/remote/${BundleName}`; + options = { + version: Version + }; + } + + cc.assetManager.loadBundle(BundleUrl, options, (err: Error, resp: cc.AssetManager.Bundle) => { + if (err) { + cc.error(err); + bundle = null; + } + bundle = resp; + }); + while (typeof bundle === "undefined") { + yield null; + } + + return bundle; + } + + /** + * 更新Bundle + * @param {HUDM} HUDName HUD + */ + public *UpdateBundle(HUDName: HUDM | string, onFileProgress?: (finish: number, total: number, item: string) => void): IterableIterator { + let HUD: HUDM; + if (HUDName instanceof HUDM) { + HUD = HUDName; + } else { + HUD = this.GetHUD(HUDName); + } + let UpdateingData: Enum_Loading.UpdateingDataObj = yield* HUD.HUD(onFileProgress); + if (UpdateingData.IsUpdatecomplete) { + this.LocalVerList[HUD.BundleName] = this.RemoteVerList[HUD.BundleName]; + this.LocalVerList[HUD.BundleName]["UseLocal"] = false; + LocalStorageData.Instance.LocalVerList = JSON.stringify(this.LocalVerList); + } + return UpdateingData; + } + + /** + * 更新Bundle + * @param {HUDM} HUDName HUD + */ + public *RetryUpdateBundle(HUDName: HUDM | string, onFileProgress?: (finish: number, total: number, item: string) => void): IterableIterator { + let HUD: HUDM; + if (HUDName instanceof HUDM) { + HUD = HUDName; + } else { + HUD = this.GetHUD(HUDName); + } + let UpdateingData: Enum_Loading.UpdateingDataObj = yield* HUD.RetryDownLoadFailedAssets(); + return UpdateingData; + } + + /** + * 從Bundle取得資源 + * @param {cc.AssetManager.Bundle | string} BundleName Bundle名稱 + * @param {string} SourceName 資源名稱 + * @param {string} type 資源型別 + * @return {any} Source + */ + public *GetBundleSource(BundleName: cc.AssetManager.Bundle | string, SourceName: string, type?: string | Bundle_Source_Type, onFileProgress?: (finish: number, total: number, item: cc.AssetManager.RequestItem) => void): IterableIterator { + let bundle: cc.AssetManager.Bundle; + let source: any; + if (BundleName instanceof cc.AssetManager.Bundle) { + bundle = BundleName; + } else { + bundle = cc.assetManager.getBundle(BundleName); + if (!bundle) { + cc.error(`GetBundleSource Error BundleName: ${BundleName}`); + return null; + } + } + + switch (type) { + case Bundle_Source_Type.Scene: { + bundle.loadScene(SourceName, onFileProgress, function (err: Error, scene: cc.SceneAsset): void { + if (err) { + cc.error(err); + return null; + } + // cc.director.runScene(scene); + source = scene; + }); + break; + } + + case Bundle_Source_Type.Json: { + bundle.load(SourceName, onFileProgress, function (err: Error, json: cc.JsonAsset): void { + if (err) { + cc.error(err); + return null; + } + // source = JSON.parse(json["_nativeAsset"]); + source = json; + }); + break; + } + + case Bundle_Source_Type.Prefab: { + bundle.load(SourceName, cc.Prefab, onFileProgress, function (err: Error, prefab: cc.Asset): void { + if (err) { + cc.error(err); + return null; + } + // source = JSON.parse(json["_nativeAsset"]); + source = prefab; + }); + break; + } + + default: + bundle.load(SourceName, function (err: Error, any: any): void { + if (err) { + cc.error(err); + return null; + } + source = any; + }); + break; + } + + while (typeof source === "undefined") { + yield null; + } + return source; + } + + /** + * 釋放Bundle + * @param {string} slotID slotID + */ + public *BundleRelease(slotID: number): IterableIterator { + let gameName: string = `Game_${slotID}`; + let sceneName: string = `Slot${slotID}`; + let bundle: cc.AssetManager.Bundle = cc.assetManager.getBundle(gameName); + if (!bundle) { + cc.log(`BundleRelease Error BundleName: ${gameName}`); + return; + } + + // let bundles: cc.AssetManager.Cache = cc.assetManager.bundles; + // let cacheDir: string = cc.assetManager.cacheManager.cacheDir; + // let cachedFiles: Object = cc.assetManager.cacheManager.cachedFiles; + + yield* this.DelBundleCache(bundle); + yield* this.DelOthersCache(slotID); + bundle.release(sceneName, cc.SceneAsset); + cc.assetManager.removeBundle(bundle); + cc.sys.garbageCollect(); + } + + /** + * 從Bundle刪除暫存資源 + * @param {string} BundleName Bundle名稱 + */ + public *DelBundleCache(BundleName: cc.AssetManager.Bundle | string): IterableIterator { + if (!CC_JSB) { + return; + } + let bundle: cc.AssetManager.Bundle; + let source: any; + if (BundleName instanceof cc.AssetManager.Bundle) { + bundle = BundleName; + } else { + bundle = cc.assetManager.getBundle(BundleName); + if (!bundle) { + // cc.error(`GetBundleSource Error BundleName: ${BundleName}`); + // return; + bundle = yield* AssetBundleMamager.Instance.GetBundle(BundleName, this.RemoteVerList[BundleName].Version); + } + } + + let _map: Object = bundle["_config"].assetInfos._map; + for (let map of Object.keys(_map)) { + let path: string = _map[map].path; + if (!path) { + break; + } + source = yield* AssetBundleMamager.Instance.GetBundleSource(bundle, path); + cc.assetManager.cacheManager.removeCache(source.nativeUrl); + bundle.release(path); + // return; + } + } + + /** + * 從cachedFiles刪除暫存資源 + * @param {number} slotID slotID + */ + public *DelOthersCache(slotID: number): IterableIterator { + if (!CC_JSB) { + return; + } + let cachedFiles: Object = cc.assetManager.cacheManager.cachedFiles["_map"]; + let delcache_group: string[] = [`shared/jsons`, `Slot/Slot${slotID}`, "sounds/Slot/Default", `${BusinessTypeSetting.FolderUrlBundle}project.manifest`, "submit.txt"]; + for (let cached of Object.keys(cachedFiles)) { + for (var i: number = 0; i < delcache_group.length; ++i) { + let delcache: string = delcache_group[i]; + if (cached.includes(delcache)) { + cc.assetManager.cacheManager.removeCache(cached); + // console.log(`removeCache: ${cached}`); + break; + } + } + } + } + + public GetHUD(BundleName: HUDM | string): HUDM { + let HUD: HUDM; + if (BundleName instanceof HUDM) { + HUD = BundleName; + } else { + if (!this.HUGroup.has(BundleName)) { + HUD = new HUDM(BundleName); + this.HUGroup.set(BundleName, HUD); + } else { + HUD = this.HUGroup.get(BundleName); + } + HUD = this.HUGroup.get(BundleName); + } + return HUD; + } + + /** 刪除全部暫存資源 */ + public ClearAllCache(): void { + cc.assetManager.cacheManager.clearCache(); + cc.game.restart(); + } + + + public *CheckBundleNeedHUD(BundleName: HUDM | string): IterableIterator { + let HUD: HUDM; + if (BundleName instanceof HUDM) { + HUD = BundleName; + } else { + HUD = this.GetHUD(BundleName); + } + if (!this.LocalVerList[HUD.BundleName]) { + this.LocalVerList[HUD.BundleName] = new Enum_Loading.BundleDataObj(); + let apkVersion: string = this.RemoteVerList[HUD.BundleName].ApkVersion; + if (apkVersion && apkVersion !== "0") { + this.LocalVerList[HUD.BundleName].UseLocal = true; + this.LocalVerList[HUD.BundleName].Version = apkVersion; + } + LocalStorageData.Instance.LocalVerList = JSON.stringify(this.LocalVerList); + } else { + if (this.RemoteVerList[HUD.BundleName].Version === this.RemoteVerList[HUD.BundleName].ApkVersion) { + this.LocalVerList[HUD.BundleName] = this.RemoteVerList[HUD.BundleName]; + this.LocalVerList[HUD.BundleName].UseLocal = true; + } + } + let UpdateData: Enum_Loading.NeedUpdateDataObj = new Enum_Loading.NeedUpdateDataObj(); + if (this.LocalVerList[HUD.BundleName].UseLocal) { + UpdateData.IsNeedUpdate = AssetBundleMamager.Instance.versionCompareHandle(this.LocalVerList[HUD.BundleName].Version, this.RemoteVerList[HUD.BundleName].Version) < 0 ? true : false; + if (UpdateData.IsNeedUpdate) { + UpdateData = yield* HUD.CheckUpdate(); + } + } else { + UpdateData = yield* HUD.CheckUpdate(); + } + return UpdateData; + } + + // public *CheckBundleNeedHUD(BundleName: string): IterableIterator { + // if (!this.LocalVerList[BundleName]) { + // this.LocalVerList[BundleName] = new Enum_Loading.BundleDataObj(); + // let apkVersion: string = this.RemoteVerList[BundleName].ApkVersion; + // if (apkVersion && apkVersion !== "0") { + // this.LocalVerList[BundleName].UseLocal = true; + // this.LocalVerList[BundleName].Version = apkVersion; + // } + // LocalStorageData.Instance.LocalVerList = JSON.stringify(this.LocalVerList); + // } + // let IsUpdate: boolean = AssetBundleMamager.Instance.versionCompareHandle(this.LocalVerList[BundleName].Version, this.RemoteVerList[BundleName].Version) < 0 ? true : false; + // return IsUpdate; + // } + + public CheckGameNeedUpdate(GameID: number): boolean { + let IsUpdate: boolean = false; + let bundleName: string = `Game_${GameID}`; + if (!this.RemoteVerList[bundleName]) { + this.RemoteVerList[bundleName] = new Enum_Loading.BundleDataObj(); + this.RemoteVerList[bundleName].HasBundle = false; + LocalStorageData.Instance.RemoteVerList = JSON.stringify(this.RemoteVerList); + IsUpdate = true; + } + if (!this.LocalVerList[bundleName]) { + this.LocalVerList[bundleName] = new Enum_Loading.BundleDataObj(); + let apkVersion: string = this.RemoteVerList[bundleName].ApkVersion; + if (apkVersion && apkVersion !== "0") { + this.LocalVerList[bundleName].UseLocal = true; + this.LocalVerList[bundleName].Version = apkVersion; + } + LocalStorageData.Instance.LocalVerList = JSON.stringify(this.LocalVerList); + } + if (CC_PREVIEW) { + return this._getIsDownload_Preview(GameID); + } + if (IsUpdate) { + return IsUpdate; + } + IsUpdate = AssetBundleMamager.Instance.versionCompareHandle(this.LocalVerList[bundleName].Version, this.RemoteVerList[bundleName].Version) < 0 ? true : false; + return IsUpdate; + } + + /** + * 比對版號(熱更能從1.0.0更新到2.0.0,從2.0.0回退到1.0.0) + * 官方提供的版本比較函數,只有服務端版本>客戶端版本時,才會進行更新。所以不能從2.0.0回退到1.0.0版本。 + * @param {string} versionA 本地版號 + * @param {string} versionB 遠程版號 + * @return {number} num = -1 須更新 + * @return {number} num = 0 不須更新 + */ + public versionCompareHandle(versionA: string, versionB: string): number { + // console.log("Ver A " + versionA + "VerB " + versionB); + var vA: string[] = versionA.split("."); + var vB: string[] = versionB.split("."); + + // 長度不相等,則進行更新 + if (vA.length !== vB.length) { + return -1; + } + + for (var i: number = 0; i < vA.length; ++i) { + var a: number = +vA[i]; + var b: number = +vB[i] || 0; + if (a === b) { + // 數字相同,則跳過 + continue; + } else { + // 數字不同,則進行更新 + return -1; + } + } + + // 長度相等且數字相等,則不更新 + return 0; + } + + //#endregion + + //#region DownloadList_Preview + + private _initdownloadList_Preview(): void { + this.DownloadList_Preview = JSON.parse(LocalStorageData.Instance.DownloadList_Preview); + this.DownloadList_Preview = this.DownloadList_Preview ? this.DownloadList_Preview : {}; + } + + private _getIsDownload_Preview(slotID: number): boolean { + if (!this.DownloadList_Preview[slotID]) { + this.SetIsDownload_Preview(slotID, false); + } + return !this.DownloadList_Preview[slotID]; + } + + public SetIsDownload_Preview(slotID: number, isDownload: boolean = true): void { + this.DownloadList_Preview[slotID] = isDownload; + LocalStorageData.Instance.DownloadList_Preview = JSON.stringify(this.DownloadList_Preview); + } + + //#endregion +} + +//#region enum + +/** Bundle資源類型 */ +export enum Bundle_Source_Type { + /** Json */ + Json = "json", + + /** Scene */ + Scene = "scene", + + /** Prefab */ + Prefab = "prefab" +} + +//#endregion + +//#region 廢棄 Function + +// /** +// * 從Bundle刪除暫存資源 +// * @param {string} BundleName Bundle名稱 +// */ +// public *DelBundleCache(BundleName: cc.AssetManager.Bundle | string): IterableIterator { +// if (!CC_JSB) { +// return; +// } +// let WritablePath: string = `${jsb.fileUtils.getWritablePath()}gamecaches/${BundleName}`; +// if (jsb.fileUtils.isDirectoryExist(WritablePath)) { +// jsb.fileUtils.removeDirectory(WritablePath); +// } +// } + +//#endregion \ No newline at end of file diff --git a/assets/Script/Engine/HUD/AssetBundleMamager.ts.meta b/assets/Script/Engine/HUD/AssetBundleMamager.ts.meta new file mode 100644 index 0000000..b228b49 --- /dev/null +++ b/assets/Script/Engine/HUD/AssetBundleMamager.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "97a0b2c9-72f8-4797-874a-263e4558f765", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/HUD/HUDM.ts b/assets/Script/Engine/HUD/HUDM.ts new file mode 100644 index 0000000..d79bc71 --- /dev/null +++ b/assets/Script/Engine/HUD/HUDM.ts @@ -0,0 +1,425 @@ +import BusinessTypeSetting from "../../_BusinessTypeSetting/BusinessTypeSetting"; +import Enum_Loading from "../HUDV2/Enum_Loading"; +import AssetBundleMamager from "./AssetBundleMamager"; + +const { ccclass, property } = cc._decorator; + +/** HUDManager */ +@ccclass +export default class HUDM extends cc.Component { + + //#region static 屬性 + + private static _instance: HUDM = null; + public static get Instance(): HUDM { return HUDM._instance; } + + //#endregion + + //#region static 屬性 + + public BundleName: string = ""; + + //#endregion + + //#region private 屬性 + + private _am: jsb.AssetsManager; + private _onFileProgress: (finish: number, total: number, item: string) => void; + private _updateListener: any; + private _checkListener: any; + private _versionCompareHandle: any = null; + private _needUpdateData: Enum_Loading.NeedUpdateDataObj = null; + private _updateingData: Enum_Loading.UpdateingDataObj = null; + private _updating: boolean = false; + private _canRetry: boolean = false; + private _isChangeUrl: boolean = false; + private _path: string = "Bundle"; + private _customManifest: string = ""; + private _storagePath: string = ""; + + //#endregion + + //#region Lifecycle + + constructor(...params: any[]) { + super(); + + if (!cc.sys.isNative) { + return; + } else if (params.length === 0) { + return; + } + HUDM._instance = this; + + this.BundleName = params[0]; + // let packageUrl: string = params[1]; + // let BundleData: Enum_Loading.BundleDataObj = AssetBundleMamager.Instance.RemoteVerList[this.BundleName]; + // let packageUrl: string = BundleData.BundleUrl; + let packageUrl: string = `${BusinessTypeSetting.UsePatch}${BusinessTypeSetting.FolderUrlBundle}${this.BundleName}`; + + this._customManifest = JSON.stringify({ + "packageUrl": packageUrl, + "remoteManifestUrl": `${packageUrl}/project.manifest`, + "remoteVersionUrl": `${packageUrl}/version.json`, + "version": "0.0.0", + }); + + this._storagePath = `${(jsb.fileUtils ? jsb.fileUtils.getWritablePath() : "./")}${this._path}/${this.BundleName}`; + + // 本地熱更目錄下已存在project.manifest,則直接修改已存在的project.manifest + if (AssetBundleMamager.Instance.IsChangeBundleUrl) { + if (jsb.fileUtils.isFileExist(this._storagePath + "/project.manifest")) { + this._isChangeUrl = true; + this._modifyAppLoadUrlForManifestFile(this._storagePath, packageUrl); + } + } + + this._versionCompareHandle = function (versionA: string, versionB: string): number { + // console.log("Ver A " + versionA + "VerB " + versionB); + var vA: string[] = versionA.split("."); + var vB: string[] = versionB.split("."); + + // 長度不相等,則進行更新 + if (vA.length !== vB.length) { + return -1; + } + + for (var i: number = 0; i < vA.length; ++i) { + var a: number = +vA[i]; + var b: number = +vB[i] || 0; + if (a === b) { + // 數字相同,則跳過 + continue; + } else { + // 數字不同,則進行更新 + return -1; + } + } + + // 長度相等且數字相等,則不更新 + return 0; + }; + this._initAssetManaget(); + } + private _initAssetManaget(): void { + // + this._am = new jsb.AssetsManager("", this._storagePath, this._versionCompareHandle); + + // Setup the verification callback, but we don't have md5 check function yet, so only print some message + // Return true if the verification passed, otherwise return false + this._am.setVerifyCallback(function (path: any, asset: { compressed: any; md5: any; path: any; size: any; }): boolean { + // When asset is compressed, we don't need to check its md5, because zip file have been deleted. + var compressed: any = asset.compressed; + // Retrieve the correct md5 value. + var expectedMD5: string = asset.md5; + // asset.path is relative path and path is absolute. + var relativePath: string = asset.path; + // The size of asset file, but this value could be absent. + var size: any = asset.size; + if (compressed) { + // panel.info.string = "Verification passed : " + relativePath; + // cc.log("onLoad -> Verification passed : " + relativePath); + return true; + } else { + // panel.info.string = "Verification passed : " + relativePath + ' (' + expectedMD5 + ')'; + // cc.log("onLoad -> setVerifyCallbackVerification passed : " + relativePath + " (" + expectedMD5 + ")"); + return true; + } + }); + + if (cc.sys.os === cc.sys.OS_ANDROID) { + // Some Android device may slow down the download process when concurrent tasks is too much. + // The value may not be accurate, please do more test and find what's most suitable for your game. + // this._am.setMaxConcurrentTask(10); + this._am["setMaxConcurrentTask"](10); + // this.panel.info.string = "Max concurrent tasks count have been limited to 2"; + // cc.log("onLoad -> Max concurrent tasks count have been limited to 10"); + } + } + + private _modifyAppLoadUrlForManifestFile(filePath: string, newBundleUrl: string): void { + let allpath: string[] = [filePath, filePath + "_temp"]; + let manifestname: string[] = ["project.manifest", "project.manifest.temp"]; + for (var i: number = 0; i < allpath.length; ++i) { + let path: string = `${allpath[i]}/${manifestname[i]}`; + if (jsb.fileUtils.isFileExist(path)) { + // console.log(`[HUD] modifyAppLoadUrlForManifestFile: 有下載的manifest文件,直接修改熱更地址`); + // 修改project.manifest + let projectManifest: string = jsb.fileUtils.getStringFromFile(path); + let projectManifestObj: any = JSON.parse(projectManifest); + projectManifestObj.packageUrl = newBundleUrl; + projectManifestObj.remoteManifestUrl = newBundleUrl + "/project.manifest"; + projectManifestObj.remoteVersionUrl = newBundleUrl + "/version.json"; + let afterString: string = JSON.stringify(projectManifestObj); + let isWrittenProject: boolean = jsb.fileUtils.writeStringToFile(afterString, path); + // // 更新數據庫中的新請求地址,下次如果檢測到不一致就重新修改 manifest 文件 + // if (isWrittenProject) { + // LocalStorageData.Instance.BundleUrl = BusinessTypeSetting.UsePatch; + // } + // console.log("[HUD] 修改是否成功,project.manifest:", isWrittenProject); + // console.log("[HUD] 修改後文件:", projectManifestObj.packageUrl, projectManifestObj.remoteManifestUrl, projectManifestObj.remoteVersionUrl); + } + } + } + //#endregion + + + public *CheckUpdate(): IterableIterator { + this._needUpdateData = null; + if (this._updating) { + // this.panel.info.string = 'Checking or updating ...'; + console.error("checkUpdate -> Checking or updating ..."); + return; + } + + if (this._am.getState() === jsb.AssetsManager.State.UNINITED) { + let manifest: jsb.Manifest = new jsb.Manifest(this._customManifest, this._storagePath); + this._am.loadLocalManifest(manifest, this._storagePath); + } + if (!this._am.getLocalManifest() || !this._am.getLocalManifest().isLoaded()) { + // this.tipsLabel.string = "Failed to load local manifest ..."; + console.error("checkUpdate -> Failed to load local manifest ..."); + return; + } + this._am.setEventCallback(this.checkCb.bind(this)); + + this._am.checkUpdate(); + this._updating = true; + + while (this._needUpdateData === null) { + yield null; + } + + let newBundleUrl: string = `${BusinessTypeSetting.UsePatch}${BusinessTypeSetting.FolderUrlBundle}${this.BundleName}`; + this._modifyAppLoadUrlForManifestFile(this._storagePath, newBundleUrl); + this._initAssetManaget(); + + let manifest: jsb.Manifest = new jsb.Manifest(this._customManifest, this._storagePath); + this._am.loadLocalManifest(manifest, this._storagePath); + if (!this._am.getLocalManifest() || !this._am.getLocalManifest().isLoaded()) { + // this.tipsLabel.string = "Failed to load local manifest ..."; + console.error("checkUpdate -> Failed to load local manifest ..."); + return; + } + + // 更新動態路徑後再跑一次 + this._am.setEventCallback(this.checkCb.bind(this)); + + this._needUpdateData = null; + this._am.checkUpdate(); + this._updating = true; + while (this._needUpdateData === null) { + yield null; + } + if (this._isChangeUrl && (!this._needUpdateData.IsNeedUpdate || this._needUpdateData.TotalBytes === "0 B")) { + if (jsb.fileUtils.isFileExist(this._storagePath)) { + let isremoveDirectory: boolean = jsb.fileUtils.removeDirectory(this._storagePath); + let isremoveDirectory_temp: boolean = jsb.fileUtils.removeDirectory(this._storagePath + "_temp"); + if (isremoveDirectory_temp) { + console.log(`removeDirectory: ${this._storagePath}_temp`); + } + if (isremoveDirectory) { + console.log(`removeDirectory: ${this._storagePath}`); + this._needUpdateData = null; + this._initAssetManaget(); + this._needUpdateData = yield* this.CheckUpdate(); + } + } + } + return this._needUpdateData; + } + + private checkCb(event: jsb.EventAssetsManager): void { + var failed: boolean = false; + switch (event.getEventCode()) { + case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: + // this.tipsLabel.string = "No local manifest file found, HUD skipped."; + console.error("checkCb -> No local manifest file found, HUD skipped."); + failed = true; + break; + case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST: + case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST: + // this.tipsLabel.string = "Fail to download manifest file, HUD skipped."; + console.error("checkCb -> Fail to download manifest file, HUD skipped."); + failed = true; + break; + case jsb.EventAssetsManager.ALREADY_UP_TO_DATE: + // this.tipsLabel.string = "Already up to date with the latest remote version."; + // cc.log("checkCb -> Already up to date with the latest remote version."); + this._needUpdateData = new Enum_Loading.NeedUpdateDataObj(false); + break; + case jsb.EventAssetsManager.NEW_VERSION_FOUND: + // this.downloadLabel.node.active = true; + // this.downloadLabel.string = "New version found, please try to update." + event.getTotalBytes(); + // this.panel.checkBtn.active = false; + // this.panel.fileProgress.progress = 0; + // this.panel.byteProgress.progress = 0; + // cc.log("checkCb -> New version found, please try to update." + event.getTotalBytes()); + this._needUpdateData = new Enum_Loading.NeedUpdateDataObj(true, this._bytesToSize(event.getTotalBytes())); + break; + default: + return; + } + + this._am.setEventCallback(null); + this._checkListener = null; + this._updating = false; + + if (failed) { + // + } + } + + public *HUD(onFileProgress?: (finish: number, total: number, item: string) => void): IterableIterator { + this._updateingData = null; + if (this._am && !this._updating) { + this._am.setEventCallback(this._updateCb.bind(this)); + + if (this._am.getState() === jsb.AssetsManager.State.UNINITED) { + let manifest: jsb.Manifest = new jsb.Manifest(this._customManifest, this._storagePath); + this._am.loadLocalManifest(manifest, this._storagePath); + } + + this._onFileProgress = onFileProgress ? onFileProgress : null; + this._am.update(); + this._updating = true; + + while (this._updateingData === null) { + yield null; + } + + return this._updateingData; + } else { + return new Enum_Loading.UpdateingDataObj(false); + } + } + + private _updateCb(event: jsb.EventAssetsManager): void { + var needRestart: boolean = false; + var failed: boolean = false; + switch (event.getEventCode()) { + case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: + // this.panel.info.string = 'No local manifest file found, HUD skipped.'; + cc.log("updateCb -> No local manifest file found, HUD skipped."); + failed = true; + break; + case jsb.EventAssetsManager.UPDATE_PROGRESSION: + // this.panel.byteProgress.progress = event.getPercent(); + // this.panel.fileProgress.progress = event.getPercentByFile(); + // this.panel.fileLabel.string = event.getDownloadedFiles() + ' / ' + event.getTotalFiles(); + // this.tipsLabel.string = event.getDownloadedBytes() + " / " + event.getTotalBytes(); + + // cc.log("updateCb -> " + event.getDownloadedBytes() + " / " + event.getTotalBytes()); + // var msg: string = event.getMessage(); + // if (msg) { + // // this.panel.info.string = 'Updated file: ' + msg; + // cc.log("updateCb -> Updated file: " + msg); + // console.log("updateCb -> " + event.getPercent() / 100 + "% : " + msg); + // } + + var msg: string = event.getMessage(); + if (this._onFileProgress) { + this._onFileProgress(event.getDownloadedBytes(), event.getTotalBytes(), msg ? msg : ""); + } + break; + case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST: + case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST: + // this.panel.info.string = 'Fail to download manifest file, HUD skipped.'; + console.error("updateCb -> Fail to download manifest file, HUD skipped."); + failed = true; + break; + case jsb.EventAssetsManager.ALREADY_UP_TO_DATE: + // this.panel.info.string = 'Already up to date with the latest remote version.'; + console.error("updateCb -> Already up to date with the latest remote version."); + failed = true; + break; + case jsb.EventAssetsManager.UPDATE_FINISHED: + // this.tipsLabel.string = "更新完成. " + event.getMessage(); + // cc.log("updateCb -> 更新完成. " + event.getMessage()); + this._updateingData = new Enum_Loading.UpdateingDataObj(true); + needRestart = true; + break; + case jsb.EventAssetsManager.UPDATE_FAILED: + // this.panel.info.string = 'Update failed. ' + event.getMessage(); + console.error("updateCb -> Update failed. " + event.getMessage()); + // this.panel.retryBtn.active = true; + this._canRetry = true; + this._updateingData = new Enum_Loading.UpdateingDataObj(false); + this._updating = false; + break; + case jsb.EventAssetsManager.ERROR_UPDATING: + // this.panel.info.string = 'Asset update error: ' + event.getAssetId() + ', ' + event.getMessage(); + console.error("updateCb -> Asset update error: " + event.getAssetId() + ", " + event.getMessage()); + break; + case jsb.EventAssetsManager.ERROR_DECOMPRESS: + // this.panel.info.string = event.getMessage(); + console.error("updateCb -> " + event.getMessage()); + break; + default: + break; + } + + if (failed) { + this._am.setEventCallback(null); + this._updateListener = null; + this._updating = false; + } + + // 測試先不restart 之後看情況 + // if (needRestart) { + // this._am.setEventCallback(null); + // this._updateListener = null; + // // Prepend the manifest's search path + // var searchPaths: string[] = jsb.fileUtils.getSearchPaths(); + + // // var newPaths = this._am.getLocalManifest().getSearchPaths(); + // // cc.log("newPath."+JSON.stringify(newPaths)); + // // Array.prototype.unshift.apply(searchPaths, newPaths); + + // cc.sys.localStorage.setItem("HUDSearchPaths", JSON.stringify(searchPaths)); + + // jsb.fileUtils.setSearchPaths(searchPaths); + + // cc.audioEngine.stopAll(); + // cc.game.restart(); + // } + } + + public *RetryDownLoadFailedAssets(): IterableIterator { + if (!this._updating && this._canRetry) { + this._updateingData = null; + // this.panel.retryBtn.active = false; + this._canRetry = false; + + // this.panel.info.string = 'Retry failed Assets...'; + // cc.log("retry -> Retry failed Assets..."); + this._am.downloadFailedAssets(); + + while (this._updateingData === null) { + yield null; + } + + return this._updateingData; + } else { + console.error(`retry -> error updating: ${this._updating}, canRetry: ${this._canRetry}`); + this._updateingData = new Enum_Loading.UpdateingDataObj(false); + } + } + + private _bytesToSize(bytes: number): string { + if (bytes === 0) { + return "0 B"; + } + let k: number = 1024; + let sizes: string[] = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + let i: number = Math.floor(Math.log(bytes) / Math.log(k)); + return (bytes / Math.pow(k, i)).toPrecision(3) + " " + sizes[i]; + } + + protected onDestroy(): void { + if (this._updateListener) { + this._am.setEventCallback(null); + this._updateListener = null; + } + } +} diff --git a/assets/Script/Engine/HUD/HUDM.ts.meta b/assets/Script/Engine/HUD/HUDM.ts.meta new file mode 100644 index 0000000..cd48b0e --- /dev/null +++ b/assets/Script/Engine/HUD/HUDM.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "dd9501f7-957a-4e62-8630-d43f62d171d1", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/HUDV2/AssetBundleMamagerV2.ts b/assets/Script/Engine/HUDV2/AssetBundleMamagerV2.ts new file mode 100644 index 0000000..83fad84 --- /dev/null +++ b/assets/Script/Engine/HUDV2/AssetBundleMamagerV2.ts @@ -0,0 +1,522 @@ +import BusinessTypeSetting from "../../_BusinessTypeSetting/BusinessTypeSetting"; +import { CoroutineV2 } from "../CatanEngine/CoroutineV2/CoroutineV2"; +import LocalStorageData from "../Data/LocalStorageData"; +import Enum_Loading from "./Enum_Loading"; + +export default class AssetBundleMamagerV2 { + //#region static 屬性 + + private static _instance: AssetBundleMamagerV2 = null; + public static get Instance(): AssetBundleMamagerV2 { return AssetBundleMamagerV2._instance; } + + //#endregion + + //#region public 屬性 + + /** 本地VerList */ + public LocalVerList: Enum_Loading.VerListObj = null; + + /** 遠端VerList */ + public RemoteVerList: JSON = null; + + public DownloadList_Preview: Object = {}; + + /** 快取資源 */ + public CachedFiles: Map = new Map(); + + //#endregion + + //#region Lifecycle + + constructor() { + AssetBundleMamagerV2._instance = this; + CC_PREVIEW && this._initdownloadList_Preview(); + } + + //#endregion + + //#region 清除資料 + + /** 判斷更改編譯版號.清除BUNDLE記錄 */ + public CheckCompileVersion(): void { + let oldCompileVersion: string = LocalStorageData.Instance.CompileVersion; + let newCompileVersion: string = BusinessTypeSetting.COMPILE_VERSION; + if (oldCompileVersion && oldCompileVersion !== newCompileVersion) { + this.ClearBundleData(); + console.log("change compile version."); + } + LocalStorageData.Instance.CompileVersion = BusinessTypeSetting.COMPILE_VERSION; + } + + /** 判斷更改PATCH環境.清除BUNDLE記錄 */ + public CheckChangePatchUrl(): void { + let oldBundleUrl: string = LocalStorageData.Instance.BundleUrl; + let newBundleUrl: string = BusinessTypeSetting.UsePatch; + if (oldBundleUrl && oldBundleUrl !== newBundleUrl) { + this.ClearBundleData(); + console.log("change patch url."); + } + LocalStorageData.Instance.BundleUrl = BusinessTypeSetting.UsePatch; + } + + /** 清除Bundle資料 */ + public ClearBundleData(): void { + cc.sys.localStorage.removeItem("LocalVerList"); + cc.sys.localStorage.removeItem("RemoteVerList"); + cc.assetManager.bundles.clear(); + if (CC_JSB) { + cc.assetManager.cacheManager.clearCache(); + console.log("clear bundle data."); + } + } + + /** 清除所有資料重啟 */ + public ClearAppDataToRestart(): void { + cc.sys.localStorage.clear(); + cc.assetManager.bundles.clear(); + if (CC_JSB) { + cc.assetManager.cacheManager.clearCache(); + cc.game.restart(); + } else { + window.location.reload(); + } + } + + //#endregion + + //#region Custom Function + + /** + * 取得Bundle + * @param {string} BundleName Bundle名稱 + * @param {string} Version 版號 + * @return {cc.AssetManager.Bundle} Bundle + */ + public *GetBundle(BundleName: string): IterableIterator { + let self: this = this; + let bundle: cc.AssetManager.Bundle | boolean = cc.assetManager.getBundle(BundleName); + if (bundle) { + yield* this.GetDepsBundle(bundle.deps); + return bundle; + } + /** 判斷是不是要下載新版本 */ + let isNeedUpdate: boolean = this.IsNeedUpdate(BundleName); + if (isNeedUpdate) { + // 下載新版本前需要先清除暫存 + console.log(`removeCache: ${BundleName}`); + this.DelBundleCache(BundleName); + this.LocalVerList[BundleName].UseLocal = false; + } + /** Bundle路徑 */ + let BundleUrl: string = BusinessTypeSetting.GetRemoteFileUrl(BundleName); + if (CC_DEV) { + // CC_DEVBundle路徑為: BundleName + // if (BundleName.indexOf("Script") != -1) { + BundleUrl = `${BundleName}`; + // } else { + // BundleUrl = "http://192.168.7.57/bj_casino/test/" + BundleName; + // } + } else if (this.LocalVerList[BundleName].UseLocal) { + // 本地Bundle路徑為: assets/assets/${BundleName} + BundleUrl = `assets/${BundleName}`; + } + if (CC_DEV) { + cc.assetManager.loadBundle(BundleUrl, (err: Error, resp: cc.AssetManager.Bundle) => { + if (err) { + console.error(err); + bundle = null; + return; + } + bundle = resp; + }); + while (typeof bundle === "undefined") { + yield null; + } + } else if (BundleName.includes("Script")) { + bundle = yield* self.loadScriptBundle(BundleUrl); + } else { + if (CC_JSB && !this.LocalVerList[BundleName].UseLocal) { + let bundlePath: string = `${jsb.fileUtils.getWritablePath()}gamecaches/${BundleName}/`; + if (!jsb.fileUtils.isFileExist(bundlePath)) { + cc.assetManager.cacheManager["makeBundleFolder"](BundleName); + } + } + bundle = yield* self.loadUIBundle(BundleUrl); + } + if (bundle) { + yield* this.GetDepsBundle((bundle).deps); + if (isNeedUpdate) { + // 下載成功後更改本地Bundle版本 + self.LocalVerList[BundleName].Version = self.RemoteVerList[BundleName]; + LocalStorageData.Instance.LocalVerList = JSON.stringify(self.LocalVerList); + } + } + return bundle; + } + + /** + * 從Bundle取得資源 + * @param {string} BundleUrl Bundle路徑 + */ + public *loadScriptBundle(BundleUrl: string): IterableIterator { + let fileName: string = `index.${CC_DEBUG ? "js" : "jsc"}`; + let fileUrl: string = `${BundleUrl}/${fileName}`; + let run: boolean = true; + let isSuceess: boolean = false; + cc.assetManager.loadScript(fileUrl, (err: Error) => { + if (err) { + console.error(`[Error] ${fileUrl}載入失敗 err: ${err}`); + run = false; + return; + } + isSuceess = true; + run = false; + }); + while (run) { + yield null; + } + return isSuceess; + } + + /** + * 從Bundle取得資源 + * @param {string} BundleUrl Bundle路徑 + */ + public *loadUIBundle(BundleUrl: string): IterableIterator { + let fileName: string = "config.json"; + let fileUrl: string = `${BundleUrl}/${fileName}`; + let data: any; + let run: boolean = true; + cc.assetManager.loadRemote(fileUrl, (err: Error, res: cc.JsonAsset) => { + if (err) { + console.error(`[Error] ${fileUrl}載入失敗 err: ${err}`); + return; + } + data = res.json; + run = false; + }); + while (run) { + yield null; + } + let bundle: cc.AssetManager.Bundle = new cc.AssetManager.Bundle(); + data.base = `${BundleUrl}/`; + bundle.init(data); + return bundle; + } + + /** + * 取得Bundle + * @param {string} BundleName Bundle名稱 + * @param {string} Version 版號 + * @return {cc.AssetManager.Bundle} Bundle + */ + public *GetDepsBundle(deps: string[]): IterableIterator { + if (!deps || deps.length <= 2) { + return; + } + let self: this = this; + let GetBundle_F_Arr: IterableIterator[] = []; + for (const bundleName of deps) { + if (!["main", "internal"].includes(bundleName)) { + let GetBundle_F: IterableIterator = function* (): IterableIterator { + yield* self.GetBundle(bundleName); + }(); + GetBundle_F_Arr.push(GetBundle_F); + } + } + yield CoroutineV2.Parallel(...GetBundle_F_Arr).Start(); + } + + /** + * 從Bundle取得資源 + * @param {number} slotID slotID + * @param {Function} onFileProgress onFileProgress + */ + public *PreloadBundleScene(slotID: number, onFileProgress?: (finish: number, total: number, item: cc.AssetManager.RequestItem) => void): IterableIterator { + let BundleName: string = `Game_${slotID}`; + let SourceName: string = `Slot${slotID}`; + let run: boolean = true; + let UpdateingData: Enum_Loading.UpdateingDataObj = new Enum_Loading.UpdateingDataObj(false); + let bundle: cc.AssetManager.Bundle = yield* AssetBundleMamagerV2.Instance.GetBundle(BundleName); + if (!bundle) { + console.error(`GetBundleSource Error BundleName: ${BundleName}`); + return UpdateingData; + } + + bundle.preloadScene(SourceName, onFileProgress, function (error: Error): void { + if (error) { + console.error(error); + run = false; + return; + } + UpdateingData.IsUpdatecomplete = true; + run = false; + }); + + while (run) { + yield null; + } + return UpdateingData; + } + + /** + * 從Bundle取得資源 + * @param {cc.AssetManager.Bundle | string} BundleName Bundle名稱 + * @param {string} SourceName 資源名稱 + * @param {string} type 資源型別 + * @return {any} Source + */ + public *GetBundleSource(BundleName: cc.AssetManager.Bundle | string, SourceName: string, type?: string | Bundle_Source_Type, onFileProgress?: (finish: number, total: number, item: cc.AssetManager.RequestItem) => void): IterableIterator { + let bundle: cc.AssetManager.Bundle; + let source: any; + if (BundleName instanceof cc.AssetManager.Bundle) { + bundle = BundleName; + } else { + bundle = yield* AssetBundleMamagerV2.Instance.GetBundle(BundleName); + if (!bundle) { + cc.error(`GetBundleSource Error BundleName: ${BundleName}`); + return null; + } + } + + switch (type) { + case Bundle_Source_Type.Scene: { + bundle.loadScene(SourceName, onFileProgress, function (err: Error, scene: cc.SceneAsset): void { + if (err) { + cc.error(err); + return null; + } + // cc.director.runScene(scene); + source = scene; + }); + break; + } + + case Bundle_Source_Type.Json: { + bundle.load(SourceName, onFileProgress, function (err: Error, json: cc.JsonAsset): void { + if (err) { + cc.error(err); + return null; + } + // source = JSON.parse(json["_nativeAsset"]); + source = json; + }); + break; + } + + case Bundle_Source_Type.Prefab: { + bundle.load(SourceName, cc.Prefab, onFileProgress, function (err: Error, prefab: cc.Asset): void { + if (err) { + cc.error(err); + return null; + } + // source = JSON.parse(json["_nativeAsset"]); + source = prefab; + }); + break; + } + + default: + bundle.load(SourceName, function (err: Error, any: any): void { + if (err) { + cc.error(err); + return null; + } + source = any; + }); + break; + } + + while (typeof source === "undefined") { + yield null; + } + return source; + } + + /** + * 從Bundle取得資源(不卡協成) + * @param {string} bundleName bundleName + * @param {string} sourcePath Bundle資料夾下的路徑 + */ + public static GetBundleSourceV2(bundleName: cc.bundleName | string, sourcePath: string): cc.Prefab { + let bundle: cc.AssetManager.Bundle = cc.assetManager.getBundle(bundleName); + if (!bundle) { + cc.error(`GetBundleSourceV2 getBundle error bundleName: ${bundleName}`); + return null; + } + let source: cc.Prefab = bundle.get(sourcePath, cc.Prefab); + if (!source) { + cc.error(`GetBundleSourceV2 bundle.get error bundleName: ${bundleName}, sourcePath: ${sourcePath}`); + return null; + } + return source; + } + + /** + * 釋放Bundle + * @param {string} slotID slotID + */ + public *BundleRelease(slotID: number): IterableIterator { + let gameName: string = `Game_${slotID}`; + let sceneName: string = `Slot${slotID}`; + let bundle: cc.AssetManager.Bundle = cc.assetManager.getBundle(gameName); + if (!bundle) { + cc.log(`BundleRelease Error BundleName: ${gameName}`); + return; + } + this.ReleaseSlotCache(slotID); + } + + /** + * 從cachedFiles刪除暫存資源 + * @param {string} BundleName Bundle名稱 + */ + public DelBundleCache(BundleName: string): void { + if (CC_BUILD && cc.sys.isNative) { + let cachedFiles: Object = cc.assetManager.cacheManager.cachedFiles["_map"]; + let delcache: string = BusinessTypeSetting.GetRemoteFileUrl(BundleName) + "/"; + for (let cached of Object.keys(cachedFiles)) { + if (cached.includes(delcache)) { + cc.assetManager.cacheManager.removeCache(cached); + // console.log(`removeCache: ${cached}`); + } + } + } + } + + /** + * 從cachedFiles釋放暫存資源 + * @param {number} slotID slotID + */ + public ReleaseSlotCache(slotID: number): void { + if (!CC_JSB) { + return; + } + let delcachedKeys: string[] = []; + let cachedFiles: Map = this.CachedFiles; + let delcache_group: string[] = [`shared/jsons`, `Slot/Slot${slotID}`, "sounds/Slot/Default", "submit.txt"]; + cachedFiles.forEach((cached: cc.Asset, key: string, map: Map) => { + for (var i: number = 0; i < delcache_group.length; ++i) { + let delcache: string = delcache_group[i]; + if (key.includes(delcache)) { + cc.assetManager.releaseAsset(cached); + delcachedKeys.push(key); + break; + } + } + }); + for (var i: number = 0; i < delcachedKeys.length; ++i) { + this.CachedFiles.delete(delcachedKeys[i]); + } + } + + /** + * 判斷要不要更新 + * @param {string} BundleName Bundle名稱 + */ + public IsNeedUpdate(BundleName: string): boolean { + let isNeedUpdate: boolean; + // 判斷本地有無Bundle資料 + if (this.LocalVerList[BundleName] && !this.LocalVerList[BundleName].HasBundle) { + if (this.RemoteVerList[BundleName]) { + // 改成有包過Bundle了,重新走下面流程 + this.LocalVerList[BundleName] = null; + } else { + return true; + } + } + + if (!this.LocalVerList[BundleName]) { + // 本地無Bundle資料需要新增 + this.LocalVerList[BundleName] = new Enum_Loading.BundleDataObj(); + LocalStorageData.Instance.LocalVerList = JSON.stringify(this.LocalVerList); + } + let version: string = this.RemoteVerList[BundleName]; + if (!version) { + // !version代表還沒包Bundle + this.LocalVerList[BundleName].HasBundle = false; + LocalStorageData.Instance.LocalVerList = JSON.stringify(this.LocalVerList); + return true; + } else if (version === "0") { + // version === "0" 代表要使用本體Bundle + this.LocalVerList[BundleName].UseLocal = true; + this.LocalVerList[BundleName].Version = "0"; + LocalStorageData.Instance.LocalVerList = JSON.stringify(this.LocalVerList); + } + isNeedUpdate = AssetBundleMamagerV2.Instance.versionCompareHandle(this.LocalVerList[BundleName].Version, this.RemoteVerList[BundleName]) !== 0 ? true : false; + return isNeedUpdate; + } + + /** + * 比對版號(熱更能從1.0.0更新到2.0.0,從2.0.0回退到1.0.0) + * 官方提供的版本比較函數,只有服務端版本>客戶端版本時,才會進行更新。所以不能從2.0.0回退到1.0.0版本。 + * @param {string} versionA 本地版號 + * @param {string} versionB 遠程版號 + * @return {number} num = -1 須更新 + * @return {number} num = 0 不須更新 + */ + public versionCompareHandle(versionA: string, versionB: string): number { + // console.log("Ver A " + versionA + "VerB " + versionB); + var vA: string[] = versionA.split("."); + var vB: string[] = versionB.split("."); + + // 長度不相等,則進行更新 + if (vA.length !== vB.length) { + return -1; + } + + for (var i: number = 0; i < vA.length; ++i) { + var a: number = +vA[i]; + var b: number = +vB[i] || 0; + if (a === b) { + // 數字相同,則跳過 + continue; + } else { + // 數字不同(且版號比目標低),則進行更新 + return a - b; + } + } + + // 長度相等且數字相等,則不更新 + return 0; + } + + //#endregion + + //#region DownloadList_Preview + + private _initdownloadList_Preview(): void { + this.DownloadList_Preview = JSON.parse(LocalStorageData.Instance.DownloadList_Preview); + this.DownloadList_Preview = this.DownloadList_Preview ? this.DownloadList_Preview : {}; + } + + public GetIsDownload_Preview(slotID: number): boolean { + if (!this.DownloadList_Preview[slotID]) { + this.SetIsDownload_Preview(slotID, false); + } + return this.DownloadList_Preview[slotID]; + } + + public SetIsDownload_Preview(slotID: number, isDownload: boolean = true): void { + this.DownloadList_Preview[slotID] = isDownload; + LocalStorageData.Instance.DownloadList_Preview = JSON.stringify(this.DownloadList_Preview); + } + + //#endregion +} + +//#region enum + +/** Bundle資源類型 */ +export enum Bundle_Source_Type { + /** Json */ + Json = "json", + + /** Scene */ + Scene = "scene", + + /** Prefab */ + Prefab = "prefab" +} + +//#endregion \ No newline at end of file diff --git a/assets/Script/Engine/HUDV2/AssetBundleMamagerV2.ts.meta b/assets/Script/Engine/HUDV2/AssetBundleMamagerV2.ts.meta new file mode 100644 index 0000000..0d4d64e --- /dev/null +++ b/assets/Script/Engine/HUDV2/AssetBundleMamagerV2.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "383b4628-2b2a-4de4-8aca-913015e91cae", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/HUDV2/Enum_Loading.ts b/assets/Script/Engine/HUDV2/Enum_Loading.ts new file mode 100644 index 0000000..839d0c2 --- /dev/null +++ b/assets/Script/Engine/HUDV2/Enum_Loading.ts @@ -0,0 +1,72 @@ +const { ccclass, property } = cc._decorator; +export module Enum_Loading { + + //#region Enum + + //#endregion + + //#region Class + + // /** BaseBundle資料 */ + // @ccclass("BaseBundleObj") + // export class BaseBundleObj { + // @property({ displayName: "Bundle名稱", tooltip: "Bundle名稱" }) + // public BundleName: string = ""; + + // @property({ displayName: "優先度", tooltip: "優先度", type: cc.Integer }) + // public Priority: number = 1; + // } + + class BundleDictionary { + [x: string]: T; + } + + /** VerList資料 */ + @ccclass("VerListObj") + export class VerListObj extends BundleDictionary { + } + + /** Bundle資料 */ + @ccclass("BundleDataObj") + export class BundleDataObj { + public Version: string = "0"; + + public ApkVersion: string = "0"; + + public UseLocal: boolean = false; + + /** 有沒有包到Bundle */ + public HasBundle: boolean = true; + } + + /** Bundle資料 */ + @ccclass("NeedUpdateDataObj") + export class NeedUpdateDataObj { + + /** 是否需要更新 */ + public IsNeedUpdate: boolean; + + /** 更新大小 */ + public TotalBytes: string; + + constructor(...params: any[]) { + this.IsNeedUpdate = params[0]; + this.TotalBytes = params[1] ? params[1] : null; + } + } + + /** Bundle資料 */ + @ccclass("UpdateingDataObj") + export class UpdateingDataObj { + + /** 是否更新完成 */ + public IsUpdatecomplete: boolean; + + constructor(...params: any[]) { + this.IsUpdatecomplete = params[0]; + } + } + + //#endregion +} +export default Enum_Loading; \ No newline at end of file diff --git a/assets/Script/Engine/HUDV2/Enum_Loading.ts.meta b/assets/Script/Engine/HUDV2/Enum_Loading.ts.meta new file mode 100644 index 0000000..5363453 --- /dev/null +++ b/assets/Script/Engine/HUDV2/Enum_Loading.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "573d847e-893c-4fa2-8bd2-41918709fcf4", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/HUDV2/GameData_HUD.ts b/assets/Script/Engine/HUDV2/GameData_HUD.ts new file mode 100644 index 0000000..22c0caa --- /dev/null +++ b/assets/Script/Engine/HUDV2/GameData_HUD.ts @@ -0,0 +1,43 @@ + +const { ccclass, property } = cc._decorator; + +export module GameData_HUD { + + //#region Enum + + /** BundleName */ + export enum BundleName { + /** CommonSound */ + CommonSound = "CommonSound", + CommonLanguageTexture = "CommonLanguageTexture", + Common = "Common", + ResourceItem = "ResourceItem", + MainControl = "MainControl", + Login = "Login", + Lobby = "Lobby", + BindAccount = "BindAccount", + Shop = "Shop", + Vip = "Vip", + Ad = "Ad", + SettingPanel = "SettingPanel", + PlayerInfo = "PlayerInfo", + Rank = "Rank", + Chat = "Chat", + Gift = "Gift", + Activity = "Activity", + Mail = "Mail", + GettingPanel = "GettingPanel", + Backpack = "Backpack", + Game_GetCoin = "Game_GetCoin", + Game_BigWinJackpot = "Game_BigWinJackpot", + Game_BottomUI_BJ = "Game_BottomUI_BJ", + Game_BottomUI_SD = "Game_BottomUI_SD", + SlotCommom = "SlotCommom", + TableCommon = "TableCommon", + FishCommon = "FishCommon", + ActivityMission = "ActivityMission" + } + + //#endregion +} +export default GameData_HUD; \ No newline at end of file diff --git a/assets/Script/Engine/HUDV2/GameData_HUD.ts.meta b/assets/Script/Engine/HUDV2/GameData_HUD.ts.meta new file mode 100644 index 0000000..7dbaad5 --- /dev/null +++ b/assets/Script/Engine/HUDV2/GameData_HUD.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "e85368d6-e018-430e-bf1d-6ecd2973c6a8", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Timer/Timer.ts b/assets/Script/Engine/Timer/Timer.ts new file mode 100644 index 0000000..ebfe3f6 --- /dev/null +++ b/assets/Script/Engine/Timer/Timer.ts @@ -0,0 +1,199 @@ +import { CoroutineV2 } from "../CatanEngine/CoroutineV2/CoroutineV2"; +import { ActionWithType } from "../CatanEngine/CSharp/System/ActionWithType"; +import { NumberEx } from "../Utils/Number/NumberEx"; + +class TimerEvent extends ActionWithType { } + +/** + * 計時器(使用CoroutineV2) + */ +export class Timer { + + //#region public + + /** 訊息資料 */ + public static Component: cc.Component; + + //#endregion + + //#region private + + /** 訊息資料 */ + private static Group: Map = new Map(); + + //#endregion + + //#region static + + /** + * 啟動計時 + * @param {number} time 計時(seconds) + * @param {Function} callback Function + * @param {any} type (選填) 可以識別的東西 + * @param {any} bindTarget (選填) 回呼時this綁定的對象 + * @example + * Timer.Start(1, () => { console.log(`example`); }); + * Timer.Start(1, () => { console.log(`example`); }, "example"); + * Timer.Start(1, () => { console.log(`example`); }, "example", this); + */ + public static Start(time: number, callback: Function, bindTarget?: any, type?: any): void { + let self: typeof Timer = this; + let thistype: any = type; + if (!type) { + thistype = callback; + } + if (Timer.Group.has(thistype)) { + cc.error(`Timer Start Error Timer.Group.has(${thistype})`); + return; + } + let timerData: TimerDataClass = new TimerDataClass(thistype, time, callback, bindTarget); + Timer.Group.set(thistype, timerData); + let Coroutine_F: Generator = function* (): Generator { + yield CoroutineV2.WaitTime(time).Start(bindTarget); + if (Timer.Group.has(thistype)) { + self._callback(timerData.Type, timerData.Callback, timerData.BindTarget); + } + }(); + CoroutineV2.Single(Coroutine_F).Start(bindTarget); + } + + /** + * 刪除計時 By Target + * @param {any} target target + * @example + * Timer.ClearByTarget(this); + */ + public static ClearByTarget(target: any): void { + let timerData_Group: TimerDataClass[] = []; + Timer.Group.forEach(timerData => { + if (timerData.BindTarget === target) { + timerData_Group.push(timerData); + } + }); + if (timerData_Group.length === 0) { + cc.warn(`Timer Clear Error Timer.Group.has not target`); + return; + } + for (let i: number = 0; i < timerData_Group.length; i++) { + let timerData: TimerDataClass = timerData_Group[i]; + let type: any = timerData.Type; + Timer.Group.delete(type); + timerData = null; + } + CoroutineV2.StopCoroutinesBy(target); + } + + /** + * 刪除計時 By Type + * @param PS 還是會吃效能在倒數 只是時間到不會執行 + * @param {any} type type + * @example + * Timer.ClearByType("example"); + */ + public static ClearByType(type: any): void { + let timerData_Group: TimerDataClass[] = []; + Timer.Group.forEach(timerData => { + if (timerData.Type === type) { + timerData_Group.push(timerData); + } + }); + if (timerData_Group.length === 0) { + cc.warn(`Timer Clear Error Timer.Group.has not type`); + return; + } + for (let i: number = 0; i < timerData_Group.length; i++) { + let timerData: TimerDataClass = timerData_Group[i]; + let type: any = timerData.Type; + Timer.Group.delete(type); + } + } + + /** + * 結束計時時callback + * @param {Function} callback Function + */ + private static _callback(type: any, callback: Function, bindTarget: any): void { + if (Timer.Group.has(type)) { + Timer.Group.delete(type); + } + if (bindTarget) { + callback.bind(bindTarget)(); + } else { + callback(); + } + } + + /** + * 定時事件(時間用 updateTime秒跑一次fn) + * @param startNum 起始index + * @param endNum 結束index + * @param updateTime 事件刷新間隔 + * @param callbackfn 事件 + * @example + * let startNum: number = 10; + * let endNum: number = 0; + * let updateTime: number = 1; + * yield CoroutineV2.Single(Timer.Timing( + * startNum, + * endNum, + * updateTime, + * (x: number) => { + * console.log(`x: ${x}`); + * } + * )).Start(this); + */ + public static *Timing(startNum: number, endNum: number, updateTime: number, callbackfn: Function): IterableIterator { + let isIncrease: boolean = endNum >= startNum; + let totalCount: number = Math.abs(endNum - startNum) + 1; + let nowCount: number = NumberEx.divide(totalCount, updateTime); + let diff: number = NumberEx.divide(totalCount, nowCount) * (isIncrease ? 1 : -1); + let tempScore: number = startNum; + callbackfn(startNum); + while (true) { + if (endNum !== tempScore) { + yield CoroutineV2.WaitTime(updateTime); + tempScore += diff; + // 遞增 + if (isIncrease && tempScore > endNum) { + tempScore = endNum; + } + // 遞減 + if (!isIncrease && tempScore < endNum) { + tempScore = endNum; + } + callbackfn(Math.floor(tempScore)); + } else { + break; + } + } + } + + //#endregion +} + + +//#region Class + +/** Timer資料 */ +export class TimerDataClass { + /** Type */ + public Type: any = null; + + /** Time */ + public Time: number = null; + + /** Callback */ + public Callback: Function = null; + + /** BindTarget */ + public BindTarget?: any = null; + + constructor(type: any, time: number, callback: Function, bindTarget?: any) { + this.Type = type; + this.Time = time; + this.Callback = callback; + this.BindTarget = bindTarget; + } +} + +// //#endregion \ No newline at end of file diff --git a/assets/Script/Engine/Timer/Timer.ts.meta b/assets/Script/Engine/Timer/Timer.ts.meta new file mode 100644 index 0000000..31a09e0 --- /dev/null +++ b/assets/Script/Engine/Timer/Timer.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "d07ed6c3-7047-4fc2-a99e-af2c15510ee7", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/Act.meta b/assets/Script/Engine/Utils/Act.meta new file mode 100644 index 0000000..e4310f3 --- /dev/null +++ b/assets/Script/Engine/Utils/Act.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "b43bf5ea-67c5-4fc8-9893-1f406a52508f", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/Act/Shake.ts b/assets/Script/Engine/Utils/Act/Shake.ts new file mode 100644 index 0000000..9f3e84a --- /dev/null +++ b/assets/Script/Engine/Utils/Act/Shake.ts @@ -0,0 +1,55 @@ +const { ccclass } = cc._decorator; + +@ccclass +export default class Shake extends cc.ActionInterval { + private _init: boolean = false; + private _initial_x: number = 0; + private _initial_y: number = 0; + private _strength_x: number = 0; + private _strength_y: number = 0; + + /** + * 建立抖動動畫 + * @param {number} duration 動畫持續時長 + * @param {number} strength_x 抖動幅度: x方向 + * @param {number} strength_y 抖動幅度: y方向 + * @returns {Shake} + */ + public static create(duration: number, strength_x: number, strength_y: number): Shake { + let act: Shake = new Shake(); + act.initWithDuration(duration, strength_x, strength_y); + return act; + } + + public initWithDuration(duration: number, strength_x: number, strength_y: number): boolean { + cc.ActionInterval.prototype['initWithDuration'].apply(this, arguments); + this._strength_x = strength_x; + this._strength_y = strength_y; + return true; + } + + public fgRangeRand(min: number, max: number): number { + let rnd: number = Math.random(); + return rnd * (max - min) + min; + } + + public update(time: number): void { + let randx = this.fgRangeRand(-this._strength_x, this._strength_x); + let randy = this.fgRangeRand(-this._strength_y, this._strength_y); + this.getTarget()?.setPosition(randx + this._initial_x, randy + this._initial_y); + } + + public startWithTarget(target: cc.Node): void { + cc.ActionInterval.prototype['startWithTarget'].apply(this, arguments); + if (!this._init) { + this._init = true; + this._initial_x = target.x; + this._initial_y = target.y; + } + } + + public stop(): void { + this.getTarget()?.setPosition(new cc.Vec2(this._initial_x, this._initial_y)); + cc.ActionInterval.prototype['stop'].apply(this); + } +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/Act/Shake.ts.meta b/assets/Script/Engine/Utils/Act/Shake.ts.meta new file mode 100644 index 0000000..86c1e44 --- /dev/null +++ b/assets/Script/Engine/Utils/Act/Shake.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "c5872cc0-91a4-49cb-a055-e037accd801d", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/Audio.meta b/assets/Script/Engine/Utils/Audio.meta new file mode 100644 index 0000000..2b358d2 --- /dev/null +++ b/assets/Script/Engine/Utils/Audio.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "d042d487-d962-4d90-920e-70ab9b8b383c", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/Audio/CSAudio.ts b/assets/Script/Engine/Utils/Audio/CSAudio.ts new file mode 100644 index 0000000..9d8e0f3 --- /dev/null +++ b/assets/Script/Engine/Utils/Audio/CSAudio.ts @@ -0,0 +1,184 @@ +import LocalStorageData from "../../Data/LocalStorageData"; + +export default class CSAudio { + private static _instance: CSAudio = null; + public static get Instance(): CSAudio { return this._instance; } + private _idToClip: Map = new Map(); + private _idToPath: Map = new Map(); + private _idToAudioId: Map = new Map(); + private _currentMusic: number = -1; + /**判斷音效播放太過頻繁不疊加 */ + private _lastPlaySoundTime: Map = new Map(); + private readonly _canPlaySoundCutTime: number = 10; + + constructor() { + CSAudio._instance = this; + if (LocalStorageData.Instance.MusicType) { + this.SetMusicVolume(+LocalStorageData.Instance.MusicType); + } else { + this.SetMusicVolume(0.3); + } + if (LocalStorageData.Instance.SoundType) { + this.SetSoundVolume(+LocalStorageData.Instance.SoundType); + } else { + this.SetSoundVolume(0.3); + } + } + + public AddClipsInfo(clips: Map, pathes: Map): void { + this._idToClip = clips; + this._idToPath = pathes; + } + + /** + * 設定AudioID + * @param id + * @param audioId + */ + private _setAudioId(id: number, audioId: number): void { + this._idToAudioId.set(id, audioId); + } + + /** + * 取得AudioID + * @param id + */ + private _getAudioId(id: number): number { + if (this._idToAudioId.has(id)) { + return this._idToAudioId.get(id); + } else { + return -1; + } + } + + /** + * 打開音效音量 + */ + public OpenSound(): void { + this.SetSoundVolume(0.3); + } + /** + * 關閉音效音量 + */ + public CloseSound(): void { + this.SetSoundVolume(0.0); + } + + /** + * 設定音效音量 + * @param volume + */ + public SetSoundVolume(volume: number): void { + LocalStorageData.Instance.SoundType = volume.toString(); + cc.audioEngine.setEffectsVolume(volume); + } + + /** + * 播放音效 + * @param id + * @param loop + */ + public PlaySound(id: number, loop: boolean = false): void { + // 靜音後有一禎仍然會撥放的問題:音量>0才撥放 + if (cc.audioEngine.getEffectsVolume() <= 0) { + return; + } + if (this._idToClip.has(id)) { + let path: string = this._idToPath.get(id); + let timenum: number = new Date().getTime(); + if (!this._lastPlaySoundTime.has(path)) { + this._lastPlaySoundTime.set(path, timenum); + let audioId: number = cc.audioEngine.playEffect(this._idToClip.get(id), loop); + this._setAudioId(id, audioId); + } else { + let lastTime: number = this._lastPlaySoundTime.get(path); + if (timenum - lastTime > this._canPlaySoundCutTime) { + this._lastPlaySoundTime.set(path, timenum); + let audioId: number = cc.audioEngine.playEffect(this._idToClip.get(id), loop); + this._setAudioId(id, audioId); + } + } + } else { + cc.error("未知的Sound Id: ", id); + } + } + + /** + * 停止音效 + * @param id + */ + public StopSound(id: number): void { + let audioId = this._getAudioId(id); + if (audioId >= 0) { + cc.audioEngine.stopEffect(audioId); + } + } + + /** + * 打開音樂音量 + */ + public OpenMusic(): void { + this.SetMusicVolume(0.3); + } + /** + * 關閉音樂音量 + */ + public CloseMusic(): void { + this.SetMusicVolume(0.0); + } + + /** + * 設定音樂音量 + * @param volume + */ + public SetMusicVolume(volume: number): void { + cc.audioEngine.setMusicVolume(volume); + LocalStorageData.Instance.MusicType = volume.toString(); + // 靜音後有一禎仍然會撥放的問題:背景音樂要回復 + if (this._currentMusic != -1 && volume > 0 && !cc.audioEngine.isMusicPlaying()) { + this._ccMusicPlayId = cc.audioEngine.playMusic(this._idToClip.get(this._currentMusic), true); + } + } + + /** + * 撥放音樂 + * @param id + * @param loop + */ + public PlayMusic(id: number, loop: boolean = true): void { + if (this._currentMusic != id) { + if (this._idToClip.has(id)) { + // 靜音後有一禎仍然會撥放的問題:音量>0才撥放 + if (cc.audioEngine.getMusicVolume() > 0) { + this._ccMusicPlayId = cc.audioEngine.playMusic(this._idToClip.get(id), loop); + } + this._currentMusic = id; + } + else { + cc.error("未知的Music Id: ", id); + } + } + } + private _ccMusicPlayId: number = -1; + private _currentMusicTime: number = -1; + public pauseOrResume(isPause?: boolean) { + if (isPause) { + this._currentMusicTime = cc.audioEngine.getCurrentTime(this._ccMusicPlayId); + cc.audioEngine.pauseAll(); + cc.audioEngine.stopMusic(); + } else { + cc.audioEngine.resumeAll(); + cc.audioEngine.setCurrentTime(this._ccMusicPlayId, this._currentMusicTime); + } + } + + /** + * 停止音樂 + * @param id + */ + public StopMusic(): void { + cc.audioEngine.stopMusic(); + this._currentMusic = -1; + } + +} diff --git a/assets/Script/Engine/Utils/Audio/CSAudio.ts.meta b/assets/Script/Engine/Utils/Audio/CSAudio.ts.meta new file mode 100644 index 0000000..33471ad --- /dev/null +++ b/assets/Script/Engine/Utils/Audio/CSAudio.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "f3ba292a-ecad-4485-ab60-1cd3ee94979a", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/Audio/CSCommonAudios.ts b/assets/Script/Engine/Utils/Audio/CSCommonAudios.ts new file mode 100644 index 0000000..abc7ac0 --- /dev/null +++ b/assets/Script/Engine/Utils/Audio/CSCommonAudios.ts @@ -0,0 +1,168 @@ +import CSAudio from "./CSAudio"; + +export default class CSAppAudios { + private static _instanceApp: CSAppAudios = null; + public static get InstanceApp(): CSAppAudios { return this._instanceApp; } + private _idToAppClip: Map = new Map(); + private _idToAppPath: Map = new Map(); + private _idToAppAudioId: Map = new Map(); + private _currentAppMusic: number = -1; + /** 判斷音效播放太過頻繁不疊加 */ + private _lastAppPlaySoundTime: Map = new Map(); + private readonly _appCanPlaySoundCutTime: number = 10; + constructor() { + CSAppAudios._instanceApp = this; + } + + public AddAppClipsInfo(clips: Map, pathes: Map): void { + this._idToAppClip = clips; + this._idToAppPath = pathes; + } + + /** + * 設定AudioID + * @param id + * @param audioId + */ + private _setAppAudioId(id: number, audioId: number): void { + this._idToAppAudioId.set(id, audioId); + } + + /** + * 打開音效音量 + */ + public OpenSound(): void { + CSAudio.Instance.OpenSound(); + } + /** + * 關閉音效音量 + */ + public CloseSound(): void { + CSAudio.Instance.CloseSound(); + } + + /** + * 取得AudioID + * @param id + */ + private _getAppAudioId(id: number): number { + if (this._idToAppAudioId.has(id)) { + return this._idToAppAudioId.get(id); + } else { + return -1; + } + } + + /** + * 播放音效 + * @param id + * @param loop + */ + public PlayAppSound(id: number, loop: boolean = false): void { + // 靜音後有一禎仍然會撥放的問題:音量>0才撥放 + if (cc.audioEngine.getEffectsVolume() <= 0) { + return; + } + if (this._idToAppClip.has(id)) { + let path: string = this._idToAppPath.get(id); + let timenum: number = new Date().getTime(); + if (!this._lastAppPlaySoundTime.has(path)) { + this._lastAppPlaySoundTime.set(path, timenum); + let audioId: number = cc.audioEngine.playEffect(this._idToAppClip.get(id), loop); + this._setAppAudioId(id, audioId); + } else { + let lastTime: number = this._lastAppPlaySoundTime.get(path); + if (timenum - lastTime > this._appCanPlaySoundCutTime) { + this._lastAppPlaySoundTime.set(path, timenum); + let audioId: number = cc.audioEngine.playEffect(this._idToAppClip.get(id), loop); + this._setAppAudioId(id, audioId); + } + } + } else { + cc.error("未知的Sound Id: ", id); + } + } + + /** + * 停止音效 + * @param id + */ + public StopAppSound(id: number): void { + let audioId: number = this._getAppAudioId(id); + if (audioId >= 0) { + cc.audioEngine.stopEffect(audioId); + } + } + + /** + * 打開音樂音量 + */ + public OpenMusic(): void { + this._setAppMusicVolume(0.3); + CSAudio.Instance.OpenMusic(); + } + /** + * 關閉音樂音量 + */ + public CloseMusic(): void { + this._setAppMusicVolume(0.0); // 這邊再改改 + CSAudio.Instance.CloseMusic(); + } + + /** + * 設定音樂音量 + * @param volume + */ + private _setAppMusicVolume(volume: number): void { + // 靜音後有一禎仍然會撥放的問題:背景音樂要回復 + if (this._currentAppMusic != -1 && volume > 0 && !cc.audioEngine.isMusicPlaying()) { + this._ccAppMusicPlayId = cc.audioEngine.playMusic(this._idToAppClip.get(this._currentAppMusic), true); + } + } + + /** + * 撥放音樂 + * @param id + * @param loop + */ + public PlayAppMusic(id: number, loop: boolean = true): void { + if (this._currentAppMusic != id) { + if (this._idToAppClip.has(id)) { + // 靜音後有一禎仍然會撥放的問題:音量>0才撥放 + if (cc.audioEngine.getMusicVolume() > 0) { + this._ccAppMusicPlayId = cc.audioEngine.playMusic(this._idToAppClip.get(id), loop); + } + this._currentAppMusic = id; + } else { + cc.error("未知的Music Id: ", id); + } + } else { + this.StopMusic(); + this.PlayAppMusic(id); + } + } + + private _ccAppMusicPlayId: number = -1; + private _currentAppMusicTime: number = -1; + + public pauseOrResume(isPause?: boolean): void { + if (isPause) { + this._currentAppMusicTime = cc.audioEngine.getCurrentTime(this._ccAppMusicPlayId); + cc.audioEngine.pauseAll(); + cc.audioEngine.stopMusic(); + } else { + cc.audioEngine.resumeAll(); + cc.audioEngine.setCurrentTime(this._ccAppMusicPlayId, this._currentAppMusicTime); + } + } + + /** + * 停止音樂 + * @param id + */ + public StopMusic(): void { + cc.audioEngine.stopMusic(); + this._currentAppMusic = -1; + } + +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/Audio/CSCommonAudios.ts.meta b/assets/Script/Engine/Utils/Audio/CSCommonAudios.ts.meta new file mode 100644 index 0000000..9a4fb45 --- /dev/null +++ b/assets/Script/Engine/Utils/Audio/CSCommonAudios.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "ce946cad-16db-4383-a734-43bb8f14089e", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/Bezier.meta b/assets/Script/Engine/Utils/Bezier.meta new file mode 100644 index 0000000..37fe652 --- /dev/null +++ b/assets/Script/Engine/Utils/Bezier.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "4e2d4321-bbfb-46d8-87bc-15a7c99c000d", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/Bezier/Bezier.ts b/assets/Script/Engine/Utils/Bezier/Bezier.ts new file mode 100644 index 0000000..7f1b8b3 --- /dev/null +++ b/assets/Script/Engine/Utils/Bezier/Bezier.ts @@ -0,0 +1,16 @@ +export module Bezier { + + export function GetPoint(p0: cc.Vec2, p1: cc.Vec2, p2: cc.Vec2, p3: cc.Vec2, t: number): cc.Vec2 { + if (t < 0) { + t = 0; + } + else if (t > 1) { + t = 1 + } + let OneMinusT = 1 - t; + return p0.mul(OneMinusT * OneMinusT * OneMinusT) + .add(p1.mul(3 * OneMinusT * OneMinusT * t)) + .add(p2.mul(3 * OneMinusT * t * t)) + .add(p3.mul(t * t * t)); + } +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/Bezier/Bezier.ts.meta b/assets/Script/Engine/Utils/Bezier/Bezier.ts.meta new file mode 100644 index 0000000..9b1485b --- /dev/null +++ b/assets/Script/Engine/Utils/Bezier/Bezier.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "b47d81c4-01a1-45cd-94f8-19daf96f17a8", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/CCExtensions.meta b/assets/Script/Engine/Utils/CCExtensions.meta new file mode 100644 index 0000000..dc8094a --- /dev/null +++ b/assets/Script/Engine/Utils/CCExtensions.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "dcb480f5-98b4-4a48-9d82-e3e1fe837e8d", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/CCExtensions/ArrayExtension.ts b/assets/Script/Engine/Utils/CCExtensions/ArrayExtension.ts new file mode 100644 index 0000000..7dd28b5 --- /dev/null +++ b/assets/Script/Engine/Utils/CCExtensions/ArrayExtension.ts @@ -0,0 +1,127 @@ +declare interface Array { + /** + * 移除一個值並且回傳 + * @param index + */ + ExRemoveAt(index: number): T; + /** + * 移除全部值(注意. 參考的也會被清空) + * @example + * + * let bar: number[] = [1, 2, 3]; + * let bar2: number[] = bar; + * bar.Clear(); + * console.log(bar, bar2); + * + * // { + * // "bar": [], + * // "bar2": [] + * // } + */ + Clear(): void; + /** + * 物件陣列排序,asc&key陣列長度請一樣 + * PS. boolean 帶false是先true在false + * @link JavaScript Object 排序 http://www.eion.com.tw/Blogger/?Pid=1170#:~:text=JavaScript%20Object%20排序 + * @param asc 是否升序排列(小到大) + * @param key 需排序的key(優先順序左到右)(沒有就放空) + */ + ObjectSort(asc?: boolean[], key?: string[]): any[]; + /** + * 設計給ArrayforHoldButton使用 + * Add a non persistent listener to the UnityEvent. + * @param call Callback function. + */ + AddListener(call: Function): void; +} + +Array.prototype.ExRemoveAt || Object.defineProperty(Array.prototype, "ExRemoveAt", { + enumerable: false, + value: function (index: number): any { + let item: any = this.splice(index, 1); + return item[0]; + } +}); + +Array.prototype.Clear || Object.defineProperty(Array.prototype, "Clear", { + enumerable: false, + value: function (): void { + this.length = 0; + + // let foo: number[] = [1, 2, 3]; + // let bar: number[] = [1, 2, 3]; + // let foo2: number[] = foo; + // let bar2: number[] = bar; + // foo = []; + // bar.length = 0; + // console.log(foo, bar, foo2, bar2); + + // { + // "foo": [], + // "bar": [], + // "foo2": [ + // 1, + // 2, + // 3 + // ], + // "bar2": [] + // } + } +}); + +Array.prototype.ObjectSort || Object.defineProperty(Array.prototype, "ObjectSort", { + enumerable: false, + /** + * @param asc 是否升序排列(小到大) + * @param key 需排序的key(優先順序左到右)(沒有就放空) + */ + value: function (asc: boolean[] = [true], key?: string[]): any[] { + if (this.length === 0) { + return this; + } else if (!key || key.length === 0) { + console.error(`ObjectSort key error`); + return this; + } else if (asc.length !== key.length) { + console.error(`ObjectSort key asc error asc.length: ${asc.length}, key.length: ${key.length}`); + return this; + } + for (let i: number = 0; i < key.length; i++) { + const keyname: string = key[i]; + if (this[0][keyname] === undefined) { + console.error(`ObjectSort has not key[${i}]: ${keyname}`); + return this; + } + } + let count: number = key ? key.length : 1; + let arr: any[]; + for (let i: number = count - 1; i >= 0; i--) { + arr = this.sort(function (a: any, b: any): 1 | -1 { + let mya: any = a; + let myb: any = b; + if (key) { + mya = a[key[i]]; + myb = b[key[i]]; + } + + // 加個等於數字相同不要再去排序到 + if (asc[i]) { + return mya >= myb ? 1 : -1; + } else { + return mya <= myb ? 1 : -1; + } + }); + } + return arr; + } +}); + +Array.prototype.AddListener || Object.defineProperty(Array.prototype, "AddListener", { + enumerable: false, + value: function (call: Function): void { + let EventHandler: cc.Component.EventHandler = new cc.Component.EventHandler(); + EventHandler.target = "Callback"; + EventHandler.component = "Callback"; + EventHandler.handler = call; + this.push(EventHandler); + } +}); \ No newline at end of file diff --git a/assets/Script/Engine/Utils/CCExtensions/ArrayExtension.ts.meta b/assets/Script/Engine/Utils/CCExtensions/ArrayExtension.ts.meta new file mode 100644 index 0000000..8c0ea8d --- /dev/null +++ b/assets/Script/Engine/Utils/CCExtensions/ArrayExtension.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "02aa6cd7-21a1-4c22-bcbe-296bb938badd", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/CCExtensions/CCExtension.ts b/assets/Script/Engine/Utils/CCExtensions/CCExtension.ts new file mode 100644 index 0000000..fd73219 --- /dev/null +++ b/assets/Script/Engine/Utils/CCExtensions/CCExtension.ts @@ -0,0 +1,429 @@ +declare namespace cc { + + export interface Node { + /** + * 設定世界座標 + * @param worldPoint + */ + SetWorldPosition(worldPoint: cc.Vec2 | cc.Vec3): void; + /** + * 取得世界座標 + */ + GetWorldPosition(): cc.Vec2; + /** + * 取得目標在自己區域的相對座標 + * @param target + */ + GetTargetInMyLocalPosition(target: cc.Node): cc.Vec2; + /** + * 取得目標距離 + * @param target + */ + GetTargetDistance(target: cc.Node): number; + /** + * 設定長寬 + * @param size + */ + SetSizeDelta(size: cc.Vec2); + /** + * 取得長寬 + */ + GetSizeDelta(): cc.Vec2; + /** + * 增加一個子物件 + * @param childObj + */ + ExAddChild(childObj: cc.Prefab | cc.Node, childActive?: boolean): cc.Node; + + /**設定層級為最低層 */ + ExSetLowestOrder(): void; + + /**設定層級為最高層 */ + ExSetHighestOrder(): void; + + /**設定層級,比目標OBJ再低一層 */ + ExSetOrderUnderTheObj(obj: cc.Node, isNew?: boolean): number; + + /**設定層級,比目標OBJ再高一層 */ + ExSetOrderOverTheObj(obj: cc.Node, isNew?: boolean): number; + /**位置維持在原位 */ + ExSetParent(parentObj: cc.Node): void; + ExSetGray(showGray: boolean): void; + + /** 通過觀察目標來設置 rotation */ + ExLookAt(targetPos: cc.Vec3): void; + + /** + * !#zh + * 当前节点的自身激活状态。
+ * 值得注意的是,一个节点的父节点如果不被激活,那么即使它自身设为激活,它仍然无法激活。
+ * 如果你想检查节点在场景中实际的激活状态可以使用 {{#crossLink "Node/activeInHierarchy:property"}}{{/crossLink}}。 + * @param value 当前节点的自身激活状态 + */ + SetActive(value: boolean): void; + } + + // export interface ActionInterval { + // step(dt: number): void; + // } + + export interface Tween { + /** + * 獲取持續時間 + * @example let duration = tween.duration(); + */ + duration(): number; + /** + * 獲取已經進行的時間 + * @example let elapsed = tween.elapsed(); + */ + elapsed(): number; + /** + * 跳轉到指定時間 + * @param time 時間(秒) + * @example tween.goto(2); + */ + goto(time: number): void; + } +} + +cc.Node.prototype.SetWorldPosition || Object.defineProperty(cc.Node.prototype, 'SetWorldPosition', { + enumerable: false, + value: function (cocosWorldPos: cc.Vec2 | cc.Vec3) { + // let cocosWorldPos = new cc.Vec2(unityWorldPos.x + 711, unityWorldPos.y + 400); + this.setPosition(this.parent.convertToNodeSpaceAR(cocosWorldPos)); + } +}) +cc.Node.prototype.GetWorldPosition || Object.defineProperty(cc.Node.prototype, 'GetWorldPosition', { + enumerable: false, + value: function () { + let cocosWorldPos = this.parent.convertToWorldSpaceAR(this.position); + // let unityWorldPos = new cc.Vec2(cocosWorldPos.x - 711, cocosWorldPos.y - 400); + return cocosWorldPos; + } +}) +cc.Node.prototype.GetTargetInMyLocalPosition || Object.defineProperty(cc.Node.prototype, 'GetTargetInMyLocalPosition', { + enumerable: false, + value: function (target: cc.Node): cc.Vec2 { + let selfPos: cc.Vec2 = this.GetWorldPosition(); + let targetPos: cc.Vec2 = target.GetWorldPosition(); + let diff: cc.Vec2 = targetPos.sub(selfPos); + let newPos: cc.Vec2 = this.getPosition().add(diff); + return newPos; + } +}); +cc.Node.prototype.GetTargetDistance || Object.defineProperty(cc.Node.prototype, 'GetTargetDistance', { + enumerable: false, + value: function (target: cc.Node): number { + let vector: cc.Vec2 = target.GetWorldPosition().sub(this.GetWorldPosition()); + let distance: number = vector.mag(); + return distance; + } +}); +cc.Node.prototype.SetSizeDelta || Object.defineProperty(cc.Node.prototype, 'SetSizeDelta', { + enumerable: false, + value: function (size: cc.Vec2) { + this.setContentSize(size.x, size.y); + } +}) +cc.Node.prototype.GetSizeDelta || Object.defineProperty(cc.Node.prototype, 'GetSizeDelta', { + enumerable: false, + value: function () { + let size: cc.Size = this.GetSizeDelta(); + return new cc.Vec2(size.width, size.width); + } +}) +cc.Node.prototype.ExAddChild || Object.defineProperty(cc.Node.prototype, 'ExAddChild', { + enumerable: false, + value: function (childObj: cc.Prefab | cc.Node, childActive: boolean = true) { + let gameObj = null; + if (childObj instanceof cc.Prefab) { + gameObj = cc.instantiate(childObj); + } + else { + gameObj = cc.instantiate(childObj); + } + gameObj.active = childActive ? true : childActive; + gameObj.parent = this; + return gameObj; + } +}) +cc.Node.prototype.ExSetLowestOrder || Object.defineProperty(cc.Node.prototype, 'ExSetLowestOrder', { + enumerable: false, + value: function () { + this.setSiblingIndex(0); + } +}) +cc.Node.prototype.ExSetHighestOrder || Object.defineProperty(cc.Node.prototype, 'ExSetHighestOrder', { + enumerable: false, + value: function () { + this.setSiblingIndex(Number.MAX_VALUE); + } +}) +cc.Node.prototype.ExSetOrderUnderTheObj || Object.defineProperty(cc.Node.prototype, 'ExSetOrderUnderTheObj', { + enumerable: false, + value: function (obj: cc.Node, isNew?: boolean) { + + let newIndex: number; + let objIndex = obj.getSiblingIndex(); + + // 如果是新創的元件 + if (isNew) { + newIndex = objIndex; + } + // 如果是已經在場景上的元件 + else { + let myIndex = this.getSiblingIndex(); + + // 如果一開始就在它下面 + if (myIndex < objIndex) { + newIndex = objIndex - 1; + } + else { + newIndex = objIndex; + } + } + this.setSiblingIndex(newIndex); + return newIndex; + } +}) +cc.Node.prototype.ExSetOrderOverTheObj || Object.defineProperty(cc.Node.prototype, 'ExSetOrderOverTheObj', { + enumerable: false, + value: function (obj: cc.Node, isNew?: boolean) { + let newIndex: number; + let objIndex = obj.getSiblingIndex(); + + // 如果是新創的元件 + if (isNew) { + newIndex = objIndex + 1; + } + // 如果是已經在場景上的元件 + else { + let myIndex = this.getSiblingIndex(); + + // 如果一開始就在它下面 + if (myIndex < objIndex) { + newIndex = objIndex; + } + else { + newIndex = objIndex + 1; + } + } + this.setSiblingIndex(newIndex); + return newIndex; + } +}) +cc.Node.prototype.ExSetParent || Object.defineProperty(cc.Node.prototype, 'ExSetParent', { + enumerable: false, + value: function (parentObj: cc.Node) { + let oriPos = this.GetWorldPosition(); + this.setParent(parentObj); + this.SetWorldPosition(oriPos); + } +}) +cc.Node.prototype.ExSetGray || Object.defineProperty(cc.Node.prototype, 'ExSetGray', { + enumerable: false, + value: function (showGray: boolean): void { + let btn: cc.Button = this.getComponent(cc.Button); + if (btn) { + btn.interactable = !showGray; + } + let material: cc.Material = cc.Material.createWithBuiltin(showGray ? cc.Material.BUILTIN_NAME.GRAY_SPRITE.toString() : cc.Material.BUILTIN_NAME.SPRITE.toString(), 0); + !showGray && material.define("USE_TEXTURE", true, 0); + let spriteComs: any[] = this.getComponentsInChildren(cc.Sprite).concat(this.getComponentsInChildren(cc.Label)); + for (let sprite of spriteComs) { + sprite.setMaterial(0, material); + } + + // 先使用createWithBuiltin,如果材質球一直Create沒被刪除,會在修改。 + // let material: cc.Material = cc.Material.getBuiltinMaterial(showGray ? cc.Material.BUILTIN_NAME.GRAY_SPRITE.toString() : cc.Material.BUILTIN_NAME.SPRITE.toString()); + // for (let sprite of spriteComs) { + // if (showGray) { + // sprite.setMaterial(0, cc.Material.getBuiltinMaterial('2d-gray-sprite')); + // } + // else { + // sprite.setMaterial(0, cc.Material.getBuiltinMaterial('2d-sprite')); + // } + // } + }, +}); +cc.Node.prototype.ExLookAt || Object.defineProperty(cc.Node.prototype, "ExLookAt", { + enumerable: false, + value: function (targetPos: cc.Vec3): void { + let TargetX: number = targetPos.x; + let TargetY: number = targetPos.y; + let SelfX: number = this.x; + let SelfY: number = this.y; + let r1: number = Math.atan2(TargetX - SelfX, TargetY - SelfY); + let angle: number = (180 * r1 / Math.PI); + this.angle = -angle; + }, +}); +cc.Node.prototype.SetActive || Object.defineProperty(cc.Node.prototype, "SetActive", { + enumerable: false, + value: function (value: boolean): void { + this.active = value; + }, +}); +// cc.Node.prototype.SetWorldPosition = function (cocosWorldPos: cc.Vec2): void { +// // let cocosWorldPos = new cc.Vec2(unityWorldPos.x + 711, unityWorldPos.y + 400); +// this.setPosition(this.parent.convertToNodeSpaceAR(cocosWorldPos)); +// } +// cc.Node.prototype.GetWorldPosition = function (): cc.Vec2 { +// let cocosWorldPos = this.parent.convertToWorldSpaceAR(this.position); +// // let unityWorldPos = new cc.Vec2(cocosWorldPos.x - 711, cocosWorldPos.y - 400); +// return cocosWorldPos; +// } + +// cc.Node.prototype.SetSizeDelta = function (size: cc.Vec2) { +// this.setContentSize(size.x, size.y); +// } +// cc.Node.prototype.GetSizeDelta = function (): cc.Vec2 { +// let size: cc.Size = this.GetSizeDelta(); +// return new cc.Vec2(size.width, size.width); +// } + +// cc.Node.prototype.ExAddChild = function (childObj: cc.Prefab | cc.Node): cc.Node { + +// let gameObj = null; +// if (childObj instanceof cc.Prefab) { +// gameObj = cc.instantiate(childObj); +// } +// else { +// gameObj = cc.instantiate(childObj); +// } +// gameObj.parent = this; +// return gameObj; +// } + +// cc.Node.prototype.ExSetLowestOrder = function (): void { +// this.setSiblingIndex(0); +// } +// cc.Node.prototype.ExSetHighestOrder = function (): void { +// this.setSiblingIndex(Number.MAX_VALUE); +// } + + +// cc.ActionInterval.prototype.step || Object.defineProperty(cc.ActionInterval.prototype, "step", { +// enumerable: false, +// value: function (dt: number): void { +// if (this.paused) { +// return; +// } + +// if (this._firstTick && !this._goto) { +// this._firstTick = false; +// this._elapsed = 0; +// } else { +// this._elapsed += dt; +// } + +// let t: number = this._elapsed / (this._duration > 0.0000001192092896 ? this._duration : 0.0000001192092896); +// t = (1 > t ? t : 1); +// this.update(t > 0 ? t : 0); + +// // Compatible with repeat class, Discard after can be deleted (this._repeatMethod) +// if (this._repeatMethod && this._timesForRepeat > 1 && this.isDone()) { +// if (!this._repeatForever) { +// this._timesForRepeat--; +// } +// this.startWithTarget(this.target); +// this.step(this._elapsed - this._duration); +// } +// } +// }); + +// /** +// * 暂停 +// * @example tween.pause(); +// */ +// cc.Tween.prototype.pause = function () { +// this._finalAction.paused = true; +// }; + +// /** +// * 恢复 +// * @example tween.resume(); +// */ +// cc.Tween.prototype.resume = function () { +// this._finalAction.paused = false; +// }; + +// /** +// * 倍速播放 +// * @param speed 倍速 +// * @example tween.speed(2); +// */ +// cc.Tween.prototype.speed = function (speed) { +// this._finalAction._speedMethod = true; +// this._finalAction._speed = speed; +// } + +cc.Tween.prototype.duration || Object.defineProperty(cc.Tween.prototype, "duration", { + enumerable: false, + value: function (): number { + // let duration: number = this._finalAction._duration; + let duration: number = 0; + for (let i: number = 0; i < this["_actions"].length - 1; i++) { + const action: any = this["_actions"][i]; + duration += action._duration; + } + // duration += ((cc.game.getFrameRate() / 3) / cc.game.getFrameRate()); + duration *= 1.3; + return duration; + } +}); + +cc.Tween.prototype.elapsed || Object.defineProperty(cc.Tween.prototype, "elapsed", { + enumerable: false, + value: function (): number { + return this._finalAction._elapsed; + } +}); + +cc.Tween.prototype.goto || Object.defineProperty(cc.Tween.prototype, "goto", { + enumerable: false, + /** + * @param time 時間(秒) + */ + value: function (time: number): void { + this._finalAction._goto = true; + this._finalAction._elapsed = time; + } +}); + +// cc.ActionManager.prototype.pauseByTag = function (tag, pause) { +// if (tag === cc.Action.TAG_INVALID) { +// cc.logID(1002); +// } + +// let hashTargets = this._hashTargets; +// for (let name in hashTargets) { +// let element = hashTargets[name]; +// for (let i = 0, l = element.actions.length; i < l; ++i) { +// let action = element.actions[i]; +// if (action && action.getTag() === tag) { +// action.paused = pause; +// break; +// } +// } +// } +// } + +// /** +// * 根据标签暂停动作 +// * @param tag tween的标签 +// * @example cc.Tween.pauseByTag(100); +// */ +// cc.Tween.pauseByTag = function (tag) { +// cc.director.getActionManager().pauseByTag(tag, true); +// } + +// /** +// * 根据标签恢复动作 +// * @param tag tween的标签 +// * @example cc.Tween.resumeByTag(100); +// */ +// cc.Tween.resumeByTag = function (tag) { +// cc.director.getActionManager().pauseByTag(tag, false); +// } \ No newline at end of file diff --git a/assets/Script/Engine/Utils/CCExtensions/CCExtension.ts.meta b/assets/Script/Engine/Utils/CCExtensions/CCExtension.ts.meta new file mode 100644 index 0000000..1ab1865 --- /dev/null +++ b/assets/Script/Engine/Utils/CCExtensions/CCExtension.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "b373f805-9297-4af5-8ea6-0a250649b5b0", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/CCExtensions/NumberExtension.ts b/assets/Script/Engine/Utils/CCExtensions/NumberExtension.ts new file mode 100644 index 0000000..5d39881 --- /dev/null +++ b/assets/Script/Engine/Utils/CCExtensions/NumberExtension.ts @@ -0,0 +1,189 @@ + +declare interface Number { + + /** + * 金額每三位數(千)加逗號, 並且補到小數點第2位 + * 輸出 41,038,560.00 + * @param precision 補到小數點第幾位 + * @param isPadZero 是否要補零 + * */ + ExFormatNumberWithComma(precision?: number, isPadZero?: boolean): string; + /** + * 基本4位數(9,999-999B-T) + * */ + ExTransferToBMK(precision?: number,offset?: number): string; + /** + * 數字轉字串, 頭補0 + * @param size + */ + Pad(size: number): string; + /** + * 四捨五入到小數點第X位 (同server計算規則) + * @param precision + */ + ExToNumRoundDecimal(precision: number): number; + /** + * 無條件捨去到小數點第X位 + * @param precision + */ + ExToNumFloorDecimal(precision: number): number; + /** + * 無條件捨去強制保留X位小數,如:2,會在2後面補上00.即2.00 + * @param precision 補到小數點第幾位 + * @param isPadZero 是否要補零 + */ + ExToStringFloorDecimal(precision: number, isPadZero?: boolean): string; + /** + * 取整數) + */ + ExToInt():number; + /** + * 小數轉整數(支援科學符號) + */ + Float2Fixed():number; + /** + * 數字長度(支援科學符號) + */ + DigitLength():number; + + target: number; + + +} + +Number.prototype.ExFormatNumberWithComma || Object.defineProperty(Number.prototype, 'ExFormatNumberWithComma', { + enumerable: false, + value: function (precision: number = 2, isPadZero: boolean = true) { + + // let arr = String(this).split('.'); + let arr = this.ExToStringFloorDecimal(precision, isPadZero).split('.'); + let num = arr[0], result = ''; + while (num.length > 3) { + result = ',' + num.slice(-3) + result; + num = num.slice(0, num.length - 3); + } + if (num.length > 0) result = num + result; + return arr[1] ? result + '.' + arr[1] : result; + } +}) + + +Number.prototype.ExTransferToBMK || Object.defineProperty(Number.prototype, 'ExTransferToBMK', { + enumerable: false, + value: function (precision: number=2,offset: number = 0) { + /**千 */ + let MONEY_1K: number = 1000; + /**萬 */ + // let MONEY_10K: number = 10000; + /**十萬 */ + // let MONEY_100K: number = 100000; + /**百萬 */ + let MONEY_1M: number = 1000000; + /**千萬 */ + // let MONEY_10M: number = 10000000; + /**億 */ + // let MONEY_100M: number = 100000000; + /**十億 */ + let MONEY_1B: number = 1000000000; + /**百億 */ + // let MONEY_10B: number = 10000000000; + /**千億 */ + // let MONEY_100B: number = 100000000000; + /**兆 */ + // let MONEY_1T: number = 1000000000000; + offset = Math.pow(10, offset); + // if (this >= MONEY_1T * offset) { + // //(3)1,000T + // //1T~ + // return (~~(this / MONEY_1T)).ExFormatNumberWithComma(0) + "T"; + // } + if (this >= MONEY_1B * offset) { + //1,000B~900,000B + //1B~900B + return (this / MONEY_1B).ExFormatNumberWithComma(3, false) + "B"; + } + else if (this >= MONEY_1M * offset) { + //1,000M~900,000M + //1M~900M + return (this / MONEY_1M).ExFormatNumberWithComma(3, false) + "M"; + } + else if (this >= MONEY_1K * offset) { + //1,000K~900,000K + //1K~90K + return (this / MONEY_1K).ExFormatNumberWithComma(3, false) + "K"; + } + else { + //0~9,000,000 + //0~9,000 + return this.ExFormatNumberWithComma(precision); + } + } +}) +Number.prototype.Pad || Object.defineProperty(Number.prototype, 'Pad', { + enumerable: false, + value: function (size: number) { + let s = this + ""; + while (s.length < size) s = "0" + s; + return s; + } +}) +Number.prototype.ExToNumRoundDecimal || Object.defineProperty(Number.prototype, 'ExToNumRoundDecimal', { + enumerable: false, + value: function (precision: number) { + return Math.round(Math.round(this * Math.pow(10, (precision || 0) + 1)) / 10) / Math.pow(10, (precision || 0)); + } +}) +Number.prototype.ExToInt || Object.defineProperty(Number.prototype, 'ExToInt',{ + enumerable: false, + value: function (){ + return ~~this; + } +}) +Number.prototype.ExToNumFloorDecimal || Object.defineProperty(Number.prototype, 'ExToNumFloorDecimal', { + enumerable: false, + value: function (precision: number) { + let str = this.toPrecision(12); + let dotPos = str.indexOf('.'); + return dotPos == -1 ? this : +`${str.substr(0, dotPos + 1 + precision)}`; + } +}) +Number.prototype.ExToStringFloorDecimal || Object.defineProperty(Number.prototype, 'ExToStringFloorDecimal', { + enumerable: false, + value: function (precision: number, isPadZero: boolean = true) { + // 取小數點第X位 + let f = this.ExToNumFloorDecimal(precision); + let s = f.toString(); + // 補0 + if (isPadZero) { + let rs = s.indexOf('.'); + if (rs < 0) { + rs = s.length; + s += '.'; + } + while (s.length <= rs + precision) { + s += '0'; + } + } + return s; + } +}) +Number.prototype.Float2Fixed || Object.defineProperty(Number.prototype, 'Float2Fixed', { + enumerable: false, + value: function () { + if (this.toString().indexOf('e') === -1) { + return Number(this.toString().replace('.', '')); + } + const dLen = this.DigitLength(); + return dLen > 0 ? +parseFloat((this * Math.pow(10, dLen)).toPrecision(12)) : this; + } +}) +Number.prototype.DigitLength || Object.defineProperty(Number.prototype, 'DigitLength', { + enumerable: false, + value: function () { + const eSplit = this.toString().split(/[eE]/); + const len = (eSplit[0].split('.')[1] || '').length - (+(eSplit[1] || 0)); + return len > 0 ? len : 0; + } +}) + + \ No newline at end of file diff --git a/assets/Script/Engine/Utils/CCExtensions/NumberExtension.ts.meta b/assets/Script/Engine/Utils/CCExtensions/NumberExtension.ts.meta new file mode 100644 index 0000000..82655df --- /dev/null +++ b/assets/Script/Engine/Utils/CCExtensions/NumberExtension.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "788e7381-bee6-4b74-addb-c4aa4c4ff4e3", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/CovrtFile.meta b/assets/Script/Engine/Utils/CovrtFile.meta new file mode 100644 index 0000000..12786a8 --- /dev/null +++ b/assets/Script/Engine/Utils/CovrtFile.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "919cddcd-eed2-40a7-be67-2e21365fefe4", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/CovrtFile/ImageConver.ts b/assets/Script/Engine/Utils/CovrtFile/ImageConver.ts new file mode 100644 index 0000000..bbef185 --- /dev/null +++ b/assets/Script/Engine/Utils/CovrtFile/ImageConver.ts @@ -0,0 +1,109 @@ +export module ImageConver { + export function ImageToBase64(Texture2D: cc.Texture2D): string { + let image: ImageBitmap = Texture2D["_image"]; + let cv: HTMLCanvasElement = document.createElement("canvas"); + let context: CanvasRenderingContext2D = cv.getContext("2d"); + cv.width = image.width || 128; + cv.height = image.height || 128; + context.drawImage(image, 0, 0, image.width || 128, image.height || 128); + let DataURL: string = cv.toDataURL(); + return DataURL; + } + export function Base64toBlob(b64Data: any, contentType: string = "image/png", sliceSize: number = 512): Blob { + let byteCharacters: string = atob(b64Data.substring(b64Data.indexOf(",") + 1)); + const byteArrays: any[] = []; + for (let offset: number = 0; offset < byteCharacters.length; offset += sliceSize) { + const slice: string = byteCharacters.slice(offset, offset + sliceSize); + + const byteNumbers: any[] = new Array(slice.length); + for (let i: number = 0; i < slice.length; i++) { + byteNumbers[i] = slice.charCodeAt(i); + } + const byteArray: Uint8Array = new Uint8Array(byteNumbers); + byteArrays.push(byteArray); + } + const blob: Blob = new Blob(byteArrays, { type: contentType }); + return blob; + } + + export function BlobToImageNode(BlobData: Blob, node: cc.Node): void { + let reader: FileReader = new FileReader(); + reader.onloadend = function (): void { + ImageConver.Base64ToImageNode(reader.result + "", node); + }; + reader.readAsDataURL(BlobData); + } + + export function Base64ToImageNode(base64: string, node: cc.Node): void { + let image: HTMLImageElement = new Image(); + image.src = base64; + image.onload = function (): void { + let texture: cc.Texture2D = new cc.Texture2D(); + texture.initWithElement(image); + texture.handleLoadedTexture(); + let spriteFrame: cc.SpriteFrame = new cc.SpriteFrame(texture); + let sprite: cc.Sprite = node.addComponent(cc.Sprite); + sprite.spriteFrame = spriteFrame; + return; + }; + } + + export function Node2Base64(nodeCapture: cc.Node): string { + let nodeCamera: cc.Node = new cc.Node(); + nodeCamera.parent = cc.find("Canvas"); + let camera: cc.Camera = nodeCamera.addComponent(cc.Camera); + + let position: cc.Vec2 = nodeCapture.getPosition(); + let width: number = nodeCapture.width; + let height: number = nodeCapture.height; + + // 当 alignWithScreen 为 true 的时候,摄像机会自动将视窗大小调整为整个屏幕的大小。如果想要完全自由地控制摄像机,则需要将 alignWithScreen 设置为 false。(v2.2.1 新增) + camera.alignWithScreen = false; + // 设置摄像机的投影模式是正交(true)还是透视(false)模式 + camera.ortho = true; + // 摄像机在正交投影模式下的视窗大小。该属性在 alignWithScreen 设置为 false 时生效。 + camera.orthoSize = height / 2; + + let texture: cc.RenderTexture = new cc.RenderTexture(); + // 如果截图内容中不包含 Mask 组件,可以不用传递第三个参数 + texture.initWithSize(width, height); + + // 如果设置了 targetTexture,那么摄像机渲染的内容不会输出到屏幕上,而是会渲染到 targetTexture 上。 + camera.targetTexture = texture; + + // 创建画布 + let canvas: HTMLCanvasElement = document.createElement("canvas"); + canvas.width = width; + canvas.height = height; + + let ctx: CanvasRenderingContext2D = canvas.getContext("2d"); + + nodeCapture.setPosition(cc.Vec2.ZERO); + // 渲染一次摄像机,即更新一次内容到 RenderTexture 中 + camera.render(nodeCapture); + nodeCapture.setPosition(position); + + // 从 render texture 读取像素数据,数据类型为 RGBA 格式的 Uint8Array 数组。 + // 默认每次调用此函数会生成一个大小为 (长 x 高 x 4) 的 Uint8Array。 + let data: Uint8Array = texture.readPixels(); + // write the render data + // PNG 中 1 像素 = 32 bit(RGBA),1 byte = 8 bit,所以 1 像素 = 4 byte + // 每行 width 像素,即 width * 4 字节 + let rowBytes: number = width * 4; + for (let row: number = 0; row < height; row++) { + // RenderTexture 得到的纹理是上下翻转的 + let srow: number = height - 1 - row; + let imageData: ImageData = ctx.createImageData(width, 1); + let start: number = srow * width * 4; + for (let i: number = 0; i < rowBytes; i++) { + imageData.data[i] = data[start + i]; + } + + ctx.putImageData(imageData, 0, row); + } + + let dataURL: string = canvas.toDataURL("image/png"); + + return dataURL; + } +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/CovrtFile/ImageConver.ts.meta b/assets/Script/Engine/Utils/CovrtFile/ImageConver.ts.meta new file mode 100644 index 0000000..566e1d8 --- /dev/null +++ b/assets/Script/Engine/Utils/CovrtFile/ImageConver.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "425b00d6-0d47-4978-9a9f-235733348e3d", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/Number.meta b/assets/Script/Engine/Utils/Number.meta new file mode 100644 index 0000000..7fa6fb1 --- /dev/null +++ b/assets/Script/Engine/Utils/Number.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "d6e55fc6-00b6-496a-aae2-74d694c1223b", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/Number/NumberEx.ts b/assets/Script/Engine/Utils/Number/NumberEx.ts new file mode 100644 index 0000000..fa54745 --- /dev/null +++ b/assets/Script/Engine/Utils/Number/NumberEx.ts @@ -0,0 +1,192 @@ +import { CoroutineV2 } from "../../CatanEngine/CoroutineV2/CoroutineV2"; +import { RandomEx } from "./RandomEx"; + +export module NumberEx { + /** + * 數字滾動 + * @param startNum + * @param endNum + * @param callbackfn + * @param chabgeRate + */ + export function* ChangeScore(startNum: number, endNum: number, callbackfn: (num: number) => void, sec: number) { + let fps = 30; + let waitTime = 0.03; + let changeRate = sec * fps; // -1為了讓changeRate數字能混亂點 + changeRate = changeRate - 1 <= 0 ? changeRate : changeRate - 1; + changeRate = 1 / changeRate; + let diff = endNum - startNum; + let isIncrease = endNum >= startNum; + let tempScore = startNum; + let counter = 0; + //let randomRate = 0; + while (true) { + if (endNum != tempScore) { + /*if (counter % 2 == 0) { + if (isIncrease) { + randomRate = RandomEx.GetFloat(0, diff * changeRate).ExToNumFloorDecimal(2); + } else { + randomRate = RandomEx.GetFloat(0, -diff * changeRate).ExToNumFloorDecimal(2); + } + } else { + randomRate = -randomRate; + }*/ + tempScore += diff * changeRate //+ randomRate; + // 遞增 + if (isIncrease && tempScore > endNum) { + tempScore = endNum; + } + // 遞減 + if (!isIncrease && tempScore < endNum) { + tempScore = endNum; + } + callbackfn(tempScore.ExToInt()); + // yield null; + counter++; + yield CoroutineV2.WaitTime(waitTime); + } + else { + callbackfn(endNum); + break; + } + } + } + + /** + * 數字跳動 + * @param minNum 起始數字 + * @param maxNum 最終數字 + * @param callbackfn callbackfn + * @param sec 時間 + */ + export function* BeatScore(minNum: number, maxNum: number, endNum: number, callbackfn: (num: number) => void, sec: number): IterableIterator { + let fps: number = 13; + let waitTime: number = 0.07; + let changeRate: number = sec * fps; // -1為了讓changeRate數字能混亂點 + changeRate = changeRate - 1 <= 0 ? changeRate : changeRate - 1; + changeRate = 1 / changeRate; + let diff: number = maxNum - minNum; + let isIncrease: boolean = maxNum >= minNum; + let tempScore: number = minNum; + let counter: number = 0; + let randomRate: number = 0; + let lastNum: number = minNum; + let nowNum: number = minNum; + while (true) { + if (maxNum !== tempScore) { + if (counter % 2 === 0) { + if (isIncrease) { + randomRate = RandomEx.GetFloat(0, diff * changeRate).ExToNumFloorDecimal(2); + } else { + randomRate = RandomEx.GetFloat(0, -diff * changeRate).ExToNumFloorDecimal(2); + } + } else { + randomRate = -randomRate; + } + + tempScore += diff * changeRate + randomRate; + // 遞增 + if (isIncrease && tempScore > maxNum) { + tempScore = maxNum; + } + // 遞減 + if (!isIncrease && tempScore < maxNum) { + tempScore = maxNum; + } + while (nowNum === lastNum) { + nowNum = RandomEx.GetInt(minNum, maxNum + 1); + } + lastNum = nowNum; + callbackfn(nowNum); + // yield null; + counter++; + yield CoroutineV2.WaitTime(waitTime); + } else { + callbackfn(endNum); + break; + } + } + } + + /** + * 检测数字是否越界,如果越界给出提示 + * @param {*number} num 输入数 + */ + function checkBoundary(num: number) { + if (_boundaryCheckingState) { + if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) { + console.warn(`${num} is beyond boundary when transfer to integer, the results may not be accurate`); + } + } + } + + /** + * 精确乘法 + */ + export function times(num1: number, num2: number, ...others: number[]): number { + if (others.length > 0) { + return times(times(num1, num2), others[0], ...others.slice(1)); + } + const num1Changed = num1.Float2Fixed(); + const num2Changed = num2.Float2Fixed(); + const baseNum = num1.DigitLength() + num2.DigitLength(); + const leftValue = num1Changed * num2Changed; + + checkBoundary(leftValue); + + return leftValue / Math.pow(10, baseNum); + } + + /** + * 精确加法 + */ + export function plus(num1: number, num2: number, ...others: number[]): number { + if (others.length > 0) { + return plus(plus(num1, num2), others[0], ...others.slice(1)); + } + const baseNum = Math.pow(10, Math.max(num1.DigitLength(), num2.DigitLength())); + return (times(num1, baseNum) + times(num2, baseNum)) / baseNum; + } + + /** + * 精确减法 + */ + export function minus(num1: number, num2: number, ...others: number[]): number { + if (others.length > 0) { + return minus(minus(num1, num2), others[0], ...others.slice(1)); + } + const baseNum = Math.pow(10, Math.max(num1.DigitLength(), num2.DigitLength())); + return (times(num1, baseNum) - times(num2, baseNum)) / baseNum; + } + + /** + * 精确除法 + */ + export function divide(num1: number, num2: number, ...others: number[]): number { + if (others.length > 0) { + return divide(divide(num1, num2), others[0], ...others.slice(1)); + } + const num1Changed = num1.Float2Fixed(); + const num2Changed = num2.Float2Fixed(); + checkBoundary(num1Changed); + checkBoundary(num2Changed); + return times((num1Changed / num2Changed), Math.pow(10, num2.DigitLength() - num1.DigitLength())); + } + + /** + * 四舍五入 + */ + export function round(num: number, ratio: number): number { + const base = Math.pow(10, ratio); + return divide(Math.round(times(num, base)), base); + } + + let _boundaryCheckingState = false; + /** + * 是否进行边界检查 + * @param flag 标记开关,true 为开启,false 为关闭 + */ + function enableBoundaryChecking(flag = true) { + _boundaryCheckingState = flag; + } +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/Number/NumberEx.ts.meta b/assets/Script/Engine/Utils/Number/NumberEx.ts.meta new file mode 100644 index 0000000..87eb62b --- /dev/null +++ b/assets/Script/Engine/Utils/Number/NumberEx.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "363f5f7f-0623-4013-8571-0bb5c1dc95e6", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/Number/RandomEx.ts b/assets/Script/Engine/Utils/Number/RandomEx.ts new file mode 100644 index 0000000..1bfc080 --- /dev/null +++ b/assets/Script/Engine/Utils/Number/RandomEx.ts @@ -0,0 +1,90 @@ + +export module RandomEx { + + /** + * 取得隨機布林值 + */ + export function GetBool() { + return GetInt() >= 0; + } + /** + * 取得隨機整數(回傳min ~ max - 1) + * @param min + * @param max + */ + export function GetInt(min: number = Number.MIN_VALUE, max: number = Number.MAX_VALUE): number { + return Math.floor(Math.random() * (max - min)) + min; + } + /** + * 取得隨機小數 + * @param min + * @param max + */ + export function GetFloat(min: number = Number.MIN_VALUE, max: number = Number.MAX_VALUE): number { + return Math.random() * (max - min) + min; + } + /** + * 隨機取得複數個不重複回傳 + * @param num 取得數量 + * @param items 陣列 + */ + export function GetMultiNoRepeat(num: number, items: any[]): any[] { + let result: any[] = []; + for (let i: number = 0; i < num; i++) { + let ran: number = Math.floor(Math.random() * items.length); + let item = items.splice(ran, 1)[0]; + if (result.indexOf(item) == -1) { + result.push(item); + } + }; + return result; + } + + /** + * 根據權重取得複數個不重複回傳 + * @param prize 獎項 + * @param weights 機率 + * @param count 數量 + */ + export function GetMultiNoRepeatByWeight(prize: any[], weights: number[] = null, count: number = 1): any[] { + if (weights === null) { + weights = []; + for (let i: number = 0; i < prize.length; i++) { + weights.push(1); + } + } + let target: any[] = []; + for (let i: number = 0; i < count; i++) { + let results: number[] = RandomEx.GetPrizeByWeight(prize, weights); + prize.splice(results[0], 1); + weights.splice(results[0], 1); + target.push(results[1]); + } + return target; + } + + + /** + * 根據權重隨機取值 + * @param prize 獎項 + * @param weights 機率 + */ + export function GetPrizeByWeight(prize: any[], weights: number[]): any[] { + if (prize.length !== weights.length) { + console.error(`GetWeight error -> prize.length:${prize.length} !== weights.length:${weights.length}`); + return null; + } + let totalWeight: number = 0; + for (let i: number = 0; i < weights.length; i++) { + totalWeight += weights[i]; + } + let random: number = RandomEx.GetInt(0, totalWeight) + 1; + let nowWeight: number = weights[0]; + for (let i: number = 0; i < weights.length; i++) { + if (nowWeight >= random) { + return [i, prize[i]]; + } + nowWeight += weights[i + 1]; + } + } +} diff --git a/assets/Script/Engine/Utils/Number/RandomEx.ts.meta b/assets/Script/Engine/Utils/Number/RandomEx.ts.meta new file mode 100644 index 0000000..3b2c3db --- /dev/null +++ b/assets/Script/Engine/Utils/Number/RandomEx.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "ba4dee5b-ca5b-4435-a068-c4f5dd832bab", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/ScrollView.meta b/assets/Script/Engine/Utils/ScrollView.meta new file mode 100644 index 0000000..cdd049d --- /dev/null +++ b/assets/Script/Engine/Utils/ScrollView.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.3", + "uuid": "f6471056-03d8-4d55-b039-6b62d056547c", + "importer": "folder", + "isBundle": false, + "bundleName": "", + "priority": 1, + "compressionType": {}, + "optimizeHotUpdate": {}, + "inlineSpriteFrames": {}, + "isRemoteBundle": {}, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/ScrollView/ScrollBase.ts b/assets/Script/Engine/Utils/ScrollView/ScrollBase.ts new file mode 100644 index 0000000..fc86840 --- /dev/null +++ b/assets/Script/Engine/Utils/ScrollView/ScrollBase.ts @@ -0,0 +1,75 @@ +import { CoroutineV2 } from "../../CatanEngine/CoroutineV2/CoroutineV2"; +import UIPanel from "../../Component/UIPanel/UIPanel"; +import ScrollItem from "./ScrollItem"; +import UISuperLayout from "./UISuperLayout"; + +const { ccclass, property } = cc._decorator; + +/** Scroll基底 */ +@ccclass +export default class ScrollBase extends UIPanel { + //#region 外調參數 + + @property({ displayName: "List", type: UISuperLayout }) + public List: UISuperLayout = null; + + //#endregion + + //#region public + + /** ListData */ + public get ListData(): any[] { throw new Error("ListData必須被override"); } + + //#endregion + + //#region protected + + /** 列表資料 */ + protected _listData: any[] = []; + + //#endregion + + //#region ScrollView Function + + /** + * 創建/刷新列表 + * @param isScrollTo 0 不滾動 + * @param isScrollTo 1 滚动到头部 + * @param isScrollTo 2 滚动到尾部 + * @param isScrollTo 3 自动居中到最近Item + */ + public *CreateList(isScrollTo: number = 0): IterableIterator { + if (!this.node.active) { + return; + } + this.List?.CreateItem(this.ListData.length); + yield CoroutineV2.WaitTime(5 / cc.game.getFrameRate()); + switch (isScrollTo) { + case 1: { + this.List?.scrollToHeader(0); + break; + } + + case 2: { + this.List?.scrollToFooter(0); + break; + } + + case 3: { + this.List?.scrollToCenter(); + break; + } + + default: + break; + } + } + + public OnRefreshEvent(node: cc.Node, index: number): void { + let listData: any[] = this.ListData; + let info: any = listData[index]; + node.getComponent(ScrollItem).ImplementSet(info, this, index); + } + + //#endregion +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/ScrollView/ScrollBase.ts.meta b/assets/Script/Engine/Utils/ScrollView/ScrollBase.ts.meta new file mode 100644 index 0000000..3e91f4a --- /dev/null +++ b/assets/Script/Engine/Utils/ScrollView/ScrollBase.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "3e607467-693c-46a9-ac93-ab973e52024d", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/ScrollView/ScrollItem.ts b/assets/Script/Engine/Utils/ScrollView/ScrollItem.ts new file mode 100644 index 0000000..f972dcb --- /dev/null +++ b/assets/Script/Engine/Utils/ScrollView/ScrollItem.ts @@ -0,0 +1,8 @@ + +const { ccclass, property } = cc._decorator; +@ccclass +export default class ScrollItem extends cc.Component { + ImplementSet(...iniData: any[]): void { + // + } +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/ScrollView/ScrollItem.ts.meta b/assets/Script/Engine/Utils/ScrollView/ScrollItem.ts.meta new file mode 100644 index 0000000..a2c33b8 --- /dev/null +++ b/assets/Script/Engine/Utils/ScrollView/ScrollItem.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "0a733d8c-80ae-4e0b-be1b-07dba226c237", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/ScrollView/UISuperLayout.ts b/assets/Script/Engine/Utils/ScrollView/UISuperLayout.ts new file mode 100644 index 0000000..6f02803 --- /dev/null +++ b/assets/Script/Engine/Utils/ScrollView/UISuperLayout.ts @@ -0,0 +1,1460 @@ +/* + * @Author: steveJobs + * @Email: icipiqkm@gmail.com + * @Date: 2021-8-1 01:15:04 + * @Last Modified by: steveJobs + * @Last Modified time: 2021-8-1 14:35:43 + * @Description: + */ + +import UISuperScrollview from "./UISuperScrollView"; + +const { ccclass, property } = cc._decorator; +const EPSILON = 1e-4; +enum Type { + HORIZONTAL = 0, + VERTICAL = 1, +} +cc.Enum(Type); +enum VerticalAxisDirection { + TOP_TO_BOTTOM = 0, + BOTTOM_TO_TOP = 1 +} +cc.Enum(VerticalAxisDirection); +enum HorizontalAxisDirection { + LEFT_TO_RIGHT = 0, + RIGHT_TO_LEFT = 1 +} +cc.Enum(HorizontalAxisDirection); +enum ScrollDirection { + NONE = 0, + HEADER = 1, + FOOTER = 2, +} + +enum IndexVerticalAxisDirection { + TOP = 0, + BOTTOM = 1, +} +cc.Enum(IndexVerticalAxisDirection); +enum IndexHorizontalAxisDirection { + LEFT = 0, + RIGHT = 1 +} +cc.Enum(IndexHorizontalAxisDirection); +@ccclass +export default class UISuperLayout extends cc.Component { + + static VerticalAxisDirection = VerticalAxisDirection; + static HorizontalAxisDirection = HorizontalAxisDirection; + + @property(UISuperScrollview) scrollView: UISuperScrollview = null; + @property(cc.Node) view: cc.Node = null; + @property(cc.Prefab) prefab: cc.Prefab = null; + @property({ type: Type }) layoutType: Type = Type.VERTICAL; + @property({ + type: IndexVerticalAxisDirection, + visible: function () { return (this as any).layoutType == Type.VERTICAL && !(this as any).autoCenter; } + }) indexVerticalAxisDirection = IndexVerticalAxisDirection.TOP; + @property({ + type: IndexHorizontalAxisDirection, + visible: function () { return (this as any).layoutType == Type.HORIZONTAL && !(this as any).autoCenter; } + }) indexHorizontalAxisDirection = IndexHorizontalAxisDirection.LEFT; + @property({ type: VerticalAxisDirection }) verticalAxisDirection = VerticalAxisDirection.TOP_TO_BOTTOM; + @property({ type: HorizontalAxisDirection }) horizontalAxisDirection = HorizontalAxisDirection.LEFT_TO_RIGHT; + + @property({ tooltip: "最小值=1,大于1就是Grid模式" }) groupItemTotal: number = 1; + @property({ tooltip: "决定最多创建Prefab的数量" }) multiple: number = 2; + @property({ tooltip: "顶部填充" }) paddingTop: number = 0; + @property({ tooltip: "底部填充" }) paddingBottom: number = 0; + @property({ tooltip: "左侧填充" }) paddingLeft: number = 0; + @property({ tooltip: "右侧填充" }) paddingRight: number = 0; + @property({ tooltip: "横轴间距" }) spacingX: number = 0; + @property({ tooltip: "纵轴间距" }) spacingY: number = 0; + @property({ tooltip: "计算缩放后的尺寸" }) affectedByScale: boolean = false; + + @property({ tooltip: "开启翻页模式" }) isPageView: boolean = false; + @property({ + tooltip: "每个页面翻页时所需时间。单位:秒", + visible: function () { return (this as any).isPageView; } + }) pageTurningSpeed = 0.3; + @property({ + type: cc.PageViewIndicator, + visible: function () { return (this as any).isPageView; } + }) indicator: cc.PageViewIndicator = null; + @property({ + slide: true, + range: [0, 1, 0.01], + tooltip: "滚动临界值,默认单位百分比,当拖拽超出该数值时,松开会自动滚动下一页,小于时则还原", + visible: function () { return (this as any).isPageView; } + }) scrollThreshold = 0.5; + @property({ + tooltip: "快速滑动翻页临界值。当用户快速滑动时,会根据滑动开始和结束的距离与时间计算出一个速度值,该值与此临界值相比较,如果大于临界值,则进行自动翻页", + visible: function () { return (this as any).isPageView; } + }) autoPageTurningThreshold = 100; + @property({ + type: cc.Component.EventHandler, + visible: function () { return (this as any).isPageView; } + }) pageEvents: cc.Component.EventHandler[] = []; + + + @property({ + tooltip: "开启自动居中", + visible: function () { return !(this as any).isPageView; } + }) autoCenter: boolean = false; + @property({ + tooltip: "自动居中的滚动时间", + visible: function () { return (this as any).autoCenter; } + }) centerTime: number = 1; + @property({ + type: cc.Node, + tooltip: "自动居中的参考节点,如果为空、则默认选择View中心", + visible: function () { return (this as any).autoCenter; } + }) centerNode: cc.Node = null; + @property({ + tooltip: "自动居中时、Item的居中锚点", + // visible: function () { return (this as any).autoCenter; } + }) centerAnchor: cc.Vec2 = new cc.Vec2(.5, .5); + + @property({ tooltip: "上/左 无限循环" }) headerLoop: boolean = false; + @property({ tooltip: "下/右 无限循环" }) footerLoop: boolean = false; + @property(cc.Component.EventHandler) refreshItemEvents: cc.Component.EventHandler[] = []; + + + private stretchLock: { + index?: number, + timeInSecond?: number, + boundary?: cc.Vec2, + reverse?: boolean, + } = {}; + private _currPageIndex: number = 0; + get currPageIndex() { + return this._currPageIndex; + } + private _lastPageIndex: number = 0; + get lastPageIndex() { + return this._lastPageIndex; + } + private isRestart: boolean = false; + /** 当前滚动方向 */ + private scrollDirection: ScrollDirection = ScrollDirection.NONE; + /** 是否垂直滚动 */ + get vertical(): boolean { return this.layoutType == Type.VERTICAL; } + /** 是否水平滚动 */ + get horizontal(): boolean { return this.layoutType == Type.HORIZONTAL; } + get transform(): cc.Node { return this.node; } + /** View 可容纳的宽度 */ + get accommodWidth() { + return this.view.width - this.paddingLeft - this.paddingRight; + } + /** View 可容纳的高度 */ + get accommodHeight() { + return this.view.height - this.paddingTop - this.paddingBottom; + } + /** 头部的节点 */ + get header(): cc.Node { + if (this.node.children.length == 0) return null; + return this.node.children[0]; + } + /** 底部的节点 */ + get footer(): cc.Node { + if (this.node.children.length == 0) return null; + return this.node.children[this.node.children.length - 1]; + } + /** 头部索引 */ + get headerIndex(): number { + if (!this.header) return -1; + let node: any = this.header; + return node["__index"]; + } + /** 底部索引 */ + get footerIndex(): number { + if (!this.footer) return -1; + let node: any = this.footer; + return node["__index"]; + } + /** Item起始位置 */ + get viewStartPoint(): cc.Vec3 { + let pos = new cc.Vec3(); + if (this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + pos.x = this.view.width * -0.5 + this.paddingLeft; + } else { + pos.x = this.view.width * 0.5 - this.paddingRight; + } + if (this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM) { + pos.y = this.view.height * 0.5 - this.paddingTop; + } else { + pos.y = this.view.height * -0.5 + this.paddingBottom; + } + return pos; + } + /** View 头部边界 */ + get viewHeaderBoundary(): number { + return this.vertical ? this.view.height * 0.5 : this.view.width * -0.5; + } + /** View 底部边界 */ + get viewFooterBoundary(): number { + return this.vertical ? this.view.height * -0.5 : this.view.width * 0.5; + } + /** 头部节点边界 */ + get headerBoundary(): number { + if (!this.header) return 0; + if (this.vertical) { + if (this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM) { + return this.node.position.y + this.getItemYMax(this.header) + this.paddingTop; + } else { + return this.node.position.y + this.getItemYMin(this.header) - this.paddingBottom; + } + } else { + if (this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + return this.node.position.x + this.getItemXMin(this.header) - this.paddingLeft; + } else { + return this.node.position.x + this.getItemXMax(this.header) + this.paddingRight; + } + } + } + /** 底部节点边界 */ + get footerBoundary(): number { + if (!this.footer) return 0; + if (this.vertical) { + if (this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM) { + return this.node.position.y + this.getItemYMin(this.footer) - this.paddingBottom; + } else { + return this.node.position.y + this.getItemYMax(this.footer) + this.paddingTop; + } + } else { + if (this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + return this.node.position.x + this.getItemXMax(this.footer) + this.paddingRight; + } else { + return this.node.position.x + this.getItemXMin(this.footer) - this.paddingLeft; + } + } + } + /** 自动居中节点头部边界 */ + get centerHeaderBoundary() { + let key = this.vertical ? "y" : "x"; + var offset; + if (this.centerNode) { + offset = this.viewHeaderBoundary - (this.centerNode.position as any)[key]; + } else { + offset = this.viewHeaderBoundary - (this.view.position as any)[key]; + } + if (this.vertical && this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM || this.horizontal && this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + return this.headerBoundary + offset; + } else { + return this.footerBoundary + offset; + } + } + /** 自动居中节点底部边界 */ + get centerFooterBoundary() { + let key = this.vertical ? "y" : "x"; + var offset; + if (this.centerNode) { + offset = this.viewFooterBoundary - (this.centerNode.position as any)[key]; + } else { + offset = this.viewFooterBoundary - (this.view.position as any)[key]; + } + if (this.vertical && this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM || this.horizontal && this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + return this.footerBoundary + offset; + } else { + return this.headerBoundary + offset; + } + } + /** 是否超出左侧边界 */ + get isOfLeftBoundary(): number { + if (this.vertical) return 0; + if (this.autoCenter) { + if (this.scrollDirection == ScrollDirection.HEADER) { + return this.centerHeaderBoundary; + } + return 0; + } + if (this.headerLoop) { + if (this.header) return 0; + return this.viewHeaderBoundary + this.node.position.x; + } + + if (!this.header || this.fixedItemWidth <= this.view.width) { + return this.viewHeaderBoundary + this.node.position.x; + } + if (this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + if (this.headerIndex == 0) { + return this.headerBoundary; + } + } else { + if (this.footerIndex == this.itemTotal - 1) { + return this.footerBoundary; + } + } + return 0; + } + /** 是否超出顶部边界 */ + get isOfTopBoundary(): number { + if (!this.vertical) return 0; + if (this.autoCenter) { + if (this.scrollDirection == ScrollDirection.HEADER) { + return this.centerHeaderBoundary; + } + return 0; + } + if (this.headerLoop) { + if (this.header) return 0; + return this.viewHeaderBoundary + this.node.position.y; + } + if (!this.header || this.fixedItemHeight <= this.view.height) { + return this.viewHeaderBoundary + this.node.position.y; + } + if (this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM) { + if (this.headerIndex == 0) { + return this.headerBoundary; + } + } else { + if (this.footerIndex == this.itemTotal - 1) { + return this.footerBoundary; + } + } + return 0; + } + /** 是否超出右侧边界 */ + get isOfRightBoundary(): number { + if (this.vertical) return 0; + if (this.autoCenter) { + if (this.scrollDirection == ScrollDirection.FOOTER) { + return this.centerFooterBoundary; + } + return 0; + } + if (this.footerLoop) { + if (this.footer) return 0; + return this.viewFooterBoundary + this.node.position.x; + } + if (!this.footer || this.fixedItemWidth <= this.view.width) { + return this.viewFooterBoundary + this.node.position.x; + } + if (this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + if (this.footerIndex == this.itemTotal - 1) { + return this.footerBoundary; + } + } else { + if (this.headerIndex == 0) { + return this.headerBoundary; + } + } + return 0; + } + /** 是否超出底部边界 */ + get isOfButtomBoundary(): number { + if (!this.vertical) return 0; + if (this.autoCenter) { + if (this.scrollDirection == ScrollDirection.FOOTER) { + return this.centerFooterBoundary; + } + return 0; + } + if (this.footerLoop) { + if (this.footer) return 0; + return this.viewFooterBoundary + this.node.position.y; + } + if (!this.footer || this.fixedItemHeight <= this.view.height) { + return this.viewFooterBoundary + this.node.position.y; + } + if (this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM) { + if (this.footerIndex == this.itemTotal - 1) { + return this.footerBoundary; + } + } else { + if (this.headerIndex == 0) { + return this.headerBoundary; + } + } + return 0; + } + /** 从头部到底部的所有Item高度总和 */ + get fixedItemHeight(): number { + if (!this.header) return 0; + if (this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM) { + return Math.abs(this.getItemYMax(this.header)) + Math.abs(this.getItemYMin(this.footer)); + } else { + return Math.abs(this.getItemYMin(this.header)) + Math.abs(this.getItemYMax(this.footer)); + } + } + /** 从头部到底部的所有Item宽度总和 */ + get fixedItemWidth(): number { + if (!this.header) return 0; + if (this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + return Math.abs(this.getItemXMin(this.header)) + Math.abs(this.getItemXMax(this.footer)); + } else { + return Math.abs(this.getItemXMax(this.header)) + Math.abs(this.getItemXMin(this.footer)); + } + } + /** 返回 header到 footer 之间的整体尺寸 如果Item数量不足以撑开View 则返回View尺寸 最小值是View尺寸 */ + get contentSize(): cc.Size { + if (this.node.children.length == 0) return this.view.getContentSize(); + let size = new cc.Size(this.view.getContentSize().width, this.view.getContentSize().height); + if (this.vertical) { + if (this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM) { + size.height = this.headerBoundary + -this.footerBoundary; + } else { + size.height = this.footerBoundary + -this.headerBoundary; + } + } else { + if (this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + size.width = this.footerBoundary + -this.headerBoundary; + } else { + size.width = this.headerBoundary + -this.footerBoundary; + } + } + if (size.width < this.view.getContentSize().width) { + size.width = this.view.getContentSize().width; + } + if (size.height < this.view.getContentSize().height) { + size.height = this.view.getContentSize().height; + } + return size; + } + private prevPos: cc.Vec3 = new cc.Vec3(0, 0, 0); + private _maxPrefabTotal: number = 0; + /** 已被创建的Item数量 */ + get maxPrefabTotal(): number { return this._maxPrefabTotal; } + private currentCreateItemTotal: number = 0; + private _itemTotal: number = 0; + /** 数据长度 */ + get itemTotal(): number { return this._itemTotal; } + private _centerPosition: cc.Vec3; + /** 自动居中的参考位置 */ + get centerPosition(): cc.Vec3 { + if (!this._centerPosition) { + this._centerPosition = new cc.Vec3(); + if (this.autoCenter) { + if (this.centerNode) { + let worldPos = this.centerNode.parent.convertToWorldSpaceAR(this.centerNode.position); + this._centerPosition = this.view.convertToNodeSpaceAR(worldPos); + } + } else { + if (this.vertical) { + if (this.indexVerticalAxisDirection == IndexVerticalAxisDirection.TOP) { + this._centerPosition.y = this.viewHeaderBoundary; + } else { + this._centerPosition.y = this.viewFooterBoundary; + } + } else { + if (this.indexHorizontalAxisDirection == IndexHorizontalAxisDirection.LEFT) { + this._centerPosition.x = this.viewHeaderBoundary; + } else { + this._centerPosition.x = this.viewFooterBoundary; + } + } + } + } + return this._centerPosition; + } + onLoad() { + this.transform.setAnchorPoint(new cc.Vec2(.5, .5)); + this.transform.setContentSize(this.view.getContentSize()); + this.node.setPosition(cc.Vec3.ZERO); + if (this.isPageView) this.autoCenter = false; + this.scrollView.view.on(cc.Node.EventType.SIZE_CHANGED, this.onViewSizeChange, this); + Object.defineProperty(this.transform, "getContentSize", { get: () => () => this.contentSize }); + Object.defineProperty(this.transform, "contentSize", { get: () => this.contentSize }); + Object.defineProperty(this.transform, "width", { get: () => this.contentSize.width }); + Object.defineProperty(this.transform, "height", { get: () => this.contentSize.height }); + } + onEnable() { + this.addEventListener(); + } + onDisable() { + this.removeEventListener(); + } + /** + * 更新item数量 + * @param count + * @param onRefreshLastItem 如果你确定只需要刷新最后一个item 那么这个设置成true 就不会刷新所有数据 + */ + public CreateItem(count: number, refreshLastItem: boolean = false): this { + this.currentCreateItemTotal = count; + this.scrollDirection = ScrollDirection.HEADER; + this.createItems(count, refreshLastItem); + let offset = count - this.itemTotal; + this._itemTotal = count; + this.refreshItems(offset, refreshLastItem); + if (!refreshLastItem) this.updateItems(); + this.scrollView.startAutoScroll(); + this.scrollView.release(); + if (this.indicator) { + this.indicator.setPageView((this.scrollView as any)); + } + if (this.autoCenter) { + this.scrollToCenter(); + } + return this; + } + /** + * 刷新所有item + */ + updateItems(): this { + this.resetIndexStartToEnd(this.headerIndex); + return this; + } + /** 告知组件你的节点尺寸 */ + updateItemSize(node: cc.Node, size: cc.Size) { + if (this.groupItemTotal > 1) return; + if (!node || !size) return; + // 20220209 開啟會導致對應位置的組件被重置錯誤Size by豆豆 + // (node as any)["__runtime_size"] = size; + this.onChangeChildSize(node); + } + /** 自动居中到最近Item */ + scrollToCenter() { + this.soonFinish(); + } + /** 滚动到头部 */ + scrollToHeader(timeInSecond?: number) { + var headerOrFooter = 0; + if (this.vertical) { + if (this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM) { + headerOrFooter = this.viewHeaderBoundary; + if (this.indexVerticalAxisDirection == IndexVerticalAxisDirection.BOTTOM) { + headerOrFooter -= this.header.height + this.paddingTop + this.paddingBottom; + } + } else { + headerOrFooter = this.viewFooterBoundary; + if (this.indexVerticalAxisDirection == IndexVerticalAxisDirection.TOP) { + headerOrFooter += this.header.height + this.paddingTop + this.paddingBottom; + } + } + } else { + if (this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + headerOrFooter = this.viewHeaderBoundary; + if (this.indexHorizontalAxisDirection == IndexHorizontalAxisDirection.RIGHT) { + headerOrFooter += this.header.width + this.paddingLeft + this.paddingRight; + } + } else { + headerOrFooter = this.viewFooterBoundary; + if (this.indexHorizontalAxisDirection == IndexHorizontalAxisDirection.LEFT) { + headerOrFooter -= this.header.width + this.paddingLeft + this.paddingRight; + } + } + } + this.scrollToIndex(0, timeInSecond, new cc.Vec2(headerOrFooter, headerOrFooter)); + + } + /** 滚动到尾部 */ + scrollToFooter(timeInSecond?: number) { + var headerOrFooter = 0; + if (this.vertical) { + if (this.fixedItemHeight < this.view.height) return; + if (this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM) { + headerOrFooter = this.viewFooterBoundary; + if (this.indexVerticalAxisDirection == IndexVerticalAxisDirection.BOTTOM) { + headerOrFooter += this.footer.height + this.paddingTop + this.paddingBottom; + } + } else { + headerOrFooter = this.viewHeaderBoundary; + if (this.indexVerticalAxisDirection == IndexVerticalAxisDirection.TOP) { + headerOrFooter -= this.footer.height + this.paddingTop + this.paddingBottom; + } + } + } else { + if (this.fixedItemWidth < this.view.width) return; + if (this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + headerOrFooter = this.viewFooterBoundary; + if (this.indexHorizontalAxisDirection == IndexHorizontalAxisDirection.RIGHT) { + headerOrFooter -= this.footer.width + this.paddingLeft + this.paddingRight; + } + } else { + headerOrFooter = this.viewHeaderBoundary; + if (this.indexHorizontalAxisDirection == IndexHorizontalAxisDirection.LEFT) { + headerOrFooter += this.footer.width + this.paddingLeft + this.paddingRight; + } + } + } + this.scrollToIndex(this.itemTotal - 1, timeInSecond, new cc.Vec2(headerOrFooter, headerOrFooter), true); + } + private isNearFooter(index: number) { + let nearFooter = false; + let flag = index > this.footerIndex && index < this.headerIndex; + if (flag) { + let result = Math.abs(index - this.headerIndex) < Math.abs(index - this.footerIndex); + if (this.vertical) { + if (this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM) { + nearFooter = !result; + } else { + nearFooter = result; + } + } else { + if (this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + nearFooter = !result; + } else { + nearFooter = result; + } + } + } else if (index > this.footerIndex) { + if (this.vertical) { + nearFooter = this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM ? true : false; + } else { + nearFooter = this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT ? true : false; + } + } else if (index < this.headerIndex) { + if (this.vertical) { + nearFooter = this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM ? false : true; + } else { + nearFooter = this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT ? false : true; + } + } + return nearFooter; + } + private getFooterOffset(index: number) { + let footerOffset = this.footerIndex % this.groupItemTotal; + let indexOffset = index % this.groupItemTotal; + return indexOffset - footerOffset + this.groupItemTotal; + } + private getHeaderOffset(index: number) { + let headerOffset = this.headerIndex % this.groupItemTotal; + let indexOffset = index % this.groupItemTotal; + return headerOffset - indexOffset + this.groupItemTotal; + } + private offsetToHeader(index: number) { + var offset = 0; + if (this.vertical) { + if (this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM) { + offset = this.getHeaderOffset(index); + } else { + offset = this.getFooterOffset(index); + } + } else { + if (this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + offset = this.getHeaderOffset(index); + } else { + offset = this.getFooterOffset(index); + } + } + offset -= this.groupItemTotal; + for (let i = 0; i < offset; i++) { + this.pushToHeader(true); + } + } + private offsetToFooter(index: number) { + var offset = 0; + if (this.vertical) { + if (this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM) { + offset = this.getFooterOffset(index); + } else { + offset = this.getHeaderOffset(index); + } + } else { + if (this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + offset = this.getFooterOffset(index); + } else { + offset = this.getHeaderOffset(index); + } + } + offset -= this.groupItemTotal; + for (let i = 0; i < offset; i++) { + this.pushToFooter(true); + } + } + private resetIndexStartToEnd(index: number) { + for (let i = 0; i < this.node.children.length; i++) { + const child: any = this.node.children[i]; + child["__index"] = index; + index++; + if (this.headerLoop || this.footerLoop) { + if (index < 0 || index >= this.itemTotal) { + index = 0; + } + } + this.notifyRefreshItem(child); + } + } + private resetIndexEndToStart(index: number) { + for (let i = this.node.children.length - 1; i >= 0; i--) { + const child: any = this.node.children[i]; + child["__index"] = index; + index--; + if (this.headerLoop || this.footerLoop) { + if (index < 0) { + index = this.itemTotal - 1; + } + } + this.notifyRefreshItem(child); + } + } + /** 跳转到指定索引位置 */ + scrollToIndex(index: number, timeInSecond?: number, boundary?: cc.Vec2, reverse: boolean = false) { + if (isNaN(index) || index < 0 || index > this.itemTotal - 1) return; + this.scrollView.stopAutoScroll(); + if (this.isPageView) { + this.scrollView.savePageIndex(index); + } + var child = this.node.children.find((item: any) => item["__index"] == index); + var nearFooter = this.isNearFooter(index); + this.stretchLock.index = index; + this.stretchLock.timeInSecond = timeInSecond; + this.stretchLock.boundary = boundary; + this.stretchLock.reverse = reverse; + if (!child) { + if (index == 0) { + this.pushToHeader(); + } + if (index == this.itemTotal - 1) { + this.pushToFooter(); + } + var flag = this.vertical && this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM || !this.vertical && this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT; + if (nearFooter) { + this.offsetToFooter(index); + flag ? this.resetIndexEndToStart(index) : this.resetIndexStartToEnd(index); + } else { + this.offsetToHeader(index); + flag ? this.resetIndexStartToEnd(index) : this.resetIndexEndToStart(index); + } + child = this.node.children.find((item: any) => item["__index"] == index); + } + if (!child) return; + let itemPos = child.getPosition().clone(); + if (!this.autoCenter) { + if (this.vertical) { + if (this.indexVerticalAxisDirection == IndexVerticalAxisDirection.TOP) { + if (!reverse) { + itemPos.y = this.getItemYMax(child) + this.paddingTop; + } else { + itemPos.y = this.getItemYMin(child) - this.paddingBottom; + } + } else { + if (!reverse) { + itemPos.y = this.getItemYMin(child) - this.paddingBottom; + } else { + itemPos.y = this.getItemYMax(child) + this.paddingTop; + } + } + } else { + if (this.indexHorizontalAxisDirection == IndexHorizontalAxisDirection.LEFT) { + if (!reverse) { + itemPos.x = this.getItemXMin(child) - this.paddingLeft; + } else { + itemPos.x = this.getItemXMax(child) + this.paddingRight; + } + } else { + if (!reverse) { + itemPos.x = this.getItemXMax(child) + this.paddingRight; + } else { + itemPos.x = this.getItemXMin(child) - this.paddingLeft; + } + } + } + } + let worldPos = this.transform.convertToWorldSpaceAR(itemPos); + let localPos = this.view.convertToNodeSpaceAR(worldPos); + var multiple; + if (!this.autoCenter && boundary) { + multiple = boundary; + } else { + multiple = this.getCenterAnchor(child, this.centerPosition); + } + localPos.x *= -1; + localPos.y *= -1; + localPos = localPos.add(multiple); + this.scrollView.scrollToAny(localPos, timeInSecond, true); + } + protected async onViewSizeChange() { + this.isRestart = true; + this.createItems(this.currentCreateItemTotal); + this.resetChilds(true); + this.scrollToHeader(); + for (let i = 0; i < this.node.children.length; i++) { + const child: any = this.node.children[i]; + const transform = child; + this.setAndSaveSizeAndScale(transform); + } + this.isRestart = false; + } + protected setAndSaveSizeAndScale(item: cc.Node) { + item.setContentSize(this.getItemSize(item)); + item["__size"] = item.getContentSize().clone(); + item["__scale"] = cc.v2(item.scaleX, item.scaleY); + } + /** 根据centerAnchor计算自动居中的真实位置 */ + protected getCenterAnchor(item: cc.Node, center: cc.Vec3) { + var pos = center.clone(); + if (this.vertical) { + let anchor = item.height * this.centerAnchor.y; + let origin = item.height * item.anchorY; + pos.y -= anchor - origin; + } else { + let anchor = item.width * this.centerAnchor.x; + let origin = item.width * item.anchorX; + pos.x += anchor - origin; + } + return pos; + } + /** 滚动即将结束时 跑自动居中的逻辑 */ + protected soonFinish() { + if (!this.autoCenter) return; + if (this.scrollView.pullRefresh) return; + this.scrollView.stopAutoScroll(); + var findedPos = new cc.Vec2(999999, 999999); + for (let i = 0; i < this.node.children.length; i++) { + const child = this.node.children[i]; + let worldPos = this.transform.convertToWorldSpaceAR(child.position)!; + let localPos = this.view.convertToNodeSpaceAR(worldPos); + let map = { width: false, height: false }; + var multiple = this.getCenterAnchor(child, this.centerPosition); + localPos.x -= multiple.x; + localPos.y -= multiple.y; + let newLocalPos = localPos; + map.width = Math.abs(newLocalPos.x) < Math.abs(findedPos.x); + map.height = Math.abs(newLocalPos.y) < Math.abs(findedPos.y); + if (this.vertical && map.height) { + findedPos = cc.v2(newLocalPos.x, newLocalPos.y); + } else if (!this.vertical && map.width) { + findedPos = cc.v2(newLocalPos.x, newLocalPos.y); + } + } + findedPos.x *= -1; + findedPos.y *= -1; + this.scrollView.scrollToAny(findedPos, this.centerTime); + } + /** 根据groupItemTotal和View可容纳的尺寸 来平均分配Item该有的尺寸 */ + protected getItemSize(item: cc.Node): cc.Size { + let size = new cc.Size(0, 0); + if (this.vertical) { + let spacing = this.spacingX * (this.groupItemTotal - 1); + size.width = (this.accommodWidth - spacing) / this.groupItemTotal; + size.height = item.height; + } else { + let spacing = this.spacingY * (this.groupItemTotal - 1); + size.height = (this.accommodHeight - spacing) / this.groupItemTotal; + size.width = item.width; + } + return size; + } + /** 获取Item的YMax */ + protected getItemYMax(item: cc.Node | null): number { + if (!item) return 0; + let height = this.getScaleHeight(item) * (1 - item.anchorY); + return item.position.y + height; + } + /** 获取Item的YMin */ + protected getItemYMin(item: cc.Node | null): number { + if (!item) return 0; + let height = this.getScaleHeight(item) * item.anchorY; + return item.position.y - height; + } + /** 获取Item的XMax */ + protected getItemXMax(item: cc.Node | null): number { + if (!item) return 0; + let width = this.getScaleWidth(item) * (1 - item.anchorX); + return item.position.x + width; + } + /** 获取Item的XMin */ + protected getItemXMin(item: cc.Node | null): number { + if (!item) return 0; + let width = this.getScaleWidth(item) * item.anchorX; + return item.position.x - width; + } + /** 获取一组Item中起始位置X */ + protected getStartX(item: cc.Node | null): number { + if (!item) return 0; + var x = 0; + if (this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + let width = this.getScaleWidth(item) * item.anchorX; + x = this.viewStartPoint.x + width; + } else { + let width = this.getScaleWidth(item) * (1 - item.anchorX); + x = this.viewStartPoint.x - width; + } + return x; + } + /** 获取一组Item中结束位置X */ + protected getEndX(item: cc.Node | null): number { + if (!item) return 0; + var x = 0; + if (this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + let width = this.getScaleWidth(item) * (1 - item.anchorX); + x = -this.viewStartPoint.x - width - this.paddingRight + this.paddingLeft; + } else { + let width = this.getScaleWidth(item) * item.anchorX; + x = -this.viewStartPoint.x + width + this.paddingLeft - this.paddingRight; + } + return x; + } + /** 获取一组Item中起始位置Y */ + protected getStartY(item: cc.Node | null): number { + if (!item) return 0; + var y = 0; + if (this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM) { + let height = this.getScaleHeight(item) * (1 - item.anchorY); + y = this.viewStartPoint.y - height; + } else { + let height = this.getScaleHeight(item) * item.anchorY; + y = this.viewStartPoint.y + height; + } + return y; + } + /** 获取一组Item中结束位置Y */ + protected getEndY(item: cc.Node | null): number { + if (!item) return 0; + var y = 0; + if (this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM) { + let height = this.getScaleHeight(item) * item.anchorY; + y = -this.viewStartPoint.y + height + this.paddingBottom - this.paddingTop; + } else { + let height = this.getScaleHeight(item) * (1 - item.anchorY); + y = -this.viewStartPoint.y - height - this.paddingTop + this.paddingBottom; + } + return y; + } + /** relative的顶部是否有也容纳空间 */ + protected isAccommodateByTop(relative: cc.Node) { + var max = this.getItemYMax(relative); + return max + this.paddingTop < this.accommodHeight * 0.5; + } + /** relative的底部是否有也容纳空间 */ + protected isAccommodateByBottom(relative: cc.Node) { + var min = this.getItemYMin(relative); + return min - this.paddingBottom > this.accommodHeight * -0.5; + } + /** relative的左侧是否有也容纳空间 */ + protected isAccommodateByLeft(relative: cc.Node) { + var min = this.getItemXMin(relative); + return min - this.paddingLeft > this.accommodWidth * -0.5; + } + /** relative的右侧是否有也容纳空间 */ + protected isAccommodateByRight(relative: cc.Node) { + var max = this.getItemXMax(relative); + return max + this.paddingRight < this.accommodWidth * 0.5; + } + /** relative的左侧位置 */ + protected getRelativeByLeft(item: cc.Node, relative: cc.Node): number { + var min = this.getItemXMin(relative); + return min - this.spacingX - this.getScaleWidth(item) * (1 - item.anchorX); + } + /** relative的右侧位置 */ + protected getRelativeByRight(item: cc.Node, relative: cc.Node): number { + var max = this.getItemXMax(relative); + return max + this.spacingX + this.getScaleWidth(item) * item.anchorX; + } + /** relative的顶部位置 */ + protected getRelativeByTop(item: cc.Node, relative: cc.Node): number { + var max = this.getItemYMax(relative); + return max + this.spacingY + this.getScaleHeight(item) * item.anchorY; + } + /** relative的底部位置 */ + protected getRelativeByBottom(item: cc.Node, relative: cc.Node): number { + var min = this.getItemYMin(relative); + return min - this.spacingY - this.getScaleHeight(item) * (1 - item.anchorY); + } + /** 设置Item的坐标位置 */ + protected setItemPosition(item: cc.Node, relative: cc.Node, reverse: boolean = false, isHeader: boolean = false) { + var pos = new cc.Vec3(); + if (isHeader) { + pos.x = this.getStartX(item); + pos.y = this.getStartY(item); + } else { + if (this.vertical) { + pos = this.getVerticalRelativePosition(item, relative, reverse); + } else { + pos = this.getHorizontalRelativePosition(item, relative, reverse); + } + } + item.setPosition(pos); + } + + /** 计算垂直模式的Item应该的位置 */ + protected getVerticalRelativePosition(item: cc.Node, relative: cc.Node, reverse: boolean) { + var pos = new cc.Vec3(); + var isAccommodate = false; + if (this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + isAccommodate = !reverse ? this.isAccommodateByRight(relative) : this.isAccommodateByLeft(relative); + } else { + isAccommodate = !reverse ? this.isAccommodateByLeft(relative) : this.isAccommodateByRight(relative); + } + // 横轴 + if (this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + if (!reverse) { + pos.x = isAccommodate ? this.getRelativeByRight(item, relative) : this.getStartX(item); + } else { + pos.x = isAccommodate ? this.getRelativeByLeft(item, relative) : this.getEndX(item); + } + } else { + if (!reverse) { + pos.x = isAccommodate ? this.getRelativeByLeft(item, relative) : this.getStartX(item); + } else { + pos.x = isAccommodate ? this.getRelativeByRight(item, relative) : this.getEndX(item); + } + } + // 纵轴 + if (this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM) { + if (!reverse) { + pos.y = isAccommodate ? relative.position.y : this.getRelativeByBottom(item, relative); + } else { + pos.y = isAccommodate ? relative.position.y : this.getRelativeByTop(item, relative); + } + } else { + if (!reverse) { + pos.y = isAccommodate ? relative.position.y : this.getRelativeByTop(item, relative); + } else { + pos.y = isAccommodate ? relative.position.y : this.getRelativeByBottom(item, relative); + } + } + return pos; + } + /** 计算水平模式的Item应该的位置 */ + protected getHorizontalRelativePosition(item: cc.Node, relative: cc.Node, reverse: boolean) { + var pos = new cc.Vec3(); + var isAccommodate = false; + if (this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM) { + isAccommodate = !reverse ? this.isAccommodateByBottom(relative) : this.isAccommodateByTop(relative); + } else { + isAccommodate = !reverse ? this.isAccommodateByTop(relative) : this.isAccommodateByBottom(relative); + } + // 纵轴 + if (this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM) { + if (!reverse) { + pos.y = isAccommodate ? this.getRelativeByBottom(item, relative) : this.getStartY(item); + } else { + pos.y = isAccommodate ? this.getRelativeByTop(item, relative) : this.getEndY(item); + } + } else { + if (!reverse) { + pos.y = isAccommodate ? this.getRelativeByTop(item, relative) : this.getStartY(item); + } else { + pos.y = isAccommodate ? this.getRelativeByBottom(item, relative) : this.getEndY(item); + } + } + // 横轴 + if (this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + if (!reverse) { + pos.x = isAccommodate ? relative.position.x : this.getRelativeByRight(item, relative); + } else { + pos.x = isAccommodate ? relative.position.x : this.getRelativeByLeft(item, relative); + } + } else { + if (!reverse) { + pos.x = isAccommodate ? relative.position.x : this.getRelativeByLeft(item, relative); + } else { + pos.x = isAccommodate ? relative.position.x : this.getRelativeByRight(item, relative); + } + } + return pos; + } + /** 当数据长度发生变化时 计算item应该怎么排列 */ + protected refreshItems(offset: number, refreshLastItem: boolean = false) { + if (offset < 0) { + var prev = this.header; + if (this.contentSize.height == this.view.height) { + for (let i = 0; i < this.node.children.length; i++) { + const child = this.node.children[i]; + this.setItemPosition(child, prev, false, i == 0); + prev = child; + } + } else { + for (let i = 0; i < -offset; i++) { + if (this.headerLoop) { + this.pushToHeader(); + } else if (this.footerLoop) { + this.pushToHeader(); + } else { + if (this.vertical && this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM || this.horizontal && this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + this.pushToHeader(true); + this.pushToFooter(); + } else { + this.pushToFooter(true); + this.pushToHeader(); + } + } + } + } + let startIndex = this.headerIndex > 0 ? this.headerIndex : 0; + if (startIndex + this.node.children.length > this.itemTotal) { + startIndex += offset; + } + if (startIndex < 0) startIndex = 0; + for (let i = 0; i < this.node.children.length; i++) { + const child: any = this.node.children[i]; + if (this.headerLoop || this.footerLoop) { + if (startIndex > this.itemTotal - 1) { + startIndex = 0; + } + } + child["__index"] = startIndex; + startIndex++; + if (refreshLastItem) { + this.notifyRefreshItem(child); + } + } + this.scrollView.stopAutoScroll(); + this.scrollView.startAutoScroll(); + } else { + for (let i = 0; i < this.node.children.length; i++) { + if (this.vertical) { + if (this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM) { + this.pushToFooter(); + } else { + this.pushToHeader(); + } + } else { + if (this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + this.pushToFooter(); + } else { + this.pushToHeader(); + } + } + } + } + } + protected createItems(count: number, refreshLastItem: boolean = false) { + // 有多余的item 需要删除 不处理 + if (this.node.children.length > count) { + this.removeItems(count); + return; + } + if (!this.needAddPrefab()) { + // 已经固定item总数 不处理 + if (this._maxPrefabTotal > 0 && this._maxPrefabTotal == this.node.children.length) { + return; + } + } + let total = count - this.node.children.length; //计算当前应该创建的总数 + for (let i = 0; i < total; i++) { + let child: any = cc.instantiate(this.prefab); + const transform = child; + this.setAndSaveSizeAndScale(transform); + child["__index"] = this.node.children.length; + this.node.addChild(child); + var reverse = false; + var index = this.node.children.length - 2; + var relative; + if (child["__index"] == 0) { + relative = this.footer; + } else { + relative = this.node.children[index]; + } + child.on(cc.Node.EventType.SIZE_CHANGED, () => { this.onChangeChildSize(transform); }, this, true); + child.on(cc.Node.EventType.SCALE_CHANGED, (type: any) => { this.onChangeChildScale(transform); }, this, true); + if (refreshLastItem) { + this.notifyRefreshItem(child); + } + this.setItemPosition(child, relative, reverse, child["__index"] == 0); + + if (!this.needAddPrefab()) { + this._maxPrefabTotal = this.node.children.length; + console.log("已固定item数量", this._maxPrefabTotal); + break; + } + } + } + protected needAddPrefab() { + const self = this.vertical ? this.contentSize.height : this.contentSize.width; + if (self > 0) { + // 当尺寸改变时 重新计算prefab的数量 + const view = this.vertical ? this.view.height : this.view.width; + if (self < view * this.multiple) { + return true; + } + } + return false; + } + protected async onChangeChildSize(item: cc.Node) { + const node: any = item; + if (this.groupItemTotal > 1) { + const __size = node["__size"]; + item.setContentSize(__size); + console.warn("表格布局不支持动态修改 Size,如果你非要修改,那你把我注释掉看效果"); + return; + } + if (this.stretchLock.index == node["__index"]) { + this.scrollToIndex(this.stretchLock.index!, this.stretchLock.timeInSecond, this.stretchLock.boundary, this.stretchLock.reverse); + } + this.resetStrectchItems(); + } + protected async onChangeChildScale(item: cc.Node) { + if (!this.affectedByScale) return; + const node: any = item; + if (this.groupItemTotal > 1) { + const __scale = node["__scale"]; + item.setScale(__scale); + // console.warn("表格布局不支持动态修改 Scale,如果你非要修改,那你把我注释掉看效果") + return; + } + if (this.stretchLock.index == node["__index"]) { + this.scrollToIndex(this.stretchLock.index!, this.stretchLock.timeInSecond, this.stretchLock.boundary, this.stretchLock.reverse); + } + this.resetStrectchItems(); + } + protected resetStrectchItems() { + if (!isNaN(this.stretchLock.index!)) { + const index = this.node.children.findIndex((item: any) => item["__index"] == this.stretchLock.index); + if (index != -1) { + for (let i = index; i >= 0; i--) { + const item = this.node.children[i]; + if (i == index) continue; + if (i < index) { + this.setItemPosition(item, this.node.children[i + 1], true); + } + } + for (let i = index; i < this.node.children.length; i++) { + const item = this.node.children[i]; + if (i == index) continue; + this.setItemPosition(item, this.node.children[i - 1]); + } + return; + } + } + if (this.scrollDirection == ScrollDirection.HEADER) { + this.unschedule(this.stretchToFooter); + this.scheduleOnce(this.stretchToFooter); + } else { + this.unschedule(this.stretchToHeader); + this.scheduleOnce(this.stretchToHeader); + } + } + private stretchToHeader() { + for (let i = this.node.children.length - 1; i >= 0; i--) { + const item = this.node.children[i]; + if (i == this.node.children.length - 1) continue; + this.setItemPosition(item, this.node.children[i + 1], true); + } + } + private stretchToFooter() { + for (let i = 0; i < this.node.children.length; i++) { + const item = this.node.children[i]; + if (i == 0) continue; + this.setItemPosition(item, this.node.children[i - 1]); + } + } + + /** 删除多余的item */ + protected removeItems(count: number) { + // 有多余的item 需要删除 + let length = this.node.children.length - count; + // 删除掉多余的item + for (let i = 0; i < length; i++) { + var child = this.node.children[this.node.children.length - 1]; + child.off(cc.Node.EventType.SIZE_CHANGED); + child.off(cc.Node.EventType.SCALE_CHANGED); + this.node.removeChild(child); + child.destroy(); + } + } + protected addEventListener() { + this.node.on(cc.Node.EventType.POSITION_CHANGED, this.onPositionChanged, this); + } + protected removeEventListener() { + this.node.off(cc.Node.EventType.POSITION_CHANGED, this.onPositionChanged, this); + } + /** 重新计算当前所有Item的位置 */ + protected resetChilds(start: boolean = false) { + if (this.vertical && this.fixedItemHeight <= this.view.height || !this.vertical && this.fixedItemWidth <= this.view.width) { + let x = this.getStartX(this.header); + let y = this.getStartY(this.header); + this.header && this.header.setPosition(new cc.Vec3(x, y)); + } + if (start) { + if (this.vertical) { + let x = this.getStartX(this.header); + this.header && this.header.setPosition(new cc.Vec3(x, this.header.position.y)); + } else { + let y = this.getStartY(this.header); + this.header && this.header.setPosition(new cc.Vec3(this.header.position.x, y)); + } + } + this.stretchToFooter(); + if (!start) { + this.scrollView.startAutoScroll(); + } + } + protected onTouchBegin() { + this.stretchLock = {}; + } + protected getUsedScaleValue(value: number) { + return this.affectedByScale ? Math.abs(value) : 1; + } + protected getScaleWidth(trans: cc.Node | null): number { + if (!trans) return 0; + const size = (trans as any)["__runtime_size"]; + const width = size ? size.width : trans.width; + return width * this.getUsedScaleValue(trans.scaleX); + } + protected getScaleHeight(trans: cc.Node | null): number { + if (!trans) return 0; + const size = (trans as any)["__runtime_size"]; + const height = size ? size.height : trans.height; + return height * this.getUsedScaleValue(trans.scaleY); + } + protected onPositionChanged() { + if (this.isRestart) return; + if (this.vertical) { + if (this.scrollView.prevLocation.y < this.scrollView.location.y) { + this.scrollDirection = ScrollDirection.FOOTER; + } else if (this.scrollView.prevLocation.y > this.scrollView.location.y) { + this.scrollDirection = ScrollDirection.HEADER; + } else { + this.scrollDirection = ScrollDirection.NONE; + } + } else { + if (this.scrollView.prevLocation.x > this.scrollView.location.x) { + this.scrollDirection = ScrollDirection.FOOTER; + } else if (this.scrollView.prevLocation.x < this.scrollView.location.x) { + this.scrollDirection = ScrollDirection.HEADER; + } else { + this.scrollDirection = ScrollDirection.NONE; + } + } + + if (this.vertical) { + for (let i = 0; i < this.node.children.length; i++) { + let isOfBoundary = Math.abs(this.prevPos.y - this.node.position.y) > EPSILON; + if (!isOfBoundary) continue; + if (this.prevPos.y < this.node.position.y) { + this.pushToFooter(); + } else if (this.prevPos.y > this.node.position.y) { + this.pushToHeader(); + } + } + } else { + for (let i = 0; i < this.node.children.length; i++) { + let isOfBoundary = Math.abs(this.prevPos.x - this.node.position.x) > EPSILON; + if (!isOfBoundary) continue; + if (this.prevPos.x > this.node.position.x) { + this.pushToFooter(); + } else if (this.prevPos.x < this.node.position.x) { + this.pushToHeader(); + } + } + } + + this.prevPos = this.node.position.clone(); + } + /** 向尾部填充 force如果为true 则强制填充 */ + protected pushToFooter(force: boolean = false) { + if (this.vertical) { + var headerHeight = this.header && this.header.height || 0; + if (this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM) { + if (force || this.headerBoundary - this.paddingTop > this.viewHeaderBoundary + headerHeight) { + this.pushToFooterHandler(); + } + } else { + if (force || this.footerBoundary - this.paddingTop > this.viewHeaderBoundary + headerHeight) { + this.pushToHeaderHandler(); + } + } + } else { + var headerWidth = this.header && this.header.width || 0; + if (this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + if (force || this.headerBoundary + this.paddingLeft < this.viewHeaderBoundary - headerWidth) { + this.pushToFooterHandler(); + } + } else { + if (force || this.footerBoundary + this.paddingLeft < this.viewHeaderBoundary - headerWidth) { + this.pushToHeaderHandler(); + } + } + } + } + /** 向头部填充 force如果为true 则强制填充 */ + protected pushToHeader(force: boolean = false) { + if (this.vertical) { + var footerHeight = this.footer && this.footer.height || 0; + if (this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM) { + if (force || this.footerBoundary + this.paddingBottom < this.viewFooterBoundary - footerHeight) { + this.pushToHeaderHandler(); + } + } else { + if (force || this.headerBoundary + this.paddingBottom < this.viewFooterBoundary - footerHeight) { + this.pushToFooterHandler(); + } + } + } else { + var footerWidth = this.footer && this.footer.width || 0; + if (this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT) { + if (force || this.footerBoundary - this.paddingRight > this.viewFooterBoundary + footerWidth) { + this.pushToHeaderHandler(); + } + } else { + if (force || this.headerBoundary - this.paddingRight > this.viewFooterBoundary + footerWidth) { + this.pushToFooterHandler(); + } + } + } + } + protected pushToFooterHandler() { + var node: any = this.header; + let loop; + if (this.vertical) { + loop = this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM ? this.footerLoop : this.headerLoop; + } else { + loop = this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT ? this.footerLoop : this.headerLoop; + } + if (loop) { + if (this.footerIndex >= this.itemTotal - 1) { + node["__index"] = 0; + } else { + node["__index"] = this.footerIndex + 1; + } + } else { + if (!this.footer || this.footerIndex >= this.itemTotal - 1) return; + node["__index"] = this.footerIndex + 1; + } + if (node["__index"] >= 0 && node["__index"] < this.currentCreateItemTotal) { + this.notifyRefreshItem(node); + } + this.setItemPosition(this.header!, this.footer!); + this.header.setSiblingIndex(this.node.children.length); + } + protected pushToHeaderHandler() { + var node: any = this.footer; + let loop; + if (this.vertical) { + loop = this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM ? this.headerLoop : this.footerLoop; + } else { + loop = this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT ? this.headerLoop : this.footerLoop; + } + // 对其头部 + if (!loop && this.headerIndex == 0) { + // 判断是否是起始位置 + var accommodate; + if (this.vertical) { + accommodate = this.horizontalAxisDirection == HorizontalAxisDirection.LEFT_TO_RIGHT ? this.isAccommodateByLeft(this.header!) : this.isAccommodateByRight(this.header!); + } else { + accommodate = this.verticalAxisDirection == VerticalAxisDirection.TOP_TO_BOTTOM ? this.isAccommodateByTop(this.header!) : this.isAccommodateByBottom(this.header!); + } + if (accommodate) { + this.resetChilds(true); + } + } + if (loop) { + if (this.headerIndex == 0) { + node["__index"] = this.itemTotal - 1; + } else { + node["__index"] = this.headerIndex - 1; + } + } else { + if (!this.header || this.headerIndex == 0) return; + node["__index"] = this.headerIndex - 1; + } + if (node["__index"] >= 0 && node["__index"] < this.currentCreateItemTotal) { + this.notifyRefreshItem(node); + } + this.setItemPosition(this.footer, this.header, true); + this.footer.setSiblingIndex(0); + } + /** 通知给定的node刷新数据 */ + protected notifyRefreshItem(target: Node) { + cc.Component.EventHandler.emitEvents(this.refreshItemEvents, target, (target as any)['__index']); + } + + // + public OnTouchEvent(): void { + this.scrollView.node.on(cc.Node.EventType.TOUCH_START, this.scrollView._onTouchBegan, this.scrollView, true); + this.scrollView.node.on(cc.Node.EventType.TOUCH_MOVE, this.scrollView._onTouchMoved, this.scrollView, true); + this.scrollView.node.on(cc.Node.EventType.TOUCH_END, this.scrollView._onTouchEnded, this.scrollView, true); + this.scrollView.node.on(cc.Node.EventType.TOUCH_CANCEL, this.scrollView._onTouchCancelled, this.scrollView, true); + } + + public OffTouchEvent(): void { + this.scrollView.node.off(cc.Node.EventType.TOUCH_START, this.scrollView._onTouchBegan, this.scrollView, true); + this.scrollView.node.off(cc.Node.EventType.TOUCH_MOVE, this.scrollView._onTouchMoved, this.scrollView, true); + this.scrollView.node.off(cc.Node.EventType.TOUCH_END, this.scrollView._onTouchEnded, this.scrollView, true); + this.scrollView.node.off(cc.Node.EventType.TOUCH_CANCEL, this.scrollView._onTouchCancelled, this.scrollView, true); + } +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/ScrollView/UISuperLayout.ts.meta b/assets/Script/Engine/Utils/ScrollView/UISuperLayout.ts.meta new file mode 100644 index 0000000..43ce07c --- /dev/null +++ b/assets/Script/Engine/Utils/ScrollView/UISuperLayout.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "67f1a0e4-877e-4ad0-bc1b-e1175c620ca1", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Engine/Utils/ScrollView/UISuperScrollView.ts b/assets/Script/Engine/Utils/ScrollView/UISuperScrollView.ts new file mode 100644 index 0000000..a570aa9 --- /dev/null +++ b/assets/Script/Engine/Utils/ScrollView/UISuperScrollView.ts @@ -0,0 +1,547 @@ +import UISuperLayout from "./UISuperLayout"; + + +const { ccclass, property } = cc._decorator; + +const quintEaseOut: (time: number) => number = (time: number) => { + time -= 1; + return (time * time * time * time * time + 1); +}; +const EPSILON = 1e-4; +const OUT_OF_BOUNDARY_BREAKING_FACTOR = 0.05; +var _tempVec2: cc.Vec2 = new cc.Vec2(); +export enum ScrollViewDirection { + HORIZONTAL, + VERTICAL, + NONE, +} + +@ccclass +export default class UISuperScrollview extends cc.ScrollView { + private direction: ScrollViewDirection = ScrollViewDirection.NONE; + private _layout: UISuperLayout; + @property({ + tooltip: "注意!向上传递事件只会发送当前滑动相反方向,如果开启horizontal则会发送vertical事件。如果开启vertical则会发送horizontal事件。同时开启horizontal和vertical 不会发送任何事件" + }) isTransmitEvent: boolean = false; + @property pullRefresh: boolean = false; + @property({ + displayName: "顶部偏移量", + tooltip: "下拉时超过此偏移会发送下拉事件", + visible: function () { return (this as any).pullRefresh; } + }) headerOutOffset: number = 200; + @property({ + displayName: "满足触发Header的倍数", + visible: function () { return (this as any).pullRefresh; } + }) headerMultiple: number = 2; + @property({ + displayName: "底部偏移量", + tooltip: "上拉时超过此偏移会发送上拉事件", + visible: function () { return (this as any).pullRefresh; } + }) footerOutOffset: number = 200; + @property({ + displayName: "满足触发Footer的倍数", + visible: function () { return (this as any).pullRefresh; } + }) footerMultiple: number = 2; + @property({ + type: cc.Component.EventHandler, + visible: function () { return (this as any).pullRefresh; } + }) headerEvents: cc.Component.EventHandler[] = []; + @property({ + type: cc.Component.EventHandler, + visible: function () { return (this as any).pullRefresh; } + }) footerEvents: cc.Component.EventHandler[] = []; + prevLocation: cc.Vec2 = new cc.Vec2(); + location: cc.Vec2 = new cc.Vec2(); + set autoScrolling(value: boolean) { (this as any)._autoScrolling = value; } + private _touchBeganPosition = new cc.Vec2(); + private _touchEndPosition = new cc.Vec2(); + private isMoveHeader: boolean = false; + private isMoveFooter: boolean = false; + private isLockHeader: boolean = false; + private isLockFooter: boolean = false; + private headerProgress: number = 0; + private footerProgress: number = 0; + private isCustomScroll: boolean = false; + canTouchMove: boolean = true; + get view(): cc.Node { return this["_view"]; } + onLoad() { + if (this.layout && this.layout.autoCenter) { + this.brake = 0.7; + } + } + public onEnable() { + super.onEnable(); + this.node.on("scroll-ended-with-threshold", this.dispatchPageTurningEvent, this); + } + public onDisable() { + super.onDisable(); + this.node.off("scroll-ended-with-threshold", this.dispatchPageTurningEvent, this); + } + get layout() { + if (!this._layout) { + this._layout = this.content && this.content.getComponent(UISuperLayout); + } + return this._layout; + } + private isCallSoonFinish: boolean = false; + get _curPageIdx() { + return this.layout["_currPageIndex"]; + } + getPages() { + return new Array(this.layout.itemTotal); + } + protected _getContentTopBoundary() { + if (!this.content) { + return -1; + } + let offset = this.layout.isOfTopBoundary == 0 ? this["_topBoundary"] : this.layout.isOfTopBoundary; + return offset; + } + protected _getContentBottomBoundary() { + if (!this.content) { + return -1; + } + let offset = this.layout.isOfButtomBoundary == 0 ? this["_bottomBoundary"] : this.layout.isOfButtomBoundary; + return offset; + } + protected _getContentLeftBoundary() { + if (!this.content) { + return -1; + } + let offset = this.layout.isOfLeftBoundary == 0 ? this["_leftBoundary"] : this.layout.isOfLeftBoundary; + return offset; + } + protected _getContentRightBoundary() { + if (!this.content) { + return -1; + } + let offset = this.layout.isOfRightBoundary == 0 ? this["_rightBoundary"] : this.layout.isOfRightBoundary; + return offset; + } + + public _onTouchBegan(event: cc.Event.EventTouch, captureListeners?: Node[]): void { + this.isCallSoonFinish = false; + this.isCustomScroll = false; + this.layout["onTouchBegin"](); + if (!this.canTouchMove) { + return; + } + this.direction = ScrollViewDirection.NONE; + if (this.layout.isPageView) { + _tempVec2 = event.getLocation(); + // cc.Vec2.set(this._touchBeganPosition, _tempVec2.x, _tempVec2.y) + this._touchBeganPosition = cc.v2(_tempVec2.x, _tempVec2.y); + } + super["_onTouchBegan"](event, captureListeners); + if (this.isTransmitEvent) { + this.transmitEvent(event, cc.Node.EventType.TOUCH_START); + } + } + public _onTouchMoved(event: cc.Event.EventTouch, captureListeners: any) { + this.isCallSoonFinish = false; + this.isCustomScroll = false; + + if (!this.canTouchMove) return; + if (this.isTransmitEvent) { + if (this.direction == ScrollViewDirection.NONE) { + var start = event.getStartLocation(); + var curre = event.getLocation(); + var xOffset = Math.abs(start.x - curre.x); + var yOffset = Math.abs(start.y - curre.y); + if (xOffset > yOffset) { + // 本ScrollView滑动方向过程中达到一定偏移量是也可以向上发送事件 + // if (this.vertical) { + // if (xOffset - yOffset > 50) { + // this.direction = UIScrollViewDirection.HORIZONTAL + // } + // } + this.direction = ScrollViewDirection.HORIZONTAL; + + } else if (yOffset > xOffset) { + // 本ScrollView滑动方向过程中达到一定偏移量是也可以向上发送事件 + // if (this.horizontal) { + // if (yOffset - xOffset > 50) { + // this.direction = UIScrollViewDirection.VERTICAL + // } + // } + this.direction = ScrollViewDirection.VERTICAL; + } + } + var canTransmit = (this.vertical && this.direction === ScrollViewDirection.HORIZONTAL) || this.horizontal && this.direction == ScrollViewDirection.VERTICAL; + if (canTransmit) { + this.transmitEvent(event, cc.Node.EventType.TOUCH_MOVE); + event.stopPropagation(); + return; + } + } + this.prevLocation = event.touch.getPreviousLocation(); + this.location = event.touch.getLocation(); + super["_onTouchMoved"](event, captureListeners); + if (this.pullRefresh) { + let outOfBoundary = this["_getHowMuchOutOfBoundary"](); + let offset = this.vertical ? outOfBoundary.y : -outOfBoundary.x; + if (offset > 0 && !this.isLockHeader && !this.isLockFooter) { + this.headerProgress = offset < EPSILON ? 0 : offset / this.headerOutOffset; + this.isMoveHeader = this.headerProgress >= this.headerMultiple; + cc.Component.EventHandler.emitEvents(this.headerEvents, this, { action: false, progress: this.headerProgress, stage: this.isMoveHeader ? "wait" : "touch" }); + cc.Component.EventHandler.emitEvents(this.footerEvents, this, { action: false, progress: 0, stage: "release" }); + } else if (offset < 0 && !this.isLockHeader && !this.isLockFooter) { + this.footerProgress = -offset < EPSILON ? 0 : -offset / this.footerOutOffset; + this.isMoveFooter = this.footerProgress >= this.footerMultiple; + cc.Component.EventHandler.emitEvents(this.footerEvents, this, { action: false, progress: this.footerProgress, stage: this.isMoveFooter ? "wait" : "touch" }); + cc.Component.EventHandler.emitEvents(this.headerEvents, this, { action: false, progress: 0, stage: "release" }); + } else if (offset == 0 && !this.isLockHeader && !this.isLockFooter) { + this.clearProgress(); + } + } + } + + public _onTouchEnded(event: cc.Event.EventTouch, captureListeners: any) { + this.isCallSoonFinish = false; + this.isCustomScroll = false; + if (!this.canTouchMove) return; + if (this.layout.isPageView) { + _tempVec2 = event.getLocation(); + // cc.Vec2.set(this._touchEndPosition, _tempVec2.x, _tempVec2.y) + this._touchEndPosition = cc.v2(_tempVec2.x, _tempVec2.y); + } + super["_onTouchEnded"](event, captureListeners); + if (this.isTransmitEvent) { + this.transmitEvent(event, cc.Node.EventType.TOUCH_END); + } + } + public _onTouchCancelled(event: cc.Event.EventTouch, captureListeners: any) { + this.isCallSoonFinish = false; + this.isCustomScroll = false; + if (!this.canTouchMove) return; + if (this.layout.isPageView) { + _tempVec2 = event.getLocation(); + // cc.Vec2.set(this._touchEndPosition, _tempVec2.x, _tempVec2.y) + this._touchEndPosition = cc.v2(_tempVec2.x, _tempVec2.y); + } + if (this.isTransmitEvent) { + this.transmitEvent(event, cc.Node.EventType.TOUCH_CANCEL); + } + super["_onTouchCancelled"](event, captureListeners); + } + scrollToAny(moveDelta: cc.Vec2, timeInSecond?: number, attenuated: boolean = true) { + this.isCustomScroll = true; + if (timeInSecond) { + this._startAutoScroll(moveDelta, timeInSecond, attenuated, true); + } else { + this["_moveContent"](moveDelta); + } + } + release() { + this.isMoveHeader = false; + this.isMoveFooter = false; + if (this.isLockHeader || this.isLockFooter) { + this.vertical && this.isLockHeader && (this["_topBoundary"] += this.headerOutOffset); + this.vertical && this.isLockFooter && (this["_bottomBoundary"] -= this.footerOutOffset); + this.horizontal && this.isLockHeader && (this["_leftBoundary"] -= this.headerOutOffset); + this.horizontal && this.isLockFooter && (this["_rightBoundary"] += this.footerOutOffset); + this.clearProgress(); + this.layout["onPositionChanged"](); + this.isLockHeader = false; + this.isLockFooter = false; + this.startAutoScroll(); + } + } + startAutoScroll() { + this["_autoScrolling"] = true; + this["_outOfBoundaryAmountDirty"] = true; + } + protected _startAutoScroll(deltaMove: any, timeInSecond: any, attenuated: any, flag: boolean = false) { + if (this.pullRefresh) { // 如果没有刷新/加载的监听者 则不计算 + if (this.isMoveHeader && !this.isLockHeader) { + if (this.vertical) { + this["_topBoundary"] -= this.headerOutOffset; + deltaMove.y -= this.headerOutOffset; + } + if (this.horizontal) { + this["_leftBoundary"] += this.headerOutOffset; + deltaMove.x += this.headerOutOffset; + } + this.isLockHeader = true; + cc.Component.EventHandler.emitEvents(this.headerEvents, this, { action: true, progress: this.headerProgress, stage: 'lock' }); + } else if (this.isMoveFooter && !this.isLockFooter) { + if (this.vertical) { + this["_bottomBoundary"] += this.footerOutOffset; + deltaMove.y += this.footerOutOffset; + } + if (this.horizontal) { + this["_rightBoundary"] -= this.footerOutOffset; + deltaMove.x -= this.footerOutOffset; + } + this.isLockFooter = true; + cc.Component.EventHandler.emitEvents(this.footerEvents, this, { action: true, progress: this.footerProgress, stage: 'lock' }); + } + } + + super["_startAutoScroll"](deltaMove, timeInSecond, attenuated); + if (!flag && this.layout.autoCenter) { + const touchMoveVelocity = this["_calculateTouchMoveVelocity"](); + if (!this.isQuicklyScrollable(touchMoveVelocity)) { + this.soonFinish(); + } + } + } + protected _updateScrollBar(outOfBoundary: any) { + super["_updateScrollBar"](cc.v2(outOfBoundary.x, outOfBoundary.y)); + if (this["_autoScrollBraking"]) return; // 自动回弹时不计算 (非手动) + if (!this["_autoScrolling"]) return; // 非自动滚动时不计算 + if (!this.pullRefresh) return; + let offset = this.vertical ? outOfBoundary.y : -outOfBoundary.x; + if (offset > 0) { // 下滑 + let progress = offset < EPSILON ? 0 : offset / this.headerOutOffset; //根据参数 headerOutOffset 计算当前下滑的办百分比 + if (this.isLockHeader) { + this.headerProgress = this.headerProgress == 1 ? this.headerProgress : Math.max(progress, 1); + cc.Component.EventHandler.emitEvents(this.headerEvents, this, { action: false, progress: this.headerProgress, stage: "lock" }); + } else { + this.headerProgress = progress < this.headerProgress ? progress : this.headerProgress; + cc.Component.EventHandler.emitEvents(this.headerEvents, this, { action: false, progress: this.headerProgress, stage: "release" }); + } + } else if (offset < 0) { + let progress = -offset < EPSILON ? 0 : -offset / this.footerOutOffset; //根据参数 footerOutOffset 计算当前下滑的办百分比 + if (this.isLockFooter) { + this.footerProgress = this.footerProgress == 1 ? this.footerProgress : Math.max(progress, 1); + cc.Component.EventHandler.emitEvents(this.footerEvents, this, { action: false, progress: this.footerProgress, stage: "lock" }); + } else { + this.footerProgress = progress < this.footerProgress ? progress : this.footerProgress; + cc.Component.EventHandler.emitEvents(this.footerEvents, this, { action: false, progress: this.footerProgress, stage: "release" }); + } + } else if (offset == 0) { + // 正常滑动时 如果没有锁定头和尾时 释放所有进度 + if (!this.isLockHeader && !this.isLockFooter) { + this.clearProgress(); + } + } + } + private clearProgress() { + cc.Component.EventHandler.emitEvents(this.headerEvents, this, { action: false, progress: 0, stage: "release" }); + cc.Component.EventHandler.emitEvents(this.footerEvents, this, { action: false, progress: 0, stage: "release" }); + } + private dispatchPageTurningEvent() { + if (this.layout["_lastPageIndex"] === this.layout["_currPageIndex"]) return; + this.layout["_lastPageIndex"] = this.layout["_currPageIndex"]; + cc.Component.EventHandler.emitEvents(this.layout.pageEvents, this, "page-turning"); + this.node.emit("page-turning", this); + } + + protected _handleReleaseLogic(touch: any) { + if (this.layout.isPageView) { + this._autoScrollToPage(); + if (this["_scrolling"]) { + this["_scrolling"] = false; + if (!this["_autoScrolling"]) { + this["_dispatchEvent"](cc.ScrollView.EventType.SCROLL_ENDED); + } + } + } else { + super["_handleReleaseLogic"](touch); + } + + } + protected _autoScrollToPage() { + const bounceBackStarted = this["_startBounceBackIfNeeded"](); + if (bounceBackStarted) { + const bounceBackAmount = this["_getHowMuchOutOfBoundary"](); + this["_clampDelta"](bounceBackAmount); + if (bounceBackAmount.x > 0 || bounceBackAmount.y < 0) { + if (this.layout.horizontal) { + if (this.layout.horizontalAxisDirection == UISuperLayout.HorizontalAxisDirection.LEFT_TO_RIGHT) { + this.layout["_currPageIndex"] = this.layout.itemTotal === 0 ? 0 : this.layout.itemTotal - 1; + } else { + this.layout["_currPageIndex"] = 0; + } + } else { + if (this.layout.verticalAxisDirection == UISuperLayout.VerticalAxisDirection.TOP_TO_BOTTOM) { + this.layout["_currPageIndex"] = this.layout.itemTotal === 0 ? 0 : this.layout.itemTotal - 1; + } else { + this.layout["_currPageIndex"] = 0; + } + } + } + if (bounceBackAmount.x < 0 || bounceBackAmount.y > 0) { + if (this.layout.horizontal) { + if (this.layout.horizontalAxisDirection == UISuperLayout.HorizontalAxisDirection.LEFT_TO_RIGHT) { + this.layout["_currPageIndex"] = 0; + } else { + this.layout["_currPageIndex"] = this.layout.itemTotal === 0 ? 0 : this.layout.itemTotal - 1; + } + } else { + if (this.layout.verticalAxisDirection == UISuperLayout.VerticalAxisDirection.TOP_TO_BOTTOM) { + this.layout["_currPageIndex"] = 0; + } else { + this.layout["_currPageIndex"] = this.layout.itemTotal === 0 ? 0 : this.layout.itemTotal - 1; + } + } + } + if (this.layout.indicator) { + this.layout.indicator["_changedState"](); + } + } else { + const moveOffset = new cc.Vec2(); + // cc.Vec2.subtract(moveOffset, this._touchBeganPosition, this._touchEndPosition) + moveOffset.x = this._touchBeganPosition.x - this._touchEndPosition.x; + moveOffset.y = this._touchBeganPosition.y - this._touchEndPosition.y; + + + const index = this.layout["_currPageIndex"]; + var nextIndex = index + this.getDragDirection(moveOffset); + var timeInSecond = this.layout.pageTurningSpeed * Math.abs(index - nextIndex); + if (this.layout.footerLoop) { + if (nextIndex >= this.layout.itemTotal) { + nextIndex = 0; + } + } + if (this.layout.headerLoop) { + if (nextIndex < 0) { + nextIndex = this.layout.itemTotal - 1; + } + } + const count = this.layout.itemTotal; + if (nextIndex < count) { + if (this.isScrollable(moveOffset, index, nextIndex)) { + this.scrollToPage(nextIndex, timeInSecond); + return; + } else { + const touchMoveVelocity = this["_calculateTouchMoveVelocity"](); + if (this.isQuicklyScrollable(touchMoveVelocity)) { + this.scrollToPage(nextIndex, timeInSecond); + return; + } + } + } + this.scrollToPage(index, timeInSecond); + } + } + savePageIndex(idx: number) { + if (idx < 0 || idx >= this.layout.itemTotal) { + return false; + } + this.layout["_currPageIndex"] = idx; + if (this.layout.indicator) { + this.layout.indicator["_changedState"](); + } + return true; + } + protected scrollToPage(idx: number, timeInSecond = 0.3) { + if (idx < 0 || idx >= this.layout.itemTotal) { + return; + } + if (this.savePageIndex(idx)) { + this.layout.scrollToIndex(idx, timeInSecond); + } + } + // 快速滑动 + protected isQuicklyScrollable(touchMoveVelocity: cc.Vec3) { + if (this.horizontal) { + if (Math.abs(touchMoveVelocity.x) > this.layout.autoPageTurningThreshold) { + return true; + } + } else if (this.vertical) { + if (Math.abs(touchMoveVelocity.y) > this.layout.autoPageTurningThreshold) { + return true; + } + } + return false; + } + protected getDragDirection(moveOffset: cc.Vec2) { + if (this.horizontal) { + if (moveOffset.x === 0) { + return 0; + } + if (this.layout.horizontalAxisDirection == UISuperLayout.HorizontalAxisDirection.LEFT_TO_RIGHT) { + return (moveOffset.x > 0 ? this.layout.groupItemTotal : -this.layout.groupItemTotal); + } else { + return (moveOffset.x < 0 ? this.layout.groupItemTotal : -this.layout.groupItemTotal); + } + } else { + // 由于滚动 Y 轴的原点在在右上角所以应该是小于 0 + if (moveOffset.y === 0) { + return 0; + } + if (this.layout.verticalAxisDirection == UISuperLayout.VerticalAxisDirection.TOP_TO_BOTTOM) { + return (moveOffset.y < 0 ? this.layout.groupItemTotal : -this.layout.groupItemTotal); + } else { + return (moveOffset.y > 0 ? this.layout.groupItemTotal : -this.layout.groupItemTotal); + } + } + } + // 是否超过自动滚动临界值 + protected isScrollable(offset: cc.Vec2, index: number, nextIndex: number) { + const viewTrans = this.view; + if (!viewTrans) { + return false; + } + if (this.horizontal) { + return Math.abs(offset.x) >= viewTrans.width * this.layout.scrollThreshold; + } else if (this.vertical) { + return Math.abs(offset.y) >= viewTrans.height * this.layout.scrollThreshold; + } + return false; + } + protected transmitEvent(event: any, eventType: string) { + var e = new cc.Event.EventTouch(event.getTouches(), event.bubbles); + e.type = eventType; + e.touch = event.touch; + let target: any = event.target; + target.parent.dispatchEvent(e); + } + private soonFinish() { + this.isCallSoonFinish = true; + this.layout["soonFinish"](); + } + protected _processAutoScrolling(dt: number) { + let isAutoScrollBrake = this["_isNecessaryAutoScrollBrake"](); + let brakingFactor = isAutoScrollBrake ? OUT_OF_BOUNDARY_BREAKING_FACTOR : 1; + this["_autoScrollAccumulatedTime"] += dt * (1 / brakingFactor); + + let percentage = Math.min(1, this["_autoScrollAccumulatedTime"] / this["_autoScrollTotalTime"]); + if (this["_autoScrollAttenuate"]) { + percentage = quintEaseOut(percentage); + } + + let newPosition = this["_autoScrollStartPosition"].add(this["_autoScrollTargetDelta"].mul(percentage)); + let reachedEnd = Math.abs(percentage - 1) <= EPSILON; + + let fireEvent = Math.abs(percentage - 1) <= this["getScrollEndedEventTiming"](); + if (fireEvent && !this["_isScrollEndedWithThresholdEventFired"]) { + this["_dispatchEvent"]('scroll-ended-with-threshold'); + this["_isScrollEndedWithThresholdEventFired"] = true; + } + + if (this.elastic) { + let brakeOffsetPosition = newPosition.sub(this["_autoScrollBrakingStartPosition"]); + if (isAutoScrollBrake) { + brakeOffsetPosition = brakeOffsetPosition.mul(brakingFactor); + } + newPosition = this["_autoScrollBrakingStartPosition"].add(brakeOffsetPosition); + } else { + let moveDelta = newPosition.sub(this.getContentPosition()); + let outOfBoundary = this["_getHowMuchOutOfBoundary"](moveDelta); + if (!outOfBoundary.fuzzyEquals(cc.v2(0, 0), EPSILON)) { + newPosition = newPosition.add(outOfBoundary); + reachedEnd = true; + } + } + + if (reachedEnd) { + this["_autoScrolling"] = false; + } + if (this.layout.autoCenter && !this.isCallSoonFinish && !this.isCustomScroll) { + if (this["_autoScrollTotalTime"] < 2 || percentage >= 0.8) { + this.soonFinish(); + } + } + let deltaMove = newPosition.sub(this.getContentPosition()); + this["_moveContent"](this["_clampDelta"](deltaMove), reachedEnd); + this["_dispatchEvent"]('scrolling'); + if (!this["_autoScrolling"]) { + this["_isBouncing"] = false; + this["_scrolling"] = false; + this["_dispatchEvent"]('scroll-ended'); + } + } +} diff --git a/assets/Script/Engine/Utils/ScrollView/UISuperScrollView.ts.meta b/assets/Script/Engine/Utils/ScrollView/UISuperScrollView.ts.meta new file mode 100644 index 0000000..cecc5e1 --- /dev/null +++ b/assets/Script/Engine/Utils/ScrollView/UISuperScrollView.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "dfb04646-c016-4594-b7b3-8d83fa7a925a", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/Manager.ts b/assets/Script/Manager.ts index 837bcfe..7a6093e 100644 --- a/assets/Script/Manager.ts +++ b/assets/Script/Manager.ts @@ -66,6 +66,9 @@ export default class Manager extends cc.Component { cc.view.setResizeCallback(this._resize.bind(this)); this._resize(); + + const FCMToken: string = NativeClass.Instance.GetFCMToken(); + console.log(`FCMToken ${FCMToken}`); } /** 跨版本溝通 diff --git a/assets/Script/NativeClass.ts b/assets/Script/NativeClass.ts index 3d2b8c7..fc7af63 100644 --- a/assets/Script/NativeClass.ts +++ b/assets/Script/NativeClass.ts @@ -132,6 +132,30 @@ export default class NativeClass extends cc.Component { //#region Custom Function + /** GetFCMToken */ + public GetFCMToken(): string { + let resp: any = ""; + switch (cc.sys.os) { + case cc.sys.OS_WINDOWS: { + // 網頁端 + break; + } + case cc.sys.OS_ANDROID: { + break; + } + + case cc.sys.OS_IOS: { + resp = jsb.reflection.callStaticMethod("AppController", "GetFCMToken"); + break; + } + + default: { + break; + } + } + return resp; + } + /** * TTS_Play * @param {string} msg msg @@ -139,7 +163,7 @@ export default class NativeClass extends cc.Component { public TTS_Play(msg: string): void { switch (cc.sys.os) { case cc.sys.OS_WINDOWS: { - // // 網頁端 + // 網頁端 break; } case cc.sys.OS_ANDROID: { diff --git a/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/GoogleService-Info.plist b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/GoogleService-Info.plist new file mode 100755 index 0000000..f94c558 --- /dev/null +++ b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/GoogleService-Info.plist @@ -0,0 +1,34 @@ + + + + + CLIENT_ID + 407474781989-oj00t4rdam3scpnaoohm2dii1c7tti5c.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.407474781989-oj00t4rdam3scpnaoohm2dii1c7tti5c + API_KEY + AIzaSyBwhNhVYqErLAwU5SMmkDiN6NOVrhpykOQ + GCM_SENDER_ID + 407474781989 + PLIST_VERSION + 1 + BUNDLE_ID + org.jianmiau.jmka + PROJECT_ID + jmka-baa31 + STORAGE_BUCKET + jmka-baa31.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:407474781989:ios:0bcba35c2e54c67a25a4cc + + \ No newline at end of file diff --git a/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/JMKA-mobile.entitlements b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/JMKA-mobile.entitlements new file mode 100644 index 0000000..903def2 --- /dev/null +++ b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/JMKA-mobile.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/JMKA.xcodeproj/project.pbxproj b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/JMKA.xcodeproj/project.pbxproj index dc84262..551c875 100755 --- a/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/JMKA.xcodeproj/project.pbxproj +++ b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/JMKA.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -81,6 +81,11 @@ FE7A3B0128641588008DDC0D /* Sound_S.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE7A3B0028641588008DDC0D /* Sound_S.swift */; }; FEBFDF542864382300EF0BF8 /* Kirby01.wav in Resources */ = {isa = PBXBuildFile; fileRef = FEBFDF522864382300EF0BF8 /* Kirby01.wav */; }; FEBFDF552864382300EF0BF8 /* Kirby02.wav in Resources */ = {isa = PBXBuildFile; fileRef = FEBFDF532864382300EF0BF8 /* Kirby02.wav */; }; + FEC511AD28B894CF00ED3051 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = FEC511AC28B894CE00ED3051 /* GoogleService-Info.plist */; }; + FEC511B028B8972800ED3051 /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = FEC511AF28B8972800ED3051 /* FirebaseAnalytics */; }; + FEC511B228B8972800ED3051 /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = FEC511B128B8972800ED3051 /* FirebaseMessaging */; }; + FEC511B528B8977400ED3051 /* Firebase_S.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC511B428B8977400ED3051 /* Firebase_S.swift */; }; + FEC511BA28B8A75000ED3051 /* Firebase_OC.m in Sources */ = {isa = PBXBuildFile; fileRef = FEC511B928B8A75000ED3051 /* Firebase_OC.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -196,6 +201,11 @@ FE7A3B0028641588008DDC0D /* Sound_S.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Sound_S.swift; path = ios/Sound_S.swift; sourceTree = ""; }; FEBFDF522864382300EF0BF8 /* Kirby01.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = Kirby01.wav; sourceTree = ""; }; FEBFDF532864382300EF0BF8 /* Kirby02.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = Kirby02.wav; sourceTree = ""; }; + FEC511AC28B894CE00ED3051 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../../../../../build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/GoogleService-Info.plist"; sourceTree = ""; }; + FEC511B328B8974500ED3051 /* JMKA-mobile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "JMKA-mobile.entitlements"; sourceTree = ""; }; + FEC511B428B8977400ED3051 /* Firebase_S.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Firebase_S.swift; path = ios/Firebase_S.swift; sourceTree = ""; }; + FEC511B828B8A75000ED3051 /* Firebase_OC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Firebase_OC.h; path = ios/Firebase_OC.h; sourceTree = ""; }; + FEC511B928B8A75000ED3051 /* Firebase_OC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Firebase_OC.m; path = ios/Firebase_OC.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -246,7 +256,9 @@ BA3A85EC1A724AE900924D24 /* Security.framework in Frameworks */, 1A676839180E9C1E0076BC67 /* libcocos2d iOS.a in Frameworks */, D6B061241803AB9F0077942B /* CoreMotion.framework in Frameworks */, + FEC511B228B8972800ED3051 /* FirebaseMessaging in Frameworks */, 1A82F5FB169AC92500C4B13A /* libsqlite3.dylib in Frameworks */, + FEC511B028B8972800ED3051 /* FirebaseAnalytics in Frameworks */, D454520C156E22BD00887EB5 /* libz.dylib in Frameworks */, A92275421517C094001B78AA /* QuartzCore.framework in Frameworks */, A92275441517C094001B78AA /* OpenGLES.framework in Frameworks */, @@ -309,6 +321,7 @@ A92275321517C094001B78AA = { isa = PBXGroup; children = ( + FEC511B328B8974500ED3051 /* JMKA-mobile.entitlements */, FE7A3AFA286413DF008DDC0D /* Sound */, 288D4372225B43BE0075FBAB /* assets */, 1AC6FB34180E9ACB004C840B /* cocos2d_libs.xcodeproj */, @@ -385,6 +398,7 @@ 22CC756321BA527F00FBF2F7 /* service */, 1AFFCD841F7A5DCD00628F2C /* Images.xcassets */, 40CEC424212BDAB0004BCE66 /* LaunchScreen.storyboard */, + FEC511AC28B894CE00ED3051 /* GoogleService-Info.plist */, 1AFFCD831F7A5DCD00628F2C /* LaunchScreenBackground.png */, 509D4AAB17EBB2AB00697056 /* AppController.h */, 509D4AAC17EBB2AB00697056 /* AppController.mm */, @@ -398,6 +412,9 @@ FE7A3B0028641588008DDC0D /* Sound_S.swift */, FE7A3AFF28641588008DDC0D /* JMKA_mobile-Bridging-Header.h */, FE50BD3F28B740C300F0E114 /* TTS_S.swift */, + FEC511B428B8977400ED3051 /* Firebase_S.swift */, + FEC511B828B8A75000ED3051 /* Firebase_OC.h */, + FEC511B928B8A75000ED3051 /* Firebase_OC.m */, ); name = ios; sourceTree = ""; @@ -459,6 +476,10 @@ 1A67682F180E9C110076BC67 /* PBXTargetDependency */, ); name = "JMKA-mobile"; + packageProductDependencies = ( + FEC511AF28B8972800ED3051 /* FirebaseAnalytics */, + FEC511B128B8972800ED3051 /* FirebaseMessaging */, + ); productName = JMKA; productReference = A922753D1517C094001B78AA /* JMKA-mobile.app */; productType = "com.apple.product-type.application"; @@ -488,6 +509,9 @@ "zh-Hans", ); mainGroup = A92275321517C094001B78AA; + packageReferences = ( + FEC511AE28B8972800ED3051 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, + ); productRefGroup = A922753E1517C094001B78AA /* Products */; projectDirPath = ""; projectReferences = ( @@ -544,6 +568,7 @@ FEBFDF552864382300EF0BF8 /* Kirby02.wav in Resources */, 1AFFCD871F7A5DCF00628F2C /* LaunchScreenBackground.png in Resources */, 1AE159EC18C9DF3600FCA372 /* project.json in Resources */, + FEC511AD28B894CF00ED3051 /* GoogleService-Info.plist in Resources */, 1AD7E0A818C9DB93004817A6 /* main.js in Resources */, 40CEC429212BDB03004BCE66 /* Localizable.strings in Resources */, 1AFFCD851F7A5DCF00628F2C /* Images.xcassets in Resources */, @@ -570,6 +595,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + FEC511BA28B8A75000ED3051 /* Firebase_OC.m in Sources */, FE7A3B0128641588008DDC0D /* Sound_S.swift in Sources */, 509D4AC917EBB2AB00697056 /* RootViewController.mm in Sources */, D4545227156E28EF00887EB5 /* AppDelegate.cpp in Sources */, @@ -577,6 +603,7 @@ 22CC756521BA528000FBF2F7 /* SDKWrapper.m in Sources */, 509D4AC817EBB2AB00697056 /* main.m in Sources */, 4D3EB2581F8F4A6C007DA644 /* jsb_module_register.cpp in Sources */, + FEC511B528B8977400ED3051 /* Firebase_S.swift in Sources */, 509D4ABC17EBB2AB00697056 /* AppController.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -640,7 +667,13 @@ LIBRARY_SEARCH_PATHS = "$(SRCROOT)/../../cocos2d-x/external/mac/libs"; OTHER_LDFLAGS = ""; SDKROOT = macosx; - USER_HEADER_SEARCH_PATHS = "$(inherited) $(SRCROOT)/../../cocos2d-x/cocos/platform/mac $(SRCROOT)/../../cocos2d-x/external/glfw3/include/mac $(SRCROOT)/../../cocos2d-x/external/mac/include/ $(SRCROOT)/../../cocos2d-x/external/mac/include/spidermonkey"; + USER_HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../../cocos2d-x/cocos/platform/mac", + "$(SRCROOT)/../../cocos2d-x/external/glfw3/include/mac", + "$(SRCROOT)/../../cocos2d-x/external/mac/include/", + "$(SRCROOT)/../../cocos2d-x/external/mac/include/spidermonkey", + ); }; name = Debug; }; @@ -665,7 +698,13 @@ LIBRARY_SEARCH_PATHS = "$(SRCROOT)/../../cocos2d-x/external/mac/libs"; OTHER_LDFLAGS = ""; SDKROOT = macosx; - USER_HEADER_SEARCH_PATHS = "$(inherited) $(SRCROOT)/../../cocos2d-x/cocos/platform/mac $(SRCROOT)/../../cocos2d-x/external/glfw3/include/mac $(SRCROOT)/../../cocos2d-x/external/mac/include/ $(SRCROOT)/../../cocos2d-x/external/mac/include/spidermonkey"; + USER_HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../../cocos2d-x/cocos/platform/mac", + "$(SRCROOT)/../../cocos2d-x/external/glfw3/include/mac", + "$(SRCROOT)/../../cocos2d-x/external/mac/include/", + "$(SRCROOT)/../../cocos2d-x/external/mac/include/spidermonkey", + ); }; name = Release; }; @@ -691,7 +730,25 @@ MACOSX_DEPLOYMENT_TARGET = 10.9; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; - USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../cocos2d-x $(SRCROOT)/../../cocos2d-x/cocos $(SRCROOT)/../../cocos2d-x/cocos/base $(SRCROOT)/../../cocos2d-x/cocos/physics $(SRCROOT)/../../cocos2d-x/cocos/math/kazmath $(SRCROOT)/../../cocos2d-x/cocos/2d $(SRCROOT)/../../cocos2d-x/cocos/gui $(SRCROOT)/../../cocos2d-x/cocos/network $(SRCROOT)/../../cocos2d-x/cocos/audio/include $(SRCROOT)/../../cocos2d-x/cocos/editor-support $(SRCROOT)/../../cocos2d-x/extensions $(SRCROOT)/../../cocos2d-x/external $(SRCROOT)/../../cocos2d-x/external/sources $(SRCROOT)/../../cocos2d-x/external/chipmunk/include/chipmunk $(SRCROOT)/../../cocos2d-x/cocos/scripting/js-bindings/auto $(SRCROOT)/../../cocos2d-x/cocos/scripting/js-bindings/manual $(SRCROOT)/../../cocos2d-x/external/mac/include/v8"; + USER_HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/../../cocos2d-x", + "$(SRCROOT)/../../cocos2d-x/cocos", + "$(SRCROOT)/../../cocos2d-x/cocos/base", + "$(SRCROOT)/../../cocos2d-x/cocos/physics", + "$(SRCROOT)/../../cocos2d-x/cocos/math/kazmath", + "$(SRCROOT)/../../cocos2d-x/cocos/2d", + "$(SRCROOT)/../../cocos2d-x/cocos/gui", + "$(SRCROOT)/../../cocos2d-x/cocos/network", + "$(SRCROOT)/../../cocos2d-x/cocos/audio/include", + "$(SRCROOT)/../../cocos2d-x/cocos/editor-support", + "$(SRCROOT)/../../cocos2d-x/extensions", + "$(SRCROOT)/../../cocos2d-x/external", + "$(SRCROOT)/../../cocos2d-x/external/sources", + "$(SRCROOT)/../../cocos2d-x/external/chipmunk/include/chipmunk", + "$(SRCROOT)/../../cocos2d-x/cocos/scripting/js-bindings/auto", + "$(SRCROOT)/../../cocos2d-x/cocos/scripting/js-bindings/manual", + "$(SRCROOT)/../../cocos2d-x/external/mac/include/v8", + ); }; name = Debug; }; @@ -714,7 +771,25 @@ MACOSX_DEPLOYMENT_TARGET = 10.9; OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; PRODUCT_NAME = "$(TARGET_NAME)"; - USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../cocos2d-x $(SRCROOT)/../../cocos2d-x/cocos $(SRCROOT)/../../cocos2d-x/cocos/base $(SRCROOT)/../../cocos2d-x/cocos/physics $(SRCROOT)/../../cocos2d-x/cocos/math/kazmath $(SRCROOT)/../../cocos2d-x/cocos/2d $(SRCROOT)/../../cocos2d-x/cocos/gui $(SRCROOT)/../../cocos2d-x/cocos/network $(SRCROOT)/../../cocos2d-x/cocos/audio/include $(SRCROOT)/../../cocos2d-x/cocos/editor-support $(SRCROOT)/../../cocos2d-x/extensions $(SRCROOT)/../../cocos2d-x/external $(SRCROOT)/../../cocos2d-x/external/sources $(SRCROOT)/../../cocos2d-x/external/chipmunk/include/chipmunk $(SRCROOT)/../../cocos2d-x/cocos/scripting/js-bindings/auto $(SRCROOT)/../../cocos2d-x/cocos/scripting/js-bindings/manual $(SRCROOT)/../../cocos2d-x/external/mac/include/v8"; + USER_HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/../../cocos2d-x", + "$(SRCROOT)/../../cocos2d-x/cocos", + "$(SRCROOT)/../../cocos2d-x/cocos/base", + "$(SRCROOT)/../../cocos2d-x/cocos/physics", + "$(SRCROOT)/../../cocos2d-x/cocos/math/kazmath", + "$(SRCROOT)/../../cocos2d-x/cocos/2d", + "$(SRCROOT)/../../cocos2d-x/cocos/gui", + "$(SRCROOT)/../../cocos2d-x/cocos/network", + "$(SRCROOT)/../../cocos2d-x/cocos/audio/include", + "$(SRCROOT)/../../cocos2d-x/cocos/editor-support", + "$(SRCROOT)/../../cocos2d-x/extensions", + "$(SRCROOT)/../../cocos2d-x/external", + "$(SRCROOT)/../../cocos2d-x/external/sources", + "$(SRCROOT)/../../cocos2d-x/external/chipmunk/include/chipmunk", + "$(SRCROOT)/../../cocos2d-x/cocos/scripting/js-bindings/auto", + "$(SRCROOT)/../../cocos2d-x/cocos/scripting/js-bindings/manual", + "$(SRCROOT)/../../cocos2d-x/external/mac/include/v8", + ); VALIDATE_PRODUCT = YES; }; name = Release; @@ -726,6 +801,7 @@ ALWAYS_SEARCH_USER_PATHS = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = "JMKA-mobile.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; COMPRESS_PNG_FILES = NO; DEVELOPMENT_TEAM = 6L2252GUFG; @@ -742,9 +818,12 @@ HEADER_SEARCH_PATHS = ""; INFOPLIST_FILE = ios/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = "$(SRCROOT)/../../cocos2d-x/external/ios/libs"; - MARKETING_VERSION = 2.0; + MARKETING_VERSION = 2.1; OTHER_LDFLAGS = ( "-ObjC", "$(inherited)", @@ -755,7 +834,14 @@ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - USER_HEADER_SEARCH_PATHS = "$(inherited) $(SRCROOT)/../../cocos2d-x/cocos/platform/ios $(SRCROOT)/../../cocos2d-x/plugin/jsbindings/auto $(SRCROOT)/../../cocos2d-x/plugin/jsbindings/manual $(SRCROOT)/../../cocos2d-x/external/ios/include $(SRCROOT)/../../cocos2d-x/external/ios/include/spidermonkey"; + USER_HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../../cocos2d-x/cocos/platform/ios", + "$(SRCROOT)/../../cocos2d-x/plugin/jsbindings/auto", + "$(SRCROOT)/../../cocos2d-x/plugin/jsbindings/manual", + "$(SRCROOT)/../../cocos2d-x/external/ios/include", + "$(SRCROOT)/../../cocos2d-x/external/ios/include/spidermonkey", + ); }; name = Debug; }; @@ -766,6 +852,7 @@ ALWAYS_SEARCH_USER_PATHS = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = "JMKA-mobile.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; COMPRESS_PNG_FILES = NO; DEVELOPMENT_TEAM = 6L2252GUFG; @@ -781,9 +868,12 @@ HEADER_SEARCH_PATHS = ""; INFOPLIST_FILE = ios/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = "$(SRCROOT)/../../cocos2d-x/external/ios/libs"; - MARKETING_VERSION = 2.0; + MARKETING_VERSION = 2.1; OTHER_LDFLAGS = ( "-ObjC", "$(inherited)", @@ -793,7 +883,14 @@ SWIFT_OBJC_BRIDGING_HEADER = "JMKA_mobile-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - USER_HEADER_SEARCH_PATHS = "$(inherited) $(SRCROOT)/../../cocos2d-x/cocos/platform/ios $(SRCROOT)/../../cocos2d-x/plugin/jsbindings/auto $(SRCROOT)/../../cocos2d-x/plugin/jsbindings/manual $(SRCROOT)/../../cocos2d-x/external/ios/include $(SRCROOT)/../../cocos2d-x/external/ios/include/spidermonkey"; + USER_HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../../cocos2d-x/cocos/platform/ios", + "$(SRCROOT)/../../cocos2d-x/plugin/jsbindings/auto", + "$(SRCROOT)/../../cocos2d-x/plugin/jsbindings/manual", + "$(SRCROOT)/../../cocos2d-x/external/ios/include", + "$(SRCROOT)/../../cocos2d-x/external/ios/include/spidermonkey", + ); }; name = Release; }; @@ -828,6 +925,30 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + FEC511AE28B8972800ED3051 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 9.0.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + FEC511AF28B8972800ED3051 /* FirebaseAnalytics */ = { + isa = XCSwiftPackageProductDependency; + package = FEC511AE28B8972800ED3051 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseAnalytics; + }; + FEC511B128B8972800ED3051 /* FirebaseMessaging */ = { + isa = XCSwiftPackageProductDependency; + package = FEC511AE28B8972800ED3051 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseMessaging; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = A92275341517C094001B78AA /* Project object */; } diff --git a/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/JMKA.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/JMKA.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..7825fe8 --- /dev/null +++ b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/JMKA.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,113 @@ +{ + "pins" : [ + { + "identity" : "abseil-cpp-swiftpm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/abseil-cpp-SwiftPM.git", + "state" : { + "revision" : "583de9bd60f66b40e78d08599cc92036c2e7e4e1", + "version" : "0.20220203.2" + } + }, + { + "identity" : "boringssl-swiftpm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/boringssl-SwiftPM.git", + "state" : { + "revision" : "dd3eda2b05a3f459fc3073695ad1b28659066eab", + "version" : "0.9.1" + } + }, + { + "identity" : "firebase-ios-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/firebase-ios-sdk", + "state" : { + "revision" : "7f31a43f8c49bd4a1723bc9fecdfaa4411dd9f36", + "version" : "9.5.0" + } + }, + { + "identity" : "googleappmeasurement", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleAppMeasurement.git", + "state" : { + "revision" : "f54f60d0164d887e1174fa51ab2efe48a8e9d178", + "version" : "9.3.0" + } + }, + { + "identity" : "googledatatransport", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleDataTransport.git", + "state" : { + "revision" : "5056b15c5acbb90cd214fe4d6138bdf5a740e5a8", + "version" : "9.2.0" + } + }, + { + "identity" : "googleutilities", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleUtilities.git", + "state" : { + "revision" : "f4abe56ce62a779e64b525eb133c8fc2a84bbc1f", + "version" : "7.7.1" + } + }, + { + "identity" : "grpc-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/grpc/grpc-ios.git", + "state" : { + "revision" : "8440b914756e0d26d4f4d054a1c1581daedfc5b6", + "version" : "1.44.3-grpc" + } + }, + { + "identity" : "gtm-session-fetcher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/gtm-session-fetcher.git", + "state" : { + "revision" : "19605024d59eaefdb1f6a2cb11ebe75df4421126", + "version" : "2.0.0" + } + }, + { + "identity" : "leveldb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/leveldb.git", + "state" : { + "revision" : "0706abcc6b0bd9cedfbb015ba840e4a780b5159b", + "version" : "1.22.2" + } + }, + { + "identity" : "nanopb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/nanopb.git", + "state" : { + "revision" : "819d0a2173aff699fb8c364b6fb906f7cdb1a692", + "version" : "2.30909.0" + } + }, + { + "identity" : "promises", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/promises.git", + "state" : { + "revision" : "3e4e743631e86c8c70dbc6efdc7beaa6e90fd3bb", + "version" : "2.1.1" + } + }, + { + "identity" : "swift-protobuf", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-protobuf.git", + "state" : { + "revision" : "131a3fc8097ef2f30b4170e88cdc234b615b6a29", + "version" : "1.20.0" + } + } + ], + "version" : 2 +} diff --git a/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/JMKA.xcodeproj/project.xcworkspace/xcuserdata/jianmiau.xcuserdatad/UserInterfaceState.xcuserstate b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/JMKA.xcodeproj/project.xcworkspace/xcuserdata/jianmiau.xcuserdatad/UserInterfaceState.xcuserstate index 7f68aae..1691682 100644 Binary files a/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/JMKA.xcodeproj/project.xcworkspace/xcuserdata/jianmiau.xcuserdatad/UserInterfaceState.xcuserstate and b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/JMKA.xcodeproj/project.xcworkspace/xcuserdata/jianmiau.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/JMKA.xcodeproj/xcuserdata/jianmiau.xcuserdatad/xcschemes/xcschememanagement.plist b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/JMKA.xcodeproj/xcuserdata/jianmiau.xcuserdatad/xcschemes/xcschememanagement.plist index 23605bc..b43a108 100644 --- a/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/JMKA.xcodeproj/xcuserdata/jianmiau.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/JMKA.xcodeproj/xcuserdata/jianmiau.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,13 +7,34 @@ JMKA-desktop.xcscheme_^#shared#^_ orderHint - 2 + 3 JMKA-mobile.xcscheme_^#shared#^_ orderHint 0 + Promises (Playground) 1.xcscheme + + isShown + + orderHint + 5 + + Promises (Playground) 2.xcscheme + + isShown + + orderHint + 6 + + Promises (Playground).xcscheme + + isShown + + orderHint + 4 + diff --git a/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/ios/AppController.h b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/ios/AppController.h index c09df20..762db31 100755 --- a/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/ios/AppController.h +++ b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/ios/AppController.h @@ -24,6 +24,8 @@ THE SOFTWARE. ****************************************************************************/ #import +#import +#import @class RootViewController; @@ -32,6 +34,8 @@ } + (void) JSBridge:(NSString *)value1 value2:(NSString *)value2; ++ (NSString*) getIntent; ++ (NSString*) GetFCMToken; + (void) TTS_Play:(NSString *) msg; @property(nonatomic, readonly) RootViewController* viewController; diff --git a/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/ios/AppController.mm b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/ios/AppController.mm index 64524c7..250af63 100755 --- a/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/ios/AppController.mm +++ b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/ios/AppController.mm @@ -29,6 +29,7 @@ #import "AppDelegate.h" #import "RootViewController.h" #import "SDKWrapper.h" +#import "Firebase_OC.h" #import "platform/ios/CCEAGLView-ios.h" #include "cocos/scripting/js-bindings/jswrapper/SeApi.h" #import "JMKA_mobile-Swift.h" @@ -42,7 +43,11 @@ using namespace cocos2d; Application* app = nullptr; Sound_S* Sound = nullptr; TTS_S* TTS = nullptr; +//Firebase_OC* Firebase = nullptr; @synthesize window; +std::string UrlData = "null"; +std::string UrlData_Q = ""; +std::string IsStart = "0"; #pragma mark - #pragma mark Application lifecycle @@ -85,8 +90,10 @@ TTS_S* TTS = nullptr; Sound = [[Sound_S alloc]init]; TTS = [[TTS_S alloc]init]; +// Firebase = [[Firebase_OC alloc]init]; [Sound StartPlay]; + [Firebase_OC application:application didFinishLaunchingWithOptions:launchOptions]; //run the cocos2d-x game scene app->start(); @@ -94,6 +101,53 @@ TTS_S* TTS = nullptr; return YES; } +// With "FirebaseAppDelegateProxyEnabled": NO +- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + [Firebase_OC application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; +} + +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{ + [Firebase_OC application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; +} + +/** + iOS 9.0 之后 +三方唤起本程序后执行的方法 + return YES 表示允许唤起本程序 + */ +- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options{ + NSLog(@"URL scheme:%@", [url scheme]); + //参数 + NSLog(@"URL host:%@", [url host]); +// UIAlertView *alertView=[[UIAlertView alloc] initWithTitle:@"分享" message:[url host] delegate:self cancelButtonTitle:nil otherButtonTitles:@"分享完成", nil]; +// [alertView show]; + + + NSLog(@"openURL"); + NSString *UrlData_url = [url.absoluteString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + NSString *UrlData_query = [[url query] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + NSString *UrlData_host = [url host]; + NSString *UrlData_path = [url path]; + NSString *UrlData_port = [[url port] stringValue]; + NSString *UrlData_scheme = [url scheme]; + + NSString *Msg_UrlData = [NSString stringWithFormat:@"open Url: %@#Query$%@#Host$%@#Path$%@#Port$%@#Scheme$%@", UrlData_url, UrlData_query, UrlData_host, UrlData_path, UrlData_port, UrlData_scheme]; + NSLog(@"Msg_UrlData:'%@'",Msg_UrlData); + UrlData = std::string([Msg_UrlData UTF8String]); + if(UrlData_query) { + UrlData_Q = std::string([UrlData_query UTF8String]); + } + + return YES; +} + +- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler { + + NSLog(@"%@", shortcutItem.type); + NSString *Msg_UrlData = [NSString stringWithFormat:@"Query$%@", [self encodeDictionary:shortcutItem.userInfo]]; + UrlData = std::string([Msg_UrlData UTF8String]); +} + - (void)applicationWillResignActive:(UIApplication *)application { /* Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. @@ -109,6 +163,16 @@ TTS_S* TTS = nullptr; */ app->onResume(); [[SDKWrapper getInstance] applicationDidBecomeActive:application]; + + if(IsStart == "1" && UrlData_Q != "") { + std::string method = "DispatchCallback"; + std::string value = UrlData_Q; + // 呼叫JS的window.JSBridge + std::string jsCallStr = cocos2d::StringUtils::format("JSBridge(\"%s\", \"%s\");", method.c_str(), value.c_str()); + se::Value *ret = new se::Value(); + se::ScriptEngine::getInstance()->evalString(jsCallStr.c_str() , -1 , ret); + UrlData_Q = ""; + } } - (void)applicationDidEnterBackground:(UIApplication *)application { @@ -123,7 +187,7 @@ TTS_S* TTS = nullptr; /* Called as part of transition from the background to the inactive state: here you can undo many of the changes made on entering the background. */ - [[SDKWrapper getInstance] applicationWillEnterForeground:application]; + [[SDKWrapper getInstance] applicationWillEnterForeground:application]; } - (void)applicationWillTerminate:(UIApplication *)application @@ -154,6 +218,28 @@ TTS_S* TTS = nullptr; se::ScriptEngine::getInstance()->evalString(jsCallStr.c_str() , -1 , ret); } ++ (NSString*) getIntent { + if(IsStart == "0") { + IsStart = "1"; + } + std::string jsCallStr = cocos2d::StringUtils::format("cc.log(\"getIntent GO\");"); + se::Value *ret = new se::Value(); + se::ScriptEngine::getInstance()->evalString(jsCallStr.c_str() , -1 , ret); + // NSLog(@"Msg :%@",Msg); + // return @"getIntent OK"; + + return [NSString stringWithUTF8String:UrlData.c_str()]; +} + +#pragma mark- FireBase + ++ (NSString*) GetFCMToken { + NSString *fcmToken = [Firebase_OC FcmToken]; + std::string value = std::string([fcmToken UTF8String]); + return [NSString stringWithUTF8String:value.c_str()]; +} + +#pragma mark- TTS + (void) TTS_Play:(NSString *) msg { [TTS playWithMsg:msg]; } diff --git a/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/ios/Firebase_OC.h b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/ios/Firebase_OC.h new file mode 100644 index 0000000..39b15e3 --- /dev/null +++ b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/ios/Firebase_OC.h @@ -0,0 +1,24 @@ +// +// Firebase_OC.h +// JMKA-mobile +// +// Created by JianMiau on 2022/8/26. +// + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface Firebase_OC : NSObject + ++ (NSString*)FcmToken; ++ (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions; ++ (void)application:(UIApplication *)application +didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken; ++ (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/ios/Firebase_OC.m b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/ios/Firebase_OC.m new file mode 100644 index 0000000..d4179a9 --- /dev/null +++ b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/ios/Firebase_OC.m @@ -0,0 +1,121 @@ +// +// Firebase_OC.m +// JMKA-mobile +// +// Created by JianMiau on 2022/8/26. +// + +#import "Firebase_OC.h" +#import "AppController.h" +#import "FirebaseCore/FirebaseCore.h" +#import +#import + +@implementation Firebase_OC + +NSString *fcmtoken = @""; + ++ (NSString*)FcmToken +{ + return fcmtoken; +} + ++ (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Use Firebase library to configure APIs + [FIRApp configure]; + + if ([UNUserNotificationCenter class] != nil) { + // iOS 10 or later + // For iOS 10 display notification (sent via APNS) + [UNUserNotificationCenter currentNotificationCenter].delegate = self; + UNAuthorizationOptions authOptions = UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge; + [[UNUserNotificationCenter currentNotificationCenter] + requestAuthorizationWithOptions:authOptions + completionHandler:^(BOOL granted, NSError * _Nullable error) { + // ... + }]; + } else { + // iOS 10 notifications aren't available; fall back to iOS 8-9 notifications. + UIUserNotificationType allNotificationTypes = + (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge); + UIUserNotificationSettings *settings = + [UIUserNotificationSettings settingsForTypes:allNotificationTypes categories:nil]; + [application registerUserNotificationSettings:settings]; + } + + [application registerForRemoteNotifications]; + [FIRMessaging messaging].delegate = self; + + return YES; +} + + ++ (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSString *)fcmToken { + NSLog(@"FCM registration token: %@", fcmToken); + fcmtoken = fcmToken; + // Notify about received token. + NSDictionary *dataDict = [NSDictionary dictionaryWithObject:fcmToken forKey:@"token"]; + [[NSNotificationCenter defaultCenter] postNotificationName: + @"FCMToken" object:nil userInfo:dataDict]; + // TODO: If necessary send token to application server. + // Note: This callback is fired at each app startup and whenever a new token is generated. +} + +// With "FirebaseAppDelegateProxyEnabled": NO ++ (void)application:(UIApplication *)application + didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + NSLog(@"FCM deviceToken: %@", deviceToken); + [FIRMessaging messaging].APNSToken = deviceToken; +} + +// 遠端通知 +// Receive displayed notifications for iOS 10 devices. +// Handle incoming notification messages while app is in the foreground. ++ (void)userNotificationCenter:(UNUserNotificationCenter *)center + willPresentNotification:(UNNotification *)notification + withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler { + NSDictionary *userInfo = notification.request.content.userInfo; + + // With swizzling disabled you must let Messaging know about the message, for Analytics + // [[FIRMessaging messaging] appDidReceiveMessage:userInfo]; + + // ... + + // Print full message. + NSLog(@"%@", userInfo); + + // Change this to your preferred presentation option + completionHandler(UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionAlert); +} + +// 點擊通知 +// Handle notification messages after display notification is tapped by the user. ++ (void)userNotificationCenter:(UNUserNotificationCenter *)center +didReceiveNotificationResponse:(UNNotificationResponse *)response + withCompletionHandler:(void(^)(void))completionHandler { + NSDictionary *userInfo = response.notification.request.content.userInfo; + + + // With swizzling disabled you must let Messaging know about the message, for Analytics + // [[FIRMessaging messaging] appDidReceiveMessage:userInfo]; + + // Print full message. + NSLog(@"%@", userInfo); + + NSString* title = [userInfo objectForKey:@"data_title"]; + NSString* contect = [userInfo objectForKey:@"data_content"]; + if (title && contect) { + [AppController JSBridge:title value2:contect]; + } + + completionHandler(); +} + ++ (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{ + NSDictionary* notd = [userInfo objectForKey:@"aps"]; + NSDictionary* message = [notd objectForKey:@"alert"]; + + NSLog(@"%@",message); +} + +@end diff --git a/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/ios/Info.plist b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/ios/Info.plist index ff5f06f..a07042d 100755 --- a/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/ios/Info.plist +++ b/build-templates/jsb-default/frameworks/runtime-src/proj.ios_mac/ios/Info.plist @@ -3,9 +3,9 @@ CFBundleDevelopmentRegion - English + zh_TW CFBundleDisplayName - 建喵記帳 + 卡羅記帳 CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0.0 + $(MARKETING_VERSION) CFBundleSignature ???? CFBundleVersion