This commit is contained in:
2026-06-18 03:04:52 +07:00
parent e545e719c6
commit 81a6798e96
2986 changed files with 4229 additions and 605733 deletions

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 756a700ebf1264c44822e56b7c9bac48
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,87 +0,0 @@
using System;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.Audio;
namespace Hallucinate.Audio
{
[Serializable]
public class AudioSample
{
[ValidateInput(nameof(HasName), "Audio sample needs a lookup name.")]
public string Name;
[Required]
public AudioClip Clip;
[PropertyRange(0f, 1f)]
public float DefaultVolume = 1f;
[PropertyRange(0.1f, 3f)]
public float DefaultPitch = 1f;
public AudioMixerGroup MixerGroup;
private bool HasName(string value)
{
return !string.IsNullOrWhiteSpace(value);
}
}
[CreateAssetMenu(fileName = "AudioDatabase", menuName = "BABA_YAGA/Audio/Audio Database")]
public class AudioDatabase : ScriptableObject
{
[Searchable]
[TableList]
[SerializeField] private List<AudioSample> samples = new List<AudioSample>();
[ShowInInspector]
[ReadOnly]
[BoxGroup("Runtime")]
public int SampleCount => samples?.Count ?? 0;
private Dictionary<string, AudioSample> _sampleCache;
[Button("Rebuild Lookup Cache")]
public void Initialize()
{
_sampleCache = new Dictionary<string, AudioSample>();
foreach (var sample in samples)
{
if (!string.IsNullOrEmpty(sample.Name) && !_sampleCache.ContainsKey(sample.Name))
{
_sampleCache.Add(sample.Name, sample);
}
}
}
public AudioSample GetSample(string name)
{
if (_sampleCache == null) Initialize();
if (_sampleCache.TryGetValue(name, out var sample))
{
return sample;
}
return null;
}
public IEnumerable<string> GetSampleNames()
{
foreach (var sample in samples)
{
if (!string.IsNullOrWhiteSpace(sample.Name))
{
yield return sample.Name;
}
}
}
[Button("Log Sample Names")]
private void LogSampleNames()
{
Debug.Log($"{name}: {string.Join(", ", GetSampleNames())}", this);
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: e54b0675598e3f946be47398a01b918a

View File

@@ -1,175 +0,0 @@
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.Audio;
namespace Hallucinate.Audio
{
public class AudioManager : MonoBehaviour
{
public static AudioManager Instance { get; private set; }
[BoxGroup("Settings")]
[Required]
[InlineEditor]
[SerializeField] private AudioDatabase database;
[BoxGroup("Settings")]
[MinValue(1)]
[SerializeField] private int poolSize = 20;
[BoxGroup("Settings")]
[Required]
[SerializeField] private AudioMixerGroup defaultGroup;
[BoxGroup("Test")]
[ValueDropdown(nameof(GetSampleNames))]
[SerializeField] private string testSampleName;
[ShowInInspector]
[ReadOnly]
[BoxGroup("Runtime")]
private int PoolCount => _pool?.Count ?? 0;
[ShowInInspector]
[ReadOnly]
[BoxGroup("Runtime")]
private int CurrentPoolIndex => _currentIndex;
private List<AudioSource> _pool;
private int _currentIndex = 0;
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
InitializePool();
if (database != null) database.Initialize();
}
private void Start()
{
ApplyAllVolumes();
}
private void InitializePool()
{
_pool = new List<AudioSource>();
for (int i = 0; i < poolSize; i++)
{
GameObject go = new GameObject($"AudioSource_{i}");
go.transform.SetParent(transform);
AudioSource source = go.AddComponent<AudioSource>();
source.playOnAwake = false;
_pool.Add(source);
}
}
public void ApplyAllVolumes()
{
SetVolume("MasterVolume", PlayerPrefs.GetFloat("MasterVolume", 80f));
SetVolume("MusicVolume", PlayerPrefs.GetFloat("MusicVolume", 80f));
SetVolume("VFXVolume", PlayerPrefs.GetFloat("VFXVolume", 80f));
SetVolume("PlayerVolume", PlayerPrefs.GetFloat("PlayerVolume", 80f));
SetVolume("UIVolume", PlayerPrefs.GetFloat("UIVolume", 80f));
}
public void SetVolume(string key, float volume)
{
if (defaultGroup == null || defaultGroup.audioMixer == null) return;
// Chuyển đổi từ 0-100 sang dB (-80f đến 0f hoặc 20f tùy mixer)
// Công thức: dB = 20 * log10(volume / 100)
float db = volume <= 0.001f ? -80f : Mathf.Log10(volume / 100f) * 20f;
// Đảm bảo Parameter đã được EXPOSE trong AudioMixer với tên tương ứng (MasterVolume, MusicVolume, etc.)
defaultGroup.audioMixer.SetFloat(key, db);
}
public void Play(string sampleName, float volumeMult = 1f, float pitchMult = 1f, Vector3? position = null)
{
if (database == null) return;
var sample = database.GetSample(sampleName);
if (sample == null || sample.Clip == null)
{
// Silence or log warning if needed
return;
}
AudioSource source = GetNextSource();
// Setup source
source.clip = sample.Clip;
source.volume = sample.DefaultVolume * volumeMult;
source.pitch = sample.DefaultPitch * pitchMult;
source.outputAudioMixerGroup = sample.MixerGroup != null ? sample.MixerGroup : defaultGroup;
if (position.HasValue)
{
source.spatialBlend = 1f; // 3D
source.transform.position = position.Value;
}
else
{
source.spatialBlend = 0f; // 2D
}
source.Play();
}
private AudioSource GetNextSource()
{
// Simple round-robin for now, can be improved to find truly "idle" sources
AudioSource source = _pool[_currentIndex];
_currentIndex = (_currentIndex + 1) % poolSize;
return source;
}
public void PlayRandom(string baseName, int variants, float volumeMult = 1f, float pitchMult = 1f)
{
int rand = UnityEngine.Random.Range(1, variants + 1);
Play($"{baseName}-{rand}", volumeMult, pitchMult);
}
// Helper for UI/Global easy access
public static void PlayGlobal(string name, float volume = 1f, float pitch = 1f)
{
if (Instance != null) Instance.Play(name, volume, pitch);
}
public static void PlayRandomGlobal(string baseName, int variants, float volume = 1f, float pitch = 1f)
{
if (Instance != null) Instance.PlayRandom(baseName, variants, volume, pitch);
}
private IEnumerable<string> GetSampleNames()
{
return database != null ? database.GetSampleNames() : new[] { "" };
}
[Button("Play Test Sample")]
private void PlayTestSample()
{
if (!Application.isPlaying)
{
Debug.LogWarning("AudioManager test playback is available in Play Mode.", this);
return;
}
if (string.IsNullOrWhiteSpace(testSampleName))
{
Debug.LogWarning("Choose an audio sample before testing playback.", this);
return;
}
Play(testSampleName);
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 35bf1b4a2b113c048874a8a4a3ea18b3

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: a121a8ff60924d099cc60596ee44a22d
timeCreated: 1780542110

View File

@@ -1,92 +0,0 @@
using UnityEngine;
namespace OnlyScove.Scripts
{
public class EnvironmentScanner : MonoBehaviour
{
[Header("Obstacle Detection")]
[SerializeField] private Vector3 forwardRayOffset = new Vector3(0, 1.5f, 0);
[SerializeField] float forwardRayLength = 1.5f;
[SerializeField] LayerMask obstacleLayer;
[SerializeField] float heightRayLength = 2.5f;
[Header("Interaction Detection")]
[SerializeField] private Vector3 interactionOffset = new Vector3(0, 1.5f, 0);
[SerializeField] private float interactionRadius = 0.5f;
public ObstacleHitInfo ObstacleCheck()
{
var hitData = new ObstacleHitInfo();
var forwardOrigin = transform.position + forwardRayOffset;
hitData.forwardHitFound = Physics.Raycast(transform.position + forwardRayOffset,
transform.forward,
out hitData.forwardHit,
forwardRayLength, obstacleLayer
);
Debug.DrawRay(forwardOrigin, transform.forward * forwardRayLength, (hitData.forwardHitFound) ? Color.red : Color.green);
if (hitData.forwardHitFound)
{
var heightOrigin = hitData.forwardHit.point + Vector3.up * heightRayLength;
hitData.heightHitFound = Physics.Raycast(heightOrigin, Vector3.down,
out hitData.heightHit,
heightRayLength, obstacleLayer);
Debug.DrawRay(heightOrigin, Vector3.down * heightRayLength, (hitData.heightHitFound) ? Color.red : Color.green);
}
return hitData;
}
public IInteractable ScanForInteractable(float range, LayerMask mask)
{
if (Camera.main == null) return null;
Transform camTransform = Camera.main.transform;
Vector3 origin = camTransform.position;
Vector3 direction = camTransform.forward;
// Vẽ tia debug trong cửa sổ Scene để bạn kiểm tra (Nhấn nút Gizmos trong Scene để thấy)
Debug.DrawRay(origin, direction * range, Color.blue);
if (Physics.Raycast(origin, direction, out RaycastHit hit, range, mask))
{
if (hit.collider.TryGetComponent(out IInteractable interactable))
{
Debug.DrawLine(origin, hit.point, Color.red); // Tia chuyển đỏ khi chạm vật tương tác
return interactable;
}
}
return null;
}
public Vector3 GetLastInteractionPoint(float range, LayerMask mask)
{
if (Camera.main == null) return Vector3.zero;
Transform camTransform = Camera.main.transform;
if (Physics.Raycast(camTransform.position, camTransform.forward, out RaycastHit hit, range, mask))
return hit.point;
return Vector3.zero;
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.cyan;
Vector3 origin = transform.position + interactionOffset;
Gizmos.DrawLine(origin, origin + transform.forward * 2f);
Gizmos.DrawWireSphere(origin + transform.forward * 2f, interactionRadius);
}
}
}
public struct ObstacleHitInfo
{
public RaycastHit forwardHit;
public RaycastHit heightHit;
public bool forwardHitFound;
public bool heightHitFound;
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 169e37d35fede30409266070c88b118f

View File

@@ -1,52 +0,0 @@
using UnityEngine;
namespace OnlyScove.Scripts
{
public abstract class BaseInteractable : MonoBehaviour, IInteractable
{
[Header("Interaction Settings")]
[SerializeField] protected ObjectInteraction interactionData;
private float lastInteractTime;
public virtual string InteractionPrompt => interactionData != null ? interactionData.promptText : "Tương tác";
public virtual void OnInteract(GameObject interactor)
{
// Kiểm tra Cooldown
float cooldown = interactionData != null ? interactionData.interactionCooldown : 0.5f;
if (Time.time < lastInteractTime + cooldown)
return;
lastInteractTime = Time.time;
// Phát âm thanh nếu có
if (interactionData != null && interactionData.interactionSound != null)
{
AudioSource.PlayClipAtPoint(interactionData.interactionSound, transform.position);
}
// Tạo hiệu ứng VFX nếu có
if (interactionData != null && interactionData.interactionVFX != null)
{
Instantiate(interactionData.interactionVFX, transform.position, Quaternion.identity);
}
// Gọi logic tương tác cụ thể của từng loại vật thể
PerformInteraction(interactor);
}
public virtual void OnHoverEnter()
{
// Sẽ thêm logic Highlight ở Giai đoạn 4
}
public virtual void OnHoverExit()
{
// Sẽ xóa logic Highlight ở Giai đoạn 4
}
// Logic cụ thể bắt buộc các lớp con (Cửa, Cần gạt...) phải triển khai
protected abstract void PerformInteraction(GameObject interactor);
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 77496ebe7c1d9c74ebbe6c390d147e04

View File

@@ -1,82 +0,0 @@
using UnityEngine;
using DoorScript; // Namespace của gói Wood Door Pack
namespace OnlyScove.Scripts
{
public class DoorInteractable : BaseInteractable
{
[Header("Door Component (Optional)")]
[SerializeField] private Door woodDoorScript;
[Header("Animator (Optional)")]
[SerializeField] private Animator animator;
[SerializeField] private string boolParameterName = "IsOpen";
private bool isOpen = false;
// Ghi đè Prompt để hiện trạng thái Mở/Đóng tùy vào cửa đang như thế nào
public override string InteractionPrompt
{
get
{
bool currentOpen = woodDoorScript != null ? woodDoorScript.open : isOpen;
return (currentOpen ? "Đóng " : "Mở ") + (interactionData != null ? interactionData.promptText : "Cửa");
}
}
private void Awake()
{
Debug.Log($"[DoorInteractable] Initializing on {gameObject.name}");
// 1. Tìm Door script (Tìm mọi nơi: bản thân, con, cha)
if (woodDoorScript == null) woodDoorScript = GetComponent<Door>();
if (woodDoorScript == null) woodDoorScript = GetComponentInChildren<Door>(true);
if (woodDoorScript == null) woodDoorScript = GetComponentInParent<Door>();
if (woodDoorScript != null)
{
Debug.Log($"[DoorInteractable] SUCCESS: Found Door script on {woodDoorScript.gameObject.name}");
// Đảm bảo có AudioSource
var source = woodDoorScript.GetComponent<AudioSource>();
if (source == null) source = woodDoorScript.gameObject.AddComponent<AudioSource>();
woodDoorScript.asource = source;
isOpen = woodDoorScript.open;
}
// 2. Tìm Animator (Tìm mọi nơi)
if (animator == null) animator = GetComponent<Animator>();
if (animator == null) animator = GetComponentInChildren<Animator>(true);
if (animator == null) animator = GetComponentInParent<Animator>();
// 3. TỰ ĐỘNG TẮT SCRIPT XUNG ĐỘT (CameraOpenDoor) nếu nó đang tồn tại trên Camera
var conflictingScript = Object.FindFirstObjectByType<CameraDoorScript.CameraOpenDoor>();
if (conflictingScript != null)
{
Debug.Log("[DoorInteractable] Disabling conflicting CameraOpenDoor script to take full control.");
conflictingScript.enabled = false;
}
}
protected override void PerformInteraction(GameObject interactor)
{
Debug.Log($"[Interaction] PerformInteraction CALLED on {gameObject.name}!");
// 1. Ưu tiên script của Door Pack (Wood Door Script)
if (woodDoorScript != null)
{
woodDoorScript.OpenDoor();
isOpen = woodDoorScript.open;
return;
}
// 2. Nếu không có script Pack mới dùng Animator
if (animator != null)
{
isOpen = !isOpen;
animator.SetBool(boolParameterName, isOpen);
}
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: d41bcdbf11a6d6c4bb61a32e85d1635f

View File

@@ -1,81 +0,0 @@
using UnityEngine;
using System.Collections.Generic;
namespace OnlyScove.Scripts
{
public class LampInteractable : BaseInteractable
{
[Header("Light Settings")] [SerializeField]
private List<Light> targetLights = new List<Light>(); // Cho phép gán nhiều đèn
[SerializeField] private bool isOn = true;
[Header("Emission Settings (Optional)")] [SerializeField]
private MeshRenderer lampRenderer;
[SerializeField] private int materialIndex = 0;
[SerializeField] private string emissionColorProperty = "_EmissionColor";
private Material lampMaterial;
private Color originalEmissionColor;
public override string InteractionPrompt
{
get
{
string action = isOn ? "Tắt " : "Bật ";
string name = interactionData != null ? interactionData.promptText : "Đèn";
return action + name;
}
}
private void Start()
{
// Khởi tạo Material (tạo bản thực thi riêng để không lỗi Shader)
if (lampRenderer != null && materialIndex < lampRenderer.materials.Length)
{
lampMaterial = lampRenderer.materials[materialIndex];
if (lampMaterial.HasProperty(emissionColorProperty))
{
originalEmissionColor = lampMaterial.GetColor(emissionColorProperty);
}
}
UpdateLightState();
}
protected override void PerformInteraction(GameObject interactor)
{
isOn = !isOn;
UpdateLightState();
// Log cực mạnh để bạn kiểm tra Console
Debug.LogWarning($"<color=yellow>[Lamp]</color> Đèn đã chuyển sang: {(isOn ? "BẬT" : "TẮT")}");
}
private void UpdateLightState()
{
// Bật/Tắt tất cả các đèn trong danh sách
foreach (var light in targetLights)
{
if (light != null) light.enabled = isOn;
}
// Bật/Tắt hiệu ứng phát sáng của vật liệu
if (lampMaterial != null)
{
if (isOn)
{
lampMaterial.SetColor(emissionColorProperty, originalEmissionColor);
lampMaterial.EnableKeyword("_EMISSION");
}
else
{
// Tắt hẳn màu phát sáng
lampMaterial.SetColor(emissionColorProperty, Color.black);
lampMaterial.DisableKeyword("_EMISSION");
}
}
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 8ad2ce50b06995b49b7380826f67114d

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: e6d134b82fd19fc4aa38896d5e45efed
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: