전장의 안개 로직 개선

미탐험지역은 검게 덮음. 탐험된 지역은 어두운 색, 보고 있는 블록은 밝은색
This commit is contained in:
2026-01-18 23:37:43 +09:00
parent 733ea30631
commit b4ac8f600f
11 changed files with 302 additions and 100 deletions

View File

@@ -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
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 7d8b19748651ad54a9350f716d0f7046
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3deb4a29e039554439a14651844edec2
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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(); }
}
}

View File

@@ -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), 멀어지면 어둡게 만든 색상을 적용합니다.

View File

@@ -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

View File

@@ -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: {}

View File

@@ -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