LibGdx游戏开发:如何让攻击仅在指定动画帧造成伤害
解决LibGdx攻击动画特定帧伤害判定丢失问题
现有代码的核心问题
- 帧索引跳过问题:直接比较动画帧索引时,若
deltaTime过大(比如性能波动导致帧间隔变长),动画帧索引会直接跳过多帧,错过目标伤害帧的判定时机。 - 时间轴混乱:
addHitEntity方法中修改statetime是错误操作——攻击动画的时间进度应该由update统一维护,碰撞逻辑里篡改时间会导致判定逻辑失效。 - 帧区间未覆盖:仅检测当前帧索引等于目标帧,没有考虑帧的时间区间范围,若
update调用时机恰好处于帧切换节点,会出现漏判。
优化方案1:基于帧时间区间的精准判定
不再依赖帧索引,转而计算目标帧对应的时间区间,检测当前动画时间是否落在该区间内;同时为每个伤害帧添加触发标记,避免同一帧内重复判定。
import java.util.Arrays; import com.badlogic.gdx.utils.Array; // 新增成员变量 private boolean[] damageFrameTriggered; private float frameDuration; // 攻击动画单帧时长,初始化时赋值为animation.getFrameDuration() private float attackStateTime; // 单独维护攻击动画的时间,避免和其他动画时间混淆 private Array<Entity> hitEntities = new Array<>(); // 攻击开始时调用,重置状态 public void startAttack() { hitEntities.clear(); damageFrameTriggered = new boolean[frameToDealDamage.length]; Arrays.fill(damageFrameTriggered, false); attackStateTime = 0f; // 攻击动画时间从0开始计时 } @Override public void update(float delta) { if (owner.isDead()) return; attackStateTime += delta; for (int i = 0; i < frameToDealDamage.length; i++) { int targetFrameIdx = frameToDealDamage[i]; // 计算目标帧的时间区间:[起始时间, 起始时间+单帧时长) float frameStart = targetFrameIdx * frameDuration; float frameEnd = frameStart + frameDuration; // 检查当前时间是否在目标帧区间,且该帧未触发过判定 if (attackStateTime >= frameStart && attackStateTime < frameEnd && !damageFrameTriggered[i]) { dealDamage(); damageFrameTriggered[i] = true; } } } public void addHitEntity(Entity entity) { // 仅做去重添加,不再修改时间 if (!hitEntities.contains(entity, false)) { hitEntities.add(entity); } }
优化方案2:使用AnimationListener回调(官方推荐)
LibGdx的Animation类支持添加监听器,可在帧切换时触发回调,完全避免帧跳过导致的判定丢失问题。
import com.badlogic.gdx.graphics.g2d.Animation; import com.badlogic.gdx.utils.Array; private Animation<TextureRegion> attackAnimation; private float attackStateTime; private Array<Entity> hitEntities = new Array<>(); // 初始化攻击动画时配置监听器 public void initAttackAnimation() { attackAnimation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { hitEntities.clear(); // 攻击开始时清空已击中列表 } @Override public void onAnimationEnd(Animation animation) { // 攻击结束后的清理逻辑,比如重置状态 } @Override public void onAnimationFrameChanged(Animation animation, int oldFrameIndex, int newFrameIndex) { // 切换到目标伤害帧时触发判定 for (int targetIdx : frameToDealDamage) { if (newFrameIndex == targetIdx) { dealDamage(); break; } } } }); } @Override public void update(float delta) { if (owner.isDead()) return; attackStateTime += delta; // 获取当前帧时,自动触发监听器的帧切换回调 attackAnimation.getKeyFrame(attackStateTime); } public void addHitEntity(Entity entity) { if (!hitEntities.contains(entity, false)) { hitEntities.add(entity); } }
额外优化建议
- 替换Array为HashSet:
hitEntities.contains()是线性遍历,敌人数量多时效率低下,改用HashSet<Entity>存储已击中目标,查询和添加操作均为O(1)复杂度。 - 主动检测碰撞:建议将碰撞检测逻辑放在
dealDamage()方法内,在目标帧区间主动扫描范围内的敌人,而非被动调用addHitEntity,减少逻辑耦合。 - 严格控制动画循环:若攻击动画是循环播放,需在
onAnimationEnd或时间超过动画时长时重置attackStateTime,避免判定逻辑失效。
内容的提问来源于stack exchange,提问作者Nhật Thành
相关产品推荐
相关产品推荐

