Compare commits
6 Commits
83544fe3d2
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 32becc12f9 | |||
| ec05fb8ddd | |||
| a4792759e6 | |||
| e2fa2ba8a9 | |||
| aa27725c4e | |||
| cf7d73ba08 |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -23,6 +23,14 @@ frontend/dist/
|
|||||||
build/
|
build/
|
||||||
nouveau_build/
|
nouveau_build/
|
||||||
build_mai/
|
build_mai/
|
||||||
|
last_build/
|
||||||
|
other_last_build/
|
||||||
|
very_last_build/
|
||||||
|
pretty_build/
|
||||||
|
schema_gen/
|
||||||
|
New folder/
|
||||||
|
game/connectwebgl.zip
|
||||||
|
game/webgl_sharing/
|
||||||
|
|
||||||
# Exception: frontend unity-build static assets (committed for deployment)
|
# Exception: frontend unity-build static assets (committed for deployment)
|
||||||
!frontend/public/unity-build/
|
!frontend/public/unity-build/
|
||||||
|
|||||||
159
README.md
Normal file
159
README.md
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
<div align="center">
|
||||||
|
|
||||||
|
<h1>ROLL'D</h1>
|
||||||
|
|
||||||
|
<p><strong>Browser-based marble MMO — multiplayer physics, real-time leaderboards, playable directly in your browser.</strong></p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<img src="https://img.shields.io/badge/Unity-6000.0-black?style=for-the-badge&logo=unity&logoColor=white" alt="Unity 6" />
|
||||||
|
<img src="https://img.shields.io/badge/WebGL-build-E34F26?style=for-the-badge&logo=webgl&logoColor=white" alt="WebGL" />
|
||||||
|
<img src="https://img.shields.io/badge/Colyseus-0.17-6C47FF?style=for-the-badge&logo=node.js&logoColor=white" alt="Colyseus" />
|
||||||
|
<img src="https://img.shields.io/badge/React-19-61DAFB?style=for-the-badge&logo=react&logoColor=black" alt="React" />
|
||||||
|
<img src="https://img.shields.io/badge/Vite-5-646CFF?style=for-the-badge&logo=vite&logoColor=white" alt="Vite" />
|
||||||
|
<img src="https://img.shields.io/badge/Tailwind_CSS-3-38BDF8?style=for-the-badge&logo=tailwindcss&logoColor=white" alt="Tailwind" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="https://rolld.kerboul.me"><img src="https://img.shields.io/badge/Play_Now-rolld.kerboul.me-22c55e?style=for-the-badge&logo=googlechrome&logoColor=white" alt="Play Now" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What is ROLL'D?
|
||||||
|
|
||||||
|
ROLL'D is a multiplayer marble game that runs entirely in the browser via Unity WebGL. Players control a physics-based ball in a shared 3D arena, competing for distance, speed, and style. The game features real-time synchronisation at 60 Hz, in-game chat, and persistent leaderboards.
|
||||||
|
|
||||||
|
No install. No account. Just open the page and roll.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Real-time multiplayer** - up to 20 players per room, 60 Hz state sync via Colyseus WebSockets
|
||||||
|
- **Physics-based gameplay** - Unity Rigidbody, jump charge, gel pads (speed boosts), ball-to-ball bumps
|
||||||
|
- **Room lobby** - browse open rooms, create your own, choose your colour and name
|
||||||
|
- **In-game chat** - accessible in-game (T key) and on the dedicated website chat page
|
||||||
|
- **Live leaderboards** - distance, max speed, jumps, bumps, playtime, updated every 30 seconds
|
||||||
|
- **Spectator camera** - orbiting camera while in lobby or after disconnecting
|
||||||
|
- **WebGL-native** - no plugins, no downloads, runs in Chrome/Firefox/Edge
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
rolld/
|
||||||
|
├── game/ # Unity 6 project (WebGL build)
|
||||||
|
│ └── Assets/Scripts/
|
||||||
|
│ ├── Network/ # Colyseus SDK integration, schema, lobby UI
|
||||||
|
│ ├── Stats/ # StatsTracker - periodic HTTP upload
|
||||||
|
│ └── UI/ # IMGUI in-game HUD, chat, keybinds
|
||||||
|
│
|
||||||
|
├── rolld_backend/game/ # Colyseus 0.17 game server (Node.js)
|
||||||
|
│ └── src/
|
||||||
|
│ ├── rooms/ # ArenaRoom - game state machine
|
||||||
|
│ ├── schema/ # Colyseus schema (Player + GameState)
|
||||||
|
│ ├── stats/ # StatsManager - JSON persistence
|
||||||
|
│ └── chat/ # ChatManager - in-memory history
|
||||||
|
│
|
||||||
|
└── frontend/ # React + Vite + Tailwind SPA
|
||||||
|
└── src/
|
||||||
|
├── pages/ # Home, Stats leaderboard, Chat
|
||||||
|
└── components/ # NavBar, GameCanvas (Unity embed)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Network flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Browser
|
||||||
|
└── Unity WebGL (GameCanvas iframe)
|
||||||
|
└── Colyseus SDK (WebSocket wss://)
|
||||||
|
└── ArenaRoom (Node.js)
|
||||||
|
└── Broadcast state @60 Hz
|
||||||
|
|
||||||
|
Browser
|
||||||
|
└── React SPA
|
||||||
|
└── REST API (HTTPS)
|
||||||
|
├── GET /stats/leaderboard/:key
|
||||||
|
├── GET /chat/history?since=
|
||||||
|
└── POST /stats/update (from Unity every 30s)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tech stack
|
||||||
|
|
||||||
|
| Layer | Technology |
|
||||||
|
|---|---|
|
||||||
|
| Game engine | Unity 6 LTS, C# |
|
||||||
|
| Multiplayer | Colyseus 0.17 (Node.js + WebSocket) |
|
||||||
|
| Frontend | React 19, Vite 5, Tailwind CSS 3 |
|
||||||
|
| Deployment | Docker, Coolify, nginx |
|
||||||
|
| Self-hosted | Proxmox LXC, Gitea, Traefik reverse proxy |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Running locally
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Node.js 20+
|
||||||
|
- Unity 6000.x (for game builds only)
|
||||||
|
|
||||||
|
### Game server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd rolld_backend/game
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Server starts on `ws://localhost:2567`.
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open `http://localhost:5173`. The frontend points to the production game server by default - edit `src/pages/StatsPage.jsx` and `src/components/GameCanvas.jsx` to switch to localhost.
|
||||||
|
|
||||||
|
### Unity (optional)
|
||||||
|
|
||||||
|
Open `game/` in Unity 6. The server URL is hardcoded in `Assets/Scripts/Network/NetworkManager.cs`. Switch to `wss://game.rolld.kerboul.me` for prod or `ws://localhost:2567` for local testing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Controls
|
||||||
|
|
||||||
|
| Key | Action |
|
||||||
|
|---|---|
|
||||||
|
| WASD / Arrow keys | Move |
|
||||||
|
| Space (hold) | Charge jump |
|
||||||
|
| Space (release) | Jump |
|
||||||
|
| T | Open chat |
|
||||||
|
| Escape | Close chat |
|
||||||
|
| Tab | Show keybindings |
|
||||||
|
| Backtick (`) | Debug network info |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Live deployment
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<img src="https://img.shields.io/badge/Frontend-rolld.kerboul.me-22c55e?style=flat-square&logo=nginx&logoColor=white" alt="Frontend" />
|
||||||
|
<img src="https://img.shields.io/badge/Game_server-game.rolld.kerboul.me-6C47FF?style=flat-square&logo=node.js&logoColor=white" alt="Game server" />
|
||||||
|
<img src="https://img.shields.io/badge/Self_hosted-Proxmox_homelab-E57000?style=flat-square&logo=proxmox&logoColor=white" alt="Self-hosted" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
The stack runs on a self-hosted Proxmox homelab cluster. Coolify handles container orchestration and auto-deployment on git push. Traefik manages HTTPS termination.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
MIT - do whatever you want with it.
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
# Unity WebGL build goes here
|
|
||||||
Place your Unity WebGL build files in a `Build/` subfolder.
|
|
||||||
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
frontend/public/unity-build/Build/pretty_build.data
Normal file
BIN
frontend/public/unity-build/Build/pretty_build.data
Normal file
Binary file not shown.
57
frontend/public/unity-build/Build/pretty_build.framework.js
Normal file
57
frontend/public/unity-build/Build/pretty_build.framework.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -52,12 +52,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var buildUrl = "Build";
|
var buildUrl = "Build";
|
||||||
var loaderUrl = buildUrl + "/nouveau_build.loader.js";
|
var loaderUrl = buildUrl + "/pretty_build.loader.js";
|
||||||
var config = {
|
var config = {
|
||||||
arguments: [],
|
arguments: [],
|
||||||
dataUrl: buildUrl + "/nouveau_build.data",
|
dataUrl: buildUrl + "/pretty_build.data",
|
||||||
frameworkUrl: buildUrl + "/nouveau_build.framework.js",
|
frameworkUrl: buildUrl + "/pretty_build.framework.js",
|
||||||
codeUrl: buildUrl + "/nouveau_build.wasm",
|
codeUrl: buildUrl + "/pretty_build.wasm",
|
||||||
streamingAssetsUrl: "StreamingAssets",
|
streamingAssetsUrl: "StreamingAssets",
|
||||||
companyName: "DefaultCompany",
|
companyName: "DefaultCompany",
|
||||||
productName: "BallProject",
|
productName: "BallProject",
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { useState, useEffect, useCallback } from 'react'
|
|||||||
// Check if Unity build files exist
|
// Check if Unity build files exist
|
||||||
const UNITY_BUILD_PATH = '/unity-build/Build'
|
const UNITY_BUILD_PATH = '/unity-build/Build'
|
||||||
// Cache-busting version — update this after each Unity build
|
// Cache-busting version — update this after each Unity build
|
||||||
const UNITY_BUILD_VERSION = '20260517b'
|
const UNITY_BUILD_VERSION = '20260520c'
|
||||||
const BUILD_PREFIX = 'build_mai'
|
const BUILD_PREFIX = 'pretty_build'
|
||||||
const LOADER_URL = `${UNITY_BUILD_PATH}/${BUILD_PREFIX}.loader.js?v=${UNITY_BUILD_VERSION}`
|
const LOADER_URL = `${UNITY_BUILD_PATH}/${BUILD_PREFIX}.loader.js?v=${UNITY_BUILD_VERSION}`
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,16 +4,17 @@ import { theme } from '../env'
|
|||||||
const SERVER = 'https://game.rolld.kerboul.me'
|
const SERVER = 'https://game.rolld.kerboul.me'
|
||||||
|
|
||||||
const TABS = [
|
const TABS = [
|
||||||
{ key: 'totalDistance', label: 'Distance', unit: 'm', format: v => Math.round(v).toLocaleString('fr-FR') },
|
{ key: 'totalDistance', label: 'Distance', unit: 'm', format: v => Math.round(v ?? 0).toLocaleString('fr-FR') },
|
||||||
{ key: 'maxSpeed', label: 'Vitesse max', unit: 'm/s', format: v => v.toFixed(1) },
|
{ key: 'maxSpeed', label: 'Vitesse max', unit: 'm/s', format: v => (v ?? 0).toFixed(1) },
|
||||||
{ key: 'totalJumps', label: 'Sauts', unit: '', format: v => v.toLocaleString('fr-FR') },
|
{ key: 'totalJumps', label: 'Sauts', unit: '', format: v => (v ?? 0).toLocaleString('fr-FR') },
|
||||||
{ key: 'bestRaceTime', label: 'Meilleur temps', unit: '', format: v => {
|
{ key: 'bumpsGiven', label: 'Bumps', unit: '', format: v => (v ?? 0).toLocaleString('fr-FR') },
|
||||||
const m = Math.floor(v / 60)
|
{ key: 'totalPlaytime',label: 'Temps de jeu',unit: '', format: v => {
|
||||||
const s = (v % 60).toFixed(2).padStart(5, '0')
|
const total = Math.round(v ?? 0)
|
||||||
return `${m}:${s}`
|
const h = Math.floor(total / 3600)
|
||||||
|
const m = Math.floor((total % 3600) / 60)
|
||||||
|
const s = total % 60
|
||||||
|
return h > 0 ? `${h}h ${m}m` : `${m}m ${s}s`
|
||||||
}},
|
}},
|
||||||
{ key: 'racesPlayed', label: 'Courses', unit: '', format: v => v.toLocaleString('fr-FR') },
|
|
||||||
{ key: 'bumpsGiven', label: 'Bumps', unit: '', format: v => v.toLocaleString('fr-FR') },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
export default function StatsPage() {
|
export default function StatsPage() {
|
||||||
@@ -38,6 +39,7 @@ export default function StatsPage() {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setRows([])
|
||||||
fetchLeaderboard(activeTab)
|
fetchLeaderboard(activeTab)
|
||||||
const id = setInterval(() => fetchLeaderboard(activeTab), 30_000)
|
const id = setInterval(() => fetchLeaderboard(activeTab), 30_000)
|
||||||
return () => clearInterval(id)
|
return () => clearInterval(id)
|
||||||
@@ -124,7 +126,7 @@ export default function StatsPage() {
|
|||||||
{row.name}
|
{row.name}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 text-right font-mono text-sm" style={{ color: theme.accentLight }}>
|
<td className="px-6 py-4 text-right font-mono text-sm" style={{ color: theme.accentLight }}>
|
||||||
{currentTab.format(row[activeTab])}
|
{currentTab.format(row.value)}
|
||||||
{currentTab.unit && <span className="text-rolld-muted ml-1">{currentTab.unit}</span>}
|
{currentTab.unit && <span className="text-rolld-muted ml-1">{currentTab.unit}</span>}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
%YAML 1.1
|
|
||||||
%TAG !u! tag:unity3d.com,2011:
|
|
||||||
--- !u!134 &13400000
|
|
||||||
PhysicsMaterial:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_Name: Bouncy
|
|
||||||
serializedVersion: 2
|
|
||||||
m_DynamicFriction: 0.6
|
|
||||||
m_StaticFriction: 0.6
|
|
||||||
m_Bounciness: 0.74
|
|
||||||
m_FrictionCombine: 0
|
|
||||||
m_BounceCombine: 0
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 38ed95051af515848a7513429d4f0413
|
|
||||||
NativeFormatImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
mainObjectFileID: 13400000
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
%YAML 1.1
|
|
||||||
%TAG !u! tag:unity3d.com,2011:
|
|
||||||
--- !u!134 &13400000
|
|
||||||
PhysicsMaterial:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_Name: GelBleu
|
|
||||||
serializedVersion: 2
|
|
||||||
m_DynamicFriction: 0
|
|
||||||
m_StaticFriction: 0
|
|
||||||
m_Bounciness: 1
|
|
||||||
m_FrictionCombine: 1
|
|
||||||
m_BounceCombine: 3
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 458e6466a22c1204cb2e77d378867d7b
|
|
||||||
NativeFormatImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
mainObjectFileID: 13400000
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
%YAML 1.1
|
|
||||||
%TAG !u! tag:unity3d.com,2011:
|
|
||||||
--- !u!134 &13400000
|
|
||||||
PhysicsMaterial:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_Name: GelOrange
|
|
||||||
serializedVersion: 2
|
|
||||||
m_DynamicFriction: 0.6
|
|
||||||
m_StaticFriction: 0.6
|
|
||||||
m_Bounciness: 0.74
|
|
||||||
m_FrictionCombine: 0
|
|
||||||
m_BounceCombine: 0
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 61512ca9473715648874e2d1f555c50f
|
|
||||||
NativeFormatImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
mainObjectFileID: 13400000
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
%YAML 1.1
|
|
||||||
%TAG !u! tag:unity3d.com,2011:
|
|
||||||
--- !u!134 &13400000
|
|
||||||
PhysicsMaterial:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_Name: GelViolet
|
|
||||||
serializedVersion: 2
|
|
||||||
m_DynamicFriction: 1
|
|
||||||
m_StaticFriction: 1
|
|
||||||
m_Bounciness: 0
|
|
||||||
m_FrictionCombine: 3
|
|
||||||
m_BounceCombine: 0
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 86c56232f118b4c4caa7fc9d124fc344
|
|
||||||
NativeFormatImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
mainObjectFileID: 0
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -8,8 +8,8 @@ Material:
|
|||||||
m_PrefabInstance: {fileID: 0}
|
m_PrefabInstance: {fileID: 0}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_Name: CheckpointMat
|
m_Name: CheckpointMat
|
||||||
m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
|
m_Shader: {fileID: -6465566751694194690, guid: c52a5eb90c085474582a223ce9475866, type: 3}
|
||||||
m_Parent: {fileID: 0}
|
m_Parent: {fileID: -876546973899608171, guid: c52a5eb90c085474582a223ce9475866, type: 3}
|
||||||
m_ModifiedSerializedProperties: 0
|
m_ModifiedSerializedProperties: 0
|
||||||
m_ValidKeywords: []
|
m_ValidKeywords: []
|
||||||
m_InvalidKeywords: []
|
m_InvalidKeywords: []
|
||||||
@@ -22,63 +22,22 @@ Material:
|
|||||||
m_LockedProperties:
|
m_LockedProperties:
|
||||||
m_SavedProperties:
|
m_SavedProperties:
|
||||||
serializedVersion: 3
|
serializedVersion: 3
|
||||||
m_TexEnvs:
|
m_TexEnvs: []
|
||||||
- _BumpMap:
|
|
||||||
m_Texture: {fileID: 0}
|
|
||||||
m_Scale: {x: 1, y: 1}
|
|
||||||
m_Offset: {x: 0, y: 0}
|
|
||||||
- _DetailAlbedoMap:
|
|
||||||
m_Texture: {fileID: 0}
|
|
||||||
m_Scale: {x: 1, y: 1}
|
|
||||||
m_Offset: {x: 0, y: 0}
|
|
||||||
- _DetailMask:
|
|
||||||
m_Texture: {fileID: 0}
|
|
||||||
m_Scale: {x: 1, y: 1}
|
|
||||||
m_Offset: {x: 0, y: 0}
|
|
||||||
- _DetailNormalMap:
|
|
||||||
m_Texture: {fileID: 0}
|
|
||||||
m_Scale: {x: 1, y: 1}
|
|
||||||
m_Offset: {x: 0, y: 0}
|
|
||||||
- _EmissionMap:
|
|
||||||
m_Texture: {fileID: 0}
|
|
||||||
m_Scale: {x: 1, y: 1}
|
|
||||||
m_Offset: {x: 0, y: 0}
|
|
||||||
- _MainTex:
|
|
||||||
m_Texture: {fileID: 0}
|
|
||||||
m_Scale: {x: 1, y: 1}
|
|
||||||
m_Offset: {x: 0, y: 0}
|
|
||||||
- _MetallicGlossMap:
|
|
||||||
m_Texture: {fileID: 0}
|
|
||||||
m_Scale: {x: 1, y: 1}
|
|
||||||
m_Offset: {x: 0, y: 0}
|
|
||||||
- _OcclusionMap:
|
|
||||||
m_Texture: {fileID: 0}
|
|
||||||
m_Scale: {x: 1, y: 1}
|
|
||||||
m_Offset: {x: 0, y: 0}
|
|
||||||
- _ParallaxMap:
|
|
||||||
m_Texture: {fileID: 0}
|
|
||||||
m_Scale: {x: 1, y: 1}
|
|
||||||
m_Offset: {x: 0, y: 0}
|
|
||||||
m_Ints: []
|
m_Ints: []
|
||||||
m_Floats:
|
m_Floats: []
|
||||||
- _BumpScale: 1
|
m_Colors: []
|
||||||
- _Cutoff: 0.5
|
|
||||||
- _DetailNormalMapScale: 1
|
|
||||||
- _DstBlend: 0
|
|
||||||
- _GlossMapScale: 1
|
|
||||||
- _Glossiness: 0.5
|
|
||||||
- _GlossyReflections: 1
|
|
||||||
- _Metallic: 0
|
|
||||||
- _Mode: 0
|
|
||||||
- _OcclusionStrength: 1
|
|
||||||
- _Parallax: 0.02
|
|
||||||
- _SmoothnessTextureChannel: 0
|
|
||||||
- _SpecularHighlights: 1
|
|
||||||
- _SrcBlend: 1
|
|
||||||
- _UVSec: 0
|
|
||||||
- _ZWrite: 1
|
|
||||||
m_Colors:
|
|
||||||
- _Color: {r: 0.1, g: 0.9, b: 0.3, a: 1}
|
|
||||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
|
||||||
m_BuildTextureStacks: []
|
m_BuildTextureStacks: []
|
||||||
m_AllowLocking: 1
|
m_AllowLocking: 1
|
||||||
|
--- !u!114 &8924139153543123182
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 11
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 639247ca83abc874e893eb93af2b5e44, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Unity.ShaderGraph.Editor::UnityEditor.Rendering.BuiltIn.AssetVersion
|
||||||
|
version: 0
|
||||||
|
|||||||
Binary file not shown.
@@ -1,15 +0,0 @@
|
|||||||
%YAML 1.1
|
|
||||||
%TAG !u! tag:unity3d.com,2011:
|
|
||||||
--- !u!134 &13400000
|
|
||||||
PhysicsMaterial:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_Name: Normal
|
|
||||||
serializedVersion: 2
|
|
||||||
m_DynamicFriction: 0.6
|
|
||||||
m_StaticFriction: 0.6
|
|
||||||
m_Bounciness: 0
|
|
||||||
m_FrictionCombine: 0
|
|
||||||
m_BounceCombine: 0
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 36e82e5cf5450404999af634c1d3cbbd
|
|
||||||
NativeFormatImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
mainObjectFileID: 13400000
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -180,20 +180,18 @@ public class PlayerController : MonoBehaviour
|
|||||||
// Update is called once per frame
|
// Update is called once per frame
|
||||||
void Update()
|
void Update()
|
||||||
{
|
{
|
||||||
// Toggle cursor lock/unlock avec clic droit (disabled when keybind menu is open)
|
// Cursor lock: right-click unlocks, left-click re-locks (disabled when any UI panel is open)
|
||||||
if (!KeyBindingUI.IsVisible && Mouse.current != null && Mouse.current.rightButton.wasPressedThisFrame)
|
if (!ChatUI.IsVisible && !KeyBindingUI.IsVisible && Mouse.current != null)
|
||||||
{
|
{
|
||||||
if (Cursor.lockState == CursorLockMode.Locked)
|
if (Cursor.lockState == CursorLockMode.Locked && Mouse.current.rightButton.wasPressedThisFrame)
|
||||||
{
|
{
|
||||||
Cursor.lockState = CursorLockMode.None;
|
Cursor.lockState = CursorLockMode.None;
|
||||||
Cursor.visible = true;
|
Cursor.visible = true;
|
||||||
Debug.Log("Cursor UNLOCKED");
|
|
||||||
}
|
}
|
||||||
else
|
else if (Cursor.lockState != CursorLockMode.Locked && Mouse.current.leftButton.wasPressedThisFrame)
|
||||||
{
|
{
|
||||||
Cursor.lockState = CursorLockMode.Locked;
|
Cursor.lockState = CursorLockMode.Locked;
|
||||||
Cursor.visible = false;
|
Cursor.visible = false;
|
||||||
Debug.Log("Cursor LOCKED");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,33 +372,19 @@ public class PlayerController : MonoBehaviour
|
|||||||
|
|
||||||
public void OnJump(InputAction.CallbackContext context)
|
public void OnJump(InputAction.CallbackContext context)
|
||||||
{
|
{
|
||||||
|
if (ChatUI.IsVisible) { isJumpPressed = false; jumpPressTime = 0f; return; }
|
||||||
|
|
||||||
if (context.started)
|
if (context.started)
|
||||||
{
|
{
|
||||||
isJumpPressed = true;
|
isJumpPressed = true;
|
||||||
jumpPressTime = 0f;
|
jumpPressTime = 0f;
|
||||||
StatsTracker.Instance?.RegisterJump();
|
StatsTracker.Instance?.RegisterJump();
|
||||||
Debug.Log("Jump Started");
|
|
||||||
}
|
|
||||||
else if (context.performed)
|
|
||||||
{
|
|
||||||
// Action validée (utile pour saut immédiat aussi)
|
|
||||||
Debug.Log("Jump Performed");
|
|
||||||
}
|
}
|
||||||
else if (context.canceled)
|
else if (context.canceled)
|
||||||
{
|
{
|
||||||
// Touche relâchée
|
|
||||||
float jumpForceFactor = Mathf.Clamp01(jumpPressTime / maxJumpHoldTime);
|
float jumpForceFactor = Mathf.Clamp01(jumpPressTime / maxJumpHoldTime);
|
||||||
if (IsGrounded())
|
if (IsGrounded())
|
||||||
{
|
|
||||||
PerformJump(jumpForceFactor * JumpForce);
|
PerformJump(jumpForceFactor * JumpForce);
|
||||||
Debug.Log($"Jump Released after {jumpPressTime}s -> Force factor: {jumpForceFactor}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.Log("Jump Released but not grounded.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset jump state so gauge goes back to 0
|
|
||||||
isJumpPressed = false;
|
isJumpPressed = false;
|
||||||
jumpPressTime = 0f;
|
jumpPressTime = 0f;
|
||||||
}
|
}
|
||||||
@@ -424,75 +408,30 @@ public class PlayerController : MonoBehaviour
|
|||||||
|
|
||||||
public void OnForward(InputAction.CallbackContext context)
|
public void OnForward(InputAction.CallbackContext context)
|
||||||
{
|
{
|
||||||
if (context.started)
|
if (ChatUI.IsVisible) { isForwardHeld = false; return; }
|
||||||
{
|
if (context.started) isForwardHeld = true;
|
||||||
isForwardHeld = true;
|
else if (context.canceled) isForwardHeld = false;
|
||||||
Debug.Log("Forward Action Started");
|
|
||||||
}
|
|
||||||
else if (context.performed)
|
|
||||||
{
|
|
||||||
// Forward action performed
|
|
||||||
Debug.Log("Forward Action Performed");
|
|
||||||
}
|
|
||||||
else if (context.canceled)
|
|
||||||
{
|
|
||||||
isForwardHeld = false;
|
|
||||||
Debug.Log("Forward Action Canceled");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnBackwards(InputAction.CallbackContext context)
|
public void OnBackwards(InputAction.CallbackContext context)
|
||||||
{
|
{
|
||||||
if (context.started)
|
if (ChatUI.IsVisible) { isBackwardsHeld = false; return; }
|
||||||
{
|
if (context.started) isBackwardsHeld = true;
|
||||||
isBackwardsHeld = true;
|
else if (context.canceled) isBackwardsHeld = false;
|
||||||
Debug.Log("Backwards Action Started");
|
|
||||||
}
|
|
||||||
else if (context.performed)
|
|
||||||
{
|
|
||||||
Debug.Log("Backwards Action Performed");
|
|
||||||
}
|
|
||||||
else if (context.canceled)
|
|
||||||
{
|
|
||||||
isBackwardsHeld = false;
|
|
||||||
Debug.Log("Backwards Action Canceled");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnLeft(InputAction.CallbackContext context)
|
public void OnLeft(InputAction.CallbackContext context)
|
||||||
{
|
{
|
||||||
if (context.started)
|
if (ChatUI.IsVisible) { isLeftHeld = false; return; }
|
||||||
{
|
if (context.started) isLeftHeld = true;
|
||||||
isLeftHeld = true;
|
else if (context.canceled) isLeftHeld = false;
|
||||||
Debug.Log("Left Action Started");
|
|
||||||
}
|
|
||||||
else if (context.performed)
|
|
||||||
{
|
|
||||||
Debug.Log("Left Action Performed");
|
|
||||||
}
|
|
||||||
else if (context.canceled)
|
|
||||||
{
|
|
||||||
isLeftHeld = false;
|
|
||||||
Debug.Log("Left Action Canceled");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnRight(InputAction.CallbackContext context)
|
public void OnRight(InputAction.CallbackContext context)
|
||||||
{
|
{
|
||||||
if (context.started)
|
if (ChatUI.IsVisible) { isRightHeld = false; return; }
|
||||||
{
|
if (context.started) isRightHeld = true;
|
||||||
isRightHeld = true;
|
else if (context.canceled) isRightHeld = false;
|
||||||
Debug.Log("Right Action Started");
|
|
||||||
}
|
|
||||||
else if (context.performed)
|
|
||||||
{
|
|
||||||
Debug.Log("Right Action Performed");
|
|
||||||
}
|
|
||||||
else if (context.canceled)
|
|
||||||
{
|
|
||||||
isRightHeld = false;
|
|
||||||
Debug.Log("Right Action Canceled");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Bump collision with remote players ---
|
// --- Bump collision with remote players ---
|
||||||
@@ -510,6 +449,9 @@ public class PlayerController : MonoBehaviour
|
|||||||
{
|
{
|
||||||
var remote = other.GetComponent<RemotePlayerController>();
|
var remote = other.GetComponent<RemotePlayerController>();
|
||||||
if (remote == null) return;
|
if (remote == null) return;
|
||||||
|
// Ignore bumps during the remote's post-spawn grace window — otherwise
|
||||||
|
// spawn-time overlap with the kinematic remote ball ejects us upward.
|
||||||
|
if (!remote.BumpReady) return;
|
||||||
|
|
||||||
int id = other.gameObject.GetInstanceID();
|
int id = other.gameObject.GetInstanceID();
|
||||||
if (_lastBumpTime.TryGetValue(id, out float lastTime) && Time.time - lastTime < bumpCooldown)
|
if (_lastBumpTime.TryGetValue(id, out float lastTime) && Time.time - lastTime < bumpCooldown)
|
||||||
@@ -564,6 +506,16 @@ public class PlayerController : MonoBehaviour
|
|||||||
_isSquashing = false;
|
_isSquashing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ResetInputs()
|
||||||
|
{
|
||||||
|
isForwardHeld = false;
|
||||||
|
isBackwardsHeld = false;
|
||||||
|
isLeftHeld = false;
|
||||||
|
isRightHeld = false;
|
||||||
|
isJumpPressed = false;
|
||||||
|
jumpPressTime = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
void OnDestroy()
|
void OnDestroy()
|
||||||
{
|
{
|
||||||
// Clean up name label (it's not parented to the ball)
|
// Clean up name label (it's not parented to the ball)
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
%YAML 1.1
|
|
||||||
%TAG !u! tag:unity3d.com,2011:
|
|
||||||
--- !u!114 &-670956545734759409
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 3
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 0}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 0
|
|
||||||
m_Script: {fileID: 11500000, guid: b94fcd11afffcb142908bfcb1e261fba, type: 3}
|
|
||||||
m_Name: MotionBlur
|
|
||||||
m_EditorClassIdentifier: Unity.Postprocessing.Runtime::UnityEngine.Rendering.PostProcessing.MotionBlur
|
|
||||||
active: 1
|
|
||||||
enabled:
|
|
||||||
overrideState: 1
|
|
||||||
value: 1
|
|
||||||
shutterAngle:
|
|
||||||
overrideState: 0
|
|
||||||
value: 270
|
|
||||||
sampleCount:
|
|
||||||
overrideState: 0
|
|
||||||
value: 10
|
|
||||||
--- !u!114 &11400000
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 0}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 0
|
|
||||||
m_Script: {fileID: 11500000, guid: 8e6292b2c06870d4495f009f912b9600, type: 3}
|
|
||||||
m_Name: PostProcessing Profile
|
|
||||||
m_EditorClassIdentifier: Unity.Postprocessing.Runtime::UnityEngine.Rendering.PostProcessing.PostProcessProfile
|
|
||||||
settings:
|
|
||||||
- {fileID: -670956545734759409}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: fc446179a9ae97a4a8ad5c8aa1c2dd47
|
|
||||||
NativeFormatImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
mainObjectFileID: 11400000
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -89,7 +89,7 @@ MeshRenderer:
|
|||||||
m_RenderingLayerMask: 1
|
m_RenderingLayerMask: 1
|
||||||
m_RendererPriority: 0
|
m_RendererPriority: 0
|
||||||
m_Materials:
|
m_Materials:
|
||||||
- {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0}
|
- {fileID: -876546973899608171, guid: 9535ecd79e34e1341bfe13a806935455, type: 3}
|
||||||
m_StaticBatchInfo:
|
m_StaticBatchInfo:
|
||||||
firstSubMesh: 0
|
firstSubMesh: 0
|
||||||
subMeshCount: 0
|
subMeshCount: 0
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
%YAML 1.1
|
|
||||||
%TAG !u! tag:unity3d.com,2011:
|
|
||||||
--- !u!114 &11400000
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 0}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 0
|
|
||||||
m_Script: {fileID: 11500000, guid: fcf7219bab7fe46a1ad266029b2fee19, type: 3}
|
|
||||||
m_Name: Readme
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
icon: {fileID: 2800000, guid: 727a75301c3d24613a3ebcec4a24c2c8, type: 3}
|
|
||||||
title: URP Empty Template
|
|
||||||
sections:
|
|
||||||
- heading: Welcome to the Universal Render Pipeline
|
|
||||||
text: This template includes the settings and assets you need to start creating with the Universal Render Pipeline.
|
|
||||||
linkText:
|
|
||||||
url:
|
|
||||||
- heading: URP Documentation
|
|
||||||
text:
|
|
||||||
linkText: Read more about URP
|
|
||||||
url: https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@latest
|
|
||||||
- heading: Forums
|
|
||||||
text:
|
|
||||||
linkText: Get answers and support
|
|
||||||
url: https://forum.unity.com/forums/universal-render-pipeline.383/
|
|
||||||
- heading: Report bugs
|
|
||||||
text:
|
|
||||||
linkText: Submit a report
|
|
||||||
url: https://unity3d.com/unity/qa/bug-reporting
|
|
||||||
loadedLayout: 1
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 8105016687592461f977c054a80ce2f2
|
|
||||||
NativeFormatImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
mainObjectFileID: 0
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -10,6 +10,6 @@ PhysicsMaterial:
|
|||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
m_DynamicFriction: 0.6
|
m_DynamicFriction: 0.6
|
||||||
m_StaticFriction: 0.6
|
m_StaticFriction: 0.6
|
||||||
m_Bounciness: 0
|
m_Bounciness: 0.2
|
||||||
m_FrictionCombine: 0
|
m_FrictionCombine: 0
|
||||||
m_BounceCombine: 0
|
m_BounceCombine: 0
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -29,7 +29,9 @@ public class CameraOrbitKeyboard : MonoBehaviour
|
|||||||
{
|
{
|
||||||
// On gère la souris nous-mêmes
|
// On gère la souris nous-mêmes
|
||||||
if (_axisController != null) _axisController.enabled = false;
|
if (_axisController != null) _axisController.enabled = false;
|
||||||
LockCursor();
|
// Only lock cursor if no UI panel is open
|
||||||
|
if (!ChatUI.IsVisible && !KeyBindingUI.IsVisible)
|
||||||
|
LockCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnDisable()
|
void OnDisable()
|
||||||
@@ -55,16 +57,16 @@ public class CameraOrbitKeyboard : MonoBehaviour
|
|||||||
|
|
||||||
var mouse = Mouse.current;
|
var mouse = Mouse.current;
|
||||||
|
|
||||||
// Clic droit = toggle lock
|
// Right-click unlocks, left-click re-locks (consistent with PlayerController)
|
||||||
if (mouse != null && mouse.rightButton.wasPressedThisFrame)
|
if (!ChatUI.IsVisible && !KeyBindingUI.IsVisible && mouse != null)
|
||||||
{
|
{
|
||||||
if (Cursor.lockState == CursorLockMode.Locked)
|
if (Cursor.lockState == CursorLockMode.Locked && mouse.rightButton.wasPressedThisFrame)
|
||||||
UnlockCursor();
|
UnlockCursor();
|
||||||
else
|
else if (Cursor.lockState != CursorLockMode.Locked && mouse.leftButton.wasPressedThisFrame)
|
||||||
LockCursor();
|
LockCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (KeyBindingUI.IsVisible) return;
|
if (KeyBindingUI.IsVisible || ChatUI.IsVisible) return;
|
||||||
|
|
||||||
// Souris — seulement quand locked (delta infini, sans accrochage au bord)
|
// Souris — seulement quand locked (delta infini, sans accrochage au bord)
|
||||||
if (Cursor.lockState == CursorLockMode.Locked && mouse != null)
|
if (Cursor.lockState == CursorLockMode.Locked && mouse != null)
|
||||||
|
|||||||
43
game/Assets/Scripts/Network/Generated/GameState.cs
Normal file
43
game/Assets/Scripts/Network/Generated/GameState.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// THIS FILE HAS BEEN GENERATED AUTOMATICALLY
|
||||||
|
// DO NOT CHANGE IT MANUALLY UNLESS YOU KNOW WHAT YOU'RE DOING
|
||||||
|
//
|
||||||
|
// GENERATED USING @colyseus/schema 4.0.15
|
||||||
|
//
|
||||||
|
|
||||||
|
using Colyseus.Schema;
|
||||||
|
#if UNITY_5_3_OR_NEWER
|
||||||
|
using UnityEngine.Scripting;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace RolldSchema {
|
||||||
|
public partial class GameState : Schema {
|
||||||
|
#if UNITY_5_3_OR_NEWER
|
||||||
|
[Preserve]
|
||||||
|
#endif
|
||||||
|
public GameState() { }
|
||||||
|
[Type(0, "map", typeof(MapSchema<Player>))]
|
||||||
|
public MapSchema<Player> players = null;
|
||||||
|
|
||||||
|
[Type(1, "string")]
|
||||||
|
public string phase = default(string);
|
||||||
|
|
||||||
|
[Type(2, "float32")]
|
||||||
|
public float countdown = default(float);
|
||||||
|
|
||||||
|
[Type(3, "int8")]
|
||||||
|
public sbyte roundNumber = default(sbyte);
|
||||||
|
|
||||||
|
[Type(4, "int8")]
|
||||||
|
public sbyte totalRounds = default(sbyte);
|
||||||
|
|
||||||
|
[Type(5, "int8")]
|
||||||
|
public sbyte playersAlive = default(sbyte);
|
||||||
|
|
||||||
|
[Type(6, "string")]
|
||||||
|
public string gameMode = default(string);
|
||||||
|
|
||||||
|
[Type(7, "string")]
|
||||||
|
public string winnerName = default(string);
|
||||||
|
}
|
||||||
|
}
|
||||||
2
game/Assets/Scripts/Network/Generated/GameState.cs.meta
Normal file
2
game/Assets/Scripts/Network/Generated/GameState.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c7c8bd319747bfa4a82569b7dc0458be
|
||||||
85
game/Assets/Scripts/Network/Generated/Player.cs
Normal file
85
game/Assets/Scripts/Network/Generated/Player.cs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
//
|
||||||
|
// THIS FILE HAS BEEN GENERATED AUTOMATICALLY
|
||||||
|
// DO NOT CHANGE IT MANUALLY UNLESS YOU KNOW WHAT YOU'RE DOING
|
||||||
|
//
|
||||||
|
// GENERATED USING @colyseus/schema 4.0.15
|
||||||
|
//
|
||||||
|
|
||||||
|
using Colyseus.Schema;
|
||||||
|
#if UNITY_5_3_OR_NEWER
|
||||||
|
using UnityEngine.Scripting;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace RolldSchema {
|
||||||
|
public partial class Player : Schema {
|
||||||
|
#if UNITY_5_3_OR_NEWER
|
||||||
|
[Preserve]
|
||||||
|
#endif
|
||||||
|
public Player() { }
|
||||||
|
[Type(0, "float32")]
|
||||||
|
public float x = default(float);
|
||||||
|
|
||||||
|
[Type(1, "float32")]
|
||||||
|
public float y = default(float);
|
||||||
|
|
||||||
|
[Type(2, "float32")]
|
||||||
|
public float z = default(float);
|
||||||
|
|
||||||
|
[Type(3, "float32")]
|
||||||
|
public float vx = default(float);
|
||||||
|
|
||||||
|
[Type(4, "float32")]
|
||||||
|
public float vy = default(float);
|
||||||
|
|
||||||
|
[Type(5, "float32")]
|
||||||
|
public float vz = default(float);
|
||||||
|
|
||||||
|
[Type(6, "float32")]
|
||||||
|
public float rx = default(float);
|
||||||
|
|
||||||
|
[Type(7, "float32")]
|
||||||
|
public float ry = default(float);
|
||||||
|
|
||||||
|
[Type(8, "float32")]
|
||||||
|
public float rz = default(float);
|
||||||
|
|
||||||
|
[Type(9, "float32")]
|
||||||
|
public float rw = default(float);
|
||||||
|
|
||||||
|
[Type(10, "float64")]
|
||||||
|
public double t = default(double);
|
||||||
|
|
||||||
|
[Type(11, "string")]
|
||||||
|
public string name = default(string);
|
||||||
|
|
||||||
|
[Type(12, "float32")]
|
||||||
|
public float colorR = default(float);
|
||||||
|
|
||||||
|
[Type(13, "float32")]
|
||||||
|
public float colorG = default(float);
|
||||||
|
|
||||||
|
[Type(14, "float32")]
|
||||||
|
public float colorB = default(float);
|
||||||
|
|
||||||
|
[Type(15, "float32")]
|
||||||
|
public float avx = default(float);
|
||||||
|
|
||||||
|
[Type(16, "float32")]
|
||||||
|
public float avy = default(float);
|
||||||
|
|
||||||
|
[Type(17, "float32")]
|
||||||
|
public float avz = default(float);
|
||||||
|
|
||||||
|
[Type(18, "boolean")]
|
||||||
|
public bool isEliminated = default(bool);
|
||||||
|
|
||||||
|
[Type(19, "boolean")]
|
||||||
|
public bool isQualified = default(bool);
|
||||||
|
|
||||||
|
[Type(20, "boolean")]
|
||||||
|
public bool isReady = default(bool);
|
||||||
|
|
||||||
|
[Type(21, "int8")]
|
||||||
|
public sbyte checkpointIndex = default(sbyte);
|
||||||
|
}
|
||||||
|
}
|
||||||
2
game/Assets/Scripts/Network/Generated/Player.cs.meta
Normal file
2
game/Assets/Scripts/Network/Generated/Player.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1d8173f164ec47946a28c13d9638d8cf
|
||||||
@@ -5,6 +5,7 @@ using UnityEngine;
|
|||||||
using UnityEngine.Networking;
|
using UnityEngine.Networking;
|
||||||
using Colyseus;
|
using Colyseus;
|
||||||
using Colyseus.Schema;
|
using Colyseus.Schema;
|
||||||
|
using RolldSchema;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Singleton managing the Colyseus connection, room lifecycle, remote player spawning,
|
/// Singleton managing the Colyseus connection, room lifecycle, remote player spawning,
|
||||||
@@ -70,8 +71,8 @@ public class NetworkManager : MonoBehaviour
|
|||||||
|
|
||||||
// --- Internals ---
|
// --- Internals ---
|
||||||
private Client _client;
|
private Client _client;
|
||||||
private Room<NetworkState> _room;
|
private Room<GameState> _room;
|
||||||
private StateCallbackStrategy<NetworkState> _callbacks;
|
private StateCallbackStrategy<GameState> _callbacks;
|
||||||
private readonly Dictionary<string, RemotePlayerController> _remotePlayers = new();
|
private readonly Dictionary<string, RemotePlayerController> _remotePlayers = new();
|
||||||
private float _broadcastTimer;
|
private float _broadcastTimer;
|
||||||
private const float BROADCAST_INTERVAL = 0.01667f; // ~60/sec
|
private const float BROADCAST_INTERVAL = 0.01667f; // ~60/sec
|
||||||
@@ -111,7 +112,7 @@ public class NetworkManager : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public NetworkPlayer GetLocalPlayerState()
|
public Player GetLocalPlayerState()
|
||||||
{
|
{
|
||||||
if (_room == null || _room.State.players == null || string.IsNullOrEmpty(LocalSessionId)) return null;
|
if (_room == null || _room.State.players == null || string.IsNullOrEmpty(LocalSessionId)) return null;
|
||||||
_room.State.players.TryGetValue(LocalSessionId, out var player);
|
_room.State.players.TryGetValue(LocalSessionId, out var player);
|
||||||
@@ -154,6 +155,7 @@ public class NetworkManager : MonoBehaviour
|
|||||||
_callbacks.OnRemove(state => state.players, (key, player) => OnPlayerRemove(key, player));
|
_callbacks.OnRemove(state => state.players, (key, player) => OnPlayerRemove(key, player));
|
||||||
_callbacks.Listen(state => state.phase, (v, _) => _OnPhaseChanged(v));
|
_callbacks.Listen(state => state.phase, (v, _) => _OnPhaseChanged(v));
|
||||||
_callbacks.Listen(state => state.countdown, (v, _) => OnCountdownChanged?.Invoke(v));
|
_callbacks.Listen(state => state.countdown, (v, _) => OnCountdownChanged?.Invoke(v));
|
||||||
|
_callbacks.Listen(state => state.playersAlive, (newVal, oldVal) => { if (GameHUD.Instance != null) GameHUD.Instance.SetPlayersAlive((int)newVal); });
|
||||||
|
|
||||||
_room.OnMessage<EliminatedMsg>("eliminated", msg => { OnEliminated?.Invoke(msg.sessionId, msg.reason); });
|
_room.OnMessage<EliminatedMsg>("eliminated", msg => { OnEliminated?.Invoke(msg.sessionId, msg.reason); });
|
||||||
_room.OnMessage<QualifiedMsg> ("qualified", msg => { OnQualified?.Invoke(msg.sessionId); });
|
_room.OnMessage<QualifiedMsg> ("qualified", msg => { OnQualified?.Invoke(msg.sessionId); });
|
||||||
@@ -163,6 +165,13 @@ public class NetworkManager : MonoBehaviour
|
|||||||
_room.OnMessage<ChatUI.ChatMessage>("chat", msg => { ChatUI.Instance?.ReceiveChatMessage(msg); });
|
_room.OnMessage<ChatUI.ChatMessage>("chat", msg => { ChatUI.Instance?.ReceiveChatMessage(msg); });
|
||||||
_room.OnLeave += OnRoomLeave;
|
_room.OnLeave += OnRoomLeave;
|
||||||
|
|
||||||
|
// Seed players already present in the room (state decoded before callbacks were registered)
|
||||||
|
if (_room.State.players != null)
|
||||||
|
{
|
||||||
|
foreach (var key in _room.State.players.Keys)
|
||||||
|
OnPlayerAdd((string)key, (Player)_room.State.players[key]);
|
||||||
|
}
|
||||||
|
|
||||||
OnConnected?.Invoke();
|
OnConnected?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +191,7 @@ public class NetworkManager : MonoBehaviour
|
|||||||
PrepareJoin(playerName, color);
|
PrepareJoin(playerName, color);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_room = await _client.JoinOrCreate<NetworkState>("arena", BuildJoinOptions(playerName, color));
|
_room = await _client.JoinOrCreate<GameState>("arena", BuildJoinOptions(playerName, color));
|
||||||
FinishJoin();
|
FinishJoin();
|
||||||
}
|
}
|
||||||
catch (Exception e) { HandleJoinError(e); }
|
catch (Exception e) { HandleJoinError(e); }
|
||||||
@@ -195,7 +204,7 @@ public class NetworkManager : MonoBehaviour
|
|||||||
PrepareJoin(playerName, color);
|
PrepareJoin(playerName, color);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_room = await _client.JoinById<NetworkState>(roomId, BuildJoinOptions(playerName, color));
|
_room = await _client.JoinById<GameState>(roomId, BuildJoinOptions(playerName, color));
|
||||||
FinishJoin();
|
FinishJoin();
|
||||||
}
|
}
|
||||||
catch (Exception e) { HandleJoinError(e); }
|
catch (Exception e) { HandleJoinError(e); }
|
||||||
@@ -210,7 +219,7 @@ public class NetworkManager : MonoBehaviour
|
|||||||
{
|
{
|
||||||
var opts = BuildJoinOptions(playerName, color);
|
var opts = BuildJoinOptions(playerName, color);
|
||||||
if (roomName != null) opts["roomName"] = roomName;
|
if (roomName != null) opts["roomName"] = roomName;
|
||||||
_room = await _client.Create<NetworkState>("arena", opts);
|
_room = await _client.Create<GameState>("arena", opts);
|
||||||
FinishJoin();
|
FinishJoin();
|
||||||
}
|
}
|
||||||
catch (Exception e) { HandleJoinError(e); }
|
catch (Exception e) { HandleJoinError(e); }
|
||||||
@@ -251,17 +260,20 @@ public class NetworkManager : MonoBehaviour
|
|||||||
OnPhaseChanged?.Invoke(phase);
|
OnPhaseChanged?.Invoke(phase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlayerAdd(string sessionId, NetworkPlayer player)
|
private void OnPlayerAdd(string sessionId, Player player)
|
||||||
{
|
{
|
||||||
Debug.Log($"[Network] Player joined: {sessionId} ({player.name})");
|
Debug.Log($"[Network] Player joined: {sessionId} ({player.name})");
|
||||||
PlayerCount = _room.State.players?.Count ?? 0;
|
PlayerCount = _room.State.players?.Count ?? 0;
|
||||||
|
|
||||||
if (sessionId == LocalSessionId) return;
|
if (sessionId == LocalSessionId) return;
|
||||||
|
if (_remotePlayers.ContainsKey(sessionId)) return; // prevent duplicate spawn
|
||||||
|
|
||||||
if (remotePlayerPrefab != null)
|
|
||||||
{
|
{
|
||||||
Vector3 spawnPos = new Vector3(player.x, player.y, player.z);
|
Vector3 spawnPos = new Vector3(player.x, player.y, player.z);
|
||||||
GameObject remoteBall = Instantiate(remotePlayerPrefab, spawnPos, Quaternion.identity);
|
GameObject remoteBall = remotePlayerPrefab != null
|
||||||
|
? Instantiate(remotePlayerPrefab, spawnPos, Quaternion.identity)
|
||||||
|
: GameObject.CreatePrimitive(PrimitiveType.Sphere);
|
||||||
|
remoteBall.transform.position = spawnPos;
|
||||||
remoteBall.name = $"RemotePlayer_{player.name}_{sessionId[..6]}";
|
remoteBall.name = $"RemotePlayer_{player.name}_{sessionId[..6]}";
|
||||||
|
|
||||||
var controller = remoteBall.GetComponent<RemotePlayerController>()
|
var controller = remoteBall.GetComponent<RemotePlayerController>()
|
||||||
@@ -277,7 +289,7 @@ public class NetworkManager : MonoBehaviour
|
|||||||
OnPlayerJoined?.Invoke(sessionId);
|
OnPlayerJoined?.Invoke(sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlayerRemove(string sessionId, NetworkPlayer player)
|
private void OnPlayerRemove(string sessionId, Player player)
|
||||||
{
|
{
|
||||||
Debug.Log($"[Network] Player left: {sessionId}");
|
Debug.Log($"[Network] Player left: {sessionId}");
|
||||||
PlayerCount = _room.State.players?.Count ?? 0;
|
PlayerCount = _room.State.players?.Count ?? 0;
|
||||||
@@ -292,7 +304,7 @@ public class NetworkManager : MonoBehaviour
|
|||||||
OnPlayerLeft?.Invoke(sessionId);
|
OnPlayerLeft?.Invoke(sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlayerChange(string sessionId, NetworkPlayer player)
|
private void OnPlayerChange(string sessionId, Player player)
|
||||||
{
|
{
|
||||||
if (sessionId == LocalSessionId) return;
|
if (sessionId == LocalSessionId) return;
|
||||||
|
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
using Colyseus.Schema;
|
|
||||||
|
|
||||||
// Must match server-side defineTypes field order exactly
|
|
||||||
public partial class NetworkPlayer : Schema
|
|
||||||
{
|
|
||||||
[Type(0, "float32")] public float x = 0;
|
|
||||||
[Type(1, "float32")] public float y = 5;
|
|
||||||
[Type(2, "float32")] public float z = 0;
|
|
||||||
[Type(3, "float32")] public float vx = 0;
|
|
||||||
[Type(4, "float32")] public float vy = 0;
|
|
||||||
[Type(5, "float32")] public float vz = 0;
|
|
||||||
[Type(6, "float32")] public float rx = 0;
|
|
||||||
[Type(7, "float32")] public float ry = 0;
|
|
||||||
[Type(8, "float32")] public float rz = 0;
|
|
||||||
[Type(9, "float32")] public float rw = 1;
|
|
||||||
[Type(10, "float64")] public double t = 0;
|
|
||||||
[Type(11, "string")] public string name = "";
|
|
||||||
[Type(12, "float32")] public float colorR = 1;
|
|
||||||
[Type(13, "float32")] public float colorG = 1;
|
|
||||||
[Type(14, "float32")] public float colorB = 1;
|
|
||||||
[Type(15, "float32")] public float avx = 0;
|
|
||||||
[Type(16, "float32")] public float avy = 0;
|
|
||||||
[Type(17, "float32")] public float avz = 0;
|
|
||||||
// Game state — order must match server defineTypes exactly
|
|
||||||
[Type(18, "boolean")] public bool isEliminated = false;
|
|
||||||
[Type(19, "boolean")] public bool isQualified = false;
|
|
||||||
[Type(20, "boolean")] public bool isReady = false;
|
|
||||||
[Type(21, "int8")] public int checkpointIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public partial class NetworkState : Schema
|
|
||||||
{
|
|
||||||
[Type(0, "map", typeof(MapSchema<NetworkPlayer>))]
|
|
||||||
public MapSchema<NetworkPlayer> players;
|
|
||||||
|
|
||||||
[Type(1, "string")] public string phase = "lobby";
|
|
||||||
[Type(2, "float32")] public float countdown = 0;
|
|
||||||
[Type(3, "int8")] public int roundNumber = 1;
|
|
||||||
[Type(4, "int8")] public int totalRounds = 4;
|
|
||||||
[Type(5, "int8")] public int playersAlive = 0;
|
|
||||||
[Type(6, "string")] public string gameMode = "race";
|
|
||||||
[Type(7, "string")] public string winnerName = "";
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 0ce16348bc0580b49860d9bd80e7bec0
|
|
||||||
@@ -24,10 +24,16 @@ public class RemotePlayerController : MonoBehaviour
|
|||||||
[Tooltip("Rotation slerp speed")]
|
[Tooltip("Rotation slerp speed")]
|
||||||
public float rotationSpeed = 24f;
|
public float rotationSpeed = 24f;
|
||||||
|
|
||||||
|
[Header("Spawn")]
|
||||||
|
[Tooltip("Seconds after spawn during which this remote ignores local bump interactions (avoids upward eject if balls overlap at spawn).")]
|
||||||
|
public float spawnBumpGrace = 1.5f;
|
||||||
|
|
||||||
// Public info
|
// Public info
|
||||||
public string SessionId { get; private set; }
|
public string SessionId { get; private set; }
|
||||||
public string PlayerName { get; private set; }
|
public string PlayerName { get; private set; }
|
||||||
public Color PlayerColor { get; private set; }
|
public Color PlayerColor { get; private set; }
|
||||||
|
public float SpawnTime { get; private set; }
|
||||||
|
public bool BumpReady => Time.time - SpawnTime > spawnBumpGrace;
|
||||||
|
|
||||||
// --- Snapshot buffer ---
|
// --- Snapshot buffer ---
|
||||||
private struct Snapshot
|
private struct Snapshot
|
||||||
@@ -61,6 +67,7 @@ public class RemotePlayerController : MonoBehaviour
|
|||||||
SessionId = sessionId;
|
SessionId = sessionId;
|
||||||
PlayerName = playerName;
|
PlayerName = playerName;
|
||||||
PlayerColor = color;
|
PlayerColor = color;
|
||||||
|
SpawnTime = Time.time;
|
||||||
_currentRotation = transform.rotation;
|
_currentRotation = transform.rotation;
|
||||||
_bufferCount = 0;
|
_bufferCount = 0;
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
@@ -99,12 +106,17 @@ public class RemotePlayerController : MonoBehaviour
|
|||||||
|
|
||||||
// Add a trigger collider slightly larger than the physics collider
|
// Add a trigger collider slightly larger than the physics collider
|
||||||
// so the local player can detect bumps
|
// so the local player can detect bumps
|
||||||
var existingCollider = GetComponent<SphereCollider>();
|
_solidCollider = GetComponent<SphereCollider>();
|
||||||
float baseRadius = existingCollider != null ? existingCollider.radius : 0.5f;
|
float baseRadius = _solidCollider != null ? _solidCollider.radius : 0.5f;
|
||||||
var trigger = gameObject.AddComponent<SphereCollider>();
|
var trigger = gameObject.AddComponent<SphereCollider>();
|
||||||
trigger.isTrigger = true;
|
trigger.isTrigger = true;
|
||||||
trigger.radius = baseRadius * 1.15f; // 15% larger
|
trigger.radius = baseRadius * 1.15f; // 15% larger
|
||||||
|
|
||||||
|
// During the spawn grace window, disable the SOLID collider so the kinematic
|
||||||
|
// remote can't physically eject an overlapping local player at spawn.
|
||||||
|
// (The trigger stays — bump detection is gated by BumpReady in PlayerController.)
|
||||||
|
if (_solidCollider != null) _solidCollider.enabled = false;
|
||||||
|
|
||||||
// Disable any player input on remote balls
|
// Disable any player input on remote balls
|
||||||
var playerInput = GetComponent<UnityEngine.InputSystem.PlayerInput>();
|
var playerInput = GetComponent<UnityEngine.InputSystem.PlayerInput>();
|
||||||
if (playerInput != null)
|
if (playerInput != null)
|
||||||
@@ -114,12 +126,16 @@ public class RemotePlayerController : MonoBehaviour
|
|||||||
if (playerController != null)
|
if (playerController != null)
|
||||||
playerController.enabled = false;
|
playerController.enabled = false;
|
||||||
|
|
||||||
// Create floating name label
|
// Create floating name label + speed trail
|
||||||
CreateNameLabel();
|
CreateNameLabel();
|
||||||
|
CreateTrail(color);
|
||||||
|
|
||||||
Debug.Log($"[RemotePlayer] Initialized: {playerName} ({sessionId[..6]}) color={color}");
|
Debug.Log($"[RemotePlayer] Initialized: {playerName} ({sessionId[..6]}) color={color}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SphereCollider _solidCollider;
|
||||||
|
private bool _solidReenabled;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called by NetworkManager when a state update arrives from the server.
|
/// Called by NetworkManager when a state update arrives from the server.
|
||||||
/// Pushes a new snapshot into the interpolation buffer.
|
/// Pushes a new snapshot into the interpolation buffer.
|
||||||
@@ -149,7 +165,16 @@ public class RemotePlayerController : MonoBehaviour
|
|||||||
|
|
||||||
void Update()
|
void Update()
|
||||||
{
|
{
|
||||||
if (!_initialized || _bufferCount == 0) return;
|
if (!_initialized) return;
|
||||||
|
|
||||||
|
// Re-enable the solid collider once the grace window has elapsed.
|
||||||
|
if (!_solidReenabled && BumpReady && _solidCollider != null)
|
||||||
|
{
|
||||||
|
_solidCollider.enabled = true;
|
||||||
|
_solidReenabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_bufferCount == 0) return;
|
||||||
|
|
||||||
// Render time = current time minus interpolation delay
|
// Render time = current time minus interpolation delay
|
||||||
float renderTime = Time.time - interpolationDelay;
|
float renderTime = Time.time - interpolationDelay;
|
||||||
@@ -248,15 +273,37 @@ public class RemotePlayerController : MonoBehaviour
|
|||||||
if (cam != null)
|
if (cam != null)
|
||||||
{
|
{
|
||||||
Vector3 lookDir = cam.transform.position - _nameLabelObj.transform.position;
|
Vector3 lookDir = cam.transform.position - _nameLabelObj.transform.position;
|
||||||
|
float camDist = lookDir.magnitude;
|
||||||
lookDir.y = 0f;
|
lookDir.y = 0f;
|
||||||
if (lookDir.sqrMagnitude > 0.001f)
|
if (lookDir.sqrMagnitude > 0.001f)
|
||||||
_nameLabelObj.transform.rotation = Quaternion.LookRotation(lookDir);
|
_nameLabelObj.transform.rotation = Quaternion.LookRotation(lookDir);
|
||||||
|
|
||||||
|
// Distance-based scale: keeps the pseudo readable when players are far apart.
|
||||||
|
// Below 8 m → base size; above → grows linearly, capped at 8× to avoid screen takeover.
|
||||||
|
float scaleFactor = Mathf.Clamp(camDist / 8f, 1f, 8f);
|
||||||
|
_nameLabelObj.transform.localScale = Vector3.one * (0.1f * scaleFactor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private GameObject _nameLabelObj; // Keep reference for billboard update
|
private GameObject _nameLabelObj; // Keep reference for billboard update
|
||||||
|
|
||||||
|
private void CreateTrail(Color playerColor)
|
||||||
|
{
|
||||||
|
var trail = GetComponent<TrailRenderer>() ?? gameObject.AddComponent<TrailRenderer>();
|
||||||
|
trail.time = 0.4f;
|
||||||
|
trail.startWidth = 0.3f;
|
||||||
|
trail.endWidth = 0.02f;
|
||||||
|
trail.minVertexDistance = 0.1f;
|
||||||
|
trail.autodestruct = false;
|
||||||
|
trail.emitting = true;
|
||||||
|
trail.material = new Material(Shader.Find("Sprites/Default"));
|
||||||
|
// Use the player's chosen color so each remote has a visually distinct trail
|
||||||
|
// (lobby presets avoid orange, so it never clashes with the local player's orange trail).
|
||||||
|
trail.startColor = new Color(playerColor.r, playerColor.g, playerColor.b, 0.7f);
|
||||||
|
trail.endColor = new Color(playerColor.r, playerColor.g, playerColor.b, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
private void CreateNameLabel()
|
private void CreateNameLabel()
|
||||||
{
|
{
|
||||||
GameObject labelObj = new GameObject("NameLabel");
|
GameObject labelObj = new GameObject("NameLabel");
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 5bf5e078a2ee9ed4fa95eacab5753f3a
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 6d1f3d6aaca8e97498f40d827f7c5216
|
|
||||||
@@ -4,41 +4,40 @@ using UnityEngine;
|
|||||||
using UnityEngine.Networking;
|
using UnityEngine.Networking;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tracks per-session and per-round player statistics and uploads them to the game server.
|
/// Tracks player statistics and uploads them to the game server every 30s + on disconnect.
|
||||||
/// All HTTP calls use UnityWebRequest coroutines (WebGL-safe, no async/await).
|
/// No dependency on round events — works even if Colyseus callbacks are broken.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class StatsTracker : MonoBehaviour
|
public class StatsTracker : MonoBehaviour
|
||||||
{
|
{
|
||||||
public static StatsTracker Instance { get; private set; }
|
public static StatsTracker Instance { get; private set; }
|
||||||
|
|
||||||
private const string SERVER_URL = "https://game.rolld.kerboul.me";
|
private const string SERVER_URL = "https://game.rolld.kerboul.me";
|
||||||
|
private const float SEND_INTERVAL = 30f;
|
||||||
|
private const float MIN_SEND_INTERVAL = 6f; // juste au-dessus du rate-limit serveur (5s)
|
||||||
|
|
||||||
// Cumulative session stats (accumulate across rounds)
|
// Cumulative stats
|
||||||
private float _totalDistance;
|
private float _totalDistance;
|
||||||
private int _totalJumps;
|
private int _totalJumps;
|
||||||
private float _maxSpeed;
|
private float _maxSpeed;
|
||||||
private int _racesPlayed;
|
|
||||||
private int _qualifications;
|
|
||||||
private int _eliminations;
|
|
||||||
private int _bumpsGiven;
|
private int _bumpsGiven;
|
||||||
private float _totalPlaytime;
|
|
||||||
|
|
||||||
// Per-round deltas (reset after each send)
|
// Playtime
|
||||||
private float _roundDistance;
|
|
||||||
private float _roundMaxSpeed;
|
|
||||||
private float _sessionStart;
|
private float _sessionStart;
|
||||||
|
private float _playtimeSentSoFar; // how much playtime we already sent
|
||||||
|
|
||||||
|
// Tracking
|
||||||
private Vector3 _lastPos;
|
private Vector3 _lastPos;
|
||||||
private bool _trackingActive;
|
private bool _tracking;
|
||||||
private string _cachedName = "";
|
private string _cachedName = "";
|
||||||
|
private float _lastSentTime = -999f;
|
||||||
|
|
||||||
private PlayerController _pc;
|
private PlayerController _pc;
|
||||||
private Rigidbody _rb;
|
private Rigidbody _rb;
|
||||||
|
|
||||||
void Awake()
|
void Awake()
|
||||||
{
|
{
|
||||||
if (Instance != null && Instance != this) { Destroy(gameObject); return; }
|
if (Instance != null && Instance != this) { Destroy(gameObject); return; }
|
||||||
Instance = this;
|
Instance = this;
|
||||||
_sessionStart = Time.time;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Start()
|
void Start()
|
||||||
@@ -49,11 +48,7 @@ public class StatsTracker : MonoBehaviour
|
|||||||
var nm = NetworkManager.Instance;
|
var nm = NetworkManager.Instance;
|
||||||
if (nm != null)
|
if (nm != null)
|
||||||
{
|
{
|
||||||
nm.OnRoundStart += OnRoundStart;
|
nm.OnConnected += OnConnected;
|
||||||
nm.OnRoundEnd += OnRoundEnd;
|
|
||||||
nm.OnQualified += OnQualified;
|
|
||||||
nm.OnEliminated += OnEliminated;
|
|
||||||
nm.OnConnected += OnConnected;
|
|
||||||
nm.OnDisconnected += OnDisconnected;
|
nm.OnDisconnected += OnDisconnected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,123 +58,90 @@ public class StatsTracker : MonoBehaviour
|
|||||||
var nm = NetworkManager.Instance;
|
var nm = NetworkManager.Instance;
|
||||||
if (nm != null)
|
if (nm != null)
|
||||||
{
|
{
|
||||||
nm.OnRoundStart -= OnRoundStart;
|
nm.OnConnected -= OnConnected;
|
||||||
nm.OnRoundEnd -= OnRoundEnd;
|
|
||||||
nm.OnQualified -= OnQualified;
|
|
||||||
nm.OnEliminated -= OnEliminated;
|
|
||||||
nm.OnConnected -= OnConnected;
|
|
||||||
nm.OnDisconnected -= OnDisconnected;
|
nm.OnDisconnected -= OnDisconnected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FixedUpdate()
|
void FixedUpdate()
|
||||||
{
|
{
|
||||||
if (!_trackingActive || _rb == null || _pc == null || !_pc.enabled) return;
|
if (!_tracking || _rb == null) return;
|
||||||
|
|
||||||
Vector3 pos = transform.position;
|
Vector3 pos = transform.position;
|
||||||
float delta = Vector3.Distance(pos, _lastPos);
|
float delta = Vector3.Distance(pos, _lastPos);
|
||||||
if (delta < 20f) // sanity cap against teleports
|
if (delta < 20f) // filtre téléportations
|
||||||
{
|
_totalDistance += delta;
|
||||||
_roundDistance += delta;
|
|
||||||
_totalDistance += delta;
|
|
||||||
}
|
|
||||||
_lastPos = pos;
|
_lastPos = pos;
|
||||||
|
|
||||||
float speed = _rb.linearVelocity.magnitude;
|
float speed = _rb.linearVelocity.magnitude;
|
||||||
if (speed > _roundMaxSpeed) _roundMaxSpeed = speed;
|
if (speed > _maxSpeed) _maxSpeed = speed;
|
||||||
if (speed > _maxSpeed) _maxSpeed = speed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Public hooks ────────────────────────────────────────────────────
|
// ─── Public hooks ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
public void RegisterJump()
|
public void RegisterJump() => _totalJumps++;
|
||||||
{
|
public void RegisterBump() => _bumpsGiven++;
|
||||||
_totalJumps++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterBump()
|
// ─── Connection events ────────────────────────────────────────────────
|
||||||
{
|
|
||||||
_bumpsGiven++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Event handlers ──────────────────────────────────────────────────
|
|
||||||
|
|
||||||
private void OnConnected()
|
private void OnConnected()
|
||||||
{
|
{
|
||||||
_cachedName = NetworkManager.Instance?.LocalPlayerName ?? "";
|
_cachedName = NetworkManager.Instance?.LocalPlayerName ?? "";
|
||||||
_lastPos = transform.position;
|
_lastPos = transform.position;
|
||||||
_trackingActive = true;
|
_sessionStart = Time.time;
|
||||||
|
_tracking = true;
|
||||||
|
StartCoroutine(PeriodicSend());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDisconnected()
|
private void OnDisconnected()
|
||||||
{
|
{
|
||||||
_trackingActive = false;
|
_tracking = false;
|
||||||
_totalPlaytime += Time.time - _sessionStart;
|
StopAllCoroutines();
|
||||||
SendStats(); // best-effort on disconnect
|
SendStats(); // envoi final best-effort
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRoundStart(int round, string mode, int totalRounds)
|
// ─── Periodic send ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private IEnumerator PeriodicSend()
|
||||||
{
|
{
|
||||||
_racesPlayed++;
|
while (_tracking)
|
||||||
_roundDistance = 0f;
|
{
|
||||||
_roundMaxSpeed = 0f;
|
yield return new WaitForSeconds(SEND_INTERVAL);
|
||||||
_lastPos = transform.position;
|
if (_tracking) SendStats();
|
||||||
_trackingActive = true;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRoundEnd(int round)
|
// ─── HTTP send ────────────────────────────────────────────────────────
|
||||||
{
|
|
||||||
_trackingActive = false;
|
|
||||||
SendStats();
|
|
||||||
_roundDistance = 0f;
|
|
||||||
_roundMaxSpeed = 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnQualified(string sessionId)
|
|
||||||
{
|
|
||||||
if (sessionId == NetworkManager.Instance?.LocalSessionId)
|
|
||||||
_qualifications++;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnEliminated(string sessionId, string reason)
|
|
||||||
{
|
|
||||||
if (sessionId == NetworkManager.Instance?.LocalSessionId)
|
|
||||||
_eliminations++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── HTTP send ───────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
private void SendStats()
|
private void SendStats()
|
||||||
{
|
{
|
||||||
// Prefer live name, fall back to cached (useful on disconnect where name is cleared)
|
if (Time.time - _lastSentTime < MIN_SEND_INTERVAL) return;
|
||||||
var nm = NetworkManager.Instance;
|
var nm = NetworkManager.Instance;
|
||||||
string name = (nm != null && !string.IsNullOrEmpty(nm.LocalPlayerName))
|
string name = (nm != null && !string.IsNullOrEmpty(nm.LocalPlayerName))
|
||||||
? nm.LocalPlayerName
|
? nm.LocalPlayerName
|
||||||
: _cachedName;
|
: _cachedName;
|
||||||
if (string.IsNullOrEmpty(name)) return;
|
if (string.IsNullOrEmpty(name)) return;
|
||||||
|
_lastSentTime = Time.time;
|
||||||
StartCoroutine(DoSendStats(name));
|
StartCoroutine(DoSendStats(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerator DoSendStats(string playerName)
|
private IEnumerator DoSendStats(string playerName)
|
||||||
{
|
{
|
||||||
_totalPlaytime += Time.time - _sessionStart;
|
float now = Time.time;
|
||||||
_sessionStart = Time.time;
|
float sessionSecs = now - _sessionStart;
|
||||||
|
float playtimeToSend = sessionSecs - _playtimeSentSoFar;
|
||||||
|
_playtimeSentSoFar = sessionSecs;
|
||||||
|
|
||||||
var payload = new StatsPayload
|
var payload = new StatsPayload
|
||||||
{
|
{
|
||||||
name = playerName,
|
name = playerName,
|
||||||
stats = new StatsData
|
stats = new StatsData
|
||||||
{
|
{
|
||||||
totalDistance = _totalDistance,
|
totalDistance = _totalDistance,
|
||||||
totalJumps = _totalJumps,
|
totalJumps = _totalJumps,
|
||||||
maxSpeed = _maxSpeed,
|
maxSpeed = _maxSpeed,
|
||||||
|
bumpsGiven = _bumpsGiven,
|
||||||
racesPlayed = _racesPlayed,
|
totalPlaytime = playtimeToSend,
|
||||||
qualifications = _qualifications,
|
|
||||||
eliminations = _eliminations,
|
|
||||||
|
|
||||||
bumpsGiven = _bumpsGiven,
|
|
||||||
totalPlaytime = _totalPlaytime,
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -196,7 +158,7 @@ public class StatsTracker : MonoBehaviour
|
|||||||
if (req.result != UnityWebRequest.Result.Success)
|
if (req.result != UnityWebRequest.Result.Success)
|
||||||
Debug.LogWarning($"[Stats] Upload failed: {req.error}");
|
Debug.LogWarning($"[Stats] Upload failed: {req.error}");
|
||||||
else
|
else
|
||||||
Debug.Log($"[Stats] Uploaded for {playerName}");
|
Debug.Log($"[Stats] Sent for {playerName} — dist:{_totalDistance:F0}m spd:{_maxSpeed:F1}m/s jumps:{_totalJumps}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── DTOs ─────────────────────────────────────────────────────────────
|
// ─── DTOs ─────────────────────────────────────────────────────────────
|
||||||
@@ -210,9 +172,6 @@ public class StatsTracker : MonoBehaviour
|
|||||||
public float totalDistance;
|
public float totalDistance;
|
||||||
public int totalJumps;
|
public int totalJumps;
|
||||||
public float maxSpeed;
|
public float maxSpeed;
|
||||||
public int racesPlayed;
|
|
||||||
public int qualifications;
|
|
||||||
public int eliminations;
|
|
||||||
public int bumpsGiven;
|
public int bumpsGiven;
|
||||||
public float totalPlaytime;
|
public float totalPlaytime;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ public class ChatUI : MonoBehaviour
|
|||||||
_pollTimer = POLL_INTERVAL; // poll immediately
|
_pollTimer = POLL_INTERVAL; // poll immediately
|
||||||
Cursor.lockState = CursorLockMode.None;
|
Cursor.lockState = CursorLockMode.None;
|
||||||
Cursor.visible = true;
|
Cursor.visible = true;
|
||||||
|
// Release held movement keys so the ball doesn't keep moving while typing
|
||||||
|
FindFirstObjectByType<PlayerController>()?.ResetInputs();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 51e21afb9dba1904bb425ac1fae825cb
|
|
||||||
@@ -101,108 +101,7 @@ public class GameHUD : MonoBehaviour
|
|||||||
|
|
||||||
void OnGUI()
|
void OnGUI()
|
||||||
{
|
{
|
||||||
if (_phase == "lobby" && !_localRaceActive) return;
|
// Race UI disabled — free-roam mode has no rounds/countdown/timer.
|
||||||
|
|
||||||
ImGuiSkin.EnsureReady();
|
|
||||||
var nm = NetworkManager.Instance;
|
|
||||||
|
|
||||||
// ── Countdown (center, large) ─────────────────────────────────────
|
|
||||||
if (_phase == "countdown" && _countdown > 0f)
|
|
||||||
{
|
|
||||||
float scale = 1f + _countdownPulse * 0.4f;
|
|
||||||
float fontSize = 96f * scale;
|
|
||||||
var countStyle = new GUIStyle(GUI.skin.label)
|
|
||||||
{
|
|
||||||
alignment = TextAnchor.MiddleCenter,
|
|
||||||
fontSize = Mathf.RoundToInt(fontSize),
|
|
||||||
fontStyle = FontStyle.Bold,
|
|
||||||
};
|
|
||||||
countStyle.normal.textColor = new Color(1f, 0.85f, 0.1f, 1f);
|
|
||||||
GUI.Label(new Rect(0, Screen.height * 0.3f, Screen.width, 120f),
|
|
||||||
Mathf.CeilToInt(_countdown).ToString(), countStyle);
|
|
||||||
|
|
||||||
// "Préparez-vous !" label below
|
|
||||||
var subStyle = new GUIStyle(GUI.skin.label)
|
|
||||||
{
|
|
||||||
alignment = TextAnchor.MiddleCenter,
|
|
||||||
fontSize = 22,
|
|
||||||
fontStyle = FontStyle.Bold,
|
|
||||||
};
|
|
||||||
subStyle.normal.textColor = new Color(1f, 1f, 1f, 0.8f);
|
|
||||||
string modeLabel = _gameMode switch {
|
|
||||||
"race" => "COURSE",
|
|
||||||
"survival" => "SURVIVAL",
|
|
||||||
"teams" => "ÉQUIPES",
|
|
||||||
_ => _gameMode.ToUpper()
|
|
||||||
};
|
|
||||||
GUI.Label(new Rect(0, Screen.height * 0.3f + 110f, Screen.width, 36f),
|
|
||||||
$"— {modeLabel} —", subStyle);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Top-left: Round & Mode ─────────────────────────────────────────
|
|
||||||
float panelX = 12f;
|
|
||||||
float panelY = 12f;
|
|
||||||
float panelW = 220f;
|
|
||||||
float panelH = 70f;
|
|
||||||
|
|
||||||
GUI.color = new Color(0.08f, 0.08f, 0.12f, 0.85f);
|
|
||||||
GUI.DrawTexture(new Rect(panelX, panelY, panelW, panelH), _bgTex);
|
|
||||||
GUI.color = Color.white;
|
|
||||||
|
|
||||||
var roundStyle = new GUIStyle(GUI.skin.label)
|
|
||||||
{
|
|
||||||
alignment = TextAnchor.MiddleLeft,
|
|
||||||
fontSize = 14,
|
|
||||||
fontStyle = FontStyle.Bold,
|
|
||||||
};
|
|
||||||
roundStyle.normal.textColor = new Color(1f, 0.85f, 0.1f);
|
|
||||||
GUI.Label(new Rect(panelX + 8f, panelY + 4f, panelW - 16f, 28f),
|
|
||||||
$"ROUND {_roundNumber} / {_totalRounds}", roundStyle);
|
|
||||||
|
|
||||||
var modeStyle = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleLeft, fontSize = 12 };
|
|
||||||
modeStyle.normal.textColor = new Color(0.7f, 0.7f, 0.85f);
|
|
||||||
string modeFull = _gameMode switch {
|
|
||||||
"race" => "COURSE", "survival" => "SURVIVAL", "teams" => "ÉQUIPES", _ => _gameMode.ToUpper()
|
|
||||||
};
|
|
||||||
GUI.Label(new Rect(panelX + 8f, panelY + 32f, panelW - 16f, 24f), modeFull, modeStyle);
|
|
||||||
|
|
||||||
// ── Top-right: Players alive ──────────────────────────────────────
|
|
||||||
float prX = Screen.width - 180f;
|
|
||||||
GUI.color = new Color(0.08f, 0.08f, 0.12f, 0.85f);
|
|
||||||
GUI.DrawTexture(new Rect(prX, panelY, 168f, panelH), _bgTex);
|
|
||||||
GUI.color = Color.white;
|
|
||||||
|
|
||||||
var aliveStyle = new GUIStyle(GUI.skin.label)
|
|
||||||
{
|
|
||||||
alignment = TextAnchor.MiddleCenter,
|
|
||||||
fontSize = 28,
|
|
||||||
fontStyle = FontStyle.Bold,
|
|
||||||
};
|
|
||||||
aliveStyle.normal.textColor = new Color(0.3f, 1f, 0.5f);
|
|
||||||
GUI.Label(new Rect(prX, panelY + 2f, 168f, 40f), $"{_cachedPlayersAlive}", aliveStyle);
|
|
||||||
|
|
||||||
var aliveLabel = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleCenter, fontSize = 11 };
|
|
||||||
aliveLabel.normal.textColor = new Color(0.6f, 0.6f, 0.7f);
|
|
||||||
GUI.Label(new Rect(prX, panelY + 40f, 168f, 22f), "joueurs en jeu", aliveLabel);
|
|
||||||
|
|
||||||
// ── Round timer (top center) ──────────────────────────────────────
|
|
||||||
float displayTimer = _timerRunning ? _roundTimer : (_localRaceActive ? _localRaceTimer : -1f);
|
|
||||||
if (displayTimer >= 0f)
|
|
||||||
{
|
|
||||||
int mins = Mathf.FloorToInt(displayTimer / 60f);
|
|
||||||
int secs = Mathf.FloorToInt(displayTimer % 60f);
|
|
||||||
var timerStyle = new GUIStyle(GUI.skin.label)
|
|
||||||
{
|
|
||||||
alignment = TextAnchor.MiddleCenter,
|
|
||||||
fontSize = 18,
|
|
||||||
fontStyle = FontStyle.Bold,
|
|
||||||
};
|
|
||||||
timerStyle.normal.textColor = new Color(0.85f, 0.85f, 0.9f, 0.9f);
|
|
||||||
GUI.Label(new Rect(Screen.width * 0.5f - 60f, panelY, 120f, 40f),
|
|
||||||
$"{mins:00}:{secs:00}", timerStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Static accessors for cross-script use
|
// Static accessors for cross-script use
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: ba062aa6c92b140379dbc06b43dd3b9b
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 8a0c9218a650547d98138cd835033977
|
|
||||||
folderAsset: yes
|
|
||||||
timeCreated: 1484670163
|
|
||||||
licenseType: Store
|
|
||||||
DefaultImporter:
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
Binary file not shown.
@@ -1,134 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 727a75301c3d24613a3ebcec4a24c2c8
|
|
||||||
TextureImporter:
|
|
||||||
internalIDToNameTable: []
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 11
|
|
||||||
mipmaps:
|
|
||||||
mipMapMode: 0
|
|
||||||
enableMipMap: 0
|
|
||||||
sRGBTexture: 1
|
|
||||||
linearTexture: 0
|
|
||||||
fadeOut: 0
|
|
||||||
borderMipMap: 0
|
|
||||||
mipMapsPreserveCoverage: 0
|
|
||||||
alphaTestReferenceValue: 0.5
|
|
||||||
mipMapFadeDistanceStart: 1
|
|
||||||
mipMapFadeDistanceEnd: 3
|
|
||||||
bumpmap:
|
|
||||||
convertToNormalMap: 0
|
|
||||||
externalNormalMap: 0
|
|
||||||
heightScale: 0.25
|
|
||||||
normalMapFilter: 0
|
|
||||||
isReadable: 0
|
|
||||||
streamingMipmaps: 0
|
|
||||||
streamingMipmapsPriority: 0
|
|
||||||
vTOnly: 0
|
|
||||||
ignoreMasterTextureLimit: 0
|
|
||||||
grayScaleToAlpha: 0
|
|
||||||
generateCubemap: 6
|
|
||||||
cubemapConvolution: 0
|
|
||||||
seamlessCubemap: 0
|
|
||||||
textureFormat: 1
|
|
||||||
maxTextureSize: 2048
|
|
||||||
textureSettings:
|
|
||||||
serializedVersion: 2
|
|
||||||
filterMode: 0
|
|
||||||
aniso: 1
|
|
||||||
mipBias: 0
|
|
||||||
wrapU: 1
|
|
||||||
wrapV: 1
|
|
||||||
wrapW: 0
|
|
||||||
nPOTScale: 0
|
|
||||||
lightmap: 0
|
|
||||||
compressionQuality: 50
|
|
||||||
spriteMode: 0
|
|
||||||
spriteExtrude: 1
|
|
||||||
spriteMeshType: 1
|
|
||||||
alignment: 0
|
|
||||||
spritePivot: {x: 0.5, y: 0.5}
|
|
||||||
spritePixelsToUnits: 100
|
|
||||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
|
||||||
spriteGenerateFallbackPhysicsShape: 1
|
|
||||||
alphaUsage: 1
|
|
||||||
alphaIsTransparency: 1
|
|
||||||
spriteTessellationDetail: -1
|
|
||||||
textureType: 2
|
|
||||||
textureShape: 1
|
|
||||||
singleChannelComponent: 0
|
|
||||||
flipbookRows: 1
|
|
||||||
flipbookColumns: 1
|
|
||||||
maxTextureSizeSet: 0
|
|
||||||
compressionQualitySet: 0
|
|
||||||
textureFormatSet: 0
|
|
||||||
ignorePngGamma: 0
|
|
||||||
applyGammaDecoding: 0
|
|
||||||
platformSettings:
|
|
||||||
- serializedVersion: 3
|
|
||||||
buildTarget: DefaultTexturePlatform
|
|
||||||
maxTextureSize: 2048
|
|
||||||
resizeAlgorithm: 0
|
|
||||||
textureFormat: -1
|
|
||||||
textureCompression: 0
|
|
||||||
compressionQuality: 50
|
|
||||||
crunchedCompression: 0
|
|
||||||
allowsAlphaSplitting: 0
|
|
||||||
overridden: 0
|
|
||||||
androidETC2FallbackOverride: 0
|
|
||||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
|
||||||
- serializedVersion: 3
|
|
||||||
buildTarget: Standalone
|
|
||||||
maxTextureSize: 2048
|
|
||||||
resizeAlgorithm: 0
|
|
||||||
textureFormat: -1
|
|
||||||
textureCompression: 1
|
|
||||||
compressionQuality: 50
|
|
||||||
crunchedCompression: 0
|
|
||||||
allowsAlphaSplitting: 0
|
|
||||||
overridden: 0
|
|
||||||
androidETC2FallbackOverride: 0
|
|
||||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
|
||||||
- serializedVersion: 3
|
|
||||||
buildTarget: Android
|
|
||||||
maxTextureSize: 2048
|
|
||||||
resizeAlgorithm: 0
|
|
||||||
textureFormat: -1
|
|
||||||
textureCompression: 1
|
|
||||||
compressionQuality: 50
|
|
||||||
crunchedCompression: 0
|
|
||||||
allowsAlphaSplitting: 0
|
|
||||||
overridden: 0
|
|
||||||
androidETC2FallbackOverride: 0
|
|
||||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
|
||||||
- serializedVersion: 3
|
|
||||||
buildTarget: iPhone
|
|
||||||
maxTextureSize: 2048
|
|
||||||
resizeAlgorithm: 0
|
|
||||||
textureFormat: -1
|
|
||||||
textureCompression: 1
|
|
||||||
compressionQuality: 50
|
|
||||||
crunchedCompression: 0
|
|
||||||
allowsAlphaSplitting: 0
|
|
||||||
overridden: 0
|
|
||||||
androidETC2FallbackOverride: 0
|
|
||||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
|
||||||
spriteSheet:
|
|
||||||
serializedVersion: 2
|
|
||||||
sprites: []
|
|
||||||
outline: []
|
|
||||||
physicsShape: []
|
|
||||||
bones: []
|
|
||||||
spriteID:
|
|
||||||
internalID: 0
|
|
||||||
vertices: []
|
|
||||||
indices:
|
|
||||||
edges: []
|
|
||||||
weights: []
|
|
||||||
secondaryTextures: []
|
|
||||||
nameFileIdTable: {}
|
|
||||||
spritePackingTag:
|
|
||||||
pSDRemoveMatte: 0
|
|
||||||
pSDShowRemoveMatteOption: 0
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,654 +0,0 @@
|
|||||||
%YAML 1.1
|
|
||||||
%TAG !u! tag:unity3d.com,2011:
|
|
||||||
--- !u!114 &1
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 52
|
|
||||||
m_PrefabParentObject: {fileID: 0}
|
|
||||||
m_PrefabInternal: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 0}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 1
|
|
||||||
m_Script: {fileID: 12004, guid: 0000000000000000e000000000000000, type: 0}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_PixelRect:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 0
|
|
||||||
y: 45
|
|
||||||
width: 1666
|
|
||||||
height: 958
|
|
||||||
m_ShowMode: 4
|
|
||||||
m_Title:
|
|
||||||
m_RootView: {fileID: 6}
|
|
||||||
m_MinSize: {x: 950, y: 542}
|
|
||||||
m_MaxSize: {x: 10000, y: 10000}
|
|
||||||
--- !u!114 &2
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 52
|
|
||||||
m_PrefabParentObject: {fileID: 0}
|
|
||||||
m_PrefabInternal: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 0}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 1
|
|
||||||
m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_Children: []
|
|
||||||
m_Position:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 0
|
|
||||||
y: 466
|
|
||||||
width: 290
|
|
||||||
height: 442
|
|
||||||
m_MinSize: {x: 234, y: 271}
|
|
||||||
m_MaxSize: {x: 10004, y: 10021}
|
|
||||||
m_ActualView: {fileID: 14}
|
|
||||||
m_Panes:
|
|
||||||
- {fileID: 14}
|
|
||||||
m_Selected: 0
|
|
||||||
m_LastSelected: 0
|
|
||||||
--- !u!114 &3
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 52
|
|
||||||
m_PrefabParentObject: {fileID: 0}
|
|
||||||
m_PrefabInternal: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 0}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 1
|
|
||||||
m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_Children:
|
|
||||||
- {fileID: 4}
|
|
||||||
- {fileID: 2}
|
|
||||||
m_Position:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 973
|
|
||||||
y: 0
|
|
||||||
width: 290
|
|
||||||
height: 908
|
|
||||||
m_MinSize: {x: 234, y: 492}
|
|
||||||
m_MaxSize: {x: 10004, y: 14042}
|
|
||||||
vertical: 1
|
|
||||||
controlID: 226
|
|
||||||
--- !u!114 &4
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 52
|
|
||||||
m_PrefabParentObject: {fileID: 0}
|
|
||||||
m_PrefabInternal: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 0}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 1
|
|
||||||
m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_Children: []
|
|
||||||
m_Position:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 0
|
|
||||||
y: 0
|
|
||||||
width: 290
|
|
||||||
height: 466
|
|
||||||
m_MinSize: {x: 204, y: 221}
|
|
||||||
m_MaxSize: {x: 4004, y: 4021}
|
|
||||||
m_ActualView: {fileID: 17}
|
|
||||||
m_Panes:
|
|
||||||
- {fileID: 17}
|
|
||||||
m_Selected: 0
|
|
||||||
m_LastSelected: 0
|
|
||||||
--- !u!114 &5
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 52
|
|
||||||
m_PrefabParentObject: {fileID: 0}
|
|
||||||
m_PrefabInternal: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 0}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 1
|
|
||||||
m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_Children: []
|
|
||||||
m_Position:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 0
|
|
||||||
y: 466
|
|
||||||
width: 973
|
|
||||||
height: 442
|
|
||||||
m_MinSize: {x: 202, y: 221}
|
|
||||||
m_MaxSize: {x: 4002, y: 4021}
|
|
||||||
m_ActualView: {fileID: 15}
|
|
||||||
m_Panes:
|
|
||||||
- {fileID: 15}
|
|
||||||
m_Selected: 0
|
|
||||||
m_LastSelected: 0
|
|
||||||
--- !u!114 &6
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 52
|
|
||||||
m_PrefabParentObject: {fileID: 0}
|
|
||||||
m_PrefabInternal: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 0}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 1
|
|
||||||
m_Script: {fileID: 12008, guid: 0000000000000000e000000000000000, type: 0}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_Children:
|
|
||||||
- {fileID: 7}
|
|
||||||
- {fileID: 8}
|
|
||||||
- {fileID: 9}
|
|
||||||
m_Position:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 0
|
|
||||||
y: 0
|
|
||||||
width: 1666
|
|
||||||
height: 958
|
|
||||||
m_MinSize: {x: 950, y: 542}
|
|
||||||
m_MaxSize: {x: 10000, y: 10000}
|
|
||||||
--- !u!114 &7
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 52
|
|
||||||
m_PrefabParentObject: {fileID: 0}
|
|
||||||
m_PrefabInternal: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 0}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 1
|
|
||||||
m_Script: {fileID: 12011, guid: 0000000000000000e000000000000000, type: 0}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_Children: []
|
|
||||||
m_Position:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 0
|
|
||||||
y: 0
|
|
||||||
width: 1666
|
|
||||||
height: 30
|
|
||||||
m_MinSize: {x: 0, y: 0}
|
|
||||||
m_MaxSize: {x: 0, y: 0}
|
|
||||||
m_LastLoadedLayoutName: Tutorial
|
|
||||||
--- !u!114 &8
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 52
|
|
||||||
m_PrefabParentObject: {fileID: 0}
|
|
||||||
m_PrefabInternal: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 0}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 1
|
|
||||||
m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_Children:
|
|
||||||
- {fileID: 10}
|
|
||||||
- {fileID: 3}
|
|
||||||
- {fileID: 11}
|
|
||||||
m_Position:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 0
|
|
||||||
y: 30
|
|
||||||
width: 1666
|
|
||||||
height: 908
|
|
||||||
m_MinSize: {x: 713, y: 492}
|
|
||||||
m_MaxSize: {x: 18008, y: 14042}
|
|
||||||
vertical: 0
|
|
||||||
controlID: 74
|
|
||||||
--- !u!114 &9
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 52
|
|
||||||
m_PrefabParentObject: {fileID: 0}
|
|
||||||
m_PrefabInternal: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 0}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 1
|
|
||||||
m_Script: {fileID: 12042, guid: 0000000000000000e000000000000000, type: 0}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_Children: []
|
|
||||||
m_Position:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 0
|
|
||||||
y: 938
|
|
||||||
width: 1666
|
|
||||||
height: 20
|
|
||||||
m_MinSize: {x: 0, y: 0}
|
|
||||||
m_MaxSize: {x: 0, y: 0}
|
|
||||||
--- !u!114 &10
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 52
|
|
||||||
m_PrefabParentObject: {fileID: 0}
|
|
||||||
m_PrefabInternal: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 0}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 1
|
|
||||||
m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_Children:
|
|
||||||
- {fileID: 12}
|
|
||||||
- {fileID: 5}
|
|
||||||
m_Position:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 0
|
|
||||||
y: 0
|
|
||||||
width: 973
|
|
||||||
height: 908
|
|
||||||
m_MinSize: {x: 202, y: 442}
|
|
||||||
m_MaxSize: {x: 4002, y: 8042}
|
|
||||||
vertical: 1
|
|
||||||
controlID: 75
|
|
||||||
--- !u!114 &11
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 52
|
|
||||||
m_PrefabParentObject: {fileID: 0}
|
|
||||||
m_PrefabInternal: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 0}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 1
|
|
||||||
m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_Children: []
|
|
||||||
m_Position:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 1263
|
|
||||||
y: 0
|
|
||||||
width: 403
|
|
||||||
height: 908
|
|
||||||
m_MinSize: {x: 277, y: 71}
|
|
||||||
m_MaxSize: {x: 4002, y: 4021}
|
|
||||||
m_ActualView: {fileID: 13}
|
|
||||||
m_Panes:
|
|
||||||
- {fileID: 13}
|
|
||||||
m_Selected: 0
|
|
||||||
m_LastSelected: 0
|
|
||||||
--- !u!114 &12
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 52
|
|
||||||
m_PrefabParentObject: {fileID: 0}
|
|
||||||
m_PrefabInternal: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 0}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 1
|
|
||||||
m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_Children: []
|
|
||||||
m_Position:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 0
|
|
||||||
y: 0
|
|
||||||
width: 973
|
|
||||||
height: 466
|
|
||||||
m_MinSize: {x: 202, y: 221}
|
|
||||||
m_MaxSize: {x: 4002, y: 4021}
|
|
||||||
m_ActualView: {fileID: 16}
|
|
||||||
m_Panes:
|
|
||||||
- {fileID: 16}
|
|
||||||
m_Selected: 0
|
|
||||||
m_LastSelected: 0
|
|
||||||
--- !u!114 &13
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 52
|
|
||||||
m_PrefabParentObject: {fileID: 0}
|
|
||||||
m_PrefabInternal: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 0}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 1
|
|
||||||
m_Script: {fileID: 12019, guid: 0000000000000000e000000000000000, type: 0}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_AutoRepaintOnSceneChange: 0
|
|
||||||
m_MinSize: {x: 275, y: 50}
|
|
||||||
m_MaxSize: {x: 4000, y: 4000}
|
|
||||||
m_TitleContent:
|
|
||||||
m_Text: Inspector
|
|
||||||
m_Image: {fileID: -6905738622615590433, guid: 0000000000000000d000000000000000,
|
|
||||||
type: 0}
|
|
||||||
m_Tooltip:
|
|
||||||
m_DepthBufferBits: 0
|
|
||||||
m_Pos:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 2
|
|
||||||
y: 19
|
|
||||||
width: 401
|
|
||||||
height: 887
|
|
||||||
m_ScrollPosition: {x: 0, y: 0}
|
|
||||||
m_InspectorMode: 0
|
|
||||||
m_PreviewResizer:
|
|
||||||
m_CachedPref: -160
|
|
||||||
m_ControlHash: -371814159
|
|
||||||
m_PrefName: Preview_InspectorPreview
|
|
||||||
m_PreviewWindow: {fileID: 0}
|
|
||||||
--- !u!114 &14
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 52
|
|
||||||
m_PrefabParentObject: {fileID: 0}
|
|
||||||
m_PrefabInternal: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 0}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 1
|
|
||||||
m_Script: {fileID: 12014, guid: 0000000000000000e000000000000000, type: 0}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_AutoRepaintOnSceneChange: 0
|
|
||||||
m_MinSize: {x: 230, y: 250}
|
|
||||||
m_MaxSize: {x: 10000, y: 10000}
|
|
||||||
m_TitleContent:
|
|
||||||
m_Text: Project
|
|
||||||
m_Image: {fileID: -7501376956915960154, guid: 0000000000000000d000000000000000,
|
|
||||||
type: 0}
|
|
||||||
m_Tooltip:
|
|
||||||
m_DepthBufferBits: 0
|
|
||||||
m_Pos:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 2
|
|
||||||
y: 19
|
|
||||||
width: 286
|
|
||||||
height: 421
|
|
||||||
m_SearchFilter:
|
|
||||||
m_NameFilter:
|
|
||||||
m_ClassNames: []
|
|
||||||
m_AssetLabels: []
|
|
||||||
m_AssetBundleNames: []
|
|
||||||
m_VersionControlStates: []
|
|
||||||
m_ReferencingInstanceIDs:
|
|
||||||
m_ScenePaths: []
|
|
||||||
m_ShowAllHits: 0
|
|
||||||
m_SearchArea: 0
|
|
||||||
m_Folders:
|
|
||||||
- Assets
|
|
||||||
m_ViewMode: 0
|
|
||||||
m_StartGridSize: 64
|
|
||||||
m_LastFolders:
|
|
||||||
- Assets
|
|
||||||
m_LastFoldersGridSize: -1
|
|
||||||
m_LastProjectPath: /Users/danielbrauer/Unity Projects/New Unity Project 47
|
|
||||||
m_IsLocked: 0
|
|
||||||
m_FolderTreeState:
|
|
||||||
scrollPos: {x: 0, y: 0}
|
|
||||||
m_SelectedIDs: ee240000
|
|
||||||
m_LastClickedID: 9454
|
|
||||||
m_ExpandedIDs: ee24000000ca9a3bffffff7f
|
|
||||||
m_RenameOverlay:
|
|
||||||
m_UserAcceptedRename: 0
|
|
||||||
m_Name:
|
|
||||||
m_OriginalName:
|
|
||||||
m_EditFieldRect:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 0
|
|
||||||
y: 0
|
|
||||||
width: 0
|
|
||||||
height: 0
|
|
||||||
m_UserData: 0
|
|
||||||
m_IsWaitingForDelay: 0
|
|
||||||
m_IsRenaming: 0
|
|
||||||
m_OriginalEventType: 11
|
|
||||||
m_IsRenamingFilename: 1
|
|
||||||
m_ClientGUIView: {fileID: 0}
|
|
||||||
m_SearchString:
|
|
||||||
m_CreateAssetUtility:
|
|
||||||
m_EndAction: {fileID: 0}
|
|
||||||
m_InstanceID: 0
|
|
||||||
m_Path:
|
|
||||||
m_Icon: {fileID: 0}
|
|
||||||
m_ResourceFile:
|
|
||||||
m_AssetTreeState:
|
|
||||||
scrollPos: {x: 0, y: 0}
|
|
||||||
m_SelectedIDs: 68fbffff
|
|
||||||
m_LastClickedID: 0
|
|
||||||
m_ExpandedIDs: ee240000
|
|
||||||
m_RenameOverlay:
|
|
||||||
m_UserAcceptedRename: 0
|
|
||||||
m_Name:
|
|
||||||
m_OriginalName:
|
|
||||||
m_EditFieldRect:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 0
|
|
||||||
y: 0
|
|
||||||
width: 0
|
|
||||||
height: 0
|
|
||||||
m_UserData: 0
|
|
||||||
m_IsWaitingForDelay: 0
|
|
||||||
m_IsRenaming: 0
|
|
||||||
m_OriginalEventType: 11
|
|
||||||
m_IsRenamingFilename: 1
|
|
||||||
m_ClientGUIView: {fileID: 0}
|
|
||||||
m_SearchString:
|
|
||||||
m_CreateAssetUtility:
|
|
||||||
m_EndAction: {fileID: 0}
|
|
||||||
m_InstanceID: 0
|
|
||||||
m_Path:
|
|
||||||
m_Icon: {fileID: 0}
|
|
||||||
m_ResourceFile:
|
|
||||||
m_ListAreaState:
|
|
||||||
m_SelectedInstanceIDs: 68fbffff
|
|
||||||
m_LastClickedInstanceID: -1176
|
|
||||||
m_HadKeyboardFocusLastEvent: 0
|
|
||||||
m_ExpandedInstanceIDs: c6230000
|
|
||||||
m_RenameOverlay:
|
|
||||||
m_UserAcceptedRename: 0
|
|
||||||
m_Name:
|
|
||||||
m_OriginalName:
|
|
||||||
m_EditFieldRect:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 0
|
|
||||||
y: 0
|
|
||||||
width: 0
|
|
||||||
height: 0
|
|
||||||
m_UserData: 0
|
|
||||||
m_IsWaitingForDelay: 0
|
|
||||||
m_IsRenaming: 0
|
|
||||||
m_OriginalEventType: 11
|
|
||||||
m_IsRenamingFilename: 1
|
|
||||||
m_ClientGUIView: {fileID: 0}
|
|
||||||
m_CreateAssetUtility:
|
|
||||||
m_EndAction: {fileID: 0}
|
|
||||||
m_InstanceID: 0
|
|
||||||
m_Path:
|
|
||||||
m_Icon: {fileID: 0}
|
|
||||||
m_ResourceFile:
|
|
||||||
m_NewAssetIndexInList: -1
|
|
||||||
m_ScrollPosition: {x: 0, y: 0}
|
|
||||||
m_GridSize: 64
|
|
||||||
m_DirectoriesAreaWidth: 110
|
|
||||||
--- !u!114 &15
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 52
|
|
||||||
m_PrefabParentObject: {fileID: 0}
|
|
||||||
m_PrefabInternal: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 0}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 1
|
|
||||||
m_Script: {fileID: 12015, guid: 0000000000000000e000000000000000, type: 0}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_AutoRepaintOnSceneChange: 1
|
|
||||||
m_MinSize: {x: 200, y: 200}
|
|
||||||
m_MaxSize: {x: 4000, y: 4000}
|
|
||||||
m_TitleContent:
|
|
||||||
m_Text: Game
|
|
||||||
m_Image: {fileID: -2087823869225018852, guid: 0000000000000000d000000000000000,
|
|
||||||
type: 0}
|
|
||||||
m_Tooltip:
|
|
||||||
m_DepthBufferBits: 32
|
|
||||||
m_Pos:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 0
|
|
||||||
y: 19
|
|
||||||
width: 971
|
|
||||||
height: 421
|
|
||||||
m_MaximizeOnPlay: 0
|
|
||||||
m_Gizmos: 0
|
|
||||||
m_Stats: 0
|
|
||||||
m_SelectedSizes: 00000000000000000000000000000000000000000000000000000000000000000000000000000000
|
|
||||||
m_TargetDisplay: 0
|
|
||||||
m_ZoomArea:
|
|
||||||
m_HRangeLocked: 0
|
|
||||||
m_VRangeLocked: 0
|
|
||||||
m_HBaseRangeMin: -242.75
|
|
||||||
m_HBaseRangeMax: 242.75
|
|
||||||
m_VBaseRangeMin: -101
|
|
||||||
m_VBaseRangeMax: 101
|
|
||||||
m_HAllowExceedBaseRangeMin: 1
|
|
||||||
m_HAllowExceedBaseRangeMax: 1
|
|
||||||
m_VAllowExceedBaseRangeMin: 1
|
|
||||||
m_VAllowExceedBaseRangeMax: 1
|
|
||||||
m_ScaleWithWindow: 0
|
|
||||||
m_HSlider: 0
|
|
||||||
m_VSlider: 0
|
|
||||||
m_IgnoreScrollWheelUntilClicked: 0
|
|
||||||
m_EnableMouseInput: 1
|
|
||||||
m_EnableSliderZoom: 0
|
|
||||||
m_UniformScale: 1
|
|
||||||
m_UpDirection: 1
|
|
||||||
m_DrawArea:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 0
|
|
||||||
y: 17
|
|
||||||
width: 971
|
|
||||||
height: 404
|
|
||||||
m_Scale: {x: 2, y: 2}
|
|
||||||
m_Translation: {x: 485.5, y: 202}
|
|
||||||
m_MarginLeft: 0
|
|
||||||
m_MarginRight: 0
|
|
||||||
m_MarginTop: 0
|
|
||||||
m_MarginBottom: 0
|
|
||||||
m_LastShownAreaInsideMargins:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: -242.75
|
|
||||||
y: -101
|
|
||||||
width: 485.5
|
|
||||||
height: 202
|
|
||||||
m_MinimalGUI: 1
|
|
||||||
m_defaultScale: 2
|
|
||||||
m_TargetTexture: {fileID: 0}
|
|
||||||
m_CurrentColorSpace: 0
|
|
||||||
m_LastWindowPixelSize: {x: 1942, y: 842}
|
|
||||||
m_ClearInEditMode: 1
|
|
||||||
m_NoCameraWarning: 1
|
|
||||||
m_LowResolutionForAspectRatios: 01000000000100000100
|
|
||||||
--- !u!114 &16
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 52
|
|
||||||
m_PrefabParentObject: {fileID: 0}
|
|
||||||
m_PrefabInternal: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 0}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 1
|
|
||||||
m_Script: {fileID: 12013, guid: 0000000000000000e000000000000000, type: 0}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_AutoRepaintOnSceneChange: 1
|
|
||||||
m_MinSize: {x: 200, y: 200}
|
|
||||||
m_MaxSize: {x: 4000, y: 4000}
|
|
||||||
m_TitleContent:
|
|
||||||
m_Text: Scene
|
|
||||||
m_Image: {fileID: 2318424515335265636, guid: 0000000000000000d000000000000000,
|
|
||||||
type: 0}
|
|
||||||
m_Tooltip:
|
|
||||||
m_DepthBufferBits: 32
|
|
||||||
m_Pos:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 0
|
|
||||||
y: 19
|
|
||||||
width: 971
|
|
||||||
height: 445
|
|
||||||
m_SceneLighting: 1
|
|
||||||
lastFramingTime: 0
|
|
||||||
m_2DMode: 0
|
|
||||||
m_isRotationLocked: 0
|
|
||||||
m_AudioPlay: 0
|
|
||||||
m_Position:
|
|
||||||
m_Target: {x: 0, y: 0, z: 0}
|
|
||||||
speed: 2
|
|
||||||
m_Value: {x: 0, y: 0, z: 0}
|
|
||||||
m_RenderMode: 0
|
|
||||||
m_ValidateTrueMetals: 0
|
|
||||||
m_SceneViewState:
|
|
||||||
showFog: 1
|
|
||||||
showMaterialUpdate: 0
|
|
||||||
showSkybox: 1
|
|
||||||
showFlares: 1
|
|
||||||
showImageEffects: 1
|
|
||||||
grid:
|
|
||||||
xGrid:
|
|
||||||
m_Target: 0
|
|
||||||
speed: 2
|
|
||||||
m_Value: 0
|
|
||||||
yGrid:
|
|
||||||
m_Target: 1
|
|
||||||
speed: 2
|
|
||||||
m_Value: 1
|
|
||||||
zGrid:
|
|
||||||
m_Target: 0
|
|
||||||
speed: 2
|
|
||||||
m_Value: 0
|
|
||||||
m_Rotation:
|
|
||||||
m_Target: {x: -0.08717229, y: 0.89959055, z: -0.21045254, w: -0.3726226}
|
|
||||||
speed: 2
|
|
||||||
m_Value: {x: -0.08717229, y: 0.89959055, z: -0.21045254, w: -0.3726226}
|
|
||||||
m_Size:
|
|
||||||
m_Target: 10
|
|
||||||
speed: 2
|
|
||||||
m_Value: 10
|
|
||||||
m_Ortho:
|
|
||||||
m_Target: 0
|
|
||||||
speed: 2
|
|
||||||
m_Value: 0
|
|
||||||
m_LastSceneViewRotation: {x: 0, y: 0, z: 0, w: 0}
|
|
||||||
m_LastSceneViewOrtho: 0
|
|
||||||
m_ReplacementShader: {fileID: 0}
|
|
||||||
m_ReplacementString:
|
|
||||||
m_LastLockedObject: {fileID: 0}
|
|
||||||
m_ViewIsLockedToObject: 0
|
|
||||||
--- !u!114 &17
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 52
|
|
||||||
m_PrefabParentObject: {fileID: 0}
|
|
||||||
m_PrefabInternal: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 0}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 1
|
|
||||||
m_Script: {fileID: 12061, guid: 0000000000000000e000000000000000, type: 0}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
m_AutoRepaintOnSceneChange: 0
|
|
||||||
m_MinSize: {x: 200, y: 200}
|
|
||||||
m_MaxSize: {x: 4000, y: 4000}
|
|
||||||
m_TitleContent:
|
|
||||||
m_Text: Hierarchy
|
|
||||||
m_Image: {fileID: -590624980919486359, guid: 0000000000000000d000000000000000,
|
|
||||||
type: 0}
|
|
||||||
m_Tooltip:
|
|
||||||
m_DepthBufferBits: 0
|
|
||||||
m_Pos:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 2
|
|
||||||
y: 19
|
|
||||||
width: 286
|
|
||||||
height: 445
|
|
||||||
m_TreeViewState:
|
|
||||||
scrollPos: {x: 0, y: 0}
|
|
||||||
m_SelectedIDs: 68fbffff
|
|
||||||
m_LastClickedID: -1176
|
|
||||||
m_ExpandedIDs: 7efbffff00000000
|
|
||||||
m_RenameOverlay:
|
|
||||||
m_UserAcceptedRename: 0
|
|
||||||
m_Name:
|
|
||||||
m_OriginalName:
|
|
||||||
m_EditFieldRect:
|
|
||||||
serializedVersion: 2
|
|
||||||
x: 0
|
|
||||||
y: 0
|
|
||||||
width: 0
|
|
||||||
height: 0
|
|
||||||
m_UserData: 0
|
|
||||||
m_IsWaitingForDelay: 0
|
|
||||||
m_IsRenaming: 0
|
|
||||||
m_OriginalEventType: 11
|
|
||||||
m_IsRenamingFilename: 0
|
|
||||||
m_ClientGUIView: {fileID: 0}
|
|
||||||
m_SearchString:
|
|
||||||
m_ExpandedScenes:
|
|
||||||
-
|
|
||||||
m_CurrenRootInstanceID: 0
|
|
||||||
m_Locked: 0
|
|
||||||
m_CurrentSortingName: TransformSorting
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: eabc9546105bf4accac1fd62a63e88e6
|
|
||||||
timeCreated: 1487337779
|
|
||||||
licenseType: Store
|
|
||||||
DefaultImporter:
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 5a9bcd70e6a4b4b05badaa72e827d8e0
|
|
||||||
folderAsset: yes
|
|
||||||
timeCreated: 1475835190
|
|
||||||
licenseType: Store
|
|
||||||
DefaultImporter:
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 3ad9b87dffba344c89909c6d1b1c17e1
|
|
||||||
folderAsset: yes
|
|
||||||
timeCreated: 1475593892
|
|
||||||
licenseType: Store
|
|
||||||
DefaultImporter:
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,242 +0,0 @@
|
|||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEditor;
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
[CustomEditor(typeof(Readme))]
|
|
||||||
[InitializeOnLoad]
|
|
||||||
public class ReadmeEditor : Editor
|
|
||||||
{
|
|
||||||
static string s_ShowedReadmeSessionStateName = "ReadmeEditor.showedReadme";
|
|
||||||
|
|
||||||
static string s_ReadmeSourceDirectory = "Assets/TutorialInfo";
|
|
||||||
|
|
||||||
const float k_Space = 16f;
|
|
||||||
|
|
||||||
static ReadmeEditor()
|
|
||||||
{
|
|
||||||
EditorApplication.delayCall += SelectReadmeAutomatically;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void RemoveTutorial()
|
|
||||||
{
|
|
||||||
if (EditorUtility.DisplayDialog("Remove Readme Assets",
|
|
||||||
|
|
||||||
$"All contents under {s_ReadmeSourceDirectory} will be removed, are you sure you want to proceed?",
|
|
||||||
"Proceed",
|
|
||||||
"Cancel"))
|
|
||||||
{
|
|
||||||
if (Directory.Exists(s_ReadmeSourceDirectory))
|
|
||||||
{
|
|
||||||
FileUtil.DeleteFileOrDirectory(s_ReadmeSourceDirectory);
|
|
||||||
FileUtil.DeleteFileOrDirectory(s_ReadmeSourceDirectory + ".meta");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.Log($"Could not find the Readme folder at {s_ReadmeSourceDirectory}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var readmeAsset = SelectReadme();
|
|
||||||
if (readmeAsset != null)
|
|
||||||
{
|
|
||||||
var path = AssetDatabase.GetAssetPath(readmeAsset);
|
|
||||||
FileUtil.DeleteFileOrDirectory(path + ".meta");
|
|
||||||
FileUtil.DeleteFileOrDirectory(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetDatabase.Refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void SelectReadmeAutomatically()
|
|
||||||
{
|
|
||||||
if (!SessionState.GetBool(s_ShowedReadmeSessionStateName, false))
|
|
||||||
{
|
|
||||||
var readme = SelectReadme();
|
|
||||||
SessionState.SetBool(s_ShowedReadmeSessionStateName, true);
|
|
||||||
|
|
||||||
if (readme && !readme.loadedLayout)
|
|
||||||
{
|
|
||||||
LoadLayout();
|
|
||||||
readme.loadedLayout = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void LoadLayout()
|
|
||||||
{
|
|
||||||
var assembly = typeof(EditorApplication).Assembly;
|
|
||||||
var windowLayoutType = assembly.GetType("UnityEditor.WindowLayout", true);
|
|
||||||
var method = windowLayoutType.GetMethod("LoadWindowLayout", BindingFlags.Public | BindingFlags.Static);
|
|
||||||
method.Invoke(null, new object[] { Path.Combine(Application.dataPath, "TutorialInfo/Layout.wlt"), false });
|
|
||||||
}
|
|
||||||
|
|
||||||
static Readme SelectReadme()
|
|
||||||
{
|
|
||||||
var ids = AssetDatabase.FindAssets("Readme t:Readme");
|
|
||||||
if (ids.Length == 1)
|
|
||||||
{
|
|
||||||
var readmeObject = AssetDatabase.LoadMainAssetAtPath(AssetDatabase.GUIDToAssetPath(ids[0]));
|
|
||||||
|
|
||||||
Selection.objects = new UnityEngine.Object[] { readmeObject };
|
|
||||||
|
|
||||||
return (Readme)readmeObject;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.Log("Couldn't find a readme");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnHeaderGUI()
|
|
||||||
{
|
|
||||||
var readme = (Readme)target;
|
|
||||||
Init();
|
|
||||||
|
|
||||||
var iconWidth = Mathf.Min(EditorGUIUtility.currentViewWidth / 3f - 20f, 128f);
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal("In BigTitle");
|
|
||||||
{
|
|
||||||
if (readme.icon != null)
|
|
||||||
{
|
|
||||||
GUILayout.Space(k_Space);
|
|
||||||
GUILayout.Label(readme.icon, GUILayout.Width(iconWidth), GUILayout.Height(iconWidth));
|
|
||||||
}
|
|
||||||
GUILayout.Space(k_Space);
|
|
||||||
GUILayout.BeginVertical();
|
|
||||||
{
|
|
||||||
|
|
||||||
GUILayout.FlexibleSpace();
|
|
||||||
GUILayout.Label(readme.title, TitleStyle);
|
|
||||||
GUILayout.FlexibleSpace();
|
|
||||||
}
|
|
||||||
GUILayout.EndVertical();
|
|
||||||
GUILayout.FlexibleSpace();
|
|
||||||
}
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnInspectorGUI()
|
|
||||||
{
|
|
||||||
var readme = (Readme)target;
|
|
||||||
Init();
|
|
||||||
|
|
||||||
foreach (var section in readme.sections)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(section.heading))
|
|
||||||
{
|
|
||||||
GUILayout.Label(section.heading, HeadingStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(section.text))
|
|
||||||
{
|
|
||||||
GUILayout.Label(section.text, BodyStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(section.linkText))
|
|
||||||
{
|
|
||||||
if (LinkLabel(new GUIContent(section.linkText)))
|
|
||||||
{
|
|
||||||
Application.OpenURL(section.url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.Space(k_Space);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GUILayout.Button("Remove Readme Assets", ButtonStyle))
|
|
||||||
{
|
|
||||||
RemoveTutorial();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool m_Initialized;
|
|
||||||
|
|
||||||
GUIStyle LinkStyle
|
|
||||||
{
|
|
||||||
get { return m_LinkStyle; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[SerializeField]
|
|
||||||
GUIStyle m_LinkStyle;
|
|
||||||
|
|
||||||
GUIStyle TitleStyle
|
|
||||||
{
|
|
||||||
get { return m_TitleStyle; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[SerializeField]
|
|
||||||
GUIStyle m_TitleStyle;
|
|
||||||
|
|
||||||
GUIStyle HeadingStyle
|
|
||||||
{
|
|
||||||
get { return m_HeadingStyle; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[SerializeField]
|
|
||||||
GUIStyle m_HeadingStyle;
|
|
||||||
|
|
||||||
GUIStyle BodyStyle
|
|
||||||
{
|
|
||||||
get { return m_BodyStyle; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[SerializeField]
|
|
||||||
GUIStyle m_BodyStyle;
|
|
||||||
|
|
||||||
GUIStyle ButtonStyle
|
|
||||||
{
|
|
||||||
get { return m_ButtonStyle; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[SerializeField]
|
|
||||||
GUIStyle m_ButtonStyle;
|
|
||||||
|
|
||||||
void Init()
|
|
||||||
{
|
|
||||||
if (m_Initialized)
|
|
||||||
return;
|
|
||||||
m_BodyStyle = new GUIStyle(EditorStyles.label);
|
|
||||||
m_BodyStyle.wordWrap = true;
|
|
||||||
m_BodyStyle.fontSize = 14;
|
|
||||||
m_BodyStyle.richText = true;
|
|
||||||
|
|
||||||
m_TitleStyle = new GUIStyle(m_BodyStyle);
|
|
||||||
m_TitleStyle.fontSize = 26;
|
|
||||||
|
|
||||||
m_HeadingStyle = new GUIStyle(m_BodyStyle);
|
|
||||||
m_HeadingStyle.fontStyle = FontStyle.Bold;
|
|
||||||
m_HeadingStyle.fontSize = 18;
|
|
||||||
|
|
||||||
m_LinkStyle = new GUIStyle(m_BodyStyle);
|
|
||||||
m_LinkStyle.wordWrap = false;
|
|
||||||
|
|
||||||
// Match selection color which works nicely for both light and dark skins
|
|
||||||
m_LinkStyle.normal.textColor = new Color(0x00 / 255f, 0x78 / 255f, 0xDA / 255f, 1f);
|
|
||||||
m_LinkStyle.stretchWidth = false;
|
|
||||||
|
|
||||||
m_ButtonStyle = new GUIStyle(EditorStyles.miniButton);
|
|
||||||
m_ButtonStyle.fontStyle = FontStyle.Bold;
|
|
||||||
|
|
||||||
m_Initialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LinkLabel(GUIContent label, params GUILayoutOption[] options)
|
|
||||||
{
|
|
||||||
var position = GUILayoutUtility.GetRect(label, LinkStyle, options);
|
|
||||||
|
|
||||||
Handles.BeginGUI();
|
|
||||||
Handles.color = LinkStyle.normal.textColor;
|
|
||||||
Handles.DrawLine(new Vector3(position.xMin, position.yMax), new Vector3(position.xMax, position.yMax));
|
|
||||||
Handles.color = Color.white;
|
|
||||||
Handles.EndGUI();
|
|
||||||
|
|
||||||
EditorGUIUtility.AddCursorRect(position, MouseCursor.Link);
|
|
||||||
|
|
||||||
return GUI.Button(position, label, LinkStyle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 476cc7d7cd9874016adc216baab94a0a
|
|
||||||
timeCreated: 1484146680
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
public class Readme : ScriptableObject
|
|
||||||
{
|
|
||||||
public Texture2D icon;
|
|
||||||
public string title;
|
|
||||||
public Section[] sections;
|
|
||||||
public bool loadedLayout;
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
public class Section
|
|
||||||
{
|
|
||||||
public string heading, text, linkText, url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: fcf7219bab7fe46a1ad266029b2fee19
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences:
|
|
||||||
- icon: {instanceID: 0}
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {fileID: 2800000, guid: a186f8a87ca4f4d3aa864638ad5dfb65, type: 3}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -4,7 +4,8 @@
|
|||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node src/index.js",
|
"start": "node src/index.js",
|
||||||
"dev": "node --watch src/index.js"
|
"dev": "node --watch src/index.js",
|
||||||
|
"schema:gen": "schema-codegen src/schema/GameState.js --csharp --namespace RolldSchema --output ../../game/Assets/Scripts/Network/Generated/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@colyseus/core": "^0.17.39",
|
"@colyseus/core": "^0.17.39",
|
||||||
|
|||||||
@@ -56,8 +56,12 @@ const gameServer = new Server({
|
|||||||
|
|
||||||
app.post('/stats/update', (req, res) => {
|
app.post('/stats/update', (req, res) => {
|
||||||
const parsed = statsUpdateSchema.safeParse(req.body);
|
const parsed = statsUpdateSchema.safeParse(req.body);
|
||||||
if (!parsed.success) return res.status(400).json({ error: parsed.error.issues });
|
if (!parsed.success) {
|
||||||
|
console.warn('[Stats] Bad update request:', JSON.stringify(parsed.error.issues));
|
||||||
|
return res.status(400).json({ error: parsed.error.issues });
|
||||||
|
}
|
||||||
const ok = Stats.update(parsed.data.name, parsed.data.stats);
|
const ok = Stats.update(parsed.data.name, parsed.data.stats);
|
||||||
|
console.log(`[Stats] Update for "${parsed.data.name}": ok=${ok}`, JSON.stringify(parsed.data.stats));
|
||||||
res.json({ ok });
|
res.json({ ok });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,28 +2,27 @@ const { Room } = require("@colyseus/core");
|
|||||||
const { GameState, Player } = require("../schema/GameState");
|
const { GameState, Player } = require("../schema/GameState");
|
||||||
const Chat = require("../chat/ChatManager");
|
const Chat = require("../chat/ChatManager");
|
||||||
|
|
||||||
const LOBBY_TIMEOUT = 30;
|
// Free-roam: no rounds, no phases, no checkpoints. Players connect, move around, leave.
|
||||||
const COUNTDOWN_DURATION = 3;
|
// Schema fields (phase, countdown, roundNumber, etc.) are kept to preserve the
|
||||||
const ROUND_END_DURATION = 5;
|
// handshake — but state.phase is pinned to "playing" forever and other fields are
|
||||||
|
// left at their default values. To fully drop them, regenerate the C# schema and
|
||||||
const QUALIFY_RATIO = 0.6;
|
// rebuild WebGL.
|
||||||
|
|
||||||
class ArenaRoom extends Room {
|
class ArenaRoom extends Room {
|
||||||
maxClients = 20;
|
maxClients = 20;
|
||||||
|
|
||||||
onCreate(options) {
|
onCreate(options) {
|
||||||
this.setState(new GameState());
|
this.setState(new GameState());
|
||||||
|
this.state.phase = "playing"; // pinned: free-roam, no state machine
|
||||||
|
this.state.gameMode = "free";
|
||||||
this.setPatchRate(16); // ~62.5 Hz
|
this.setPatchRate(16); // ~62.5 Hz
|
||||||
this.setMetadata({ name: options?.roomName || ('Salle #' + this.roomId.substring(0, 6)) });
|
this.setMetadata({ name: options?.roomName || ('Salle #' + this.roomId.substring(0, 6)) });
|
||||||
|
|
||||||
this._phaseTimer = null;
|
console.log(`[ArenaRoom] Room ${this.roomId} created (free-roam)`);
|
||||||
this._lobbyTimer = null;
|
|
||||||
|
|
||||||
console.log(`[ArenaRoom] Room ${this.roomId} created`);
|
|
||||||
|
|
||||||
this.onMessage("position", (client, data) => {
|
this.onMessage("position", (client, data) => {
|
||||||
const player = this.state.players.get(client.sessionId);
|
const player = this.state.players.get(client.sessionId);
|
||||||
if (!player || player.isEliminated) return;
|
if (!player) return;
|
||||||
player.x = data.x ?? player.x;
|
player.x = data.x ?? player.x;
|
||||||
player.y = data.y ?? player.y;
|
player.y = data.y ?? player.y;
|
||||||
player.z = data.z ?? player.z;
|
player.z = data.z ?? player.z;
|
||||||
@@ -40,14 +39,6 @@ class ArenaRoom extends Room {
|
|||||||
player.t = Date.now();
|
player.t = Date.now();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.onMessage("ready", (client) => {
|
|
||||||
const player = this.state.players.get(client.sessionId);
|
|
||||||
if (!player || this.state.phase !== "lobby") return;
|
|
||||||
player.isReady = true;
|
|
||||||
console.log(`[ArenaRoom] ${client.sessionId} ready`);
|
|
||||||
this._checkAllReady();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.onMessage("chat", (client, data) => {
|
this.onMessage("chat", (client, data) => {
|
||||||
const player = this.state.players.get(client.sessionId);
|
const player = this.state.players.get(client.sessionId);
|
||||||
if (!player || !data.text) return;
|
if (!player || !data.text) return;
|
||||||
@@ -55,18 +46,9 @@ class ArenaRoom extends Room {
|
|||||||
if (msg) this.broadcast("chat", msg);
|
if (msg) this.broadcast("chat", msg);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.onMessage("checkpointReached", (client, data) => {
|
// Accept legacy "ready" / "checkpointReached" silently so old clients don't error.
|
||||||
if (this.state.phase !== "playing") return;
|
this.onMessage("ready", () => {});
|
||||||
const player = this.state.players.get(client.sessionId);
|
this.onMessage("checkpointReached", () => {});
|
||||||
if (!player || player.isEliminated || player.isQualified) return;
|
|
||||||
const expected = player.checkpointIndex;
|
|
||||||
if (data.index !== expected) return;
|
|
||||||
player.checkpointIndex = data.index + 1;
|
|
||||||
const TOTAL_CHECKPOINTS = 5;
|
|
||||||
if (player.checkpointIndex >= TOTAL_CHECKPOINTS) {
|
|
||||||
this._qualifyPlayer(client.sessionId, "finish");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onJoin(client, options) {
|
onJoin(client, options) {
|
||||||
@@ -82,199 +64,22 @@ class ArenaRoom extends Room {
|
|||||||
player.z = spawn.z;
|
player.z = spawn.z;
|
||||||
player.t = Date.now();
|
player.t = Date.now();
|
||||||
this.state.players.set(client.sessionId, player);
|
this.state.players.set(client.sessionId, player);
|
||||||
this._updatePlayersAlive();
|
|
||||||
|
|
||||||
if (this.state.players.size === 1 && this.state.phase === "lobby") {
|
|
||||||
this._startLobbyTimer();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onLeave(client, consented) {
|
onLeave(client, consented) {
|
||||||
console.log(`[ArenaRoom] ${client.sessionId} left`);
|
console.log(`[ArenaRoom] ${client.sessionId} left`);
|
||||||
this.state.players.delete(client.sessionId);
|
this.state.players.delete(client.sessionId);
|
||||||
this._updatePlayersAlive();
|
|
||||||
if (this.state.phase === "playing") {
|
|
||||||
this._checkRoundEndCondition();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDispose() {
|
onDispose() {
|
||||||
this._clearAllTimers();
|
|
||||||
console.log(`[ArenaRoom] Room ${this.roomId} disposed`);
|
console.log(`[ArenaRoom] Room ${this.roomId} disposed`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Phase transitions ──────────────────────────────────────────────
|
|
||||||
|
|
||||||
_startLobbyTimer() {
|
|
||||||
if (this._lobbyTimer) return;
|
|
||||||
this._lobbyTimer = setTimeout(() => this._startCountdown(), LOBBY_TIMEOUT * 1000);
|
|
||||||
console.log(`[ArenaRoom] Lobby timer started (${LOBBY_TIMEOUT}s)`);
|
|
||||||
}
|
|
||||||
|
|
||||||
_checkAllReady() {
|
|
||||||
if (this.state.players.size < 2) return;
|
|
||||||
let allReady = true;
|
|
||||||
this.state.players.forEach((p) => { if (!p.isReady) allReady = false; });
|
|
||||||
if (allReady) {
|
|
||||||
clearTimeout(this._lobbyTimer);
|
|
||||||
this._lobbyTimer = null;
|
|
||||||
this._startCountdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_startCountdown() {
|
|
||||||
if (this.state.phase !== "lobby") return;
|
|
||||||
this.state.phase = "countdown";
|
|
||||||
this.state.countdown = COUNTDOWN_DURATION;
|
|
||||||
console.log(`[ArenaRoom] Countdown started`);
|
|
||||||
|
|
||||||
const tick = () => {
|
|
||||||
this.state.countdown -= 1;
|
|
||||||
if (this.state.countdown <= 0) {
|
|
||||||
this._startPlaying();
|
|
||||||
} else {
|
|
||||||
this._phaseTimer = setTimeout(tick, 1000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this._phaseTimer = setTimeout(tick, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
_startPlaying() {
|
|
||||||
this.state.gameMode = "race";
|
|
||||||
this.state.phase = "playing";
|
|
||||||
this.state.countdown = 0;
|
|
||||||
|
|
||||||
this.state.players.forEach((p) => {
|
|
||||||
p.isEliminated = false;
|
|
||||||
p.isQualified = false;
|
|
||||||
p.isReady = false;
|
|
||||||
p.checkpointIndex = 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
this._updatePlayersAlive();
|
|
||||||
|
|
||||||
this.broadcast("roundStart", {
|
|
||||||
round: this.state.roundNumber,
|
|
||||||
mode: this.state.gameMode,
|
|
||||||
totalRounds: this.state.totalRounds,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`[ArenaRoom] Round ${this.state.roundNumber} started (race)`);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_endRound() {
|
|
||||||
if (this.state.phase !== "playing") return;
|
|
||||||
this._clearAllTimers();
|
|
||||||
this.state.phase = "roundEnd";
|
|
||||||
this.broadcast("roundEnd", { round: this.state.roundNumber });
|
|
||||||
console.log(`[ArenaRoom] Round ${this.state.roundNumber} ended`);
|
|
||||||
|
|
||||||
if (this.state.roundNumber >= this.state.totalRounds) {
|
|
||||||
this._phaseTimer = setTimeout(() => this._endGame(), ROUND_END_DURATION * 1000);
|
|
||||||
} else {
|
|
||||||
this._phaseTimer = setTimeout(() => this._nextRound(), ROUND_END_DURATION * 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_nextRound() {
|
|
||||||
this.state.roundNumber += 1;
|
|
||||||
this.state.phase = "lobby";
|
|
||||||
this.state.players.forEach((p) => {
|
|
||||||
p.isReady = false;
|
|
||||||
const spawn = this._findSpawnPosition();
|
|
||||||
p.x = spawn.x; p.y = spawn.y; p.z = spawn.z;
|
|
||||||
});
|
|
||||||
this._updatePlayersAlive();
|
|
||||||
this._lobbyTimer = null;
|
|
||||||
this._startLobbyTimer();
|
|
||||||
console.log(`[ArenaRoom] Lobby for round ${this.state.roundNumber}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
_endGame() {
|
|
||||||
this.state.phase = "gameEnd";
|
|
||||||
let winner = "";
|
|
||||||
let best = -1;
|
|
||||||
this.state.players.forEach((p) => {
|
|
||||||
const score = p.isQualified ? 1000 : p.checkpointIndex;
|
|
||||||
if (score > best) { best = score; winner = p.name; }
|
|
||||||
});
|
|
||||||
this.state.winnerName = winner;
|
|
||||||
this.broadcast("gameEnd", { winner });
|
|
||||||
console.log(`[ArenaRoom] Game over — winner: ${winner}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Elimination helpers ─────────────────────────────────────────────
|
|
||||||
|
|
||||||
_eliminatePlayer(sessionId, reason) {
|
|
||||||
const player = this.state.players.get(sessionId);
|
|
||||||
if (!player || player.isEliminated || player.isQualified) return;
|
|
||||||
player.isEliminated = true;
|
|
||||||
this._updatePlayersAlive();
|
|
||||||
this.broadcast("eliminated", { sessionId, name: player.name, reason });
|
|
||||||
console.log(`[ArenaRoom] ${player.name} (${sessionId}) eliminated: ${reason}`);
|
|
||||||
this._checkRoundEndCondition();
|
|
||||||
}
|
|
||||||
|
|
||||||
_qualifyPlayer(sessionId, reason) {
|
|
||||||
const player = this.state.players.get(sessionId);
|
|
||||||
if (!player || player.isQualified || player.isEliminated) return;
|
|
||||||
player.isQualified = true;
|
|
||||||
this._updatePlayersAlive();
|
|
||||||
this.broadcast("qualified", { sessionId, name: player.name });
|
|
||||||
console.log(`[ArenaRoom] ${player.name} (${sessionId}) qualified: ${reason}`);
|
|
||||||
|
|
||||||
const totalActive = this._getActiveCount();
|
|
||||||
const qualifiedCount = this._getQualifiedCount();
|
|
||||||
const toQualify = Math.ceil(totalActive * QUALIFY_RATIO);
|
|
||||||
if (qualifiedCount >= toQualify) {
|
|
||||||
this.state.players.forEach((p, id) => {
|
|
||||||
if (!p.isQualified && !p.isEliminated) {
|
|
||||||
this._eliminatePlayer(id, "too_slow");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this._endRound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_checkRoundEndCondition() {
|
|
||||||
if (this.state.phase !== "playing") return;
|
|
||||||
const alive = this._getAliveCount();
|
|
||||||
if (alive === 0) this._endRound();
|
|
||||||
}
|
|
||||||
|
|
||||||
_getAliveCount() {
|
|
||||||
let n = 0;
|
|
||||||
this.state.players.forEach((p) => { if (!p.isEliminated && !p.isQualified) n++; });
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
_getQualifiedCount() {
|
|
||||||
let n = 0;
|
|
||||||
this.state.players.forEach((p) => { if (p.isQualified) n++; });
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
_getActiveCount() {
|
|
||||||
let n = 0;
|
|
||||||
this.state.players.forEach((p) => { if (!p.isEliminated) n++; });
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
_updatePlayersAlive() {
|
|
||||||
this.state.playersAlive = this._getAliveCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
_clearAllTimers() {
|
|
||||||
if (this._phaseTimer) { clearTimeout(this._phaseTimer); this._phaseTimer = null; }
|
|
||||||
if (this._lobbyTimer) { clearTimeout(this._lobbyTimer); this._lobbyTimer = null; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Spawn helper ────────────────────────────────────────────────────
|
// ─── Spawn helper ────────────────────────────────────────────────────
|
||||||
|
|
||||||
_findSpawnPosition() {
|
_findSpawnPosition() {
|
||||||
const MIN_DIST = 3.0;
|
const MIN_DIST = 5.0;
|
||||||
const SPAWN_Y = 5;
|
const SPAWN_Y = 1.5;
|
||||||
const RANGE = 20;
|
const RANGE = 20;
|
||||||
const existing = [];
|
const existing = [];
|
||||||
this.state.players.forEach((p) => existing.push({ x: p.x, z: p.z }));
|
this.state.players.forEach((p) => existing.push({ x: p.x, z: p.z }));
|
||||||
@@ -285,7 +90,7 @@ class ArenaRoom extends Room {
|
|||||||
|
|
||||||
let best = { x: 0, y: SPAWN_Y, z: 0 };
|
let best = { x: 0, y: SPAWN_Y, z: 0 };
|
||||||
let bestDist = 0;
|
let bestDist = 0;
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 20; i++) {
|
||||||
const cx = (Math.random() - 0.5) * RANGE;
|
const cx = (Math.random() - 0.5) * RANGE;
|
||||||
const cz = (Math.random() - 0.5) * RANGE;
|
const cz = (Math.random() - 0.5) * RANGE;
|
||||||
let minD = Infinity;
|
let minD = Infinity;
|
||||||
|
|||||||
Reference in New Issue
Block a user