在第一篇教程中,我们主要介绍了Dota2 AI的基本情况,在这篇文章中,我们将介绍如何为AI选择阵容和技能使用(以宙斯为例)。
选择阵容
在官方开发者维基中有着这样的说明:
如果你想控制英雄选择和分路,你可以在文件名为hero_selection.lua的文件中实现如下函数:
Think() - 每帧被调用。负责机器人选择英雄。
UpdateLaneAssignments() - 在游戏开始前的每一帧被调用。返回玩家ID-分路的值对。
GetBotNames() - 调用一次,返回一个包含玩家名称的表。
在dota 2 beta\game\dota\scripts\vscripts\bots_example
V社的示例文件中,我们可以找到hero_selection.lua
文件,这就是为AI选择阵容的脚本文件。
如果我们要开始制作自己的AI,那么首先需要创建dota 2 beta\game\dota\scripts\vscripts\bots
文件夹,之后建立hero_selection.lua
文件,在其中编写选择阵容的部分,这样的话AI就会选择自定义阵容了。
打开这个文件,我们可以看到如下内容
function Think()
if ( GetTeam() == TEAM_RADIANT ) --处于天辉方时
then
print( "selecting radiant" ); --在控制台输出调试信息
SelectHero( 0, "npc_dota_hero_antimage" ); --为0号玩家选择敌法师
SelectHero( 1, "npc_dota_hero_axe" );
SelectHero( 2, "npc_dota_hero_bane" );
SelectHero( 3, "npc_dota_hero_bloodseeker" );
SelectHero( 4, "npc_dota_hero_crystal_maiden" );
elseif ( GetTeam() == TEAM_DIRE )
then
print( "selecting dire" );
SelectHero( 5, "npc_dota_hero_drow_ranger" );
SelectHero( 6, "npc_dota_hero_earthshaker" );
SelectHero( 7, "npc_dota_hero_juggernaut" );
SelectHero( 8, "npc_dota_hero_mirana" );
SelectHero( 9, "npc_dota_hero_nevermore" );
end
end
其中Think()是主函数,游戏选择阵容时就会调用这个函数,然后判断是天辉方还是夜魇方,之后分别选择各自的阵容。那么我们如何找到英雄对应的名字呢?在这里便可找到英雄的实际名称。
例如宙斯的实际名称是"npc_dota_hero_zuus"
,那么我们将夜魇方的SelectHero( 5, "npc_dota_hero_drow_ranger" );
替换为SelectHero( 5, "npc_dota_hero_zuus" );
,那么AI便会选择在5号玩家的位置上选择宙斯。需要注意的是,玩家的ID是会随着人类玩家的加入而发生改变的,将玩家ID硬编码是一种不好的做法,比较好的方法是通过循环遍历出玩家的ID,这样无论人类玩家有多少,AI都能正常选择阵容。
下面便是能够自动适应玩家变化选择阵容的代码。
function Think()
for i,id in pairs(GetTeamPlayers(GetTeam())) --Lua语言自带的迭代器pairs,能够依次读取表中的值。
do
if(IsPlayerBot(id)) --判断该玩家是否为电脑
then
SelectHero( id, "npc_dota_hero_drow_ranger" ); --为id号玩家选择黑暗游侠
end
end
end
如果我们要AI能够选择随机的阵容,而不是仅仅选择10个黑暗游侠,那该怎么办呢?很简单,通过Lua语言唯一的数据结构表(称作Table,可以用作数组)保存可以选择的英雄列表,然后随机出一个序号,选出表中的英雄。
hero_pool_my=
{
'npc_dota_hero_zuus',
'npc_dota_hero_skywrath_mage',
'npc_dota_hero_ogre_magi',
'npc_dota_hero_chaos_knight',
'npc_dota_hero_viper',
"npc_dota_hero_lina",
"npc_dota_hero_kunkka",
"npc_dota_hero_lich",
"npc_dota_hero_shadow_shaman",
"npc_dota_hero_centaur",
'npc_dota_hero_crystal_maiden',
}
function Think()
for i,id in pairs(GetTeamPlayers(GetTeam()))
do
if(IsPlayerBot(id) and (GetSelectedHeroName(id)=="" or GetSelectedHeroName(id)==nil))
then
local num=hero_pool_my[RandomInt(1, #hero_pool_my)] --取随机数
SelectHero( id, num ); --在保存英雄名称的表中,随机选择出AI的英雄
table.remove(hero_pool_my,num) --移除这个英雄
end
end
end
以上代码会为两边的AI依次从hero_pool_my表中随机选出5个英雄。如果要让AI不选择重复的英雄,那么还需要进一步的修改。
选择分路
为AI分路的函数也是在hero_selection.lua
中,如果我们想让AI遵循自定义的分路,那么需要在这个文件里新建一个UpdateLaneAssignments()
函数。这个函数是用于游戏底层C++端调用,所以需要返回一个分路情况的表。
function UpdateLaneAssignments()
local npcBot = GetBot()
local lanes=
{
[1]=LANE_MID,
[2]=LANE_BOT,
[3]=LANE_TOP,
[4]=LANE_BOT,
[5]=LANE_TOP,
}
return lanes;
end
以上函数根据AI的位置分路,1楼中路,2楼下路,3楼上路,4楼下路,5楼上路。当然,如果要根据英雄的特性分路,那么需要加入更多判断。如果没有自己写好分路系统的话,可以暂时启用默认分路系统,即不编写这个函数。
技能加点
在官方开发者维基中有着这样的说明:
如果你只想重写在技能和物品使用时的决策,你可以在文件名为ability_item_usage_generic.lua的文件中实现如下函数:
ItemUsageThink() - 每帧被调用。负责发出物品使用行为。
AbilityUsageThink() - 每帧被调用。负责发出技能使用行为。
CourierUsageThink() - 每帧被调用。负责发出信使的相关命令。
BuybackUsageThink() - 每帧被调用。负责发出买活指令。
AbilityLevelUpThink() - 每帧被调用。负责升级技英雄能。
这些函数中未被重写的,会自动采用默认的C++实现。
你也可以仅重写某个特定英雄对技能/物品使用的逻辑,比如火女(Lina),写在文件名为ability_item_usage_lina.lua的文件中。如果你想在特定英雄对技能/物品使用的逻辑重写时调用泛用的逻辑代码,请参考附件A的实现细节。
我们要实现的第一个英雄为宙斯,那么就可以新建一个ability_item_usage_zuus.lua
的文件。首先要实现AI的加点,就要在此文件中编写AbilityLevelUpThink()
函数。然后通过 npcBot:ActionImmediate_LevelAbility(abilityname);
升级名称为abilityname的技能。那么怎样获取升级技能所需的技能名称呢?比较原始的方法是用一个表记录从1到25级所有的技能名称。其中的数据要在dota 2 beta\game\dota\scripts\npc\npc_heroes.txt
中获取。
例如宙斯的技能为
--以下为宙斯的技能名称
"zuus_arc_lightning",
"zuus_lightning_bolt",
"zuus_static_field",
"zuus_cloud",
"zuus_thundergods_wrath",
--以下为某个版本的天赋,请注意天赋名称会随着版本更新而变化,所以以下部分可能在当前版本中无效
"special_bonus_mp_regen_2",
"special_bonus_movement_speed_25",
"special_bonus_armor_7",
"special_bonus_magic_resistance_15",
"special_bonus_unique_zeus_2",
"special_bonus_unique_zeus_3",
"special_bonus_cast_range_200",
"special_bonus_unique_zeus",
如果通过原始方法记录,则需要排列组合以上的技能天赋。比较好的方法则是用两个表分别保存技能和天赋的名称,然后在一个统一的表中引用技能天赋名称,以存储加点顺序。
local Talents =
{