import { _decorator, AudioClip, CCFloat, Collider2D, Component, Contact2DType, Director, director, EventTarget, IPhysics2DContact, RigidBody2D, Vec2, geometry, math, Vec3, ParticleSystem, Prefab, CircleCollider2D, Animation, Sprite, Node, SpriteFrame, } from 'cc'; import IPoolable from '../Pool/IPoolable'; import { SoundManager } from '../Manager/SoundManager'; import PhysicsGroup from '../Enum/PhysicGroup'; import ObjectPool from '../Pool/ObjectPool'; import Utilities from '../Utilities'; import { EventManger } from '../Manager/EventManger'; import GameEvent from '../Events/GameEvent'; import { SequenceSound } from '../Environments/SequenceSound'; const { ccclass, property } = _decorator; @ccclass('Ball') export class Ball extends Component implements IPoolable { @property({ type: Prefab, visible: true }) private _impactPrefab: Prefab; @property({ type: CCFloat, visible: true }) private _maxSpeed: number; @property({ type: RigidBody2D, visible: true }) private _rigidBody: RigidBody2D; @property({ type: Animation, visible: true }) private _animation: Animation; @property({ type: ParticleSystem, visible: true }) private _trail: ParticleSystem; @property({ type: ParticleSystem, visible: true }) private _buffParticle: ParticleSystem; @property({ type: ParticleSystem, visible: true }) private _fireParticle: ParticleSystem; @property({ type: CircleCollider2D, visible: true }) private _collider: CircleCollider2D; @property({ type: Sprite, visible: true }) private _normalSprite: Sprite; @property({ type: Sprite, visible: true }) private _cheeseModeSprite: Sprite; @property({ type: Sprite, visible: true }) private _spriteShadow: Sprite; @property({ type: AudioClip, visible: true }) private _impactSound: AudioClip; @property({ type: AudioClip, visible: true }) private _impactFlipperSound: AudioClip; @property({ type: SequenceSound, visible: true }) private _collectSound: SequenceSound; @property({ type: SequenceSound, visible: true }) private _cheeseModeCollectSound: SequenceSound; @property({ type: geometry.AnimationCurve, visible: true }) private _jumpCurve: geometry.AnimationCurve = new geometry.AnimationCurve(); private _impactPool: ObjectPool; private _isHit = false; private _isJumping = false; private _jumpTime: number; private _jumpDuration: number; private _parent: Node; private _cheeseModeOn = false; public init(boosterActive: boolean) { if (boosterActive) { this.onBoosterActive(); } else { this.onBoosterDisable(); } } protected onLoad(): void { if (this._collider) { this._collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this); this._collider.on(Contact2DType.END_CONTACT, this.onEndContact, this); } director.on(Director.EVENT_AFTER_PHYSICS, this.afterPhysicUpdate, this); this._impactPool = new ObjectPool(this._impactPrefab, 10, false); EventManger.instance.on(GameEvent.BoosterActive, this.onBoosterActive, this); EventManger.instance.on(GameEvent.BoosterDisable, this.onBoosterDisable, this); } protected update(dt: number): void { if (this._isJumping) { this._jumpTime += dt; let jumpProcess = this._jumpTime / this._jumpDuration; jumpProcess = math.clamp01(jumpProcess); let scale = Vec3.ONE.clone(); const jumpValue = this._jumpCurve.evaluate(jumpProcess); scale = scale.add(Vec3.ONE.clone().multiplyScalar(jumpValue)); this._normalSprite.node.setScale(scale); this._cheeseModeSprite.node.setScale(scale); this._spriteShadow.node.setScale(scale); this._trail.trailModule.widthRatio.multiplier = scale.x; this._spriteShadow.node.setWorldPosition( this.node.worldPosition.clone().add(Vec3.ONE.clone().multiplyScalar(-jumpValue * 100)), ); if (jumpProcess >= 1) { this._spriteShadow.node.setPosition(Vec3.ZERO); this._normalSprite.node.setScale(Vec3.ONE); this._cheeseModeSprite.node.setScale(Vec3.ONE); this._trail.trailModule.widthRatio.multiplier = 1; this._isJumping = false; this._collider.group = PhysicsGroup.BALL; this._rigidBody.group = PhysicsGroup.BALL; this.node.setParent(this._parent); } } } protected lateUpdate(dt: number): void { if (this._rigidBody.linearVelocity.length() > 60) { this._fireParticle.rateOverTime.constant = 8; this._fireParticle.rateOverDistance.constant = 0.02; // if (this._fireParticle.isStopped) { // } } else { this._fireParticle.rateOverDistance.constant = 0; this._fireParticle.rateOverTime.constant = 0; // if (this._fireParticle.isPlaying) { // this._fireParticle.stopEmitting(); // } } } private async onBeginContact( selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null, ) { if (this._isHit) return; this._isHit = true; const velocity = this._rigidBody.linearVelocity.length(); if (!otherCollider.sensor) { if (velocity >= 5) { this._animation.play(); let hitPoint = contact.getWorldManifold().points[0]; if (!hitPoint) { const dir = otherCollider.node .getWorldPosition() .subtract(selfCollider.node.getWorldPosition()) .normalize(); dir.multiplyScalar(this._collider.radius / 2); const point = selfCollider.node.getWorldPosition().add(dir); hitPoint = new Vec2(point.x, point.y); } const hitFx = this._impactPool.get(ParticleSystem, this.node.parent); hitFx.node.setWorldPosition(new Vec3(hitPoint.x, hitPoint.y, 10)); SoundManager.instance.playSfx( otherCollider.group == PhysicsGroup.FLIPPER ? this._impactFlipperSound : this._impactSound, ); await Utilities.waitUntil(() => hitFx.isStopped, 0.1); this._impactPool.release(hitFx); } } else if (otherCollider.tag == 1) { if (this._cheeseModeOn) { this._cheeseModeCollectSound.playSound(); } else { this._collectSound.playSound(); } } } private onEndContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) { this._isHit = false; } private onBoosterActive() { // this._fireParticle.play(); this._cheeseModeOn = true; this._cheeseModeSprite.setNodeActive(true); this._normalSprite.setNodeActive(false); } private onBoosterDisable() { // this._fireParticle.stop(); this._cheeseModeOn = false; this._cheeseModeSprite.setNodeActive(false); this._normalSprite.setNodeActive(true); } private afterPhysicUpdate() { let velocity = this._rigidBody.linearVelocity.length(); if (velocity > this._maxSpeed) { this._rigidBody.linearVelocity = this._rigidBody.linearVelocity.normalize().multiplyScalar(this._maxSpeed); } } public addForce(force: Vec2) { this._rigidBody.applyLinearImpulseToCenter(force, true); } public throwBall(force: Vec2) { this._collider.group = PhysicsGroup.BALL_THROWING; this._rigidBody.group = PhysicsGroup.BALL_THROWING; this._rigidBody.applyAngularImpulse(-5 * force.x || 2, true); this._rigidBody.applyLinearImpulseToCenter(force, true); this._isJumping = true; this._jumpTime = 0; this._jumpDuration = this._rigidBody.linearVelocity.length() * 0.05; } public playMultiBallEffect() { this._buffParticle.play(); } public setActiveRigi(value: boolean) { // this._rigidBody.enabled = value; if (!value) { this._rigidBody.linearVelocity = Vec2.ZERO.clone(); this._rigidBody.angularVelocity = 0; } } onGet() { this._isJumping = false; this._isHit = false; this._rigidBody.enabled = true; this._parent = this.node.getParent(); this._fireParticle.rateOverDistance.constant = 0; this._fireParticle.rateOverTime.constant = 0; } onRelease() { this._rigidBody.linearVelocity = Vec2.ZERO.clone(); this._rigidBody.angularVelocity = 0; this._buffParticle.stop(); this.getComponentsInChildren(ParticleSystem).forEach((particle) => { particle.clear(); }); } }