No description
Find a file
2026-01-11 02:15:17 +03:00
media change logo 2025-12-13 06:41:33 +03:00
src Revised readme.md file 2025-12-23 21:35:33 +03:00
tests Added Javascript section to the cheatsheet. 2025-12-22 12:10:32 +03:00
.gitignore update readme.md 2025-12-06 03:42:11 +03:00
config.nims first commit 2025-12-03 05:02:15 +03:00
kirpi.nimble Update version value of the nimble file 2026-01-11 02:15:17 +03:00
LICENSE added kirpi_app_template info to readme.md 2025-12-05 07:32:07 +03:00
minshell.html Revised minshell.html file 2025-12-15 05:33:29 +03:00
readme.md Update readme.md 2026-01-07 02:15:36 +03:00

logo

Kirpi aims to be an elegant framework for the Nim language, enabling you to create 2D creative coding projects or awesome 2D games. The graphics approach is inspired by Löve2D (LÖVE2D users will feel right at home).

Currently, it uses Naylib (the most maintained and crowded Nim wrapper for Raylib) as its backend. Consequently, it can target Web/WASM,Linux, Windows, macOS, and Android. In the future, new backends might be added or existing ones might change; however, the Kirpi API you write today shall remain the same. Your applications and modules will continue to function thanks to this flexible and pragmatic approach.

Why kirpi?

  • Minimal API: Kirpi features a carefully curated and purposeful abstraction. While it allows you to do a lot, there is very little you actually need to learn. With basic programming knowledge and a quick glance at the Nim docs, you can jump right in.
  • A Great (or Aspiring to be Great) 2D Graphics API: It covers almost every option found in vector software, except for boolean operations (which you can implement as an independent module). Visualizing your code is both fun and takes only seconds. Plus, it aims to do this while providing options for maximum performance.
  • Built for Building Modules and Tools: Kirpi was designed as a core API that is fun to use standalone, as a concept of how 2D development could be better. However, it also aims to stay out of your way if you want to do more. Can you develop external modules for things missing in Kirpi? We actually encourage it—go ahead. Is Kirpi blocking you from doing so? Open an issue and lets discuss it.
  • Very small Web builds for a WASM-based runtime: Performance-first builds of an empty project ship at ~750 KB uncompressed and ~350 KB zipped, comparable to many JavaScript game frameworks. It also features a well-written JavaScript bridge for adapting SDKs and plugins.
  • Solid Support on Supported Platforms: There is a project template with configurations that handle most of the heavy lifting for Android and other platforms. Improvements regarding platform configurations happen directly within the project template repository.
  • Survival and Backward Compatibility: Kirpi decouples its API and workflow from the backend. By keeping the API consistent while remaining pragmatic about the backend, it ensures that games and tools developed with Kirpi remain functional and portable for years. If a backend issue arises, it's easy to fix or swap.

Why Nim?

  • Write your games and modules elegantly, as if using a scripting language, but get C-like performance. With Nims ARC/ORC memory management—similar to C++'s RAII but more modern—you don't just get massive performance; you also don't have to worry about memory safety most of the time. No need to write logic in a script and extensions in C/C++ for performance. Just sit down and write everything in one language: Nim.

  • Get incredibly small binaries on almost every platform.

  • Benefit from a mature standard library that answers most of your needs.

  • Nim is a general-purpose language that can target C, C++, JS, and Objective-C, making it useful far beyond just game development.

There are Great Game Engines Out There Today

I completely agree. As the creator of this project, I also prefer popular engines for projects where the scale and requirements demand it. However, after over 10 years of game development, I can tell you this: Hey friend, most 2D games don't actually need more than a framework like this and a few simple modules. You don't need to fight giant physics engine APIs just to make a simple platformer. You don't need to carry the weight of scripting runtimes and massive abstraction layers for a 3-match puzzle game. You don't have to make players wait to download dozens of megabytes for small-to-midscale games made with Unity or Godot. While watching people brag about rendering 10-15k bullets using crazy optimizations and C++ plugins in certain engines, here, a "magazine" of at least 70k-80k bullets is waiting for you.

On the other hand, this framework (and even Nim itself) has such a low learning curve that you won't even think like you're choosing it instead of your current engines and languages. You can easily fit all of this right alongside what you already have.

Getting Started

Install kirpi easily with nimble install kirpi.

To compile a Kirpi project for your desktop platform, the Nim toolchain and compiler are sufficient. The command nim c -r game.nim will do the job.

#game.nim
import kirpi

var sampleText:Text

proc load() =
    sampleText=newText("Hello World!",getDefaultFont())

proc update(dt: float) =
    if isKeyPressed(KeyboardKey.Escape):
        quit()

proc draw() =
    clear(Black)
    setColor(White)
    draw(sampleText,100,100)

run( "sample game",load,update,draw)

However, for smooth builds across all supported platforms and an ideal folder structure, we recommend using the kirpi_app_template repository. This repo also includes extensively customized compiler configuration files, thanks in part to the Naylib community, which automate nearly everything for Android builds. Additionally, we provide customized Emscripten configurations for Web builds. In short, you can deploy your project to Android with just a few commands, and to other platforms with a single command. Detailed build instructions are available in our template repository.

Learning Samples

Flappy Bird

🦔 Repo | 🎮 Play Now
Snake

🦔 Repo | 🎮 Play Now
Simple Platformer

🦔 Repo | 🎮 Play Now

Documentation

The examples repo is here.

Kirpi doesnt have fancy tutorials yet. But honestly, the entire API is basically the cheatsheet below. We werent joking about the simplicity of the API.

Cheatsheet
### CALLBACK FUNCTIONS
load()     # to do one-time setup of your game
update(dt:float)   # which is used to manage your game's state frame-to-frame
draw()     #  which is used to render the game state onto the screen
config(settings:AppSettings) = # which is used to config the game app
    #all properties with default values.

    settings.fps=60
    settings.printFPS=false
    settings.printFrameTime=false

    settings.defaultTextureFilter=TextureFilterSettings.Linear
    settings.antialias=true

    
    setting.iconPath=""  # Should be RGBA 32bit PNG
    settings.window.width=800   
    settings.window.height=600  
    settings.window.borderless=false
    settings.window.resizeable= false
    settings.window.minWidth=1
    settings.window.minHeight=1
    settings.window.fullscreen=false
    settings.window.alwaysOnTop=false

getFramesPerSecond() # Returns FPS
getFrameMiliseconds() # Returns time in seconds for last frame (delta time)
getTime() # Returns the elapsed time in seconds since the application started.

#  opens a window and runs the game 
run(title:string,load: proc(), update: proc(dt:float), draw: proc(), config : proc (settings : var AppSettings)=nil)  


### GRAPHICS
#Coordinate System
applyTransform(t: Transform)  #applies the given Transform object to the current coordinate transformation.	
origin()   #resets the current coordinate transformation. 
push()     #copies and pushes the current coordinate transformation to the transformation stack.
pop()      #pops the current coordinate transformation from the transformation stack.
translate(dx:float,dy:float)   #translates the coordinate system in two dimensions.
rotate(angle:float)    #rotates the coordinate system in two dimensions.	
scale(sx:float,sy:float)   #scales the coordinate system in two dimensions.	
shear(shx:float,shy:float)     #shears the coordinate system.	
transformPoint(x:float,y:float)    #converts the given 2D position from global coordinates into screen-space.	
inverseTransformPoint(x:float,y:float)    #converts the given 2D position from screen-space into global coordinates.	
replaceTransform(t: Transform)    #replaces the current coordinate transformation with the given Transform object.


#Object Creation
newTexture(filename:string):Texture    #creates a new Texture.
newFont(filename:string, antialias:bool=true, rasterSize:int=32):Font #creates a new Font
newShader(vertexShaderFile: string, fragmentShaderFile: string) #creates a new shader. If you don't want to use a vertex shader, set the vertexShaderFile argument to an empty string("")
newText(text:string, font:ptr rl.Font):Text    #creates a new drawable Text object.
newQuad(x,y,width,height,sw,sh:int):Quad  #creates a new Quad.
newQuad(x,y,width,height:int, texture:var Texture):Quad   #creates a new Quad (it just uses the texture to get width&height properties).
newSpriteBatch(texture:var Texture, maxSprites:int=1000): SpriteBatch     #creates a new SpriteBatch

#Drawing State
setColor (r:uint8, g:uint8,b:uint8, a:uint8)  #sets the color used for drawing.
setColor (color:Color)     #sets the color used for drawing.	
getColor () :Color   #gets the current color
setLine(width:float,joinType:JoinTypes=JoinTypes.Miter,beginCap:CapTypes,endCap:CapTypes=beginCap)   # sets the line stroke parameters (width, join type, and cap styles).
setLineWidth(width:float)   #sets the line stroke width.
getLineWidth():float    #returns the current line stroke width.
setLineJoin(joinType:JoinTypes)      # sets the line join style.
getLineJoin()    # returns the current line join style.
setLineCaps(beginCap:CapTypes,endCap:CapTypes=beginCap)  # sets the line cap style for the start and end of strokes.
getLineBeginCap():CapTypes  # returns the line cap style used at the beginning of strokes.
getLineEndCap():CapTypes  # returns the line cap style used at the end of strokes.
setFont(font:rl.Font) #sets the font
getFont() :Font   #returns the current font
getDefaultFont() :Font  #returns the framework's default font.
pushState()     #copies the current drawing state (color, line settings, font, etc.) and pushes it onto the state stack.
popState()      #restores the previous drawing state by popping it from the state stack.
resetState()    # resets the current drawing state (color, line settings, font, etc.) back to its default values.Does not affect previously saved states in the stack.

#Drawing
polygon(mode:DrawModes,points:varargs[float])   #draws a polygon
line(points:varargs[float])     #draws lines between points
arc(mode:DrawModes,arcType:ArcType, x:float,y:float,radius:float,angle1:float,angle2:float,segments:int=16)     #draws an arc
circle(mode:DrawModes,x:float,y:float,radius:float)     #draws a circle
ellipse(mode:DrawModes,x:float,y:float,radiusX:float,radiusY:float) #draws an ellipse.
rectangle(mode:DrawModes,x:float,y:float,width:float,height:float,rx:float=0,ry:float=rx,segments:int=12)   #draws a rectangle
quad(mode:DrawModes,x1:float,y1:float,x2:float,y2:float,x3:float,y3:float,x4:float,y4:float)    #draws a quadrilateral shape.
pixel(x:float,y:float)  #Draws a pixel.
draw(texture:Texture, x:float=0.0,y:float=0.0)  #draws a texture
draw(texture:Texture, quad:Quad, x:float=0.0,y:float=0.0)   #draws a texture with the specified quad 
draw(spriteBatch:SpriteBatch, x:float=0,y:float=0)  #draws a spritebatch
draw(text:Text ,x:float=0.0,y:float=0.0, size:float=16, spacing:float=1.0 )     #draws a text
clear()     #clears the screen with the active color.
clear(color:Color)     #clears the screen with the specified color.

#Text Methods 
getSizeWith(text:Text,fontSize:float,spacing:float=1.0) :tuple[x:float,y:float] # returns the text size using the specified font size and spacing.

#SpriteBatch Methods 
#adds an instance to SpriteBatch
add(spriteBatch: var SpriteBatch,x,y:float,r:float=0,sx:float=1,sy:float=1,ox:float=0,oy:float=0,kx:float=0,ky:float=0):int
#adds an instance to SpriteBatch with Quad
add(spriteBatch: var SpriteBatch, quad:Quad, x,y:float,r:float=0,sx:float=1,sy:float=1,ox:float=0,oy:float=0,kx:float=0,ky:float=0):int 

#Shader Methods  
setShader(shader:Shader) #  sets the specified shader for subsequent drawing operations. 
setShader() # Unsets the currently active shader.
setValue(shader: Shader, uniformName: string, value: float) # sets the value of a float-typed uniform.
setValue(shader: Shader, uniformName: string, value: int) #sets the value of a int-typed uniform.
setValue(shader: Shader, uniformName: string, value: (float,float)) #sets the value of a vec2-typed uniform.
setValue(shader: Shader, uniformName: string, value: (float,float,float))  #sets the value of a vec3-typed uniform.
setValue(shader: Shader, uniformName: string, value: (float,float,float,float)) #sets the value of a vec4-typed uniform.
setValue(shader: Shader, uniformName: string, value: (int,int)) #sets the value of a Ivec2-typed uniform.
setValue(shader: Shader, uniformName: string, value: (int,int,int))  #sets the value of a Ivec3-typed uniform.
setValue(shader: Shader, uniformName: string, value: (int,int,int,int)) #sets the value of a Ivec4-typed uniform.
setValue(shader: Shader, uniformName: string, value:Texture) #sets the value of a texture-typed uniform.

### SOUND
newSound(fileName:string, soundType:SoundType)  #creates a sound.

play(sound:Sound)  #plays the specified sound.	
stop(sound:Sound)  #stops the specified sound.
pause(sound:Sound)     #Pauses the specific sound.
resume(sound:Sound)    #Resumes the specific sound.

isPlaying(sound:Sound) #returns whether the specific sound is playing
isValid(sound:Sound) #returns whether the specific sound is valid

setVolume(sound:Sound)    #sets the volume of the specified sound
setPitch(sound:Sound)    #sets the pitch of the specified sound
setPan(sound:Sound)    #sets the pan of the specified sound

### INPUTS
#Keyboard
isKeyPressed(key:rl.KeyboardKey):bool     #checks if a key has been pressed once

isKeyReleased(key:rl.KeyboardKey):bool        #checks if a key has been released once

isKeyDown(key:rl.KeyboardKey):bool        #checks if a key is being pressed

isKeyUp(key:rl.KeyboardKey):bool      #checks if a key is not being pressed

getKeyPressed():KeyboardKey     #get key pressed (keycode), call it multiple times for keys queued, returns KeyboardKey.Null when the queue is empty

getCharPressed():Rune     #get char pressed (unicode), call it multiple times for chars queued, returns 0.Rune when the queue is empty


#Mouse
isMouseButtonPressed(button:rl.MouseButton):bool   #checks if a mouse button has been pressed once

isMouseButtonReleased(button:rl.MouseButton):bool  #checks if a mouse button has been released once

isMouseButtonDown(button:rl.MouseButton):bool  #checks if a mouse button is being pressed

isMouseButtonUp(button:rl.MouseButton):bool    #checks if a mouse button is not being pressed

getMouseX():int    #returns mouse position x

getMouseY():int    #returns mouse position y

#Gamepad
isGamepadAvailable(gamepad:int):bool       #checks if a gamepad is available

getGamepadName(gamepad:int):string     #returns gamepad internal name id

isGamepadButtonPressed(gamepad:int, button:rl.GamepadButton):bool      #checks if a gamepad button has been pressed once

isGamepadButtonReleased(gamepad:int, button:rl.GamepadButton):bool     #checks if a gamepad button has been released once

isGamepadButtonDown(gamepad:int, button:rl.GamepadButton):bool     #checks if a gamepad button is being pressed

isGamepadButtonUp(gamepad:int, button:rl.GamepadButton):bool       #checks if a gamepad button is not being pressed

getGamepadAxisCount(gamepad:int):int       #returns gamepad axis count for a gamepad

getGamepadAxisMovement(gamepad:int, axis:rl.GamepadAxis):float     #returns axis movement value for a gamepad axis

setGamepadMappings(mappings:string): int32     #set internal gamepad mappings (SDL_GameControllerDB)

setGamepadVibration(gamepad:int, leftMotor:float, rightMotor:float, duration:float)   #set gamepad vibration for both motors (duration in seconds)

#Touch
getTouchX():int    #get touch position x for touch point 0 (relative to screen size)

getTouchY():int    #get touch position Y for touch point 0 (relative to screen size)

getTouchPointId(index:int):int     #get touch point identifier for given index

getTouchPointCount():int   #get number of touch points

getTouchHoldDuration(index:int):float  #get touch hold time in seconds

getTouchDragX(index:int):float     #get touch drag vector x

getTouchDragY(index:int):float     #get touch drag vector y

getTouchDragAngle():float  #get touch drag angle

getTouchPinchX() :float    #get gesture pinch delta x

getTouchPinchY() :float    #get gesture pinch delta y

getTouchPinchAngle():float     #get gesture pinch angle

#WINDOW
setFullScreenMode(value:bool)   #sets window state: fullscreen/windowed, resizes monitor to match window resolution
getFullScreenMode(): bool   #checks if window is currently fullscreen
setBorderlessMode(value: bool )     #sets window state: borderless windowed, resizes window to match monitor resolution
getBorderlessMode() :bool   #checks if window state is currently borderless

setMinSize(width:int=1,height:int=1)  #sets window minimum dimensions (for resizeable windows)
setFocused()   #sets window focused
isFocused(): bool  #checks if window is currently focused
isResized(): bool  #checks if window has been resized last frame
setTitle(title:string)    #sets title for window
getWidth() : int   #returns current window width
getHeight() : int  #returns current window height

#JAVASCRIPT
eval(code: string): string   # Executes JS code and returns result as string.
createCallback(cb: JSCallback; jsEvent: cstring)  # Registers a callback that receives JS event data as JSON. (JSCallback type is: proc(arg: cstring){.cdecl.}  )
createCallback(cb: JSCallbackVoid; jsEvent: cstring) # Registers a more performant callback thanks to no event data arguments (JSCallbackVoid type is: proc(){.cdecl.} )

Contributing

To contribute code to the project, please try to follow Nims standard library style guide. We generally strive to maintain consistency with it throughout this project.(You might not like this guide, but after all, its a prepared style guide that everyone can access and use collectively.)

Core principles

  • Do not use macros or metaprogramming. This project does not need them and should not rely on them. It is important that the framework and its internal code remain understandable with zero learning curve, using only basic Nim knowledge.
  • Avoid unnecessary abstractions. Prefer primitive types in API method arguments (e.g. float, int, bool). Only group values using tuple when grouping is actually meaningful. Use seq or array for collections. For example, defining a vertex format explicitly as tuple[x, y, u, v: float] is preferred over introducing custom types like Vertex2D. The intent is for an intermediate Nim developer to understand the expected data layout immediately using Nim language knowledge alone, without learning framework-specific types or relying on detailed API documentation.
  • Preserve minimalism. We aim to keep the API small and stable, and to avoid expanding or changing it unless it is truly necessary. Suggestions are always welcome, but please dont take offense if an idea is declined. If what you are building can live as a separate module, plugin, or library in an external repository, prefer that approach. This framework is intentionally designed as a small core API meant to be surrounded by an ecosystem of extensions. If you believe a missing capability in the core API genuinely prevents this, then open a PR and lets discuss it.
  • Treat the backend as a replaceable layer. This project currently uses Naylib (Nims Raylib wrapper) as its backend. However, if you inspect the source code, youll see that Raylib is used at an arms length, making it entirely feasible to add alternative backends alongside it. The graphics API is largely built on top of Raylibs rlgl layer and its OpenGL-oriented calls. Because of this, ideas or experiments around different backends are very welcome. As long as the approach remains pragmatic, the backend layer is intentionally the most flexible part of this project.

Other ways to contribute

  • Write and share plugins or modules that are either fully dependent on the framework or can be used independently while working well with it (ECS, physics, GUI, etc.)
  • Open issues to report bugs or missing features.
  • Create tutorials or documentation related to the framework.
  • If you enjoy kirpi, share your thoughts on social media to help others discover it :) Dont forget to share your work(game, prototype, creative coding projects ) with the #madewithkirpi tag.

What's mean kirpi?

"Hedgehog” in my native language Turkish is “kirpi.” Its pronunciation is /keer-pee/.