Note : this code was written in Godot 3.2.
Let me start by saying that I have rewritten this post several times, and that the end result is
nothing like what I imagined it was going to be.
Yes, in figuring out this particular bit of workflow I have definitely taken the scenic route ...
When I was working on my first two point&click games (not made in Godot), I ended up creating 3 types of objects : 'interactables' (IA), 'pickups' (PU), and 'hotspots' (HS).
Hotspots are the 'things' that make interactables and pickups work. They are small areas (rectangles, in a 2d game) that can be made to respond to mouse clicks, or that can be used to 'receive' other game items.
Pickups are game items that have HS's that can respond to mouse clicks. Clicking on a PU moves the game item from the game world into the inventory. It can then be used on other objects in the game world or in the inventory.
Interactables are usually a bit more complex to write, because they can have both types of HS.
Think about a locked door. The door needs a key (receive) → the key needs to be turned to unlock the door (click) → and finally, the door can be opened (click).
Often, interacting with IA's also involves playing animations. In BetterWorld, the dragonfly is an IA that responds to repeated mouse clicks with a different animation.
The plan with this new game was to use a similar setup. But ... Aaargh ! Class-based code (Java, Haxe, ...) just does not translate well to the node/script system of Godot ...
As this game is intended to be a bigger game than the last one, I wanted to make sure that it was going to be easy to
set up IA's. I also wanted to be able to reuse 3d models and scripts.
This is the workflow I settled on :
This is a silly example of an IA with 3 keys :
'SB' is the StaticBody/CollisionShape that was generated in Blender ; it is used to prevent the player from walking
through a solid object.
'HS' is a custom Area/CollisionShape node that can handle mouse clicks.
'MI' is a 'MeshInstance' ; it represents the visible part of the model.
Note that key1 (yellow) and key2 (light brown) are actually the same 3d model !
Key2 was given a different color by dragging an existing material into the material slot of the 'key' MeshInstance INSIDE THE IA SCENE.
Remember that if you change the material inside the 3d key model scene, all keys in all IA scenes will change !
So far, so good. But now consider this IA :
We have a chest model and a key model combined into one IA.
At the start of the game, the chest is locked. It is waiting for the yellow key (receive). The yellow key needs to be turned (click) to unlock the chest. Once the chest is unlocked, we can click the lid to open the chest.
For each state of the IA, we have to update the relevant HS's.
For instance, at the start of the game, the only active HS is the one that can receive the key. Once the key has been received, that HS has to be disabled, and the HS for the key has to be enabled. Etc.
We also need a bunch of StaticBodies to prevent the player from walking through the chest and key. When the key is invisible, we should make sure that its StaticBody node is disabled as well.
And this is where it gets really complicated : we also need to write those state changes to our save_game file !
If the player inserts the yellow key into the chest lock, and then saves the game and quits ... When he next starts playing the game again, he will be expecting to find the key still in the chest lock !
In my earlier attempts to store all this information in my db_state.json file, I had to write something like this for each IA in the game :
However, I wrote a short function that I could run whenever I had finished creating an IA, that would create that information for me. I only needed to copy/paste it into the JSON file ... and done.
But, it quickly turned out to be a pain to use something like this. It's error prone, and there's a bunch of code required to inactivate/activate HS's and StaticBodies whenever a model becomes (in)visible, or when an IA 'receives' the correct pickup.
The one good thing that came out of this ... mess ... is that I now understand why games do this (annoying thing) :
One less state to save ! =)
In case you were wondering ... this is from Riven (probably the best point&click game of all times).
I don't recall how I stumbled upon this solution, but it seems to be exactly what I had been looking for.
It requires that every IA has exactly 1 AnimationPlayer.
The AnimationPlayer has 1 Animation for each 'state' of the IA ; one of the Animations has to be named 'default' and it contains the initial state of the IA at the start of the game. 'Changing state' now equals 'changing animation'.
And that's it.
No more code required !
Of course, things could be further improved upon because ... well, it quickly becomes tedious to create an animation
with tracks for all MeshInstances (vis/invis), StaticBodies (disabled or not), and HS's (disabled or not) in the IA.
So, I resorted to my previous trick and created a small function that can create the 'invis' and 'default' Animations for me, whenever I need them. The 'invis' Animation is created with MeshInstances invisible and SB's and HS's disabled. The 'default' Animation is the reverse.
Afterwards, the Animations can easily be altered if necessary. For instance, for the chest with key IA, the default Animation should have the key invisible (and its HS and SB disabled).
Adding more Animations is very simple : select an existing Animation, click the 'Animation' button, select 'duplicate', rename. Adjust tracks as required.
If you want to reorder the list of tracks with drag and drop, you first need to press the 'list' btn below the tracks :
Saving IA 'state' has now become ridiculously simple ; I only need to store the name of the Animation and the position the Animation needs to start playing from (seconds) :
To actually set this animation, I use :
As an aside, I've noticed that the AnimationPlayer node seems to behave in a different way than other nodes and resources.
It would make things easier if I could for instance create an AnimationPlayer for one type of chest, and then reuse it slightly modified with another type of chest ... But, unfortunately :
So, I'll have to stick to my own solution for now ...
Up until now, I have been exporting models to OBJ. This was fine for me because I only needed the mesh and the materials.
Some things to be aware of when using OBJ :
Just recently, I decided to try exporting to glTF 2.0, a newer format.
This seems to work really well, even with the default export settings. I've been using the binary format (.GLB), but it seems that if you are using textures, 'glTF separate' might be a better option ( Godot docs).
Some things to be aware of when using glTF :
The final thing I wanted to mention here, is the (confusing) popup that shows up when you double-click imported models (.OBJ or .GLB) in Godot :
What it really means is this :
More information on GOAT (Godot Open Adventure Template) can be found on github. You should definitely watch his talk on YouTube to see all the things it can do, and how easy it is to make your own game with it !