是什么导致我的AI在没有选择真实目标的情况下在地图上随机寻找点?

背景

我正在编写一个具有基本目标选择和移动功能的AI。有两种类型的“生物”:活跃的fauna和不活跃的目标flora

问题

我的AI(附着在fauna上)首先瞄准flora,但它只能“看到”一些flora。当AI看不到任何flora时,AI会原地旋转并看似随机地弹跳;即使还有剩余的flora

为什么只有一些flora会被看到?为什么fauna在停止找到flora后会看似无目的地弹跳?为什么fauna在代码运行一段时间后会聚集在一起?是什么导致flora看不到?

如果您需要更多信息,请告诉我。

我的修复尝试

我第一次尝试修复这个问题取得了一些成功,但没有完全解决问题。那时我重写了代码,使用对象而不是数组。完成这一步后,目标选择功能正常了,但一些fauna会无休止地旋转。

然后我意识到可能是生物的旋转与getAngle函数的返回值不兼容。生物的旋转可能与getAngle的返回值等效,但不相等(例如,360度 ~= 720度,但360度 != 720度)。在修复这个问题后,它似乎在一段时间内工作正常,但当我进行更仔细的测试并运行更长时间时,我发现了这些问题。

我真的不确定是什么导致了这样的问题,但我非常好奇想知道。感谢任何帮助 🙂

代码解释

代码在线可用,地址是:http://codepen.io/CKH4/pen/wgZqgL/

在我的代码开头,我有一些Object原型扩展,允许我像使用数组一样使用对象。这些大致相当于它们的Array对应物。我认为这些不是问题的来源,但它们是程序运行所必需的。

Object.prototype.filter = function(fn) {  let ob = this, keep = {},      k = Object.keys(ob);  for (let i = 0; i < k.length; i++) {    if (fn(k[i], ob[k[i]]))      keep[k[i]] = ob[k[i]];  }  return keep;}Object.prototype.forEach = function(fn) {  let ob = this, k = Object.keys(ob);  for (let i = 0; i < k.length; i++)    fn(k[i], ob[k[i]]);}Object.prototype.reduce = function(test, initialValue = null) {  let ob = this, k = Object.keys(ob),      accumulator = initialValue || ob[k[0]],      i = (initialValue === null) ? 1 : 0;  for (; i < k.length; i++)    accumulator = test(accumulator, k[i], ob[k[i]], ob);  return accumulator;}

接下来我有一些用于操作“生物”的辅助函数。

// 计算两个生物之间的距离,通过它们的[pos]作为输入function getDist(p1, p2) {  return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));}// 计算从点1到点2的角度function getAngle(p1, p2) {  return (Math.atan2(p2.y - p1.y, p2.x - p1.x) / Math.PI * 180 + 360) % 360;}// 使生物朝向他们面对的方向移动function move() {  this.pos.x += this.speed * Math.cos(this.direction * Math.PI / 180);  this.pos.y += this.speed * Math.sin(this.direction * Math.PI / 180);}// 使生物朝向输入的角度旋转,根据生物的转向速度function rotateTowards(angle) {  this.direction += Math.sign(angle - this.direction) * this.turnSpeed;  this.direction = this.direction % 360;}// 使生物按提供的角度旋转function rotateBy(angle) {  this.direction += angle;  this.direction = this.direction % 360;}

现在我有目标选择函数。它首先接受运行AI的生物,然后接受一个要查找的生物对象,接下来接受模式,目前只能找到最近的,最后接受一个过滤函数,让目标查找器只查看flora。

代码首先过滤掉不在AI视线内的生物。我认为问题可能出在这里。接下来应用输入过滤器(这样在我的情况下只剩下flora)。最后,只有当生物对象中还有剩余时,代码才将对象减少到仅剩最近的生物。如果生物对象中没有任何剩余,它会返回一个包含undefined的数组。

function getTarget(c, of, mode = `nearest`, filter) {  let first;  // 过滤以便只查看视野内的生物  of = of.filter((k, t) => {    return Math.abs(getAngle(c.pos, t.pos) - c.direction) < c.viewAngle / 2;  });  // 过滤目标类型;例如,只返回flora  if (filter)    of = of.filter(filter);  if (Object.keys(of).length) {    first = of[Object.keys(of)[0]];    if (mode == `nearest`) {      return of.reduce((acc, k, cur) => {        let dist = getDist(c.pos, cur.pos);        if (dist < acc[0])          return [dist, k];        else          return acc;      }, [getDist(c.pos, first.pos), first]);    }  }  else    return [undefined, undefined];}

最后我有一个通用的AI,它将目标选择系统与移动代码结合在一起。如果有目标,生物会转向并朝目标移动。如果目标在生物5像素范围内,生物会摧毁目标。否则,生物会朝正方向转动,“寻找”另一个目标。

function findfood() {  let target = getTarget(this, ob, `nearest`, (k, c) => c.type == `flora`);  this.target = target[1];  if (ob[this.target]) {    rotateTowards.call(this, getAngle(this.pos, ob[this.target].pos));    if (getDist(this.pos, ob[this.target].pos) > 5)      move.call(this);    else {      delete ob[this.target];    }  }  else    rotateBy.call(this, this.turnSpeed);}

在这里我生成一个包含随机放置的florafauna的对象。我使用一个基于Object而不是Array的ID系统。所有的生物都被存储在一个动态的全局对象中。

ob = {};for (let i = 20; i > 0; i--) {  let id = Math.floor(Math.random() * 1000000000),      type = (Math.random() > .2 ? `flora` : `fauna`);  ob[id] = {    type: type,    pos: { x: Math.floor(Math.random() * canvas.width), y: Math.floor(Math.random() * canvas.height) },    direction: Math.random() * 360  }  if (type == `fauna`) {    ob[id].ai = findfood;    ob[id].viewAngle = 90;    ob[id].speed = .8;    ob[id].turnSpeed = 1.6;  }}

然后我通过setInterval运行模拟,它会在生物拥有AI时调用AI函数。问题也不在这里。

let fixedUpdate = setInterval(function() {  Object.keys(ob).forEach((ck) => {    let c = ob[ck];    if (c && c.ai)      c.ai.apply(c);  });}, 1000 / 60);

这是我用来显示的代码。只是基本的canvas操作,所以问题肯定不在这里。

let draw = () => {  // 清除画布  ctx.putImageData(emptyCanvas, 0, 0);  Object.keys(ob).forEach((ck) => {    let c = ob[ck];    if (c.type == 'flora')      ctx.fillStyle = '#22cc33';    else if (c.type == 'fauna') {      ctx.fillStyle = '#0066ee';      ctx.beginPath();      ctx.moveTo(c.pos.x, c.pos.y);      // ctx.lineTo(c.pos.x + 100, c.pos.y - 50);      // ctx.lineTo(c.pos.x + 100, c.pos.y + 50);      ctx.lineTo(c.pos.x, c.pos.y);      ctx.fill();      ctx.beginPath();      ctx.arc(c.pos.x, c.pos.y, 100, (c.direction - c.viewAngle / 2) * Math.PI / 180, (c.direction + c.viewAngle / 2) * Math.PI / 180);      ctx.fill();    }    else      ctx.fillStyle = '#424242';    ctx.beginPath();    ctx.arc(c.pos.x, c.pos.y, 10, 0, 2 * Math.PI);    ctx.fill();  });  requestAnimationFrame(draw);}draw();

这是嵌入的代码:

console.clear();Object.prototype.filter = function(fn) {  let ob = this, keep = {},      k = Object.keys(ob);  for (let i = 0; i < k.length; i++) {    if (fn(k[i], ob[k[i]]))      keep[k[i]] = ob[k[i]];  }  return keep;}Object.prototype.forEach = function(fn) {  let ob = this, k = Object.keys(ob);  for (let i = 0; i < k.length; i++)    fn(k[i], ob[k[i]]);}Object.prototype.reduce = function(test, initialValue = null) {  let ob = this, k = Object.keys(ob),      accumulator = initialValue || ob[k[0]],      i = (initialValue === null) ? 1 : 0;  for (; i < k.length; i++)    accumulator = test(accumulator, k[i], ob[k[i]], ob);  return accumulator;}function getDist(p1, p2) {  return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));}function getAngle(p1, p2) {  return (Math.atan2(p2.y - p1.y, p2.x - p1.x) / Math.PI * 180 + 360) % 360;}function move() {  this.pos.x += this.speed * Math.cos(this.direction * Math.PI / 180);  this.pos.y += this.speed * Math.sin(this.direction * Math.PI / 180);}function rotateTowards(angle) {  this.direction += Math.sign(angle - this.direction) * this.turnSpeed;  this.direction = this.direction % 360;}function rotateBy(angle) {  this.direction += angle;  this.direction = this.direction % 360;}function getTarget(c, of, mode = `nearest`, filter) {  let first;  // 过滤以便只查看视野内的生物  of = of.filter((k, t) => {    return Math.abs(getAngle(c.pos, t.pos) - c.direction) < c.viewAngle / 2;  });  // 过滤目标类型;例如,只返回flora  if (filter)    of = of.filter(filter);  if (Object.keys(of).length) {    first = of[Object.keys(of)[0]];    if (mode == `nearest`) {      return of.reduce((acc, k, cur) => {        let dist = getDist(c.pos, cur.pos);        if (dist < acc[0])          return [dist, k];        else          return acc;      }, [getDist(c.pos, first.pos), first]);    }  }  else    return [undefined, undefined];}function findfood() {  let target = getTarget(this, ob, `nearest`, (k, c) => c.type == `flora`);  this.target = target[1];  if (ob[this.target]) {    rotateTowards.call(this, getAngle(this.pos, ob[this.target].pos));    if (getDist(this.pos, ob[this.target].pos) > 5)      move.call(this);    else {      delete ob[this.target];    }  }  else    rotateBy.call(this, this.turnSpeed);}ob = {};for (let i = 20; i > 0; i--) {  let id = Math.floor(Math.random() * 1000000000),      type = (Math.random() > .2 ? `flora` : `fauna`);  ob[id] = {    type: type,    pos: { x: Math.floor(Math.random() * canvas.width), y: Math.floor(Math.random() * canvas.height) },    direction: Math.random() * 360  }  if (type == `fauna`) {    ob[id].ai = findfood;    ob[id].viewAngle = 90;    ob[id].speed = .8;    ob[id].turnSpeed = 1.6;  }}console.log(ob);let ctx = canvas.getContext(`2d`);let emptyCanvas = ctx.getImageData(0,0,canvas.width,canvas.height);let draw = () => {  // 清除画布  ctx.putImageData(emptyCanvas, 0, 0);  Object.keys(ob).forEach((ck) => {    let c = ob[ck];    if (c.type == 'flora')      ctx.fillStyle = '#22cc33';    else if (c.type == 'fauna') {      ctx.fillStyle = '#0066ee';      ctx.beginPath();      ctx.moveTo(c.pos.x, c.pos.y);      // ctx.lineTo(c.pos.x + 100, c.pos.y - 50);      // ctx.lineTo(c.pos.x + 100, c.pos.y + 50);      ctx.lineTo(c.pos.x, c.pos.y);      ctx.fill();      ctx.beginPath();      ctx.arc(c.pos.x, c.pos.y, 100, (c.direction - c.viewAngle / 2) * Math.PI / 180, (c.direction + c.viewAngle / 2) * Math.PI / 180);      ctx.fill();    }    else      ctx.fillStyle = '#424242';    ctx.beginPath();    ctx.arc(c.pos.x, c.pos.y, 10, 0, 2 * Math.PI);    ctx.fill();  });  requestAnimationFrame(draw);}draw();let fixedUpdate = setInterval(function() {  Object.keys(ob).forEach((ck) => {    let c = ob[ck];    if (c && c.ai)      c.ai.apply(c);  })}, 1000 / 60);
body {  margin: 0;}
<canvas height="1000" id="canvas" width="1000"></canvas>


回答:

我找到了代码中的错误。在getTarget()中,当我获取可能目标的第一个(of[0])时,我将first存储为生物的引用,而不是生物的ID。

为了修复它,我必须存储ID而不是对象的引用。我将以下代码更改为:

first = of[Object.keys(of)[0]];

改为:

first = Object.keys(of)[0];

这在getTarget()的其余代码中引起了问题,因为我试图获取附加到生物对象上的属性,而不是生物的ID。我通过更改以下代码来修复这个问题:

}, [getDist(c.pos, first.pos), first]);

改为:

}, [getDist(c.pos, of[first].pos), first]);

这给了我最终的getTarget()函数:

function getTarget(c, of, mode = `nearest`, filter) {  let first;  // 过滤以便只查看视野内的生物  of = of.filter((k, t) => {    return Math.abs(getAngle(c.pos, t.pos) - c.direction) < c.viewAngle / 2;  });  // 过滤目标类型;例如,只返回flora  if (filter)    of = of.filter(filter);  if (Object.keys(of).length) {    first = Object.keys(of)[0];    if (mode == `nearest`) {      return of.reduce((acc, k, cur) => {        let dist = getDist(c.pos, cur.pos);        if (dist < acc[0])          return [dist, k];        else          return acc;      }, [getDist(c.pos, of[first].pos), first]);    }  }  else    return [undefined, undefined];}

Related Posts

使用LSTM在Python中预测未来值

这段代码可以预测指定股票的当前日期之前的值,但不能预测…

如何在gensim的word2vec模型中查找双词组的相似性

我有一个word2vec模型,假设我使用的是googl…

dask_xgboost.predict 可以工作但无法显示 – 数据必须是一维的

我试图使用 XGBoost 创建模型。 看起来我成功地…

ML Tuning – Cross Validation in Spark

我在https://spark.apache.org/…

如何在React JS中使用fetch从REST API获取预测

我正在开发一个应用程序,其中Flask REST AP…

如何分析ML.NET中多类分类预测得分数组?

我在ML.NET中创建了一个多类分类项目。该项目可以对…

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注