feat(combat): 新增战斗距离调整与位置对齐逻辑
- 在场景与脚本中增加 attackPreferredDistance / attackDistanceTolerance 配置 - 碰撞后自动停止移动并调度 adjustPositionsForAttack,确保玩家与怪物保持理想水平距离 - 统一 Y 轴居中,限制在地图边界内,并强制玩家位于右侧 - 优化攻击动画触发,仅播放 attack3 并依据最终朝向设置 direction - 增加日志输出便于调试最终距离与误差
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放生命值标签强调动画(成功时)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user