Kippy
Kippy 2D web game engine for JS
Install / Use
/learn @nguyenphuminh/KippyREADME
Kippy is a 2D JS game engine written purely for fun and simplicity. It currently utilizes the Canvas 2D context for rendering and aims to have a small set of APIs viable for game dev, but do expect a lot of components to change in the future.
Usage
Install through npm:
npm install kippy
then import, for example:
import { Game, Scene, Entity } from "kippy";
or if you are on raw HTML5, you can pull from a cdn:
import { Game, Scene, Entity } from "https://unpkg.com/kippy";
Example
There is a Flappy Bird game in ./example for now. You can run it by cloning this repo, then install deps:
npm install
and build:
npm run build
and start Vite:
npx vite
Tutorial
Here is a vaguely-written tutorial for now:
Initialize game
First, prepare a canvas tag in your html file:
<canvas></canvas>
Then mount your game there:
import { Game } from "kippy";
const canvas = document.querySelector("canvas");
const game = new Game({
canvas
});
// Start the game loop
game.start();
// You can also swap canvas if you want
// game.setCanvas(someOtherCanvas);
Create a scene
Here is how you can create a scene and load that scene into your game object:
import { Scene } from "kippy";
class Main extends Scene {
// Runs when this scene gets loaded into a game
init() {}
// Runs on every frame, dt is the time between each frame
update(dt) {}
// Runs when another scene replaces this scene in a game
exit() {}
}
// Create scene and load into game
const main = new Main();
game.setScene(main);
Create an entity
A scene can have multiple entities like players, mobs, obstacles, etc in it. This is how you can create an entity:
import { Entity } from "kippy";
const entity = new Entity({
// These are all optional, and can also be set later
// Graphics
animator, // Entity's animator to be rendered, type Animator
sprite, // Entity's sprite to be rendered, type Sprite
// Position
position, // Entity's position (centered), type Vector2
rotation, // Entity's rotation in radians, type number
// Physics
body, // Entity's physical body, type EntityBody
collider, // Entity's collider, type Collider
// Effects
glow
});
// Add it to a scene
scene.addEntity(entity);
// Remove it from a scene
scene.removeEntity(entity);
// These props contain movement info and you can mutate them to edit its position
entity.position; // Initialized from the "position" param above, Vector2(0, 0) if not specified
// You can mutate these directly:
entity.position.x;
entity.position.y;
entity.rotation; // Initialized from the "rotation" param above, 0 if not specified
Create a sprite
A sprite represents what an entity looks like (the "graphics part"), and you can create a sprite like this:
import { Sprite } from "kippy";
const sprite = new Sprite({
texture, // Sprite's texture - HTMLImageElement, HTMLCanvasElement, OffscreenCanvas, ImageBitmap
width, // Sprite's width, type number
height // Sprite's height, type number
});
// Set sprite for an entity
entity.sprite = sprite;
Animation
First you create a sprite sheet that contains your frames:
import { SpriteSheet } from "kippy";
const spriteSheet = new SpriteSheet({
texture, // Similar to sprite texture
frameWidth, // Width of each frame, type number
frameHeight, // Height of each frame, type number
width, // Width when render, default is frameWidth
height // Height when render, default is frameHeight
});
Then you create an animation object which defines the order of frame to play, fps, and whether to loop or not:
import { Animation } from "kippy";
const animation = new Animation({
spriteSheet, // A SpriteSheet instance
frames, // Order of frames, type number[]
fps, // Frames per second, type number
loop // Loop animation endlessly, type boolean
});
Then you create an animator, attach it to an entity, and then play an animation:
import { Animator } from "kippy";
const animator = new Animator({
animations, // A Record<string, Animation> map that contains all animations
default // The animation that will be played first, type string
});
// Attach it to an entity
entity.animator = animator
// Play an animation
entity.animator.play("animationName");
// You can also call a handler when animation ends
entity.animator.onEnd = function(name) {
// Do something
}
Add controls
Game controls like mouse presses, key presses, touch, and cursor tracking (in the game canvas, not the web window) can be done by using the input handler from your game instance:
const input = game.input;
Then in a scene's update method, you can use these utilities to check for key presses:
// Keyboard
input.isKeyDown(/* Character/key here */); // true if key is held, false otherwise
input.isKeyPressed(/* Character/key here */); // true if key is pressed, false otherwise
input.isKeyReleased(/* Character/key here */); // true if key is released, false otherwise
// Mouse/touch
input.isPointerDown(/* 0 for left, 1 for right, 2 for touch */); // true if held, false otherwise
input.isPointerPressed(/* 0 for left, 1 for right, 2 for touch */); // true if pressed, false otherwise
input.isPointerReleased(/* 0 for left, 1 for right, 2 for touch */); // true if released, false otherwise
// Mouse/touch position
input.pointer; // Pointer's position vector
input.pointer.x; // Current X position of mouse/touch
input.pointer.y; // Current Y position of mouse/touch
Vectors
To work with positions and movements in Kippy, it is best to know about Vector2 first. Positions, velocities, forces, etc are all represented as vectors in Kippy. And here are how you can create a 2D vector and some vector math utilities that come along with it:
import { Vector2 } from "kippy";
const vect = new Vector2(/* x coordinate, number */, /*y coordinate, number */);
// Props
vect.x; // X coordinate
vect.y; // Y coordinate
// Utilities
vect.toString(); // Returns "Vector2(x, y)"
vect.add(otherVect); // Add another vector and return the result vector
vect.sub(otherVect); // Subtract another vector and return the result vector
vect.mul(otherVect); // Multiply with another vector and return the result vector
vect.div(otherVect); // Divide by another vector and return the result vector
vect.neg(); // Negate and return the result vector
vect.scale(scale); // Multiply with scale and return the result vector
vect.magnitude(); // Return the magnitude/length of vector
vect.magnitudeSquared(); // Return the squared magnitude/length of vector
vect.normalize(); // Return the normalized vector by magnitude
vect.dot(otherVect); // Return dot product with another vector
vect.cross(otherVect); // Return cross product with another vector
vect.project(otherVect); // Return projection on another vector
vect.min(otherVect); // Return a new vector with min coordinates
vect.max(otherVect); // Return a new vector with max coordinates
vect.floor(); // Floor rounding
vect.ceil(); // Ceil rounding
vect.round(); // Normal rounding
vect.distance(otherVect); // Return distance to another vector
vect.distanceSquared(otherVect); // Return squared distance to another vector
vect.copy(); // Return a copy (same coordinates, different reference)
vect.lerp(otherVect, scale); // Apply linear interpolation and return
vect.clamp(maxLength); // Clamp vector to have length below maxLength
vect.rotate(angle); // Return rotated vector by provided angle
vect.orthogonal(); // Return orthogonal vector of this vector
vect.angle(); // Return angle of vector.
vect.angleTo(otherVec); // Return angle between this and another vector
vect.reflect(otherVect); // Return reflection/bounce back vector
vect.equals(otherVect); // Check if two vectors are equal
// Useful constants
Vector2.ZERO; // Vector2(0, 0)
Vector2.ONE; // Vector2(1, 1);
Vector2.UP; // Vector2(0, -1);
Vector2.DOWN; // Vector2(0, 1);
Vector2.LEFT; // Vector2(-1, 0);
Vector2.RIGHT; // Vector2(1, 0);
Physics
For movements, currently you can create a RigidBody:
import { RigidBody } from "kippy";
// Create a rigid body
const rigidBody = new RigidBody({
velocity, // Entity's velocity vector, type Vector2
rotationVelocity, // Entity's angular/rotation velocity, type number
mass, // Entity's mass, type number
inertia, // Entity's inertia, type number
force, // Entity's force vector, type Vector2
torque, // Entity's torque/rotational force, type number
restitution // Entity's restitution for collision bounce back, type number
});
// Attach body to an entity
entity.body = rigidBody;
// And you can mutate these props to update movement every frame
entity.body.velocity; // Set with the matching parameter above, default is Vector2(0, 0)
entity.body.rotationVelocity; // Set with the matching parameter above, default is 0
entity.body.mass; // Set with the matching parameter above, default is 1
entity.body.inertia; // Set with the matching parameter above, default is 1
// Note that forces are reset after every frame
entity.body.force; // Set with the matching parameter above, default is Vector2(0, 0)
entity.body.torque; // Set with the matching parameter above, default is 0
entity.body.restitution; // Set with the matching parameter above, default is 0
For collisions, you can create a CircleCollider for now:
import { CircleCollider } from "kippy";
const collider = new CircleCollider({
radius, // Circle collider's radius,
Related Skills
node-connect
347.9kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
108.7kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
347.9kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
347.9kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
