diff --git a/packages/client/src/3d/Phantom.tsx b/packages/client/src/3d/Phantom.tsx new file mode 100644 index 0000000..5022666 --- /dev/null +++ b/packages/client/src/3d/Phantom.tsx @@ -0,0 +1,40 @@ +// @ts-nocheck +import React, { useEffect, useRef } from 'react' + +import api, { Transform } from '../store' + +type PhantomProps = { + id: string +} + +const Phantom: React.FC = ({ id }) => { + const ref = useRef(null) + + useEffect(() => { + if (ref.current) { + api.subscribe( + (state: Transform | null) => { + if (!state) { + return + } + ref.current?.position.fromArray([ + state.position[0], + state.position[1] + 1.25, + state.position[2], + ]) + ref.current?.rotation.fromArray(state.rotation) + }, + (state) => state?.userTransforms?.[id], + ) + } + }, [ref.current]) + + return ( + + + + + ) +} + +export default Phantom diff --git a/packages/client/src/3d/Player.tsx b/packages/client/src/3d/Player.tsx index b9fb77e..44d53da 100644 --- a/packages/client/src/3d/Player.tsx +++ b/packages/client/src/3d/Player.tsx @@ -4,6 +4,8 @@ import { SphereProps, useSphere } from '@react-three/cannon' import { useThree, useFrame } from 'react-three-fiber' import { Event } from 'three' +import useStore from '../store' + const SPEED = 5 const keys: Record = { KeyW: 'forward', @@ -45,23 +47,49 @@ const usePlayerControls = () => { // TODO Improve physics in player const Player = (props: SphereProps) => { + const socket = useStore((state) => state.socket) + const { forward, backward, left, right, running } = usePlayerControls() + const { camera } = useThree() const [ref, api] = useSphere(() => ({ type: 'Dynamic', fixedRotation: true, - position: [0, 4, 0], + position: [0, 1.5, 0], + args: 0.2, mass: 9, linearDamping: 0, angularDamping: 0, material: { friction: 0, restitution: 0 }, ...props, })) - const { forward, backward, left, right, running } = usePlayerControls() - const { camera } = useThree() + + useEffect(() => { + const socketEmitTransformInterval = setInterval(() => { + if (socket && ref.current && camera) { + socket.emit('transform', { + position: [ + ref.current.position.x, + ref.current.position.y, + ref.current.position.z, + ], + rotation: [camera.rotation.x, camera.rotation.y, camera.rotation.z], + }) + } + }, 16) + + return () => { + clearInterval(socketEmitTransformInterval) + } + }, []) + const velocity = useRef([0, 0, 0]) useEffect(() => void api.velocity.subscribe((v) => (velocity.current = v)), []) useFrame(() => { if (!ref.current) return - camera.position.copy(ref.current.position) + camera.position.set( + ref.current.position.x, + ref.current.position.y + 1, + ref.current.position.z, + ) frontVector.set(0, 0, Number(backward) - Number(forward)) sideVector.set(Number(left) - Number(right), 0, 0) direction diff --git a/packages/client/src/3d/Users.tsx b/packages/client/src/3d/Users.tsx new file mode 100644 index 0000000..25f0525 --- /dev/null +++ b/packages/client/src/3d/Users.tsx @@ -0,0 +1,27 @@ +import React from 'react' +import shallow from 'zustand/shallow' + +import Phantom from './Phantom' +import useStore from '../store' + +const Users: React.FC = () => { + const socket = useStore((state) => state.socket) + const users = useStore( + (state) => (state.userTransforms ? Object.keys(state.userTransforms) : null), + shallow, + ) + + if (users === null || socket === null) { + return null + } + + return ( + <> + {users.map((user) => + user === socket.id ? null : , + )} + + ) +} + +export default Users diff --git a/packages/client/src/3d/index.tsx b/packages/client/src/3d/index.tsx index e1938ca..fa9421d 100644 --- a/packages/client/src/3d/index.tsx +++ b/packages/client/src/3d/index.tsx @@ -7,6 +7,7 @@ import Player from './Player' import Lighting from './Lighting' import Effects from './Effects' import Controls from './Controls' +import Users from './Users' const Scene: React.FC = () => { return ( @@ -18,7 +19,7 @@ const Scene: React.FC = () => { { + diff --git a/packages/client/src/components/MenuOverlay.tsx b/packages/client/src/components/MenuOverlay.tsx index 6e41912..002ad00 100644 --- a/packages/client/src/components/MenuOverlay.tsx +++ b/packages/client/src/components/MenuOverlay.tsx @@ -43,7 +43,7 @@ const MenuOverlay: React.FC = () => { museo.red - Usa las teclas W, A S y D para + Usa las teclas W, A, S y D para moverte. Usá el mouse para mirar al rededor diff --git a/packages/client/src/components/Museo.tsx b/packages/client/src/components/Museo.tsx index 090e8fe..2534e79 100644 --- a/packages/client/src/components/Museo.tsx +++ b/packages/client/src/components/Museo.tsx @@ -8,18 +8,22 @@ import MenuOverlay from './MenuOverlay' import Error from './Error' import Scene from '../3d' -import useStore from '../store' +import useStore, { UserTransforms } from '../store' const Museo: React.FC = () => { const setSocket = useStore((state) => state.setSocket) const setError = useStore((state) => state.setError) const error = useStore((state) => state.error) + const setUserTransforms = useStore((state) => state.setUserTransforms) useEffect(() => { const socket = io() setSocket(socket) socket.on('disconnect', () => setError('SOCKET')) + socket.on('broadcast-transforms', (payload: UserTransforms) => { + setUserTransforms(payload) + }) return () => { socket.close() @@ -34,7 +38,7 @@ const Museo: React.FC = () => { ) : ( <> - + diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts index 6d56be4..68e194d 100644 --- a/packages/client/src/store.ts +++ b/packages/client/src/store.ts @@ -4,15 +4,26 @@ import { Socket } from 'socket.io-client' type Error = null | 'SOCKET' +export type Transform = { + position?: number[] + rotation?: number[] +} + +type userId = string + +export type UserTransforms = Record + type State = { pointerLockControls: PointerLockControls | undefined + setPointerLockControls: (controls: PointerLockControls) => void pointerLocked: boolean setPointerLockStatus: (status: boolean) => void - setPointerLockControls: (controls: PointerLockControls) => void socket: null | Socket setSocket: (socket: Socket | null) => void error: Error setError: (error: Error) => void + userTransforms: UserTransforms | null + setUserTransforms: (userTransforms: UserTransforms) => void } const useStore = create((set) => ({ @@ -24,6 +35,8 @@ const useStore = create((set) => ({ setSocket: (socket) => set(() => ({ socket })), error: null, setError: (error) => set(() => ({ error })), + userTransforms: null, + setUserTransforms: (userTransforms) => set(() => ({ userTransforms })), })) export default useStore diff --git a/packages/server/src/index.js b/packages/server/src/index.js index 261bc3c..93b6c5a 100644 --- a/packages/server/src/index.js +++ b/packages/server/src/index.js @@ -23,7 +23,14 @@ import logger, { morganStream } from './logger' import passport from 'passport' import mongoose from 'mongoose' -import redisClient, { asyncHEXISTS, asyncHGET, asyncHSET, asyncHDEL } from './redis' +import redisClient, { + asyncHEXISTS, + asyncHGET, + asyncHSET, + asyncHDEL, + asyncHGETALL, + asyncDEL, +} from './redis' import { authRouter, initPassport } from './auth/passport' @@ -47,6 +54,9 @@ mongoose.connect( const app = express() app.set('trust proxy', 1) const server = http.createServer(app) + +asyncDEL('socket') +asyncDEL('transform') const io = new SocketIO(server, { serveClient: false }) const pubClient = redisClient @@ -120,15 +130,34 @@ io.on('connection', async (socket) => { await asyncHSET('socket', String(socket.request.user._id), String(socket.id)) logger.debug(`Assigned ${socket.id} to ${socket.request.user._id}`) - socket.on('kick', () => { - console.log('kicking') + socket.on('transform', async (payload) => { + await asyncHSET('transform', String(socket.id), JSON.stringify(payload)) }) + socket.on('disconnect', async () => { logger.debug(`User with id ${socket.id} disconnected`) - await asyncHDEL('socket', String(socket.request.user._id), String(socket.id)) + await asyncHDEL('transform', String(socket.id)) + await asyncHDEL('socket', String(socket.request.user._id)) }) }) +async function broadcastTransforms() { + const transforms = await asyncHGETALL('transform') + + if (transforms) { + let parsed = {} + Object.entries(transforms).forEach(([key, value]) => { + parsed = { ...parsed, [key]: { ...JSON.parse(value) } } + }) + + io.sockets.emit('broadcast-transforms', parsed) + } + + setTimeout(broadcastTransforms, 30) +} + +broadcastTransforms() + if (process.env.NODE_ENV !== 'PRODUCTION') { app.use('/', proxy('http://localhost:4000/')) } else { diff --git a/packages/server/src/redis.js b/packages/server/src/redis.js index 5e2a709..6314b51 100644 --- a/packages/server/src/redis.js +++ b/packages/server/src/redis.js @@ -25,5 +25,7 @@ export const asyncHEXISTS = promisify(client.HEXISTS).bind(client) export const asyncHGET = promisify(client.HGET).bind(client) export const asyncHSET = promisify(client.HSET).bind(client) export const asyncHDEL = promisify(client.HDEL).bind(client) +export const asyncHGETALL = promisify(client.HGETALL).bind(client) +export const asyncDEL = promisify(client.DEL).bind(client) export default client