另一种面向对象写法参考
还有一种写法,可以参考openresty最佳实践的面向对象一章
这里给了大致的示例如下:
local B = {}
local mt = {__index=B}
function B:new(opt)
opt.age = opt.age or 10
opt.name = opt.name or 'heli'
return setmetatable(opt, mt)
end
function B:string()
print(string.format("****, %s %s", self.age, self.name))
end
local c = B:new{age=20, name='c'}
c:string()
local d = B:new{age=30, name='d'}
c:string()
d:string()
之前一篇文章只是简单的介绍lua的面向对象, 对这方面的理解还很浅,最近在游戏中实现了一个状态机, 这里简单介绍;
####思路介绍
- 状态有很多,需要有扩展性, 所以这里的状态有独立的类结构, 同时状态机作为一个功能要让每个角色对象身上都拥有自己的状态机, 因此也是一个类;
- 状态机里包含以唯一名字标记对应的状态, 由于状态对于一个游戏角色的行为来说, 可以看作是底层实现, 所以一些公共状态可以被; 状态之间的转换也通过一个类来实现, 这样 状态机就是有不同的状态实例, 状态转换实例组合而成, 这样做的目的自然是为了让状态机有更强的适应性, 针对不同的场景, 可以共用之前实现的状态, 由状态转换实例来决定状态机的运行;
####实现代码
行为状态机ASM
-- 有限状态机
ActionStateMechaine = {}
function ActionStateMechaine:new(o)
o = o or {}
setmetatable(o,self)
self.__index = self
o._curState = nil -- 注意好多网上与书上这里用self.xxx=xxx,这是错的,在生成多个实例时会出现属性覆盖
o._nextstate = nil
o._ASM = nil # 包含该状态的状态机
o._states = nil #保存当前状态机中的状态实例
o._transition = nil #保存状态转换实例
--other attributes...
return o
注意:上面的_states, _transition是用table保存实例对象, 但这里一定要为nil, 因为如果这里写成了空表如
self._states={}
则后面的对states的保存和查找都是在这个表中进行, 那么就会出现所有状态机ASM实例都共享这个table, 即所有ASM有相同的_states表, 所以可以认为基类中的非空table 是类似static成员变量的功能, 类似python中的子类的list在基类中的功能, 浅拷贝问题;状态机成员函数:
注意: 所有的成员函数必须如python一样手动加上self, 虽然可以有自动self功能, 但这里如果省略self ,则后面继承时会出现错误, 因为子类调用该函数时用到的self还是基类的self, 数据也是基类的数据; (这个坑我花了不少时间解决)
function ActionStateMechaine.ContainState(self, statename)
return self._staes[statename] ~= nil
end
下面的成员函数就是用于添加转换实例和状态实例的:
function ActionStateMechaine.AddTranstion(self, fromname, toname,condTrans)
self._tansition = self._transition or {}
if self:COntainState(fromname) and self:ContainState(toname) then
if not self._transition[fromname] then
self._transition[fromname] = {}
end
self._transition[fromname][toname] = conTrans
end
– 状态
function ActionStateMechaine.AddState(self, state)
asert(state)
self._states = self._state or {}
self._states[state._name] = staet
state._ASM = self
end
状态机的更新函数:
function ActionStateMechaine.Update(self,obj)
if self._crestate == nil and self._nextstate then
self._crestate = self._nextstate
self.nextstate=nil
end
– 每次循环调用状态的执行函数
if self._curstate then
self._curstate:run(obj)
else
error(“not found a state to fun!”)
return false
end
– get the next state based on the returnd result and priority of each statetrans
if self._transition[self._curstate._name] then
nextstate = nil; nextStaLvl = -1;
nxtSts = self._transition[self._curstate._name]
for nxtname in pairs(nxtSts) do
– function Judge: condition is passed and the priority is higher
local bpassed, priLvl = nextstates[nxtname].Judge(obj)
if bpassed and priLvl > nextStaLvl then
nextStaLvl = self._states[nxtname]._lvl
nextState = self._states[nxtname]
end
end
self._nextstate = nextState
end
self._curstate = nil
return true
end
当然这里的nextstate其实没什么太大的作用, 不过先留着以防以后会有更多的内容添加;
小结: 上面就是状态机的主要结构, 主要提供了一个框架的东西在这, 里面的内容(state, conTransition)需要具体的去实现;
####状态类
前面的ASM只是一个空壳, 现在要实现各种不同的state去填充ASM; 所有的state都将继承与一个ActionState类, 并封装在模块内,这样对于后面可以对不同种类的状态好管理;
– state base class
ActionState = {}
function ActionState:new(o)
o = o or {}
setmetatable(o,self)
self.__index = self
self._name = o._name or “base” – the identify name for each state
self._ASM = nil
– other attributes…
– the default running function of each state
setf.run = function()
print(“call base state run(doing nothing…)”)
end
return o
end
– other member function can alse defined below…
…
基于上面的基类, 后面各种具体状态可以添加一些自己的属性, 同时必须实现一个run执行函数, 否则就会调用基类的run函数; 下面给出我本人的几个状态, 这里为每个状态直接通过一个函数来创建, 最后通过一个相同的接口来提供, 这样做一是简便,而是封装:
– 几个状态实现类
–wander state
local function Create_Wander()
local state_wander = ActionState{_name=“wander”}
function state_wander.run(self,obj)
obj.wander()
end
return state_wander
end
– other state implementation
…
类似的可以实现其它不同的状态; 该模块会提供如下的接口来统一创建模块:
function CreateState(statename,…)
local s = nil
if statename==“wander” then
s = Create_Wander()
elseif statename ==“search” then
…
else
error("wrong state name "… statename )
end
return s
end
####状态转换条件
有了状态, 那么当当前状态运行完了, 后面运行哪个状态, 这个完全有状态转换条件类来判断, 这个类可以完全针对不同的应用场景实现不同的转换类,每个类通过提供一个Judge函数来返回状态是否可以运行,以及优先级就可以;
####测试代码
下面创建了两个状态, CreDefStateTrans是用来创建默认的状态转换实例的函数, 这里两个状态相互转换构成一个循环;
require “ActionStateMechaine”
local Ast = require “AttackState”
require “StateTransCond”
function StateTest(obj)
local ASM = ActionStateMechaine:new()
ASM.AddState(Ast.CreateState(“wander”))
ASM.AddState(Ast.CreateState(“search”,OT.MONSTER))
ASM.AddTransition(“wander”,“search”,CreDefStateTrans())
ASM.AddTransition(“search”,“wander”,CreDefStateTrans())
ASM.RequestState(“wander”) --初始状态
while true do
ASM:Update(obj)
edn
end