From cce130755ce8667e04a196ce3955a0b554182aeb Mon Sep 17 00:00:00 2001 From: richarjiang Date: Tue, 30 Sep 2025 17:19:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(combat):=20=E6=96=B0=E5=A2=9E=E6=88=98?= =?UTF-8?q?=E6=96=97=E8=B7=9D=E7=A6=BB=E8=B0=83=E6=95=B4=E4=B8=8E=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE=E5=AF=B9=E9=BD=90=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在场景与脚本中增加 attackPreferredDistance / attackDistanceTolerance 配置 - 碰撞后自动停止移动并调度 adjustPositionsForAttack,确保玩家与怪物保持理想水平距离 - 统一 Y 轴居中,限制在地图边界内,并强制玩家位于右侧 - 优化攻击动画触发,仅播放 attack3 并依据最终朝向设置 direction - 增加日志输出便于调试最终距离与误差 --- assets/scenes/main.scene | 14 ++-- assets/scripts/PlayerController.ts | 124 ++++++++++++++++++++++++++++- 2 files changed, 129 insertions(+), 9 deletions(-) diff --git a/assets/scenes/main.scene b/assets/scenes/main.scene index 4eabe68..41e47ec 100644 --- a/assets/scenes/main.scene +++ b/assets/scenes/main.scene @@ -1915,6 +1915,8 @@ "moveSpeed": 300, "mapWidth": 1080, "mapHeight": 1920, + "attackPreferredDistance": 220, + "attackDistanceTolerance": 10, "_id": "c1AuAU3IlKnLOzgk9vsBr4" }, { @@ -4106,8 +4108,8 @@ "_prefab": null, "_lpos": { "__type__": "cc.Vec3", - "x": 265.122, - "y": -426.369, + "x": 313.621, + "y": -435.009, "z": 0 }, "_lrot": { @@ -4558,13 +4560,13 @@ "_restitution": 0, "_offset": { "__type__": "cc.Vec2", - "x": 2.5, - "y": -2.4 + "x": -8.8, + "y": 2.4 }, "_size": { "__type__": "cc.Size", - "width": 41.4, - "height": 96.6 + "width": 63.9, + "height": 128.4 }, "_id": "b3AIQdLypA3YoQsfzhSOs1" }, diff --git a/assets/scripts/PlayerController.ts b/assets/scripts/PlayerController.ts index 1f6eac0..34af26f 100644 --- a/assets/scripts/PlayerController.ts +++ b/assets/scripts/PlayerController.ts @@ -37,6 +37,12 @@ export class PlayerController extends Component { @property mapHeight: number = 2560; // 地图高度 + @property({ tooltip: '玩家与怪物进入战斗时的理想距离' }) + attackPreferredDistance: number = 80; + + @property({ tooltip: '允许的距离误差范围,超出后会进行位置调整' }) + attackDistanceTolerance: number = 20; + private isMoving: boolean = false; private isAttacking: boolean = false; private currentPath: Vec3[] = []; @@ -51,6 +57,9 @@ export class PlayerController extends Component { private hasWinTimes = 0 + private readonly _tempVec3A: Vec3 = new Vec3(); + private readonly _tempVec3B: Vec3 = new Vec3(); + // 道具列表 private props: Node[] = []; @@ -254,11 +263,15 @@ export class PlayerController extends Component { // 限制玩家位置在地图边界内 private clampPlayerPosition(position: Vec3): Vec3 { + return this.clampPositionWithinMap(position); + } + + private clampPositionWithinMap(position: Vec3): Vec3 { // 计算地图边界(地图锚点为0.5,0.5,所以范围是-mapWidth/2到+mapWidth/2) const mapHalfWidth = this.mapWidth * 0.5; const mapHalfHeight = this.mapHeight * 0.5; - // 限制玩家位置 + // 限制位置 const clampedPosition = position.clone(); clampedPosition.x = Math.max(-mapHalfWidth, Math.min(mapHalfWidth, position.x)); clampedPosition.y = Math.max(-mapHalfHeight, Math.min(mapHalfHeight, position.y)); @@ -406,6 +419,7 @@ export class PlayerController extends Component { } onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D) { + console.log('碰撞检测', selfCollider.node.name, otherCollider.node.name); if (otherCollider.node.name.startsWith('guai_')) { this.handleAttack(otherCollider); } @@ -415,9 +429,22 @@ export class PlayerController extends Component { * 处理攻击逻辑 */ private handleAttack(otherCollider: Collider2D) { + if (this.isAttacking) { + return + } this.isAttacking = true; + + console.log('开始攻击,怪物名称:', otherCollider.node.name); + this.stopMovement(); + + this.scheduleOnce(() => { + this.adjustPositionsForAttack(otherCollider.node); + }, 0); + + + // 获取玩家和怪物的生命值 const playerHpLabel = this.player.getChildByName('hp'); const monsterHpLabel = otherCollider.node.getChildByName('hp'); @@ -456,7 +483,8 @@ export class PlayerController extends Component { if (monsterAnimation) { monsterAnimation.play(`${otherCollider.node.name}_attack`); } - this.switchAnimation(this.currentDirection === 3 ? 'attack3' : 'attack5'); + // this.switchAnimation(this.currentDirection === 3 ? 'attack3' : 'attack5'); + this.switchAnimation('attack3'); // 1.2秒后判定攻击结果 this.scheduleOnce(async () => { @@ -486,8 +514,10 @@ export class PlayerController extends Component { // 1秒后怪物消失 this.scheduleOnce(() => { - otherCollider.node.destroy(); console.log('怪物已消失'); + console.log('otherCollider', otherCollider); + + otherCollider.node?.destroy(); if (this.hasWinTimes === 10) { this.isWin = true @@ -540,6 +570,94 @@ export class PlayerController extends Component { }, 1.2); } + private adjustPositionsForAttack(monsterNode: Node) { + if (!this.player || !monsterNode) { + return; + } + + const desiredDistance = Math.max(0, this.attackPreferredDistance); + const tolerance = Math.max(0, this.attackDistanceTolerance); + + if (desiredDistance === 0) { + return; + } + + const playerWorldPos = this._tempVec3A; + const monsterWorldPos = this._tempVec3B; + this.player.getWorldPosition(playerWorldPos); + monsterNode.getWorldPosition(monsterWorldPos); + + const playerTarget = playerWorldPos.clone(); + const monsterTarget = monsterWorldPos.clone(); + + const deltaX = playerWorldPos.x - monsterWorldPos.x; + const horizontalDistance = Math.abs(deltaX); + const verticalDifference = Math.abs(playerWorldPos.y - monsterWorldPos.y); + + const targetY = (playerWorldPos.y + monsterWorldPos.y) * 0.5; + let adjusted = false; + if (verticalDifference > 1e-3) { + playerTarget.y = targetY; + monsterTarget.y = targetY; + adjusted = true; + } + + const orderCorrect = playerWorldPos.x > monsterWorldPos.x; + const distanceOutOfRange = horizontalDistance < desiredDistance - tolerance || horizontalDistance > desiredDistance + tolerance; + + if (!orderCorrect || distanceOutOfRange) { + const midX = (playerWorldPos.x + monsterWorldPos.x) * 0.5; + const halfDistance = desiredDistance * 0.5; + playerTarget.x = midX + halfDistance; + monsterTarget.x = midX - halfDistance; + adjusted = true; + } + + if (!adjusted) { + return; + } + + // 确保最终目标仍然满足玩家在右侧的要求 + if (playerTarget.x <= monsterTarget.x) { + const swapMidX = (playerTarget.x + monsterTarget.x) * 0.5; + const halfDistance = desiredDistance * 0.5; + playerTarget.x = swapMidX + halfDistance; + monsterTarget.x = swapMidX - halfDistance; + } + + const clampedPlayer = this.clampPositionWithinMap(playerTarget); + const clampedMonster = this.clampPositionWithinMap(monsterTarget); + + this.setNodeWorldPosition(this.player, clampedPlayer); + this.setNodeWorldPosition(monsterNode, clampedMonster); + + this.player.getWorldPosition(playerWorldPos); + monsterNode.getWorldPosition(monsterWorldPos); + const finalHorizontalDistance = Math.abs(playerWorldPos.x - monsterWorldPos.x); + const finalVerticalOffset = Math.abs(playerWorldPos.y - monsterWorldPos.y); + console.log(`战斗位置已调整,水平距离: ${finalHorizontalDistance.toFixed(2)}, 垂直误差: ${finalVerticalOffset.toFixed(2)}`); + if (playerWorldPos.x >= monsterWorldPos.x) { + this.currentDirection = 3; + } + } + + private setNodeWorldPosition(node: Node, worldPos: Vec3) { + const parent = node.parent; + if (!parent) { + node.setWorldPosition(worldPos); + return; + } + + const parentTransform = parent.getComponent(UITransform); + if (parentTransform) { + const localResult = parentTransform.convertToNodeSpaceAR(new Vec3(worldPos.x, worldPos.y, worldPos.z)); + node.setPosition(localResult); + return; + } + + node.setWorldPosition(worldPos); + } + /** * 播放生命值标签强调动画(成功时) */