feat(combat): 新增战斗距离调整与位置对齐逻辑

- 在场景与脚本中增加 attackPreferredDistance / attackDistanceTolerance 配置
- 碰撞后自动停止移动并调度 adjustPositionsForAttack,确保玩家与怪物保持理想水平距离
- 统一 Y 轴居中,限制在地图边界内,并强制玩家位于右侧
- 优化攻击动画触发,仅播放 attack3 并依据最终朝向设置 direction
- 增加日志输出便于调试最终距离与误差
This commit is contained in:
richarjiang
2025-09-30 17:19:20 +08:00
parent a19a3d07a9
commit cce130755c
2 changed files with 129 additions and 9 deletions

View File

@@ -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"
},

View File

@@ -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);
}
/**
* 播放生命值标签强调动画(成功时)
*/