google-code-export / colonist

Automatically exported from code.google.com/p/colonist
0 stars 1 forks source link

New AI framework bugfix & Enhancement #19

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
所有新AI框架的ISSUE都请在这里发布和解决.

Original issue reported on code.google.com by yinyuanq...@gmail.com on 4 Jan 2013 at 8:39

GoogleCodeExporter commented 9 years ago
已知问题:
1. HoldPosition还没有实现
2. AttackToPosition / AttackAtDirection 还没有实现
3. AttackData.ScriptObjectAttachToTarget 
还没有使用(因为还没有看见有这个需求)

Original comment by yinyuanq...@gmail.com on 4 Jan 2013 at 11:38

GoogleCodeExporter commented 9 years ago
AttackData.ScriptObjectAttachToTarget其实本来有用,就是那个减速枪
我已经改写成一个Component了,不过这个只能针对predator
如果以后把predator和AI都改成基于同一个底层,那就可以用在两
者身上了

另外,相对的,应该考虑加入ScriptObjectAttachToSource这样的东西

Original comment by lightnin...@gmail.com on 6 Jan 2013 at 2:43

GoogleCodeExporter commented 9 years ago
关于AI和Unit的关系:

现在的Unit不是一个Monobehavior,只是一个普通的类,并且是AI的一
个成员.相当于是一个纯粹的数据封装.

这样有一个问题,就是无法使用 
GetComponent<Unit>去获取单位,而只能通过GetComponent<AI>().Unit获取Un
it,于是Unit只对AI有意义,对玩家单位而言没有意义.

所以带来一个改动:
Unit将是一个独立的Monobehavior而不只是AI的一个成员. 
这样做对于Decal system也有意义,decal 
system可以直接通过GameObjecr的Unit组件去获知这个单位的属性.

Original comment by yinyuanq...@gmail.com on 8 Jan 2013 at 5:59

GoogleCodeExporter commented 9 years ago
把Unit做成一个独立的Monobehavior也可以同时解决上一帖中Jeffery
提到的问题 - 用一个Unit类去统一AI和玩家单位的接口.

Original comment by yinyuanq...@gmail.com on 8 Jan 2013 at 6:01

GoogleCodeExporter commented 9 years ago
SVN改动已提交

UnitData.cs - 原来的Unit.cs中所有的类除了Unit都放到这个文件中
AISystem/Common/Unit.cs - 独立定义Unit (monobehavior)

Original comment by yinyuanq...@gmail.com on 8 Jan 2013 at 6:08

GoogleCodeExporter commented 9 years ago
可能不需要这么麻烦,因为这些减速之类的效果,影响的只是最
终结果,而不是原始的MoveData

PS:
ReceiveDamage部分,玩家单位已经在用unithealth,没什么问题
但是攻击(造成伤害)部分,目前就没有什么办法修改这个参数

Original comment by lightnin...@gmail.com on 8 Jan 2013 at 6:38

GoogleCodeExporter commented 9 years ago
AttackData.ScriptObjectAttachToTarget不应该是MonoBehaviour类型
给一个GameObject增加一个组件的办法似乎只有用AddComponent,只需
要class name 
或者直接强类型添加一个,好像没办法添加一个已经实例化的M
onoBehaviour

(其实我之前写Weapon的时候也想把MonoBehaviour做成一个字段什么
的,不过没有达到想要的效果)不知道Editor有没有一些特殊的功
能...

既然写成了MonoBehaviour类型,我就暂时用这个做了一个减速和持
续伤害的效果的script
(我这个还是不导出unitypackage好了..附件里面包含场景文件和��
�个效果的script(放到weapon文件夹里面把),以及测试用的AttackData
.ScriptObjectAttachToTarget功能)

Original comment by lightnin...@gmail.com on 8 Jan 2013 at 12:04

Attachments:

GoogleCodeExporter commented 9 years ago
- AttackData.ScriptObjectAttachToTarget不应该是MonoBehaviour类型

呵呵,我想的恰恰相反,ScriptObjectAttachToTarget 
应该是一个专门用于操作Unit属性的MonoBehavior.

做了这一段时间的Unity的开发, 
感觉Unity编程最重要的,是数据封装,或者说数据结构定义.这里
的本质就是,你做为一个开发者,你怎样解析,你要实现的功能. 
由于Unity编程需要考虑动画,时间,事件等等许多因素, 
所以数据结构定义得好,你的代码就越灵活,能兼容的功能就越
多,拓展起来就更容易.

回到这个case上来,为什么ScriptObjectAttachToTarget要定义成一个Mono
behavior? 因为这样做最能和当前"Unit掌管数据, 
AI掌管行为"的结构结合起来. 
Unit掌管数据,所以,当我要改动一个单位的属性,例如,减速的时
候,我就只需要在Unit类上做改动,而不用去管AI会怎么工作. 
这也是为什么我要把Unit从AI独立出来. 
那么,当设计一个减速效果的ScriptObjectAttachToTarget类时,这个类�
��做的事情,就非常简单了,只需要把Unit的MoveData减低,然后再慢
慢提升. 这里还要做一个小改动, 
Unit要加入一个SpeedModifier,MoveData计算速度的时候,MoveData.Speed * 
SpeedModifier. 
这样,减速的脚本只需要修改这个SpeedModifier就可以了. 

Original comment by yinyuanq...@gmail.com on 8 Jan 2013 at 1:58

GoogleCodeExporter commented 9 years ago
可能你没看完我说的
你想做的和我想做其实是一样的

都是想用一个MonoBehavior或者说一个script去操作这个单位
但是要知道,这在Inspector上赋值的是一个已经实例化的脚本,没
有办法把这个放到其他对象里面去,如果要用MonoBehavior类型,那
么AddComponent之后就必须要重新把加在那个游戏对象的组件上,�
��新赋值

这和直接把脚本拖到游戏对象上是有不同的..你试试就知道了

Original comment by lightnin...@gmail.com on 8 Jan 2013 at 2:05

GoogleCodeExporter commented 9 years ago
这个问题先暂时hardcode 一个固定的值在脚本中吧, 
以后等开始难度级别设计的时候再来考虑这个问题. 
用PlayerPrefs 类应该可以解决这个问题:

If LevelManager.difficulty == Easy
  PlayerPrefs.SetFloat("DecelerateFactor", 1.0f);
If LevelManager.difficulty == Middle
  PlayerPrefs.SetFloat("DecelerateFactor", 1.5f);

减速脚本里面使用这个 PlayerPrefs.getfloat("DecelerateFactor")

用全局变量统一这些运行时添加的脚本的参数.

Original comment by yinyuanq...@gmail.com on 8 Jan 2013 at 2:21

GoogleCodeExporter commented 9 years ago
bugs
1.BiochemicalTrooper的Fire Behavuer结束条件,应该是always 
true,否则目标一旦进入dectective range就永远无法执行其他行为
2.AIValueComparisionCondition.BehaviorLastExecutionInterval这个condition应该
是ShouldCompare=true的,但是没有设置.而且执行次数为0的时候这��
�条件应该直接就是true

Original comment by lightnin...@gmail.com on 14 Jan 2013 at 4:17

GoogleCodeExporter commented 9 years ago
另外,只能2个条件的组合,可能有点少(目前暂时够用)

我加了一个晕眩(无伤害)的攻击给AI

Original comment by lightnin...@gmail.com on 14 Jan 2013 at 4:40

Attachments:

GoogleCodeExporter commented 9 years ago
https://docs.google.com/file/d/0B6yPKfIXOM7PZmVaNHlBUXpBLTQ/edit
apk已经传至google drive

Original comment by lightnin...@gmail.com on 14 Jan 2013 at 4:49

GoogleCodeExporter commented 9 years ago
对AI框架又做了一些调整,Jeffery有时间UPDATE一下代码, 
有些结构变了:

1. AI类不再继承于UnitHealth类.
2. 新增UnitBase类,UnitBase继承UnitHealth类.
类结构现在如下:

1. UnitHealth 
提供(而不是封装)最基本的生命值获取方法.UnitHealth的本质就�
��一个接口,只不过U3D中无法通过GetComponent()获取结构,所以才��
�UnitHealth 定义成MonoBehavior的子类.

2. UnitBase 
封装最基本生命值和一些LayerMask属性.以及新加入的两个属性:

public float SpeedModifier = 1; //减速器的那个通用属性
//盔甲类型
public ArmorType Armor = ArmorType.NoArmor_Human;

盔甲类型,这是从魔兽争霸III单位编辑器抄过来的一个设定. 
暂时想到的就是用来约束声音. 比如说, ArmorType.NoArmor_Human 
这个值的单位,在收到 DamageForm = 
Predator_Single_Claw的伤害的时候, 
GlobalAudioManager就会根据这个盔甲类和伤害类去检索对应的声��
� 然后播放.

3. 然后, Unit 继承与UnitBase类, 
Predator3rdPersonalUnit也是继承于unitbase类,这样就把玩家单位和NPC
单位统一起来了. 它们都衍生于UnitBase类, 
但是有各自的数据封装方法(MoveData, AttackData..等等)

Original comment by yinyuanq...@gmail.com on 14 Jan 2013 at 6:27

GoogleCodeExporter commented 9 years ago
Comment 11 by project member lightning.X.revenant, Today (2 hours ago)

bugs
1.BiochemicalTrooper的Fire Behavuer结束条件,应该是always 
true,否则目标一旦进入dectective range就永远无法执行其他行为
2.AIValueComparisionCondition.BehaviorLastExecutionInterval这个condition应该
是ShouldCompare=true的,但是没有设置.而且执行次数为0的时候这��
�条件应该直接就是true

Delete comment
Comment 12 by project member lightning.X.revenant, Today (106 minutes ago)

另外,只能2个条件的组合,可能有点少(目前暂时够用)

我加了一个晕眩(无伤害)的攻击给AI

这些BUG将来会慢慢解决. 
只有2个组合条件的原因是,这个框架只应用于基础的AI,而对更
加丰富的组合的情况, 需要: 自定义一个 AIEliteSoldier类, 
然后这个AIEliteSoldier有新的ConditionData,例如EliteCOnditionData,这个
EliteCOnditionData提供了更多的组合条件, 
然后AIEliteSoldier则基于这个条件编码.

Original comment by yinyuanq...@gmail.com on 14 Jan 2013 at 6:31

GoogleCodeExporter commented 9 years ago
bugs:
projectile的判定有问题,只要撞上任何一个物体,就能对指定目��
�造成伤害

我刚好把那个晕眩改成projectile形式的攻击
我已经fix了,顺便加入了一个callback,当projectile击中以后会往Src
.SendMessage(附带参数:击中的目标)

apk is uploading

Original comment by lightnin...@gmail.com on 14 Jan 2013 at 1:25

Attachments:

GoogleCodeExporter commented 9 years ago
1. 我觉得调用 Stun 的过程过于迂回了, Projectile命中 -> 
通过设置 public string[] MessagesOnHit; 
再调用Stun类的OnProjectileHit消息 . 这样,就需要: 1. 
要在Inspector中配置MessagesOnHit, 
2.要在Stun中添加一个OnProjectileHit(IEnumerable<GameObject> 
targets)方法. 

这个调用链条的点太多了. 为何不改成这样:

在Projectile中加入一个:

public MonoBehavior[] debuff; 在Inspector中指定Stun脚本

然后在DoDamage()中,直接AddComponent<debuff>

Stun则是:

void Start()
{
AI = GetComponent<AI>();
//do something
OnProjectileHit();
}

2. 另外, 
bugs:
projectile的判定有问题,只要撞上任何一个物体,就能对指定目��
�造成伤害

这里,Projectile的判定是有BUG,但是你找到的这个应该不是BUG. 
因为不是对指定目标造成伤害,而是对被撞上,同时又处于Attack
able层的物体造成伤害.
原来的代码是:
    void OnTriggerEnter(Collider other)
    {
        HitSomething = true;
        if (Util.CheckLayerWithinMask(other.gameObject.layer, this.AttackableLayer))
        {
            Target = other.gameObject;//如果撞上某物,切该物属于可攻击层,则选为目标
        }

   public virtual void DoDamage()
    {
        switch (this.AttackType)
        {
            case ProjectileAttackType.SingleTarget:
                if (Target != null)//如果Target非空则发送HIT MESSAGE
                {
                    Target.SendMessage("ApplyDamage", DamageParameter);
                }

这一段其实是没有问题的. 这样想: 
我希望机枪里打出的子弹, 既可以伤害玩家,也可以伤害人类, 
所以,每颗子弹只需要把它的attackable layer设为human + 
xenz就可以了,于是它打出时的Target设为Predator,但是真正的Target
是哪个,则是在OnTriggerEnter()方法里判定.按照你的改动, 
只有HitObject == Target才发送HitMessage, 
那么这个子弹就打不到所有非Target对象了:
                if (HitObject == Target)
                {
                    Target.SendMessage("ApplyDamage", DamageParameter);
                    targets.Add(Target);
                }

真正的BUG应该是: 
对于直线飞行的Projectile,当前的脚本没有考虑到射弹永远也无
法命中Target的情况. 
例如,一颗子弹直线飞行,但是被玩家躲过了, 
这样当过了数秒后,Launch脚本就会执行到DoDamage()方法,但是, 
这个时候Target依然不为空(还是发射时指定的Target) 
,于是,玩家照样中枪.... 
所以,需要在脚本考虑怎样加入这一段逻辑.

Original comment by yinyuanq...@gmail.com on 14 Jan 2013 at 2:23

GoogleCodeExporter commented 9 years ago
刚才又看了一遍, 发现之前看少了一行:

        HitObject = other.gameObject;

楼上问题#2 不成立. 提交的代码在目标命中修复上没有问题.

Original comment by yinyuanq...@gmail.com on 14 Jan 2013 at 2:52

GoogleCodeExporter commented 9 years ago
1.这个问题其实就是ScriptObjectAttachToTarget的问题...(根本就没有
做这部分嘛)

2.
不如用一个实际例子说吧,我都测试了这么多遍了
不需要撞到其他东西,撞墙就好了

当物体撞到bridge
HitSomething = true;//projectile停止移动,然后DoDamage
        if (Util.CheckLayerWithinMask(other.gameObject.layer, this.AttackableLayer))
        {
            Target = other.gameObject;//如果撞上某物,切该物属于可攻击层,则选为目标
        }
//这个部分,条件不成立,所以不执行,target还是那个target,没有��
�化(PS:Target在AI.CreateProjectile方法里面被赋值为CurrentTarget.gameOb
ject)

然后跳到DoDamage()
因为
 if (Target != null)//如果Target非空则发送HIT MESSAGE
这一个条件只是判断目标是不是存在,所以不管碰到是什么,照
样给原定的Target造成伤害
实际上嘛...一定成立的....除非 CurrentTarget.gameObject被destroy

我不止在if (HitObject == Target)动了手脚
我也在OnTriggerEnter那边加了一句, HitObject = 
other.gameObject;(不然怎么来的HitObject啊!)

如果打到的是NPC,如果NPC也在AttackableLayer里面,那么就会先Target
 = other.gameObject
然后HitObject = other.gameObject;
再去做if (HitObject == Target)判断(这肯定是成立的)

其实你想要的"躺着也中枪"的效果依然存在

Original comment by lightnin...@gmail.com on 14 Jan 2013 at 2:55

GoogleCodeExporter commented 9 years ago
ScriptObjectAttachToTarget 
改成string或者TextAsset或者System.Type就可以了

然后AIEditor做相应的修改
在EditorCommon.cs的EditAttackData方法接近最后的地方

1.改成Type类型的话
Editor相应代码改成
            MonoScript scriptValue = null;
            if (AttackData.ScriptObjectAttachToTarget != null)
                scriptValue=MonoScript.FromScriptableObject(ScriptableObject.CreateInstance(AttackData.ScriptObjectAttachToTarget));

            var field = EditorGUILayout.ObjectField(new GUIContent("Script attach to FetchDamageSource:", "造成伤害时,自动附加该脚本组件."), scriptValue, typeof(MonoScript), false);
            AttackData.ScriptObjectAttachToTarget = field == null ? null : ((MonoScript)field).GetClass();

2.改成string

            MonoScript scriptValue = null;
            if (AttackData.ScriptObjectAttachToTarget != null)
                scriptValue=MonoScript.FromScriptableObject(ScriptableObject.CreateInstance(AttackData.ScriptObjectAttachToTarget));

            var field = EditorGUILayout.ObjectField(new GUIContent("Script attach to FetchDamageSource:", "造成伤害时,自动附加该脚本组件."), scriptValue, typeof(MonoScript), false);
            AttackData.ScriptObjectAttachToTarget = field == null ? null : ((MonoScript)field).name;

3.改成TextAsset的话...似乎会遇到一点问题,unity好像把TextAsset看
成是string.
不怕麻烦的话还是可以和上面一样的改

以上几种方法都可以直接AddComponent了,Editor上也可以正确的选�
��本
唯一的问题就是没办法设定脚本的值(还是之前说那个PlayerPref
s先把)

Original comment by lightnin...@gmail.com on 16 Jan 2013 at 8:52

GoogleCodeExporter commented 9 years ago

AI.cs1065行一处BUG:

原:
                    foreach (Collider c in Physics.OverlapSphere(transform.position, attackData.HitTestDistance, Unit.EnemyLayer))
                    {
                        StartCoroutine(SendHitmessage(CurrentTarget.gameObject, attackData));
                    }

中间那句改成StartCoroutine(SendHitmessage(c.gameObject, attackData));

Original comment by lightnin...@gmail.com on 25 Jan 2013 at 2:24

GoogleCodeExporter commented 9 years ago
Util.GetTopestParent不必这么麻烦,直接return transform.root;
http://docs.unity3d.com/Documentation/ScriptReference/Transform-root.html

Original comment by lightnin...@gmail.com on 27 Jan 2013 at 7:40

GoogleCodeExporter commented 9 years ago
AttackData.ScriptObjectAttachToTarget可以考虑修改成AttackData.ObjectAttac
hToTarget

一方面可以脚本也可以配置
还能附加一些特效

Original comment by lightnin...@gmail.com on 27 Jan 2013 at 7:49

GoogleCodeExporter commented 9 years ago
AI framework改动:

I_AIBehaviorHandler.cs  
Projectile.cs  
AI.cs  

基础AI接口方法改动:
基础接口删除 AlterBehavior方法,增加 StartAI/StopAI方法. 
Projectile.cs 命中BUG改动,见之前Jeffery的COMMENT #16.

Original comment by yinyuanq...@gmail.com on 1 Feb 2013 at 8:30

GoogleCodeExporter commented 9 years ago
对AI 框架做出一些改动, 已提交SVN:

1.Editor改动:
a.Assets\Scripts\AISystem\Editor\AIEditor.cs和Assets\Scripts\Editor\EditorCommo
n.cs这两个类装载编辑AI的公用方法.

b.Editor界面则分离到 Assets\Scripts\AISystem\Editor\AIEditorWindow.cs 和 
AIInspector.cs 中.

2. AI.cs改动:
a. A*Pathfind相关函数改动. 
只在当前对象不可见的情况下使用A*Pathfind 系统.
b. IdleData加入几个参数, 
是否在Idle的时候保持面朝向当前目标(CurrentTarget)
c. AIBooleanConditionEnum - Boolean判断条件类,加入 LatestBehaviorNameIs 
和 LastestBehaviorNameIsOneOf 
两种,如果当前正在执行的行为名字叫XXX或者是 
{XXX,YYY,ZZZ}之一的时候, 执行判断.

另外,即将进行的下一个改动,是让AI可以并行工作.例如,一个Ga
meObject上同时有三个AI组件,一个组件负责移动,另一个组件负��
�攻击, 第三个组件负责攻击间隔期间的摇摆行为. 
这就需要一个类似AIManager的类,负责在三个AI组件之间进行调��
�.

Original comment by yinyuanq...@gmail.com on 9 Feb 2013 at 4:50

GoogleCodeExporter commented 9 years ago
AI 框架改动, 已提交SVN:

主要改动: 
1. 
对条件(ConditionData)系列的改动。现在支持多个条件组合了。
原本的ConditionData改名为AtomConditionData, 即原子条件。
增加CompositeConditionData, 复合条件, 
复合条件可以引用其它的复合条件或者原子条件,用逻辑运��
�符 AND , OR组合,例如:

预定义三个原子条件:
原子条件ID 001 为: Enemy In Offensive Range = True,
原子条件ID 002 为: Enemy In Detective Range = False,
原子条件ID 003 为: Random Value <= 50

然后预定义两个复合条件:
Composite Condition ID001 = 001 : <Atom Condition 002> AND Atom <Condition 003>
Composite Condition ID002 = AtomCondition 001 OR CompositeConditionID 001

这样,复合条件CompositeCondition ID 002的表达式就是: 
(Enemy In Offensive Range = True) OR (Enemy In Detective Range = False AND 
Random Value <= 50)
具体定义见 CompositeConditionWrapper , CompositeCondition, AtomCondition.
旧的 AIBehaviorCondition 类 已经弃用。
在 Editor中添加一个专门编辑条件的窗口类 
ConditionEditorWindow,在编辑AI的时候(AIEditor窗口或者Inspector), 
编辑AIBehavior -> 点击 Edit Start Condition /Edit end condition 
编辑条件。

2. 把 A* navigation 的功能从AI类移除,放到 AStarNavigator 类中。 
这样做主要是为了多个AI组件并存。

3. ApplyDamage移除到AIApplyDamage中, 
原因同样是为了多个AI组件并存,

4. 一些小的BUG FIX。

5. 引入 AIController 类(功能还未实现), 
用于在数个AI之间调度AI的开/关。

6. 引入AIEventListener 
类,用于处理各种事件,例如,动画事件。

Original comment by yinyuanq...@gmail.com on 4 Mar 2013 at 2:28

GoogleCodeExporter commented 9 years ago
最新的 AI.cs.....不会玩捉迷藏的士兵不是好AI

NavigateToTransform的结束条件与Start_Attack方法中执行攻击的条件�
��一致
中间隔着那些Obstacle层的东西就站着不动了

解决办法:
增加一个NavigateToTransform重载方法
    public virtual IEnumerator NavigateToTransform(Transform TargetTransform,
                                       MoveData moveData,
                                       System.Predicate<Transform> BreakCondition)
{
...

        while (TargetTransform != null &&
           !BreakCondition(TargetTransform))
...
}

相应的Start_Attack方法的那部分改成
                yield return StartCoroutine(NavigateToTransform(CurrentTarget,
                    moveData, tar => this.CanSeeCurrentTarget && CurrentTargetDistance <= attackData.AttackableRange));

Original comment by lightnin...@gmail.com on 5 Apr 2013 at 10:36

GoogleCodeExporter commented 9 years ago
#27 的楼指出的BUG已修正。

Original comment by yinyuanq...@gmail.com on 19 Apr 2013 at 7:45

GoogleCodeExporter commented 9 years ago
Projectile命中判断BUG:
对于身前具有"挡箭牌"的BOT,无法正常造成伤害
如Defenser-I,它的两个TriggerCollider都会有这种作用

修改:
OnTriggerStay与OnTriggerEnter增加对无效图层的判断
(在上面那个例子里面,无效图层是Ignore Raycast)

PS:可以考虑直接归类到LevelManager

Original comment by lightnin...@gmail.com on 18 May 2013 at 6:22

GoogleCodeExporter commented 9 years ago
To #29 

这种情况,我认为合理的解决方案是在ProjectSetting -> 
Physics里面,让 Collider 不与Projectile做碰撞校验. 问题的本质是, 
挡箭牌Collider放错了层.

Original comment by yinyuanq...@gmail.com on 20 May 2013 at 3:00

GoogleCodeExporter commented 9 years ago
Reply #30
对喔,还有ProjectSetting -> Physics这个东西
我这的TriggerCollider在Ignore Raycast层,感觉好像没什么问题!?

Original comment by lightnin...@gmail.com on 20 May 2013 at 3:14

GoogleCodeExporter commented 9 years ago
UnitData的Clone改进:
看代码上面的GetClone方法,做的基本都是浅复制,所以不需要每�
��字段/属性都自己复制一次
.net框架有提供一个MemberwiseClone()的方法

另外我看数组也有一个专门的Util.CloneArray方法
实际上Array有实现ICloneable接口,而且实现的是也是和Util.CloneArr
ay一样的浅复制,所以也不用自己增加工作量了

另外建议这些Data直接实现System.ICloneable接口吧

Original comment by lightnin...@gmail.com on 21 May 2013 at 3:11

GoogleCodeExporter commented 9 years ago
bugs:
隐身之后AI不会丢失目标
AI找不到目标就原地站着不动(暂时加一个原地左右观望的动��
�和behavior好了)

Original comment by lightnin...@gmail.com on 4 Jun 2013 at 5:10

GoogleCodeExporter commented 9 years ago
bugs:
AI.cs的Start_MoveAtDirection方法内第一句
Vector3 direction = behavior.IsWorldDirection ? behavior.MoveDirection : 
transform.InverseTransformDirection(behavior.MoveDirection);

按代码看,这个direction应该是世界坐标下面的方向
而Transform.InverseTransformDirection这个方法的作用是Transforms a 
direction from world space to local space. The opposite of 
Transform.TransformDirection(http://docs.unity3d.com/Documentation/ScriptRefer
ence/Transform.InverseTransformDirection.html)
显然搞错了。。。。

Original comment by lightnin...@gmail.com on 4 Jun 2013 at 6:27

GoogleCodeExporter commented 9 years ago
AI.cs
Start_SwitchToAI方法最后
还要再加一句
nextAI.StartAI();

否则不会执行新的AI

Original comment by lightnin...@gmail.com on 14 Jun 2013 at 3:19