You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

LibGdx游戏开发:如何让攻击仅在指定动画帧造成伤害

解决LibGdx攻击动画特定帧伤害判定丢失问题

现有代码的核心问题

  1. 帧索引跳过问题:直接比较动画帧索引时,若deltaTime过大(比如性能波动导致帧间隔变长),动画帧索引会直接跳过多帧,错过目标伤害帧的判定时机。
  2. 时间轴混乱:addHitEntity方法中修改statetime是错误操作——攻击动画的时间进度应该由update统一维护,碰撞逻辑里篡改时间会导致判定逻辑失效。
  3. 帧区间未覆盖:仅检测当前帧索引等于目标帧,没有考虑帧的时间区间范围,若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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 21:00:31