@@ -28,6 +28,14 @@ function createAsteroid(seed) {
2828 } ;
2929}
3030
31+ function getCenter ( transform3D , size3D ) {
32+ return {
33+ x : transform3D . x + size3D . width * 0.5 ,
34+ y : transform3D . y + size3D . height * 0.5 ,
35+ z : transform3D . z + size3D . depth * 0.5 ,
36+ } ;
37+ }
38+
3139export default class SpaceShooter3DScene extends Scene {
3240 constructor ( ) {
3341 super ( ) ;
@@ -48,8 +56,16 @@ export default class SpaceShooter3DScene extends Scene {
4856 this . bulletSpeed = 26 ;
4957 this . asteroidSpeed = 7.5 ;
5058 this . fireCooldown = 0 ;
59+ this . bulletHitPadding = 1.2 ;
5160 this . score = 0 ;
5261 this . misses = 0 ;
62+ this . loopMissLimit = 10 ;
63+ this . loopNumber = 1 ;
64+ this . loopElapsedSeconds = 0 ;
65+ this . loopScoreAtStart = 0 ;
66+ this . lastLoopScore = 0 ;
67+ this . lastLoopReason = 'active' ;
68+ this . resetLatch = false ;
5369 }
5470
5571 setCamera3D ( camera3D ) {
@@ -83,8 +99,33 @@ export default class SpaceShooter3DScene extends Scene {
8399 asteroid . driftY = ( ( seed * 5 ) % 10 - 5 ) * 0.11 ;
84100 }
85101
102+ startLoop ( reason = 'manual-reset' ) {
103+ this . lastLoopReason = reason ;
104+ this . lastLoopScore = this . score - this . loopScoreAtStart ;
105+ this . loopNumber += 1 ;
106+ this . loopElapsedSeconds = 0 ;
107+ this . loopScoreAtStart = this . score ;
108+ this . misses = 0 ;
109+ this . fireCooldown = 0 ;
110+ this . bullets = [ ] ;
111+ this . ship . transform3D . x = 0 ;
112+ this . ship . transform3D . y = 0 ;
113+ this . ship . transform3D . z = 6.5 ;
114+
115+ this . asteroids . forEach ( ( asteroid , asteroidIndex ) => {
116+ this . resetAsteroid ( asteroid , asteroidIndex + this . loopNumber * 11 ) ;
117+ } ) ;
118+ this . syncCamera ( ) ;
119+ }
120+
86121 step3DPhysics ( dt , engine ) {
87122 const input = engine . input ;
123+ const resetPressed = input ?. isDown ( 'KeyR' ) === true ;
124+ if ( resetPressed && ! this . resetLatch ) {
125+ this . startLoop ( 'manual-reset' ) ;
126+ }
127+ this . resetLatch = resetPressed ;
128+
88129 const moveX = ( input ?. isDown ( 'KeyD' ) ? 1 : 0 ) - ( input ?. isDown ( 'KeyA' ) ? 1 : 0 ) ;
89130 const moveY = ( input ?. isDown ( 'KeyW' ) ? 1 : 0 ) - ( input ?. isDown ( 'KeyS' ) ? 1 : 0 ) ;
90131 const moveLength = Math . hypot ( moveX , moveY ) || 1 ;
@@ -97,21 +138,63 @@ export default class SpaceShooter3DScene extends Scene {
97138 this . fireCooldown = Math . max ( 0 , this . fireCooldown - dt ) ;
98139 const firePressed = input ?. isDown ( 'Space' ) === true ;
99140 if ( firePressed && this . fireCooldown <= 0 ) {
141+ const bulletSize = { width : 0.25 , height : 0.25 , depth : 0.8 } ;
142+ const bulletTransform = {
143+ x : this . ship . transform3D . x + this . ship . size3D . width * 0.5 - bulletSize . width * 0.5 ,
144+ y : this . ship . transform3D . y + this . ship . size3D . height * 0.5 - bulletSize . height * 0.5 ,
145+ z : this . ship . transform3D . z + 1.7 ,
146+ } ;
147+ const bulletCenter = getCenter ( bulletTransform , bulletSize ) ;
148+
149+ let target = null ;
150+ for ( const asteroid of this . asteroids ) {
151+ if ( asteroid . transform3D . z + asteroid . size3D . depth < bulletTransform . z ) {
152+ continue ;
153+ }
154+ if ( ! target || asteroid . transform3D . z < target . transform3D . z ) {
155+ target = asteroid ;
156+ }
157+ }
158+
159+ let velocityX = 0 ;
160+ let velocityY = 0 ;
161+ let velocityZ = this . bulletSpeed ;
162+ if ( target ) {
163+ const targetCenter = getCenter ( target . transform3D , target . size3D ) ;
164+ const dx = targetCenter . x - bulletCenter . x ;
165+ const dy = targetCenter . y - bulletCenter . y ;
166+ const dz = Math . max ( 0.8 , targetCenter . z - bulletCenter . z ) ;
167+ const magnitude = Math . hypot ( dx , dy , dz ) || 1 ;
168+ velocityX = ( dx / magnitude ) * this . bulletSpeed ;
169+ velocityY = ( dy / magnitude ) * this . bulletSpeed ;
170+ velocityZ = ( dz / magnitude ) * this . bulletSpeed ;
171+ }
172+
100173 this . bullets . push ( {
101- transform3D : {
102- x : this . ship . transform3D . x + 0.38 ,
103- y : this . ship . transform3D . y + 0.2 ,
104- z : this . ship . transform3D . z + 1.7 ,
105- } ,
106- size3D : { width : 0.25 , height : 0.25 , depth : 0.8 } ,
174+ transform3D : bulletTransform ,
175+ previousTransform3D : { ...bulletTransform } ,
176+ size3D : bulletSize ,
177+ velocity3D : { x : velocityX , y : velocityY , z : velocityZ } ,
107178 } ) ;
108179 this . fireCooldown = 0.13 ;
109180 }
110181
111182 this . bullets . forEach ( ( bullet ) => {
112- bullet . transform3D . z += this . bulletSpeed * dt ;
183+ bullet . previousTransform3D . x = bullet . transform3D . x ;
184+ bullet . previousTransform3D . y = bullet . transform3D . y ;
185+ bullet . previousTransform3D . z = bullet . transform3D . z ;
186+ bullet . transform3D . x += bullet . velocity3D . x * dt ;
187+ bullet . transform3D . y += bullet . velocity3D . y * dt ;
188+ bullet . transform3D . z += bullet . velocity3D . z * dt ;
113189 } ) ;
114- this . bullets = this . bullets . filter ( ( bullet ) => bullet . transform3D . z < 40 ) ;
190+ this . bullets = this . bullets . filter (
191+ ( bullet ) =>
192+ bullet . transform3D . z < 40 &&
193+ bullet . transform3D . x > - 9 &&
194+ bullet . transform3D . x < 9 &&
195+ bullet . transform3D . y > - 5 &&
196+ bullet . transform3D . y < 7 ,
197+ ) ;
115198
116199 this . asteroids . forEach ( ( asteroid , asteroidIndex ) => {
117200 asteroid . transform3D . z -= this . asteroidSpeed * dt ;
@@ -133,12 +216,15 @@ export default class SpaceShooter3DScene extends Scene {
133216 const asteroid = this . asteroids [ asteroidIndex ] ;
134217 const collided = isAabbColliding3D (
135218 {
136- x : bullet . transform3D . x ,
137- y : bullet . transform3D . y ,
138- z : bullet . transform3D . z ,
139- width : bullet . size3D . width ,
140- height : bullet . size3D . height ,
141- depth : bullet . size3D . depth ,
219+ x : Math . min ( bullet . previousTransform3D . x , bullet . transform3D . x ) - this . bulletHitPadding ,
220+ y : Math . min ( bullet . previousTransform3D . y , bullet . transform3D . y ) - this . bulletHitPadding ,
221+ z : Math . min ( bullet . previousTransform3D . z , bullet . transform3D . z ) - this . bulletHitPadding ,
222+ width : bullet . size3D . width + this . bulletHitPadding * 2 ,
223+ height : bullet . size3D . height + this . bulletHitPadding * 2 ,
224+ depth :
225+ Math . abs ( bullet . transform3D . z - bullet . previousTransform3D . z ) +
226+ bullet . size3D . depth +
227+ this . bulletHitPadding * 2 ,
142228 } ,
143229 {
144230 x : asteroid . transform3D . x ,
@@ -163,15 +249,20 @@ export default class SpaceShooter3DScene extends Scene {
163249 }
164250 }
165251
252+ this . loopElapsedSeconds += dt ;
253+ if ( this . misses >= this . loopMissLimit ) {
254+ this . startLoop ( 'miss-limit' ) ;
255+ }
256+
166257 this . syncCamera ( ) ;
167258 }
168259
169260 render ( renderer ) {
170261 drawFrame ( renderer , theme , [
171262 'Sample 1607 - 3D Space Shooter' ,
172- 'Pilot a ship lane, fire at incoming asteroids, and track score .' ,
173- 'Move: W A S D | Fire: Space' ,
174- 'Keep the asteroid lane clear before misses stack up .' ,
263+ 'Pilot a ship lane, fire at incoming asteroids, and track looping rounds .' ,
264+ 'Move: W A S D | Fire: Space | Reset round: R ' ,
265+ 'Round auto-resets when misses reach the round limit .' ,
175266 ] ) ;
176267
177268 renderer . strokeRect ( this . viewport . x , this . viewport . y , this . viewport . width , this . viewport . height , '#d8d5ff' , 2 ) ;
@@ -202,13 +293,14 @@ export default class SpaceShooter3DScene extends Scene {
202293 drawWireBox ( renderer , asteroid . transform3D , asteroid . size3D , cameraState , projectionViewport , '#fb7185' , 2 ) ;
203294 } ) ;
204295
205- drawPanel ( renderer , 620 , 34 , 300 , 126 , 'Shooter Runtime' , [
296+ drawPanel ( renderer , 620 , 34 , 300 , 186 , 'Shooter Runtime' , [
206297 `Ship: x=${ this . ship . transform3D . x . toFixed ( 2 ) } y=${ this . ship . transform3D . y . toFixed ( 2 ) } z=${ this . ship . transform3D . z . toFixed ( 2 ) } ` ,
207298 `Bullets: ${ this . bullets . length } | Cooldown: ${ this . fireCooldown . toFixed ( 2 ) } s` ,
208299 `Asteroids: ${ this . asteroids . length } ` ,
209300 `Score: ${ this . score } ` ,
210- `Misses: ${ this . misses } ` ,
301+ `Round ${ this . loopNumber } | Misses: ${ this . misses } /${ this . loopMissLimit } ` ,
302+ `Round time: ${ this . loopElapsedSeconds . toFixed ( 1 ) } s | Last round score: ${ this . lastLoopScore } ` ,
303+ `Last round reset: ${ this . lastLoopReason } ` ,
211304 ] ) ;
212305 }
213306}
214-
0 commit comments