263 lines
9.7 KiB
C#
263 lines
9.7 KiB
C#
using UnityEngine;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using TMPro;
|
|
using UnityEngine.InputSystem; // WAJIB: Karena kamu pakai Input System baru
|
|
|
|
public class Manager : MonoBehaviour
|
|
{
|
|
[Header("Data References")]
|
|
public Atom[] atoms;
|
|
public Molecule[] molecules;
|
|
|
|
[Header("UI Reference")]
|
|
public GameObject panelInfo;
|
|
public TextMeshProUGUI textNamaMolekul;
|
|
|
|
[Header("Integration")]
|
|
public ARInteractionManager interactionManager;
|
|
|
|
[Header("Glitch Prevention")]
|
|
public float minAtomDistance = 0.05f;
|
|
|
|
// Variabel Private Internal
|
|
private GameObject activeModelObject;
|
|
private Molecule activeMolecule;
|
|
private Atom activeAtom;
|
|
|
|
// Variabel Baru untuk Fitur Tap Nama
|
|
private Atom selectedAtom;
|
|
private int totalAtomsTracked = 0;
|
|
|
|
void Start()
|
|
{
|
|
// Urutkan resep agar prioritas molekul kompleks (O3) lebih tinggi dari sederhana (O2)
|
|
molecules = molecules.OrderByDescending(m => m.requirements.Sum(r => r.count)).ToArray();
|
|
|
|
foreach (var m in molecules)
|
|
if(m.model) m.model.SetActive(false);
|
|
|
|
if(panelInfo) panelInfo.SetActive(false);
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
var trackedAtoms = GetTrackedAtomsFiltered();
|
|
|
|
// Hitung total atom yang muncul di layar saat ini
|
|
totalAtomsTracked = 0;
|
|
foreach (var list in trackedAtoms.Values) totalAtomsTracked += list.Count;
|
|
|
|
foreach (var typeList in trackedAtoms.Values)
|
|
{
|
|
foreach (var atom in typeList)
|
|
{
|
|
if (!atom.visual.activeSelf) atom.visual.SetActive(true);
|
|
}
|
|
}
|
|
|
|
// Jalankan logika deteksi Tap setiap frame
|
|
HandleSelectionInput();
|
|
|
|
Molecule matched = null;
|
|
foreach (var m in molecules)
|
|
{
|
|
if (Match(m, trackedAtoms)) { matched = m; break; }
|
|
}
|
|
|
|
if (matched != null)
|
|
{
|
|
if (matched != activeMolecule)
|
|
{
|
|
HideAllMolecules();
|
|
Vector3 startPos = CalculateSmartCenter(matched, trackedAtoms);
|
|
Quaternion startRot = GetSmartRotation(trackedAtoms);
|
|
|
|
matched.model.transform.position = startPos;
|
|
matched.model.transform.rotation = startRot;
|
|
matched.model.SetActive(true);
|
|
matched.model.transform.localScale = Vector3.one;
|
|
StartCoroutine(AnimatePop(matched.model.transform));
|
|
}
|
|
|
|
activeModelObject = matched.model;
|
|
activeMolecule = matched;
|
|
activeAtom = null;
|
|
|
|
Vector3 center = CalculateSmartCenter(activeMolecule, trackedAtoms);
|
|
activeMolecule.model.transform.position = Vector3.Lerp(activeMolecule.model.transform.position, center, Time.deltaTime * 10f);
|
|
|
|
if (interactionManager != null && !interactionManager.IsUserInteracting())
|
|
{
|
|
Quaternion targetRot = GetSmartRotation(trackedAtoms);
|
|
activeMolecule.model.transform.rotation = Quaternion.Lerp(activeMolecule.model.transform.rotation, targetRot, Time.deltaTime * 10f);
|
|
}
|
|
|
|
HideAtomVisuals(activeMolecule, trackedAtoms);
|
|
}
|
|
else
|
|
{
|
|
HideAllMolecules();
|
|
activeMolecule = null;
|
|
Atom foundAtom = GetFirstTrackedAtom(trackedAtoms);
|
|
if (foundAtom != null) { activeModelObject = foundAtom.visual; activeAtom = foundAtom; }
|
|
else { activeModelObject = null; activeAtom = null; }
|
|
}
|
|
|
|
UpdateUI();
|
|
}
|
|
|
|
// --- LOGIKA BARU: DETEKSI TAP PADA ATOM ---
|
|
void HandleSelectionInput()
|
|
{
|
|
// Cek apakah user menekan layar
|
|
if (Pointer.current != null && Pointer.current.press.wasPressedThisFrame)
|
|
{
|
|
Vector2 touchPos = Pointer.current.position.ReadValue();
|
|
Ray ray = Camera.main.ScreenPointToRay(touchPos);
|
|
RaycastHit hit;
|
|
|
|
// Jika raycast mengenai sesuatu yang punya Collider
|
|
if (Physics.Raycast(ray, out hit))
|
|
{
|
|
// Cari apakah objek tersebut adalah Atom (atau bagian dari prefab Atom)
|
|
Atom atom = hit.collider.GetComponentInParent<Atom>();
|
|
if (atom != null && atom.isTracked)
|
|
{
|
|
selectedAtom = atom; // Simpan atom yang dipilih
|
|
}
|
|
else
|
|
{
|
|
selectedAtom = null; // Tap objek lain, hilangkan seleksi
|
|
}
|
|
}
|
|
else
|
|
{
|
|
selectedAtom = null; // Tap area kosong, hilangkan seleksi
|
|
}
|
|
}
|
|
|
|
// Reset seleksi otomatis jika marker atom tersebut hilang dari kamera
|
|
if (selectedAtom != null && !selectedAtom.isTracked)
|
|
{
|
|
selectedAtom = null;
|
|
}
|
|
}
|
|
|
|
// --- REVISI LOGIKA UI PANEL ---
|
|
void UpdateUI()
|
|
{
|
|
if (panelInfo == null) return;
|
|
|
|
// 1. CEK DULU: Apakah ada molekul dari BUKU yang lagi fokus?[cite: 21]
|
|
bool isBookFocused = false;
|
|
if (interactionManager != null)
|
|
{
|
|
// Kita cek ke Interaction Manager, apakah dia lagi pegang model dari buku
|
|
foreach (var b in interactionManager.bookManagers)
|
|
{
|
|
if (b != null && b.GetFocusedMolecule() != null)
|
|
{
|
|
isBookFocused = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. LOGIKA TAMPILAN
|
|
if (activeMolecule != null) // Prioritas Kartu: Molekul
|
|
{
|
|
panelInfo.SetActive(true);
|
|
textNamaMolekul.text = activeMolecule.moleculeName;
|
|
}
|
|
else if (totalAtomsTracked == 1 && activeAtom != null) // Prioritas Kartu: 1 Atom
|
|
{
|
|
panelInfo.SetActive(true);
|
|
textNamaMolekul.text = GetAtomDisplayName(activeAtom.atomType);
|
|
}
|
|
else if (totalAtomsTracked > 1 && selectedAtom != null) // Prioritas Kartu: Tap Atom[cite: 19]
|
|
{
|
|
panelInfo.SetActive(true);
|
|
textNamaMolekul.text = GetAtomDisplayName(selectedAtom.atomType);
|
|
}
|
|
else if (isBookFocused)
|
|
{
|
|
// JANGAN DIMATIKAN: Biarkan BookPageManager yang mengontrol teksnya
|
|
panelInfo.SetActive(true);
|
|
}
|
|
else
|
|
{
|
|
// Matikan HANYA jika tidak ada kartu DAN tidak ada buku yang fokus[cite: 19]
|
|
panelInfo.SetActive(false);
|
|
}
|
|
}
|
|
|
|
// --- FUNGSI HELPER (TETAP SAMA) ---
|
|
Quaternion GetSmartRotation(Dictionary<AtomType, List<Atom>> atoms)
|
|
{
|
|
foreach(var list in atoms.Values) if (list.Count > 0) return list[0].transform.rotation;
|
|
return Quaternion.identity;
|
|
}
|
|
|
|
Dictionary<AtomType, List<Atom>> GetTrackedAtomsFiltered()
|
|
{
|
|
var rawDict = new Dictionary<AtomType, List<Atom>>();
|
|
foreach (var atom in atoms) {
|
|
if (atom.isTracked) {
|
|
if (!rawDict.ContainsKey(atom.atomType)) rawDict[atom.atomType] = new List<Atom>();
|
|
rawDict[atom.atomType].Add(atom);
|
|
}
|
|
}
|
|
var cleanDict = new Dictionary<AtomType, List<Atom>>();
|
|
foreach (var kvp in rawDict) {
|
|
List<Atom> cleanList = new List<Atom>();
|
|
foreach (var atom in kvp.Value) {
|
|
bool isGhost = false;
|
|
foreach (var cleanAtom in cleanList)
|
|
if (Vector3.Distance(atom.transform.position, cleanAtom.transform.position) < minAtomDistance) { isGhost = true; break; }
|
|
if (!isGhost) cleanList.Add(atom); else atom.visual.SetActive(false);
|
|
}
|
|
cleanDict[kvp.Key] = cleanList;
|
|
}
|
|
return cleanDict;
|
|
}
|
|
|
|
bool Match(Molecule m, Dictionary<AtomType, List<Atom>> a) {
|
|
foreach (var r in m.requirements) if (!a.ContainsKey(r.atomType) || a[r.atomType].Count < r.count) return false;
|
|
return true;
|
|
}
|
|
|
|
Vector3 CalculateSmartCenter(Molecule m, Dictionary<AtomType, List<Atom>> a) {
|
|
Vector3 c = Vector3.zero; int t = 0;
|
|
foreach (var r in m.requirements) if (a.ContainsKey(r.atomType))
|
|
for(int i=0; i<a[r.atomType].Count && i<r.count; i++) { c += a[r.atomType][i].transform.position; t++; }
|
|
return t == 0 ? Vector3.zero : c / t;
|
|
}
|
|
|
|
void HideAtomVisuals(Molecule m, Dictionary<AtomType, List<Atom>> a) {
|
|
Vector3 c = CalculateSmartCenter(m, a);
|
|
foreach (var r in m.requirements) if (a.ContainsKey(r.atomType)) {
|
|
var s = a[r.atomType].OrderBy(x => Vector3.Distance(x.transform.position, c)).ToList();
|
|
for (int i=0; i<r.count && i<s.Count; i++) s[i].visual.SetActive(false);
|
|
}
|
|
}
|
|
|
|
void HideAllMolecules() { foreach (var m in molecules) if(m.model) m.model.SetActive(false); }
|
|
Atom GetFirstTrackedAtom(Dictionary<AtomType, List<Atom>> d) { foreach(var l in d.Values) if (l.Count > 0) return l[0]; return null; }
|
|
|
|
IEnumerator AnimatePop(Transform t) {
|
|
float timer = 0; float dur = 0.6f;
|
|
while (timer < dur) {
|
|
timer += Time.deltaTime; float x = timer / dur;
|
|
float scale = 1 + (1.70158f + 1) * Mathf.Pow(x - 1, 3) + 1.70158f * Mathf.Pow(x - 1, 2);
|
|
t.localScale = Vector3.one * scale; yield return null;
|
|
}
|
|
t.localScale = Vector3.one;
|
|
}
|
|
|
|
string GetAtomDisplayName(AtomType type) {
|
|
switch (type) { case AtomType.H: return "Hidrogen"; case AtomType.O: return "Oksigen"; case AtomType.C: return "Karbon"; case AtomType.N: return "Nitrogen"; case AtomType.Na: return "Natrium"; case AtomType.S: return "Sulfur"; case AtomType.Cl: return "Klorin"; default: return type.ToString(); }
|
|
}
|
|
public GameObject GetActiveModel() { return activeModelObject; }
|
|
} |