Kernelplay
A lightweight JavaScript game core library for building games and engines.
Install / Use
/learn @Soubhik1000/KernelplayREADME
KernelPlayJS
A 2D/3D JavaScript game engine that feels like Unity — but lives in your browser. Built on an Entity–Component architecture, KernelPlayJS is fast, flexible, and surprisingly fun to use.
v0.2.3-alpha · MIT License · Built by Soubhik Mukherjee
<div align="left"> <img height="400" src="https://soubhik2.github.io/HomeLand.github.oi/Images/PerformanceM.png" /> </div>
🔴 Live Demo
👉 https://soubhik-rjs.github.io/kernelplay-js-demo/examples/Canvas2D/
🏁 Benchmark Demo · 📚 Full Documentation
⚡ Why KernelPlayJS?
Most browser game engines either hold your hand too much or leave you drowning in boilerplate. KernelPlayJS hits the sweet spot — it handles the hard stuff (physics [AABB], rendering [Canvas 2D, Pixi JS, Three JS], collision, object pooling) so you can focus on making your game actually fun.
- 10,000+ objects at 60 FPS on a 7th gen i3 — yes, really
- 3,000 physics objects with full collision detection at 40+ FPS
- Spatial grid turns O(n²) collision into O(n) — automatic, no config needed
- Frustum culling — skips anything off-screen entirely
- Object pooling — spawn 1000+ bullets/sec with zero GC stutters
- Dirty flag system — 91% fewer transform recalculations for static objects
- Batch rendering — groups draws by color to cut canvas state changes by 100×
📦 Installation
npm install kernelplay-js
Or drop it straight into HTML with a CDN:
<script type="importmap">
{
"imports": {
"kernelplay-js": "https://cdn.jsdelivr.net/npm/kernelplay-js/dist/kernelplay.es.js"
}
}
</script>
Optional Renderer Plugins
The core engine ships with Canvas 2D — zero external dependencies. When your game needs more visual firepower, bolt on a renderer plugin:
npm install @kernelplay/pixi-renderer # Pixi.js — GPU-accelerated 2D sprites & effects
npm install @kernelplay/three-renderer # Three.js — full 3D with lights, meshes, shadows
🎥 CameraComponent (New in v0.2.3)
No more hassle with manual game camera handling.
❌ Old Way (Manual Camera)
Previously, you had to manually sync the camera with the player:
// Camera follows player manually
this.camera.x = transform.position.x - this.camera.width / 2;
this.camera.y = transform.position.y - this.camera.height / 2;
✅ New Way (Camera as a GameObject)
Now, the camera is just another Entity in your scene.
class GameScene extends Scene {
init() {
// Create player
const player = new Player(400, 300);
this.addEntity(player);
// Create camera entity
const camera = new Entity("MainCamera");
camera.addComponent("transform", new TransformComponent({
position: { x: 400, y: 300, z: 0 }
}));
camera.addComponent("camera", new CameraComponent({
width: this.game.config.width,
height: this.game.config.height,
// 🔥 Follow system
target: player,
followSpeed: 5,
// Offset from target
offset: { x: 0, y: -50, z: 0 },
// Level bounds (prevents showing outside world)
bounds: {
minX: 0,
maxX: 2000,
minY: 0,
maxY: 1500
},
isPrimary: true
}));
this.addEntity(camera);
}
}
🎮 Using Camera in Scripts
export class PlayerController extends ScriptComponent {
onStart() {
// Get primary camera
this.primaryCamera = this.entity.scene.getPrimaryCamera();
}
update(dt) {
// Shortcut access
this.camera;
}
}
🧠 CameraComponent Methods
// Set follow target
this.camera.setTarget(this.entity);
// Screen shake effect
this.camera.shake(20, 0.5);
// Convert screen → world coordinates
const worldPos = this.camera.screenToWorld(Mouse.x, Mouse.y);
// Convert world → screen coordinates
const screenPos = this.camera.worldToScreen(pos.x, pos.y);
// Check if a position is visible
this.camera.isInView(x, y);
🎥 Multiple Cameras Setup
// Camera 1 (Primary)
const camera1 = new Entity("MainCamera");
camera1.id = 100;
camera1.addComponent("transform", new TransformComponent({
position: { x: 400, y: 300, z: 10 }
}));
camera1.addComponent("camera", new CameraComponent({
width: 800,
height: 600,
isPrimary: true
}));
// Camera 2
const camera2 = new Entity("Camera2");
camera2.id = 101;
camera2.addComponent("transform", new TransformComponent({
position: { x: 0, y: 0, z: 10 }
}));
camera2.addComponent("camera", new CameraComponent({
width: 800,
height: 600,
isPrimary: false
}));
🔄 Switching Between Cameras
// Inside ScriptComponent
this.setPrimaryCamera(this.camera2);
⚡ Prop Injection System
ScriptComponent now supports automatic prop injection.
You can directly pass references and values — no manual lookup needed.
🔗 Setup
import { ref } from "kernelplay-js";
player.addComponent("playerController", new PlayerController({
enemy: ref(5),
force: 800,
camera1: ref(100),
camera2: ref(101),
}));
🚀 Usage
// Use injected values directly
if (Keyboard.isPressed(KeyCode.ArrowRight)) {
rb.addForce(this.force, 0);
}
// Switch camera
this.setPrimaryCamera(this.camera2);
// Destroy referenced entity
this.enemy.destroy();
// Change camera target
this.camera2.getComponent("camera").setTarget(this.enemy);
✨ Summary
- Camera is now a full Entity
- Built-in smooth follow system
- Supports multiple cameras
- Easy camera switching
- Powerful prop injection system
- Cleaner, modular, and scalable 🎯
🚀 Quick Start
import { Game, Scene, Entity, TransformComponent, BoxRenderComponent } from "kernelplay-js";
class MyScene extends Scene {
init() {
const box = new Entity();
box.addComponent("transform", new TransformComponent({ position: { x: 300, y: 200 } }));
box.addComponent("renderer", new BoxRenderComponent({ color: "red" }));
this.addEntity(box);
}
}
class MyGame extends Game {
init() {
this.sceneManager.addScene(new MyScene("Main"));
this.sceneManager.startScene("Main");
}
}
new MyGame({ width: 800, height: 600, fps: 60 }).start();
🎮 Core Concepts
Everything in KernelPlayJS is built around three ideas:
- Entities — your game objects (player, bullet, enemy, tree)
- Components — data attached to entities (position, physics, renderer)
- Scripts — the brains; custom logic that runs every frame
export class Player extends Entity {
constructor(x, y) {
super("Player");
this.tag = "player";
this.zIndex = 10; // renders on top
this.addComponent("transform", new TransformComponent({ position: { x, y } }));
this.addComponent("rigidbody2d", new Rigidbody2DComponent({ mass: 1, gravityScale: 1 }));
this.addComponent("collider", new ColliderComponent({ width: 50, height: 50 }));
this.addComponent("renderer", new BoxRenderComponent({ color: "red" }));
this.addComponent("controller", new PlayerController());
}
}
🧠 Script Lifecycle
Scripts work just like Unity's MonoBehaviour — with clean hooks for every stage of an entity's life:
export class PlayerController extends ScriptComponent {
onStart() {
this.speed = 200;
this.jumpForce = 500;
}
update(dt) {
const rb = this.entity.getComponent("rigidbody2d");
rb.velocity.x = 0;
if (Keyboard.isDown(KeyCode.ArrowRight)) rb.velocity.x = this.speed;
if (Keyboard.isDown(KeyCode.ArrowLeft)) rb.velocity.x = -this.speed;
if (Keyboard.wasPressed(KeyCode.Space) && rb.isGrounded) {
rb.velocity.y = -this.jumpForce;
}
if (Mouse.wasPressed(MouseButton.Left)) {
this.instantiate(Bullet, this.transform.position.x, this.transform.position.y);
}
}
onCollision(other) {
if (other.tag === "enemy") this.takeDamage(10);
}
onDestroy() {
// cleanup resources here
}
}
Lifecycle order: onAttach → onStart → update → lateUpdate → onDestroy
🔫 Object Pooling (Automatic)
Spawning hundreds of bullets per second? KernelPlayJS silently recycles destroyed entities back into a pool so they can be reused — no setup, no pool sizes to configure, no GC spikes to fight.
// Creates a Bullet, or quietly reuses a destroyed one from the pool
this.instantiate(Bullet, x, y);
// When the bullet's lifetime ends or it hits something:
this.destroy(); // entity goes back to the pool, not the garbage collector
Bullet Prefab (Auto-pooled)
// Do not use Entity Object for the Bullet prefab if it will be instantiated.
// It now contains data only for ECS.
export function Bullet(entity, x = 100, y = 100) {
entity.name = "Bullet";
entity.tag = "bullet";
entity.addComponent("transform", new TransformComponent({
position: { x, y },
scale: { x: 0.2, y: 0.2 }
}));
entity.addComponent("rigidbody2d", new Rigidbody2DComponent({
mass: 1,
gravityScale: 1,
drag: 1,
useGravity: false
}));
entity.addComponent("collider", new ColliderComponent({
isTrigger: true
}));
entity.addComponent("renderer", new BoxRenderComponent({color:"#00ff11", zIndex:-20}));
entity.addComponent("bulletscript", new BulletScript());
}
class BulletScript extends ScriptComponent {
constructor(direction) {
super();
this.direction = direction;
this.lifetime = 2.0; // seconds
}
update(dt) {
this.transform.position.x += this.direction.x * 500 * dt;
this.transform.position.y += this.direction.y * 500 * dt;
this.lifetime -= dt;
if (this.lifetime <= 0) {
this.entity.destroy(); // Returns to pool automatically
}
}
onTriggerEnter(other) {
if (other.tag === "enemy") {
othe
