+Add : Blocking test & Cleanup
This commit is contained in:
@@ -10,7 +10,7 @@ public class DiapasonPoseTweaker : MonoBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
[SerializeField] private OctoIkArmsImproved _armsSystem;
|
||||
[SerializeField] private int _armIndex = 1;
|
||||
// [SerializeField] private int _armIndex = 1;
|
||||
|
||||
[Header("Quick Presets")]
|
||||
[SerializeField] private bool _applyPreset1 = false;
|
||||
|
||||
@@ -14,10 +14,8 @@ namespace FBXImporter
|
||||
/// 4. Force reimport
|
||||
/// 5. Connect PBR textures → create .mat files
|
||||
/// 6. Extract animation clips → Animations/
|
||||
/// 7. Generate prefab
|
||||
/// 7. Generate prefab(s) — one per mesh if multi-mesh FBX
|
||||
/// 8. Create AnimatorController + assign to prefab
|
||||
/// 9. Delete Sources/Materials/ (Unity auto-generates it, we don't need it)
|
||||
/// 10. Delete original source folder (cleanup)
|
||||
/// </summary>
|
||||
public static void Process(string fbxAssetPath,
|
||||
ModelImporterAnimationType animationType = ModelImporterAnimationType.Generic,
|
||||
@@ -33,8 +31,8 @@ namespace FBXImporter
|
||||
|
||||
Debug.Log($"[FBXImporter] ── Processing {Path.GetFileName(fbxAssetPath)} ──");
|
||||
|
||||
var source = FBXSourceDetector.Detect(fbxAssetPath);
|
||||
var folders = FolderSetup.Resolve(fbxAssetPath);
|
||||
var source = FBXSourceDetector.Detect(fbxAssetPath);
|
||||
var folders = FolderSetup.Resolve(fbxAssetPath);
|
||||
bool withAnims = animationType != ModelImporterAnimationType.None;
|
||||
|
||||
FolderSetup.Create(
|
||||
@@ -67,10 +65,10 @@ namespace FBXImporter
|
||||
if (withAnims && !string.IsNullOrEmpty(folders.Animations))
|
||||
AnimationExtractor.ExtractClips(fbxAssetPath, folders.Animations);
|
||||
|
||||
// Generate prefab
|
||||
// Generate prefab(s)
|
||||
string assetName = Path.GetFileNameWithoutExtension(fbxAssetPath);
|
||||
string prefabPath = $"{folders.Prefabs}/{assetName}.prefab";
|
||||
PrefabBuilder.CreatePrefab(fbxAsset, folders.Prefabs, assetName);
|
||||
PrefabBuilder.CreatePrefabs(fbxAsset, folders, assetName);
|
||||
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
@@ -79,28 +77,9 @@ namespace FBXImporter
|
||||
if (withAnims && !string.IsNullOrEmpty(folders.Animations))
|
||||
AnimatorControllerBuilder.CreateAndAssign(folders.Animations, prefabPath, assetName);
|
||||
|
||||
// Delete Sources/Materials/ — Unity creates it automatically but we use
|
||||
// the root Materials/ folder instead, so this one is redundant
|
||||
string sourceMaterialsPath = $"{folders.Sources}/Materials";
|
||||
if (AssetDatabase.IsValidFolder(sourceMaterialsPath))
|
||||
{
|
||||
AssetDatabase.DeleteAsset(sourceMaterialsPath);
|
||||
Debug.Log($"[FBXImporter] Deleted Sources/Materials/ (using root Materials/ instead)");
|
||||
}
|
||||
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
|
||||
// Cleanup original source folder if provided and different from our Sources/
|
||||
if (!string.IsNullOrEmpty(originalSourceFolder) &&
|
||||
originalSourceFolder != folders.Sources &&
|
||||
originalSourceFolder != folders.Root &&
|
||||
AssetDatabase.IsValidFolder(originalSourceFolder))
|
||||
{
|
||||
AssetDatabase.DeleteAsset(originalSourceFolder);
|
||||
Debug.Log($"[FBXImporter] Deleted original folder: {originalSourceFolder}");
|
||||
}
|
||||
|
||||
Debug.Log($"[FBXImporter] ── Done: {assetName} ──");
|
||||
}
|
||||
|
||||
@@ -121,7 +100,7 @@ namespace FBXImporter
|
||||
importer.materialImportMode = ModelImporterMaterialImportMode.ImportViaMaterialDescription;
|
||||
importer.materialLocation = ModelImporterMaterialLocation.External;
|
||||
importer.materialName = ModelImporterMaterialName.BasedOnMaterialName;
|
||||
importer.materialSearch = ModelImporterMaterialSearch.RecursiveUp;
|
||||
importer.materialSearch = ModelImporterMaterialSearch.Local;
|
||||
|
||||
importer.animationType = animType;
|
||||
if (!string.IsNullOrWhiteSpace(rootNode))
|
||||
|
||||
@@ -1,20 +1,29 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace FBXImporter
|
||||
{
|
||||
public static class PrefabBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a single prefab from the imported FBX root GameObject.
|
||||
/// Adds a LODGroup automatically if _LOD0, _LOD1... meshes are found.
|
||||
/// </summary>
|
||||
public static void CreatePrefabs(GameObject fbxRoot, AssetFolders folders, string assetName)
|
||||
{
|
||||
CreateSinglePrefab(fbxRoot, folders.Prefabs, assetName);
|
||||
}
|
||||
|
||||
public static GameObject CreatePrefab(GameObject fbxRoot, string prefabDir, string assetName)
|
||||
{
|
||||
if (fbxRoot == null) return null;
|
||||
return CreateSinglePrefab(fbxRoot, prefabDir, assetName);
|
||||
}
|
||||
|
||||
if (!AssetDatabase.IsValidFolder(prefabDir))
|
||||
{
|
||||
string parent = System.IO.Path.GetDirectoryName(prefabDir).Replace("\\", "/");
|
||||
AssetDatabase.CreateFolder(parent, System.IO.Path.GetFileName(prefabDir));
|
||||
}
|
||||
static GameObject CreateSinglePrefab(GameObject fbxRoot, string prefabDir, string assetName)
|
||||
{
|
||||
EnsureFolder(prefabDir);
|
||||
|
||||
var instance = Object.Instantiate(fbxRoot);
|
||||
instance.name = assetName;
|
||||
@@ -68,5 +77,14 @@ namespace FBXImporter
|
||||
if (idx < 0) return -1;
|
||||
return int.TryParse(name.Substring(idx + 4), out int level) ? level : -1;
|
||||
}
|
||||
|
||||
static void EnsureFolder(string path)
|
||||
{
|
||||
if (AssetDatabase.IsValidFolder(path)) return;
|
||||
string parent = Path.GetDirectoryName(path).Replace("\\", "/");
|
||||
string child = Path.GetFileName(path);
|
||||
if (!AssetDatabase.IsValidFolder(parent)) EnsureFolder(parent);
|
||||
AssetDatabase.CreateFolder(parent, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,34 @@ namespace FBXImporter
|
||||
/// Persistent registry of FBX files seen by the importer.
|
||||
/// Stores GUID + filename so the file is human-readable.
|
||||
/// Format per line: "guid TAB filename.fbx"
|
||||
/// Registry file is stored next to this script, wherever the plugin is installed.
|
||||
/// </summary>
|
||||
public static class ProcessedFBXRegistry
|
||||
{
|
||||
static readonly string RegistryPath = "Assets/Scripts/Editor/FBXImporter/processed_fbx.txt";
|
||||
// Resolved dynamically so the registry follows the plugin wherever it's installed
|
||||
static string _registryPath;
|
||||
static string RegistryPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_registryPath != null) return _registryPath;
|
||||
|
||||
var guids = AssetDatabase.FindAssets("ProcessedFBXRegistry t:Script");
|
||||
foreach (var guid in guids)
|
||||
{
|
||||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||||
if (path.Contains("FBXImporter"))
|
||||
{
|
||||
_registryPath = Path.GetDirectoryName(path).Replace("\\", "/") + "/processed_fbx.txt";
|
||||
return _registryPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback — should never happen if plugin is properly installed
|
||||
_registryPath = "Assets/processed_fbx.txt";
|
||||
return _registryPath;
|
||||
}
|
||||
}
|
||||
|
||||
// guid → filename (for display)
|
||||
static Dictionary<string, string> _registry;
|
||||
@@ -28,7 +52,7 @@ namespace FBXImporter
|
||||
return !string.IsNullOrEmpty(guid) && Registry.ContainsKey(guid);
|
||||
}
|
||||
|
||||
public static void Register(IEnumerable<string> assetPaths)
|
||||
public static void Register(System.Collections.Generic.IEnumerable<string> assetPaths)
|
||||
{
|
||||
bool changed = false;
|
||||
foreach (var path in assetPaths)
|
||||
@@ -95,8 +119,7 @@ namespace FBXImporter
|
||||
var lines = Registry.Select(kvp => $"{kvp.Key}\t{kvp.Value}").ToArray();
|
||||
File.WriteAllLines(RegistryPath, lines);
|
||||
|
||||
// Nullify cache so the next access reloads from disk.
|
||||
// This prevents stale in-memory state after domain reloads.
|
||||
// Nullify cache so the next access reloads from disk
|
||||
_registry = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
using UnityEditor;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FBXImporter
|
||||
{
|
||||
public class FBXDropWatcher : AssetPostprocessor
|
||||
{
|
||||
static readonly List<string> _pendingNewFBX = new List<string>();
|
||||
static bool _dialogScheduled = false;
|
||||
|
||||
bool ShouldWatch()
|
||||
{
|
||||
return assetPath.StartsWith("Assets/")
|
||||
&& assetPath.EndsWith(".fbx", System.StringComparison.OrdinalIgnoreCase)
|
||||
&& !ProcessedFBXRegistry.IsRegistered(assetPath);
|
||||
}
|
||||
|
||||
void OnPreprocessModel()
|
||||
{
|
||||
if (!ShouldWatch()) return;
|
||||
|
||||
var importer = assetImporter as ModelImporter;
|
||||
importer.importCameras = false;
|
||||
importer.importLights = false;
|
||||
importer.importNormals = ModelImporterNormals.Import;
|
||||
importer.importTangents = ModelImporterTangents.CalculateMikk;
|
||||
importer.importBlendShapes = true;
|
||||
importer.importVisibility = true;
|
||||
importer.materialImportMode = ModelImporterMaterialImportMode.ImportViaMaterialDescription;
|
||||
importer.materialLocation = ModelImporterMaterialLocation.External;
|
||||
importer.materialName = ModelImporterMaterialName.BasedOnMaterialName;
|
||||
importer.materialSearch = ModelImporterMaterialSearch.RecursiveUp;
|
||||
importer.useFileScale = true;
|
||||
importer.globalScale = 1f;
|
||||
}
|
||||
|
||||
void OnPostprocessModel(UnityEngine.GameObject root)
|
||||
{
|
||||
if (!ShouldWatch()) return;
|
||||
|
||||
if (!_pendingNewFBX.Contains(assetPath))
|
||||
_pendingNewFBX.Add(assetPath);
|
||||
|
||||
if (!_dialogScheduled)
|
||||
{
|
||||
_dialogScheduled = true;
|
||||
EditorApplication.delayCall += ShowBatchDialog;
|
||||
}
|
||||
}
|
||||
|
||||
static void ShowBatchDialog()
|
||||
{
|
||||
_dialogScheduled = false;
|
||||
if (_pendingNewFBX.Count == 0) return;
|
||||
|
||||
var batch = new List<string>(_pendingNewFBX);
|
||||
_pendingNewFBX.Clear();
|
||||
|
||||
// Register all immediately — won't appear again regardless of user choice
|
||||
ProcessedFBXRegistry.Register(batch);
|
||||
|
||||
FBXBatchDialog.Show(batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b0ef00f525fdd4498665e9164696f58
|
||||
@@ -27,7 +27,6 @@ namespace FBXImporter
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
minSize = new Vector2(450, 650);
|
||||
GUILayout.Label("FBX Importer", EditorStyles.boldLabel);
|
||||
EditorGUILayout.HelpBox(
|
||||
"Select a .fbx in the Project panel.\n" +
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Unheimlich.Features.DetectiveBoard.UI
|
||||
[SerializeField] private float _maxFanHalfAngle = 40f;
|
||||
|
||||
[Header("Placement Settings")]
|
||||
[SerializeField] private float _placementLerpSpeed = 5f;
|
||||
// [SerializeField] private float _placementLerpSpeed = 5f;
|
||||
|
||||
private readonly List<ClueHandCard> _handCards = new List<ClueHandCard>();
|
||||
private bool _isPlacing; protected override void OnAwake() { }
|
||||
|
||||
@@ -14,6 +14,8 @@ namespace Unheimlich.Features.Dialogue.UI
|
||||
/// - Min distance: label is pushed away from the camera so it never clips into the player's face.
|
||||
/// - Optional organic shake: label drifts gently around its home position.
|
||||
/// - Optional ghost trail: a semi-transparent copy follows with a position delay.
|
||||
/// - Optional camera attraction: label is gently pulled toward screen center when the player looks near it.
|
||||
/// - Optional one-shot: label permanently disappears after being seen and moving away.
|
||||
/// </summary>
|
||||
public class FloatingLabel : MonoBehaviour
|
||||
{
|
||||
@@ -62,6 +64,25 @@ namespace Unheimlich.Features.Dialogue.UI
|
||||
[Tooltip("How far behind the main label the ghost is pushed (along the camera-to-label ray). Ensures the ghost is always occluded by the main word regardless of viewing angle.")]
|
||||
[SerializeField] private float _ghostDepthOffset = 0.02f;
|
||||
|
||||
[Header("Camera Attraction (Optional)")]
|
||||
[Tooltip("When enabled, the label is gently pulled toward the screen center when the player looks near it.")]
|
||||
[SerializeField] private bool _enableCameraAttraction = false;
|
||||
[Tooltip("Half-angle (degrees) within which attraction is active. " +
|
||||
"Below this angle the label is drawn toward the screen center. " +
|
||||
"Keep small (e.g. 8-15°) so it only triggers when the player is really looking at the word.")]
|
||||
[SerializeField] [Range(1f, 45f)] private float _attractionAngle = 10f;
|
||||
[Tooltip("Maximum world-space offset applied toward the screen center at full attraction strength.")]
|
||||
[SerializeField] private float _attractionMaxOffset = 0.3f;
|
||||
[Tooltip("How quickly the attraction offset interpolates toward its target (higher = snappier).")]
|
||||
[SerializeField] private float _attractionSmoothness = 4f;
|
||||
|
||||
[Header("One-Shot (Optional)")]
|
||||
[Tooltip("When enabled, the label permanently disappears once the player has seen it and then moved away.")]
|
||||
[SerializeField] private bool _enableOneShot = false;
|
||||
[Tooltip("Alpha threshold above which the label is considered 'seen'. " +
|
||||
"The player must look at the label long enough to exceed this visibility before the one-shot condition can trigger.")]
|
||||
[SerializeField] [Range(0.01f, 1f)] private float _seenAlphaThreshold = 0.5f;
|
||||
|
||||
[Header("Reappear Fade")]
|
||||
[Tooltip("Duration of the fade-in when the label reappears after being culled.")]
|
||||
[SerializeField] private float _fadeInDuration = 0.5f;
|
||||
@@ -71,7 +92,7 @@ namespace Unheimlich.Features.Dialogue.UI
|
||||
[SerializeField] private float _cullCheckInterval = 0.4f;
|
||||
|
||||
// ── State ──────────────────────────────────────────────────────────────
|
||||
private Camera _camera;
|
||||
private Camera _camera;
|
||||
private Vector3 _homePosition;
|
||||
private Vector3 _baseScale;
|
||||
|
||||
@@ -81,16 +102,26 @@ namespace Unheimlich.Features.Dialogue.UI
|
||||
private float _fadeInAlpha = 1f;
|
||||
|
||||
// Shake
|
||||
private bool _isShaking;
|
||||
private bool _isShaking;
|
||||
private Vector3 _shakeOffset;
|
||||
private Vector3 _shakeTarget;
|
||||
private float _shakeTimer;
|
||||
|
||||
// Ghost trail
|
||||
private TextMeshPro _ghostText;
|
||||
private Queue<Vector3> _positionHistory = new Queue<Vector3>();
|
||||
private float _historyTimer;
|
||||
private const float HISTORY_SAMPLE_RATE = 0.016f;
|
||||
private TextMeshPro _ghostText;
|
||||
private Queue<Vector3> _positionHistory = new Queue<Vector3>();
|
||||
private float _historyTimer;
|
||||
private const float HISTORY_SAMPLE_RATE = 0.016f;
|
||||
|
||||
// Camera attraction
|
||||
// World-space offset currently applied on top of the home+shake position.
|
||||
private Vector3 _attractionOffset;
|
||||
|
||||
// One-shot
|
||||
// True once the label has been visible above the threshold at least once.
|
||||
private bool _hasBeenSeen;
|
||||
// True once the one-shot has fired and the label should stay invisible forever.
|
||||
private bool _permanentlyHidden;
|
||||
|
||||
// ── Unity ──────────────────────────────────────────────────────────────
|
||||
private void Awake()
|
||||
@@ -117,6 +148,8 @@ namespace Unheimlich.Features.Dialogue.UI
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_permanentlyHidden) return;
|
||||
|
||||
if (_camera == null)
|
||||
{
|
||||
_camera = Camera.main;
|
||||
@@ -144,7 +177,28 @@ namespace Unheimlich.Features.Dialogue.UI
|
||||
if (!_isActive) return;
|
||||
|
||||
UpdatePosition();
|
||||
UpdateVisibility();
|
||||
|
||||
// Compute the distance-based alpha first so one-shot and attraction can read it.
|
||||
float dist = Vector3.Distance(_camera.transform.position, _homePosition);
|
||||
float alpha = ComputeAlpha(dist) * _fadeInAlpha;
|
||||
|
||||
// One-shot: track visibility and trigger permanent hide when the player walks away after seeing the label.
|
||||
if (_enableOneShot)
|
||||
{
|
||||
if (!_hasBeenSeen && alpha >= _seenAlphaThreshold)
|
||||
_hasBeenSeen = true;
|
||||
|
||||
if (_hasBeenSeen && alpha <= 0f)
|
||||
{
|
||||
_permanentlyHidden = true;
|
||||
_textComponent.enabled = false;
|
||||
if (_ghostText != null) _ghostText.enabled = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateCameraAttraction(alpha);
|
||||
ApplyAlpha(alpha);
|
||||
UpdateAdaptiveScale();
|
||||
|
||||
if (_isShaking)
|
||||
@@ -160,29 +214,24 @@ namespace Unheimlich.Features.Dialogue.UI
|
||||
Vector3 toHome = _homePosition - _camera.transform.position;
|
||||
float dist = toHome.magnitude;
|
||||
|
||||
Vector3 basePos;
|
||||
if (dist < _minDistance && dist > 0.001f)
|
||||
{
|
||||
// Push label away so it stays at least _minDistance from the camera.
|
||||
transform.position = _camera.transform.position + toHome.normalized * _minDistance;
|
||||
}
|
||||
basePos = _camera.transform.position + toHome.normalized * _minDistance;
|
||||
else
|
||||
{
|
||||
transform.position = _homePosition + _shakeOffset;
|
||||
}
|
||||
basePos = _homePosition + _shakeOffset;
|
||||
|
||||
transform.position = basePos + _attractionOffset;
|
||||
}
|
||||
|
||||
// ── Visibility (distance fade) ─────────────────────────────────────────
|
||||
|
||||
private void UpdateVisibility()
|
||||
private void ApplyAlpha(float alpha)
|
||||
{
|
||||
if (_textComponent == null) return;
|
||||
|
||||
if (_fadeInAlpha < 1f)
|
||||
_fadeInAlpha = Mathf.MoveTowards(_fadeInAlpha, 1f, Time.deltaTime / Mathf.Max(_fadeInDuration, 0.001f));
|
||||
|
||||
float dist = Vector3.Distance(_camera.transform.position, _homePosition);
|
||||
float alpha = ComputeAlpha(dist) * _fadeInAlpha;
|
||||
|
||||
SetTextAlpha(_textComponent, alpha);
|
||||
|
||||
if (_ghostText != null)
|
||||
@@ -224,16 +273,64 @@ namespace Unheimlich.Features.Dialogue.UI
|
||||
float scaleFactor = Mathf.Clamp(dist / Mathf.Max(_referenceDistance, 0.001f), _minScaleFactor, _maxScaleFactor);
|
||||
transform.localScale = _baseScale * scaleFactor;
|
||||
|
||||
// Keep ghost in sync — it has its own BillboardUI for rotation but we drive scale here.
|
||||
if (_ghostText != null)
|
||||
_ghostText.transform.localScale = transform.localScale;
|
||||
}
|
||||
|
||||
// ── Camera Attraction ──────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Pulls the label toward the screen center when the player is looking near it.
|
||||
/// Strength uses a quadratic falloff so it only kicks in meaningfully
|
||||
/// when the gaze angle is truly small.
|
||||
/// </summary>
|
||||
private void UpdateCameraAttraction(float currentAlpha)
|
||||
{
|
||||
if (!_enableCameraAttraction)
|
||||
{
|
||||
_attractionOffset = Vector3.zero;
|
||||
return;
|
||||
}
|
||||
|
||||
// Direction from camera to the label's home position.
|
||||
Vector3 toLabel = _homePosition - _camera.transform.position;
|
||||
float dist = toLabel.magnitude;
|
||||
|
||||
if (dist < 0.001f)
|
||||
{
|
||||
_attractionOffset = Vector3.zero;
|
||||
return;
|
||||
}
|
||||
|
||||
// Angle between the camera's forward direction and the direction to the label.
|
||||
float angleDeg = Vector3.Angle(_camera.transform.forward, toLabel.normalized);
|
||||
|
||||
// Quadratic falloff: 1 at angleDeg==0, 0 at angleDeg==_attractionAngle and beyond.
|
||||
float t = Mathf.Clamp01(1f - angleDeg / Mathf.Max(_attractionAngle, 0.001f));
|
||||
float strength = t * t; // quadratic — only strong when really close to center
|
||||
|
||||
// Also scale with the distance-based alpha so attraction is zero when the label is faded out.
|
||||
strength *= currentAlpha;
|
||||
|
||||
// Target offset: move the label to where Camera.forward ray at distance |toLabel| lands.
|
||||
Vector3 labelOnCenterRay = _camera.transform.position + _camera.transform.forward * dist;
|
||||
Vector3 targetOffset = (labelOnCenterRay - _homePosition) * strength;
|
||||
|
||||
// Clamp magnitude so it never overrides the home position completely.
|
||||
if (targetOffset.magnitude > _attractionMaxOffset)
|
||||
targetOffset = targetOffset.normalized * _attractionMaxOffset;
|
||||
|
||||
_attractionOffset = Vector3.Lerp(
|
||||
_attractionOffset,
|
||||
targetOffset,
|
||||
Time.deltaTime * _attractionSmoothness
|
||||
);
|
||||
}
|
||||
|
||||
// ── Organic Shake ──────────────────────────────────────────────────────
|
||||
|
||||
private Vector3 RandomShakeTarget()
|
||||
{
|
||||
// XY only — no depth displacement.
|
||||
float scaledRadius = _shakeRadius * transform.localScale.x;
|
||||
Vector2 circle = Random.insideUnitCircle * scaledRadius;
|
||||
return new Vector3(circle.x, circle.y, 0f);
|
||||
@@ -284,7 +381,6 @@ namespace Unheimlich.Features.Dialogue.UI
|
||||
_ghostAlpha
|
||||
);
|
||||
|
||||
// Ghost renderer always sorts behind the main word's renderer.
|
||||
MeshRenderer mainRenderer = _textComponent.GetComponent<MeshRenderer>();
|
||||
MeshRenderer ghostRenderer = _ghostText.GetComponent<MeshRenderer>();
|
||||
if (mainRenderer != null && ghostRenderer != null)
|
||||
@@ -293,7 +389,6 @@ namespace Unheimlich.Features.Dialogue.UI
|
||||
ghostRenderer.sortingOrder = mainRenderer.sortingOrder - 1;
|
||||
}
|
||||
|
||||
// Ghost must also face the player.
|
||||
ghostObj.AddComponent<BillboardUI>();
|
||||
}
|
||||
|
||||
@@ -301,7 +396,6 @@ namespace Unheimlich.Features.Dialogue.UI
|
||||
{
|
||||
if (!_enableGhostTrail || _ghostText == null) return;
|
||||
|
||||
// Sample position history at a fixed rate.
|
||||
_historyTimer += Time.deltaTime;
|
||||
if (_historyTimer >= HISTORY_SAMPLE_RATE)
|
||||
{
|
||||
@@ -317,11 +411,7 @@ namespace Unheimlich.Features.Dialogue.UI
|
||||
? _positionHistory.Peek()
|
||||
: transform.position;
|
||||
|
||||
// Push ghost along camera forward (away from camera) so it always sits
|
||||
// behind the main word in depth — stable regardless of viewing angle.
|
||||
_ghostText.transform.position = ghostPos + _camera.transform.forward * _ghostDepthOffset;
|
||||
|
||||
// Keep text content and size in sync.
|
||||
_ghostText.text = _textComponent.text;
|
||||
_ghostText.fontSize = _textComponent.fontSize;
|
||||
}
|
||||
@@ -339,8 +429,18 @@ namespace Unheimlich.Features.Dialogue.UI
|
||||
/// <summary>Teleports the label's home position without animation.</summary>
|
||||
public void SetHomePosition(Vector3 worldPos)
|
||||
{
|
||||
_homePosition = worldPos;
|
||||
transform.position = worldPos;
|
||||
_homePosition = worldPos;
|
||||
transform.position = worldPos;
|
||||
}
|
||||
|
||||
/// <summary>Resets the one-shot state so the label can be seen again.</summary>
|
||||
public void ResetOneShot()
|
||||
{
|
||||
_hasBeenSeen = false;
|
||||
_permanentlyHidden = false;
|
||||
if (_textComponent != null) _textComponent.enabled = true;
|
||||
if (_ghostText != null) _ghostText.enabled = true;
|
||||
_fadeInAlpha = 0f;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
@@ -351,4 +451,4 @@ namespace Unheimlich.Features.Dialogue.UI
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ namespace Unheimlich.Features.Portals
|
||||
[SerializeField] private float _timeSyncThreshold = 0.1f;
|
||||
[SerializeField] private float _minSpread = 60f;
|
||||
[SerializeField] private float _maxSpread = 180f;
|
||||
[SerializeField] private float _volumeRolloffPower = 2f;
|
||||
// [SerializeField] private float _volumeRolloffPower = 2f;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private List<AudioSource> _remoteSources = new List<AudioSource>();
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Unheimlich.World.CameraSystem
|
||||
[Header("Zoom Settings")]
|
||||
[SerializeField] private float _minDistance = 0.5f;
|
||||
[SerializeField] private float _maxDistance = 3f;
|
||||
[SerializeField] private float _zoomSpeed = 2f;
|
||||
// [SerializeField] private float _zoomSpeed = 2f;
|
||||
[SerializeField] private float _zoomSmoothTime = 0.15f;
|
||||
[SerializeField] private float _scrollSensitivity = 0.3f;
|
||||
|
||||
|
||||
@@ -153,8 +153,6 @@ namespace Unheimlich.World.Environment
|
||||
|
||||
BuildTower();
|
||||
_currentState = StairState.Active;
|
||||
|
||||
Debug.Log($"[InfiniteStaircase] Activated — {(_enteredFromBelow ? "UP" : "DOWN")}, {_maxIterations} iterations.");
|
||||
}
|
||||
|
||||
/// <summary>Called by <see cref="StaircaseTriggerZone"/> when the player exits. No action needed — the limit check drives the stop.</summary>
|
||||
@@ -284,10 +282,7 @@ namespace Unheimlich.World.Environment
|
||||
: _towerRoot.localPosition.y >= _limitLocalY; // going down, tower ascended enough
|
||||
|
||||
if (reached)
|
||||
{
|
||||
_currentState = StairState.Stopping;
|
||||
Debug.Log("[InfiniteStaircase] Stock exhausted — decelerating.");
|
||||
}
|
||||
_currentState = StairState.Stopping;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -330,7 +325,6 @@ namespace Unheimlich.World.Environment
|
||||
_scrollSpeedVelocity = 0f;
|
||||
_playerTransform = null;
|
||||
|
||||
Debug.Log("[InfiniteStaircase] Cleanup done — ready for next interaction.");
|
||||
}
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────
|
||||
@@ -339,7 +333,6 @@ namespace Unheimlich.World.Environment
|
||||
{
|
||||
if (_centralColumnRenderer == null) return;
|
||||
_segmentHeight = _centralColumnRenderer.bounds.size.y;
|
||||
Debug.Log($"[InfiniteStaircase] Auto-detected height: {_segmentHeight:F3} m");
|
||||
}
|
||||
|
||||
private void CacheTriggerCenterY()
|
||||
@@ -353,10 +346,8 @@ namespace Unheimlich.World.Environment
|
||||
|
||||
Collider col = zone.GetComponent<Collider>();
|
||||
if (col != null)
|
||||
{
|
||||
_triggerCenterWorldY = col.bounds.center.y;
|
||||
Debug.Log($"[InfiniteStaircase] Trigger center Y: {_triggerCenterWorldY:F3}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void EnsurePlayerCached()
|
||||
|
||||
Reference in New Issue
Block a user