Files
Game/src/main/java/uno/mloluyu/characters/Fighter.java

361 lines
10 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<TextureRegion>[] 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<TextureRegion> 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<TextureRegion> 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<TextureRegion> 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() {
}
}