Package Exports
- emeraldengine
Readme
Emerald
Emerald is a comprehensive 2D graphics engine that can help you create games easier than ever.
Table of Contents
- Emerald
Getting Started
To get started with Emerald, you need to have a canvas element in your HTML and import the necessary classes.
Usage
Basic Setup
import { Emerald } from "./emerald/Emerald";
import { Scene } from "./emerald/Scene";
import { Color } from "./emerald/Color";
import SceneManager from "./emerald/managers/SceneManager";
const emerald = new Emerald(canvas); // You should pass your own canvas element here
const scene = new Scene();
SceneManager.setScene(scene);
Set the background color for the engine
emerald.setBackgroundColor(color); // color = new Color(r, g, b, a = 255);
Drawing the scene
To draw items in the screen you need some sort of animation loop. I use window.requestAnimationFrame
for this. Here is a basic example:
let lastTime = 0;
const animate = (currentTime) => {
const deltaTime = (currentTime - lastTime) / 1000;
lastTime = currentTime;
emerald.drawScene(scene, deltaTime); // You need this line to tell the engine what to draw
window.requestAnimationFrame(animate);
};
animate(0);
Scene
Emerald has multiple scenes support. In order to render any object it has to be added to the scene using the add
method.
Adding and removing items from the scene
// Adding an object to the scene
scene.add(gameObject);
// Removing an object from the scene
scene.remove(gameObject);
Set Active Scene
// When changing a scene you should deactivate current scene to not mess up the event manager.
// Activate Scene
scene.setIsActive(true);
// Deactivate Scene
scene.setIsActive(false);
Game Objects
Creating a new GameObject
import GameObject from "./emerald/components/GameObject";
import { Vector3, Vector2 } from "./emerald/Physics";
/*
ARGUMENTS:
1. name: string = Name of the new GameObject
2. position: Vector3 = Position of the new GameObject
3. rotation: number = Rotation of the new GameObject
4. scale: Vector2 = Scale of the new GameObject
*/
const gameObject = new GameObject(name, position, rotation, scale);
This will create a new empty GameObject. At this stage you will not see anything on the screen until you add some components.
Components
There are currently 8 components: Texture, InstancedTexture, Square2D, Circle2D, Triangle2D, RigidBody, BoxCollider, CircleCollider
Texture
import { Texture } from "./emerald/Texture";
/*
ARGUMENTS:
1. texturePath = Specify the path for the texture that you want to use.
2. frameWidth: number = The width of each frame.
3. frameHeight: number = The height of each frame.
4. framesPerRow: number = How many frames are in one row in your spritesheet.
5. totalFrames: number = How many total frames does your spritesheet have.
6. animationSpeed: number = Speed of change of every frame.
7. autoPlay: boolean = Specify if you want the animation to play automatically. If you don't want any animation then pass false for it.
8. pixelart: boolean = Specify whether the texture should be rendered in pixel art style. (THIS IS OPTIONAL. If you don't specify it then it will be defaulted to true)
9. useLighting: boolean = Specify whether the texture should react to lighting or not. If you don't want any lighting then pass false for it. (THIS IS OPTIONAL. If you don't specify it then it will be defaulted to true)
*/
const texture = new Texture(
texturePath,
frameWidth,
frameHeight,
framesPerRow,
totalFrames,
animationSpeed,
autoPlay,
(pixelart = true),
(useLighting = true)
);
// Add the texture to a game object
gameObject.addComponent(texture);
InstancedTexture
InstancedTexture is perfect for rendering many objects with the same texture efficiently, such as tiles, particles, or repeating elements.
import { InstancedTexture } from "./emerald/InstancedTexture";
/*
ARGUMENTS:
1. texturePath = Specify the path for the texture that you want to use.
2. instanceCount: number = How many instances of the texture you want to create.
3. frameWidth: number = The width of each frame.
4. frameHeight: number = The height of each frame.
5. framesPerRow: number = How many frames are in one row in your spritesheet.
6. totalFrames: number = How many total frames does your spritesheet have.
7. animationSpeed: number = Speed of change of every frame.
8. autoPlay: boolean = Specify if you want the animation to play automatically. If you don't want any animation then pass false for it.
9. pixelart: boolean = Specify whether the texture should be rendered in pixel art style. (THIS IS OPTIONAL. If you don't specify it then it will be defaulted to true)
10. useLighting: boolean = Specify whether the texture should react to lighting or not. If you don't want any lighting then pass false for it. (THIS IS OPTIONAL. If you don't specify it then it will be defaulted to true)
*/
const instancedTexture = new InstancedTexture(
texturePath,
instanceCount,
frameWidth,
frameHeight,
framesPerRow,
totalFrames,
animationSpeed,
autoPlay,
(pixelart = true),
(useLighting = true)
);
// Add the instanced texture to a game object
gameObject.addComponent(instancedTexture);
Square2D
import { Square2D } from "./emerald/Shapes";
let square = new Square2D();
gameObject.addComponent(square);
Triangle2D
import { Triangle2D } from "./emerald/Shapes";
let triangle = new Triangle2D();
gameObject.addComponent(triangle);
Circle2D
import { Circle2D } from "./emerald/Shapes";
/*
ARGUMENTS:
1. segments = number of segments that the circle will have. Default is 32.
*/
let circle = new Circle2D(segments);
gameObject.addComponent(circle);
RigidBody
RigidBody is a component that allows you to add physics to your game objects. However, it won't work until you create a Physics instance at the top of your code.
import RigidBody from "./emerald/components/RigidBody";
import { Physics, Vector2 } from "./emerald/Physics";
// Create physics engine first
const physics = new Physics(-70, 32, 2); // gravity, scale, velocityThreshold
/*
ARGUMENTS:
1. physics: Physics = Instance of the Physics class that you created at the top of your code.
2. type: string = Type of the rigid body. It can be "dynamic", "kinematic", or "static".
3. position: Vector2 = Position of the rigid body is Vector2 because it doesn't need any Z index.
4. fixedRotation: boolean = Specify whether the rigid body should have a fixed rotation or not. Default is false.
5. parentObject: GameObject = (OPTIONAL) If you want to attach the rigid body to a GameObject you can pass it here. If you don't want to attach it to any GameObject then pass null.
6. offset: Vector2 = (OPTIONAL) Offset from the GameObject's position.
*/
const rigidBody = new RigidBody(
physics,
"dynamic",
new Vector2(0, 0),
false,
gameObject,
new Vector2(0, 0)
);
gameObject.addComponent(rigidBody);
BoxCollider
import BoxCollider from "./emerald/components/BoxCollider";
/*
ARGUMENTS:
1. rigidBody: RigidBody = The rigid body component that this collider will be attached to.
2. size: Vector2 = Size of the box collider.
3. density: number = Density of the collider.
4. friction: number = Friction of the collider.
5. restitution: number = Restitution (bounciness) of the collider.
6. isSensor: boolean = Whether this collider is a sensor (triggers events but doesn't collide physically).
7. parentObject: GameObject = (OPTIONAL) Parent GameObject.
*/
const boxCollider = new BoxCollider(
rigidBody,
new Vector2(1, 1),
1,
0.3,
0.1,
false,
gameObject
);
gameObject.addComponent(boxCollider);
The BoxCollider
is specifically made bigger than the Square2D
component in this image to demonstrate how it works. You can adjust the size of the collider to fit your needs.
CircleCollider
import CircleCollider from "./emerald/components/CircleCollider";
/*
ARGUMENTS:
1. rigidBody: RigidBody = The rigid body component that this collider will be attached to.
2. radius: number = Radius of the circle collider.
3. density: number = Density of the collider.
4. friction: number = Friction of the collider.
5. restitution: number = Restitution (bounciness) of the collider.
6. isSensor: boolean = Whether this collider is a sensor.
7. parentObject: GameObject = (OPTIONAL) Parent GameObject.
*/
const circleCollider = new CircleCollider(
rigidBody,
1.5,
1,
0.3,
0.8,
false,
gameObject
);
gameObject.addComponent(circleCollider);
The CircleCollider
is specifically made bigger than the Circle2D
component in this image to demonstrate how it works. You can adjust the radius of the collider to fit your needs.
Object methods
Position
// Set position
gameObject.transform.position.x = 100;
gameObject.transform.position.y = 200;
gameObject.transform.position.z = 0;
// Or set all at once
gameObject.transform.position = new Vector3(100, 200, 0);
Rotation
// Set rotation (in radians)
gameObject.transform.rotation = Math.PI / 4; // 45 degrees
Scale
// Set scale
gameObject.transform.scale.x = 2;
gameObject.transform.scale.y = 2;
// Or set both at once
gameObject.transform.scale = new Vector2(2, 2);
Change color
// For textures
const texture = gameObject.getComponent(Texture);
texture.setColor(new Color(255, 0, 0)); // Red
Set texture frame
// For animated textures
const texture = gameObject.getComponent(Texture);
texture.setFrame(2); // Set to frame 2
Animations
// Play animation
texture.playAnimation([0, 1, 2, 3], 200); // frames array, speed in ms
// Stop animation
texture.stopAnimation();
// Check if playing
if (texture.isPlaying) {
// Animation is currently playing
}
Instance System
The Instance system allows you to efficiently manage multiple copies of the same object.
Creating Instances
import Instance from "./emerald/Instance";
// Create an instance
const instance = new Instance(
"InstanceName",
new Vector3(x, y, z),
new Vector2(width, height),
rotation,
frame
);
// Add to InstancedTexture
const instancedTexture = gameObject.getComponent(InstancedTexture);
instancedTexture.addInstance(instance);
Instance Management
// Remove instance
instancedTexture.removeInstance(instanceId);
// Get instance by ID
const instance = instancedTexture.getInstanceWithId(instanceId);
// Get instance at position
const instance = instancedTexture.getInstanceAtPosition(position, tolerance);
// Clear all instances
instancedTexture.clearInstances();
Instance Events
// Add click event to specific instance
instancedTexture.addInstanceClickEvent(instanceId, (event) => {
console.log("Instance clicked!");
});
// Add hover events to specific instance
instancedTexture.addInstanceHoverEvent(
instanceId,
(event) => console.log("Mouse entered"),
(event) => console.log("Mouse left")
);
Physics Engine
Emerald includes a comprehensive physics engine built on top of Planck.js.
Setting up Physics
import { Physics } from "./emerald/Physics";
/*
ARGUMENTS:
1. gravity: number = Gravity force (negative for downward)
2. scale: number = Scale factor for physics units to pixels
3. velocityThreshold: number = Minimum velocity threshold
*/
const physics = new Physics(-70, 32, 2);
Physics Bodies
// Get the physics body from a RigidBody component
const body = rigidBody.getBody();
// Set velocity
body.setLinearVelocity(new Vector2(10, 0));
// Get velocity
const velocity = body.getLinearVelocity();
// Get position
const position = body.getPosition();
Collision Detection
// Handle collision enter
physics.onCollisionEnter((bodyA, bodyB, contact) => {
console.log("Collision started!");
// Get collision normal
const normal = contact.getWorldManifold().normal;
//normal.y = -1 when player is on the ground
//normal.y = 1 when player hits the ceiling
//normal.x = -1 when player hits the left wall
//normal.x = 1 when player hits the right wall
// Check if bodies are sensors
const fixtureA = contact.getFixtureA();
const fixtureB = contact.getFixtureB();
if (fixtureA.isSensor() || fixtureB.isSensor()) {
// Handle sensor collision
}
});
// Handle collision exit
physics.onCollisionExit((bodyA, bodyB, contact) => {
console.log("Collision ended!");
});
Collision Events
// Process physics in your update loop
const animate = (currentTime) => {
physics.process(deltaTime);
};
Particle System
Emerald includes a powerful particle system for creating visual effects.
Particle Settings
import ParticleSettings from "./emerald/particlesystem/ParticleSettings";
const particleSettings = new ParticleSettings({
lifetime: 1.2,
velocity: new Vector2(200, 300),
gravity: new Vector2(0, -400),
amount: 16,
direction: new Vector2(0, 1), // upward
spread: Math.PI * 2,
emissionRate: Infinity, // one-shot emission
frame: 0,
offset: 5,
rotation: 0,
scale: new Vector2(5, 5),
animation: { frames: [0, 1, 2], speed: 200 },
});
Creating Particle Systems
import Particles from "./emerald/particlesystem/Particles";
/*
ARGUMENTS:
1. name: string = Name of the particle system
2. texturePath: string = Path to the texture
3. frameWidth: number = Width of each frame
4. frameHeight: number = Height of each frame
5. framesPerRow: number = Frames per row in spritesheet
6. totalFrames: number = Total frames in spritesheet
7. duration: number = Duration of the effect
8. settings: ParticleSettings = Particle settings object
*/
const particles = new Particles(
"explosion",
texturePath,
16,
16,
9,
27,
1.2,
particleSettings
);
// Add to scene
scene.add(particles.gameObject);
Particle System Methods
// Play particle effect at position
particles.play(new Vector3(x, y, z));
// Stop particle system
particles.stop();
// Reset particle system
particles.reset();
// Update particles (call in your animation loop)
particles.update(deltaTime);
// Check if active
if (particles.active) {
// Particles are currently active
}
Lighting System
Emerald supports ambient, point, and directional lighting.
Ambient Light
// Set ambient light
emerald.setAmbientLight(new Vector3(0.3, 0.3, 0.3)); // RGB values 0-1
Point Light
import PointLight from "./emerald/lights/PointLight";
/*
ARGUMENTS:
1. position: Vector2 = Position of the light
2. color: Color = Color of the light
3. intensity: number = Light intensity
4. radius: number = Light radius
*/
const pointLight = new PointLight(
new Vector2(100, 0),
new Color(255, 204, 153),
1.5,
400
);
// Add to engine
emerald.addPointLight(pointLight);
// Update position
pointLight.position.x = newX;
pointLight.position.y = newY;
Directional Light
import DirectionalLight from "./emerald/lights/DirectionalLight";
/*
ARGUMENTS:
1. position: Vector2 = Position of the light
2. direction: Vector2 = Direction vector
3. color: Color = Color of the light
4. intensity: number = Light intensity
5. width: number = Width of the light beam
*/
const directionalLight = new DirectionalLight(
new Vector2(0, 300),
new Vector2(0, -1), // pointing down
new Color(255, 255, 255),
3.0,
200
);
// Add to engine
emerald.addDirectionalLight(directionalLight);
// Rotate direction
const angle = 0.1;
const newX =
directionalLight.direction.x * Math.cos(angle) -
directionalLight.direction.y * Math.sin(angle);
const newY =
directionalLight.direction.x * Math.sin(angle) +
directionalLight.direction.y * Math.cos(angle);
directionalLight.direction.x = newX;
directionalLight.direction.y = newY;
Text Rendering
BitmapText
Emerald supports bitmap font rendering using the BitmapText component. This allows you to display text with custom fonts and styles.
import BitmapText from "./emerald/BitmapText";
/*
ARGUMENTS:
1. text: string = Text to display
2. texturePath: string = Path to bitmap font texture
3. letters: string = String containing all available characters
4. letterSpacing: number = Spacing between letters
5. frameWidth: number = Width of each character frame
6. frameHeight: number = Height of each character frame
7. framesPerRow: number = Characters per row in font texture
8. totalFrames: number = Total character frames
9. pixelArt: boolean = Whether to use pixel art rendering
10. fontSize: number = Font size
11. color: Color = Text color
12. position: Vector3 = Text position
13. rotation: number = Text rotation
14. useLighting: boolean = Whether text should react to lighting
*/
const bitmapText = new BitmapText(
"Hello World!",
fontTexturePath,
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?.",
16,
32,
32,
10,
95,
true,
24,
new Color(255, 255, 255),
new Vector3(0, 200, 0),
0,
false
);
// Add to scene
scene.add(bitmapText.gameObject);
// Update text
bitmapText.setText("New Text!");
bitmapText.setColor(new Color(255, 0, 0));
bitmapText.setFontSize(32);
bitmapText.setLetterSpacing(20);
EventManager
Emerald supports keyboard, mouse, click, and hover events. All events are handled using the built-in EventManager class.
import EventManager from "./emerald/managers/EventManager";
let eventManager = new EventManager(canvas, scene, emerald.camera);
Keyboard Events
// Key down events
eventManager.addKeyDown("w", () => {
console.log("W key pressed");
});
// Key up events
eventManager.addKeyUp("w", () => {
console.log("W key released");
});
// Check if key is currently pressed
if (eventManager.isKeyPressed("w")) {
// W key is currently held down
}
// Remove key events
eventManager.removeKeyDown("w", callbackFunction);
eventManager.removeKeyUp("w", callbackFunction);
Mouse Events
// Get mouse position
const mousePos = eventManager.getMousePosition();
console.log(mousePos.x, mousePos.y);
// Check if camera was moved
if (eventManager.wasCameraMoved()) {
// Camera was moved by dragging
eventManager.resetCameraMoved();
}
Object Events
// Click events
eventManager.addClickEvent(gameObject, (event, object) => {
console.log("Object clicked!");
});
// Hover events
eventManager.addHoverEvent(
gameObject,
(event) => {
console.log("Mouse entered object");
},
(event) => {
console.log("Mouse left object");
}
);
// Remove events
eventManager.removeClickEvent(gameObject, callbackFunction);
eventManager.removeHoverEvent(gameObject, enterCallback, leaveCallback);
Event Cleanup
// Clean up all events when done
eventManager.clean();
// Change scene
eventManager.changeScene(newScene);
AudioManager
Emerald includes a comprehensive audio management system.
Adding Audio
import AudioManager from "./emerald/managers/AudioManager";
const audioManager = new AudioManager();
// Add audio files
audioManager.add("path/to/sound.wav", "soundName");
audioManager.add("path/to/music.mp3", "backgroundMusic");
Playing Audio
// Play audio
audioManager.play("soundName");
// Play exclusively (stops all other audio first)
audioManager.playExclusive("soundName");
Audio Control
// Stop specific audio
audioManager.stop("soundName");
// Stop all audio
audioManager.stopAll();
// Remove audio
audioManager.remove("soundName");
// Get audio object
const sound = audioManager.getSound("soundName");
Camera
The engine has simple controls for the camera. The camera is stored in the emerald variable.
// Set camera position
emerald.camera.setPosition(x, y, z);
// Access camera transform directly
emerald.camera.transform.position.x = 100;
emerald.camera.transform.position.y = 200;
emerald.camera.transform.scale.x = 1.5;
emerald.camera.transform.scale.y = 1.5;
FPSCounter
Emerald has a built-in FPS counter.
import { FPSCounter } from "./emerald/FPSCounter";
let fpsCounter = new FPSCounter();
const animate = (currentTime) => {
emerald.drawScene(scene, deltaTime);
fpsCounter.update(); // Call this in your animation loop
window.requestAnimationFrame(animate);
};
animate();
Scene Management
import SceneManager from "./emerald/managers/SceneManager";
// Set active scene
SceneManager.setScene(scene);
// Get current scene
const currentScene = SceneManager.getScene();
Time Management
import Time from "./emerald/Time";
// Get delta time
const deltaTime = Time.deltaTime;
// Time is automatically updated when you call emerald.drawScene()
// You can also manually set it
Time.setDeltaTime(deltaTime);
Advanced Features
Resize Handling
// Handle window resize
const handleResize = () => {
const { width, height } = getCanvasDimensions();
emerald.resize(width, height);
};
window.addEventListener("resize", handleResize);