package uno.mloluyu.characters; import uno.mloluyu.util.SimpleFormatter; import com.badlogic.gdx.graphics.g2d.Animation; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.utils.Disposable; /** * 格斗角色父类,封装所有角色共有的动画和状态管理逻辑 */ public abstract class Fighter implements Disposable { // 动作类型枚举 - 所有角色共用的基础动作 public enum Action { IDLE, WALK, JUMP, FALL, ATTACK1, ATTACK2, ATTACK3, ATTACK4, HIT, DEFEND, SPECIAL1, SPECIAL2, DEATH } protected String name; // 动画帧间隔(秒) protected static final float DEFAULT_FRAME_DURATION = 0.1f; protected float[] frameDurations; // 当前状态 protected Action currentAction; protected float stateTime; protected boolean isFacingRight; protected boolean isAnimationFinished; // 动画集合 protected Animation[] animations; // 碰撞检测 protected Rectangle hitbox; protected Rectangle attackbox; // 角色属性 protected float speed; protected int health; protected int maxHealth; protected int attackPower; // 精灵图表 protected TextureAtlas atlas; @SuppressWarnings("unchecked") public Fighter(TextureAtlas atlas) { this.atlas = atlas; int actionCount = Action.values().length; // 初始化动画数组和帧间隔数组 animations = new Animation[actionCount]; frameDurations = new float[actionCount]; // 设置默认帧间隔 for (int i = 0; i < actionCount; i++) { frameDurations[i] = DEFAULT_FRAME_DURATION; } // 初始化碰撞框 hitbox = new Rectangle(0, 0, 64, 128); attackbox = new Rectangle(0, 0, 80, 80); // 初始化默认属性(子类可以重写) speed = 300f; maxHealth = 100; health = maxHealth; attackPower = 10; // 初始状态 isFacingRight = true; currentAction = Action.IDLE; stateTime = 0; isAnimationFinished = false; // 加载动画(由子类实现具体的动画帧) loadAnimations(); } /** * 加载角色动画,由子类实现具体的动画帧加载 */ protected abstract void loadAnimations(); /** * 从精灵图表加载动画 * * @param action 动作类型 * @param regionPrefix 该动作在图表中的区域前缀 * @param frameCount 帧数 * @param loop 是否循环 */ protected void loadAnimationFromAtlas(Action action, String regionPrefix, int frameCount, boolean loop) { Array frames = new Array<>(); // 从精灵图表中获取所有帧 for (int i = 0; i < frameCount; i++) { // 生成带三位前导零的序号(000, 001, 002...) String formattedIndex = SimpleFormatter.addLeadingZeros(i, 3); // 拼接完整的区域名称(如"stand/stand" + "000" → "stand/stand000") String regionName = regionPrefix + formattedIndex; TextureRegion region = atlas.findRegion(regionName); if (region == null) { throw new IllegalArgumentException("精灵图表中未找到区域: " + regionName); } frames.add(region); } // 创建动画 Animation animation = new Animation<>( frameDurations[action.ordinal()], frames); animation.setPlayMode(loop ? Animation.PlayMode.LOOP : Animation.PlayMode.NORMAL); animations[action.ordinal()] = animation; } /** * 为特定动作设置帧间隔 */ protected void setFrameDuration(Action action, float duration) { frameDurations[action.ordinal()] = duration; // 如果动画已加载,更新它 if (animations[action.ordinal()] != null) { animations[action.ordinal()].setFrameDuration(duration); } } /** * 更新角色状态 */ public void update(float deltaTime) { stateTime += deltaTime; isAnimationFinished = animations[currentAction.ordinal()].isAnimationFinished(stateTime); // 处理动画完成后的状态转换 handleAnimationTransitions(); // 更新碰撞框位置 updateHitboxes(); } /** * 处理动画完成后的状态转换,子类可以重写以实现特定逻辑 */ protected void handleAnimationTransitions() { Animation currentAnim = animations[currentAction.ordinal()]; // 检查非循环动画是否已完成 if (currentAnim.getPlayMode() != Animation.PlayMode.LOOP && isAnimationFinished) { switch (currentAction) { case ATTACK1: case ATTACK2: case ATTACK3: case SPECIAL1: case SPECIAL2: // 攻击动作完成后回到 idle changeAction(Action.IDLE); break; case HIT: // 受击后回到 idle changeAction(Action.IDLE); break; case JUMP: // 跳跃后进入下落状态 changeAction(Action.FALL); break; // 其他默认转换逻辑 } } } /** * 绘制角色 */ public void render(SpriteBatch batch) { TextureRegion currentFrame = animations[currentAction.ordinal()].getKeyFrame(stateTime, true); // 绘制角色,考虑方向翻转 float x = hitbox.x; float y = hitbox.y; float width = hitbox.width; float height = hitbox.height; if (isFacingRight) { batch.draw(currentFrame, x, y, width, height); } else { batch.draw(currentFrame, x + width, y, -width, height); } } /** * 改变角色动作 */ public boolean changeAction(Action newAction) { // 某些动作不能被打断 if (isActionUninterruptible(currentAction)) { return false; } // 如果是新动作,重置状态时间 if (currentAction != newAction) { currentAction = newAction; stateTime = 0; isAnimationFinished = false; return true; } return false; } /** * 判断动作是否不可被打断 */ protected boolean isActionUninterruptible(Action action) { return action == Action.HIT || action == Action.DEATH; } /** * 更新碰撞框位置 */ protected void updateHitboxes() { // 根据角色朝向更新攻击框位置 if (isFacingRight) { attackbox.setPosition(hitbox.x + hitbox.width - 10, hitbox.y + 20); } else { attackbox.setPosition(hitbox.x - attackbox.width + 10, hitbox.y + 20); } } /** * 处理角色移动 */ public void move(float x, float deltaTime) { if (x != 0) { // 改变朝向 isFacingRight = x > 0; // 移动位置 hitbox.x += x * speed * deltaTime; // 处理移动状态转换 handleMoveState(); } else if (currentAction == Action.WALK) { // 如果停止移动且当前是行走状态,切换到 idle changeAction(Action.IDLE); } } /** * 处理移动状态转换,子类可重写 */ protected void handleMoveState() { // 如果不是攻击或特殊动作,切换到行走动画 if (currentAction != Action.ATTACK1 && currentAction != Action.ATTACK2 && currentAction != Action.ATTACK3 && currentAction != Action.SPECIAL1 && currentAction != Action.SPECIAL2 && currentAction != Action.JUMP && currentAction != Action.FALL && currentAction != Action.DEFEND) { changeAction(Action.WALK); } } /** * 执行攻击 */ public boolean attack(int attackType) { // 只能在允许攻击的状态下攻击 if (!canAttack()) { return false; } Action attackAction; switch (attackType) { case 1: attackAction = Action.ATTACK1; break; case 2: attackAction = Action.ATTACK2; break; case 3: attackAction = Action.ATTACK3; break; case 4: attackAction = Action.SPECIAL1; break; case 5: attackAction = Action.SPECIAL2; break; default: return false; } return changeAction(attackAction); } /** * 判断是否可以攻击,子类可重写以实现特定逻辑 */ protected boolean canAttack() { return currentAction == Action.IDLE || currentAction == Action.WALK; } /** * 处理受击 */ public void takeHit(int damage) { if (currentAction != Action.DEATH) { health -= damage; if (health <= 0) { health = 0; changeAction(Action.DEATH); } else { changeAction(Action.HIT); } } } public Rectangle getHitbox() { return hitbox; } public Rectangle getAttackbox() { return attackbox; } public boolean isFacingRight() { return isFacingRight; } public int getHealth() { return health; } public int getMaxHealth() { return maxHealth; } public Action getCurrentAction() { return currentAction; } public int getAttackPower() { return attackPower; } public String getName(){ return ""; } @Override public void dispose() { } }