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