00. 笨鸟,状态机,第一行代码
2.24,继续未完成的 CS50G 课程。
引入状态机
lua
StateMachine = Class {}
function StateMachine:init(states)
self.empty = {
render = function() end,
update = function() end,
enter = function() end,
exit = function() end
}
self.states = states or {}
self.current = self.empty
end
function StateMachine:change(stateName, enterParams)
assert(self.states[stateName]) -- state must exist!
self.current:exit()
self.current = self.states[stateName]()
self.current:enter(enterParams)
end
function StateMachine:update(dt)
self.current:update(dt)
end
function StateMachine:render()
self.current:render()
endlua
function love.load()
-- 初始化状态机
gStateMachine = StateMachine {
['title'] = function() return TitleScreenState() end,
['countdown'] = function() return CountdownState() end,
['play'] = function() return PlayState() end,
['score'] = function() return ScoreState() end
}
gStateMachine:change('title')
end
function love.update(dt)
gStateMachine:update(dt)
end
function love.draw()
gStateMachine:render()
endlua
TitleScreenState = Class { __includes = BaseState }
function TitleScreenState:update(dt)
if love.keyboard.wasPressed('enter') or love.keyboard.wasPressed('return') then
gStateMachine:change('countdown')
end
end
function TitleScreenState:render()
love.graphics.setFont(flappyFont)
love.graphics.printf('Fifty Bird', 0, 64, VIRTUAL_WIDTH, 'center')
love.graphics.setFont(mediumFont)
love.graphics.printf('Press Enter', 0, 100, VIRTUAL_WIDTH, 'center')
endlua
BaseState = Class {}
function BaseState:init() end
function BaseState:enter() end
function BaseState:exit() end
function BaseState:update(dt) end
function BaseState:render() end实现原理
我大概能理解上面状态机的实现:
- 初始化传入各个状态的工厂函数
- 切换状态时调用工厂函数,创建一个该状态的实例
- 然后用这个实例调用
update和render方法
但是我不明白 self.current 也就是创建的这个实例到底长什么样子。
AI 的解释如下,但是我看不懂 lua 这块的语法,下面仔细研究一下。
TitleScreenState()
└─ 触发元表的 __call
└─ local o = setmetatable({}, TitleScreenState)
└─ o:init(...) -- 调用 init(这里 TitleScreenState 没有 init,走继承的 BaseState:init,即空函数)
└─ return o
self.current (实例 o)
├── 自身字段: {}(空,init 没有写 self.xxx = ...)
└── 元表: TitleScreenState
├── __index = TitleScreenState(自指)
├── update(dt) ← 子类覆盖了
├── render() ← 子类覆盖了
├── enter() ← 继承自 BaseState(深拷贝进来的,空函数)
├── exit() ← 继承自 BaseState(深拷贝进来的,空函数)
└── init() ← 继承自 BaseState(深拷贝进来的,空函数)最后总结就是:
lua
-- 1. 定义"类"
TitleScreenState = Class { __includes = BaseState }
-- 此时 TitleScreenState 是一个 table,带 __call 元方法
-- 2. 注册到状态机
gStateMachine = StateMachine {
['title'] = function() return TitleScreenState() end,
-- ↑ 工厂函数,还没调用
}
-- 3. 切换状态时
gStateMachine:change('title')
-- → self.current:exit() (旧状态退出)
-- → self.current = TitleScreenState() ← 调用 __call,创建实例
-- → self.current:enter(params) (新状态进入)
-- 4. 每帧
gStateMachine:update(dt)
-- → self.current:update(dt)
-- → 通过元表 __index 找到 TitleScreenState.update,执行
gStateMachine:render()
-- → self.current:render()
-- → 同上