Compare commits
38 Commits
cae3dc9af5
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| c52a604f0f | |||
| 8e9a089d34 | |||
| d5893eb8ef | |||
| 1e006d46b9 | |||
| 0f84f90e05 | |||
| 4287edab71 | |||
| 5c2eaca35a | |||
| e7dc780173 | |||
| 9721b311eb | |||
| d3f6cf9b71 | |||
| 5b058a58b3 | |||
| 9fca05ae6e | |||
| 1a7628bb42 | |||
| bc4ba54c0a | |||
| 653befdc02 | |||
| 1b3f837bf0 | |||
| 375d47397e | |||
| efb6482238 | |||
| 7fd3c04a2d | |||
| 1e42c2160f | |||
| 74bf8521fb | |||
| fef08f64e8 | |||
| 3c8fc95e23 | |||
| f7b7b74e01 | |||
| af9e0c481a | |||
| 90eb3247e0 | |||
| 2a25dbe4b0 | |||
| d9397c4bcb | |||
| ca93423bf8 | |||
| ae8dcb7077 | |||
| 6ed614d536 | |||
| 8c5b704b3c | |||
| 1b0f1c07d2 | |||
| c8eb029987 | |||
| 825fbca97b | |||
| ce94604fc0 | |||
| 07daa4a9cf | |||
| abf562fd89 |
5
README.md
Normal file
5
README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# teTra
|
||||||
|
|
||||||
|
Falling blocks web game made with three.js librairy
|
||||||
|
|
||||||
|

|
||||||
76
app.js
76
app.js
@ -1,11 +1,12 @@
|
|||||||
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, Mino, Playfield, HoldQueue, NextQueue } from './jsm/Tetrominoes.js'
|
import { TRANSLATION, ROTATION, environment, InstancedMino, Mino, Playfield, 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 { TetraGUI } from './jsm/TetraGUI.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'
|
||||||
|
|
||||||
|
|
||||||
HTMLElement.prototype.addNewChild = function (tag, properties) {
|
HTMLElement.prototype.addNewChild = function (tag, properties) {
|
||||||
@ -25,11 +26,11 @@ let game = {
|
|||||||
start: function() {
|
start: function() {
|
||||||
stats.init()
|
stats.init()
|
||||||
|
|
||||||
gui.startButton.hide()
|
menu.startButton.hide()
|
||||||
gui.settings.close()
|
menu.stats.show()
|
||||||
gui.stats.show()
|
menu.settings.close()
|
||||||
|
|
||||||
Mino.meshes.clear()
|
Mino.instances.clear()
|
||||||
|
|
||||||
nextQueue.init()
|
nextQueue.init()
|
||||||
holdQueue.piece = undefined
|
holdQueue.piece = undefined
|
||||||
@ -49,11 +50,11 @@ let game = {
|
|||||||
document.onkeydown = onkeydown
|
document.onkeydown = onkeydown
|
||||||
document.onkeyup = onkeyup
|
document.onkeyup = onkeyup
|
||||||
window.onblur = game.pause
|
window.onblur = game.pause
|
||||||
gui.settings.domElement.onclick = game.pause
|
menu.settings.domElement.onclick = game.pause
|
||||||
|
|
||||||
document.body.classList.remove("pause")
|
document.body.classList.remove("pause")
|
||||||
gui.resumeButton.hide()
|
menu.resumeButton.hide()
|
||||||
gui.pauseButton.show()
|
menu.pauseButton.show()
|
||||||
|
|
||||||
stats.clock.start()
|
stats.clock.start()
|
||||||
stats.clock.elapsedTime = stats.elapsedTime
|
stats.clock.elapsedTime = stats.elapsedTime
|
||||||
@ -61,7 +62,7 @@ let game = {
|
|||||||
if (settings.musicVolume) scene.music.play()
|
if (settings.musicVolume) scene.music.play()
|
||||||
|
|
||||||
if (playfield.piece) {
|
if (playfield.piece) {
|
||||||
scheduler.setInterval(game.fall, stats.fallPeriod)
|
scheduler.resetInterval(game.fall, stats.fallPeriod)
|
||||||
} else {
|
} else {
|
||||||
this.generate()
|
this.generate()
|
||||||
}
|
}
|
||||||
@ -73,7 +74,7 @@ let game = {
|
|||||||
playfield.piece.onLockDown = game.lockDown
|
playfield.piece.onLockDown = game.lockDown
|
||||||
|
|
||||||
if (playfield.piece.canMove(TRANSLATION.NONE)) {
|
if (playfield.piece.canMove(TRANSLATION.NONE)) {
|
||||||
scheduler.setInterval(game.fall, stats.fallPeriod)
|
scheduler.resetInterval(game.fall, stats.fallPeriod)
|
||||||
} else {
|
} else {
|
||||||
game.over() // block out
|
game.over() // block out
|
||||||
}
|
}
|
||||||
@ -106,7 +107,7 @@ let game = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
pause: function() {
|
pause: function() {
|
||||||
gui.settings.domElement.onclick = null
|
menu.settings.domElement.onclick = null
|
||||||
|
|
||||||
stats.elapsedTime = stats.clock.elapsedTime
|
stats.elapsedTime = stats.clock.elapsedTime
|
||||||
stats.clock.stop()
|
stats.clock.stop()
|
||||||
@ -123,8 +124,8 @@ let game = {
|
|||||||
|
|
||||||
pauseSpan.onfocus = game.resume
|
pauseSpan.onfocus = game.resume
|
||||||
document.body.classList.add("pause")
|
document.body.classList.add("pause")
|
||||||
gui.pauseButton.hide()
|
menu.pauseButton.hide()
|
||||||
gui.resumeButton.show()
|
menu.resumeButton.show()
|
||||||
},
|
},
|
||||||
|
|
||||||
over: function() {
|
over: function() {
|
||||||
@ -133,15 +134,15 @@ let game = {
|
|||||||
document.onkeydown = null
|
document.onkeydown = null
|
||||||
window.onblur = null
|
window.onblur = null
|
||||||
renderer.domElement.onfocus = null
|
renderer.domElement.onfocus = null
|
||||||
gui.settings.domElement.onfocus = null
|
menu.settings.domElement.onfocus = null
|
||||||
game.playing = false
|
game.playing = false
|
||||||
scene.music.pause()
|
scene.music.pause()
|
||||||
stats.clock.stop()
|
stats.clock.stop()
|
||||||
messagesSpan.addNewChild("div", { className: "show-level-animation", innerHTML: `<h1>GAME<br/>OVER</h1>` })
|
messagesSpan.addNewChild("div", { className: "show-level-animation", innerHTML: `<h1>GAME<br/>OVER</h1>` })
|
||||||
|
|
||||||
gui.pauseButton.hide()
|
menu.pauseButton.hide()
|
||||||
gui.startButton.name("Rejouer")
|
menu.startButton.name("Rejouer")
|
||||||
gui.startButton.show()
|
menu.startButton.show()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,8 +216,8 @@ function onkeydown(event) {
|
|||||||
actionsQueue.unshift(action)
|
actionsQueue.unshift(action)
|
||||||
scheduler.clearTimeout(repeat)
|
scheduler.clearTimeout(repeat)
|
||||||
scheduler.clearInterval(autorepeat)
|
scheduler.clearInterval(autorepeat)
|
||||||
if (action == playerActions.softDrop) scheduler.setInterval(autorepeat, settings.fallPeriod / 20)
|
if (action == playerActions.softDrop) scheduler.resetInterval(autorepeat, settings.fallPeriod / 20)
|
||||||
else scheduler.setTimeout(repeat, settings.dasDelay)
|
else scheduler.resetTimeout(repeat, settings.dasDelay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,7 +226,7 @@ function onkeydown(event) {
|
|||||||
function repeat() {
|
function repeat() {
|
||||||
if (actionsQueue.length) {
|
if (actionsQueue.length) {
|
||||||
actionsQueue[0]()
|
actionsQueue[0]()
|
||||||
scheduler.setInterval(autorepeat, settings.arrDelay)
|
scheduler.resetInterval(autorepeat, settings.arrDelay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,7 +265,6 @@ function resumeOnKeyDown(event) {
|
|||||||
|
|
||||||
/* Scene */
|
/* Scene */
|
||||||
|
|
||||||
|
|
||||||
const renderer = new THREE.WebGLRenderer({
|
const renderer = new THREE.WebGLRenderer({
|
||||||
powerPreference: "high-performance",
|
powerPreference: "high-performance",
|
||||||
antialias: true,
|
antialias: true,
|
||||||
@ -278,8 +278,8 @@ renderer.domElement.tabIndex = 1
|
|||||||
|
|
||||||
let loadingManager = new THREE.LoadingManager(
|
let loadingManager = new THREE.LoadingManager(
|
||||||
function() {
|
function() {
|
||||||
loaddingCircle.style.display = "none"
|
loadingDiv.style.display = "none"
|
||||||
gui.startButton.show()
|
menu.startButton.show()
|
||||||
renderer.setAnimationLoop(animate)
|
renderer.setAnimationLoop(animate)
|
||||||
},
|
},
|
||||||
function (url, itemsLoaded, itemsTotal) {
|
function (url, itemsLoaded, itemsTotal) {
|
||||||
@ -291,15 +291,16 @@ let loadingManager = new THREE.LoadingManager(
|
|||||||
)
|
)
|
||||||
loadingManager.onStart = function (url, itemsLoaded, itemsTotal) {
|
loadingManager.onStart = function (url, itemsLoaded, itemsTotal) {
|
||||||
loadingPercent.innerText = "0%"
|
loadingPercent.innerText = "0%"
|
||||||
loaddingCircle.style.display = "block"
|
loadingDiv.style.display = "flex"
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
scene.add(Mino.meshes)
|
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)
|
const playfield = new Playfield(loadingManager)
|
||||||
@ -307,8 +308,14 @@ scene.add(playfield)
|
|||||||
const nextQueue = new NextQueue()
|
const nextQueue = new NextQueue()
|
||||||
scene.add(nextQueue)
|
scene.add(nextQueue)
|
||||||
|
|
||||||
const gui = new TetraGUI(game, settings, stats, scene, controls, playfield)
|
const menu = new Menu(game, settings, stats, scene, minoes, playfield)
|
||||||
gui.load()
|
menu.load()
|
||||||
|
|
||||||
|
let fps
|
||||||
|
if (window.location.search.includes("fps")) {
|
||||||
|
let fps = new FPS.default()
|
||||||
|
document.body.appendChild(fps.dom)
|
||||||
|
}
|
||||||
|
|
||||||
messagesSpan.onanimationend = function (event) {
|
messagesSpan.onanimationend = function (event) {
|
||||||
event.target.remove()
|
event.target.remove()
|
||||||
@ -322,12 +329,13 @@ function animate() {
|
|||||||
scene.updateMatrixWorld()
|
scene.updateMatrixWorld()
|
||||||
scene.update(delta)
|
scene.update(delta)
|
||||||
playfield.update(delta)
|
playfield.update(delta)
|
||||||
Mino.meshes.update()
|
minoes.update()
|
||||||
controls.update()
|
controls.update()
|
||||||
gui.update()
|
|
||||||
|
|
||||||
renderer.render(scene, scene.camera)
|
renderer.render(scene, scene.camera)
|
||||||
environment.camera.update(renderer, scene)
|
environment.camera.update(renderer, scene)
|
||||||
|
|
||||||
|
fps?.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("resize", () => {
|
window.addEventListener("resize", () => {
|
||||||
@ -337,7 +345,7 @@ window.addEventListener("resize", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
window.onbeforeunload = function (event) {
|
window.onbeforeunload = function (event) {
|
||||||
gui.save()
|
menu.save()
|
||||||
localStorage["teTraHighScore"] = stats.highScore
|
localStorage["teTraHighScore"] = stats.highScore
|
||||||
return !game.playing
|
return !game.playing
|
||||||
}
|
}
|
||||||
323
css/loading.css
323
css/loading.css
@ -1,279 +1,70 @@
|
|||||||
@-webkit-keyframes outerRotate1 {
|
#loadingDiv {
|
||||||
0% {
|
position: absolute;
|
||||||
transform: translate(-50%, -50%) rotate(0);
|
top: 0;
|
||||||
}
|
left: 0;
|
||||||
|
display: flex;
|
||||||
100% {
|
flex-flow: column;
|
||||||
transform: translate(-50%, -50%) rotate(360deg);
|
justify-content: center;
|
||||||
}
|
box-sizing: border-box;
|
||||||
|
font-family: "Open Sans", sans-serif;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: lightsteelblue;
|
||||||
|
text-align: center;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
padding: 30vh;
|
||||||
|
background-color: black;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@-moz-keyframes outerRotate1 {
|
.scene {
|
||||||
0% {
|
width: 200px;
|
||||||
transform: translate(-50%, -50%) rotate(0);
|
height: 200px;
|
||||||
}
|
margin: 0 auto;
|
||||||
|
perspective: 200px;
|
||||||
100% {
|
font-size: 40px;
|
||||||
transform: translate(-50%, -50%) rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@-o-keyframes outerRotate1 {
|
.tetromino {
|
||||||
0% {
|
position: relative;
|
||||||
transform: translate(-50%, -50%) rotate(0);
|
top: 2em;
|
||||||
}
|
left: 2em;
|
||||||
|
width: 1em;
|
||||||
100% {
|
height: 1em;
|
||||||
transform: translate(-50%, -50%) rotate(360deg);
|
transform-style: preserve-3d;
|
||||||
}
|
transform: translateZ(0.5em);
|
||||||
|
animation: spinCube 5s infinite ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes outerRotate1 {
|
@keyframes spinCube {
|
||||||
0% {
|
0% { transform: translateZ(0.5em) rotateX( 0deg) rotateY( 0deg); }
|
||||||
transform: translate(-50%, -50%) rotate(0);
|
100% { transform: translateZ(0.5em) rotateX(360deg) rotateY(360deg); }
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: translate(-50%, -50%) rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@-webkit-keyframes outerRotate2 {
|
.mino {
|
||||||
0% {
|
width: 1em;
|
||||||
transform: translate(-50%, -50%) rotate(0);
|
height: 1em;
|
||||||
}
|
position: absolute;
|
||||||
|
transform-style: preserve-3d;
|
||||||
100% {
|
|
||||||
transform: translate(-50%, -50%) rotate(-360deg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@-moz-keyframes outerRotate2 {
|
.T.tetromino .first.mino { top: -0.5em; left: -1em; }
|
||||||
0% {
|
.T.tetromino .second.mino { top: -0.5em; left: 0em; }
|
||||||
transform: translate(-50%, -50%) rotate(0);
|
.T.tetromino .third.mino { top: -0.5em; left: 1em; }
|
||||||
}
|
.T.tetromino .fourth.mino { top: 0.5em; left: 0em; }
|
||||||
|
|
||||||
100% {
|
.face {
|
||||||
transform: translate(-50%, -50%) rotate(-360deg);
|
position: absolute;
|
||||||
}
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
padding: 0;
|
||||||
|
background: hsla(240, 100%, 0%, 0.4);
|
||||||
|
border: 1px solid hsla(240, 100%, 70%, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
@-o-keyframes outerRotate2 {
|
.front.face { transform: rotateY( 0deg) translateZ(0.5em); }
|
||||||
0% {
|
.right.face { transform: rotateY( 90deg) translateZ(0.5em); }
|
||||||
transform: translate(-50%, -50%) rotate(0);
|
.back.face { transform: rotateY(180deg) translateZ(0.5em); }
|
||||||
}
|
.left.face { transform: rotateY(-90deg) translateZ(0.5em); }
|
||||||
|
.top.face { transform: rotateX( 90deg) translateZ(0.5em); }
|
||||||
100% {
|
.bottom.face { transform: rotateX(-90deg) translateZ(0.5em); }
|
||||||
transform: translate(-50%, -50%) rotate(-360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes outerRotate2 {
|
|
||||||
0% {
|
|
||||||
transform: translate(-50%, -50%) rotate(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: translate(-50%, -50%) rotate(-360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes textColour {
|
|
||||||
0% {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
color: #3BB2D0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@-moz-keyframes textColour {
|
|
||||||
0% {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
color: #3BB2D0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@-o-keyframes textColour {
|
|
||||||
0% {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
color: #3BB2D0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes textColour {
|
|
||||||
0% {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
color: #3BB2D0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: #222;
|
|
||||||
}
|
|
||||||
|
|
||||||
#loaddingCircle {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
cursor: progress;
|
|
||||||
}
|
|
||||||
|
|
||||||
.e-loadholder {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
-webkit-transform: translate(-51%, -50%);
|
|
||||||
-moz-transform: translate(-51%, -50%);
|
|
||||||
-ms-transform: translate(-51%, -50%);
|
|
||||||
-o-transform: translate(-51%, -50%);
|
|
||||||
transform: translate(-51%, -50%);
|
|
||||||
width: 240px;
|
|
||||||
height: 240px;
|
|
||||||
border: 5px solid #1B5F70;
|
|
||||||
border-radius: 120px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.e-loadholder:after {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
-webkit-transform: translate(-51%, -50%);
|
|
||||||
-moz-transform: translate(-51%, -50%);
|
|
||||||
-ms-transform: translate(-51%, -50%);
|
|
||||||
-o-transform: translate(-51%, -50%);
|
|
||||||
transform: translate(-51%, -50%);
|
|
||||||
content: " ";
|
|
||||||
display: block;
|
|
||||||
background: #222;
|
|
||||||
transform-origin: center;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.e-loadholder:after {
|
|
||||||
width: 100px;
|
|
||||||
height: 200%;
|
|
||||||
-webkit-animation: outerRotate2 30s infinite linear;
|
|
||||||
-moz-animation: outerRotate2 30s infinite linear;
|
|
||||||
-o-animation: outerRotate2 30s infinite linear;
|
|
||||||
animation: outerRotate2 30s infinite linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
.e-loadholder .m-loader {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
-webkit-transform: translate(-51%, -50%);
|
|
||||||
-moz-transform: translate(-51%, -50%);
|
|
||||||
-ms-transform: translate(-51%, -50%);
|
|
||||||
-o-transform: translate(-51%, -50%);
|
|
||||||
transform: translate(-51%, -50%);
|
|
||||||
width: 200px;
|
|
||||||
height: 200px;
|
|
||||||
color: #888;
|
|
||||||
text-align: center;
|
|
||||||
border: 5px solid #2a93ae;
|
|
||||||
border-radius: 100px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
z-index: 20;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.e-loadholder .m-loader:after {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
-webkit-transform: translate(-51%, -50%);
|
|
||||||
-moz-transform: translate(-51%, -50%);
|
|
||||||
-ms-transform: translate(-51%, -50%);
|
|
||||||
-o-transform: translate(-51%, -50%);
|
|
||||||
transform: translate(-51%, -50%);
|
|
||||||
content: " ";
|
|
||||||
display: block;
|
|
||||||
background: #222;
|
|
||||||
transform-origin: center;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.e-loadholder .m-loader:after {
|
|
||||||
width: 100px;
|
|
||||||
height: 106%;
|
|
||||||
-webkit-animation: outerRotate1 15s infinite linear;
|
|
||||||
-moz-animation: outerRotate1 15s infinite linear;
|
|
||||||
-o-animation: outerRotate1 15s infinite linear;
|
|
||||||
animation: outerRotate1 15s infinite linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
.e-loadholder .m-loader .e-text {
|
|
||||||
font-family: "Open Sans", sans-serif;
|
|
||||||
font-size: 10px;
|
|
||||||
font-size: 1rem;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
-webkit-transform: translate(-51%, -50%);
|
|
||||||
-moz-transform: translate(-51%, -50%);
|
|
||||||
-ms-transform: translate(-51%, -50%);
|
|
||||||
-o-transform: translate(-51%, -50%);
|
|
||||||
transform: translate(-51%, -50%);
|
|
||||||
-webkit-animation: textColour 1s alternate linear infinite;
|
|
||||||
-moz-animation: textColour 1s alternate linear infinite;
|
|
||||||
-o-animation: textColour 1s alternate linear infinite;
|
|
||||||
animation: textColour 1s alternate linear infinite;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
width: 140px;
|
|
||||||
height: 140px;
|
|
||||||
text-align: center;
|
|
||||||
border: 5px solid #3bb2d0;
|
|
||||||
border-radius: 70px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
z-index: 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
.e-loadholder .m-loader .e-text:before, .e-loadholder .m-loader .e-text:after {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
-webkit-transform: translate(-51%, -50%);
|
|
||||||
-moz-transform: translate(-51%, -50%);
|
|
||||||
-ms-transform: translate(-51%, -50%);
|
|
||||||
-o-transform: translate(-51%, -50%);
|
|
||||||
transform: translate(-51%, -50%);
|
|
||||||
content: " ";
|
|
||||||
display: block;
|
|
||||||
background: #222;
|
|
||||||
transform-origin: center;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.e-loadholder .m-loader .e-text:before {
|
|
||||||
width: 110%;
|
|
||||||
height: 40px;
|
|
||||||
-webkit-animation: outerRotate2 3.5s infinite linear;
|
|
||||||
-moz-animation: outerRotate2 3.5s infinite linear;
|
|
||||||
-o-animation: outerRotate2 3.5s infinite linear;
|
|
||||||
animation: outerRotate2 3.5s infinite linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
.e-loadholder .m-loader .e-text:after {
|
|
||||||
width: 40px;
|
|
||||||
height: 110%;
|
|
||||||
-webkit-animation: outerRotate1 8s infinite linear;
|
|
||||||
-moz-animation: outerRotate1 8s infinite linear;
|
|
||||||
-o-animation: outerRotate1 8s infinite linear;
|
|
||||||
animation: outerRotate1 8s infinite linear;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -9,14 +9,16 @@ body {
|
|||||||
|
|
||||||
span {
|
span {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lil-gui {
|
.lil-menu {
|
||||||
--background-color: rgba(33, 37, 41, 30%);
|
--background-color: rgba(33, 37, 41, 30%);
|
||||||
--width: 200px;
|
--width: 200px;
|
||||||
}
|
}
|
||||||
@supports (backdrop-filter: blur()) {
|
@supports (backdrop-filter: blur()) {
|
||||||
.lil-gui {
|
.lil-menu {
|
||||||
backdrop-filter: blur(15px);
|
backdrop-filter: blur(15px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -27,7 +29,11 @@ span {
|
|||||||
left: 15px;
|
left: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lil-gui .controller.disabled {
|
.lil-menu.root > .title {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lil-menu .controller.disabled {
|
||||||
opacity: .8;
|
opacity: .8;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +57,7 @@ canvas {
|
|||||||
#messagesSpan div {
|
#messagesSpan div {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@ -166,11 +173,10 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pause #pauseSpan {
|
.pause #pauseSpan {
|
||||||
display: flex;
|
position: absolute;
|
||||||
position:absolute;
|
|
||||||
display: flex;
|
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
display: flex;
|
||||||
filter: blur(2px);
|
filter: blur(2px);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -181,4 +187,5 @@ h1 {
|
|||||||
font-size: 20vh;
|
font-size: 20vh;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
letter-spacing: .1em;
|
letter-spacing: .1em;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
BIN
images/edge.png
BIN
images/edge.png
Binary file not shown.
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 6.0 KiB |
BIN
images/edges.png
Normal file
BIN
images/edges.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 792 B After Width: | Height: | Size: 11 KiB |
63
index.html
63
index.html
@ -4,32 +4,73 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>teTra</title>
|
<title>ᵀᴱTᴿᴬ</title>
|
||||||
<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">
|
||||||
{
|
{
|
||||||
"imports": {
|
"imports": {
|
||||||
"three": "https://unpkg.com/three@0.155/build/three.module.js?module",
|
"three": "https://unpkg.com/three@0.169/build/three.module.js?module",
|
||||||
"three/addons/": "https://unpkg.com/three@0.155/examples/jsm/"
|
"three/addons/": "https://unpkg.com/three@0.169/examples/jsm/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="loaddingCircle">
|
<span id="loadingDiv">
|
||||||
<div class="e-loadholder">
|
<div class="scene">
|
||||||
<div class="m-loader">
|
<div class="T tetromino">
|
||||||
<span class="e-text">
|
<div class="first mino">
|
||||||
|
<div class="front face"></div>
|
||||||
|
<div class="back face"></div>
|
||||||
|
<div class="left face"></div>
|
||||||
|
<div class="right face"></div>
|
||||||
|
<div class="top face"></div>
|
||||||
|
<div class="bottom face"></div>
|
||||||
|
</div>
|
||||||
|
<div class="second mino">
|
||||||
|
<div class="front face"></div>
|
||||||
|
<div class="back face"></div>
|
||||||
|
<div class="left face"></div>
|
||||||
|
<div class="right face"></div>
|
||||||
|
<div class="top face"></div>
|
||||||
|
<div class="bottom face"></div>
|
||||||
|
</div>
|
||||||
|
<div class="third mino">
|
||||||
|
<div class="front face"></div>
|
||||||
|
<div class="back face"></div>
|
||||||
|
<div class="left face"></div>
|
||||||
|
<div class="right face"></div>
|
||||||
|
<div class="top face"></div>
|
||||||
|
<div class="bottom face"></div>
|
||||||
|
</div>
|
||||||
|
<div class="fourth mino">
|
||||||
|
<div class="front face"></div>
|
||||||
|
<div class="back face"></div>
|
||||||
|
<div class="left face"></div>
|
||||||
|
<div class="right face"></div>
|
||||||
|
<div class="top face"></div>
|
||||||
|
<div class="bottom face"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
<div>Chargement</div>
|
<div>Chargement</div>
|
||||||
<div id="loadingPercent">0%</div>
|
<div id="loadingPercent">0%</div>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</span>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span id="messagesSpan"></span>
|
<span id="messagesSpan"></span>
|
||||||
<span id="pauseSpan" tabindex="1">II</span>
|
<span id="pauseSpan" tabindex="1">II</span>
|
||||||
<audio id="music" src="audio/benevolence.m4a" loop></audio>
|
<audio id="music" src="audio/benevolence.m4a" loop></audio>
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
|
|
||||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
@ -10,15 +9,13 @@ class TetraControls extends OrbitControls {
|
|||||||
this.dampingFactor = 0.04
|
this.dampingFactor = 0.04
|
||||||
this.maxDistance = 21
|
this.maxDistance = 21
|
||||||
this.keys = {}
|
this.keys = {}
|
||||||
this.minPolarAngle = 1
|
this.minPolarAngle = 1.05
|
||||||
this.maxPolarAngle = 2.1
|
this.maxPolarAngle = 2.1
|
||||||
this.minAzimuthAngle = 0.9 - Math.PI / 2
|
this.minAzimuthAngle = 0.9 - Math.PI / 2
|
||||||
this.maxAzimuthAngle = 2.14 - Math.PI / 2
|
this.maxAzimuthAngle = 2.14 - Math.PI / 2
|
||||||
this.target.set(5, 7, 0)
|
this.target.set(5, 7.5, 0)
|
||||||
|
|
||||||
this.addEventListener("start", () => domElement.style.cursor = "grabbing")
|
this.addEventListener("start", () => domElement.style.cursor = "grabbing")
|
||||||
this.addEventListener("end", () => domElement.style.cursor = "grab")
|
this.addEventListener("end", () => domElement.style.cursor = "grab")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { TetraControls }
|
|
||||||
@ -1,18 +1,17 @@
|
|||||||
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 * as FPS from 'three/addons/libs/stats.module.js'
|
import { environment } from './Tetrominoes.js'
|
||||||
import { Mino, environment } from './Tetrominoes.js'
|
|
||||||
|
|
||||||
|
|
||||||
export class TetraGUI extends GUI {
|
export class Menu extends GUI {
|
||||||
constructor(game, settings, stats, scene, controls, playfield, loadingManager) {
|
constructor(game, settings, stats, scene, minoes, playfield) {
|
||||||
super({title: "teTra"})
|
super({title: "ᵀᴱTᴿᴬ"})
|
||||||
|
|
||||||
this.startButton = this.add(game, "start").name("Jouer").hide()
|
this.startButton = this.add(game, "start").name("Jouer").hide()
|
||||||
this.pauseButton = this.add(game, "pause").name("Pause").hide()
|
this.pauseButton = this.add(game, "pause").name("Pause").hide()
|
||||||
this.resumeButton = this.add(game, "resume").name("Reprendre").hide()
|
this.resumeButton = this.add(game, "resume").name("Reprendre").hide()
|
||||||
|
|
||||||
this.stats = this.addFolder("Stats").hide()
|
this.stats = this.addFolder("Statistiques").hide()
|
||||||
this.stats.add(stats, "time").name("Temps").disable().listen()
|
this.stats.add(stats, "time").name("Temps").disable().listen()
|
||||||
this.stats.add(stats, "score").name("Score").disable().listen()
|
this.stats.add(stats, "score").name("Score").disable().listen()
|
||||||
this.stats.add(stats, "highScore").name("Meilleur score").disable().listen()
|
this.stats.add(stats, "highScore").name("Meilleur score").disable().listen()
|
||||||
@ -24,44 +23,41 @@ export class TetraGUI extends GUI {
|
|||||||
this.stats.add(stats, "maxCombo").name("Combos max").disable().listen()
|
this.stats.add(stats, "maxCombo").name("Combos max").disable().listen()
|
||||||
this.stats.add(stats, "maxB2B").name("BàB max").disable().listen()
|
this.stats.add(stats, "maxB2B").name("BàB max").disable().listen()
|
||||||
|
|
||||||
this.settings = this.addFolder("Options").open()
|
this.settings = this.addFolder("Options")
|
||||||
|
|
||||||
this.settings.add(settings, "startLevel").name("Niveau initial").min(1).max(15).step(1)
|
this.settings.add(settings, "startLevel").name("Niveau initial").min(1).max(15).step(1)
|
||||||
|
|
||||||
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
|
||||||
Mino.meshes.material = Mino.materials[theme]
|
minoes.theme = theme
|
||||||
if (theme == "Rétro") {
|
if (theme == "Rétro") {
|
||||||
playfield.edge.visible = false
|
playfield.edge.visible = false
|
||||||
playfield.retroEdge.visible = true
|
playfield.retroEdge.visible = true
|
||||||
Mino.meshes.resetColor()
|
|
||||||
Mino.meshes.update = Mino.meshes.updateOffset
|
|
||||||
music.src = "audio/Tetris_MkVaffQuasi_Ultimix_OC_ReMix.mp3"
|
music.src = "audio/Tetris_MkVaffQuasi_Ultimix_OC_ReMix.mp3"
|
||||||
} else {
|
} else {
|
||||||
playfield.edge.visible = true
|
playfield.edge.visible = true
|
||||||
playfield.retroEdge.visible = false
|
playfield.retroEdge.visible = false
|
||||||
Mino.meshes.update = Mino.meshes.updateColor
|
|
||||||
music.src = "audio/benevolence.m4a"
|
music.src = "audio/benevolence.m4a"
|
||||||
}
|
}
|
||||||
|
if (dev) changeMaterial()
|
||||||
})
|
})
|
||||||
|
|
||||||
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);
|
||||||
@ -77,14 +73,71 @@ export class TetraGUI extends GUI {
|
|||||||
scene.hardDropSound.setVolume(volume/100)
|
scene.hardDropSound.setVolume(volume/100)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.dev = window.location.search.includes("dev")
|
let material
|
||||||
if (this.dev) {
|
function changeMaterial() {
|
||||||
let dev = this.addFolder("dev")
|
material?.destroy()
|
||||||
|
material = dev.addFolder("minoes material")
|
||||||
|
material.add(minoes.material, "constructor", ["MeshBasicMaterial", "MeshStandardMaterial", "MeshPhysicalMaterial"]).listen().onChange(type => {
|
||||||
|
switch(type) {
|
||||||
|
case "MeshBasicMaterial":
|
||||||
|
minoes.material = new THREE.MeshBasicMaterial({
|
||||||
|
envMap: environment,
|
||||||
|
side: THREE.DoubleSide,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.5,
|
||||||
|
reflectivity: 0.9,
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case "MeshStandardMaterial":
|
||||||
|
minoes.material = new THREE.MeshStandardMaterial({
|
||||||
|
envMap: environment,
|
||||||
|
side: THREE.DoubleSide,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.7,
|
||||||
|
roughness: 0.48,
|
||||||
|
metalness: 0.67,
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case "MeshPhysicalMaterial":
|
||||||
|
minoes.material = new THREE.MeshPhysicalMaterial({
|
||||||
|
envMap: environment,
|
||||||
|
side: THREE.DoubleSide,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.7,
|
||||||
|
roughness: 0.5,
|
||||||
|
ior: 1.8,
|
||||||
|
metalness: 0.9,
|
||||||
|
transmission: 1,
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
minoes.update = minoes.updateColor
|
||||||
|
changeMaterial()
|
||||||
|
})
|
||||||
|
|
||||||
|
let minoMaterial = minoes.material instanceof Array ? minoes.material[0] : minoes.material
|
||||||
|
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 ("roughness" in minoMaterial) material.add(minoMaterial, "roughness" ).min(0).max(1)
|
||||||
|
if ("bumpScale" in minoMaterial) material.add(minoMaterial, "bumpScale" ).min(0).max(5)
|
||||||
|
if ("metalness" in minoMaterial) material.add(minoMaterial, "metalness" ).min(0).max(1)
|
||||||
|
if ("attenuationDistance" in minoMaterial) material.add(minoMaterial, "attenuationDistance").min(0)
|
||||||
|
if ("ior" in minoMaterial) material.add(minoMaterial, "ior" ).min(1).max(2)
|
||||||
|
if ("sheen" in minoMaterial) material.add(minoMaterial, "sheen" ).min(0).max(1)
|
||||||
|
if ("sheenRoughness" in minoMaterial) material.add(minoMaterial, "sheenRoughness" ).min(0).max(1)
|
||||||
|
if ("specularIntensity" in minoMaterial) material.add(minoMaterial, "specularIntensity" ).min(0).max(1)
|
||||||
|
if ("thickness" in minoMaterial) material.add(minoMaterial, "thickness" ).min(0).max(5)
|
||||||
|
if ("transmission" in minoMaterial) material.add(minoMaterial, "transmission" ).min(0).max(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
let dev
|
||||||
|
if (window.location.search.includes("dev")) {
|
||||||
|
dev = this.addFolder("dev")
|
||||||
let cameraPosition = dev.addFolder("camera").close()
|
let cameraPosition = dev.addFolder("camera").close()
|
||||||
cameraPosition.add(scene.camera.position, "x")
|
cameraPosition.add(scene.camera.position, "x").listen()
|
||||||
cameraPosition.add(scene.camera.position, "y")
|
cameraPosition.add(scene.camera.position, "y").listen()
|
||||||
cameraPosition.add(scene.camera.position, "z")
|
cameraPosition.add(scene.camera.position, "z").listen()
|
||||||
cameraPosition.add(scene.camera, "fov", 0, 200).onChange(() => scene.camera.updateProjectionMatrix())
|
cameraPosition.add(scene.camera, "fov", 0, 200).onChange(() => scene.camera.updateProjectionMatrix()).listen()
|
||||||
|
|
||||||
let light = dev.addFolder("lights intensity").close()
|
let light = dev.addFolder("lights intensity").close()
|
||||||
light.add(scene.ambientLight, "intensity").name("ambient").min(0).max(20).listen()
|
light.add(scene.ambientLight, "intensity").name("ambient").min(0).max(20).listen()
|
||||||
@ -99,75 +152,8 @@ export class TetraGUI extends GUI {
|
|||||||
vortex.add(scene.vortex.darkCylinder.material, "opacity").name("dark").min(0).max(1)
|
vortex.add(scene.vortex.darkCylinder.material, "opacity").name("dark").min(0).max(1)
|
||||||
vortex.add(scene.vortex.colorFullCylinder.material, "opacity").name("colorFull").min(0).max(1)
|
vortex.add(scene.vortex.colorFullCylinder.material, "opacity").name("colorFull").min(0).max(1)
|
||||||
|
|
||||||
let material
|
changeMaterial(minoes.material.constructor.name)
|
||||||
function changeMaterial(type) {
|
|
||||||
material?.destroy()
|
|
||||||
material = dev.addFolder("minoes material")
|
|
||||||
material.add(Mino.meshes.material, "constructor", ["MeshBasicMaterial", "MeshStandardMaterial", "MeshPhysicalMaterial"]).name("type").onChange(changeMaterial)
|
|
||||||
switch(type) {
|
|
||||||
case "MeshBasicMaterial":
|
|
||||||
Mino.meshes.material = new THREE.MeshBasicMaterial({
|
|
||||||
envMap: environment,
|
|
||||||
side: THREE.DoubleSide,
|
|
||||||
transparent: true,
|
|
||||||
opacity: 0.5,
|
|
||||||
reflectivity: 0.9,
|
|
||||||
})
|
|
||||||
break
|
|
||||||
case "MeshStandardMaterial":
|
|
||||||
Mino.meshes.material = new THREE.MeshStandardMaterial({
|
|
||||||
envMap: environment,
|
|
||||||
side: THREE.DoubleSide,
|
|
||||||
transparent: true,
|
|
||||||
opacity: 0.7,
|
|
||||||
roughness: 0.48,
|
|
||||||
metalness: 0.67,
|
|
||||||
})
|
|
||||||
break
|
|
||||||
case "MeshPhysicalMaterial":
|
|
||||||
Mino.meshes.material = new THREE.MeshPhysicalMaterial({
|
|
||||||
envMap: environment,
|
|
||||||
side: THREE.DoubleSide,
|
|
||||||
transparent: true,
|
|
||||||
opacity: 0.6,
|
|
||||||
roughness: 0.5,
|
|
||||||
metalness: 0.67,
|
|
||||||
attenuationDistance: 0.5,
|
|
||||||
ior: 2,
|
|
||||||
sheen: 0,
|
|
||||||
sheenRoughness: 1,
|
|
||||||
specularIntensity: 1,
|
|
||||||
thickness: 5,
|
|
||||||
transmission: 1,
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if ("opacity" in Mino.meshes.material) material.add(Mino.meshes.material, "opacity" ).min(0).max(1).listen()
|
|
||||||
if ("reflectivity" in Mino.meshes.material) material.add(Mino.meshes.material, "reflectivity" ).min(0).max(1).listen()
|
|
||||||
if ("roughness" in Mino.meshes.material) material.add(Mino.meshes.material, "roughness" ).min(0).max(1).listen()
|
|
||||||
if ("metalness" in Mino.meshes.material) material.add(Mino.meshes.material, "metalness" ).min(0).max(1).listen()
|
|
||||||
if ("attenuationDistance" in Mino.meshes.material) material.add(Mino.meshes.material, "attenuationDistance").min(0).listen()
|
|
||||||
if ("ior" in Mino.meshes.material) material.add(Mino.meshes.material, "ior" ).min(1).max(2).listen()
|
|
||||||
if ("sheen" in Mino.meshes.material) material.add(Mino.meshes.material, "sheen" ).min(0).max(1).listen()
|
|
||||||
if ("sheenRoughness" in Mino.meshes.material) material.add(Mino.meshes.material, "sheenRoughness" ).min(0).max(1).listen()
|
|
||||||
if ("specularIntensity" in Mino.meshes.material) material.add(Mino.meshes.material, "specularIntensity" ).min(0).max(1).listen()
|
|
||||||
if ("thickness" in Mino.meshes.material) material.add(Mino.meshes.material, "thickness" ).min(0).max(5).listen()
|
|
||||||
if ("transmission" in Mino.meshes.material) material.add(Mino.meshes.material, "transmission" ).min(0).max(1).listen()
|
|
||||||
}
|
|
||||||
changeMaterial(this.materialType)
|
|
||||||
material.close()
|
material.close()
|
||||||
|
|
||||||
let fps = new FPS.default()
|
|
||||||
document.body.appendChild(fps.dom)
|
|
||||||
|
|
||||||
this.update = function() {
|
|
||||||
fps.update()
|
|
||||||
}
|
|
||||||
|
|
||||||
controls.addEventListener("change", () => cameraPosition.controllersRecursive().forEach((control) => {
|
|
||||||
control.updateDisplay()
|
|
||||||
}))
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,8 +167,8 @@ export class TetraGUI extends GUI {
|
|||||||
localStorage["teTraSettings"] = JSON.stringify(this.settings.save())
|
localStorage["teTraSettings"] = JSON.stringify(this.settings.save())
|
||||||
}
|
}
|
||||||
|
|
||||||
changeKey() {
|
changeKey(settings) {
|
||||||
let controller = this
|
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) {
|
||||||
@ -190,6 +176,4 @@ export class TetraGUI extends GUI {
|
|||||||
input.blur()
|
input.blur()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {}
|
|
||||||
}
|
}
|
||||||
@ -27,7 +27,7 @@ let friendyKeyRenamer = new Proxy({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
class Settings {
|
export default class Settings {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.startLevel = 1
|
this.startLevel = 1
|
||||||
|
|
||||||
@ -80,6 +80,3 @@ class Settings {
|
|||||||
this.theme = "Plasma"
|
this.theme = "Plasma"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export { Settings }
|
|
||||||
@ -7,7 +7,7 @@ export class TetraScene extends THREE.Scene {
|
|||||||
super()
|
super()
|
||||||
|
|
||||||
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, 3, 12)
|
this.camera.position.set(5, 4, 12)
|
||||||
|
|
||||||
this.vortex = new Vortex(loadingManager)
|
this.vortex = new Vortex(loadingManager)
|
||||||
this.add(this.vortex)
|
this.add(this.vortex)
|
||||||
@ -16,9 +16,9 @@ export class TetraScene extends THREE.Scene {
|
|||||||
this.add(this.ambientLight)
|
this.add(this.ambientLight)
|
||||||
|
|
||||||
this.directionalLight = new THREE.DirectionalLight(0xffffff, 5)
|
this.directionalLight = new THREE.DirectionalLight(0xffffff, 5)
|
||||||
this.directionalLight.position.set(5, -20, 20)
|
|
||||||
this.add(this.directionalLight)
|
this.add(this.directionalLight)
|
||||||
|
|
||||||
|
this.theme = settings.theme
|
||||||
|
|
||||||
/* Sounds */
|
/* Sounds */
|
||||||
this.music = music
|
this.music = music
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { TileMaterial } from './TileMaterial.js'
|
|||||||
|
|
||||||
Array.prototype.pick = function () { return this.splice(Math.floor(Math.random() * this.length), 1)[0] }
|
Array.prototype.pick = function () { return this.splice(Math.floor(Math.random() * this.length), 1)[0] }
|
||||||
|
|
||||||
let P = (x, y, z = 0) => new THREE.Vector3(x, y, z)
|
let P = (x, y, z=0) => new THREE.Vector3(x, y, z)
|
||||||
|
|
||||||
const GRAVITY = -30
|
const GRAVITY = -30
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ const COLORS = {
|
|||||||
LOCKING: 0xffffff,
|
LOCKING: 0xffffff,
|
||||||
GHOST: 0x99a9b2,
|
GHOST: 0x99a9b2,
|
||||||
EDGE: 0x88abe0,
|
EDGE: 0x88abe0,
|
||||||
RETRO: 0xffffff,
|
RETRO: 0xd0d4c1,
|
||||||
}
|
}
|
||||||
|
|
||||||
const TRANSLATION = {
|
const TRANSLATION = {
|
||||||
@ -69,102 +69,8 @@ const sideMaterial = new THREE.MeshStandardMaterial({
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
class InstancedMino extends THREE.InstancedMesh {
|
export class InstancedMino extends THREE.InstancedMesh {
|
||||||
constructor(geometry, material, count) {
|
constructor() {
|
||||||
super(geometry, material, count)
|
|
||||||
this.instances = new Set()
|
|
||||||
this.count = 0
|
|
||||||
this.offsets = new Uint8Array(2*count)
|
|
||||||
this.update = this.updateColor
|
|
||||||
}
|
|
||||||
|
|
||||||
add(instance) {
|
|
||||||
this.instances.add(instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(instance) {
|
|
||||||
this.instances.delete(instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
this.instances.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
setOffsetAt(index, offset) {
|
|
||||||
this.offsets[index * 2] = offset
|
|
||||||
}
|
|
||||||
|
|
||||||
resetColor() {
|
|
||||||
this.instanceColor = null
|
|
||||||
}
|
|
||||||
|
|
||||||
updateColor() {
|
|
||||||
this.count = 0
|
|
||||||
this.instances.forEach(mino => {
|
|
||||||
if (mino.parent?.visible) {
|
|
||||||
this.setMatrixAt(this.count, mino.matrixWorld)
|
|
||||||
this.setColorAt(this.count, mino.color)
|
|
||||||
this.count++
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (this.count) {
|
|
||||||
this.instanceMatrix.needsUpdate = true
|
|
||||||
this.instanceColor.needsUpdate = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateOffset() {
|
|
||||||
this.count = 0
|
|
||||||
this.instances.forEach(mino => {
|
|
||||||
if (mino.parent?.visible) {
|
|
||||||
this.setMatrixAt(this.count, mino.matrixWorld)
|
|
||||||
this.setOffsetAt(this.count, mino.offset)
|
|
||||||
this.count++
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (this.count) {
|
|
||||||
this.instanceMatrix.needsUpdate = true
|
|
||||||
this.geometry.setAttribute('offset', new THREE.InstancedBufferAttribute(this.offsets, 2))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Mino extends THREE.Object3D {
|
|
||||||
static materials = {
|
|
||||||
Plasma: new THREE.MeshStandardMaterial({
|
|
||||||
envMap: environment,
|
|
||||||
side: THREE.DoubleSide,
|
|
||||||
transparent: true,
|
|
||||||
opacity: 0.7,
|
|
||||||
roughness: 0.48,
|
|
||||||
metalness: 0.67,
|
|
||||||
}),
|
|
||||||
Espace: new THREE.MeshStandardMaterial({
|
|
||||||
envMap: environment,
|
|
||||||
side: THREE.DoubleSide,
|
|
||||||
transparent: true,
|
|
||||||
opacity: 0.8,
|
|
||||||
roughness: 0.05,
|
|
||||||
metalness: 0.997,
|
|
||||||
}),
|
|
||||||
Rétro: [sideMaterial, sideMaterial, sideMaterial, sideMaterial, sideMaterial, sideMaterial]
|
|
||||||
}
|
|
||||||
static {
|
|
||||||
new THREE.TextureLoader().load("images/sprites.png", (texture) => {
|
|
||||||
this.materials.Rétro[0] = this.materials.Rétro[2] = new TileMaterial({
|
|
||||||
color: 0xd0d4c1,
|
|
||||||
map: texture,
|
|
||||||
bumpMap: texture,
|
|
||||||
bumpScale: 1.5,
|
|
||||||
roughness: 0.25,
|
|
||||||
metalness: 0.8,
|
|
||||||
transparent: true,
|
|
||||||
}, 8, 8)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
static meshes
|
|
||||||
static {
|
|
||||||
let minoFaceShape = new THREE.Shape()
|
let minoFaceShape = new THREE.Shape()
|
||||||
minoFaceShape.moveTo(.1, .1)
|
minoFaceShape.moveTo(.1, .1)
|
||||||
minoFaceShape.lineTo(.1, .9)
|
minoFaceShape.lineTo(.1, .9)
|
||||||
@ -180,18 +86,119 @@ class Mino extends THREE.Object3D {
|
|||||||
bevelOffset: 0,
|
bevelOffset: 0,
|
||||||
bevelSegments: 1
|
bevelSegments: 1
|
||||||
}
|
}
|
||||||
let minoGeometry = new THREE.ExtrudeGeometry(minoFaceShape, minoExtrudeSettings)
|
const geometry = new THREE.ExtrudeGeometry(minoFaceShape, minoExtrudeSettings)
|
||||||
this.meshes = new InstancedMino(minoGeometry, this.materials.Plasma, 2*ROWS*COLUMNS)
|
super(geometry, undefined, 2*ROWS*COLUMNS)
|
||||||
|
this.offsets = new Uint8Array(2*this.count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set theme(theme) {
|
||||||
|
if (theme == "Rétro") {
|
||||||
|
this.resetColor()
|
||||||
|
this.update = this.updateOffset
|
||||||
|
if (this.materials["Rétro"]) {
|
||||||
|
this.material = this.materials["Rétro"]
|
||||||
|
} else {
|
||||||
|
this.materials["Rétro"] = []
|
||||||
|
const loadingManager = new THREE.LoadingManager(() => this.material = this.materials["Rétro"])
|
||||||
|
new THREE.TextureLoader(loadingManager).load("images/sprites.png", (texture) => {
|
||||||
|
this.materials.Rétro[0] = this.materials.Rétro[2] = new TileMaterial({
|
||||||
|
color: COLORS.RETRO,
|
||||||
|
map: texture,
|
||||||
|
bumpMap: texture,
|
||||||
|
bumpScale: 1.5,
|
||||||
|
roughness: 0.25,
|
||||||
|
metalness: 0.9,
|
||||||
|
transparent: true,
|
||||||
|
}, 8, 8)
|
||||||
|
})
|
||||||
|
new THREE.TextureLoader(loadingManager).load("images/edges.png", (texture) => {
|
||||||
|
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,
|
||||||
|
map: texture,
|
||||||
|
bumpMap: texture,
|
||||||
|
bumpScale: 1.5,
|
||||||
|
roughness: 0.25,
|
||||||
|
metalness: 0.9,
|
||||||
|
transparent: true,
|
||||||
|
}, 1, 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.update = this.updateColor
|
||||||
|
this.material = this.materials[theme]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setOffsetAt(index, offset) {
|
||||||
|
this.offsets[2*index] = offset.x
|
||||||
|
this.offsets[2*index + 1] = offset.y
|
||||||
|
}
|
||||||
|
|
||||||
|
resetColor() {
|
||||||
|
this.instanceColor = null
|
||||||
|
}
|
||||||
|
|
||||||
|
updateColor() {
|
||||||
|
this.count = 0
|
||||||
|
Mino.instances.forEach(mino => {
|
||||||
|
if (mino.parent?.visible) {
|
||||||
|
this.setMatrixAt(this.count, mino.matrixWorld)
|
||||||
|
this.setColorAt(this.count, mino.color)
|
||||||
|
this.count++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (this.count) {
|
||||||
|
this.instanceMatrix.needsUpdate = true
|
||||||
|
this.instanceColor.needsUpdate = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOffset() {
|
||||||
|
this.count = 0
|
||||||
|
Mino.instances.forEach(mino => {
|
||||||
|
if (mino.parent?.visible) {
|
||||||
|
this.setMatrixAt(this.count, mino.matrixWorld)
|
||||||
|
this.setOffsetAt(this.count, mino.offset)
|
||||||
|
this.count++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (this.count) {
|
||||||
|
this.instanceMatrix.needsUpdate = true
|
||||||
|
this.geometry.setAttribute('offset', new THREE.InstancedBufferAttribute(this.offsets, 2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
static instances = new Set()
|
||||||
|
|
||||||
constructor(color, offset) {
|
constructor(color, offset) {
|
||||||
super()
|
super()
|
||||||
this.color = color
|
this.color = color
|
||||||
this.offset = offset
|
this.offset = offset
|
||||||
this.velocity = P(50 - 100 * Math.random(), 50 - 100 * Math.random(), 50 - 100 * Math.random())
|
this.velocity = P(50 - 100 * Math.random(), 60 - 100 * Math.random(), 50 - 100 * Math.random())
|
||||||
this.rotationAngle = P(Math.random(), Math.random(), Math.random()).normalize()
|
this.rotationAngle = P(Math.random(), Math.random(), Math.random()).normalize()
|
||||||
this.angularVelocity = 5 - 10 * Math.random()
|
this.angularVelocity = 5 - 10 * Math.random()
|
||||||
this.constructor.meshes.add(this)
|
this.constructor.instances.add(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
explode(delta) {
|
explode(delta) {
|
||||||
@ -200,14 +207,14 @@ class Mino extends THREE.Object3D {
|
|||||||
this.rotateOnWorldAxis(this.rotationAngle, delta * this.angularVelocity)
|
this.rotateOnWorldAxis(this.rotationAngle, delta * this.angularVelocity)
|
||||||
if (Math.sqrt(this.position.x * this.position.x + this.position.z * this.position.z) > 40 || this.position.y < -50) {
|
if (Math.sqrt(this.position.x * this.position.x + this.position.z * this.position.z) > 40 || this.position.y < -50) {
|
||||||
this.dispose()
|
this.dispose()
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return true
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this.constructor.meshes.delete(this)
|
this.constructor.instances.delete(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,6 +229,7 @@ class Tetromino extends THREE.Group {
|
|||||||
constructor(position) {
|
constructor(position) {
|
||||||
super()
|
super()
|
||||||
if (position) this.position.copy(position)
|
if (position) this.position.copy(position)
|
||||||
|
this.offset = this.offset.clone()
|
||||||
this.minoesPosition[FACING.NORTH].forEach(() => this.add(new Mino(this.freeColor, this.offset)))
|
this.minoesPosition[FACING.NORTH].forEach(() => this.add(new Mino(this.freeColor, this.offset)))
|
||||||
this.facing = FACING.NORTH
|
this.facing = FACING.NORTH
|
||||||
this.rotatedLast = false
|
this.rotatedLast = false
|
||||||
@ -242,8 +250,10 @@ class Tetromino extends THREE.Group {
|
|||||||
set locking(locking) {
|
set locking(locking) {
|
||||||
if (locking) {
|
if (locking) {
|
||||||
this.color = this.lockingColor
|
this.color = this.lockingColor
|
||||||
|
this.offset.y = 2
|
||||||
} else {
|
} else {
|
||||||
this.color = this.freeColor
|
this.color = this.freeColor
|
||||||
|
this.offset.y = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,20 +263,19 @@ class Tetromino extends THREE.Group {
|
|||||||
|
|
||||||
canMove(translation, facing=this.facing) {
|
canMove(translation, facing=this.facing) {
|
||||||
let testPosition = this.position.clone().add(translation)
|
let testPosition = this.position.clone().add(translation)
|
||||||
return this.minoesPosition[facing].every(minoPosition => this.parent.cellIsEmpty(minoPosition.clone().add(testPosition)))
|
return this.minoesPosition[facing].every(minoPosition => this.parent?.cellIsEmpty(minoPosition.clone().add(testPosition)))
|
||||||
}
|
}
|
||||||
|
|
||||||
move(translation, rotatedFacing, rotationPoint) {
|
move(translation, rotatedFacing) {
|
||||||
if (this.canMove(translation, rotatedFacing)) {
|
if (this.canMove(translation, rotatedFacing)) {
|
||||||
this.position.add(translation)
|
this.position.add(translation)
|
||||||
this.rotatedLast = rotatedFacing
|
this.rotatedLast = rotatedFacing
|
||||||
if (rotatedFacing != undefined) {
|
if (rotatedFacing != undefined) {
|
||||||
this.facing = rotatedFacing
|
this.facing = rotatedFacing
|
||||||
if (rotationPoint == 4) this.rotationPoint4Used = true
|
|
||||||
}
|
}
|
||||||
if (this.canMove(TRANSLATION.DOWN)) {
|
if (this.canMove(TRANSLATION.DOWN)) {
|
||||||
this.locking = false
|
this.locking = false
|
||||||
this.parent.ghost.copy(this)
|
this.parent?.ghost.copy(this)
|
||||||
scheduler.clearTimeout(this.onLockDown)
|
scheduler.clearTimeout(this.onLockDown)
|
||||||
} else {
|
} else {
|
||||||
scheduler.resetTimeout(this.onLockDown, this.lockDelay)
|
scheduler.resetTimeout(this.onLockDown, this.lockDelay)
|
||||||
@ -277,16 +286,19 @@ class Tetromino extends THREE.Group {
|
|||||||
} else if (translation == TRANSLATION.DOWN) {
|
} else if (translation == TRANSLATION.DOWN) {
|
||||||
this.locked = true
|
this.locked = true
|
||||||
if (!scheduler.timeoutTasks.has(this.onLockDown))
|
if (!scheduler.timeoutTasks.has(this.onLockDown))
|
||||||
scheduler.setTimeout(this.onLockDown, this.lockDelay)
|
scheduler.resetTimeout(this.onLockDown, this.lockDelay)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rotate(rotation) {
|
rotate(rotation) {
|
||||||
let testFacing = (this.facing + rotation) % 4
|
let testFacing = (this.facing + rotation) % 4
|
||||||
return this.srs[this.facing][rotation].some(
|
return this.srs[this.facing][rotation].some((translation, rotationPoint) => {
|
||||||
(translation, rotationPoint) => this.move(translation, testFacing, rotationPoint)
|
if (this.move(translation, testFacing)) {
|
||||||
)
|
if (rotationPoint == 4) this.rotationPoint4Used = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
get tSpin() {
|
get tSpin() {
|
||||||
@ -295,7 +307,7 @@ class Tetromino extends THREE.Group {
|
|||||||
}
|
}
|
||||||
Tetromino.prototype.lockingColor = new THREE.Color(COLORS.LOCKING)
|
Tetromino.prototype.lockingColor = new THREE.Color(COLORS.LOCKING)
|
||||||
// Super Rotation System
|
// Super Rotation System
|
||||||
// freedom of movement = srs[this.parent.piece.facing][rotation]
|
// freedom of movement = srs[this.facing][rotation]
|
||||||
Tetromino.prototype.srs = [
|
Tetromino.prototype.srs = [
|
||||||
{ [ROTATION.CW]: [P(0, 0), P(-1, 0), P(-1, 1), P(0, -2), P(-1, -2)], [ROTATION.CCW]: [P(0, 0), P(1, 0), P(1, 1), P(0, -2), P(1, -2)] },
|
{ [ROTATION.CW]: [P(0, 0), P(-1, 0), P(-1, 1), P(0, -2), P(-1, -2)], [ROTATION.CCW]: [P(0, 0), P(1, 0), P(1, 1), P(0, -2), P(1, -2)] },
|
||||||
{ [ROTATION.CW]: [P(0, 0), P(1, 0), P(1, -1), P(0, 2), P(1, 2)], [ROTATION.CCW]: [P(0, 0), P(1, 0), P(1, -1), P(0, 2), P(1, 2)] },
|
{ [ROTATION.CW]: [P(0, 0), P(1, 0), P(1, -1), P(0, 2), P(1, 2)], [ROTATION.CCW]: [P(0, 0), P(1, 0), P(1, -1), P(0, 2), P(1, 2)] },
|
||||||
@ -309,7 +321,7 @@ class Ghost extends Tetromino {
|
|||||||
copy(piece) {
|
copy(piece) {
|
||||||
this.position.copy(piece.position)
|
this.position.copy(piece.position)
|
||||||
this.minoesPosition = piece.minoesPosition
|
this.minoesPosition = piece.minoesPosition
|
||||||
//this.children.forEach(mino => mino.offset = piece.offset)
|
this.children.forEach(mino => {mino.offset = piece.ghostOffset})
|
||||||
this.facing = piece.facing
|
this.facing = piece.facing
|
||||||
this.visible = true
|
this.visible = true
|
||||||
while (this.canMove(TRANSLATION.DOWN)) this.position.y--
|
while (this.canMove(TRANSLATION.DOWN)) this.position.y--
|
||||||
@ -319,7 +331,7 @@ Ghost.prototype.freeColor = new THREE.Color(COLORS.GHOST)
|
|||||||
Ghost.prototype.minoesPosition = [
|
Ghost.prototype.minoesPosition = [
|
||||||
[P(0, 0, 0), P(0, 0, 0), P(0, 0, 0), P(0, 0, 0)],
|
[P(0, 0, 0), P(0, 0, 0), P(0, 0, 0), P(0, 0, 0)],
|
||||||
]
|
]
|
||||||
Ghost.prototype.offset = 0
|
Ghost.prototype.offset = P(0, 1)
|
||||||
|
|
||||||
|
|
||||||
class I extends Tetromino { }
|
class I extends Tetromino { }
|
||||||
@ -336,7 +348,8 @@ I.prototype.srs = [
|
|||||||
{ [ROTATION.CW]: [P(0, 0), P(1, 0), P(-2, 0), P(1, -2), P(-2, 1)], [ROTATION.CCW]: [P(0, 0), P(-2, 0), P(1, 0), P(-2, -1), P(1, 2)] },
|
{ [ROTATION.CW]: [P(0, 0), P(1, 0), P(-2, 0), P(1, -2), P(-2, 1)], [ROTATION.CCW]: [P(0, 0), P(-2, 0), P(1, 0), P(-2, -1), P(1, 2)] },
|
||||||
]
|
]
|
||||||
I.prototype.freeColor = new THREE.Color(COLORS.I)
|
I.prototype.freeColor = new THREE.Color(COLORS.I)
|
||||||
I.prototype.offset = 1
|
I.prototype.offset = P(0, 0)
|
||||||
|
I.prototype.ghostOffset = P(0, 1)
|
||||||
|
|
||||||
class J extends Tetromino { }
|
class J extends Tetromino { }
|
||||||
J.prototype.minoesPosition = [
|
J.prototype.minoesPosition = [
|
||||||
@ -346,7 +359,8 @@ J.prototype.minoesPosition = [
|
|||||||
[P(0, 1), P(-1, -1), P(0, 0), P(0, -1)],
|
[P(0, 1), P(-1, -1), P(0, 0), P(0, -1)],
|
||||||
]
|
]
|
||||||
J.prototype.freeColor = new THREE.Color(COLORS.J)
|
J.prototype.freeColor = new THREE.Color(COLORS.J)
|
||||||
J.prototype.offset = 2
|
J.prototype.offset = P(1, 0)
|
||||||
|
J.prototype.ghostOffset = P(1, 1)
|
||||||
|
|
||||||
class L extends Tetromino {
|
class L extends Tetromino {
|
||||||
}
|
}
|
||||||
@ -357,7 +371,8 @@ L.prototype.minoesPosition = [
|
|||||||
[P(0, 1), P(0, 0), P(0, -1), P(-1, 1)],
|
[P(0, 1), P(0, 0), P(0, -1), P(-1, 1)],
|
||||||
]
|
]
|
||||||
L.prototype.freeColor = new THREE.Color(COLORS.L)
|
L.prototype.freeColor = new THREE.Color(COLORS.L)
|
||||||
L.prototype.offset = 3
|
L.prototype.offset = P(2, 0)
|
||||||
|
L.prototype.ghostOffset = P(2, 1)
|
||||||
|
|
||||||
class O extends Tetromino { }
|
class O extends Tetromino { }
|
||||||
O.prototype.minoesPosition = [
|
O.prototype.minoesPosition = [
|
||||||
@ -367,7 +382,8 @@ O.prototype.srs = [
|
|||||||
{ [ROTATION.CW]: [], [ROTATION.CCW]: [] }
|
{ [ROTATION.CW]: [], [ROTATION.CCW]: [] }
|
||||||
]
|
]
|
||||||
O.prototype.freeColor = new THREE.Color(COLORS.O)
|
O.prototype.freeColor = new THREE.Color(COLORS.O)
|
||||||
O.prototype.offset = 4
|
O.prototype.offset = P(3, 0)
|
||||||
|
O.prototype.ghostOffset = P(3, 1)
|
||||||
|
|
||||||
class S extends Tetromino { }
|
class S extends Tetromino { }
|
||||||
S.prototype.minoesPosition = [
|
S.prototype.minoesPosition = [
|
||||||
@ -377,7 +393,8 @@ S.prototype.minoesPosition = [
|
|||||||
[P(-1, 1), P(0, 0), P(-1, 0), P(0, -1)],
|
[P(-1, 1), P(0, 0), P(-1, 0), P(0, -1)],
|
||||||
]
|
]
|
||||||
S.prototype.freeColor = new THREE.Color(COLORS.S)
|
S.prototype.freeColor = new THREE.Color(COLORS.S)
|
||||||
S.prototype.offset = 5
|
S.prototype.offset = P(4, 0)
|
||||||
|
S.prototype.ghostOffset = P(4, 1)
|
||||||
|
|
||||||
class T extends Tetromino {
|
class T extends Tetromino {
|
||||||
get tSpin() {
|
get tSpin() {
|
||||||
@ -405,7 +422,8 @@ T.prototype.tSlots = [
|
|||||||
[P(-1, -1), P(-1, 1), P(1, 1), P(1, -1)],
|
[P(-1, -1), P(-1, 1), P(1, 1), P(1, -1)],
|
||||||
]
|
]
|
||||||
T.prototype.freeColor = new THREE.Color(COLORS.T)
|
T.prototype.freeColor = new THREE.Color(COLORS.T)
|
||||||
T.prototype.offset = 6
|
T.prototype.offset = P(5, 0)
|
||||||
|
T.prototype.ghostOffset = P(5, 1)
|
||||||
|
|
||||||
class Z extends Tetromino { }
|
class Z extends Tetromino { }
|
||||||
Z.prototype.minoesPosition = [
|
Z.prototype.minoesPosition = [
|
||||||
@ -415,7 +433,8 @@ Z.prototype.minoesPosition = [
|
|||||||
[P(0, 1), P(-1, 0), P(0, 0), P(-1, -1)]
|
[P(0, 1), P(-1, 0), P(0, 0), P(-1, -1)]
|
||||||
]
|
]
|
||||||
Z.prototype.freeColor = new THREE.Color(COLORS.Z)
|
Z.prototype.freeColor = new THREE.Color(COLORS.Z)
|
||||||
Z.prototype.offset = 7
|
Z.prototype.offset = P(6, 0)
|
||||||
|
Z.prototype.ghostOffset = P(6, 1)
|
||||||
|
|
||||||
|
|
||||||
class Playfield extends THREE.Group {
|
class Playfield extends THREE.Group {
|
||||||
@ -457,20 +476,20 @@ class Playfield extends THREE.Group {
|
|||||||
.lineTo(COLUMNS, 0)
|
.lineTo(COLUMNS, 0)
|
||||||
.lineTo(COLUMNS, SKYLINE)
|
.lineTo(COLUMNS, SKYLINE)
|
||||||
.lineTo(COLUMNS + 1, SKYLINE)
|
.lineTo(COLUMNS + 1, SKYLINE)
|
||||||
.lineTo(COLUMNS + 1, -.5)
|
.lineTo(COLUMNS + 1, -1/3)
|
||||||
.lineTo(-1, -.5)
|
.lineTo(-1, -1/3)
|
||||||
.moveTo(-1, SKYLINE)
|
.moveTo(-1, SKYLINE)
|
||||||
const retroEdgeTexture = new THREE.TextureLoader(loadingManager).load("images/edge.png", (texture) => {
|
const retroEdgeTexture = new THREE.TextureLoader(loadingManager).load("images/edge.png", (texture) => {
|
||||||
texture.wrapS = THREE.RepeatWrapping
|
texture.wrapS = THREE.RepeatWrapping
|
||||||
texture.wrapT = THREE.RepeatWrapping
|
texture.wrapT = THREE.RepeatWrapping
|
||||||
})
|
})
|
||||||
const retroEdgeMaterial = new THREE.MeshStandardMaterial({
|
const retroEdgeMaterial = new THREE.MeshStandardMaterial({
|
||||||
color: 0xd0d4c1,
|
color: COLORS.RETRO,
|
||||||
map: retroEdgeTexture,
|
map: retroEdgeTexture,
|
||||||
bumpMap: retroEdgeTexture,
|
bumpMap: retroEdgeTexture,
|
||||||
bumpScale: 0.3,
|
bumpScale: 1.5,
|
||||||
roughness: 0.25,
|
roughness: 0.25,
|
||||||
metalness: 0.8,
|
metalness: 0.9,
|
||||||
})
|
})
|
||||||
this.retroEdge = new THREE.Mesh(
|
this.retroEdge = new THREE.Mesh(
|
||||||
new THREE.ExtrudeGeometry(retroEdgeShape, {
|
new THREE.ExtrudeGeometry(retroEdgeShape, {
|
||||||
@ -479,7 +498,6 @@ class Playfield extends THREE.Group {
|
|||||||
}),
|
}),
|
||||||
[retroEdgeMaterial, sideMaterial, sideMaterial, sideMaterial, sideMaterial, sideMaterial],
|
[retroEdgeMaterial, sideMaterial, sideMaterial, sideMaterial, sideMaterial, sideMaterial],
|
||||||
)
|
)
|
||||||
this.add(this.retroEdge)
|
|
||||||
const back = new THREE.Mesh(
|
const back = new THREE.Mesh(
|
||||||
new THREE.PlaneGeometry(COLUMNS, SKYLINE),
|
new THREE.PlaneGeometry(COLUMNS, SKYLINE),
|
||||||
new THREE.MeshStandardMaterial({
|
new THREE.MeshStandardMaterial({
|
||||||
@ -488,9 +506,10 @@ class Playfield extends THREE.Group {
|
|||||||
metalness: 0.9,
|
metalness: 0.9,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
back.position.set(COLUMNS/2, SKYLINE/2, 0)
|
back.position.set(COLUMNS/2, SKYLINE/2)
|
||||||
this.retroEdge.add(back)
|
this.retroEdge.add(back)
|
||||||
this.retroEdge.visible = false
|
this.retroEdge.visible = false
|
||||||
|
this.add(this.retroEdge)
|
||||||
|
|
||||||
const positionKF = new THREE.VectorKeyframeTrack('.position', [0, 1, 2], [0, 0, 0, 0, -0.2, 0, 0, 0, 0])
|
const positionKF = new THREE.VectorKeyframeTrack('.position', [0, 1, 2], [0, 0, 0, 0, -0.2, 0, 0, 0, 0])
|
||||||
const clip = new THREE.AnimationClip('HardDrop', 3, [positionKF])
|
const clip = new THREE.AnimationClip('HardDrop', 3, [positionKF])
|
||||||
@ -524,6 +543,7 @@ class Playfield extends THREE.Group {
|
|||||||
|
|
||||||
set piece(piece) {
|
set piece(piece) {
|
||||||
if (piece) {
|
if (piece) {
|
||||||
|
this.remove(this.piece)
|
||||||
this.add(piece)
|
this.add(piece)
|
||||||
piece.position.set(4, SKYLINE)
|
piece.position.set(4, SKYLINE)
|
||||||
this.ghost.copy(piece)
|
this.ghost.copy(piece)
|
||||||
@ -569,7 +589,10 @@ class Playfield extends THREE.Group {
|
|||||||
|
|
||||||
updateFreedMinoes(delta) {
|
updateFreedMinoes(delta) {
|
||||||
this.freedMinoes.forEach(mino => {
|
this.freedMinoes.forEach(mino => {
|
||||||
if (mino.explode(delta)) this.freedMinoes.delete(this)
|
if (mino.explode(delta)) {
|
||||||
|
this.remove(mino)
|
||||||
|
this.freedMinoes.delete(mino)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -583,11 +606,12 @@ class Playfield extends THREE.Group {
|
|||||||
class HoldQueue extends THREE.Group {
|
class HoldQueue extends THREE.Group {
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
this.position.set(-4, SKYLINE - 2)
|
this.position.set(-5, SKYLINE - 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
set piece(piece) {
|
set piece(piece) {
|
||||||
if(piece) {
|
if(piece) {
|
||||||
|
this.remove(this.piece)
|
||||||
piece.holdEnabled = false
|
piece.holdEnabled = false
|
||||||
piece.locking = false
|
piece.locking = false
|
||||||
piece.position.set(0, 0)
|
piece.position.set(0, 0)
|
||||||
@ -606,7 +630,7 @@ class HoldQueue extends THREE.Group {
|
|||||||
class NextQueue extends THREE.Group {
|
class NextQueue extends THREE.Group {
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
this.position.set(13, SKYLINE - 2)
|
this.position.set(14, SKYLINE - 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
@ -616,6 +640,7 @@ class NextQueue extends THREE.Group {
|
|||||||
|
|
||||||
shift() {
|
shift() {
|
||||||
let fistPiece = this.children.shift()
|
let fistPiece = this.children.shift()
|
||||||
|
this.remove(fistPiece)
|
||||||
this.add(new Tetromino.random())
|
this.add(new Tetromino.random())
|
||||||
this.positions.forEach((position, i) => this.children[i].position.copy(position))
|
this.positions.forEach((position, i) => this.children[i].position.copy(position))
|
||||||
return fistPiece
|
return fistPiece
|
||||||
|
|||||||
@ -24,7 +24,6 @@ export class Vortex extends THREE.Group {
|
|||||||
blending: THREE.AdditiveBlending,
|
blending: THREE.AdditiveBlending,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
this.darkCylinder.position.y = -100
|
|
||||||
this.add(this.darkCylinder)
|
this.add(this.darkCylinder)
|
||||||
|
|
||||||
this.colorFullCylinder = new THREE.Mesh(
|
this.colorFullCylinder = new THREE.Mesh(
|
||||||
@ -34,10 +33,9 @@ export class Vortex extends THREE.Group {
|
|||||||
blending: THREE.AdditiveBlending,
|
blending: THREE.AdditiveBlending,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
this.colorFullCylinder.position.y = -100
|
|
||||||
this.add(this.colorFullCylinder)
|
this.add(this.colorFullCylinder)
|
||||||
|
|
||||||
this.position.set(5, 10, -10)
|
this.position.set(5, 100, -10)
|
||||||
}
|
}
|
||||||
|
|
||||||
set theme(theme) {
|
set theme(theme) {
|
||||||
@ -57,7 +55,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.7
|
this.colorFullCylinder.material.opacity = 0.5
|
||||||
|
|
||||||
this.globalRotation = 0.028
|
this.globalRotation = 0.028
|
||||||
this.darkTextureRotation = 0.005
|
this.darkTextureRotation = 0.005
|
||||||
@ -72,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
|
||||||
@ -83,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.34
|
this.colorFullCylinder.material.opacity = 0.15
|
||||||
|
|
||||||
this.globalRotation = 0.028
|
this.globalRotation = 0.028
|
||||||
this.darkTextureRotation = 0.006
|
this.darkTextureRotation = 0.006
|
||||||
|
|||||||
@ -15,6 +15,11 @@ class Scheduler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetInterval(func, delay, ...args) {
|
||||||
|
this.clearInterval(func)
|
||||||
|
this.setInterval(func, delay, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(func, delay, ...args) {
|
setTimeout(func, delay, ...args) {
|
||||||
this.timeoutTasks.set(func, window.setTimeout(func, delay, ...args))
|
this.timeoutTasks.set(func, window.setTimeout(func, delay, ...args))
|
||||||
}
|
}
|
||||||
@ -33,7 +38,4 @@ class Scheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const scheduler = new Scheduler()
|
export const scheduler = new Scheduler()
|
||||||
|
|
||||||
|
|
||||||
export { scheduler }
|
|
||||||
Reference in New Issue
Block a user