icarus_groundmark 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/Test.zip +0 -0
- package/package.json +31 -0
- package/src/Icarus.ts +2485 -0
package/src/Icarus.ts
ADDED
|
@@ -0,0 +1,2485 @@
|
|
|
1
|
+
import { renderer, CCObject } from "cc";
|
|
2
|
+
import { ccenum } from "cc";
|
|
3
|
+
import { EventHandler } from "cc";
|
|
4
|
+
import { _decorator, Animation, assetManager, AsyncDelegate, AudioClip, AudioSource, Color, Component, director, EventTarget, Game, game, instantiate, Label, math, Node, NodePool, NodeSpace, Prefab, Quat, resources, Sprite, SpriteFrame, sys, tween, UIOpacity, UITransform, Vec3, view } from "cc";
|
|
5
|
+
import { KOREA, WECHAT, WECHAT_DIALOG, AGE16 } from "cc/userland/macro";
|
|
6
|
+
const { ccclass, property } = _decorator;
|
|
7
|
+
|
|
8
|
+
//#region 反序列化或配置解析
|
|
9
|
+
|
|
10
|
+
@ccclass('EventConfig')
|
|
11
|
+
export class EventConfig {
|
|
12
|
+
@property({ type: EventHandler, displayName: "事件" })
|
|
13
|
+
event: EventHandler = new EventHandler();
|
|
14
|
+
@property({ displayName: "是否延迟触发" })
|
|
15
|
+
delay_emit: boolean = false;
|
|
16
|
+
@property({ displayName: "延迟时间(ms)", min: 1 / 60, visible: function (this) { return this.delay_emit } })
|
|
17
|
+
delay_time: number = 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface PoolBody {
|
|
21
|
+
/**对象池名字 */
|
|
22
|
+
name: string;
|
|
23
|
+
/**路径 */
|
|
24
|
+
path: string;
|
|
25
|
+
/**数量 */
|
|
26
|
+
count: number,
|
|
27
|
+
/**自动补充阈值 */
|
|
28
|
+
auto_supplement: number,
|
|
29
|
+
/**自动扩容 */
|
|
30
|
+
auto_expansion: boolean,
|
|
31
|
+
/**自动实例 */
|
|
32
|
+
auto_example: boolean,
|
|
33
|
+
/**是否添加监听 */
|
|
34
|
+
handler: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class PoolConfig {
|
|
38
|
+
pool: PoolBody[] = [];
|
|
39
|
+
|
|
40
|
+
constructor(object) {
|
|
41
|
+
object.forEach((item, index) => {
|
|
42
|
+
this.pool.push({ name: item.name, path: item.path, count: item.count, auto_supplement: item.auto_supplement, auto_expansion: item.auto_expansion, auto_example: item.auto_example, handler: item.handler });
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@ccclass('BoneSynchronizationConfig')
|
|
49
|
+
export class BoneSynchronizationConfig {
|
|
50
|
+
@property({ displayName: '骨骼名' })
|
|
51
|
+
bone_name: string = '';
|
|
52
|
+
@property({ type: Node, displayName: '绑定节点' })
|
|
53
|
+
bind_node: Node = null;
|
|
54
|
+
@property({ type: Node, displayName: '同步节点' })
|
|
55
|
+
sync_node: Node = null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@ccclass('BoneSynchronization')
|
|
59
|
+
export class BoneSynchronization {
|
|
60
|
+
@property({ type: Node, displayName: "骨架根节点" })
|
|
61
|
+
skeleton_root: Node = null;
|
|
62
|
+
@property({ type: [BoneSynchronizationConfig], displayName: '骨骼同步配置' })
|
|
63
|
+
bone_synchronization_config: BoneSynchronizationConfig[] = [];
|
|
64
|
+
|
|
65
|
+
init() {
|
|
66
|
+
if (!this.skeleton_root) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
for (let i = 0; i < this.bone_synchronization_config.length; ++i) {
|
|
70
|
+
let config = this.bone_synchronization_config[i];
|
|
71
|
+
if (!config.bind_node) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (is_null_string(config.bone_name)) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
this.bind_bone(config);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private bind_bone(config: BoneSynchronizationConfig) {
|
|
82
|
+
let bone = this.find_bone(config.bone_name);
|
|
83
|
+
config.bind_node.parent = bone;
|
|
84
|
+
if (config.sync_node) {
|
|
85
|
+
config.bind_node.setWorldPosition(config.sync_node.worldPosition.clone());
|
|
86
|
+
config.bind_node.setWorldRotation(config.sync_node.rotation.clone());
|
|
87
|
+
config.bind_node.scale = config.sync_node.scale.clone();
|
|
88
|
+
} else {
|
|
89
|
+
config.bind_node.setPosition(Vec3.ZERO);
|
|
90
|
+
config.bind_node.setRotation(Quat.IDENTITY);
|
|
91
|
+
config.bind_node.scale = Vec3.ONE;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private find_bone(name: string): Node | null {
|
|
96
|
+
return this.recursive(this.skeleton_root, name);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private recursive(node: Node, name: string): Node | null {
|
|
100
|
+
if (node.name === name) return node;
|
|
101
|
+
for (let child of node.children) {
|
|
102
|
+
const found = this.recursive(child, name);
|
|
103
|
+
if (found) return found;
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
export enum ResourcesType {
|
|
111
|
+
None,Money,Gold,Water,Ice,Oil,Coin,Stone,Mine
|
|
112
|
+
}
|
|
113
|
+
ccenum(ResourcesType)
|
|
114
|
+
@ccclass('Packge')
|
|
115
|
+
export class Packge {
|
|
116
|
+
@property({ type: ResourcesType, displayName: "资源类型" })
|
|
117
|
+
type: ResourcesType = ResourcesType.Gold;
|
|
118
|
+
@property({ displayName: "数量" })
|
|
119
|
+
count: number = 0;
|
|
120
|
+
@property({ type: Node, displayName: "根节点" })
|
|
121
|
+
root: Node = null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
//#endregion
|
|
125
|
+
|
|
126
|
+
//#region 组件拓展
|
|
127
|
+
|
|
128
|
+
/**排序方式,不排序,升序,降序 */
|
|
129
|
+
export enum SortType {
|
|
130
|
+
None, AscendingOrder, DescendingOrder
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
declare module 'cc' {
|
|
134
|
+
interface Node {
|
|
135
|
+
is_shaking: boolean;
|
|
136
|
+
/**在列表中寻找距离自己最近的 */
|
|
137
|
+
find_near(list: Node[],range:number): Node | null;
|
|
138
|
+
/**对列表进行一次排序
|
|
139
|
+
* @param list 列表
|
|
140
|
+
* @param sort 排序方式,None:不进行排序,AscendingOrder:升序排序,DescendingOrder:降序排序
|
|
141
|
+
* @param count 截取数量
|
|
142
|
+
*/
|
|
143
|
+
distance_sort(list: Node[], sort: SortType, count: number,range:number): { distance: number, item: Node }[] | null;
|
|
144
|
+
init_retinue();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
interface Vec3 {
|
|
148
|
+
/**
|
|
149
|
+
* 判断两个向量值是否相等
|
|
150
|
+
* @param other 另一个
|
|
151
|
+
*/
|
|
152
|
+
same(other: Vec3): boolean;
|
|
153
|
+
/**
|
|
154
|
+
* 面向目标
|
|
155
|
+
* @param target 面向的目标
|
|
156
|
+
* @param up 上方
|
|
157
|
+
* @returns 相对角度
|
|
158
|
+
*/
|
|
159
|
+
look(target: Vec3, up?: Vec3): Vec3;
|
|
160
|
+
threshold_check(other: Vec3, threshold: Vec3): boolean;
|
|
161
|
+
/**计算差值 */
|
|
162
|
+
difference(a: number, b: number): number;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
interface Component {
|
|
166
|
+
/**
|
|
167
|
+
* 用于中止使用全局方法:timer定时器的方法
|
|
168
|
+
* PS:注意是用于中止全局timer的而不是this.timer,如需要中止this.timer请调用this.cancel_timer();
|
|
169
|
+
*/
|
|
170
|
+
cancel_thred(): void;
|
|
171
|
+
/**
|
|
172
|
+
* 深拷贝一个数组
|
|
173
|
+
* @param array
|
|
174
|
+
* @returns
|
|
175
|
+
*/
|
|
176
|
+
copy<T>(array: T[]): T[];
|
|
177
|
+
timer_thred: number;
|
|
178
|
+
/**
|
|
179
|
+
* 定时器,因为设计原因,每次只能存在一个
|
|
180
|
+
* PS:模拟器或浏览器运行时,取消定时器可能会抛出异常不必理会
|
|
181
|
+
* @param time 定时器时间,单位为秒
|
|
182
|
+
*/
|
|
183
|
+
timer(time: number): Promise<unknown>;
|
|
184
|
+
/**
|
|
185
|
+
* 取消当前定时器
|
|
186
|
+
* PS:模拟器或浏览器运行时,取消定时器可能会抛出异常不必理会
|
|
187
|
+
*/
|
|
188
|
+
cancel_timer(): void;
|
|
189
|
+
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
//#region Node拓展
|
|
195
|
+
Node.prototype.find_near = function (list: Node[],range): Node | null {
|
|
196
|
+
if (list.length == 0) return null;
|
|
197
|
+
if (list.length == 1) return list[0];
|
|
198
|
+
let _list = this.distance_sort(list, SortType.AscendingOrder,1,range);
|
|
199
|
+
return _list.length > 0 ? _list[0].item : null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
Node.prototype.init_retinue = function () {
|
|
203
|
+
this.components[0].init();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
Node.prototype.distance_sort = function (list: Node[], sort: SortType, count: number = 0,range:number = 0): { distance: number, item: Node }[] | null {
|
|
207
|
+
if (list.length == 0) return null;
|
|
208
|
+
if (list.length == 1) return [{ distance: 0, item: list[0] }];
|
|
209
|
+
let new_list: Node[] = list.map(x => x);
|
|
210
|
+
let distance_list: { distance: number, item: Node }[] = [];
|
|
211
|
+
let my_position: Vec3 = this.worldPosition.clone();
|
|
212
|
+
new_list.forEach(item => {
|
|
213
|
+
distance_list.push({ distance: Vec3.distance(my_position, item.worldPosition.clone()), item: item });
|
|
214
|
+
})
|
|
215
|
+
if (sort != SortType.None) {
|
|
216
|
+
distance_list.sort((a, b) => (sort == SortType.AscendingOrder ? a : b).distance - (sort == SortType.AscendingOrder ? b : a).distance);
|
|
217
|
+
}
|
|
218
|
+
if(range > 0){
|
|
219
|
+
distance_list = distance_list.filter(x => x.distance <= range);
|
|
220
|
+
}
|
|
221
|
+
if (count == 0 || count > list.length) {
|
|
222
|
+
return distance_list;
|
|
223
|
+
} else {
|
|
224
|
+
return distance_list.slice(0, count);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
//#endregion
|
|
228
|
+
|
|
229
|
+
//#region Component拓展
|
|
230
|
+
Component.prototype.copy = function <T>(list: T[]): T[] {
|
|
231
|
+
let new_list: T[] = list.map(x => x);
|
|
232
|
+
return new_list;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
Component.prototype.timer = function (time: number): Promise<unknown> {
|
|
236
|
+
return new Promise((resolve, reject) => {
|
|
237
|
+
this.cancel_timer = () => {
|
|
238
|
+
clearTimeout(this.timer_thred);
|
|
239
|
+
this.timer_thred = -1;
|
|
240
|
+
this.cancel_timer = null;
|
|
241
|
+
reject('cancel');
|
|
242
|
+
}
|
|
243
|
+
this.timer_thred = setTimeout(() => {
|
|
244
|
+
resolve('');
|
|
245
|
+
}, time * 1000);
|
|
246
|
+
}).catch(error => {
|
|
247
|
+
throw new Error();
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
//#endregion
|
|
251
|
+
|
|
252
|
+
//#region Vec3拓展
|
|
253
|
+
Vec3.prototype.same = function (other: Vec3): boolean {
|
|
254
|
+
return this.x === other.x && this.y === other.y && this.z === other.z;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
Vec3.prototype.threshold_check = function (other: Vec3, threshold: Vec3): boolean {
|
|
258
|
+
return this.difference(this.x, other.x) > threshold.x || this.difference(this.y, other.y) > threshold.y || this.difference(this.z, other.z) > threshold.z;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
Vec3.prototype.difference = function (a: number, b: number): number {
|
|
262
|
+
if (a < 0) {
|
|
263
|
+
//负数
|
|
264
|
+
if (b > 0) {
|
|
265
|
+
return Math.abs(a) + b;
|
|
266
|
+
} else {
|
|
267
|
+
if (b == 0) {
|
|
268
|
+
return Math.abs(a);
|
|
269
|
+
} else {
|
|
270
|
+
if (a > b) {
|
|
271
|
+
return Math.abs(b) - Math.abs(a);
|
|
272
|
+
} else if (a < b) {
|
|
273
|
+
return Math.abs(a) - Math.abs(b);
|
|
274
|
+
} else {
|
|
275
|
+
return 0;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
//正数
|
|
281
|
+
if (b > 0) {
|
|
282
|
+
if (a > b) {
|
|
283
|
+
return a - b;
|
|
284
|
+
} else if (a < b) {
|
|
285
|
+
return b - a;
|
|
286
|
+
} else {
|
|
287
|
+
return 0;
|
|
288
|
+
}
|
|
289
|
+
} else {
|
|
290
|
+
if (b == 0) {
|
|
291
|
+
return a;
|
|
292
|
+
} else {
|
|
293
|
+
return a + Math.abs(b);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
Vec3.prototype.look = function (target: Vec3, up: Vec3 = Vec3.UP): Vec3 {
|
|
300
|
+
Vec3.subtract(this, this, target);
|
|
301
|
+
Vec3.normalize(this, this);
|
|
302
|
+
let quat = new Quat();
|
|
303
|
+
Quat.fromViewUp(quat, this, up);
|
|
304
|
+
let euler = new Vec3();
|
|
305
|
+
quat.getEulerAngles(euler);
|
|
306
|
+
return euler;
|
|
307
|
+
}
|
|
308
|
+
//#endregion
|
|
309
|
+
|
|
310
|
+
//#endregion
|
|
311
|
+
|
|
312
|
+
//#region 管理类基类
|
|
313
|
+
|
|
314
|
+
export enum GameResult{
|
|
315
|
+
Win,Lose
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
@ccclass('BaseManager')
|
|
319
|
+
export abstract class BaseManager extends Component {
|
|
320
|
+
|
|
321
|
+
protected static instance: BaseManager = null;
|
|
322
|
+
protected abstract onLoad():void;
|
|
323
|
+
protected abstract onEnable(): void;
|
|
324
|
+
protected abstract start(): void;
|
|
325
|
+
protected abstract onDisable(): void;
|
|
326
|
+
protected abstract onDestroy(): void;
|
|
327
|
+
public abstract update(dt: number): void;
|
|
328
|
+
public abstract lateUpdate(dt: number): void;
|
|
329
|
+
/**注册消息 */
|
|
330
|
+
protected abstract register_message(): void;
|
|
331
|
+
/**注销消息 */
|
|
332
|
+
protected abstract cancel_message(): void;
|
|
333
|
+
/**注册监听 */
|
|
334
|
+
protected abstract register_listener(): void;
|
|
335
|
+
/**注销监听 */
|
|
336
|
+
protected abstract cancel_listener(): void;
|
|
337
|
+
|
|
338
|
+
protected abstract game_ready(): void;
|
|
339
|
+
protected abstract game_start(): void;
|
|
340
|
+
protected abstract game_pause(): void;
|
|
341
|
+
protected abstract game_resume(): void;
|
|
342
|
+
protected abstract game_over(): void;
|
|
343
|
+
protected abstract game_settlement(result: GameResult): void;
|
|
344
|
+
protected abstract game_replay(): void;
|
|
345
|
+
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
//#endregion
|
|
349
|
+
|
|
350
|
+
//#region 音效控制
|
|
351
|
+
|
|
352
|
+
export class AudioItem {
|
|
353
|
+
source;
|
|
354
|
+
clip;
|
|
355
|
+
volume;
|
|
356
|
+
playing;
|
|
357
|
+
loop;
|
|
358
|
+
duration;
|
|
359
|
+
start_time;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export class AudioManager {
|
|
363
|
+
private static _sources: AudioSource = null;
|
|
364
|
+
private static clip_map: Record<string, AudioClip> = {};
|
|
365
|
+
private static items: Record<string, AudioItem> = {};
|
|
366
|
+
private static root: Node = null;
|
|
367
|
+
private static _path: string = 'audio/';
|
|
368
|
+
public static set path(value) { AudioManager._path = value; }
|
|
369
|
+
private static music_switch: boolean = true;
|
|
370
|
+
private static sound_switch: boolean = true;
|
|
371
|
+
private static volume: number = 1.0;
|
|
372
|
+
private static first_click: boolean = true;
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* 初始化音频管理器,自动添加根节点以及AudioSource组件
|
|
376
|
+
*/
|
|
377
|
+
static init() {
|
|
378
|
+
if (AudioManager._sources) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
let _root = new Node('AudioManager');
|
|
382
|
+
director.addPersistRootNode(_root);
|
|
383
|
+
let _sources = _root.addComponent(AudioSource);
|
|
384
|
+
AudioManager._sources = _sources;
|
|
385
|
+
AudioManager.root = _root;
|
|
386
|
+
for (let name in AudioManager.items) {
|
|
387
|
+
const item = AudioManager.items[name];
|
|
388
|
+
item && item.playing && item.source.stop();
|
|
389
|
+
}
|
|
390
|
+
AudioManager.items = {};
|
|
391
|
+
if ('volumeSwitch' in window && window.volumeSwitch !== void 0) {
|
|
392
|
+
console.log('Init AudioManager volumeSwitch', window.volumeSwitch);
|
|
393
|
+
AudioManager.music_switch = !!window.volumeSwitch;
|
|
394
|
+
AudioManager.sound_switch = !!window.volumeSwitch;
|
|
395
|
+
}
|
|
396
|
+
if (window.__PLATFORM == 'Ironsource') {
|
|
397
|
+
//Ironsource 平台音量
|
|
398
|
+
var vs = window.volumeAudio;
|
|
399
|
+
if (vs <= 0) {
|
|
400
|
+
AudioManager.volume = 0;
|
|
401
|
+
AudioManager.music_switch = false;
|
|
402
|
+
AudioManager.sound_switch = false;
|
|
403
|
+
} else if (typeof vs == 'number') {
|
|
404
|
+
AudioManager.volume = vs;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
console.log('Init AudioManager:', AudioManager.music_switch, AudioManager.volume);
|
|
408
|
+
game.on(Game.EVENT_HIDE, function () {
|
|
409
|
+
AudioManager.pause();
|
|
410
|
+
});
|
|
411
|
+
game.on(Game.EVENT_SHOW, function () {
|
|
412
|
+
AudioManager.resume();
|
|
413
|
+
});
|
|
414
|
+
view.on("audioVolumeChange", AudioManager.audio_volume_change, this);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
static audio_volume_change(event) {
|
|
418
|
+
let last = AudioManager.music_switch;
|
|
419
|
+
AudioManager.music_switch = event;
|
|
420
|
+
AudioManager.sound_switch = event;
|
|
421
|
+
if (last && !event) {
|
|
422
|
+
AudioManager.pause();
|
|
423
|
+
} else if (!last && event) {
|
|
424
|
+
AudioManager.resume();
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* 暂停所有长音效和背景音
|
|
431
|
+
*/
|
|
432
|
+
static pause() {
|
|
433
|
+
const source = AudioManager._sources;
|
|
434
|
+
if (source) {
|
|
435
|
+
source.pause();
|
|
436
|
+
}
|
|
437
|
+
for (let name in AudioManager.items) {
|
|
438
|
+
const item = AudioManager.items[name];
|
|
439
|
+
item && item.playing && item.source.pause();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* 恢复所有长音效和背景音
|
|
445
|
+
*/
|
|
446
|
+
static resume() {
|
|
447
|
+
if (!AudioManager.music_switch) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
const source = AudioManager._sources;
|
|
451
|
+
if (source) {
|
|
452
|
+
source.play();
|
|
453
|
+
}
|
|
454
|
+
for (let name in AudioManager.items) {
|
|
455
|
+
const item = AudioManager.items[name];
|
|
456
|
+
item && item.playing && (item.loop || game.totalTime - item.start_time < item.duration - 500) && item.source.play();
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* 音效播放
|
|
462
|
+
* @param name 音效名称
|
|
463
|
+
* @param ratio 当同时或近似时间多次申请同一个音效的播放,会按照播放进度进行调整,避免多个音效同时播放
|
|
464
|
+
* @param _source 音源,如果不传入,则使用默认音源
|
|
465
|
+
* @param end_call 播放结束回调
|
|
466
|
+
* @param volume 音量
|
|
467
|
+
* @returns
|
|
468
|
+
*/
|
|
469
|
+
static sound_play(name: string, ratio: number = 0.2, _source: AudioSource = null, end_call = null, volume: number = 1) {
|
|
470
|
+
if (!AudioManager.sound_switch || !AudioManager.first_click) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
if (!AudioManager._sources && !_source) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
const source = _source ? _source : AudioManager._sources;
|
|
477
|
+
let path = `${AudioManager._path}${name}`;
|
|
478
|
+
let clip = AudioManager.clip_map[path];
|
|
479
|
+
if (clip) {
|
|
480
|
+
source.playOneShot(clip, volume, ratio, end_call);
|
|
481
|
+
} else {
|
|
482
|
+
assetManager.resources?.load(path, AudioClip, (err, clip) => {
|
|
483
|
+
if (err) {
|
|
484
|
+
console.warn(err);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
AudioManager.clip_map[path] = clip;
|
|
488
|
+
source.playOneShot(clip, volume, ratio, end_call);
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* 背景音播放
|
|
495
|
+
* @param name 背景音名称
|
|
496
|
+
* @param loop 是否循环播放
|
|
497
|
+
*/
|
|
498
|
+
static music_play(name: string, loop: boolean = true) {
|
|
499
|
+
if (!AudioManager._sources) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const source = AudioManager._sources;
|
|
503
|
+
if (source.playing) {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
source.loop = loop;
|
|
507
|
+
let path = `${AudioManager._path}${name}`;
|
|
508
|
+
let clip = AudioManager.clip_map[path];
|
|
509
|
+
if (clip) {
|
|
510
|
+
source.clip = clip;
|
|
511
|
+
if (AudioManager.music_switch) {
|
|
512
|
+
source.play();
|
|
513
|
+
}
|
|
514
|
+
if (!AudioManager.first_click) {
|
|
515
|
+
source.pause();
|
|
516
|
+
}
|
|
517
|
+
} else {
|
|
518
|
+
assetManager.resources?.load(path, AudioClip, (err, clip) => {
|
|
519
|
+
if (err) {
|
|
520
|
+
console.warn(err);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
AudioManager.clip_map[path] = clip;
|
|
524
|
+
source.clip = clip;
|
|
525
|
+
if (AudioManager.music_switch) {
|
|
526
|
+
source.play();
|
|
527
|
+
}
|
|
528
|
+
if (!AudioManager.first_click) {
|
|
529
|
+
source.pause();
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* 背景音暂停
|
|
537
|
+
*/
|
|
538
|
+
static music_pause() {
|
|
539
|
+
if (!AudioManager._sources) {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
const source = AudioManager._sources;
|
|
543
|
+
if (source.playing) {
|
|
544
|
+
source.pause();
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* 背景音停止
|
|
550
|
+
*/
|
|
551
|
+
static music_stop() {
|
|
552
|
+
if (!AudioManager._sources) {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const source = AudioManager._sources;
|
|
556
|
+
if (source.playing) {
|
|
557
|
+
source.stop();
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* 背景音恢复
|
|
563
|
+
*/
|
|
564
|
+
static music_resume() {
|
|
565
|
+
if (!AudioManager.music_switch) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
if (!AudioManager._sources) {
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
const source = AudioManager._sources;
|
|
572
|
+
if (!source.playing) {
|
|
573
|
+
source.play();
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
static _create_new_source(name: string, loop: boolean) {
|
|
578
|
+
let node = new Node(`Audio_${name}_Source`);
|
|
579
|
+
node.parent = AudioManager.root;
|
|
580
|
+
return AudioManager.items[name] = { source: node.addComponent(AudioSource), playing: false, loop: loop, duration: 0, start_time: 0, clip: null, volume: 1 };
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* 长音频播放
|
|
585
|
+
* @param name 长音频名称
|
|
586
|
+
* @param loop 是否循环播放
|
|
587
|
+
* @param _source 音源,如果不传入,则自动寻找或创建对应音源
|
|
588
|
+
* @param volume 音量
|
|
589
|
+
* @returns
|
|
590
|
+
*/
|
|
591
|
+
static audio_play(name: string, loop: boolean = false, _source: AudioSource = null, volume: number = 1) {
|
|
592
|
+
//如果提供了音源,或存在音源,则可以直接播放
|
|
593
|
+
let item = AudioManager.items[name];
|
|
594
|
+
const source = item ? item.source : _source ? _source : (item = AudioManager._create_new_source(name, loop)).source;
|
|
595
|
+
let source_play = (item, loop, volume) => {
|
|
596
|
+
item.loop = item.source.loop = loop;
|
|
597
|
+
item.source.volume = volume && !isNaN(volume) ? AudioManager.volume * volume : AudioManager.volume;
|
|
598
|
+
item.volume = volume;
|
|
599
|
+
item.duration = item.source.duration * 1000;
|
|
600
|
+
item.playing = true;
|
|
601
|
+
if (AudioManager.sound_switch) {
|
|
602
|
+
item.source.play();
|
|
603
|
+
}
|
|
604
|
+
if (!AudioManager.first_click) {
|
|
605
|
+
item.source.pause();
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
if (source) {
|
|
609
|
+
if (!source.playing && source.clip) {
|
|
610
|
+
source_play(item, loop, volume);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
let path = `${AudioManager._path}${name}`;
|
|
615
|
+
let clip = AudioManager.clip_map[path];
|
|
616
|
+
if (clip) {
|
|
617
|
+
item.clip = source.clip = clip;
|
|
618
|
+
source_play(item, loop, volume);
|
|
619
|
+
} else {
|
|
620
|
+
assetManager.resources?.load(path, AudioClip, (err, clip) => {
|
|
621
|
+
if (err) {
|
|
622
|
+
console.warn(err);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
AudioManager.clip_map[path] = clip;
|
|
626
|
+
item.clip = source.clip = clip;
|
|
627
|
+
source_play(item, loop, volume);
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* 长音频暂停
|
|
634
|
+
* @param name 长音频名称,如果不传入,则暂停所有音效
|
|
635
|
+
*/
|
|
636
|
+
static audio_pause(name?: string);
|
|
637
|
+
/**
|
|
638
|
+
* 长音频暂停
|
|
639
|
+
* @param name 长音频名称数组,如果不传入,则暂停所有音效
|
|
640
|
+
*/
|
|
641
|
+
static audio_pause(name?: string[]);
|
|
642
|
+
static audio_pause(name: string | string[] = 'pause_all') {
|
|
643
|
+
if (name === 'pause_all') {
|
|
644
|
+
//暂停所有音效
|
|
645
|
+
for (let key in AudioManager.items) {
|
|
646
|
+
let item = AudioManager.items[key];
|
|
647
|
+
if (item.playing) {
|
|
648
|
+
item.playing = false;
|
|
649
|
+
item.source.pause();
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (name instanceof Array) {
|
|
655
|
+
for (let key in AudioManager.items) {
|
|
656
|
+
let item = AudioManager.items[key];
|
|
657
|
+
if (name.indexOf(key) === -1) {
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
name.splice(name.indexOf(key), 1);
|
|
661
|
+
if (item.playing) {
|
|
662
|
+
item.playing = false;
|
|
663
|
+
item.source.pause();
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
let item = AudioManager.items[name];
|
|
669
|
+
if (item) {
|
|
670
|
+
item.playing = false;
|
|
671
|
+
item.source.pause();
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* 长音频停止
|
|
677
|
+
* @param name 长音频名称,如果不传入,则停止所有音效
|
|
678
|
+
*/
|
|
679
|
+
static audio_stop(name?: string);
|
|
680
|
+
/**
|
|
681
|
+
* 长音频停止
|
|
682
|
+
* @param name 长音频名称数组,如果不传入,则停止所有音效
|
|
683
|
+
*/
|
|
684
|
+
static audio_stop(name?: string[]);
|
|
685
|
+
static audio_stop(name: string | string[] = 'stop_all') {
|
|
686
|
+
if (name === 'stop_all') {
|
|
687
|
+
//停止所有音效
|
|
688
|
+
for (let key in AudioManager.items) {
|
|
689
|
+
let item = AudioManager.items[key];
|
|
690
|
+
if (item.playing) {
|
|
691
|
+
item.playing = false;
|
|
692
|
+
item.source.stop();
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (name instanceof Array) {
|
|
698
|
+
for (let key in AudioManager.items) {
|
|
699
|
+
let item = AudioManager.items[key];
|
|
700
|
+
if (name.indexOf(key) === -1) {
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
name.splice(name.indexOf(key), 1);
|
|
704
|
+
if (item.playing) {
|
|
705
|
+
item.playing = false;
|
|
706
|
+
item.source.stop();
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
let item = AudioManager.items[name];
|
|
712
|
+
if (item) {
|
|
713
|
+
item.playing = false;
|
|
714
|
+
item.source.stop();
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* 长音频恢复播放
|
|
720
|
+
* @param name 长音频名称,如果不传入,则恢复所有音效
|
|
721
|
+
*/
|
|
722
|
+
static audio_resume(name?: string);
|
|
723
|
+
/**
|
|
724
|
+
* 长音频恢复播放
|
|
725
|
+
* @param name 长音频名称数组,如果不传入,则恢复所有音效
|
|
726
|
+
*/
|
|
727
|
+
static audio_resume(name?: string[]);
|
|
728
|
+
static audio_resume(name: string | string[] = 'resume_all') {
|
|
729
|
+
if (!AudioManager.sound_switch) {
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
if (name === 'resume_all') {
|
|
733
|
+
//恢复所有音效
|
|
734
|
+
for (let key in AudioManager.items) {
|
|
735
|
+
let item = AudioManager.items[key];
|
|
736
|
+
if (!item.playing) {
|
|
737
|
+
item.playing = true;
|
|
738
|
+
item.source.play();
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
if (name instanceof Array) {
|
|
744
|
+
for (let key in AudioManager.items) {
|
|
745
|
+
let item = AudioManager.items[key];
|
|
746
|
+
if (name.indexOf(key) === -1) {
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
name.splice(name.indexOf(key), 1);
|
|
750
|
+
if (!item.playing) {
|
|
751
|
+
item.playing = true;
|
|
752
|
+
item.source.play();
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
let item = AudioManager.items[name];
|
|
758
|
+
if (item) {
|
|
759
|
+
item.playing = true;
|
|
760
|
+
item.source.play();
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* 长音频和音效音量
|
|
766
|
+
* @param name 音量,范围0-1
|
|
767
|
+
*/
|
|
768
|
+
static audio_volume(name?: number);
|
|
769
|
+
/**
|
|
770
|
+
* 长音频音量
|
|
771
|
+
* @param name 长音频名称数组,如果不传入,则设置所有音效音量
|
|
772
|
+
* @param volume 音量,范围0-1
|
|
773
|
+
*/
|
|
774
|
+
static audio_volume(name?: string, volume?: number);
|
|
775
|
+
static audio_volume(name: string | number = 'volume_all', volume = 1) {
|
|
776
|
+
if (typeof (name) === 'string') {
|
|
777
|
+
let item = AudioManager.items[name];
|
|
778
|
+
if (item) {
|
|
779
|
+
item.source.volume = AudioManager.volume * volume;
|
|
780
|
+
}
|
|
781
|
+
} else {
|
|
782
|
+
//所有的音效
|
|
783
|
+
for (let key in AudioManager.items) {
|
|
784
|
+
let item = AudioManager.items[key];
|
|
785
|
+
item.source.volume = AudioManager.volume * name;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* 整体音量
|
|
792
|
+
*/
|
|
793
|
+
static set Volume(volume) {
|
|
794
|
+
if (AudioManager._sources) {
|
|
795
|
+
AudioManager.volume = volume;
|
|
796
|
+
AudioManager._sources.volume = AudioManager.volume * volume;
|
|
797
|
+
}
|
|
798
|
+
for (let key in AudioManager.items) {
|
|
799
|
+
let item = AudioManager.items[key];
|
|
800
|
+
item.source.volume = AudioManager.volume * item.volume;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* 背景音乐开关
|
|
806
|
+
*/
|
|
807
|
+
static set MusicSwitch(value) {
|
|
808
|
+
AudioManager.music_switch = value;
|
|
809
|
+
if (AudioManager._sources) {
|
|
810
|
+
if (AudioManager._sources.playing) {
|
|
811
|
+
AudioManager._sources.pause();
|
|
812
|
+
} else {
|
|
813
|
+
AudioManager._sources.play();
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* 背景音乐开关
|
|
820
|
+
*/
|
|
821
|
+
static get MusicSwitch() {
|
|
822
|
+
return AudioManager.music_switch;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* 音效开关
|
|
827
|
+
*/
|
|
828
|
+
static set SoundSwitch(value) {
|
|
829
|
+
AudioManager.sound_switch = value;
|
|
830
|
+
//更新所有音效的音量
|
|
831
|
+
if (value) {
|
|
832
|
+
AudioManager.audio_resume();
|
|
833
|
+
} else {
|
|
834
|
+
AudioManager.audio_pause();
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* 音效开关
|
|
840
|
+
*/
|
|
841
|
+
static get SoundSwitch() {
|
|
842
|
+
return AudioManager.sound_switch;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
//#endregion
|
|
846
|
+
|
|
847
|
+
//#region 国际化
|
|
848
|
+
|
|
849
|
+
export class PlayableSDK {
|
|
850
|
+
|
|
851
|
+
////////////// 上报打点数据接口,所有接口都需要实现 /////////////////////////////////////
|
|
852
|
+
////////下面这3个函数在框架会自动调用
|
|
853
|
+
static onInitPlayable() { } //可玩广告启动
|
|
854
|
+
static onLoaded() { } //资源加载完成
|
|
855
|
+
static onCompleted() { } //当用户关闭页面或离开可试玩广告时调用
|
|
856
|
+
//////// 这两个函数在PlayableSDK中自动调用
|
|
857
|
+
static onDisplay() { } // 加载完显示也是游戏开始
|
|
858
|
+
static onInstall(type) { }; //下载安装
|
|
859
|
+
|
|
860
|
+
static androidUrl = ""
|
|
861
|
+
static iosUrl = ""
|
|
862
|
+
|
|
863
|
+
static designate_platform = '';
|
|
864
|
+
static set DesignatePlatform(value) {
|
|
865
|
+
PlayableSDK.designate_platform = value;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
static isFirstClick = false;
|
|
869
|
+
|
|
870
|
+
static _platform = {
|
|
871
|
+
Aarki: 'Aarki',
|
|
872
|
+
AdColony: 'AdColony',
|
|
873
|
+
Applovin: 'Applovin',
|
|
874
|
+
Bigo: 'Bigo',
|
|
875
|
+
Chartboost: 'Chartboost',
|
|
876
|
+
Facebook: 'Facebook',
|
|
877
|
+
Google: 'Google',
|
|
878
|
+
Ironsource: 'Ironsource',
|
|
879
|
+
Kwai: 'Kwai',
|
|
880
|
+
Liftoff: 'Liftoff',
|
|
881
|
+
Mintegral: 'Mintegral',
|
|
882
|
+
Mytarget: 'Mytarget',
|
|
883
|
+
Moloco: 'Moloco',
|
|
884
|
+
Pangle: 'Pangle',
|
|
885
|
+
Snapchat: 'Snapchat',
|
|
886
|
+
Tapjoy: 'Tapjoy',
|
|
887
|
+
Tiktok: 'Tiktok',
|
|
888
|
+
Unity: 'Unity',
|
|
889
|
+
Vungle: 'Vungle',
|
|
890
|
+
Wechat: 'Wechat',
|
|
891
|
+
Douyin: 'Douyin',
|
|
892
|
+
Preview: 'Preview',
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
static get platform() {
|
|
896
|
+
if (!is_null_string(PlayableSDK.designate_platform)) {
|
|
897
|
+
return PlayableSDK.designate_platform;
|
|
898
|
+
}
|
|
899
|
+
if ('__PLATFORM' in window) {
|
|
900
|
+
if (window.__PLATFORM) return PlayableSDK._platform[window.__PLATFORM];
|
|
901
|
+
}
|
|
902
|
+
if (window.wx) {
|
|
903
|
+
return PlayableSDK._platform.Wechat;
|
|
904
|
+
} else if (window.tt) {
|
|
905
|
+
return PlayableSDK._platform.Douyin;
|
|
906
|
+
}
|
|
907
|
+
return PlayableSDK._platform.Preview;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* 上报游戏进度
|
|
912
|
+
* @param progress 游戏进度,取值固定0,25,50,75,100
|
|
913
|
+
*/
|
|
914
|
+
static game_progress(progress: number) {
|
|
915
|
+
PlayableSDK.trackCustomEvent('challenge progress', { progress: progress });
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
static hideLoadingBar() {
|
|
919
|
+
if (window.setLoadingProgress) {
|
|
920
|
+
window.setLoadingProgress(100);
|
|
921
|
+
}
|
|
922
|
+
if (window.AnalyticsIns && window.AnalyticsIns.onDisplay) {
|
|
923
|
+
window.AnalyticsIns.onDisplay();
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
static getGameConfs(key) {
|
|
928
|
+
if ('GameConfs' in window) {
|
|
929
|
+
return window.GameConfs[key];
|
|
930
|
+
} else {
|
|
931
|
+
return null;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
static download(type = 'download') {
|
|
936
|
+
if (window.xsd_playable) {
|
|
937
|
+
window.xsd_playable.download();
|
|
938
|
+
}
|
|
939
|
+
else if (PlayableSDK.platform === PlayableSDK._platform.Wechat) {
|
|
940
|
+
window.wx.notifyMiniProgramPlayableStatus({ isEnd: true });
|
|
941
|
+
}
|
|
942
|
+
else if (PlayableSDK.platform === PlayableSDK._platform.Douyin) {
|
|
943
|
+
// 目前是30秒自动跳,没有主动跳转
|
|
944
|
+
window.tt.notifyMiniProgramPlayableStatus({
|
|
945
|
+
success(res) {
|
|
946
|
+
},
|
|
947
|
+
fail(res) {
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
if (window.AnalyticsIns && window.AnalyticsIns.onInstall) {
|
|
952
|
+
window.AnalyticsIns.onInstall(type);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* 上报自定义事件,对于不同厂家的要求,均可以用这个来自己实现
|
|
958
|
+
* @param eventName 事件名称
|
|
959
|
+
* @param params 事件参数,字典对象
|
|
960
|
+
*/
|
|
961
|
+
static trackCustomEvent(eventName, params) {
|
|
962
|
+
if (window.AnalyticsIns) {
|
|
963
|
+
window.AnalyticsIns.trackCustomEvent(eventName, params);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* 当游戏进入结束状态(如显示得分、结算界面等)时调用
|
|
969
|
+
* @param result 本次游戏结果,通关win、通关失败lose
|
|
970
|
+
*/
|
|
971
|
+
static onShowEndCard(result = 'win') {
|
|
972
|
+
if (window.AnalyticsIns) {
|
|
973
|
+
window.AnalyticsIns.onShowEndCard(result)
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
/**
|
|
978
|
+
* 当玩家点击重新开始游戏时调用
|
|
979
|
+
*/
|
|
980
|
+
static onRetry() {
|
|
981
|
+
if (window.AnalyticsIns) {
|
|
982
|
+
window.AnalyticsIns.onRetry()
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
static adapter() {
|
|
987
|
+
window.xsd_playable && window.xsd_playable.adapter()
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
static gameReady() {
|
|
991
|
+
window.xsd_playable && window.xsd_playable.gameReady()
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
static gameDownload() {
|
|
995
|
+
window.xsd_playable && window.xsd_playable.install()
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
static gameEnd() {
|
|
999
|
+
window.xsd_playable && window.xsd_playable.gameEnd()
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
static onInteracted() {
|
|
1003
|
+
if (!this.isFirstClick) {
|
|
1004
|
+
window.xsd_playable && window.xsd_playable.onInteracted();
|
|
1005
|
+
this.isFirstClick = true;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
static unicodeEncode(t) {
|
|
1010
|
+
for (var e = "", o = 0; o < t.length; o++) {
|
|
1011
|
+
var n = t.charCodeAt(o);
|
|
1012
|
+
e += "\\u" + this.padLeft(n.toString(16), "0", 4)
|
|
1013
|
+
}
|
|
1014
|
+
return e
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
static unicodeDecode(t) {
|
|
1018
|
+
return t.replace(/\\u([\d\w]{4})/gi, function (t, e) {
|
|
1019
|
+
return String.fromCharCode(parseInt(e, 16))
|
|
1020
|
+
})
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
static padLeft(t, e, o) {
|
|
1024
|
+
for (; t.length < o;) t = e + t;
|
|
1025
|
+
return t
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
export class Language {
|
|
1031
|
+
static _code = '';
|
|
1032
|
+
static init(code = '', texture_base_path = 'Internationalization', end_call = null) {
|
|
1033
|
+
if (is_null_string(code)) {
|
|
1034
|
+
Language._code = Language._get();
|
|
1035
|
+
} else {
|
|
1036
|
+
Language._code = code;
|
|
1037
|
+
}
|
|
1038
|
+
if (is_null_string(texture_base_path)) {
|
|
1039
|
+
texture_base_path = 'Internationalization';
|
|
1040
|
+
}
|
|
1041
|
+
resources.loadDir(texture_base_path + '/' + Language._code, () => {
|
|
1042
|
+
end_call && end_call();
|
|
1043
|
+
})
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
static get code() {
|
|
1047
|
+
if (is_null_string(Language._code)) {
|
|
1048
|
+
Language.init(KOREA ? 'ko' : '');
|
|
1049
|
+
}
|
|
1050
|
+
return Language._code;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
static _get() {
|
|
1054
|
+
if (Language._code) return Language._code;
|
|
1055
|
+
let code = "";
|
|
1056
|
+
switch (sys.languageCode) {
|
|
1057
|
+
//繁中
|
|
1058
|
+
case 'zh-tw':
|
|
1059
|
+
case 'zh-hk':
|
|
1060
|
+
code = 'zh-c';
|
|
1061
|
+
break;
|
|
1062
|
+
//简中
|
|
1063
|
+
case 'zh':
|
|
1064
|
+
case 'zh-cn':
|
|
1065
|
+
case 'zh_CN':
|
|
1066
|
+
code = 'zh-s';
|
|
1067
|
+
break;
|
|
1068
|
+
//未知
|
|
1069
|
+
case 'unknown':
|
|
1070
|
+
code = 'en';
|
|
1071
|
+
break;
|
|
1072
|
+
default:
|
|
1073
|
+
if (sys.languageCode.includes('-')) {
|
|
1074
|
+
code = sys.languageCode.split("-")[0];
|
|
1075
|
+
} else {
|
|
1076
|
+
code = sys.languageCode;
|
|
1077
|
+
}
|
|
1078
|
+
break;
|
|
1079
|
+
}
|
|
1080
|
+
Language._code = code;
|
|
1081
|
+
return Language._code;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
@ccclass('InternationalizationConfig')
|
|
1086
|
+
export class InternationalizationConfig {
|
|
1087
|
+
|
|
1088
|
+
@property({ displayName: "简中", group: '文本' })
|
|
1089
|
+
zh_s = '';
|
|
1090
|
+
@property({ displayName: "繁中", group: '文本' })
|
|
1091
|
+
zh_c = '';
|
|
1092
|
+
@property({ displayName: "英语", group: '文本' })
|
|
1093
|
+
en = '';
|
|
1094
|
+
@property({ displayName: "意大利", group: '文本' })
|
|
1095
|
+
it = '';
|
|
1096
|
+
@property({ displayName: "西班牙", group: '文本' })
|
|
1097
|
+
es = '';
|
|
1098
|
+
@property({ displayName: "荷兰", group: '文本' })
|
|
1099
|
+
du = '';
|
|
1100
|
+
@property({ displayName: "匈牙利", group: '文本' })
|
|
1101
|
+
hu = '';
|
|
1102
|
+
@property({ displayName: "葡萄牙", group: '文本' })
|
|
1103
|
+
pt = '';
|
|
1104
|
+
@property({ displayName: "挪威", group: '文本' })
|
|
1105
|
+
no = '';
|
|
1106
|
+
@property({ displayName: "波兰", group: '文本' })
|
|
1107
|
+
pl = '';
|
|
1108
|
+
@property({ displayName: "土耳其", group: '文本' })
|
|
1109
|
+
tr = '';
|
|
1110
|
+
@property({ displayName: "乌克兰", group: '文本' })
|
|
1111
|
+
uk = '';
|
|
1112
|
+
@property({ displayName: "罗马尼亚", group: '文本' })
|
|
1113
|
+
ro = '';
|
|
1114
|
+
@property({ displayName: "保加利亚", group: '文本' })
|
|
1115
|
+
bg = '';
|
|
1116
|
+
@property({ displayName: "印度", group: '文本' })
|
|
1117
|
+
hi = '';
|
|
1118
|
+
@property({ displayName: "未知", group: '文本' })
|
|
1119
|
+
unknown = '';
|
|
1120
|
+
@property({ displayName: "法国", group: '文本' })
|
|
1121
|
+
fr = '';
|
|
1122
|
+
@property({ displayName: "德国", group: '文本' })
|
|
1123
|
+
de = '';
|
|
1124
|
+
@property({ displayName: "俄语", group: '文本' })
|
|
1125
|
+
ru = '';
|
|
1126
|
+
@property({ displayName: "日本", group: '文本' })
|
|
1127
|
+
ja = '';
|
|
1128
|
+
@property({ displayName: "阿拉伯", group: '文本' })
|
|
1129
|
+
ar = '';
|
|
1130
|
+
@property({ displayName: "韩国", group: '文本' })
|
|
1131
|
+
ko = '';
|
|
1132
|
+
@property({ displayName: "越南", group: '文本' })
|
|
1133
|
+
vi = '';
|
|
1134
|
+
|
|
1135
|
+
string_map = null;
|
|
1136
|
+
get(code) {
|
|
1137
|
+
if (this.string_map == null) {
|
|
1138
|
+
this.set();
|
|
1139
|
+
}
|
|
1140
|
+
return this.string_map[code] && !is_null_string(this.string_map[code]) ? this.string_map[code] : this.string_map.en;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
set() {
|
|
1144
|
+
this.string_map = {};
|
|
1145
|
+
this.string_map['zh-s'] = this.zh_s;
|
|
1146
|
+
this.string_map['zh-c'] = this.zh_c;
|
|
1147
|
+
this.string_map['en'] = this.en;
|
|
1148
|
+
this.string_map['it'] = this.it;
|
|
1149
|
+
this.string_map['es'] = this.es;
|
|
1150
|
+
this.string_map['du'] = this.du;
|
|
1151
|
+
this.string_map['hu'] = this.hu;
|
|
1152
|
+
this.string_map['pt'] = this.pt;
|
|
1153
|
+
this.string_map['no'] = this.no;
|
|
1154
|
+
this.string_map['pl'] = this.pl;
|
|
1155
|
+
this.string_map['tr'] = this.tr;
|
|
1156
|
+
this.string_map['uk'] = this.uk;
|
|
1157
|
+
this.string_map['ro'] = this.ro;
|
|
1158
|
+
this.string_map['bg'] = this.bg;
|
|
1159
|
+
this.string_map['hi'] = this.hi;
|
|
1160
|
+
this.string_map['fr'] = this.fr;
|
|
1161
|
+
this.string_map['de'] = this.de;
|
|
1162
|
+
this.string_map['ru'] = this.ru;
|
|
1163
|
+
this.string_map['ja'] = this.ja;
|
|
1164
|
+
this.string_map['ar'] = this.ar;
|
|
1165
|
+
this.string_map['ko'] = this.ko;
|
|
1166
|
+
this.string_map['vi'] = this.vi;
|
|
1167
|
+
this.string_map['unknown'] = this.unknown;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
@ccclass('PlatformConfig')
|
|
1172
|
+
export class PlatformConfig {
|
|
1173
|
+
@property({ displayName: '动画', tooltip: '勾选后会保留节点的动画组件' })
|
|
1174
|
+
Animation = true;
|
|
1175
|
+
@property({ displayName: '可见', tooltip: '勾选后会显示节点' })
|
|
1176
|
+
Visable = true;
|
|
1177
|
+
@property({ displayName: '精灵', tooltip: '勾选后会强制变更纹理' })
|
|
1178
|
+
Sprite = false;
|
|
1179
|
+
@property({ displayName: '纹理路径', tooltip: '指定纹理路径', visible: function (this) { return this.Sprite } })
|
|
1180
|
+
path = '';
|
|
1181
|
+
@property({ displayName: '着色器', tooltip: '勾选后会修改shader的属性' })
|
|
1182
|
+
Shader = false;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
@ccclass('Internationalization')
|
|
1186
|
+
export class Internationalization extends Component {
|
|
1187
|
+
|
|
1188
|
+
@property({ displayName: '预览', group: '平台处理', tooltip: '预览启用的规则名字,默认Google' })
|
|
1189
|
+
preview = 'Google';
|
|
1190
|
+
@property({ type: PlatformConfig, displayName: 'Google', group: '平台处理' })
|
|
1191
|
+
google = new PlatformConfig();
|
|
1192
|
+
@property({ type: PlatformConfig, displayName: 'Wechat', group: '平台处理' })
|
|
1193
|
+
wechat = new PlatformConfig();
|
|
1194
|
+
@property({ type: PlatformConfig, displayName: '抖音', group: '平台处理' })
|
|
1195
|
+
douyin = new PlatformConfig();
|
|
1196
|
+
@property({ type: InternationalizationConfig, displayName: '国际化文本', group: '国际化', visible: function (this) { return this.getComponent(Label) != null; } })
|
|
1197
|
+
config = new InternationalizationConfig();
|
|
1198
|
+
|
|
1199
|
+
scale = null;
|
|
1200
|
+
size = null;
|
|
1201
|
+
|
|
1202
|
+
onLoad() {
|
|
1203
|
+
//优先处理平台设定
|
|
1204
|
+
if (!this.platform_deploy()) {
|
|
1205
|
+
this.internationalization_deploy();
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
/**
|
|
1210
|
+
* 按渠道部署
|
|
1211
|
+
* @returns
|
|
1212
|
+
*/
|
|
1213
|
+
platform_deploy() {
|
|
1214
|
+
switch (PlayableSDK.platform) {
|
|
1215
|
+
case PlayableSDK._platform.Google:
|
|
1216
|
+
return this.google_deploy();
|
|
1217
|
+
case PlayableSDK._platform.Wechat:
|
|
1218
|
+
return this.wechat_deploy();
|
|
1219
|
+
case PlayableSDK._platform.Douyin:
|
|
1220
|
+
return this.douyin_deploy();
|
|
1221
|
+
case PlayableSDK._platform.Preview:
|
|
1222
|
+
switch (this.preview) {
|
|
1223
|
+
case PlayableSDK._platform.Google:
|
|
1224
|
+
return this.google_deploy();
|
|
1225
|
+
case PlayableSDK._platform.Wechat:
|
|
1226
|
+
return this.wechat_deploy();
|
|
1227
|
+
case PlayableSDK._platform.Douyin:
|
|
1228
|
+
return this.douyin_deploy();
|
|
1229
|
+
}
|
|
1230
|
+
break;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
/**
|
|
1235
|
+
* 部署Google平台
|
|
1236
|
+
* @returns
|
|
1237
|
+
*/
|
|
1238
|
+
google_deploy() {
|
|
1239
|
+
let deploy_visible_or_sprite = false;
|
|
1240
|
+
if (!this.google.Animation) {
|
|
1241
|
+
//需要禁用动画组件
|
|
1242
|
+
let animation = this.getComponent(Animation);
|
|
1243
|
+
if (animation != null) {
|
|
1244
|
+
animation.enabled = false;
|
|
1245
|
+
} else {
|
|
1246
|
+
console.error('Google平台禁用动画组件失败,动画组件未获取');
|
|
1247
|
+
}
|
|
1248
|
+
} else {
|
|
1249
|
+
if (!this.getComponent(Animation).enabled) {
|
|
1250
|
+
this.getComponent(Animation).enabled = true;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
if (!this.google.Visable) {
|
|
1254
|
+
//需要禁用节点
|
|
1255
|
+
this.node.active = false;
|
|
1256
|
+
deploy_visible_or_sprite = true;
|
|
1257
|
+
}
|
|
1258
|
+
if (this.google.Sprite) {
|
|
1259
|
+
//需要变更纹理
|
|
1260
|
+
this.sprite_deploy(`${this.google.path}/spriteFrame`);
|
|
1261
|
+
deploy_visible_or_sprite = true;
|
|
1262
|
+
}
|
|
1263
|
+
if (this.google.Shader) {
|
|
1264
|
+
this.getComponent(Sprite).getMaterialInstance(0).setProperty('speedScale', 0);
|
|
1265
|
+
}
|
|
1266
|
+
return deploy_visible_or_sprite;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
/**
|
|
1270
|
+
* 部署Wechat平台
|
|
1271
|
+
* @returns
|
|
1272
|
+
*/
|
|
1273
|
+
wechat_deploy() {
|
|
1274
|
+
let deploy_visible_or_sprite = false;
|
|
1275
|
+
if (!this.wechat.Animation) {
|
|
1276
|
+
//需要禁用动画组件
|
|
1277
|
+
let animation = this.getComponent(Animation);
|
|
1278
|
+
if (animation != null) {
|
|
1279
|
+
animation.enabled = false;
|
|
1280
|
+
} else {
|
|
1281
|
+
console.error('Wechat平台禁用动画组件失败,动画组件未获取');
|
|
1282
|
+
}
|
|
1283
|
+
} else {
|
|
1284
|
+
if (!this.getComponent(Animation).enabled) {
|
|
1285
|
+
this.getComponent(Animation).enabled = true;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
if (!this.wechat.Visable) {
|
|
1289
|
+
//需要禁用节点
|
|
1290
|
+
this.node.active = false;
|
|
1291
|
+
deploy_visible_or_sprite = true;
|
|
1292
|
+
}
|
|
1293
|
+
if (this.wechat.Sprite) {
|
|
1294
|
+
//需要变更纹理
|
|
1295
|
+
this.sprite_deploy(`${this.wechat.path}/spriteFrame`);
|
|
1296
|
+
deploy_visible_or_sprite = true;
|
|
1297
|
+
}
|
|
1298
|
+
if (this.wechat.Shader) {
|
|
1299
|
+
this.getComponent(Sprite).getMaterialInstance(0).setProperty('speedScale', 0);
|
|
1300
|
+
}
|
|
1301
|
+
return deploy_visible_or_sprite;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
/**
|
|
1305
|
+
* 部署抖音平台
|
|
1306
|
+
* @returns
|
|
1307
|
+
*/
|
|
1308
|
+
douyin_deploy() {
|
|
1309
|
+
let deploy_visible_or_sprite = false;
|
|
1310
|
+
if (!this.douyin.Animation) {
|
|
1311
|
+
//需要禁用动画组件
|
|
1312
|
+
let animation = this.getComponent(Animation);
|
|
1313
|
+
if (animation != null) {
|
|
1314
|
+
animation.enabled = false;
|
|
1315
|
+
} else {
|
|
1316
|
+
console.error('抖音平台禁用动画组件失败,动画组件未获取');
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
if (!this.douyin.Visable) {
|
|
1320
|
+
//需要禁用节点
|
|
1321
|
+
this.node.active = false;
|
|
1322
|
+
deploy_visible_or_sprite = true;
|
|
1323
|
+
}
|
|
1324
|
+
if (this.douyin.Sprite) {
|
|
1325
|
+
//需要变更纹理
|
|
1326
|
+
this.sprite_deploy(`${this.douyin.path}/spriteFrame`);
|
|
1327
|
+
deploy_visible_or_sprite = true;
|
|
1328
|
+
}
|
|
1329
|
+
if (this.douyin.Shader) {
|
|
1330
|
+
this.getComponent(Sprite).getMaterialInstance(0).setProperty('speedScale', 0);
|
|
1331
|
+
}
|
|
1332
|
+
return deploy_visible_or_sprite;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
/**
|
|
1336
|
+
* 变更精灵
|
|
1337
|
+
* @param path 精灵路径
|
|
1338
|
+
*/
|
|
1339
|
+
sprite_deploy(path) {
|
|
1340
|
+
let sprite = this.getComponent(Sprite);
|
|
1341
|
+
let self = this;
|
|
1342
|
+
let adaptive = () => {
|
|
1343
|
+
let new_size = self.node.getComponent(UITransform).contentSize.clone();
|
|
1344
|
+
let _w = self.size.width / new_size.width, _h = self.size.height / new_size.height;
|
|
1345
|
+
self.node.scale = self.scale.clone().multiplyScalar(Math.min(_w, _h));
|
|
1346
|
+
if (self.node.scale.x > self.scale.x || self.node.scale.y > self.scale.y) {
|
|
1347
|
+
self.node.scale = self.scale;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
if (sprite != null) {
|
|
1351
|
+
this.scale = this.node.scale.clone();
|
|
1352
|
+
this.size = this.node.getComponent(UITransform).contentSize.clone();
|
|
1353
|
+
let sprite_frame = resources.get(path, SpriteFrame);
|
|
1354
|
+
if (sprite_frame) {
|
|
1355
|
+
sprite.spriteFrame = sprite_frame;
|
|
1356
|
+
adaptive();
|
|
1357
|
+
} else {
|
|
1358
|
+
resources.load(path, SpriteFrame, (err, sprite_frame) => {
|
|
1359
|
+
if (err) {
|
|
1360
|
+
console.error('变更精灵失败,纹理未加载', err);
|
|
1361
|
+
} else {
|
|
1362
|
+
sprite.spriteFrame = sprite_frame;
|
|
1363
|
+
adaptive();
|
|
1364
|
+
}
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
} else {
|
|
1368
|
+
console.error('变更精灵失败,精灵组件未获取');
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
/**
|
|
1373
|
+
* 按国际化部署
|
|
1374
|
+
* @returns
|
|
1375
|
+
*/
|
|
1376
|
+
internationalization_deploy() {
|
|
1377
|
+
let label = this.node.getComponent(Label);
|
|
1378
|
+
if (label) {
|
|
1379
|
+
label.string = this.config.get(Language.code);
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
let sprite = this.getComponent(Sprite);
|
|
1383
|
+
let uuid = sprite.spriteFrame.uuid;
|
|
1384
|
+
let info : any = resources.getAssetInfo(uuid);
|
|
1385
|
+
let code = Language.code;
|
|
1386
|
+
if (this.size == null) {
|
|
1387
|
+
this.scale = this.node.scale.clone();
|
|
1388
|
+
this.size = this.node.getComponent(UITransform).contentSize.clone();
|
|
1389
|
+
}
|
|
1390
|
+
if (info && info.path) {
|
|
1391
|
+
let path = info.path;
|
|
1392
|
+
path = path.replace('/en/', '/' + code + '/');
|
|
1393
|
+
this.sprite_deploy(path);
|
|
1394
|
+
} else {
|
|
1395
|
+
console.log('国际化更换纹理失败,未查询到原纹理信息', info);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
//#endregion
|
|
1400
|
+
|
|
1401
|
+
//#region 消息机
|
|
1402
|
+
export class EventSystem {
|
|
1403
|
+
|
|
1404
|
+
_event_target = new EventTarget();
|
|
1405
|
+
_async_delegates = new Map();
|
|
1406
|
+
|
|
1407
|
+
/**
|
|
1408
|
+
* 注册事件目标的特定事件类型回调。这种类型的事件应该被 `emit` 触发。
|
|
1409
|
+
* @param event_name 事件名
|
|
1410
|
+
* @param callback 回调函数
|
|
1411
|
+
* @param target 目标对象
|
|
1412
|
+
* @param once 是否只触发一次
|
|
1413
|
+
*/
|
|
1414
|
+
on(event_name: string, callback: (...args: any[]) => void, target?, once?) {
|
|
1415
|
+
this._event_target.on(event_name, callback , target,once);
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
/**
|
|
1419
|
+
* 注册事件目标的特定事件类型回调,回调会在第一时间被触发后删除自身。
|
|
1420
|
+
* @param event_name 事件名
|
|
1421
|
+
* @param callback 回调函数
|
|
1422
|
+
* @param target 目标对象
|
|
1423
|
+
*/
|
|
1424
|
+
once(event_name: string, callback: (...args: any[]) => void, target?) {
|
|
1425
|
+
this._event_target.once(event_name, callback, target);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
/**
|
|
1429
|
+
* 注册同步事件目标的特定事件类型回调。这种类型的事件应该被 `emit_async` 触发。支持await等待。
|
|
1430
|
+
* @param event_name 事件名
|
|
1431
|
+
* @param callback 回调函数
|
|
1432
|
+
*/
|
|
1433
|
+
on_async(event_name: string, callback: (...args: any[]) => void) {
|
|
1434
|
+
if (!this._async_delegates.has(event_name)) {
|
|
1435
|
+
this._async_delegates.set(event_name, new AsyncDelegate());
|
|
1436
|
+
}
|
|
1437
|
+
this._async_delegates.get(event_name)?.add(callback);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
/**
|
|
1441
|
+
* 派发一个指定事件,并传递需要的参数
|
|
1442
|
+
* @param event_name 事件名
|
|
1443
|
+
* @param args 事件参数
|
|
1444
|
+
*/
|
|
1445
|
+
emit(event_name: string, ...args) {
|
|
1446
|
+
this._event_target.emit(event_name, ...args);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
|
|
1450
|
+
/**
|
|
1451
|
+
* 派发一个指定事件,并传递需要的参数,支持await等待。
|
|
1452
|
+
* @param event_name 事件名
|
|
1453
|
+
* @param args 事件参数
|
|
1454
|
+
*/
|
|
1455
|
+
async emit_async(event_name: string, ...args) {
|
|
1456
|
+
await this._async_delegates.get(event_name)?.dispatch(...args);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
/**
|
|
1460
|
+
* 删除之前用同类型,回调,目标或 useCapture 注册的事件监听器,如果只传递 type,将会删除 type 类型的所有事件监听器。
|
|
1461
|
+
* @param event_name 事件名
|
|
1462
|
+
* @param callback 回调函数
|
|
1463
|
+
* @param target 目标对象
|
|
1464
|
+
*/
|
|
1465
|
+
off(event_name: string, callback = null, target = null) {
|
|
1466
|
+
if (callback) {
|
|
1467
|
+
this._event_target.off(event_name, callback, target);
|
|
1468
|
+
} else {
|
|
1469
|
+
this._event_target.removeAll(event_name);
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
/**
|
|
1474
|
+
* 在当前 target 上删除指定目标(target 参数)注册的所有事件监听器。
|
|
1475
|
+
* 这个函数无法删除当前 target 的所有事件监听器,也无法删除 target 参数所注册的所有事件监听器。
|
|
1476
|
+
* 这个函数只能删除 target 参数在当前 target 上注册的所有事件监听器。
|
|
1477
|
+
* @param target 目标对象
|
|
1478
|
+
*/
|
|
1479
|
+
target_off(target) {
|
|
1480
|
+
this._event_target.targetOff(target);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
/**
|
|
1484
|
+
* @zh 移除在特定事件类型中注册的所有回调或在某个目标中注册的所有回调。
|
|
1485
|
+
* @param type_or_target 事件类型或目标对象
|
|
1486
|
+
*/
|
|
1487
|
+
remove_all(type_or_target: string | EventTarget) {
|
|
1488
|
+
this._event_target.removeAll(type_or_target);
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
//#endregion
|
|
1494
|
+
|
|
1495
|
+
//#region 对象池
|
|
1496
|
+
|
|
1497
|
+
class NodePoolMore extends NodePool {
|
|
1498
|
+
|
|
1499
|
+
static scheduler = null;
|
|
1500
|
+
full_size = 0;
|
|
1501
|
+
prefab = null;
|
|
1502
|
+
progress = -1;
|
|
1503
|
+
supplement = false;
|
|
1504
|
+
expand = false;
|
|
1505
|
+
|
|
1506
|
+
/**
|
|
1507
|
+
* @param _prefab 预制体
|
|
1508
|
+
* @param prog 进度
|
|
1509
|
+
* @param _full 最大数量
|
|
1510
|
+
* @param _expand 是否自动扩展
|
|
1511
|
+
* @param auto 是否自动实例化
|
|
1512
|
+
* @param pool_handler 事件组件
|
|
1513
|
+
*/
|
|
1514
|
+
constructor(_prefab, prog, _full, _expand = true, auto = false, pool_handler = null) {
|
|
1515
|
+
super(pool_handler);
|
|
1516
|
+
if (!NodePoolMore.scheduler) {
|
|
1517
|
+
let node = new Node();
|
|
1518
|
+
node.name = `NodePoolMoreScheduler`;
|
|
1519
|
+
director.addPersistRootNode(node);
|
|
1520
|
+
NodePoolMore.scheduler = node.addComponent(Component);
|
|
1521
|
+
}
|
|
1522
|
+
this.progress = prog;
|
|
1523
|
+
this.full_size = _full;
|
|
1524
|
+
this.expand = _expand;
|
|
1525
|
+
if (_prefab instanceof Prefab) {
|
|
1526
|
+
this.prefab = _prefab;
|
|
1527
|
+
if (auto) {
|
|
1528
|
+
generator(this.get_item_generator(0, this.full_size, this.prefab, this), 8, NodePoolMore.scheduler);
|
|
1529
|
+
}
|
|
1530
|
+
} else {
|
|
1531
|
+
this.load_prefab(_prefab, auto);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
/**
|
|
1536
|
+
* 绑定对象池事件组件
|
|
1537
|
+
* @param pool_handler 事件组件
|
|
1538
|
+
*/
|
|
1539
|
+
set_handler(pool_handler) {
|
|
1540
|
+
this.poolHandlerComp = pool_handler;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
/**
|
|
1544
|
+
* 加载预制体
|
|
1545
|
+
* @param path 预制体路径
|
|
1546
|
+
* @param auto 是否自动实例化
|
|
1547
|
+
*/
|
|
1548
|
+
async load_prefab(path: string, auto = false) {
|
|
1549
|
+
let _prefab = await load_resources(path);
|
|
1550
|
+
this.prefab = _prefab;
|
|
1551
|
+
if (auto) {
|
|
1552
|
+
generator(this.get_item_generator(0, this.full_size, this.prefab, this), 8, NodePoolMore.scheduler);
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
async instantiate_pool() {
|
|
1557
|
+
await generator(this.get_item_generator(0, this.full_size, this.prefab, this), 8, NodePoolMore.scheduler);
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
get(...args) {
|
|
1561
|
+
let item = super.get(args);
|
|
1562
|
+
if (this.size() < this.full_size * this.progress && !this.supplement) {
|
|
1563
|
+
//补充
|
|
1564
|
+
this.supplement_example();
|
|
1565
|
+
}
|
|
1566
|
+
if (!item) {
|
|
1567
|
+
item = instantiate(this.prefab);
|
|
1568
|
+
}
|
|
1569
|
+
item.active = true;
|
|
1570
|
+
return item;
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
async supplement_example() {
|
|
1574
|
+
this.supplement = true;
|
|
1575
|
+
if (this.expand) {
|
|
1576
|
+
this.full_size *= 2;
|
|
1577
|
+
console.log(`will expand : ${this.full_size},prefab : ${this.prefab.name}`);
|
|
1578
|
+
}
|
|
1579
|
+
console.log(`will supplement example : ${this.full_size - this.size()},prefab : ${this.prefab.name}`);
|
|
1580
|
+
await generator(this.get_item_generator(0, this.full_size - this.size(), this.prefab, this), 8, NodePoolMore.scheduler);
|
|
1581
|
+
this.supplement = false;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
*get_item_generator(start, length, prefab, pool) {
|
|
1585
|
+
for (var i = start; i < length; i++) {
|
|
1586
|
+
yield this.create_item(prefab, pool);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
create_item(prefab, pool) {
|
|
1591
|
+
var item = instantiate(prefab);
|
|
1592
|
+
pool.put(item);
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
export class PoolManager {
|
|
1598
|
+
|
|
1599
|
+
pools = {};
|
|
1600
|
+
|
|
1601
|
+
async init_pool(config) {
|
|
1602
|
+
let start_time_gen = game.totalTime;
|
|
1603
|
+
for (let i = 0; i < config.pool.length; ++i) {
|
|
1604
|
+
if (config.pool[i].auto_example) {
|
|
1605
|
+
this.free_pool(config.pool[i]);
|
|
1606
|
+
} else {
|
|
1607
|
+
await this.load_pool(config.pool[i]);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
console.log(`prefab and nodepool complete ,start time : ${start_time_gen} ,time consuming ${game.totalTime - start_time_gen} ms`);
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
async free_pool(config) {
|
|
1614
|
+
console.log(`free load pool ${config.name},count : ${config.count}`);
|
|
1615
|
+
let prefab = await load_resources(config.path);
|
|
1616
|
+
this.pools[config.name] = this.load_prefab_and_instantiate_pool(config, prefab);
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
async load_pool(config) {
|
|
1620
|
+
console.log(`async load pool ${config.name},count : ${config.count}`);
|
|
1621
|
+
let prefab = await load_resources(config.path);
|
|
1622
|
+
this.pools[config.name] = this.load_prefab_and_instantiate_pool(config, prefab);
|
|
1623
|
+
await this.pools[config.name].instantiate_pool();
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
load_prefab_and_instantiate_pool(config, prefab) {
|
|
1627
|
+
return new NodePoolMore(prefab, config.auto_supplement, config.count, config.auto_expansion, config.auto_example, config.handler);
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
/**从对象池获取实例 */
|
|
1631
|
+
get_item_from_pool(pool) {
|
|
1632
|
+
let item = this.pools[pool].get();
|
|
1633
|
+
return item;
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
/**将实例放回对象池 */
|
|
1637
|
+
destroy_item(item, pool) {
|
|
1638
|
+
this.pools[pool].put(item);
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
//#endregion
|
|
1644
|
+
|
|
1645
|
+
//#region 防怠机
|
|
1646
|
+
export class PlacingSlack {
|
|
1647
|
+
static thread = -1;
|
|
1648
|
+
static start_time = 0;
|
|
1649
|
+
static remaining_time = 0;
|
|
1650
|
+
static callback = null;
|
|
1651
|
+
|
|
1652
|
+
static enabled(time, callback) {
|
|
1653
|
+
PlacingSlack.start_time = game.totalTime;
|
|
1654
|
+
PlacingSlack.callback = callback;
|
|
1655
|
+
PlacingSlack.thread = setTimeout(() => {
|
|
1656
|
+
PlacingSlack.callback?.();
|
|
1657
|
+
}, time);
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
static cancel() {
|
|
1661
|
+
if (PlacingSlack.thread == -1) {
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
clearTimeout(PlacingSlack.thread);
|
|
1665
|
+
PlacingSlack.thread = -1;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
static pause() {
|
|
1669
|
+
if (PlacingSlack.thread == -1) {
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
clearTimeout(PlacingSlack.thread);
|
|
1673
|
+
PlacingSlack.thread = -1;
|
|
1674
|
+
PlacingSlack.remaining_time = game.totalTime - PlacingSlack.start_time;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
static resume() {
|
|
1678
|
+
if (PlacingSlack.remaining_time == -1) {
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
PlacingSlack.thread = setTimeout(() => {
|
|
1682
|
+
PlacingSlack.remaining_time = -1;
|
|
1683
|
+
PlacingSlack.callback?.();
|
|
1684
|
+
}, PlacingSlack.remaining_time);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
//#endregion
|
|
1688
|
+
|
|
1689
|
+
//#region 定时器
|
|
1690
|
+
class NewPromise {
|
|
1691
|
+
|
|
1692
|
+
_time = 0;
|
|
1693
|
+
_handler = null;
|
|
1694
|
+
_paused = false;
|
|
1695
|
+
_remaining_time = 0;
|
|
1696
|
+
_start_time = 0;
|
|
1697
|
+
_resume = [];
|
|
1698
|
+
_resolve = null;
|
|
1699
|
+
_reject = null;
|
|
1700
|
+
_promise = null;
|
|
1701
|
+
_timeout_Id = null;
|
|
1702
|
+
_check_time = 100;
|
|
1703
|
+
|
|
1704
|
+
/**
|
|
1705
|
+
* 构造函数
|
|
1706
|
+
* @param time 倒计时时间,单位毫秒
|
|
1707
|
+
* @param check_time 检查时间间隔,单位毫秒
|
|
1708
|
+
*/
|
|
1709
|
+
constructor(time, check_time) {
|
|
1710
|
+
this._time = time;
|
|
1711
|
+
let node = new Node();
|
|
1712
|
+
this._handler = node.addComponent(Component);
|
|
1713
|
+
this._handler.name = 'TimerHandler';
|
|
1714
|
+
this._check_time = check_time;
|
|
1715
|
+
this._paused = false;
|
|
1716
|
+
this._remaining_time = time;
|
|
1717
|
+
this._start_time = 0;
|
|
1718
|
+
this._resume = [];
|
|
1719
|
+
this._promise = new Promise((resolve, reject) => {
|
|
1720
|
+
this._resolve = resolve;
|
|
1721
|
+
this._reject = reject;
|
|
1722
|
+
});
|
|
1723
|
+
this._setup_timer();
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
_setup_timer() {
|
|
1727
|
+
this._start_time = Date.now();
|
|
1728
|
+
this._remaining_time = this._time;
|
|
1729
|
+
this._start_countdown();
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
async _start_countdown() {
|
|
1733
|
+
while (this._remaining_time > 0) {
|
|
1734
|
+
await this._check();
|
|
1735
|
+
if (this._remaining_time <= 0) break;
|
|
1736
|
+
const wait_time = Math.min(this._remaining_time, this._check_time);
|
|
1737
|
+
await this._delay(wait_time);
|
|
1738
|
+
if (!this._paused) {
|
|
1739
|
+
this._remaining_time -= wait_time;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
if (this._remaining_time <= 0 && !this._paused) {
|
|
1743
|
+
this._complete_timer();
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
_delay(ms) {
|
|
1748
|
+
return new Promise(resolve => {
|
|
1749
|
+
this._timeout_Id = setTimeout(() => {
|
|
1750
|
+
this._timeout_Id = null;
|
|
1751
|
+
resolve('');
|
|
1752
|
+
}, ms);
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
async _check() {
|
|
1757
|
+
if (this._paused) {
|
|
1758
|
+
await new Promise(resolve => {
|
|
1759
|
+
this._resume.push(resolve);
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
_complete_timer() {
|
|
1765
|
+
this._handler.name == 'TimerHandler' && this._handler.node.destroy();
|
|
1766
|
+
if (this._timeout_Id) {
|
|
1767
|
+
clearTimeout(this._timeout_Id);
|
|
1768
|
+
this._timeout_Id = null;
|
|
1769
|
+
}
|
|
1770
|
+
this._resolve("complete");
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
/**
|
|
1774
|
+
* 取消倒计时,预览环境中会出现ErrorAlert,无需关注构建后会自动屏蔽
|
|
1775
|
+
*/
|
|
1776
|
+
cancel() {
|
|
1777
|
+
if (this._timeout_Id) {
|
|
1778
|
+
clearTimeout(this._timeout_Id);
|
|
1779
|
+
this._timeout_Id = null;
|
|
1780
|
+
}
|
|
1781
|
+
this._handler.name == 'TimerHandler' && this._handler.node.destroy();
|
|
1782
|
+
this._reject("cancel");
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
/**
|
|
1786
|
+
* 暂停
|
|
1787
|
+
*/
|
|
1788
|
+
pause() {
|
|
1789
|
+
if (this._paused) return;
|
|
1790
|
+
this._paused = true;
|
|
1791
|
+
if (this._timeout_Id) {
|
|
1792
|
+
clearTimeout(this._timeout_Id);
|
|
1793
|
+
this._timeout_Id = null;
|
|
1794
|
+
}
|
|
1795
|
+
if (!this._paused) {
|
|
1796
|
+
const elapsed = Date.now() - this._start_time;
|
|
1797
|
+
this._remaining_time = Math.max(0, this._time - elapsed);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
/**
|
|
1802
|
+
* 恢复
|
|
1803
|
+
*/
|
|
1804
|
+
resume() {
|
|
1805
|
+
if (!this._paused) return;
|
|
1806
|
+
this._paused = false;
|
|
1807
|
+
this._start_time = Date.now();
|
|
1808
|
+
this._resume.forEach(callback => callback());
|
|
1809
|
+
this._resume = [];
|
|
1810
|
+
this._start_countdown();
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
/**
|
|
1814
|
+
* 获取当前倒计时时间
|
|
1815
|
+
* @returns 倒计时时间,单位毫秒
|
|
1816
|
+
*/
|
|
1817
|
+
get_remaining_time() {
|
|
1818
|
+
if (this._paused) {
|
|
1819
|
+
return this._remaining_time;
|
|
1820
|
+
} else {
|
|
1821
|
+
const elapsed = Date.now() - this._start_time;
|
|
1822
|
+
return Math.max(0, this._time - elapsed);
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
/**
|
|
1827
|
+
* 获取原始倒计时
|
|
1828
|
+
* @returns
|
|
1829
|
+
*/
|
|
1830
|
+
get_promise() {
|
|
1831
|
+
return this._promise;
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
then(fulfilled, rejected) {
|
|
1835
|
+
return this._promise.then(fulfilled, rejected);
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
catch(rejected) {
|
|
1839
|
+
return this._promise.catch(rejected);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
/**
|
|
1844
|
+
*
|
|
1845
|
+
* @param {*} time 倒计时时间
|
|
1846
|
+
* @param {*} check_time 检查时间间隔
|
|
1847
|
+
* @returns {Promise<any> & {pause: () => void;resume: () => void;cancel: () => void;get_remaining_time: () => number;}}
|
|
1848
|
+
*/
|
|
1849
|
+
export function timer(time, check_time = 16) {
|
|
1850
|
+
const pausableTimer = new NewPromise(time, check_time);
|
|
1851
|
+
const timer_promise = pausableTimer.get_promise();
|
|
1852
|
+
timer_promise.pause = () => pausableTimer.pause();
|
|
1853
|
+
timer_promise.resume = () => pausableTimer.resume();
|
|
1854
|
+
timer_promise.cancel = () => pausableTimer.cancel();
|
|
1855
|
+
timer_promise.get_remaining_time = () => pausableTimer.get_remaining_time();
|
|
1856
|
+
return timer_promise;
|
|
1857
|
+
}
|
|
1858
|
+
//#endregion
|
|
1859
|
+
|
|
1860
|
+
//#region 生成器
|
|
1861
|
+
/**
|
|
1862
|
+
* 生成器函数,用于按指定时间间隔执行迭代操作
|
|
1863
|
+
* @param generator 生成器函数
|
|
1864
|
+
* @param duration 每个迭代的时间间隔
|
|
1865
|
+
* @param scheduler 调度器组件
|
|
1866
|
+
* @param callback 完成回调
|
|
1867
|
+
* @returns Promise<string>
|
|
1868
|
+
*/
|
|
1869
|
+
export function generator(generator:Generator, duration:number, scheduler = null, callback = null) {
|
|
1870
|
+
let component;
|
|
1871
|
+
if (scheduler) {
|
|
1872
|
+
component = scheduler;
|
|
1873
|
+
} else {
|
|
1874
|
+
let node = new Node();
|
|
1875
|
+
node.setParent(director.getScene());
|
|
1876
|
+
component = node.addComponent(Component);
|
|
1877
|
+
}
|
|
1878
|
+
return new Promise((resolve, reject) => {
|
|
1879
|
+
var gen = generator;
|
|
1880
|
+
var execute = () => {
|
|
1881
|
+
var startTime = new Date().getTime();
|
|
1882
|
+
for (var iter = gen.next(); ; iter = gen.next()) {
|
|
1883
|
+
if (iter == null || iter.done) {
|
|
1884
|
+
/**分帧完成 */
|
|
1885
|
+
if (!scheduler) {
|
|
1886
|
+
component.node.destroy();
|
|
1887
|
+
}
|
|
1888
|
+
callback && callback();
|
|
1889
|
+
resolve("");
|
|
1890
|
+
return;
|
|
1891
|
+
}
|
|
1892
|
+
if (new Date().getTime() - startTime > duration) {
|
|
1893
|
+
component.scheduleOnce(() => {
|
|
1894
|
+
execute();
|
|
1895
|
+
});
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
};
|
|
1900
|
+
// 运行执行函数
|
|
1901
|
+
execute();
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
//#endregion
|
|
1905
|
+
|
|
1906
|
+
//#region 命令
|
|
1907
|
+
/**
|
|
1908
|
+
* 命令函数,用于按指定时间间隔检查条件并执行处理函数
|
|
1909
|
+
* @param check_func 检查函数
|
|
1910
|
+
* @param handler 处理函数
|
|
1911
|
+
* @param interval 检查间隔
|
|
1912
|
+
* @param max_wait 最大等待时间
|
|
1913
|
+
* @returns Promise<string>
|
|
1914
|
+
*/
|
|
1915
|
+
export function command(check_func:Function, handler:Component, interval = 16.6, max_wait = 100000) {
|
|
1916
|
+
return new Promise((resolve, reject) => {
|
|
1917
|
+
let wait_time = 0;
|
|
1918
|
+
try {
|
|
1919
|
+
handler.cancel_thred = () => {
|
|
1920
|
+
reject(`cancel`);
|
|
1921
|
+
handler.cancel_thred = null;
|
|
1922
|
+
};
|
|
1923
|
+
} catch (error) {
|
|
1924
|
+
console.log(error);
|
|
1925
|
+
}
|
|
1926
|
+
const interfal_thread = setInterval(() => {
|
|
1927
|
+
wait_time += interval;
|
|
1928
|
+
if (check_func()) {
|
|
1929
|
+
clearInterval(interfal_thread);
|
|
1930
|
+
resolve('');
|
|
1931
|
+
}
|
|
1932
|
+
if (wait_time >= max_wait) {
|
|
1933
|
+
reject('Time Out');
|
|
1934
|
+
}
|
|
1935
|
+
}, interval);
|
|
1936
|
+
})
|
|
1937
|
+
}
|
|
1938
|
+
//#endregion
|
|
1939
|
+
|
|
1940
|
+
//#region 贝塞尔
|
|
1941
|
+
|
|
1942
|
+
/**
|
|
1943
|
+
* 击退效果
|
|
1944
|
+
* @param target 目标节点
|
|
1945
|
+
* @param direction 击退方向
|
|
1946
|
+
* @param distance 击退距离
|
|
1947
|
+
* @param height 击退高度
|
|
1948
|
+
* @param speed 击退速度
|
|
1949
|
+
* @param control_ratio 控制点比例
|
|
1950
|
+
* @param peak_call 峰值回调
|
|
1951
|
+
* @param complete_call 完成回调
|
|
1952
|
+
*/
|
|
1953
|
+
export function knock_back(target: Node, direction: Vec3, distance: number, height: number, speed: number, control_ratio: number, peak_call = null, complete_call = null) {
|
|
1954
|
+
const normalizedDir = direction.clone().normalize();
|
|
1955
|
+
const start_position = target.position.clone();
|
|
1956
|
+
const complete_position = start_position.clone().add(normalizedDir.multiplyScalar(distance));
|
|
1957
|
+
const control_point = start_position.clone().lerp(complete_position, control_ratio);
|
|
1958
|
+
control_point.y += height;
|
|
1959
|
+
const peak_time = get_peak_time(start_position, control_point, complete_position);
|
|
1960
|
+
let peak_pass = false;
|
|
1961
|
+
let last_position = start_position.clone();
|
|
1962
|
+
tween(target)
|
|
1963
|
+
.to(distance / speed, {}, {
|
|
1964
|
+
onUpdate: (target, ratio) => {
|
|
1965
|
+
const pos = bezier_on_update(start_position, control_point, complete_position, ratio);
|
|
1966
|
+
target.position = pos;
|
|
1967
|
+
if (!peak_pass && ratio >= peak_time) {
|
|
1968
|
+
peak_pass = true;
|
|
1969
|
+
peak_call?.();
|
|
1970
|
+
}
|
|
1971
|
+
last_position = pos;
|
|
1972
|
+
}
|
|
1973
|
+
})
|
|
1974
|
+
.call(() => {
|
|
1975
|
+
complete_call?.();
|
|
1976
|
+
})
|
|
1977
|
+
.start();
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
/**
|
|
1981
|
+
* 获取贝塞尔曲线的峰值时间
|
|
1982
|
+
* @param start_position 起点
|
|
1983
|
+
* @param control_point 控制点
|
|
1984
|
+
* @param complete_position 终点
|
|
1985
|
+
* @returns 峰值时间
|
|
1986
|
+
*/
|
|
1987
|
+
export function get_peak_time(start_position: Vec3, control_point: Vec3, complete_position: Vec3) : number{
|
|
1988
|
+
const denominator = start_position.y - 2 * control_point.y + complete_position.y;
|
|
1989
|
+
if (Math.abs(denominator) < 0.0001) {
|
|
1990
|
+
return 0.5;
|
|
1991
|
+
}
|
|
1992
|
+
const t = (start_position.y - control_point.y) / denominator;
|
|
1993
|
+
return Math.min(Math.max(t, 0), 1);
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
/**
|
|
1997
|
+
* 贝塞尔曲线路径计算
|
|
1998
|
+
* @param c1 起点
|
|
1999
|
+
* @param c2 控制点
|
|
2000
|
+
* @param c3 终点
|
|
2001
|
+
* @param t 时间参数,范围0-1
|
|
2002
|
+
* @returns 贝塞尔曲线上的点
|
|
2003
|
+
*/
|
|
2004
|
+
export function bezier_on_update(c1: Vec3, c2: Vec3, c3: Vec3, t: number) : Vec3{
|
|
2005
|
+
const t1 = 1 - t;
|
|
2006
|
+
const tt = t * t;
|
|
2007
|
+
let x = t1 * t1 * c1.x + 2 * t * t1 * c2.x + tt * c3.x;
|
|
2008
|
+
let y = t1 * t1 * c1.y + 2 * t * t1 * c2.y + tt * c3.y;
|
|
2009
|
+
let z = t1 * t1 * c1.z + 2 * t * t1 * c2.z + tt * c3.z;
|
|
2010
|
+
return new Vec3(x, y, z);
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
//#endregion
|
|
2014
|
+
|
|
2015
|
+
//#region 其他工具方法
|
|
2016
|
+
|
|
2017
|
+
/**
|
|
2018
|
+
* 角度转换为弧度
|
|
2019
|
+
* @param angle 角度
|
|
2020
|
+
* @returns 弧度
|
|
2021
|
+
*/
|
|
2022
|
+
export function angle_to_radians(angle) {
|
|
2023
|
+
return angle * 180 / Math.PI;
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
/**
|
|
2027
|
+
* 获取两个节点之间的角度
|
|
2028
|
+
* PS 仅获取Y轴,建议用于人物转向
|
|
2029
|
+
* @param center_item 中心节点
|
|
2030
|
+
* @param target_item 目标节点
|
|
2031
|
+
* @returns 欧拉角
|
|
2032
|
+
*/
|
|
2033
|
+
export function lookat(center_item: Node, target_item: Node) : Vec3{
|
|
2034
|
+
let my_position = center_item.worldPosition.clone();
|
|
2035
|
+
let look_position = target_item.worldPosition.clone();
|
|
2036
|
+
const direction = new Vec3(
|
|
2037
|
+
look_position.x - my_position.x,
|
|
2038
|
+
0,
|
|
2039
|
+
look_position.z - my_position.z
|
|
2040
|
+
).normalize();
|
|
2041
|
+
const angleRad = Math.atan2(direction.x, direction.z);
|
|
2042
|
+
const angleDeg = math.toDegree(angleRad);
|
|
2043
|
+
return new Vec3(0, angleDeg, 0);
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
/**
|
|
2047
|
+
* 判断字符串是否为空
|
|
2048
|
+
* @param str 字符串
|
|
2049
|
+
* @returns 是否为空
|
|
2050
|
+
*/
|
|
2051
|
+
export function is_null_string(str: string) : boolean{
|
|
2052
|
+
return !str || str.length === 0 || str === 'null' || str === 'Null' || str === 'undefined' || str === 'Undefined' || str === undefined;
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
/**
|
|
2056
|
+
* 加载资源
|
|
2057
|
+
* @param path 资源路径
|
|
2058
|
+
* @param bundle 资源包名
|
|
2059
|
+
* @param callback 回调函数
|
|
2060
|
+
* @returns 资源
|
|
2061
|
+
*/
|
|
2062
|
+
export function load_resources<T>(path: string, bundle: string = 'resources', callback: (asset: T) => void = null) : Promise<T>{
|
|
2063
|
+
return new Promise((resolve, reject) => {
|
|
2064
|
+
let loader = assetManager.getBundle(bundle);
|
|
2065
|
+
if (loader) {
|
|
2066
|
+
let assets = loader.get(path);
|
|
2067
|
+
if (assets) {
|
|
2068
|
+
callback && callback(assets as T);
|
|
2069
|
+
resolve(assets as T);
|
|
2070
|
+
} else {
|
|
2071
|
+
loader.load(path, (error, asset) => {
|
|
2072
|
+
if (asset) {
|
|
2073
|
+
callback && callback(asset as T);
|
|
2074
|
+
resolve(asset as T);
|
|
2075
|
+
} else {
|
|
2076
|
+
reject(error);
|
|
2077
|
+
}
|
|
2078
|
+
})
|
|
2079
|
+
}
|
|
2080
|
+
} else {
|
|
2081
|
+
assetManager.loadBundle(bundle, (error, bundle_instance) => {
|
|
2082
|
+
let assets = bundle_instance.get(path);
|
|
2083
|
+
if (assets) {
|
|
2084
|
+
callback && callback(assets as T);
|
|
2085
|
+
resolve(assets as T);
|
|
2086
|
+
} else {
|
|
2087
|
+
bundle_instance.load(path, (error, asset) => {
|
|
2088
|
+
if (asset) {
|
|
2089
|
+
callback && callback(asset as T);
|
|
2090
|
+
resolve(asset as T);
|
|
2091
|
+
} else {
|
|
2092
|
+
reject(error);
|
|
2093
|
+
}
|
|
2094
|
+
})
|
|
2095
|
+
}
|
|
2096
|
+
})
|
|
2097
|
+
}
|
|
2098
|
+
})
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
const UNITS: number[] = [1e18, 1e15, 1e12, 1e9, 1e6, 1e3];
|
|
2102
|
+
|
|
2103
|
+
/**
|
|
2104
|
+
* 格式化数字,将大数字转换为K、M、B、T等单位
|
|
2105
|
+
* @param num 数字
|
|
2106
|
+
* @returns 格式化后的字符串
|
|
2107
|
+
*/
|
|
2108
|
+
export function format(num: number) :string{
|
|
2109
|
+
let str = '';
|
|
2110
|
+
if (num >= UNITS[0]) {
|
|
2111
|
+
str = (num / UNITS[0]).toFixed(1) + 'Z';
|
|
2112
|
+
} else if (num >= UNITS[1]) {
|
|
2113
|
+
str = (num / UNITS[1]).toFixed(1) + 'J';
|
|
2114
|
+
} else if (num >= UNITS[2]) {
|
|
2115
|
+
str = (num / UNITS[2]).toFixed(1) + 'T';
|
|
2116
|
+
} else if (num >= UNITS[3]) {
|
|
2117
|
+
str = (num / UNITS[3]).toFixed(1) + 'B';
|
|
2118
|
+
} else if (num >= UNITS[4]) {
|
|
2119
|
+
str = (num / UNITS[4]).toFixed(1) + 'M';
|
|
2120
|
+
} else if (num >= UNITS[5]) {
|
|
2121
|
+
str = (num / UNITS[5]).toFixed(1) + 'K';
|
|
2122
|
+
} else {
|
|
2123
|
+
str = `${num}`;
|
|
2124
|
+
}
|
|
2125
|
+
return str.replace(/\.0/g, '');
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
/**
|
|
2129
|
+
* 获取两个节点之间在指定轴上的距离
|
|
2130
|
+
* @param node1 节点1
|
|
2131
|
+
* @param node2 节点2
|
|
2132
|
+
* @param axis 轴向量
|
|
2133
|
+
* @returns 距离
|
|
2134
|
+
*/
|
|
2135
|
+
export function get_axis_distance(node1: Node, node2: Node, axis: Vec3) :number{
|
|
2136
|
+
const vec = new Vec3();
|
|
2137
|
+
Vec3.subtract(vec, node2.worldPosition, node1.worldPosition);
|
|
2138
|
+
const norm_axis = new Vec3(axis);
|
|
2139
|
+
Vec3.normalize(norm_axis, norm_axis);
|
|
2140
|
+
return Math.abs(Vec3.dot(vec, norm_axis));
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
/**
|
|
2144
|
+
* 计算扩展位置(注意统一坐标系,建议使用WorldSpace)
|
|
2145
|
+
* @param start_position 起点
|
|
2146
|
+
* @param aim_position 目标点
|
|
2147
|
+
* @param distance 延长距离(米)
|
|
2148
|
+
* @returns 延长后的坐标
|
|
2149
|
+
*/
|
|
2150
|
+
export function calculate_extended_position(start_position: Vec3, aim_position: Vec3, distance: number) :Vec3{
|
|
2151
|
+
const direction = Vec3.subtract(new Vec3(), aim_position, start_position);
|
|
2152
|
+
Vec3.normalize(direction, direction);
|
|
2153
|
+
const extended = Vec3.multiplyScalar(new Vec3(), direction, distance);
|
|
2154
|
+
return Vec3.add(new Vec3(), aim_position, extended);
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
/**
|
|
2158
|
+
* 在环上通过时钟模式生成指定数量坐标(如果密度较高,则会有重复或近似坐标)
|
|
2159
|
+
* @param center 原点
|
|
2160
|
+
* @param min 圆环内径,内外径任意顺序传入,但值不能相同
|
|
2161
|
+
* @param max 圆环外径,内外径任意顺序传入,但值不能相同
|
|
2162
|
+
* @param count 生成数量,生成数量不得小于1
|
|
2163
|
+
* @param start 起始角度(时钟模式)如果不填入默认12点钟方向
|
|
2164
|
+
* @param end 结束角度(时钟模式)如果不填入默认12点钟方向
|
|
2165
|
+
* @param clockwise 是否顺时针方向
|
|
2166
|
+
* @returns 生成的随机坐标组
|
|
2167
|
+
*/
|
|
2168
|
+
export function get_random_position_with_clock(center: Vec3, min: number, max: number, count = 1, start = 12, end = 12, clockwise = true) :Vec3[] | null{
|
|
2169
|
+
if (count < 1) {
|
|
2170
|
+
console.error(`生成数量不得小于1`)
|
|
2171
|
+
return null;
|
|
2172
|
+
}
|
|
2173
|
+
if (min === max) {
|
|
2174
|
+
console.error(`内外径不能相同`)
|
|
2175
|
+
return null;
|
|
2176
|
+
}
|
|
2177
|
+
let real_min = Math.min(min, max);
|
|
2178
|
+
let real_max = Math.max(min, max);
|
|
2179
|
+
const clock_to_rad = (clock) => {
|
|
2180
|
+
const normalized_clock = clock % 12
|
|
2181
|
+
return -Math.PI / 2 + (normalized_clock * Math.PI / 6);
|
|
2182
|
+
};
|
|
2183
|
+
let start_angle = clock_to_rad(start);
|
|
2184
|
+
let end_angle = clock_to_rad(end);
|
|
2185
|
+
if (start % 12 === end % 12) {
|
|
2186
|
+
end_angle = start_angle + Math.PI * 2;
|
|
2187
|
+
} else {
|
|
2188
|
+
if (clockwise) {
|
|
2189
|
+
if (end_angle < start_angle) {
|
|
2190
|
+
end_angle += Math.PI * 2;
|
|
2191
|
+
}
|
|
2192
|
+
} else {
|
|
2193
|
+
if (end_angle > start_angle) {
|
|
2194
|
+
end_angle -= Math.PI * 2;
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
let points = [];
|
|
2199
|
+
for (let i = 0; i < count; ++i) {
|
|
2200
|
+
points.push(get_random_position_in_ring(center, real_min, real_max, start_angle, end_angle));
|
|
2201
|
+
}
|
|
2202
|
+
return points;
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
/**
|
|
2206
|
+
* 在环上生成随机坐标
|
|
2207
|
+
* @param center 原点
|
|
2208
|
+
* @param min 圆环内径,内外径任意顺序传入,但值不能相同
|
|
2209
|
+
* @param max 圆环外径,内外径任意顺序传入,但值不能相同
|
|
2210
|
+
* @param start 起始角度(时钟模式)如果不填入默认0点钟方向
|
|
2211
|
+
* @param end 结束角度(时钟模式)如果不填入默认0点钟方向
|
|
2212
|
+
* @returns 生成的随机坐标
|
|
2213
|
+
*/
|
|
2214
|
+
export function get_random_position_in_ring(center: Vec3, min: number, max: number, start = 0, end = Math.PI * 2) :Vec3{
|
|
2215
|
+
const random_angle = Math.random() * (end - start) + start;
|
|
2216
|
+
const random_radius = Math.random() * (max - min) + min;
|
|
2217
|
+
const x = center.x + Math.cos(random_angle) * random_radius;
|
|
2218
|
+
const z = center.z + Math.sin(random_angle) * random_radius;
|
|
2219
|
+
const y = center.y;
|
|
2220
|
+
return new Vec3(x, y, z);
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
/**
|
|
2224
|
+
* 查找子节点
|
|
2225
|
+
* @param parent 父节点
|
|
2226
|
+
* @param name 子节点名称
|
|
2227
|
+
* @param component 组件类型,寻找到组件,会返回组件实例,否则为null
|
|
2228
|
+
* @returns 子节点或组件实例
|
|
2229
|
+
*/
|
|
2230
|
+
export function find(parent: Node, name: string, component?: typeof Component) :Node | Component | null{
|
|
2231
|
+
for (let child of parent.children) {
|
|
2232
|
+
if (child.name === name) {
|
|
2233
|
+
return component ? child.getComponent(component) ? child.getComponent(component) : null : child;
|
|
2234
|
+
}
|
|
2235
|
+
const result = find(child, name, component);
|
|
2236
|
+
if (result) {
|
|
2237
|
+
return result;
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
return null;
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
/**
|
|
2244
|
+
* 从字符串创建颜色, 支持 "#RRGGBB" 和 "#RRGGBBAA" 格式
|
|
2245
|
+
* @param value 颜色字符串,格式为 "#RRGGBB" 或 "#RRGGBBAA"
|
|
2246
|
+
* @returns 颜色实例
|
|
2247
|
+
*/
|
|
2248
|
+
export function color_from_string(value: string) :Color{
|
|
2249
|
+
if (value == null || value.length < 7) {
|
|
2250
|
+
return null;
|
|
2251
|
+
}
|
|
2252
|
+
let color = value.toLowerCase();
|
|
2253
|
+
if (color.charAt(0) != "#") {
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
color = color.slice(1);
|
|
2257
|
+
let r = parseInt(color[0] + color[1], 16);
|
|
2258
|
+
let g = parseInt(color[2] + color[3], 16);
|
|
2259
|
+
let b = parseInt(color[4] + color[5], 16);
|
|
2260
|
+
let a = color.length > 7 ? parseInt(color[6] + color[7], 16) : 255;
|
|
2261
|
+
return new Color(r, g, b, a);
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
/**
|
|
2265
|
+
* 插值旋转四元数
|
|
2266
|
+
* @param now_rotation 当前旋转四元数
|
|
2267
|
+
* @param target_rotation 目标旋转四元数
|
|
2268
|
+
* @returns 插值后的旋转四元数
|
|
2269
|
+
*/
|
|
2270
|
+
export function lerp_rotate(now_rotation: Quat, target_rotation: Quat) :Quat{
|
|
2271
|
+
const temp_quat = new Quat();
|
|
2272
|
+
const result = new Quat();
|
|
2273
|
+
const dot = Quat.dot(now_rotation, target_rotation);
|
|
2274
|
+
if (dot < 0) {
|
|
2275
|
+
Quat.multiplyScalar(temp_quat, target_rotation, -1);
|
|
2276
|
+
Quat.slerp(result, now_rotation, temp_quat, 1 / 10);
|
|
2277
|
+
} else {
|
|
2278
|
+
Quat.slerp(result, now_rotation, target_rotation, 1 / 10);
|
|
2279
|
+
}
|
|
2280
|
+
return result;
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
/**
|
|
2284
|
+
* 插值旋转向量
|
|
2285
|
+
* @param now_rotation 当前旋转向量
|
|
2286
|
+
* @param target_rotation 目标旋转向量
|
|
2287
|
+
* @param speed 插值速度
|
|
2288
|
+
* @param delta_time 时间间隔
|
|
2289
|
+
* @returns 插值后的旋转向量
|
|
2290
|
+
*/
|
|
2291
|
+
export function lerp_rotate_by_speed(now_rotation: Vec3, target_rotation: Vec3, speed: number, delta_time: number) :Vec3{
|
|
2292
|
+
now_rotation.y = (now_rotation.y + 360) % 360;
|
|
2293
|
+
let left_turn = (target_rotation.y - now_rotation.y + 360) % 360;
|
|
2294
|
+
let right_turn = (now_rotation.y - target_rotation.y + 360) % 360;
|
|
2295
|
+
let euler = new Vec3();
|
|
2296
|
+
if (left_turn > right_turn) {
|
|
2297
|
+
euler = new Vec3(now_rotation.x, now_rotation.y - speed * delta_time, now_rotation.z);
|
|
2298
|
+
if (euler.y < target_rotation.y) {
|
|
2299
|
+
euler.y = target_rotation.y;
|
|
2300
|
+
}
|
|
2301
|
+
return euler;
|
|
2302
|
+
} else {
|
|
2303
|
+
euler = new Vec3(now_rotation.x, now_rotation.y + speed * delta_time, now_rotation.z);
|
|
2304
|
+
if (euler.y > target_rotation.y) {
|
|
2305
|
+
euler.y = target_rotation.y;
|
|
2306
|
+
}
|
|
2307
|
+
return euler;
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
/**
|
|
2312
|
+
* 插值移动向量
|
|
2313
|
+
* @param start 起点
|
|
2314
|
+
* @param now 当前位置
|
|
2315
|
+
* @param target 目标位置
|
|
2316
|
+
* @param speed 插值速度
|
|
2317
|
+
* @param delta_time 时间间隔
|
|
2318
|
+
* @returns 插值后的位置和移动距离
|
|
2319
|
+
*/
|
|
2320
|
+
export function lerp_move_by_speed(start: Vec3 | Node, now: Vec3 | Node, target: Vec3 | Node, speed: number, delta_time: number) :{position: Vec3, step: number} {
|
|
2321
|
+
let new_position = new Vec3(), start_position = start instanceof Vec3 ? start : start.worldPosition.clone(), now_position = now instanceof Vec3 ? now : now.worldPosition.clone(), target_position = target instanceof Vec3 ? target : target.worldPosition.clone();
|
|
2322
|
+
let t = 0;
|
|
2323
|
+
const current_distance = Vec3.distance(now_position, start_position);
|
|
2324
|
+
const total_distance = Vec3.distance(start_position, target_position);
|
|
2325
|
+
t = (current_distance + speed * delta_time) / total_distance;
|
|
2326
|
+
Vec3.lerp(new_position, start_position, target_position, Math.min(t, 1));
|
|
2327
|
+
return { position: new_position, step: t };
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
/**
|
|
2331
|
+
* 按方向和速度移动节点
|
|
2332
|
+
* @param target 目标节点
|
|
2333
|
+
* @param direction 移动方向
|
|
2334
|
+
* @param speed 移动速度
|
|
2335
|
+
* @param delta_time 时间间隔
|
|
2336
|
+
* @param space 空间参考,默认世界空间
|
|
2337
|
+
*/
|
|
2338
|
+
export function move_by_direction(target: Node, direction: Vec3, speed: number, delta_time: number, space: NodeSpace = NodeSpace.WORLD) {
|
|
2339
|
+
const move_delta = new Vec3();
|
|
2340
|
+
Vec3.multiplyScalar(move_delta, direction, speed * delta_time);
|
|
2341
|
+
target.translate(move_delta, space);
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
/**
|
|
2345
|
+
* 按方向和距离移动节点
|
|
2346
|
+
* @param start_position 起点位置
|
|
2347
|
+
* @param target 目标节点
|
|
2348
|
+
* @param direction 移动方向
|
|
2349
|
+
* @param distance 移动距离
|
|
2350
|
+
*/
|
|
2351
|
+
export function move_with_direction_and_distance(start_position: Vec3, target: Node, direction: Vec3, distance: number) {
|
|
2352
|
+
let move_delta = new Vec3();
|
|
2353
|
+
Vec3.scaleAndAdd(move_delta, start_position, direction, distance);
|
|
2354
|
+
target.setWorldPosition(move_delta);
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
/**
|
|
2358
|
+
* 按前方向移动节点
|
|
2359
|
+
* @param target 目标节点
|
|
2360
|
+
* @param speed 移动速度
|
|
2361
|
+
* @param delta_time 时间间隔
|
|
2362
|
+
*/
|
|
2363
|
+
export function move_forward(target: Node, speed: number, delta_time: number) {
|
|
2364
|
+
let forward = target.forward;
|
|
2365
|
+
forward.z *= -1;
|
|
2366
|
+
forward.x *= -1;
|
|
2367
|
+
let director = forward.multiplyScalar(speed * delta_time);
|
|
2368
|
+
const newPos = target.worldPosition.clone().add(director);
|
|
2369
|
+
target.setWorldPosition(newPos);
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
|
|
2373
|
+
/**
|
|
2374
|
+
* 获取两个节点或向量之间的方向向量
|
|
2375
|
+
* @param start 起点节点或向量
|
|
2376
|
+
* @param end 终点节点或向量
|
|
2377
|
+
* @returns 方向向量
|
|
2378
|
+
*/
|
|
2379
|
+
export function get_direction(start: Node | Vec3 | Component, end: Node | Vec3 | Component) :Vec3{
|
|
2380
|
+
let start_position = start instanceof Node ? start.getWorldPosition().clone() : start instanceof Component ? start.node.getWorldPosition().clone() : start.clone();
|
|
2381
|
+
let end_position = end instanceof Node ? end.getWorldPosition().clone() : end instanceof Component ? end.node.getWorldPosition().clone() : end.clone();
|
|
2382
|
+
return end_position.subtract(start_position).normalize();
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
|
|
2386
|
+
|
|
2387
|
+
/**
|
|
2388
|
+
* 果冻效果
|
|
2389
|
+
* @param node 动效节点
|
|
2390
|
+
* @param ratio 缩放比例
|
|
2391
|
+
* @param callback 动效完成回调
|
|
2392
|
+
*/
|
|
2393
|
+
export function jelly_effect(node: Node, ratio: number, callback?:Function) {
|
|
2394
|
+
node.setScale(Vec3.ZERO);
|
|
2395
|
+
tween(node)
|
|
2396
|
+
.to(.05, { scale: new Vec3(1.2 * ratio, 0.8 * ratio, 1.2 * ratio) })
|
|
2397
|
+
.to(.1, { scale: new Vec3(0.8 * ratio, 1.1 * ratio, 0.8 * ratio) })
|
|
2398
|
+
.to(.05, { scale: new Vec3(1.1 * ratio, 0.9 * ratio, 1.1 * ratio) })
|
|
2399
|
+
.to(.04, { scale: new Vec3(.85 * ratio, 1.1 * ratio, .85 * ratio) })
|
|
2400
|
+
.to(.04, { scale: new Vec3(1 * ratio, 1 * ratio, 1 * ratio) })
|
|
2401
|
+
.call(() => {
|
|
2402
|
+
callback?.();
|
|
2403
|
+
})
|
|
2404
|
+
.start();
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
/**
|
|
2408
|
+
* 呼吸效果
|
|
2409
|
+
* @param node 节点
|
|
2410
|
+
* @param delay 呼吸间隔
|
|
2411
|
+
*/
|
|
2412
|
+
export function breath_effect(node: Node, delay:number = 0.5) {
|
|
2413
|
+
let opacity = node.getComponent(UIOpacity);
|
|
2414
|
+
opacity.opacity = 0;
|
|
2415
|
+
node.active = true;
|
|
2416
|
+
tween(opacity)
|
|
2417
|
+
.repeatForever(
|
|
2418
|
+
tween(opacity)
|
|
2419
|
+
.to(0.8, { opacity: 255 }, { easing: 'quadInOut' })
|
|
2420
|
+
.to(0.8, { opacity: 0 }, { easing: 'quadInOut' })
|
|
2421
|
+
.delay(delay)
|
|
2422
|
+
)
|
|
2423
|
+
.start();
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
/**
|
|
2427
|
+
* 脉冲效果
|
|
2428
|
+
* @param node 节点
|
|
2429
|
+
*/
|
|
2430
|
+
export function pulsation_effect(node: Node) {
|
|
2431
|
+
tween(node)
|
|
2432
|
+
.repeatForever(
|
|
2433
|
+
tween(node)
|
|
2434
|
+
.by(0.25, { scale: new Vec3(0.05, 0.05, 0) }, { easing: 'sineOut' })
|
|
2435
|
+
.by(0.25, { scale: new Vec3(-0.05, -0.05, 0) }, { easing: 'sineOut' })
|
|
2436
|
+
.by(0.35, { scale: new Vec3(0.14, 0.14, 0) }, { easing: 'sineOut' })
|
|
2437
|
+
.by(0.35, { scale: new Vec3(-0.14, -0.14, 0) }, { easing: 'sineIn' })
|
|
2438
|
+
.delay(1)
|
|
2439
|
+
)
|
|
2440
|
+
.start();
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
/**
|
|
2444
|
+
* 放大后缩小
|
|
2445
|
+
* @param node 动效节点
|
|
2446
|
+
* @param amplitude 缩放幅度
|
|
2447
|
+
*/
|
|
2448
|
+
export function scale_b_s(node: Node, amplitude: number) {
|
|
2449
|
+
tween(node)
|
|
2450
|
+
.by(0.25, { scale: new Vec3(amplitude, amplitude, 0) }, { easing: 'quadOut' })
|
|
2451
|
+
.by(0.25, { scale: new Vec3(-amplitude, -amplitude, 0) }, { easing: 'quadOut' })
|
|
2452
|
+
.start()
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
/**
|
|
2456
|
+
* 缩放效果,从小到大
|
|
2457
|
+
* @param node 节点
|
|
2458
|
+
* @param amplitude 振幅
|
|
2459
|
+
*/
|
|
2460
|
+
export function scale_s_b(node: Node, amplitude: number) {
|
|
2461
|
+
tween(node)
|
|
2462
|
+
.by(0.25, { scale: new Vec3(-amplitude, -amplitude, 0) }, { easing: 'quadOut' })
|
|
2463
|
+
.by(0.25, { scale: new Vec3(amplitude, amplitude, 0) }, { easing: 'quadOut' })
|
|
2464
|
+
.start()
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
/**
|
|
2468
|
+
* 延迟调用
|
|
2469
|
+
* @param node 节点
|
|
2470
|
+
* @param delay 延迟时间
|
|
2471
|
+
* @param callback 回调函数
|
|
2472
|
+
*/
|
|
2473
|
+
export function delay_call(node:Node, delay:number, callback?:Function) {
|
|
2474
|
+
tween(node)
|
|
2475
|
+
.delay(delay)
|
|
2476
|
+
.call(() => { callback?.(); })
|
|
2477
|
+
.start();
|
|
2478
|
+
}
|
|
2479
|
+
//#endregion
|
|
2480
|
+
|
|
2481
|
+
|
|
2482
|
+
|
|
2483
|
+
export let MessageSystem = new EventSystem();
|
|
2484
|
+
|
|
2485
|
+
export let pool: PoolManager = new PoolManager();
|