1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 神码ai人工智能写作机器人_游戏AI:机器人反击!

神码ai人工智能写作机器人_游戏AI:机器人反击!

时间:2023-12-12 03:20:56

相关推荐

神码ai人工智能写作机器人_游戏AI:机器人反击!

神码ai人工智能写作机器人

以下是摘自Earle Castledine撰写的新书HTML5 Games:Ninja的新手 。这本书的访问权限包含在SitePoint Premium会员资格中,或者您可以在世界各地的商店中索取副本。您可以在此处查看第一章的免费样本 。

现在,我们拥有所有可用的工具,可以制作出令人难以置信的详细世界来探索和居住。 不幸的是,我们的同居者并没有证明自己是非常值得的对手。 他们很愚蠢:他们没有情感,没有思想,没有生气。 我们可以通过图形,动画,尤其是人工智能(AI)来灌输这些特征。

人工智能是一个巨大而极其复杂的领域。 幸运的是,我们可以得到骄人的成绩甚至比智力更大量的人工。 几个简单的规则(与我们的老朋友Math.random结合使用)可以给人以通俗易懂的意图和思想幻想。 只要它支持我们的游戏机制并且很有趣,就不必过于现实。

像碰撞检测一样,AI不太好时通常是最好的。 电脑的对手是超人的。 他们拥有无所不知的天赋,可以在每个时间点理解整个世界。 可怜的老人类玩家只能看到屏幕上可见的内容。 它们通常无法与计算机匹敌。

但是我们不让他们知道! 他们会感到难过,质疑人类的未来,不想玩我们的游戏。 作为游戏设计师,平衡和支配我们的游戏流程是我们的工作,以使它们始终对玩家公平,具有挑战性且令人惊讶。

故意运动

选择精灵在游戏中如何移动非常有趣。update功能是您的空白画布,您可以对实体进行神似的控制。 那不喜欢什么!

实体移动的方式取决于我们每帧改变其xy位置的数量(“一点一点地移动!”)。 到目前为止,我们主要通过pos.x += speed * dt直线移动了物体。 增加速度(乘以增量)会导致精灵向右移动。 减法将其向左移动。 更改y坐标可上下移动。

为了使直线更有趣,请注入一些三角函数。 使用pos.y += Math.sin(t * 10) * 200 * dt,子画面通过正弦波上下摆动。t * 10是波的频率。t是我们更新系统中的时间(以秒为单位),因此它总是线性增加。 将其Math.sin会产生平滑的正弦波。 改变倍频会改变频率:数字越小振荡越快。200是波的振幅。

您可以组合波浪以获得更有趣的结果。 假设您在y位置添加了另一个正弦波:pos.y += Math.sin(t * 11) * 200 * dt。 它几乎与第一个完全相同,但是频率变化很小。 现在,由于这两个波浪在相移和相移时相互增强和抵消,因此实体会越来越快地上下摆动。 大量改变频率和幅度会产生一些有趣的反弹模式。 使用Math.cos更改x位置,您将有一个圆圈。

重要的方面是,可以将动作组合起来以做出看起来更复杂的行为。 他们可以痉挛地移动,可以懒惰地漂移。 当我们阅读本章时,他们将能够直接向玩家充电或直接逃跑。 他们将能够穿越迷宫。 当您组合这些技能(将弹跳动作与玩家的冲刺动作结合使用)或排序时(先逃跑两秒钟,然后上下摆动一秒钟),即可将它们雕刻成栩栩如生的生物。

航点

我们需要给这些冷漠的幽灵和蝙蝠加些香料,为它们提供一些生存的空间。 我们将从“航路点”的概念开始。航点是实体将要到达的里程碑或中间目标位置。 一旦到达航路点,便继续前进到下一个路点,直到到达目的地。 精心放置的一组航点可以为游戏角色提供一种目标感,并可以在关卡设计中发挥巨大作用。

为了使我们能够集中精力于航点背后的概念,我们将介绍一个不受迷宫墙约束的飞行坏人。 飞行中最可怕的敌人是蚊子(它是仅次于人类的世界上最致命的动物)。 但不是很诡异。 我们将使用“蝙蝠”。

蝙蝠不会是复杂的野兽。 他们将是不可预测的。 他们只会有一个飞向的航路点。 当他们到达那里时,他们会选择一个新的航路点。 稍后(当我们穿越迷宫时),我们将介绍具有多个结构化的航路点。 就目前而言,蝙蝠从一个点到另一个点飘荡,通常对玩家是个麻烦。

要创建它们,请在entities/Bat.js基于TileSprite创建一个名为Bat的新实体。 蝙蝠需要一些聪明才智来选择所需的航路点。 这可能是挑选在屏幕上任意位置的任意位置的功能,反而使他们更加强大一点,我们将给予他们findFreeSpot功能,所以航点永远是一个适宜步行的瓷砖,玩家可能会旅行:

const bats = this.add(new Container());for (let i = 0; i < 5; i++) {bats.add(new Bat(() => map.findFreeSpot()))}

我们有一个新的蝙蝠Container,并创建了五个新的蝙蝠。 每个人都可以参考我们的航点选择功能。 调用时,它将运行map.findFreeSpot并在迷宫中找到一个空单元格。 这成为蝙蝠的新航路点:

class Bat extends TileSprite {constructor(findWaypoint) {super(texture, 48, 48);this.findWaypoint = findWaypoint;this.waypoint = findWaypoint();...}}

Bat.js我们分配一个初始目标位置,然后在bat的update方法中向其移动。 距离足够近后,我们选择另一个位置作为下一个航路点:

// Move in the direction of the pathconst xo = waypoint.x - pos.x;const yo = waypoint.y - pos.y;const step = speed * dt;const xIsClose = Math.abs(xo) <= step;const yIsClose = Math.abs(yo) <= step;

我们如何“走向”某事物,以及我们如何知道自己是否“足够接近”? 为了回答这两个问题,我们首先要找到航点位置和蝙蝠之间的区别。 从蝙蝠的位置减去航路点的xy值,便得出了每个轴上的距离。 对于每个轴,我们定义“足够接近”以表示Math.abs(distance) <= step。 使用step(基于speed)意味着我们行进得越快,我们就需要离“足够近”(以便我们永远不会超调)。

注意:获取距离的绝对值,因为如果我们在航路点的另一侧,则它可能为负。我们不在乎方向,只在乎距离。

if (!xIsClose) {pos.x += speed * (xo > 0 ? 1 : -1) * dt;}if (!yIsClose) {pos.y += speed * (yo > 0 ? 1 : -1) * dt;}

免费学习PHP!

全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。

原价$ 11.95您的完全免费

为了朝着航路点的方向移动,我们将移动分为两部分。 如果我们在xy方向上都不太靠近,则将实体移向航路点。 如果重影位于航路点上方(y > 0),则将其向下移动,否则将其向上移动-与x轴相同。 这不会给我们一条直线(当我们开始向玩家射击时会出现一条直线),但是它确实使我们更接近每一帧的航路点。

if (xIsClose && yIsClose) {// New way pointthis.waypoint = this.findWaypoint();}

最后,如果水平和垂直距离都足够近,则表明蝙蝠已经到达目的地,我们将this.waypoint重新分配给新位置。 现在,蝙蝠像我们可能期望的那样,无意识地在大厅中漫游。

这是一个非常简单的航点系统。 通常,您需要构成完整路径的点列表。 当实体到达第一个航点时,它将从列表中拉出,下一个航点取而代之。 当我们很快遇到寻路时,我们将做与此类似的事情。

向目标移动并射击

回想一下第3章中的第一个射击游戏。坏人只是从右向左飞来飞去,注意他们自己的事,而我们这些球员则在嘲笑那些毫无头脑的僵尸飞行员。 为了平整游戏场并使游戏玩法更有趣,我们的敌人至少应该能够向我们发射弹丸。 这给玩家提供了在屏幕上四处移动的动机,以及消灭原本相当平静的实体的动机。 突然我们又成为英雄了。

向坏人提供玩家位置的感知非常容易:这只是player.pos! 但是,我们如何使用这些信息将事物发送到特定的方向呢? 答案当然是三角函数!

function angle (a, b) {const dx = a.x - b.x;const dy = a.y - b.y;const angle = Math.atan2(dy, dx);return angle;}

注意:在本章中,我们将看到几个三角函数,用于实现我们的“更好的坏人”的近期目标-但我们将不会真正探讨它们的工作原理。这是下一章的主题……因此,如果您对数学有些生疏,可以暂时放松一下。

以我们实现math.distance的相同方式,我们首先需要获取两个点(dxdy)之间的差,然后使用内置的反正切数学运算符Math.atan2来获取两个向量之间创建的角度。 请注意,atan2y差作为第一个参数,将x用作第二个参数。 将angle函数添加到utils/math.js

在我们的游戏中,大多数时候,我们都会寻找两个实体之间的夹角(而不是点)。 因此,我们通常对实体中心之间的角度感兴趣,而不是由pos定义的实体左上角。 我们还可以向utils/entity.js添加一个angle函数,该函数首先找到两个实体的中心,然后调用math.angle

function angle(a, b) {return math.angle(center(a), center(b));}

angle函数以弧度返回两个位置之间的角度。 现在,使用这些信息,我们可以计算出修改实体的xy位置以朝正确方向移动的数量:

const angleToPlayer = entity.angle(player.pos, baddie.pos);pos.x += Math.cos(angle) * speed * dt;pos.y += Math.sin(angle) * speed * dt;

要在游戏中使用角度,请记住,角度的余弦是在角度方向上移动一个像素时需要沿x轴移动的距离。 角度的正弦是您需要沿着y轴移动多远。 乘以标量(speed)像素数,子画面会沿正确的方向移动。

知道两件事之间的夹角在gamedev中非常重要。 将该方程式存储到内存中,因为您会经常使用它。 例如,我们现在可以直接对事物开枪-让我们开始吧! 创建一个Bullet.js精灵以充当弹丸:

class Bullet extends Sprite {constructor(dir, speed = 100) {super(texture);this.speed = speed;this.dir = dir;this.life = 3;}}

Bullet将是一个小的精灵,它是由一个位置,一个速度(速度和方向)和一个“生命”(默认为三秒钟)创建的。 当生命变为零时,子弹将被设置为dead弹……而我们最终将不会获得数百万发向无限远的子弹(就像我们第3章中的子弹一样)。

update(dt) {const { pos, speed, dir } = this;// Move in the direction of the pathpos.x += speed * dt * dir.x;pos.y += speed * dt * dir.y;if ((this.life -= dt) < 0) {this.dead = true;}}

与我们的第3章项目符号的不同之处在于,它们现在沿实例化时给定的方向移动。 因为xy代表两个实体之间的角度,所以子弹将以直线向目标射击-就是我们。

子弹不仅会神秘地凭空出现。 需要解雇他们。 我们需要另一个新的坏蛋! 我们将以礼帽图腾的形式部署几个哨兵。 图腾是地牢的守护者,他们从迷宫的中心监视世界,摧毁了所有盗窃宝藏的主角。

Totem.js实体生成Bullets并将其发射给Player。 因此,他们需要引用玩家(他们不知道它是玩家,他们只是将其视为target),并且需要一个在生成子弹时调用的函数。 我们将称之为onFire,并从在它传递GameScreen这样的Totem并不需要担心本身有关Bullets

class Totem extends TileSprite {constructor(target, onFire) {super(texture, 48, 48);this.target = target;this.onFire = onFire;this.fireIn = 0;}}

创建新的Totem,会为其分配一个目标,并为它发射Bullet时提供调用功能。 该功能会将子弹添加到主游戏容器中,以便可以检查是否存在碰撞。 现在,勇敢者必须避开BatsBullets。 我们将容器重命名为baddies因为两者的碰撞逻辑是相同的:

new Totem(player, bullet => baddies.add(bullet)))

要在屏幕上显示实体,需要将其放入Container以包含在场景图中。 我们有很多方法可以做到这一点。 我们可以使我们的主GameScreen对象成为全局变量,gameScreen.add从任何地方调用gameScreen.add。 这可以工作,但是不利于信息封装。 通过传递函数,我们可以仅指定我们希望Totem执行的功能。 与往常一样,最终取决于您。

警告:我们的Container逻辑中有一个隐藏的陷阱。如果我们在该容器自身的update调用期间将一个实体添加到该容器中,则不会添加该实体!例如,如果Totem在里面baddies,并试图还添加了一个新的子弹baddies,会不会出现子弹。查看Container的代码,看看是否可以理解原因。我们将在第9章的“遍历数组”中解决此问题。

图腾何时应该向玩家射击? 当然是随机的! 在拍摄时,fireIn变量将设置为倒数计时。 在倒计时的过程中,图腾具有较小的动画(在两个帧之间切换)。 在游戏设计中,这称为“电报”-一种向玩家微妙的视觉指示,表明他们最好保持警惕。 如果不进行电报,我们的图腾就会突然随机地向玩家射击,即使它们真的很近。 他们没有机会躲避子弹,会感到被欺骗和烦恼。

if (math.randOneIn(250)) {this.fireIn = 1;}if (this.fireIn > 0) {this.fireIn -= dt;// Telegraph to the playerthis.frame.x = [2, 4][Math.floor(t / 0.1) % 2];if (this.fireIn < 0) {this.fireAtTarget();}}

图腾发射的每一帧都有250分之一的机会。 如果是这样,倒数计时将开始一秒钟。 倒数之后,fireAtTarget方法将进行艰苦的工作,以计算弹丸击中目标所需的轨迹:

fireAtTarget() {const { target, onFire } = this;const totemPos = entity.center(this);const targetPos = entity.center(target);const angle = math.angle(targetPos, totemPos);...}

第一步是使用math.angle获取目标和图腾之间的角度。 我们可以使用帮助器entity.angle(由entity.center调用我们),但是我们还需要图腾的中心位置来正确设置项目符号的起始位置:

const x = Math.cos(angle);const y = Math.sin(angle);const bullet = new Bullet({ x, y }, 300);bullet.pos.x = totemPos.x - bullet.w / 2;bullet.pos.y = totemPos.y - bullet.h / 2;onFire(bullet);

一旦有了角度,就可以使用余弦和正弦来计算方向的分量。 (再次,嗯:也许您想把它变成另一个对您有用的数学函数?)然后我们创建一个新的Bullet,它将沿着正确的方向移动。

这突然使迷宫遍历变得非常具有挑战性! 您应该花一些时间来尝试“射击”代码:更改随机间隔的机会,或者将其设置为每两秒钟持续发射一次的计时器……或者是会短暂发射子弹的子弹头生成器一段的时间。

注意:在本书中,我们已经看到许多说明各种概念的小型机制。不要忘记游戏机制很灵活。它们可以重复使用,并与其他机制,控件或图形重新组合,以产生更多的游戏创意和游戏类型!例如,如果您将“鼠标单击”与“航路点”和“朝…射击”结合使用,我们将提供基本的塔防游戏!创建一个供敌人遵循的航路点路径:单击鼠标会添加一个炮塔(使用math.distance查找最接近的敌人),然后向其发射。

聪明的坏蛋:攻击和规避

我们的坏蛋们一心一意。 给他们一个简单的任务(随机射击时向左飞;向玩家射击……),并且他们永久地做同样的事情,就像一些盲目的自动机一样。 但是真正的坏蛋不是那样的:他们计划,徘徊,闲置,处于各种戒备状态,攻击,撤退,停下来吃冰淇淋……

为这些需求建模的一种方法是通过状态机。状态机协调行为在一定数量的状态之间的变化。 不同的事件可能会导致从当前状态到新状态的转变。状态将是特定于游戏的行为,例如“闲置”,“行走”,“攻击”,“停止吃冰淇淋”。 您不能攻击并停下来喝冰淇淋。 实现状态机就像存储状态变量一样简单,我们将状态变量限制为列表中的一项。 这是我们可能的蝙蝠状态的初始列表(在Bat.js文件中定义):

const states = {ATTACK: 0,EVADE: 1,WANDER: 2};

注意:不必在这样的对象中定义状态。我们可以只使用字符串“ ATTACK”,“ EVADE”和“ WANDER”。使用这样的对象仅能使我们组织思想-在一个位置列出所有可能的状态-并且如果我们犯了错误(例如分配不存在的状态),我们的工具可以警告我们。字符串很好!

蝙蝠在任何时候都只能处于ATTACKEVADEWANDER状态之一。 攻击将在玩家身上进行,逃避是在直接远离玩家的地方飞行,而游荡则随机在周围飞来飞去。 在函数构造函数中,我们将分配ATTACK的初始状态:this.state = state.ATTACK。 在update内部,我们根据当前状态切换行为:

const angle = entity.angle(target, this);const distance = entity.distance(target, this);if (state === states.ATTACK) {...} else if (state === states.EVADE) {...} else if (state === states.WANDER) {...}

根据当前状态(并结合与玩家的距离和角度),Bat可以决定其行为方式。 例如,如果在进攻,​​它可以直接向玩家移动:

xo = Math.cos(angle) * speed * dt;yo = Math.sin(angle) * speed * dt;if (distance < 60) {this.state = states.EVADE;}

但是事实证明,我们的蝙蝠就像鸡一样:当它们离目标太近(60像素以内)时,状态切换为state.EVADE。 躲避的作用与攻击相同,但是我们忽略了速度,因此它们直接飞离玩家:

xo = -Math.cos(angle) * speed * dt;yo = -Math.sin(angle) * speed * dt;if (distance > 120) {if (math.randOneIn(2)) {this.state = states.WANDER;this.waypoint = findFreeSpot();} else {this.state = states.ATTACK;}}

在躲避时,蝙蝠会不断考虑下一步行动。 如果距离播放器足够远,无法感到安全(120像素),它将重新评估其状况。 也许它想再次进攻,或者它想向随机的航路点走去。

以这种方式组合和排序行为是在游戏中制作真实可信的角色的关键。 当各种实体的状态机受其他实体的状态影响而导致紧急行为时,可能会变得更加有趣。 这是当实体的明显特征神奇地出现的时候,即使您(作为程序员)并没有专门设计它们。

注意:在Minecraft中就是一个例子。动物被设计成在受到伤害后可以躲避。如果您攻击一头母牛,它将持续一生(因此,对玩家来说狩猎更具挑战性)。游戏中的狼也具有攻击状态(因为它们是狼)。这些状态机的意外结果是,您有时会看到狼参与快节奏的狩猎!没有明确添加此行为,但是由于合并系统而出现。

更庄重的状态机

编排游戏时,不仅会在实体AI中使用状态机,还会使用很多状态机。 他们可以控制屏幕的显示时间(例如“准备就绪!”对话框),设置游戏的节奏和规则(例如管理冷却时间和计数器),并且对于将任何复杂的行为分解为细小,可重复使用的片段。 (处于不同状态的功能可以由不同类型的实体共享。)

使用自变量处理所有这些状态,if … else条款可能变得笨拙。 一种更强大的方法是将状态机抽象到其自己的类中,该类可通过其他功能重用和扩展(例如,记住我们之前所处的状态)。 这将在我们制作的大多数游戏中使用,因此让我们为其创建一个名为State.js的新文件,并将其添加到Pop库中:

class State {constructor(state) {this.set(state);}set(state) {this.last = this.state;this.state = state;this.time = 0;this.justSetState = true;}update(dt) {this.first = this.justSetState;this.justSetState = false;...}}

State类将保存当前和以前的状态,并记住我们进入当前状态已经有多长时间了。 它还可以告诉我们这是否是我们进入当前状态的第一帧。 它通过一个标志(justSetState)来实现。 在每一帧中,我们都必须更新state对象(使用MouseControls方式相同),以便可以进行时序计算。 在这里,如果它是第一次更新,我们还将设置第first标志。 这对于执行状态初始化任务(例如重置计数器)很有用。

if (state.first) {// just entered this state!this.spawnEnemy();}

当状态被设置(通过state.set("ATTACK")则属性first将被设置为true。 随后的更新会将标志重置为false。 增量时间也会传递到update因此我们可以跟踪当前状态处于活动状态的时间。 如果是第一帧,则将时间重置为0;否则,将时间重置为0。 否则,我们添加dt

this.time += this.first ? 0 : dt;

现在,我们可以改写我们的追逐逃逸示例以使用状态机,并删除if的嵌套:

switch (state.get()) {case states.ATTACK:break;case states.EVADE:break;case states.WANDER:break;}state.update(dt);

对于Bat的大脑来说,这是一些不错的文档-根据当前的输入来决定下一步要做什么。 因为状态的first帧有一个标记,所以现在还有个添加任何初始化任务的好地方。 例如,当Bat开始进行WANDER,它需要选择一个新的航点位置:

case states.WANDER:if (state.first) {this.waypoint = findFreeSpot();}...break;}

它通常是一个好主意,做初始化任务在state.first框架,而不是当你转换前一帧的出来。 例如,我们可以像设置state.set("WANDER")一样设置路标。 如果状态逻辑是独立的,则测试会更容易。 我们可以将Bat默认设置为this.state = state.WANDER并且知道航点将在更新的第一帧中设置。

我们将添加到State.js中的一些其他便捷函数来查询当前状态:

is(state) {return this.state === state;}isIn(...states) {return states.some(s => this.is(s));}

使用这些帮助器功能,我们可以方便地确定我们是否处于一种或多种状态:

if (state.isIn("EVADE", "WANDER")) {// Evading or wandering - but not attacking.}

我们为实体选择的状态可以根据需要进行细化。 我们可能具有“ BORN”(首次创建实体时),“ DYING”(当其被击中并被击晕时)和“ DEAD”(当其结束时)的状态,从而为我们提供了处理逻辑的离散位置和动画代码。

控制游戏流程

在需要控制操作流程的任何地方,状态机都非常有用。 一种出色的应用程序是管理我们的高级游戏状态。 当地牢游戏开始时,不应将用户扔进猛烈的猛烈攻击中,怪物和子弹无处不在。 取而代之的是,出现一条友好的“ READY READY”消息,使玩家有几秒钟的时间来调查情况并为未来的混乱做好心理准备。

状态机可以将GameScreen更新中的主要逻辑分解为“ READY”,“ PLAYING”,“ GAMEOVER”之类的内容。 它使我们更清楚如何构造代码以及整个游戏流程将变得更加清晰。 不必处理update功能中的所有内容; switch语句可以调度到其他方法。 例如,可以将所有“ PLAYING”状态的代码归为一个updatePlaying函数:

switch(state.get()) {case "READY":if (state.first) {this.scoreText.text = "GET READY";}if (state.time > 2) {state.set("PLAYING");}break;case "PLAYING":if (entity.hit(player, bat)) {state.set("GAMEOVER");}break;case "GAMEOVER":if (controls.action) {state.set("READY");}break;}state.update(dt);

GameScreen将以READY状态启动,并显示消息“ GET READY”。 两秒钟后(state.time > 2),它过渡到“ PLAYING”,游戏开始。 当玩家被击中时,状态会移至“ GAMEOVER”,在这里我们可以等到按下空格键再重新开始。

翻译自: /game-ai-the-bots-strike-back/

神码ai人工智能写作机器人

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。