
If anyone is interested in helping me with coding some of the game, please reach out. It's something I've been working on in between @enginewitty and the PIMP backend work, and my own PeakeCoin Projects. This is the code as far as I have come. It can be a lot of work, but it does come with its perks.
I'll work on getting this uploaded so it's playable as I work on it.
//game.js
// Babylon.js 2D top-down setup
const canvas = document.getElementById("gameCanvas");
const engine = new BABYLON.Engine(canvas, true);
const scene = new BABYLON.Scene(engine);
scene.clearColor = new BABYLON.Color3(0.95, 0.95, 0.95);
// Camera for side-view 2.5D (like Mario)
const camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 50, 150), scene);
camera.setTarget(new BABYLON.Vector3(0, 50, 0));
camera.mode = BABYLON.Camera.ORTHOGRAPHIC_CAMERA;
camera.orthoLeft = -canvas.width / 2;
camera.orthoRight = canvas.width / 2;
camera.orthoTop = canvas.height / 2;
camera.orthoBottom = -canvas.height / 2;
// Add lighting (essential for Babylon.js materials)
const light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene);
light.intensity = 1.0;
// Simple tile map (10x10 for demo)
const TILE_SIZE = 48;
const MAP_W = 10;
const MAP_H = 10;
const map = [
[0,0,0,0,0,0,0,0,0,0],
[0,1,1,0,0,0,1,1,1,0],
[0,0,0,0,1,0,0,0,1,0],
[0,1,0,0,1,0,1,0,0,0],
[0,1,0,0,0,0,1,0,1,0],
[0,0,0,1,1,0,0,0,1,0],
[0,1,0,0,0,0,1,0,0,0],
[0,1,1,1,0,1,1,0,1,0],
[0,0,0,0,0,0,0,0,1,0],
[0,0,1,1,1,0,0,0,0,0],
];
// Create side-view ground platforms (like Mario levels)
for (let x = 0; x < MAP_W * 2; x++) {
// Ground platform
const ground = BABYLON.MeshBuilder.CreateBox(`ground_${x}`, {width: TILE_SIZE, height: TILE_SIZE/2, depth: TILE_SIZE}, scene);
ground.position.x = (x - MAP_W) * TILE_SIZE;
ground.position.y = 0; // Ground level
ground.position.z = 0;
const groundMat = new BABYLON.StandardMaterial(`groundMat_${x}`, scene);
groundMat.diffuseColor = new BABYLON.Color3(0.4, 0.3, 0.2); // Brown ground
ground.material = groundMat;
// Occasional platforms at different heights
if (x % 4 === 0 && x > 2) {
const platform = BABYLON.MeshBuilder.CreateBox(`platform_${x}`, {width: TILE_SIZE * 2, height: TILE_SIZE/3, depth: TILE_SIZE}, scene);
platform.position.x = (x - MAP_W) * TILE_SIZE;
platform.position.y = 60 + Math.sin(x * 0.5) * 30; // Varying platform heights
platform.position.z = 0;
const platMat = new BABYLON.StandardMaterial(`platMat_${x}`, scene);
platMat.diffuseColor = new BABYLON.Color3(0.2, 0.6, 0.2); // Green platforms
platform.material = platMat;
}
}
// Player as a colored circle
// Player as a simple human figure (top-down)
const playerGroup = new BABYLON.TransformNode("playerGroup", scene);
// Create super smooth character with joint hierarchy
const charSize = 20;
// Create materials with bright outline effect
const createOutlineMaterial = (color, outlineColor = new BABYLON.Color3(0, 1, 1)) => {
const mat = new BABYLON.StandardMaterial("material", scene);
mat.diffuseColor = color;
mat.emissiveColor = color.scale(0.1); // Slight glow
// Create outline effect using edge rendering
mat.backFaceCulling = false;
return mat;
};
// Head with smooth sphere
const head = BABYLON.MeshBuilder.CreateSphere("head", {diameter: charSize, segments: 32}, scene);
const headMat = createOutlineMaterial(new BABYLON.Color3(1, 0.85, 0.7));
head.material = headMat;
head.position.y = 55;
head.position.z = 0;
head.parent = playerGroup;
// Neck connector (smooth joint)
const neck = BABYLON.MeshBuilder.CreateCylinder("neck", {height: 8, diameter: 6, tessellation: 16}, scene);
neck.material = headMat;
neck.position.y = 46;
neck.parent = playerGroup;
// Torso (smooth rounded rectangle)
const torso = BABYLON.MeshBuilder.CreateCylinder("torso", {height: charSize * 1.4, diameter: charSize * 0.9, tessellation: 8}, scene);
const torsoMat = createOutlineMaterial(new BABYLON.Color3(0.9, 0.3, 0.3));
torso.material = torsoMat;
torso.position.y = 35;
torso.parent = playerGroup;
// Shoulder joints (smooth connection points)
const leftShoulder = BABYLON.MeshBuilder.CreateSphere("leftShoulder", {diameter: 6, segments: 16}, scene);
leftShoulder.material = headMat;
leftShoulder.position.x = -charSize * 0.4;
leftShoulder.position.y = 42;
leftShoulder.parent = playerGroup;
const rightShoulder = BABYLON.MeshBuilder.CreateSphere("rightShoulder", {diameter: 6, segments: 16}, scene);
rightShoulder.material = headMat;
rightShoulder.position.x = charSize * 0.4;
rightShoulder.position.y = 42;
rightShoulder.parent = playerGroup;
// Upper arms (smooth cylinders)
const leftUpperArm = BABYLON.MeshBuilder.CreateCylinder("leftUpperArm", {height: charSize * 0.7, diameter: 5, tessellation: 12}, scene);
const armMat = createOutlineMaterial(new BABYLON.Color3(1, 0.8, 0.6));
leftUpperArm.material = armMat;
leftUpperArm.position.x = -charSize * 0.4;
leftUpperArm.position.y = 35;
leftUpperArm.parent = playerGroup;
const rightUpperArm = BABYLON.MeshBuilder.CreateCylinder("rightUpperArm", {height: charSize * 0.7, diameter: 5, tessellation: 12}, scene);
rightUpperArm.material = armMat;
rightUpperArm.position.x = charSize * 0.4;
rightUpperArm.position.y = 35;
rightUpperArm.parent = playerGroup;
// Elbow joints
const leftElbow = BABYLON.MeshBuilder.CreateSphere("leftElbow", {diameter: 4, segments: 12}, scene);
leftElbow.material = armMat;
leftElbow.position.x = -charSize * 0.4;
leftElbow.position.y = 28;
leftElbow.parent = playerGroup;
const rightElbow = BABYLON.MeshBuilder.CreateSphere("rightElbow", {diameter: 4, segments: 12}, scene);
rightElbow.material = armMat;
rightElbow.position.x = charSize * 0.4;
rightElbow.position.y = 28;
rightElbow.parent = playerGroup;
// Forearms
const leftForearm = BABYLON.MeshBuilder.CreateCylinder("leftForearm", {height: charSize * 0.6, diameter: 4, tessellation: 12}, scene);
leftForearm.material = armMat;
leftForearm.position.x = -charSize * 0.4;
leftForearm.position.y = 22;
leftForearm.parent = playerGroup;
const rightForearm = BABYLON.MeshBuilder.CreateCylinder("rightForearm", {height: charSize * 0.6, diameter: 4, tessellation: 12}, scene);
rightForearm.material = armMat;
rightForearm.position.x = charSize * 0.4;
rightForearm.position.y = 22;
rightForearm.parent = playerGroup;
// Hip joints
const leftHip = BABYLON.MeshBuilder.CreateSphere("leftHip", {diameter: 6, segments: 16}, scene);
const legMat = createOutlineMaterial(new BABYLON.Color3(0.2, 0.2, 0.9));
leftHip.material = legMat;
leftHip.position.x = -charSize * 0.2;
leftHip.position.y = 25;
leftHip.parent = playerGroup;
const rightHip = BABYLON.MeshBuilder.CreateSphere("rightHip", {diameter: 6, segments: 16}, scene);
rightHip.material = legMat;
rightHip.position.x = charSize * 0.2;
rightHip.position.y = 25;
rightHip.parent = playerGroup;
// Thighs
const leftThigh = BABYLON.MeshBuilder.CreateCylinder("leftThigh", {height: charSize * 0.8, diameter: 6, tessellation: 12}, scene);
leftThigh.material = legMat;
leftThigh.position.x = -charSize * 0.2;
leftThigh.position.y = 18;
leftThigh.parent = playerGroup;
const rightThigh = BABYLON.MeshBuilder.CreateCylinder("rightThigh", {height: charSize * 0.8, diameter: 6, tessellation: 12}, scene);
rightThigh.material = legMat;
rightThigh.position.x = charSize * 0.2;
rightThigh.position.y = 18;
rightThigh.parent = playerGroup;
// Knee joints
const leftKnee = BABYLON.MeshBuilder.CreateSphere("leftKnee", {diameter: 5, segments: 12}, scene);
leftKnee.material = legMat;
leftKnee.position.x = -charSize * 0.2;
leftKnee.position.y = 12;
leftKnee.parent = playerGroup;
const rightKnee = BABYLON.MeshBuilder.CreateSphere("rightKnee", {diameter: 5, segments: 12}, scene);
rightKnee.material = legMat;
rightKnee.position.x = charSize * 0.2;
rightKnee.position.y = 12;
rightKnee.parent = playerGroup;
// Calves
const leftCalf = BABYLON.MeshBuilder.CreateCylinder("leftCalf", {height: charSize * 0.7, diameter: 5, tessellation: 12}, scene);
leftCalf.material = legMat;
leftCalf.position.x = -charSize * 0.2;
leftCalf.position.y = 7;
leftCalf.parent = playerGroup;
const rightCalf = BABYLON.MeshBuilder.CreateCylinder("rightCalf", {height: charSize * 0.7, diameter: 5, tessellation: 12}, scene);
rightCalf.material = legMat;
rightCalf.position.x = charSize * 0.2;
rightCalf.position.y = 7;
rightCalf.parent = playerGroup;
// Feet for complete walking cycle
const leftFoot = BABYLON.MeshBuilder.CreateBox("leftFoot", {width: 8, height: 3, depth: 12}, scene);
const footMat = createOutlineMaterial(new BABYLON.Color3(0.3, 0.2, 0.1));
leftFoot.material = footMat;
leftFoot.position.x = -charSize * 0.2;
leftFoot.position.y = 2;
leftFoot.parent = playerGroup;
const rightFoot = BABYLON.MeshBuilder.CreateBox("rightFoot", {width: 8, height: 3, depth: 12}, scene);
rightFoot.material = footMat;
rightFoot.position.x = charSize * 0.2;
rightFoot.position.y = 2;
rightFoot.parent = playerGroup;
// Add bright outline effect to entire character
playerGroup.getChildMeshes().forEach(mesh => {
if (mesh.material) {
mesh.renderOutline = true;
mesh.outlineWidth = 0.5;
mesh.outlineColor = new BABYLON.Color3(0, 1, 1); // Bright cyan outline
}
});
// Store body parts for smooth animation
const bodyParts = {
head, neck, torso,
leftShoulder, rightShoulder,
leftUpperArm, rightUpperArm,
leftElbow, rightElbow,
leftForearm, rightForearm,
leftHip, rightHip,
leftThigh, rightThigh,
leftKnee, rightKnee,
leftCalf, rightCalf,
leftFoot, rightFoot
};
// Initial position (standing on ground)
playerGroup.position.x = 0;
playerGroup.position.y = 25; // Standing on ground level
playerGroup.position.z = 0;
// Add a simple test box to verify rendering
const testBox = BABYLON.MeshBuilder.CreateBox("testBox", {size: 20}, scene);
const testMat = new BABYLON.StandardMaterial("testMat", scene);
testMat.diffuseColor = new BABYLON.Color3(1, 0, 0); // bright red
testBox.material = testMat;
testBox.position.x = 100;
testBox.position.y = 100;
testBox.position.z = 2;
console.log("Player created at position:", playerGroup.position);
console.log("Camera position:", camera.position);
console.log("Camera ortho bounds:", camera.orthoLeft, camera.orthoRight, camera.orthoTop, camera.orthoBottom);
console.log("Test box created at:", testBox.position);
// Player movement with Mario-style physics
let playerX = 0;
let playerVelocityY = 0; // For jumping/gravity
let isMoving = false;
let walkCycle = 0;
const speed = 3;
const keys = {};
// Super smooth joint animation with fluid movement
function animateWalk() {
if (!isMoving) {
// Smoothly return to neutral pose with interpolation
bodyParts.leftUpperArm.rotation.z = BABYLON.Scalar.Lerp(bodyParts.leftUpperArm.rotation.z, 0, 0.15);
bodyParts.rightUpperArm.rotation.z = BABYLON.Scalar.Lerp(bodyParts.rightUpperArm.rotation.z, 0, 0.15);
bodyParts.leftForearm.rotation.z = BABYLON.Scalar.Lerp(bodyParts.leftForearm.rotation.z, 0, 0.15);
bodyParts.rightForearm.rotation.z = BABYLON.Scalar.Lerp(bodyParts.rightForearm.rotation.z, 0, 0.15);
bodyParts.leftThigh.rotation.z = BABYLON.Scalar.Lerp(bodyParts.leftThigh.rotation.z, 0, 0.15);
bodyParts.rightThigh.rotation.z = BABYLON.Scalar.Lerp(bodyParts.rightThigh.rotation.z, 0, 0.15);
bodyParts.leftCalf.rotation.z = BABYLON.Scalar.Lerp(bodyParts.leftCalf.rotation.z, 0, 0.15);
bodyParts.rightCalf.rotation.z = BABYLON.Scalar.Lerp(bodyParts.rightCalf.rotation.z, 0, 0.15);
bodyParts.torso.position.y = BABYLON.Scalar.Lerp(bodyParts.torso.position.y, 35, 0.15);
bodyParts.head.position.y = BABYLON.Scalar.Lerp(bodyParts.head.position.y, 55, 0.15);
return;
}
walkCycle += 0.2; // Smooth cycle speed
// Calculate fluid movement phases
const armPhase = Math.sin(walkCycle) * 0.5;
const legPhase = Math.sin(walkCycle) * 0.7;
const kneePhase = Math.sin(walkCycle + Math.PI/3) * 0.4;
const elbowPhase = Math.sin(walkCycle + Math.PI/4) * 0.3;
const bounce = Math.abs(Math.sin(walkCycle * 2)) * 2;
const sway = Math.sin(walkCycle * 0.5) * 0.05;
// Super smooth arm movement with natural joint articulation
bodyParts.leftUpperArm.rotation.z = armPhase;
bodyParts.rightUpperArm.rotation.z = -armPhase;
// Forearms follow with natural elbow bend
bodyParts.leftForearm.rotation.z = armPhase * 0.6 + elbowPhase;
bodyParts.rightForearm.rotation.z = -armPhase * 0.6 - elbowPhase;
// Biomechanically correct leg movement
bodyParts.leftThigh.rotation.z = legPhase;
bodyParts.rightThigh.rotation.z = -legPhase;
// Correct knee bending: knees bend when leg swings forward (lifting), straighten when pushing back
const leftKneeBend = legPhase > 0 ? Math.abs(legPhase) * 0.8 : 0; // Bend when swinging forward
const rightKneeBend = -legPhase > 0 ? Math.abs(-legPhase) * 0.8 : 0; // Bend when swinging forward
bodyParts.leftCalf.rotation.z = -leftKneeBend; // Negative rotation bends knee forward
bodyParts.rightCalf.rotation.z = -rightKneeBend;
// Smooth body movement
bodyParts.torso.position.y = 35 + bounce;
bodyParts.head.position.y = 55 + bounce;
bodyParts.neck.position.y = 46 + bounce;
// Natural body sway and rotation
bodyParts.torso.rotation.z = sway;
bodyParts.head.rotation.z = sway * 0.5;
// Shoulder movement follows arm swing
bodyParts.leftShoulder.position.y = 42 + bounce + Math.sin(walkCycle + armPhase) * 0.5;
bodyParts.rightShoulder.position.y = 42 + bounce + Math.sin(walkCycle - armPhase) * 0.5;
// Hip movement follows leg swing
bodyParts.leftHip.rotation.z = legPhase * 0.3;
bodyParts.rightHip.rotation.z = -legPhase * 0.3;
// Joint connections move smoothly
bodyParts.leftElbow.position.y = 28 + bounce + Math.sin(walkCycle + elbowPhase) * 0.3;
bodyParts.rightElbow.position.y = 28 + bounce + Math.sin(walkCycle - elbowPhase) * 0.3;
// Knee position follows thigh movement but also lifts during bending
const leftKneeHeight = 12 + bounce * 0.5 + (leftKneeBend * 2); // Knee lifts when bending
const rightKneeHeight = 12 + bounce * 0.5 + (rightKneeBend * 2);
bodyParts.leftKnee.position.y = leftKneeHeight;
bodyParts.rightKnee.position.y = rightKneeHeight;
// Calf position should follow knee movement
bodyParts.leftCalf.position.y = 7 + bounce * 0.3 + (leftKneeBend * 1.5);
bodyParts.rightCalf.position.y = 7 + bounce * 0.3 + (rightKneeBend * 1.5);
// Feet follow the walking cycle - lift when leg swings forward, plant when stepping
const leftFootLift = leftKneeBend * 3; // Foot lifts more dramatically
const rightFootLift = rightKneeBend * 3;
bodyParts.leftFoot.position.y = 2 + leftFootLift;
bodyParts.rightFoot.position.y = 2 + rightFootLift;
// Foot angle changes during walk cycle
bodyParts.leftFoot.rotation.z = leftKneeBend * 0.3; // Foot tilts up when lifting
bodyParts.rightFoot.rotation.z = rightKneeBend * 0.3;
}
// Handle keyboard input
window.addEventListener("keydown", (e) => {
keys[e.key.toLowerCase()] = true;
});
window.addEventListener("keyup", (e) => {
keys[e.key.toLowerCase()] = false;
});
// Side-scrolling movement (Mario-style)
function updateMovement() {
let moving = false;
let deltaX = 0;
// Horizontal movement only (side-scrolling)
if (keys['d'] || keys['arrowright']) {
deltaX += speed;
moving = true;
// Face right
playerGroup.rotation.y = 0;
}
if (keys['a'] || keys['arrowleft']) {
deltaX -= speed;
moving = true;
// Face left
playerGroup.rotation.y = Math.PI;
}
// Jump (Mario-style)
if (keys['w'] || keys['arrowup'] || keys[' ']) {
if (Math.abs(playerGroup.position.y - 25) < 5) { // Only jump if on ground
playerVelocityY = 8; // Jump force
}
}
// Apply horizontal movement
playerX += deltaX;
playerGroup.position.x = playerX;
// Apply gravity and vertical movement
playerVelocityY -= 0.3; // Gravity
playerGroup.position.y += playerVelocityY;
// Ground collision
if (playerGroup.position.y <= 25) {
playerGroup.position.y = 25;
playerVelocityY = 0;
}
// Update animation state
isMoving = moving;
animateWalk();
}
engine.runRenderLoop(() => {
updateMovement();
scene.render();
});