添加角色类Alice,继承自Fighter,设置属性和动画,

This commit is contained in:
2025-09-21 22:20:07 +08:00
parent 3b9d794163
commit 87cfe5aed6
7 changed files with 95 additions and 90 deletions

View File

@@ -1,5 +1,5 @@
package uno.mloluyu.desktop.character; package uno.mloluyu.characters;
import uno.mloluyu.characters.Fighter;
import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.g2d.TextureAtlas;
/** /**
@@ -14,44 +14,43 @@ public class Alice extends Fighter {
health = maxHealth; health = maxHealth;
attackPower = 12; // 攻击力中等 attackPower = 12; // 攻击力中等
} }
@Override @Override
protected void loadAnimations() { protected void loadAnimations() {
loadAnimationFromAtlas(Action.IDLE, "stand", 15, true); loadAnimationFromAtlas(Action.IDLE, "stand/stand", 15, true);
loadAnimationFromAtlas(Action.WALK, "walkFront", 9, true); loadAnimationFromAtlas(Action.WALK, "walkFront/walkFront", 9, true);
loadAnimationFromAtlas(Action.JUMP, "jump", 8, false); loadAnimationFromAtlas(Action.JUMP, "jump/jump", 8, false);
loadAnimationFromAtlas(Action.FALL, "hitSpin", 5, false); loadAnimationFromAtlas(Action.FALL, "hitSpin/hitSpin", 5, false);
loadAnimationFromAtlas(Action.ATTACK1, "attackAa", 6, false); loadAnimationFromAtlas(Action.ATTACK1, "attackAa/attackAa", 6, false);
loadAnimationFromAtlas(Action.ATTACK2, "attackAb", 6, false); loadAnimationFromAtlas(Action.ATTACK2, "attackAb/attackAb", 6, false);
loadAnimationFromAtlas(Action.ATTACK3, "attackAc", 6, false); loadAnimationFromAtlas(Action.ATTACK3, "attackAc/attackAc", 6, false);
loadAnimationFromAtlas(Action.ATTACK4, "attackAd", 6, false); loadAnimationFromAtlas(Action.ATTACK4, "attackAd/attackAd", 6, false);
loadAnimationFromAtlas(Action.HIT, "hitSpin", 5, false); loadAnimationFromAtlas(Action.HIT, "hitSpin/hitSpin", 5, false);
// 忍者特定动作设置帧间隔 // 为特定动作设置帧间隔
setFrameDuration(Action.WALK, 0.08f); // 行走更快 setFrameDuration(Action.WALK, 0.08f); // 行走更快
setFrameDuration(Action.ATTACK1, 0.07f); // 攻击更快 setFrameDuration(Action.ATTACK1, 0.07f); // 攻击更快
setFrameDuration(Action.SPECIAL2, 0.06f); // 特殊技能2非常快 setFrameDuration(Action.SPECIAL2, 0.06f); // 特殊技能2非常快
} }
// 忍者特定的移动状态处理 // 特定的移动状态处理
@Override @Override
protected void handleMoveState() { protected void handleMoveState() {
// 忍者在跳跃时也能移动 // 在跳跃时也能移动
if (currentAction != Action.ATTACK1 && currentAction != Action.ATTACK2 && if (currentAction != Action.ATTACK1 && currentAction != Action.ATTACK2 &&
currentAction != Action.ATTACK3 && currentAction != Action.SPECIAL1 && currentAction != Action.ATTACK3 && currentAction != Action.SPECIAL1 &&
currentAction != Action.SPECIAL2 && currentAction != Action.DEFEND) { currentAction != Action.SPECIAL2 && currentAction != Action.DEFEND) {
if (currentAction != Action.JUMP && currentAction != Action.FALL) { if (currentAction != Action.JUMP && currentAction != Action.FALL) {
changeAction(Action.WALK); changeAction(Action.WALK);
} }
} }
} }
// 忍者可以在空中攻击 // 可以在空中攻击
@Override @Override
protected boolean canAttack() { protected boolean canAttack() {
return super.canAttack() || currentAction == Action.JUMP || currentAction == Action.FALL; return super.canAttack() || currentAction == Action.JUMP || currentAction == Action.FALL;
} }
} }

View File

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

View File

@@ -7,7 +7,7 @@ import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import uno.mloluyu.desktop.character.Alice; import uno.mloluyu.characters.Alice;
public class GameCore implements ApplicationListener { public class GameCore implements ApplicationListener {
private SpriteBatch batch; private SpriteBatch batch;
@@ -17,8 +17,9 @@ public class GameCore implements ApplicationListener {
@Override @Override
public void create() { public void create() {
atlas = new TextureAtlas(Gdx.files.internal("characters/alice.atlas")); batch = new SpriteBatch();
alice1= new Alice(); atlas = new TextureAtlas(Gdx.files.internal("src\\main\\resources\\character\\alice\\alice.atlas"));
alice1= new Alice(atlas);
} }
@@ -31,6 +32,7 @@ public class GameCore implements ApplicationListener {
alice1.update(Gdx.graphics.getDeltaTime()); alice1.update(Gdx.graphics.getDeltaTime());
batch.begin(); batch.begin();
alice1.render(batch); alice1.render(batch);
batch.end();
} }
@Override @Override