@vladkrutenyuk/three-kvy-core
v2.3.9
Published
DEPRECATED — moved to the 'three-start' package. This library is no longer maintained; please migrate to 'three-start'.
Maintainers
Readme
⚠️ DEPRECATED
This package is no longer maintained.
It has been replaced by a redesigned and improved successor:
three-start.Please migrate:
npm uninstall @vladkrutenyuk/three-kvy-core npm i three-startAll future development, fixes, and documentation happen in
three-start.
three-kvy-core
A powerful Three.js extension to create scalable 3D apps of any-complexity.
This is lightweight component-oriented library, enabling an elegant lifecycle management system and basic initializations. Empower Three.js objects by reusable features within seamless context propagation with pluggable modules. The OOP-driven.
This library is designed in way not no include three.js as dependency.
It manipulates three.js entities but does not refer to them.
Doesn't impose any restrictions on your existing Three.js logic. Compatible with any approach you already use.
- Framework agnostic
- Extensible & plugin architecture
- Provides scalable development
- Events based
- Typescript support
- Lightweight. (3.5kb minzipped)
Installation
npm i three eventemitter3 @vladkrutenyuk/three-kvy-core Documentation, tutorials, examples
Visit three-kvy-core.vladkrutenyuk.ru
An
llms.txtfile is included in the package — a comprehensive guide for LLMs and AI coding agents covering all APIs, lifecycle, patterns, and addons.
What does it look like?
Quick Example: Coin Collector Mini-Game
WASD movement, spinning coins, pickup detection, and a simple score system — showcasing custom modules, multiple features per object, cross-feature communication, and fixed-tick logic.
import * as THREE from "three/webgpu";
import * as KVY from "@vladkrutenyuk/three-kvy-core";
// --- Setup ---
const renderer = new THREE.WebGPURenderer({ antialias: true });
const ctx = KVY.CoreContext.create({
renderer,
camera: new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100),
scene: new THREE.Scene(),
clock: new THREE.Clock(),
modules: {}
});
renderer.init().then(() => {
ctx.three.mount(document.querySelector("#canvas-container"));
ctx.run();
});
// --- Modules ---
class InputModule extends KVY.CoreContextModule {
keys = {};
useCtx() {
const onDown = (e) => { this.keys[e.code] = true; };
const onUp = (e) => { this.keys[e.code] = false; };
window.addEventListener("keydown", onDown);
window.addEventListener("keyup", onUp);
return () => {
window.removeEventListener("keydown", onDown);
window.removeEventListener("keyup", onUp);
};
}
pressed(code) {
return !!this.keys[code];
}
}
class FixedTickModule extends KVY.CoreContextModule {
step = 1 / 60;
_accumulator = 0;
useCtx(ctx) {
const onBeforeRender = () => {
this._accumulator += ctx.deltaTime;
while (this._accumulator >= this.step) {
this._accumulator -= this.step;
this.emit("fixedtick", this.step);
}
};
ctx.three.on("renderbefore", onBeforeRender);
return () => ctx.three.off("renderbefore", onBeforeRender);
}
}
ctx.assignModules({
input: new InputModule(),
fixedTick: new FixedTickModule(),
});
// --- Features ---
class PlayerMovement extends KVY.Object3DFeature {
speed = 5;
score = 0;
onBeforeRender(ctx) {
const { input } = ctx.modules;
const dt = ctx.deltaTime;
const dir = new THREE.Vector3();
if (input.pressed("KeyW")) dir.z -= 1;
if (input.pressed("KeyS")) dir.z += 1;
if (input.pressed("KeyA")) dir.x -= 1;
if (input.pressed("KeyD")) dir.x += 1;
if (dir.length() > 0) {
dir.normalize();
this.object.position.addScaledVector(dir, this.speed * dt);
}
}
addScore() {
this.score += 1;
console.log("%c Score: " + this.score, "color: gold; font-weight: bold;");
}
}
class CoinSpin extends KVY.Object3DFeature {
onBeforeRender(ctx) {
this.object.rotateY(ctx.deltaTime * 3);
}
}
class CoinPickup extends KVY.Object3DFeature {
_playerFeature = null;
useCtx(ctx) {
// Find PlayerMovement feature on any object in the scene
ctx.root.traverse((child) => {
const found = KVY.getFeature(child, PlayerMovement);
if (found) this._playerFeature = found;
});
// Subscribe to fixed tick for consistent pickup detection
const onFixedTick = () => this._checkPickup();
ctx.modules.fixedTick.on("fixedtick", onFixedTick);
return () => ctx.modules.fixedTick.off("fixedtick", onFixedTick);
}
_checkPickup() {
if (!this._playerFeature) return;
const dist = this.object.position.distanceTo(this._playerFeature.object.position);
if (dist < 1) {
this._playerFeature.addScore();
this.object.removeFromParent();
KVY.clear(this.object);
}
}
}
// --- Build Scene ---
const player = new THREE.Mesh(
new THREE.BoxGeometry(0.8, 0.8, 0.8),
new THREE.MeshNormalMaterial()
);
ctx.root.add(player);
KVY.addFeature(player, PlayerMovement);
for (let i = 0; i < 6; i++) {
const coin = new THREE.Mesh(
new THREE.CylinderGeometry(0.3, 0.3, 0.08, 16),
new THREE.MeshStandardMaterial({ color: 0xffd700 })
);
coin.position.set(
(Math.random() - 0.5) * 10,
0,
(Math.random() - 0.5) * 10
);
coin.rotateX(Math.PI / 2);
ctx.root.add(coin);
KVY.addFeature(coin, CoinSpin);
KVY.addFeature(coin, CoinPickup);
}
ctx.root.add(new THREE.AmbientLight(0xffffff, 0.5));
ctx.root.add(new THREE.DirectionalLight(0xffffff, 1));
ctx.three.camera.position.set(0, 10, 10);
ctx.three.camera.lookAt(0, 0, 0);