Compare commits
122 Commits
Gameclient
...
4655e57aaf
| Author | SHA1 | Date | |
|---|---|---|---|
| 4655e57aaf | |||
| b2aa499a59 | |||
| 413b0df60b | |||
| 1593daae43 | |||
| 13f62000b3 | |||
| e96ce2de20 | |||
| d0f89893a5 | |||
| 015c9b5caa | |||
| 31522cda01 | |||
|
|
213e894640 | ||
| dea21e2af5 | |||
| 4f9aa12cad | |||
|
|
abc5801b18 | ||
|
|
f427dbb6e7 | ||
|
|
2d5b6fae60 | ||
|
|
3b2a5a0677 | ||
| e776b9476e | |||
| 37797d5f7d | |||
| 645bc8203b | |||
| c2566b8db0 | |||
| fed08d08ad | |||
| 843c121666 | |||
| a15a9790b5 | |||
| dfec8df767 | |||
| 5b8e4eaeac | |||
| 9571be257c | |||
| 03c0b158a4 | |||
| b0945c9bdb | |||
| 28a81b6014 | |||
| ab6938e6cf | |||
| c5703fc92a | |||
|
|
e4970c738f | ||
| 82c120f362 | |||
| 099f3ec939 | |||
|
|
bd646df706 | ||
|
|
8ab0a350f3 | ||
| e0a4cf31b2 | |||
| 1aa3d2a787 | |||
|
|
f9d2a951da | ||
|
|
059cfa6d28 | ||
|
|
f2931c1c0b | ||
|
|
176bb7a704 | ||
| d7211e62de | |||
|
|
2c13a899d7 | ||
| a2a55a7135 | |||
| a5e657ef05 | |||
| 2040b59593 | |||
|
|
54bfcf745b | ||
| 8bec2f0cf8 | |||
|
|
9a99405f4b | ||
|
|
b4b746de25 | ||
|
|
d2e3adbacc | ||
| 023bddc91b | |||
| c8d8b6b802 | |||
| 1082fc9ad0 | |||
| 2ae5d28cc9 | |||
|
|
dd5aefcb49 | ||
| 2d7b5481c0 | |||
|
|
6d660f5d89 | ||
| 37c6d7a552 | |||
| e55aa6b258 | |||
| d7838c0a04 | |||
| 90fd3514fb | |||
| 9a736e1d53 | |||
| 2f86bab336 | |||
| 8948cbdb14 | |||
| 7e89f37d90 | |||
|
|
207f997254 | ||
| f9ceea4992 | |||
|
|
d886f97e14 | ||
| ad604daec7 | |||
| e0b808faed | |||
| 666f731b6d | |||
| a9ea88c125 | |||
|
|
3ac90ed7b6 | ||
| 700e6bfbfc | |||
| ab6263cb10 | |||
| abbe4842fe | |||
| f800e78f14 | |||
| e29581cc21 | |||
| 74fa735322 | |||
| 208696487e | |||
| 3879c0879d | |||
| 0a163b2a1e | |||
| 1de91b0d57 | |||
| b0e90221dc | |||
| dcb1066d80 | |||
| d80ac111c2 | |||
| 4fdfdea5cf | |||
| 32c7589ab3 | |||
|
|
c11ca05ea8 | ||
| 72a75c121a | |||
| 677c875c58 | |||
| 44155796d0 | |||
| a0a675676d | |||
| 5b166244b2 | |||
| 11575ef9b1 | |||
| 76a38d741c | |||
| 114a0d3997 | |||
| ce6e4450e6 | |||
| 95f2f63259 | |||
| dc5ed7d49f | |||
|
|
2fadf819cc | ||
| a1465de9a0 | |||
| ff9a2cebd3 | |||
| f7926a218e | |||
| 9defaa314a | |||
| e14a3ddf2b | |||
| 5bd6eabec6 | |||
| f2ebd125f3 | |||
| 2fdfabe2b8 | |||
| 4a84e729f3 | |||
| 055e8aa426 | |||
| 1957d26b1f | |||
| 8f62dc8873 | |||
| 4b8e4c69f5 | |||
| e086bedb19 | |||
| 13300e885b | |||
| a73f75ffa4 | |||
| e9beb05083 | |||
|
|
9f71b6a84a | ||
| a04ce40779 |
5
.gitignore
vendored
@@ -303,6 +303,9 @@ PublishScripts/
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
@@ -496,3 +499,5 @@ FodyWeavers.xsd
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/unity,visualstudiocode,visualstudio,vim
|
||||
|
||||
|
||||
.utmp/
|
||||
|
||||
5
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"visualstudiotoolsforunity.vstuc"
|
||||
]
|
||||
}
|
||||
10
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Attach to Unity",
|
||||
"type": "vstuc",
|
||||
"request": "attach"
|
||||
}
|
||||
]
|
||||
}
|
||||
72
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"files.exclude": {
|
||||
"**/.DS_Store": true,
|
||||
"**/.git": true,
|
||||
"**/.vs": true,
|
||||
"**/.gitmodules": true,
|
||||
"**/.vsconfig": true,
|
||||
"**/*.booproj": true,
|
||||
"**/*.pidb": true,
|
||||
"**/*.suo": true,
|
||||
"**/*.user": true,
|
||||
"**/*.userprefs": true,
|
||||
"**/*.unityproj": true,
|
||||
"**/*.dll": true,
|
||||
"**/*.exe": true,
|
||||
"**/*.pdf": true,
|
||||
"**/*.mid": true,
|
||||
"**/*.midi": true,
|
||||
"**/*.wav": true,
|
||||
"**/*.gif": true,
|
||||
"**/*.ico": true,
|
||||
"**/*.jpg": true,
|
||||
"**/*.jpeg": true,
|
||||
"**/*.png": true,
|
||||
"**/*.psd": true,
|
||||
"**/*.tga": true,
|
||||
"**/*.tif": true,
|
||||
"**/*.tiff": true,
|
||||
"**/*.3ds": true,
|
||||
"**/*.3DS": true,
|
||||
"**/*.fbx": true,
|
||||
"**/*.FBX": true,
|
||||
"**/*.lxo": true,
|
||||
"**/*.LXO": true,
|
||||
"**/*.ma": true,
|
||||
"**/*.MA": true,
|
||||
"**/*.obj": true,
|
||||
"**/*.OBJ": true,
|
||||
"**/*.asset": true,
|
||||
"**/*.cubemap": true,
|
||||
"**/*.flare": true,
|
||||
"**/*.mat": true,
|
||||
"**/*.meta": true,
|
||||
"**/*.prefab": true,
|
||||
"**/*.unity": true,
|
||||
"build/": true,
|
||||
"Build/": true,
|
||||
"Library/": true,
|
||||
"library/": true,
|
||||
"obj/": true,
|
||||
"Obj/": true,
|
||||
"Logs/": true,
|
||||
"logs/": true,
|
||||
"ProjectSettings/": true,
|
||||
"UserSettings/": true,
|
||||
"temp/": true,
|
||||
"Temp/": true
|
||||
},
|
||||
"files.associations": {
|
||||
"*.asset": "yaml",
|
||||
"*.meta": "yaml",
|
||||
"*.prefab": "yaml",
|
||||
"*.unity": "yaml",
|
||||
},
|
||||
"explorer.fileNesting.enabled": true,
|
||||
"explorer.fileNesting.patterns": {
|
||||
"*.sln": "*.csproj",
|
||||
"*.slnx": "*.csproj"
|
||||
},
|
||||
"dotnet.defaultSolution": "GeoSusGame.slnx",
|
||||
"dotnet.enableWorkspaceBasedDevelopment": false
|
||||
}
|
||||
BIN
Assets/2026-04-26 13-58-02.mp3.mkv
Normal file
11
Assets/2026-04-26 13-58-02.mp3.mkv.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
<<<<<<<< HEAD:Assets/Scenes/Main Screen Scenes/bubak.unity.meta
|
||||
guid: 2ac55b32f60c0f44691e1588d19d610f
|
||||
========
|
||||
guid: d6a21cbd9c8f68f4fbe65763566ca7c9
|
||||
>>>>>>>> origin/main:Assets/2026-04-26 13-58-02.mp3.mkv.meta
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/2026-04-26 13-58-02.mp3.mp3
Normal file
23
Assets/2026-04-26 13-58-02.mp3.mp3.meta
Normal file
@@ -0,0 +1,23 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ab90ac2243447c478fbb930f101d94a
|
||||
AudioImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 8
|
||||
defaultSettings:
|
||||
serializedVersion: 2
|
||||
loadType: 0
|
||||
sampleRateSetting: 0
|
||||
sampleRateOverride: 44100
|
||||
compressionFormat: 1
|
||||
quality: 1
|
||||
conversionMode: 0
|
||||
preloadAudioData: 0
|
||||
platformSettingOverrides: {}
|
||||
forceToMono: 0
|
||||
normalize: 1
|
||||
loadInBackground: 0
|
||||
ambisonic: 0
|
||||
3D: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/202604261352.mp4
Normal file
18
Assets/202604261352.mp4.meta
Normal file
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c92126c07bfe7e4f8eea9bf78d8f29f
|
||||
VideoClipImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 3
|
||||
frameRange: 0
|
||||
startFrame: -1
|
||||
endFrame: -1
|
||||
colorSpace: 0
|
||||
deinterlace: 0
|
||||
encodeAlpha: 0
|
||||
flipVertical: 0
|
||||
flipHorizontal: 0
|
||||
importAudio: 1
|
||||
targetSettings: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Adaptive Performance.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7eaf47040f6a6ba4bb9df4eab675de30
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,46 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &-4008054574566821997
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 2
|
||||
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: 536372c49e1ca914d822849d36de938c, type: 3}
|
||||
m_Name: Standalone Providers
|
||||
m_EditorClassIdentifier:
|
||||
m_AutomaticLoading: 0
|
||||
m_AutomaticRunning: 0
|
||||
m_Loaders: []
|
||||
--- !u!114 &-1024531111154556285
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 2
|
||||
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: 179fc3111e144bc4688dca4038b3265d, type: 3}
|
||||
m_Name: Standalone Settings
|
||||
m_EditorClassIdentifier:
|
||||
m_LoaderManagerInstance: {fileID: -4008054574566821997}
|
||||
m_InitManagerOnStart: 1
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 2
|
||||
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: cb0ece14d1f711a4fb9325ca819dee95, type: 3}
|
||||
m_Name: AdaptivePerformanceGeneralSettings
|
||||
m_EditorClassIdentifier:
|
||||
Keys: 01000000
|
||||
Values:
|
||||
- {fileID: -1024531111154556285}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 83147ac123bf5e149ba22b1e8722b8a2
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Adaptive Performance/Settings.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a369fde97a303eb4ebfe7de3af10fac4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,316 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 2
|
||||
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: b592865877cb54284a5d1d88aec9cfbb, type: 3}
|
||||
m_Name: Simulator Provider Settings
|
||||
m_EditorClassIdentifier:
|
||||
m_Logging: 1
|
||||
m_AutomaticPerformanceModeEnabled: 1
|
||||
m_AutomaticGameModeEnabled: 0
|
||||
m_EnableBoostOnStartup: 1
|
||||
m_StatsLoggingFrequencyInFrames: 50
|
||||
m_IndexerSettings:
|
||||
m_Active: 1
|
||||
m_ThermalActionDelay: 10
|
||||
m_PerformanceActionDelay: 4
|
||||
m_ScalerSettings:
|
||||
m_AdaptiveFramerate:
|
||||
m_Name: Adaptive Framerate
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 2
|
||||
m_Target: 7
|
||||
m_MaxLevel: 45
|
||||
m_MinBound: 15
|
||||
m_MaxBound: 60
|
||||
m_AdaptiveResolution:
|
||||
m_Name: Adaptive Resolution
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 0
|
||||
m_Target: 6
|
||||
m_MaxLevel: 9
|
||||
m_MinBound: 0.5
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveBatching:
|
||||
m_Name: Adaptive Batching
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 1
|
||||
m_Target: 1
|
||||
m_MaxLevel: 1
|
||||
m_MinBound: 0
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveLOD:
|
||||
m_Name: Adaptive LOD
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 2
|
||||
m_Target: 2
|
||||
m_MaxLevel: 3
|
||||
m_MinBound: 0.4
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveLut:
|
||||
m_Name: Adaptive Lut
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 1
|
||||
m_Target: 3
|
||||
m_MaxLevel: 1
|
||||
m_MinBound: 0
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveMSAA:
|
||||
m_Name: Adaptive MSAA
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 1
|
||||
m_Target: 6
|
||||
m_MaxLevel: 2
|
||||
m_MinBound: 0
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveShadowCascade:
|
||||
m_Name: Adaptive Shadow Cascade
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 1
|
||||
m_Target: 3
|
||||
m_MaxLevel: 2
|
||||
m_MinBound: 0
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveShadowDistance:
|
||||
m_Name: Adaptive Shadow Distance
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 0
|
||||
m_Target: 2
|
||||
m_MaxLevel: 3
|
||||
m_MinBound: 0.15
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveShadowmapResolution:
|
||||
m_Name: Adaptive Shadowmap Resolution
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 0
|
||||
m_Target: 2
|
||||
m_MaxLevel: 3
|
||||
m_MinBound: 0.15
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveShadowQuality:
|
||||
m_Name: Adaptive Shadow Quality
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 2
|
||||
m_Target: 3
|
||||
m_MaxLevel: 3
|
||||
m_MinBound: 0
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveSorting:
|
||||
m_Name: Adaptive Sorting
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 1
|
||||
m_Target: 1
|
||||
m_MaxLevel: 1
|
||||
m_MinBound: 0
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveTransparency:
|
||||
m_Name: Adaptive Transparency
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 2
|
||||
m_Target: 2
|
||||
m_MaxLevel: 1
|
||||
m_MinBound: 0
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveViewDistance:
|
||||
m_Name: Adaptive View Distance
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 2
|
||||
m_Target: 2
|
||||
m_MaxLevel: 40
|
||||
m_MinBound: 50
|
||||
m_MaxBound: 1000
|
||||
m_AdaptivePhysics:
|
||||
m_Name: Adaptive Physics
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 0
|
||||
m_Target: 1
|
||||
m_MaxLevel: 5
|
||||
m_MinBound: 0.5
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveDecals:
|
||||
m_Name: Adaptive Decals
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 1
|
||||
m_Target: 2
|
||||
m_MaxLevel: 20
|
||||
m_MinBound: 0.01
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveLayerCulling:
|
||||
m_Name: Adaptive Layer Culling
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 1
|
||||
m_Target: 1
|
||||
m_MaxLevel: 40
|
||||
m_MinBound: 0.01
|
||||
m_MaxBound: 1
|
||||
m_scalerProfileList:
|
||||
- m_AdaptiveFramerate:
|
||||
m_Name: Adaptive Framerate
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 2
|
||||
m_Target: 7
|
||||
m_MaxLevel: 45
|
||||
m_MinBound: 15
|
||||
m_MaxBound: 60
|
||||
m_AdaptiveResolution:
|
||||
m_Name: Adaptive Resolution
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 0
|
||||
m_Target: 6
|
||||
m_MaxLevel: 9
|
||||
m_MinBound: 0.5
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveBatching:
|
||||
m_Name: Adaptive Batching
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 1
|
||||
m_Target: 1
|
||||
m_MaxLevel: 1
|
||||
m_MinBound: 0
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveLOD:
|
||||
m_Name: Adaptive LOD
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 2
|
||||
m_Target: 2
|
||||
m_MaxLevel: 3
|
||||
m_MinBound: 0.4
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveLut:
|
||||
m_Name: Adaptive Lut
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 1
|
||||
m_Target: 3
|
||||
m_MaxLevel: 1
|
||||
m_MinBound: 0
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveMSAA:
|
||||
m_Name: Adaptive MSAA
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 1
|
||||
m_Target: 6
|
||||
m_MaxLevel: 2
|
||||
m_MinBound: 0
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveShadowCascade:
|
||||
m_Name: Adaptive Shadow Cascade
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 1
|
||||
m_Target: 3
|
||||
m_MaxLevel: 2
|
||||
m_MinBound: 0
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveShadowDistance:
|
||||
m_Name: Adaptive Shadow Distance
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 0
|
||||
m_Target: 2
|
||||
m_MaxLevel: 3
|
||||
m_MinBound: 0.15
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveShadowmapResolution:
|
||||
m_Name: Adaptive Shadowmap Resolution
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 0
|
||||
m_Target: 2
|
||||
m_MaxLevel: 3
|
||||
m_MinBound: 0.15
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveShadowQuality:
|
||||
m_Name: Adaptive Shadow Quality
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 2
|
||||
m_Target: 3
|
||||
m_MaxLevel: 3
|
||||
m_MinBound: 0
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveSorting:
|
||||
m_Name: Adaptive Sorting
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 1
|
||||
m_Target: 1
|
||||
m_MaxLevel: 1
|
||||
m_MinBound: 0
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveTransparency:
|
||||
m_Name: Adaptive Transparency
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 2
|
||||
m_Target: 2
|
||||
m_MaxLevel: 1
|
||||
m_MinBound: 0
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveViewDistance:
|
||||
m_Name: Adaptive View Distance
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 2
|
||||
m_Target: 2
|
||||
m_MaxLevel: 40
|
||||
m_MinBound: 50
|
||||
m_MaxBound: 1000
|
||||
m_AdaptivePhysics:
|
||||
m_Name: Adaptive Physics
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 0
|
||||
m_Target: 1
|
||||
m_MaxLevel: 5
|
||||
m_MinBound: 0.5
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveDecals:
|
||||
m_Name: Adaptive Decals
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 1
|
||||
m_Target: 2
|
||||
m_MaxLevel: 20
|
||||
m_MinBound: 0.01
|
||||
m_MaxBound: 1
|
||||
m_AdaptiveLayerCulling:
|
||||
m_Name: Adaptive Layer Culling
|
||||
m_Enabled: 0
|
||||
m_Scale: 1
|
||||
m_VisualImpact: 1
|
||||
m_Target: 1
|
||||
m_MaxLevel: 40
|
||||
m_MinBound: 0.01
|
||||
m_MaxBound: 1
|
||||
m_Name: Default Scaler Profile
|
||||
m_DefaultScalerProfilerIndex: 0
|
||||
k_AssetVersion: 2
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9fb757fd9f29fb4f9be1be9c492abbc
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
267
Assets/ArenaRoot.prefab
Normal file
@@ -0,0 +1,267 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &25324321885539938
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1256957957520000306}
|
||||
m_Layer: 0
|
||||
m_Name: ProjectileSpawn
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &1256957957520000306
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 25324321885539938}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 2.1, y: -2.86, z: 1.87}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 7228744653633915258}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &306349634079512810
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 7055327180212611754}
|
||||
m_Layer: 0
|
||||
m_Name: ButtonTarget
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &7055327180212611754
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 306349634079512810}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 6009521584277000886}
|
||||
m_Father: {fileID: 7228744653633915258}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &578482260246237550
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 6009521584277000886}
|
||||
- component: {fileID: 3827426293549565123}
|
||||
- component: {fileID: 1470073872372251261}
|
||||
- component: {fileID: 3778101033228877197}
|
||||
- component: {fileID: 8782929001941168503}
|
||||
- component: {fileID: 8076614220732688013}
|
||||
m_Layer: 0
|
||||
m_Name: CenterButtonTarget
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &6009521584277000886
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 578482260246237550}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: -0.7071068, y: -0, z: -0, w: 0.7071067}
|
||||
m_LocalPosition: {x: 2.03, y: -0.089999914, z: 1.9}
|
||||
m_LocalScale: {x: 0.5, y: 0.5, z: 0.5}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 7055327180212611754}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!33 &3827426293549565123
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 578482260246237550}
|
||||
m_Mesh: {fileID: 5687779609372477813, guid: 8ee80b1e2cfa1c747877549e20403fd3, type: 3}
|
||||
--- !u!23 &1470073872372251261
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 578482260246237550}
|
||||
m_Enabled: 1
|
||||
m_CastShadows: 1
|
||||
m_ReceiveShadows: 1
|
||||
m_DynamicOccludee: 1
|
||||
m_StaticShadowCaster: 0
|
||||
m_MotionVectors: 1
|
||||
m_LightProbeUsage: 1
|
||||
m_ReflectionProbeUsage: 1
|
||||
m_RayTracingMode: 2
|
||||
m_RayTraceProcedural: 0
|
||||
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||
m_RayTracingAccelStructBuildFlags: 1
|
||||
m_SmallMeshCulling: 1
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 2100000, guid: d88c7dcf650af2c4e812eaa19f43e2e4, type: 2}
|
||||
m_StaticBatchInfo:
|
||||
firstSubMesh: 0
|
||||
subMeshCount: 0
|
||||
m_StaticBatchRoot: {fileID: 0}
|
||||
m_ProbeAnchor: {fileID: 0}
|
||||
m_LightProbeVolumeOverride: {fileID: 0}
|
||||
m_ScaleInLightmap: 1
|
||||
m_ReceiveGI: 1
|
||||
m_PreserveUVs: 0
|
||||
m_IgnoreNormalsForChartDetection: 0
|
||||
m_ImportantGI: 0
|
||||
m_StitchLightmapSeams: 1
|
||||
m_SelectedEditorRenderState: 3
|
||||
m_MinimumChartSize: 4
|
||||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!114 &3778101033228877197
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 578482260246237550}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 9d8029baff330b94d836a23c421021a8, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
owner: {fileID: 0}
|
||||
--- !u!64 &8782929001941168503
|
||||
MeshCollider:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 578482260246237550}
|
||||
m_Material: {fileID: 0}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_LayerOverridePriority: 0
|
||||
m_IsTrigger: 0
|
||||
m_ProvidesContacts: 0
|
||||
m_Enabled: 1
|
||||
serializedVersion: 5
|
||||
m_Convex: 1
|
||||
m_CookingOptions: 30
|
||||
m_Mesh: {fileID: 5687779609372477813, guid: 8ee80b1e2cfa1c747877549e20403fd3, type: 3}
|
||||
--- !u!114 &8076614220732688013
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 578482260246237550}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 20fa9c796cd377047ba2c43230717531, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
rotationSpeed: 90
|
||||
--- !u!1 &1062886443160141632
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 6403106612870554802}
|
||||
m_Layer: 0
|
||||
m_Name: AimPoint
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &6403106612870554802
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1062886443160141632}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 2.032, y: -0.23, z: 1.87}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 7228744653633915258}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &9110341383532608413
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 7228744653633915258}
|
||||
m_Layer: 0
|
||||
m_Name: ArenaRoot
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &7228744653633915258
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 9110341383532608413}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 3, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 6403106612870554802}
|
||||
- {fileID: 1256957957520000306}
|
||||
- {fileID: 7055327180212611754}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
7
Assets/ArenaRoot.prefab.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5091877278b8b6a47a9afa6f50d6b2a6
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Build Profiles.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 92df50b8fba934144a4c4dcaf506f9b4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
18
Assets/CenterButtonTarget.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class CenterButtonTarget : MonoBehaviour
|
||||
{
|
||||
public TimingWheelShooter owner;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (owner == null)
|
||||
owner = FindFirstObjectByType<TimingWheelShooter>();
|
||||
}
|
||||
|
||||
public void NotifyHit(ProjectileBehaviour projectile)
|
||||
{
|
||||
if (owner != null)
|
||||
owner.NotifyButtonHit(projectile);
|
||||
}
|
||||
}
|
||||
2
Assets/CenterButtonTarget.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7bc9c797f71ebf4c84a6c6698a8dfd9
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 799f52449ae21404c9a7593f6dc28c60
|
||||
guid: 50a0b21c151e150428fd2803d6b95db0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
|
||||
@@ -132,7 +132,20 @@ public class GameClient : IDisposable
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Disconnect(string reason = "User disconnected")
|
||||
/// <summary>
|
||||
/// Tears down the socket and crypto session. When `transient` is true
|
||||
/// (network drop, decrypt-failure cascade, anything we expect to retry),
|
||||
/// the lobby/role/task/state caches are preserved so the post-reconnect
|
||||
/// flow can re-associate via Reconnect(LobbyId). Default false matches
|
||||
/// pre-P9 behavior (full state wipe) for explicit user disconnects.
|
||||
///
|
||||
/// Critical for the P9 reconnect bug: previously every Disconnect path
|
||||
/// nuked LobbyId, so by the time GameManager_Network's reconnect coroutine
|
||||
/// fired, the client had no idea which lobby it had been in - the
|
||||
/// post-handshake Reconnect call had nothing to send and the server
|
||||
/// answered the next vote/action with NOT_IN_LOBBY.
|
||||
/// </summary>
|
||||
public void Disconnect(string reason = "User disconnected", bool transient = false)
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_tcpClient?.Close();
|
||||
@@ -141,13 +154,20 @@ public class GameClient : IDisposable
|
||||
_encryption?.Dispose();
|
||||
_encryption = null;
|
||||
|
||||
LobbyId = null;
|
||||
JoinCode = null;
|
||||
CurrentLobbyState = null;
|
||||
MyRole = null;
|
||||
MyTasks.Clear();
|
||||
PlayerPositions.Clear();
|
||||
Bodies.Clear();
|
||||
if (!transient)
|
||||
{
|
||||
LobbyId = null;
|
||||
JoinCode = null;
|
||||
CurrentLobbyState = null;
|
||||
MyRole = null;
|
||||
MyTasks.Clear();
|
||||
PlayerPositions.Clear();
|
||||
Bodies.Clear();
|
||||
}
|
||||
// PlayerPositions are stale anyway after a drop, but we keep them so
|
||||
// the UI doesn't blink avatars off-map mid-meeting; the next position
|
||||
// broadcast overwrites them. LastEventId is intentionally preserved
|
||||
// so the Reconnect message can ask the server for missed events.
|
||||
|
||||
Dispatcher.Post(() => OnDisconnected?.Invoke(reason));
|
||||
}
|
||||
@@ -236,7 +256,8 @@ public class GameClient : IDisposable
|
||||
decryptFailures++;
|
||||
if (decryptFailures >= 3)
|
||||
{
|
||||
Disconnect("Too many decryption failures");
|
||||
// Transient: keep LobbyId for the reconnect coroutine.
|
||||
Disconnect("Too many decryption failures", transient: true);
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
@@ -253,7 +274,9 @@ public class GameClient : IDisposable
|
||||
}
|
||||
catch (Exception ex) when (!ct.IsCancellationRequested)
|
||||
{
|
||||
Disconnect($"Connection error: {ex.Message}");
|
||||
// Transient: TCP RST / read failure is exactly what reconnect was
|
||||
// designed for. Keep LobbyId so post-reconnect flow can re-attach.
|
||||
Disconnect($"Connection error: {ex.Message}", transient: true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +316,35 @@ public class GameClient : IDisposable
|
||||
{
|
||||
LobbyId = r.LobbyId;
|
||||
JoinCode = r.JoinCode;
|
||||
CurrentLobbyState = r.LobbyState;
|
||||
// Ensure we always have a valid lobby state with the creator as owner
|
||||
if (r.LobbyState != null)
|
||||
{
|
||||
CurrentLobbyState = r.LobbyState;
|
||||
if (string.IsNullOrEmpty(CurrentLobbyState.OwnerId))
|
||||
CurrentLobbyState.OwnerId = ClientUuid;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentLobbyState = new LobbyState
|
||||
{
|
||||
LobbyId = r.LobbyId ?? "",
|
||||
JoinCode = r.JoinCode ?? "",
|
||||
OwnerId = ClientUuid
|
||||
};
|
||||
}
|
||||
// Make sure creator appears in the player list
|
||||
if (CurrentLobbyState.Players == null)
|
||||
CurrentLobbyState.Players = new System.Collections.Generic.List<PlayerInfo>();
|
||||
if (!CurrentLobbyState.Players.Any(p => p.ClientUuid == ClientUuid))
|
||||
{
|
||||
CurrentLobbyState.Players.Insert(0, new PlayerInfo
|
||||
{
|
||||
ClientUuid = ClientUuid,
|
||||
DisplayName = DisplayName,
|
||||
IsOwner = true,
|
||||
State = PlayerState.Alive
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -303,6 +354,22 @@ public class GameClient : IDisposable
|
||||
LobbyId = r.LobbyId;
|
||||
CurrentLobbyState = r.LobbyState;
|
||||
JoinCode = r.LobbyState?.JoinCode;
|
||||
// Ensure self is in the player list
|
||||
if (CurrentLobbyState != null)
|
||||
{
|
||||
if (CurrentLobbyState.Players == null)
|
||||
CurrentLobbyState.Players = new System.Collections.Generic.List<PlayerInfo>();
|
||||
if (!CurrentLobbyState.Players.Any(p => p.ClientUuid == ClientUuid))
|
||||
{
|
||||
CurrentLobbyState.Players.Add(new PlayerInfo
|
||||
{
|
||||
ClientUuid = ClientUuid,
|
||||
DisplayName = DisplayName,
|
||||
IsOwner = CurrentLobbyState.OwnerId == ClientUuid,
|
||||
State = PlayerState.Alive
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -344,7 +411,6 @@ public class GameClient : IDisposable
|
||||
var joinedPayload = evt.GetPayload<PlayerJoinedPayload>();
|
||||
if (joinedPayload != null && CurrentLobbyState?.Players != null)
|
||||
{
|
||||
// Check if player already exists
|
||||
bool exists = CurrentLobbyState.Players.Any(p => p.ClientUuid == joinedPayload.ClientUuid);
|
||||
if (!exists)
|
||||
{
|
||||
@@ -352,7 +418,7 @@ public class GameClient : IDisposable
|
||||
{
|
||||
ClientUuid = joinedPayload.ClientUuid,
|
||||
DisplayName = joinedPayload.DisplayName,
|
||||
IsOwner = false,
|
||||
IsOwner = joinedPayload.ClientUuid == CurrentLobbyState.OwnerId,
|
||||
IsReady = false,
|
||||
State = PlayerState.Alive
|
||||
});
|
||||
@@ -465,15 +531,22 @@ public class GameClient : IDisposable
|
||||
|
||||
#region Game Actions
|
||||
|
||||
public void CreateLobby(Position? center = null, int impostorCount = 1, int taskCount = 5, string? password = null, double playAreaRadius = 500)
|
||||
public void CreateLobby(Position? center = null, int impostorCount = 1, int taskCount = 5, string? password = null, double playAreaRadius = 500, GameSettingsOverrides? settings = null)
|
||||
{
|
||||
// DisplayName is sent on every CreateLobby/JoinLobby so the server
|
||||
// picks up the live nickname (typed into the input field after the
|
||||
// ClientHello handshake fired). Without this the server uses the
|
||||
// ClientHello-time name, which is the GameManager prefab default
|
||||
// for any user who immediately created/joined a lobby.
|
||||
Send(new CreateLobby
|
||||
{
|
||||
PlayAreaCenter = center,
|
||||
PlayAreaRadius = playAreaRadius,
|
||||
ImpostorCount = impostorCount,
|
||||
TaskCount = taskCount,
|
||||
Password = password
|
||||
Password = password,
|
||||
Settings = settings,
|
||||
DisplayName = DisplayName
|
||||
});
|
||||
}
|
||||
|
||||
@@ -482,7 +555,8 @@ public class GameClient : IDisposable
|
||||
Send(new JoinLobby
|
||||
{
|
||||
JoinCode = joinCode.ToUpperInvariant(),
|
||||
Password = password
|
||||
Password = password,
|
||||
DisplayName = DisplayName
|
||||
});
|
||||
}
|
||||
|
||||
@@ -491,6 +565,7 @@ public class GameClient : IDisposable
|
||||
Send(new LeaveLobby());
|
||||
LobbyId = null;
|
||||
JoinCode = null;
|
||||
CurrentLobbyState = null;
|
||||
}
|
||||
|
||||
public void StartGame()
|
||||
|
||||
@@ -49,6 +49,11 @@ public enum PlayerRole { Crew, Impostor }
|
||||
public enum PlayerState { Alive, Dead }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
// NOTE: `Voting` is reserved-but-unused on the wire as of 2026. The server
|
||||
// keeps the entire vote cycle inside `Meeting` and uses MeetingStartedPayload
|
||||
// timestamps (DiscussionEndTime / VotingEndTime) to distinguish sub-phases.
|
||||
// The enum value is preserved here for serialization compatibility with old
|
||||
// saves; new code should not assign it.
|
||||
public enum GamePhase { Lobby, Loading, Playing, Meeting, Voting, Ended }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
@@ -184,6 +189,24 @@ public class CreateLobby : Message
|
||||
|
||||
[JsonProperty("taskCount")]
|
||||
public int TaskCount { get; set; } = 5;
|
||||
|
||||
/// <summary>
|
||||
/// P13b: optional per-lobby settings overrides supplied by the host.
|
||||
/// Any field left null falls through to the server's current default
|
||||
/// (snapshotted at lobby creation, immutable thereafter for this lobby).
|
||||
/// </summary>
|
||||
[JsonProperty("settings")]
|
||||
public GameSettingsOverrides? Settings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional. Live host display name from the nickname input field at
|
||||
/// the moment of CreateLobby. ClientHello-time name is stale because
|
||||
/// the handshake fires before the user has typed anything; this lets
|
||||
/// the server pick up the freshly-typed name without a separate
|
||||
/// rename round-trip.
|
||||
/// </summary>
|
||||
[JsonProperty("displayName")]
|
||||
public string? DisplayName { get; set; }
|
||||
}
|
||||
|
||||
public class CreateLobbyResponse : Message
|
||||
@@ -215,6 +238,13 @@ public class JoinLobby : Message
|
||||
|
||||
[JsonProperty("password")]
|
||||
public string? Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional. Live joiner display name from the nickname input field
|
||||
/// at the moment of Join. See CreateLobby.DisplayName for rationale.
|
||||
/// </summary>
|
||||
[JsonProperty("displayName")]
|
||||
public string? DisplayName { get; set; }
|
||||
}
|
||||
|
||||
public class JoinLobbyResponse : Message
|
||||
@@ -623,6 +653,15 @@ public class PlayerEjectedPayload
|
||||
public PlayerRole Role { get; set; }
|
||||
}
|
||||
|
||||
public class TaskStartedPayload
|
||||
{
|
||||
[JsonProperty("clientUuid")]
|
||||
public string ClientUuid { get; set; } = "";
|
||||
|
||||
[JsonProperty("taskId")]
|
||||
public string TaskId { get; set; } = "";
|
||||
}
|
||||
|
||||
public class TaskCompletedPayload
|
||||
{
|
||||
[JsonProperty("clientUuid")]
|
||||
@@ -790,6 +829,162 @@ public class LobbyState
|
||||
/// <summary>True if map data has been loaded (or Overpass is disabled)</summary>
|
||||
[JsonProperty("mapDataReady")]
|
||||
public bool MapDataReady { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// P13b: full per-lobby settings snapshot. Clients use this for HUD
|
||||
/// (button visibility, countdown timings, etc.) instead of hardcoded
|
||||
/// values. Always populated for new server builds; old client builds
|
||||
/// can ignore the field.
|
||||
/// </summary>
|
||||
[JsonProperty("settings")]
|
||||
public GameSettings? Settings { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// P13b: per-lobby gameplay settings on the wire. Server populates this from
|
||||
/// its per-lobby snapshot so clients can drive HUD logic from authoritative
|
||||
/// values rather than hardcoded constants.
|
||||
/// </summary>
|
||||
public class GameSettings
|
||||
{
|
||||
// Round shape
|
||||
[JsonProperty("maxPlayers")]
|
||||
public int MaxPlayers { get; set; }
|
||||
|
||||
[JsonProperty("impostorCount")]
|
||||
public int ImpostorCount { get; set; }
|
||||
|
||||
[JsonProperty("taskCount")]
|
||||
public int TaskCount { get; set; }
|
||||
|
||||
[JsonProperty("tiePolicy")]
|
||||
public string TiePolicy { get; set; } = "NoEject";
|
||||
|
||||
// Distances (m)
|
||||
[JsonProperty("killDistanceM")]
|
||||
public double KillDistanceM { get; set; }
|
||||
|
||||
[JsonProperty("reportDistanceM")]
|
||||
public double ReportDistanceM { get; set; }
|
||||
|
||||
[JsonProperty("taskStartDistanceM")]
|
||||
public double TaskStartDistanceM { get; set; }
|
||||
|
||||
[JsonProperty("meetingArrivalRadiusM")]
|
||||
public double MeetingArrivalRadiusM { get; set; }
|
||||
|
||||
[JsonProperty("emergencyMeetingCallRadiusM")]
|
||||
public double EmergencyMeetingCallRadiusM { get; set; }
|
||||
|
||||
[JsonProperty("repairStationDistanceM")]
|
||||
public double RepairStationDistanceM { get; set; }
|
||||
|
||||
// Cooldowns / counts
|
||||
[JsonProperty("killCooldownMs")]
|
||||
public int KillCooldownMs { get; set; }
|
||||
|
||||
[JsonProperty("emergencyMeetingCooldownMs")]
|
||||
public int EmergencyMeetingCooldownMs { get; set; }
|
||||
|
||||
[JsonProperty("maxEmergencyMeetingsPerPlayer")]
|
||||
public int MaxEmergencyMeetingsPerPlayer { get; set; }
|
||||
|
||||
// Meeting phases (ms)
|
||||
[JsonProperty("arrivalBaseMs")]
|
||||
public int ArrivalBaseMs { get; set; }
|
||||
|
||||
[JsonProperty("allowedLateMs")]
|
||||
public int AllowedLateMs { get; set; }
|
||||
|
||||
[JsonProperty("discussionPhaseMs")]
|
||||
public int DiscussionPhaseMs { get; set; }
|
||||
|
||||
[JsonProperty("votingPhaseMs")]
|
||||
public int VotingPhaseMs { get; set; }
|
||||
|
||||
// Sabotage
|
||||
[JsonProperty("sabotageCooldownMs")]
|
||||
public int SabotageCooldownMs { get; set; }
|
||||
|
||||
[JsonProperty("commsBlackoutDurationMs")]
|
||||
public int CommsBlackoutDurationMs { get; set; }
|
||||
|
||||
[JsonProperty("criticalMeltdownDeadlineMs")]
|
||||
public int CriticalMeltdownDeadlineMs { get; set; }
|
||||
|
||||
[JsonProperty("repairStationHoldMs")]
|
||||
public int RepairStationHoldMs { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// P13b: host-supplied overrides at CreateLobby. Every field is nullable so
|
||||
/// the host can opt into changing only what they care about; null = use the
|
||||
/// server's current default at the moment of lobby creation.
|
||||
/// </summary>
|
||||
public class GameSettingsOverrides
|
||||
{
|
||||
[JsonProperty("maxPlayers")]
|
||||
public int? MaxPlayers { get; set; }
|
||||
|
||||
[JsonProperty("impostorCount")]
|
||||
public int? ImpostorCount { get; set; }
|
||||
|
||||
[JsonProperty("taskCount")]
|
||||
public int? TaskCount { get; set; }
|
||||
|
||||
[JsonProperty("tiePolicy")]
|
||||
public string? TiePolicy { get; set; }
|
||||
|
||||
[JsonProperty("killDistanceM")]
|
||||
public double? KillDistanceM { get; set; }
|
||||
|
||||
[JsonProperty("reportDistanceM")]
|
||||
public double? ReportDistanceM { get; set; }
|
||||
|
||||
[JsonProperty("taskStartDistanceM")]
|
||||
public double? TaskStartDistanceM { get; set; }
|
||||
|
||||
[JsonProperty("meetingArrivalRadiusM")]
|
||||
public double? MeetingArrivalRadiusM { get; set; }
|
||||
|
||||
[JsonProperty("emergencyMeetingCallRadiusM")]
|
||||
public double? EmergencyMeetingCallRadiusM { get; set; }
|
||||
|
||||
[JsonProperty("repairStationDistanceM")]
|
||||
public double? RepairStationDistanceM { get; set; }
|
||||
|
||||
[JsonProperty("killCooldownMs")]
|
||||
public int? KillCooldownMs { get; set; }
|
||||
|
||||
[JsonProperty("emergencyMeetingCooldownMs")]
|
||||
public int? EmergencyMeetingCooldownMs { get; set; }
|
||||
|
||||
[JsonProperty("maxEmergencyMeetingsPerPlayer")]
|
||||
public int? MaxEmergencyMeetingsPerPlayer { get; set; }
|
||||
|
||||
[JsonProperty("arrivalBaseMs")]
|
||||
public int? ArrivalBaseMs { get; set; }
|
||||
|
||||
[JsonProperty("allowedLateMs")]
|
||||
public int? AllowedLateMs { get; set; }
|
||||
|
||||
[JsonProperty("discussionPhaseMs")]
|
||||
public int? DiscussionPhaseMs { get; set; }
|
||||
|
||||
[JsonProperty("votingPhaseMs")]
|
||||
public int? VotingPhaseMs { get; set; }
|
||||
|
||||
[JsonProperty("sabotageCooldownMs")]
|
||||
public int? SabotageCooldownMs { get; set; }
|
||||
|
||||
[JsonProperty("commsBlackoutDurationMs")]
|
||||
public int? CommsBlackoutDurationMs { get; set; }
|
||||
|
||||
[JsonProperty("criticalMeltdownDeadlineMs")]
|
||||
public int? CriticalMeltdownDeadlineMs { get; set; }
|
||||
|
||||
[JsonProperty("repairStationHoldMs")]
|
||||
public int? RepairStationHoldMs { get; set; }
|
||||
}
|
||||
|
||||
// Map data classes for rendering - compact format from server
|
||||
|
||||
BIN
Assets/Dancing triangle 1 Hour [YB3yHfqdw3Y].mp3
Normal file
23
Assets/Dancing triangle 1 Hour [YB3yHfqdw3Y].mp3.meta
Normal file
@@ -0,0 +1,23 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58ebc3ffa125a9949953f6704e0a8c39
|
||||
AudioImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 8
|
||||
defaultSettings:
|
||||
serializedVersion: 2
|
||||
loadType: 0
|
||||
sampleRateSetting: 0
|
||||
sampleRateOverride: 44100
|
||||
compressionFormat: 1
|
||||
quality: 1
|
||||
conversionMode: 0
|
||||
preloadAudioData: 0
|
||||
platformSettingOverrides: {}
|
||||
forceToMono: 0
|
||||
normalize: 1
|
||||
loadInBackground: 0
|
||||
ambisonic: 0
|
||||
3D: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/DataTransfer scene and assets.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16c3692935d75294f9404be0a4ba0039
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
6503
Assets/DataTransfer scene and assets/Upload.unity
Normal file
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e95f16d8e50b3341925e51e50768027
|
||||
guid: 1b8722ddfeb323a4da4a18797ed7df32
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
6522
Assets/DataTransfer scene and assets/download.unity
Normal file
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: facb9eb5d6c7d484097d6167562da786
|
||||
guid: 83edd2ecead106542bc862143208dd4c
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
8
Assets/DataTransfer scene and assets/minigame.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5145a323a08373d4a9074774f7f3c501
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f5e4c6e6f8367342893fd7030d1b4cb
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b02f5e5a2bd2df479219d58104b58e4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f4fa73205ab4db41871cc3e9260180f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/DataTransfer scene and assets/sprites.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d08b4a9b983113c4a9c56b2738a85291
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,53 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!74 &7400000
|
||||
AnimationClip:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: border anim
|
||||
serializedVersion: 7
|
||||
m_Legacy: 0
|
||||
m_Compressed: 0
|
||||
m_UseHighQualityCurve: 1
|
||||
m_RotationCurves: []
|
||||
m_CompressedRotationCurves: []
|
||||
m_EulerCurves: []
|
||||
m_PositionCurves: []
|
||||
m_ScaleCurves: []
|
||||
m_FloatCurves: []
|
||||
m_PPtrCurves: []
|
||||
m_SampleRate: 60
|
||||
m_WrapMode: 0
|
||||
m_Bounds:
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
m_Extent: {x: 0, y: 0, z: 0}
|
||||
m_ClipBindingConstant:
|
||||
genericBindings: []
|
||||
pptrCurveMapping: []
|
||||
m_AnimationClipSettings:
|
||||
serializedVersion: 2
|
||||
m_AdditiveReferencePoseClip: {fileID: 0}
|
||||
m_AdditiveReferencePoseTime: 0
|
||||
m_StartTime: 0
|
||||
m_StopTime: 1
|
||||
m_OrientationOffsetY: 0
|
||||
m_Level: 0
|
||||
m_CycleOffset: 0
|
||||
m_HasAdditiveReferencePose: 0
|
||||
m_LoopTime: 1
|
||||
m_LoopBlend: 0
|
||||
m_LoopBlendOrientation: 0
|
||||
m_LoopBlendPositionY: 0
|
||||
m_LoopBlendPositionXZ: 0
|
||||
m_KeepOriginalOrientation: 0
|
||||
m_KeepOriginalPositionY: 1
|
||||
m_KeepOriginalPositionXZ: 0
|
||||
m_HeightFromFeet: 0
|
||||
m_Mirror: 0
|
||||
m_EditorCurves: []
|
||||
m_EulerEditorCurves: []
|
||||
m_HasGenericRootTransform: 0
|
||||
m_HasMotionFloatCurves: 0
|
||||
m_Events: []
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5b8b3d1765137a40a4094e14ea0b1c8
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,72 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1102 &-7814012930283619509
|
||||
AnimatorState:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: New Animation
|
||||
m_Speed: 1
|
||||
m_CycleOffset: 0
|
||||
m_Transitions: []
|
||||
m_StateMachineBehaviours: []
|
||||
m_Position: {x: 50, y: 50, z: 0}
|
||||
m_IKOnFeet: 0
|
||||
m_WriteDefaultValues: 1
|
||||
m_Mirror: 0
|
||||
m_SpeedParameterActive: 0
|
||||
m_MirrorParameterActive: 0
|
||||
m_CycleOffsetParameterActive: 0
|
||||
m_TimeParameterActive: 0
|
||||
m_Motion: {fileID: 7400000, guid: f5b8b3d1765137a40a4094e14ea0b1c8, type: 2}
|
||||
m_Tag:
|
||||
m_SpeedParameter:
|
||||
m_MirrorParameter:
|
||||
m_CycleOffsetParameter:
|
||||
m_TimeParameter:
|
||||
--- !u!91 &9100000
|
||||
AnimatorController:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: border
|
||||
serializedVersion: 5
|
||||
m_AnimatorParameters: []
|
||||
m_AnimatorLayers:
|
||||
- serializedVersion: 5
|
||||
m_Name: Base Layer
|
||||
m_StateMachine: {fileID: 2563971018880681404}
|
||||
m_Mask: {fileID: 0}
|
||||
m_Motions: []
|
||||
m_Behaviours: []
|
||||
m_BlendingMode: 0
|
||||
m_SyncedLayerIndex: -1
|
||||
m_DefaultWeight: 0
|
||||
m_IKPass: 0
|
||||
m_SyncedLayerAffectsTiming: 0
|
||||
m_Controller: {fileID: 9100000}
|
||||
--- !u!1107 &2563971018880681404
|
||||
AnimatorStateMachine:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Base Layer
|
||||
m_ChildStates:
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: -7814012930283619509}
|
||||
m_Position: {x: 270, y: 0, z: 0}
|
||||
m_ChildStateMachines: []
|
||||
m_AnyStateTransitions: []
|
||||
m_EntryTransitions: []
|
||||
m_StateMachineTransitions: {}
|
||||
m_StateMachineBehaviours: []
|
||||
m_AnyStatePosition: {x: 50, y: 20, z: 0}
|
||||
m_EntryPosition: {x: 50, y: 120, z: 0}
|
||||
m_ExitPosition: {x: 800, y: 120, z: 0}
|
||||
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
|
||||
m_DefaultState: {fileID: -7814012930283619509}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cecdd3ffd08949d49bfa9bad93dddd3b
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 9100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/DataTransfer scene and assets/sprites/loading0.png
Normal file
|
After Width: | Height: | Size: 469 KiB |
117
Assets/DataTransfer scene and assets/sprites/loading0.png.meta
Normal file
@@ -0,0 +1,117 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79c3437643e68be4e88c3bf039f0680d
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
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
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
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: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/DataTransfer scene and assets/sprites/loading1.png
Normal file
|
After Width: | Height: | Size: 435 KiB |
117
Assets/DataTransfer scene and assets/sprites/loading1.png.meta
Normal file
@@ -0,0 +1,117 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d962c88742b40ec4594c568cba2848e4
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
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
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
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: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/DataTransfer scene and assets/sprites/loading2.png
Normal file
|
After Width: | Height: | Size: 405 KiB |
117
Assets/DataTransfer scene and assets/sprites/loading2.png.meta
Normal file
@@ -0,0 +1,117 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d68a7660c51d4454f915a1c427cb01ce
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
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
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
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: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/DataTransfer scene and assets/sprites/loading3.png
Normal file
|
After Width: | Height: | Size: 371 KiB |
117
Assets/DataTransfer scene and assets/sprites/loading3.png.meta
Normal file
@@ -0,0 +1,117 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 805221047ed3e7c48a13ff21d97f6c66
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
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
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
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: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/DataTransfer scene and assets/sprites/loading4.png
Normal file
|
After Width: | Height: | Size: 340 KiB |
117
Assets/DataTransfer scene and assets/sprites/loading4.png.meta
Normal file
@@ -0,0 +1,117 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b3f2382597d46c640ab466c1609bd193
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
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
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
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: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/DataTransfer scene and assets/sprites/loading5.png
Normal file
|
After Width: | Height: | Size: 308 KiB |
117
Assets/DataTransfer scene and assets/sprites/loading5.png.meta
Normal file
@@ -0,0 +1,117 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1f13902211756a4d9b7246f52ac5005
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
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
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
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: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/DataTransfer scene and assets/sprites/loading6.png
Normal file
|
After Width: | Height: | Size: 276 KiB |
117
Assets/DataTransfer scene and assets/sprites/loading6.png.meta
Normal file
@@ -0,0 +1,117 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 997acda7ef7df0e4eaeb2a8dff863abf
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
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
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
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: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/DataTransfer scene and assets/sprites/loading7.png
Normal file
|
After Width: | Height: | Size: 248 KiB |
117
Assets/DataTransfer scene and assets/sprites/loading7.png.meta
Normal file
@@ -0,0 +1,117 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8292414d4bad4364f874555af2f7e712
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
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
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
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: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/DataTransfer scene and assets/sprites/loading8.png
Normal file
|
After Width: | Height: | Size: 248 KiB |
117
Assets/DataTransfer scene and assets/sprites/loading8.png.meta
Normal file
@@ -0,0 +1,117 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3b63bdfb82042f94887a00a48094f69a
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
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
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
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: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/DataTransfer scene and assets/sprites/sipka 1.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
117
Assets/DataTransfer scene and assets/sprites/sipka 1.png.meta
Normal file
@@ -0,0 +1,117 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae21ad83b0f7d5941822a82c37238864
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
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
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
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: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/DataTransfer scene and assets/sprites/sipka 2.png
Normal file
|
After Width: | Height: | Size: 189 KiB |
117
Assets/DataTransfer scene and assets/sprites/sipka 2.png.meta
Normal file
@@ -0,0 +1,117 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a6ffeb1058a6f8409a669fbd1d5c463
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
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
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
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: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/DataTransfer scene and assets/sprites/sipka 3.png
Normal file
|
After Width: | Height: | Size: 152 KiB |
117
Assets/DataTransfer scene and assets/sprites/sipka 3.png.meta
Normal file
@@ -0,0 +1,117 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68ab2eea03d99d544b9c5c607019b2c0
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
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
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
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: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/DataTransfer scene and assets/sprites/sipka 4.png
Normal file
|
After Width: | Height: | Size: 129 KiB |
117
Assets/DataTransfer scene and assets/sprites/sipka 4.png.meta
Normal file
@@ -0,0 +1,117 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7a206d138cef964aa45af4cfa97fa9a
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
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
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
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: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 43 KiB |
@@ -0,0 +1,117 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a04092104e630434a84804e17040195a
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
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
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
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: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 51 KiB |
@@ -0,0 +1,117 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 982be63b1292049488295e60ce74abe2
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
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
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
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: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8fa0d9c695119af49bd1693054cf3174
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Editor/com.unity.mobile.notifications.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70729d202603eef42955f52bd64f7c69
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,42 @@
|
||||
%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: 0863bf92b4fcc45b0b9267325249bf0f, type: 3}
|
||||
m_Name: NotificationSettings
|
||||
m_EditorClassIdentifier:
|
||||
toolbarInt: 0
|
||||
iOSNotificationEditorSettingsValues:
|
||||
keys:
|
||||
- UnityNotificationRequestAuthorizationOnAppLaunch
|
||||
- UnityNotificationDefaultAuthorizationOptions
|
||||
- UnityAddRemoteNotificationCapability
|
||||
- UnityNotificationRequestAuthorizationForRemoteNotificationsOnAppLaunch
|
||||
- UnityRemoteNotificationForegroundPresentationOptions
|
||||
- UnityUseAPSReleaseEnvironment
|
||||
- UnityUseLocationNotificationTrigger
|
||||
values:
|
||||
- True
|
||||
- 7
|
||||
- False
|
||||
- False
|
||||
- -1
|
||||
- False
|
||||
- False
|
||||
AndroidNotificationEditorSettingsValues:
|
||||
keys:
|
||||
- UnityNotificationAndroidRescheduleOnDeviceRestart
|
||||
- UnityNotificationAndroidUseCustomActivity
|
||||
- UnityNotificationAndroidCustomActivityString
|
||||
values:
|
||||
- False
|
||||
- False
|
||||
- com.unity3d.player.UnityPlayerActivity
|
||||
TrackedResourceAssets: []
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 55822530f24ba9b4c9950ed46293252f
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5fd2bf33031fe9d4ea3439b41d7f4b97
|
||||
guid: bbd26b895bc2b894b8989c08d9fd9197
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
|
||||
@@ -4,145 +4,752 @@ using Subsystems;
|
||||
using System.Collections;
|
||||
using System;
|
||||
using TMPro;
|
||||
/*
|
||||
GameManager - hlavní tøida pro správu hry
|
||||
GameManager_Network - subsystém pro správu komunikace se serverem
|
||||
GameManager_Game - subsystém pro správu logiky hry (sabotáže, tasky, atd.)
|
||||
GameManager_Map - subsystém pro správu mapy a prostøedí
|
||||
GameManager_Input - subsystém pro správu vstupu od hráèe
|
||||
GameManager_UI - subsystém pro správu uživatelského rozhraní
|
||||
GamaManager_Stats - subsystém pro správu statistik pro server
|
||||
*/
|
||||
using UnityEngine.SceneManagement;
|
||||
public class GameManager : MonoBehaviour
|
||||
{
|
||||
[Header("Subsystems")]
|
||||
protected GameManager_Network networkSubsystem;
|
||||
protected GameManager_UI uiSubsystem;
|
||||
protected GameManager_Map mapSubsystem;
|
||||
protected GameManager_Input inputSubsystem;
|
||||
// Singleton
|
||||
public static GameManager Instance { get; private set; }
|
||||
|
||||
protected GameClient gameClient;
|
||||
[Header("Subsystems")]
|
||||
public GameManager_Network networkSubsystem;
|
||||
public GameManager_UI uiSubsystem;
|
||||
public GameManager_Map mapSubsystem;
|
||||
public GameManager_Input inputSubsystem;
|
||||
public GameManager_Tasks taskSubsystem;
|
||||
|
||||
public GameClient gameClient;
|
||||
|
||||
[Header("Player Info")]
|
||||
public string displayName;
|
||||
|
||||
[Header("UI Elements")]
|
||||
public Canvas JoinCreateLobby;
|
||||
public Canvas InLobby;
|
||||
public Canvas LoadingScreen;
|
||||
public Canvas GameScreen;
|
||||
[Header("Scene Management")]
|
||||
[SerializeField] public string firstMenuScene = "main menu asi idk lol";
|
||||
|
||||
[Header("UI Elements (Client.unity)")]
|
||||
// Canvas names in Client.unity — found at runtime in OnSceneLoaded
|
||||
private const string CanvasNameJoinCreate = "LobbySelector";
|
||||
private const string CanvasNameInLobby = "InLobby";
|
||||
private const string CanvasNameLoading = "LoadingScreen";
|
||||
private const string CanvasNameGame = "InGame";
|
||||
|
||||
[Header("Map")]
|
||||
public GameObject MapCenterPoint;
|
||||
// MapCenterPoint and Player are in Client.unity — wired at runtime in OnSceneLoaded.
|
||||
// buildingSettings/pathwaySettings/areaSettings must be assigned in SampleScene Inspector.
|
||||
public BuildingSettings buildingSettings;
|
||||
public PathwaySettings pathwaySettings;
|
||||
public AreaSettings areaSettings;
|
||||
|
||||
[Header("GPS")]
|
||||
public GameObject Player;
|
||||
[Header("Lobby Settings")]
|
||||
public double pendingRadius = 500;
|
||||
public int pendingImpostorCount = 1;
|
||||
public int pendingTaskCount = 5;
|
||||
/// <summary>
|
||||
/// P13b/c: full settings overrides accumulated by HostLobbyUI before the
|
||||
/// host taps "Create". Null = host didn't change anything beyond the three
|
||||
/// flat fields above; server falls through to its current defaults for
|
||||
/// every field. Each field is independently nullable so the host can
|
||||
/// opt into changing only what they care about.
|
||||
/// </summary>
|
||||
public GameSettingsOverrides pendingSettings;
|
||||
|
||||
[Header("Task Minigames (round-robin)")]
|
||||
// Names MUST match the scene file names in Assets/Scenes (case-sensitive)
|
||||
// and each one MUST be enabled in EditorBuildSettings, or LoadSceneAsync
|
||||
// will silently fail and the task button will appear dead.
|
||||
[SerializeField] public string[] minigameScenes = {
|
||||
"MiniGame-Kabely",
|
||||
//"MiniGame-insertkeys", Obsolete ?
|
||||
"MiniGame-KeyInsert",
|
||||
//"MiniGame-FlappyBird", Out Of Order, Not assigned prefabs
|
||||
//"MiniGame-ThrowInHole", Not pushed into main branch
|
||||
//"MiniGame-Satelit", No assets, just placeholder task
|
||||
};
|
||||
|
||||
[Header("Debug")]
|
||||
public bool testMode = false;
|
||||
private GameClient _secondClient;
|
||||
private GameClient _thirdClient;
|
||||
private GameManager_Network _secondNetwork;
|
||||
private GameManager_Network _thirdNetwork;
|
||||
/// <summary>
|
||||
/// When true, draw a small GPS status banner across the top of every
|
||||
/// screen. Useful for diagnosing why CreateLobby is blocked or why a
|
||||
/// joiner's position isn't updating - failures otherwise only show up
|
||||
/// in logcat which most users can't reach. Toggle off for release.
|
||||
/// </summary>
|
||||
public bool showGPSDebugOverlay = true;
|
||||
|
||||
/// <summary>
|
||||
/// Number of in-process test client bots to spawn alongside the host
|
||||
/// when testMode is on. Each gets its own GameClient + Network and
|
||||
/// joins the host's lobby automatically. Bots are switchable via
|
||||
/// number keys 1..N (host = 0). Default 3 keeps memory reasonable;
|
||||
/// bump for stress-testing voting / sabotage flows.
|
||||
/// </summary>
|
||||
public int testClientCount = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Per-bot network + display-name + sim-position state. The active slot
|
||||
/// (host = 0, bots = 1..N) gets WASD on the next tick.
|
||||
/// </summary>
|
||||
private class TestBot
|
||||
{
|
||||
public GameClient Client;
|
||||
public GameManager_Network Network;
|
||||
public string DisplayName;
|
||||
public GeoSus.Client.Position SimPosition;
|
||||
public bool Joined;
|
||||
public float LastSendTime;
|
||||
}
|
||||
private System.Collections.Generic.List<TestBot> _testBots = new System.Collections.Generic.List<TestBot>();
|
||||
/// <summary>Slot 0 = host (real player), 1..N = test bot index.</summary>
|
||||
private int _activeClientSlot = 0;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
|
||||
// Keep the screen on while the player is in the app. A geographic
|
||||
// social-deduction game asks the user to walk around for 5-15 minutes
|
||||
// staring at the map; default Android sleep timeout (15-60s) blacks
|
||||
// the screen out mid-round, drops GPS updates, and requires the
|
||||
// player to re-unlock the phone. Two layers of belt-and-suspenders:
|
||||
// (1) Unity's Screen.sleepTimeout, which works on most devices and
|
||||
// is one line, but is overridden by some MIUI/EMUI ROMs.
|
||||
// (2) Android FLAG_KEEP_SCREEN_ON on the activity window, harder for
|
||||
// OEM ROMs to override and the standard pattern for navigation/maps
|
||||
// apps. Wrapped in #if UNITY_ANDROID so editor/iOS skip it.
|
||||
Screen.sleepTimeout = SleepTimeout.NeverSleep;
|
||||
AcquireAndroidWakelock();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set FLAG_KEEP_SCREEN_ON on the Unity activity's window. This is the
|
||||
/// standard navigation/maps-app pattern and survives ROM-level overrides
|
||||
/// of Unity's Screen.sleepTimeout. No-op on non-Android platforms.
|
||||
/// </summary>
|
||||
private static void AcquireAndroidWakelock()
|
||||
{
|
||||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||
try
|
||||
{
|
||||
using (var player = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
|
||||
using (var activity = player.GetStatic<AndroidJavaObject>("currentActivity"))
|
||||
{
|
||||
// addFlags must run on the UI thread. Capture activity into a
|
||||
// local for the closure - AndroidJavaObject can be reused.
|
||||
var act = activity;
|
||||
act.Call("runOnUiThread", new AndroidJavaRunnable(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var window = act.Call<AndroidJavaObject>("getWindow"))
|
||||
{
|
||||
// WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
||||
const int FLAG_KEEP_SCREEN_ON = 0x00000080;
|
||||
window.Call("addFlags", FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogWarning("[Wakelock] addFlags failed: " + ex.Message);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogWarning("[Wakelock] Android JNI bridge failed: " + ex.Message);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
DontDestroyOnLoad(this);
|
||||
if (displayName == null || displayName == "")
|
||||
{
|
||||
displayName = GenerateUsername();
|
||||
}
|
||||
// The prefab default in SampleScene.unity is "Hrac" (Czech for
|
||||
// "player"). Treat it as equivalent to "no name set" so users who
|
||||
// never customize their name don't all show up identically. This
|
||||
// override only fires at startup; users who explicitly type "Hrac"
|
||||
// into the nickname field will still send "Hrac" via the live
|
||||
// DisplayName payload field.
|
||||
if (string.IsNullOrEmpty(displayName) || displayName == "Hrac")
|
||||
displayName = PlayerPrefs.GetString("PlayerName", GenerateUsername());
|
||||
|
||||
gameClient = new GameClient(GenerateUUID(), displayName);
|
||||
networkSubsystem = new GameManager_Network(gameClient, this);
|
||||
mapSubsystem = new GameManager_Map(gameClient, null, buildingSettings, pathwaySettings, areaSettings);
|
||||
uiSubsystem = new GameManager_UI(gameClient);
|
||||
inputSubsystem = new GameManager_Input(gameClient, null, testMode);
|
||||
taskSubsystem = new GameManager_Tasks(gameClient, minigameScenes, this);
|
||||
|
||||
if (testMode)
|
||||
{
|
||||
_secondClient = new GameClient(GenerateUUID(), GenerateUsername());
|
||||
_secondNetwork = new GameManager_Network(_secondClient);
|
||||
_thirdClient = new GameClient(GenerateUUID(), GenerateUsername());
|
||||
_thirdNetwork = new GameManager_Network(_thirdClient);
|
||||
|
||||
_secondNetwork.OpenConection();
|
||||
_thirdNetwork.OpenConection();
|
||||
}
|
||||
gameClient = new GameClient(GenerateUUID(), displayName);
|
||||
uiSubsystem = new GameManager_UI(gameClient, JoinCreateLobby, InLobby, LoadingScreen, GameScreen);
|
||||
networkSubsystem = new GameManager_Network(gameClient);
|
||||
mapSubsystem = new GameManager_Map(gameClient, MapCenterPoint, buildingSettings, pathwaySettings, areaSettings);
|
||||
inputSubsystem = new GameManager_Input(gameClient, Player, testMode);
|
||||
networkSubsystem.OpenConection();
|
||||
}
|
||||
private void Update()
|
||||
{
|
||||
if (gameClient.CurrentLobbyState != null)
|
||||
{
|
||||
uiSubsystem.UpdateLobbyUI();
|
||||
}
|
||||
try
|
||||
{
|
||||
if (gameClient.CurrentLobbyState.MapDataReady)
|
||||
int n = Mathf.Max(0, testClientCount);
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
mapSubsystem.BuildMap();
|
||||
gameClient.CurrentLobbyState.MapDataReady = false;
|
||||
var bot = new TestBot
|
||||
{
|
||||
DisplayName = "TestBot" + (i + 1),
|
||||
};
|
||||
bot.Client = new GameClient(GenerateUUID(), bot.DisplayName);
|
||||
bot.Network = new GameManager_Network(bot.Client, null);
|
||||
bot.Network.OpenConnection();
|
||||
_testBots.Add(bot);
|
||||
}
|
||||
}
|
||||
catch (NullReferenceException ex) { }
|
||||
inputSubsystem.positionCheck();
|
||||
|
||||
networkSubsystem.OpenConnection();
|
||||
|
||||
// Start GPS immediately at app launch. Acquiring a fix on a cold
|
||||
// device can take 5-30 seconds; if we wait until CreateLobby is
|
||||
// pressed, the lobby will be seeded with bad coords. Starting here
|
||||
// means the user's normal navigation through the menus gives the
|
||||
// GPS subsystem time to settle.
|
||||
inputSubsystem?.EnsureGPSStarted();
|
||||
|
||||
// Load main menu after GameManager is ready
|
||||
if (!string.IsNullOrEmpty(firstMenuScene))
|
||||
SceneManager.LoadScene(firstMenuScene, LoadSceneMode.Single);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a GPS status banner across the top of every screen. We use OnGUI
|
||||
/// rather than a uGUI Canvas element because OnGUI works without any
|
||||
/// scene wiring - we want this visible from the very first frame, on
|
||||
/// every screen, even if the lobby canvas hasn't been bound yet. This is
|
||||
/// a debug overlay; toggle showGPSDebugOverlay off for release builds.
|
||||
/// </summary>
|
||||
private void OnGUI()
|
||||
{
|
||||
if (!showGPSDebugOverlay) return;
|
||||
if (inputSubsystem == null) return;
|
||||
|
||||
protected string GenerateUUID()
|
||||
{
|
||||
string UUID = System.Guid.NewGuid().ToString();
|
||||
Debug.Log(UUID);
|
||||
return UUID;
|
||||
}
|
||||
protected string GenerateUsername()
|
||||
{
|
||||
string Username = UnityEngine.Random.Range(0,10).ToString() + UnityEngine.Random.Range(0, 10).ToString() + UnityEngine.Random.Range(0, 10).ToString() + UnityEngine.Random.Range(0, 10).ToString();
|
||||
Debug.Log(Username);
|
||||
return Username;
|
||||
}
|
||||
public void CreateLobbyButton()
|
||||
{
|
||||
networkSubsystem.CrateLobby(50.7727264, 15.0719876);
|
||||
if (testMode)
|
||||
var diag = inputSubsystem.GpsDiagnostic;
|
||||
var label = "GPS: " + diag;
|
||||
|
||||
// Scale font size to screen so it's legible on phones (HDPI) and
|
||||
// editor (lower DPI) alike. Phones tend to have ~400dpi; the
|
||||
// editor game view runs at ~100dpi.
|
||||
int fontSize = Mathf.Max(14, Screen.width / 50);
|
||||
|
||||
var style = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
StartCoroutine(ConnectTestClients());
|
||||
fontSize = fontSize,
|
||||
fontStyle = FontStyle.Bold,
|
||||
alignment = TextAnchor.MiddleLeft,
|
||||
wordWrap = false,
|
||||
normal = { textColor = Color.white }
|
||||
};
|
||||
|
||||
// Width covers most of the screen so longer error strings don't get
|
||||
// clipped. Height auto-fits the chosen font size.
|
||||
float pad = fontSize * 0.5f;
|
||||
float bannerH = fontSize * 2f;
|
||||
var rect = new Rect(pad, pad, Screen.width - pad * 2, bannerH);
|
||||
|
||||
// Translucent black background for legibility against the map.
|
||||
var prevColor = GUI.color;
|
||||
GUI.color = new Color(0f, 0f, 0f, 0.65f);
|
||||
GUI.Box(rect, GUIContent.none);
|
||||
GUI.color = prevColor;
|
||||
|
||||
// Indent the label inside the box.
|
||||
var textRect = new Rect(rect.x + pad, rect.y, rect.width - pad * 2, rect.height);
|
||||
GUI.Label(textRect, label, style);
|
||||
|
||||
// Second row: position-source picker (tap to cycle) + active client
|
||||
// indicator (testMode only). Both are diagnostic; the source picker
|
||||
// is the recovery path when one backend silently fails on a phone.
|
||||
float row2Y = rect.y + bannerH + pad * 0.5f;
|
||||
var btnStyle = new GUIStyle(GUI.skin.button)
|
||||
{
|
||||
fontSize = Mathf.Max(12, fontSize - 2),
|
||||
fontStyle = FontStyle.Bold,
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
};
|
||||
|
||||
// Source button: shows current source name + invites tap.
|
||||
string sourceLabel = "Source: " + inputSubsystem.CurrentSourceName + " [tap to cycle]";
|
||||
// Width sized to the text so the touch area matches the label.
|
||||
Vector2 sourceSize = btnStyle.CalcSize(new GUIContent(sourceLabel));
|
||||
float sourceW = Mathf.Min(Screen.width - pad * 2, sourceSize.x + pad * 2);
|
||||
var sourceRect = new Rect(pad, row2Y, sourceW, bannerH);
|
||||
if (GUI.Button(sourceRect, sourceLabel, btnStyle))
|
||||
{
|
||||
inputSubsystem.CycleNextPositionSource();
|
||||
}
|
||||
|
||||
// Active-client indicator (only when we have test bots).
|
||||
if (testMode && _testBots.Count > 0)
|
||||
{
|
||||
string slot = _activeClientSlot == 0 ? "Host" : ("Bot " + _activeClientSlot);
|
||||
string indicator = $"WASD: {slot} (0..{_testBots.Count} to switch)";
|
||||
var indStyle = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
fontSize = Mathf.Max(12, fontSize - 2),
|
||||
fontStyle = FontStyle.Bold,
|
||||
alignment = TextAnchor.MiddleLeft,
|
||||
normal = { textColor = new Color(0.9f, 1f, 0.4f) },
|
||||
};
|
||||
Vector2 indSize = indStyle.CalcSize(new GUIContent(indicator));
|
||||
var indRect = new Rect(sourceRect.xMax + pad, row2Y, indSize.x + pad * 2, bannerH);
|
||||
GUI.color = new Color(0f, 0f, 0f, 0.65f);
|
||||
GUI.Box(indRect, GUIContent.none);
|
||||
GUI.color = prevColor;
|
||||
GUI.Label(new Rect(indRect.x + pad, indRect.y, indRect.width, indRect.height), indicator, indStyle);
|
||||
}
|
||||
}
|
||||
public void JoinLobbyButton()
|
||||
|
||||
private void Update()
|
||||
{
|
||||
TMP_InputField joinCode = JoinCreateLobby.transform.Find("InputCode").GetComponent<TMP_InputField>();
|
||||
if (joinCode.text != null && joinCode.text != "")
|
||||
// Tick the SDK dispatcher so callbacks fire on main thread
|
||||
gameClient?.Update();
|
||||
if (testMode)
|
||||
{
|
||||
networkSubsystem.JoinLobby(joinCode.text);
|
||||
for (int i = 0; i < _testBots.Count; i++)
|
||||
_testBots[i].Client?.Update();
|
||||
HandleTestBotInput();
|
||||
}
|
||||
|
||||
if (gameClient?.CurrentLobbyState != null)
|
||||
{
|
||||
uiSubsystem?.UpdateLobbyUI();
|
||||
taskSubsystem?.UpdateProximity();
|
||||
}
|
||||
if (gameClient?.MyRole == PlayerRole.Impostor)
|
||||
UpdateKillCooldown();
|
||||
|
||||
inputSubsystem?.positionCheck();
|
||||
|
||||
if (testMode) StepActiveTestBot();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number-key handling for slot switching. 0 = host, 1..N = test bot N.
|
||||
/// Suppress host WASD when a non-host bot is active so the host capsule
|
||||
/// doesn't drift while the user is moving a bot. Only fires when
|
||||
/// testMode is on; release builds never see this path.
|
||||
/// </summary>
|
||||
private void HandleTestBotInput()
|
||||
{
|
||||
// 0 = host. 1..9 = bots (capped by Unity KeyCode.Alpha9).
|
||||
if (Input.GetKeyDown(KeyCode.Alpha0)) _activeClientSlot = 0;
|
||||
for (int i = 1; i <= 9 && i <= _testBots.Count; i++)
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.Alpha0 + i)) _activeClientSlot = i;
|
||||
}
|
||||
|
||||
// Tell the host's input subsystem to ignore WASD when a bot is active.
|
||||
if (inputSubsystem != null)
|
||||
inputSubsystem.SuppressWasd = (_activeClientSlot != 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the active slot is a bot, step its sim position from WASD axes
|
||||
/// and send to the server. Idle bots get a periodic keep-alive so their
|
||||
/// avatars don't time out.
|
||||
/// </summary>
|
||||
private void StepActiveTestBot()
|
||||
{
|
||||
if (_testBots.Count == 0) return;
|
||||
var state = gameClient?.CurrentLobbyState;
|
||||
if (state == null || state.MapData == null) return;
|
||||
|
||||
// Lazy-init each bot's sim position to the lobby's map center on
|
||||
// first lobby state. Until the bot has joined a lobby it can't
|
||||
// send position updates.
|
||||
for (int i = 0; i < _testBots.Count; i++)
|
||||
{
|
||||
var bot = _testBots[i];
|
||||
if (!bot.Joined) continue;
|
||||
if (bot.SimPosition.Lat == 0 && bot.SimPosition.Lon == 0)
|
||||
{
|
||||
// Spawn each bot in a small ring around the map center so
|
||||
// they don't all stack on top of each other on frame one.
|
||||
double offsetLat = 0.00003 * Mathf.Cos(i * Mathf.PI * 2f / Mathf.Max(1, _testBots.Count));
|
||||
double offsetLon = 0.00003 * Mathf.Sin(i * Mathf.PI * 2f / Mathf.Max(1, _testBots.Count));
|
||||
bot.SimPosition = new GeoSus.Client.Position(
|
||||
state.MapData.Center.Lat + offsetLat,
|
||||
state.MapData.Center.Lon + offsetLon);
|
||||
bot.Client.UpdatePosition(bot.SimPosition);
|
||||
bot.LastSendTime = Time.time;
|
||||
}
|
||||
}
|
||||
|
||||
// WASD only drives the active bot.
|
||||
if (_activeClientSlot >= 1 && _activeClientSlot <= _testBots.Count)
|
||||
{
|
||||
var bot = _testBots[_activeClientSlot - 1];
|
||||
if (bot.Joined)
|
||||
{
|
||||
float dx = Input.GetAxis("Horizontal");
|
||||
float dy = Input.GetAxis("Vertical");
|
||||
const double speed = 0.00001;
|
||||
bool moved = Mathf.Abs(dx) > 0.001f || Mathf.Abs(dy) > 0.001f;
|
||||
if (moved)
|
||||
{
|
||||
bot.SimPosition = new GeoSus.Client.Position(
|
||||
bot.SimPosition.Lat + dy * speed,
|
||||
bot.SimPosition.Lon + dx * speed);
|
||||
}
|
||||
// Send on movement OR on keep-alive cadence so the server
|
||||
// doesn't drop our presence.
|
||||
bool dueKeepAlive = (Time.time - bot.LastSendTime) >= 1.0f;
|
||||
if (moved || dueKeepAlive)
|
||||
{
|
||||
bot.Client.UpdatePosition(bot.SimPosition);
|
||||
bot.LastSendTime = Time.time;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("Join code is empty!");
|
||||
// No bot is active. All bots get keep-alive only.
|
||||
for (int i = 0; i < _testBots.Count; i++)
|
||||
{
|
||||
var bot = _testBots[i];
|
||||
if (!bot.Joined) continue;
|
||||
if ((Time.time - bot.LastSendTime) >= 1.0f)
|
||||
{
|
||||
bot.Client.UpdatePosition(bot.SimPosition);
|
||||
bot.LastSendTime = Time.time;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
SceneManager.sceneLoaded += OnSceneLoaded;
|
||||
}
|
||||
void OnDisable()
|
||||
{
|
||||
SceneManager.sceneLoaded -= OnSceneLoaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// After Client.unity loads, re-bind all canvas/HUD references because
|
||||
/// those GameObjects don't exist in the Art menu scenes.
|
||||
/// </summary>
|
||||
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
||||
{
|
||||
if (scene.name == "Client")
|
||||
{
|
||||
var roots = scene.GetRootGameObjects();
|
||||
|
||||
// Find a root or deep GameObject by name in the loaded scene
|
||||
GameObject FindGO(string n) {
|
||||
foreach (var go in roots) {
|
||||
if (go.name == n) return go;
|
||||
var found = go.transform.Find(n);
|
||||
if (found != null) return found.gameObject;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Canvas FindCanvas(string n) {
|
||||
var go = FindGO(n);
|
||||
return go != null ? go.GetComponent<Canvas>() : null;
|
||||
}
|
||||
|
||||
// ── Build HUD BEFORE BindClientScene so FindTMP/Find can locate new elements ──
|
||||
var inGameGO = FindGO("InGame");
|
||||
if (inGameGO != null)
|
||||
{
|
||||
var builder = inGameGO.GetComponent<InGameHUDBuilder>()
|
||||
?? inGameGO.AddComponent<InGameHUDBuilder>();
|
||||
builder.BuildNow();
|
||||
}
|
||||
|
||||
// ── Wire canvases (after HUD is built) ──
|
||||
// Apply our standard CanvasScaler (1080x1920 reference, match=0.5)
|
||||
// to every canvas in the scene before binding so layouts scale
|
||||
// identically across phones and tablets without per-device tweaks.
|
||||
var cJoin = FindCanvas(CanvasNameJoinCreate);
|
||||
var cLobby = FindCanvas(CanvasNameInLobby);
|
||||
var cLoad = FindCanvas(CanvasNameLoading);
|
||||
var cGame = FindCanvas(CanvasNameGame);
|
||||
InGameHUDBuilder.ConfigureCanvasScaler(cJoin);
|
||||
InGameHUDBuilder.ConfigureCanvasScaler(cLobby);
|
||||
InGameHUDBuilder.ConfigureCanvasScaler(cLoad);
|
||||
InGameHUDBuilder.ConfigureCanvasScaler(cGame);
|
||||
uiSubsystem?.BindClientScene(cJoin, cLobby, cLoad, cGame);
|
||||
|
||||
// ── Wire map center point and player capsule ──
|
||||
var mapCenter = FindGO("MapCenterPoint");
|
||||
var player = FindGO("Capsule");
|
||||
mapSubsystem?.SetMapCenterPoint(mapCenter);
|
||||
inputSubsystem?.SetPlayerObject(player);
|
||||
|
||||
// ── Attach camera controller to Main Camera ──
|
||||
var mainCamGO = FindGO("Main Camera");
|
||||
if (mainCamGO != null)
|
||||
{
|
||||
var camCtrl = mainCamGO.GetComponent<MapCameraController>()
|
||||
?? mainCamGO.AddComponent<MapCameraController>();
|
||||
camCtrl.SetTarget(player);
|
||||
}
|
||||
|
||||
// If MapDataReady arrived before Client scene finished loading,
|
||||
// this will build the map now that scene references are valid.
|
||||
networkSubsystem?.OnClientSceneReady();
|
||||
}
|
||||
else if (scene.name == "create" || scene.name == "join loading")
|
||||
{
|
||||
// Lobby scene just loaded — ensure LobbyDisplayUI refreshes once
|
||||
// its Start() has run and registered itself (happens before Update).
|
||||
uiSubsystem?.NotifyLobbyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private float _killCooldownSeconds = 0f;
|
||||
private const float KillCooldownDuration = 20f;
|
||||
|
||||
private void UpdateKillCooldown()
|
||||
{
|
||||
if (_killCooldownSeconds > 0)
|
||||
{
|
||||
_killCooldownSeconds -= Time.deltaTime;
|
||||
// Mirror into GameState so UI reads from the single source of truth
|
||||
if (networkSubsystem?.State != null)
|
||||
networkSubsystem.State.KillCooldownRemaining = _killCooldownSeconds;
|
||||
uiSubsystem?.SetKillCooldownText($"Kill: {Mathf.CeilToInt(_killCooldownSeconds)}s");
|
||||
}
|
||||
else
|
||||
{
|
||||
_killCooldownSeconds = 0f;
|
||||
if (networkSubsystem?.State != null)
|
||||
networkSubsystem.State.KillCooldownRemaining = 0;
|
||||
uiSubsystem?.SetKillCooldownText("");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by the ActionButton. Routes to kill / report / emergency / use-task
|
||||
/// depending on current proximity state.
|
||||
/// </summary>
|
||||
public void PerformAction()
|
||||
{
|
||||
if (uiSubsystem == null || uiSubsystem.IsPlayerDead) return;
|
||||
|
||||
bool isImpostor = gameClient?.MyRole == PlayerRole.Impostor;
|
||||
|
||||
// P13b: pull per-lobby distances from the server-snapshotted settings
|
||||
// instead of hardcoding 5m for every check. ?? fallback keeps the
|
||||
// pre-P13b behavior on old server builds that don't ship settings.
|
||||
var settings = networkSubsystem?.State?.Settings;
|
||||
double reportDist = settings?.ReportDistanceM ?? 5.0;
|
||||
double emergencyDist = settings?.EmergencyMeetingCallRadiusM ?? 5.0;
|
||||
double killDist = settings?.KillDistanceM ?? 5.0;
|
||||
|
||||
// 1. Nearby task → USE
|
||||
var nearbyTask = taskSubsystem?.NearbyTask;
|
||||
if (nearbyTask != null && !isImpostor)
|
||||
{
|
||||
taskSubsystem.TriggerNearbyTask();
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Nearby body → REPORT
|
||||
if (!uiSubsystem.IsCommsBlackout)
|
||||
{
|
||||
var nearbyBody = gameClient?.FindNearbyBody(reportDist);
|
||||
if (nearbyBody != null)
|
||||
{
|
||||
gameClient.ReportBody(nearbyBody.BodyId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Near map centre → EMERGENCY
|
||||
if (gameClient?.CurrentLobbyState?.MapData != null)
|
||||
{
|
||||
double distToCenter = gameClient.MyPosition.DistanceTo(gameClient.CurrentLobbyState.MapData.Center);
|
||||
if (distToCenter <= emergencyDist)
|
||||
{
|
||||
gameClient.CallEmergencyMeeting();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Impostor kill
|
||||
if (isImpostor && _killCooldownSeconds <= 0)
|
||||
{
|
||||
var targetUuid = gameClient?.FindNearbyPlayer(killDist);
|
||||
if (!string.IsNullOrEmpty(targetUuid))
|
||||
{
|
||||
gameClient.Kill(targetUuid);
|
||||
_killCooldownSeconds = KillCooldownDuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Called by Impostor sabotage buttons.</summary>
|
||||
public void StartSabotage(int typeIndex)
|
||||
{
|
||||
gameClient?.Send(new GeoSus.Client.StartSabotage { SabotageType = (SabotageType)typeIndex });
|
||||
}
|
||||
|
||||
/// <summary>Called by the meeting vote buttons. Pass null to skip.</summary>
|
||||
public void CastVote(string targetUuid)
|
||||
{
|
||||
gameClient?.Vote(targetUuid);
|
||||
}
|
||||
|
||||
protected string GenerateUUID()
|
||||
{
|
||||
return System.Guid.NewGuid().ToString();
|
||||
}
|
||||
protected string GenerateUsername()
|
||||
{
|
||||
return "Player" + UnityEngine.Random.Range(1000, 9999).ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pull the nickname input field's current text into displayName +
|
||||
/// gameClient.DisplayName + PlayerPrefs before sending a network
|
||||
/// action. Defensive against any TMP_InputField / soft-keyboard race
|
||||
/// where the user types and immediately taps a button: onValueChanged
|
||||
/// normally fires before the click handler in the same frame, but
|
||||
/// some Android keyboards batch text events oddly. Call this at the
|
||||
/// top of any Create/Join/Rename flow. No-op if the input field
|
||||
/// doesn't exist in the current scene.
|
||||
/// </summary>
|
||||
private void CommitNicknameFromInput()
|
||||
{
|
||||
var nameGO = GameObject.Find("name");
|
||||
if (nameGO == null) return;
|
||||
var field = nameGO.GetComponent<TMPro.TMP_InputField>();
|
||||
if (field == null) return;
|
||||
// Force the InputField to flush any pending soft-keyboard text.
|
||||
// ForceLabelUpdate() is harmless if there's nothing pending.
|
||||
field.ForceLabelUpdate();
|
||||
string typed = (field.text ?? "").Trim();
|
||||
if (string.IsNullOrEmpty(typed)) return;
|
||||
if (typed == displayName) return; // already in sync, skip the writes
|
||||
displayName = typed;
|
||||
if (gameClient != null) gameClient.DisplayName = typed;
|
||||
PlayerPrefs.SetString("PlayerName", typed);
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
|
||||
// Called by HostLobbyUI
|
||||
public void CreateLobbyButton()
|
||||
{
|
||||
CommitNicknameFromInput();
|
||||
// Refuse to create a lobby without a real GPS fix. The previous
|
||||
// behavior of silently using a hardcoded Czechia fallback meant the
|
||||
// game always started at the same place no matter where the host was,
|
||||
// and the player capsule would spawn miles away in coordinate space
|
||||
// because they're at their real GPS while the map was built around
|
||||
// the fallback. Both bugs share this single gate.
|
||||
if (inputSubsystem?.LastKnownPosition == null)
|
||||
{
|
||||
// testMode bypasses the GPS gate entirely so debug runs still work.
|
||||
if (!testMode)
|
||||
{
|
||||
// Surface the actual GPS state in both logs and the toast
|
||||
// instead of the generic "Waiting for GPS fix..." that hides
|
||||
// permission/timeout/device-disabled distinctions.
|
||||
string diag = inputSubsystem?.GpsDiagnostic ?? "no input subsystem";
|
||||
Debug.LogWarning("[GameManager] CreateLobby blocked. " + diag);
|
||||
uiSubsystem?.ShowToast("Cannot create lobby. " + diag);
|
||||
inputSubsystem?.EnsureGPSStarted();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var pos = inputSubsystem?.LastKnownPosition;
|
||||
double lat = pos?.Lat ?? 0;
|
||||
double lon = pos?.Lon ?? 0;
|
||||
networkSubsystem.CreateLobby(lat, lon, pendingRadius, pendingImpostorCount, pendingTaskCount, pendingSettings);
|
||||
if (testMode) StartCoroutine(ConnectTestClients());
|
||||
}
|
||||
|
||||
// Called by JoinLobbyUI with the code from the input field
|
||||
public void JoinLobbyButton(string code)
|
||||
{
|
||||
CommitNicknameFromInput();
|
||||
if (!string.IsNullOrEmpty(code))
|
||||
networkSubsystem.JoinLobby(code);
|
||||
else
|
||||
Debug.LogWarning("Join code is empty!");
|
||||
}
|
||||
|
||||
public void LeaveLobbyButton()
|
||||
{
|
||||
networkSubsystem.LeaveLobby();
|
||||
}
|
||||
|
||||
public void StartGameButton()
|
||||
{
|
||||
networkSubsystem.StartGame();
|
||||
}
|
||||
|
||||
void OnApplicationQuit()
|
||||
{
|
||||
gameClient.Disconnect();
|
||||
_secondClient?.Disconnect();
|
||||
_thirdClient?.Disconnect();
|
||||
gameClient?.Disconnect();
|
||||
for (int i = 0; i < _testBots.Count; i++)
|
||||
_testBots[i].Client?.Disconnect();
|
||||
}
|
||||
|
||||
IEnumerator ConnectTestClients()
|
||||
{
|
||||
yield return new WaitForSeconds(2f);
|
||||
_secondNetwork.JoinLobby(gameClient.CurrentLobbyState.JoinCode);
|
||||
_thirdNetwork.JoinLobby(gameClient.CurrentLobbyState.JoinCode);
|
||||
if (_testBots.Count == 0) yield break;
|
||||
|
||||
// Wait until host lobby code exists
|
||||
float wait = 0f;
|
||||
while ((gameClient?.CurrentLobbyState == null || string.IsNullOrEmpty(gameClient.CurrentLobbyState.JoinCode)) && wait < 20f)
|
||||
{
|
||||
wait += 0.25f;
|
||||
yield return new WaitForSeconds(0.25f);
|
||||
}
|
||||
|
||||
var joinCode = gameClient?.CurrentLobbyState?.JoinCode;
|
||||
if (string.IsNullOrEmpty(joinCode))
|
||||
{
|
||||
Debug.LogWarning("[TestMode] Could not join test bots: join code not available.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Wait until every bot's client has finished its TCP handshake.
|
||||
// IsReady flips once ClientHello + ClientHelloAck round-trip.
|
||||
wait = 0f;
|
||||
bool allReady;
|
||||
do
|
||||
{
|
||||
allReady = true;
|
||||
for (int i = 0; i < _testBots.Count; i++)
|
||||
{
|
||||
if (_testBots[i].Client == null || !_testBots[i].Client.IsReady)
|
||||
{
|
||||
allReady = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!allReady)
|
||||
{
|
||||
wait += 0.25f;
|
||||
yield return new WaitForSeconds(0.25f);
|
||||
}
|
||||
} while (!allReady && wait < 20f);
|
||||
|
||||
if (!allReady)
|
||||
{
|
||||
Debug.LogWarning("[TestMode] Some test bots not ready, joining the ready ones only.");
|
||||
}
|
||||
|
||||
for (int i = 0; i < _testBots.Count; i++)
|
||||
{
|
||||
var bot = _testBots[i];
|
||||
if (bot.Client != null && bot.Client.IsReady)
|
||||
{
|
||||
bot.Network?.JoinLobby(joinCode);
|
||||
bot.Joined = true;
|
||||
}
|
||||
}
|
||||
Debug.Log($"[TestMode] {_testBots.Count} bot(s) joined lobby with code {joinCode}.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e2c3e4ba4e36ea40a686e58feca4d2b
|
||||
guid: 22bf82e679cf6e1419440d236360ba3b
|
||||
@@ -3,9 +3,8 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using UnityEditor;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Localization.Pseudo;
|
||||
using UnityEngine.UI;
|
||||
|
||||
|
||||
@@ -13,8 +12,8 @@ namespace Subsystems{
|
||||
[System.Serializable]
|
||||
public class BuildingSettings
|
||||
{
|
||||
public Material ResidentalBuildingsMat;
|
||||
public float ResidentalBuildingHeight;
|
||||
public Material ResidentialBuildingsMat;
|
||||
public float ResidentialBuildingHeight;
|
||||
public Material CommercialBuildingsMat;
|
||||
public float CommercialBuildingHeight;
|
||||
public Material IndustrialBuildingsMat;
|
||||
@@ -66,6 +65,43 @@ namespace Subsystems{
|
||||
private PathwaySettings _pathwaySettings;
|
||||
private AreaSettings _areaSettings;
|
||||
private const float _metersPerUnit = 1f;
|
||||
|
||||
// ── Layer Y separation (single source of truth for vertical stacking) ───
|
||||
// Areas at the bottom, paths above areas, buildings extruded upward from
|
||||
// their own base, POIs floating well above everything else. Z-fighting
|
||||
// happens when adjacent geometry shares a Y; these constants keep each
|
||||
// logical layer at a distinct elevation.
|
||||
private const float kAreaBaseY = 0.10f;
|
||||
private const float kPathY = 0.30f;
|
||||
private const float kBuildingBaseY = 0.50f;
|
||||
private const float kPoiY = 2.00f;
|
||||
|
||||
// Render-queue forcing was tried in P3 to disambiguate same-Y geometry
|
||||
// but turned out to be the cause of the "blank map in mobile game view,
|
||||
// fine in scene view" regression: forcing transparent-class shaders
|
||||
// (default queue 3000+) into the Geometry range (2000-2150) breaks
|
||||
// their depth-write/blend assumptions on mobile shader paths. The
|
||||
// editor's scene view masks it because it uses different render paths
|
||||
// and post-process is off there. Queue forcing removed in P8;
|
||||
// disambiguation is now via Y-layering + per-area Y-stagger alone,
|
||||
// which the depth buffer resolves correctly even on weak mobile GPUs.
|
||||
|
||||
// ── Marker sizing (top-down camera, units = meters) ─────────────────
|
||||
// The camera's orthographic size pushes "1 meter" to a small fraction
|
||||
// of the screen. Markers need to be visibly larger than buildings'
|
||||
// footprints for instant recognition.
|
||||
private const float kMarkerHeight = 8f; // pillar height
|
||||
private const float kMarkerRadius = 3f; // pillar radius (cylinder X/Z)
|
||||
private const float kMarkerY = 4f; // base Y so pillar centers ~mid-height
|
||||
private const float kLabelY = 9f; // text label sits above pillar top
|
||||
private const float kLabelFontSize = 14f; // 3D text size in world units
|
||||
|
||||
// Runtime marker collections
|
||||
private Dictionary<string, GameObject> _taskMarkers = new Dictionary<string, GameObject>();
|
||||
private Dictionary<string, GameObject> _bodyMarkers = new Dictionary<string, GameObject>();
|
||||
private Dictionary<string, GameObject> _playerAvatars = new Dictionary<string, GameObject>();
|
||||
private List<GameObject> _sabotageMarkers = new List<GameObject>();
|
||||
|
||||
public GameManager_Map(GameClient gameClient, GameObject mapCenterPoint, BuildingSettings buildingSettings, PathwaySettings pathwaySettings, AreaSettings areaSettings)
|
||||
{
|
||||
_gameClient = gameClient;
|
||||
@@ -74,8 +110,25 @@ namespace Subsystems{
|
||||
_pathwaySettings = pathwaySettings;
|
||||
_areaSettings = areaSettings;
|
||||
}
|
||||
|
||||
public bool IsSceneReady => _mapCenterPoint != null;
|
||||
|
||||
/// <summary>Called from OnSceneLoaded when Client.unity is loaded so the
|
||||
/// MapCenterPoint (which lives in Client.unity) can be wired at runtime.</summary>
|
||||
public void SetMapCenterPoint(GameObject go) { _mapCenterPoint = go; }
|
||||
public void BuildMap()
|
||||
{
|
||||
if (_mapCenterPoint == null)
|
||||
{
|
||||
Debug.LogWarning("[Map] BuildMap skipped: MapCenterPoint is not yet bound.");
|
||||
return;
|
||||
}
|
||||
if (_gameClient?.CurrentLobbyState?.MapData == null)
|
||||
{
|
||||
Debug.LogWarning("[Map] BuildMap skipped: no MapData in CurrentLobbyState.");
|
||||
return;
|
||||
}
|
||||
|
||||
ClearChildren();
|
||||
_centerPosition = _gameClient.CurrentLobbyState.MapData.Center;
|
||||
GameObject buildingsRoot = new GameObject("Buildings");
|
||||
@@ -109,7 +162,133 @@ namespace Subsystems{
|
||||
GameObject a = BuildAreaMesh(area);
|
||||
a.transform.parent = areaRoot.transform;
|
||||
}
|
||||
//TODO: POIs
|
||||
|
||||
GameObject poiRoot = new GameObject("POIs");
|
||||
poiRoot.transform.parent = _mapCenterPoint.transform;
|
||||
int poiCount = 0;
|
||||
foreach (var poi in _gameClient.CurrentLobbyState.MapData.GetPOIs())
|
||||
{
|
||||
GameObject p = BuildPOIMarker(poi);
|
||||
if (p != null) { p.transform.parent = poiRoot.transform; poiCount++; }
|
||||
}
|
||||
|
||||
// Diagnostic - if the user reports "map missing in game view" but
|
||||
// the counts here are non-zero, the bug is camera/culling related,
|
||||
// not a build issue.
|
||||
int buildings = _gameClient.CurrentLobbyState.MapData.GetBuildings()?.Count ?? 0;
|
||||
int paths = _gameClient.CurrentLobbyState.MapData.GetPathways()?.Count ?? 0;
|
||||
int areas = _gameClient.CurrentLobbyState.MapData.GetAreas()?.Count ?? 0;
|
||||
Debug.Log($"[Map] BuildMap done: {buildings} buildings, {paths} paths, " +
|
||||
$"{areas} areas, {poiCount} POIs. MapCenterPoint={_mapCenterPoint.name} " +
|
||||
$"layer={_mapCenterPoint.layer} pos={_mapCenterPoint.transform.position} " +
|
||||
$"scale={_mapCenterPoint.transform.localScale}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build a tall, brightly-colored pillar for a Point of Interest with
|
||||
/// a 3D text label above it (e.g. "FOOD", "SHOP"). The label is laid
|
||||
/// flat on the XZ plane facing UP so it reads correctly under the
|
||||
/// orthogonal top-down camera.
|
||||
/// </summary>
|
||||
private GameObject BuildPOIMarker(MapPOI poi)
|
||||
{
|
||||
if (poi == null) return null;
|
||||
var color = ColorForPOI(poi.POIType);
|
||||
string label = LabelForPOI(poi.POIType);
|
||||
var pos = poi.Location.ToLocalVector3(_centerPosition);
|
||||
return CreateMarkerWithLabel($"POI_{poi.POIType}_{poi.Id}", pos, color, label);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shared marker builder: tall colored cylinder pillar + 3D text label
|
||||
/// above it. Used by POIs, tasks, bodies, and sabotage stations so
|
||||
/// they all share a visual language ("colored pillar with a name").
|
||||
/// </summary>
|
||||
private GameObject CreateMarkerWithLabel(string name, Vector3 worldPos, Color color, string label)
|
||||
{
|
||||
var go = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
|
||||
go.name = name;
|
||||
|
||||
// Strip the auto-added collider - markers are visual only.
|
||||
var col = go.GetComponent<Collider>();
|
||||
if (col != null) UnityEngine.Object.Destroy(col);
|
||||
|
||||
go.transform.position = worldPos + Vector3.up * kMarkerY;
|
||||
// Cylinder's default unit is 2 tall, 1 wide. Scale Y by half of
|
||||
// kMarkerHeight (built-in is 2 units), X/Z by kMarkerRadius.
|
||||
go.transform.localScale = new Vector3(kMarkerRadius, kMarkerHeight * 0.5f, kMarkerRadius);
|
||||
|
||||
var mr = go.GetComponent<MeshRenderer>();
|
||||
if (mr != null)
|
||||
{
|
||||
// One .material access -> single clone of the primitive's
|
||||
// default mat. Don't touch renderQueue (P3 regression cause).
|
||||
var inst = mr.material;
|
||||
if (inst != null) inst.color = color;
|
||||
}
|
||||
|
||||
// 3D text label - lays flat on top of the pillar facing up.
|
||||
// Parented to the marker so it follows position changes.
|
||||
var labelGO = new GameObject("Label");
|
||||
labelGO.transform.SetParent(go.transform, worldPositionStays: false);
|
||||
// Local Y offset: pillar's local scale Y is kMarkerHeight/2, but
|
||||
// the cylinder primitive is 2 units tall in local space, so its
|
||||
// top is at local +1. Label sits a hair above that.
|
||||
labelGO.transform.localPosition = new Vector3(0, 1.05f, 0);
|
||||
// Rotate 90 around X so the text quad's normal points +Y (toward
|
||||
// the top-down camera). The default TMP forward is +Z.
|
||||
labelGO.transform.localRotation = Quaternion.Euler(90f, 0f, 0f);
|
||||
// Compensate for the cylinder's non-uniform parent scale so the
|
||||
// text size in world units matches kLabelFontSize regardless of
|
||||
// how the pillar was scaled.
|
||||
labelGO.transform.localScale = new Vector3(
|
||||
1f / kMarkerRadius,
|
||||
1f / (kMarkerHeight * 0.5f),
|
||||
1f / kMarkerRadius);
|
||||
|
||||
var tmp = labelGO.AddComponent<TextMeshPro>();
|
||||
tmp.text = label;
|
||||
tmp.fontSize = kLabelFontSize;
|
||||
tmp.color = Color.white;
|
||||
tmp.fontStyle = FontStyles.Bold;
|
||||
tmp.alignment = TextAlignmentOptions.Center;
|
||||
tmp.outlineColor = Color.black;
|
||||
tmp.outlineWidth = 0.25f;
|
||||
// Reasonable bounds so the text mesh isn't auto-clipped.
|
||||
var rt = tmp.rectTransform;
|
||||
rt.sizeDelta = new Vector2(20, 4);
|
||||
|
||||
return go;
|
||||
}
|
||||
|
||||
private static Color ColorForPOI(MapPOIType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case MapPOIType.FoodDrink: return new Color(1.00f, 0.55f, 0.00f); // orange
|
||||
case MapPOIType.Shop: return new Color(0.20f, 0.60f, 1.00f); // blue
|
||||
case MapPOIType.Health: return new Color(0.96f, 0.27f, 0.27f); // red
|
||||
case MapPOIType.Transport: return new Color(0.85f, 0.85f, 0.20f); // yellow
|
||||
case MapPOIType.Culture: return new Color(0.65f, 0.30f, 0.95f); // purple
|
||||
case MapPOIType.Landmark: return new Color(0.95f, 0.85f, 0.40f); // gold
|
||||
case MapPOIType.Recreation: return new Color(0.30f, 0.85f, 0.30f); // green
|
||||
default: return new Color(0.75f, 0.75f, 0.80f); // muted grey
|
||||
}
|
||||
}
|
||||
|
||||
private static string LabelForPOI(MapPOIType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case MapPOIType.FoodDrink: return "FOOD";
|
||||
case MapPOIType.Shop: return "SHOP";
|
||||
case MapPOIType.Health: return "HEALTH";
|
||||
case MapPOIType.Transport: return "TRANSIT";
|
||||
case MapPOIType.Culture: return "CULTURE";
|
||||
case MapPOIType.Landmark: return "LANDMARK";
|
||||
case MapPOIType.Recreation: return "PARK";
|
||||
default: return "POI";
|
||||
}
|
||||
}
|
||||
void ClearChildren()
|
||||
{
|
||||
@@ -126,9 +305,12 @@ namespace Subsystems{
|
||||
{
|
||||
var building = new GameObject($"Building_{b.Name ?? "Unknown"}");
|
||||
|
||||
// Výpočet středu budovy
|
||||
// Výpočet středu budovy. Lift the base above kPathY so building
|
||||
// walls visibly extrude *upward* from above the road/area layer
|
||||
// instead of starting at ground (which made them clip into paved
|
||||
// areas that share their footprint).
|
||||
Vector3 center = CalculatePolygonCenter(b.Outline);
|
||||
building.transform.position = center;
|
||||
building.transform.position = center + Vector3.up * kBuildingBaseY;
|
||||
|
||||
// Vytvoření mesh pro budovu
|
||||
MeshFilter meshFilter = building.AddComponent<MeshFilter>();
|
||||
@@ -139,8 +321,8 @@ namespace Subsystems{
|
||||
switch (b.BuildingType.ToLower())
|
||||
{
|
||||
case "residential":
|
||||
mat = _buildingSettings.ResidentalBuildingsMat;
|
||||
height = _buildingSettings.ResidentalBuildingHeight;
|
||||
mat = _buildingSettings.ResidentialBuildingsMat;
|
||||
height = _buildingSettings.ResidentialBuildingHeight;
|
||||
break;
|
||||
case "commercial":
|
||||
mat = _buildingSettings.CommercialBuildingsMat;
|
||||
@@ -159,8 +341,12 @@ namespace Subsystems{
|
||||
meshFilter.mesh = mesh;
|
||||
|
||||
//TODO: material by type
|
||||
// Použijeme barvu podle typu budovy
|
||||
meshRenderer.material = mat;
|
||||
// Použijeme barvu podle typu budovy. Use sharedMaterial to keep
|
||||
// the project's Material asset reference - no clone, no leak.
|
||||
// Y-position alone disambiguates building geometry from area/path
|
||||
// layers; we don't need renderQueue overrides (which broke mobile
|
||||
// rendering for transparent-class shaders in P3).
|
||||
meshRenderer.sharedMaterial = mat;
|
||||
|
||||
// Přidání collideru pro interakci
|
||||
building.AddComponent<MeshCollider>();
|
||||
@@ -219,15 +405,19 @@ namespace Subsystems{
|
||||
break;
|
||||
}
|
||||
|
||||
line.material = mat;
|
||||
// sharedMaterial avoids the LineRenderer cloning the project's
|
||||
// shared path Material on every BuildMap call. Queue overrides
|
||||
// dropped (P3 mobile-render regression cause).
|
||||
line.sharedMaterial = mat;
|
||||
line.widthMultiplier = width;
|
||||
|
||||
// Nastavení bodů cesty
|
||||
// Nastavení bodů cesty - kPathY sits above all area polygons but
|
||||
// below building bases, so paths visibly run on top of areas.
|
||||
line.positionCount = w.Points.Count;
|
||||
for (int i = 0; i < w.Points.Count; i++)
|
||||
{
|
||||
Vector3 pos = w.Points[i].ToLocalVector3(_gameClient.CurrentLobbyState.MapData.Center);
|
||||
pos.y = 0.1f; // Mírně nad zemí
|
||||
pos.y = kPathY;
|
||||
line.SetPosition(i, pos);
|
||||
}
|
||||
return path;
|
||||
@@ -270,13 +460,58 @@ namespace Subsystems{
|
||||
break;
|
||||
}
|
||||
|
||||
meshRenderer.material = mat;
|
||||
// sharedMaterial: no per-area material clone. Render-queue forcing
|
||||
// dropped in P8 (caused mobile-render regression). The Y-stagger
|
||||
// below alone now drives "smaller polygon on top of larger one"
|
||||
// depth ordering - which is what the depth buffer was always
|
||||
// designed to do, and works on mobile GPUs with weak precision
|
||||
// because the stagger spread (0.04 units) is well above any
|
||||
// reasonable depth-buffer epsilon.
|
||||
meshRenderer.sharedMaterial = mat;
|
||||
|
||||
area.transform.position = new Vector3(0, 0.05f, 0); // Těsně nad zemí
|
||||
// Y stagger: smaller polygons sit a hair higher than larger ones,
|
||||
// so depth-test draws them on top of bigger area polygons they sit
|
||||
// inside (e.g. a playground inside a park). Total spread is 0.04
|
||||
// units - visually invisible but plenty for the depth buffer.
|
||||
float yStagger = ComputeAreaYStagger(a.Outline);
|
||||
area.transform.position = new Vector3(0, kAreaBaseY + yStagger, 0);
|
||||
|
||||
return area;
|
||||
}
|
||||
//TODO: POIs
|
||||
|
||||
/// <summary>
|
||||
/// Returns a non-negative size proxy used to bucket areas by footprint.
|
||||
/// Larger polygons return higher numbers; used inversely for queue/Y.
|
||||
/// </summary>
|
||||
private float AreaSizeBucket(List<Position> outline)
|
||||
{
|
||||
if (outline == null || outline.Count < 3) return 1f;
|
||||
// Cheap bbox area in lat-lon space scaled by 1e6 - we only need a
|
||||
// monotonic ordering, not a real geographic area.
|
||||
double minLat = outline[0].Lat, maxLat = outline[0].Lat;
|
||||
double minLon = outline[0].Lon, maxLon = outline[0].Lon;
|
||||
for (int i = 1; i < outline.Count; i++)
|
||||
{
|
||||
if (outline[i].Lat < minLat) minLat = outline[i].Lat;
|
||||
if (outline[i].Lat > maxLat) maxLat = outline[i].Lat;
|
||||
if (outline[i].Lon < minLon) minLon = outline[i].Lon;
|
||||
if (outline[i].Lon > maxLon) maxLon = outline[i].Lon;
|
||||
}
|
||||
double bbox = (maxLat - minLat) * (maxLon - minLon) * 1e6;
|
||||
return (float)System.Math.Max(0.001, bbox);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Smaller areas get a higher Y so they render on top of any larger
|
||||
/// area they overlap. Returns a value in [0, 0.04] units.
|
||||
/// </summary>
|
||||
private float ComputeAreaYStagger(List<Position> outline)
|
||||
{
|
||||
float bucket = AreaSizeBucket(outline);
|
||||
// Inverse mapping: huge area -> 0, tiny area -> 0.04.
|
||||
float t = Mathf.Clamp01(1f - bucket / (bucket + 50f));
|
||||
return t * 0.04f;
|
||||
}
|
||||
#endregion
|
||||
#region Polygon Utils
|
||||
private Vector3 CalculatePolygonCenter(List<Position> points)
|
||||
@@ -288,19 +523,52 @@ namespace Subsystems{
|
||||
}
|
||||
return center / points.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signed XZ shoelace area for a polygon expressed in local Vector3.
|
||||
/// Positive = CCW (Unity left-handed Y-up: upward-facing normal),
|
||||
/// negative = CW (downward-facing normal -> top face invisible from
|
||||
/// above unless we reverse the winding before triangulating).
|
||||
/// </summary>
|
||||
private static float PolygonSignedAreaXZ(List<Vector3> verts)
|
||||
{
|
||||
float area = 0f;
|
||||
int n = verts.Count;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
var a = verts[i];
|
||||
var b = verts[(i + 1) % n];
|
||||
area += (b.x - a.x) * (a.z + b.z);
|
||||
}
|
||||
return area * 0.5f;
|
||||
}
|
||||
private Mesh CreateExtrudedPolygonMesh(List<Position> outline, float height)
|
||||
{
|
||||
Mesh mesh = new Mesh();
|
||||
|
||||
// Reject degenerates - Recast/Overpass can hand back 1-2 vertex
|
||||
// outlines on broken ways. Empty mesh -> renderer draws nothing,
|
||||
// safer than a malformed triangle list.
|
||||
if (outline == null || outline.Count < 3) return mesh;
|
||||
|
||||
// Convert to local space first so we can run a winding check, then
|
||||
// reverse if needed. Without this, CW outlines from Overpass yield
|
||||
// downward-facing top normals and the building roof is invisible
|
||||
// from the top-down map camera.
|
||||
int vertexCount = outline.Count;
|
||||
var localVerts = new List<Vector3>(vertexCount);
|
||||
Vector3 center = CalculatePolygonCenter(outline);
|
||||
for (int i = 0; i < vertexCount; i++)
|
||||
localVerts.Add(outline[i].ToLocalVector3(_gameClient.CurrentLobbyState.MapData.Center) - center);
|
||||
|
||||
if (PolygonSignedAreaXZ(localVerts) < 0f)
|
||||
localVerts.Reverse();
|
||||
|
||||
// Vertices - spodní a horní podstava
|
||||
Vector3[] vertices = new Vector3[vertexCount * 2];
|
||||
Vector3 center = CalculatePolygonCenter(outline);
|
||||
|
||||
for (int i = 0; i < vertexCount; i++)
|
||||
{
|
||||
Vector3 pos = outline[i].ToLocalVector3(_gameClient.CurrentLobbyState.MapData.Center) - center;
|
||||
Vector3 pos = localVerts[i];
|
||||
vertices[i] = pos; // Spodní
|
||||
vertices[i + vertexCount] = pos + Vector3.up * height; // Horní
|
||||
}
|
||||
@@ -344,25 +612,30 @@ namespace Subsystems{
|
||||
{
|
||||
Mesh mesh = new Mesh();
|
||||
|
||||
int vertexCount = outline.Count;
|
||||
Vector3[] vertices = new Vector3[vertexCount];
|
||||
Vector3 center = CalculatePolygonCenter(outline);
|
||||
// Reject degenerates (matches CreateExtrudedPolygonMesh).
|
||||
if (outline == null || outline.Count < 3) return mesh;
|
||||
|
||||
int vertexCount = outline.Count;
|
||||
var localVerts = new List<Vector3>(vertexCount);
|
||||
Vector3 center = CalculatePolygonCenter(outline);
|
||||
for (int i = 0; i < vertexCount; i++)
|
||||
{
|
||||
vertices[i] = outline[i].ToLocalVector3(_gameClient.CurrentLobbyState.MapData.Center) - center;
|
||||
}
|
||||
localVerts.Add(outline[i].ToLocalVector3(_gameClient.CurrentLobbyState.MapData.Center) - center);
|
||||
|
||||
// Force CCW so RecalculateNormals produces an upward-facing normal.
|
||||
// CW polygons from Overpass would otherwise render as black voids
|
||||
// when the top-down camera looks at their back face.
|
||||
if (PolygonSignedAreaXZ(localVerts) < 0f)
|
||||
localVerts.Reverse();
|
||||
|
||||
Vector3[] vertices = localVerts.ToArray();
|
||||
|
||||
// Triangulace - fan pattern
|
||||
List<int> triangles = new List<int>();
|
||||
if (vertexCount >= 3)
|
||||
for (int i = 1; i < vertexCount - 1; i++)
|
||||
{
|
||||
for (int i = 1; i < vertexCount - 1; i++)
|
||||
{
|
||||
triangles.Add(0);
|
||||
triangles.Add(i);
|
||||
triangles.Add(i + 1);
|
||||
}
|
||||
triangles.Add(0);
|
||||
triangles.Add(i);
|
||||
triangles.Add(i + 1);
|
||||
}
|
||||
|
||||
mesh.vertices = vertices;
|
||||
@@ -372,5 +645,164 @@ namespace Subsystems{
|
||||
return mesh;
|
||||
}
|
||||
#endregion
|
||||
#region Markers
|
||||
|
||||
public void CreateTaskMarkers(List<GeoSus.Client.GameTask> tasks)
|
||||
{
|
||||
if (_mapCenterPoint == null) return;
|
||||
if (_centerPosition.Lat == 0 && _centerPosition.Lon == 0)
|
||||
{
|
||||
var md = _gameClient?.CurrentLobbyState?.MapData;
|
||||
if (md != null) _centerPosition = md.Center;
|
||||
}
|
||||
if (_centerPosition.Lat == 0 && _centerPosition.Lon == 0) return;
|
||||
var taskColor = new Color(0.20f, 0.95f, 0.55f); // bright green - "GO HERE"
|
||||
foreach (var task in tasks)
|
||||
{
|
||||
if (_taskMarkers.ContainsKey(task.TaskId)) continue;
|
||||
var pos = task.Location.ToLocalVector3(_centerPosition);
|
||||
var go = CreateMarkerWithLabel($"Task_{task.TaskId}", pos, taskColor, "TASK");
|
||||
go.transform.parent = _mapCenterPoint.transform;
|
||||
|
||||
// Pulsing point light so the task literally glows on the map.
|
||||
var light = go.AddComponent<Light>();
|
||||
light.color = taskColor;
|
||||
light.intensity = 3f;
|
||||
light.range = 25f;
|
||||
_taskMarkers[task.TaskId] = go;
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveTaskMarker(string taskId)
|
||||
{
|
||||
if (_taskMarkers.TryGetValue(taskId, out var go))
|
||||
{
|
||||
UnityEngine.Object.Destroy(go);
|
||||
_taskMarkers.Remove(taskId);
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateBodyMarker(string bodyId, Position location)
|
||||
{
|
||||
if (_mapCenterPoint == null) return;
|
||||
if (_bodyMarkers.ContainsKey(bodyId)) return;
|
||||
var pos = location.ToLocalVector3(_centerPosition);
|
||||
// Bright red pillar with "BODY" label - players need to see this
|
||||
// from across the map to call it in.
|
||||
var go = CreateMarkerWithLabel($"Body_{bodyId}", pos,
|
||||
new Color(0.96f, 0.18f, 0.18f), "BODY");
|
||||
go.transform.parent = _mapCenterPoint?.transform;
|
||||
_bodyMarkers[bodyId] = go;
|
||||
}
|
||||
|
||||
public void ClearBodyMarkers()
|
||||
{
|
||||
foreach (var go in _bodyMarkers.Values)
|
||||
if (go) UnityEngine.Object.Destroy(go);
|
||||
_bodyMarkers.Clear();
|
||||
}
|
||||
|
||||
// ── Player avatar sizing ────────────────────────────────────────────
|
||||
// The default Unity capsule primitive is 2m tall in local space. The
|
||||
// map camera defaults to 150m orthographic-ish height (see
|
||||
// MapCameraController), so anything smaller than ~3m world-size is a
|
||||
// pixel on screen. Original code used scale=0.4 (~0.8m capsule) which
|
||||
// was invisible. Markers (POIs/tasks/bodies) are 8m pillars; players
|
||||
// need to be visibly distinct from those AND from each other. The
|
||||
// local player gets a halo light + larger scale so the user can find
|
||||
// themselves on the map at a glance.
|
||||
private const float kLocalPlayerScale = 4f; // ~8m capsule (matches marker height)
|
||||
private const float kRemotePlayerScale = 2f; // ~4m capsule (smaller than markers)
|
||||
private const float kLocalPlayerHaloRange = 18f;
|
||||
private const float kLocalPlayerHaloIntensity = 2.5f;
|
||||
|
||||
public void UpdatePlayerAvatars(Dictionary<string, PlayerPositionInfo> positions, string myUuid)
|
||||
{
|
||||
if (_mapCenterPoint == null) return;
|
||||
if (_centerPosition.Lat == 0 && _centerPosition.Lon == 0)
|
||||
{
|
||||
var md = _gameClient?.CurrentLobbyState?.MapData;
|
||||
if (md != null) _centerPosition = md.Center;
|
||||
}
|
||||
if (_centerPosition.Lat == 0 && _centerPosition.Lon == 0) return;
|
||||
foreach (var kvp in positions)
|
||||
{
|
||||
string uuid = kvp.Key;
|
||||
var info = kvp.Value;
|
||||
bool isLocal = uuid == myUuid;
|
||||
if (!_playerAvatars.TryGetValue(uuid, out var go) || go == null)
|
||||
{
|
||||
go = GameObject.CreatePrimitive(PrimitiveType.Capsule);
|
||||
go.name = $"Player_{uuid.Substring(0, Mathf.Min(8, uuid.Length))}";
|
||||
go.transform.parent = _mapCenterPoint?.transform;
|
||||
// Strip the auto-collider - avatars are visual only and the
|
||||
// collider would interact with the map's MeshColliders.
|
||||
var col = go.GetComponent<Collider>();
|
||||
if (col != null) UnityEngine.Object.Destroy(col);
|
||||
|
||||
float scale = isLocal ? kLocalPlayerScale : kRemotePlayerScale;
|
||||
go.transform.localScale = Vector3.one * scale;
|
||||
|
||||
if (isLocal)
|
||||
{
|
||||
// Halo light around the local player so the user can
|
||||
// find themselves at a glance even at the widest zoom.
|
||||
// Range/intensity tuned so it reads as "this is me"
|
||||
// without bleeding far enough to drown POI markers.
|
||||
var halo = go.AddComponent<Light>();
|
||||
halo.color = new Color(0.30f, 1.00f, 0.55f); // matches green capsule color
|
||||
halo.intensity = kLocalPlayerHaloIntensity;
|
||||
halo.range = kLocalPlayerHaloRange;
|
||||
}
|
||||
|
||||
_playerAvatars[uuid] = go;
|
||||
}
|
||||
|
||||
// Lift the avatar so the bottom of the capsule sits roughly at
|
||||
// ground level despite the larger scale. Capsule's local pivot
|
||||
// is at center, height = 2 * localScale.y world units, so we
|
||||
// raise by half the local height.
|
||||
float halfHeight = (isLocal ? kLocalPlayerScale : kRemotePlayerScale);
|
||||
go.transform.position = info.Position.ToLocalVector3(_centerPosition)
|
||||
+ Vector3.up * halfHeight;
|
||||
|
||||
var mr = go.GetComponent<MeshRenderer>();
|
||||
if (mr)
|
||||
{
|
||||
if (isLocal) mr.material.color = new Color(0.30f, 1.00f, 0.55f);
|
||||
else if (info.State == GeoSus.Client.PlayerState.Dead) mr.material.color = Color.grey;
|
||||
else mr.material.color = Color.white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateSabotageMarkers(List<RepairStationInfo> stations)
|
||||
{
|
||||
var color = new Color(1.0f, 0.55f, 0.0f); // strong orange = repair urgency
|
||||
foreach (var station in stations)
|
||||
{
|
||||
var pos = station.Location.ToLocalVector3(_centerPosition);
|
||||
var go = CreateMarkerWithLabel($"Sabotage_{station.StationId}", pos,
|
||||
color, "REPAIR");
|
||||
go.transform.parent = _mapCenterPoint?.transform;
|
||||
|
||||
// Repair stations also pulse light so impostors and crew see
|
||||
// the urgency from across the map.
|
||||
var light = go.AddComponent<Light>();
|
||||
light.color = color;
|
||||
light.intensity = 4f;
|
||||
light.range = 30f;
|
||||
_sabotageMarkers.Add(go);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearSabotageMarkers()
|
||||
{
|
||||
foreach (var go in _sabotageMarkers)
|
||||
if (go) UnityEngine.Object.Destroy(go);
|
||||
_sabotageMarkers.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using Subsystems;
|
||||
using System.Linq;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Subsystems
|
||||
{
|
||||
@@ -13,9 +14,34 @@ namespace Subsystems
|
||||
private const string _serverAddress = "geosus.honzuvkod.dev";
|
||||
private const int _serverPort = 7777;
|
||||
private GameClient _gameClient;
|
||||
private GameManager_Map _mapSubsystem;
|
||||
public async void OpenConection()
|
||||
private GameManager _manager;
|
||||
private bool _pendingMapBuild;
|
||||
|
||||
/// <summary>
|
||||
/// Authoritative game state; written here, read by GameManager_UI.
|
||||
/// </summary>
|
||||
public GameState State { get; } = new GameState();
|
||||
|
||||
public GameManager_Network(GameClient gameClient, GameManager manager)
|
||||
{
|
||||
_gameClient = gameClient;
|
||||
_manager = manager;
|
||||
RegisterEventHandlers();
|
||||
}
|
||||
|
||||
public async void OpenConnection()
|
||||
{
|
||||
// Snapshot the lobby we believed we were in BEFORE the new connect
|
||||
// attempt. If the client SDK preserved it across a transient drop
|
||||
// (P9 fix), this is non-null and we'll send a Reconnect message
|
||||
// post-handshake to re-associate with the lobby on the server side.
|
||||
// Without it, the next CastVote / TaskComplete / etc. would arrive
|
||||
// on a fresh connection the server doesn't recognize and bounce
|
||||
// with NOT_IN_LOBBY.
|
||||
var rejoinLobbyId = _gameClient.LobbyId;
|
||||
|
||||
int retries = 0;
|
||||
int delayMs = 5000;
|
||||
while (true)
|
||||
{
|
||||
Task<bool> state = _gameClient.ConnectAsync(_serverAddress, _serverPort);
|
||||
@@ -23,139 +49,544 @@ namespace Subsystems
|
||||
if (state.Result)
|
||||
{
|
||||
Debug.Log("Connected to server.");
|
||||
|
||||
// Re-attach to the prior lobby if we had one. Server-side
|
||||
// HandleReconnectAsync will replay missed events and ack
|
||||
// with a ReconnectResponse carrying the snapshot.
|
||||
if (!string.IsNullOrEmpty(rejoinLobbyId))
|
||||
{
|
||||
Debug.Log($"Re-associating with lobby {rejoinLobbyId} after reconnect.");
|
||||
_gameClient.Reconnect(rejoinLobbyId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
retries++;
|
||||
if (retries >= 10)
|
||||
{
|
||||
Debug.Log("Failed to connect to server");
|
||||
Debug.LogError("Failed to connect after 10 attempts. Giving up.");
|
||||
break;
|
||||
}
|
||||
await Task.Delay(5000);
|
||||
Debug.Log($"Failed to connect (attempt {retries}). Retrying in {delayMs / 1000}s...");
|
||||
await Task.Delay(delayMs);
|
||||
delayMs = Mathf.Min(delayMs * 2, 30000);
|
||||
}
|
||||
}
|
||||
public GameManager_Network(GameClient gameClient)
|
||||
{
|
||||
_gameClient = gameClient;
|
||||
RegisterEventHandlers();
|
||||
}
|
||||
|
||||
public void RegisterEventHandlers()
|
||||
{
|
||||
_gameClient.OnConnected += OnConnected;
|
||||
_gameClient.OnConnected += OnConnected;
|
||||
_gameClient.OnDisconnected += OnDisconnected;
|
||||
_gameClient.OnError += OnError;
|
||||
_gameClient.OnMessage += OnMessage;
|
||||
_gameClient.OnGameEvent += OnGameEvent;
|
||||
_gameClient.OnError += OnError;
|
||||
_gameClient.OnMessage += OnMessage;
|
||||
_gameClient.OnGameEvent += OnGameEvent;
|
||||
}
|
||||
|
||||
private void OnConnected()
|
||||
{
|
||||
Debug.Log("Successfully connected to the server.");
|
||||
// Tear the reconnect overlay down once the socket is healthy.
|
||||
// No-op if it wasn't shown.
|
||||
_manager?.uiSubsystem?.HideReconnecting();
|
||||
}
|
||||
|
||||
private void OnError(string e) => Debug.LogError($"Network error: {e}");
|
||||
|
||||
private void OnDisconnected(string reason)
|
||||
{
|
||||
Debug.Log($"Host disconnected due to {reason}");
|
||||
Debug.Log($"Disconnected: {reason}");
|
||||
// Show the reconnect overlay only if the user is mid-game; we
|
||||
// don't want it flashing during a clean shutdown ("Disposed") or
|
||||
// before a real game has started.
|
||||
if (reason != "Disposed" && State.Phase != GamePhase.Lobby)
|
||||
_manager?.uiSubsystem?.ShowReconnecting();
|
||||
|
||||
if (reason != "Disposed" && _manager != null)
|
||||
_manager.StartCoroutine(ReconnectAfterDelay(3f));
|
||||
}
|
||||
private void OnError(string error)
|
||||
|
||||
private IEnumerator ReconnectAfterDelay(float seconds)
|
||||
{
|
||||
Debug.LogError($"Network error: {error}");
|
||||
yield return new UnityEngine.WaitForSeconds(seconds);
|
||||
Debug.Log("Attempting to reconnect...");
|
||||
OpenConnection();
|
||||
}
|
||||
|
||||
private void OnMessage(Message message)
|
||||
{
|
||||
switch (message.Type)
|
||||
{
|
||||
case "GameEvent":
|
||||
OnGameEvent(message as GameEvent);
|
||||
break;
|
||||
case "CreateLobbyResponse":
|
||||
Debug.Log("Received CreateLobbyResponse message");
|
||||
HandleCreateLobbyResponse(message as CreateLobbyResponse);
|
||||
break;
|
||||
case "JoinLobbyResponse":
|
||||
Debug.Log("Received JoinLobbyResponse message");
|
||||
HandleJoinLobbyResponse(message as JoinLobbyResponse);
|
||||
break;
|
||||
case "PositionBroadcast":
|
||||
HandlePositionBroadcast(message as PositionBroadcast);
|
||||
break;
|
||||
case "Error":
|
||||
HandleErrorMessage(message as ErrorMessage);
|
||||
break;
|
||||
case "Ack":
|
||||
Debug.Log("Received Ack message");
|
||||
case "GameEvent":
|
||||
break;
|
||||
default:
|
||||
Debug.Log("Received message of type: " + message.Type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// P9 defensive path: if the server tells us NOT_IN_LOBBY but we still
|
||||
/// believe we have a lobby (LobbyId preserved across the transient
|
||||
/// disconnect), the lobby association on the server's side of the new
|
||||
/// connection is missing - typically a race between OpenConnection's
|
||||
/// Reconnect call and an in-flight action message that beat it. Retry
|
||||
/// the Reconnect; if the second attempt also bounces, the lobby really
|
||||
/// is gone and we'll surface the error to the user.
|
||||
/// </summary>
|
||||
private void HandleErrorMessage(ErrorMessage err)
|
||||
{
|
||||
if (err == null) return;
|
||||
Debug.Log($"Server error: code={err.ErrorCode} text={err.ErrorText}");
|
||||
|
||||
if (err.ErrorCode == "NOT_IN_LOBBY" && !string.IsNullOrEmpty(_gameClient.LobbyId))
|
||||
{
|
||||
Debug.Log($"NOT_IN_LOBBY but we still have LobbyId={_gameClient.LobbyId}; resending Reconnect.");
|
||||
_gameClient.Reconnect(_gameClient.LobbyId);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGameEvent(GameEvent gameEvent)
|
||||
{
|
||||
// Always sync player list from lobby state after any event
|
||||
SyncPlayersFromLobby();
|
||||
|
||||
switch (gameEvent.EventType)
|
||||
{
|
||||
case "PlayerJoined":
|
||||
Debug.Log($"Player {gameEvent.GetPayload<PlayerJoinedPayload>().DisplayName} joined");
|
||||
break;
|
||||
case "PlayerLeft":
|
||||
Debug.Log($"Player {gameEvent.GetPayload<PlayerLeftPayload>()} left");
|
||||
case "HostChanged":
|
||||
_manager?.uiSubsystem?.NotifyLobbyChanged();
|
||||
break;
|
||||
|
||||
case "GameStarting":
|
||||
Debug.Log("Game is starting!");
|
||||
break;
|
||||
case "GameStarted":
|
||||
Debug.Log("Game started");
|
||||
State.Phase = GamePhase.Loading;
|
||||
HandleGameStarting();
|
||||
break;
|
||||
|
||||
case "MapDataReady":
|
||||
Debug.Log("Map data ready");
|
||||
HandleMapDataReady();
|
||||
break;
|
||||
case "PlayerMapDataReceived":
|
||||
Debug.Log("Player map data recieved");
|
||||
|
||||
case "GameStarted":
|
||||
State.Phase = GamePhase.Playing;
|
||||
break;
|
||||
|
||||
case "RoleAssigned":
|
||||
HandleRoleAssigned(gameEvent);
|
||||
break;
|
||||
|
||||
case "TaskCompleted":
|
||||
HandleTaskCompleted(gameEvent);
|
||||
break;
|
||||
|
||||
case "PlayerKilled":
|
||||
HandlePlayerKilled(gameEvent);
|
||||
break;
|
||||
|
||||
case "BodyReported":
|
||||
case "EmergencyMeetingCalled":
|
||||
Toast("Meeting called! Head to the meeting point.");
|
||||
break;
|
||||
|
||||
case "MeetingStarted":
|
||||
HandleMeetingStarted(gameEvent);
|
||||
break;
|
||||
|
||||
case "PlayerArrivedAtMeeting":
|
||||
HandlePlayerArrivedAtMeeting(gameEvent);
|
||||
break;
|
||||
|
||||
case "PlayerVoted":
|
||||
HandlePlayerVoted(gameEvent);
|
||||
break;
|
||||
|
||||
case "VotingClosed":
|
||||
HandleVotingClosed(gameEvent);
|
||||
break;
|
||||
|
||||
case "GameEnded":
|
||||
HandleGameEnded(gameEvent);
|
||||
break;
|
||||
|
||||
case "ReturnedToLobby":
|
||||
HandleReturnedToLobby();
|
||||
break;
|
||||
|
||||
case "SabotageStarted":
|
||||
HandleSabotageStarted(gameEvent);
|
||||
break;
|
||||
|
||||
case "RepairStarted":
|
||||
HandleRepairStarted(gameEvent);
|
||||
break;
|
||||
|
||||
case "RepairStopped":
|
||||
HandleRepairStopped(gameEvent);
|
||||
break;
|
||||
|
||||
case "SabotageRepaired":
|
||||
case "SabotageMeltdown":
|
||||
case "SabotageExpired":
|
||||
State.ActiveSabotage = null;
|
||||
State.ActiveRepairs.Clear();
|
||||
_manager?.uiSubsystem?.HideSabotageTimer();
|
||||
_manager?.mapSubsystem?.ClearSabotageMarkers();
|
||||
break;
|
||||
|
||||
case "TaskStarted":
|
||||
// Server now broadcasts when a player begins a task. Phase 1
|
||||
// only acks; Phase 2/3 will surface this to other players.
|
||||
break;
|
||||
|
||||
case "MapDataError":
|
||||
Debug.Log("Received MapData server error");
|
||||
HandleMapDataError(gameEvent);
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.Log("Received GameEvent of type: " + gameEvent.EventType);
|
||||
Debug.Log("GameEvent: " + gameEvent.EventType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Lobby responses ───────────────────────────────────────────────────
|
||||
|
||||
private void HandleCreateLobbyResponse(CreateLobbyResponse message)
|
||||
{
|
||||
if (message == null) return;
|
||||
if (message.Success)
|
||||
{
|
||||
Debug.Log("Lobby created successfully. Join Code: " + message.JoinCode + ", Lobby ID: " + message.LobbyId);
|
||||
Debug.Log($"Lobby created. Code: {message.JoinCode}");
|
||||
// P13b: snapshot the server's authoritative settings into
|
||||
// GameState so HUD / proximity code can read distances and
|
||||
// cooldowns from a single source of truth instead of hardcodes.
|
||||
State.Settings = _gameClient.CurrentLobbyState?.Settings;
|
||||
SceneManager.LoadScene("create", LoadSceneMode.Single);
|
||||
_manager?.uiSubsystem?.NotifyLobbyChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Failed to create lobby: " + message.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleJoinLobbyResponse(JoinLobbyResponse message)
|
||||
{
|
||||
if (message == null) return;
|
||||
if (message.Success)
|
||||
{
|
||||
Debug.Log("Lobby created successfully." + ", Lobby ID: " + message.LobbyId);
|
||||
Debug.Log($"Joined lobby: {message.LobbyId}");
|
||||
// P13b: same settings snapshot path as host. Joiners read the
|
||||
// server's snapshot taken at lobby creation; they cannot edit.
|
||||
State.Settings = _gameClient.CurrentLobbyState?.Settings;
|
||||
// Unified lobby: both host and joiners land on create.unity.
|
||||
// LobbyDisplayUI handles the role split internally (start
|
||||
// button for host, waiting text for joiners).
|
||||
SceneManager.LoadScene("create", LoadSceneMode.Single);
|
||||
_manager?.uiSubsystem?.NotifyLobbyChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Failed to create lobby: " + message.Error);
|
||||
Debug.LogError("Failed to join lobby: " + message.Error);
|
||||
}
|
||||
}
|
||||
public void CrateLobby(double lat, double lon)
|
||||
|
||||
// ── Game flow ─────────────────────────────────────────────────────────
|
||||
|
||||
private void HandleGameStarting()
|
||||
{
|
||||
_gameClient.CreateLobby(new Position(lat, lon));
|
||||
_pendingMapBuild = false;
|
||||
// Reset per-game state
|
||||
State.MyRole = null;
|
||||
State.IsDead = false;
|
||||
State.MyTasks = new List<GameTask>();
|
||||
State.MyCompletedTaskIds = new HashSet<string>();
|
||||
State.TotalCompleted = 0;
|
||||
State.TotalRequired = 0;
|
||||
State.ActiveMeeting = null;
|
||||
State.LastVoteResult = null;
|
||||
State.VotedPlayerIds = new HashSet<string>();
|
||||
State.ActiveSabotage = null;
|
||||
State.GameEndData = null;
|
||||
State.KillCooldownRemaining = 0;
|
||||
SceneManager.LoadScene("Client", LoadSceneMode.Single);
|
||||
}
|
||||
|
||||
private void HandleMapDataReady()
|
||||
{
|
||||
_pendingMapBuild = true;
|
||||
TryBuildMapAndMarkers();
|
||||
}
|
||||
|
||||
public void OnClientSceneReady()
|
||||
{
|
||||
TryBuildMapAndMarkers();
|
||||
}
|
||||
|
||||
private void TryBuildMapAndMarkers()
|
||||
{
|
||||
if (!_pendingMapBuild) return;
|
||||
if (_manager?.mapSubsystem == null || !_manager.mapSubsystem.IsSceneReady) return;
|
||||
if (_gameClient?.CurrentLobbyState?.MapData == null) return;
|
||||
|
||||
_manager.mapSubsystem.BuildMap();
|
||||
_manager.mapSubsystem.CreateTaskMarkers(_gameClient.MyTasks);
|
||||
_pendingMapBuild = false;
|
||||
Debug.Log("[Network] Map built.");
|
||||
}
|
||||
|
||||
private void HandleRoleAssigned(GameEvent evt)
|
||||
{
|
||||
var payload = evt.GetPayload<RoleAssignedPayload>();
|
||||
if (payload == null || payload.ClientUuid != _gameClient.ClientUuid) return;
|
||||
|
||||
State.MyRole = payload.Role;
|
||||
State.MyTasks = payload.Tasks ?? new List<GameTask>();
|
||||
State.MyCompletedTaskIds.Clear();
|
||||
|
||||
Debug.Log($"Role: {payload.Role}, Tasks: {State.MyTasks.Count}");
|
||||
_manager?.taskSubsystem?.Initialize(State.MyTasks);
|
||||
}
|
||||
|
||||
private void HandleTaskCompleted(GameEvent evt)
|
||||
{
|
||||
var payload = evt.GetPayload<TaskCompletedPayload>();
|
||||
if (payload == null) return;
|
||||
|
||||
// Track if it's our task
|
||||
if (payload.ClientUuid == _gameClient.ClientUuid)
|
||||
State.MyCompletedTaskIds.Add(payload.TaskId);
|
||||
|
||||
State.TotalCompleted = payload.TotalCompleted;
|
||||
State.TotalRequired = payload.TotalTasks;
|
||||
|
||||
_manager?.uiSubsystem?.UpdateTaskProgress(payload.TotalCompleted, payload.TotalTasks);
|
||||
_manager?.mapSubsystem?.RemoveTaskMarker(payload.TaskId);
|
||||
}
|
||||
|
||||
private void HandlePlayerKilled(GameEvent evt)
|
||||
{
|
||||
var payload = evt.GetPayload<PlayerKilledPayload>();
|
||||
if (payload == null) return;
|
||||
|
||||
_manager?.mapSubsystem?.CreateBodyMarker(payload.BodyId, payload.Location);
|
||||
|
||||
if (payload.VictimId == _gameClient.ClientUuid)
|
||||
{
|
||||
State.IsDead = true;
|
||||
_manager?.uiSubsystem?.OnLocalPlayerDied();
|
||||
}
|
||||
|
||||
// Update player state in our list
|
||||
var p = State.Players.Find(x => x.ClientUuid == payload.VictimId);
|
||||
if (p != null) p.State = PlayerState.Dead;
|
||||
}
|
||||
|
||||
private void HandleMeetingStarted(GameEvent evt)
|
||||
{
|
||||
var payload = evt.GetPayload<MeetingStartedPayload>();
|
||||
if (payload == null) return;
|
||||
|
||||
State.Phase = GamePhase.Meeting;
|
||||
State.ActiveMeeting = payload;
|
||||
State.VotedPlayerIds = new HashSet<string>();
|
||||
State.ArrivedPlayerIds = new HashSet<string>();
|
||||
State.VoterTargets = new Dictionary<string, string>();
|
||||
State.VoteTallies = new Dictionary<string, int>();
|
||||
State.MyVoteTarget = null;
|
||||
State.LastVoteResult = null;
|
||||
|
||||
SyncPlayersFromLobby();
|
||||
_manager?.uiSubsystem?.ShowMeetingPanel(State.Players, payload);
|
||||
}
|
||||
|
||||
private void HandlePlayerArrivedAtMeeting(GameEvent evt)
|
||||
{
|
||||
var payload = evt.GetPayload<PlayerArrivedAtMeetingPayload>();
|
||||
if (payload == null) return;
|
||||
State.ArrivedPlayerIds.Add(payload.ClientUuid);
|
||||
}
|
||||
|
||||
private void HandlePlayerVoted(GameEvent evt)
|
||||
{
|
||||
var payload = evt.GetPayload<PlayerVotedPayload>();
|
||||
if (payload == null) return;
|
||||
|
||||
// Server allows vote changes within a 2s rate limit, so we always
|
||||
// overwrite the voter's previous target rather than appending.
|
||||
string target = payload.TargetId ?? GameState.VoteSkip;
|
||||
|
||||
State.VotedPlayerIds.Add(payload.VoterId);
|
||||
State.VoterTargets[payload.VoterId] = target;
|
||||
RecomputeVoteTallies();
|
||||
|
||||
if (payload.VoterId == _gameClient.ClientUuid)
|
||||
State.MyVoteTarget = target;
|
||||
}
|
||||
|
||||
private void RecomputeVoteTallies()
|
||||
{
|
||||
State.VoteTallies.Clear();
|
||||
foreach (var t in State.VoterTargets.Values)
|
||||
{
|
||||
if (string.IsNullOrEmpty(t)) continue;
|
||||
State.VoteTallies.TryGetValue(t, out var count);
|
||||
State.VoteTallies[t] = count + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleVotingClosed(GameEvent evt)
|
||||
{
|
||||
var payload = evt.GetPayload<VotingClosedPayload>();
|
||||
if (payload == null) return;
|
||||
|
||||
State.Phase = GamePhase.Playing;
|
||||
State.ActiveMeeting = null;
|
||||
State.LastVoteResult = payload;
|
||||
|
||||
// Mark ejected player dead in our list
|
||||
if (!string.IsNullOrEmpty(payload.EjectedPlayerId))
|
||||
{
|
||||
var p = State.Players.Find(x => x.ClientUuid == payload.EjectedPlayerId);
|
||||
if (p != null) p.State = PlayerState.Dead;
|
||||
}
|
||||
|
||||
_manager?.uiSubsystem?.ShowVoteResult(payload, State.Players);
|
||||
_manager?.mapSubsystem?.ClearBodyMarkers();
|
||||
}
|
||||
|
||||
private void HandleGameEnded(GameEvent evt)
|
||||
{
|
||||
var payload = evt.GetPayload<GameEndedPayload>();
|
||||
if (payload == null) return;
|
||||
|
||||
State.Phase = GamePhase.Ended;
|
||||
State.GameEndData = payload;
|
||||
|
||||
// If the round ended while the meeting/vote-result overlay was
|
||||
// still up (e.g. ejection won the game outright), the auto-close
|
||||
// coroutine would otherwise fire 5s later and tear down the
|
||||
// meeting panel while the GameEndPanel sits on top - leaving a
|
||||
// glimpse of the dead overlay during the transition.
|
||||
_manager?.uiSubsystem?.HideMeetingPanel();
|
||||
_manager?.uiSubsystem?.ShowGameEndPanel(payload, _gameClient.ClientUuid);
|
||||
}
|
||||
|
||||
private void HandleReturnedToLobby()
|
||||
{
|
||||
State.Phase = GamePhase.Lobby;
|
||||
_manager?.uiSubsystem?.HideMeetingPanel();
|
||||
// Bodies survive the scene reload because the marker GameObjects are
|
||||
// parented under MapCenterPoint (which lives in the persistent
|
||||
// Client.unity scene). Without this clear, returning to lobby and
|
||||
// starting a new round leaves stale corpses on the map of the new
|
||||
// round. Server already cleared its `_bodies` set in
|
||||
// ProcessReturnToLobby; this is the client-side mirror that was
|
||||
// missing in HandleVotingClosed's symmetry.
|
||||
_manager?.mapSubsystem?.ClearBodyMarkers();
|
||||
_manager?.mapSubsystem?.ClearSabotageMarkers();
|
||||
// Unified lobby: regardless of role, return to create.unity.
|
||||
SceneManager.LoadScene("create", LoadSceneMode.Single);
|
||||
}
|
||||
|
||||
private void HandleSabotageStarted(GameEvent evt)
|
||||
{
|
||||
var payload = evt.GetPayload<SabotageStartedPayload>();
|
||||
if (payload == null) return;
|
||||
|
||||
State.ActiveSabotage = payload;
|
||||
State.ActiveRepairs.Clear();
|
||||
|
||||
_manager?.mapSubsystem?.CreateSabotageMarkers(payload.RepairStations);
|
||||
if (payload.Type == SabotageType.CriticalMeltdown && payload.Deadline.HasValue)
|
||||
_manager?.uiSubsystem?.ShowSabotageTimer(payload.Deadline.Value);
|
||||
if (payload.Type == SabotageType.CommsBlackout)
|
||||
_manager?.uiSubsystem?.SetCommsBlackout(true);
|
||||
}
|
||||
|
||||
private void HandleRepairStarted(GameEvent evt)
|
||||
{
|
||||
var payload = evt.GetPayload<RepairStartedPayload>();
|
||||
if (payload == null || string.IsNullOrEmpty(payload.StationId)) return;
|
||||
State.ActiveRepairs.Add(payload.StationId);
|
||||
}
|
||||
|
||||
private void HandleRepairStopped(GameEvent evt)
|
||||
{
|
||||
// A player abandoned a repair station mid-fix. The station is no
|
||||
// longer counted as active for the simultaneous-repair coaching;
|
||||
// the marker stays on the map until the sabotage resolves.
|
||||
var payload = evt.GetPayload<RepairStoppedPayload>();
|
||||
if (payload != null && !string.IsNullOrEmpty(payload.StationId))
|
||||
State.ActiveRepairs.Remove(payload.StationId);
|
||||
}
|
||||
|
||||
private void HandleMapDataError(GameEvent evt)
|
||||
{
|
||||
// Server failed to fetch Overpass data. Without this the loading
|
||||
// screen would hang forever. Drop back to lobby and surface the
|
||||
// failure so the player can re-host or try a different center.
|
||||
Debug.LogError("[Network] Server could not generate map data.");
|
||||
State.Phase = GamePhase.Lobby;
|
||||
_manager?.uiSubsystem?.ShowToast("Map fetch failed. Returning to lobby.");
|
||||
LeaveLobby();
|
||||
}
|
||||
|
||||
private void HandlePositionBroadcast(PositionBroadcast broadcast)
|
||||
{
|
||||
if (broadcast == null) return;
|
||||
_manager?.mapSubsystem?.UpdatePlayerAvatars(_gameClient.PlayerPositions, _gameClient.ClientUuid);
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
private void SyncPlayersFromLobby()
|
||||
{
|
||||
var lobby = _gameClient.CurrentLobbyState;
|
||||
if (lobby?.Players != null)
|
||||
State.Players = lobby.Players;
|
||||
}
|
||||
|
||||
private void Toast(string message)
|
||||
{
|
||||
State.ToastMessage = message;
|
||||
State.ToastExpiry = UnityEngine.Time.time + 4f;
|
||||
}
|
||||
|
||||
// ── Send helpers ──────────────────────────────────────────────────────
|
||||
|
||||
public void CreateLobby(double lat, double lon, double radius = 500, int impostorCount = 1, int taskCount = 5, GameSettingsOverrides settings = null)
|
||||
{
|
||||
_gameClient.CreateLobby(new Position(lat, lon), impostorCount, taskCount, null, radius, settings);
|
||||
}
|
||||
|
||||
public void JoinLobby(string joinCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
_gameClient.JoinLobby(joinCode);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogError("Error joining lobby: " + ex.Message);
|
||||
}
|
||||
try { _gameClient.JoinLobby(joinCode); }
|
||||
catch (System.Exception ex) { Debug.LogError("JoinLobby error: " + ex.Message); }
|
||||
}
|
||||
|
||||
public void LeaveLobby()
|
||||
{
|
||||
_gameClient.Disconnect();
|
||||
Application.Quit();
|
||||
_gameClient.LeaveLobby();
|
||||
State.Phase = GamePhase.Lobby;
|
||||
SceneManager.LoadScene(_manager?.firstMenuScene ?? "main menu asi idk lol", LoadSceneMode.Single);
|
||||
}
|
||||
|
||||
public void StartGame()
|
||||
{
|
||||
_gameClient.StartGame();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c2032ed1184ad7418cc415edf97b69e
|
||||
guid: 989e9292fe24c2a4ba95ceae191dd330
|
||||
328
Assets/GameManager/GameManager_Tasks.cs
Normal file
@@ -0,0 +1,328 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using GeoSus.Client;
|
||||
|
||||
namespace Subsystems
|
||||
{
|
||||
/// <summary>
|
||||
/// Round-robin task-to-minigame assignment, proximity detection, additive scene launch.
|
||||
/// </summary>
|
||||
public class GameManager_Tasks
|
||||
{
|
||||
private class TaskEntry
|
||||
{
|
||||
public GeoSus.Client.GameTask ServerTask;
|
||||
public string MinigameScene;
|
||||
public bool Completed;
|
||||
}
|
||||
|
||||
private GameClient _gameClient;
|
||||
private string[] _minigameScenes;
|
||||
private MonoBehaviour _host; // GameManager MonoBehaviour for coroutines
|
||||
private List<TaskEntry> _tasks = new List<TaskEntry>();
|
||||
private bool _minigameOpen;
|
||||
private string _loadedMinigameScene;
|
||||
private Camera _hostCameraSuspended;
|
||||
private GameObject _hostInGameHudHidden;
|
||||
|
||||
// Proximity state (checked every frame in UpdateProximity)
|
||||
public GeoSus.Client.GameTask NearbyTask { get; private set; }
|
||||
|
||||
// P13b: per-check distances pulled from the server-snapshotted lobby
|
||||
// settings (null-fallback to 5m matches the old hardcoded behavior).
|
||||
// Different actions use different fields so a host can tune e.g. a
|
||||
// long-range "spotter" task radius without also widening kill range.
|
||||
private const float ProximityRadiusFallback = 5f;
|
||||
|
||||
public GameManager_Tasks(GameClient gameClient, string[] minigameScenes, MonoBehaviour host)
|
||||
{
|
||||
_gameClient = gameClient;
|
||||
_minigameScenes = minigameScenes ?? new string[0];
|
||||
_host = host;
|
||||
}
|
||||
|
||||
/// <summary>Called by Network subsystem when RoleAssigned fires.</summary>
|
||||
public void Initialize(List<GeoSus.Client.GameTask> serverTasks)
|
||||
{
|
||||
_tasks.Clear();
|
||||
if (_minigameScenes.Length == 0) return;
|
||||
|
||||
for (int i = 0; i < serverTasks.Count; i++)
|
||||
{
|
||||
_tasks.Add(new TaskEntry
|
||||
{
|
||||
ServerTask = serverTasks[i],
|
||||
MinigameScene = _minigameScenes[i % _minigameScenes.Length],
|
||||
Completed = false
|
||||
});
|
||||
}
|
||||
|
||||
// Create map markers
|
||||
GameManager.Instance?.mapSubsystem?.CreateTaskMarkers(serverTasks);
|
||||
Debug.Log($"[Tasks] Initialized {_tasks.Count} tasks.");
|
||||
}
|
||||
|
||||
/// <summary>Called every frame from GameManager.Update().</summary>
|
||||
public void UpdateProximity()
|
||||
{
|
||||
if (_minigameOpen) return;
|
||||
|
||||
// P13b: distances now come from the per-lobby settings snapshot
|
||||
// instead of one hardcoded 5m radius for everything. ?? fallback
|
||||
// matches the old behavior when running against an old server.
|
||||
var state = GameManager.Instance?.networkSubsystem?.State;
|
||||
var settings = state?.Settings;
|
||||
double taskDist = settings?.TaskStartDistanceM ?? ProximityRadiusFallback;
|
||||
double reportDist = settings?.ReportDistanceM ?? ProximityRadiusFallback;
|
||||
double emergencyDist = settings?.EmergencyMeetingCallRadiusM?? ProximityRadiusFallback;
|
||||
double killDist = settings?.KillDistanceM ?? ProximityRadiusFallback;
|
||||
|
||||
NearbyTask = null;
|
||||
var myPos = _gameClient.MyPosition;
|
||||
if (myPos.Lat == 0 && myPos.Lon == 0) return;
|
||||
|
||||
foreach (var entry in _tasks)
|
||||
{
|
||||
if (entry.Completed) continue;
|
||||
double dist = myPos.DistanceTo(entry.ServerTask.Location);
|
||||
if (dist <= taskDist)
|
||||
{
|
||||
NearbyTask = entry.ServerTask;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Drive the action button in UI
|
||||
var ui = GameManager.Instance?.uiSubsystem;
|
||||
if (ui == null || ui.IsPlayerDead) return;
|
||||
|
||||
bool isImpostor = _gameClient.MyRole == GeoSus.Client.PlayerRole.Impostor;
|
||||
|
||||
if (!isImpostor && NearbyTask != null)
|
||||
{
|
||||
ui.SetActionButton("USE", true, () => GameManager.Instance?.PerformAction());
|
||||
return;
|
||||
}
|
||||
|
||||
// Check body proximity
|
||||
if (!ui.IsCommsBlackout)
|
||||
{
|
||||
var body = _gameClient.FindNearbyBody(reportDist);
|
||||
if (body != null)
|
||||
{
|
||||
ui.SetActionButton("REPORT", true, () => GameManager.Instance?.PerformAction());
|
||||
return;
|
||||
}
|
||||
|
||||
// Emergency meeting proximity
|
||||
if (_gameClient.CurrentLobbyState?.MapData != null)
|
||||
{
|
||||
double dist = myPos.DistanceTo(_gameClient.CurrentLobbyState.MapData.Center);
|
||||
if (dist <= emergencyDist)
|
||||
{
|
||||
ui.SetActionButton("EMERGENCY", true, () => GameManager.Instance?.PerformAction());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Impostor kill
|
||||
if (isImpostor)
|
||||
{
|
||||
var target = _gameClient.FindNearbyPlayer(killDist);
|
||||
if (!string.IsNullOrEmpty(target))
|
||||
{
|
||||
ui.SetActionButton("KILL", true, () => GameManager.Instance?.PerformAction());
|
||||
// Hide sabotage menu while a kill is on offer (cleaner HUD).
|
||||
ui.SetSabotageMenuVisible(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing nearby
|
||||
ui.SetActionButton("", false);
|
||||
|
||||
// P13g: persistent sabotage menu for impostors when no proximity
|
||||
// action is on offer. Hidden when state isn't suitable - dead,
|
||||
// not-impostor, in meeting, sabotage already active, or comms
|
||||
// blackout (the impostor's own sabotage triggers a UI lock).
|
||||
bool inPlayingPhase = state != null && state.Phase == GeoSus.Client.GamePhase.Playing;
|
||||
bool sabotageActive = state?.ActiveSabotage != null;
|
||||
bool showSabMenu = isImpostor && !ui.IsPlayerDead && inPlayingPhase &&
|
||||
!sabotageActive && !ui.IsCommsBlackout;
|
||||
ui.SetSabotageMenuVisible(showSabMenu);
|
||||
}
|
||||
|
||||
/// <summary>Called externally (e.g., GameManager.PerformAction) to launch the nearby task.</summary>
|
||||
public void TriggerNearbyTask()
|
||||
{
|
||||
OnUsePressed();
|
||||
}
|
||||
|
||||
private void OnUsePressed()
|
||||
{
|
||||
if (NearbyTask == null || _minigameOpen) return;
|
||||
var entry = _tasks.Find(t => t.ServerTask.TaskId == NearbyTask.TaskId);
|
||||
if (entry != null) _host.StartCoroutine(LaunchMinigame(entry));
|
||||
}
|
||||
|
||||
private IEnumerator LaunchMinigame(TaskEntry entry)
|
||||
{
|
||||
_minigameOpen = true;
|
||||
Debug.Log($"[Tasks] Launching minigame '{entry.MinigameScene}' for task '{entry.ServerTask.Name}'");
|
||||
|
||||
// Validate that the scene name resolves to a build-included scene.
|
||||
// LoadSceneAsync silently returns null when the scene name doesn't
|
||||
// match (case-sensitive) or isn't in EditorBuildSettings, which
|
||||
// leaves the action button looking dead from the player's POV.
|
||||
if (string.IsNullOrEmpty(entry.MinigameScene) ||
|
||||
!Application.CanStreamedLevelBeLoaded(entry.MinigameScene))
|
||||
{
|
||||
Debug.LogError($"[Tasks] Minigame scene '{entry.MinigameScene}' is not loadable. " +
|
||||
$"Check the scene name (case-sensitive) and that it's enabled in Build Settings.");
|
||||
GameManager.Instance?.uiSubsystem?.ShowToast(
|
||||
$"Task scene missing: {entry.MinigameScene}");
|
||||
_minigameOpen = false;
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Inform server that task started
|
||||
_gameClient.Send(new TaskStart { TaskId = entry.ServerTask.TaskId });
|
||||
|
||||
// Disable the host scene's main camera while the minigame is up.
|
||||
// With both cameras enabled the minigame's UI/3D content would
|
||||
// fight the host's map camera for screen space, and what gets
|
||||
// drawn depends on Camera.depth which isn't guaranteed across
|
||||
// scenes. Restored in FinishMinigame.
|
||||
_hostCameraSuspended = Camera.main;
|
||||
if (_hostCameraSuspended != null) _hostCameraSuspended.enabled = false;
|
||||
|
||||
// Hide the persistent InGame HUD canvas (if present). It lives
|
||||
// in Client.unity and renders Screen Space - Overlay so it would
|
||||
// otherwise stack on top of the minigame's UI regardless of
|
||||
// which scene is active. SetActive(false) is reversible.
|
||||
_hostInGameHudHidden = GameObject.Find("InGame");
|
||||
if (_hostInGameHudHidden != null && _hostInGameHudHidden.activeSelf)
|
||||
_hostInGameHudHidden.SetActive(false);
|
||||
else
|
||||
_hostInGameHudHidden = null; // nothing to restore
|
||||
|
||||
var op = SceneManager.LoadSceneAsync(entry.MinigameScene, LoadSceneMode.Additive);
|
||||
if (op == null)
|
||||
{
|
||||
Debug.LogError($"[Tasks] LoadSceneAsync returned null for '{entry.MinigameScene}'.");
|
||||
GameManager.Instance?.uiSubsystem?.ShowToast(
|
||||
$"Task scene failed to load: {entry.MinigameScene}");
|
||||
if (_hostCameraSuspended != null) { _hostCameraSuspended.enabled = true; _hostCameraSuspended = null; }
|
||||
_minigameOpen = false;
|
||||
yield break;
|
||||
}
|
||||
yield return op;
|
||||
|
||||
_loadedMinigameScene = entry.MinigameScene;
|
||||
|
||||
// CRITICAL: switch the active scene to the loaded minigame.
|
||||
// LoadSceneMode.Additive stacks scenes without changing which one
|
||||
// is "active" - and an inactive scene's RenderSettings, ambient
|
||||
// light, and skybox don't drive rendering. The host (Client.unity)
|
||||
// remains active and its lighting context still applies, which
|
||||
// is the root cause of "task opens to white screen": the
|
||||
// minigame's content loads but its visuals don't take over.
|
||||
// Without SetActiveScene, even minigames that ARE wired up
|
||||
// correctly render against the host's lighting and look broken.
|
||||
Scene scene = SceneManager.GetSceneByName(entry.MinigameScene);
|
||||
if (scene.IsValid()) SceneManager.SetActiveScene(scene);
|
||||
|
||||
// Diagnostic: count cameras / canvases / lights in the loaded
|
||||
// scene. If the white screen persists after this fix, the
|
||||
// numbers tell us whether the scene is missing rendering bits
|
||||
// (camera=0, canvas=0) or if the issue is elsewhere.
|
||||
int camCount = 0, canvasCount = 0, lightCount = 0;
|
||||
foreach (var root in scene.GetRootGameObjects())
|
||||
{
|
||||
camCount += root.GetComponentsInChildren<Camera>(true).Length;
|
||||
canvasCount += root.GetComponentsInChildren<Canvas>(true).Length;
|
||||
lightCount += root.GetComponentsInChildren<Light>(true).Length;
|
||||
}
|
||||
Debug.Log($"[Tasks] Loaded '{entry.MinigameScene}': cameras={camCount}, " +
|
||||
$"canvases={canvasCount}, lights={lightCount}, " +
|
||||
$"activeScene={SceneManager.GetActiveScene().name}");
|
||||
|
||||
// Find the ITask component in the newly loaded scene
|
||||
ITask taskComponent = null;
|
||||
foreach (var root in scene.GetRootGameObjects())
|
||||
{
|
||||
taskComponent = root.GetComponentInChildren<ITask>();
|
||||
if (taskComponent != null) break;
|
||||
}
|
||||
|
||||
if (taskComponent == null)
|
||||
{
|
||||
Debug.LogWarning($"[Tasks] No ITask found in '{entry.MinigameScene}'. " +
|
||||
$"Either the minigame's controller script isn't attached to a GameObject in the scene, " +
|
||||
$"or the script doesn't implement ITask. Auto-completing.");
|
||||
yield return FinishMinigame(entry, true);
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Set task metadata
|
||||
taskComponent.TaskID = entry.ServerTask.TaskId;
|
||||
taskComponent.TaskName = entry.ServerTask.Name;
|
||||
taskComponent.TaskLocation = (entry.ServerTask.Location.Lat, entry.ServerTask.Location.Lon);
|
||||
|
||||
bool done = false;
|
||||
taskComponent.Initialize(t => { done = true; });
|
||||
|
||||
// Wait for completion or exit
|
||||
yield return new WaitUntil(() => done);
|
||||
|
||||
yield return FinishMinigame(entry, done);
|
||||
}
|
||||
|
||||
private IEnumerator FinishMinigame(TaskEntry entry, bool completed)
|
||||
{
|
||||
if (completed)
|
||||
{
|
||||
entry.Completed = true;
|
||||
_gameClient.CompleteTask(entry.ServerTask.TaskId);
|
||||
Debug.Log($"[Tasks] Task '{entry.ServerTask.Name}' completed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"[Tasks] Task '{entry.ServerTask.Name}' exited without completion.");
|
||||
}
|
||||
|
||||
// Unload minigame scene. Switch the active scene back to the
|
||||
// host BEFORE the unload so we don't end up with no active
|
||||
// scene mid-frame (Unity will complain and lighting flickers).
|
||||
if (!string.IsNullOrEmpty(_loadedMinigameScene))
|
||||
{
|
||||
var hostScene = SceneManager.GetSceneByName("Client");
|
||||
if (hostScene.IsValid()) SceneManager.SetActiveScene(hostScene);
|
||||
|
||||
var unload = SceneManager.UnloadSceneAsync(_loadedMinigameScene);
|
||||
yield return unload;
|
||||
_loadedMinigameScene = null;
|
||||
}
|
||||
|
||||
// Re-enable the host camera that was suspended during the minigame.
|
||||
if (_hostCameraSuspended != null)
|
||||
{
|
||||
_hostCameraSuspended.enabled = true;
|
||||
_hostCameraSuspended = null;
|
||||
}
|
||||
|
||||
// Re-show the InGame HUD canvas hidden at minigame entry.
|
||||
if (_hostInGameHudHidden != null)
|
||||
{
|
||||
_hostInGameHudHidden.SetActive(true);
|
||||
_hostInGameHudHidden = null;
|
||||
}
|
||||
|
||||
_minigameOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/GameManager/GameManager_Tasks.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 27a123dbda9eef8ba4815c0c0d30b6fb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,71 +1,934 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using Subsystems;
|
||||
using GeoSus.Client;
|
||||
using System.ComponentModel;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using TMPro;
|
||||
|
||||
namespace Subsystems
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads from GameManager_Network.State (the authoritative GameState) and drives
|
||||
/// all in-game canvas panels. No business logic lives here.
|
||||
/// </summary>
|
||||
public class GameManager_UI
|
||||
{
|
||||
private GameClient _gameClient;
|
||||
private Canvas _CreateJoinLobby;
|
||||
private Canvas _InLobby;
|
||||
private Canvas _LoadingScreen;
|
||||
private Canvas _GameScreen;
|
||||
public GameManager_UI(GameClient gameClient, Canvas CreateJoinLobby, Canvas InLobby, Canvas LoadingScreen, Canvas GameScreen)
|
||||
private GameState _state => GameManager.Instance?.networkSubsystem?.State;
|
||||
|
||||
// ── Canvas refs (wired by BindClientScene from Client.unity) ──────────
|
||||
public Canvas ClientCreateJoinLobby;
|
||||
public Canvas ClientInLobby;
|
||||
public Canvas ClientLoadingScreen;
|
||||
public Canvas ClientGameScreen;
|
||||
|
||||
// ── HUD element refs (resolved once in BindClientScene) ───────────────
|
||||
private TMP_Text _roleText;
|
||||
private TMP_Text _taskListText;
|
||||
private TMP_Text _taskProgressText;
|
||||
private Button _actionButton;
|
||||
private TMP_Text _actionButtonText;
|
||||
private TMP_Text _killCooldownText;
|
||||
private GameObject _sabotagePanel;
|
||||
private TMP_Text _sabotageTimerText;
|
||||
private GameObject _meetingPanel;
|
||||
private TMP_Text _meetingHeader;
|
||||
private TMP_Text _meetingPhaseLabel;
|
||||
private TMP_Text _meetingPhaseCountdown;
|
||||
private Image _meetingPhaseProgressBar;
|
||||
private TMP_Text _myVoteIndicator;
|
||||
private GameObject _meetingScrollGO;
|
||||
private Transform _meetingScrollContent;
|
||||
private TMP_Text _meetingFallbackText;
|
||||
private GameObject _voteResultPanel;
|
||||
private TMP_Text _voteResultText;
|
||||
private Button _skipButton;
|
||||
private GameObject _gameEndPanel;
|
||||
private TMP_Text _gameEndText;
|
||||
private RectTransform _returnToLobbyBtn;
|
||||
private TMP_Text _toastText;
|
||||
private GameObject _toastGO;
|
||||
private GameObject _reconnectOverlay;
|
||||
|
||||
// ── Internal state ────────────────────────────────────────────────────
|
||||
private bool _isDead;
|
||||
private bool _commsBlackout;
|
||||
private DateTime _sabotageMeltdownDeadline;
|
||||
private bool _sabotageTimerActive;
|
||||
private volatile bool _lobbyDirty;
|
||||
|
||||
// Meeting vote-row references rebuilt each meeting
|
||||
private readonly List<GameObject> _voteRows = new List<GameObject>();
|
||||
private string _pendingVoteResultDisplay; // shown after voting
|
||||
private Coroutine _meetingCloseCoroutine; // tracked so phase changes can cancel it
|
||||
|
||||
public GameManager_UI(GameClient gameClient) { _gameClient = gameClient; }
|
||||
|
||||
public void NotifyLobbyChanged() => _lobbyDirty = true;
|
||||
public bool IsCommsBlackout => _commsBlackout;
|
||||
public bool IsPlayerDead => _isDead;
|
||||
|
||||
// ── Scene binding ─────────────────────────────────────────────────────
|
||||
|
||||
public void BindClientScene(Canvas createJoin, Canvas inLobby, Canvas loading, Canvas game)
|
||||
{
|
||||
_gameClient = gameClient;
|
||||
_CreateJoinLobby = CreateJoinLobby;
|
||||
_LoadingScreen = LoadingScreen;
|
||||
_GameScreen = GameScreen;
|
||||
_InLobby = InLobby;
|
||||
_CreateJoinLobby.enabled = true;
|
||||
_InLobby.enabled = false;
|
||||
_GameScreen.enabled = false;
|
||||
_LoadingScreen.enabled = false;
|
||||
ClientCreateJoinLobby = createJoin;
|
||||
ClientInLobby = inLobby;
|
||||
ClientLoadingScreen = loading;
|
||||
ClientGameScreen = game;
|
||||
|
||||
foreach (var c in new[] { createJoin, inLobby, loading, game })
|
||||
EnsureCanvasReady(c);
|
||||
|
||||
if (createJoin) createJoin.gameObject.SetActive(false);
|
||||
if (inLobby) inLobby.gameObject.SetActive(false);
|
||||
if (loading) loading.gameObject.SetActive(false);
|
||||
if (game) game.gameObject.SetActive(false);
|
||||
|
||||
if (game == null) return;
|
||||
var t = game.transform;
|
||||
|
||||
_roleText = FindTMP(t, "Role");
|
||||
_taskListText = FindTMP(t, "TaskList");
|
||||
_taskProgressText = FindTMP(t, "TaskProgress");
|
||||
_killCooldownText = FindTMP(t, "KillCooldown");
|
||||
_sabotageTimerText = FindTMP(t, "SabotageTimer");
|
||||
_gameEndText = FindTMP(t, "GameEndText");
|
||||
_toastText = FindTMP(t, "Toast");
|
||||
_meetingHeader = FindTMP(t, "MeetingHeader");
|
||||
_meetingPhaseLabel = FindTMP(t, "MeetingPhaseLabel");
|
||||
_meetingPhaseCountdown = FindTMP(t, "MeetingPhaseCountdown");
|
||||
_myVoteIndicator = FindTMP(t, "MyVoteIndicator");
|
||||
_meetingFallbackText = FindTMP(t, "MeetingPlayerList");
|
||||
_voteResultText = FindTMP(t, "VoteResult");
|
||||
_meetingScrollContent = FindTransform(t, "MeetingContent");
|
||||
_meetingScrollGO = FindTransformGO(t, "_MeetingScroll");
|
||||
|
||||
var progressBarGO = FindTransformGO(t, "MeetingPhaseProgressBar");
|
||||
if (progressBarGO != null) _meetingPhaseProgressBar = progressBarGO.GetComponent<Image>();
|
||||
|
||||
var skipGO = FindTransformGO(t, "SkipButton");
|
||||
if (skipGO != null) _skipButton = skipGO.GetComponent<Button>();
|
||||
|
||||
var actionGO = t.Find("ActionButton");
|
||||
if (actionGO != null)
|
||||
{
|
||||
_actionButton = actionGO.GetComponent<Button>();
|
||||
_actionButtonText = actionGO.GetComponentInChildren<TMP_Text>();
|
||||
}
|
||||
|
||||
_sabotagePanel = t.Find("SabotagePanel")?.gameObject;
|
||||
_meetingPanel = t.Find("MeetingPanel")?.gameObject;
|
||||
_gameEndPanel = t.Find("GameEndPanel")?.gameObject;
|
||||
_voteResultPanel = FindTransformGO(t, "VoteResultPanel");
|
||||
_toastGO = FindTransformGO(t, "Toast");
|
||||
_reconnectOverlay = FindTransformGO(t, "ReconnectOverlay");
|
||||
|
||||
var retBtn = FindTransform(t, "ReturnToLobbyButton");
|
||||
if (retBtn != null) _returnToLobbyBtn = retBtn as RectTransform;
|
||||
|
||||
if (_meetingPanel) _meetingPanel.SetActive(false);
|
||||
if (_gameEndPanel) _gameEndPanel.SetActive(false);
|
||||
if (_voteResultPanel) _voteResultPanel.SetActive(false);
|
||||
if (_toastGO) _toastGO.SetActive(false);
|
||||
if (_reconnectOverlay) _reconnectOverlay.SetActive(false);
|
||||
}
|
||||
|
||||
// ── Update (called every frame from GameManager.Update) ───────────────
|
||||
|
||||
public void UpdateLobbyUI()
|
||||
{
|
||||
if (_gameClient.CurrentLobbyState == null)
|
||||
var lobbyState = _gameClient.CurrentLobbyState;
|
||||
if (lobbyState == null) return;
|
||||
|
||||
if (_lobbyDirty)
|
||||
{
|
||||
_CreateJoinLobby.enabled = true;
|
||||
_InLobby.enabled = false;
|
||||
_GameScreen.enabled = false;
|
||||
_LoadingScreen.enabled = false;
|
||||
return;
|
||||
_lobbyDirty = false;
|
||||
LobbyDisplayUI.RefreshAll(lobbyState);
|
||||
}
|
||||
else if (_gameClient.CurrentLobbyState.Phase == GamePhase.Loading)
|
||||
|
||||
if (ClientGameScreen == null) return;
|
||||
|
||||
switch (lobbyState.Phase)
|
||||
{
|
||||
_CreateJoinLobby.enabled = false;
|
||||
_InLobby.enabled = false;
|
||||
_GameScreen.enabled = false;
|
||||
_LoadingScreen.enabled = true;
|
||||
return;
|
||||
case GamePhase.Loading:
|
||||
SetCanvases(false, false, true, false);
|
||||
break;
|
||||
case GamePhase.Lobby:
|
||||
SetCanvases(false, true, false, false);
|
||||
break;
|
||||
case GamePhase.Playing:
|
||||
case GamePhase.Meeting:
|
||||
case GamePhase.Voting:
|
||||
SetCanvases(false, false, false, true);
|
||||
UpdateGameHUD();
|
||||
break;
|
||||
case GamePhase.Ended:
|
||||
SetCanvases(false, false, false, true);
|
||||
break;
|
||||
}
|
||||
else if (_gameClient.CurrentLobbyState.Phase == GamePhase.Lobby)
|
||||
|
||||
TickToast();
|
||||
}
|
||||
|
||||
// ── Game HUD tick ─────────────────────────────────────────────────────
|
||||
|
||||
private void UpdateGameHUD()
|
||||
{
|
||||
var s = _state;
|
||||
if (s == null) return;
|
||||
|
||||
// Role
|
||||
if (_roleText != null)
|
||||
{
|
||||
_InLobby.enabled = true;
|
||||
_CreateJoinLobby.enabled = false;
|
||||
var playerList = _InLobby.transform.Find("PlayerList").GetComponent<TMPro.TMP_Text>();
|
||||
playerList.text = "";
|
||||
foreach (var player in _gameClient.CurrentLobbyState.Players)
|
||||
string ghostSuffix = s.IsDead ? " (GHOST)" : "";
|
||||
_roleText.text = $"{s.MyRole?.ToString() ?? "?"}{ghostSuffix}";
|
||||
_roleText.color = s.MyRole == PlayerRole.Impostor ? new Color(0.9f,0.2f,0.2f) : new Color(0.2f,0.8f,1f);
|
||||
}
|
||||
|
||||
// Task list with checkmarks
|
||||
if (_taskListText != null)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
foreach (var task in s.MyTasks)
|
||||
{
|
||||
playerList.text += player.DisplayName + "\n";
|
||||
bool done = s.MyCompletedTaskIds.Contains(task.TaskId);
|
||||
string mark = done ? "<color=#2DB84B>✓</color>" : "○";
|
||||
sb.AppendLine($"{mark} {task.Name}");
|
||||
}
|
||||
_InLobby.transform.Find("JoinCode").GetComponent<TMPro.TMP_Text>().text = _gameClient.CurrentLobbyState.JoinCode;
|
||||
return;
|
||||
_taskListText.text = sb.ToString();
|
||||
}
|
||||
else if (_gameClient.CurrentLobbyState.Phase == GamePhase.Playing)
|
||||
|
||||
// Global task progress
|
||||
if (_taskProgressText != null && s.TotalRequired > 0)
|
||||
_taskProgressText.text = $"Tasks: {s.TotalCompleted}/{s.TotalRequired}";
|
||||
|
||||
// Kill cooldown
|
||||
if (_killCooldownText != null)
|
||||
{
|
||||
_CreateJoinLobby.enabled = false;
|
||||
_InLobby.enabled = false;
|
||||
_GameScreen.enabled = true;
|
||||
_LoadingScreen.enabled = false;
|
||||
_GameScreen.transform.Find("Role").GetComponent<TMPro.TMP_Text>().text = _gameClient.MyRole.ToString() ;
|
||||
bool show = s.KillCooldownRemaining > 0;
|
||||
_killCooldownText.gameObject.SetActive(show);
|
||||
if (show) _killCooldownText.text = $"Kill: {Mathf.CeilToInt(s.KillCooldownRemaining)}s";
|
||||
}
|
||||
|
||||
// Sabotage banner - meltdown countdown plus simultaneous-repair coaching
|
||||
if (_sabotageTimerActive && _sabotageTimerText != null)
|
||||
{
|
||||
double remaining = (_sabotageMeltdownDeadline - DateTime.UtcNow).TotalSeconds;
|
||||
string head = remaining > 0 ? $"⚠ MELTDOWN: {remaining:F0}s" : "⚠ MELTDOWN!";
|
||||
|
||||
// For multi-station sabotages, surface how many of the required
|
||||
// simultaneous repair stations are currently active. This is
|
||||
// what makes "you're alone, you need a partner" obvious.
|
||||
int required = s.ActiveSabotage?.RequiredSimultaneousRepairs ?? 0;
|
||||
if (required > 1)
|
||||
{
|
||||
int active = s.ActiveRepairs.Count;
|
||||
head += $" <size=32>{active}/{required} stations active</size>";
|
||||
}
|
||||
_sabotageTimerText.text = head;
|
||||
}
|
||||
|
||||
// Keep meeting sub-phase strip, countdown, vote gating, tallies and
|
||||
// my-vote indicator fresh each frame.
|
||||
UpdateMeetingPhaseStrip();
|
||||
}
|
||||
|
||||
// ── Kill cooldown helper (called from GameManager) ────────────────────
|
||||
|
||||
// ── Reconnect overlay ─────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Show a full-screen "Reconnecting..." overlay. Call when the socket
|
||||
/// drops mid-game; the server keeps the player slot for ~60s before
|
||||
/// removing them so a brief disconnect is recoverable.
|
||||
/// </summary>
|
||||
public void ShowReconnecting()
|
||||
{
|
||||
if (_reconnectOverlay) _reconnectOverlay.SetActive(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide the reconnect overlay. Call from OnConnected once the socket
|
||||
/// is healthy again.
|
||||
/// </summary>
|
||||
public void HideReconnecting()
|
||||
{
|
||||
if (_reconnectOverlay) _reconnectOverlay.SetActive(false);
|
||||
}
|
||||
|
||||
public void SetKillCooldownText(string text)
|
||||
{
|
||||
if (_killCooldownText == null) return;
|
||||
bool show = !string.IsNullOrEmpty(text);
|
||||
_killCooldownText.gameObject.SetActive(show);
|
||||
if (show) _killCooldownText.text = text;
|
||||
}
|
||||
|
||||
public void UpdateTaskProgress(int completed, int total)
|
||||
{
|
||||
if (_taskProgressText != null)
|
||||
_taskProgressText.text = $"Tasks: {completed}/{total}";
|
||||
}
|
||||
|
||||
// ── Action button ─────────────────────────────────────────────────────
|
||||
|
||||
public void SetActionButton(string label, bool visible, UnityEngine.Events.UnityAction onClick = null)
|
||||
{
|
||||
if (_actionButton == null) return;
|
||||
_actionButton.gameObject.SetActive(visible);
|
||||
if (_actionButtonText != null) _actionButtonText.text = label;
|
||||
if (onClick != null)
|
||||
{
|
||||
_actionButton.onClick.RemoveAllListeners();
|
||||
_actionButton.onClick.AddListener(onClick);
|
||||
}
|
||||
}
|
||||
|
||||
// ── P13g: Impostor sabotage menu ──────────────────────────────────────
|
||||
// The audit found that the production HUD never had an impostor
|
||||
// sabotage trigger - GameManager.StartSabotage exists, the wire path
|
||||
// is intact (StartSabotage -> server -> SabotageStarted broadcast +
|
||||
// station markers), but no UI ever called it. So sabotages literally
|
||||
// never fired in production. This menu fixes that gap with a runtime-
|
||||
// built two-button overlay (no scene file change, no prefab needed).
|
||||
|
||||
private GameObject _sabotageMenuRoot;
|
||||
private Button _sabotageBlackoutBtn;
|
||||
private Button _sabotageMeltdownBtn;
|
||||
|
||||
private void EnsureSabotageMenu()
|
||||
{
|
||||
if (_sabotageMenuRoot != null || ClientGameScreen == null) return;
|
||||
|
||||
var canvasRT = ClientGameScreen.transform as RectTransform;
|
||||
if (canvasRT == null) return;
|
||||
|
||||
// Root container - top-right corner, vertical stack.
|
||||
_sabotageMenuRoot = new GameObject("ImpostorSabotageMenu", typeof(RectTransform), typeof(CanvasRenderer));
|
||||
var rootRT = _sabotageMenuRoot.GetComponent<RectTransform>();
|
||||
rootRT.SetParent(canvasRT, worldPositionStays: false);
|
||||
rootRT.anchorMin = new Vector2(1, 1);
|
||||
rootRT.anchorMax = new Vector2(1, 1);
|
||||
rootRT.pivot = new Vector2(1, 1);
|
||||
rootRT.anchoredPosition = new Vector2(-24, -180); // below the top-right safe-area
|
||||
rootRT.sizeDelta = new Vector2(360, 240);
|
||||
|
||||
_sabotageBlackoutBtn = BuildSabotageOption(rootRT, "📡 BLACKOUT",
|
||||
new Color(0.20f, 0.55f, 1.0f), 0, () => GameManager.Instance?.StartSabotage(0));
|
||||
|
||||
_sabotageMeltdownBtn = BuildSabotageOption(rootRT, "☢️ MELTDOWN",
|
||||
new Color(1.0f, 0.30f, 0.30f), 1, () => GameManager.Instance?.StartSabotage(1));
|
||||
|
||||
_sabotageMenuRoot.SetActive(false);
|
||||
}
|
||||
|
||||
private static Button BuildSabotageOption(RectTransform parent, string label, Color tint, int slot, UnityEngine.Events.UnityAction onClick)
|
||||
{
|
||||
// Each button: 360w x 110h, stacked vertically with 10px gap.
|
||||
var go = new GameObject($"SabBtn_{slot}", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(Button));
|
||||
var rt = go.GetComponent<RectTransform>();
|
||||
rt.SetParent(parent, worldPositionStays: false);
|
||||
rt.anchorMin = new Vector2(0, 1);
|
||||
rt.anchorMax = new Vector2(1, 1);
|
||||
rt.pivot = new Vector2(0.5f, 1);
|
||||
rt.anchoredPosition = new Vector2(0, -slot * 120);
|
||||
rt.sizeDelta = new Vector2(0, 110);
|
||||
|
||||
var img = go.GetComponent<Image>();
|
||||
img.color = new Color(tint.r * 0.4f, tint.g * 0.4f, tint.b * 0.4f, 0.92f);
|
||||
|
||||
// Border via outline component
|
||||
var outline = go.AddComponent<Outline>();
|
||||
outline.effectColor = tint;
|
||||
outline.effectDistance = new Vector2(2, -2);
|
||||
|
||||
// Text child
|
||||
var txtGO = new GameObject("Label", typeof(RectTransform));
|
||||
var txtRT = txtGO.GetComponent<RectTransform>();
|
||||
txtRT.SetParent(rt, worldPositionStays: false);
|
||||
txtRT.anchorMin = Vector2.zero;
|
||||
txtRT.anchorMax = Vector2.one;
|
||||
txtRT.offsetMin = Vector2.zero;
|
||||
txtRT.offsetMax = Vector2.zero;
|
||||
var tmp = txtGO.AddComponent<TextMeshProUGUI>();
|
||||
tmp.text = label;
|
||||
tmp.alignment = TextAlignmentOptions.Center;
|
||||
tmp.fontSize = 36;
|
||||
tmp.color = Color.white;
|
||||
tmp.fontStyle = FontStyles.Bold;
|
||||
|
||||
var btn = go.GetComponent<Button>();
|
||||
btn.onClick.AddListener(onClick);
|
||||
return btn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// P13g: show the impostor sabotage menu when the local player is
|
||||
/// alive impostor in the Playing phase with no active sabotage and
|
||||
/// not in a meeting. Driven from GameManager_Tasks.UpdateProximity.
|
||||
/// </summary>
|
||||
public void SetSabotageMenuVisible(bool visible)
|
||||
{
|
||||
if (visible) EnsureSabotageMenu();
|
||||
if (_sabotageMenuRoot != null && _sabotageMenuRoot.activeSelf != visible)
|
||||
_sabotageMenuRoot.SetActive(visible);
|
||||
}
|
||||
|
||||
// ── Player state ──────────────────────────────────────────────────────
|
||||
|
||||
public void OnLocalPlayerDied()
|
||||
{
|
||||
_isDead = true;
|
||||
if (_state != null) _state.IsDead = true;
|
||||
}
|
||||
|
||||
// ── Meeting ───────────────────────────────────────────────────────────
|
||||
|
||||
public void ShowMeetingAlert()
|
||||
{
|
||||
ShowToast("⚠ Meeting called! Head to the meeting point.");
|
||||
}
|
||||
|
||||
public void ShowMeetingPanel(List<PlayerInfo> players, MeetingStartedPayload payload)
|
||||
{
|
||||
if (_meetingPanel == null) return;
|
||||
_meetingPanel.SetActive(true);
|
||||
|
||||
if (_meetingHeader != null)
|
||||
_meetingHeader.text = payload.Type == MeetingType.BodyReport ? "BODY REPORTED!" : "EMERGENCY MEETING!";
|
||||
|
||||
// Make sure the result subpanel is hidden at start of a fresh meeting,
|
||||
// and the scroll list is visible (results phase will swap them).
|
||||
if (_voteResultPanel) _voteResultPanel.SetActive(false);
|
||||
if (_meetingScrollGO) _meetingScrollGO.SetActive(true);
|
||||
if (_myVoteIndicator) _myVoteIndicator.text = "";
|
||||
|
||||
BuildMeetingVoteRows(players);
|
||||
UpdateMeetingPhaseStrip();
|
||||
}
|
||||
|
||||
private void BuildMeetingVoteRows(List<PlayerInfo> players)
|
||||
{
|
||||
// Clear old rows
|
||||
foreach (var r in _voteRows) if (r) UnityEngine.Object.Destroy(r);
|
||||
_voteRows.Clear();
|
||||
|
||||
if (_meetingScrollContent == null || players == null)
|
||||
{
|
||||
// Fall back to text list
|
||||
if (_meetingFallbackText != null)
|
||||
{
|
||||
_meetingFallbackText.gameObject.SetActive(true);
|
||||
var sb = new System.Text.StringBuilder();
|
||||
foreach (var p in players ?? new List<PlayerInfo>())
|
||||
sb.AppendLine($"{p.DisplayName} [{p.State}]");
|
||||
_meetingFallbackText.text = sb.ToString();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
string myId = _gameClient.ClientUuid;
|
||||
bool canVote = !_isDead;
|
||||
|
||||
// Dynamic row height: spread the available scroll-area height
|
||||
// across however many players we have. Clamps so rows never get
|
||||
// tinier than legible (small phone, many players -> 80px) or
|
||||
// ridiculously tall (tablet, two players -> 140px).
|
||||
float rowH = ComputeVoteRowHeight(players.Count);
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
bool isMe = player.ClientUuid == myId;
|
||||
bool isAlive = player.State == PlayerState.Alive;
|
||||
var row = BuildVoteRow(player, isMe, isAlive, canVote && isAlive && !isMe, rowH);
|
||||
row.transform.SetParent(_meetingScrollContent, false);
|
||||
_voteRows.Add(row);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute a per-row height that fills the scroll viewport when there
|
||||
/// are few players, and shrinks (until scrolling kicks in) when there
|
||||
/// are many. Inputs are CanvasScaler reference coordinates, so the
|
||||
/// values are device-independent.
|
||||
/// </summary>
|
||||
private float ComputeVoteRowHeight(int playerCount)
|
||||
{
|
||||
if (playerCount <= 0) return 110f;
|
||||
// The scroll area occupies y=0.18 to y=0.74 of the canvas (per
|
||||
// InGameHUDBuilder.BuildMeetingPanel) and reference height is 1920.
|
||||
const float referenceHeight = 1920f;
|
||||
const float scrollFraction = 0.74f - 0.18f; // 0.56
|
||||
float available = referenceHeight * scrollFraction;
|
||||
float h = available / playerCount;
|
||||
return Mathf.Clamp(h, 80f, 140f);
|
||||
}
|
||||
|
||||
private GameObject BuildVoteRow(PlayerInfo player, bool isMe, bool isAlive, bool canVote, float rowH)
|
||||
{
|
||||
var go = new GameObject($"VoteRow_{player.ClientUuid}");
|
||||
var rt = go.AddComponent<RectTransform>();
|
||||
rt.sizeDelta = new Vector2(0, rowH);
|
||||
var le = go.AddComponent<LayoutElement>();
|
||||
le.minHeight = le.preferredHeight = rowH;
|
||||
|
||||
var bg = go.AddComponent<Image>();
|
||||
bg.color = isMe ? new Color(0.12f,0.18f,0.30f) : new Color(0.10f,0.12f,0.20f);
|
||||
|
||||
// Dead overlay
|
||||
if (!isAlive)
|
||||
{
|
||||
bg.color = new Color(0.08f,0.08f,0.10f,0.7f);
|
||||
}
|
||||
|
||||
// Name label - left 50% (was 65%, gave width back to tally + button)
|
||||
var namRT = MakeChild("Name", rt);
|
||||
namRT.anchorMin = new Vector2(0,0); namRT.anchorMax = new Vector2(0.50f,1);
|
||||
namRT.offsetMin = new Vector2(16,6); namRT.offsetMax = new Vector2(0,-6);
|
||||
var namTmp = namRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
namTmp.text = (player.IsOwner ? "👑 " : "") + (player.DisplayName ?? "???");
|
||||
namTmp.fontSize = 36;
|
||||
namTmp.color = !isAlive ? Color.gray : (isMe ? Color.white : new Color(0.73f,0.8f,0.88f));
|
||||
namTmp.fontStyle = isMe ? FontStyles.Bold : FontStyles.Normal;
|
||||
namTmp.alignment = TextAlignmentOptions.MidlineLeft;
|
||||
|
||||
// Tally column - middle 18%, shows live vote count for this player
|
||||
var tallyRT = MakeChild("Tally", rt);
|
||||
tallyRT.anchorMin = new Vector2(0.50f,0); tallyRT.anchorMax = new Vector2(0.66f,1);
|
||||
tallyRT.offsetMin = Vector2.zero; tallyRT.offsetMax = Vector2.zero;
|
||||
var tallyTmp = tallyRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
tallyTmp.text = "";
|
||||
tallyTmp.fontSize = 30;
|
||||
tallyTmp.fontStyle = FontStyles.Bold;
|
||||
tallyTmp.color = new Color(1f,0.72f,0.10f); // C_YELLOW-ish
|
||||
tallyTmp.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
// Vote button - right 30% (interactability is updated each frame)
|
||||
var voteBtnRT = MakeChild("VoteBtn", rt);
|
||||
voteBtnRT.anchorMin = new Vector2(0.68f,0.10f); voteBtnRT.anchorMax = new Vector2(0.95f,0.90f);
|
||||
var voteBg = voteBtnRT.gameObject.AddComponent<Image>();
|
||||
voteBg.color = canVote ? new Color(0.2f,0.6f,1f) : new Color(0.2f,0.2f,0.2f,0.5f);
|
||||
var voteBtn = voteBtnRT.gameObject.AddComponent<Button>();
|
||||
voteBtn.targetGraphic = voteBg;
|
||||
voteBtn.interactable = canVote;
|
||||
string capturedId = player.ClientUuid;
|
||||
voteBtn.onClick.AddListener(() => GameManager.Instance?.CastVote(capturedId));
|
||||
var voteTxtRT = MakeChild("Txt", voteBtnRT);
|
||||
Stretch(voteTxtRT);
|
||||
var voteTmp = voteTxtRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
voteTmp.text = isAlive ? "VOTE" : "DEAD";
|
||||
voteTmp.fontSize = 28;
|
||||
voteTmp.fontStyle = FontStyles.Bold;
|
||||
voteTmp.color = Color.white;
|
||||
voteTmp.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
// Voted-by-this-player checkmark (shown when the row's player has cast a vote)
|
||||
var votedRT = MakeChild("VotedTick", rt);
|
||||
votedRT.anchorMin = new Vector2(0.95f,0.20f); votedRT.anchorMax = new Vector2(1f,0.80f);
|
||||
var vtTmp = votedRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
vtTmp.text = "✓"; vtTmp.fontSize = 34;
|
||||
vtTmp.color = new Color(0.18f,0.75f,0.30f); vtTmp.alignment = TextAlignmentOptions.Center;
|
||||
votedRT.gameObject.SetActive(false);
|
||||
|
||||
return go;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Per-frame meeting UI update. Computes the meeting sub-phase from the
|
||||
/// timestamps in MeetingStartedPayload (server doesn't broadcast a
|
||||
/// discrete discussion-end event) and uses it to drive the countdown
|
||||
/// label, progress bar, vote-button interactivity, live tallies, and
|
||||
/// "Your vote: X" indicator.
|
||||
/// </summary>
|
||||
private void UpdateMeetingPhaseStrip()
|
||||
{
|
||||
var s = _state;
|
||||
if (s == null) return;
|
||||
// Only run if we're actually in a meeting; phase Playing skips the work.
|
||||
if (s.Phase != GamePhase.Meeting && s.LastVoteResult == null) return;
|
||||
|
||||
var sub = s.GetMeetingSubPhase();
|
||||
|
||||
// ── Sub-phase label + countdown text + progress bar ───────────────
|
||||
string label;
|
||||
switch (sub)
|
||||
{
|
||||
case MeetingSubPhase.Arrival: label = "ARRIVAL"; break;
|
||||
case MeetingSubPhase.Discussion: label = "DISCUSSION"; break;
|
||||
case MeetingSubPhase.Voting: label = "VOTING"; break;
|
||||
case MeetingSubPhase.Resolved: label = "RESULTS"; break;
|
||||
default: label = ""; break;
|
||||
}
|
||||
if (_meetingPhaseLabel != null) _meetingPhaseLabel.text = label;
|
||||
|
||||
if (s.ActiveMeeting != null && sub != MeetingSubPhase.Resolved)
|
||||
{
|
||||
var deadline = s.GetMeetingSubPhaseDeadline(sub);
|
||||
var remaining = (deadline - DateTime.UtcNow).TotalSeconds;
|
||||
if (remaining < 0) remaining = 0;
|
||||
|
||||
if (_meetingPhaseCountdown != null)
|
||||
{
|
||||
int mins = (int)(remaining / 60);
|
||||
int secs = (int)(remaining % 60);
|
||||
string verb = sub == MeetingSubPhase.Voting ? "Voting ends in"
|
||||
: sub == MeetingSubPhase.Discussion ? "Voting begins in"
|
||||
: "Arrival ends in";
|
||||
_meetingPhaseCountdown.text = $"{verb} {mins}:{secs:D2}";
|
||||
}
|
||||
|
||||
// Progress bar drains over the current sub-phase. The server
|
||||
// doesn't tell us when the meeting started, so we can only
|
||||
// compute a meaningful fill for Discussion (start = arrival
|
||||
// deadline) and Voting (start = discussion end / arrival
|
||||
// deadline). Arrival's start time is unknown here; show full.
|
||||
if (_meetingPhaseProgressBar != null)
|
||||
{
|
||||
if (sub == MeetingSubPhase.Arrival)
|
||||
{
|
||||
_meetingPhaseProgressBar.fillAmount = 1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
DateTime start = sub == MeetingSubPhase.Discussion
|
||||
? s.ActiveMeeting.ArrivalDeadline
|
||||
: (s.ActiveMeeting.DiscussionEndTime ?? s.ActiveMeeting.ArrivalDeadline);
|
||||
var total = (deadline - start).TotalSeconds;
|
||||
var elapsed = (DateTime.UtcNow - start).TotalSeconds;
|
||||
float fill = total > 0.001
|
||||
? Mathf.Clamp01(1f - (float)(elapsed / total))
|
||||
: 0f;
|
||||
_meetingPhaseProgressBar.fillAmount = fill;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_meetingPhaseCountdown != null) _meetingPhaseCountdown.text = "";
|
||||
if (_meetingPhaseProgressBar != null) _meetingPhaseProgressBar.fillAmount = 0f;
|
||||
}
|
||||
|
||||
// ── Vote button gating + per-row tally / voted-indicator ──────────
|
||||
bool votingOpen = sub == MeetingSubPhase.Voting && !_isDead;
|
||||
bool iAmArrived = s.ActiveMeeting == null
|
||||
|| s.ArrivedPlayerIds.Contains(_gameClient.ClientUuid);
|
||||
|
||||
// Skip button mirrors the same gate
|
||||
if (_skipButton != null) _skipButton.interactable = votingOpen && iAmArrived;
|
||||
|
||||
foreach (var row in _voteRows)
|
||||
{
|
||||
if (row == null) continue;
|
||||
string rowUuid = row.name.Replace("VoteRow_", "");
|
||||
|
||||
// Voted-tick: this row's player has cast a vote
|
||||
var tick = row.transform.Find("VotedTick")?.gameObject;
|
||||
if (tick != null) tick.SetActive(s.VotedPlayerIds.Contains(rowUuid));
|
||||
|
||||
// Tally text: how many votes is this row's player receiving?
|
||||
var tally = row.transform.Find("Tally")?.GetComponent<TMP_Text>();
|
||||
if (tally != null)
|
||||
{
|
||||
s.VoteTallies.TryGetValue(rowUuid, out var count);
|
||||
tally.text = count > 0 ? count.ToString() : "";
|
||||
}
|
||||
|
||||
// Vote button: gate by sub-phase + arrival + alive + not-self
|
||||
var btnGO = row.transform.Find("VoteBtn")?.gameObject;
|
||||
if (btnGO != null)
|
||||
{
|
||||
var btn = btnGO.GetComponent<Button>();
|
||||
var btnImg = btnGO.GetComponent<Image>();
|
||||
var rowPlayer = s.Players?.FirstOrDefault(p => p.ClientUuid == rowUuid);
|
||||
bool isMe = rowUuid == _gameClient.ClientUuid;
|
||||
bool rowAlive = rowPlayer?.State == PlayerState.Alive;
|
||||
|
||||
bool canPress = votingOpen && iAmArrived && rowAlive && !isMe;
|
||||
if (btn != null) btn.interactable = canPress;
|
||||
if (btnImg != null)
|
||||
btnImg.color = canPress ? new Color(0.2f,0.6f,1f)
|
||||
: new Color(0.2f,0.2f,0.2f,0.5f);
|
||||
|
||||
// Mark the row's button if it's the local player's chosen vote
|
||||
if (s.MyVoteTarget != null && s.MyVoteTarget == rowUuid && btnImg != null)
|
||||
btnImg.color = new Color(0.2f,0.75f,0.30f); // green = your vote
|
||||
}
|
||||
}
|
||||
|
||||
// ── My vote indicator strip ───────────────────────────────────────
|
||||
if (_myVoteIndicator != null)
|
||||
{
|
||||
if (s.LastVoteResult != null) _myVoteIndicator.text = "";
|
||||
else if (!iAmArrived) _myVoteIndicator.text = "Travel to the meeting point to vote";
|
||||
else if (sub == MeetingSubPhase.Discussion) _myVoteIndicator.text = "Discussion - voting opens shortly";
|
||||
else if (sub == MeetingSubPhase.Arrival) _myVoteIndicator.text = "Waiting for players to arrive";
|
||||
else if (s.MyVoteTarget == null) _myVoteIndicator.text = "Cast your vote";
|
||||
else if (s.MyVoteTarget == GameState.VoteSkip) _myVoteIndicator.text = "You voted: SKIP";
|
||||
else
|
||||
{
|
||||
var target = s.Players?.FirstOrDefault(p => p.ClientUuid == s.MyVoteTarget);
|
||||
_myVoteIndicator.text = $"You voted for: {target?.DisplayName ?? s.MyVoteTarget}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AppendVoteInstruction()
|
||||
{
|
||||
// no-op - vote instructions are embedded in the row buttons
|
||||
}
|
||||
|
||||
public void ShowVoteResult(VotingClosedPayload payload, List<PlayerInfo> players)
|
||||
{
|
||||
// Swap scroll list out, result subpanel in. They occupy the same
|
||||
// anchor region (0.18-0.74) so the result text replaces the vote
|
||||
// rows rather than overlapping them.
|
||||
if (_meetingScrollGO != null) _meetingScrollGO.SetActive(false);
|
||||
if (_voteResultPanel != null) _voteResultPanel.SetActive(true);
|
||||
// Skip + my-vote strips are no longer relevant once voting ended.
|
||||
if (_skipButton != null) _skipButton.gameObject.SetActive(false);
|
||||
if (_myVoteIndicator != null) _myVoteIndicator.text = "";
|
||||
|
||||
if (_voteResultText != null)
|
||||
{
|
||||
// Build a compact tally summary alongside the headline.
|
||||
var sb = new System.Text.StringBuilder();
|
||||
if (payload.WasTie)
|
||||
sb.AppendLine("⚖ TIE — nobody ejected.");
|
||||
else if (string.IsNullOrEmpty(payload.EjectedPlayerId))
|
||||
sb.AppendLine("Nobody ejected (skip).");
|
||||
else
|
||||
{
|
||||
var ej = players?.Find(p => p.ClientUuid == payload.EjectedPlayerId);
|
||||
sb.AppendLine($"🚪 {ej?.DisplayName ?? payload.EjectedPlayerId} ejected!");
|
||||
}
|
||||
|
||||
if (payload.VoteCounts != null && payload.VoteCounts.Count > 0)
|
||||
{
|
||||
sb.AppendLine();
|
||||
foreach (var kv in payload.VoteCounts.OrderByDescending(p => p.Value))
|
||||
{
|
||||
if (kv.Value <= 0) continue;
|
||||
string name = kv.Key == GameState.VoteSkip
|
||||
? "(skip)"
|
||||
: (players?.Find(p => p.ClientUuid == kv.Key)?.DisplayName ?? kv.Key);
|
||||
sb.AppendLine($"<size=24>{name}: {kv.Value}</size>");
|
||||
}
|
||||
}
|
||||
_voteResultText.text = sb.ToString();
|
||||
}
|
||||
|
||||
// Auto-close meeting panel after 5 s. Track the handle so we can
|
||||
// cancel it if the game ends or returns to lobby before it fires
|
||||
// (otherwise the coroutine fires mid-GameEndPanel and hides nothing
|
||||
// useful while the meeting overlay sits visibly stacked on top).
|
||||
CancelMeetingAutoClose();
|
||||
var gm = GameManager.Instance;
|
||||
if (gm != null) _meetingCloseCoroutine = gm.StartCoroutine(CloseMeetingAfterDelay(5f));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide the meeting/vote panels immediately and cancel any pending
|
||||
/// auto-close coroutine. Resets internal toggles (skip/result/scroll
|
||||
/// visibility) so the next meeting starts from a clean state. Safe to
|
||||
/// call from any phase transition.
|
||||
/// </summary>
|
||||
public void HideMeetingPanel()
|
||||
{
|
||||
CancelMeetingAutoClose();
|
||||
if (_meetingPanel) _meetingPanel.SetActive(false);
|
||||
if (_voteResultPanel) _voteResultPanel.SetActive(false);
|
||||
if (_meetingScrollGO) _meetingScrollGO.SetActive(true);
|
||||
if (_skipButton) _skipButton.gameObject.SetActive(true);
|
||||
if (_myVoteIndicator) _myVoteIndicator.text = "";
|
||||
if (_meetingPhaseLabel) _meetingPhaseLabel.text = "";
|
||||
if (_meetingPhaseCountdown) _meetingPhaseCountdown.text = "";
|
||||
if (_meetingPhaseProgressBar) _meetingPhaseProgressBar.fillAmount = 0f;
|
||||
}
|
||||
|
||||
private void CancelMeetingAutoClose()
|
||||
{
|
||||
if (_meetingCloseCoroutine != null)
|
||||
{
|
||||
var gm = GameManager.Instance;
|
||||
if (gm != null) gm.StopCoroutine(_meetingCloseCoroutine);
|
||||
_meetingCloseCoroutine = null;
|
||||
}
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator CloseMeetingAfterDelay(float delay)
|
||||
{
|
||||
yield return new UnityEngine.WaitForSeconds(delay);
|
||||
// Use HideMeetingPanel so we restore the scroll/skip/indicator
|
||||
// state for the next meeting, not just hide the root panel.
|
||||
HideMeetingPanel();
|
||||
_meetingCloseCoroutine = null;
|
||||
}
|
||||
|
||||
// ── Sabotage ──────────────────────────────────────────────────────────
|
||||
|
||||
public void ShowSabotageTimer(DateTime deadline)
|
||||
{
|
||||
_sabotageMeltdownDeadline = deadline;
|
||||
_sabotageTimerActive = true;
|
||||
if (_sabotagePanel) _sabotagePanel.SetActive(true);
|
||||
if (_sabotageTimerText) _sabotageTimerText.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public void HideSabotageTimer()
|
||||
{
|
||||
_sabotageTimerActive = false;
|
||||
if (_sabotagePanel) _sabotagePanel.SetActive(false);
|
||||
SetCommsBlackout(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the comms-blackout flag and (when active) raise the sabotage
|
||||
/// banner with a clear "comms down" message. The flag is read by
|
||||
/// GameManager_Tasks.UpdateProximity to suppress the REPORT/EMERGENCY
|
||||
/// action button while comms are jammed - this gives the player the
|
||||
/// visible reason why those buttons disappeared.
|
||||
/// </summary>
|
||||
public void SetCommsBlackout(bool active)
|
||||
{
|
||||
_commsBlackout = active;
|
||||
if (active)
|
||||
{
|
||||
if (_sabotagePanel) _sabotagePanel.SetActive(true);
|
||||
if (_sabotageTimerText)
|
||||
{
|
||||
_sabotageTimerText.gameObject.SetActive(true);
|
||||
_sabotageTimerText.text = "📡 COMMS DOWN — reports & meetings disabled";
|
||||
}
|
||||
}
|
||||
else if (!_sabotageTimerActive)
|
||||
{
|
||||
// Only tear the banner down if no meltdown timer is using it.
|
||||
if (_sabotagePanel) _sabotagePanel.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Game end ──────────────────────────────────────────────────────────
|
||||
|
||||
public void ShowGameEndPanel(GameEndedPayload payload, string myUuid)
|
||||
{
|
||||
if (_gameEndPanel) _gameEndPanel.SetActive(true);
|
||||
if (_gameEndText != null)
|
||||
{
|
||||
bool won = payload.Winners?.Contains(myUuid) ?? false;
|
||||
string title = won ? "<color=#FFB800>🏆 VICTORY</color>" : "<color=#C43232>💔 DEFEAT</color>";
|
||||
string faction = payload.WinningFaction == "Impostor" ? "Impostors win!" : "Crew wins!";
|
||||
|
||||
// Non-owners can't actually return to lobby themselves; tell
|
||||
// them who they're waiting on so the panel doesn't read as
|
||||
// "tap leave or stare at the wall." If we can't find an
|
||||
// owner record, fall back to a generic message.
|
||||
string waitMessage = "";
|
||||
if (!_gameClient.IsOwner)
|
||||
{
|
||||
var s = _state;
|
||||
var host = s?.Players?.Find(p => p.IsOwner);
|
||||
string hostName = host?.DisplayName ?? "the host";
|
||||
waitMessage = $"\n\n<size=32>Waiting for {hostName} to return to lobby...</size>";
|
||||
}
|
||||
_gameEndText.text = $"{title}\n{faction}\n<size=38>{payload.Reason}</size>{waitMessage}";
|
||||
}
|
||||
|
||||
// Show "Return to Lobby" only for the host
|
||||
if (_returnToLobbyBtn != null)
|
||||
_returnToLobbyBtn.gameObject.SetActive(_gameClient.IsOwner);
|
||||
}
|
||||
|
||||
// ── Toast ─────────────────────────────────────────────────────────────
|
||||
|
||||
public void ShowToast(string message)
|
||||
{
|
||||
if (_state != null) { _state.ToastMessage = message; _state.ToastExpiry = UnityEngine.Time.time + 4f; }
|
||||
if (_toastGO == null) return;
|
||||
_toastGO.SetActive(true);
|
||||
if (_toastText != null) _toastText.text = message;
|
||||
}
|
||||
|
||||
private void TickToast()
|
||||
{
|
||||
var s = _state;
|
||||
if (_toastGO == null) return;
|
||||
|
||||
if (s != null && !string.IsNullOrEmpty(s.ToastMessage) && UnityEngine.Time.time < s.ToastExpiry)
|
||||
{
|
||||
_toastGO.SetActive(true);
|
||||
if (_toastText != null) _toastText.text = s.ToastMessage;
|
||||
}
|
||||
else
|
||||
{
|
||||
_toastGO.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Canvas switching ──────────────────────────────────────────────────
|
||||
|
||||
private void SetCanvases(bool createJoin, bool inLobby, bool loading, bool game)
|
||||
{
|
||||
EnsureCanvasReady(ClientCreateJoinLobby);
|
||||
EnsureCanvasReady(ClientInLobby);
|
||||
EnsureCanvasReady(ClientLoadingScreen);
|
||||
EnsureCanvasReady(ClientGameScreen);
|
||||
|
||||
if (ClientCreateJoinLobby) ClientCreateJoinLobby.gameObject.SetActive(createJoin);
|
||||
if (ClientInLobby) ClientInLobby.gameObject.SetActive(inLobby);
|
||||
if (ClientLoadingScreen) ClientLoadingScreen.gameObject.SetActive(loading);
|
||||
if (ClientGameScreen) ClientGameScreen.gameObject.SetActive(game);
|
||||
}
|
||||
|
||||
// ── Utilities ─────────────────────────────────────────────────────────
|
||||
|
||||
private static void EnsureCanvasReady(Canvas canvas)
|
||||
{
|
||||
if (canvas == null) return;
|
||||
if (!canvas.enabled) canvas.enabled = true;
|
||||
var t = canvas.transform;
|
||||
if (t != null)
|
||||
{
|
||||
var s = t.localScale;
|
||||
if (Mathf.Abs(s.x) < 0.001f || Mathf.Abs(s.y) < 0.001f || Mathf.Abs(s.z) < 0.001f)
|
||||
t.localScale = Vector3.one;
|
||||
}
|
||||
}
|
||||
|
||||
private static TMP_Text FindTMP(Transform root, string name)
|
||||
{
|
||||
if (root == null) return null;
|
||||
foreach (var tmp in root.GetComponentsInChildren<TMP_Text>(true))
|
||||
if (tmp != null && tmp.name == name) return tmp;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Transform FindTransform(Transform root, string name)
|
||||
{
|
||||
if (root == null) return null;
|
||||
foreach (Transform t in root.GetComponentsInChildren<Transform>(true))
|
||||
if (t.name == name) return t;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static GameObject FindTransformGO(Transform root, string name)
|
||||
=> FindTransform(root, name)?.gameObject;
|
||||
|
||||
private static RectTransform MakeChild(string name, RectTransform parent)
|
||||
{
|
||||
var go = new GameObject(name);
|
||||
var rt = go.AddComponent<RectTransform>();
|
||||
rt.SetParent(parent, false);
|
||||
rt.localScale = Vector3.one;
|
||||
return rt;
|
||||
}
|
||||
|
||||
private static void Stretch(RectTransform rt)
|
||||
{
|
||||
rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one;
|
||||
rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f575016e02384774d88b46ed7f09579f
|
||||
guid: cbe0afd6cfb57b44781533cfa4ce4196
|
||||
@@ -2,52 +2,21 @@ using GeoSus.Client;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
/*public enum TaskType
|
||||
|
||||
public enum TaskType
|
||||
{
|
||||
Task //TODO: Typy úkolù
|
||||
}*/
|
||||
|
||||
|
||||
Task
|
||||
}
|
||||
|
||||
public interface ITask
|
||||
{
|
||||
public string TaskID { get; } // Unikátní ID úkolu pro server
|
||||
public TaskType TaskType { get; } // Typ úkolu
|
||||
public string TaskName { get; } // Viditelný název úkolu
|
||||
public Position TaskLocation { get; } // Polohy na mapì
|
||||
public bool IsCompleted { get; } // Stav dokonèení úkolu
|
||||
|
||||
void Initialize(Action<ITask> onCompleted); // Vytvoøení tasku + naètení postupu
|
||||
void ExitTask(Action<ITask> onExit); // Pøi opuštìní úkolu poslat hotovo / uložit postup / reset
|
||||
void Complete(); // Oznaèit úkol jako dokonèený, poslat na server a zavøít
|
||||
public string TaskID { get; set; } // Unikátní ID úkolu pro server
|
||||
public TaskType TaskType { get; set; } // Typ úkolu
|
||||
public string TaskName { get; set; } // Viditelný název úkolu
|
||||
public (double, double) TaskLocation { get; set; } // Poloha na mapě
|
||||
public bool IsCompleted { get; } // Stav dokončení úkolu
|
||||
|
||||
void Initialize(Action<ITask> onCompleted); // Vytvoření tasku
|
||||
void ExitTask(Action<ITask> onExit); // Při opuštění úkolu
|
||||
void Complete(); // Označit úkol jako dokončený
|
||||
}
|
||||
/* Ukázoková implementace ITask
|
||||
public class Wires : ITask{
|
||||
public string TaskID { get; set; } // Unikátní ID úkolu pro server
|
||||
public TaskType TaskType { get; set; } // Typ úkolu
|
||||
public string TaskName { get; set; } // Viditelný název úkolu
|
||||
public Position TaskLocation { get; set; } // Poloha na mapì
|
||||
public bool IsCompleted { get; private set; } // Stav dokonèení úkolu
|
||||
private Action<ITask> _onCompleted;
|
||||
|
||||
public void Initialize(Action<ITask> onCompleted) // Vytvoøení tasku
|
||||
{
|
||||
IsCompleted = false;
|
||||
_onCompleted = onCompleted;
|
||||
}
|
||||
public void ExitTask(Action<ITask> onExit) //Zavøení tasku
|
||||
{
|
||||
onExit?.Invoke(this);
|
||||
}
|
||||
public void Complete() // Dokonèení tasku a zavøení
|
||||
{
|
||||
IsCompleted = true;
|
||||
_onCompleted?.Invoke(this);
|
||||
ExitTask(null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
*/
|
||||
@@ -1,2 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e926b313c00d4f48ad68750c88817bf
|
||||
guid: 00f17be43b5049645915f193bf99516b
|
||||
101
Assets/GameManager/New Material.mat
Normal file
@@ -0,0 +1,101 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: New Material
|
||||
m_Shader: {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _AlphaTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _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_Floats:
|
||||
- PixelSnap: 0
|
||||
- _BumpScale: 1
|
||||
- _ColorMask: 15
|
||||
- _CullMode: 0
|
||||
- _Cutoff: 0.5
|
||||
- _DetailNormalMapScale: 1
|
||||
- _DstBlend: 0
|
||||
- _EnableExternalAlpha: 0
|
||||
- _GlossMapScale: 1
|
||||
- _Glossiness: 0.5
|
||||
- _GlossyReflections: 1
|
||||
- _Metallic: 0
|
||||
- _Mode: 0
|
||||
- _OcclusionStrength: 1
|
||||
- _Parallax: 0.02
|
||||
- _SmoothnessTextureChannel: 0
|
||||
- _SpecularHighlights: 1
|
||||
- _SrcBlend: 1
|
||||
- _Stencil: 0
|
||||
- _StencilComp: 8
|
||||
- _StencilOp: 0
|
||||
- _StencilReadMask: 255
|
||||
- _StencilWriteMask: 255
|
||||
- _UVSec: 0
|
||||
- _UseUIAlphaClip: 0
|
||||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _ClipRect: {r: -32767, g: -32767, b: 32767, a: 32767}
|
||||
- _Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
- _Flip: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _RendererColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
8
Assets/GameManager/New Material.mat.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dac0a6a54861f2c438fc5fd58864473d
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
113
Assets/GlassPiece.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class GlassPiece : MonoBehaviour
|
||||
{
|
||||
public int PieceIndex { get; private set; }
|
||||
public bool IsBroken { get; private set; }
|
||||
|
||||
[SerializeField] private float maxHealth = 1000f;
|
||||
|
||||
[Header("Damage Visuals")]
|
||||
[SerializeField] private float damagedAlpha = 0.85f;
|
||||
|
||||
private float currentHealth;
|
||||
private Rigidbody rb;
|
||||
private Renderer rend;
|
||||
private Vector3 originalScale;
|
||||
|
||||
private Color intactColor = Color.white;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
rb = GetComponent<Rigidbody>();
|
||||
rend = GetComponent<Renderer>();
|
||||
|
||||
if (rend == null)
|
||||
rend = GetComponentInChildren<Renderer>();
|
||||
|
||||
originalScale = transform.localScale;
|
||||
|
||||
if (rend != null)
|
||||
{
|
||||
if (rend.material.HasProperty("_BaseColor"))
|
||||
intactColor = rend.material.GetColor("_BaseColor");
|
||||
else if (rend.material.HasProperty("_Color"))
|
||||
intactColor = rend.material.color;
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize(int index, float startHealth)
|
||||
{
|
||||
PieceIndex = index;
|
||||
maxHealth = startHealth;
|
||||
currentHealth = startHealth;
|
||||
IsBroken = false;
|
||||
|
||||
if (rb != null)
|
||||
{
|
||||
rb.isKinematic = true;
|
||||
rb.useGravity = false;
|
||||
rb.linearVelocity = Vector3.zero;
|
||||
rb.angularVelocity = Vector3.zero;
|
||||
}
|
||||
|
||||
transform.localScale = originalScale;
|
||||
UpdateVisual();
|
||||
}
|
||||
|
||||
public bool ApplyDamage(float damage, Vector3 hitPoint, Vector3 impulseDirection, float impulseStrength)
|
||||
{
|
||||
if (IsBroken) return false;
|
||||
|
||||
currentHealth = Mathf.Max(0f, currentHealth - damage);
|
||||
UpdateVisual();
|
||||
|
||||
if (currentHealth <= 0f)
|
||||
{
|
||||
Break(hitPoint, impulseDirection, impulseStrength);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void UpdateVisual()
|
||||
{
|
||||
if (rend == null) return;
|
||||
|
||||
float damage01 = 1f - (currentHealth / maxHealth);
|
||||
float visualT = Mathf.Pow(damage01, 0.8f);
|
||||
|
||||
Color targetColor = new Color(1f, 1f, 1f, damagedAlpha);
|
||||
Color finalColor = Color.Lerp(intactColor, targetColor, visualT);
|
||||
|
||||
if (rend.material.HasProperty("_BaseColor"))
|
||||
rend.material.SetColor("_BaseColor", finalColor);
|
||||
else if (rend.material.HasProperty("_Color"))
|
||||
rend.material.color = finalColor;
|
||||
|
||||
float shrink = Mathf.Lerp(1f, 0.92f, visualT * 0.25f);
|
||||
transform.localScale = originalScale * shrink;
|
||||
}
|
||||
|
||||
private void Break(Vector3 hitPoint, Vector3 impulseDirection, float impulseStrength)
|
||||
{
|
||||
if (IsBroken) return;
|
||||
|
||||
IsBroken = true;
|
||||
|
||||
transform.SetParent(null, true);
|
||||
|
||||
if (rb != null)
|
||||
{
|
||||
rb.isKinematic = false;
|
||||
rb.useGravity = true;
|
||||
rb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
|
||||
|
||||
if (impulseDirection.sqrMagnitude < 0.0001f)
|
||||
impulseDirection = transform.forward;
|
||||
|
||||
rb.AddForceAtPosition(impulseDirection.normalized * impulseStrength, hitPoint, ForceMode.Impulse);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/GlassPiece.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ceeafc1169c5a7143b9464f59e08660f
|
||||
194
Assets/GlassRingController.cs
Normal file
@@ -0,0 +1,194 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class GlassRingController : MonoBehaviour
|
||||
{
|
||||
[Header("Auto Setup")]
|
||||
public bool autoCollectOnAwake = true;
|
||||
public bool includeInactive = true;
|
||||
public float pieceMaxHealth = 100f;
|
||||
|
||||
[Header("Rotation")]
|
||||
public float rotationSpeedDegrees = 45f;
|
||||
|
||||
[Header("Audio")]
|
||||
public AudioSource audioSource;
|
||||
public AudioClip damageClip;
|
||||
public AudioClip breakClip;
|
||||
[Range(0f, 1f)] public float damageVolume = 0.7f;
|
||||
[Range(0f, 1f)] public float breakVolume = 1f;
|
||||
|
||||
private GlassPiece[] pieces;
|
||||
public GlassPiece[] Pieces => pieces;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (audioSource == null)
|
||||
audioSource = GetComponent<AudioSource>();
|
||||
|
||||
if (audioSource == null)
|
||||
audioSource = gameObject.AddComponent<AudioSource>();
|
||||
|
||||
if (Application.isPlaying && autoCollectOnAwake)
|
||||
{
|
||||
CollectPiecesFromChildren();
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!Application.isPlaying || Mathf.Abs(rotationSpeedDegrees) < 0.001f)
|
||||
return;
|
||||
|
||||
transform.Rotate(Vector3.right, rotationSpeedDegrees * Time.deltaTime, Space.World);
|
||||
}
|
||||
|
||||
[ContextMenu("Collect Pieces From Children")]
|
||||
public void CollectPiecesFromChildren()
|
||||
{
|
||||
List<GlassPiece> foundPieces = new List<GlassPiece>();
|
||||
|
||||
Transform[] allChildren = GetComponentsInChildren<Transform>(includeInactive);
|
||||
|
||||
foreach (Transform t in allChildren)
|
||||
{
|
||||
if (t == transform) continue;
|
||||
|
||||
Renderer rend = t.GetComponent<Renderer>();
|
||||
if (rend == null)
|
||||
rend = t.GetComponentInChildren<Renderer>();
|
||||
|
||||
if (rend == null)
|
||||
continue;
|
||||
|
||||
Collider col = t.GetComponent<Collider>();
|
||||
|
||||
bool invalidMeshCollider = false;
|
||||
MeshCollider existingMeshCollider = col as MeshCollider;
|
||||
if (existingMeshCollider != null && existingMeshCollider.sharedMesh == null)
|
||||
{
|
||||
invalidMeshCollider = true;
|
||||
}
|
||||
|
||||
if (col == null || invalidMeshCollider)
|
||||
{
|
||||
if (invalidMeshCollider)
|
||||
Destroy(existingMeshCollider);
|
||||
|
||||
BoxCollider box = t.GetComponent<BoxCollider>();
|
||||
if (box == null)
|
||||
box = t.gameObject.AddComponent<BoxCollider>();
|
||||
|
||||
Bounds worldBounds = rend.bounds;
|
||||
Vector3 localCenter = t.InverseTransformPoint(worldBounds.center);
|
||||
|
||||
box.center = localCenter;
|
||||
|
||||
Vector3 lossy = t.lossyScale;
|
||||
box.size = new Vector3(
|
||||
worldBounds.size.x / Mathf.Max(lossy.x, 0.0001f),
|
||||
worldBounds.size.y / Mathf.Max(lossy.y, 0.0001f),
|
||||
worldBounds.size.z / Mathf.Max(lossy.z, 0.0001f)
|
||||
);
|
||||
|
||||
col = box;
|
||||
}
|
||||
|
||||
GlassPiece piece = t.GetComponent<GlassPiece>();
|
||||
if (piece == null)
|
||||
piece = t.gameObject.AddComponent<GlassPiece>();
|
||||
|
||||
Rigidbody rb = t.GetComponent<Rigidbody>();
|
||||
if (rb == null)
|
||||
rb = t.gameObject.AddComponent<Rigidbody>();
|
||||
|
||||
rb.isKinematic = true;
|
||||
rb.useGravity = false;
|
||||
|
||||
foundPieces.Add(piece);
|
||||
}
|
||||
|
||||
foundPieces.Sort((a, b) =>
|
||||
{
|
||||
float angleA = Mathf.Atan2(a.transform.localPosition.z, a.transform.localPosition.x);
|
||||
float angleB = Mathf.Atan2(b.transform.localPosition.z, b.transform.localPosition.x);
|
||||
return angleA.CompareTo(angleB);
|
||||
});
|
||||
|
||||
pieces = foundPieces.ToArray();
|
||||
|
||||
for (int i = 0; i < pieces.Length; i++)
|
||||
{
|
||||
pieces[i].Initialize(i, pieceMaxHealth);
|
||||
}
|
||||
|
||||
Debug.Log($"GlassRingController: collected {pieces.Length} pieces.");
|
||||
}
|
||||
|
||||
public void ApplyProjectileImpact(int hitIndex, ProjectileBehaviour projectile, float charge01, Vector3 hitPoint)
|
||||
{
|
||||
if (pieces == null || pieces.Length == 0) return;
|
||||
|
||||
float sigma = Mathf.Max(0.01f, projectile.sigma);
|
||||
float chargeMultiplier = 0.55f + 1.45f * Mathf.Pow(charge01, 1.4f);
|
||||
|
||||
bool anyHealthChanged = false;
|
||||
bool anyNewBreak = false;
|
||||
|
||||
for (int i = 0; i < pieces.Length; i++)
|
||||
{
|
||||
GlassPiece piece = pieces[i];
|
||||
if (piece == null || piece.IsBroken) continue;
|
||||
|
||||
int d = CircularDistance(i, hitIndex, pieces.Length);
|
||||
float gaussian = Mathf.Exp(-(d * d) / (2f * sigma * sigma));
|
||||
|
||||
if (gaussian < 0.005f) continue;
|
||||
|
||||
float damage = projectile.baseDamage * chargeMultiplier * gaussian;
|
||||
if (i == hitIndex)
|
||||
damage *= projectile.directHitMultiplier;
|
||||
|
||||
Vector3 impulseDir = piece.transform.position - hitPoint;
|
||||
if (impulseDir.sqrMagnitude < 0.0001f)
|
||||
impulseDir = piece.transform.position - transform.position;
|
||||
|
||||
float impulse = projectile.breakImpulse * chargeMultiplier * gaussian;
|
||||
if (i == hitIndex)
|
||||
impulse *= projectile.directHitMultiplier;
|
||||
|
||||
bool brokeNow = piece.ApplyDamage(damage, hitPoint, impulseDir, impulse);
|
||||
|
||||
anyHealthChanged = true;
|
||||
if (brokeNow)
|
||||
anyNewBreak = true;
|
||||
}
|
||||
|
||||
if (anyNewBreak)
|
||||
{
|
||||
PlayBreakSound();
|
||||
}
|
||||
else if (anyHealthChanged)
|
||||
{
|
||||
PlayDamageSound();
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayDamageSound()
|
||||
{
|
||||
if (audioSource != null && damageClip != null)
|
||||
audioSource.PlayOneShot(damageClip, damageVolume);
|
||||
}
|
||||
|
||||
private void PlayBreakSound()
|
||||
{
|
||||
if (audioSource != null && breakClip != null)
|
||||
audioSource.PlayOneShot(breakClip, breakVolume);
|
||||
}
|
||||
|
||||
private int CircularDistance(int a, int b, int count)
|
||||
{
|
||||
int raw = Mathf.Abs(a - b);
|
||||
return Mathf.Min(raw, count - raw);
|
||||
}
|
||||
}
|
||||
2
Assets/GlassRingController.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bae40152c8c09ac40bb8b2ca0a85308b
|
||||
@@ -0,0 +1,23 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b6397bdf2a1c0de459fbfb30ea5c831b
|
||||
AudioImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 8
|
||||
defaultSettings:
|
||||
serializedVersion: 2
|
||||
loadType: 0
|
||||
sampleRateSetting: 0
|
||||
sampleRateOverride: 44100
|
||||
compressionFormat: 1
|
||||
quality: 1
|
||||
conversionMode: 0
|
||||
preloadAudioData: 0
|
||||
platformSettingOverrides: {}
|
||||
forceToMono: 0
|
||||
normalize: 1
|
||||
loadInBackground: 0
|
||||
ambisonic: 0
|
||||
3D: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Hra_Kabely.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e8c70d2b2080681448d8f781f73c73a0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Hra_Kabely/Kabely-Material.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52b482693f234054aa4d20f92fbef10d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
84
Assets/Hra_Kabely/Kabely-Material/BLUE.mat
Normal file
@@ -0,0 +1,84 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: BLUE
|
||||
m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
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_Floats:
|
||||
- _BumpScale: 1
|
||||
- _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, g: 0, b: 1, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
8
Assets/Hra_Kabely/Kabely-Material/BLUE.mat.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09c36d1bce0ccb84183ec9ae484ad36f
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
84
Assets/Hra_Kabely/Kabely-Material/GREEN.mat
Normal file
@@ -0,0 +1,84 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: GREEN
|
||||
m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
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_Floats:
|
||||
- _BumpScale: 1
|
||||
- _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, g: 1, b: 0, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||