惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

K
Kaspersky official blog
罗磊的独立博客
F
Fortinet All Blogs
人人都是产品经理
人人都是产品经理
量子位
V
Visual Studio Blog
Blog — PlanetScale
Blog — PlanetScale
M
MIT News - Artificial intelligence
B
Blog RSS Feed
腾讯CDC
博客园_首页
aimingoo的专栏
aimingoo的专栏
博客园 - 三生石上(FineUI控件)
博客园 - Franky
S
SegmentFault 最新的问题
N
Netflix TechBlog - Medium
小众软件
小众软件
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
L
LINUX DO - 热门话题
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Martin Fowler
Martin Fowler
D
Docker
P
Privacy & Cybersecurity Law Blog
S
Securelist
V
V2EX
Jina AI
Jina AI
阮一峰的网络日志
阮一峰的网络日志
T
Tor Project blog
The Hacker News
The Hacker News
Microsoft Azure Blog
Microsoft Azure Blog
AWS News Blog
AWS News Blog
The GitHub Blog
The GitHub Blog
有赞技术团队
有赞技术团队
T
The Exploit Database - CXSecurity.com
Help Net Security
Help Net Security
酷 壳 – CoolShell
酷 壳 – CoolShell
Application and Cybersecurity Blog
Application and Cybersecurity Blog
博客园 - 叶小钗
Recent Announcements
Recent Announcements
Cloudbric
Cloudbric
Y
Y Combinator Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Latest news
Latest news
MongoDB | Blog
MongoDB | Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Recorded Future
Recorded Future
V2EX - 技术
V2EX - 技术

UNITY

刚才清理废纸篓的时候发现一封没看的 unity 邮件, 这是以后不让用 Unity 开发国外版本的游戏了? 逼着使用腾讯的 Tuanjie 引擎吗? Tuanjie 打出来的包好像会强加水印啥的. - V2EX 中国区用户的海外 Unity 资源商店的访问权限将于 2026 年 3 月 31 日终止 有哪个头显是能实时连接 Unity 编辑场景的? - V2EX unity 国区账号如何下载 unity 商店资源? - V2EX unity 可以做三维建模 vr 效果吗 - V2EX gaia pro 问题请教 - V2EX Cursor 等一众 AI 编程工具目前和 Unity 游戏开发的结合怎么样了? Unity 游戏开发想用 AI 提高生产率目前都有什么比较好的方案或者工具? - V2EX Unity 股价只剩三年前的 10%了(200USD=>20USD)会不会被哪个大公司收购? - V2EX Unity 6 现在国区不让下载了 - V2EX Unity 答疑,任何问题都可以 - V2EX 当我用 Unity 开发游戏的时候有什么场景下要使用“栈”操作? - V2EX 为什么 Unity 自带的资源加密功能是中国版特供的? - V2EX Unity 远程开发岗位,感兴趣小伙伴看过来 - V2EX unity 2D 像素精灵的生成有推荐的方法吗? - V2EX 远程 Unity 开发工程师(英国) - V2EX 求推荐点 Unity 入门教程 - V2EX 请教个语音听写的问题 - V2EX Unity 打包后出现 "trial version" - V2EX 有没老哥用 m2 开发 unity 的 - V2EX 如何看待 Unity 新出的每次安装收费? - V2EX 为什么 Unity 的定价策略增加了对安装量的限制? - V2EX unity 将于 2024 年 1 月调整收费模式 - V2EX [Unity 离奇 Bug] 游戏在编辑器内运行 ok,构建出的 exe 却不能在本机正常运行 - V2EX 3 维空间中,求过原点的直线的方向向量? - V2EX 求推荐资源 - 俯瞰视角场景设计相关 - V2EX 震惊,有个框架竟然能快速让 Unity 新手制作可热更游戏!竟然可以让老项目快速对接具备热更性质! - V2EX 大佬们 学习 Unity 遇到的两个问题求助 - V2EX 有人对 unity 的 photon 组件熟悉的嘛,想有偿咨询几个问题 - V2EX 用 Unity3d 来做跨平台开发是不是很好? - V2EX 对于 U3D Stopwatch ElapsedTick 的疑惑 - V2EX 有偿提供 unity 虚拟面试~ - V2EX 问一下各位大哥关于买网络授课是否有必要的问题 - V2EX 请问通过 unity WebGL 能播放 rtmp,可以实现吗? - V2EX [UnityTips] 一种仿 Minecraft 物品合成的思路 - V2EX ShaderLab 入门实践 - V2EX Unity 热更新 之 如何使用 AST 转换 C# -> Lua - V2EX 按需渲染如何改善移动端性能? - V2EX 怎么通过 Unity 使用 Vulkan API & 移动端 Vulkan GPU 大 PK - V2EX Unity3D 实用技巧 - 快速学会模型合理导入 Unity3D 引擎 - V2EX Unity3D 实用技巧 - 从实践中总结经验(1) - V2EX [HDRP] PBR Lit shader(一.基础用法篇) - V2EX 如何将 Unity 以库形式集成到原生 iOS 和 Android 应用 - V2EX 如何在 Timeline 中创建自定义轨道? - V2EX 写给 Unity 开发者的 iOS 内存调试指南 - V2EX Unity3D 常用插件汇总 - V2EX 在 Unity 实现游戏命令模式 - V2EX Unity ECS(一)了解 ECS 与 DOTS - V2EX 零基础入门 Unity Shader(二) - V2EX 如何实现存储和加载游戏数据 - V2EX
使用 Unity 制作游戏 AI - V2EX
unn · 2020-01-15 · via UNITY

本文转自 Unity Connect 官方文章

简介

本教程主要介绍游戏 AI 的概念和开发方法。虽然实现过程是面向 Unity 的,但整个理论方法可以应用于任何其它游戏引擎。

本文介绍的所有概念都是我们团队在开发《 Radiant Blade 》游戏的原型阶段学习到的,目前该游戏已经到达成品阶段。

使用游戏 AI 的原因

开始介绍技术内容前,我们首先要思考为什么要为游戏添加 AI。

很长一段时间以来,我都在幻想着为游戏开发令人惊奇的 AI,让 AI 给玩家带来印象深刻的体验。这种 AI 可以预料到玩家的每一个操作,几乎无法被打败。但说实话,这种 AI 毫无对抗的乐趣。

值得玩家去玩的游戏应该是玩家可以获得乐趣的游戏。因此我们的 AI 必须可以和玩家旗鼓相当。AI 可以作为伙伴,让玩家通过特别的方法进行交互。

显然,只有乐趣的游戏不会是优秀的游戏。游戏也必须有炫酷的机制,深刻的含义,以及精美的外观。但对我们的 AI 而言,我们希望 AI 具有娱乐性,因此我们要进一步缩小这个概念。

游戏设计

什么是娱乐性?更具体来说,游戏中的娱乐性是什么?

开发团队花了一些时间思考这个问题,我们的结论可以总结为一个词:学习。具备娱乐性的游戏是玩家可以从中学习和利用知识的游戏。

娱乐性源于小小的好奇心,在玩家看到新事物时,好奇心会占据玩家的头脑,并会不断增长,直到玩家完全理解这项新事物。

也就是说,具有娱乐性的 AI 必须是可以被玩家学习的。

这个简单的概念形成了所有游戏中 AI 的广泛理解,包括:《超级玛丽》,《毁灭战士》,《魔兽世界》和《以撒的结合》。

如果分析这些游戏的 AI,我们会发现它们都是可以预测的。由于加入了一些随机元素,这些游戏 AI 不是完全固定不变的,但仍有预测的可能。

这样又出现了另一个问题:如何制作出可预测的游戏 AI ?

答案很简单:使用状态机。

状态机

状态机是包含状态和过渡的数学工具。

基本的状态机

在确定性状态机中,我们会处于一个特定状态,在移动时,我们会随着其中一个可用过渡转变到新状态。过渡可能会受到条件限制,例如:只有在拥有特定法术时,AI 才可以到达指定状态。

状态机的优点是:它们具有表现力和可预测性。例如,假设状态包括“攻击”,“受击”,“奔跑至目标”和“逃跑”,我们可以使用一些过渡,创建出模拟 AI 基本行为的状态机。

简单的 AI 示例

我们制作的 AI 可以用下面三句话描述:

生命值在 10%以下时,AI 会逃跑。

AI 可以受到攻击。

玩家处在 AI 范围内时,AI 会向玩家跑去,然后攻击玩家。

这意味着 AI 很简单。简单是件好事情。如果我们无法简单地描述自己的 AI,那么我们可能需要对 AI 做进一步思考。

状态机和 Unity

我们知道状态机很厉害,那么我们是否可以在 Unity 使用状态机?

当然可以。

大致的方法有三种:

自己开发;

使用 Animator 实现;

从 Asset Store 资源商店获取相应资源。

由于状态机是游戏中很常见的工具,我不建议开发者自己开发状态机,因为已经有很多人实现过状态机,除非开发者希望学习怎么通过代码实现状态机,否则我们可以直接获取可以使用的状态机。

第二种方法是使用 Unity 的内置 Animator 功能。虽然这个名称不太好理解,但它其实是一种可以播放动画的状态机。但在 Animator 中,我们不一定要使用动画,如果不使用动画的话,它的工作方式和状态机一样。

Animator 使用起来快捷而直观。

《 Radiant Blade 》中使用 Unity Animator 实现的弓箭手 AI

第三种方法是从 Asset Store 资源商店获取相关资源。我们没有试过这个方法,但我们相信应该不少资源有和 Animator 一样不错的效果。

如果你使用过比 Animator 更好的资源,请来告诉我们。

Animator

或许你使用过 Animator 在 Unity 中实现标准动画,但我们在此会根据需求调整一些方法。

下面开始吧。

状态

通常,Animator 的状态包含动画。我们没有这样使用,而是把状态关联到描述行为的代码。

为了演示这一点,我们现在查看定义弓箭手的游戏对象。

Behaviours 对象的子对象是 AI 行为。它们其实是小型控制器,在对应状态激活时,它们会控制弓箭手。

在 Shoot 状态激活时,会在弓箭手上使用 Shoot Behaviour 脚本

这是基于状态的对象。在完成行为后,Shoot Behaviour 会通知 Animator。Animator 内置的蓝色进度条可能会让人迷惑,但它只在外观上起到作用。

变量

这里的 AI 设计是响应式系统,它会随条件而变化,那么条件是什么呢?当然是玩家和环境。

Animator 的变量用于描述游戏的状态,以及做出已知决策。

上图是弓箭手使用的变量,它们描述了形成 AI 的所有要素

这是一项重要的概念。在以传统方法使用 Animator 时,大多数状态过渡会随着关联动画结束而结束。对于 AI 来说,状态就是行为,它会在未定义的时间内保存游戏逻辑。

我们使用了两个变量,它们的作用是通知状态的结束,即 behaviour_ended 和 behaviour_error。它们是状态的输出结果,表示状态成功结束,或是出现错误。

过渡

过渡定义了 AI 行为的改变过程,表示:当 AI 完成向目标行走的过程后,它应该要做什么。

示例过渡:如果目标在近战范围内,AI 会进行攻击

对 Unity 的 Animator,有些开发者可能不知道的是:过渡是有先后顺序的。特定过渡会被首先评估,仅在它的相关条件为假时,第二个过渡才会进行评估。

选中 Neutral 状态时,我们可以查看过渡的优先级

这项功能很不错,因为它允许我们把 AI 设计为中心大脑,根据优先级来做出合适的选择。

是否还记得我们之前展示的弓箭手 AI ?请注意 AI 的顺序和中心部分。Neutral 节点是决策中心,它的主要工作过程如下:

如果没有玩家的话,AI 停止战斗;

如果玩家距离较远,AI 向玩家移动,进入射击范围;

如果玩家不在 AI 的视线方向,AI 向玩家移动,从而能够进行射击;

如果处于近战范围,则进行近战攻击;

如果玩家过于接近 AI,AI 可能会向后退;

AI 有可能随机改变和玩家的方向;

AI 会向玩家射击。

该功能的好处在于,每个单独的过渡都非常简单:过渡会总结为一次测试,或甚至没有测试。使用后续过渡的前提是之前的过渡条件必须为假。

实现方法

从这部分开始,我们应该会开始了解具体操作。你是否注意到,到现在我还未提供过任何相关代码。

这个状态不错,因为这意味着我们的框架有足够高的抽象级,不必处理任何技术细节,就可以很好进行解释。在代码部分完成后,设计 AI 的过程非常直观。

我们需要什么

下面是实现 AI 的任务:

编写 AI 行为;

把 Animator 和可用行为关联;

为 Animator 更新游戏相关变量的列表。

行为

开始处理前,首先回顾行为的功能。

行为会和游戏的角色控制器一起工作;

行为可以被识别;

行为可以被启用;

行为可以成功完成;

行为也可以出现错误;

行为可以被中断;

大概就是这样。

public abstract class AbstractAIBehaviour : MonoBehaviour {

// 角色由行为控制

[SerializeField]

protected CharController charController;

// 必须返回对应行为的 Animator 状态的短哈希值。

abstract public int GetBehaviourHash();

// 在行为成功结束时调用的事件。

public event Action OnBehaviourEnded;

// 在行为失败时,要调用的事件

public event Action OnBehaviourError;

// OnEnable()

// OnDisable()

// enable = true/false;

}

对于启用和禁用部分,我们会利用 Unity 的内置方法,这里不必自己编写方法。

我们会使用简洁的 API。

对于识别符,我创建了带有特殊名称的方法:GetBehaviourHash。因为 Animator 状态的识别方式是:状态的识别符是其名称的哈希值。https://docs.unity3d.com/ScriptReference/Animator.StringToHash.html

因此对于 Shoot 状态,对应的识别符是 Animator.StringToHash(“Shoot”)。

为了弄清楚对象,避免再次计算相同的哈希值,我们可以把它们保存为静态变量:

/**

  • 该类是预计算哈希值的占位符。

  • 目的是创建 Animator 状态名称和 AI 行为之间的关联。

  • 下面定义的整数应该用于 GetBehaviourHash 中继承自 AbstractAIBehaviour 的类。

*/

public class BehaviourHashes {

// 我们会使用行为,让角色向目标移动。

static public readonly int OBJ_MOVETO_STATE = Animator.StringToHash("Obj MoveTo");

// 我们会使用行为,让角色什么都不做。

static public readonly int IDLE_STATE = Animator.StringToHash("Idle");

// 此时角色会漫无目的地四处移动。

static public readonly int ROAM_STATE = Animator.StringToHash("Roam")

// ...

}

考虑到这点,AbstractAIBehaviour 的实现代码如下:

// 必须返回对应行为的 Animator 状态的短哈希值。

public override int GetBehaviourHash()

{

    // Animator 中的状态名称为 Idle。

    return BehaviourHashes.IDLE_STATE;

}

我们会把每个哈希值存到对应的脚本中,因此 ROAM_STATE 可以保存在 RoamBehaviour 类中。

唯一的问题在于:由于我们暗中把每个行为关联到名称,因此可能很难打开每个行为类,从而收集 Animator 状态的授权名称。

从此开始,我们的工作是为真实行为编写实际的代码,但这取决于开发者,因为这要根据自己的游戏来实现。我们需要做的是实现 AbstractAIBehaviour 的子类。

关联行为和 Animator

我们的 AI 的行为可以被识别,监听,启用和禁用。现在我们要利用行为。

我们要从控制器开始,由于我们有多个互相独立的实体,我们需要同步它们,实现流畅的工作效果。

该控制器的目的是确保每次只启用一个行为,并提供修改当前行为的切入点。

一些开发者可能不知道应该何时给游戏添加新控制器的类。好的习惯是把控制器看作用来同步多个较小功能的代码。

/**

  • AIBehaviourController 应该关联 AI 的 Animator 和相应行为。

*/

public class AIBehaviourController

/**

* Contains the available Behaviours.

* 包含可用行为

*

* The key of a Behaviour is the value returned by its GetBehaviourHash method.

* 行为的关键是 GetBehaviourHash 方法返回的数值

*/

protected Dictionary<int, AbstractAIBehaviour> behaviours = new Dictionary<int, AbstractAIBehaviour>();

// AI 的 Animator

private Animator stateMachine;

// 正在执行的行为

private AbstractAIBehaviour currentBehaviour;   

// 必须存在 AI Animator 中的触发器

public static readonly int BEHAVIOUR_ENDED = Animator.StringToHash("behaviour_ended");

public static readonly int BEHAVIOUR_ERROR = Animator.StringToHash("behaviour_error");

/**

* 强制某个行为中断正在执行的行为

*/

public void SetBehaviour(int behaviorHash)

{

    // 安全地禁用当前行为

    if (currentBehaviour)

        currentBehaviour.enabled = false;

    try

    {

        // 开始新的行为

        currentBehaviour = behaviours[behaviorHash];

        currentBehaviour.enabled = true;

    }

    catch (KeyNotFoundException)

    {

        currentBehaviour = null;

    }

}

void Awake()

{

    stateMachine = GetComponent<Animator>();

    // 对于每个子对象

    foreach (AbstractAIBehaviour behaviour in GetComponentsInChildren<AbstractAIBehaviour>())

    {

        // 注册行为

        behaviours.Add(behaviour.GetBehaviourHash(), behaviour);

        // 监听行为

        behaviour.OnBehaviourEnded += OnBehaviourEnded;

        behaviour.OnBehaviourError += OnBehaviourError;

    }

}

/**

* 在行为结束时,通知 AI 的 Animator

*/

private void OnBehaviourEnded()

{

    stateMachine.SetTrigger(BEHAVIOUR_ENDED);

}

/**

* 在行为失败时,通知 AI 的 Animator

*/

private void OnBehaviourError()

{

    stateMachine.SetTrigger(BEHAVIOUR_ERROR);

}

}

这个类比较长,但是代码其实很简单:

字典包含我们已知的行为;

方法可以激活特定行为;

两个事件用于在行为结束时通知 Animator。

有了切入点,我们可以把它和 Animator 连接起来。怎么连接呢?我们会使用一个不常用的功能:StateMachineBehaviour。

选中 Animator 时,如果在空白处单击左键,我们会聚焦 Animator 本身,并显示 Animator 的隐藏检视窗口

StateMachineBehaviour 的功能是什么?它允许我们向 Animator 插入自定义代码。我们要怎么使用它呢?

我们会在 Animator 的状态变化时,调用我们的 AIBehaviourController。

/**

  • 该类会插入 AI 的 Animator。

  • 它的唯一作用是监视 Animator 中的状态转换。

*/

public class AIStateController : StateMachineBehaviour {

/**

* 在 Animator 进入新状态时,通知 AI 控制器。

*/

override public void OnStateEnter(Animator animator, AnimatorStateInfo info, int layerIndex)

{

    if (!animator.GetComponent<AIBehaviourController>().SetBehaviour(animatorStateInfo.shortNameHash))

    {

        // 如果状态不存在,那么把它设为决策中心。

        // 强制 Animator 直接评估该状态。

        animator.Update(0f);

    }

}

}

这些代码非常直观,它会处理 Unity 的一个特别之处:Animator 无法在每帧处理多个状态,因此在我们遍历决策中心时,会造成短暂的延迟。

幸运的是,解决方法很简单,我们可以强行执行 Update 方法,强制 Animator 处理状态。

通过使用我们的新类,我们可以把功能结合起来,只要把该脚本添加到 AI 的 Animator 即可。

现在进入新状态时,我们的 AI Animator 会调用 AIBehaviourController

最后,我们有框架的三个类部分,子类,以及角色控制器,它们包含着实际的游戏逻辑。

组合成 AI 框架的小型类图示

包含游戏逻辑

总而言之,技术方面的解决方法可以总结为三个类,每个类都非常简洁。

我们还需要什么呢?当然是游戏本身了。但这个部分必须由开发者自己制作。

总之,实现自己的 AI 需要的内容如下:

一个角色控制器,负责角色和其渲染的实际逻辑;

变量,以及让变量与 Animator 保持同步的代码;

自定义行为,例如:攻击,移动。

此时我们要处理的都是常见的 Unity 标准代码。

变量,Animator 和行为都协同工作

原文链接: https://connect.unity.com/p/creating-an-a-i-with-unity-shi-yong-unityzhi-zuo-you-xi-ai?app=true

各位在开发过程中遇到问题?欢迎大家戳上方链接,下载官方 app,可在线答疑哦,还有更多学习资源等你来发现~