【Defold】階層的なステートモジュールを作成しました

2019/07/15Defold, DEVELOP

階層的なステートモジュールが欲しくなったので、作ってみました。

追記:2019/07/15 GitHubにも公開しました。
https://github.com/stepism/defold-mira-substate

モジュール

local M = {}

function M.new()
	local instance = {}

	instance.on_enter = bit.lshift(1, 0)
	instance.loop = bit.lshift(1, 1)
	
	instance.flag = instance.on_enter
	instance.time = 0
		
	function instance.has_flag(flag)
		return (bit.band(instance.flag, flag) ~= 0)
	end

	function instance.add_flag(flag)
		instance.flag = bit.bor(instance.flag, flag)
	end

	function instance.del_flag(flag)
		instance.flag = bit.band(instance.flag, bit.bnot(flag))
	end

	function instance.del_flag_all()
		instance.flag = 0
	end

	function instance.set_flag(flag, b)
		if b then
			instance.add_flag(flag)
		else
			instance.del_flag(flag)
		end
	end

	function instance.one_time_flag(flag)
		local temp = instance.has_flag(flag)
		instance.del_flag(flag)
		return temp
	end

	function instance.update(dt)
		--instance.del_flag_all()
		instance.time = instance.time + dt
	end

	function instance.reset()
		instance.add_flag(instance.on_enter)
		instance.time = 0
	end
	return instance
	
end

return M
local M = {}
local state_info = require("mira.state.state_info")
local unused_state = 99999

function M.new()
	local instance = {}
	
	instance.info = state_info.new()
	instance.child_state = nil
	instance.prev_state = 0
	instance.state = 0

	function instance.set_state(state, loop)
		instance.prev_state = state
		instance.state = state
		instance.info.reset()

		if loop then
			instance.info.add_flag(instance.info.loop)
		end

		if instance.child_state ~= nil then
			instance.child_state.reset()
		end
	end

	function instance.term()
		instance.set_state(unusedState, falsefactory)
	end

	function instance.is_term()
		return instance.is_state(unusedState)
	end

	function instance.get_state()
		return instance.state;
	end
	
	function instance.is_state(state)
		return (instance.state == state)
	end

	function instance.prev(loop)
		instance.set_state(instance.get_state() - 1, loop)
	end
	
	function instance.next(loop)
		instance.set_state(instance.get_state() + 1, loop)
	end

	function instance.get_child()
		if instance.child_state == nil then
			instance.child_state = M.new()
		end
		return instance.child_state
	end

	function instance.update(dt)
		if instance.prev_state == instance.state then
			instance.info.update(dt)
			if instance.child_state ~= nil then
				instance.child_state.update(dt)
			end
		end
		
		instance.prev_state = instance.state
		
	end

	function instance.is_on_enter()
		return instance.info.one_time_flag(instance.info.on_enter)
	end

	function instance.is_loop()
		if instance.child_state ~= nil then
			return instance.child_state.is_loop() or instance.info.one_time_flag(instance.info.loop)
		end
		
		return instance.info.one_time_flag(instance.info.loop)
	end

	function instance.reset()
		instance.prev_state = 0
		instance.state = 0
		if instance.child_state ~= nil then
			instance.child_state.reset()
		end
	end

	function instance.wait_time(duration)
		return (instance.info.time > duration)
	end

	return instance
end

return M

これら二つのluaファイルを以下に配置して使います。

  • [project]/mira/state/state.lua
  • [project]/mira/state/state_info.lua

使用例


local mira_state = require("mira.state.state")

-- state for update()
local state_idle = 0
local state_main = 1
local state_sub = 2

-- state for sub_update()
local sub_state_idle = 0
local sub_state_wait = 1

function init(self)
	self.state = mira_state.new()
end

function update(self, dt)

	local state = self.state.get_state()
	
	if state == state_idle then
		if self.state.is_on_enter() then
			print("update state_idle is_on_enter")
			self.state.next(true)
		end
		
	elseif state == state_main then		
		if self.state.is_on_enter() then
			print("update state_main is_on_enter")
			self.state.next(true)
		end

	elseif state == state_sub then
		if sub_update(self.state.get_child()) then
			self.state.set_state(state_idle, true)
		end
	end

	self.state.update(dt)
end

function sub_update(sub_state)

	local state = sub_state.get_state()

	if state == sub_state_idle then
		if sub_state.is_on_enter() then
			print("sub_update sub_state_idle is_on_enter")
			sub_state.next(true)
		end

	elseif state == sub_state_wait then
		print("sub_update sub_state_wait waiting...")

		if sub_state.wait_time(2.0) then
			print("sub_update sub_state_wait end")
			-- exit sub_update
			return true
		end
	end

	-- loop sub_update
	return false
end

特徴的な機能は以下の2点です。

関数名機能
is_on_enter ステート進入時1度だけtrueを返します
wait_time指定した時間が経過するとtrueを返します

動作確認

ステートマシンは、ひとつ有ると便利です。

Posted by kazupon