refactor into scene

This commit is contained in:
2026-01-25 22:49:42 +01:00
parent 83801a792f
commit 17a87bf16f
3 changed files with 74 additions and 65 deletions

74
app.js
View File

@@ -1,10 +1,9 @@
import * as THREE from 'three' import * as THREE from 'three'
import { scheduler } from './jsm/scheduler.js' import { scheduler } from './jsm/scheduler.js'
import { TRANSLATION, ROTATION, environment, InstancedMino, Mino, Playfield, HoldQueue, NextQueue } from './jsm/Tetrominoes.js' import { TRANSLATION, ROTATION, environment, Mino, HoldQueue, NextQueue } from './jsm/Tetrominoes.js'
import Settings from './jsm/Settings.js' import Settings from './jsm/Settings.js'
import { Stats } from './jsm/Stats.js' import { Stats } from './jsm/Stats.js'
import { Menu } from './jsm/Menu.js' import { Menu } from './jsm/Menu.js'
import CameraControls from './jsm/CameraControls.js'
import { TetraScene } from './jsm/TetraScene.js' import { TetraScene } from './jsm/TetraScene.js'
import * as FPS from 'three/addons/libs/stats.module.js' import * as FPS from 'three/addons/libs/stats.module.js'
@@ -35,7 +34,7 @@ let game = {
nextQueue.init() nextQueue.init()
holdQueue.piece = undefined holdQueue.piece = undefined
holdQueue.clear() holdQueue.clear()
playfield.init() scene.playfield.init()
scene.music.currentTime = 0 scene.music.currentTime = 0
@@ -61,7 +60,7 @@ let game = {
if (settings.musicVolume) scene.music.play() if (settings.musicVolume) scene.music.play()
if (playfield.piece) { if (scene.playfield.piece) {
scheduler.resetInterval(game.fall, stats.fallPeriod) scheduler.resetInterval(game.fall, stats.fallPeriod)
} else { } else {
this.generate() this.generate()
@@ -70,10 +69,10 @@ let game = {
generate: function(nextPiece=nextQueue.shift()) { generate: function(nextPiece=nextQueue.shift()) {
nextPiece.lockDelay = stats.lockDelay nextPiece.lockDelay = stats.lockDelay
playfield.piece = nextPiece scene.playfield.piece = nextPiece
playfield.piece.onLockDown = game.lockDown scene.playfield.piece.onLockDown = game.lockDown
if (playfield.piece.canMove(TRANSLATION.NONE)) { if (scene.playfield.piece.canMove(TRANSLATION.NONE)) {
scheduler.resetInterval(game.fall, stats.fallPeriod) scheduler.resetInterval(game.fall, stats.fallPeriod)
} else { } else {
game.over() // block out game.over() // block out
@@ -81,16 +80,16 @@ let game = {
}, },
fall: function() { fall: function() {
playfield.piece.move(TRANSLATION.DOWN) scene.playfield.piece.move(TRANSLATION.DOWN)
}, },
lockDown: function() { lockDown: function() {
scheduler.clearTimeout(game.lockDown) scheduler.clearTimeout(game.lockDown)
scheduler.clearInterval(game.fall) scheduler.clearInterval(game.fall)
if (playfield.lock(playfield.piece)) { if (scene.playfield.lock(scene.playfield.piece)) {
let tSpin = playfield.piece.tSpin let tSpin = scene.playfield.piece.tSpin
let nbClearedLines = playfield.clearLines() let nbClearedLines = scene.playfield.clearLines()
stats.lockDown(nbClearedLines, tSpin) stats.lockDown(nbClearedLines, tSpin)
if (settings.sfxVolume) { if (settings.sfxVolume) {
if (nbClearedLines == 4 || (tSpin && nbClearedLines)) { if (nbClearedLines == 4 || (tSpin && nbClearedLines)) {
@@ -129,11 +128,11 @@ let game = {
}, },
over: function() { over: function() {
playfield.piece.locking = false scene.playfield.piece.locking = false
document.onkeydown = null document.onkeydown = null
window.onblur = null window.onblur = null
renderer.domElement.onfocus = null scene.renderer.domElement.onfocus = null
menu.settings.domElement.onfocus = null menu.settings.domElement.onfocus = null
this.playing = false this.playing = false
scene.music.pause() scene.music.pause()
@@ -170,16 +169,16 @@ function playSound(sound, note=0) {
/* Handle player inputs */ /* Handle player inputs */
let playerActions = { let playerActions = {
moveLeft: () => playfield.piece.move(TRANSLATION.LEFT), moveLeft: () => scene.playfield.piece.move(TRANSLATION.LEFT),
moveRight: () => playfield.piece.move(TRANSLATION.RIGHT), moveRight: () => scene.playfield.piece.move(TRANSLATION.RIGHT),
rotateCW: () => playfield.piece.rotate(ROTATION.CW), rotateCW: () => scene.playfield.piece.rotate(ROTATION.CW),
rotateCCW: () => playfield.piece.rotate(ROTATION.CCW), rotateCCW: () => scene.playfield.piece.rotate(ROTATION.CCW),
softDrop: function () { softDrop: function () {
if (playfield.piece.move(TRANSLATION.DOWN)) stats.score++ if (scene.playfield.piece.move(TRANSLATION.DOWN)) stats.score++
}, },
hardDrop: function () { hardDrop: function () {
@@ -188,19 +187,19 @@ let playerActions = {
scene.hardDropSound.stop() scene.hardDropSound.stop()
scene.hardDropSound.play() scene.hardDropSound.play()
} }
while (playfield.piece.move(TRANSLATION.DOWN)) stats.score += 2 while (scene.playfield.piece.move(TRANSLATION.DOWN)) stats.score += 2
game.lockDown() game.lockDown()
playfield.hardDropAnimation.reset() scene.playfield.hardDropAnimation.reset()
playfield.hardDropAnimation.play() scene.playfield.hardDropAnimation.play()
}, },
hold: function () { hold: function () {
if (playfield.piece.holdEnabled) { if (scene.playfield.piece.holdEnabled) {
scheduler.clearInterval(game.fall) scheduler.clearInterval(game.fall)
scheduler.clearTimeout(game.lockDown) scheduler.clearTimeout(game.lockDown)
let heldpiece = holdQueue.piece let heldpiece = holdQueue.piece
holdQueue.piece = playfield.piece holdQueue.piece = scene.playfield.piece
game.generate(heldpiece) game.generate(heldpiece)
} }
}, },
@@ -277,22 +276,11 @@ function resumeOnKeyDown(event) {
/* Scene */ /* Scene */
const renderer = new THREE.WebGLRenderer({
powerPreference: "high-performance",
antialias: true,
stencil: false
})
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setClearColor(0x000000, 10)
renderer.toneMapping = THREE.ACESFilmicToneMapping
document.body.appendChild(renderer.domElement)
renderer.domElement.tabIndex = 1
let loadingManager = new THREE.LoadingManager( let loadingManager = new THREE.LoadingManager(
function() { function() {
loadingDiv.style.display = "none" loadingDiv.style.display = "none"
menu.startButton.show() menu.startButton.show()
renderer.setAnimationLoop(animate) scene.renderer.setAnimationLoop(animate)
}, },
function (url, itemsLoaded, itemsTotal) { function (url, itemsLoaded, itemsTotal) {
loadingPercent.innerText = Math.floor(100 * itemsLoaded / itemsTotal) + '%' loadingPercent.innerText = Math.floor(100 * itemsLoaded / itemsTotal) + '%'
@@ -309,18 +297,13 @@ loadingManager.onStart = function (url, itemsLoaded, itemsTotal) {
const settings = new Settings() const settings = new Settings()
const stats = new Stats(settings) const stats = new Stats(settings)
const scene = new TetraScene(settings, loadingManager) const scene = new TetraScene(settings, loadingManager)
const controls = new CameraControls(scene.camera, renderer.domElement)
const minoes = new InstancedMino()
scene.add(minoes)
const holdQueue = new HoldQueue() const holdQueue = new HoldQueue()
scene.add(holdQueue) scene.add(holdQueue)
const playfield = new Playfield(loadingManager)
scene.add(playfield)
const nextQueue = new NextQueue() const nextQueue = new NextQueue()
scene.add(nextQueue) scene.add(nextQueue)
const menu = new Menu(game, settings, stats, scene, minoes, playfield) const menu = new Menu(game, settings, stats, scene)
menu.load() menu.load()
let fps let fps
@@ -338,20 +321,15 @@ const clock = new THREE.Clock()
function animate() { function animate() {
const delta = clock.getDelta() const delta = clock.getDelta()
scene.updateMatrixWorld()
scene.update(delta) scene.update(delta)
playfield.update(delta)
minoes.update()
controls.update()
renderer.render(scene, scene.camera) environment.camera.update(scene.renderer, scene)
environment.camera.update(renderer, scene)
fps?.update() fps?.update()
} }
window.addEventListener("resize", () => { window.addEventListener("resize", () => {
renderer.setSize(window.innerWidth, window.innerHeight) scene.renderer.setSize(window.innerWidth, window.innerHeight)
scene.camera.aspect = window.innerWidth / window.innerHeight scene.camera.aspect = window.innerWidth / window.innerHeight
scene.camera.updateProjectionMatrix() scene.camera.updateProjectionMatrix()
}) })

View File

@@ -4,7 +4,7 @@ import { environment } from './Tetrominoes.js'
export class Menu extends GUI { export class Menu extends GUI {
constructor(game, settings, stats, scene, minoes, playfield) { constructor(game, settings, stats, scene) {
super({title: "ᵀᴱTᴿᴬ"}) super({title: "ᵀᴱTᴿᴬ"})
this.startButton = this.add(game, "start").name("Jouer").hide() this.startButton = this.add(game, "start").name("Jouer").hide()
@@ -28,14 +28,6 @@ export class Menu extends GUI {
this.settings.add(settings, "theme", ["Plasma", "Espace", "Rétro"]).name("Thème").onChange(theme => { this.settings.add(settings, "theme", ["Plasma", "Espace", "Rétro"]).name("Thème").onChange(theme => {
scene.theme = theme scene.theme = theme
minoes.theme = theme
if (theme == "Rétro") {
playfield.edge.visible = false
playfield.retroEdge.visible = true
} else {
playfield.edge.visible = true
playfield.retroEdge.visible = false
}
if (dev) changeMaterial() if (dev) changeMaterial()
}) })
@@ -76,10 +68,14 @@ export class Menu extends GUI {
function changeMaterial() { function changeMaterial() {
material?.destroy() material?.destroy()
material = dev.addFolder("minoes material").close() material = dev.addFolder("minoes material").close()
material.add(minoes.material, "constructor", ["MeshBasicMaterial", "MeshStandardMaterial", "MeshPhysicalMaterial"]).listen().onChange(type => { material.add(scene.minoes.material, "constructor", [
"MeshBasicMaterial",
"MeshStandardMaterial",
"MeshPhysicalMaterial"
]).listen().onChange(type => {
switch(type) { switch(type) {
case "MeshBasicMaterial": case "MeshBasicMaterial":
minoes.material = new THREE.MeshBasicMaterial({ scene.minoes.material = new THREE.MeshBasicMaterial({
envMap: environment, envMap: environment,
side: THREE.DoubleSide, side: THREE.DoubleSide,
transparent: true, transparent: true,
@@ -88,7 +84,7 @@ export class Menu extends GUI {
}) })
break break
case "MeshStandardMaterial": case "MeshStandardMaterial":
minoes.material = new THREE.MeshStandardMaterial({ scene.minoes.material = new THREE.MeshStandardMaterial({
envMap: environment, envMap: environment,
side: THREE.DoubleSide, side: THREE.DoubleSide,
transparent: true, transparent: true,
@@ -98,7 +94,7 @@ export class Menu extends GUI {
}) })
break break
case "MeshPhysicalMaterial": case "MeshPhysicalMaterial":
minoes.material = new THREE.MeshPhysicalMaterial({ scene.minoes.material = new THREE.MeshPhysicalMaterial({
envMap: environment, envMap: environment,
side: THREE.DoubleSide, side: THREE.DoubleSide,
transparent: true, transparent: true,
@@ -110,11 +106,11 @@ export class Menu extends GUI {
}) })
break break
} }
minoes.update = minoes.updateColor scene.minoes.update = scene.minoes.updateColor
changeMaterial() changeMaterial()
}) })
let minoMaterial = minoes.material instanceof Array ? minoes.material[0] : minoes.material let minoMaterial = scene.minoes.material instanceof Array ? scene.minoes.material[0] : scene.minoes.material
if ("opacity" in minoMaterial) material.add(minoMaterial, "opacity" ).min(0).max(1) if ("opacity" in minoMaterial) material.add(minoMaterial, "opacity" ).min(0).max(1)
if ("reflectivity" in minoMaterial) material.add(minoMaterial, "reflectivity" ).min(0).max(1) if ("reflectivity" in minoMaterial) material.add(minoMaterial, "reflectivity" ).min(0).max(1)
if ("roughness" in minoMaterial) material.add(minoMaterial, "roughness" ).min(0).max(1) if ("roughness" in minoMaterial) material.add(minoMaterial, "roughness" ).min(0).max(1)
@@ -155,7 +151,7 @@ export class Menu extends GUI {
vortex.add(scene.vortex.darkCylinder.material, "opacity").name("dark").min(0).max(1).listen() vortex.add(scene.vortex.darkCylinder.material, "opacity").name("dark").min(0).max(1).listen()
vortex.add(scene.vortex.colorFullCylinder.material, "opacity").name("colorFull").min(0).max(1).listen() vortex.add(scene.vortex.colorFullCylinder.material, "opacity").name("colorFull").min(0).max(1).listen()
changeMaterial(minoes.material.constructor.name) changeMaterial(scene.minoes.material.constructor.name)
let fog = dev.addFolder("fog").close() let fog = dev.addFolder("fog").close()
fog.add(scene.fog, "near", 0, 200) fog.add(scene.fog, "near", 0, 200)

View File

@@ -1,13 +1,30 @@
import * as THREE from 'three' import * as THREE from 'three'
import CameraControls from './CameraControls.js'
import { Vortex } from './Vortex.js' import { Vortex } from './Vortex.js'
import { Playfield, InstancedMino } from './Tetrominoes.js'
export class TetraScene extends THREE.Scene { export class TetraScene extends THREE.Scene {
constructor(settings, loadingManager) { constructor(settings, loadingManager) {
super() super()
this.renderer = new THREE.WebGLRenderer({
powerPreference: "high-performance",
antialias: true,
stencil: false
})
this.renderer.setSize(window.innerWidth, window.innerHeight)
this.renderer.setClearColor(0x000000, 10)
this.renderer.toneMapping = THREE.ACESFilmicToneMapping
this.renderer.toneMappingExposure = .8
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
document.body.appendChild(this.renderer.domElement)
this.renderer.domElement.tabIndex = 1
this.camera = new THREE.PerspectiveCamera(100, window.innerWidth / window.innerHeight, 0.1, 1000) this.camera = new THREE.PerspectiveCamera(100, window.innerWidth / window.innerHeight, 0.1, 1000)
this.camera.position.set(5, 16, 12) this.camera.position.set(5, 15, 12)
this.controls = new CameraControls(this.camera, this.renderer.domElement)
this.vortex = new Vortex(loadingManager) this.vortex = new Vortex(loadingManager)
this.add(this.vortex) this.add(this.vortex)
@@ -43,6 +60,12 @@ export class TetraScene extends THREE.Scene {
this.hardDropSound.setBuffer(buffer) this.hardDropSound.setBuffer(buffer)
}.bind(this)) }.bind(this))
this.playfield = new Playfield(loadingManager)
this.add(this.playfield)
this.minoes = new InstancedMino()
this.add(this.minoes)
this.theme = settings.theme this.theme = settings.theme
} }
@@ -54,6 +77,8 @@ export class TetraScene extends THREE.Scene {
this.directionalLight.position.set(5, -20, 20) this.directionalLight.position.set(5, -20, 20)
this.music.src = "audio/benevolence.m4a" this.music.src = "audio/benevolence.m4a"
this.fog.color.set(0xffffff) this.fog.color.set(0xffffff)
this.playfield.edge.visible = true
this.playfield.retroEdge.visible = false
break break
case "Espace": case "Espace":
this.ambientLight.intensity = 7 this.ambientLight.intensity = 7
@@ -61,6 +86,8 @@ export class TetraScene extends THREE.Scene {
this.directionalLight.position.set(2, -3, 20) this.directionalLight.position.set(2, -3, 20)
this.music.src = "audio/benevolence.m4a" this.music.src = "audio/benevolence.m4a"
this.fog.color.set(0x000000) this.fog.color.set(0x000000)
this.playfield.edge.visible = true
this.playfield.retroEdge.visible = false
break break
case "Rétro": case "Rétro":
this.ambientLight.intensity = 1 this.ambientLight.intensity = 1
@@ -68,9 +95,12 @@ export class TetraScene extends THREE.Scene {
this.directionalLight.position.set(19, 120, 200) this.directionalLight.position.set(19, 120, 200)
this.music.src = "audio/Tetris_MkVaffQuasi_Ultimix_OC_ReMix.mp3" this.music.src = "audio/Tetris_MkVaffQuasi_Ultimix_OC_ReMix.mp3"
this.fog.color.set(0x000000) this.fog.color.set(0x000000)
this.playfield.edge.visible = false
this.playfield.retroEdge.visible = true
break break
} }
this.vortex.theme = theme this.vortex.theme = theme
this.minoes.theme = theme
} }
get fogColor() { get fogColor() {
@@ -81,6 +111,11 @@ export class TetraScene extends THREE.Scene {
} }
update(delta) { update(delta) {
this.controls.update()
this.updateMatrixWorld()
this.vortex.update(delta) this.vortex.update(delta)
this.playfield.update(delta)
this.minoes.update(delta)
this.renderer.render(this, this.camera)
} }
} }