Compare commits

...

9 Commits

Author SHA1 Message Date
c52a604f0f add README 2025-05-21 10:21:03 +02:00
8e9a089d34 meta 2025-05-20 17:01:59 +02:00
d5893eb8ef fix changeKey 2025-04-08 00:31:52 +02:00
1e006d46b9 little more bumpScale 2024-10-05 15:23:18 +02:00
0f84f90e05 tweak space theme 2024-10-05 15:22:15 +02:00
4287edab71 rename TetraControls 2024-10-03 22:26:37 +02:00
5c2eaca35a remove import 2024-10-03 00:38:10 +02:00
e7dc780173 T class 2024-10-03 00:37:53 +02:00
9721b311eb move mino materials to InstancedMino.prototype 2024-10-03 00:31:12 +02:00
8 changed files with 63 additions and 51 deletions

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# teTra
Falling blocks web game made with three.js librairy
![screenshot](https://git.malingrey.fr/adrien/teTra/raw/branch/master/thumbnail.png)

4
app.js
View File

@ -4,7 +4,7 @@ import { TRANSLATION, ROTATION, environment, InstancedMino, Mino, Playfield, Hol
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 TetraControls from './jsm/TetraControls.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'
@ -297,7 +297,7 @@ loadingManager.onStart = function (url, itemsLoaded, itemsTotal) {
const stats = new Stats() const stats = new Stats()
const settings = new Settings() const settings = new Settings()
const scene = new TetraScene(settings, loadingManager) const scene = new TetraScene(settings, loadingManager)
const controls = new TetraControls(scene.camera, renderer.domElement) const controls = new CameraControls(scene.camera, renderer.domElement)
const minoes = new InstancedMino() const minoes = new InstancedMino()
scene.add(minoes) scene.add(minoes)

View File

@ -48,10 +48,10 @@
transform-style: preserve-3d; transform-style: preserve-3d;
} }
.first.mino { top: -0.5em; left: -1em; } .T.tetromino .first.mino { top: -0.5em; left: -1em; }
.second.mino { top: -0.5em; left: 0em; } .T.tetromino .second.mino { top: -0.5em; left: 0em; }
.third.mino { top: -0.5em; left: 1em; } .T.tetromino .third.mino { top: -0.5em; left: 1em; }
.fourth.mino { top: 0.5em; left: 0em; } .T.tetromino .fourth.mino { top: 0.5em; left: 0em; }
.face { .face {
position: absolute; position: absolute;

View File

@ -8,6 +8,15 @@
<link rel="icon" href="favicon.ico"> <link rel="icon" href="favicon.ico">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/loading.css"> <link rel="stylesheet" href="css/loading.css">
<meta property="og:title" content="ᵀᴱTᴿᴬ"/>
<meta property="og:type" content="game"/>
<meta property="og:url" content="https://adrien.malingrey.fr/jeux/tetra/"/>
<meta property="og:image" content="https://adrien.malingrey.fr/jeux/tetra/thumbnail.png"/>
<meta property="og:image:width" content="250"/>
<meta property="og:image:height" content="250"/>
<meta property="og:description" content="Des blocs qui tombent en 3D"/>
<meta property="og:locale" content="fr_FR"/>
<meta property="og:site_name" content="adrien.malingrey.fr"/>
<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
<script type="importmap"> <script type="importmap">
{ {
@ -22,7 +31,7 @@
<body> <body>
<span id="loadingDiv"> <span id="loadingDiv">
<div class="scene"> <div class="scene">
<div class="tetromino"> <div class="T tetromino">
<div class="first mino"> <div class="first mino">
<div class="front face"></div> <div class="front face"></div>
<div class="back face"></div> <div class="back face"></div>

View File

@ -1,7 +1,7 @@
import { OrbitControls } from 'three/addons/controls/OrbitControls.js' import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
export default class TetraControls extends OrbitControls { export default class CameraControls extends OrbitControls {
constructor(camera, domElement) { constructor(camera, domElement) {
super(camera, domElement) super(camera, domElement)
this.autoRotate this.autoRotate

View File

@ -1,6 +1,6 @@
import * as THREE from 'three' import * as THREE from 'three'
import { GUI } from 'three/addons/libs/lil-gui.module.min.js' import { GUI } from 'three/addons/libs/lil-gui.module.min.js'
import { Mino, environment } from './Tetrominoes.js' import { environment } from './Tetrominoes.js'
export class Menu extends GUI { export class Menu extends GUI {
@ -43,21 +43,21 @@ export class Menu extends GUI {
this.settings.key = this.settings.addFolder("Commandes").open() this.settings.key = this.settings.addFolder("Commandes").open()
let moveLeftKeyController = this.settings.key.add(settings.key, "moveLeft").name('Gauche') let moveLeftKeyController = this.settings.key.add(settings.key, "moveLeft").name('Gauche')
moveLeftKeyController.domElement.onclick = this.changeKey.bind(moveLeftKeyController) moveLeftKeyController.domElement.onclick = this.changeKey(moveLeftKeyController)
let moveRightKeyController = this.settings.key.add(settings.key, "moveRight").name('Droite') let moveRightKeyController = this.settings.key.add(settings.key, "moveRight").name('Droite')
moveRightKeyController.domElement.onclick = this.changeKey.bind(moveRightKeyController) moveRightKeyController.domElement.onclick = this.changeKey(moveRightKeyController)
let rotateCWKeyController = this.settings.key.add(settings.key, "rotateCW").name('Rotation horaire') let rotateCWKeyController = this.settings.key.add(settings.key, "rotateCW").name('Rotation horaire')
rotateCWKeyController.domElement.onclick = this.changeKey.bind(rotateCWKeyController) rotateCWKeyController.domElement.onclick = this.changeKey(rotateCWKeyController)
let rotateCCWKeyController = this.settings.key.add(settings.key, "rotateCCW").name('anti-horaire') let rotateCCWKeyController = this.settings.key.add(settings.key, "rotateCCW").name('anti-horaire')
rotateCCWKeyController.domElement.onclick = this.changeKey.bind(rotateCCWKeyController) rotateCCWKeyController.domElement.onclick = this.changeKey(rotateCCWKeyController)
let softDropKeyController = this.settings.key.add(settings.key, "softDrop").name('Chute lente') let softDropKeyController = this.settings.key.add(settings.key, "softDrop").name('Chute lente')
softDropKeyController.domElement.onclick = this.changeKey.bind(softDropKeyController) softDropKeyController.domElement.onclick = this.changeKey(softDropKeyController)
let hardDropKeyController = this.settings.key.add(settings.key, "hardDrop").name('Chute rapide') let hardDropKeyController = this.settings.key.add(settings.key, "hardDrop").name('Chute rapide')
hardDropKeyController.domElement.onclick = this.changeKey.bind(hardDropKeyController) hardDropKeyController.domElement.onclick = this.changeKey(hardDropKeyController)
let holdKeyController = this.settings.key.add(settings.key, "hold").name('Garder') let holdKeyController = this.settings.key.add(settings.key, "hold").name('Garder')
holdKeyController.domElement.onclick = this.changeKey.bind(holdKeyController) holdKeyController.domElement.onclick = this.changeKey(holdKeyController)
let pauseKeyController = this.settings.key.add(settings.key, "pause").name('Pause') let pauseKeyController = this.settings.key.add(settings.key, "pause").name('Pause')
pauseKeyController.domElement.onclick = this.changeKey.bind(pauseKeyController) pauseKeyController.domElement.onclick = this.changeKey(pauseKeyController)
this.settings.delay = this.settings.addFolder("Répétition automatique").open() this.settings.delay = this.settings.addFolder("Répétition automatique").open()
this.settings.delay.add(settings,"arrDelay").name("ARR (ms)").min(2).max(200).step(1); this.settings.delay.add(settings,"arrDelay").name("ARR (ms)").min(2).max(200).step(1);
@ -167,8 +167,8 @@ export class Menu extends GUI {
localStorage["teTraSettings"] = JSON.stringify(this.settings.save()) localStorage["teTraSettings"] = JSON.stringify(this.settings.save())
} }
changeKey() { changeKey(settings) {
let controller = this.settings let controller = settings
let input = controller.domElement.getElementsByTagName("input")[0] let input = controller.domElement.getElementsByTagName("input")[0]
input.select() input.select()
input.onkeydown = function (event) { input.onkeydown = function (event) {

View File

@ -70,25 +70,6 @@ const sideMaterial = new THREE.MeshStandardMaterial({
export class InstancedMino extends THREE.InstancedMesh { export class InstancedMino extends THREE.InstancedMesh {
static materials = {
Plasma: new THREE.MeshStandardMaterial({
envMap: environment,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.7,
roughness: 0.6,
metalness: 1,
}),
Espace: new THREE.MeshStandardMaterial({
envMap: environment,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.8,
roughness: 0.1,
metalness: 0.99,
})
}
constructor() { constructor() {
let minoFaceShape = new THREE.Shape() let minoFaceShape = new THREE.Shape()
minoFaceShape.moveTo(.1, .1) minoFaceShape.moveTo(.1, .1)
@ -108,35 +89,34 @@ export class InstancedMino extends THREE.InstancedMesh {
const geometry = new THREE.ExtrudeGeometry(minoFaceShape, minoExtrudeSettings) const geometry = new THREE.ExtrudeGeometry(minoFaceShape, minoExtrudeSettings)
super(geometry, undefined, 2*ROWS*COLUMNS) super(geometry, undefined, 2*ROWS*COLUMNS)
this.offsets = new Uint8Array(2*this.count) this.offsets = new Uint8Array(2*this.count)
this.count = 0
} }
set theme(theme) { set theme(theme) {
if (theme == "Rétro") { if (theme == "Rétro") {
this.resetColor() this.resetColor()
this.update = this.updateOffset this.update = this.updateOffset
if (this.constructor.materials["Rétro"]) { if (this.materials["Rétro"]) {
this.material = this.constructor.materials["Rétro"] this.material = this.materials["Rétro"]
} else { } else {
this.constructor.materials["Rétro"] = [] this.materials["Rétro"] = []
const loadingManager = new THREE.LoadingManager(() => this.material = this.constructor.materials["Rétro"]) const loadingManager = new THREE.LoadingManager(() => this.material = this.materials["Rétro"])
new THREE.TextureLoader(loadingManager).load("images/sprites.png", (texture) => { new THREE.TextureLoader(loadingManager).load("images/sprites.png", (texture) => {
this.constructor.materials.Rétro[0] = this.constructor.materials.Rétro[2] = new TileMaterial({ this.materials.Rétro[0] = this.materials.Rétro[2] = new TileMaterial({
color: COLORS.RETRO, color: COLORS.RETRO,
map: texture, map: texture,
bumpMap: texture, bumpMap: texture,
bumpScale: 1.4, bumpScale: 1.5,
roughness: 0.25, roughness: 0.25,
metalness: 0.9, metalness: 0.9,
transparent: true, transparent: true,
}, 8, 8) }, 8, 8)
}) })
new THREE.TextureLoader(loadingManager).load("images/edges.png", (texture) => { new THREE.TextureLoader(loadingManager).load("images/edges.png", (texture) => {
this.constructor.materials.Rétro[1] = this.constructor.materials.Rétro[3] = this.constructor.materials.Rétro[4] = this.constructor.materials.Rétro[5] = new TileMaterial({ this.materials.Rétro[1] = this.materials.Rétro[3] = this.materials.Rétro[4] = this.materials.Rétro[5] = new TileMaterial({
color: COLORS.RETRO, color: COLORS.RETRO,
map: texture, map: texture,
bumpMap: texture, bumpMap: texture,
bumpScale: 1.4, bumpScale: 1.5,
roughness: 0.25, roughness: 0.25,
metalness: 0.9, metalness: 0.9,
transparent: true, transparent: true,
@ -145,7 +125,7 @@ export class InstancedMino extends THREE.InstancedMesh {
} }
} else { } else {
this.update = this.updateColor this.update = this.updateColor
this.material = this.constructor.materials[theme] this.material = this.materials[theme]
} }
} }
@ -188,6 +168,24 @@ export class InstancedMino extends THREE.InstancedMesh {
} }
} }
} }
InstancedMino.prototype.materials = {
Plasma: new THREE.MeshStandardMaterial({
envMap: environment,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.7,
roughness: 0.6,
metalness: 1,
}),
Espace: new THREE.MeshStandardMaterial({
envMap: environment,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.8,
roughness: 0.1,
metalness: 0.99,
})
}
class Mino extends THREE.Object3D { class Mino extends THREE.Object3D {

View File

@ -70,10 +70,10 @@ export class Vortex extends THREE.Group {
new THREE.TextureLoader(this.loadingManager).load("./images/dark.jpg", texture => { new THREE.TextureLoader(this.loadingManager).load("./images/dark.jpg", texture => {
texture.wrapS = THREE.RepeatWrapping texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.MirroredRepeatWrapping texture.wrapT = THREE.MirroredRepeatWrapping
texture.repeat.set(2, 2) texture.repeat.set(2, 4)
this.darkCylinder.material.map = texture this.darkCylinder.material.map = texture
}) })
this.darkCylinder.material.opacity = 0.05 this.darkCylinder.material.opacity = 0.08
new THREE.TextureLoader(this.loadingManager).load("./images/colorfull.jpg", texture => { new THREE.TextureLoader(this.loadingManager).load("./images/colorfull.jpg", texture => {
texture.wrapS = THREE.RepeatWrapping texture.wrapS = THREE.RepeatWrapping
@ -81,7 +81,7 @@ export class Vortex extends THREE.Group {
texture.repeat.set(2, 2) texture.repeat.set(2, 2)
this.colorFullCylinder.material.map = texture this.colorFullCylinder.material.map = texture
}) })
this.colorFullCylinder.material.opacity = 0.14 this.colorFullCylinder.material.opacity = 0.15
this.globalRotation = 0.028 this.globalRotation = 0.028
this.darkTextureRotation = 0.006 this.darkTextureRotation = 0.006