Working materials with two modes - binary and continuous

This commit is contained in:
Michal Pikulski
2025-12-10 12:12:30 +01:00
parent b3ba4c35f3
commit 5de62563cf
16 changed files with 791 additions and 760 deletions

View File

@@ -0,0 +1,59 @@
%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: MazeObjectProgressiveTemplate
m_Shader: {fileID: 4800000, guid: 732fa975ac924d89bb0078279d2cdb0b, 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: []
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}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MaskTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _NormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OutlineTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _RevealMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _EnableExternalAlpha: 0
- _IsInVision: 0
- _ZWrite: 0
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _RendererColor: {r: 1, g: 1, b: 1, a: 1}
m_BuildTextureStacks: []
m_AllowLocking: 1

View File

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

View File

@@ -504,7 +504,7 @@ PrefabInstance:
m_Modifications:
- target: {fileID: 6259373434446242904, guid: 07f826f001311e04984c3efc9ee2b897, type: 3}
propertyPath: m_Name
value: RevealableObject (2)
value: RevealableObject_Full_2
objectReference: {fileID: 0}
- target: {fileID: 6487644332527623320, guid: 07f826f001311e04984c3efc9ee2b897, type: 3}
propertyPath: m_SortingOrder
@@ -565,7 +565,7 @@ PrefabInstance:
m_Modifications:
- target: {fileID: 6259373434446242904, guid: 07f826f001311e04984c3efc9ee2b897, type: 3}
propertyPath: m_Name
value: RevealableObject (1)
value: RevealableObject_Full
objectReference: {fileID: 0}
- target: {fileID: 6487644332527623320, guid: 07f826f001311e04984c3efc9ee2b897, type: 3}
propertyPath: m_SortingOrder
@@ -1035,14 +1035,22 @@ PrefabInstance:
serializedVersion: 3
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: 397845239581813408, guid: 07f826f001311e04984c3efc9ee2b897, type: 3}
propertyPath: revealMode
value: 1
objectReference: {fileID: 0}
- target: {fileID: 6259373434446242904, guid: 07f826f001311e04984c3efc9ee2b897, type: 3}
propertyPath: m_Name
value: RevealableObject
value: RevealableObject_Partial
objectReference: {fileID: 0}
- target: {fileID: 6487644332527623320, guid: 07f826f001311e04984c3efc9ee2b897, type: 3}
propertyPath: m_SortingOrder
value: 5
objectReference: {fileID: 0}
- target: {fileID: 6487644332527623320, guid: 07f826f001311e04984c3efc9ee2b897, type: 3}
propertyPath: 'm_Materials.Array.data[0]'
value:
objectReference: {fileID: 2100000, guid: 6e053220514a0c64883d9484863533fe, type: 2}
- target: {fileID: 7983424933738472089, guid: 07f826f001311e04984c3efc9ee2b897, type: 3}
propertyPath: m_LocalPosition.x
value: 0

View File

@@ -452,7 +452,6 @@ namespace UI.CardSystem
if (cardData == null) return null;
var allSlots = FindObjectsByType<AlbumCardSlot>(FindObjectsSortMode.None);
foreach (var slot in allSlots)
{
if (slot.TargetCardDefinition != null &&

View File

@@ -72,6 +72,9 @@ namespace AppleHills.Core.Settings
[SerializeField] private float followUpdateInterval = 0.1f;
[SerializeField] private float followerSpeedMultiplier = 1.2f;
[SerializeField] private float heldIconDisplayHeight = 2.0f;
[Header("Trash Maze Vision")]
[SerializeField] private float trashMazeVisionRadius = 8f;
public float FollowDistance => followDistance;
public float ManualMoveSmooth => manualMoveSmooth;
@@ -81,6 +84,7 @@ namespace AppleHills.Core.Settings
public float FollowUpdateInterval => followUpdateInterval;
public float FollowerSpeedMultiplier => followerSpeedMultiplier;
public float HeldIconDisplayHeight => heldIconDisplayHeight;
public float TrashMazeVisionRadius => trashMazeVisionRadius;
public void Validate()
{
@@ -92,6 +96,7 @@ namespace AppleHills.Core.Settings
followUpdateInterval = Mathf.Max(0.01f, followUpdateInterval);
followerSpeedMultiplier = Mathf.Max(0.1f, followerSpeedMultiplier);
heldIconDisplayHeight = Mathf.Max(0f, heldIconDisplayHeight);
trashMazeVisionRadius = Mathf.Max(1f, trashMazeVisionRadius);
}
}
}

View File

@@ -49,6 +49,7 @@ namespace AppleHills.Core.Settings
float FollowUpdateInterval { get; }
float FollowerSpeedMultiplier { get; }
float HeldIconDisplayHeight { get; }
float TrashMazeVisionRadius { get; }
}
/// <summary>

View File

@@ -14,16 +14,16 @@ namespace Minigames.TrashMaze.Core
{
public static PulverController Instance { get; private set; }
[Header("Vision")]
[SerializeField] private float visionRadius = 3f;
// Cached shader property IDs for performance
private static readonly int PlayerWorldPosID = Shader.PropertyToID("_PlayerWorldPos");
private static readonly int VisionRadiusID = Shader.PropertyToID("_VisionRadius");
// Vision radius loaded from settings
private float _visionRadius;
// Public accessors for other systems
public static Vector2 PlayerPosition => Instance != null ? Instance.transform.position : Vector2.zero;
public static float VisionRadius => Instance != null ? Instance.visionRadius : 3f;
public static float VisionRadius => Instance != null ? Instance._visionRadius : 8f;
internal override void OnManagedAwake()
{
@@ -46,6 +46,10 @@ namespace Minigames.TrashMaze.Core
{
var configs = GameManager.GetSettingsObject<IPlayerMovementConfigs>();
_movementSettings = configs.TrashMazeMovement;
// Load vision radius from follower settings
_visionRadius = configs.FollowerMovement.TrashMazeVisionRadius;
Logging.Debug($"[PulverController] Loaded vision radius from settings: {_visionRadius}");
}
protected override void Update()
@@ -62,7 +66,7 @@ namespace Minigames.TrashMaze.Core
private void UpdateShaderGlobals()
{
Shader.SetGlobalVector(PlayerWorldPosID, transform.position);
Shader.SetGlobalFloat(VisionRadiusID, visionRadius);
Shader.SetGlobalFloat(VisionRadiusID, _visionRadius);
}
internal override void OnManagedDestroy()
@@ -80,7 +84,7 @@ namespace Minigames.TrashMaze.Core
/// </summary>
public void SetVisionRadius(float radius)
{
visionRadius = Mathf.Max(0.1f, radius);
_visionRadius = Mathf.Max(0.1f, radius);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3a935f5e791c46df8920c2c33f1c24c0
timeCreated: 1765361215

View File

@@ -0,0 +1,99 @@
using UnityEngine;
using UnityEditor;
using Minigames.TrashMaze.Objects;
namespace Minigames.TrashMaze.Editor
{
[CustomEditor(typeof(RevealableObject))]
public class RevealableObjectEditor : UnityEditor.Editor
{
private RenderTexture _cachedStampTexture;
private Texture2D _previewTexture;
public override void OnInspectorGUI()
{
DrawDefaultInspector();
// Only show debug info in play mode
if (!Application.isPlaying)
{
return;
}
EditorGUILayout.Space(10);
EditorGUILayout.LabelField("Progressive Reveal Debug (Play Mode Only)", EditorStyles.boldLabel);
EditorGUILayout.HelpBox("Shows the current stamp texture for Progressive reveal mode.", MessageType.Info);
RevealableObject revealableObject = (RevealableObject)target;
// Use reflection to get private _revealStampTexture field
var field = typeof(RevealableObject).GetField("_revealStampTexture",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (field != null)
{
RenderTexture stampTexture = field.GetValue(revealableObject) as RenderTexture;
if (stampTexture != null)
{
// Display stamp texture info
EditorGUILayout.LabelField("Stamp Texture:", $"{stampTexture.width}x{stampTexture.height}");
// Show preview of stamp texture
GUILayout.Label("Reveal Mask Preview (White = Revealed):");
// Create preview texture if needed
if (_cachedStampTexture != stampTexture || _previewTexture == null)
{
_cachedStampTexture = stampTexture;
if (_previewTexture != null)
{
DestroyImmediate(_previewTexture);
}
_previewTexture = new Texture2D(stampTexture.width, stampTexture.height, TextureFormat.R8, false);
_previewTexture.filterMode = FilterMode.Point;
}
// Copy RenderTexture to Texture2D for preview
RenderTexture.active = stampTexture;
_previewTexture.ReadPixels(new Rect(0, 0, stampTexture.width, stampTexture.height), 0, 0);
_previewTexture.Apply();
RenderTexture.active = null;
// Display preview with fixed size
float previewSize = 256f;
float aspectRatio = (float)stampTexture.height / stampTexture.width;
Rect previewRect = GUILayoutUtility.GetRect(previewSize, previewSize * aspectRatio);
EditorGUI.DrawPreviewTexture(previewRect, _previewTexture, null, ScaleMode.ScaleToFit);
// Auto-refresh in play mode
if (Application.isPlaying)
{
Repaint();
}
}
else
{
EditorGUILayout.HelpBox("No stamp texture found. Make sure object is in Progressive reveal mode.", MessageType.Warning);
}
}
else
{
EditorGUILayout.HelpBox("Could not access stamp texture via reflection.", MessageType.Error);
}
}
private void OnDisable()
{
// Clean up preview texture
if (_previewTexture != null)
{
DestroyImmediate(_previewTexture);
_previewTexture = null;
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1d081993ee424269bf8eae99db36a54c
timeCreated: 1765361215

View File

@@ -1,9 +1,20 @@
using Core;
using Minigames.TrashMaze.Core;
using UnityEngine;
using System.Collections;
using AppleHills.Core.Settings;
namespace Minigames.TrashMaze.Objects
{
/// <summary>
/// Reveal mode for object visibility
/// </summary>
public enum RevealMode
{
Binary, // Simple on/off reveal (current system)
Progressive // Pixel-by-pixel progressive reveal with stamp texture
}
/// <summary>
/// Component for objects that need reveal memory (obstacles, booster packs, treasures).
/// Tracks if object has been revealed and updates material properties accordingly.
@@ -11,10 +22,17 @@ namespace Minigames.TrashMaze.Objects
[RequireComponent(typeof(SpriteRenderer))]
public class RevealableObject : MonoBehaviour
{
[Header("Reveal Settings")]
[SerializeField] private RevealMode revealMode = RevealMode.Binary;
[Header("Textures")]
[SerializeField] private Sprite normalSprite;
[SerializeField] private Sprite outlineSprite;
[Header("Progressive Reveal Settings")]
[SerializeField, Range(0.5f, 3f)] private float stampWorldRadius = 1.5f;
[Tooltip("Size of each stamp in world units. Smaller = more gradual reveal. Should be smaller than vision radius.")]
[Header("Object Type")]
[SerializeField] private bool isBoosterPack = false;
[SerializeField] private bool isExit = false;
@@ -27,64 +45,403 @@ namespace Minigames.TrashMaze.Objects
// Material property IDs (cached for performance)
private static readonly int IsRevealedID = Shader.PropertyToID("_IsRevealed");
private static readonly int IsInVisionID = Shader.PropertyToID("_IsInVision");
private static readonly int RevealMaskID = Shader.PropertyToID("_RevealMask");
// Progressive reveal system
private RenderTexture _revealStampTexture;
private Coroutine _stampCoroutine;
private Bounds _objectBounds;
private float _activationDistance;
// Binary reveal system
private Coroutine _binaryRevealCoroutine;
private void Awake()
{
_spriteRenderer = GetComponent<SpriteRenderer>();
// Create instance material so each object has its own properties
if (_spriteRenderer.material != null)
if (_spriteRenderer.material == null)
{
_instanceMaterial = new Material(_spriteRenderer.material);
_spriteRenderer.material = _instanceMaterial;
Logging.Error($"[RevealableObject] No material assigned to {gameObject.name}");
return;
}
// Create instance material
_instanceMaterial = new Material(_spriteRenderer.material);
_spriteRenderer.material = _instanceMaterial;
// Call mode-specific initialization - COMPLETELY SEPARATE
if (revealMode == RevealMode.Binary)
{
InitializeBinaryMode();
}
else if (revealMode == RevealMode.Progressive)
{
InitializeProgressiveMode();
}
}
// ========================================
// BINARY MODE INITIALIZATION
// ========================================
private void InitializeBinaryMode()
{
// Validate Binary shader
string shaderName = _instanceMaterial.shader.name;
if (!shaderName.Contains("ObjectVisibility") || shaderName.Contains("Progressive"))
{
Logging.Error($"[RevealableObject] {gameObject.name} Binary mode needs shader 'TrashMaze/ObjectVisibility', currently: {shaderName}");
}
// Set initial Binary mode properties
_instanceMaterial.SetFloat(IsRevealedID, 0f);
_instanceMaterial.SetFloat(IsInVisionID, 0f);
// Set textures
if (normalSprite != null)
{
_instanceMaterial.SetTexture("_MainTex", normalSprite.texture);
}
if (outlineSprite != null)
{
_instanceMaterial.SetTexture("_OutlineTex", outlineSprite.texture);
}
Logging.Debug($"[RevealableObject] {gameObject.name} Binary mode initialized");
}
// ========================================
// PROGRESSIVE MODE INITIALIZATION
// ========================================
private void InitializeProgressiveMode()
{
// Validate Progressive shader
string shaderName = _instanceMaterial.shader.name;
if (!shaderName.Contains("ObjectVisibilityProgressive"))
{
Logging.Error($"[RevealableObject] {gameObject.name} Progressive mode needs shader 'TrashMaze/ObjectVisibilityProgressive', currently: {shaderName}");
}
// Initialize progressive reveal system
InitializeProgressiveReveal();
}
/// <summary>
/// Initialize progressive reveal system with dynamic texture sizing
/// </summary>
private void InitializeProgressiveReveal()
{
// Get object bounds for UV calculations
_objectBounds = _spriteRenderer.bounds;
// Load activation distance from settings (use vision radius from follower settings)
var configs = GameManager.GetSettingsObject<IPlayerMovementConfigs>();
_activationDistance = configs.FollowerMovement.TrashMazeVisionRadius;
Logging.Debug($"[RevealableObject] {gameObject.name} loaded activation distance from settings: {_activationDistance}");
// Dynamically determine texture size from sprite
int textureWidth = 128;
int textureHeight = 128;
if (normalSprite != null && normalSprite.texture != null)
{
// Use sprite's texture resolution (match 1:1 for pixel-perfect)
textureWidth = normalSprite.texture.width;
textureHeight = normalSprite.texture.height;
}
else
{
Logging.Error($"[RevealableObject] No material assigned to {gameObject.name}");
Logging.Warning($"[RevealableObject] {gameObject.name} in Progressive mode but normalSprite not assigned! Using default 128x128 texture. Assign Normal Sprite in inspector!");
}
// Create reveal stamp texture (R8 format = 8-bit grayscale, minimal memory)
_revealStampTexture = new RenderTexture(textureWidth, textureHeight, 0, RenderTextureFormat.R8);
_revealStampTexture.filterMode = FilterMode.Point; // Sharp edges for binary reveals
_revealStampTexture.wrapMode = TextureWrapMode.Clamp;
_revealStampTexture.Create(); // Explicitly create the texture
// Clear to black (nothing revealed initially)
RenderTexture previousActive = RenderTexture.active;
RenderTexture.active = _revealStampTexture;
GL.Clear(false, true, Color.black);
RenderTexture.active = previousActive;
Logging.Debug($"[RevealableObject] {gameObject.name} cleared reveal texture to black - should be invisible initially");
// Set Progressive shader properties
_instanceMaterial.SetTexture(RevealMaskID, _revealStampTexture);
// Progressive shader uses global _PlayerWorldPos and _VisionRadius (set by PulverController)
// No need to set _IsInVision - shader does per-pixel distance checks
// Set textures
if (normalSprite != null)
{
_instanceMaterial.SetTexture("_MainTex", normalSprite.texture);
}
if (outlineSprite != null)
{
_instanceMaterial.SetTexture("_OutlineTex", outlineSprite.texture);
}
Logging.Debug($"[RevealableObject] {gameObject.name} Progressive mode initialized: {textureWidth}x{textureHeight} reveal texture");
}
private void Start()
{
// Initialize material properties
if (_instanceMaterial != null)
if (PulverController.Instance == null)
{
_instanceMaterial.SetFloat(IsRevealedID, 0f);
_instanceMaterial.SetFloat(IsInVisionID, 0f);
// Set textures if provided
if (normalSprite != null)
{
_instanceMaterial.SetTexture("_MainTex", normalSprite.texture);
}
if (outlineSprite != null)
{
_instanceMaterial.SetTexture("_OutlineTex", outlineSprite.texture);
}
Logging.Error($"[RevealableObject] {gameObject.name} cannot start - PulverController not found!");
return;
}
// Start mode-specific runtime logic - COMPLETELY SEPARATE
if (revealMode == RevealMode.Binary)
{
StartBinaryModeTracking();
}
else if (revealMode == RevealMode.Progressive)
{
StartProgressiveModeTracking();
}
}
private void Update()
// ========================================
// BINARY MODE: Start tracking
// ========================================
private void StartBinaryModeTracking()
{
if (_isCollected || _instanceMaterial == null) return;
_binaryRevealCoroutine = StartCoroutine(BinaryRevealTrackingCoroutine());
Logging.Debug($"[RevealableObject] {gameObject.name} Binary mode tracking started");
}
// ========================================
// PROGRESSIVE MODE: Start tracking
// ========================================
private void StartProgressiveModeTracking()
{
// Subscribe to movement events for stamping
PulverController.Instance.OnMovementStarted += OnPlayerMovementStarted;
PulverController.Instance.OnMovementStopped += OnPlayerMovementStopped;
// Calculate distance to player
float distance = Vector2.Distance(transform.position, PulverController.PlayerPosition);
bool isInRadius = distance < PulverController.VisionRadius;
// NO vision tracking coroutine - Progressive shader does per-pixel distance checks using global _PlayerWorldPos
// Update real-time vision flag
_instanceMaterial.SetFloat(IsInVisionID, isInRadius ? 1f : 0f);
// Set revealed flag (once true, stays true)
if (isInRadius && !_hasBeenRevealed)
Logging.Debug($"[RevealableObject] {gameObject.name} Progressive mode tracking started");
}
// ========================================
// BINARY MODE: Vision-based reveal coroutine
// ========================================
/// <summary>
/// Binary mode coroutine - tracks player distance and updates vision/reveal flags
/// Runs continuously, checks every 0.1s for performance
/// </summary>
private IEnumerator BinaryRevealTrackingCoroutine()
{
while (!_isCollected && _instanceMaterial != null)
{
_hasBeenRevealed = true;
_instanceMaterial.SetFloat(IsRevealedID, 1f);
// Calculate distance to player
float distance = Vector2.Distance(transform.position, PulverController.PlayerPosition);
bool isInRadius = distance < PulverController.VisionRadius;
Logging.Debug($"[RevealableObject] {gameObject.name} revealed!");
// Set real-time vision flag (controls shader color vs outline)
_instanceMaterial.SetFloat(IsInVisionID, isInRadius ? 1f : 0f);
// Set reveal flag (once revealed, stays revealed)
if (isInRadius && !_hasBeenRevealed)
{
_hasBeenRevealed = true;
_instanceMaterial.SetFloat(IsRevealedID, 1f);
Logging.Debug($"[RevealableObject] {gameObject.name} revealed!");
}
// Wait before next check (reduces CPU load)
yield return new WaitForSeconds(0.1f);
}
}
// ========================================
// PROGRESSIVE MODE: Event-based stamp reveal
// ========================================
/// <summary>
/// Called when player starts moving - begin stamping if near object
/// </summary>
private void OnPlayerMovementStarted()
{
if (_isCollected || revealMode != RevealMode.Progressive) return;
// Check if player's vision circle could overlap with object bounds
// Use closest point on bounds to check distance
Vector2 playerPos = PulverController.PlayerPosition;
Vector2 closestPoint = _objectBounds.ClosestPoint(playerPos);
float distanceToBounds = Vector2.Distance(playerPos, closestPoint);
// Start stamping if vision radius could reach the object
// Add padding to account for vision radius overlap
float activationThreshold = _activationDistance + _objectBounds.extents.magnitude;
if (distanceToBounds < activationThreshold && _stampCoroutine == null)
{
_stampCoroutine = StartCoroutine(StampRevealCoroutine());
Logging.Debug($"[RevealableObject] {gameObject.name} started stamping coroutine (distanceToBounds: {distanceToBounds:F2}, threshold: {activationThreshold:F2})");
}
}
/// <summary>
/// Called when player stops moving - stop stamping
/// </summary>
private void OnPlayerMovementStopped()
{
if (_stampCoroutine != null)
{
StopCoroutine(_stampCoroutine);
_stampCoroutine = null;
}
}
/// <summary>
/// Coroutine that stamps reveal texture while player is moving and near object
/// </summary>
private IEnumerator StampRevealCoroutine()
{
while (!_isCollected)
{
// Check if player's vision circle overlaps with object bounds
Vector2 playerPos = PulverController.PlayerPosition;
Vector2 closestPoint = _objectBounds.ClosestPoint(playerPos);
float distanceToBounds = Vector2.Distance(playerPos, closestPoint);
// Calculate activation threshold with padding
float activationThreshold = _activationDistance + _objectBounds.extents.magnitude;
// If player moved too far away, stop stamping
if (distanceToBounds >= activationThreshold)
{
Logging.Debug($"[RevealableObject] {gameObject.name} stopping stamping coroutine (too far)");
_stampCoroutine = null;
yield break;
}
// Stamp if player's vision radius reaches any part of the object
if (distanceToBounds < PulverController.VisionRadius)
{
StampPlayerPosition();
}
// Wait before next stamp (reduces GPU writes)
yield return new WaitForSeconds(0.1f);
}
}
/// <summary>
/// Stamp the player's current position onto the reveal texture
/// Direct CPU-based stamping - calculates circle-rectangle intersection in world space
/// </summary>
private void StampPlayerPosition()
{
if (_revealStampTexture == null)
{
Logging.Warning($"[RevealableObject] {gameObject.name} cannot stamp: texture is null");
return;
}
// Get player position and vision radius in world space
Vector2 playerWorldPos = PulverController.PlayerPosition;
float visionRadius = PulverController.VisionRadius;
// Get object bounds in world space
Vector2 boundsMin = _objectBounds.min;
Vector2 boundsSize = _objectBounds.size;
// Get texture dimensions
int texWidth = _revealStampTexture.width;
int texHeight = _revealStampTexture.height;
// Calculate the bounding box of the circle in world space
Vector2 circleMin = playerWorldPos - Vector2.one * visionRadius;
Vector2 circleMax = playerWorldPos + Vector2.one * visionRadius;
// Calculate intersection of circle bounding box with object bounds
Vector2 intersectMin = new Vector2(
Mathf.Max(circleMin.x, boundsMin.x),
Mathf.Max(circleMin.y, boundsMin.y)
);
Vector2 intersectMax = new Vector2(
Mathf.Min(circleMax.x, boundsMin.x + boundsSize.x),
Mathf.Min(circleMax.y, boundsMin.y + boundsSize.y)
);
// Check if there's any intersection
if (intersectMin.x >= intersectMax.x || intersectMin.y >= intersectMax.y)
{
return; // No intersection, nothing to stamp
}
// Convert world space intersection to texture pixel coordinates
int pixelMinX = Mathf.FloorToInt((intersectMin.x - boundsMin.x) / boundsSize.x * texWidth);
int pixelMaxX = Mathf.CeilToInt((intersectMax.x - boundsMin.x) / boundsSize.x * texWidth);
int pixelMinY = Mathf.FloorToInt((intersectMin.y - boundsMin.y) / boundsSize.y * texHeight);
int pixelMaxY = Mathf.CeilToInt((intersectMax.y - boundsMin.y) / boundsSize.y * texHeight);
// Clamp to texture bounds
pixelMinX = Mathf.Max(0, pixelMinX);
pixelMaxX = Mathf.Min(texWidth, pixelMaxX);
pixelMinY = Mathf.Max(0, pixelMinY);
pixelMaxY = Mathf.Min(texHeight, pixelMaxY);
// Read current texture data
RenderTexture.active = _revealStampTexture;
Texture2D tempTex = new Texture2D(texWidth, texHeight, TextureFormat.R8, false);
tempTex.ReadPixels(new Rect(0, 0, texWidth, texHeight), 0, 0);
tempTex.Apply();
// Stamp pixels within the circle
bool anyPixelStamped = false;
float radiusSquared = visionRadius * visionRadius;
for (int py = pixelMinY; py < pixelMaxY; py++)
{
for (int px = pixelMinX; px < pixelMaxX; px++)
{
// Convert pixel coordinates back to world space
float worldX = boundsMin.x + (px / (float)texWidth) * boundsSize.x;
float worldY = boundsMin.y + (py / (float)texHeight) * boundsSize.y;
// Check if this pixel is within the circle
float dx = worldX - playerWorldPos.x;
float dy = worldY - playerWorldPos.y;
float distSquared = dx * dx + dy * dy;
if (distSquared <= radiusSquared)
{
// Stamp this pixel (set to white)
tempTex.SetPixel(px, py, Color.white);
anyPixelStamped = true;
}
}
}
if (anyPixelStamped)
{
// Upload modified texture back to GPU
tempTex.Apply();
Graphics.CopyTexture(tempTex, _revealStampTexture);
Logging.Debug($"[RevealableObject] {gameObject.name} stamped pixels at world pos ({playerWorldPos.x:F2}, {playerWorldPos.y:F2}), radius {visionRadius:F2}");
}
RenderTexture.active = null;
Destroy(tempTex);
}
private void OnTriggerEnter2D(Collider2D other)
{
// Check if player is interacting
@@ -135,6 +492,33 @@ namespace Minigames.TrashMaze.Objects
private void OnDestroy()
{
// Stop Binary mode coroutine
if (_binaryRevealCoroutine != null)
{
StopCoroutine(_binaryRevealCoroutine);
}
// Unsubscribe from Progressive mode movement events
if (revealMode == RevealMode.Progressive && PulverController.Instance != null)
{
PulverController.Instance.OnMovementStarted -= OnPlayerMovementStarted;
PulverController.Instance.OnMovementStopped -= OnPlayerMovementStopped;
}
// Stop Progressive mode stamping coroutine
if (_stampCoroutine != null)
{
StopCoroutine(_stampCoroutine);
}
// Clean up progressive reveal resources
if (_revealStampTexture != null)
{
_revealStampTexture.Release();
Destroy(_revealStampTexture);
}
// Clean up instance material
if (_instanceMaterial != null)
{

View File

@@ -0,0 +1,106 @@
Shader "TrashMaze/ObjectVisibilityProgressive"
{
Properties
{
_MainTex ("Normal Texture (Color)", 2D) = "white" {}
_OutlineTex ("Outline Texture (White)", 2D) = "white" {}
_RevealMask ("Reveal Mask", 2D) = "black" {}
[PerRendererData] _IsInVision ("Is In Vision", Float) = 0
}
SubShader
{
Tags
{
"Queue"="Transparent"
"RenderType"="Transparent"
"IgnoreProjector"="True"
}
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
Cull Off
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float2 uv : TEXCOORD0;
float4 positionCS : SV_POSITION;
float3 positionWS : TEXCOORD1;
};
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
TEXTURE2D(_OutlineTex);
SAMPLER(sampler_OutlineTex);
TEXTURE2D(_RevealMask);
SAMPLER(sampler_RevealMask);
float4 _MainTex_ST;
// Global shader properties (set by PulverController)
float2 _PlayerWorldPos;
float _VisionRadius;
Varyings vert(Attributes input)
{
Varyings output;
VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
output.positionCS = vertexInput.positionCS;
output.positionWS = vertexInput.positionWS;
output.uv = TRANSFORM_TEX(input.uv, _MainTex);
return output;
}
half4 frag(Varyings input) : SV_Target
{
// Sample reveal mask (0 = not revealed, 1 = revealed)
float revealAmount = SAMPLE_TEXTURE2D(_RevealMask, sampler_RevealMask, input.uv).r;
// Binary decision: is this pixel revealed?
bool isRevealed = revealAmount > 0.5;
// If pixel was never revealed, hide it completely
if (!isRevealed)
{
return half4(0, 0, 0, 0);
}
// Calculate per-pixel distance to player in world space
float2 pixelWorldPos = input.positionWS.xy;
float distanceToPlayer = distance(pixelWorldPos, _PlayerWorldPos);
// Three-state logic per pixel:
// 1. Never revealed -> hide (handled above)
// 2. Revealed + currently in player vision radius -> show color
// 3. Revealed + outside player vision radius -> show outline
if (distanceToPlayer < _VisionRadius)
{
// Pixel is revealed AND currently in vision - show color
return SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
}
else
{
// Pixel is revealed but NOT currently in vision - show outline
return SAMPLE_TEXTURE2D(_OutlineTex, sampler_OutlineTex, input.uv);
}
}
ENDHLSL
}
}
FallBack "Transparent/Diffuse"
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 732fa975ac924d89bb0078279d2cdb0b
timeCreated: 1765358086

View File

@@ -0,0 +1,64 @@
Shader "Hidden/TrashMaze/RevealStamp"
{
Properties
{
_StampPos ("Stamp Position", Vector) = (0.5, 0.5, 0, 0)
_StampRadius ("Stamp Radius", Float) = 0.2
}
SubShader
{
Tags { "Queue"="Overlay" "RenderType"="Opaque" }
// Additive blend to accumulate stamps
Blend One One
ZTest Always
ZWrite Off
Cull Off
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
float4 _StampPos;
float _StampRadius;
Varyings vert(Attributes input)
{
Varyings output;
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}
half4 frag(Varyings input) : SV_Target
{
// Calculate distance from stamp center
float dist = distance(input.uv, _StampPos.xy);
// Binary circle: 1.0 inside radius, 0.0 outside
float alpha = dist < _StampRadius ? 1.0 : 0.0;
// Return white with calculated alpha (additive blend accumulates)
return half4(alpha, alpha, alpha, alpha);
}
ENDHLSL
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 33afb80a55e64e53b8552498ad61acfa
timeCreated: 1765358067