feat(player): 改进玩家动画系统,支持四方向动画
- 添加新的动画文件支持上下左右四个方向的站立和行走动画 - 重构PlayerDirection枚举,支持左上、左下、右上、右下四个象限 - 优化动画切换逻辑,增加动画候选机制和兜底策略 - 改进方向判断算法,基于移动增量精确计算朝向 - 移除BonusWuqi相关资源和节点引用 - 更新场景文件,添加新动画剪辑引用
This commit is contained in:
@@ -6,6 +6,13 @@ const { ccclass, property } = _decorator;
|
||||
// EPhysics2DDrawFlags.Aabb |
|
||||
// EPhysics2DDrawFlags.Shape;
|
||||
|
||||
enum PlayerDirection {
|
||||
LeftUp = 'LeftUp',
|
||||
LeftDown = 'LeftDown',
|
||||
RightUp = 'RightUp',
|
||||
RightDown = 'RightDown',
|
||||
}
|
||||
|
||||
@ccclass('PlayerController')
|
||||
export class PlayerController extends Component {
|
||||
@property(Canvas)
|
||||
@@ -38,12 +45,12 @@ export class PlayerController extends Component {
|
||||
private currentPath: Vec3[] = [];
|
||||
private currentPathIndex: number = 0;
|
||||
private originalPosition: Vec3 = new Vec3();
|
||||
private currentAnimation: string = 'stand'; // 当前播放的动画
|
||||
private currentAnimation: string | null = null; // 当前播放的动画剪辑名称
|
||||
private lastTargetPosition: Vec3 = new Vec3(); // 上一个目标位置,用于方向判断
|
||||
private isUpgraded: boolean = false; // 玩家是否已升级
|
||||
private isGameOver: boolean = false; // 游戏是否结束(玩家死亡)
|
||||
private isWin: boolean = false; // 游戏是否胜利(到达终点)
|
||||
private currentDirection: number = 5; // 当前玩家朝向:3表示左/上,5表示右/下,默认为5
|
||||
private currentDirection: PlayerDirection = PlayerDirection.RightDown; // 当前玩家朝向:四象限(左上/左下/右上/右下)
|
||||
|
||||
// 平滑移动相关变量
|
||||
private moveTween: any = null; // 当前移动的tween对象
|
||||
@@ -279,20 +286,13 @@ export class PlayerController extends Component {
|
||||
// 如果移动距离很小,保持当前动画
|
||||
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
if (distance < 1) {
|
||||
return this.currentAnimation.includes('walk') ? 'walk' : this.currentAnimation;
|
||||
if (this.isCurrentAction('walk')) {
|
||||
return 'walk';
|
||||
}
|
||||
return 'stand';
|
||||
}
|
||||
|
||||
// 计算主要移动方向
|
||||
const absX = Math.abs(deltaX);
|
||||
const absY = Math.abs(deltaY);
|
||||
|
||||
if (absX > absY) {
|
||||
// 水平移动为主
|
||||
this.currentDirection = deltaX < 0 ? 3 : 5;
|
||||
} else {
|
||||
// 垂直移动为主
|
||||
this.currentDirection = deltaY < 0 ? 3 : 5;
|
||||
}
|
||||
this.currentDirection = this.resolveDirectionFromDelta(deltaX, deltaY);
|
||||
|
||||
// 只返回基础动画名称,不包含方向信息
|
||||
return 'walk';
|
||||
@@ -302,63 +302,65 @@ export class PlayerController extends Component {
|
||||
* 切换动画,避免不必要的切换
|
||||
* 根据 this.currentDirection 来决定 player 的 scale 是否要取反,不再需要通过动画名称进行区分
|
||||
*/
|
||||
private switchAnimation(animationName: string) {
|
||||
private switchAnimation(actionName: string) {
|
||||
if (!this.player) {
|
||||
console.warn('Player节点未设置,无法切换动画');
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据动画类型获取基础动画名称,不再包含方向信息
|
||||
let baseAnimationName = animationName;
|
||||
if (animationName === 'stand') {
|
||||
baseAnimationName = 'stand';
|
||||
} else if (animationName.startsWith('walk')) {
|
||||
baseAnimationName = 'walk';
|
||||
} else if (animationName.startsWith('attack')) {
|
||||
baseAnimationName = 'attack';
|
||||
} else if (animationName.startsWith('die')) {
|
||||
baseAnimationName = 'die';
|
||||
const animNode = this.player.getChildByName('Anim');
|
||||
if (!animNode) {
|
||||
console.warn('未找到Anim子节点,无法切换动画');
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果玩家已升级,在动画名称后添加 "_2" 后缀
|
||||
let finalAnimationName = baseAnimationName;
|
||||
if (this.isUpgraded && !baseAnimationName.endsWith('_2')) {
|
||||
finalAnimationName = baseAnimationName + '_2';
|
||||
const animation = animNode.getComponent(Animation);
|
||||
if (!animation) {
|
||||
console.warn('未找到Animation组件,无法播放动画');
|
||||
return;
|
||||
}
|
||||
|
||||
const candidates = this.getAnimationNameCandidates(actionName);
|
||||
let finalAnimationName: string | null = null;
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (!candidate) {
|
||||
continue;
|
||||
}
|
||||
const state = animation.getState(candidate);
|
||||
if (state) {
|
||||
finalAnimationName = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!finalAnimationName) {
|
||||
const fallback = this.isUpgraded ? 'stand_2' : 'stand5';
|
||||
if (!this.currentAnimation || this.currentAnimation !== fallback) {
|
||||
console.warn(`未找到可用的动画 ${actionName},使用兜底动画 ${fallback}`);
|
||||
}
|
||||
const fallbackState = animation.getState(fallback);
|
||||
if (!fallbackState) {
|
||||
console.error('无法找到兜底动画,动画切换失败');
|
||||
return;
|
||||
}
|
||||
finalAnimationName = fallback;
|
||||
}
|
||||
|
||||
// 检查是否需要切换动画
|
||||
if (this.currentAnimation === finalAnimationName) {
|
||||
// 即使动画名称相同,也需要检查方向是否改变,可能需要调整scale
|
||||
this.updatePlayerScale();
|
||||
return;
|
||||
}
|
||||
|
||||
const animation = this.player.getChildByName('Anim').getComponent(Animation);
|
||||
if (animation) {
|
||||
// 检查动画是否存在
|
||||
const state = animation.getState(finalAnimationName);
|
||||
if (!state) {
|
||||
console.warn(`动画 ${finalAnimationName} 不存在,使用默认动画`);
|
||||
this.currentAnimation = 'stand_2';
|
||||
animation.play('stand_2');
|
||||
this.updatePlayerScale();
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentAnimation = finalAnimationName;
|
||||
animation.play(finalAnimationName);
|
||||
console.log(`切换动画: ${finalAnimationName}`);
|
||||
|
||||
// 根据当前方向更新玩家scale
|
||||
this.updatePlayerScale();
|
||||
} else {
|
||||
console.warn('未找到Animation组件,无法播放动画');
|
||||
}
|
||||
animation.play(finalAnimationName);
|
||||
this.currentAnimation = finalAnimationName;
|
||||
console.log(`切换动画: ${finalAnimationName}`);
|
||||
this.updatePlayerScale();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据当前方向更新玩家Anim子节点的scale
|
||||
* 当 currentDirection 为 3 时,需要翻转Anim节点(scale.x 取反)
|
||||
* 左向(LeftUp/LeftDown)时需要翻转Anim节点(scale.x 取反)
|
||||
*/
|
||||
private updatePlayerScale() {
|
||||
if (!this.player) return;
|
||||
@@ -371,28 +373,127 @@ export class PlayerController extends Component {
|
||||
}
|
||||
|
||||
const currentScale = animNode.scale.clone();
|
||||
const faceLeft = this.currentDirection === PlayerDirection.LeftUp || this.currentDirection === PlayerDirection.LeftDown;
|
||||
const desiredScaleX = faceLeft ? -Math.abs(currentScale.x) : Math.abs(currentScale.x);
|
||||
|
||||
// 根据方向决定是否需要翻转
|
||||
// currentDirection: 3表示左/上,5表示右/下
|
||||
if (this.currentDirection === 3) {
|
||||
// 需要翻转,确保scale.x为负值
|
||||
if (currentScale.x > 0) {
|
||||
animNode.setScale(-currentScale.x, currentScale.y, currentScale.z);
|
||||
}
|
||||
} else {
|
||||
// 不需要翻转,确保scale.x为正值
|
||||
if (currentScale.x < 0) {
|
||||
animNode.setScale(-currentScale.x, currentScale.y, currentScale.z);
|
||||
}
|
||||
if (currentScale.x !== desiredScaleX) {
|
||||
animNode.setScale(desiredScaleX, currentScale.y, currentScale.z);
|
||||
}
|
||||
}
|
||||
|
||||
private normalizeAnimationAction(animationName: string): string {
|
||||
if (!animationName) {
|
||||
return animationName;
|
||||
}
|
||||
if (animationName.startsWith('walk')) {
|
||||
return 'walk';
|
||||
}
|
||||
if (animationName.startsWith('attack')) {
|
||||
return 'attack';
|
||||
}
|
||||
if (animationName.startsWith('stand')) {
|
||||
return 'stand';
|
||||
}
|
||||
if (animationName.startsWith('die')) {
|
||||
return 'die';
|
||||
}
|
||||
return animationName;
|
||||
}
|
||||
|
||||
private isCurrentAction(action: string): boolean {
|
||||
if (!this.currentAnimation) {
|
||||
return false;
|
||||
}
|
||||
return this.normalizeAnimationAction(this.currentAnimation) === action;
|
||||
}
|
||||
|
||||
private resolveDirectionFromDelta(
|
||||
deltaX: number,
|
||||
deltaY: number,
|
||||
overrides?: { horizontal?: 'Left' | 'Right', vertical?: 'Up' | 'Down' }
|
||||
): PlayerDirection {
|
||||
const horizontalThreshold = 0.5;
|
||||
const verticalThreshold = 0.5;
|
||||
|
||||
let horizontal: 'Left' | 'Right' = overrides?.horizontal ?? (this.isFacingLeft() ? 'Left' : 'Right');
|
||||
if (!overrides?.horizontal && Math.abs(deltaX) > horizontalThreshold) {
|
||||
horizontal = deltaX < 0 ? 'Left' : 'Right';
|
||||
}
|
||||
|
||||
let vertical: 'Up' | 'Down' = overrides?.vertical ?? (this.isFacingUp() ? 'Up' : 'Down');
|
||||
if (!overrides?.vertical && Math.abs(deltaY) > verticalThreshold) {
|
||||
vertical = deltaY > 0 ? 'Up' : 'Down';
|
||||
}
|
||||
|
||||
return this.composeDirection(horizontal, vertical);
|
||||
}
|
||||
|
||||
private composeDirection(horizontal: 'Left' | 'Right', vertical: 'Up' | 'Down'): PlayerDirection {
|
||||
if (horizontal === 'Left') {
|
||||
return vertical === 'Up' ? PlayerDirection.LeftUp : PlayerDirection.LeftDown;
|
||||
}
|
||||
return vertical === 'Up' ? PlayerDirection.RightUp : PlayerDirection.RightDown;
|
||||
}
|
||||
|
||||
private isFacingLeft(): boolean {
|
||||
return this.currentDirection === PlayerDirection.LeftUp || this.currentDirection === PlayerDirection.LeftDown;
|
||||
}
|
||||
|
||||
private isFacingUp(): boolean {
|
||||
return this.currentDirection === PlayerDirection.LeftUp || this.currentDirection === PlayerDirection.RightUp;
|
||||
}
|
||||
|
||||
private getVerticalAnimationSuffix(): '3' | '5' {
|
||||
return this.isFacingUp() ? '3' : '5';
|
||||
}
|
||||
|
||||
private getAnimationNameCandidates(requestedName: string): string[] {
|
||||
const action = this.normalizeAnimationAction(requestedName);
|
||||
const suffix = this.getVerticalAnimationSuffix();
|
||||
const oppositeSuffix = suffix === '3' ? '5' : '3';
|
||||
const candidates: string[] = [];
|
||||
|
||||
const pushUnique = (name?: string) => {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
if (candidates.indexOf(name) === -1) {
|
||||
candidates.push(name);
|
||||
}
|
||||
};
|
||||
|
||||
if (action === 'walk' || action === 'attack') {
|
||||
if (this.isUpgraded) {
|
||||
pushUnique(`${action}${suffix}_2`);
|
||||
pushUnique(`${action}_2`);
|
||||
}
|
||||
pushUnique(`${action}${suffix}`);
|
||||
pushUnique(action);
|
||||
pushUnique(`${action}${oppositeSuffix}`);
|
||||
} else if (action === 'stand') {
|
||||
if (this.isUpgraded) {
|
||||
pushUnique(`stand${suffix}_2`);
|
||||
pushUnique('stand_2');
|
||||
}
|
||||
pushUnique(`stand${suffix}`);
|
||||
pushUnique(`stand${oppositeSuffix}`);
|
||||
pushUnique('stand');
|
||||
} else {
|
||||
if (this.isUpgraded) {
|
||||
pushUnique(`${action}_2`);
|
||||
}
|
||||
pushUnique(action);
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动到路径中的下一个路径点
|
||||
*/
|
||||
private moveToNextWaypoint() {
|
||||
if (this.currentAnimation === 'attack' || this.isAttacking) {
|
||||
return
|
||||
if (this.isCurrentAction('attack') || this.isAttacking) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.player || this.currentPath.length === 0 || this.currentPathIndex >= this.currentPath.length) {
|
||||
@@ -551,20 +652,7 @@ export class PlayerController extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
// 梦幻西游方向判断逻辑:
|
||||
// 1. 优先判断水平方向:左上、左下、左都算左边;右上、右下、右都算右边
|
||||
// 2. 只有当水平方向不明显时(接近垂直),才判断垂直方向
|
||||
|
||||
// 设置一个阈值,当水平方向的绝对值大于这个阈值时,优先判断水平方向
|
||||
const horizontalThreshold = 0.5; // 可以根据需要调整这个值
|
||||
|
||||
if (Math.abs(deltaX) > horizontalThreshold) {
|
||||
// 水平方向明显,优先判断左右
|
||||
this.currentDirection = deltaX < 0 ? 3 : 5; // 3表示左,5表示右
|
||||
} else {
|
||||
// 水平方向不明显,判断垂直方向
|
||||
this.currentDirection = deltaY < 0 ? 3 : 5; // 3表示上,5表示下
|
||||
}
|
||||
this.currentDirection = this.resolveDirectionFromDelta(deltaX, deltaY);
|
||||
|
||||
// 切换到对应的动画(只传递基础动画名称)
|
||||
this.switchAnimation('walk');
|
||||
@@ -657,9 +745,23 @@ export class PlayerController extends Component {
|
||||
|
||||
const distance = Vec3.distance(currentLocalPos, targetLocalPos);
|
||||
|
||||
const targetPlayerCenterY = targetWorldPos.y + playerOffsetY;
|
||||
const monsterCenterY = monsterWorldPos.y + monsterOffsetY;
|
||||
const verticalDeltaToMonster = monsterCenterY - targetPlayerCenterY;
|
||||
const verticalOverride = Math.abs(verticalDeltaToMonster) <= 0.5 ? undefined : (verticalDeltaToMonster > 0 ? 'Up' : 'Down');
|
||||
const desiredDirection = this.resolveDirectionFromDelta(
|
||||
targetWorldPos.x - playerWorldPos.x,
|
||||
targetWorldPos.y - playerWorldPos.y,
|
||||
{
|
||||
horizontal: standOnLeft ? 'Right' : 'Left',
|
||||
vertical: verticalOverride,
|
||||
}
|
||||
);
|
||||
this.currentDirection = desiredDirection;
|
||||
|
||||
if (distance < 1) {
|
||||
playerNode.setPosition(targetLocalPos);
|
||||
this.currentDirection = standOnLeft ? 5 : 3;
|
||||
this.updatePlayerScale();
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
@@ -675,7 +777,8 @@ export class PlayerController extends Component {
|
||||
easing: 'smooth',
|
||||
onComplete: () => {
|
||||
playerNode.setPosition(targetLocalPos);
|
||||
this.currentDirection = standOnLeft ? 5 : 3;
|
||||
this.currentDirection = desiredDirection;
|
||||
this.updatePlayerScale();
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user