feat(combat): 新增战斗距离调整与位置对齐逻辑
- 在场景与脚本中增加 attackPreferredDistance / attackDistanceTolerance 配置 - 碰撞后自动停止移动并调度 adjustPositionsForAttack,确保玩家与怪物保持理想水平距离 - 统一 Y 轴居中,限制在地图边界内,并强制玩家位于右侧 - 优化攻击动画触发,仅播放 attack3 并依据最终朝向设置 direction - 增加日志输出便于调试最终距离与误差
This commit is contained in:
@@ -1915,6 +1915,8 @@
|
|||||||
"moveSpeed": 300,
|
"moveSpeed": 300,
|
||||||
"mapWidth": 1080,
|
"mapWidth": 1080,
|
||||||
"mapHeight": 1920,
|
"mapHeight": 1920,
|
||||||
|
"attackPreferredDistance": 220,
|
||||||
|
"attackDistanceTolerance": 10,
|
||||||
"_id": "c1AuAU3IlKnLOzgk9vsBr4"
|
"_id": "c1AuAU3IlKnLOzgk9vsBr4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -4106,8 +4108,8 @@
|
|||||||
"_prefab": null,
|
"_prefab": null,
|
||||||
"_lpos": {
|
"_lpos": {
|
||||||
"__type__": "cc.Vec3",
|
"__type__": "cc.Vec3",
|
||||||
"x": 265.122,
|
"x": 313.621,
|
||||||
"y": -426.369,
|
"y": -435.009,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"_lrot": {
|
"_lrot": {
|
||||||
@@ -4558,13 +4560,13 @@
|
|||||||
"_restitution": 0,
|
"_restitution": 0,
|
||||||
"_offset": {
|
"_offset": {
|
||||||
"__type__": "cc.Vec2",
|
"__type__": "cc.Vec2",
|
||||||
"x": 2.5,
|
"x": -8.8,
|
||||||
"y": -2.4
|
"y": 2.4
|
||||||
},
|
},
|
||||||
"_size": {
|
"_size": {
|
||||||
"__type__": "cc.Size",
|
"__type__": "cc.Size",
|
||||||
"width": 41.4,
|
"width": 63.9,
|
||||||
"height": 96.6
|
"height": 128.4
|
||||||
},
|
},
|
||||||
"_id": "b3AIQdLypA3YoQsfzhSOs1"
|
"_id": "b3AIQdLypA3YoQsfzhSOs1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ export class PlayerController extends Component {
|
|||||||
@property
|
@property
|
||||||
mapHeight: number = 2560; // 地图高度
|
mapHeight: number = 2560; // 地图高度
|
||||||
|
|
||||||
|
@property({ tooltip: '玩家与怪物进入战斗时的理想距离' })
|
||||||
|
attackPreferredDistance: number = 80;
|
||||||
|
|
||||||
|
@property({ tooltip: '允许的距离误差范围,超出后会进行位置调整' })
|
||||||
|
attackDistanceTolerance: number = 20;
|
||||||
|
|
||||||
private isMoving: boolean = false;
|
private isMoving: boolean = false;
|
||||||
private isAttacking: boolean = false;
|
private isAttacking: boolean = false;
|
||||||
private currentPath: Vec3[] = [];
|
private currentPath: Vec3[] = [];
|
||||||
@@ -51,6 +57,9 @@ export class PlayerController extends Component {
|
|||||||
|
|
||||||
private hasWinTimes = 0
|
private hasWinTimes = 0
|
||||||
|
|
||||||
|
private readonly _tempVec3A: Vec3 = new Vec3();
|
||||||
|
private readonly _tempVec3B: Vec3 = new Vec3();
|
||||||
|
|
||||||
// 道具列表
|
// 道具列表
|
||||||
private props: Node[] = [];
|
private props: Node[] = [];
|
||||||
|
|
||||||
@@ -254,11 +263,15 @@ export class PlayerController extends Component {
|
|||||||
|
|
||||||
// 限制玩家位置在地图边界内
|
// 限制玩家位置在地图边界内
|
||||||
private clampPlayerPosition(position: Vec3): Vec3 {
|
private clampPlayerPosition(position: Vec3): Vec3 {
|
||||||
|
return this.clampPositionWithinMap(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private clampPositionWithinMap(position: Vec3): Vec3 {
|
||||||
// 计算地图边界(地图锚点为0.5,0.5,所以范围是-mapWidth/2到+mapWidth/2)
|
// 计算地图边界(地图锚点为0.5,0.5,所以范围是-mapWidth/2到+mapWidth/2)
|
||||||
const mapHalfWidth = this.mapWidth * 0.5;
|
const mapHalfWidth = this.mapWidth * 0.5;
|
||||||
const mapHalfHeight = this.mapHeight * 0.5;
|
const mapHalfHeight = this.mapHeight * 0.5;
|
||||||
|
|
||||||
// 限制玩家位置
|
// 限制位置
|
||||||
const clampedPosition = position.clone();
|
const clampedPosition = position.clone();
|
||||||
clampedPosition.x = Math.max(-mapHalfWidth, Math.min(mapHalfWidth, position.x));
|
clampedPosition.x = Math.max(-mapHalfWidth, Math.min(mapHalfWidth, position.x));
|
||||||
clampedPosition.y = Math.max(-mapHalfHeight, Math.min(mapHalfHeight, position.y));
|
clampedPosition.y = Math.max(-mapHalfHeight, Math.min(mapHalfHeight, position.y));
|
||||||
@@ -406,6 +419,7 @@ export class PlayerController extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D) {
|
onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D) {
|
||||||
|
console.log('碰撞检测', selfCollider.node.name, otherCollider.node.name);
|
||||||
if (otherCollider.node.name.startsWith('guai_')) {
|
if (otherCollider.node.name.startsWith('guai_')) {
|
||||||
this.handleAttack(otherCollider);
|
this.handleAttack(otherCollider);
|
||||||
}
|
}
|
||||||
@@ -415,9 +429,22 @@ export class PlayerController extends Component {
|
|||||||
* 处理攻击逻辑
|
* 处理攻击逻辑
|
||||||
*/
|
*/
|
||||||
private handleAttack(otherCollider: Collider2D) {
|
private handleAttack(otherCollider: Collider2D) {
|
||||||
|
if (this.isAttacking) {
|
||||||
|
return
|
||||||
|
}
|
||||||
this.isAttacking = true;
|
this.isAttacking = true;
|
||||||
|
|
||||||
|
|
||||||
console.log('开始攻击,怪物名称:', otherCollider.node.name);
|
console.log('开始攻击,怪物名称:', otherCollider.node.name);
|
||||||
|
|
||||||
|
this.stopMovement();
|
||||||
|
|
||||||
|
this.scheduleOnce(() => {
|
||||||
|
this.adjustPositionsForAttack(otherCollider.node);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 获取玩家和怪物的生命值
|
// 获取玩家和怪物的生命值
|
||||||
const playerHpLabel = this.player.getChildByName('hp');
|
const playerHpLabel = this.player.getChildByName('hp');
|
||||||
const monsterHpLabel = otherCollider.node.getChildByName('hp');
|
const monsterHpLabel = otherCollider.node.getChildByName('hp');
|
||||||
@@ -456,7 +483,8 @@ export class PlayerController extends Component {
|
|||||||
if (monsterAnimation) {
|
if (monsterAnimation) {
|
||||||
monsterAnimation.play(`${otherCollider.node.name}_attack`);
|
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秒后判定攻击结果
|
// 1.2秒后判定攻击结果
|
||||||
this.scheduleOnce(async () => {
|
this.scheduleOnce(async () => {
|
||||||
@@ -486,8 +514,10 @@ export class PlayerController extends Component {
|
|||||||
|
|
||||||
// 1秒后怪物消失
|
// 1秒后怪物消失
|
||||||
this.scheduleOnce(() => {
|
this.scheduleOnce(() => {
|
||||||
otherCollider.node.destroy();
|
|
||||||
console.log('怪物已消失');
|
console.log('怪物已消失');
|
||||||
|
console.log('otherCollider', otherCollider);
|
||||||
|
|
||||||
|
otherCollider.node?.destroy();
|
||||||
|
|
||||||
if (this.hasWinTimes === 10) {
|
if (this.hasWinTimes === 10) {
|
||||||
this.isWin = true
|
this.isWin = true
|
||||||
@@ -540,6 +570,94 @@ export class PlayerController extends Component {
|
|||||||
}, 1.2);
|
}, 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