PlayCanvas

PlayCanvas 62,178 (github.com/playcanvas/engine (https://github.com/playcanvas/engine 16,941 ), MIT, 2.22.4) is a game engine "built on WebGL2 & WebGPU" with glTF streaming, Draco 7,495 and Basis compression, ammo.js 4,573 physics and first-class 3D Gaussian splat rendering. Its distinguishing feature is the PlayCanvas Editor, a collaborative browser-based scene editor: the engine is free, while the hosted Editor has a Free plan (unlimited public projects, 1 GB), Personal at $15 a month and Organization at $50 per seat a month for private projects (prices from playcanvas.com/plans in September 2026). Choose it when a team wants a Unity-like editor in the browser; Babylon.js Versus PlayCanvas compares it with Babylon.js 89,603 .

PlayCanvas pricing in 3D: the free MIT engine and the hosted Editor's plans per monthHTMLLive
<!doctype html>
<style>
  body { margin: 0; background: #f5f1e8; font: 13px system-ui, sans-serif; color: #263238; }
  canvas { display: block; max-width: 100%; }
  .ui { padding: 4px 8px; }
</style>
<script type="importmap">
{ "imports": {
  "three": "https://cdn.jsdelivr.net/npm/three@0.186.1/build/three.module.js",
  "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/" } }
</script>
<div class="ui">Team size <input type="range" id="team" min="1" max="10" value="3"> <b id="n">3</b> people (monthly cost; prices from playcanvas.com/plans, September 2026)</div>
<script type="module">
import * as THREE from 'three';

const PLANS = [
  { name: 'Engine (MIT)', cost: () => 0, note: 'free, self-hosted', color: '#3f7d3a' },
  { name: 'Editor Free', cost: () => 0, note: 'public projects, 1 GB', color: '#2a9d8f' },
  { name: 'Personal', cost: () => 15, note: '$15 / month', color: '#e09a10' },
  { name: 'Organization', cost: (n) => 50 * n, note: '$50 per seat', color: '#b5452f' },
];

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(600, 300);
document.body.prepend(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color('#fbf8f2');
const camera = new THREE.PerspectiveCamera(35, 2, 0.1, 50);
camera.position.set(0.8, 2.4, 6.4);
camera.lookAt(0, 1, 0);
scene.add(new THREE.HemisphereLight('#ffffff', '#8d6e63', 1.3));
const sun = new THREE.DirectionalLight('#ffffff', 2);
sun.position.set(3, 5, 4);
scene.add(sun);
const base = new THREE.Mesh(new THREE.BoxGeometry(5, 0.05, 1.4), new THREE.MeshStandardMaterial({ color: '#e0d8c8' }));
scene.add(base);

// A redrawable text sprite
function textSprite(width = 1.6) {
  const canvas = Object.assign(document.createElement('canvas'), { width: 512, height: 128 });
  const g = canvas.getContext('2d');
  const map = new THREE.CanvasTexture(canvas);
  map.colorSpace = THREE.SRGBColorSpace;
  const sprite = new THREE.Sprite(new THREE.SpriteMaterial({ map }));
  sprite.scale.set(width, width / 4, 1);
  sprite.draw = (lines) => {
    g.clearRect(0, 0, 512, 128);
    g.textAlign = 'center';  g.fillStyle = '#263238';
    lines.forEach(([text, size], i) => { g.font = `bold ${size}px system-ui, sans-serif`; g.fillText(text, 256, 50 + i * 50); });
    map.needsUpdate = true;
  };
  scene.add(sprite);
  return sprite;
}

const bars = PLANS.map((plan, i) => {
  const bar = new THREE.Mesh(new THREE.BoxGeometry(0.7, 1, 0.7), new THREE.MeshStandardMaterial({ color: plan.color, roughness: 0.5 }));
  bar.position.x = (i - 1.5) * 1.2;
  scene.add(bar);
  const name = textSprite();
  name.position.set(bar.position.x, -0.3, 0.8);
  name.draw([[plan.name, 40], [plan.note, 30]]);
  return { plan, bar, value: textSprite(1.3), h: 0.02 };
});

const slider = document.getElementById('team');
renderer.setAnimationLoop(() => {
  const n = +slider.value;
  document.getElementById('n').textContent = n;
  for (const b of bars) {
    const cost = b.plan.cost(n);
    const target = Math.max(0.03, cost / 200 * 2.6);    // $200 → 2.6 m
    b.h += (target - b.h) * 0.1;
    b.bar.scale.y = b.h;
    b.bar.position.y = b.h / 2;
    b.value.position.set(b.bar.position.x, b.h + 0.3, 0);
    const text = cost ? `$${cost}` : 'free';
    if (b.value.last !== text) { b.value.last = text; b.value.draw([[text, 64]]); }
  }
  renderer.render(scene, camera);
});
</script>