And action...
Everything is action reaction

- Martin
- 6 min read

Yaml
Since I did not want to write a game editor to save time, I had decided to use yaml files. The advantage is that they can also use references to avoid repetition. And in the future I could possibly create an editor that exports yaml files.
What needed to be described in the yaml files was every action that could take place per scene and character. Below you see such a piece of scene20.yml. In it you see that it will rain for 5 seconds and that a text becomes visible during the start of the scene. Also you can see some hover actions and what should happen when you choose e.g. the action “open” “information board”.
# START scene
# ==================================================
*action-scene-entities-ready-after:
- <<: *particle-system
<<: *particle-system-settings-rain
id: 200143
ukey: *particle-rain-20
stopTime: 5
*action-scene-start:
- <<: *trigger
id: 200144
run_once: true
methods:
- *particle-rain-20:
- start: nil
- <<: *text
id: 200104
run_once: true
text: "The Netherlands, Drente\n52°52'3.95\"N, 6°52'15.01\"E"
<<: *tween-text-bottom-left-fade-in-fade-out
# HOVER
# ==================================================
*action-hover:
*information-board1: Information board
*portal-key: An old stone with inscriptions
*branch: A tree root
*pole: Pole
*broken-information-board1: Broken information board
*dolmens: Dolmens
*stone-circle: Stone circle
*hotspot-20-exit: Go home
*hotspot-20-stone-circle: The center of the stone circle
# GLOBAL OPEN
# ==================================================
*action-open:
*information-board1:
- <<: *say
id: 200007
text: You cannot open an information board, it is a wooden panel with text and images
*portal-key:
- <<: *walk
id: 200105
- <<: *say
id: 200008
text: I cannot open the stone
*action-lookat:
*portal-key:
- <<: *walk
id: 200121
- <<: *say
id: 200041
text: It looks like an old stone with something written on it, it looks very interesting
It is also possible for an entity to get other actions than the globally described actions of the scene, you can do this by doing the following at the bottom of the file.
*diana:
*action-pickup:
*dolmens:
- <<: *walk
id: 200094
target:
x: 600
y: 175
framesetIndex: *move-up
- <<: *say
id: 200095
text: What do you think? Do you really think I could lift that?
The references of all entities e.g. *dolmens
are described in assets/refs.yaml and I can reuse them everywhere. The texts text:
are converted to keys and are possibly translated. Below you see a part of the refs.yaml
...
# tweens
# fade in, show text 3 seconds, fade out and move to the left
tween-text-bottom-left-fade-in-fade-out: &tween-text-bottom-left-fade-in-fade-out
textArguments:
color: *color-white-hidden # going to fadein, using the tweens
scale: .50 # @todo *scale-half-size
font: *font-ui-text
xOffset: *offset-left
yOffset: *offset-bottom
yOffsetIncludeUi: true # offset including menus (above menu)
offsetMargin: [0,0,5,5] # top, right, bottom, left @todo global margin
tweens:
- duration: 2
properties:
color: *color-white
- duration: 1
properties:
color: *color-white-hidden
x: -10
wait: 3
# particle schortcuts
particle-system-settings-rain: &particle-system-settings-rain
emissionRate: 200
speed:
- 300
- 400
linearAcceleration:
- 0
- 800
- 0
- 1000
sizes:
- 1
- 0.5
- 0.2
- 0.1
- 0.8
sizeVariation: 1
emitterLifetime: -1
particleLifetime:
- 0.8
- 1.5
colors:
- 0 # r
- 0 # g
- 0.3 # b
- 0.8 # a
- 0 # r
- 0 # g
- 0.8 # b
- 0.8 # a
# characters
nikki: &nikki 10
martin: &martin 11
diana: &diana 12
# -- scene 20
information-board1: &information-board1 20002
portal-key: &portal-key 20003
branch: &branch 20004
broken-information-board1: &broken-information-board1 20005
pole: &pole 20006
dolmens: &dolmens 20007
stone-circle: &stone-circle 20008
hotspot-20-stone-circle: &hotspot-20-stone-circle 20009
hotspot-20-exit: &hotspot-20-exit 20010
scene-menu-20: &scene-menu-20 20011
particle-rain-20: &particle-rain-20 20012
...
# -- scene 21
...
I can create a custom action of anything and have it caught in Lua. The list of actions is currently as follows.
action-open: &action-open 1
action-close: &action-close 2
action-give: &action-give 3
action-pickup: &action-pickup 4
action-lookat: &action-lookat 5
action-talkto: &action-talkto 6
action-push: &action-push 7
action-pull: &action-pull 8
action-use: &action-use 9
action-hover: &action-hover 30
action-walkto: &action-walkto 31
action-say: &action-say 32
action-say_options: &action-say_options 34
action-inventory: &action-inventory 35
action-cannot-walk-to-point: &action-cannot-walk-to-point 36
action-popup: &action-popup 37
action-animation: &action-animation 38
action-scene-start: &action-scene-start 39
action-scene-entities-ready-after: &action-scene-entities-ready-after 40
action-camera: &action-camera 41
action-text: &action-text 44
action-modify-action: &action-modify-action 45
action-hotspot: &action-hotspot 46
action-trigger: &action-trigger 47
action-statement: &action-statement 48
action-world-loaded: &action-world-loaded 49
action-use-direct: &action-use-direct 50
action-audio: &action-audio 51
action-particle-system: &action-particle-system 52
Processing the actions
The yaml files are converted to json and then to messagepack files, which are much smaller and are processed much faster.
Then comes the real work, but that is not really clear here since action.lua is +/- 600 lines of code and there is also an action-event.lua and action-status.lua.
In action-event.lua is the logic of whether the player has clicked an action button such as ‘pick up’ and then e.g. an apple ‘selectedItem’. Or ‘give to’ > selectedActor. Here is all the logic described of what a player can do (in combination with a described yaml action). Eventually the action will be executed.
local activeActor = entityManager:find({movableByUserInput = true}, "Player")
action:execute(
{
action = tonumber(self.selectedActionButton.name),
initiator = #activeActor > 0 and activeActor[1] or nil,
selectedActionButton = self.selectedActionButton,
selectedActor = self.selectedActor,
selectedItem = self.selectedItem,
selectedInventoryItem = self.selectedInventoryItem,
selectedInventoryItem2 = self.selectedInventoryItem2
}
)
In action.lua there is too much, but below are some important parts. I create a special object of each yaml action type, containing all logicia. This object is constantly reused.
-- intial only once for the whole game
function Action:new()
self.actionObject = {}
self.actionObject[Action.WALK_TO] = ActionWalkTo()
self.actionObject[Action.SAY] = ActionSay()
self.actionObject[Action.SAY_OPTIONS] = ActionSayOptions()
self.actionObject[Action.INVENTORY] = ActionInventory()
self.actionObject[Action.POPUP] = ActionPopup()
self.actionObject[Action.ANIMATION] = ActionAnimation()
self.actionObject[Action.CAMERA] = ActionCamera()
self.actionObject[Action.TEXT] = ActionText()
self.actionObject[Action.ACTION_MODIFY] = ActionModify()
self.actionObject[Action.TRIGGER] = ActionTrigger()
self.actionObject[Action.STATEMENT] = ActionStatement()
self.actionObject[Action.AUDIO] = ActionAudio()
self.actionObject[Action.PARTICLE_SYSTEM] = ActionParticleSystem()
end
function Action:processActionList(actionList, actionListIndex, triggerData, afterProcessActionListCallback)
...
-- maybe this action may run once
if self:didRunOnce(actionId, actionData) then
-- maybe there is another (next) action in the list that don't have run_once
self:processActionList(actionList, actionListIndex + 1, triggerData, afterProcessActionListCallback)
return
end
-- going to start this action
-- maybe there are children, update the status to QUEUE and set this actionId on WORKING
-- this will create all needed action status objects
for _, actionListObj in pairs(actionList) do
local actionStatusObj = self:getActionStatusFromList(actionListObj.id)
if actionListObj.id == actionId then
self:getActionStatusFromList(actionListObj.id):setStatus(ActionStatus.WORKING)
elseif actionStatusObj:getStatus() == ActionStatus.WORKING or actionStatusObj:getStatus() == ActionStatus.DONE then
-- do nothing
else
self:getActionStatusFromList(actionListObj.id):setStatus(ActionStatus.QUEUE)
end
end
-- start the action
self.actionObject[actionData.action]:execute(
triggerData,
actionData,
actionList,
actionListIndex,
actionId,
afterProcessActionListCallback
)
end
Sound
An example is the action type audio, the logic class for this is in framework.core.action.audio.lua and is controlled from the action/.yml files in the following way
*action-use-direct:
*inventory-strange-whistle:
*martin:
- <<: *audio
id: 100072
audiosetName: martin-play-strange-whistle.ogg
audiosetStatus: *audioset-playing
---@class ActionAudioActionData
---@field wait? integer The time to wait before starting the next action, default 0 sec.
---@field run_once? boolean If you want to run this action once
---@field audiosetName string The audioset name "something.ogg"
---@field audiosetStatus integer<Audioset.IDLE|Audioset.PLAYING|Audioset.PAUSE>
---@field dontWaitForCallback? boolean Wait until the audioset is done playing default false
---@param triggerData triggerData
---@param actionData ActionAudioActionData
---@param actionList table
---@param actionListIndex integer
---@param actionId integer
---@param afterProcessActionListCallback function
function ActionAudio:execute(
triggerData,
actionData,
actionList,
actionListIndex,
actionId,
afterProcessActionListCallback)
---@type Audioset
local audioset =
entityManager:findOne(
{
name = actionData.audiosetName
},
"Audioset"
)
-- create the callbacks first
if actionData.dontWaitForCallback ~= true then
audioset.callbackAfterStopped["actionAudiosetCallbackAfterStopped"] = function(s, framekey)
action:afterProcessActionListAction(
self,
triggerData,
actionData,
actionList,
actionListIndex,
actionId,
afterProcessActionListCallback
)
end
end
audioset:setAudiosetStatus(actionData.audiosetStatus)
audioset:play()
if actionData.dontWaitForCallback == true then
action:afterProcessActionListAction(
self,
triggerData,
actionData,
actionList,
actionListIndex,
actionId,
afterProcessActionListCallback
)
end
end
return ActionAudio
Finally
Everything is set up in such a way that it is separated from the engine3 and the game. This is necessary to make it manageable. If you were to program all actions in a fixed way in the game, you would run into enormous problems. It is better to make generic functions that can process all actions.
Loading external comment system...