跳转至内容

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()
end
lua
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()
end
lua
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')
end
lua
BaseState = Class {}

function BaseState:init() end
function BaseState:enter() end
function BaseState:exit() end
function BaseState:update(dt) end
function BaseState:render() end

实现原理

我大概能理解上面状态机的实现:

  • 初始化传入各个状态的工厂函数
  • 切换状态时调用工厂函数,创建一个该状态的实例
  • 然后用这个实例调用 updaterender 方法

但是我不明白 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()
-- → 同上