From b4ac8f600fa88afc9b6b8682d7d73799da64d6ad Mon Sep 17 00:00:00 2001 From: Dal4segno Date: Sun, 18 Jan 2026 23:37:43 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A0=84=EC=9E=A5=EC=9D=98=20=EC=95=88?= =?UTF-8?q?=EA=B0=9C=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 미탐험지역은 검게 덮음. 탐험된 지역은 어두운 색, 보고 있는 블록은 밝은색 --- Assets/Materials/FogHeightMask.shader | 45 +++++++ Assets/Materials/FogHeightMask.shader.meta | 9 ++ Assets/Materials/FogMaterial.mat | 139 +++++++++++++++++++++ Assets/Materials/FogMaterial.mat.meta | 8 ++ Assets/Prefabs/GenericDroppedItem.prefab | 4 +- Assets/Prefabs/Player.prefab | 12 +- Assets/Scripts/GameBase/FogOfWar.cs | 71 +++++++++-- Assets/Scripts/MineableBlock.cs | 31 +++-- ProjectSettings/ProjectSettings.asset | 2 +- ProjectSettings/QualitySettings.asset | 79 +----------- ProjectSettings/UnityConnectSettings.asset | 2 +- 11 files changed, 302 insertions(+), 100 deletions(-) create mode 100644 Assets/Materials/FogHeightMask.shader create mode 100644 Assets/Materials/FogHeightMask.shader.meta create mode 100644 Assets/Materials/FogMaterial.mat create mode 100644 Assets/Materials/FogMaterial.mat.meta diff --git a/Assets/Materials/FogHeightMask.shader b/Assets/Materials/FogHeightMask.shader new file mode 100644 index 0000000..1d1f19a --- /dev/null +++ b/Assets/Materials/FogHeightMask.shader @@ -0,0 +1,45 @@ +// 파일명: FogHeightMask.shader +Shader "Custom/FogHeightMask" +{ + Properties { _MainColor ("Fog Color", Color) = (0,0,0,1) } + SubShader { + Tags { "Queue"="Transparent" "RenderType"="Transparent" } + Blend SrcAlpha OneMinusSrcAlpha // 투명도 사용 설정 + ZWrite Off + + Pass { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #include "UnityCG.cginc" + + struct v2f { + float4 vertex : SV_POSITION; + float3 worldPos : TEXCOORD0; + }; + + sampler2D _GlobalFogTex; + float _FogWorldSize; + float _GroundLevelY; // 매니저가 보내줄 높이값 + float4 _MainColor; + + v2f vert (appdata_base v) { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; + return o; + } + + fixed4 frag (v2f i) : SV_Target { + // [핵심 로직] 현재 픽셀의 높이가 지상 레벨보다 높으면 투명하게! + if (i.worldPos.y > _GroundLevelY) return fixed4(0,0,0,0); + + float2 uv = i.worldPos.xz / _FogWorldSize + 0.5; + fixed4 fogData = tex2D(_GlobalFogTex, uv); + + return fixed4(_MainColor.rgb, fogData.a); // 안개 텍스처의 알파값(검정 정도) 적용 + } + ENDCG + } + } +} \ No newline at end of file diff --git a/Assets/Materials/FogHeightMask.shader.meta b/Assets/Materials/FogHeightMask.shader.meta new file mode 100644 index 0000000..6c756a3 --- /dev/null +++ b/Assets/Materials/FogHeightMask.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 7d8b19748651ad54a9350f716d0f7046 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Materials/FogMaterial.mat b/Assets/Materials/FogMaterial.mat new file mode 100644 index 0000000..59bd4bc --- /dev/null +++ b/Assets/Materials/FogMaterial.mat @@ -0,0 +1,139 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &-8250424374119353640 +MonoBehaviour: + m_ObjectHideFlags: 11 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Editor::UnityEditor.Rendering.Universal.AssetVersion + version: 10 +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: FogMaterial + m_Shader: {fileID: 4800000, guid: 7d8b19748651ad54a9350f716d0f7046, type: 3} + 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: + - MOTIONVECTORS + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + 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} + - _SpecGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _AddPrecomputedVelocity: 0 + - _AlphaClip: 0 + - _AlphaToMask: 0 + - _Blend: 0 + - _BlendModePreserveSpecular: 1 + - _BlendOp: 0 + - _BumpScale: 1 + - _ClearCoatMask: 0 + - _ClearCoatSmoothness: 0 + - _Cull: 2 + - _Cutoff: 0.5 + - _DetailAlbedoMapScale: 1 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _DstBlendAlpha: 0 + - _EnvironmentReflections: 1 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 0 + - _Metallic: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.005 + - _QueueOffset: 0 + - _ReceiveShadows: 1 + - _SampleGI: 0 + - _Smoothness: 0.5 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _SrcBlendAlpha: 1 + - _Surface: 0 + - _WorkflowMode: 1 + - _XRMotionVectorsPass: 1 + - _ZWrite: 1 + m_Colors: + - _BaseColor: {r: 0, g: 0, b: 0, a: 1} + - _Color: {r: 0, g: 0, b: 0, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + - _MainColor: {r: 0, g: 0, b: 0, a: 1} + - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 diff --git a/Assets/Materials/FogMaterial.mat.meta b/Assets/Materials/FogMaterial.mat.meta new file mode 100644 index 0000000..218265d --- /dev/null +++ b/Assets/Materials/FogMaterial.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3deb4a29e039554439a14651844edec2 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/GenericDroppedItem.prefab b/Assets/Prefabs/GenericDroppedItem.prefab index f1bc03c..7d2b97d 100644 --- a/Assets/Prefabs/GenericDroppedItem.prefab +++ b/Assets/Prefabs/GenericDroppedItem.prefab @@ -99,8 +99,8 @@ MonoBehaviour: myFilter: {fileID: 8145339523266927793} myRenderer: {fileID: 1476831640209354726} dropScale: 0.3 - jumpForce: 2 - spreadForce: 1 + jumpForce: 0.4 + spreadForce: 0.1 rotationSpeed: 100 --- !u!54 &6380490795664845462 Rigidbody: diff --git a/Assets/Prefabs/Player.prefab b/Assets/Prefabs/Player.prefab index 4a6ec54..5976fd5 100644 --- a/Assets/Prefabs/Player.prefab +++ b/Assets/Prefabs/Player.prefab @@ -305,7 +305,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject - GlobalObjectIdHash: 4268073897 + GlobalObjectIdHash: 856270328 InScenePlacedSourceGlobalObjectIdHash: 0 DeferredDespawnTick: 0 Ownership: 1 @@ -335,7 +335,7 @@ MonoBehaviour: rotationSpeed: 10 jumpHeight: 1.5 gravity: -19.62 - interactRange: 3 + interactRange: 1 interactableLayer: serializedVersion: 2 m_Bits: 25600 @@ -359,7 +359,7 @@ MonoBehaviour: crosshairUI: {fileID: 0} idleCrosshair: {fileID: 2628378444897590106, guid: 174f7cb20aaa6d4409b788a700a925ad, type: 3} targetCrosshair: {fileID: -5662625722731528258, guid: 7652364ca249c3144813de7eb3d1b129, type: 3} - visionRadius: 5 + visionRadius: 2 --- !u!114 &106528027568436521 MonoBehaviour: m_ObjectHideFlags: 0 @@ -426,7 +426,7 @@ MonoBehaviour: - IsCrossFadeExit: 0 Layer: 0 OriginatingState: -2089788878 - DestinationState: -437043462 + DestinationState: 581277072 TransitionDuration: 0.1 TriggerNameHash: 1080829965 TransitionIndex: 0 @@ -448,6 +448,10 @@ MonoBehaviour: NameHash: -662453572 Synchronize: 1 ParameterType: 9 + - name: ActionSpeed + NameHash: 1673499952 + Synchronize: 1 + ParameterType: 1 AnimatorParametersExpanded: 0 --- !u!114 &5271692149337681293 MonoBehaviour: diff --git a/Assets/Scripts/GameBase/FogOfWar.cs b/Assets/Scripts/GameBase/FogOfWar.cs index 6048ad0..b9bfaca 100644 --- a/Assets/Scripts/GameBase/FogOfWar.cs +++ b/Assets/Scripts/GameBase/FogOfWar.cs @@ -6,28 +6,83 @@ public class FogOfWarManager : MonoBehaviour public static FogOfWarManager Instance; [Header("Settings")] - public RenderTexture fogMaskTexture; // 쉐이더에 전달될 텍스처 - public Material fogMaterial; // 검은 안개 머티리얼 - public float revealRadius = 5f; // 밝혀지는 반경 + public float worldSize = 100f; // 1단계에서 만든 Plane의 크기와 맞춤 + public int textureSize = 512; // 안개 해상도 (높을수록 부드러움) + public float revealRadius = 5f; // 밝혀지는 반경 - void Awake() => Instance = this; + [Header("Underground Settings")] + public float groundLevelY = 0f; // 이 높이보다 낮으면 지하로 간주 + + private Texture2D _fogTexture; + private Color32[] _pixels; + + void Awake() + { + Instance = this; + InitTexture(); + } + + private void InitTexture() + { + // 1. 텍스처 생성 (모두 검은색으로 시작) + _fogTexture = new Texture2D(textureSize, textureSize, TextureFormat.RGBA32, false); + _pixels = new Color32[textureSize * textureSize]; + for (int i = 0; i < _pixels.Length; i++) _pixels[i] = new Color32(0, 0, 0, 255); + + _fogTexture.SetPixels32(_pixels); + _fogTexture.Apply(); + + // 2. 셰이더 전역 변수로 전달 (이름이 중요!) + Shader.SetGlobalTexture("_GlobalFogTex", _fogTexture); + Shader.SetGlobalFloat("_FogWorldSize", worldSize); + + // 셰이더에 기준 높이 전달 + Shader.SetGlobalFloat("_GroundLevelY", groundLevelY); + } void Update() { - // 멀티플레이어이므로 로컬 플레이어(IsOwner)의 위치만 마스크에 그립니다. - if (NetworkManager.Singleton.LocalClient != null) + if (NetworkManager.Singleton != null && NetworkManager.Singleton.LocalClient != null) { var localPlayer = NetworkManager.Singleton.LocalClient.PlayerObject; if (localPlayer != null) { UpdateFogMask(localPlayer.transform.position); + + // 추가: 플레이어가 지상에 있을 때는 안개 판을 아예 꺼버릴 수도 있습니다 (선택 사항) + // bool isUnderground = localPlayer.transform.position.y < groundLevelY + 2f; + // fogOverlayObject.SetActive(isUnderground); } } } private void UpdateFogMask(Vector3 playerPos) { - // 플레이어의 월드 좌표를 텍스처 좌표로 변환하여 - // 해당 지점의 알파값을 0(투명)으로 깎아내는 로직 (Compute Shader나 GPU 가속 권장) + // 월드 좌표를 텍스처 좌표로 변환 + int centerX = Mathf.RoundToInt((playerPos.x / worldSize + 0.5f) * textureSize); + int centerZ = Mathf.RoundToInt((playerPos.z / worldSize + 0.5f) * textureSize); + int radius = Mathf.RoundToInt((revealRadius / worldSize) * textureSize); + + bool changed = false; + for (int y = centerZ - radius; y <= centerZ + radius; y++) + { + for (int x = centerX - radius; x <= centerX + radius; x++) + { + if (x >= 0 && x < textureSize && y >= 0 && y < textureSize) + { + float dist = Vector2.Distance(new Vector2(x, y), new Vector2(centerX, centerZ)); + if (dist < radius) + { + int idx = y * textureSize + x; + if (_pixels[idx].a != 0) + { // 아직 안 밝혀진 곳만 + _pixels[idx] = new Color32(0, 0, 0, 0); // 투명하게! + changed = true; + } + } + } + } + } + if (changed) { _fogTexture.SetPixels32(_pixels); _fogTexture.Apply(); } } } \ No newline at end of file diff --git a/Assets/Scripts/MineableBlock.cs b/Assets/Scripts/MineableBlock.cs index b047db9..75668ae 100644 --- a/Assets/Scripts/MineableBlock.cs +++ b/Assets/Scripts/MineableBlock.cs @@ -80,22 +80,33 @@ public class MineableBlock : NetworkBehaviour void Update() { - // 1. 서버에서 한 번이라도 발견되었고 - // 2. 최근(0.2초 이내)에 플레이어의 시야 범위 안에 있었다면 렌더러를 켭니다. - bool isCurrentlyVisible = (Time.time - _lastVisibleTime) < VisibilityThreshold; - - if (_renderer != null) + // 1. 이미 발견된 블록인지는 서버 변수(isDiscovered)로 확인 + // 2. 현재 내 위치가 안개에서 벗어났는지 확인 (매우 단순화된 로직) + if (!isDiscovered.Value) { - // 실시간 시야: 발견되었더라도 지금 근처에 플레이어가 없으면 다시 숨깁니다. - _renderer.enabled = isDiscovered.Value && isCurrentlyVisible; + float dist = Vector3.Distance(transform.position, NetworkManager.Singleton.LocalClient.PlayerObject.transform.position); + if (dist < FogOfWarManager.Instance.revealRadius) + { + // 서버에 "나 발견됐어!"라고 보고 + RequestRevealServerRpc(); + } } - // 1. 이미 발견된 블록만 실시간 시야 연산을 수행합니다. - if (!isDiscovered.Value) return; + // 3. 비주얼 업데이트: 발견된 적이 있을 때만 렌더러를 켬 + if (_renderer != null) + { + _renderer.enabled = isDiscovered.Value; + } UpdateState(); } + [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)] + private void RequestRevealServerRpc() + { + isDiscovered.Value = true; + } + private void UpdateState() { if (_renderer == null) return; @@ -104,7 +115,7 @@ public class MineableBlock : NetworkBehaviour bool isCurrentlyVisible = (Time.time - _lastVisibleTime) < VisibilityThreshold; // 3. 상태에 따라 색상과 렌더러 상태를 결정합니다. - _renderer.enabled = true; // 발견된 상태이므로 항상 켭니다. + if (_renderer.enabled == false) return; _renderer.GetPropertyBlock(_propBlock); // 2. 시야 내에 있으면 원본 색상(_originalColor), 멀어지면 어둡게 만든 색상을 적용합니다. diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 488e002..98515ea 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -83,7 +83,7 @@ PlayerSettings: androidApplicationEntry: 2 defaultIsNativeResolution: 1 macRetinaSupport: 1 - runInBackground: 0 + runInBackground: 1 muteOtherAudioSources: 0 Prepare IOS For Recording: 0 Force IOS Speakers When Recording: 0 diff --git a/ProjectSettings/QualitySettings.asset b/ProjectSettings/QualitySettings.asset index f55198a..7a5ecbf 100644 --- a/ProjectSettings/QualitySettings.asset +++ b/ProjectSettings/QualitySettings.asset @@ -4,63 +4,9 @@ QualitySettings: m_ObjectHideFlags: 0 serializedVersion: 5 - m_CurrentQuality: 1 + m_CurrentQuality: 0 m_QualitySettings: - - serializedVersion: 4 - name: Mobile - pixelLightCount: 2 - shadows: 2 - shadowResolution: 1 - shadowProjection: 1 - shadowCascades: 2 - shadowDistance: 40 - shadowNearPlaneOffset: 3 - shadowCascade2Split: 0.33333334 - shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} - shadowmaskMode: 0 - skinWeights: 2 - globalTextureMipmapLimit: 0 - textureMipmapLimitSettings: [] - anisotropicTextures: 1 - antiAliasing: 0 - softParticles: 0 - softVegetation: 1 - realtimeReflectionProbes: 0 - billboardsFaceCameraPosition: 1 - useLegacyDetailDistribution: 1 - adaptiveVsync: 0 - vSyncCount: 0 - realtimeGICPUUsage: 100 - adaptiveVsyncExtraA: 0 - adaptiveVsyncExtraB: 0 - lodBias: 1 - maximumLODLevel: 0 - enableLODCrossFade: 1 - streamingMipmapsActive: 0 - streamingMipmapsAddAllCameras: 1 - streamingMipmapsMemoryBudget: 512 - streamingMipmapsRenderersPerFrame: 512 - streamingMipmapsMaxLevelReduction: 2 - streamingMipmapsMaxFileIORequests: 1024 - particleRaycastBudget: 256 - asyncUploadTimeSlice: 2 - asyncUploadBufferSize: 16 - asyncUploadPersistentBuffer: 1 - resolutionScalingFixedDPIFactor: 1 - customRenderPipeline: {fileID: 11400000, guid: 5e6cbd92db86f4b18aec3ed561671858, - type: 2} - terrainQualityOverrides: 0 - terrainPixelError: 1 - terrainDetailDensityScale: 1 - terrainBasemapDistance: 1000 - terrainDetailDistance: 80 - terrainTreeDistance: 5000 - terrainBillboardStart: 50 - terrainFadeLength: 5 - terrainMaxTrees: 50 - excludedTargetPlatforms: - - Standalone - - serializedVersion: 4 + - serializedVersion: 5 name: PC pixelLightCount: 2 shadows: 2 @@ -88,6 +34,7 @@ QualitySettings: adaptiveVsyncExtraA: 0 adaptiveVsyncExtraB: 0 lodBias: 2 + meshLodThreshold: 1 maximumLODLevel: 0 enableLODCrossFade: 1 streamingMipmapsActive: 0 @@ -101,8 +48,7 @@ QualitySettings: asyncUploadBufferSize: 16 asyncUploadPersistentBuffer: 1 resolutionScalingFixedDPIFactor: 1 - customRenderPipeline: {fileID: 11400000, guid: 4b83569d67af61e458304325a23e5dfd, - type: 2} + customRenderPipeline: {fileID: 11400000, guid: 4b83569d67af61e458304325a23e5dfd, type: 2} terrainQualityOverrides: 0 terrainPixelError: 1 terrainDetailDensityScale: 1 @@ -116,19 +62,4 @@ QualitySettings: - Android - iPhone m_TextureMipmapLimitGroupNames: [] - m_PerPlatformDefaultQuality: - Android: 0 - GameCoreScarlett: 1 - GameCoreXboxOne: 1 - Lumin: 0 - Nintendo Switch: 1 - PS4: 1 - PS5: 1 - Server: 0 - Stadia: 0 - Standalone: 1 - WebGL: 0 - Windows Store Apps: 0 - XboxOne: 0 - iPhone: 0 - tvOS: 0 + m_PerPlatformDefaultQuality: {} diff --git a/ProjectSettings/UnityConnectSettings.asset b/ProjectSettings/UnityConnectSettings.asset index 029ad8b..7a17e8f 100644 --- a/ProjectSettings/UnityConnectSettings.asset +++ b/ProjectSettings/UnityConnectSettings.asset @@ -4,7 +4,7 @@ UnityConnectSettings: m_ObjectHideFlags: 0 serializedVersion: 1 - m_Enabled: 0 + m_Enabled: 1 m_TestMode: 0 m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events m_EventUrl: https://cdp.cloud.unity3d.com/v1/events