YueCat is a software development framework, largely focused on making games. It allows you to create software entirely without an editor using a flavor of Lua called YueScript.
The API largely mirrors raylib's, with some key differences to make typing simpler.
- FAQ
- 🔢 Version Info
- 💬 A Note on "Typing"
- 🔑 License
- 🔴 OOP?
- 月 YueScript (Scripting)
- 🔨 Building
- ⚙️ Config
- 🤙 Callbacks
- 🔌 Functions
- 🏗️ "Structs"
A: Yue (月) is the word for "moon" in Chinese, and it's pronunciation is [jyɛ].
A: Probably not.
A: Yeah kinda. Except YueCat implicitly uses YueScript. Also, LÖVE2D uses SDL under the hood for rendering, whereas YueCat uses raylib.
Oh, also, we have 3D.
A: Derived from the YueScript repo:
YueScript is a MoonScript dialect. It is derived from MoonScript 0.5.0 and continuously adopting new features to be more up to date.
MoonScript is a language that compiles to Lua. Since original MoonScript has been used to write web framework lapis and run a few business web sites like itch.io and streak.club with some large code bases. The original language is getting too hard to adopt new features for those may break the stablility for existing applications.
So YueScript is a new code base for pushing the language to go forward and being a playground to try introducing new language syntax or programing paradigms to make MoonScript language more expressive and productive.
YueCat Alpha
YueScript v0.29.9
Lua 5.4
raylib 5.5
SDL2 (Controllers only)
Odin dev-2025-10-nightly:7237747
This library also makes use of my extended bindings for rlgl & raymath - rlgl_ex & raymath_ex.
In the future I will also use a custom build of raylib to fix a few features that aren't going to be updated in the base library, unfortunately (no way to disable screenshots, etc).
When I use the phrase "typing" or "making typing simpler" or something like that, I'm referring to reducing the number of keys the user needs to press in order to program the thing they want. I have hand pain problems, so being able to code as much as possible is important to me. I hope these considerations will be useful to you as well.
YueCat is free and open source. It can be used for commerical and non-commerical purposes. It is licensed under the zlib license.
YueCat does not use any built-in OOP (though YueScript does provide basic classes), as it instead opts to largely mirror Odin's style of separating procedures and data. Basically, this is a mix of functional and procedural programming, to keep the implementation (and typing) relatively simple.
For those still curious, here is an example of how converting a color would work in OOP:
hsv = Color.White.ToHSV()
Again, though, YueCat is designed with procedural programming in mind, so this would end up looking like:
hsv = Color.ToHSV(Color.White)
YueCat uses YueScript for compiling scripts before running. It is a flavor of MoonScript that compiles down to Lua.
Here is the documentation. Fair warning: the overview on the site makes it look a lot more complicated than it actually is. The gist is that it makes all variables local (generally considered best practice nowadays) and provides a much cleaner syntax for... well, everything.
Assuming there are no major breaking changes, upgrading to a newer version of YueScript should be as simple as replacing the one in vendor/.
YueCat has no external dependencies, as it uses bindings actively maintained by the Odin language, which are built-in with the compiler. As such, compiling and modifying YueCat is trivial.
Simply open the main directory and execute odin build ., which will compile the host program. In order to run your code, place a file named "main.lua" in the base directory. Use the various engine callbacks to implement the functionality you need.
This repo also includes .vscode tasks to build and run the current version, for testing.
This is a table featuring extended descriptions for each error code in YueCat and what it means.
| Error Code | Description |
|---|---|
| NO_SLASH_RUNTIME | When attempting to find the working directory of YueCat, the folder it is in checks for a path divider (i.e. \ on Windows or / on Linux) and removes the trailing filename. This error is triggered if there is no path divider in the string. As any file on Windows requires a base drive location, this should not be an issue. In the future this error may be ammended to support more operating systems. |
| FIND_EXECUTABLE_PATH | Something went wrong while trying to access the program's location. See the console for more details. |
| SDL_INIT_FAILED | Something went wrong while trying to set up SDL for controllers. |
| YUESCRIPT_COMPILER_FAILED | Something went wrong while running YueScript. Admittedly, this could be any number of things, so look at the console for details. |
| LUA_CRASHED | The Lua VM crashed while running your code. See the console for more details. |
Define this global table during the "Ready" callback to modify how YueCat launches.
Config = {
Window: {
size: Vector2 = Vector2(640, 480)
title: string = "YueCat <version>"
Flags: {
msaa: false -- Antialiasing
borderless: false -- Undecorated window
topmost = false -- Window always stays on top
resizable = false -- Window is resizable
vsync = false -- Vsync
}
}
Audio: {
active: bool = true
}
}
Callbacks are functions that you redefine in order to run your own custom behavior. They are listed in order, from initialization, to main loop, to cleanup.
Engine.Init()
Called immediately after the program is started, but before the window is created (as such, be sure to load assets in Ready() instead).
Used to modify the Config table.
Engine.Ready()
Called immediately after the window is created.
Load assets here.
Engine.Step()
Called during the main loop.
Update your program state here, moving objects/otherwise.
Engine.Draw()
Called during the main loop.
Draw your program state here.
Engine.Cleanup()
Called after the main loop has finished, but just before the program is closed.
Unload your assets here.
Controller.Connected(controller: Controller)
Called when a controller connects. Store the value passed in order to read the controller.
Controller.Disconnected(index)
Called when a controller disconnects. Compare the index to any controllers you're tracking with controller.index == index.
These are global tables of functions.
Engine.GetTime() -> (timeSinceStart: number)
Engine.GetDelta() -> (deltaTime: number)
Engine.GetFPS() -> (currentFps: integer)
Engine.SetFPSTarget(target: integer)
(Unlimited by default)
Mouse.GetPosition() -> (mousePosition: Vector2)
Get the position of the cursor.
Mouse.GetX() -> (xPosition: integer)
Get the x position of the cursor.
Mouse.GetY() -> (yPosition: integer)
Get the y position of the cursor.
Mouse.SetX(xPosition: integer)
Set the x position of the cursor.
Mouse.SetY(yPostion: integer)
Set the y position of the cursor.
Mouse.SetPosition(position: Vector2)
Set the position of the cursor.
Mouse.IsButtonPressed(index: Integer = 0) -> boolean
Checks if the button was just pressed this frame.
Mouse.IsButtonHeld(index: Integer = 0) -> boolean
Checks if the button is held down.
Mouse.IsButtonReleased(index: Integer = 0) -> boolean
Checks if the button was just released this frame.
Mouse.IsOnScreen() -> boolean
Mouse.Left: 0
Mouse.Right: 1
Mouse.Middle: 2
Keyboard.IsKeyHeld(key: Keyboard.Key) -> boolean
Keyboard.IsKeyPressed(key: Keyboard.Key) -> boolean
Keyboard.IsKeyReleased(key: Keyboard.Key) -> boolean
Null: 0
Apostrophe: 39
Comma: 44
Minus: 45
Period: 46
Slash: 47
Zero: 48
One: 49
Two: 50
Three: 51
Four: 52
Five: 53
Six: 54
Seven: 55
Eight: 56
Nine: 57
Semicolon: 59
Equal: 61
A: 65
B: 66
C: 67
D: 68
E: 69
F: 70
G: 71
H: 72
I: 73
J: 74
K: 75
L: 76
M: 77
N: 78
O: 79
P: 80
Q: 81
R: 82
S: 83
T: 84
U: 85
V: 86
W: 87
X: 88
Y: 89
Z: 90
Bracket: {
Left: 91
Right: 93
}
BackSlash: 92
Grave: 96
Space: 32
Escape: 256
Enter: 257
Tab: 258
Backspace: 259
Insert: 260
Delete: 261
Right: 262
Left: 263
Down: 264
Up: 265
PageUp: 266
PageDown: 267
Home: 268
End: 269
CapsLock: 280
ScrollLock: 281
NumLock: 282
PrintScreen: 283
Pause: 284
F1: 290
F2: 291
F3: 292
F4: 293
F5: 294
F6: 295
F7: 296
F8: 297
F9: 298
F10: 299
F11: 300
F12: 301
Shift: {
Left: 340
Right: 344
}
Control: {
Left: 341
Right: 345
}
Alt: {
Left: 342
Right: 346
}
Super: {
Left: 343
Right: 347
}
KBMenu: 348
-- Keypad
Keypad: {
Zero: 320
One: 321
Two: 322
Three: 323
Four: 324
Five: 325
Six: 326
Seven: 327
Eight: 328
Nine: 329
Decimal: 330
Divide: 331
Multiply: 332
Subtract: 333
Add: 334
Enter: 335
Equal: 336
}
-- Android buttons
Button: {
Back: 4
Menu: 5
VolumeUp: 24
VolumeDown: 25
}
SDL2 is used as an underlying base for the controller system. As such, all programs support rebinding with Steam and other various community-made tools, and almost every controller (over two-thousand) will accurate map button names/bindings properly to the simple API.
In order to access information about a controller, you'll need to catch it's "connected" callback and store the passed Controller. Then pass that into any functions that you need, and when a controller is disconnected, you can check if it's the same controller by doing controller.index = index in the "disconnected" callback.
Attempting to read from an index with no connected gamepad will simply return zero-values (false, 0, etc).
Controller.IsButtonHeld(controller: Controller, button: Controller.Button) -> (isHeld: boolean)
Checks if a button is held down on a controller.
Controller.IsButtonPressed(controller: Controller, button: Controller.Button) -> (isPressed: boolean)
Checks if a button was just pressed this frame.
Controller.IsButtonReleased(controller: Controller, button: Controller.Button) -> (isReleased: boolean)
Checks if a button was just released this frame.
Controller.GetAxis(controller: Controller, axis: Controller.Axis) -> (axis: number)
Get the current value of a particular axis on a controller.
Controller.GetVector(controller: Controller, axis: Controller.Vector) -> (vector: Vector2)
Get the current value of a vector on a controller.
Controller.GetName(controller: Controller) -> (name: string)
Get the internal name of a controller. Marginally useful for identifying specific controllers.
Controller.SetDeadzone(controller: Controller, deadzone: number)
A, B, X, Y,
Back,
Guide,
Start,
LeftStick, RightStick,
LeftShoulder,
RightShoulder,
DPad: {
Up,
Down,
Left,
Right
}
Misc1,
Paddle1,
Paddle2,
Paddle3,
Paddle4,
Touchpad
Left,
Right,
Trigger: {
Left,
Right
}
DPad,
Left,
Right
Draw.Clear(color: Color)
Clear the entire screen to a single color.
It is suggested to call this first in most programs, before you draw anything else. Otherwise, graphics from previous frames will stick around (think out-of-bounds in Source games). You can also recreate this behavior by using a full-screen rectangle.
Draw.Line(startPosition: Vector2, endPosition: Vector2, color: Color = Color.Black)
Draw.Rectangle(rectangle: Rectangle, color: Color = Color.Black)
Draw.RectangleLined(rectangle: Rectangle, color: Color = Color.Black)
Draw.Circle(circle: Circle, color: Color = Color.Black)
Draw.CircleLined(circle: Circle, color: Color = Color.Black)
Draw.Triangle(triangle: Triangle, color: Color = Color.Black)
Draw.TriangleLined(triangle: Triangle, color: Color = Color.Black)
Draw.Texture(texture: Texture, postion: Vector2 = Vector2.Zero(), tint: Color = Color.White)
Draw.TexturePro(texture: Texture, source: Rectangle = Rectangle(Vector2(), Texture.GetSize(texture), destinationPosition: Vector2 = Vector2(), destinationSize: Vector2 = GetSize(texture), origin: Vector2 = Vector2(), rotation: number = 0, tint: Color = Color.White)
Draw.Text(text: string, position: Vector2 = Vector2(), color: Color = Color.Black)
Draw.TextEx(font: Font, text: string, position: Vector2 = Vector2(), fontSize: number = Font.GetBaseSize(font), spacing: number = Font.GetSpacing(font), color: Color = Color.Black)
Draw.FPS(position: Vector2 = Vector2())
Draw.BeginScissor(rectangle: Rectangle)
Draw.EndScissor()
Draw.Pixel(position: Vector2 = Vector2(), Color = Color.Black)
Draw.RectangleGradientV(rectangle: Rectangle, topColor: Color, bottomColor: Color)
Draw.RectangleGradientH(rectangle: Rectangle, leftColor: Color, rightColor: Color)
Draw.RectangleGradient(rectangle: Rectangle, topLeftColor: Color, topRightColor: Color, bottomLeftColor: Color, bottomRightColor: Color = Color.Black)
Draw.RectangleRounded(rectangle: Rectangle, roundness: number, segments: integer, color: Color = Color.Black)
Draw.RectangleRoundedLined(rectangle: Rectangle, roundness: number, segments: integer, color: Color = Color.Black)
Draw.Begin3D(camera: Camera3D)
Draw.End3D()
Draw.Cube(cube: Cube, color: Color)
Draw.Box(box: Box, color: Color)
Draw.Sphere(sphere: Sphere, color: Color)
Draw.Cylinder(cylinder: Cylinder, color: Color)
Draw.Grid(slices: integer = 100, spacing: number = 1)
Texture.Load(fileName: string) -> (texture: Texture)
Texture.Unload(texture: Texture)
Texture.GetSize(texture: Texture) -> (size: Vector2)
Texture.GetWidth(texture: Texture) -> (width: integer)
Texture.GetHeight(texture: Texture) -> (height: integer)
Texture.BeginMode(texture: Texture)
Texture.EndMode()
Texture.SetFilter(texture: Texture, filter: Texture.Filter)
Texture.SetWrap(texture: Texture, wrap: Texture.Wrap)
Vertex.Begin(Vertex.Mode)
Vertex.End()
Vertex.Position2(position: Vector2)
Vertex.Position3(position: Vector3)
Vertex.UV(textureCoordinate: Vector2)
Vertex.Color(color: Color)
Vertex.Normal3(normal: Vector3)
Vertex.SetTexture(texture: Texture)
Vertex.EnableWireMode()
Vertex.EnablePointMode()
Vertex.DisableWirePointMode()
Vertex.SetCullMode(face: Vertex.Face)
Vertex.SetLineThickness(thickness: number = 0)
Vertex.GetLineThickness() -> (thickness: number)
Vertex.Face.Front = 0
Vertex.Face.Back = 1
Vertex.Mode.Quads = 0x0001
Vertex.Mode.Lines = 0x0004
Vertex.Mode.Triangles = 0x0007
Audio.Load(fileName: string) -> (audio: Audio)
Audio.Unload(audio: Audio)
Audio.Play(audio: Audio)
Audio.SetMasterVolume(volume: number)
Audio.GetMasterVolume() -> (volume: number)
Audio.GetChannelCount(audio: Audio) -> (channelCount: integer)
Audio.GetFrameCount(audio: Audio) -> (frameCount: integer)
Audio.GetSampleRate(audio: Audio) -> (sampleRate: integer)
Audio.SetVolume(audio: Audio, volume: number)
Audio.SetPitch(audio: Audio, pitch: number)
Audio.SetPan(audio: Audio, pan: number)
Audio.Stop(audio: Audio)
Audio.Pause(audio: Audio)
Audio.Resume(audio: Audio)
Audio.IsAudioPlaying(audio: Audio)
Constructor example:
vec = Vector2(5, 4)
Fields:
x: number = 0
y: number = x or 0
Supports math operations and length (#) operator.
Vector2.Zero() = Vector2()
Vector2.Up = Vector2(0, 1)
Vector2.Down = Vector2(0, -1)
Vector2.Left = Vector2(-1, 0)
Vector2.Right = Vector2(1, 0)
Fields:
x: number = 0
y: number = x or 0
z: number = y or x or 0
Supports math operations and length (#) operator.
Vector3.Zero() = Vector3()
Fields: (All 0-1)
r: number = 0 -- Red
g: number = r or 0 -- Green
b: number = g or r or 0 -- Blue
a: number = 1 -- Alpha
Color.White = Color(1)
Color.Black = Color()
Color.Gray = Color(.510)
Color.LightGray = Color(.784)
Color.DarkGray = Color(.314)
Color.Yellow = Color(.992, .976, 0)
Color.Gold = Color(1, .796, 0)
Color.Orange = Color(1, .631, 0)
Color.Pink = Color(1, .427, .761)
Color.Red = Color(.902, .161, .216)
Color.Maroon = Color(.745, .129, .216)
Color.Green = Color(0, .894, .188)
Color.Lime = Color(0, .620, .184)
Color.DarkGreen = Color(0, .459, .173)
Color.SkyBlue = Color(.4, .749, 1)
Color.Blue = Color(0, .475, .945)
Color.DarkBlue = Color(0, .322, .675)
Color.Purple = Color(.784, .478, 1)
Color.Violet = Color(.529, .235, .745)
Color.DarkPurple = Color(.439, .122, .494)
Color.Beige = Color(.827, .690, .514)
Color.Brown = Color(.498, .415, .310)
Color.DarkBrown = Color(.298, .247, .184)
Color.Transparent = Color(0, 0, 0, 0)
Color.Magenta = Color(1, 0, 1)
Color.RayWhite = Color(.961)
Fields:
h: number = 0 -- Hue [0-360]
s: number = 1 -- Saturation [0-1]
v: number = 1 -- Value [0-1]
a: number = 1 -- Alpha
Fields:
position: Vector2 = Vector2.Zero()
size: Vector2 = Vector2.Zero()
Fields:
position: Vector2 = Vector2.Zero()
diameter: number = 0
Fields:
firstPoint: Vector2 = Vector2.Zero()
secondPoint: Vector2 = Vector2.Zero()
thirdPoint: Vector2 = Vector2.Zero()
Fields:
position: Vector3 = Vector3.Zero()
size: number = 0
Fields:
position: Vector3 = Vector3.Zero()
size: Vector3 = Vector3.Zero()
Fields:
position: Vector3 = Vector3.Zero()
diameter: number = 0
Fields:
position: Vector3 = Vector3.Zero()
diameter: number = 0
height: number = 0
Fields:
firstPoint: Vector3 = Vector3.Zero()
secondPoint: Vector3 = Vector3.Zero()
thirdPoint: Vector3 = Vector3.Zero()
Fields:
position: Vector3 = Vector3.Zero()
target: Vector3 = Vector3.Zero()
up: Vector3 = Vector3.Zero()
fov: number = 45
projection: Camera3D.Projection = Camera3D.Projection.Perspective
Camera3D.Projection:
Perspective,
Orthographic
YueCat is intentionally designed very loosely. If you want a list of objects every frame, create a table and table.insert "object" tables into it, with step and draw fields, then in the respective callbacks just do a loop like:
for obj in *objs
obj.FUNCTIONNAME()
Thank you for reading the docs and your interest in YueCat!
Made with 💗 in New England <(˶ᵔᵕᵔ˶)>
Various inspirations for this project:
- LÖVE2D
- Balatro
- stabyourself.net
- GameMaker
- UNDERTALE
- DELTARUNE
- Cave Story
- raylib
- Godot
- RPG Maker
- Ib
- The Witch's House
- Basic
- Blitz BASIC
- SCP: Containment Breach
- Clickteam Fusion
- Direct3D
- DirectDraw
- Csound
- SFML
- GLFW
This is a rough reference of the YueScript/MoonScript language, in an attempt to put everything more in one place.
tbl = {
variable_name: "variable value"
name2: 1.3
tbl2 = {
thing = 2
}
}
tbl = {
1,
"element",
3.4,
{
6
},
false
}
Argument parenthesis () are optional.
Scope is controlled by indents.
function_name = (arguments) ->
//do stuff here, return value
Function implicitly passes self when called.
function_name = (arguments, other_stuff) =>
// do stuff here, access `self` as necessary to affect state.
+=, -=, *=, /=.