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

React中用setInterval绘制Canvas图形的施法速度更新异常问题

问题:React + Canvas游戏中施法速度滑块修改后不立即生效

我用HTML Canvas和React做了一款对战小游戏:两名英雄(大圆形)在场地两侧移动,互相投掷法术(小圆形)。每个英雄有调节移动速度和施法速度的滑块,目前移动速度调节正常,但施法速度修改后不会立即生效,只有鼠标移到Canvas区域后才变化。

问题原因分析

  1. 直接修改外部数据对象,React未追踪变化:直接修改从data导入的hero1和hero2对象属性,这些不是React状态,React无法感知到变化,也就不会触发useInterval的更新。
  2. useInterval缺少依赖项:useInterval的延迟参数hero1.castSpeed/hero2.castSpeed变化时,组件没有重新执行useInterval来更新定时器。
  3. rangeInputHandler的逻辑错误:修改hero2施法速度时,错误地赋值给了hero1.castSpeed,导致hero2的施法速度永远不会变化。
  4. SpellCast.js的addSpell逻辑问题:每次调用addSpell都会清空hero.spellPool,导致英雄永远只能有一个法术在场上。

修复步骤

1. 将英雄数据转为React状态

把hero1和hero2从导入的静态对象转为React的useState状态,这样修改属性时会触发组件重渲染:

// 替换原来的const { hero1, hero2, spell } = data;
const [hero1, setHero1] = useState({...data.hero1});
const [hero2, setHero2] = useState({...data.hero2});
const { spell } = data;

2. 修复rangeInputHandler的更新逻辑

使用状态更新函数来修改英雄属性,确保状态正确更新,同时修正hero2施法速度的赋值错误:

function rangeInputHandler(event) {
  event.preventDefault();
  const { id, value } = event.target;

  if (id === "hero1_move-speed_control") {
    setHero1(prev => ({...prev, moveSpeed: Number(value)}));
  }
  if (id === "hero2_move-speed_control") {
    setHero2(prev => ({...prev, moveSpeed: Number(value)}));
  }

  if (id === "hero1_cast-speed_control") {
    setHero1(prev => ({...prev, castSpeed: Number(value) * 1000}));
  }
  if (id === "hero2_cast-speed_control") {
    // 修复:这里应该修改hero2的castSpeed
    setHero2(prev => ({...prev, castSpeed: Number(value) * 1000}));
  }
}

3. 给useInterval添加依赖项

让useInterval依赖对应的castSpeed,这样当施法速度变化时,定时器会重新创建:

useInterval(() => {
  addSpell(hero1, spell);
}, hero1.castSpeed);

useInterval(() => {
  addSpell(hero2, spell);
}, hero2.castSpeed);

(注:确保你的useInterval自定义hook支持依赖变化时重新启动定时器,标准实现会处理这种情况)

4. 修复SpellCast.js中的addSpell逻辑

不要每次添加法术都清空spellPool,改为向数组中push新法术:

export function addSpell(hero, spell) {
  // 移除清空数组的代码,直接push新法术
  hero.spellPool.push(
    new Spell(
      hero.x,
      hero.y,
      spell.dx,
      spell.dy,
      spell.rad,
      hero.spellColor,
      hero.id
    )
  );
}
// 同时记得在初始化英雄数据时,给spellPool设置为空数组
// 比如在data.js里给hero1和hero2加上spellPool: []

5. 调整Canvas渲染的依赖

确保Canvas的渲染函数能获取到最新的英雄状态,把hero1和hero2加入useEffect的依赖数组:

useEffect(() => {
  const canvas = canvasRef.current;
  const context = canvas.getContext("2d");

  const render = () => {
    context.clearRect(0, 0, canvas.width, canvas.height);
    HeroMove(context, hero1);
    HeroMove(context, hero2);

    hero1.spellPool.forEach((spell) => {
      spell.cast(context);
    });
    hero2.spellPool.forEach((spell) => {
      spell.cast(context);
    });
    collisionBorder(canvas, hero1, hero2);
    requestAnimationFrame(render);
  };

  const animationId = requestAnimationFrame(render);
  // 添加清理函数,避免内存泄漏
  return () => cancelAnimationFrame(animationId);
}, [hero1, hero2]); // 加入依赖,状态变化时重新渲染

额外优化提示

  • 鼠标移动事件中,setMouseCoordinates等状态每次都创建新数组,会导致不必要的重渲染,可以考虑只存储最新坐标而不是历史数组:setMouseCoordinates(currentMouseCoord)
  • collisionBorder、HeroMove等函数如果直接修改英雄状态,建议改为返回新状态,保持状态的不可变性

内容的提问来源于stack exchange,提问作者Tim M.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 22:42:09