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); })();