-
Notifications
You must be signed in to change notification settings - Fork 119
Developing Games Using Frogatto's Engine: Part 1
This is the first page of a walkthrough which will show just how to do that. This walkthrough is targeted at people with some technical knowledge — ideally, you:
- are comfortable running Frogatto from the command line
- can pass arguments to it, such as
--edit-and-continue
- can edit plain text files
- have some general coding knowledge, such as Python or C++
I will also assume that you have some level of familiarity with the Frogatto Editor (accessible by pressing
ctrl
-e
while in a game of Frogatto).
We are going to develop a simple platforming character, Elisa, our sword-wielding heroine!
Frogatto's engine is called Anura. Each game that uses Anura has its own module. We will develop Elisa as a new module, so we don't have to interfere with our copy of the Frogatto module.
To create the new module for Elisa, we first make a new directory in our Frogatto install. In the modules
folder, there is a folder called frogatto
. We will create a folder beside it, called elisa
. In that folder, we create a new file called module.cfg
using our favourite text editor. We will add the following text to it:
{
id: "elisa",
name: "Elisa",
min_engine_version: 1.4,
arguments: ["--edit-and-continue"],
dependencies: ["frogatto"],
player_type: "obj player_controlled",
}
The file we just made, ~modules/elisa/module.cfg
, is our module configuration file. It has a few fields:
-
id
: The internal name of our module. Must be set to our folder name. -
name
: The external name of our module, displayed as the window title. Can be pretty much anything. -
min_engine_version
: The version of Anura we're using. -
arguments
: A list of default command-line args.--edit-and-continue
make it so that when we make a mistake, the engine pops up an editor so we can fix it instead of just exiting and letting us deal with it ourselves. -
dependencies
: A list of one item. By setting this to "frogatto", we're telling the engine to look for files first in our module Elisa, and then in Frogatto's module. This means we can build our game piece-by-piece, using Frogatto assets as placeholders until we make our own. -
player_type
: Required by the Frogatto module to run.
If you encounter a problem you can not solve, you'll need to get some help. There's no shame in it - us on the project all often have to seek help, and we've been doing this for years! The best way to get help is to hit us up on our Discord chat. If you've got a more complex problem, we recommend making a post in our forums instead. (It's probably best to link to it on the Discord after, though – we'll see it faster that way. 😉)
In our game, we'll want Elisa to jump from platform to platform, battling vampire bats, collecting coins, and avoiding traps of spikes. Each of these things — our heroine, the platforms, bats, coins, and spikes — are examples of game objects that we must define to the game engine.
An object's appearance and behaviour is specified in a single .cfg file that will be placed under ~modules/elisa/data/objects/
. It can be placed directly in this directory or in any sub-directory, so if you're making a substantial game, it’s a good idea to categorize the different objects into different directories.
An object also needs a sprite sheet, a .png file which contains its art. Multiple objects can share a single sprite sheet, or one complex object might have its images split across multiple sprite sheets. All our sprite sheets will go in ~modules/elisa/images/
– this is the directory Anura will search for our images.
We'll start by defining our heroine, Elisa.
Here is the sprite sheet for her, which goes in ~modules/elisa/images/characters/elisa-spritesheet1.png
.
Both the background colour (hex #6f6d51
) and the red rectangles (hex #f9303d
) are special colours. The Anura engine treats as fully transparent. This allows you to organize your sprite sheets and indicate frame boundaries. This isn’t strictly necessary – Anura fully supports the alpha channel which is used for transparency as well. (I recommend using the solid colours when you can, though - they make stray pixels much more apparent.)
Note also the size of the image. In Anura, for compatibility with different graphics cards, we recommend a spritesheet's dimensions be an even number smaller than 2048. For example; 100
, 500
, 512
, and 1024
are all valid dimensions.
Now it’s time to start defining Elisa’s behavior. We create a file in ~modules/elisa/data/objects/characters/elisa.cfg
:
{
id: "elisa",
prototype: ["player_controlled"],
editor_info: { category: "elisa" },
animation: [{
id: "stand",
image: "characters/elisa-spritesheet1.png",
rect: [4,4,57,57]
}]
}
This is a fairly bare-bones definition of an object. Let’s see what it tells us:
- The object’s ID is 'elisa'. This must correspond to the filename (
elisa.cfg
) - Elisa's prototype matches what we specified in module.cfg for Frogatto, which means she inherits some behaviour from Frogatto that makes her a playable character in the game. In a single player game, there may only be one object in existence at a time that has this prototype. Many games will only have one object that is player controlled.
- elisa goes into an editor category called 'elisa'. This means when you want to add an elisa object to a level in the Frogatto editor, you will find her under a category called elisa.
- All objects must have a list of one or more animations. An animation consists of one or more frames. Right now we just have a single frame animation for elisa. Note that the rect: [4,4,57,57] specifies the top-left most sprite in her sprite-sheet.
This definition of Elisa is written in Frogatto’s Object Notation (FSON) — basically JSON with a few additional features. FSON is used extensively throughout Frogatto as a data language to define the properties of objects.
Now we can try out the Elisa object in-game. One of the nice things about Frogatto’s module system is when you run a module you still have access to Frogatto core game data, when you’re first testing your game you can do so, mixing it with Frogatto’s assets, before all yours are fully developed.
We invoke the game with the --module command line argument: --module=elisa
. (eg, on linux, ./anura --module=elisa
) This runs the game, loading our module. We press ctrl+e to open the editor, and then we can easily find and add the elisa object to a game of Frogatto:
Now, at the moment, all the elisa object does is stand there, frozen still. We haven’t given her any other behavior yet. If we look at the Elisa spritesheet, we notice that the top three images are all part of a standing animation, meant to make Elisa move a little, even when standing.
Note how the frames are of uniform size and spacing. This is how the engine expects frames of the same animation to be laid out. An animation definition may contain a specification of how many frames there are in the animation, along with the duration, in game cycles, of each frame. A padding may be specified to show how many empty pixels there are between the frames on the sprite sheet. In this case we have three pixels between frames.
This animation also isn’t meant to be played to the end, and then started over. Instead, it’s meant to be played forward (Elisa inhaling) then played in reverse (Elisa exhaling). Some animations are designed to be played forwards and then backwards, and the engine provides a special reverse property to allow for that.
A little note at this point: we can continue to edit Elisa’s code using our text editor of choice. We can also use Frogatto’s built-in editor by pressing the “Code” button in the editor. If we use the in-game code editor, we will see Elisa update instantly as we make changes to the code. Even if we use an external editor, the game engine will update the changes we make without us having to exit and restart Frogatto! This feature is enabled as long as we are in the Frogatto editor.
Here is the code to turn the still standing frame into a proper animation:
{
id: "elisa",
prototype: ["player_controlled"],
editor_info: { category: "elisa" },
animation: [{
id: "stand",
image: "characters/elisa-spritesheet1.png",
rect: [4,4,57,57],
pad: 3,
duration: 5,
frames: 3,
reverse: true
}]
}
After making this change, when we play the game, Elisa will play through her animation, but only once. Then she will freeze again, staying in the last frame of her animation. The game engine doesn’t know what to do with her next without some instruction. It might seem obvious: “she should start the animation over again!” — but it’s not so obvious. When elisa finishes an animation involving swinging a sword, we wouldn’t want her to just play that animation over again. Instead, we must provide instruction on what to do, and we provide that instruction through events.
When we think about the behaviour we want to specify for an object, much of it revolves around us wanting to specify how the object behaves when something happens. What should the object do when it lands on the ground? When it collides with a bat? When the player presses a button? When an amount of time elapses? When the level starts? These are all events. In this case, we are interested in what Elisa should do when her stand
animation finishes.
We specify what should happen when an event occurs in an event handler. An event handler is a property in FSON which contains a formula that is evaluated when the event occurs. The formula is written in Frogatto Formula Language (FFL). This formula is able to query the current state of the game, and then return some commands. These commands that the formula returns will be executed by the game engine.
In this case, all we want to do is specify that when the animation finishes, it should be played over from the start. That, is quite simple:
{
id: "elisa",
prototype: ["player_controlled"],
editor_info: { category: "elisa" },
on_end_stand_anim: "set(animation, 'stand')",
animation: [{
id: "stand",
image: "characters/elisa-spritesheet1.png",
rect: [4,4,57,57],
pad: 3,
duration: 5,
frames: 3,
reverse: true
}]
}
Whenever the end_stand_anim
event occurs on this object, the event handler is invoked, and it returns a command to set the object’s animation to stand
— which will start the stand animation over from scratch.
Now, let’s make it so Elisa can swing her sword. To do this, we’ll need to define her sword swing animation. We’ll also need to have an event to make her enter the animation. A ctrl_tongue
event will be sent when the s
button is pressed (due to this normally being the button which activates Frogatto’s tongue in Frogatto). We will use is to make Elisa swing her sword. However, Elisa can’t swing her sword any time. For instance, if she’s already swinging her sword, another press of s
should be ignored, so we’ll add some logic to make it so she can only swing her sword if she’s currently in her stand
animation:
{
id: "elisa",
is_human: true,
editor_info: { category: "elisa" },
on_end_stand_anim: "set(animation, 'stand')",
on_end_attack_anim: "set(animation, 'stand')",
on_ctrl_tongue: "if(animation = 'stand', set(animation, 'attack'))",
animation: [{
id: "stand",
image: "characters/elisa-spritesheet1.png",
rect: [4,4,57,57],
pad: 3,
duration: 5,
frames: 3,
reverse: true
},{
id: "attack",
image: "characters/elisa-spritesheet1.png",
rect: [5,180,93,240],
pad: 3,
duration: 5,
frames: 6,
frames_per_row: 3
}]
}
Note how ctrl_tongue
has its code enclosed in an if statement, so the event handler will only cause anything to happen if Elisa is in her stand animation. If she’s already in her attack animation, the event won’t do anything. This is a simple example of the logic capabilities of FFL. Next time I want to explore FFL in much more detail.
I think this is enough for our first lesson — we’ve added an object, and made her perform a few basic actions. We are a little lacking in documentation on the Frogatto engine right now, and I’m hoping this series can improve it, so please feel free to reply with any questions or comments on the forums!
More help can be found via chat in Frogatto's Discord server, or by posting on the forums. This wiki is not a complete reference. Thank you for reading! You're the best. 🙂