From 71231ad7591a894d3c7fdedf8888493535ac451b Mon Sep 17 00:00:00 2001 From: richarjiang Date: Fri, 10 Oct 2025 15:17:58 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E6=94=BB=E5=87=BB?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/scenes/main.scene | 4 +- assets/scripts/PlayerController.ts | 148 +++++++++++++++++++++++++---- 2 files changed, 133 insertions(+), 19 deletions(-) diff --git a/assets/scenes/main.scene b/assets/scenes/main.scene index 8a0699a..465bb45 100644 --- a/assets/scenes/main.scene +++ b/assets/scenes/main.scene @@ -3748,8 +3748,8 @@ "_prefab": null, "_lpos": { "__type__": "cc.Vec3", - "x": 310.51, - "y": -233.009, + "x": 283.495, + "y": -254.621, "z": 0 }, "_lrot": { diff --git a/assets/scripts/PlayerController.ts b/assets/scripts/PlayerController.ts index 896f50a..9742e6d 100644 --- a/assets/scripts/PlayerController.ts +++ b/assets/scripts/PlayerController.ts @@ -1,4 +1,4 @@ -import { _decorator, Component, Node, Vec3, input, Input, EventTouch, Camera, view, tween, Animation, Collider2D, Contact2DType, Label, Color, Canvas, UITransform, AudioSource, Sprite, director, PhysicsSystem2D, EPhysics2DDrawFlags } from 'cc'; +import { _decorator, Component, Node, Vec3, input, Input, EventTouch, Camera, view, tween, Animation, Collider2D, BoxCollider2D, Contact2DType, Label, Color, Canvas, UITransform, AudioSource, Sprite, director, PhysicsSystem2D, EPhysics2DDrawFlags } from 'cc'; import { TiledMapPathfinder } from './TiledMapPathfinder'; const { ccclass, property } = _decorator; @@ -58,6 +58,7 @@ export class PlayerController extends Component { private lastPosition: Vec3 = new Vec3(); // 上一帧位置 private hasWinTimes = 0 + private readonly attackAlignGap = 10; // 玩家与怪物对阵时的额外左右间距,单位:像素 // 道具列表 private props: Node[] = []; @@ -575,26 +576,127 @@ export class PlayerController extends Component { // 禁用碰撞器,防止重复触发 otherCollider.enabled = false; if (otherCollider.node.name.startsWith('guai_')) { - this.handleAttack(otherCollider); + void this.handleAttack(selfCollider, otherCollider); } else if (otherCollider.node.name.startsWith('box_')) { this.handleBoxCollision(otherCollider); } } + /** + * 将玩家移动到怪物正对位置,确保攻击前双方站位合理 + */ + private alignPlayerForAttack(selfCollider: Collider2D, monsterCollider: Collider2D): Promise { + return new Promise((resolve) => { + if (!this.player || !selfCollider || !monsterCollider || !monsterCollider.node || !monsterCollider.node.isValid) { + resolve(); + return; + } + + const playerNode = this.player; + const monsterNode = monsterCollider.node; + + const playerWorldPos = playerNode.worldPosition.clone(); + const monsterWorldPos = monsterNode.worldPosition.clone(); + + const playerBox = selfCollider instanceof BoxCollider2D ? selfCollider : null; + const monsterBox = monsterCollider instanceof BoxCollider2D ? monsterCollider : null; + + const playerScale = playerNode.worldScale; + const monsterScale = monsterNode.worldScale; + + const playerHalfWidth = playerBox ? (playerBox.size.x * Math.abs(playerScale.x)) / 2 : 40; + const monsterHalfWidth = monsterBox ? (monsterBox.size.x * Math.abs(monsterScale.x)) / 2 : 60; + + const playerOffsetX = playerBox ? playerBox.offset.x * playerScale.x : 0; + const playerOffsetY = playerBox ? playerBox.offset.y * playerScale.y : 0; + const monsterOffsetX = monsterBox ? monsterBox.offset.x * monsterScale.x : 0; + const monsterOffsetY = monsterBox ? monsterBox.offset.y * monsterScale.y : 0; + + const playerCenterX = playerWorldPos.x + playerOffsetX; + const monsterCenterX = monsterWorldPos.x + monsterOffsetX; + const standOnLeft = playerCenterX <= monsterCenterX; + + const totalHalfWidth = playerHalfWidth + monsterHalfWidth + this.attackAlignGap; + const directionMultiplier = standOnLeft ? -1 : 1; + + const targetWorldPos = new Vec3( + monsterWorldPos.x + monsterOffsetX + directionMultiplier * totalHalfWidth - playerOffsetX, + monsterWorldPos.y + monsterOffsetY - playerOffsetY, + playerWorldPos.z + ); + + const targetLocalPos = this.convertWorldToParentSpace(playerNode, targetWorldPos); + const currentLocalPos = playerNode.position.clone(); + + const distance = Vec3.distance(currentLocalPos, targetLocalPos); + + if (distance < 1) { + playerNode.setPosition(targetLocalPos); + this.currentDirection = standOnLeft ? 5 : 3; + resolve(); + return; + } + + const movingRight = targetLocalPos.x >= currentLocalPos.x; + const moveAnimation = movingRight ? 'walk5' : 'walk3'; + this.switchAnimation(moveAnimation); + + const baseDuration = this.moveSpeed > 0 ? distance / this.moveSpeed : 0.2; + const duration = Math.min(Math.max(baseDuration, 0.12), 0.45); + + tween(playerNode) + .to(duration, { position: targetLocalPos }, { + easing: 'smooth', + onComplete: () => { + playerNode.setPosition(targetLocalPos); + this.currentDirection = standOnLeft ? 5 : 3; + resolve(); + } + }) + .start(); + }); + } + + private convertWorldToParentSpace(node: Node, worldPos: Vec3): Vec3 { + const parent = node.parent; + if (!parent) { + return worldPos.clone(); + } + + const parentTransform = parent.getComponent(UITransform); + if (parentTransform) { + return parentTransform.convertToNodeSpaceAR(worldPos); + } + + const fallback = worldPos.clone(); + fallback.subtract(parent.worldPosition); + return fallback; + } + /** * 处理攻击逻辑 */ - private handleAttack(otherCollider: Collider2D) { + private async handleAttack(selfCollider: Collider2D, otherCollider: Collider2D) { if (this.isAttacking) { - return + return; } - this.isAttacking = true; + if (!this.player || !otherCollider || !otherCollider.node || !otherCollider.node.isValid) { + return; + } + + this.isAttacking = true; + this.stopMovement(); + + await this.alignPlayerForAttack(selfCollider, otherCollider); + + if (!this.player || !otherCollider.node || !otherCollider.node.isValid) { + this.isAttacking = false; + return; + } console.log('开始攻击,怪物名称:', otherCollider.node.name); - this.stopMovement(); - // 获取玩家和怪物的生命值 const playerHpLabel = this.player.getChildByName('hp'); const monsterHpLabel = otherCollider.node.getChildByName('hp'); @@ -629,25 +731,35 @@ export class PlayerController extends Component { } // 播放攻击动画 - const monsterAnimation = otherCollider.node.getChildByName('Anim').getComponent(Animation); + const animNode = otherCollider.node.getChildByName('Anim'); + const monsterAnimation = animNode ? animNode.getComponent(Animation) : null; if (monsterAnimation) { monsterAnimation.play(`${otherCollider.node.name}_attack`); } this.switchAnimation(this.currentDirection === 3 ? 'attack3' : 'attack5'); - // this.switchAnimation('attack3'); // 1.2秒后判定攻击结果 this.scheduleOnce(async () => { + if (!this.player || !playerLabel.isValid || !monsterLabel.isValid) { + this.isAttacking = false; + if (this.attackAudio) { + const audioSource = this.attackAudio.getComponent(AudioSource); + if (audioSource) { + audioSource.stop(); + } + } + return; + } // 比较生命值,判断输赢 console.log('判定攻击结果,玩家HP:', playerHp, '怪物HP:', monsterHp); if (playerHp >= monsterHp) { - const hit = otherCollider.node.getChildByName('Hit') + const hit = otherCollider.node.getChildByName('Hit'); if (hit) { hit.active = true; } - this.hasWinTimes++ + this.hasWinTimes++; // 玩家获胜 console.log('玩家获胜!更新玩家生命值为:', playerHp + monsterHp); @@ -665,19 +777,21 @@ export class PlayerController extends Component { // 如果是攻击 guai_2 并且成功,创建道具飞向 player 的动画 if (otherCollider.node.name === 'guai_2') { - await this.createPropsFlyToPlayerAnimation() + await this.createPropsFlyToPlayerAnimation(); } // 1秒后怪物消失 this.scheduleOnce(() => { - console.log('怪物已消失'); - console.log('otherCollider', otherCollider); + if (!otherCollider.node || !otherCollider.node.isValid) { + return; + } - otherCollider.node?.destroy(); + console.log('怪物已消失'); + otherCollider.node.destroy(); if (this.hasWinTimes === 7) { - this.isWin = true - this.showBonusPopup() + this.isWin = true; + this.showBonusPopup(); } }, 1);