1
+ "use strict" ;
2
+ console . clear ( ) ;
3
+ class Stage {
4
+ constructor ( ) {
5
+ // container
6
+ this . render = function ( ) {
7
+ this . renderer . render ( this . scene , this . camera ) ;
8
+ } ;
9
+ this . add = function ( elem ) {
10
+ this . scene . add ( elem ) ;
11
+ } ;
12
+ this . remove = function ( elem ) {
13
+ this . scene . remove ( elem ) ;
14
+ } ;
15
+ this . container = document . getElementById ( 'game' ) ;
16
+ // renderer
17
+ this . renderer = new THREE . WebGLRenderer ( {
18
+ antialias : true ,
19
+ alpha : false
20
+ } ) ;
21
+ this . renderer . setSize ( window . innerWidth , window . innerHeight ) ;
22
+ this . renderer . setClearColor ( '#D0CBC7' , 1 ) ;
23
+ this . container . appendChild ( this . renderer . domElement ) ;
24
+ // scene
25
+ this . scene = new THREE . Scene ( ) ;
26
+ // camera
27
+ let aspect = window . innerWidth / window . innerHeight ;
28
+ let d = 20 ;
29
+ this . camera = new THREE . OrthographicCamera ( - d * aspect , d * aspect , d , - d , - 100 , 1000 ) ;
30
+ this . camera . position . x = 2 ;
31
+ this . camera . position . y = 2 ;
32
+ this . camera . position . z = 2 ;
33
+ this . camera . lookAt ( new THREE . Vector3 ( 0 , 0 , 0 ) ) ;
34
+ //light
35
+ this . light = new THREE . DirectionalLight ( 0xffffff , 0.5 ) ;
36
+ this . light . position . set ( 0 , 499 , 0 ) ;
37
+ this . scene . add ( this . light ) ;
38
+ this . softLight = new THREE . AmbientLight ( 0xffffff , 0.4 ) ;
39
+ this . scene . add ( this . softLight ) ;
40
+ window . addEventListener ( 'resize' , ( ) => this . onResize ( ) ) ;
41
+ this . onResize ( ) ;
42
+ }
43
+ setCamera ( y , speed = 0.3 ) {
44
+ TweenLite . to ( this . camera . position , speed , { y : y + 4 , ease : Power1 . easeInOut } ) ;
45
+ TweenLite . to ( this . camera . lookAt , speed , { y : y , ease : Power1 . easeInOut } ) ;
46
+ }
47
+ onResize ( ) {
48
+ let viewSize = 30 ;
49
+ this . renderer . setSize ( window . innerWidth , window . innerHeight ) ;
50
+ this . camera . left = window . innerWidth / - viewSize ;
51
+ this . camera . right = window . innerWidth / viewSize ;
52
+ this . camera . top = window . innerHeight / viewSize ;
53
+ this . camera . bottom = window . innerHeight / - viewSize ;
54
+ this . camera . updateProjectionMatrix ( ) ;
55
+ }
56
+ }
57
+ class Block {
58
+ constructor ( block ) {
59
+ // set size and position
60
+ this . STATES = { ACTIVE : 'active' , STOPPED : 'stopped' , MISSED : 'missed' } ;
61
+ this . MOVE_AMOUNT = 12 ;
62
+ this . dimension = { width : 0 , height : 0 , depth : 0 } ;
63
+ this . position = { x : 0 , y : 0 , z : 0 } ;
64
+ this . targetBlock = block ;
65
+ this . index = ( this . targetBlock ? this . targetBlock . index : 0 ) + 1 ;
66
+ this . workingPlane = this . index % 2 ? 'x' : 'z' ;
67
+ this . workingDimension = this . index % 2 ? 'width' : 'depth' ;
68
+ // set the dimensions from the target block, or defaults.
69
+ this . dimension . width = this . targetBlock ? this . targetBlock . dimension . width : 10 ;
70
+ this . dimension . height = this . targetBlock ? this . targetBlock . dimension . height : 2 ;
71
+ this . dimension . depth = this . targetBlock ? this . targetBlock . dimension . depth : 10 ;
72
+ this . position . x = this . targetBlock ? this . targetBlock . position . x : 0 ;
73
+ this . position . y = this . dimension . height * this . index ;
74
+ this . position . z = this . targetBlock ? this . targetBlock . position . z : 0 ;
75
+ this . colorOffset = this . targetBlock ? this . targetBlock . colorOffset : Math . round ( Math . random ( ) * 100 ) ;
76
+ // set color
77
+ if ( ! this . targetBlock ) {
78
+ this . color = 0x333344 ;
79
+ }
80
+ else {
81
+ let offset = this . index + this . colorOffset ;
82
+ var r = Math . sin ( 0.3 * offset ) * 55 + 200 ;
83
+ var g = Math . sin ( 0.3 * offset + 2 ) * 55 + 200 ;
84
+ var b = Math . sin ( 0.3 * offset + 4 ) * 55 + 200 ;
85
+ this . color = new THREE . Color ( r / 255 , g / 255 , b / 255 ) ;
86
+ }
87
+ // state
88
+ this . state = this . index > 1 ? this . STATES . ACTIVE : this . STATES . STOPPED ;
89
+ // set direction
90
+ this . speed = - 0.1 - ( this . index * 0.005 ) ;
91
+ if ( this . speed < - 4 )
92
+ this . speed = - 4 ;
93
+ this . direction = this . speed ;
94
+ // create block
95
+ let geometry = new THREE . BoxGeometry ( this . dimension . width , this . dimension . height , this . dimension . depth ) ;
96
+ geometry . applyMatrix ( new THREE . Matrix4 ( ) . makeTranslation ( this . dimension . width / 2 , this . dimension . height / 2 , this . dimension . depth / 2 ) ) ;
97
+ this . material = new THREE . MeshToonMaterial ( { color : this . color , shading : THREE . FlatShading } ) ;
98
+ this . mesh = new THREE . Mesh ( geometry , this . material ) ;
99
+ this . mesh . position . set ( this . position . x , this . position . y + ( this . state == this . STATES . ACTIVE ? 0 : 0 ) , this . position . z ) ;
100
+ if ( this . state == this . STATES . ACTIVE ) {
101
+ this . position [ this . workingPlane ] = Math . random ( ) > 0.5 ? - this . MOVE_AMOUNT : this . MOVE_AMOUNT ;
102
+ }
103
+ }
104
+ reverseDirection ( ) {
105
+ this . direction = this . direction > 0 ? this . speed : Math . abs ( this . speed ) ;
106
+ }
107
+ place ( ) {
108
+ this . state = this . STATES . STOPPED ;
109
+ let overlap = this . targetBlock . dimension [ this . workingDimension ] - Math . abs ( this . position [ this . workingPlane ] - this . targetBlock . position [ this . workingPlane ] ) ;
110
+ let blocksToReturn = {
111
+ plane : this . workingPlane ,
112
+ direction : this . direction
113
+ } ;
114
+ if ( this . dimension [ this . workingDimension ] - overlap < 0.3 ) {
115
+ overlap = this . dimension [ this . workingDimension ] ;
116
+ blocksToReturn . bonus = true ;
117
+ this . position . x = this . targetBlock . position . x ;
118
+ this . position . z = this . targetBlock . position . z ;
119
+ this . dimension . width = this . targetBlock . dimension . width ;
120
+ this . dimension . depth = this . targetBlock . dimension . depth ;
121
+ }
122
+ if ( overlap > 0 ) {
123
+ let choppedDimensions = { width : this . dimension . width , height : this . dimension . height , depth : this . dimension . depth } ;
124
+ choppedDimensions [ this . workingDimension ] -= overlap ;
125
+ this . dimension [ this . workingDimension ] = overlap ;
126
+ let placedGeometry = new THREE . BoxGeometry ( this . dimension . width , this . dimension . height , this . dimension . depth ) ;
127
+ placedGeometry . applyMatrix ( new THREE . Matrix4 ( ) . makeTranslation ( this . dimension . width / 2 , this . dimension . height / 2 , this . dimension . depth / 2 ) ) ;
128
+ let placedMesh = new THREE . Mesh ( placedGeometry , this . material ) ;
129
+ let choppedGeometry = new THREE . BoxGeometry ( choppedDimensions . width , choppedDimensions . height , choppedDimensions . depth ) ;
130
+ choppedGeometry . applyMatrix ( new THREE . Matrix4 ( ) . makeTranslation ( choppedDimensions . width / 2 , choppedDimensions . height / 2 , choppedDimensions . depth / 2 ) ) ;
131
+ let choppedMesh = new THREE . Mesh ( choppedGeometry , this . material ) ;
132
+ let choppedPosition = {
133
+ x : this . position . x ,
134
+ y : this . position . y ,
135
+ z : this . position . z
136
+ } ;
137
+ if ( this . position [ this . workingPlane ] < this . targetBlock . position [ this . workingPlane ] ) {
138
+ this . position [ this . workingPlane ] = this . targetBlock . position [ this . workingPlane ] ;
139
+ }
140
+ else {
141
+ choppedPosition [ this . workingPlane ] += overlap ;
142
+ }
143
+ placedMesh . position . set ( this . position . x , this . position . y , this . position . z ) ;
144
+ choppedMesh . position . set ( choppedPosition . x , choppedPosition . y , choppedPosition . z ) ;
145
+ blocksToReturn . placed = placedMesh ;
146
+ if ( ! blocksToReturn . bonus )
147
+ blocksToReturn . chopped = choppedMesh ;
148
+ }
149
+ else {
150
+ this . state = this . STATES . MISSED ;
151
+ }
152
+ this . dimension [ this . workingDimension ] = overlap ;
153
+ return blocksToReturn ;
154
+ }
155
+ tick ( ) {
156
+ if ( this . state == this . STATES . ACTIVE ) {
157
+ let value = this . position [ this . workingPlane ] ;
158
+ if ( value > this . MOVE_AMOUNT || value < - this . MOVE_AMOUNT )
159
+ this . reverseDirection ( ) ;
160
+ this . position [ this . workingPlane ] += this . direction ;
161
+ this . mesh . position [ this . workingPlane ] = this . position [ this . workingPlane ] ;
162
+ }
163
+ }
164
+ }
165
+ class Game {
166
+ constructor ( ) {
167
+ this . STATES = {
168
+ 'LOADING' : 'loading' ,
169
+ 'PLAYING' : 'playing' ,
170
+ 'READY' : 'ready' ,
171
+ 'ENDED' : 'ended' ,
172
+ 'RESETTING' : 'resetting'
173
+ } ;
174
+ this . blocks = [ ] ;
175
+ this . state = this . STATES . LOADING ;
176
+ this . stage = new Stage ( ) ;
177
+ this . mainContainer = document . getElementById ( 'container' ) ;
178
+ this . scoreContainer = document . getElementById ( 'score' ) ;
179
+ this . startButton = document . getElementById ( 'start-button' ) ;
180
+ this . instructions = document . getElementById ( 'instructions' ) ;
181
+ this . scoreContainer . innerHTML = '0' ;
182
+ this . newBlocks = new THREE . Group ( ) ;
183
+ this . placedBlocks = new THREE . Group ( ) ;
184
+ this . choppedBlocks = new THREE . Group ( ) ;
185
+ this . stage . add ( this . newBlocks ) ;
186
+ this . stage . add ( this . placedBlocks ) ;
187
+ this . stage . add ( this . choppedBlocks ) ;
188
+ this . addBlock ( ) ;
189
+ this . tick ( ) ;
190
+ this . updateState ( this . STATES . READY ) ;
191
+ document . addEventListener ( 'keydown' , e => {
192
+ if ( e . keyCode == 32 )
193
+ this . onAction ( ) ;
194
+ } ) ;
195
+ document . addEventListener ( 'click' , e => {
196
+ this . onAction ( ) ;
197
+ } ) ;
198
+ document . addEventListener ( 'touchstart' , e => {
199
+ e . preventDefault ( ) ;
200
+ // this.onAction();
201
+ // this triggers after click on android so you
202
+ // insta-lose, will figure it out later.
203
+ } ) ;
204
+ }
205
+ updateState ( newState ) {
206
+ for ( let key in this . STATES )
207
+ this . mainContainer . classList . remove ( this . STATES [ key ] ) ;
208
+ this . mainContainer . classList . add ( newState ) ;
209
+ this . state = newState ;
210
+ }
211
+ onAction ( ) {
212
+ switch ( this . state ) {
213
+ case this . STATES . READY :
214
+ this . startGame ( ) ;
215
+ break ;
216
+ case this . STATES . PLAYING :
217
+ this . placeBlock ( ) ;
218
+ break ;
219
+ case this . STATES . ENDED :
220
+ this . restartGame ( ) ;
221
+ break ;
222
+ }
223
+ }
224
+ startGame ( ) {
225
+ if ( this . state != this . STATES . PLAYING ) {
226
+ this . scoreContainer . innerHTML = '0' ;
227
+ this . updateState ( this . STATES . PLAYING ) ;
228
+ this . addBlock ( ) ;
229
+ }
230
+ }
231
+ restartGame ( ) {
232
+ this . updateState ( this . STATES . RESETTING ) ;
233
+ let oldBlocks = this . placedBlocks . children ;
234
+ let removeSpeed = 0.2 ;
235
+ let delayAmount = 0.02 ;
236
+ for ( let i = 0 ; i < oldBlocks . length ; i ++ ) {
237
+ TweenLite . to ( oldBlocks [ i ] . scale , removeSpeed , { x : 0 , y : 0 , z : 0 , delay : ( oldBlocks . length - i ) * delayAmount , ease : Power1 . easeIn , onComplete : ( ) => this . placedBlocks . remove ( oldBlocks [ i ] ) } ) ;
238
+ TweenLite . to ( oldBlocks [ i ] . rotation , removeSpeed , { y : 0.5 , delay : ( oldBlocks . length - i ) * delayAmount , ease : Power1 . easeIn } ) ;
239
+ }
240
+ let cameraMoveSpeed = removeSpeed * 2 + ( oldBlocks . length * delayAmount ) ;
241
+ this . stage . setCamera ( 2 , cameraMoveSpeed ) ;
242
+ let countdown = { value : this . blocks . length - 1 } ;
243
+ TweenLite . to ( countdown , cameraMoveSpeed , { value : 0 , onUpdate : ( ) => { this . scoreContainer . innerHTML = String ( Math . round ( countdown . value ) ) ; } } ) ;
244
+ this . blocks = this . blocks . slice ( 0 , 1 ) ;
245
+ setTimeout ( ( ) => {
246
+ this . startGame ( ) ;
247
+ } , cameraMoveSpeed * 1000 ) ;
248
+ }
249
+ placeBlock ( ) {
250
+ let currentBlock = this . blocks [ this . blocks . length - 1 ] ;
251
+ let newBlocks = currentBlock . place ( ) ;
252
+ this . newBlocks . remove ( currentBlock . mesh ) ;
253
+ if ( newBlocks . placed )
254
+ this . placedBlocks . add ( newBlocks . placed ) ;
255
+ if ( newBlocks . chopped ) {
256
+ this . choppedBlocks . add ( newBlocks . chopped ) ;
257
+ let positionParams = { y : '-=30' , ease : Power1 . easeIn , onComplete : ( ) => this . choppedBlocks . remove ( newBlocks . chopped ) } ;
258
+ let rotateRandomness = 10 ;
259
+ let rotationParams = {
260
+ delay : 0.05 ,
261
+ x : newBlocks . plane == 'z' ? ( ( Math . random ( ) * rotateRandomness ) - ( rotateRandomness / 2 ) ) : 0.1 ,
262
+ z : newBlocks . plane == 'x' ? ( ( Math . random ( ) * rotateRandomness ) - ( rotateRandomness / 2 ) ) : 0.1 ,
263
+ y : Math . random ( ) * 0.1 ,
264
+ } ;
265
+ if ( newBlocks . chopped . position [ newBlocks . plane ] > newBlocks . placed . position [ newBlocks . plane ] ) {
266
+ positionParams [ newBlocks . plane ] = '+=' + ( 40 * Math . abs ( newBlocks . direction ) ) ;
267
+ }
268
+ else {
269
+ positionParams [ newBlocks . plane ] = '-=' + ( 40 * Math . abs ( newBlocks . direction ) ) ;
270
+ }
271
+ TweenLite . to ( newBlocks . chopped . position , 1 , positionParams ) ;
272
+ TweenLite . to ( newBlocks . chopped . rotation , 1 , rotationParams ) ;
273
+ }
274
+ this . addBlock ( ) ;
275
+ }
276
+ addBlock ( ) {
277
+ let lastBlock = this . blocks [ this . blocks . length - 1 ] ;
278
+ if ( lastBlock && lastBlock . state == lastBlock . STATES . MISSED ) {
279
+ return this . endGame ( ) ;
280
+ }
281
+ this . scoreContainer . innerHTML = String ( this . blocks . length - 1 ) ;
282
+ let newKidOnTheBlock = new Block ( lastBlock ) ;
283
+ this . newBlocks . add ( newKidOnTheBlock . mesh ) ;
284
+ this . blocks . push ( newKidOnTheBlock ) ;
285
+ this . stage . setCamera ( this . blocks . length * 2 ) ;
286
+ if ( this . blocks . length >= 5 )
287
+ this . instructions . classList . add ( 'hide' ) ;
288
+ }
289
+ endGame ( ) {
290
+ this . updateState ( this . STATES . ENDED ) ;
291
+ }
292
+ tick ( ) {
293
+ this . blocks [ this . blocks . length - 1 ] . tick ( ) ;
294
+ this . stage . render ( ) ;
295
+ requestAnimationFrame ( ( ) => { this . tick ( ) ; } ) ;
296
+ }
297
+ }
298
+ let game = new Game ( ) ;
0 commit comments