361 lines
10 KiB
Java
361 lines
10 KiB
Java
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() {
|
||
}
|
||
}
|