TomatitoToho commited on
Commit
49f2946
·
verified ·
1 Parent(s): a701e52

Upload src/components/game/GameCanvas.tsx with huggingface_hub

Browse files
Files changed (1) hide show
  1. src/components/game/GameCanvas.tsx +80 -38
src/components/game/GameCanvas.tsx CHANGED
@@ -1,8 +1,9 @@
1
  'use client'
2
 
3
- import { useEffect, useState, useRef } from 'react'
4
  import { Canvas } from '@react-three/fiber'
5
- import { GameScene } from './Scene'
 
6
  import { usePlayerStore } from '@/stores/playerStore'
7
  import { useWorldStore } from '@/stores/worldStore'
8
  import { useGameStore } from '@/stores/gameStore'
@@ -14,17 +15,27 @@ import { DebugOverlay } from '../ui/DebugOverlay'
14
  import { Crosshair } from '../ui/Crosshair'
15
  import { MobileControls } from '../ui/MobileControls'
16
  import { CreativeInventory } from '../ui/CreativeInventory'
17
- import { BLOCK_DEFS, CREATIVE_BLOCKS, BLOCK_AIR } from '@/engine/constants'
18
 
19
  export function GameCanvas() {
20
  const { keys, isPointerLocked, requestPointerLock, raycastBlock } = usePlayerControls()
21
  const [ready, setReady] = useState(false)
22
  const lastTimeRef = useRef(0)
 
23
 
24
- // Init
25
  useEffect(() => {
26
- useWorldStore.getState().initWorld(Math.floor(Math.random() * 1000000))
27
- usePlayerStore.getState().initPlayer()
 
 
 
 
 
 
 
 
 
28
  useGameStore.getState().setLoading(false)
29
  setReady(true)
30
  }, [])
@@ -35,26 +46,35 @@ export function GameCanvas() {
35
  const loop = (time: number) => {
36
  const delta = Math.min((time - lastTimeRef.current) / 1000, 0.1)
37
  lastTimeRef.current = time
38
- if (keys.current) updatePlayerMovement(keys.current, delta)
 
 
 
 
39
  useWorldStore.getState().updateWorldTime()
40
 
 
 
 
 
 
41
  // Handle mobile block interaction
42
- if (window.__MC_MOBILE_BREAK) {
43
  const hit = raycastBlock()
44
  if (hit) useWorldStore.getState().setBlock(hit.blockPos[0], hit.blockPos[1], hit.blockPos[2], BLOCK_AIR)
45
- window.__MC_MOBILE_BREAK = false
46
  }
47
- if (window.__MC_MOBILE_PLACE) {
48
  const hit = raycastBlock()
49
  const { player } = usePlayerStore.getState()
50
  if (hit && player) {
51
  const slot = useUIStore.getState().hotbarSlot
52
  const item = player.inventory.hotbar[slot]
53
- if (item && BLOCK_DEFS[item.id]?.solid) {
54
  useWorldStore.getState().setBlock(hit.placePos[0], hit.placePos[1], hit.placePos[2], item.id)
55
  }
56
  }
57
- window.__MC_MOBILE_PLACE = false
58
  }
59
 
60
  animId = requestAnimationFrame(loop)
@@ -63,70 +83,94 @@ export function GameCanvas() {
63
  return () => cancelAnimationFrame(animId)
64
  }, [keys, raycastBlock])
65
 
 
66
  if (!ready) {
67
  return (
68
  <div className="w-screen h-screen bg-[#2C2C2C] flex flex-col items-center justify-center gap-4">
69
- <div className="text-4xl font-bold text-white" style={{ fontFamily: 'monospace', textShadow: '2px 2px #000' }}>
70
- Minecraft Clone
 
71
  </div>
72
- <div className="w-48 h-2 bg-gray-700 rounded overflow-hidden">
73
- <div className="h-full bg-green-500 animate-pulse" style={{ width: '60%' }} />
 
 
74
  </div>
75
- <div className="text-gray-400 text-sm">Generating world...</div>
76
  </div>
77
  )
78
  }
79
 
80
- const isMobileDevice = typeof window !== 'undefined' && ('ontouchstart' in window || navigator.maxTouchPoints > 0)
 
81
 
82
  return (
83
  <div className="relative w-screen h-screen overflow-hidden bg-black select-none">
84
  <Canvas
85
- gl={{ antialias: false, powerPreference: 'high-performance', alpha: false, depth: true, stencil: false }}
 
 
 
 
 
 
86
  dpr={[1, isMobileDevice ? 1 : 1.5]}
87
- camera={{ fov: isMobileDevice ? 80 : 70, near: 0.05, far: 300, position: [8, 80 + 1.62, 8] }}
 
 
 
 
 
88
  style={{ position: 'absolute', inset: 0 }}
89
- onCreated={({ gl }) => { gl.toneMapping = THREE.ACESFilmicToneMapping }}
 
 
 
90
  >
91
  <GameScene />
92
  </Canvas>
93
 
94
- {/* Crosshair (desktop only) */}
95
  {!isMobileDevice && <Crosshair />}
96
 
97
  {/* Hotbar */}
98
- <div className="absolute bottom-1 left-1/2 -translate-x-1/2 z-10 sm:bottom-2">
99
  <Hotbar />
100
  </div>
101
 
102
  {/* HUD */}
103
  <HUD />
104
 
105
- {/* Debug */}
106
  <DebugOverlay />
107
 
108
- {/* Creative inventory panel */}
109
  <CreativeInventory />
110
 
111
  {/* Mobile controls */}
112
- <MobileControls />
113
 
114
- {/* Click to play overlay (desktop only) */}
115
  {!isMobileDevice && !document.pointerLockElement && (
116
  <div
117
  className="absolute inset-0 flex items-center justify-center bg-black/60 cursor-pointer z-50"
118
  onClick={requestPointerLock}
119
  >
120
- <div className="text-center text-white p-8 rounded-xl bg-black/40 backdrop-blur-sm">
121
- <h2 className="text-3xl sm:text-5xl font-bold mb-4" style={{ fontFamily: 'monospace', textShadow: '3px 3px #000' }}>
122
- Minecraft Clone
 
123
  </h2>
124
- <p className="text-xl mb-4 text-green-400">Click to play</p>
125
- <div className="text-sm opacity-70 space-y-1 text-left max-w-md mx-auto">
126
- <p>🎮 <b>WASD</b> - Move | <b>Space</b> - Jump | <b>Shift</b> - Sneak</p>
127
- <p>🖱️ <b>Left Click</b> - Break | <b>Right Click</b> - Place</p>
128
- <p>⚡ <b>F</b> - Fly | <b>Ctrl</b> - Sprint | <b>1-9</b> - Hotbar</p>
129
- <p>🔧 <b>F3</b> - Debug | <b>E</b> - Inventory | <b>Scroll</b> - Switch item</p>
 
 
 
 
130
  </div>
131
  </div>
132
  </div>
@@ -134,5 +178,3 @@ export function GameCanvas() {
134
  </div>
135
  )
136
  }
137
-
138
- import * as THREE from 'three'
 
1
  'use client'
2
 
3
+ import { useEffect, useState, useRef, useCallback } from 'react'
4
  import { Canvas } from '@react-three/fiber'
5
+ import * as THREE from 'three'
6
+ import { GameScene, raycastResultRef } from './Scene'
7
  import { usePlayerStore } from '@/stores/playerStore'
8
  import { useWorldStore } from '@/stores/worldStore'
9
  import { useGameStore } from '@/stores/gameStore'
 
15
  import { Crosshair } from '../ui/Crosshair'
16
  import { MobileControls } from '../ui/MobileControls'
17
  import { CreativeInventory } from '../ui/CreativeInventory'
18
+ import { BLOCK_DEFS, CREATIVE_BLOCKS, BLOCK_AIR, SEA_LEVEL, CHUNK_WIDTH, CHUNK_DEPTH } from '@/engine/constants'
19
 
20
  export function GameCanvas() {
21
  const { keys, isPointerLocked, requestPointerLock, raycastBlock } = usePlayerControls()
22
  const [ready, setReady] = useState(false)
23
  const lastTimeRef = useRef(0)
24
+ const [loadingProgress, setLoadingProgress] = useState(0)
25
 
26
+ // Init world and player
27
  useEffect(() => {
28
+ const seed = Math.floor(Math.random() * 1000000)
29
+ useWorldStore.getState().initWorld(seed)
30
+
31
+ // Find spawn height
32
+ const terrainGen = new (require('@/engine/terrain/generator').TerrainGenerator)(seed)
33
+ let spawnY = 100
34
+ try {
35
+ spawnY = terrainGen.getHeight(8, 8) + 3
36
+ } catch {}
37
+
38
+ usePlayerStore.getState().initPlayer(spawnY)
39
  useGameStore.getState().setLoading(false)
40
  setReady(true)
41
  }, [])
 
46
  const loop = (time: number) => {
47
  const delta = Math.min((time - lastTimeRef.current) / 1000, 0.1)
48
  lastTimeRef.current = time
49
+
50
+ if (keys.current && !useGameStore.getState().paused) {
51
+ updatePlayerMovement(keys.current, delta)
52
+ }
53
+
54
  useWorldStore.getState().updateWorldTime()
55
 
56
+ // Update FPS counter
57
+ if (delta > 0) {
58
+ useGameStore.getState().updateFPS(Math.round(1 / delta))
59
+ }
60
+
61
  // Handle mobile block interaction
62
+ if ((window as any).__MC_MOBILE_BREAK) {
63
  const hit = raycastBlock()
64
  if (hit) useWorldStore.getState().setBlock(hit.blockPos[0], hit.blockPos[1], hit.blockPos[2], BLOCK_AIR)
65
+ ;(window as any).__MC_MOBILE_BREAK = false
66
  }
67
+ if ((window as any).__MC_MOBILE_PLACE) {
68
  const hit = raycastBlock()
69
  const { player } = usePlayerStore.getState()
70
  if (hit && player) {
71
  const slot = useUIStore.getState().hotbarSlot
72
  const item = player.inventory.hotbar[slot]
73
+ if (item && BLOCK_DEFS[item.id]) {
74
  useWorldStore.getState().setBlock(hit.placePos[0], hit.placePos[1], hit.placePos[2], item.id)
75
  }
76
  }
77
+ ;(window as any).__MC_MOBILE_PLACE = false
78
  }
79
 
80
  animId = requestAnimationFrame(loop)
 
83
  return () => cancelAnimationFrame(animId)
84
  }, [keys, raycastBlock])
85
 
86
+ // Loading screen
87
  if (!ready) {
88
  return (
89
  <div className="w-screen h-screen bg-[#2C2C2C] flex flex-col items-center justify-center gap-4">
90
+ <div className="text-4xl sm:text-5xl font-bold text-white tracking-wide"
91
+ style={{ fontFamily: 'monospace', textShadow: '3px 3px #000' }}>
92
+ MINECRAFT
93
  </div>
94
+ <div className="text-sm text-gray-400 mb-2">Browser Edition</div>
95
+ <div className="w-64 h-3 bg-gray-700 rounded-full overflow-hidden">
96
+ <div className="h-full bg-green-500 transition-all duration-300"
97
+ style={{ width: `${Math.min(loadingProgress, 100)}%` }} />
98
  </div>
99
+ <div className="text-gray-500 text-xs">Generating world...</div>
100
  </div>
101
  )
102
  }
103
 
104
+ const isMobileDevice = typeof window !== 'undefined' &&
105
+ (('ontouchstart' in window) || navigator.maxTouchPoints > 0)
106
 
107
  return (
108
  <div className="relative w-screen h-screen overflow-hidden bg-black select-none">
109
  <Canvas
110
+ gl={{
111
+ antialias: false,
112
+ powerPreference: 'high-performance',
113
+ alpha: false,
114
+ depth: true,
115
+ stencil: false,
116
+ }}
117
  dpr={[1, isMobileDevice ? 1 : 1.5]}
118
+ camera={{
119
+ fov: isMobileDevice ? 80 : 70,
120
+ near: 0.05,
121
+ far: 400,
122
+ position: [8, 100, 8],
123
+ }}
124
  style={{ position: 'absolute', inset: 0 }}
125
+ onCreated={({ gl }) => {
126
+ gl.toneMapping = THREE.ACESFilmicToneMapping
127
+ gl.toneMappingExposure = 1.0
128
+ }}
129
  >
130
  <GameScene />
131
  </Canvas>
132
 
133
+ {/* Crosshair */}
134
  {!isMobileDevice && <Crosshair />}
135
 
136
  {/* Hotbar */}
137
+ <div className="absolute bottom-1 left-1/2 -translate-x-1/2 z-10 sm:bottom-3">
138
  <Hotbar />
139
  </div>
140
 
141
  {/* HUD */}
142
  <HUD />
143
 
144
+ {/* Debug overlay */}
145
  <DebugOverlay />
146
 
147
+ {/* Creative inventory */}
148
  <CreativeInventory />
149
 
150
  {/* Mobile controls */}
151
+ {isMobileDevice && <MobileControls />}
152
 
153
+ {/* Click to play overlay (desktop) */}
154
  {!isMobileDevice && !document.pointerLockElement && (
155
  <div
156
  className="absolute inset-0 flex items-center justify-center bg-black/60 cursor-pointer z-50"
157
  onClick={requestPointerLock}
158
  >
159
+ <div className="text-center text-white p-6 sm:p-10 rounded-xl bg-gradient-to-b from-gray-900/80 to-black/80 backdrop-blur-md border border-gray-700/50 max-w-lg">
160
+ <h2 className="text-4xl sm:text-6xl font-black mb-2 tracking-tight"
161
+ style={{ fontFamily: 'monospace', textShadow: '3px 3px #000' }}>
162
+ MINECRAFT
163
  </h2>
164
+ <div className="text-green-400 text-lg mb-6">Browser Edition</div>
165
+ <div className="text-xl mb-6 text-white font-semibold animate-pulse">
166
+ Click to Play
167
+ </div>
168
+ <div className="text-sm opacity-60 space-y-1.5 text-left max-w-sm mx-auto">
169
+ <p>🎮 <b>WASD</b> Move &nbsp;|&nbsp; <b>Space</b> Jump &nbsp;|&nbsp; <b>Shift</b> Sneak</p>
170
+ <p>🖱️ <b>Left Click</b> Break &nbsp;|&nbsp; <b>Right Click</b> Place</p>
171
+ <p>⚡ <b>F</b> Fly &nbsp;|&nbsp; <b>Ctrl</b> Sprint &nbsp;|&nbsp; <b>1-9</b> Hotbar</p>
172
+ <p>🔧 <b>F3</b> Debug &nbsp;|&nbsp; <b>E</b> Inventory &nbsp;|&nbsp; <b>Scroll</b> Switch</p>
173
+ <p>🌍 <b>G</b> Gamemode &nbsp;|&nbsp; <b>T</b> Time &nbsp;|&nbsp; <b>Q</b> Drop</p>
174
  </div>
175
  </div>
176
  </div>
 
178
  </div>
179
  )
180
  }