这篇介绍一下A*寻路算法。

基本概念

A*(A-Star)算法是一种静态路网中求解最短路最有效的直接搜索方法。 我们会把地图分割成一个个方格(也可以是别的形状),在此基础上进行寻路算法。

启发式搜索

启发式搜索就是对每一个位置进行评估,得到最好的位置,再从这个位置进行搜索直到目标。这样可以省略大量无谓的搜索路径,提高了效率。 在启发式搜索中,对位置的估价是十分重要的。

估价函数

从当前节点移动到目标节点的预估费用。在寻路问题和迷宫问题中,我们通常用曼哈顿(manhattan)估价函数预估费用。

特点

A*算法在理论上是时间最优的,但是也有缺点:它的空间增长是指数级别的。(优化:二叉堆)

通过从点A开始,检查相邻方格的方式,向外扩展直到找到目标。

算法步骤

术语

开启列表:待检查方格的集合列表,寻找周围可以达到的点,加入到此表中,并保存中心点为父节点。
关闭列表:列表中保存不需要再次检查的方格。

G:与起始点的距离。
H:与目标点的距离。
F:表示G与H的和。F最小的格子表示该选择的位置。

F,G和H的评分被写在每个方格里。F在中间,G在左上角,H则在右上角。

我们另水平或垂直移动的耗费为10,对角线方向的耗费为14。因为,对角线距离是根号2即1.4,为了取整都乘以10。
求G的值,就是一个格子到它父节点的距离,加上父节点的G的值。
求H的值,就是直接求这个格子到终点的最短路径的距离,并且这个过程会忽略障碍。

开始查找

1、从A点开始,把它作为待处理点加入“开启列表”。开启列表的格子会逐渐增多,这是一个待检测方格的列表。

2、寻找起点周围所有可到达或通过的方格,把他们加入“开启列表”。把A点作为它们的父节点。

3、从开启列表中删除点A,把它加入到一个“关闭列表”,列表中保存所有不需要再次检查的方格。

为了继续搜索,我们简单的从开启列表中选择F值最低的方格,如果最低的F值相同,就取其中H值最低的。然后,对选中的方格做如下处理:

4、把当前格子从开启列表中删除,然后添加到关闭列表中。

5、检查所有相邻格子。跳过已经在关闭列表中的或者有障碍的,剩下的如果还不在开启列表的话就添加进去,把当前格子作为这些新的方格的父节点。

6、如果某个相邻格已经在开启列表里了,检查新的路径到达它的话,G值是否会更低一些。如果不是,那就什么都不做。反之,如果新的G值更低,那就把相邻方格的父节点改为目前选中的方格,重新计算F和G的值。

步骤总结

  1. 把起始格添加到开启列表。

  2. 重复如下的工作:

    1. 寻找开启列表中F值最低的格子。我们称它为当前格。

    2. 把它切换到关闭列表。

    3. 对相邻的格中的每一个
      • 如果它不可通过或者已经在关闭列表中,略过它。反之如下。
      • 如果它不在开启列表中,把它添加进去。把当前格作为这一格的父节点。记录这一格的F,G,和H值。
      • 如果它已经在开启列表中,用G值为参考检查新的路径是否更好。更低的G值意味着更好的路径。如果是这样,就把这一格的父节点改成当前格,并且重新计算这一格的G和F值。
    4. 停止,当你
      • 把目标格添加进了关闭列表,这时候路径被找到。
      • 没有找到目标格,开启列表已经空了。这时候,路径不存在。
  3. 保存路径。从目标格开始,沿着每一格的父节点移动直到回到起始格。

例子

这里是我写的一个简单的 U3D A* Demo

在AssetBundle中存储和加载二进制数据

第一步是使用“.bytes”扩展名保存你的二进制数据。Unity会将这个文件视为TextAsset。一旦你下载了这个AssetBundle,并且加载了 TextAsset 对象,你就可以使用TextAsset的 .bytes 属性来获取二进制数据。

 
string url = "http://www.mywebsite.com/mygame/assetbundles/assetbundle1.unity3d";
IEnumerator Start () {
    // Start a download of the given URL
    WWW www = WWW.LoadFromCacheOrDownload (url, 1);

    // Wait for download to complete
    yield return www;

    // Load and retrieve the AssetBundle
    AssetBundle bundle = www.assetBundle;

    // Load the TextAsset object
    TextAsset txt = bundle.Load("myBinaryAsText", typeof(TextAsset)) as TextAsset;

    // Retrieve the binary data as an array of bytes
    byte[] bytes = txt.bytes;
}

内容保护

虽然使用加密技术可以确保你的Asset在传输时的安全,但一旦用户掌握了数据,他们总有办法获取到数据的内容。比如有工具可以获取发送到GPU的3D模型和贴图。因此只要用户愿意,你的资源总是会被提取到的。

当然,你还是可以使用自己的加密方法到AssetBundle文件。

一种方法是使用 TextAsset 类型来保存你的数据为字节。你可以编码你的文件然后保存为“.bytes”类型。客户端下载了AssetBundle后,从TextAsset获取到数据然后进行解密。这种方法,AssetBundle没有加密,但其存储的TextAsset数据是加过密的。

 
string url = "http://www.mywebsite.com/mygame/assetbundles/assetbundle1.unity3d";
IEnumerator Start () {
    // Start a download of the encrypted assetbundle
    WWW www = new WWW.LoadFromCacheOrDownload (url, 1);

    // Wait for download to complete
    yield return www;

    // Load the TextAsset from the AssetBundle
    TextAsset textAsset = www.assetBundle.Load("EncryptedData", typeof(TextAsset));
 
    // Get the byte data
    byte[] encryptedData = textAsset.bytes;

    // Decrypt the AssetBundle data
    byte[] decryptedData = YourDecryptionMethod(encryptedData);

    // Use your byte array. The AssetBundle will be cached
}

另一种方法是从源头上,完全加密AssetBundle,然后使用WWW类下载。你可以使用任意的文件类型,只要你的服务器将其视为二进制数据。一旦下载了数据,你可以从WWW的bytes属性获取数据,然后使用自己的解密方法来获得解密后的AssetBundle文件,然后再使用 AssetBundle.CreateFromMemory 从内存创建AssetBundle。

 
string url = "http://www.mywebsite.com/mygame/assetbundles/assetbundle1.unity3d";
IEnumerator Start () {
    // Start a download of the encrypted assetbundle
    WWW www = new WWW (url);

    // Wait for download to complete
    yield return www;

    // Get the byte data
    byte[] encryptedData = www.bytes;

    // Decrypt the AssetBundle data
    byte[] decryptedData = YourDecryptionMethod(encryptedData);

    // Create an AssetBundle from the bytes array

    AssetBundleCreateRequest acr = AssetBundle.CreateFromMemory(decryptedData);
    yield return acr;

    AssetBundle bundle = acr.assetBundle;

    // You can now use your AssetBundle. The AssetBundle is not cached.
}

第二种方法的好处是可以使用任意方法(除了AssetBundles.LoadFromCacheOrDownload)传输加密的数据,比如socket插件。坏处是它不会自动缓存。除了WebPlayer,你可以手动的存储文件到硬盘然后使用 AssetBundles.CreateFromFile 加载。

第三种方法结合了前两种方法,存储一个AssetBundle为TextAsset,并将其存入另外普通的AssetBundle。这个未加密的AssetBundle保存了一个会被缓存的加密的AssetBundle。原始的AssetBundle然后会被加载进内存,然后使用 AssetBundle.CreateFromMemory 获取并解密。

 
string url = "http://www.mywebsite.com/mygame/assetbundles/assetbundle1.unity3d";
IEnumerator Start () {
    // Start a download of the encrypted assetbundle
    WWW www = new WWW.LoadFromCacheOrDownload (url, 1);

    // Wait for download to complete
    yield return www;

    // Load the TextAsset from the AssetBundle
    TextAsset textAsset = www.assetBundle.Load("EncryptedData", typeof(TextAsset));
 
    // Get the byte data
    byte[] encryptedData = textAsset.bytes;

    // Decrypt the AssetBundle data
    byte[] decryptedData = YourDecryptionMethod(encryptedData);

    // Create an AssetBundle from the bytes array
    AssetBundleCreateRequest acr = AssetBundle.CreateFromMemory(decryptedData);
    yield return acr;

    AssetBundle bundle = acr.assetBundle;

    // You can now use your AssetBundle. The wrapper AssetBundle is cached
}

在AssetBundles中包含脚本

AssetBundle可以将脚本保存为TextAsset,但这样就不是可执行代码了。如果你想包含可执行代码,就需要预编译脚本并使用 Mono 反射类(反射在AOT编译平台无法使用,比如iOS)。

注意:从AssetBundle加载代码在 Windows Store Apps 和 Windows Phone 是不支持的。

 
//c# example
string url = "http://www.mywebsite.com/mygame/assetbundles/assetbundle1.unity3d";
IEnumerator Start () {
    // Start a download of the given URL
    WWW www = WWW.LoadFromCacheOrDownload (url, 1);

    // Wait for download to complete
    yield return www;

    // Load and retrieve the AssetBundle
    AssetBundle bundle = www.assetBundle;

    // Load the TextAsset object
    TextAsset txt = bundle.Load("myBinaryAsText", typeof(TextAsset)) as TextAsset;

    // Load the assembly and get a type (class) from it
    var assembly = System.Reflection.Assembly.Load(txt.bytes);
    var type = assembly.GetType("MyClassDerivedFromMonoBehaviour");

    // Instantiate a GameObject and add a component with the loaded class
    GameObject go = new GameObject();
    go.AddComponent(type);
}

下载 AssetBundles

有两种方法下载:

  1. 非缓存:通过构造一个WWW对象完成。AssetBundles不会缓存到本地存储设备上的Unity缓存文件夹。

  2. 缓存:通过使用WWW.LoadFromCacheOrDownload实现。AssetBundles会缓存到本地存储设备上的Unity缓存文件夹。WebPlayer共享缓存允许50MB大小的缓存。PC/Mac 和 iOS/Android 限制是4GB。

下面是一个非缓存的例子:

 
using System;
using UnityEngine;
using System.Collections; 

class NonCachingLoadExample : MonoBehaviour {
    
    public string BundleURL;
    public string AssetName;

    IEnumerator Start() {
        // Download the file from the URL. It will not be saved in the Cache
        using (WWW www = new WWW(BundleURL)) {
            yield return www;
            
            if (www.error != null)
                throw new Exception("WWW download had an error:" + www.error);
                
            AssetBundle bundle = www.assetBundle;
            if (AssetName == "")
                Instantiate(bundle.mainAsset);
            else
                Instantiate(bundle.LoadAsset(AssetName));
                
            // Unload the AssetBundles compressed contents to conserve memory
            bundle.Unload(false);
        } 
        // memory is freed from the web stream (www.Dispose() gets called implicitly)
    }
}

一个使用 WWW.LoadFromCacheOrDownload 的缓存的例子:

 
using System;
using UnityEngine;
using System.Collections;

public class CachingLoadExample : MonoBehaviour {
    public string BundleURL;
    public string AssetName;
    public int version;

    void Start() {
        StartCoroutine (DownloadAndCache());
    }

    IEnumerator DownloadAndCache (){
        // Wait for the Caching system to be ready
        while (!Caching.ready)
            yield return null;

        // Load the AssetBundle file from Cache if it exists with the same version 
        // or download and store it in the cache
        using(WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)){
            yield return www;
            
            if (www.error != null)
                throw new Exception("WWW download had an error:" + www.error);
                
            AssetBundle bundle = www.assetBundle;
            
            if (AssetName == "")
                Instantiate(bundle.mainAsset);
            else
                Instantiate(bundle.LoadAsset(AssetName));
                
            // Unload the AssetBundles compressed contents to conserve memory
            bundle.Unload(false);

        } // memory is freed from the web stream (www.Dispose() gets called implicitly)
    }
}

当你获取 .assetBundle 资源,下载的数据被提取并且 AssetBundle 对象会被创建。这时,你就可以加载bundle中包含的对象。LoadFromCacheOrDownload 第二个参数指定了要下载的AssetBundle的版本。如果缓存中没有或者版本比需要的低,就会下载AssetBundle。否则就直接从缓存中获取AssetBundle。

Editor环境下加载

Editor下加载AssetBundles比较麻烦,因为有资源更新时,还要重新打包AssetBundles。这时候通过 Resources.LoadAssetAtPath 直接获取Asset比较方便:

 
// C# Example
// Loading an Asset from disk instead of loading from an AssetBundle
// when running in the Editor
using System.Collections;
using UnityEngine;

class LoadAssetFromAssetBundle : MonoBehaviour
{
    public Object Obj;

    public IEnumerator DownloadAssetBundle<T>(string asset, string url, int version) where T : Object {
        Obj = null;

#if UNITY_EDITOR
        Obj = Resources.LoadAssetAtPath("Assets/" + asset, typeof(T));
        yield return null;

#else
        // Wait for the Caching system to be ready
        while (!Caching.ready)
            yield return null;

        // Start the download
        using(WWW www = WWW.LoadFromCacheOrDownload (url, version)){
            yield return www;
            
            if (www.error != null)
                throw new Exception("WWW download:" + www.error);
                
            AssetBundle assetBundle = www.assetBundle;
            
            Obj = assetBundle.LoadAsset(asset, typeof(T));
            
            // Unload the AssetBundles compressed contents to conserve memory
            bundle.Unload(false);

        } // memory is freed from the web stream (www.Dispose() gets called implicitly)

#endif
    }
}

加载和卸载AssetBundle中的对象

当从下载的数据创建了AssetBundle对象后,有三种方法加载AssetBundle中的资源:

  • AssetBundle.LoadAsset,会使用名字作为参数加载物体,还有一个可选的参数指定加载类型。

  • AssetBundle.LoadAssetAsync,效果和上面一样,只是它是异步的,加载时不会阻塞主线程。这对加载大资源或很多资源很有效。

  • AssetBundle.LoadAllAssets,会加载AssetBundle中的所有物体,可以指定对象类型。

要卸载资源,需要使用 AssetBundle.Unload。这个方法有一个布尔参数,以指示是卸载所有数据(包括以加载的数据),还是只卸载下载的压缩文件。如果你正在使用来自AssetBundle的一些资源,而你又想释放一些内存,可以传递参数false来卸载压缩的资源文件。而如果你不再使用AssetBundle的任何资源,可以传递true来彻底卸载已经加载的资源。

异步加载AssetBundle的资源

 
using UnityEngine;

// Note: This example does not check for errors. Please look at the example in the DownloadingAssetBundles section for more information
IEnumerator Start () {
    // Start a download of the given URL
    WWW www = WWW.LoadFromCacheOrDownload (url, 1);

    // Wait for download to complete
    yield return www;

    // Load and retrieve the AssetBundle
    AssetBundle bundle = www.assetBundle;

    // Load the object asynchronously
    AssetBundleRequest request = bundle.LoadAssetAsync ("myObject", typeof(GameObject));

    // Wait for completion
    yield return request;

    // Get the reference to the loaded object
    GameObject obj = request.asset as GameObject;

    // Unload the AssetBundles compressed contents to conserve memory
    bundle.Unload(false);

    // Frees the memory from the web stream
    www.Dispose();
}

记录加载的AssetBundle

对于一个AssetBundle,Unity只允许同一时间加载一个实例。这意味着如果一个AssetBundle已经加载过了,且没有卸载,你就不能再用WWW获取它了。即当你试图使用如下方式获取一个已经加载过的AssetBundle:

1
 AssetBundle bundle = www.assetBundle;

这会报错:Cannot load cached AssetBundle. A file of the same name is already loaded from another AssetBundle。 而且assetBundle会返回null。如果第一个资源还在加载中时,你是不能在第二个下载时获得AssetBundle的,因此你要做的要么就是在不需要时卸载AssetBundle,要么对它保持引用以避免当它已经存在于内存中时还下载它。

要注意再Unity5之前,在要卸载bundles之前,所有bundles的加载都要完成。

一般还是推荐在不需要时卸载AssetBundle,如果你一定要记录下载的AssetBundle,你可以使用一个包装类,如下所示:

 
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;

static public class AssetBundleManager {
    
   // A dictionary to hold the AssetBundle references
   static private Dictionary<string, AssetBundleRef> dictAssetBundleRefs;
   
   static AssetBundleManager (){
       dictAssetBundleRefs = new Dictionary<string, AssetBundleRef>();
   }
   
   // Class with the AssetBundle reference, url and version
   private class AssetBundleRef {
       public AssetBundle assetBundle = null;
       public int version;
       public string url;
       public AssetBundleRef(string strUrlIn, int intVersionIn) {
           url = strUrlIn;
           version = intVersionIn;
       }
   };
   
   // Get an AssetBundle
   public static AssetBundle getAssetBundle (string url, int version){
       string keyName = url + version.ToString();
       AssetBundleRef abRef;
       
       if (dictAssetBundleRefs.TryGetValue(keyName, out abRef))
           return abRef.assetBundle;
       else
           return null;
   }
   
   // Download an AssetBundle
   public static IEnumerator downloadAssetBundle (string url, int version){
       string keyName = url + version.ToString();
       if (dictAssetBundleRefs.ContainsKey(keyName))
           yield return null;
       else {
           using(WWW www = WWW.LoadFromCacheOrDownload (url, version)){
               yield return www;
               if (www.error != null)
                   throw new Exception("WWW download:" + www.error);
               AssetBundleRef abRef = new AssetBundleRef (url, version);
               abRef.assetBundle = www.assetBundle;
               dictAssetBundleRefs.Add (keyName, abRef);
           }
       }
   }
   
   // Unload an AssetBundle
   public static void Unload (string url, int version, bool allObjects){
       string keyName = url + version.ToString();
       AssetBundleRef abRef;
       if (dictAssetBundleRefs.TryGetValue(keyName, out abRef)){
           abRef.assetBundle.Unload (allObjects);
           abRef.assetBundle = null;
           dictAssetBundleRefs.Remove(keyName);
       }
   }
}

使用上面的类:

 
using UnityEditor;

class ManagedAssetBundleExample : MonoBehaviour {
   public string url;
   public int version;
   AssetBundle bundle;
   
   void OnGUI (){
       if (GUILayout.Label ("Download bundle"){
           bundle = AssetBundleManager.getAssetBundle (url, version);
           if(!bundle)
               StartCoroutine (DownloadAB());
       }
   }
   
   IEnumerator DownloadAB (){
       yield return StartCoroutine(AssetBundleManager.downloadAssetBundle (url, version));
       bundle = AssetBundleManager.getAssetBundle (url, version);
   }
   
   void OnDisable (){
       AssetBundleManager.Unload (url, version);
   }
}

要注意AssetBundleManager是静态类,你记录的任何AssetBundles在加载新场景后都不会被销毁。这个类只是个参考,还是推荐一但不使用就马上卸载AssetBundles。

创建及导出 Asset Bundle

设置名称

Unity5的AssetBundle系统和4有些差别,现在创建AssetBundle, 首先,你需要选中资源,然后在inspector面板最下面设置bundle名称,名称可以使用”/”来分层:

之后对于要导出的资源,只需要选择你创建的名称即可。

当然,也可以通过代码设置名称,从而实现自动化工具,只需设置这个属性即可:

1
AssetImporter.assetBundleName

导出AB

导出时,需要在代码操作:

1
2
3
4
5
6
7
8
9
10
using UnityEditor;

public class CreateAssetBundles
{
    [MenuItem ("Assets/Build AssetBundles")]
    static void BuildAllAssetBundles ()
    {
        BuildPipeline.BuildAssetBundles ("Assets/AssetBundles");
    }
}

这会在Asset菜单创建一个按钮,点击后开始创建AssetBundle。

BuildPipeline.BuildAssetBundles函数的原型为:

1
public static AssetBundleManifest BuildAssetBundles(string outputPath, BuildAssetBundleOptions assetBundleOptions = BuildAssetBundleOptions.None, BuildTarget targetPlatform = BuildTarget.WebPlayer);

第一个参数为输出路径,第二个为构建选项,第三个为构建目标平台。

注意,创建之前需要先创建好输出文件夹。

你会发现,创建好之后,每个AssetBundle会对应生成一个.manifest文件,其中包含了校验、依赖文件等信息。
另外还会生成两个文件:AssetBundle 和 AssetBundle.manifest 文件,这会在每个生成Assetbundle的文件夹都自动创建。因此,如果你一直输出到一个文件夹,那就只会创建这两个文件。AssetBundle.manifest 文件会包含一些AssetBundles的信息和依赖关系。

AssetBundle变型

这是Unity5中的新特性,实现类似虚拟资源的效果。

比如你可以实现一个变型 “MyAssets.hd”和“MyAssets.sd”。构建时两个变型AssetBunlde会有相同的内部ID,而在运行时只要通过不同的扩展变型名,就可以任意切换两个资源。

设置方法是,先设置名称,然后在名称旁边的下拉菜单设置变型名。

Asset Bundle 压缩

Unity支持三种压缩方式:LZMA, LZ4, 和不压缩。

LZMA压缩后体积最小,但解压缩需要一定时间因此加载时较慢。LZ4压缩包较大,但解压缩时间消耗很少,另外只在5.3以后版本可以使用。不压缩是占空间最大的,但当然相对的是读取速度最快的。

压缩包的缓存

WWW.LoadFromCacheOrDownload函数会下载并缓存AssetBundle到硬盘,以便之后使用更加快速。从5.3开始,缓存的数据也可以使用LZ4算法被压缩,这会比不压缩节省40%–60%的空间。 随着数据从网络下载,Unity会解压它们并使用LZ4再压缩,压缩过程发生在下载流期间,这意味着一旦获取到足够的数据缓存就会开始压缩,并持续递增直到下载完成。之后,会通过即时解压来从缓存的bundle获取所需的数据。

缓存的压缩是默认开启的,并由Caching.compressionEnabled属性控制。

AssetBundle加载API

下表列出了使用不同压缩类型和加载方法时内存和性能开销的对比。

注意:当使用WWW下载一个bundle,WebRequest还会有一个864KB的缓存区来存储来自socket的数据。

参考上表,可以使用下面的规则来使用加载API:

  1. 部署bundle作为流媒体资源(StreamingAssets):构建时使用 BuildAssetBundleOptions.ChunkBasedCompression,并且加载时使用 AssetBundle.LoadFromFileAsync。这会使你的数据有一定压缩,并且在加载时获得最大可能的加载速度,以及和读取缓存一样的内存开销。

  2. Asset bundles 作为下载内容:使用默认设置(LZMA压缩)构建,并且使用 LoadFromCacheOrDownload/WebRequest 下载并缓存。这样你会有最大压缩率,以及接下来加载时 AssetBundle.LoadFromFile 的加载效率。

  3. 加密过的bundles:选择 BuildAssetBundleOptions.ChunkBasedCompression 并且使用 LoadFromMemoryAsync 来加载。(这几乎是 LoadFromMemory[Async] 唯一的使用情景)。

  4. 自定义压缩:使用 BuildAssetBundleOptions.UncompressedAssetBundle 构建,并在用你自己的解压缩算法解压后,使用 AssetBundle.LoadFromFileAsync 加载。

一般你应该使用异步方法 - 它们不会阻塞主线程并使加载操作更有效率。并且绝对要避免同时使用同步和异步加载方法 - 这会引起主线程停顿。

Asset Bundle 的内部结构

Asset Bundle实质是一些物体组合在一起形成的序列化文件的集合。在部署时根据它是数据bundle还是场景bundle,结构上有一些不同。

普通Asset Bundle结构

Streamed scene Asset Bundle结构

Asset Bundle 的压缩

如上所示,可能会有块压缩或流压缩两种方式。块压缩(LZ4)意味着原始数据被分成同样大小的块而这些块被分别压缩。如果你需要实时解压,应该使用这种方法 - 随机读取的消耗很小。而流压缩(LZMA)使用同样的字典处理整个数据块,它会提供最高的压缩率但是只支持顺序的读取。

这篇我们学习如何通过感知系统实现AI行为。一个AI的质量取决于它能从周围环境获取的信息,并根据这个信息决定什么逻辑被执行。

基本感知系统

基本感知系统包括两个方面:目标(Aspect)和感觉(Sense)。AI角色会有包括视觉、味觉、触觉等感觉。这些感觉会针对特定目标来检测。比如创建一个守卫巡逻AI,拥有视觉来查找作为目标的敌人。也可以拥有触觉,当与敌人靠的很近时,就能觉察到。

检测区域

对于视线,比好常用的检测区域是锥形。如下图所示。根据游戏类型,锥形可以是2D或3D的。

如图示,锥形区域是视线的检测区域。就像颜色逐渐变淡,随着距离边远,检测精度逐渐降低。 实现这样的锥形视线,既可以是简单的重叠检测,也可以是复杂的真实模拟。简单的方法,就是检测物体是否和锥形重合,忽略距离或边缘。而对于复杂的模拟实现,随着锥形扩散距离边缘,远处发现敌人的几率将比近处的低。

而对于听觉、触觉和嗅觉,一个非常简单有效的检测区域是使用圆(球)形检测。

通过全知来扩展AI

全知可以使你的AI变得机智,当然,并不是说它需要知道所有事,而是说它可以知道所有事。某些时候这与现实不符,但通常最简单的方法就是最好的方法。允许AI获取隐藏的信息,可以增加游戏的复杂度。 游戏中,我们会通过具体的数值模拟虚拟的概念。比如血量、魔法之类的。让AI能获取这类信息,可以使它们做出真实的抉择,即使某些时候它们没权限获取这种信息。

例子演示

我们实现一个简单的例子来演示一下。主角为坦克,可以移动到玩家点击的位置。敌人为AI,在地图上巡逻。敌人的感觉有视觉和触觉。即它可以发现前方视线范围内的主角,或是主角触碰到它时也能感觉到。 实现视线检测,只要通过方向、距离和射线,检测目标是否在范围内。碰触检测,直接使用碰撞块进行碰撞检测。