OhMyGame
OhMyGame은 AI로 게임을 만들 수 있는 오픈소스 스튜디오로, 디자인·구축·플레이테스트·출판을 하나의 앱에서 모델 키와 함께 수행합니다
three.js 기반 4인까지 P2P 연결 FPS, 서버 없이 설치 없이 오픈소스
Play a three.js shooter with up to four players, linked peer-to-peer over NET. No game server, no install. Open source.
게임JavaScript · CSSGitHub @ai-2070온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Open-source browser FPS with peer-to-peer multiplayer co-op over NET and WebRTC. No game server, end-to-end encrypted. three.js, host-authoritative netcode with client-side prediction and lag compensation, a server browser, and an F4 network debug panel.
README 전체를 불러오는 중입니다.
README 전문 보기 ↗An open-source first-person shooter that runs in the browser, with peer-to-peer multiplayer co-op over NET (@net-mesh/browser 0.45.1) and WebRTC. There's no game server: the host's browser runs the match, every player links straight to it, and the traffic is end-to-end encrypted. It's built with three.js and plain JavaScript, and it's meant as a working example to copy from when you add multiplayer to your own web game.
▶ Play it in your browser · NET docs · NET browser quickstart
hostNetcode / joinNetcode).rewind).npm install
npm start
npm start builds the game into dist/ (scripts/build.mjs), rebuilds it on every change, and serves it over HTTPS at https://localhost:8081/ (WebCrypto and WebRTC need a secure context). For co-op, open it in two browsers, or two tabs. In one, go to Play → Co-op → Host game. In the other, open the server browser and join.
Co-op needs a NET anchor, which introduces players' browsers to each other. By default it uses NET's public anchor, https://anchor.ai2070.net, so there's nothing to run for it. The public anchor keeps each site's games apart: players of your page see each other's lobbies, never another site's, even one that also calls its game net-shooter.
One-time setup for npm start: mkcert, for a certificate your browser trusts. winget install FiloSottile.mkcert (or brew install mkcert), then mkcert -install.
To run your own anchor instead, npm start -- --local-anchor starts one on :8444 and prints a page URL with ?anchor= pointing at it. It needs a net-mesh with the anchor feature: download net-mesh-anchor-v0.45.1-<platform> from the NET release (x86_64 Linux and Windows), or build it with cargo install net-cli --features rtc-bootstrap. Put it at .anchor/bin/net-mesh(.exe), or set NET_MESH_BIN. The first run fills .anchor/ with the mesh key, the credential issuer key and the certificate. It's git-ignored; keep it private.
Other options:
npm start -- --host <LAN IP>: play across your LAN. Other machines must trust your mkcert CA.?anchor=https://… on the page URL: use a different anchor than the public one.ANCHOR_STATS=1, ANCHOR_VERBOSE=1|2|3: anchor diagnostics.netDebug() in the devtools console: this player's connection details.Use a regular browser. Embedded editor previews may block WebRTC.
npm run build writes the game to dist/, ready for any static host:
js/main.js, bundled with three.js and NET from node_modules and minified. NET (js/net.js) stays a separate chunk, loaded only when co-op is opened, so solo never loads it.net_leaf.js and net_leaf_bg.wasm), copied next to the chunks. NET imports it at run time from beside its own code, which no bundler can follow.index.html and css/style.css.three.js and NET are pinned npm dependencies (Renovate keeps them current); only the font still comes from a CDN (Google Fonts).
| Action | Input |
|---|---|
| Move | W A S D |
| Look | Mouse |
| Fire | Left click (hold for automatic fire) |
| Aim down sights | Right click |
| Reload | R |
| Sprint | Shift |
| Jump | Space |
| Pause | Esc |
| Network debug panel | F4 |
Solo: a walled arena with crates and cover, plus eight target dummies that patrol, take damage (headshots do double), and respawn three seconds after they go down.
Menus: main menu, mode select (Solo / Co-op), settings (player name, sensitivity, FOV, volume, saved in localStorage), controls, and a pause menu (Resume, Settings, Controls, Quit to main menu). Esc steps back out of sub-menus. Beside the main menu, a card on NET, in NET's own look, with links to its site and its browser quickstart.
Co-op over NET, 2–4 players:
listLobbies, with columns for server, region, map, players and route. Search matches server names; the region dropdown, Hide full and Hide empty filter the list; column headers sort it; Refresh fetches it again. If NET can't be reached, the list says why and the status reads "Not connected to NET".js/sim.js), and the host confirms it. Other players are smoothed between host snapshots.rewind): a shot hits what the shooter saw when they fired.Press F4 at any time, and again to close it. The panel shows NET as it runs, in your own browser, for debugging your connection or your own NET game built from this one. It's the same panel as in Rose & Blade, adapted to this game, where players connect to the host and not to each other.
node.counters(), node.rtcStats()), the ones that aren't zero.How it gets the numbers (js/net.js, under "For the network debug panel"):
openStream and each stream's send are wrapped, and incoming stream_data events are counted per peer. That covers movement and the lobby store alike.stats().clock). The host has no clock of its own, so every player reports its round trip with an input about once a second (twice a second while its panel is open), and the host's panel shows that.peerAttempt, read every half second while the panel is open.net:links entity to its snapshots, with each player's link and round trip, so that player's map shows the whole game. When no panel is open, the host stops sending it, about 3 s later.The panel only reads. Opening it changes nothing in the game, apart from those small reports and the net:links entity.
All of it is in js/net.js, which js/main.js loads only when you open co-op. A NET outage never breaks solo.
| Piece | NET API | What it does here |
|---|---|---|
| Connection | requestCredential, connect | The page asks the anchor for an anonymous credential and becomes a NET node. There's one node per page, with a fresh identity on every load. |
| Staying connected | call (to a service nobody runs) | Before browsing or hosting, and when the tab comes back to the front, the node checks its session with the anchor is still there; if not, it's replaced. |
| Hosting | createLobby | Hosts the game's store: the roster of names, colours and kills. It also publishes the listing the server browser shows. |
| Server browser | listLobbies | Lists open games: name, region, map, players, lock. |
| Joining | joinLobby, then an enlist action | Joins the store. The host checks the password in enlist, so it never leaves the host's page. |
| Movement | hostNetcode / joinNetcode | Host-authoritative tick at 30 Hz. Each client predicts itself and reconciles, and other players are interpolated. |
| Shots | netcode inputs + rewind | Shots ride along with inputs, and the host resolves them against the targets as the shooter saw them. |
| Direct links | peerAttempt, acceptPeer | The host answers joining players, so they connect peer-to-peer instead of staying on the anchor's relay. |
Things worth knowing, found while building it:
liveNode in js/net.js checks the node before it's used for that (at most every 20 s) and when the tab comes back to the front, and makes a new one if the session is gone. Never during a game.connectPeer only returns once the direct attempt has settled, which where none can be had is the whole ICE deadline. The lobby is joined through a node whose connectPeer goes on after 1.5 s, over the relayed session; the direct link, when it comes, takes over underneath. If that fast join fails, it's retried with the full wait. Netcode is handed the node without connectPeer at all, so movement starts on the session the lobby already brought up.connectPeer itself never cancels an attempt under way or re-offers on a direct pair.The game started as a solo shooter whose co-op menus were already shaped around NET: a server browser with Region and Route columns and a Host Game form, but only placeholder data behind them. NET was added in these steps:
Load NET only for co-op. js/main.js loads js/net.js with a dynamic import() the first time co-op is opened, so solo never loads NET. (It first came from the jsDelivr CDN; it's now an npm dependency, bundled into its own chunk.)
Share the movement rules. Player movement and collision moved out of js/game.js into js/sim.js as a pure stepPlayer(state, input) function with no three.js in it, next to the arena layout. Solo, the co-op host and each client's prediction all run the same function, so the host and clients agree on movement (0 prediction corrections in testing).
Write the session layer (js/net.js). hostGame and joinGame return a small session object that the game uses: input(), entities(), roster(), teamKills(), onEnd(), leave(). Everything NET-specific stays behind it: lobby, store, netcode and connection handling.
Add a co-op mode to js/game.js.
resolveShot(), moving targets back to where the shooter saw them (netcode rewind) before raycasting.Connect the menus. The server browser's placeholder generator was replaced with listLobbies. The Join button now joins, with a password box for locked games. The Host Game form now calls createLobby with its settings. Settings gained a player name.
Add a local anchor. scripts/dev.mjs (npm start -- --local-anchor) runs net-mesh anchor serve for the game ID net-shooter, creating the mesh key, issuer key and mkcert certificate on first run.
Test with real browsers. A Playwright script ran the whole flow with separate browser contexts: host with a password, find the game in the browser, wrong and then right password, move, shoot, close the tab, re-host. It found and fixed these issues:
index.html markup for the HUD and every menu screen
css/style.css styling
js/main.js entry point; game state (menu / playing / paused), pointer lock
js/game.js scene, camera, player controls, rifle, targets, co-op rendering
js/sim.js arena layout and player movement (shared by solo, co-op host and prediction)
js/net.js NET: lobbies, host/join, netcode, connection handling (loaded only for co-op)
js/servers.js the server browser
js/ui.js menu screen stack, settings, host form
js/hud.js HUD: ammo, kills, hit marker
js/audio.js synthesized sound effects (Web Audio)
js/toast.js notification toast
js/netdebug.js the network debug panel (F4)
js/netrain.js the NET card's packet rain, beside the main menu
scripts/build.mjs npm run build: the game into dist/
scripts/dev.mjs npm start: build and watch, HTTPS page server, optional local NET anchor
docs/ README screenshots and the social preview image
MIT. Use it, fork it, ship it. three.js and NET keep their own licenses (NET is MIT or Apache-2.0).
How the browser maps to NET:
shot:<id> entities.Some lessons came from an earlier NET test game and were built in from the start:
acceptPeer, so joins go direct instead of waiting about 20 s for the direct attempt to time out.