emeraldengine
Version:
2D graphics library for Web
969 lines (751 loc) • 26.8 kB
Markdown
# Emerald
Emerald is a comprehensive 2D graphics engine that can help you create games easier than ever.
## Table of Contents
- [Emerald](#emerald)
- [Table of Contents](#table-of-contents)
- [Getting Started](#getting-started)
- [Usage](#usage)
- [Basic Setup](#basic-setup)
- [Set the background color for the engine](#set-the-background-color-for-the-engine)
- [Drawing the scene](#drawing-the-scene)
- [Scene](#scene)
- [Adding and removing items from the scene](#adding-and-removing-items-from-the-scene)
- [Set Active Scene](#set-active-scene)
- [Game Objects](#game-objects)
- [Creating a new GameObject](#creating-a-new-gameobject)
- [Components](#components)
- [Texture](#texture)
- [InstancedTexture](#instancedtexture)
- [Square2D](#square2d)
- [Triangle2D](#triangle2d)
- [Circle2D](#circle2d)
- [RigidBody](#rigidbody)
- [BoxCollider](#boxcollider)
- [CircleCollider](#circlecollider)
- [Object methods](#object-methods)
- [Position](#position)
- [Rotation](#rotation)
- [Scale](#scale)
- [Change color](#change-color)
- [Set texture frame](#set-texture-frame)
- [Animations](#animations)
- [Instance System](#instance-system)
- [Creating Instances](#creating-instances)
- [Instance Management](#instance-management)
- [Instance Events](#instance-events)
- [Physics Engine](#physics-engine)
- [Setting up Physics](#setting-up-physics)
- [Physics Bodies](#physics-bodies)
- [Collision Detection](#collision-detection)
- [Collision Events](#collision-events)
- [Particle System](#particle-system)
- [Particle Settings](#particle-settings)
- [Creating Particle Systems](#creating-particle-systems)
- [Particle System Methods](#particle-system-methods)
- [Lighting System](#lighting-system)
- [Ambient Light](#ambient-light)
- [Point Light](#point-light)
- [Directional Light](#directional-light)
- [Text Rendering](#text-rendering)
- [BitmapText](#bitmaptext)
- [EventManager](#eventmanager)
- [Keyboard Events](#keyboard-events)
- [Mouse Events](#mouse-events)
- [Object Events](#object-events)
- [Event Cleanup](#event-cleanup)
- [AudioManager](#audiomanager)
- [Adding Audio](#adding-audio)
- [Playing Audio](#playing-audio)
- [Audio Control](#audio-control)
- [Camera](#camera)
- [FPSCounter](#fpscounter)
- [Scene Management](#scene-management)
- [Time Management](#time-management)
- [Advanced Features](#advanced-features)
- [Resize Handling](#resize-handling)
## 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
```javascript
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
```javascript
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:
```javascript
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
```javascript
// Adding an object to the scene
scene.add(gameObject);
// Removing an object from the scene
scene.remove(gameObject);
```
### Set Active Scene
```javascript
// 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
```javascript
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
```javascript
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.
```javascript
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
```javascript
import { Square2D } from "./emerald/Shapes";
let square = new Square2D();
gameObject.addComponent(square);
```

#### Triangle2D
```javascript
import { Triangle2D } from "./emerald/Shapes";
let triangle = new Triangle2D();
gameObject.addComponent(triangle);
```

#### Circle2D
```javascript
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.
```javascript
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
```javascript
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
```javascript
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
```javascript
// 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
```javascript
// Set rotation (in radians)
gameObject.transform.rotation = Math.PI / 4; // 45 degrees
```

### Scale
```javascript
// 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
```javascript
// For textures
const texture = gameObject.getComponent(Texture);
texture.setColor(new Color(255, 0, 0)); // Red
```

### Set texture frame
```javascript
// For animated textures
const texture = gameObject.getComponent(Texture);
texture.setFrame(2); // Set to frame 2
```
## Animations
```javascript
// 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
```javascript
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
```javascript
// 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
```javascript
// 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
```javascript
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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
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
```javascript
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
```javascript
// 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
```javascript
// Set ambient light
emerald.setAmbientLight(new Vector3(0.3, 0.3, 0.3)); // RGB values 0-1
```
### Point Light
```javascript
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
```javascript
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.

```javascript
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.

```javascript
import EventManager from "./emerald/managers/EventManager";
let eventManager = new EventManager(canvas, scene, emerald.camera);
```
### Keyboard Events
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// Clean up all events when done
eventManager.clean();
// Change scene
eventManager.changeScene(newScene);
```
## AudioManager
Emerald includes a comprehensive audio management system.
### Adding Audio
```javascript
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
```javascript
// Play audio
audioManager.play("soundName");
// Play exclusively (stops all other audio first)
audioManager.playExclusive("soundName");
```
### Audio Control
```javascript
// 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.
```javascript
// 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.
```javascript
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
```javascript
import SceneManager from "./emerald/managers/SceneManager";
// Set active scene
SceneManager.setScene(scene);
// Get current scene
const currentScene = SceneManager.getScene();
```
## Time Management
```javascript
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
```javascript
// Handle window resize
const handleResize = () => {
const { width, height } = getCanvasDimensions();
emerald.resize(width, height);
};
window.addEventListener("resize", handleResize);
```