Score: 0
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
window.THREE = THREE;
window.PlayscapeGame = {
sdk: null,
scene: null,
camera: null,
renderer: null,
player: null,
playerModel: null,
targets: [],
bullets: [],
score: 0,
spawnTimer: 0,
lastShootTime: 0,
shootCooldown: 0.3,
loader: null,
init({ sdk, mountEl, config }) {
this.sdk = sdk;
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(0x87CEEB);
this.scene.fog = new THREE.Fog(0x87CEEB, 20, 100);
const viewport = sdk.getViewport();
this.camera = new THREE.PerspectiveCamera(75, viewport.width / viewport.height, 0.1, 1000);
this.camera.position.set(0, 5, 10);
this.camera.lookAt(0, 0, 0);
this.renderer = new THREE.WebGLRenderer({ antialias: true });
const dpr = window.devicePixelRatio || 1;
this.renderer.setSize(viewport.width, viewport.height);
this.renderer.setPixelRatio(dpr);
this.renderer.shadowMap.enabled = true;
mountEl.appendChild(this.renderer.domElement);
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
this.scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 10, 5);
directionalLight.castShadow = true;
this.scene.add(directionalLight);
const groundGeometry = new THREE.PlaneGeometry(50, 100);
const groundMaterial = new THREE.MeshStandardMaterial({ color: 0xA8E6CF });
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
this.scene.add(ground);
this.player = new THREE.Group();
this.player.position.set(0, 0, 8);
this.scene.add(this.player);
this.loader = new GLTFLoader();
this.loader.load('character.glb', (gltf) => {
this.playerModel = gltf.scene;
this.playerModel.scale.set(1.5, 1.5, 1.5);
this.playerModel.rotation.y = Math.PI;
this.playerModel.traverse((node) => {
if (node.isMesh) {
node.castShadow = true;
}
});
this.player.add(this.playerModel);
}, undefined, (error) => {
console.error('Error loading character:', error);
const fallbackGeometry = new THREE.ConeGeometry(0.5, 1, 4);
const fallbackMaterial = new THREE.MeshStandardMaterial({ color: 0x4ECDC4 });
const fallback = new THREE.Mesh(fallbackGeometry, fallbackMaterial);
fallback.rotation.z = Math.PI;
fallback.castShadow = true;
this.player.add(fallback);
});
this.handleResize = () => {
const viewport = sdk.getViewport();
this.camera.aspect = viewport.width / viewport.height;
this.camera.updateProjectionMatrix();
this.renderer.setSize(viewport.width, viewport.height);
};
window.addEventListener('resize', this.handleResize);
},
spawnTarget() {
const x = (Math.random() - 0.5) * 12;
const size = 0.5 + Math.random() * 0.5;
const colors = [0xFF6B6B, 0xFFE66D, 0xFF8B94];
const color = colors[Math.floor(Math.random() * colors.length)];
const geometry = new THREE.BoxGeometry(size, size, size);
const material = new THREE.MeshStandardMaterial({ color });
const target = new THREE.Mesh(geometry, material);
target.position.set(x, size / 2, -40);
target.castShadow = true;
target.userData.speed = 3 + Math.random() * 2;
this.scene.add(target);
this.targets.push(target);
},
shootBullet() {
const bulletGroup = new THREE.Group();
const geometry = new THREE.SphereGeometry(0.15, 8, 8);
const material = new THREE.MeshStandardMaterial({
color: 0xFFE66D,
emissive: 0xFFE66D,
emissiveIntensity: 1.0,
metalness: 0.3,
roughness: 0.4
});
const bullet = new THREE.Mesh(geometry, material);
bulletGroup.add(bullet);
const glowGeometry = new THREE.SphereGeometry(0.25, 8, 8);
const glowMaterial = new THREE.MeshBasicMaterial({
color: 0xFFE66D,
transparent: true,
opacity: 0.3
});
const glow = new THREE.Mesh(glowGeometry, glowMaterial);
bulletGroup.add(glow);
const trailGeometry = new THREE.ConeGeometry(0.08, 0.5, 8);
const trailMaterial = new THREE.MeshBasicMaterial({
color: 0xFFE66D,
transparent: true,
opacity: 0.5
});
const trail = new THREE.Mesh(trailGeometry, trailMaterial);
trail.rotation.x = Math.PI / 2;
trail.position.z = 0.25;
bulletGroup.add(trail);
bulletGroup.position.copy(this.player.position);
bulletGroup.position.z -= 1;
this.scene.add(bulletGroup);
this.bullets.push(bulletGroup);
},
update(dt) {
if (this.sdk.isPaused()) return;
const controls = this.sdk.getControls();
const tunables = this.sdk.getTunables();
const moveSpeed = tunables['player.moveSpeed'] || 10;
const bulletSpeed = tunables['bullet.speed'] || 20;
const spawnRate = tunables['spawn.rate'] || 1.5;
this.player.position.x += controls.MOVE_X * moveSpeed * dt;
this.player.position.x = Math.max(-7, Math.min(7, this.player.position.x));
const currentTime = performance.now() / 1000;
if (controls.SHOOT && currentTime - this.lastShootTime > this.shootCooldown) {
this.shootBullet();
this.lastShootTime = currentTime;
}
this.spawnTimer += dt;
if (this.spawnTimer > spawnRate) {
this.spawnTarget();
this.spawnTimer = 0;
}
for (let i = this.bullets.length - 1; i >= 0; i--) {
const bullet = this.bullets[i];
bullet.position.z -= bulletSpeed * dt;
bullet.rotation.z += dt * 10;
if (bullet.position.z < -50) {
this.scene.remove(bullet);
this.bullets.splice(i, 1);
continue;
}
for (let j = this.targets.length - 1; j >= 0; j--) {
const target = this.targets[j];
const distance = bullet.position.distanceTo(target.position);
if (distance < 1) {
this.scene.remove(bullet);
this.scene.remove(target);
this.bullets.splice(i, 1);
this.targets.splice(j, 1);
this.score += 10;
document.getElementById('score').textContent = 'Score: ' + this.score;
this.sdk.addCoins(1);
break;
}
}
}
for (let i = this.targets.length - 1; i >= 0; i--) {
const target = this.targets[i];
target.position.z += target.userData.speed * dt;
target.rotation.y += dt * 2;
if (target.position.z > 10) {
this.scene.remove(target);
this.targets.splice(i, 1);
}
}
this.renderer.render(this.scene, this.camera);
},
destroy() {
window.removeEventListener('resize', this.handleResize);
this.renderer.dispose();
this.renderer.domElement.remove();
}
};
(async () => {
const manifest = await fetch('playscape.json').then(r => r.json());
const sdk = new Playscape.PlayscapeSDK({
container: document.getElementById('game-container'),
manifest,
debug: false
});
await sdk.startGame(window.PlayscapeGame);
})();