教程贴:教你自己编写DIY武将的AI(太阳神三国杀lua)

教程贴:教你自己编写DIY武将的AI(太阳神三国杀lua)
教程贴:教你自己编写DIY武将的AI(太阳神三国杀lua)

在cpp区发了那篇DIY教程贴之后,我转战到了lua区,发现大家都是再写lua武将的DIY,没有人关心AI的lua。在我看来,与cpp相比,lua来实现DIY的武将优点是安装卸载方便且不会影响cpp源代码的整洁,缺点是不能debug,通过设置断点来观察变量,在测试的过程中比较辛苦,还有就是偏于与系统耦合的技能比较难实现。这两天把AI部分的lua 代码看了一遍清楚了,除了身份判断部分,基本清楚了它的原理。鉴于lua武将DIY的教程和范例已经比较完善,这次我就把AI的编写简要的整理一下。附件中的内容是和这次教程相关的代码、相关武将的卡牌图片以及本帖的PDF版本。附件中有“侠”三武将的lua和AI,以及两个我DIY的娱乐武将的AI(娱乐武将用CPP实现的,由于有一个修改性别的技能改变了底层的Player类,因此无法用lua完美实现,所以就不提供代码了,有兴趣的同学可以和我交流)。

首先,AI的编写与lua技能的代码有很大关系,我用自己设计的两个武将和“侠”三武将做了实验并且完成了这五个武将的AI。在编写“侠”三武将的AI时遇到了一些问题,后来我把寒秋的代码优化了一下才解决了问题。附件里会有“侠”三武将的代码,一个是原版的,一个是我改进过的,大家可以对比下。(并没有冒犯的意思,纯学术上的交流)所以算是给广大lua的DIY爱好者们几条编程方面的建议吧。

1.写代码要整齐,缩进量要层层递进,保证代码的易读性

2.写触发技的时候,要清楚每个触发事件的原理,最好不要加入多余的触发事件,否

则可能引入异常的行为。当然,一般来说引入多余事件只是降低程序运行的效率罢了,对于神杀影响应该不大。

3.没有必要的时候不要重载can_trigger、enabled_at_play、enabled_at_response这些

函数。对于can_trigger这个函数,重载直接return true然后在on_trigger里判断玩家是否拥有该触发技和使用默认的can_trigger基本是一样的(因为can_trigger本身返回值就是触发者是否拥有该技能且触发者是否活着,貌似死人没法触发技能)。

4.如果一个触发技可以在多个时间下触发,在写on_trigger函数的时候大家请按照触

发的事件来写条件分支语句,这样逻辑会更加清晰一些。

5.尽量避免在视作技中使用askForCard这个函数,我来举个例子:如果一个视作技可

以把闪当作无中生有来使用,如果你不用askForCard,那么操作流程将是点击技能按钮à选择一张闪à点击确定当无中生有打出;但是如果使用了askForCard并且你装备了八卦阵,那么你点击技能按钮à系统提示你需要打出一张闪à使用八卦阵à判红à直接打出一张无中生有。一张牌当无中生有和零张牌当无中生有,知道区别了吧。

6.在触发技一定要使用askForSkillInvoke(player,self:objectName(),data),这里要注意第

三个参数data,它包含触发事件的详细信息,一般卖血技的是damage的结构体、无言那种是cardEffect结构体、烈弓是slashEffect结构体。这些data中的信息在写AI的时候十分重要,因此务必要加上以便于AI中完成复杂的判断。我之前写AI就是因为askForSkillInvoke(player,self:objectName())导致需要的变量总是空值,郁闷了很久。

7.触发技中askForCard之前最好加上askForSkillInvoke。对于人来说,如果不加

askForSkillInvoke,不想发动机能的时候直接点取消就行,加上了askForSkillInvoke 无非就是在提示是否使用XXX技能时多点一个确定罢了。但是对于AI的编写,如果不加askForSkillInvoke,那么这个技能对应的AI就要写在smart-ai.lua的SmartAI:askForCard里,有点像cpp中的系统耦合技,这对于代码的整洁还有AI的移植是不利的。但是加上askForSkillInvoke后,我们就可以单独的对这个选择写一个AI,消除了耦合。

OK,建议提完了,开始说AI的编写吧。首先,基本上所有的非锁定技的视作技和触发技都可以独立新建一个lua文件编写,禁止技在smart-ai中的cardProhibit和slashProhibit里编写,锁定技在smart-ai.lua中的damageIsEffective、slashIsAvailable、prohibitUseDirectly、isCompulsoryView中编写,一些锁定技的响应(需要调用askForCard的)在smart-ai中的askForCard里编写,卡牌转换类技能在smart-ai中的getSkillViewCard里编写。

我把AI技能的编写分为几类:

1.将卡牌视为三国杀已有主动使用卡牌的视作技,比如双雄、连环、火计

2.将卡牌视为三国杀已有卡牌的视作技,这些卡牌用于响应,比如倾国、看破、急救

注:武圣、龙胆、酒池这些技能既属于第一类也属于第二类

3.将卡牌视为技能卡的视作技,比如青囊、离间、驱虎

4.所有的非锁定技的触发技,比如刚烈、八阵、悲歌

5.禁止技,比如空城、谦逊、帷幕

6.使效果无效的技能,暂且称无效技,比如无言、智迟、神君;有威慑力的技能,比

如刚烈、恩怨、雷击

7.视作锁定技,比如戒酒、武神

8.对出杀次数、杀指定目标数、杀的距离修改的技能,比如天义、咆哮、方天画戟

9.响应时的斟酌,比如AOE奸雄收牌、无双肉林只有一张闪就直接掉血

此外,一些技能还涉及到选项,比如志继觉醒摸牌还是回血、反间选择花色、无谋弃掉标记还是流失体力;另一些技能涉及到目标的选择,比如驱虎赢了选择受伤害者、旋风选择伤害者或杀的目标、眩惑来的牌给谁。这就要求我们具体问题具体分析了。

第一类技能编写的通式是:

local skillname_skill={}

skillname _https://www.360docs.net/doc/3614222834.html,=" skillname "

table.insert(sgs.ai_skills, skillname)

skillname _skill.getTurnUseCard=function(self)

判断是否满足应该使用的条件,若不满足则return nil

local card

选则要发动技能的卡牌,赋值给card

if not card then return nil end

local suit = card:getSuitString()

local number = card:getNumberString()

local card_id = card:getEffectiveId()

local card_str = ("转换卡牌名称:skillname[%s:%s]=%d"):format(suit, number, card_id)

local skillcard = sgs.Card_Parse(card_str)

assert(skillcard)

return skillcard

end

例子是以下这个武将的技能—求败:

["dgqb"] = "独孤求败",

["luawujian"] = "无剑",

["luayoujian"] = "有剑",

["luayoujiandev"] = "有剑",

["luaqiubai"] = "求败",

["luaqiubaix"] = "求败",

[":luawujian"] = "当你没有装备武器时你的杀不可被闪避。",

[":luaqiubai"] = "回合内你可以将任意武器牌当决斗使用。若决斗成功则你可以摸两张牌否则对方摸两张牌",

[":luayoujian"] = "当你装备有武器时本回合你可以出X张杀,X为你当前的攻击范围,回合内你使用的所有杀都将被计数(不包括决斗出的杀)。",

--luaqiubai

local luaqiubai_skill={}

--设置技能名称

luaqiubai_https://www.360docs.net/doc/3614222834.html,="luaqiubai"

--把这个技能添加进ai_skills列表中,使AI可以使用这个技能

table.insert(sgs.ai_skills,luaqiubai_skill)

--判断技能是否可以发动的函数

luaqiubai_skill.getTurnUseCard=function(self)

--获取手牌中杀的个数

local slash_num = self:getCardsNum("Slash")

--获取该回合出杀的次数

local slash_used = self.player:usedTimes("Slash")

--如果已装备武器,且手中有杀多于一个,

--且获取该回合出杀的次数小于攻击范围,则不发动该技能

--这是为了保证有剑和求败两个技能利用最大化

if self.player:getWeapon() and slash_num > 0

and slash_used < self.player:getAttackRange() then return nil end

--获取我所有的手牌和装备牌

local cards = self.player:getCards("he")

cards=sgs.QList2Table(cards)

local card

--根据使用价值来排序

self:sortByUseValue(cards,true)

--遍历所有卡牌

for _,acard in ipairs(cards) do

--如果是武器牌则使用这个卡牌

if acard:inherits("Weapon") then

card = acard

break

end

end

--如果没选发动技能的卡牌则返回空值

if not card then return nil end

local suit = card:getSuitString()

local number = card:getNumberString()

local card_id = card:getEffectiveId()

--构造卡牌字符串,卡牌名称:技能名称[花色:点数]=卡牌ID

local card_str = ("duel:luaqiubai[%s:%s]=%d"):format(suit, number, card_id)

--指定要使用的卡牌

local skillcard = sgs.Card_Parse(card_str)

assert(skillcard)

return skillcard

end

第二类技能编写是在smart-ai.lua中的getSkillViewCard中添加代码,用于响应的卡牌有杀、闪、桃、酒、无懈,我们只需要在对应的分支语句里添加即可。比如在对闪的处理可以看到龙魂、龙胆和倾国,如果我们加一个技能叫“我闪”,这个技能可以把装备牌当闪使用,那么用下方红色的代码就能实现它的AI:

…………

elseif class_name == "Jink" then

if player:hasSkill("longhun") and player:getHp() <= 1 then

if card:getSuit() == sgs.Card_Club then

return ("jink:longhun[%s:%s]=%d"):format(suit, number, card_id)

end

end

if card_place ~= sgs.Player_Equip then

if player:hasSkill("longdan") and card:inherits("Slash") then

return ("jink:longdan[%s:%s]=%d"):format(suit, number, card_id)

elseif player:hasSkill("qingguo") and card:isBlack() then

return ("jink:qingguo[%s:%s]=%d"):format(suit, number, card_id)

end

end

if card_place == sgs.Player_Equip and player:hasSkill("woshan") then

return ("jink:woshan[%s:%s]=%d"):format(suit, number, card_id)

end

…………

有一个情况比较特殊,zeroCardView就是把0张牌当某牌,这个很特殊,目前只用来实现酒诗的AI,前两种技能如果是把0张牌当某牌,AI都可以用这个函数实现。如果写一个技能“万兽”和酒诗相似,只是正面向上时翻面可以当南蛮入侵,那么添加红色代码即可:local function zeroCardView(class_name, player)

if class_name == "Analeptic" then

if player:hasSkill("jiushi") and player:faceUp() then

return ("analeptic:jiushi[no_suit:0]=.")

end

else if class_name == "SavageAssault" then

if player:hasSkill("wanshou") and player:faceUp() then

return ("savage_assault:wanshou[no_suit:0]=.")

end

end

end

第三类技能编写的通式是:

local skillname_skill={}

skillname _https://www.360docs.net/doc/3614222834.html,=" skillname "

table.insert(sgs.ai_skills, skillname)

skillname _skill.getTurnUseCard=function(self)

判断是否满足应该使用的条件,若不满足则return nil

local card

选则要发动技能的卡牌,赋值给card

if not card then return nil end

local card_str = ("@SkillNameCard=%d"):format(card:getId())

local skillcard = sgs.Card_Parse(card_str)

assert(skillcard)

return skillcard

end

sgs.ai_skill_use_func["SkillNameCard"]=function(card,use,self)

local target

选择技能发动的目标,若没找到合适目标则return nil

use.card = card

use.to =append(target)

return

end

例子是以下这个武将的技能—争宠:

["wujian"] = "吴健",

["zhengchong"] = "争宠",

[":zhengchong"] = "出牌阶段,你可以弃一张手牌,指定两名女性角色。若如此做,视为其中一名女性角色对另一名女性角色使用一张【决斗】(此【决斗】不可被【无懈可击】响应),每回合限用一次",

["yange"] = "阉割",

[":yange"] = "你每次对其他男性角色造成伤害,可使收到该伤害的角色变为女性,直到游戏结束",

--zhengchong

local zhengchong_skill={}

--设置技能名称

zhengchong_https://www.360docs.net/doc/3614222834.html,="zhengchong"

--把这个技能添加进ai_skills列表中,使AI可以使用这个技能

table.insert(sgs.ai_skills,zhengchong_skill)

--判断技能是否可以发动的函数

zhengchong_skill.getTurnUseCard=function(self)

--如果该回合发动过则不发动

if self.player:hasUsed("ZhengchongCard") then

return

end

--如果我有手牌或装备

if not self.player:isNude() then

local card

local card_id

--如果我装备了白银狮子并且受伤了,那就用白银狮子发动技能

if self:isEquip("SilverLion") and self.player:isWounded() then

card = sgs.Card_Parse("@ZhengchongCard=" .. self.player:getArmor():getId()) --如果我没受伤或没装备狮子,且手牌数大于体力值

elseif self.player:getHandcardNum() > self.player:getHp() then

--获取我所有手牌

local cards = self.player:getHandcards()

cards=sgs.QList2Table(cards)

--对所有手牌进行遍历

for _, acard in ipairs(cards) do

--如果是基本牌或装备牌或五谷丰登且不是桃和屎

if (acard:inherits("BasicCard") or acard:inherits("EquipCard") or acard:inherits("AmazingGrace"))

and not acard:inherits("Peach") and not acard:inherits("Shit") then

--获取这张牌的ID

card_id = acard:getEffectiveId()

break

end

end

--如果我没受伤或没装备狮子,且手牌数不大于体力值,且有装备

elseif not self.player:getEquips():isEmpty() then

local player=self.player

--按照有武器、有进攻马、有防御马、有防具且手牌数不大于1的顺序弃置来发动技能

if player:getWeapon() then card_id=player:getWeapon():getId()

elseif player:getOffensiveHorse() then card_id=player:getOffensiveHorse():getId()

elseif player:getDefensiveHorse() then card_id=player:getDefensiveHorse():getId()

elseif player:getArmor() and player:getHandcardNum()<=1 then card_id=player:getArmor():getId()

end

end

--如果我没受伤或没装备狮子,且手牌数不大于体力值,且装备只有防具,且手牌数大于1

if not card_id then

cards=sgs.QList2Table(self.player:getHandcards())

--对所有手牌进行遍历

for _, acard in ipairs(cards) do

--如果是基本牌或装备牌或五谷丰登且不是桃和屎

if (acard:inherits("BasicCard") or acard:inherits("EquipCard") or acard:inherits("AmazingGrace"))

and not acard:inherits("Peach") and not acard:inherits("Shit") then

card_id = acard:getEffectiveId()

break

end

end

end

--如果还没有选到发动技能的卡牌就不发动了否则返回技能卡牌字符串

if not card_id then

return nil

else

card = sgs.Card_Parse("@ZhengchongCard=" .. card_id)

return card

end

end

return nil

end

--判断技能卡牌该如何使用的函数

sgs.ai_skill_use_func["ZhengchongCard"]=function(card,use,self)

--一个技能内部使用的函数,用来返回杀最多的友方女性

local findFriend_maxSlash=function(self,first)

self:log("Looking for the friend!")

local maxSlash = 0

local friend_maxSlash

--遍历每个友方玩家,找到杀最多的那个女性

for _, friend in ipairs(self.friends_noself) do

if (self:getCardsNum("Slash", friend)> maxSlash) and friend:getGeneral():isFemale() then

maxSlash=self:getCardsNum("Slash", friend)

friend_maxSlash = friend

end

end

--如果找到了杀最多的友方女性

if friend_maxSlash then

--定义一个用来判断是否安全的布尔变量

local safe = false

--被决斗的人要是有刚烈、反馈、恩怨

if (first:hasSkill("ganglie") or first:hasSkill("fankui") or first:hasSkill("enyuan")) then

--如果他们1血0手牌则判断为安全

if (first:getHp()<=1 and first:getHandcardNum()==0) then safe=true end

--若敌方没有刚烈、反馈、恩怨,且友方杀的数量大于敌方手牌数则判断为安全

elseif (self:getCardsNum("Slash", friend_maxSlash) >= first:getHandcardNum()) then safe=true end

--如果安全则返回杀最多的友方女性否则返回空值

if safe then return friend_maxSlash end

else

self:log("unfound")

end

return nil

end

--如果该回合没发动过该技能

if not self.player:hasUsed("ZhengchongCard") then

--将敌人按照体力值排序

self:sort(self.enemies, "hp")

local females = {}

local first, second

local zhugeliang_kongcheng --改变两专门用于识别空城诸葛亮

--创建一个虚拟的决斗卡牌

local duel = sgs.Sanguosha:cloneCard("duel", sgs.Card_NoSuit, 0)

--遍历每个敌人

for _, enemy in ipairs(self.enemies) do

--如果敌人中有空城诸葛亮且已选被决斗者且空城诸葛亮决斗伤害对被决斗者有效

if zhugeliang_kongcheng and #females==1 and self:damageIsEffective(zhugeliang_kongcheng, sgs.DamageStruct_Normal, females[1])

--则发起决斗者为空城诸葛亮

then table.insert(females, zhugeliang_kongcheng) end

--如果该敌人是女性且没有无言

if enemy:getGeneral():isFemale() and not enemy:hasSkill("wuyan") then

--如果该敌人有空城且没有手牌,则该敌人是空城诸葛亮

if enemy:hasSkill("kongcheng") and enemy:isKongcheng() then zhugeliang_kongcheng=enemy

--如果该敌人不是诸葛亮

else

--如果被决斗者还没定且决斗对该敌人有效,则该敌人为被决斗者

if #females == 0 and self:hasTrickEffective(duel, enemy) then table.insert(females, enemy)

--如果发起决斗者还没定且决斗伤害对被决斗者有效,则该敌人为发起决斗者

elseif #females == 1 and self:damageIsEffective(enemy, sgs.DamageStruct_Normal, females[1]) then table.insert(females, enemy) end

end

--如果决斗的两个人选好了就跳出循环

if #females >= 2 then break end

end

end

--如果在敌人中只找到了被决斗者,且友方不只有我一个人

if (#females==1) and #self.friends_noself>0 then

self:log("Only 1")

first = females[1]

--如果敌方有空城诸葛亮且空城诸葛亮决斗伤害对被决斗者有效

if zhugeliang_kongcheng and self:damageIsEffective(zhugeliang_kongcheng, sgs.DamageStruct_Normal, females[1]) then

--则发起决斗者为诸葛亮

table.insert(females, zhugeliang_kongcheng)

--如果敌方没有空城诸葛亮或空城诸葛亮决斗伤害对被决斗者无效

else

--找出友方杀最多的女性

local friend_maxSlash = findFriend_maxSlash(self,first)

--如果友方杀最多的女性对被决斗者的决斗造成的伤害有效,则友方杀最多的女性为发起决斗者

if friend_maxSlash and self:damageIsEffective(friend_maxSlash, sgs.DamageStruct_Normal, females[1]) then table.insert(females, friend_maxSlash) end

end

end

--如果决斗双方已选好

if (#females >= 2) then

first = females[1]

second = females[2]

--获取主公玩家

local lord = self.room:getLord()

--如果被决斗者体力不大于1

if (first:getHp()<=1) then

--如果我是主公且身份预知被开启

if self.player:isLord() or isRolePredictable() then

--找到友方杀最多的女性

local friend_maxSlash = findFriend_maxSlash(self,first)

--如果存在友方杀最多的女性,决斗发起者为友方杀最多的女性

if friend_maxSlash then second=friend_maxSlash end

--如果我不是主公或身份预知未开启,且主公是女性且没有无言

elseif (lord:getGeneral():isFemale()) and (not lord:hasSkill("wuyan")) then

--如果我是反贼,且被决斗者不是主公,且主公对被决斗者的决斗伤害有效,则决斗发起者为主公

if (self.role=="rebel") and (not first:isLord()) and self:damageIsEffective(lord, sgs.DamageStruct_Normal, first) then

second = lord

--如果我不是反贼或被决斗者不是主公

else

--如果我是忠或内,且被决斗者没有刚烈、恩怨,且被决斗者杀的数量小于决斗发起者,则决斗发起者为主公

if ((self.role=="loyalist" or (self.role=="renegade") and not (first:hasSkill("ganglie") and first:hasSkill("enyuan"))))

and ( self:getCardsNum("Slash", first)<=self:getCardsNum("Slash", second)) then

second = lord

end

end

end

end

--如果决斗双方不为空,则指定卡牌使用目标

if first and second then

if use.to then

use.to:append(first)

use.to:append(second)

end

end

use.card=card

end

end

end

话说这个技能的AI是我照离间的AI改的,其实就是把性别和变量名改了,其他的都没动,确实考虑的相当周到,不过我感觉damageIsEffective这个函数貌似用错了。因为在smart-ai.lua中函数定义是function SmartAI:damageIsEffective(player, nature, source),也就是说第一个参数是被伤害者,第三个参数是伤害者,感觉在这个技能的AI中两者写反了。不过对于绝大多数情况这个函数的返回值都是true,所以结果都一样。请专业编写AI的前辈们看一下这里是不是写错了。

第四类技能编写的通式是:

sgs.ai_skill_invoke.skillname = function(self, data)

判断是否触发技能,是则return true,否则return false

end

例子是以下这个武将的技能—销魂:

["yangguo"] = "杨过",

["luazhongjian"] = "重剑",

["luaxiaohun"] = "销魂",

[":luazhongjian"] = "当你使用杀时对方需使用一张闪加一张杀来闪避。",

[":luaxiaohun"] = "当你对女性或女性对你造成伤害时,被伤害者需判定,若结果为红色该

伤害无效否则该伤害+1。",

--判断触发技是否该触发的函数

sgs.ai_skill_invoke.luaxiaohun = function(self, data)

--如果友方有改判能力则发动

if self:hasWizard(self.friends) then return true end

--如果敌方有改判能力则发动

if self:hasWizard(self.enemies) then return false end

--获取伤害者和受伤者

local damage = data:toDamage()

local from = damage.from

local to = damage.to

--如果我是伤害者,且受伤者体力小于2或伤害值大于1,则不发动

if from:objectName() == self.player:objectName() and (to:getHp() < 2 or damage.damage > 1) then

return false

end

--如果我是受伤者,且我的体力大于2则不发动

if to:objectName() == self.player:objectName() and self.player:getHp() > 2 then return false

end

--其他情况都发动

return true

end

这里需要说明一点,有些触发技的发动频率是Frequent,比如洛神、枭姬、英姿、连营,一般来说我们不必对这类技能写AI。但是有些技能是例外,比如天妒和奸雄,如果是屎的话就不要这个牌,这里多了一些判断,因此需要写AI。

以下代码就是奸雄的AI:

sgs.ai_skill_invoke.jianxiong = function(self, data)

return not sgs.Shit_HasShit(data:toCard())

end

第五类技能编写是在smart-ai.lua的cardProhibit和slashProhibit中添加技能AI的代码,如果不添加这部分代码的话,lua写出来的禁止技只对人有效,AI照样无视你的技能。

杀以外其他卡牌的禁止技在cardProhibit中添加相应代码,比如写一个技能叫“吝啬”,你不能成为过河拆桥的目标,那么就应该像红色代码这样写:

function SmartAI:cardProhibit(card, to)

if card:inherits("Slash") then return self:slashProhibit(card, to) end

if card:getTypeId() == sgs.Card_Trick then

if card:isBlack() and to:hasSkill("weimu") then return true end

if card:inherits("Indulgence") or card:inherits("Snatch") and to:hasSkill("qianxun") then return true end

if card:inherits("Duel") and to:hasSkill("kongcheng") and to:isKongcheng() then

return true end

if card:inherits("Dismantlement") and to:hasSkill("linse") then return true end end

return false

end

杀的禁止技要在slashProhibit中添加相应代码,比如实现一个技能“黑盾”,你不能成为黑色杀的目标,那么就应该在slashProhibit函数中添加以下代码:

if card:isBlack() and self:hasSkill(“heidun”) then return true end

slashProhibit这个函数是用来让AI不把杀用在某目标身上,例如若只有一张杀是基本牌,我就不会杀刘禅;我手牌少体力也少,那我就不会杀夏侯惇;我不会杀有明闪的敌方张角等。也就是说,这个函数完成了对禁止技、无效技和威慑技的AI。

第六类技能的编写,对于杀的处理在slashProhibit里已经讲过了,建议大家认真读一下这个函数。对于其他技能,比如无言,这个就比较麻烦,在每个锦囊的使用函数useCardTrickName,判断AOE是否有效的函数aoeIsEffective,是否使用锦囊的函数useTrickCard、判断锦囊是否有效hasTrickEffective、获取使用优先级的函数getUsePriority中都有对它的考虑。从这里可以看出,要想把AI做好,全面的考虑是非常繁琐但是又必要的。

这里提供几个函数及其功能,对于这类技能大家可以在这些函数里添加代码:

slashIsEffective:判断杀是否有效,比如黑杀对于禁和仁王盾

damageIsEffective:判断伤害是否有效,比如非雷属性伤害对大雾

aoeIsEffective:判断AOE是否有效,比如无言、鸡肋、祸首、巨象、帷幕、藤甲

hasTrickEffective:判断锦囊是否有效,比如无言、智迟

第七类技能的编写,就是不能把某牌当作它原来用,只能当作锁定技锁定的牌来用,高顺酒当杀,神关羽红桃当杀。这是通过smart-ai.lua中的prohibitUseDirectly和isCompulsoryView来完成的。比如我写一个技能“好战”,所有的延时类锦囊锁定视为决斗,那么它的AI就使用红色代码部分实现:

local function prohibitUseDirectly(card, player)

if player:hasSkill("jiejiu") then return card:inherits("Analeptic")

elseif player:hasSkill("wushen") then return card:getSuit() == sgs.Card_Heart

elseif player:hasSkill("ganran") then return card:getTypeId() == sgs.Card_Equip

elseif player:hasSkill(“haozhan”) then return card: inherits("DelayedTrick")

end

end

local function isCompulsoryView(card, class_name, player, card_place)

local suit = card:getSuitString()

local number = card:getNumberString()

local card_id = card:getEffectiveId()

if class_name == "Slash" and card_place ~= sgs.Player_Equip then

if player:hasSkill("wushen") and card:getSuit() == sgs.Card_Heart then return ("slash:wushen[%s:%s]=%d"):format(suit, number, card_id) end

if player:hasSkill("jiejiu") and card:inherits("Analeptic") then return

("slash:jiejiu[%s:%s]=%d"):format(suit, number, card_id) end

elseif class_name == “DelayedTrick” then

if player: hasSkill("haozhan") then return ("duel:haozhan[%s:%s]=%d"):format(suit, number, card_id) end

end

end

第八类技能中对于出杀次数的限制是在smart-ai.lua的slashIsAvailable中添加代码:例子是以下这个武将的技能—有剑,红色代码是这个技能的AI:

["dgqb"] = "独孤求败",

["luawujian"] = "无剑",

["luayoujian"] = "有剑",

["luayoujiandev"] = "有剑",

["luaqiubai"] = "求败",

["luaqiubaix"] = "求败",

[":luawujian"] = "当你没有装备武器时你的杀不可被闪避。",

[":luaqiubai"] = "回合内你可以将任意武器牌当决斗使用。若决斗成功则你可以摸两张牌否则对方摸两张牌",

[":luayoujian"] = "当你装备有武器时本回合你可以出X张杀,X为你当前的攻击范围,回合内你使用的所有杀都将被计数(不包括决斗出的杀)。",

function SmartAI:slashIsAvailable(player)

player = player or self.player

if player:hasFlag("tianyi_failed") or player:hasFlag("xianzhen_failed") then return false end if player:hasWeapon("crossbow") or player:hasSkill("paoxiao") then

return true

end

if player:hasSkill("luayoujian") and player:getWeapon() then

return (player:usedTimes("Slash") + player:usedTimes("FireSlash") + player:usedTimes("ThunderSlash")) < player:getAttackRange()

end

if player:hasFlag("tianyi_success") then

return (player:usedTimes("Slash") + player:usedTimes("FireSlash") + player:usedTimes("ThunderSlash")) < 2

else

return (player:usedTimes("Slash") + player:usedTimes("FireSlash") + player:usedTimes("ThunderSlash")) < 1

end

end

对出杀距离和指定目标数的修改是在smart-ai.lua中的getTurnUse或useBasicCard中添加相应代码,这里我就不举例了,大家看一下我下面列出的代码应该就会写了。两者有所不

同,像天义拼点成功是一种状态,这种状态与使用什么牌无关,所以在getTurnUse中有这样一段代码:

if self.player:hasFlag("tianyi_success") then

slashAvail = 2

self.slash_targets = 2

self.slash_distance_limit = true

end

而像武神无视距离出杀、方天最后一张牌三杀这些效果与卡牌有关,因此要写在useBasicCard里,虎牢关神吕布第二形态的神戟我认为写在getTurnUse中是比较合理的,但是实际上是在useBasicCard里:

if card:getSkillName() == "wushen" then no_distance = true end

if (self.player:getHandcardNum() == 1

and self.player:getHandcards():first():inherits("Slash")

and self.player:getWeapon()

and self.player:getWeapon():inherits("Halberd"))

or (self.player:hasSkill("shenji") and not self.player:getWeapon()) then

self.slash_targets = 3

end

第九类是响应事件的AI,这是在smart-ai.lua中的askForCard中实现,比如我对敌方的法正造成了伤害,那我宁愿掉血也不会给他红桃的桃,吕布杀我而我只有一张闪,那我肯定不出(别跟我提老诸葛为了空城、邓艾为了屯田可以出,我说的是普遍情况,当然如果把这些都实现了,那AI真是太NB了,这个需要神杀AI的编写者们慢慢积累,不是一天两天能达到的)。

例子是对以下这个武将的技能—重剑的响应:

["yangguo"] = "杨过",

["luazhongjian"] = "重剑",

["luaxiaohun"] = "销魂",

[":luazhongjian"] = "当你使用杀时对方需使用一张闪加一张杀来闪避。",

[":luaxiaohun"] = "当你对女性或女性对你造成伤害时,被伤害者需判定,若结果为红色该伤害无效否则该伤害+1。",

红色代码部分是对响应这个技能添加的AI,可以看到下面还有对无双和肉林响应的AI function SmartAI:askForCard(pattern, prompt, data)

…………

elseif pattern == "jink" then

--如果我没有杀,那我连第一张闪也不必出

if parsedPrompt[1] == "@luazhongjian-jink-1" and self:getCardsNum("Slash") < 1 then return "." end

if (parsedPrompt[1] == "@wushuang-jink-1" or parsedPrompt[1] == "@roulin1-jink-1" or parsedPrompt[1] == "@roulin2-jink-1")

and self:getCardsNum("Jink") < 2 then return "." end

…………

在一开始我们还说到有些技能需要在几个选项中做选择,或者是选择目标发动效果。

对于实现在几个选项中做选择的AI的通式是:

sgs.ai_skill_choice.skillname = function(self, choices)

if 选第1个选项的条件then return “代表选项1的字符串”

elseif …………

elseif选第N个选项的条件then return “代表选项N的字符串”

end

return “默认选项”

end

对于实现选择目标发动效果的AI的通式是:

sgs.ai_skill_playerchosen.skillname = function(self, targets)

local target

找到最适合发动效果的目标,为target赋值

return target

end

例子是凌统发动旋风时的选项和法正眩惑给牌对玩家的选择:

sgs.ai_skill_choice.xuanfeng = function(self, choices)

--对敌人按防御强度排序

self:sort(self.enemies, "defense")

--创建一个虚拟的无属性杀

local slash = sgs.Card_Parse(("slash[%s:%s]"):format(sgs.Card_NoSuit, 0))

--遍历所有敌人

for _, enemy in ipairs(self.enemies) do

--若敌人与我距离不大于1,就选择对距离为1的人造成伤害

if self.player:distanceTo(enemy)<=1 then

return "damage"

--若无属性杀对敌人有效,就选择无视距离出杀

elseif not self:slashProhibit(slash ,enemy) then

return "slash"

end

end

--否则选择什么都不做

return "nothing"

end

sgs.ai_skill_playerchosen.xuanhuo = function(self, targets)

--遍历所有玩家

for _, player in sgs.qlist(targets) do

--如果玩家手牌数不大于2或体力小于2,且他是我的友方

--且他不是被眩惑的人,则给他眩惑来的牌

if (player:getHandcardNum() <= 2 or player:getHp() < 2) and self:isFriend(player) and not player:hasFlag("xuanhuo_target") then

return player

end

end

end

说完了如何编写AI,就该说一下对AI测试的方法,因为AI是用lua编写的,不能像cpp 那样有编译环境给你debug,因此经过反复的实验,我总结了一些可以当作debug的方法和技巧:

1.游戏打不开,提示lua第XXX行有错误:根据这个行数和文件改你的语法错误去吧。

2.你写AI的那个武将不会主动出牌了,每回合就是摸牌弃牌:技能的AI里出问题了,

一般是调用了一个不存在的函数,或者是对空对象进行访问。这时你应该打开三国

杀程序à启动服务器à启动游戏à再做一次测试,在服务器的输出里会看到:

lua/ai/错误所在文件名.lua:XX行: attempt to call method '某函数' (a nil value)

根据这个线索我们就可以找到是哪里出错误了。

3.有时测试为了判断AI是否跑进了某个条件语句,我们可以在那个条件语句里加入

以下代码,这个代码会在游戏的记录框(就是那个显示XX)里输出你给的字符串,如果显示了正确的字符串,那么肯定跑进对应的条件语句里了。

local log = sgs.LogMessage()

log.type = 你想要输出的字符串

room:sendLog(log)

4.视作技的测试方法:作弊获取足够你要测试的卡牌,或创造好视作技能够发动的条

件(我表示自定义小场景就是测试神器),然后托管看AI能不能发动技能。

5.触发技的测试方法:人工操作手动触发,当弹出对话框提示是否发动技能时,托管

看AI的选择是否符合你的预期。

6.无效技、威慑技、禁止技的测试方法:创造好你需要的条件,通常需要使用获取卡

牌拿顺拆把AI的手牌拆干净,然后你要测试的武将配刘备双将,把要测试的牌给

AI,然后弃牌到AI的回合看AI的表现。

7.常用来配双将的测试型武将:刘备、二张、姜维,话说我还用cpp写了个能观看别

人手牌的锦囊,用来测试确实方便

终于把太阳神三国杀的CPP和LUA都研究完了,最后总结一下:

武将的DIY:我倾向用CPP实现,对于比较底层的修改(如卡牌的使用方法、攻击范围的变化、新的触发事件)CPP有很大优势,因为CPP可以实现的比较完美,只要尽量避免耦合,在原版的代码上修改时做好标记,保证代码的整洁就可以。

卡牌的DIY:貌似没见过用LUA来实现卡牌的,而且卡牌的实现比武将要底层得多,果断CPP不解释。

AI的编写:虽然CPP可以写一些AI,而且我记得宇文天启有个帖子就是简单介绍这个的,但是毕竟LUA已经非常完善并且是主流,所以果断LUA。

最后祝愿大家在DIY方面好点子越来越多,编程实现越来越顺利。本帖结束,OVER~

LUA各种库

1.math库 函数名描述示例结果 pi圆周率math.pi 3.1415926535898 abs取绝对值math.abs(-2012)2012 ceil向上取整math.ceil(9.1)10 floor向下取整math.floor(9.9)9 max取参数最大值math.max(2,4,6,8)8 min取参数最小值math.min(2,4,6,8)2 pow计算x的y次幂math.pow(2,16)65536 sqrt开平方math.sqrt(65536)256 mod取模math.mod(65535,2)1 modf取整数和小数部分math.modf(20.12)200.12 randomseed设随机数种子math.randomseed(os.time()) random取随机数math.random(5,90)5~90 rad角度转弧度math.rad(180) 3.1415926535898 deg弧度转角度math.deg(math.pi)180 exp e的x次方math.exp(4)54.598150033144 log计算x的自然对数math.log(54.598150033144)4 log10计算10为底,x的对数math.log10(1000)3 frexp将参数拆成x*(2^y)的形式math.frexp(160)0.6258 ldexp计算x*(2^y)math.ldexp(0.625,8)160 sin正弦math.sin(math.rad(30))0.5 cos余弦math.cos(math.rad(60))0.5 tan正切math.tan(math.rad(45))1 asin反正弦math.deg(math.asin(0.5))30 acos反余弦math.deg(math.acos(0.5))60 atan正切math.deg(math.atan(1))45

串口屏LUA教程-定时器的使用

工程技术笔记 LUA 教程-定时器的使用V1.0 Technical Note

修订历史 版本日期原因编制审查V1.02019/01/15创建文档林绍佳刘启鑫

目录 1.适合范围 (1) 2.开发环境版本 (2) 3.概述 (3) 4.实现教程 (4) 4.1工程准备 (4) 4.1.1硬件平台 (4) 4.1.2素材准备 (4) 4.1.3LUA编辑器 (5) 4.2API函数说明 (6) 4.3教程实现过程 (7) 4.3.1定时器的使用 (7) 4.4编译和下载 (10) 4.4.1编译工程 (10) 4.4.2下载 (11) 5.完整程序清单 (12) 6.免责声明 (15)

1.适合范围 该文档适合所有大彩物联型系列。

2.开发环境版本 1.VisualTFT软件版本:V3.0.0.944及以上的版本。 版本查看: a)打开VisualTFT软件启动页面如图2-1软件版本,右上角会显示的软件版本号; 图2-1软件版本 b)打开VisualTFT,在软件右下角可以查看软件版本图2-2软件版本, 最新版本可登录大彩官网进行下载。 图2-2软件版本 2.串口屏硬件版本:V 3.0.301.0及以上的版本。 版本查看: a)查看屏幕背面版本号贴纸; b)VisualTFT与屏幕联机成功后,右下角显示的版本号。 3.LUA语言版本V5.5。

3.概述 物联型可以通过LUA脚本配合工程完成丰富多样的操作。 本文将介绍在LUA脚本中使用定时器循环设置按钮的状态。在按下按钮后,定时器启动,定时器以设定的时间为标准,循环设置灯亮/灭。

串口屏LUA例程-音频播放器V1.0

工程技术笔记 LUA 例程-音频播放器V1.0 Technical Note

修订历史 版本日期原因编制审查V1.02019/02/28创建文档林青田刘启鑫

目录 1.适合范围 (1) 2.开发环境版本 (2) 3.概述 (3) 4.参考资料.........................................................................错误!未定义书签。 5.教程实现 (4) 5.1准备工程素材 (4) 5.1.1硬件平台 (4) 5.1.2UI素材准备 (4) 5.1.3LUA编辑器 (5) 5.2API函数说明 (6) 5.3实现功能 (8) 5.3.1工程配置 (8) 5.3.2程序具体解析 (8) 6.完整程序清单 (13) 7.免责声明 (24)

1.适合范围 该文档适合所有广州大彩物联型系列。

2.开发环境版本 1.VisualTFT软件版本:V3.0.0.944及以上的版本。 版本查看: a)打开VisualTFT软件启动页面如图2-1软件版本,右上角会显示的软件版本号; 图2-1软件版本 b)打开VisualTFT,在软件右下角可以查看软件版本图2-2软件版本, 最新版本可登录大彩官网进行下载。 图2-2软件版本 2.串口屏硬件版本:V 3.0.301.0及以上的版本。 版本查看: a)查看屏幕背面版本号贴纸; b)VisualTFT与屏幕联机成功后,右下角显示的版本号。 3.LUA语言版本V5.5。

3.概述 物联型串口屏通过LUA脚本配合工程可以实现播放循环屏内部或者外部音频的功能。

XLua_API

C# API LuaEnv类 object[] DoString(string chunk, string chunkName = "chuck", LuaTable env = null) 描述: 执行一个代码块。 参数: chunk: Lua代码文字串; chunkName:发生error时的debug显示信息中使用,指明某某代码块的某行错误; env :这个代码块的环境变量; 返回值: 代码块里return语句的返回值; 比如:return 1, “hello”,DoString返回将包含两个object的数组,一个是double类型的1,一个是string类型的“hello” 例子: LuaEnv luaenv = new LuaEnv(); object[] ret = luaenv.DoString("print(‘hello’)\r\nreturn 1") UnityEngine.Debug.Log("ret="+ret[0]); luaenv.Dispose() T LoadString(string chunk, string chunkName = "chunk", LuaTable env = null) 描述: 加载一个代码块,但不执行,只返回类型可以指定为一个delegate或者一个LuaFunction 参数: chunk: Lua代码的字符串; chunkName:发生error时的debug显示信息中使用,指明某某代码块的某行错误; env :这个代码块的环境变量; 返回值: 代表该代码块的delegate或者LuaFunction类; LuaTable Global; 描述: 代表lua全局环境的LuaTable void Tick() 描述: 清除Lua的未手动释放的LuaBase对象(比如:LuaTable, LuaFunction),以及其它一些事情。 需要定期调用,比如在MonoBehaviour的Update中调用。 void AddLoader(CustomLoader loader)

xLua教程

教程 文件加载 一、执行字符串 最基本是直接用执行一个字符串,当然,字符串得符合语法 比如:("(' ')") 完整代码见\\\目录 但这种方式并不建议,更建议下面介绍这种方法。 二、加载文件 用的函数即可 比如:(" ''") 完整代码见\\\目录 实际上是调一个个的去加载,有一个成功就不再往下尝试,全失败则报文件找不到。 目前除了原生的外,还添加了从加载的,需要注意的是因为只支持有限的后缀,放下的文件得加上后缀(见附带的例子)。 建议的加载脚本方式是:整个程序就一个(" ''"),然后在加载其它脚本(类似脚本的命令行执行:)。 有童鞋会问:要是我的文件是下载回来的,或者某个自定义的文件格式里头解压出来,或者需要解密等等,怎么办?问得好,的自定义可以满足这些需求。 三、自定义 在加自定义是很简单的,只涉及到一个接口: [] ( ); ( ) 通过可以注册个回调,该回调参数是字符串,代码里头调用时,参数将会透传给回调,回调中就可以根据这个参数去加载指定文件,如果需要支持调试,需要把修改为真实路径传出。该回调返回值是一个数组,如果为空表示该找不到,否则则为文件的内容。 有了这个就简单了,用的?没问题。写个调用的接口读文件内容即可。文件已经加密?没问题,自己写读取文件解密后返回即可。。。

完整示例见\\\ 访问 这里指的是主动发起对数据结构的访问。 本章涉及到的例子都可以在\\下找到。 一、获取一个全局基本数据类型 访问就可以了,上面有个模版方法,可指定返回的类型。 <>("") <>("") <>("") 二、访问一个全局的 也是用上面的方法,那类型要指定成啥呢? 、映射到普通或 定义一个,有对应于的字段的属性,而且有无参数构造函数即可,比如对于{ , }可以定义一个包含;的。 这种方式下会帮你一个实例,并把对应的字段赋值过去。 的属性可以多于或者少于的属性。可以嵌套其它复杂类型。 要注意的是,这个过程是值拷贝,如果比较复杂代价会比较大。而且修改的字段值不会同步到,反过来也不会。 这个功能可以通过把类型加到生成降低开销,详细可参见配置介绍文档。 那有没有引用方式的映射呢?有,下面这个就是: 、映射到一个 这种方式依赖于生成代码(如果没生成代码会抛异常),代码生成器会生成这个的实例,如果一个属性,生成代码会对应的字段,如果属性也会设置对应的字段。甚至可以通过的方法访问的函数。 、更轻量级的方式:映射到<>,<> 不想定义或者的话,可以考虑用这个,前提下和的类型都是一致的。 、另外一种方式:映射到类 这种方式好处是不需要生成代码,但也有一些问题,比如慢,比方式要慢一个数量级,

LUA简明教程

Lua 简明教程 2013-12-313:45|发布者:红黑魂|查看:2591|评论:0|原作者:陈皓|来自:酷壳网 摘要:这几天系统地学习了一下Lua 这个脚本语言,Lua 脚本是一个很轻量级的脚本,也是号称性能最高的脚本,用在很多需要性能的地方,比如:游戏脚本,nginx,wireshark 的脚本,当你把他的源码下下来编译后,你会发现解释器居... 这几天系统地学习了一下Lua 这个脚本语言,Lua 脚本是一个很轻量级的脚本,也是号称性能最高的脚本,用在很多需要性能的地方,比如:游戏脚本,nginx ,wireshark 的脚本,当你把他的源码下下来编译后,你会发现解释器居然不到200k ,这是多么地变态啊(/bin/sh 都要1M ,MacOS 平台),而且能和C 语言非常好的互动。我很好奇得浏览了一下Lua 解释器的源码,这可能是我看过最干净的C 的源码了。 我不想写一篇大而全的语言手册,一方面是因为已经有了(见本文后面的链接),重要的原因是,因为大篇幅的文章会挫败人的学习热情,我始终觉得好的文章读起来就像拉大便一样,能一口气很流畅地搞完,才会让人爽(这也是我为什么不想写书的原因)。所以,这必然又是一篇“入厕文章” ,还是那句话,我希望本文能够让大家利用上下班,上厕所大便的时间学习一个技术。呵呵。相信你现在已经在厕所里脱掉裤子露出屁股已经准备好大便了,那就让我们畅快地排泄吧…… 运行 首先,我们需要知道,Lua 是类C 的,所以,他是大小写字符敏感的。 下面是Lua 的Hello World 。注意:Lua 脚本的语句的分号是可选的,这个和GO 语言很类似。 你可以像python 一样,在命令行上运行lua 命令后进入lua 的shell 中执行语句。 也可以把脚本存成一个文件,用如下命令行来运行。 或是像shell 一样运行:

Lua中的元表和元方法

LUA中的元表和元方法 Posted on 2009-09-01 16:13 白耘阅读(227) 评论(0) 编辑收藏所属分类: Lua学习笔记 Lua中每个值都可具有元表。元表是普通的Lua表,定义了原始值在某些特定操作下的行为。你可通过在值的原表中设置特定的字段来改变作用于该值的操作的某些行为特征。例如,当数字值作为加法的操作数时,Lua检查其元表中的"__add"字段是否有个函数。如果有,Lua调用它执行加法。 我们称元表中的键为事件(event),称值为元方法(metamethod)。前述例子中的事件是"add",元方法是执行加法的函数。 可通过函数getmetatable查询任何值的元表。 可通过函数setmetatable替换表的元表。不能从Lua中改变其他类型的元表(除了使用调试库);必须使用C API才能做到。 表和完整的用户数据具有独立的元表(尽管多个表和用户数据可共享元表);每种其他类型的所有值共享一个元表。所以,所有数字共享一个元表,字符串也是,等等。 元表可以控制对象的数学运算、顺序比较、连接、取长、和索引操作的行为。元表也能定义用户数据被垃圾收集时调用的函数。Lua给这些操作的每一个都关联了称为事件的特定键。当Lua对某值执行其中一个操作时,检查该值是否含有元表以及相应的事件。如果有,与该键关联的值(元方法)控制Lua如何完成操作。 元表控制后面列举的操作。每个操作由相应的名字标识。每个操作的键是由其名字前缀两个下划线“__”的字符串;例如,操作“加(add)”的键是字符串"__add"。这些操作的语义通过一个Lua函数描述解释器如何执行操作作了更好的说明。 下面显示的Lua代码只是说明性的;真实的行为被硬编码到解释器中,并且比这里的模拟更加高效。这些描述中的所有函数(rawget、tonumber等等。)在§5.1中描述。特别一提,要获取给定对象的元方法,我们使用表达式

教程贴:教你自己编写DIY武将的AI(太阳神三国杀lua)

在cpp区发了那篇DIY教程贴之后,我转战到了lua区,发现大家都是再写lua武将的DIY,没有人关心AI的lua。在我看来,与cpp相比,lua来实现DIY的武将优点是安装卸载方便且不会影响cpp源代码的整洁,缺点是不能debug,通过设置断点来观察变量,在测试的过程中比较辛苦,还有就是偏于与系统耦合的技能比较难实现。这两天把AI部分的lua 代码看了一遍清楚了,除了身份判断部分,基本清楚了它的原理。鉴于lua武将DIY的教程和范例已经比较完善,这次我就把AI的编写简要的整理一下。附件中的内容是和这次教程相关的代码、相关武将的卡牌图片以及本帖的PDF版本。附件中有“侠”三武将的lua和AI,以及两个我DIY的娱乐武将的AI(娱乐武将用CPP实现的,由于有一个修改性别的技能改变了底层的Player类,因此无法用lua完美实现,所以就不提供代码了,有兴趣的同学可以和我交流)。 首先,AI的编写与lua技能的代码有很大关系,我用自己设计的两个武将和“侠”三武将做了实验并且完成了这五个武将的AI。在编写“侠”三武将的AI时遇到了一些问题,后来我把寒秋的代码优化了一下才解决了问题。附件里会有“侠”三武将的代码,一个是原版的,一个是我改进过的,大家可以对比下。(并没有冒犯的意思,纯学术上的交流)所以算是给广大lua的DIY爱好者们几条编程方面的建议吧。 1.写代码要整齐,缩进量要层层递进,保证代码的易读性 2.写触发技的时候,要清楚每个触发事件的原理,最好不要加入多余的触发事件,否 则可能引入异常的行为。当然,一般来说引入多余事件只是降低程序运行的效率罢了,对于神杀影响应该不大。 3.没有必要的时候不要重载can_trigger、enabled_at_play、enabled_at_response这些 函数。对于can_trigger这个函数,重载直接return true然后在on_trigger里判断玩家是否拥有该触发技和使用默认的can_trigger基本是一样的(因为can_trigger本身返回值就是触发者是否拥有该技能且触发者是否活着,貌似死人没法触发技能)。 4.如果一个触发技可以在多个时间下触发,在写on_trigger函数的时候大家请按照触 发的事件来写条件分支语句,这样逻辑会更加清晰一些。 5.尽量避免在视作技中使用askForCard这个函数,我来举个例子:如果一个视作技可 以把闪当作无中生有来使用,如果你不用askForCard,那么操作流程将是点击技能按钮à选择一张闪à点击确定当无中生有打出;但是如果使用了askForCard并且你装备了八卦阵,那么你点击技能按钮à系统提示你需要打出一张闪à使用八卦阵à判红à直接打出一张无中生有。一张牌当无中生有和零张牌当无中生有,知道区别了吧。 6.在触发技一定要使用askForSkillInvoke(player,self:objectName(),data),这里要注意第 三个参数data,它包含触发事件的详细信息,一般卖血技的是damage的结构体、无言那种是cardEffect结构体、烈弓是slashEffect结构体。这些data中的信息在写AI的时候十分重要,因此务必要加上以便于AI中完成复杂的判断。我之前写AI就是因为askForSkillInvoke(player,self:objectName())导致需要的变量总是空值,郁闷了很久。 7.触发技中askForCard之前最好加上askForSkillInvoke。对于人来说,如果不加 askForSkillInvoke,不想发动机能的时候直接点取消就行,加上了askForSkillInvoke 无非就是在提示是否使用XXX技能时多点一个确定罢了。但是对于AI的编写,如果不加askForSkillInvoke,那么这个技能对应的AI就要写在smart-ai.lua的SmartAI:askForCard里,有点像cpp中的系统耦合技,这对于代码的整洁还有AI的移植是不利的。但是加上askForSkillInvoke后,我们就可以单独的对这个选择写一个AI,消除了耦合。

太阳神三国杀lua教程

--大家好我是hypercross。 --从这个文件开始讲解DIY接口的用法。 --首先,这个文件说明DIY需要的文件及其结构。 --DIY是以module的形式存在的。每个Module即是一个UTF8格式的Lua文件(建议用notepad++编辑),包含如下格式的代码: module("extensions.moligaloo", package.seeall) -- 进入module。这里moligaloo这个词必须和文件名相同。 extension = sgs.Package("moligaloo") -- 创建扩展包对象。变量名必须为extension。参数名为扩展包的objectName,也是通常会使用的扩展包标识 shiqian = sgs.General(extension, "shiqian", "qun") -- 创建武将对象,这里我们的武将是时迁。关于武将属性的详细说明见reference文档。 shentou = sgs.CreateViewAsSkill{ --创建技能,技能种类为ViewAsSkill。这里的技能是“出牌阶段,你可以将任意一张梅花手牌当作顺手牵羊使用。” name = "shentou", n = 1, view_filter = function(self, selected, to_select) return to_select:getSuit() == sgs.Card_Club and not to_select:isEquipped() end, view_as = function(self, cards) if #cards == 1 then local card = cards[1] local new_card =sgs.Sanguosha:cloneCard("snatch", card:getSuit(), card:getNumber()) new_card:addSubcard(card:getId()) new_card:setSkillName(self:objectName()) return new_card end end }--关于技能的说明将是几乎所有其他帮助文件的重点。此处省略。 sgs.LoadTranslationTable{ ["shentou"] = "神偷", [":shentou"] = "你可以将你的梅花手牌当做顺手牵羊使用。", }

CEGUI中文教程

CEGUI从入门到精通开发必备知识

一: 1.你的第一个CEGUI程序 2.强烈建议仔细研究CEGUISample程序!因为那里介绍了它的一些基本 用法,其实最后在游戏当中出现的,也就是这些例子的变化而已。 3.这里我会引导你写一个第一个自己的简单的CEGUI程序,它使用 CEGUISampleHelper提供的框架,使用OpenGL渲染。 4.这是在CEGUI自己给的Sample程序基础上简化、简化、再简化得出的, 目的是让大家对CEGUI程序有一个简单明了的认识,其实就是这么简单。 里面只定义了一个背景,一个静态文本,写着“Hello!CEGUI”。 5.具体的,有几块重要的东西。有一些包含在框架里了,如下: 6.DefaultResourceProvider,这个是CEGUI的一个全局的东西,可以 在任何地方操作它的指针,但是一般只需要在一开始操作它。 7.在CEGuiOpenGLBaseApplication当中,你可以看见下面这样的代码, 这就是在使用DefaultResourceProvider的指针在设置资源的路径,你可以毫不客气地复制粘贴到你的工程里 8.CEGUI::DefaultResourceProvider* rp = static_cast 9.(CEGUI::System::getSingleton().getResourceProvider());

10. 11.rp->setResourceGroupDirectory("schemes", "../datafiles/schemes/"); 12.rp->setResourceGroupDirectory("imagesets", "../datafiles/imagesets/"); 13.rp->setResourceGroupDirectory("fonts", "../datafiles/fonts/"); 14.rp->setResourceGroupDirectory("layouts", "../datafiles/layouts/"); 15.rp->setResourceGroupDirectory("looknfeels", "../datafiles/looknfeel/"); 16.rp->setResourceGroupDirectory("lua_scripts", "../datafiles/lua_scripts/"); 17.下面是整个的代码: 18.#ifndef _TDemo0_h_ 19.#define _TDemo0_h_ 20. 21.#include "CEGuiSample.h" 22.#include "CEGUI.h" 23. 24.// 例子类 25.class TDemo0Sample : public CEGuiSample

XLua使用教程

xLua教程 Lua文件加载 一、执行字符串 最基本是直接用LuaEnv.DoString执行一个字符串,当然,字符串得符合Lua语法 比如:luaenv.DoString("print('hello world')") 完整代码见XLua\Tutorial\LoadLuaScript\ByString目录 但这种方式并不建议,更建议下面介绍这种方法。 二、加载Lua文件 用lua的require函数即可 比如:DoString("require 'byfile'") 完整代码见XLua\Tutorial\LoadLuaScript\ByFile目录 require实际上是调一个个的loader去加载,有一个成功就不再往下尝试,全失败则报文件找不到。 目前xLua除了原生的loader外,还添加了从Resource加载的loader,需要注意的是因为Resource只支持有限的后缀,放Resources下的lua文件得加上txt后缀(见附带的例子)。 建议的加载Lua脚本方式是:整个程序就一个DoString("require 'main'"),然后在main.lua加载其它脚本(类似lua脚本的命令行执行:lua main.lua)。 有童鞋会问:要是我的Lua文件是下载回来的,或者某个自定义的文件格式里头解压出来,或者需要解密等等,怎么办?问得好,xLua的自定义Loader可以满足这些需求。三、自定义Loader 在xLua加自定义loader是很简单的,只涉及到一个接口: public delegate byte[] CustomLoader(ref string filepath); public void LuaEnv.AddLoader(CustomLoader loader) 通过AddLoader可以注册个回调,该回调参数是字符串,lua代码里头调用require时,参数将会透传给回调,回调中就可以根据这个参数去加载指定文件,如果需要支持调试,需要把filepath修改为真实路径传出。该回调返回值是一个byte数组,如果为空表示该loader 找不到,否则则为lua文件的内容。

lua简易教程

Lua脚本语法说明(修订) Posted on 2006-08-04 11:39 沐枫阅读(3512) 评论(9)编辑收藏引用网摘所属分类: C++ Lua脚本语法说明(增加lua5.1部份特性) Lua 的语法比较简单,学习起来也比较省力,但功能却并不弱。 所以,我只简单的归纳一下Lua的一些语法规则,使用起来方便好查就可以了。估计看完了,就懂得怎么写Lua 程序了。 在Lua中,一切都是变量,除了关键字。 I. 首先是注释 写一个程序,总是少不了注释的。 在Lua中,你可以使用单行注释和多行注释。 单行注释中,连续两个减号"--"表示注释的开始,一直延续到行末为止。相当于C++语言中的"//"。 多行注释中,由"--[["表示注释开始,并且一直延续到"]]"为止。这种注释相当于C语言中的"/*...*/"。在注释当中,"[["和"]]"是可以嵌套的(在lua5.1中,中括号中间是可以加若干个"="号的,如[==[ ... ]==]),见下面的 字符串表示说明。 II. Lua编程 经典的"Hello world"的程序总是被用来开始介绍一种语言。在Lua中,写一个这样的程序很简单: print("Hello world") 在Lua中,语句之间可以用分号";"隔开,也可以用空白隔开。一般来说,如果多个语句写在同一行的话,建议总是用分号隔开。 Lua 有好几种程序控制语句,如:

注意一下,for的循环变量总是只作用于for的局部变量;当省略步进值时,for循环会使用1作为步进值。 使用break可以用来中止一个循环。 相对C语言来说,Lua有几个地方是明显不同的,所以面要特别注意一下: .语句块 语句块在C中是用"{"和"}"括起来的,在Lua中,它是用do 和end 括起来的。比如: do print("Hello") end 可以在函数中和语句块中定局部变量。 .赋值语句 赋值语句在Lua被强化了。它可以同时给多个变量赋值。 例如: a,b,c,d=1,2,3,4 甚至是: a,b=b,a -- 多么方便的交换变量功能啊。 在默认情况下,变量总是认为是全局的。假如需要定义局部变量,则在第一次赋值的时候,需要用local说明。比如: local a,b,c = 1,2,3 -- a,b,c都是局部变量 .数值运算 和C语言一样,支持+, -, *, /。但Lua还多了一个"^"。这表示指数乘方运算。比如2^3 结果为8, 2^4结果为16。 连接两个字符串,可以用".."运处符。如: "This a " .. "string." -- 等于"this a string"

Lua 5.1 虚拟机指令简明手册

A No-Frills Introduction to Lua5.1VM Instructions Lua5.1虚拟机指令简明手册 作者Kein-Hong Man,esq. 版本0.1,20060313 Contents目录 1Introduction序言2 2Lua Instruction Basics Lua指令基础3 3Really Simple Chunks十分简单的程序块5 4Lua Binary Chunks Lua二进制程序快7 5Instruction Notation指令记法15 6Loading Constants加载常量16 7Upvalues and Globals Upvalue和全局变量20 8Table Instructions表指令22 9Arithmetic and String Instructions算术和字符串指令23 10Jumps and Calls跳转和调用28 11Relational and Logic Instructions关系和逻辑指令35 12Loop Instructions循环指令42 13Table Creation表创建48 14Closures and Closing创建和结束闭包52 15Comparing Lua5.0.2and Lua5.1比较Lua5.0.2和Lua5.156 16Digging Deeper深入探究57 17Acknowledgements致谢57 18ChangeLog&ToDos变更纪录&待做的57 “A No-Frills Introduction to Lua 5.1VM Instructions”is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike License 2.0.You are free to copy, distribute and display the work,and make derivative works as long as you give the original author credit,you do not use this work for commercial purposes,and if you alter,transform, or build upon this work,you distribute the resulting work only under a license identical to -1-

AR入门教程

AR入门教程 AR(增强现实)技术可以将真实世界的物体与虚拟世界结合,复杂的AR 应用可以扫描客厅,然后将虚拟物件投放在墙上或者平面表面上。用户可以与虚拟物件互动,比如玩游戏、解谜。 近来AR(增强现实)技术火了,在游戏业、电影业等诸多行业我们都能觅到它们的身影,许多公司把AR技术视为全新的市场契机,并另辟蹊径,摸索各种可从中获利的渠道。那么AR开发要从哪学起? 一是基础篇(C#入门) 目标:掌握C#基本语法,可以用计算机逻辑思维编程。 ●C#基础 1.数据类型 2.分支结构 3.循环结构 4.枚举和结构体 ●C#进阶 1.类和对象 2.面向对象编程 3.字段,属性和方法 4.静态类和抽象类 5.方法参数

●C#高级 1.接口 2.泛型 3.集合 4.委托 5.事件 二是核心篇(Unity3d引擎从入门到精通) 目标:使用引擎实现游戏/AR/VR中的绝大部分功能,为后期做团队项目打下坚实基础,并且能够独立做出不同风格的游戏项目。 ●Unity引擎的界面和常用类 1.引擎界面认知 2.Vetor3类的使用 3.Input类的使用 4.Transform类的使用 5.GameObject类的使用 6.Quatenion类的用法 ●物理引擎 1.刚体 2.碰撞检测 3.触发检测 4.射线检测 ●动画系统 1.新动画系统 2.动画状态机 3.融合树 4.动画曲线 5.帧事件 6.动画遮罩 7.IK动画 ●数据持久化 1.PlayerPrefs 2.XML 3.JSON 4.SQLITE ●优化 1.光照贴图 2.遮挡剔除 3.LOD 4.对象池 ●网络 1.socket https://www.360docs.net/doc/3614222834.html,Work类使用 3.RPC状态同步 三是进阶篇 目标是:使用NGUI搭建UI界面,学会Shader基本语法和LUA热更新 ●NGUI界面搭建

串口屏LUA教程-如何显示文本

工程技术笔记 LUA 例程-如何显示文本V1.0 Technical Note

修订历史 版本日期原因编制审查V1.02019/02/27创建文档林青田

目录 1.适合范围 (1) 2.开发环境版本 (2) 3.概述 (3) 4.教程实现 (4) 4.1准备工程素材 (4) 4.1.1硬件平台 (4) 4.1.2UI素材准备 (5) 4.1.3LUA编辑器 (5) 4.2API函数说明 (6) 4.3实现功能 (7) 4.3.1显示文本 (7) 4.4下载工程 (9) 4.4.1下载 (10) 5.完整程序清单 (11) 6.免责声明 (13)

1.适合范围 该文档适合所有大彩物联型系列。

2.开发环境版本 1.VisualTFT软件版本:V3.0.0.944及以上的版本。 版本查看: a)打开VisualTFT软件启动页面如图2-1软件版本,右上角会显示的软件版本号; 图2-1软件版本 b)打开VisualTFT,在软件右下角可以查看软件版本图2-2软件版本, 最新版本可登录大彩官网进行下载。 图2-2软件版本 2.串口屏硬件版本:V 3.0.301.0及以上的版本。 版本查看: a)查看屏幕背面版本号贴纸; b)VisualTFT与屏幕联机成功后,右下角显示的版本号。 3.LUA语言版本V5.5。

3.概述 物联型串口屏通过LUA脚本配合工程可以完成大部分的内部逻辑处理,可以做到让MCU只参与数据处理,不参与屏的逻辑处理。 本文将介绍大彩LUA脚本API函数中的绘图API函数使用方法,以及使用函数的注意事项。

Lua游戏脚本语言入门

官方站点 开发者网站 手册 论坛 工具

tolua++一个很好的帮助程序员将C/C++代码整合进Lua的工具 在这篇文章中,我想向大家介绍如何进行Lua程序设计。我假设大家都学过至少一门编程语言,比如Basic或C,特别是C。因为Lua的最大用途是在宿主程序中作为脚本使用的。 Lua 的语法比较简单,学习起来也比较省力,但功能却并不弱。 在Lua中,一切都是变量,除了关键字。请记住这句话。 I. 首先是注释 写一个程序,总是少不了注释的。 在Lua中,你可以使用单行注释和多行注释。 单行注释中,连续两个减号"--"表示注释的开始,一直延续到行末为止。相当于C++语言中的"//"。 多行注释中,由"--[["表示注释开始,并且一直延续到"]]"为止。这种注释相当于C语言中的"/*…*/"。在注释当中,"[["和"]]"是可以嵌套的。 II. Lua编程 经典的"Hello world"的程序总是被用来开始介绍一种语言。在Lua中,写一个这样的程序很简单: print("Hello world") 在Lua中,语句之间可以用分号";"隔开,也可以用空白隔开。一般来说,如果多个语句写在同一行的话,建议总是用分号隔开。 Lua 有好几种程序控制语句,如: 条件控制:if 条件then … elseif 条件then … else … end While循环:while 条件do … end Repeat循环:repeat … until 条件 For循环:for 变量 = 初值,终点值,步进do … end For循环:for 变量1,变量2,… ,变量N in表或枚举函数do … end 注意一下,for的循环变量总是只作用于for的局部变量,你也可以省略步进值,这时候,for循环会使用1作为步进值。 你可以用break来中止一个循环。

XLua增加删除第三方lua库

What&Why XLua目前内置的扩展库: 1、针对luajit的64位整数支持; 2、函数调用耗时以及内存泄漏定位工具; 3、用于支持ZeroBraneStudio的luasocket库; 4、tdr 4 lua; 随着使用项目的增加以及项目使用的深入程度,仅有这几个扩展已经没法满足项目组了,而由于各个项目对扩展差异化比较大,以及手机平台对安装包大小的敏感,XLua是无法通过预集成去满足这些需求,这也是这篇教程的由来。 这篇教程,将以lua-rapidjson为例,一步步的讲述怎么往xLua添加c/c++扩展,当然,会添加了,自然删除也就会了,项目组可以自行删除不需要用到的预集成扩展。 How 分三步 1、修改build文件、工程设置,把要集成的扩展编译到XLua Plugin里头; 2、调用xLua的C# API,使得扩展可以被按需(在lua代码里头require的时候)加载; 3、可选,如果你的扩展里头需要用到64位整数,你可以通过XLua的64位扩展库来实现和C#的配合。 一、添加扩展&编译 准备工作 把xLua的C源码包解压到你Unity工程的Assets同级目录下。 下载lua-rapidjson代码,按你的习惯放置。本教程是把rapidjson头文件放到$UnityProj\build\lua-rapidjson\include目录下,而扩展的源码rapidjson.cpp放到$UnityProj\build\lua-rapidjson\source目录下(注:$UnityProj指的是你工程的目录)在CMakeLists.txt加入扩展 xLua的各平台Plugins编译使用cmake编译,好处是所有平台的编译都写在一个makefile,大部分编译处理逻辑是跨平台的。 xLua配套的CMakeLists.txt为第三方扩展提供了扩展点(都是list): THIRDPART_INC:第三方扩展的头文件搜索路径。 THIRDPART_SRC:第三方扩展的源代码。 THIRDPART_LIB:第三方扩展依赖的库。 如下是rapidjson的加法 #begin lua-rapidjson set (RAPIDJSON_SRC lua-rapidjson/source/rapidjson.cpp) set_property( SOURCE ${RAPIDJSON_SRC}

串口屏LUA教程-自定义串口指令V1.0

工程技术笔记 LUA 教程-自定义串口指令V1.0 Technical Note

修订历史

目录 1.适合范围 (1) 2.开发环境版本 (2) 3.概述 (3) 4.实现教程 (4) 4.1工程准备 (4) 4.1.1硬件平台 (4) 4.1.2素材准备 (5) 4.1.3LUA编辑器 (5) 4.2API函数说明 (6) 4.3教程实现过程 (8) 4.3.1设置自定义串口 (8) 4.3.2设置按钮状态 (9) 4.3.3设置文本内容 (10) 4.3.4设置蜂鸣器响 (11) 4.3.5发送按钮指令 (11) 4.3.6发送键盘输入内容 (12) 4.4编译和下载 (13) 4.4.1编译工程 (13) 4.4.2下载 (14) 5.完整程序清单 (16) 6.免责声明 (21)

1.适合范围 该文档适合所有大彩物联型系列。

2.开发环境版本 1.VisualTFT软件版本:V3.0.0.944及以上的版本。 版本查看: a)打开VisualTFT软件启动页面如图2-1软件版本,右上角会显示的软件版本号; 图2-1软件版本 b)打开VisualTFT,在软件右下角可以查看软件版本图2-2软件版本, 最新版本可登录大彩官网进行下载。 图2-2软件版本 2.串口屏硬件版本:V 3.0.301.0及以上的版本。 版本查看: a)查看屏幕背面版本号贴纸; b)VisualTFT与屏幕联机成功后,右下角显示的版本号。 3.LUA语言版本V5.5。

3.概述 物联型可以通过LUA脚本配合工程完成丰富多样的操作。 本文将介绍通过LUA脚本的自定义串口指令设置按钮按下、设置文本、设置蜂鸣器响。并在按下按钮或通过键盘输入数据后发送自定义指令。

串口屏LUA教程-系统参数设置V1.0

工程技术笔记 LUA 教程-系统参数设置V1.0 Technical Note

修订历史

目录 1.适合范围 (1) 2.开发环境版本 (2) 3.概述 (3) 4.实现教程 (4) 4.1工程准备 (4) 4.1.1硬件平台 (4) 4.1.2素材准备 (4) 4.1.3LUA编辑器 (5) 4.2API函数说明 (6) 4.3教程实现过程 (8) 4.3.1设置波特率 (8) 4.3.2设置背光 (9) 4.3.3设置蜂鸣器响 (11) 4.3.4设置RTC (11) 4.4编译和下载 (13) 4.4.1编译工程 (13) 4.4.2下载 (13) 5.完整程序清单 (15) 6.免责声明 (18)

1.适合范围 该文档适合所有大彩物联型系列。

2.开发环境版本 1.VisualTFT软件版本:V3.0.0.944及以上的版本。 版本查看: a)打开VisualTFT软件启动页面如图2-1软件版本,右上角会显示的软件版本号; 图2-1软件版本 b)打开VisualTFT,在软件右下角可以查看软件版本图2-2软件版本, 最新版本可登录大彩官网进行下载。 图2-2软件版本 2.串口屏硬件版本:V 3.0.301.0及以上的版本。 版本查看: a)查看屏幕背面版本号贴纸; b)VisualTFT与屏幕联机成功后,右下角显示的版本号。 3.LUA语言版本V5.5。

3.概述 物联型可以通过LUA脚本配合工程完成丰富多样的操作。 本文将介绍通过LUA脚本设置4个系统参数的功能,包括使用按钮设置波特率、使用按钮设置蜂鸣器响、使用按钮设置RTC、使用滑动条调节背光。

wow 魔兽世界 插件 脚本语言 : LUA语言学习教程

LUA语言学习教程 在这篇文章中,我想向大家介绍如何进行Lua程序设计。我假设大家都学过至少一门编程语言,比如Basic或C,特别是C。因为Lua的最大用途是在宿主程序中作为脚本使用的。 Lua 的语法比较简单,学习起来也比较省力,但功能却并不弱。 在Lua中,一切都是变量,除了关键字。请记住这句话。 I. 首先是注释 写一个程序,总是少不了注释的。 在Lua中,你可以使用单行注释和多行注释。 单行注释中,连续两个减号"--"表示注释的开始,一直延续到行末为止。相当于C++语言中的"//"。 多行注释中,由"--[["表示注释开始,并且一直延续到"]]"为止。这种注释相当于C语言中的"/*…*/"。在注释当中,"[["和"]]"是可以嵌套的。 II. Lua编程 经典的"Hello world"的程序总是被用来开始介绍一种语言。在Lua中,写一个这样的程序很简单: print("Hello world") 在Lua中,语句之间可以用分号";"隔开,也可以用空白隔开。一般来说,如果多个语句写在同一行的话,建议总是用分号隔开。 Lua 有好几种程序控制语句,如: 条件控制:if 条件then … elseif 条件then … else … end While循环:while 条件do … end Repeat循环:repeat … until 条件 For循环:for 变量 = 初值,终点值,步进do … end For循环:for 变量1,变量2,… ,变量N in表或枚举函数do … end 注意一下,for的循环变量总是只作用于for的局部变量,你也可以省略步进值,这时候,for循环会使用1作为步进值。 你可以用break来中止一个循环。 如果你有程序设计的基础,比如你学过Basic,C之类的,你会觉得Lua也不难。但Lua有几个地方是明显不同于这些程序设计语言的,所以请特别注意。 .语句块 语句块在C++中是用"{"和"}"括起来的,在Lua中,它是用do 和 end 括起来的。比如: do print("Hello") end 你可以在函数中和语句块中定局部变量。 .赋值语句 赋值语句在Lua被强化了。它可以同时给多个变量赋值。

相关主题
相关文档
最新文档