Funny Wars Platformer Lua Documentation
This site contains everything necessary for a team of people to build a thing codenamed “Funny Wars”.
If you found this on Google or otherwise do not expect this to be useful, as of writing this is relevant to a private project.
Used Tools
You probably already know which tools the project is using at this point but fuck it I’ll document them here for posterity and give links.
The engine itself is done in C++/Rust but the game allows interacting with the engine via Lua, which is the most important part here as this is what the levels are made with.
Maps are done with the Tiled Editor
You should use Aseprite for images.
Assets Directory
The assets directory is the core place for any content the game needs to load at runtime. When the game is packaged, this will likely be a .zip file that the game automatically extracts, but either way, the idea will remain the same.
Most of the folders in assets can be named anything, and files can be anywhere, with the exception of a few folders that the game expects to be named appropriately.
levels- See Level Structurescripts- See Global Scriptsobjects- See Level Objects
Level Objects
Objects are created by creating a folder in level/objects. As each folder corresponds to an object that can be referenced from within Tiled, the name of the folder == the object name as used in Tiled.
From there, an Object must have a Lua file titled main.lua within it. This file is the first file that the engine executes when it loads the object. From here, you can use Lua’s require function to include other files in the same folder (or files in subfolders), and the engine will execute them appropriately, allowing you to split up the object into multiple files. This combination of files, whatever it may be, is the scope in which an object can access/set its own variables. This scope is local to each instance of an object; changes to variables are not shared with objects of the same type.
Each Object has certain variables that are always created by the engine, such as their position and their size. These are presented to scripts as fields within a global variable called Object. This same type is also relevent to an object’s collision callback (see below) as it’s how you get information on what it collides with. The Object global also has functions that can be used with your object (i.e. Object.get_collision_info for checking for an object’s collision with a tile), and the Object type received in the collision function has its own methods that can be executed (this is how objects can communicate in the collision function, they can get/set each other’s variables via Object:get and Object:set). However, these are not mutually exclusive; you can’t use the Object functions on an object you get in the collision function, and you can’t use the Object’s methods on the global Object.
Objects are also currently* responsible for loading textures with Renderer:load_texture. They can draw them manually via Renderer:draw_texture_opt, but the engine will automatically draw objects that don’t override the draw function (see below).
*The plan is to eventually have objects have configuration files which can set this too, allowing for objects that only have a tick function.When in doubt, see the player object’s main.lua, which has everything wrapped up neatly and can thus serve as good example code.
Callbacks
To give an object functionality, there are several functions (“callbacks”) you can define anywhere within your object’s Lua scope that will get called by the engine appropriately:
These should idomatically be put in main.lua, but they can be placed in other files.
All of them are optional. Your main.lua file can be completely blank (though it does have to exist), resulting in an invisible object that does nothing.
init function
Called upon the object’s creation.
--- @param win Window
--- @param renderer Renderer
function init(win, renderer)
-- ...
end
tick function
Called every tick for every object in the level.
---@param win Window
-- tick function
function tick(win)
-- ...
end
draw function
Called for every object on screen. This is where you draw a texture at your object’s position.
--- @param renderer Renderer
--- @param cameraPos Vec2
function draw(renderer, cameraPos)
-- ...
end
Notice that camera position is passed in. If you want to draw something at your object’s current position, you need to do something like:
-- textureOptions being a TextureOptions in this case.
textureOptions.dest.x = (Object.position.x - cameraPos.x)
textureOptions.dest.y = (Object.position.y - cameraPos.y)
If this function is not present in your script, the above equations will be applied to Object.texture_options, and the object will be drawn automatically using them.
on_collide function
Called upon the object colliding with another object.
---@param obj Object
function on_collide(obj)
-- ...
end
Documented Level Objects
This documentation file is specifically for level objects. Generally this isn’t accessibly as normal Lua types, but instead via methods like Object:get. This also concerns how these objects are set up in the Tiled editor.
bigdoor
Big door that takes the player to another level when they press the up key.
Custom Properties:
level(string): The .tmx file to load the player into. Path must be relative to the assets directory, i.e.levels/prophets_castle/room_1.tmx.
player
Player Object
water
Water Object
music_queue
Planned to have this be an invisible object that starts a music track when you walk through it. Currently unimplemented.
Level Structure
Level Structure
Each level in the game is to be placed in its own folder within assets/levels. There are two files it is expected to have with a given name. Anything else can go in here with any name, and similarly the folder can be titled anything.
-
entry_point.tmx: The .tmx file that the game loads when the level is loaded. It can point to other
.tmxfiles with their own names, but it must be named this. A level is not considered valid and will not show up in the level select without this. -
name: The contents of this file contain the name of the level as shown on the Level Select screen. If this file is missing, the game will display the filename instead.
Global Scripts
assets contains a folder known as scripts which can be used for global Lua scripts loaded for every object.
As of writing this, there is a blank file in it called strict.lua, and the feature as a whole is unused. If this is ever used, this file is to be updated.
References
So the engine has Object right. And Lua has this thing called the garbage collector that does automatic memory management. The engine owns the fields within Object, and the lua context should never try to release them because otherwise that interferes with the engine’s ability to operate. So the engine needs to ensure that this garbage collector does not release any of those fields. The solution we go with is “references”: wrappers around certain types that are used identically to their regular types, but they are set up to never get dropped.
For the most part, this is only relevant at a low level, such to the point where this page would be useless. References carry the same methods/fields with their non-reference counterparts, and the same behavior, and thus there is no difference. However, because they are technically different types internally, there is going to be a known bug where certain engine functions will reject the reference types. Said engine functions are few and far between, especially since, again, this rule doesn’t apply to any methods of the classes. The one that comes to mind as of writing this, for example, is Renderer:draw_texture_opt; if you find yourself calling this function you must do it with a TextureOptions that You created, NOT Object.texture_options, otherwise you will get an error because that is a reference.
And this is why future projects I work on I will search for a different scripting language other then Lua if we need it :) or we’ll just write in native code haha idfk
Object
CollisionInfo
Collision info gotten by Object.get_collision_info
Properties
collides : boolean
semisolid : boolean
Object
Object placed within the map. Object is a little unique compared to other types because of the two ways to use it. Using its functions (i.e. Object.get_collision_info) will act upon the calling object. However, it can also be gotten via the collision callback, and from there, the methods can be used. These are not mutually exclusive; you cannot call i.e. get_collision_info on an Object obtained via the collision callback, and vice versa.
Properties
position : Vec2
Two component Vector
size : Vec2
Two component Vector
name : string
Functions
get_collision_info(x : number, y : number)
Get information about the Object’s collision the current tile
get_prop_bool(key : string)
->boolean
Get a custom property from the Object (bool)
get_prop_color(key : string)
->Color
Get a custom property from the Object (Color)
get_prop_float(key : number)
->boolean
Get a custom property from the Object (float)
get_prop_int(key : integer)
->boolean
Get a custom property from the Object (int)
get_prop_string(key : string)
->string
Get a custom property from the Object (string)
set_map(path : string)
->string
Change the current map.
get(self, name : string)
->boolean | string | number | nil
Get a global variable defined within the Object’s scope.
Type Limitations
This works, obviously, because each Object has its own Lua state where its variables are stored. But a very unfortunate limitation of Lua is that complex types such as tables and userdatas (most important because this is the underlying type for every engine type, i.e. Vec2s and Textures) cannot be passed between two Lua states.
This limits this to only the following primitives: boolean, integer, number, string, or nil.
set(self, name : string, value : boolean | string | number | nil)
->boolean | string | number | nil
Set a global variable defined within the Object’s scope. If the variable is undefined, a new one will be set within that object.
Type Limitations
This works, obviously, because each Object has its own Lua state where its variables are stored. But a very unfortunate limitation of Lua is that complex types such as tables and userdatas (most important because this is the underlying type for every engine type, i.e. Vec2s and Textures) cannot be passed between two Lua states.
This limits this to only the following primitives: boolean, integer, number, string, or nil.
Renderer
Renderer
Interface for drawing to the window.
Functions
clear_background(self, color : Color)
Clear the background to the given color.
Note that this is for use within texture render mode! The actual screen is cleared on its own.
fps(self)
->number
Return the FPS the renderer is running at
set_target_fps(self, fps : number)
Set a cap for the renderer’s FPS.
load_texture(self, path : string)
->Texture
Load a texture from the given file name (relative to the assets directory, i.e.
objects/bigdoor/bigdoor.png).Note that the final output is converted to RGBA8
load_texture_from_image(self, image : Image)
->Texture
Load a texture from the given image.
Note that the final output is converted to RGBA8
load_texture_blank(self, width : number, height : number)
->Texture
Load a blank RGBA8 texture with the given width and height.
load_texture_blank_ex(self, width : number, height : number, length : number, format : TextureFormat)
->Texture
Load a blank texture with the given width and height, length of data, and format.
I kind of just absent-mindedly bound this to Lua, you probably don’t want this. ~ ioi
update_texture(self, texture : Texture, data : [number])
Upload data to the GPU, replacing the texture’s contents. MUST be a matching (width*height*pitch) array (pitch usually being 4 unless you loaded a texture with a custom format).
draw_texture(self, texture : Texture, source : Rectangle, dest : Rectangle, color : Color)
Draw a given texture to the screen.
draw_texture_opt(self, opts : TextureOptions)
Draw a given texture to the screen with the provided options.
begin_texture_render(self, texture : Texture)
Begins a “texture render mode”, in which any rendering operations after this to be drawn to texture. This lasts until you call end_texture_render.
The texture in question is pushed to a stack of textures and whichever one is at the forefront is what’s rendered to. This allows you to stack the call, and then appropriately pop the stack.
This function is also used internally to lock the game to a certain resolution, so if you forget to call end_texture_render, you will fuck up the game output.
end_texture_render(self)
End previous texture render switch and fall back to whatever was being rendered to before
draw_text(self, font : Font, text : string, pos_x : number, pos_y : number, font_size : number, color : Color)
Types
Global
Constants
ImageFormat
{ PNG: integer = -1, JPEG: integer = -1, GIF: integer = -1, WEBP: integer = -1, PNM: integer = -1, TIFF: integer = -1, TGA: integer = -1, DDS: integer = -1, BMP: integer = -1, ICO: integer = -1, HDR: integer = -1, OPEN_EXR: integer = -1, FARBFELD: integer = -1, AVIF: integer = -1, QOI: integer = -1, PCX: integer = -1, UNSUPPORTED: integer = -1, }
ImageFilterType
{ NEAREST: integer = -1, TRIANGLE: integer = -1, CATMULL_ROM: integer = -1, GAUSSIAN: integer = -1, LANCZOS_3: integer = -1, }
TextureFormat
{ UNDEFINED: integer = -1, R8G8B8A8_SINT: integer = -1, R8G8B8A8_UINT: integer = -1, R8G8B8A8_UNORM: integer = -1, R8G8B8A8_SNORM: integer = -1, A8B8G8R8_UNORM_PACK32: integer = -1, A8B8G8R8_SNORM_PACK32: integer = -1, A8B8G8R8_SINT_PACK32: integer = -1, A8B8G8R8_UINT_PACK32: integer = -1, R16G16B16A16_SFLOAT: integer = -1, R16G16B16A16_SINT: integer = -1, R16G16B16A16_UINT: integer = -1, R32_SFLOAT: integer = -1, R32_SINT: integer = -1, R32_UINT: integer = -1, R32G32_SFLOAT: integer = -1, R32G32_SINT: integer = -1, R32G32_UINT: integer = -1, R32G32B32A32_SFLOAT: integer = -1, R32G32B32A32_SINT: integer = -1, R32G32B32A32_UINT: integer = -1, R8G8B8A8_SRGB: integer = -1, }
TextureFilter
{ POINT: integer = -1, NEAREST: integer = -1, }
TextureWrap
{ REPEAT: integer = -1, CLAMP: integer = -1, MIRROR_REPEAT: integer = -1, MIRROR_CLAMP: integer = -1, }
KeyboardKey
{ NULL: integer = -1, APOSTROPHE: integer = -1, COMMA: integer = -1, MINUS: integer = -1, PERIOD: integer = -1, SLASH: integer = -1, ZERO: integer = -1, ONE: integer = -1, TWO: integer = -1, THREE: integer = -1, FOUR: integer = -1, FIVE: integer = -1, SIX: integer = -1, SEVEN: integer = -1, EIGHT: integer = -1, NINE: integer = -1, SEMICOLON: integer = -1, EQUAL: integer = -1, A: integer = -1, B: integer = -1, C: integer = -1, D: integer = -1, E: integer = -1, F: integer = -1, G: integer = -1, H: integer = -1, I: integer = -1, J: integer = -1, K: integer = -1, L: integer = -1, M: integer = -1, N: integer = -1, O: integer = -1, P: integer = -1, Q: integer = -1, R: integer = -1, S: integer = -1, T: integer = -1, U: integer = -1, V: integer = -1, W: integer = -1, X: integer = -1, Y: integer = -1, Z: integer = -1, LEFT_BRACKET: integer = -1, BACKSLASH: integer = -1, RIGHT_BRACKET: integer = -1, GRAVE: integer = -1, SPACE: integer = -1, ESCAPE: integer = -1, ENTER: integer = -1, TAB: integer = -1, BACKSPACE: integer = -1, INSERT: integer = -1, DELETE: integer = -1, RIGHT: integer = -1, LEFT: integer = -1, DOWN: integer = -1, UP: integer = -1, PAGE_UP: integer = -1, PAGE_DOWN: integer = -1, HOME: integer = -1, END: integer = -1, CAPS_LOCK: integer = -1, SCROLL_LOCK: integer = -1, NUM_LOCK: integer = -1, PRINT_SCREEN: integer = -1, PAUSE: integer = -1, F1: integer = -1, F2: integer = -1, F3: integer = -1, F4: integer = -1, F5: integer = -1, F6: integer = -1, F7: integer = -1, F8: integer = -1, F9: integer = -1, F10: integer = -1, F11: integer = -1, F12: integer = -1, LEFT_SHIFT: integer = -1, LEFT_CONTROL: integer = -1, LEFT_ALT: integer = -1, LEFT_SUPER: integer = -1, RIGHT_SHIFT: integer = -1, RIGHT_CONTROL: integer = -1, RIGHT_ALT: integer = -1, RIGHT_SUPER: integer = -1, KB_MENU: integer = -1, KP_0: integer = -1, KP_1: integer = -1, KP_2: integer = -1, KP_3: integer = -1, KP_4: integer = -1, KP_5: integer = -1, KP_6: integer = -1, KP_7: integer = -1, KP_8: integer = -1, KP_9: integer = -1, KP_DECIMAL: integer = -1, KP_DIVIDE: integer = -1, KP_MULTIPLY: integer = -1, KP_SUBTRACT: integer = -1, KP_ADD: integer = -1, KP_ENTER: integer = -1, KP_EQUAL: integer = -1, }
MouseButton
{ LEFT: integer = -1, RIGHT: integer = -1, MIDDLE: integer = -1, SIDE: integer = -1, EXTRA: integer = -1, FORWARD: integer = -1, BACK: integer = -1, }
MouseCursor
{ DEFAULT: integer = -1, ARROW: integer = -1, IBEAM: integer = -1, CROSSHAIR: integer = -1, POINTING_HAND: integer = -1, RESIZE_EW: integer = -1, RESIZE_NS: integer = -1, RESIZE_NWSE: integer = -1, RESIZE_NESW: integer = -1, RESIZE_ALL: integer = -1, NOT_ALLOWED: integer = -1, }
Color
R8G8B8A8 (32-bit RGBA) Color
Properties
r : number
g : number
b : number
a : number
Functions
from_rgb(r : number, g : number, b : number, a : number)
->Color
lightgray()
->Color
gray()
->Color
darkgray()
->Color
yellow()
->Color
gold()
->Color
orange()
->Color
pink()
->Color
red()
->Color
maroon()
->Color
green()
->Color
lime()
->Color
darkgreen()
->Color
skyblue()
->Color
blue()
->Color
darkblue()
->Color
purple()
->Color
beige()
->Color
brown()
->Color
darkbrown()
->Color
white()
->Color
black()
->Color
transparent()
->Color
magenta()
->Color
Font
Image
Rectangle
Properties
x : number
y : number
width : number
height : number
Functions
new(x : number, y : number, width : number, height : number)
Texture
Functions
width(self)
->number
height(self)
->number
set_filter(self, filter : TextureFilter)
set_wrap(self, wrap : TextureWrap)
bounding_box(self)
set_shader(self, renderer : Renderer, shader : string)
Set a texture’s shader to one of any preset shaders.
TextureOptions
Options used for drawing a texture
Properties
texture : Texture
source : Rectangle
dest : Rectangle
flippedX : boolean
flippedY : boolean
origin : Vec2
Two component Vector
rotation : number
tint : Color
R8G8B8A8 (32-bit RGBA) Color
Vec2
Two component Vector
Properties
x : number
y : number
Functions
new(x : number, y : number)
->Vec2
Vec3
Three component Vector
Properties
x : number
y : number
z : number
Functions
new(x : number, y : number, z : number)
->Vec3
Vec4
Four component Vector
Properties
x : number
y : number
z : number
w : number
Functions
new(x : number, y : number, z : number, w : number)
->Vec4
Window
Window
Application window
Functions
width(self)
->number
Return the window’s width.
height(self)
->number
Return the window’s height
is_fullscreen(self)
->boolean
Return whether the window is fullscreen.
is_hidden(self)
->boolean
Return whether the window is hidden.
is_minimized(self)
->boolean
Return whether the window is minimized.
is_maximized(self)
->boolean
Return whether the window is maximized.
is_focused(self)
->boolean
Return whether the window is focused.
toggle_fullscreen(self)
Togggle the window between fullscreen and not.
toggle_borderless_windowed(self)
Togggle the window having a border or not.
maximize(self)
Maximize the window.
minimize(self)
Minimize the window.
restore(self)
Undo a maximize for the window.
set_window_title(self, title : string)
Set the window’s title.
set_window_min_size(self, width : number, height : number)
Set the minimum size the window can be at.
set_window_max_size(self, width : number, height : number)
Set the maximum size the window can be at.
set_window_size(self, width : number, height : number)
Set the window’s size.
is_key_pressed(self, key : KeyboardKey)
Check whether key was pressed.
is_key_down(self, key : KeyboardKey)
Check whether key is being held down.
is_key_released(self, key : KeyboardKey)
Check whether key is being released.
is_mouse_button_pressed(self, button : MouseButton)
Check whether mouse button was pressed.
is_mouse_button_down(self, button : MouseButton)
Check whether mouse button was held down.
is_mouse_button_released(self, button : MouseButton)
Check whether mouse button was released.
get_mouse_position(self)
->Vec2
Get the mouse position relative to the window.
get_mouse_delta(self)
->Vec2
Get the delta of the mouse’s movement.
get_mouse_wheel_move(self)
->Vec2
Get the movement of the mouse wheel.
set_mouse_cursor(self, cursor : MouseCursor)
Set mouse cursor.
open_url(self, url : string)
Open the given URL in the user’s default browser.