Dota2 AI 简易开发教程(二)——英雄出装及其相关功能
2017-11-27 13:15
459 查看
距离上一篇教程也过去一个多月了,是时候写一篇新教程了。上一篇文章主要介绍了如何选择阵容和技能的使用,在这一篇文章中,我们将介绍如何配置英雄出装和其相关的一些模块。
所以中间几行应该修改为,这样从表中才能正确移除已经选择的英雄
物品购买
如果你只想重写在购买物品时的决策,你可以在文件名为item_purchase_generic.lua的文件中实现如下函数:
ItemPurchaseThink() - 每帧被调用。负责物品购买。
你也可以仅重写某个特定英雄的物品购买逻辑,比如火女(Lina),写在文件名为item_purchase_lina.lua的文件中。
在
打开这个文件,我们可以看到:
大家注意到,在上面那个记录出装顺序的表中,物品的名称似乎和平时用的有所区别,其实也可以在这个页面找到。
在上面这个函数中,V社为我们提供了购买物品的基本函数,但是这只能实现在泉水的商店中购买物品,如果要去神秘商店和边路商店购买物品怎么办呢?如果要配置多种出装风格应该怎么办?如何控制信使去购买物品?那就需要靠我们自己实现了。而且如果要配置物品出装,那么需要手动填写每一个配方。不过在V社最近的api更新之后,添加了一个函数GetItemComponents(sItemName),可以直接获取物品的配方。
下面
4000
,我们开始在V社的基本函数上逐渐添加更多的功能。
在游戏中,一个能在神秘商店购买的物品必定不能在基地商店购买,而边路商店的物品则有可能来自于其他商店。所以便能通过IsItemPurchasedFromSideShop(sNextItem)和IsItemPurchasedFromSecretShop(sNextItem )函数来判断一个物品能在哪被购买,进而决定下一个物品的购买位置。
然后我们在mode_secret_shop_generic.lua和mode_side_shop_generic.lua函数中分别实现控制英雄前往神秘商店和边路商店购买的操作。
在开发者维基中提到了以下内容:
模式重写
如果你想在已有模式体系下修改某个模式逻辑的需求和行为,例如修改对线模式(laning mode),你可以在文件名为mode_laning_generic.lua的文件中实现如下函数:
GetDesire() - 每帧都被调用,需要返回一个0到1之间的浮点值,该值标志了该模式有多大可能成为当前激活模式
OnStart() - 当该模式成为当前激活模式时调用
OnEnd() - 当该模式让出控制权给其他被激活模式时调用
Think() - 当该模式为当前激活模式时,每帧都被调用。负责发出机器人的行为指令。
你也可以仅重写某个特定英雄的模式逻辑,比如火女(Lina),写在文件名为mode_laning_lina.lua的文件中。如果你想在特定英雄的模式重写时调用泛用的英雄模式代码,请参考附件A的实现细节。
所以,我们便需要GetDesire()和Think()以重写该模式。当英雄离边路商店较近,而且下一件物品可以在边路商店购买时,英雄便不需要用信使或自己返回基地购买物品,而是直接前往边路商店。
mode_side_shop_generic.lua 边路商店模式
mode_secret_shop_generic.lua 神秘商店模式
然后需要在主购买函数中检查英雄是否已经到达神秘或野外商店附近,并购买物品。
购买信使的函数
在购买物品时,用一个变量记录购买结果,并随后检查,以免出现购买失败的情况。
local PurchaseResult=npcBot:ActionImmediate_PurchaseItem( sNextItem )
两个效用函数,用于判断物品栏是否已满和出售特定物品
在前文中我们提到过,信使控制的函数位于ability_item_usage_generic.lua的CourierUsageThink()中,所以我们只要在这个文件中重写此函数,便能完全地控制信使。
信使有以下几种状态和命令,参见开发者维基。通过ActionImmediate_Courier( hCourier, nAction ) 函数命令信使,int GetCourierState( hCourier )函数则能获取信使的状态。
所以信使控制模块需要先检测信使的状态,然后再根据英雄是否有装备需要运送来决定信使的行为。
以下便是信使控制主函数
最后需要每个英雄调用主函数。例如ability_item_usage_zuus.lua。
下面上张图。
勘误
首先先纠正一下上一篇文章中的一些错误,在选择阵容部分,下面的table.remove函数中出现了一个错误,bad argument #2 to ‘remove’ (number expected, got string),就是第二个参数应该为数字而不是字符串。这个错误将导致已选择的英雄不能从英雄列表中移除,从而会选择出重复的英雄。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
所以中间几行应该修改为,这样从表中才能正确移除已经选择的英雄
local i=RandomInt(1, #hero_pool_my) --取随机数 local heroname=hero_pool_my[i] --获取英雄的名称 SelectHero( id, heroname ); --在保存英雄名称的表中,随机选择出AI的英雄 table.remove(hero_pool_my,i) --移除这个英雄
出装系统简介
在官方开发者维基中有着这样的说明:物品购买
如果你只想重写在购买物品时的决策,你可以在文件名为item_purchase_generic.lua的文件中实现如下函数:
ItemPurchaseThink() - 每帧被调用。负责物品购买。
你也可以仅重写某个特定英雄的物品购买逻辑,比如火女(Lina),写在文件名为item_purchase_lina.lua的文件中。
在
dota 2 beta\game\dota\scripts\vscripts\bots_exampleV社的示例文件中,我们可以找到
item_purchase_lina.lua文件,这就是为火女配置出装的文件。
打开这个文件,我们可以看到:
--这个表用来记录AI出装的顺序 local tableItemsToBuy = { "item_tango", "item_tango", "item_clarity", "item_clarity", "item_branches", "item_branches", "item_magic_stick", "item_circlet", "item_boots", "item_energy_booster", "item_staff_of_wizardry", "item_ring_of_regen", "item_recipe_force_staff", "item_point_booster", "item_staff_of_wizardry", "item_ogre_axe", "item_blade_of_alacrity", "item_mystic_staff", "item_ultimate_orb", "item_void_stone", "item_staff_of_wizardry", "item_wind_lace", "item_void_stone", "item_recipe_cyclone", "item_cyclone", }; ------------------------------------------------------------------------------------------ --用来实现基本的物品购买函数 function ItemPurchaseThink() local npcBot = GetBot(); if ( #tableItemsToBuy == 0 ) then npcBot:SetNextItemPurchaseValue( 0 ); return; end local sNextItem = tableItemsToBuy[1]; npcBot:SetNextItemPurchaseValue( GetItemCost( sNextItem ) ); --用于默认AI的相关函数 if ( npcBot:GetGold() >= GetItemCost( sNextItem ) ) --判断金钱是否足够 then npcBot:Action_PurchaseItem( sNextItem ); --购买物品 table.remove( tableItemsToBuy, 1 ); end end
大家注意到,在上面那个记录出装顺序的表中,物品的名称似乎和平时用的有所区别,其实也可以在这个页面找到。
在上面这个函数中,V社为我们提供了购买物品的基本函数,但是这只能实现在泉水的商店中购买物品,如果要去神秘商店和边路商店购买物品怎么办呢?如果要配置多种出装风格应该怎么办?如何控制信使去购买物品?那就需要靠我们自己实现了。而且如果要配置物品出装,那么需要手动填写每一个配方。不过在V社最近的api更新之后,添加了一个函数GetItemComponents(sItemName),可以直接获取物品的配方。
下面
4000
,我们开始在V社的基本函数上逐渐添加更多的功能。
在神秘商店和边路商店购买装备
由于物品购买是一个通用的功能,所以我们可以在item_purchase_generic.lua中编写函数,以便重复调用。在默认AI的框架下,有两个模式用于物品购买secret_shop和side_shop,我们需要通过在主函数中控制前往神秘商店和边路商店购买物品。(尽管将选择前往哪个商店的功能耦合在一个函数中不好。更好的方法是在其它两个模式中重新编写GetDesire()函数。)在游戏中,一个能在神秘商店购买的物品必定不能在基地商店购买,而边路商店的物品则有可能来自于其他商店。所以便能通过IsItemPurchasedFromSideShop(sNextItem)和IsItemPurchasedFromSecretShop(sNextItem )函数来判断一个物品能在哪被购买,进而决定下一个物品的购买位置。
if(npcBot.secretShopMode~=true and npcBot.sideShopMode ~=true) then if (IsItemPurchasedFromSideShop( sNextItem ) and npcBot:DistanceFromSideShop() <= 2000) --只有在离边路商店较近时才前往边路商店 then npcBot.sideShopMode = true; end if (IsItemPurchasedFromSecretShop( sNextItem )) then npcBot.secretShopMode = true; end End
然后我们在mode_secret_shop_generic.lua和mode_side_shop_generic.lua函数中分别实现控制英雄前往神秘商店和边路商店购买的操作。
在开发者维基中提到了以下内容:
模式重写
如果你想在已有模式体系下修改某个模式逻辑的需求和行为,例如修改对线模式(laning mode),你可以在文件名为mode_laning_generic.lua的文件中实现如下函数:
GetDesire() - 每帧都被调用,需要返回一个0到1之间的浮点值,该值标志了该模式有多大可能成为当前激活模式
OnStart() - 当该模式成为当前激活模式时调用
OnEnd() - 当该模式让出控制权给其他被激活模式时调用
Think() - 当该模式为当前激活模式时,每帧都被调用。负责发出机器人的行为指令。
你也可以仅重写某个特定英雄的模式逻辑,比如火女(Lina),写在文件名为mode_laning_lina.lua的文件中。如果你想在特定英雄的模式重写时调用泛用的英雄模式代码,请参考附件A的实现细节。
所以,我们便需要GetDesire()和Think()以重写该模式。当英雄离边路商店较近,而且下一件物品可以在边路商店购买时,英雄便不需要用信使或自己返回基地购买物品,而是直接前往边路商店。
mode_side_shop_generic.lua 边路商店模式
function GetDesire() local npcBot = GetBot(); local desire = 0.0; if ( npcBot:IsUsingAbility() or npcBot:IsChanneling() ) --不应打断持续施法 then return 0 end if ( npcBot.sideShopMode == true and npcBot:GetGold() >= npcBot:GetNextItemPurchaseValue()) then local d=npcBot:DistanceFromSideShop() if d<2000 then desire = (2000-d)/d*0.3+0.3; --根据离边路商店的距离返回欲望值 end else npcBot.sideShopMode = false end return desire end function Think() local npcBot = GetBot(); local shopLoc1 = GetShopLocation( GetTeam(), SHOP_SIDE ); local shopLoc2 = GetShopLocation( GetTeam(), SHOP_SIDE2 ); if ( GetUnitToLocationDistance(npcBot, shopLoc1) <= GetUnitToLocationDistance(npcBot, shopLoc2) ) then --选择前往距离自己更近的商店 npcBot:Action_MoveToLocation( shopLoc1 ); else npcBot:Action_MoveToLocation( shopLoc2 ); end end
mode_secret_shop_generic.lua 神秘商店模式
function GetDesire() local npcBot = GetBot(); local desire = 0.0; if ( npcBot:IsUsingAbility() or npcBot:IsChanneling() ) --不应打断持续施法 then return 0 end if ( npcBot.secretShopMode == true and npcBot:GetGold() >= npcBot:GetNextItemPurchaseValue()) then local d=npcBot:DistanceFromSecretShop() if d<3000 then desire = (3000-d)/d*0.3+0.3; --根据离边路商店的距离返回欲望值 end else npcBot.secretShopMode = false end return desire end function Think() local npcBot = GetBot(); local shopLoc1 = GetShopLocation( GetTeam(), SHOP_SECRET ); local shopLoc2 = GetShopLocation( GetTeam(), SHOP_SECRET2 ); if ( GetUnitToLocationDistance(npcBot, shopLoc1) <= GetUnitToLocationDistance(npcBot, shopLoc2) ) then --选择前往距离自己更近的商店 npcBot:Action_MoveToLocation( shopLoc1 ); else npcBot:Action_MoveToLocation( shopLoc2 ); end end
然后需要在主购买函数中检查英雄是否已经到达神秘或野外商店附近,并购买物品。
local PurchaseResult --接收购买结果,后文会介绍 if(npcBot.sideShopMode == true) then if(npcBot:DistanceFromSideShop() <= 200) then PurchaseResult=npcBot:ActionImmediate_PurchaseItem( sNextItem ) end elseif(npcBot.secretShopMode == true) --如果目标是神秘商店,则命令信使购买物品 then if(npcBot:DistanceFromSecretShop() <= 200) then PurchaseResult=npcBot:ActionImmediate_PurchaseItem( sNextItem ) end local courier=GetCourier(0) if(courier==nil) then BuyCourier() --没有信使的话则会购买 else if(courier:DistanceFromSecretShop() <= 200) --信使已到达商店 then PurchaseResult=GetCourier(0):ActionImmediate_PurchaseItem( sNextItem ) end end else PurchaseResult=npcBot:ActionImmediate_PurchaseItem( sNextItem ) end
购买信使的函数
function BuyCourier() local npcBot=GetBot() local courier=GetCourier(0) if(courier==nil) --购买小鸡 then if(npcBot:GetGold()>=GetItemCost("item_courier")) then local info=npcBot:ActionImmediate_PurchaseItem("item_courier"); if info ==PURCHASE_ITEM_SUCCESS then print(npcBot:GetUnitName()..' buy the courier',info); end end else --购买飞行信使 if DotaTime()>60*3 and npcBot:GetGold()>=GetItemCost("item_flying_courier") and (courier:GetMaxHealth()==75) then local info=npcBot:ActionImmediate_PurchaseItem("item_flying_courier"); if info ==PURCHASE_ITEM_SUCCESS then print(npcBot:GetUnitName()..' has upgraded the courier.',info); end end end end
检查购买结果
npcBot:Action_PurchaseItem( sNextItem )函数会返回一个购买结果的值,其可能的值为:Item Purchase Results PURCHASE_ITEM_SUCCESS --成功购买 PURCHASE_ITEM_OUT_OF_STOCK --物品栏已满 PURCHASE_ITEM_DISALLOWED_ITEM --不被允许的物品 PURCHASE_ITEM_INSUFFICIENT_GOLD --金钱不足 PURCHASE_ITEM_NOT_AT_HOME_SHOP --不在基地商店 PURCHASE_ITEM_NOT_AT_SIDE_SHOP --不在边路商店 PURCHASE_ITEM_NOT_AT_SECRET_SHOP --不在神秘商店 PURCHASE_ITEM_INVALID_ITEM_NAME --不存在的物品名称
在购买物品时,用一个变量记录购买结果,并随后检查,以免出现购买失败的情况。
local PurchaseResult=npcBot:ActionImmediate_PurchaseItem( sNextItem )
if(PurchaseResult==PURCHASE_ITEM_SUCCESS) --成功购买便从出装表中移除该物品 then npcBot.secretShopMode = false; npcBot.sideShopMode = false; table.remove( ItemsToBuy, 1 ) end if(PurchaseResult==PURCHASE_ITEM_OUT_OF_STOCK) --物品栏已满,出售多余的物品 then SellExtraItem() end if(PurchaseResult==PURCHASE_ITEM_INVALID_ITEM_NAME or PurchaseResult==PURCHASE_ITEM_DISALLOWED_ITEM) --不存在的物品,移除该物品 then table.remove( ItemsToBuy, 1 ) end if(PurchaseResult==PURCHASE_ITEM_INSUFFICIENT_GOLD ) --金额不足(其实该情况也较少出现,因为我 d654 们已经在上面判断了金钱) then npcBot.secretShopMode = false; npcBot.sideShopMode = false; end if(PurchaseResult==PURCHASE_ITEM_NOT_AT_SECRET_SHOP) --不在神秘商店,前往神秘商店 then npcBot.secretShopMode = true npcBot.sideShopMode = false; end if(PurchaseResult==PURCHASE_ITEM_NOT_AT_SIDE_SHOP) --不在边路商店(其实该情况不会出现,因为在边路商店的物品能在其他商店购买) then npcBot.sideShopMode = true npcBot.secretShopMode = false; end if(PurchaseResult==PURCHASE_ITEM_NOT_AT_HOME_SHOP) --不在基地商店(也不会出现的情况,因为如果英雄不在家中,那么物品会购买于贮藏处) then npcBot.secretShopMode = false; npcBot.sideShopMode = false; end
两个效用函数,用于判断物品栏是否已满和出售特定物品
function SellSpecifiedItem( item_name ) --出售特定物品 local npcBot = GetBot(); local itemCount = 0; local item = nil; for i = 0, 14 do local sCurItem = npcBot:GetItemInSlot(i); if ( sCurItem ~= nil ) then itemCount = itemCount + 1; if ( sCurItem:GetName() == item_name ) then item = sCurItem; end end end if ( item ~= nil and itemCount > 5 and (npcBot:DistanceFromFountain() <= 600 or npcBot:DistanceFromSideShop() <= 200 or npcBot:DistanceFromSecretShop() <= 200) ) then npcBot:ActionImmediate_SellItem( item ); end end function SellExtraItem() --出售已经没有用处的物品 if(GameTime()>15*60) then SellSpecifiedItem("item_faerie_fire") SellSpecifiedItem("item_enchanted_mango") SellSpecifiedItem("item_tango") SellSpecifiedItem("item_clarity") SellSpecifiedItem("item_flask") end if(GameTime()>20*60) then SellSpecifiedItem("item_stout_shield") SellSpecifiedItem("item_orb_of_venom") end if(GameTime()>30*60) then SellSpecifiedItem("item_branches") SellSpecifiedItem("item_bottle") SellSpecifiedItem("item_magic_wand") SellSpecifiedItem("item_magic_stick") SellSpecifiedItem("item_urn_of_shadows") SellSpecifiedItem("item_drums_of_endurance") end end
信使控制
信使又称为小鸡,一直都是dota中有很大用处的单位,一直以来都是各方神圣追杀的对象。但是因为7.00版本刚出时,默认AI的信使控制机制有些问题,不知道现在修复了没有。而且为了与我们之前所写的神秘商店控制系统配合,所以需要重写信使控制模块。在前文中我们提到过,信使控制的函数位于ability_item_usage_generic.lua的CourierUsageThink()中,所以我们只要在这个文件中重写此函数,便能完全地控制信使。
信使有以下几种状态和命令,参见开发者维基。通过ActionImmediate_Courier( hCourier, nAction ) 函数命令信使,int GetCourierState( hCourier )函数则能获取信使的状态。
Courier Actions and States COURIER_ACTION_BURST --加速 COURIER_ACTION_ENEMY_SECRET_SHOP --前往敌方的神秘商店 COURIER_ACTION_RETURN --返回基地 COURIER_ACTION_SECRET_SHOP --前往神秘商店 COURIER_ACTION_SIDE_SHOP --前往边路商店 COURIER_ACTION_SIDE_SHOP2 --前往边路商店 COURIER_ACTION_TAKE_STASH_ITEMS --拾起物品 COURIER_ACTION_TAKE_AND_TRANSFER_ITEMS --拾起并运送物品 COURIER_ACTION_TRANSFER_ITEMS --运送物品 COURIER_STATE_IDLE --空闲 COURIER_STATE_AT_BASE --处于基地 COURIER_STATE_MOVING --移动 COURIER_STATE_DELIVERING_ITEMS --运送物品 COURIER_STATE_RETURNING_TO_BASE --返回基地 COURIER_STATE_DEAD --信使已死亡
所以信使控制模块需要先检测信使的状态,然后再根据英雄是否有装备需要运送来决定信使的行为。
以下便是信使控制主函数
function CourierUsageThink() local npcBot=GetBot() local courier=GetCourier(0) --获取信使句柄 if(npcBot:IsAlive()==false or courier==nil or npcBot:IsHero()==false or npcBot:HasModifier("modifier_arc_warden_tempest_double")) --判断使用者是不是真正的英雄 then return end local state=GetCourierState(courier) --获取信使状态 local burst=courier:GetAbilityByName("courier_burst") local CanCastBurst=burst~=nil and burst:IsFullyCastable() --检查信使加速能否使用 if(state == COURIER_STATE_DEAD) --信使已死亡 then return end if(courier:GetHealth()/courier:GetMaxHealth()<=0.9) --信使受到攻击,立刻回家 then if(CanCastBurst) then npcBot:ActionImmediate_Courier(courier, COURIER_ACTION_BURST) end npcBot:ActionImmediate_Courier(courier, COURIER_ACTION_RETURN) return end if(state == COURIER_STATE_IDLE) --信使处于空闲状态10秒以上则回家 then if(courier.idletime==nil) then courier.idletime=GameTime() else if(GameTime()-courier.idletime>10) then npcBot:ActionImmediate_Courier(courier, COURIER_ACTION_RETURN) courier.idletime=nil return end end end if(state == COURIER_STATE_RETURNING_TO_BASE and npcBot:GetCourierValue()>0 and not utility.IsItemSlotsFull()) --运送物品 then npcBot:ActionImmediate_Courier(courier, COURIER_ACTION_TRANSFER_ITEMS) return end if (state == COURIER_STATE_AT_BASE ) --从基地运送 then if( npcBot:GetStashValue() >= 400) then if(courier.time==nil) then courier.time=DotaTime() end if(courier.time+1<DotaTime()) then npcBot:ActionImmediate_Courier(courier, COURIER_ACTION_TAKE_AND_TRANSFER_ITEMS) courier.time=nil end return end end if(state == COURIER_STATE_AT_BASE or state == COURIER_STATE_RETURNING_TO_BASE) --前往神秘商店 then if(npcBot.secretShopMode == true and npcBot:GetActiveMode() ~= BOT_MODE_SECRET_SHOP) then npcBot:ActionImmediate_Courier(courier, COURIER_ACTION_SECRET_SHOP) return end end if(state == COURIER_STATE_DELIVERING_ITEMS) --当信使正在运送物品时加速 then if(CanCastBurst) then npcBot:ActionImmediate_Courier(courier, COURIER_ACTION_BURST) end end end
最后需要每个英雄调用主函数。例如ability_item_usage_zuus.lua。
function CourierUsageThink() ability_item_usage_generic.CourierUsageThink() end
示例
为宙斯配置的出装:ability_item_usage_zuus.luarequire( GetScriptDirectory().."/item_purchase_generic" ) local ItemsToBuy = { "item_tango", "item_clarity", "item_branches", "item_branches", "item_faerie_fire", "item_bottle", "item_boots", "item_energy_booster", --秘法鞋 "item_mantle", "item_circlet", "item_recipe_null_talisman", --无用挂件 "item_mantle", "item_circlet", "item_recipe_null_talisman", --无用挂件 "item_helm_of_iron_will", "item_recipe_veil_of_discord", --纷争 "item_void_stone", "item_energy_booster", "item_recipe_aether_lens", --以太之镜7.06 "item_staff_of_wizardry", "item_void_stone", "item_recipe_cyclone", "item_wind_lace", --风杖 "item_point_booster", "item_staff_of_wizardry", "item_ogre_axe", "item_blade_of_alacrity", --蓝杖 "item_point_booster", "item_vitality_booster", "item_energy_booster", "item_mystic_staff", --玲珑心 } function ItemPurchaseThink() purchase.ItemPurchase(ItemsToBuy) --将出装表传至主函数 end
总结
看完了这篇文章后,大家应该能够了解AI出装的相关功能了,虽然说这篇文章因为作者太懒拖了很久,不过还是要比V社好一点。V社说好的战役第二章迟迟没有推出,即将重新定义“七月下旬”。下面上张图。
附件
1.示例代码相关文章推荐
- Dota2 AI 简易开发教程
- Dota2 AI 简易开发教程(一)——选择阵容及技能使用
- Dota2 AI 开发 (二)定制AI阵容 配置英雄出装
- 移动开发之【微信小程序】的原理与权限问题以及相关的简易教程
- 移动开发之【微信小程序】的原理与权限问题以及相关的简易教程
- Mock.js简易教程,脱离后端独立开发,实现增删改查功能(转)
- 【转】FDT+MTASC Flash开发 简易教程 下载相关软件,配置eclipse
- 介绍DOTA2 AI的开发基础——调试
- Web服务开发的相关教程(不断更新中…)
- Android实战简易教程-第五十三枪(通过实现OnScrollListener接口实现上拉加载更多功能)
- Android开发教程--自定义接听/挂断电话功能
- (学习笔记)51单片机的中断功能及其相关的寄存器
- 教程:基于Spring快速开发电子邮件发送功能
- 稳扎稳打Silverlight(18) - 2.0视频之详解MediaElement, 开发一个简易版的全功能播放器
- iOS---Touch ID于密码的简易开发教程
- Android开发教程之调用摄像头功能的方法详解
- 安卓开发 中文教程 (11)-- 安卓4.0系列的新功能
- Android实战简易教程-第三十一枪(基于加速度传感器的摇一摇功能实例)
- 系统相关功能开发(六)-桌面相关