移除未使用的字符类及相关资源;更新二进制文件和项目结构。(完成联机和攻击框)
This commit is contained in:
@@ -8,23 +8,9 @@ public class AdvancedFighter extends SimpleFighter {
|
||||
|
||||
@Override
|
||||
public void attack(String attackType) {
|
||||
// 根据攻击类型设置不同攻击力或状态
|
||||
switch (attackType.toLowerCase()) {
|
||||
case "light":
|
||||
changeAction(Action.ATTACK);
|
||||
// System.out.println(getName() + " 发起轻攻击!");
|
||||
break;
|
||||
case "heavy":
|
||||
changeAction(Action.ATTACK);
|
||||
// System.out.println(getName() + " 发起重攻击!");
|
||||
break;
|
||||
case "special":
|
||||
changeAction(Action.ATTACK);
|
||||
// System.out.println(getName() + " 发动特殊技能!");
|
||||
break;
|
||||
default:
|
||||
super.attack(attackType); // 默认调用父类攻击逻辑
|
||||
break;
|
||||
}
|
||||
// 先使用父类的攻击逻辑来保证 isAttacking/attackTimer/attackbox 等状态被正确设置
|
||||
super.attack(attackType);
|
||||
// 在这里可以添加 AdvancedFighter 特有的扩展行为(攻击力、特效等)
|
||||
// 例如:根据 attackType 调整伤害或触发粒子/声音,但不要忘记保留父类的状态设置
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,13 @@ package uno.mloluyu.characters;
|
||||
// 注意:本类使用的是包 uno.mloluyu.characters 下的 Action (IDLE, JUMP, MOVE, ATTACK, DEFEND, HIT, DEAD)
|
||||
// 避免与 uno.mloluyu.characters.character.Action (ATTACK1/2/3...) 混淆
|
||||
|
||||
// 简化:去除内部按键时长跟踪,统一由 FighterController 负责
|
||||
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.Input;
|
||||
import com.badlogic.gdx.graphics.Color;
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
|
||||
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
|
||||
import com.badlogic.gdx.math.Rectangle;
|
||||
import uno.mloluyu.network.NetworkManager;
|
||||
|
||||
/**
|
||||
* 简化版角色类,仅包含移动、攻击、受击等基础功能。
|
||||
@@ -18,51 +17,27 @@ import com.badlogic.gdx.math.Rectangle;
|
||||
public class SimpleFighter {
|
||||
|
||||
private String name; // 角色名称
|
||||
|
||||
private Action currentAction = Action.IDLE; // 当前动作状态(待机、攻击、受击等)
|
||||
private float verticalSpeed = 0f; // 垂直速度(可用于跳跃或下落)
|
||||
private boolean isGrounded = true; // 是否在地面上
|
||||
|
||||
private Rectangle hitbox = new Rectangle(0, 0, 64, 128); // 碰撞盒,用于位置和受击判定
|
||||
private Rectangle attackbox = new Rectangle(0, 0, 80, 80); // 攻击盒,用于攻击判定
|
||||
private boolean isFacingRight = true; // 是否面向右侧
|
||||
|
||||
private float speed = 300f; // 移动速度(像素/秒)
|
||||
private int health = 100; // 当前生命值
|
||||
|
||||
private boolean isAttacking = false; // 是否正在攻击(攻击状态标志)
|
||||
private boolean attackJustStarted = false; // 攻击刚开始的标记,避免第一帧被减掉
|
||||
private int attackInvokeCount = 0; // 调试:attack()调用次数
|
||||
|
||||
// 攻击持续时间(秒)
|
||||
private float attackTimer = 0f;
|
||||
private static final float ATTACK_DURATION = 0.15f; // 攻击判定显示时间
|
||||
|
||||
private static boolean debugEnabled = true; // F3 开关
|
||||
|
||||
public static void toggleDebug() {
|
||||
debugEnabled = !debugEnabled;
|
||||
}
|
||||
|
||||
public static boolean isDebugEnabled() {
|
||||
return debugEnabled;
|
||||
}
|
||||
private Action currentAction = Action.IDLE; // 当前动作状态
|
||||
private float verticalSpeed = 0f; // 垂直速度(跳跃/下落)
|
||||
private boolean isGrounded = true; // 是否着地
|
||||
private Rectangle hitbox = new Rectangle(0, 0, 64, 128); // 碰撞盒
|
||||
private Rectangle attackbox = new Rectangle(0, 0, 80, 80); // 攻击判定盒
|
||||
private boolean isFacingRight = true; // 朝向(右/左)
|
||||
private float speed = 300f; // 水平移动速度
|
||||
private int health = 100; // 生命值
|
||||
private boolean isAttacking = false; // 是否正在攻击
|
||||
private boolean attackJustStarted = false; // 攻击是否刚开始
|
||||
private float attackTimer = 0f; // 攻击计时器
|
||||
private static final float ATTACK_DURATION = 0.15f; // 攻击持续时间
|
||||
|
||||
public SimpleFighter(String name) {
|
||||
this.name = name; // 构造函数,初始化角色名称
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void update(float deltaTime) {
|
||||
// 自愈:动作是 ATTACK 但标记丢失
|
||||
if (currentAction == Action.ATTACK && attackTimer > 0f && !isAttacking) {
|
||||
isAttacking = true;
|
||||
attackJustStarted = false;
|
||||
}
|
||||
|
||||
// 攻击计时
|
||||
if (isAttacking) {
|
||||
if (attackJustStarted) {
|
||||
attackJustStarted = false; // 第一帧不扣时间
|
||||
attackJustStarted = false;
|
||||
} else {
|
||||
attackTimer -= deltaTime;
|
||||
}
|
||||
@@ -71,15 +46,11 @@ public class SimpleFighter {
|
||||
attackTimer = 0f;
|
||||
if (currentAction == Action.ATTACK)
|
||||
changeAction(Action.IDLE);
|
||||
if (debugEnabled)
|
||||
System.out.println("[ATTACK-END]");
|
||||
}
|
||||
} else {
|
||||
// 空闲/移动状态下保持一个默认攻击盒(便于调试观察)
|
||||
updateAttackbox("light");
|
||||
}
|
||||
|
||||
// 垂直运动 & 重力
|
||||
if (!isGrounded) {
|
||||
verticalSpeed -= 2500 * deltaTime;
|
||||
hitbox.y += verticalSpeed * deltaTime;
|
||||
@@ -92,36 +63,23 @@ public class SimpleFighter {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void render(SpriteBatch batch, ShapeRenderer shapeRenderer) {
|
||||
batch.end();
|
||||
shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
|
||||
renderDebug(shapeRenderer);
|
||||
shapeRenderer.end();
|
||||
batch.begin();
|
||||
public void renderSprite(SpriteBatch batch) {
|
||||
}
|
||||
|
||||
public void renderSprite(SpriteBatch batch) {
|
||||
/* 预留贴图渲染入口 */ }
|
||||
|
||||
public void renderDebug(ShapeRenderer sr) {
|
||||
if (!debugEnabled)
|
||||
return;
|
||||
sr.setColor(Color.BLUE);
|
||||
sr.rect(hitbox.x, hitbox.y, hitbox.width, hitbox.height);
|
||||
if (isAttacking) {
|
||||
// 只画轮廓;填充在 GameScreen 专门的 Filled pass 中画
|
||||
sr.setColor(Color.RED);
|
||||
sr.rect(attackbox.x, attackbox.y, attackbox.width, attackbox.height);
|
||||
}
|
||||
// 朝向箭头
|
||||
float arrowX = isFacingRight ? hitbox.x + hitbox.width + 5 : hitbox.x - 15;
|
||||
sr.setColor(Color.YELLOW);
|
||||
sr.line(arrowX, hitbox.y + hitbox.height * 0.7f, arrowX + (isFacingRight ? 10 : -10),
|
||||
hitbox.y + hitbox.height * 0.7f);
|
||||
}
|
||||
|
||||
public void handleInput(int keycode, boolean isPressed, float duration) {
|
||||
public void handleInput(int keycode, boolean isPressed) {
|
||||
if (isPressed) {
|
||||
if (keycode == Input.Keys.LEFT || keycode == Input.Keys.A) {
|
||||
move(-1, Gdx.graphics.getDeltaTime());
|
||||
@@ -131,42 +89,38 @@ public class SimpleFighter {
|
||||
if (keycode == Input.Keys.SPACE || keycode == Input.Keys.UP || keycode == Input.Keys.W) {
|
||||
jump();
|
||||
}
|
||||
// 攻击按键
|
||||
if (!isAttacking) {
|
||||
if (keycode == Input.Keys.Z || keycode == Input.Keys.J) {
|
||||
attack("light");
|
||||
NetworkManager.getInstance().sendAttack("light");
|
||||
} else if (keycode == Input.Keys.X || keycode == Input.Keys.K) {
|
||||
attack("heavy");
|
||||
NetworkManager.getInstance().sendAttack("heavy");
|
||||
} else if (keycode == Input.Keys.SHIFT_LEFT || keycode == Input.Keys.SHIFT_RIGHT) {
|
||||
attack("special");
|
||||
NetworkManager.getInstance().sendAttack("special");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((keycode == Input.Keys.LEFT || keycode == Input.Keys.RIGHT || keycode == Input.Keys.A
|
||||
|| keycode == Input.Keys.D) &&
|
||||
getCurrentAction() == Action.MOVE) {
|
||||
|| keycode == Input.Keys.D) && getCurrentAction() == Action.MOVE) {
|
||||
changeAction(Action.IDLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void handleInput(int keycode, boolean isPressed) {
|
||||
handleInput(keycode, isPressed, 0f); // 调用已有方法,补充默认持续时间
|
||||
}
|
||||
|
||||
public Action getCurrentAction() {
|
||||
return currentAction; // 获取当前动作状态
|
||||
return currentAction;
|
||||
}
|
||||
|
||||
public void changeAction(Action newAction) {
|
||||
this.currentAction = newAction; // 切换角色动作状态
|
||||
this.currentAction = newAction;
|
||||
}
|
||||
|
||||
public void jump() {
|
||||
if (isGrounded) {
|
||||
verticalSpeed = 1000f;
|
||||
isGrounded = false;
|
||||
System.out.println("跳跃高度: " + verticalSpeed);
|
||||
changeAction(Action.JUMP);
|
||||
}
|
||||
}
|
||||
@@ -175,18 +129,14 @@ public class SimpleFighter {
|
||||
if (x != 0) {
|
||||
isFacingRight = x > 0;
|
||||
hitbox.x += x * speed * deltaTime;
|
||||
changeAction(Action.MOVE); // 移动时切换为 MOVE 状态
|
||||
changeAction(Action.MOVE);
|
||||
} else if (isGrounded && !isAttacking) {
|
||||
changeAction(Action.IDLE); // 停止移动时恢复待机
|
||||
changeAction(Action.IDLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAttackbox(String attackType) {
|
||||
float offsetX;
|
||||
float offsetY = 20; // 默认偏移量
|
||||
float width = 80;
|
||||
float height = 80;
|
||||
|
||||
float offsetX, offsetY = 20, width = 80, height = 80;
|
||||
switch (attackType) {
|
||||
case "heavy":
|
||||
offsetX = isFacingRight ? hitbox.width : -100;
|
||||
@@ -206,7 +156,6 @@ public class SimpleFighter {
|
||||
default:
|
||||
offsetX = isFacingRight ? hitbox.width - 10 : -attackbox.width + 10;
|
||||
}
|
||||
|
||||
attackbox.setPosition(hitbox.x + offsetX, hitbox.y + offsetY);
|
||||
attackbox.setSize(width, height);
|
||||
}
|
||||
@@ -217,59 +166,42 @@ public class SimpleFighter {
|
||||
attackJustStarted = true;
|
||||
changeAction(Action.ATTACK);
|
||||
updateAttackbox(attackType);
|
||||
attackInvokeCount++;
|
||||
if (debugEnabled)
|
||||
System.out.println("[ATTACK] type=" + attackType + " count=" + attackInvokeCount);
|
||||
}
|
||||
|
||||
public void takeHit(int damage) {
|
||||
health = Math.max(0, health - damage); // 扣除生命值,最小为 0
|
||||
changeAction(health > 0 ? Action.HIT : Action.DEAD); // 根据生命值切换为受击或死亡状态
|
||||
health = Math.max(0, health - damage);
|
||||
changeAction(health > 0 ? Action.HIT : Action.DEAD);
|
||||
}
|
||||
|
||||
public boolean isAlive() {
|
||||
return health > 0; // 判断角色是否存活
|
||||
return health > 0;
|
||||
}
|
||||
|
||||
public boolean isAttacking() {
|
||||
return isAttacking; // 判断是否处于攻击状态
|
||||
return isAttacking;
|
||||
}
|
||||
|
||||
// 常用访问器
|
||||
public Rectangle getHitbox() {
|
||||
return hitbox; // 获取碰撞盒
|
||||
return hitbox;
|
||||
}
|
||||
|
||||
public Rectangle getAttackbox() {
|
||||
return attackbox; // 获取攻击盒
|
||||
return attackbox;
|
||||
}
|
||||
|
||||
public int getHealth() {
|
||||
return health; // 获取当前生命值
|
||||
return health;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name; // 获取角色名称
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setPosition(float x, float y) {
|
||||
hitbox.setPosition(x, y); // 设置角色位置
|
||||
}
|
||||
|
||||
public void debugPrintState() {
|
||||
if (debugEnabled)
|
||||
System.out.println("[STATE] action=" + currentAction + ", atk=" + isAttacking + ", t=" + attackTimer);
|
||||
hitbox.setPosition(x, y);
|
||||
}
|
||||
|
||||
public float getAttackTimer() {
|
||||
return attackTimer;
|
||||
}
|
||||
|
||||
public float getAttackTimerPercent() {
|
||||
return isAttacking ? attackTimer / ATTACK_DURATION : 0f;
|
||||
}
|
||||
|
||||
public int getAttackInvokeCount() {
|
||||
return attackInvokeCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package uno.mloluyu.characters.character;
|
||||
|
||||
public enum Action {
|
||||
IDLE, WALK, JUMP, FALL,
|
||||
ATTACK1, ATTACK2, ATTACK3, ATTACK4,
|
||||
HIT, DEFEND,
|
||||
SPECIAL1, SPECIAL2,
|
||||
DEATH
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package uno.mloluyu.characters.character;
|
||||
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
|
||||
|
||||
/**
|
||||
* Alice角色类,继承自Fighter父类,定义其专属属性和动画
|
||||
*/
|
||||
public class Alice extends Fighter {
|
||||
|
||||
private static final String ATLAS_PATH = "src/main/resources/character/alice/精灵1.2.atlas";
|
||||
|
||||
public Alice() {
|
||||
super("Alice", new TextureAtlas(Gdx.files.internal(ATLAS_PATH)));
|
||||
|
||||
speed = 350f;
|
||||
maxHealth = 90;
|
||||
health = maxHealth;
|
||||
attackPower = 12;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadAnimations() {
|
||||
animationManager.loadLooping(Action.IDLE, "stand/stand", 15);
|
||||
animationManager.loadLooping(Action.WALK, "walkFront/walkFront", 9);
|
||||
animationManager.loadOneShot(Action.JUMP, "jump/jump", 8);
|
||||
animationManager.loadOneShot(Action.FALL, "hitSpin/hitSpin", 5);
|
||||
|
||||
animationManager.loadOneShot(Action.ATTACK1, "attackAa/attackAa", 6);
|
||||
animationManager.loadOneShot(Action.ATTACK2, "attackAb/attackAb", 6);
|
||||
animationManager.loadOneShot(Action.ATTACK3, "attackAc/attackAc", 6);
|
||||
animationManager.loadOneShot(Action.ATTACK4, "attackAd/attackAd", 6);
|
||||
|
||||
animationManager.loadOneShot(Action.HIT, "hitSpin/hitSpin", 5);
|
||||
// animationManager.loadOneShot(Action.DEATH, "death/death", 8);
|
||||
|
||||
// 可选特殊动作(如资源存在可启用)
|
||||
// animationManager.loadOneShot(Action.SPECIAL1, "special/special1", 6);
|
||||
// animationManager.loadOneShot(Action.SPECIAL2, "special/special2", 6);
|
||||
|
||||
animationManager.setFrameDuration(Action.IDLE, 0.04f);
|
||||
animationManager.setFrameDuration(Action.WALK, 0.08f);
|
||||
animationManager.setFrameDuration(Action.ATTACK1, 0.07f);
|
||||
animationManager.setFrameDuration(Action.SPECIAL2, 0.06f);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleMoveState() {
|
||||
if (currentAction != Action.ATTACK1 &&
|
||||
currentAction != Action.ATTACK2 &&
|
||||
currentAction != Action.ATTACK3 &&
|
||||
currentAction != Action.ATTACK4 &&
|
||||
currentAction != Action.SPECIAL1 &&
|
||||
currentAction != Action.SPECIAL2 &&
|
||||
currentAction != Action.DEFEND &&
|
||||
currentAction != Action.JUMP &&
|
||||
currentAction != Action.FALL) {
|
||||
changeAction(Action.WALK);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canAttack() {
|
||||
return super.canAttack() || currentAction == Action.JUMP || currentAction == Action.FALL;
|
||||
}
|
||||
|
||||
public int getHp() {
|
||||
return health;
|
||||
}
|
||||
}
|
||||
@@ -1,287 +0,0 @@
|
||||
package uno.mloluyu.characters.character;
|
||||
|
||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
|
||||
import com.badlogic.gdx.math.Rectangle;
|
||||
import com.badlogic.gdx.utils.Disposable;
|
||||
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
|
||||
|
||||
/**
|
||||
* 抽象类 Fighter,定义所有角色的基础属性与行为。
|
||||
* 包括动画控制、移动、攻击、受击、渲染等核心逻辑。
|
||||
*/
|
||||
public abstract class Fighter implements Disposable {
|
||||
|
||||
// 默认帧持续时间(秒)
|
||||
protected static final float DEFAULT_FRAME_DURATION = 0.1f;
|
||||
// 默认生命值
|
||||
protected static final int DEFAULT_HEALTH = 100;
|
||||
// 默认移动速度(像素/秒)
|
||||
protected static final float DEFAULT_SPEED = 300f;
|
||||
|
||||
// 角色名称
|
||||
protected String name;
|
||||
// 当前动作状态
|
||||
protected Action currentAction = Action.IDLE;
|
||||
// 当前动作已持续时间
|
||||
protected float stateTime = 0f;
|
||||
// 是否面向右侧
|
||||
protected boolean isFacingRight = true;
|
||||
// 当前动画是否播放完毕
|
||||
protected boolean isAnimationFinished = false;
|
||||
|
||||
// 碰撞盒(用于位置和受击判定)
|
||||
protected Rectangle hitbox = new Rectangle(0, 0, 64, 128);
|
||||
// 攻击盒(用于攻击判定)
|
||||
protected Rectangle attackbox = new Rectangle(0, 0, 80, 80);
|
||||
|
||||
// 移动速度
|
||||
protected float speed = DEFAULT_SPEED;
|
||||
// 当前生命值
|
||||
protected int health = DEFAULT_HEALTH;
|
||||
// 最大生命值
|
||||
protected int maxHealth = DEFAULT_HEALTH;
|
||||
// 攻击力
|
||||
protected int attackPower = 10;
|
||||
|
||||
// 动画管理器
|
||||
protected FighterAnimationManager animationManager;
|
||||
|
||||
/**
|
||||
* 构造函数,初始化角色名称与动画资源。
|
||||
*
|
||||
* @param name 角色名称
|
||||
* @param atlas 动画图集
|
||||
*/
|
||||
public Fighter(String name, TextureAtlas atlas) {
|
||||
this.name = name;
|
||||
this.animationManager = new FighterAnimationManager(atlas);
|
||||
loadAnimations();
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载角色的所有动画资源,由子类实现。
|
||||
*/
|
||||
protected abstract void loadAnimations();
|
||||
|
||||
/**
|
||||
* 每帧更新角色状态,包括动画播放与碰撞盒更新。
|
||||
*
|
||||
* @param deltaTime 帧间隔时间
|
||||
*/
|
||||
public void update(float deltaTime) {
|
||||
stateTime += deltaTime;
|
||||
isAnimationFinished = animationManager.isFinished(currentAction, stateTime);
|
||||
handleAnimationTransitions();
|
||||
updateHitboxes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染角色当前帧。
|
||||
*
|
||||
* @param batch 渲染批处理器
|
||||
*/
|
||||
public void render(SpriteBatch batch) {
|
||||
animationManager.render(batch, currentAction, stateTime, hitbox, isFacingRight);
|
||||
}
|
||||
|
||||
/**
|
||||
* 动画播放完毕后的动作切换逻辑。
|
||||
*/
|
||||
protected void handleAnimationTransitions() {
|
||||
if (!isAnimationFinished)
|
||||
return;
|
||||
|
||||
switch (currentAction) {
|
||||
case ATTACK1, ATTACK2, ATTACK3, ATTACK4, SPECIAL1, SPECIAL2, HIT -> changeAction(Action.IDLE);
|
||||
case JUMP -> changeAction(Action.FALL);
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换角色动作状态。
|
||||
*
|
||||
* @param newAction 新动作
|
||||
* @return 是否成功切换
|
||||
*/
|
||||
public boolean changeAction(Action newAction) {
|
||||
if (isActionUninterruptible(currentAction))
|
||||
return false;
|
||||
if (currentAction != newAction) {
|
||||
currentAction = newAction;
|
||||
stateTime = 0f;
|
||||
isAnimationFinished = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断某个动作是否不可打断(如受击或死亡)。
|
||||
*
|
||||
* @param action 动作枚举
|
||||
* @return 是否不可打断
|
||||
*/
|
||||
protected boolean isActionUninterruptible(Action action) {
|
||||
return action == Action.HIT || action == Action.DEATH;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新攻击盒位置(根据角色朝向调整)。
|
||||
*/
|
||||
protected void updateHitboxes() {
|
||||
float offsetX = isFacingRight ? hitbox.width - 10 : -attackbox.width + 10;
|
||||
attackbox.setPosition(hitbox.x + offsetX, hitbox.y + 20);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动角色。
|
||||
*
|
||||
* @param x 水平移动方向(-1左,1右)
|
||||
* @param deltaTime 帧间隔时间
|
||||
*/
|
||||
public void move(float x, float deltaTime) {
|
||||
if (x != 0) {
|
||||
isFacingRight = x > 0;
|
||||
hitbox.x += x * speed * deltaTime;
|
||||
handleMoveState();
|
||||
} else if (currentAction == Action.WALK) {
|
||||
changeAction(Action.IDLE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动状态下的动作切换逻辑。
|
||||
*/
|
||||
protected void handleMoveState() {
|
||||
if (!isActionUninterruptible(currentAction) &&
|
||||
currentAction != Action.JUMP &&
|
||||
currentAction != Action.FALL &&
|
||||
currentAction != Action.DEFEND &&
|
||||
!currentAction.name().startsWith("ATTACK") &&
|
||||
!currentAction.name().startsWith("SPECIAL")) {
|
||||
changeAction(Action.WALK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起攻击动作。
|
||||
*
|
||||
* @param attackType 攻击类型(1~5)
|
||||
* @return 是否成功发起攻击
|
||||
*/
|
||||
public boolean attack(int attackType) {
|
||||
if (!canAttack())
|
||||
return false;
|
||||
|
||||
Action attackAction = switch (attackType) {
|
||||
case 1 -> Action.ATTACK1;
|
||||
case 2 -> Action.ATTACK2;
|
||||
case 3 -> Action.ATTACK3;
|
||||
case 4 -> Action.SPECIAL1;
|
||||
case 5 -> Action.SPECIAL2;
|
||||
default -> null;
|
||||
};
|
||||
|
||||
return attackAction != null && changeAction(attackAction);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前是否可以攻击。
|
||||
*
|
||||
* @return 是否可攻击
|
||||
*/
|
||||
protected boolean canAttack() {
|
||||
return currentAction == Action.IDLE || currentAction == Action.WALK;
|
||||
}
|
||||
|
||||
/**
|
||||
* 接受伤害。
|
||||
*
|
||||
* @param damage 伤害值
|
||||
*/
|
||||
public void takeHit(int damage) {
|
||||
if (currentAction == Action.DEATH)
|
||||
return;
|
||||
|
||||
health = Math.max(0, health - damage);
|
||||
changeAction(health == 0 ? Action.DEATH : Action.HIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置角色位置。
|
||||
*
|
||||
* @param x 横坐标
|
||||
* @param y 纵坐标
|
||||
*/
|
||||
public void setPosition(float x, float y) {
|
||||
hitbox.setPosition(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置角色朝向。
|
||||
*
|
||||
* @param facingRight 是否面向右
|
||||
*/
|
||||
public void setFacingRight(boolean facingRight) {
|
||||
this.isFacingRight = facingRight;
|
||||
}
|
||||
|
||||
// 以下为常用属性访问器
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Rectangle getHitbox() {
|
||||
return hitbox;
|
||||
}
|
||||
|
||||
public Rectangle getAttackbox() {
|
||||
return attackbox;
|
||||
}
|
||||
|
||||
public boolean isFacingRight() {
|
||||
return isFacingRight;
|
||||
}
|
||||
|
||||
public int getHealth() {
|
||||
return health;
|
||||
}
|
||||
|
||||
public Action getCurrentAction() {
|
||||
return currentAction;
|
||||
}
|
||||
|
||||
public float getX() {
|
||||
return hitbox.x;
|
||||
}
|
||||
|
||||
public float getY() {
|
||||
return hitbox.y;
|
||||
}
|
||||
|
||||
public float getCenterX() {
|
||||
return hitbox.x + hitbox.width / 2;
|
||||
}
|
||||
|
||||
public float getCenterY() {
|
||||
return hitbox.y + hitbox.height / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 帧事件监听器接口,用于在动画播放到某一帧时触发逻辑。
|
||||
*/
|
||||
public interface FrameEventListener {
|
||||
void onFrameEvent(Action action, int frameIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放资源(如动画图集)。
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
animationManager.dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package uno.mloluyu.characters.character;
|
||||
|
||||
import com.badlogic.gdx.graphics.g2d.*;
|
||||
import com.badlogic.gdx.math.Rectangle;
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
|
||||
import uno.mloluyu.util.SimpleFormatter;
|
||||
|
||||
import java.util.EnumMap;
|
||||
|
||||
public class FighterAnimationManager {
|
||||
private EnumMap<Action, Animation<TextureRegion>> animations = new EnumMap<>(Action.class);
|
||||
private EnumMap<Action, Float> frameDurations = new EnumMap<>(Action.class);
|
||||
private TextureAtlas atlas;
|
||||
private float scaleX = 1.0f;
|
||||
private float scaleY = 1.0f;
|
||||
|
||||
public FighterAnimationManager(TextureAtlas atlas) {
|
||||
this.atlas = atlas;
|
||||
for (Action action : Action.values()) {
|
||||
frameDurations.put(action, 0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadAnimation(Action action, String prefix, int count, boolean loop) {
|
||||
Array<TextureRegion> frames = new Array<>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
String regionName = prefix + SimpleFormatter.addLeadingZeros(i, 3);
|
||||
TextureRegion region = atlas.findRegion(regionName);
|
||||
if (region == null) {
|
||||
throw new IllegalArgumentException("未找到区域: " + regionName);
|
||||
}
|
||||
frames.add(region);
|
||||
}
|
||||
|
||||
Animation<TextureRegion> animation = new Animation<>(frameDurations.get(action), frames);
|
||||
animation.setPlayMode(loop ? Animation.PlayMode.LOOP : Animation.PlayMode.NORMAL);
|
||||
animations.put(action, animation);
|
||||
}
|
||||
|
||||
public void loadLooping(Action action, String prefix, int count) {
|
||||
loadAnimation(action, prefix, count, true);
|
||||
}
|
||||
|
||||
public void loadOneShot(Action action, String prefix, int count) {
|
||||
loadAnimation(action, prefix, count, false);
|
||||
}
|
||||
|
||||
public void setFrameDuration(Action action, float duration) {
|
||||
frameDurations.put(action, duration);
|
||||
Animation<TextureRegion> anim = animations.get(action);
|
||||
if (anim != null) anim.setFrameDuration(duration);
|
||||
}
|
||||
|
||||
public boolean isFinished(Action action, float stateTime) {
|
||||
Animation<TextureRegion> anim = animations.get(action);
|
||||
return anim != null && anim.isAnimationFinished(stateTime);
|
||||
}
|
||||
|
||||
public void render(SpriteBatch batch, Action action, float stateTime, Rectangle hitbox, boolean isFacingRight) {
|
||||
Animation<TextureRegion> anim = animations.get(action);
|
||||
if (anim == null) return;
|
||||
|
||||
TextureRegion frame = anim.getKeyFrame(stateTime, anim.getPlayMode() == Animation.PlayMode.LOOP);
|
||||
if (frame == null) return;
|
||||
|
||||
float frameWidth = frame.getRegionWidth() * scaleX;
|
||||
float frameHeight = frame.getRegionHeight() * scaleY;
|
||||
float drawX = hitbox.x + (hitbox.width - frameWidth) / 2;
|
||||
float drawY = hitbox.y;
|
||||
|
||||
boolean wasFlippedX = frame.isFlipX();
|
||||
frame.flip(!isFacingRight && !wasFlippedX, false);
|
||||
frame.flip(isFacingRight && wasFlippedX, false);
|
||||
|
||||
batch.draw(frame, drawX, drawY, frameWidth / 2, frameHeight / 2, frameWidth, frameHeight, 1f, 1f, 0f);
|
||||
frame.flip(wasFlippedX != frame.isFlipX(), false);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
if (atlas != null) atlas.dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package uno.mloluyu.characters.character;
|
||||
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
|
||||
|
||||
public class FighterList {
|
||||
|
||||
public static final TextureAtlas aliceAtlas = new TextureAtlas(Gdx.files.internal("src\\main\\resources\\character\\alice\\alice.atlas"));
|
||||
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package uno.mloluyu.characters.character;
|
||||
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
|
||||
|
||||
public class Reimu extends Fighter {
|
||||
public Reimu() {
|
||||
super("Reimu", new TextureAtlas(Gdx.files.internal("src/main/resources/character/reimu/reimu.atlas")));
|
||||
|
||||
// 设置角色属性
|
||||
speed = 350f; // 更快的移动速度
|
||||
maxHealth = 90; // 较低的生命值
|
||||
health = maxHealth;
|
||||
attackPower = 12; // 中等攻击力
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadAnimations() {
|
||||
// 基础动作 (looping)
|
||||
animationManager.loadLooping(Action.IDLE, "other/stand", 9);
|
||||
animationManager.loadLooping(Action.WALK, "other/walkFront", 9);
|
||||
// 一次性动作 (one-shot)
|
||||
animationManager.loadOneShot(Action.JUMP, "other/jump", 8);
|
||||
animationManager.loadOneShot(Action.FALL, "other/hitSpin", 5);
|
||||
|
||||
// 攻击动作
|
||||
animationManager.loadOneShot(Action.ATTACK1, "attackAa/attackAa", 6);
|
||||
animationManager.loadOneShot(Action.ATTACK2, "attackAb/attackAb", 6);
|
||||
animationManager.loadOneShot(Action.ATTACK3, "attackAc/attackAc", 6);
|
||||
animationManager.loadOneShot(Action.ATTACK4, "attackAd/attackAd", 6);
|
||||
|
||||
// 受击
|
||||
animationManager.loadOneShot(Action.HIT, "hitSpin/hitSpin", 5);
|
||||
|
||||
// 帧间隔
|
||||
animationManager.setFrameDuration(Action.IDLE, 0.04f);
|
||||
animationManager.setFrameDuration(Action.WALK, 0.08f);
|
||||
animationManager.setFrameDuration(Action.ATTACK1, 0.07f);
|
||||
animationManager.setFrameDuration(Action.SPECIAL2, 0.06f);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleMoveState() {
|
||||
if (currentAction != Action.ATTACK1 &&
|
||||
currentAction != Action.ATTACK2 &&
|
||||
currentAction != Action.ATTACK3 &&
|
||||
currentAction != Action.SPECIAL1 &&
|
||||
currentAction != Action.SPECIAL2 &&
|
||||
currentAction != Action.DEFEND &&
|
||||
currentAction != Action.JUMP &&
|
||||
currentAction != Action.FALL) {
|
||||
changeAction(Action.WALK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 空中也可以攻击
|
||||
*/
|
||||
@Override
|
||||
protected boolean canAttack() {
|
||||
return super.canAttack() || currentAction == Action.JUMP || currentAction == Action.FALL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前生命值
|
||||
*/
|
||||
public int getHp() {
|
||||
return health;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user