1+ // Wait until the entire HTML document is loaded and parsed
2+ window . addEventListener ( 'DOMContentLoaded' , ( ) => {
3+
4+ const canvas = document . getElementById ( "renderCanvas" ) ;
5+ const engine = new BABYLON . Engine ( canvas , true ) ;
6+ const debugInfo = document . getElementById ( "debugInfo" ) ;
7+ const inputMap = { } ;
8+
9+ const createScene = ( ) => {
10+ const scene = new BABYLON . Scene ( engine ) ;
11+
12+ const camera = new BABYLON . ArcRotateCamera ( "camera" , Math . PI / 4 , Math . PI / 4 , 30 , BABYLON . Vector3 . Zero ( ) , scene ) ;
13+ camera . attachControl ( canvas , true ) ;
14+ camera . upperBetaLimit = Math . PI / 2.2 ;
15+ camera . lowerRadiusLimit = 10 ;
16+ camera . upperRadiusLimit = 50 ;
17+
18+ const light = new BABYLON . HemisphericLight ( "light" , new BABYLON . Vector3 ( 0 , 1 , 0 ) , scene ) ;
19+ light . intensity = 0.8 ;
20+ const gravityVector = new BABYLON . Vector3 ( 0 , - 9.81 , 0 ) ;
21+ const physicsPlugin = new BABYLON . CannonJSPlugin ( ) ;
22+ scene . enablePhysics ( gravityVector , physicsPlugin ) ;
23+
24+ const floorMaterial = new BABYLON . StandardMaterial ( "floorMat" , scene ) ;
25+ floorMaterial . diffuseTexture = new BABYLON . Texture ( "checkerboard.jpg" , scene ) ;
26+ floorMaterial . specularColor = BABYLON . Color3 . Black ( ) ;
27+
28+ const wallMaterial = new BABYLON . StandardMaterial ( "wallMat" , scene ) ;
29+ wallMaterial . diffuseTexture = new BABYLON . Texture ( "wood.jpg" , scene ) ;
30+ wallMaterial . specularColor = BABYLON . Color3 . Black ( ) ;
31+
32+ const goalMaterial = new BABYLON . StandardMaterial ( "goalMat" , scene ) ;
33+ goalMaterial . diffuseColor = new BABYLON . Color3 . Green ( ) ;
34+ goalMaterial . emissiveColor = new BABYLON . Color3 ( 0.2 , 0.7 , 0.2 ) ;
35+
36+ const levelMap = [ "WWWWWWWWWW" , "WS-------W" , "W-WWWW-O-W" , "W-W----O-W" , "W-W-WWWW-W" , "W-O-W----W" , "W-O-WW-W-W" , "W---G--W-W" , "WWWWWWWWWW" ] ;
37+ const tileSize = 2 , wallHeight = 2 ;
38+ let startPosition = BABYLON . Vector3 . Zero ( ) , goalMesh = null ;
39+
40+ const createLevelFromMap = ( map ) => {
41+ const levelWidth = map [ 0 ] . length , levelHeight = map . length ;
42+ const offsetX = - ( levelWidth * tileSize ) / 2 + tileSize / 2 , offsetZ = ( levelHeight * tileSize ) / 2 - tileSize / 2 ;
43+ for ( let row = 0 ; row < map . length ; row ++ ) for ( let col = 0 ; col < map [ row ] . length ; col ++ ) {
44+ const char = map [ row ] [ col ] , x = col * tileSize + offsetX , z = - ( row * tileSize - offsetZ ) ;
45+ if ( char === '-' || char === 'S' || char === 'G' ) {
46+ const tile = BABYLON . MeshBuilder . CreateBox ( `f_${ row } _${ col } ` , { width :tileSize , height :0.1 , depth :tileSize } , scene ) ;
47+ tile . position = new BABYLON . Vector3 ( x , 0 , z ) ; tile . material = floorMaterial ;
48+ // <-- FIX: Using full property names for physics
49+ tile . physicsImpostor = new BABYLON . PhysicsImpostor ( tile , BABYLON . PhysicsImpostor . BoxImpostor , { mass :0 , restitution :0.5 , friction :0.5 } , scene ) ;
50+ if ( char === 'S' ) startPosition = new BABYLON . Vector3 ( x , 1 , z ) ;
51+ if ( char === 'G' ) { tile . material = goalMaterial ; goalMesh = tile ; }
52+ } else if ( char === 'W' ) {
53+ const wall = BABYLON . MeshBuilder . CreateBox ( `w_${ row } _${ col } ` , { width :tileSize , height :wallHeight , depth :tileSize } , scene ) ;
54+ wall . position = new BABYLON . Vector3 ( x , wallHeight / 2 , z ) ; wall . material = wallMaterial ;
55+ // <-- FIX: Using full property names for physics
56+ wall . physicsImpostor = new BABYLON . PhysicsImpostor ( wall , BABYLON . PhysicsImpostor . BoxImpostor , { mass :0 , restitution :0.5 , friction :0.5 } , scene ) ;
57+ }
58+ }
59+ } ;
60+ createLevelFromMap ( levelMap ) ;
61+
62+ const ball = BABYLON . MeshBuilder . CreateSphere ( "ball" , { diameter : 1 } , scene ) ;
63+ const ballMaterial = new BABYLON . StandardMaterial ( "ballMat" , scene ) ;
64+ ballMaterial . diffuseTexture = new BABYLON . Texture ( "ball8.png" , scene ) ;
65+ ballMaterial . specularColor = BABYLON . Color3 . Black ( ) ;
66+ ball . material = ballMaterial ;
67+ // <-- FIX: Using full property names for physics
68+ ball . physicsImpostor = new BABYLON . PhysicsImpostor ( ball , BABYLON . PhysicsImpostor . SphereImpostor , { mass :1 , restitution :0.5 , friction :0.1 , damping :0.1 } ) ;
69+ const resetBall = ( ) => { ball . physicsImpostor . setLinearVelocity ( BABYLON . Vector3 . Zero ( ) ) ; ball . physicsImpostor . setAngularVelocity ( BABYLON . Vector3 . Zero ( ) ) ; ball . position = startPosition ; } ;
70+ resetBall ( ) ;
71+
72+ scene . actionManager = new BABYLON . ActionManager ( scene ) ;
73+ if ( goalMesh ) {
74+ goalMesh . actionManager = new BABYLON . ActionManager ( scene ) ;
75+ goalMesh . actionManager . registerAction ( new BABYLON . ExecuteCodeAction ( { trigger :BABYLON . ActionManager . OnIntersectionEnterTrigger , parameter :ball } , ( ) => { alert ( "You Win!" ) ; resetBall ( ) ; } ) ) ;
76+ }
77+ scene . actionManager . registerAction ( new BABYLON . ExecuteCodeAction ( BABYLON . ActionManager . OnKeyDownTrigger , ( evt ) => { inputMap [ evt . sourceEvent . key . toLowerCase ( ) ] = true ; } ) ) ;
78+ scene . actionManager . registerAction ( new BABYLON . ExecuteCodeAction ( BABYLON . ActionManager . OnKeyUpTrigger , ( evt ) => { inputMap [ evt . sourceEvent . key . toLowerCase ( ) ] = false ; } ) ) ;
79+
80+ scene . onBeforeRenderObservable . add ( ( ) => {
81+ if ( ball . position . y < - 5 ) { alert ( "You fell! Try again." ) ; resetBall ( ) ; }
82+
83+ const moveForce = 15 ;
84+ const forceDirection = new BABYLON . Vector3 . Zero ( ) ;
85+ const cameraForward = camera . getDirection ( BABYLON . Vector3 . Forward ( ) ) ;
86+ const forward = new BABYLON . Vector3 ( cameraForward . x , 0 , cameraForward . z ) . normalize ( ) ;
87+ const right = BABYLON . Vector3 . Cross ( BABYLON . Vector3 . Up ( ) , forward ) . normalize ( ) ;
88+
89+ if ( inputMap [ "w" ] ) { forceDirection . addInPlace ( forward ) ; }
90+ if ( inputMap [ "s" ] ) { forceDirection . subtractInPlace ( forward ) ; }
91+ if ( inputMap [ "d" ] ) { forceDirection . addInPlace ( right ) ; }
92+ if ( inputMap [ "a" ] ) { forceDirection . subtractInPlace ( right ) ; }
93+
94+ if ( forceDirection . length ( ) > 0.01 ) {
95+ forceDirection . normalize ( ) . scaleInPlace ( moveForce ) ;
96+ ball . physicsImpostor . applyForce ( forceDirection , ball . getAbsolutePosition ( ) ) ;
97+ }
98+
99+ if ( debugInfo ) {
100+ const alphaDeg = BABYLON . Tools . ToDegrees ( camera . alpha ) . toFixed ( 2 ) ;
101+ const betaDeg = BABYLON . Tools . ToDegrees ( camera . beta ) . toFixed ( 2 ) ;
102+ const radius = camera . radius . toFixed ( 2 ) ;
103+ debugInfo . innerHTML = `Alpha (Y-rot): ${ alphaDeg } °<br>Beta (X-rot): ${ betaDeg } °<br>Radius (Zoom): ${ radius } ` ;
104+ }
105+ } ) ;
106+
107+ return scene ;
108+ } ;
109+
110+ const setupButtonControls = ( buttonId , key ) => {
111+ const button = document . getElementById ( buttonId ) ;
112+ if ( ! button ) return ;
113+ const pressEvent = ( e ) => { e . preventDefault ( ) ; inputMap [ key ] = true ; } ;
114+ const releaseEvent = ( e ) => { e . preventDefault ( ) ; inputMap [ key ] = false ; } ;
115+ button . addEventListener ( "mousedown" , pressEvent ) ;
116+ button . addEventListener ( "mouseup" , releaseEvent ) ;
117+ button . addEventListener ( "mouseleave" , releaseEvent ) ;
118+ button . addEventListener ( "touchstart" , pressEvent , { passive : false } ) ;
119+ button . addEventListener ( "touchend" , releaseEvent ) ;
120+ button . addEventListener ( "touchcancel" , releaseEvent ) ;
121+ } ;
122+ setupButtonControls ( "btn-up" , "w" ) ;
123+ setupButtonControls ( "btn-down" , "s" ) ;
124+ setupButtonControls ( "btn-left" , "a" ) ;
125+ setupButtonControls ( "btn-right" , "d" ) ;
126+
127+ const scene = createScene ( ) ;
128+ engine . runRenderLoop ( ( ) => { scene . render ( ) ; } ) ;
129+ window . addEventListener ( "resize" , ( ) => { engine . resize ( ) ; } ) ;
130+
131+ } ) ;
0 commit comments