【问题标题】:Smoothly increase a variable平滑增加一个变量
【发布时间】:2021-01-29 03:13:30
【问题描述】:

我正在制作一款游戏,您可以在其中以稳定的速度(例如每秒 50 美元)赚钱。但是,当增加“钱”时,我希望它能够平稳地增加,而不是每秒跳 50 美元。

setInterval(increasemoney,1000);
function increasemoney() {
  money = money + moneypersecond;
}

我目前的策略是将其分为两类;一种是您每 1/moneypersecond 秒赚 1 美元(其中 moneypersecond

但是,有两个问题; 因为我想避免浮动,y 必须四舍五入,但这会失去精度。 编辑:浮动很好!我还可以通过增加“更新”之间的时间来提高精度(这也会使其看起来不那么平滑,并且只会提高精度而不是消除不精确)。

我的问题:

  • 这是正确的轨道吗?
  • 我应该如何实施解决方案? (最好以消除不精确的方式)

要求:我要避免浮动编辑:浮动是可以的,越准确越好。

【问题讨论】:

  • 为什么不使用带小数的数字,在向用户显示时只显示整数?似乎没有精度就不可能有精度。
  • 你想要lerp的值,详见Linear Interpolation。另请参阅slerpSpherical Linear Interpolation
  • setInterval 不会正好是一秒,具体取决于计算机/浏览器的工作负载。

标签: javascript


【解决方案1】:

1.常见的游戏循环

在这里,我们构建了一个示例,其中包含一个用于初始化游戏的通用 start 函数,以及一个每帧调用的 update 函数。继续并实时更改 target 以观察输出 lerp 到其目的地 -

let money;
let target;

// initialize
function start ()
{ money = document.forms.example.money
  target = document.forms.example.target
}

// each frame
function update (delta)
{ const next = lerp(Number(money.value), Number(target.value), delta/1e2)
  money.value = next.toFixed(2)
}

// helpers
function lerp (v0, v1, t, p = 1e-3)
{ const next = (1 - t) * v0 + t * v1
  if (Math.abs(v1 - next) < p)
    return v1
  else
    return next
}

function sleep (ms)
{ return new Promise(r => setTimeout(r, ms)) }

function time ()
{ let now = Date.now()
  let last
  return _ =>
  { last = now
    now = Date.now()
    return now - last
  }
}

async function loop ()
{ const deltaTime = time()
  await start()               // call `start` to initialize
  while (true)
  { await update(deltaTime()) // call `update` each frame
    await sleep(50)
  }
}

// run the game
loop()
<form id="example">
  target: <input type="number" name="target" value="3000">
  &larr; try a big value like <b>99999999</b><br>
  money: <output name="money">0.00</output><br>
</form>

2。原版 html 表单

如果您想在典型的游戏循环情况之外看到这种情况,我们可以用基本的 Html 形式演示同样的事情。 lerp 这里的实现(同上)改编自维基百科的Linear Interpolation article -

function lerp (v0, v1, t, p = 1e-3)
{ const next = (1 - t) * v0 + t * v1
  if (Math.abs(v1 - next) < p)
    return v1
  else
    return next
}

function sleep (ms)
{ return new Promise(r => setTimeout(r, ms)) }

async function onSubmit (event)
{ // prevent default form behaviour
  event.preventDefault()

  // get input values from form as Number
  const f = event.target
  let from = Number(f.from.value)
  let to = Number(f.to.value)

  // lerp loop!
  do
  { f.output.value = from.toFixed(2)
    from = lerp(from, to, 0.33)
    await sleep(50)
  } while(from < to)
}

// attach form listener
document.forms.example.addEventListener("submit", onSubmit)
<form id="example">
  from: <input name="from" value="100"><br>
  to: <input name="to" value="3000"><br>
  output: <output name="output"></output><br>
  <button type="submit">Lerp it!</button>
</form>

3.带有生成器的香草 html 表单

我们还可以将lerp 实现为生成器,这大大降低了onSubmit 侦听器的复杂性-

function* lerp (v0, v1, t, p = 1e-3)
{ do
  { yield v0
    v0 = (1 - t) * v0 + t * v1
  } while (Math.abs(v1 - v0) > p)
  yield v1
}

function sleep (ms)
{ return new Promise(r => setTimeout(r, ms)) }

async function onSubmit (event)
{ event.preventDefault()
  const f = event.target

  // iterate over lerp generator
  for (const v of lerp(Number(f.from.value), Number(f.to.value), 0.33))
  { f.output.value = v.toFixed(2)
    await sleep(50)
  }
}

document.forms.example.addEventListener("submit", onSubmit)
<form id="example">
  from: <input name="from" value="123.45"><br>
  to: <input name="to" value="3000.99"><br>
  output: <output name="output"></output><br>
  <button type="submit">Lerp it!</button>
</form>

【讨论】:

    【解决方案2】:

    基本思想是您应该在代码内部跟踪小数。当您向用户显示值时,不要向他们显示小数。使用 floor() 只显示整数。

    这对于您的要求来说太过分了,但下面展示了我如何进行多项计算以及如何向用户显示整数。

    class TimerCalc {
      constructor(incAmt, interval, updater) {
        this.incAmt = incAmt;
        this.interval = interval;
        this.updater = updater;
        this.lastTimeRun = performance.now();
        this.calc();
      }
      calc() {
        const diff = performance.now() - this.lastTimeRun;
        this.lastTimeRun = performance.now();
    
        const incAmt = diff / this.interval * this.incAmt;
        this.updater(incAmt);
    
        this.timer = window.setTimeout(() => this.calc(), 10);
      }
      kill() {
        this.timer && window.clearTimeout(this.timer);
      }
    }
    
    class Bank {
      constructor(initialAmount, outputSelector) {
        this.funds = initialAmount || 0;
        this.outputElem = document.querySelector(outputSelector);
        this.timers = {};
      }
      updateAccount(diff) {
        this.funds += diff;
        this.updateScreen();
      }
      updateScreen() {
        this.outputElem.textContent = Math.floor(this.funds).toLocaleString();
      }
      addResource(name, rate, interval) {
        this.timers[name] = new TimerCalc(rate, interval, this.updateAccount.bind(this));
      }
    }
    
    const myBank1 = new Bank(0, '#out1');
    myBank1.addResource("fast", 1, 1000);
    
    const myBank2 = new Bank(0, '#out2');
    myBank2.addResource("slow", 1, 10000);
    
    const myBank3 = new Bank(0, '#out3');
    myBank3.addResource("mine1", 1, 10);
    myBank3.addResource("worker1", -1.50, 1000);
    myBank3.addResource("worker2", -2.50, 1000);
    myBank3.addResource("manager", -5.50, 1000);
    <div id="out1"></div>
    <div id="out2"></div>
    <div id="out3"></div>

    【讨论】:

    • 这种方法的主要缺点是,从valuetarget 的补间时间会根据差异进行调整。如果差异很大,则需要很长时间才能达到目标。有关使用线性插值在用户指定的时间内到达任何目标的替代方案,请参阅我的答案。
    • @Thankyou 您的代码似乎没有考虑到 setTimeout 不是完美的 50 毫秒。我的代码有一个缺陷,时间跨度很小,值不是最新的。如果我需要它,我会以不同的方式进行计算,但对于基本的 cookie 点击器脚本,它可以正常工作。
    • 我的脚本确实将deltaTime 传递给update,允许用户根据每帧传递的精确时间来缩放计算。除此之外,我真的不明白你的评论。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-27
    • 2012-03-19
    相关资源
    最近更新 更多