feat: add gacha
parent
73d3848b3a
commit
aa57deaa58
|
@ -0,0 +1,8 @@
|
|||
import { _decorator, Component, Node } from 'cc';
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass('GachaBase')
|
||||
export default abstract class GachaBase extends Component {
|
||||
public abstract show(): void;
|
||||
}
|
|
@ -0,0 +1,282 @@
|
|||
import { _decorator, Component, Enum, Node, setPropertyEnumType, sp } from 'cc';
|
||||
|
||||
const { ccclass, property, type } = _decorator;
|
||||
|
||||
export enum SocketPath {}
|
||||
Enum(SocketPath);
|
||||
|
||||
export enum SpineAnimation {}
|
||||
Enum(SpineAnimation);
|
||||
|
||||
export enum SpineSkin {}
|
||||
Enum(SpineSkin);
|
||||
|
||||
@ccclass('SpineAnimationHandler')
|
||||
export default class SpineAnimationHandler extends Component {
|
||||
@property(sp.Skeleton)
|
||||
private skeleton: sp.Skeleton;
|
||||
@property({ visible: true })
|
||||
private _flipX: boolean = false;
|
||||
@property({ visible: true })
|
||||
private _flipY: boolean = false;
|
||||
|
||||
public get flipX() {
|
||||
return this._flipX;
|
||||
}
|
||||
public set flipX(value: boolean) {
|
||||
if (value) {
|
||||
this.skeleton._skeleton.scaleX = -Math.abs(this.skeleton._skeleton.scaleX);
|
||||
} else {
|
||||
this.skeleton._skeleton.scaleX = Math.abs(this.skeleton._skeleton.scaleX);
|
||||
}
|
||||
|
||||
this._flipX = value;
|
||||
}
|
||||
public get flipY() {
|
||||
return this._flipY;
|
||||
}
|
||||
public set flipY(value: boolean) {
|
||||
if (value) {
|
||||
this.skeleton._skeleton.scaleY = -Math.abs(this.skeleton._skeleton.scaleY);
|
||||
} else {
|
||||
this.skeleton._skeleton.scaleY = Math.abs(this.skeleton._skeleton.scaleY);
|
||||
}
|
||||
|
||||
this._flipX = value;
|
||||
}
|
||||
|
||||
private _enumSocketPath = Enum({});
|
||||
private _enumAnimation = Enum({});
|
||||
private _enumSkin = Enum({});
|
||||
|
||||
onFocusInEditor(): void {
|
||||
this.setPropertySocketPath(this, '');
|
||||
}
|
||||
|
||||
protected onLoad(): void {
|
||||
this.skeleton._skeleton.scaleX = this._flipX ? -this.skeleton._skeleton.scaleX : this.skeleton._skeleton.scaleX;
|
||||
this.skeleton._skeleton.scaleY = this._flipY ? -this.skeleton._skeleton.scaleY : this.skeleton._skeleton.scaleY;
|
||||
}
|
||||
|
||||
public setPropertySocketPath(object: object, propertyName: string) {
|
||||
//update enum SocketPath
|
||||
let pathEnum = {};
|
||||
this.skeleton.querySockets().forEach((path, i) => {
|
||||
pathEnum[path] = i;
|
||||
});
|
||||
this._enumSocketPath = Enum({});
|
||||
Object.assign(this._enumSocketPath, pathEnum);
|
||||
Enum.update(this._enumSocketPath);
|
||||
setPropertyEnumType(object, propertyName, this._enumSocketPath);
|
||||
}
|
||||
|
||||
public setPropertySpineAnimation(object: object, propertyName: string) {
|
||||
//update enum Animations
|
||||
let animEnum = this.skeleton.skeletonData.getAnimsEnum();
|
||||
this._enumAnimation = Enum({});
|
||||
Object.assign(this._enumAnimation, animEnum);
|
||||
Enum.update(this._enumAnimation);
|
||||
setPropertyEnumType(object, propertyName, this._enumAnimation);
|
||||
}
|
||||
|
||||
public setPropertySpineSkin(object: object, propertyName: string) {
|
||||
//update enum Skin
|
||||
let skinEnum = this.skeleton.skeletonData.getSkinsEnum();
|
||||
this._enumSkin = Enum({});
|
||||
Object.assign(this._enumSkin, skinEnum);
|
||||
Enum.update(this._enumSkin);
|
||||
setPropertyEnumType(object, propertyName, this._enumSkin);
|
||||
}
|
||||
|
||||
public setAnimation(
|
||||
animName: string,
|
||||
options?: {
|
||||
loop?: boolean;
|
||||
trackIndex?: number;
|
||||
ignoreAnimationRunning?: boolean;
|
||||
onComplete?: (entry: sp.spine.TrackEntry) => void;
|
||||
},
|
||||
): sp.spine.TrackEntry {
|
||||
if (!this.skeleton) return null;
|
||||
const opts = {
|
||||
trackIndex: 0,
|
||||
loop: false,
|
||||
...options,
|
||||
};
|
||||
|
||||
if (opts.ignoreAnimationRunning) {
|
||||
const trackEntry = this.skeleton.getCurrent(opts.trackIndex);
|
||||
const animRunning = trackEntry?.animation?.name;
|
||||
if (animRunning && animRunning == animName) return trackEntry;
|
||||
}
|
||||
|
||||
this.skeleton.setCompleteListener(null);
|
||||
const trackEntry = this.skeleton.setAnimation(opts?.trackIndex || 0, animName, opts?.loop);
|
||||
if (opts.onComplete) {
|
||||
this.skeleton.setCompleteListener(opts.onComplete);
|
||||
}
|
||||
|
||||
return trackEntry;
|
||||
}
|
||||
|
||||
public setListener(listener: (entry: sp.spine.TrackEntry, e: sp.spine.Event) => void) {
|
||||
this.skeleton.setEventListener(listener);
|
||||
}
|
||||
|
||||
public setAnimationAsync(
|
||||
animName: string,
|
||||
options?: {
|
||||
loop?: boolean;
|
||||
trackIndex?: number;
|
||||
ignoreAnimationRunning?: boolean;
|
||||
},
|
||||
): Promise<sp.spine.TrackEntry> {
|
||||
return new Promise((resolve) => {
|
||||
this.setAnimation(animName, {
|
||||
...options,
|
||||
onComplete(entry) {
|
||||
resolve(entry);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public addAnimation(
|
||||
animName: string,
|
||||
options?: {
|
||||
loop?: boolean;
|
||||
trackIndex?: number;
|
||||
mixDuration?: number;
|
||||
delay?: number;
|
||||
onComplete?: (entry: sp.spine.TrackEntry) => void;
|
||||
},
|
||||
): sp.spine.TrackEntry {
|
||||
if (!this.skeleton) return null;
|
||||
const opts = {
|
||||
trackIndex: 0,
|
||||
loop: false,
|
||||
...options,
|
||||
};
|
||||
|
||||
this.skeleton.setCompleteListener(null);
|
||||
const trackEntry = this.skeleton.addAnimation(opts?.trackIndex || 0, animName, opts?.loop, opts?.delay);
|
||||
|
||||
if (opts?.mixDuration) {
|
||||
trackEntry.mixDuration = opts.mixDuration;
|
||||
}
|
||||
|
||||
if (opts.onComplete) {
|
||||
this.skeleton.setCompleteListener(opts.onComplete);
|
||||
}
|
||||
|
||||
return trackEntry;
|
||||
}
|
||||
|
||||
public addAnimationAsync(
|
||||
animName: string,
|
||||
options?: {
|
||||
loop?: boolean;
|
||||
trackIndex?: number;
|
||||
mixDuration?: number;
|
||||
delay?: number;
|
||||
},
|
||||
): Promise<sp.spine.TrackEntry> {
|
||||
return new Promise((resolve) => {
|
||||
this.addAnimation(animName, {
|
||||
...options,
|
||||
onComplete(entry) {
|
||||
resolve(entry);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public addEmptyAnimation(options?: {
|
||||
trackIndex?: number;
|
||||
mixDuration?: number;
|
||||
delay?: number;
|
||||
onComplete?: (entry: sp.spine.TrackEntry) => void;
|
||||
}): sp.spine.TrackEntry {
|
||||
if (!this.skeleton) return;
|
||||
const opts = {
|
||||
trackIndex: 0,
|
||||
mixDuration: 0.2,
|
||||
delay: 0.2,
|
||||
...options,
|
||||
};
|
||||
|
||||
const trackEntry = this.skeleton.getState().addEmptyAnimation(opts.trackIndex, opts.mixDuration, opts.delay);
|
||||
|
||||
if (opts.onComplete) {
|
||||
this.skeleton.setTrackCompleteListener(trackEntry, opts.onComplete);
|
||||
}
|
||||
return trackEntry;
|
||||
}
|
||||
|
||||
public addEmptyAnimationAsync(options?: {
|
||||
trackIndex?: number;
|
||||
mixDuration?: number;
|
||||
delay?: number;
|
||||
}): Promise<sp.spine.TrackEntry> {
|
||||
return new Promise((resolve) => {
|
||||
this.addEmptyAnimation({
|
||||
...options,
|
||||
onComplete(entry) {
|
||||
resolve(entry);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public clearTrack(trackIndex: number = 0): void {
|
||||
if (!this.skeleton) return;
|
||||
this.skeleton.clearTrack(trackIndex);
|
||||
}
|
||||
|
||||
public findBone(boneName: string): sp.spine.Bone {
|
||||
return this.skeleton.findBone(boneName);
|
||||
}
|
||||
|
||||
public socketPathToString(socketPath: SocketPath): string {
|
||||
return this.skeleton.querySockets()[socketPath];
|
||||
}
|
||||
|
||||
public SpineAnimationToString(animationName: SpineAnimation): string {
|
||||
return this.skeleton._skeleton.data.animations[animationName].name;
|
||||
}
|
||||
|
||||
public SpineAnimationToAnimation(animationName: SpineAnimation): sp.spine.Animation {
|
||||
return this.skeleton._skeleton.data.animations[animationName];
|
||||
}
|
||||
|
||||
public addSocket(socketPath: SocketPath | string, target: Node): sp.SpineSocket {
|
||||
let socket: sp.SpineSocket;
|
||||
if (typeof socketPath === 'string') {
|
||||
socket = new sp.SpineSocket(socketPath, target);
|
||||
} else {
|
||||
socket = new sp.SpineSocket(this.socketPathToString(socketPath), target);
|
||||
}
|
||||
this.skeleton.sockets.push(socket);
|
||||
this.skeleton!.sockets = this.skeleton!.sockets;
|
||||
return socket;
|
||||
}
|
||||
|
||||
public updateSocketPath(socket: sp.SpineSocket, socketPath: SocketPath | string): sp.SpineSocket {
|
||||
let newSocket: sp.SpineSocket;
|
||||
if (typeof socketPath === 'string') {
|
||||
newSocket = new sp.SpineSocket(socketPath, socket.target);
|
||||
} else {
|
||||
newSocket = new sp.SpineSocket(this.socketPathToString(socketPath), socket.target);
|
||||
}
|
||||
const index = this.skeleton.sockets.indexOf(socket);
|
||||
this.skeleton.sockets[index] = newSocket;
|
||||
this.skeleton!.sockets = this.skeleton!.sockets;
|
||||
return newSocket;
|
||||
}
|
||||
|
||||
public removeSocket(socket: sp.SpineSocket): void {
|
||||
const index = this.skeleton.sockets.indexOf(socket);
|
||||
this.skeleton.sockets.splice(index, 1);
|
||||
this.skeleton!.sockets = this.skeleton!.sockets;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import { _decorator, CCBoolean, Component, game, Node, sp } from 'cc';
|
||||
|
||||
const { ccclass, property, requireComponent } = _decorator;
|
||||
|
||||
@ccclass('SpineIgnoreTimeScale')
|
||||
@requireComponent(sp.Skeleton)
|
||||
export default class SpineIgnoreTimeScale extends Component {
|
||||
@property(CCBoolean)
|
||||
private ignoreTimeScale: boolean = false;
|
||||
|
||||
private _skeleton: sp.Skeleton;
|
||||
|
||||
protected onLoad(): void {
|
||||
this._skeleton = this.getComponent(sp.Skeleton);
|
||||
}
|
||||
|
||||
protected update(dt: number): void {
|
||||
if (game.timeScale != 1) {
|
||||
this._skeleton.updateAnimation(this.ignoreTimeScale ? game.deltaTime : dt);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ import BoosterType from '../Enum/BoosterType';
|
|||
import ControllerSide from '../Enum/ControllerSide';
|
||||
import GameState from '../Enum/GameState';
|
||||
import ScoreType from '../Enum/ScoreType';
|
||||
import { RewardType } from '../Manager/GachaManager';
|
||||
|
||||
enum GameEvent {
|
||||
Score,
|
||||
|
@ -17,6 +18,7 @@ enum GameEvent {
|
|||
ControlTouchEnd,
|
||||
WarningTime,
|
||||
TicketUpdate,
|
||||
GachaReward,
|
||||
}
|
||||
|
||||
export interface GameEventCallbackMap {
|
||||
|
@ -47,6 +49,7 @@ export interface GameEventArgMap {
|
|||
[GameEvent.ControlTouchEnd]: ControllerSide;
|
||||
[GameEvent.WarningTime]: boolean;
|
||||
[GameEvent.TicketUpdate]: number;
|
||||
[GameEvent.GachaReward]: [RewardType, number];
|
||||
}
|
||||
|
||||
export default GameEvent;
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
import { _decorator, Component, Node, Sprite } from 'cc';
|
||||
import GachaBase from '../Base/GachaBase';
|
||||
import SpineAnimationHandler from '../Base/SpineAnimationHandler';
|
||||
import GachaManager from '../Manager/GachaManager';
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass('FlipCard')
|
||||
export default class FlipCard extends GachaBase {
|
||||
@property(SpineAnimationHandler)
|
||||
private animationHandler: SpineAnimationHandler;
|
||||
@property(Node)
|
||||
private spineRoot: Node;
|
||||
@property(Sprite)
|
||||
private cards: Sprite[] = [];
|
||||
|
||||
private _opened: boolean = false;
|
||||
|
||||
protected onLoad(): void {
|
||||
this.animationHandler.setListener((_, e) => {
|
||||
switch (e.data.name) {
|
||||
case 'card1-active':
|
||||
this.cards[0].setNodeActive(true);
|
||||
break;
|
||||
case 'card2-active':
|
||||
this.cards[1].setNodeActive(true);
|
||||
break;
|
||||
case 'card3-active':
|
||||
this.cards[2].setNodeActive(true);
|
||||
break;
|
||||
case 'card4-active':
|
||||
this.cards[3].setNodeActive(true);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected onEnable(): void {
|
||||
this._opened = false;
|
||||
this.cards.forEach((card) => card.setNodeActive(false));
|
||||
this.spineRoot.setActive(false);
|
||||
}
|
||||
|
||||
public async show(): Promise<void> {
|
||||
this.spineRoot.setActive(true);
|
||||
await this.animationHandler.setAnimationAsync('appear');
|
||||
this.animationHandler.addAnimation('idle', { loop: true });
|
||||
}
|
||||
|
||||
public async open(event: Event, value: string) {
|
||||
if (this._opened) return;
|
||||
this._opened = true;
|
||||
if (this.cards[+value - 1].node.active) return;
|
||||
const reward = await GachaManager.instance.getReward();
|
||||
if (reward) {
|
||||
this.cards[+value - 1].spriteFrame = reward.icon;
|
||||
await this.animationHandler.setAnimationAsync(`card${value}-active`, { trackIndex: +value });
|
||||
GachaManager.instance.gachaDone();
|
||||
}
|
||||
|
||||
GachaManager.instance.gachaDone();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
import { _decorator, clamp01, Component, easing, game, Label, Node, Sprite, Vec3 } from 'cc';
|
||||
import GachaBase from '../Base/GachaBase';
|
||||
import SpineAnimationHandler from '../Base/SpineAnimationHandler';
|
||||
import GachaManager from '../Manager/GachaManager';
|
||||
import Utils from '../Utilities';
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass('FreeReward')
|
||||
export default class FreeReward extends GachaBase {
|
||||
@property(SpineAnimationHandler)
|
||||
private animationHandler: SpineAnimationHandler;
|
||||
@property(Node)
|
||||
private spineRoot: Node;
|
||||
@property(Sprite)
|
||||
private rewardSprite: Sprite;
|
||||
@property(Label)
|
||||
private rewardLabel: Label;
|
||||
|
||||
private _opened: boolean = false;
|
||||
private _startShowReward: boolean = false;
|
||||
private _timer: number = 0;
|
||||
|
||||
protected onEnable(): void {
|
||||
this.spineRoot.setActive(false);
|
||||
this.rewardSprite.setNodeActive(false);
|
||||
this._opened = false;
|
||||
}
|
||||
|
||||
protected update(dt: number): void {
|
||||
if (this._startShowReward) {
|
||||
let k = clamp01(this._timer / 0.3);
|
||||
k = easing.quintInOut(k);
|
||||
const targetScale = Vec3.lerp(this.rewardSprite.node.scale, Vec3.ZERO, Vec3.ONE, k);
|
||||
const targetPosition = Vec3.lerp(this.rewardSprite.node.position, new Vec3(0, 150), new Vec3(0, 700), k);
|
||||
this.rewardSprite.node.setScale(targetScale);
|
||||
this.rewardSprite.node.setPosition(targetPosition);
|
||||
this._timer += game.deltaTime;
|
||||
if (k === 1) {
|
||||
this._startShowReward = false;
|
||||
GachaManager.instance.gachaDone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async show() {
|
||||
this.spineRoot.setActive(true);
|
||||
await this.animationHandler.setAnimationAsync('appear');
|
||||
this.animationHandler.addAnimation('idle', { loop: true });
|
||||
}
|
||||
|
||||
public async open() {
|
||||
if (this._opened) return;
|
||||
this._timer = 0;
|
||||
this._opened = true;
|
||||
const reward = await GachaManager.instance.getReward();
|
||||
if (reward) {
|
||||
this.animationHandler.setAnimation('open');
|
||||
this.rewardSprite.spriteFrame = reward.icon;
|
||||
this.rewardLabel.string = reward.quantity.toString();
|
||||
await Utils.delay(1);
|
||||
this.rewardSprite.setNodeActive(true);
|
||||
this._startShowReward = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this._startShowReward = false;
|
||||
GachaManager.instance.gachaDone();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
import { _decorator, Component, Node, sp, Sprite } from 'cc';
|
||||
import GachaBase from '../Base/GachaBase';
|
||||
import SpineAnimationHandler, { SocketPath, SpineAnimation, SpineSkin } from '../Base/SpineAnimationHandler';
|
||||
import GachaManager from '../Manager/GachaManager';
|
||||
import Utils from '../Utilities';
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
class ActiveCard {
|
||||
public socket: sp.SpineSocket;
|
||||
public node: Node;
|
||||
public idReward: string;
|
||||
|
||||
constructor(node: Node, idReward: string) {
|
||||
this.node = node;
|
||||
this.idReward = idReward;
|
||||
}
|
||||
|
||||
public setActive(value: boolean) {
|
||||
this.node.setActive(value);
|
||||
}
|
||||
}
|
||||
|
||||
@ccclass('LuckyChain')
|
||||
export default class LuckyChain extends GachaBase {
|
||||
@property(SpineAnimationHandler)
|
||||
private animationHandler: SpineAnimationHandler;
|
||||
@property(Node)
|
||||
private spriteRoot: Node;
|
||||
@property(Node)
|
||||
private cards: Node[] = [];
|
||||
@property({ type: SocketPath })
|
||||
private cardSlotPath: SocketPath[] = [];
|
||||
|
||||
private _activeCards: ActiveCard[] = [];
|
||||
private _allCards: ActiveCard[] = [];
|
||||
private _rewardId: string;
|
||||
|
||||
onFocusInEditor(): void {
|
||||
this.animationHandler.setPropertySocketPath(this, 'cardSlotPath');
|
||||
}
|
||||
|
||||
protected onLoad(): void {
|
||||
this.setReward();
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const card = this._allCards.shift();
|
||||
const path = this.cardSlotPath[i];
|
||||
const socket = this.animationHandler.addSocket(path, card.node);
|
||||
card.setActive(true);
|
||||
card.socket = socket;
|
||||
this._activeCards.push(card);
|
||||
}
|
||||
|
||||
this.animationHandler.setListener((_, e) => {
|
||||
switch (e.data.name) {
|
||||
case 'card-claimed':
|
||||
const cardRemove = this._activeCards.shift();
|
||||
this._rewardId = cardRemove.idReward;
|
||||
this.animationHandler.removeSocket(cardRemove.socket);
|
||||
cardRemove.setActive(false);
|
||||
cardRemove.socket = null;
|
||||
this._allCards.push(cardRemove);
|
||||
break;
|
||||
case 'new-card-spawned':
|
||||
const card = this._allCards.shift();
|
||||
const path = this.cardSlotPath[this.cardSlotPath.length - 1];
|
||||
const socket = this.animationHandler.addSocket(path, card.node);
|
||||
card.setActive(true);
|
||||
card.socket = socket;
|
||||
this._activeCards.push(card);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected onEnable(): void {
|
||||
this.spriteRoot.setActive(false);
|
||||
}
|
||||
|
||||
private setReward() {
|
||||
this._allCards = this.cards.map((card) => {
|
||||
const rw = GachaManager.instance.getRandomReward();
|
||||
card.getComponent(Sprite).spriteFrame = rw.icon;
|
||||
card.setActive(false);
|
||||
return new ActiveCard(card, rw.id);
|
||||
});
|
||||
}
|
||||
|
||||
public async show(): Promise<void> {
|
||||
this.spriteRoot.setActive(true);
|
||||
await this.animationHandler.setAnimationAsync('appear');
|
||||
await this.animationHandler.addAnimationAsync('active');
|
||||
|
||||
GachaManager.instance.setReward(this._rewardId);
|
||||
|
||||
await Utils.delay(1.5);
|
||||
//update socket path
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const card = this._activeCards[i];
|
||||
const path = this.cardSlotPath[i];
|
||||
this._activeCards[i].socket = this.animationHandler.updateSocketPath(card.socket, path);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
import { _decorator, CCInteger, CCString, Component, game, Node, randomRangeInt, RealCurve, sp, Sprite } from 'cc';
|
||||
import GachaBase from '../Base/GachaBase';
|
||||
import SpineAnimationHandler from '../Base/SpineAnimationHandler';
|
||||
import GachaManager from '../Manager/GachaManager';
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass('LuckyWheel')
|
||||
export default class LuckyWheel extends GachaBase {
|
||||
@property(SpineAnimationHandler)
|
||||
private animationHandler: SpineAnimationHandler;
|
||||
@property(Node)
|
||||
private spineRoot: Node;
|
||||
@property(CCString)
|
||||
private wheelBoneName: string = '';
|
||||
@property(CCInteger)
|
||||
private speed: number = 1;
|
||||
@property(RealCurve)
|
||||
private spinCurve: RealCurve = new RealCurve();
|
||||
@property(Sprite)
|
||||
private sprites: Sprite[] = [];
|
||||
|
||||
private _wheel: sp.spine.Bone;
|
||||
private _targetAngle: number = 0;
|
||||
private _spinning: boolean = false;
|
||||
private _timer: number = 0;
|
||||
private _timeSpin: number = 0;
|
||||
private _maxAngle: number = 0;
|
||||
|
||||
protected onLoad(): void {
|
||||
this._wheel = this.animationHandler.findBone(this.wheelBoneName);
|
||||
}
|
||||
|
||||
protected onEnable(): void {
|
||||
this.spineRoot.setActive(false);
|
||||
this._wheel.rotation = 0;
|
||||
this._wheel.update();
|
||||
}
|
||||
|
||||
protected update(): void {
|
||||
if (this._spinning) {
|
||||
this._timer += game.deltaTime * this.speed;
|
||||
const angle = this._maxAngle * this.spinCurve.evaluate(this._timer / this._timeSpin);
|
||||
this._wheel.rotation = this._targetAngle + angle;
|
||||
this._wheel.update();
|
||||
if (this._timer >= this._timeSpin) {
|
||||
this._spinning = false;
|
||||
GachaManager.instance.gachaDone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async show(): Promise<void> {
|
||||
for (let i = 0; i < GachaManager.instance.rewards.length; i++) {
|
||||
this.sprites[i].spriteFrame = GachaManager.instance.rewards[i].icon;
|
||||
}
|
||||
this.spineRoot.setActive(true);
|
||||
await this.animationHandler.setAnimationAsync('appear');
|
||||
|
||||
this.animationHandler.addAnimation('idle', { loop: true });
|
||||
}
|
||||
|
||||
public async spin() {
|
||||
if (this._spinning) return;
|
||||
this._spinning = true;
|
||||
const reward = await GachaManager.instance.getReward();
|
||||
const item = GachaManager.instance.getRewardIndex(reward);
|
||||
if (reward) {
|
||||
this.animationHandler.clearTrack(0);
|
||||
this._targetAngle = -36 * item;
|
||||
this._timer = 0;
|
||||
this._timeSpin = randomRangeInt(10, 15);
|
||||
this._maxAngle = 360 * this._timeSpin;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._spinning = true;
|
||||
GachaManager.instance.gachaDone();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
import {
|
||||
_decorator,
|
||||
CCInteger,
|
||||
CCString,
|
||||
clamp01,
|
||||
Component,
|
||||
easing,
|
||||
Enum,
|
||||
game,
|
||||
lerp,
|
||||
Node,
|
||||
SpriteFrame,
|
||||
tween,
|
||||
UIOpacity,
|
||||
} from 'cc';
|
||||
import GachaBase from '../Base/GachaBase';
|
||||
import GameEvent from '../Events/GameEvent';
|
||||
import Singleton from '../Singleton';
|
||||
import Utils from '../Utilities';
|
||||
import { EventManger } from './EventManger';
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
export enum GachaType {
|
||||
FreeReward,
|
||||
LuckyWheel,
|
||||
LuckyChain,
|
||||
FlipCard,
|
||||
}
|
||||
Enum(GachaType);
|
||||
|
||||
export enum RewardType {
|
||||
Oxi,
|
||||
Gas,
|
||||
Star,
|
||||
Shield,
|
||||
ScoreX2,
|
||||
Magnet,
|
||||
}
|
||||
Enum(RewardType);
|
||||
|
||||
@ccclass('Gacha')
|
||||
class Gacha {
|
||||
@property(CCString)
|
||||
public id: string = '';
|
||||
@property({ type: GachaType })
|
||||
public type: GachaType = GachaType.FreeReward;
|
||||
@property(GachaBase)
|
||||
public gacha: GachaBase;
|
||||
}
|
||||
|
||||
@ccclass('Reward')
|
||||
class RewardConfig {
|
||||
@property(CCString)
|
||||
public id: string = '';
|
||||
@property({ type: RewardType })
|
||||
public type: RewardType = RewardType.Oxi;
|
||||
@property(SpriteFrame)
|
||||
public icon: SpriteFrame;
|
||||
@property(CCInteger)
|
||||
public quantity: number;
|
||||
}
|
||||
|
||||
@ccclass('GachaManager')
|
||||
export default class GachaManager extends Singleton<GachaManager>() {
|
||||
@property(CCString)
|
||||
public gachaId: string;
|
||||
@property(UIOpacity)
|
||||
private container: UIOpacity;
|
||||
@property(Gacha)
|
||||
private gachas: Gacha[] = [];
|
||||
@property(RewardConfig)
|
||||
public rewards: RewardConfig[] = [];
|
||||
|
||||
private _reward: RewardConfig;
|
||||
private _showing: boolean = false;
|
||||
private _showTimer: number = 0;
|
||||
private _idType: string;
|
||||
|
||||
protected onLoad(): void {
|
||||
super.onLoad();
|
||||
this.container.setNodeActive(false);
|
||||
}
|
||||
|
||||
public show(idType: string) {
|
||||
game.timeScale = 0;
|
||||
this.container.setNodeActive(true);
|
||||
this._showTimer = 0;
|
||||
this._showing = true;
|
||||
this._idType = idType;
|
||||
}
|
||||
|
||||
protected update(dt: number): void {
|
||||
if (this._showing) {
|
||||
this._showTimer += game.deltaTime;
|
||||
const k = easing.smooth(clamp01(this._showTimer / 0.2));
|
||||
this.container.opacity = lerp(0, 255, k);
|
||||
if (k === 1) {
|
||||
this._showing = false;
|
||||
this.showGacha();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private showGacha() {
|
||||
const gacha = this.gachas.find((gacha) => gacha.id == this._idType);
|
||||
gacha.gacha.show();
|
||||
}
|
||||
|
||||
public async getReward(): Promise<RewardConfig> {
|
||||
this._reward = this.getRandomReward();
|
||||
return this._reward;
|
||||
}
|
||||
|
||||
public getRewardIndex(reward: RewardConfig): number {
|
||||
return this.rewards.indexOf(reward);
|
||||
}
|
||||
|
||||
public async gachaDone() {
|
||||
console.log(`Gacha reward: ${RewardType[this._reward.type]} quantity: ${this._reward.quantity}`);
|
||||
EventManger.instance.emit(GameEvent.GachaReward, [this._reward.type, this._reward.quantity]);
|
||||
await Utils.delay(1);
|
||||
game.timeScale = 1;
|
||||
tween(this.container)
|
||||
.to(0.1, { opacity: 0 })
|
||||
.call(() => this.container.setNodeActive(false))
|
||||
.start();
|
||||
}
|
||||
|
||||
public getRandomReward() {
|
||||
return this.rewards.getRandom();
|
||||
}
|
||||
|
||||
public setReward(id: string) {
|
||||
this._reward = this.rewards.find((r) => r.id == id);
|
||||
this.gachaDone();
|
||||
}
|
||||
}
|
|
@ -110,7 +110,7 @@
|
|||
"_value": false
|
||||
},
|
||||
"spine": {
|
||||
"_value": false
|
||||
"_value": true
|
||||
},
|
||||
"dragon-bones": {
|
||||
"_value": false
|
||||
|
@ -138,6 +138,7 @@
|
|||
"particle-2d",
|
||||
"physics-2d-box2d",
|
||||
"profiler",
|
||||
"spine",
|
||||
"tween",
|
||||
"ui",
|
||||
"websocket"
|
||||
|
|
Loading…
Reference in New Issue