这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录前一修订版后一修订版 | 前一修订版 | ||
scripting:extend:character_ai [2023/09/15 09:37] – bibiboxs | scripting:extend:character_ai [2024/04/05 16:11] (当前版本) – bibiboxs | ||
---|---|---|---|
行 4: | 行 4: | ||
**角色作为游戏世界内的关键交互,AI功能(移动、跟随、寻路等)是必不可少的,在引擎中默认提供了几种方式使用。** | **角色作为游戏世界内的关键交互,AI功能(移动、跟随、寻路等)是必不可少的,在引擎中默认提供了几种方式使用。** | ||
- | 沙盘引擎默认**只提供AI操控功能**,不提供具体的AI逻辑实现,开发者应根据自身项目需求编写AI逻辑。 | + | 沙盘引擎默认**只提供基础AI操控功能**,关于具体的世界内AI逻辑,开发者应根据自身项目需求编写。 |
行 12: | 行 12: | ||
这里仅提供编写AI时可能用到的关键代码指南,具体API使用方法请参考[《脚本开发及API》](scripting)及下方【AI逻辑关系】解释。 | 这里仅提供编写AI时可能用到的关键代码指南,具体API使用方法请参考[《脚本开发及API》](scripting)及下方【AI逻辑关系】解释。 | ||
- | - `character.Team` 指定角色Team阵营(用于分辨敌我阵营) | + | - `character.Group` 指定角色阵营(用于分辨敌我阵营) |
- `character.AIDistance` 指定逻辑检测距离 | - `character.AIDistance` 指定逻辑检测距离 | ||
- | - `character.AITarget` 读\写一个`Entity`类型的对象(Character\Vehicle\Model) | + | - `character.AITarget` 读\写一个`Entity`类型的跟随对象(`Character\Vehicle\Model`) |
- | - `character.AIState` 指定一个AI状态(和平、被动、中立、主动) | + | - `character.AITargetPos` 读\写一个`Vector`类型的跟随坐标(与`character.AITarget`二选一) |
- | - `character.AIMove`() 让角色移动到指定坐标位置 | + | - `character.AIState` |
- | - `Distance()` 测量两个坐标的距离 | + | - `character.AIForceMode`设置角色AI是否为专注模式(即设定`Target`后不会被自动重置或改变,对象销毁除外) |
- | - `Rayline()` | + | |
- …… | - …… | ||
行 25: | 行 24: | ||
## 编写逻辑示例 | ## 编写逻辑示例 | ||
- | 在实际开发过程中,大多数AI逻辑并非想象中那么复杂,更多只需要`计时器事件+循环检测逻辑`就可以实现多数需求。 | + | 在《沙盘引擎》的AI开发过程中,AI部分逻辑我们将其进行了简化处理,大多数AI逻辑并非想象中那么复杂,通常只需要**【计时器事件+循环检测逻辑】**就可以实现多数相关需求。 |
- | 比如可以新建一个计时器,每1秒循环执行一次(根据需求自行调整间隔时间,时间间隔越小,理论上越灵敏),在计时器事件内编写循环遍历代码,根据需求+上方AI关键代码组合,就可以实现许多AI逻辑。 | + | > 例如:新建一个计时器,每1秒循环执行一次(根据需求自行调整间隔时间,时间间隔越小,理论上越灵敏),在计时器事件内编写循环遍历代码,根据需求+上方AI关键代码组合,就可以实现许多AI逻辑。 |
+ | > | ||
+ | > **注意:AI逻辑编写时需要相对严谨一些,提前预想到不同情况下的处理预判。** | ||
```javascript | ```javascript | ||
- | //示例代码仅供逻辑参考,不保证实时开箱即用性 | + | //Test Code |
var npcs = []; | var npcs = []; | ||
//Create NPCs | //Create NPCs | ||
- | for(let i=0; | + | function OnPlayerComplete( player |
{ | { | ||
- | let veh = Character.Create(0, | + | |
- | npcs.push(veh); | + | player.SetEntity(chara); |
+ | |||
+ | for(let i=0; | ||
+ | { | ||
+ | | ||
+ | npcs.push(veh); | ||
+ | } | ||
} | } | ||
+ | | ||
//Timer AIEvent | //Timer AIEvent | ||
Timer.Create(() => { | Timer.Create(() => { | ||
行 49: | 行 56: | ||
npc.AIState = 2; // | npc.AIState = 2; // | ||
}else{ | }else{ | ||
- | npc.AIState = 1; // | + | npc.AIState = 1; // |
} | } | ||
| | ||
行 57: | 行 64: | ||
{ | { | ||
let plr = Player.Find(j); | let plr = Player.Find(j); | ||
- | if(plr.Entity != null && plr.Entity.Health > 0) | + | if(plr.Entity != null && plr.Entity.IsSpawned) |
{ | { | ||
- | npc.AIMove(plr.Pos); | + | npc.AITarget = plr.Entity; |
break; | break; | ||
} | } | ||
行 70: | 行 77: | ||
## 玩家同步流限制 | ## 玩家同步流限制 | ||
- | > ==此部分内容可能是过时的,目前仅供参考。== | + | AI寻路逻辑是一个性能昂贵的操作,所以在多数情况下需要基于**玩家+网络同步流(Stream)**概念。 |
- | 沙盘引擎采用了【网络+单机】互相兼容的同步逻辑,所以无论在多数情况下仍然有**网络同步流(Stream)**的概念。 | + | > 网络同步流,可以理解为每个真实玩家(并非角色)的实时观察位置,许多网络同步\AI寻路相关的操作,只有在以此位置为中心的**规范距离内**才得以生效,否则视为**“离开此玩家的实际控制\同步范围”**,部分操作不予生效。 |
- | + | ||
- | > 网络同步流,可以理解为以每个玩家为中心点,实时更新有效的网络同步范围(比如32x32),只有在这个**合法距离内**部分操作才得以生效,否则视为“离开此玩家控制展示范围”,部分操作不予生效。 | + | |
> | > | ||
- | > 例如:在《我的世界》中也存在类似的概念,只有玩家在指定(默认10)范围内,附近的僵尸、作物、生物才会进行生长和移动及计算,超过此距离将不再进行同步。 | + | > *例如:在《我的世界》中也存在类似的概念,只有玩家在指定范围内,附近的僵尸、作物、生物才会进行生长、移动和实时计算,超过此距离将不再进行同步。* |
- | + | ||
- | 考虑到此概念,开发者在编写、使用AI系统时,应该根据实际应用情况注意同步流范围的问题。 | + | |
- | + | ||
- | 比如有一个特大的城市地图,让`NPC-A`从城南寻路到城北,这将被弹出警告并忽略,因为这明显超出了玩家流、CPU合理计算范围,正确的做法应该是优先引用离玩家更近的NPC,或者使用强制位移同步参数(详情见`character.MoveTo()`方法解释)。 | + | |
- | > 强制位移:实际上是将Character强制移动到能够AI寻路范围的**最边缘位置**,然后开始正常寻路移动。 | + | 在实际AI操作过程中,比如部分大的城市地图,让`NPC-A`从城南寻路到城北,这期间可能有非常复杂的路程情况及干扰,这将被弹出警告并忽略寻路请求,因为这明显超出了**玩家流、CPU合理计算范围**,正确的做法应该是优先引用离玩家更近的NPC,**或提前设置好`AIState`,利用此机制等待玩家靠近自动执行相关逻辑**。 |
行 88: | 行 89: | ||
## AI逻辑关系 | ## AI逻辑关系 | ||
- | 沙盘引擎为AI逻辑提供了一些通用的控制代码,无论是简单还是复杂的AI整体逻辑,都应该通过这些基础AI代码来实现。 | + | 《沙盘引擎》为AI逻辑提供了一些通用的代码,**无论是简单或复杂的AI整体逻辑,都应该通过这些基础代码来实现**。 |
AI代码根据情况不同,应该使用不同的API进行调用,且不同代码执行后可能会有一些关系影响,下方将做一些示例规范及解释。 | AI代码根据情况不同,应该使用不同的API进行调用,且不同代码执行后可能会有一些关系影响,下方将做一些示例规范及解释。 | ||
行 98: | 行 99: | ||
如果`AITarget`功能不能满足需求,或者有针对自定义坐标的AI需求,可以使用`AITargetPos`进行针对坐标的AI指令。 | 如果`AITarget`功能不能满足需求,或者有针对自定义坐标的AI需求,可以使用`AITargetPos`进行针对坐标的AI指令。 | ||
- | > 值得注意的是,无论是`AITarget`还是`AITargetPos`方法,两者在根本上都属于【设定寻路目标】的功能作用,真正起到AI行为逻辑的是`AIState`属性。 | + | > 值得注意的是,无论是`AITarget`还是`AITargetPos`方法,两者在根本上都属于【设定寻路目标】的功能作用,**真正起到AI行为逻辑的是`AIState`属性**。 |
> | > | ||
- | > 无论使用以上两种哪个方法(实体或Vector),AI逻辑上都认定它们是一个【目标】,例如`AIState==1`、`AITargetPos`设置坐标的情况下,NPC会尝试逃离反向逃离这个目标(坐标点)。 | + | > 无论使用以上哪种方法*(实体或Vector)*,AI逻辑上都认定它们是一个【目标】,例如`AIState==1`、`AITargetPos`设置坐标的情况下,NPC会尝试逃离反向逃离这个目标位置(坐标点)。 |
### AIState状态机制 | ### AIState状态机制 | ||
- | 除了使用`AITarget\AIMove`等方式移动外,`AIState`是十分关键的**AI逻辑行为**代码,这将直接控制当前Character的**“性格和想法”**。 | + | 除了正常使用`AITarget\AIMove`等方式指定目标外,`AIState`是十分关键的**AI逻辑行为**代码,这将直接控制`Character`的**“性格和想法”**。 |
- | 此状态机制会和`AIDistance`共同搭配使用,在不同状态\行为下的数值可能要动态调整。 | + | **此状态机制会和`AIDistance`搭配使用,在不同状态\行为下的数值可能需要开发者动态调整。** |
- | | ID | 行为 | + | **==特别注意:每当目标死亡\离开范围,角色将停止跟随并重置`AITarget`(除非强制模式`AIForceMode`)。==** |
- | | ---- | ---------------------------- | ------------------------------------------------------------ | | + | |
- | | -1 | 静态 | + | |
- | | 0 | **和平(默认)(忽略Team)** | 无任何行为,默认正常寻路移动,不会主动攻击或逃脱< | + | |
- | | 1 | 被动(忽略Team) | + | |
- | | 2 | 中立 | + | |
- | | 3 | 主动(敌对) | + | |
- | **注意:以上有关==【静态(攻击)、中立、主动】==状态下,均遵循`对方.Team != 自身.Team`前提条件,如果两者`Team`相同则会忽略(认为是友军),==将不会进行触发和主动攻击==。** | + | | ID | 状态 |
+ | | ---- | ---------------- | ------------------------------------------------------------ | ---------------------------------------- | --------- | | ||
+ | | -1 | 静态 | ||
+ | | 0 | **和平(默认)** | 无其他行为,默认**正常寻路(跟随)移动**,不会主动攻击或逃脱(即使被攻击) | ||
+ | | 1 | 逃离 | ||
+ | | 2 | 中立 | ||
+ | | 3 | 敌对 | ||
- | > 如果确实有关于【中立】情况下Team相同也触发攻击的需求,可以在合适的地方提前更换`character.Team`,然后再默认触发AI逻辑)。 | + | **注意:以上AI状态均遵循【同组==Group影响==】机制(`self.Group != target.Group`),如果两者`Group`相同则会忽略(认为是友军),==将不会触发行为,并自动清除`AITarget`==。** |
- | > | + | |
- | + | > **因此,如果希望实现某些NPC跟随并攻击指定`Character`,一定要将两方的`character.Group`设定为不同的值,它们才会变成“非友军”。** | |
- | + | ||
- | ### AIDistance距离机制 | + | |
- | + | ||
- | 在使用AI功能进行对象跟随时,`AIDistance`将在不同状态时起到不同作用。 | + | |
- | + | ||
- | > `AIDistance`数值在不同`AIState`下**代表不同的限制意义**。 | + | |
> | > | ||
- | > `AIDistance`默认值:`5.0`。 | + | > 如果确实有关于【中立】情况下Group相同也触发攻击的需求,可以在合适的地方提前更换`character.Group`,然后再默认触发AI逻辑)。 |
- | + | ||
- | | AIState状态 | 机制作用 | + | |
- | | ----------- | ------------------------------------------------------------ | | + | |
- | | -1(静态) | + | |
- | | 0(和平) | + | |
- | | 1(被动) | + | |
- | | 2(中立) | + | |
- | | 3(主动) | + | |
- | + | ||
- | **特别注意:引擎默认只提供相对基础可扩展的方式,类似`AITarget(& | + | |
行 149: | 行 133: | ||
`AITargetPos`优先权更高(在值不为`null`时),对于更高级的自定义需求,以及只想让NPC前往某个坐标,那么可以使用`AITargetPos`属性来实现**向坐标移动**。 | `AITargetPos`优先权更高(在值不为`null`时),对于更高级的自定义需求,以及只想让NPC前往某个坐标,那么可以使用`AITargetPos`属性来实现**向坐标移动**。 | ||
- | **两种方式搭配`AIDistance+AIState`均可以实现追击\逃跑\中立等功能。** | + | **两种方式均可搭配`AIDistance+AIState`以实现追击\逃跑\中立等功能,通常更推荐`AITarget`的方式。** |
行 159: | 行 143: | ||
| AIMoveState状态 | 说明 | | AIMoveState状态 | 说明 | ||
| --------------- | ------------------------------------------------------------ | | | --------------- | ------------------------------------------------------------ | | ||
- | | 0(自动) | + | | 0(自动) |
| 1(行走) | | 1(行走) | ||
| 2(奔跑) | | 2(奔跑) | ||
行 169: | 行 153: | ||
由于各类游戏玩法的需求不同,沙盘引擎默认只提供基础的AI操控逻辑代码。 | 由于各类游戏玩法的需求不同,沙盘引擎默认只提供基础的AI操控逻辑代码。 | ||
- | 实际上编写一个生动且智能化的AI并非很容易的事情,尽管【计时器循环】的入口事件起点很简单,但具体怎样安排循环周期的代码?这个需要根据项目玩法提前设计完善。 | + | 编写一个生动且智能化的AI并非很容易的事情,尽管【计时器循环】的入口事件起点很简单,但具体怎样安排循环周期的代码?这个需要根据项目玩法提前设计完善。 |
+ | |||
+ | *注:一个相对复杂的AI控制代码块,成百上千行的控制代码是正常的。* | ||
+ | |||
+ | |||