由于暂时不是那么想写最近在做的游戏的代码,又动了歪心思想怎么在室友大晚上聊天让我难以睡着的时候能在tic-80做点游戏,又由于我欠缺低封装框架的代码经验,于是花了一点时间研究了一下怎么实现最小场景树。
因为只是最小场景树,所以不涉及面向对象或者ECS的编写,这些或许会在以后考虑到。所以目前这个最小场景树仅仅只是做到了场景切换而没有实例等的概念。
因为是tic-80,所以pico-8也是通用的,而且如果你写的是lua,那么我的代码理论上可以在你想的任何地方使用。
原理 #
首先我们有一个游戏,游戏之所以能每一帧都能对玩家的操作进行反应是因为有一个主循环。
在tic-80里面,主循环表示为
function TIC() then
end
然后游戏可以往下细分为无数的场景,比如开始界面,主游戏界面,和游戏结束界面。
那么要在这些场景中间进行切换,就需要有一些分支,一个分支里面发生的事情不影响另一边发生的事情。
lua的数据结构为table,只有table,它很自由,也很符合我们的预期。
我们可以把场景分为某几个函数,用函数来控制游戏的发生。在这里我分成了Init和Loop。Init表示进入场景的时候,Loop表示循环进行。
每个场景里面都有这两个函数,我们要做的就是在必要的时候运行不同的Init和Loop。
首先,我们需要一个场景的table,所以我们可以
Scene = {}
为了初始化第一个场景,以及控制所有场景,我们还需要两个变量。
CurrentScene = "Init"
FirstScene = "First" -- Change it to any scene you want
然后需要一个创建场景的方法:
function Scene:New(_scene)
Scene[_scene] = {}
self.__index = self
return setmetatable(Scene[_scene], self)
end
很基本的lua面向对象,之所以要这样写是因为我们还需要一个Scene:Change来改变当前的场景。
function Scene:Change(...)
local _scene, _keep = ...
if _keep == nil then
Scene[_scene]:Init()
end
CurrentScene = _scene
end
这里给了一个可控的参数,我们不希望在调用这个函数的时候写多余的代码,所以只需要在需要的时候保持目标场景。
为了初始化第一个场景,我们需要一个前置场景。
local initScene = Scene:New("Init")
function initScene:Loop()
initScene:Change(FirstScene)
end
接着,可以写游戏中的场景了
-- Scene First
-- Must contain an Init and a Loop Function
firstScene = Scene:New("First")
function firstScene:Init() -- Only emit once
x = 1
end
function firstScene:Loop() -- Emit every frame
x = x + 1
cls(2)
print(x)
if btn(3) then
firstScene:Change("Second") -- Change to any Scene you want
end
end
-- Scene Second
secondScene = Scene:New("Second")
function secondScene:Init()
end
function secondScene:Loop()
cls(3)
print("Hello World!")
if btn(2) then
-- The sceond parameter controls
-- whether you want to keep the target
-- scene since the last changes.
-- Default is not to keep
secondScene:Change("First", true)
end
end
最后,只需要在主循环里加上这么一句,我们的最小场景树就完成了:
Scene[CurrentScene]:Loop()
全部代码:
-- title: MinimalSceneTree
-- author: Feishiko, feishiko@foxmail.com
-- desc: A minimal scene tree
-- site: feishiko.itch.io
-- license: MIT License
-- version: 0.1
-- script: lua
CurrentScene = "Init"
FirstScene = "First" -- Change it to any scene you want
Scene = {}
function Scene:New(_scene)
Scene[_scene] = {}
self.__index = self
return setmetatable(Scene[_scene], self)
end
function Scene:Change(...)
local _scene, _keep = ...
if _keep == nil then
Scene[_scene]:Init()
end
CurrentScene = _scene
end
local initScene = Scene:New("Init")
function initScene:Loop()
initScene:Change(FirstScene)
end
-- Scene First
-- Must contain an Init and a Loop Function
firstScene = Scene:New("First")
function firstScene:Init() -- Only emit once
x = 1
end
function firstScene:Loop() -- Emit every frame
x = x + 1
cls(2)
print(x)
if btn(3) then
firstScene:Change("Second") -- Change to any Scene you want
end
end
-- Scene Second
secondScene = Scene:New("Second")
function secondScene:Init()
end
function secondScene:Loop()
cls(3)
print("Hello World!")
if btn(2) then
-- The sceond parameter controls
-- whether you want to keep the target
-- scene since the last changes.
-- Default is not to keep
secondScene:Change("First", true)
end
end
function TIC()
Scene[CurrentScene]:Loop()
end
如果省去中间两个场景,全部代码大致如下:
CurrentScene = "Init"
FirstScene = nil -- Change it to any scene you want
Scene = {}
function Scene:New(_scene)
Scene[_scene] = {}
self.__index = self
return setmetatable(Scene[_scene], self)
end
function Scene:Change(...)
local _scene, _keep = ...
if _keep == nil then
Scene[_scene]:Init()
end
CurrentScene = _scene
end
local initScene = Scene:New("Init")
function initScene:Loop()
initScene:Change(FirstScene)
end
function TIC()
Scene[CurrentScene]:Loop()
end
最后,这段代码也上传到了我的github上,如果有兴趣可以前往查阅:https://github.com/Feishiko/Minimal-Scene-Tree-in-Tic-80