532 lines
18 KiB
C#
532 lines
18 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using TMPro;
|
|
using UnityEngine.SceneManagement;
|
|
using Firebase;
|
|
using Firebase.Database;
|
|
using Firebase.Extensions;
|
|
|
|
[System.Serializable]
|
|
public class SoalData
|
|
{
|
|
public int id;
|
|
public string tipe;
|
|
public string pertanyaan;
|
|
public string gambar;
|
|
public string audio_pertanyaan;
|
|
public string audio_jawaban;
|
|
public string[] pilihan;
|
|
public string[] pilihan_gambar;
|
|
public string jawaban;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class SoalWrapper
|
|
{
|
|
public string kategori;
|
|
public SoalData[] soal;
|
|
}
|
|
|
|
public class KuisManager : MonoBehaviour
|
|
{
|
|
[Header("Panel")]
|
|
public GameObject panelSoal;
|
|
public GameObject panelHasil;
|
|
|
|
[Header("Loading")]
|
|
public GameObject panelLoading;
|
|
public TextMeshProUGUI textLoading;
|
|
|
|
[Header("Elemen Soal")]
|
|
public TextMeshProUGUI textSoal;
|
|
public Image imageSoal;
|
|
public Button btnA, btnB, btnC;
|
|
public TextMeshProUGUI textA, textB, textC;
|
|
public GameObject frameSoal;
|
|
public GameObject frameSoalGambar;
|
|
public TextMeshProUGUI textSoalGambar;
|
|
|
|
[Header("Skor")]
|
|
public TextMeshProUGUI textSkor;
|
|
|
|
[Header("Panel Hasil")]
|
|
public TextMeshProUGUI textPesan;
|
|
public TextMeshProUGUI textSkorAkhir;
|
|
public Button btnUlang, btnMenu, btnNextLevel;
|
|
public Image imgStikerHasil;
|
|
public Sprite stikerHasilBagus;
|
|
public Sprite stikerHasilKurang;
|
|
|
|
[Header("Popup Benar/Salah")]
|
|
public GameObject panelPopup;
|
|
public Image imgStiker;
|
|
public Sprite stikerBenar;
|
|
public Sprite stikerSalah;
|
|
|
|
[Header("Jawaban Gambar")]
|
|
public GameObject panelJawabanGambar;
|
|
public Button btnGambarA, btnGambarB, btnGambarC;
|
|
|
|
[Header("Navigasi")]
|
|
public Button btnHome;
|
|
|
|
// --- private ---
|
|
private List<SoalData> daftarSoal = new List<SoalData>();
|
|
private int indexSoal = 0;
|
|
private int skor = 0;
|
|
private int totalSoal = 5;
|
|
private string jawabanBenar;
|
|
private AudioSource audioSource;
|
|
private AudioClip clipPertanyaan;
|
|
private string kategoriAktif;
|
|
|
|
private DatabaseReference dbRef;
|
|
|
|
private List<string> listHewan = new List<string> {
|
|
"anjing","ayam","babi","bebek","buaya","burung",
|
|
"gajah","harimau","ikan","jerapah","kambing","katak","kelinci",
|
|
"kepiting","kucing","kupu-kupu","kura","landak","lebah","rubah",
|
|
"rusa","sapi","singa","ular","ulat","zebra"
|
|
};
|
|
|
|
private List<string> listAssetWarna = new List<string> {
|
|
"bintang_merah","bintang_biru","bintang_hijau","bintang_kuning",
|
|
"bintang_orange","bintang_ungu","bintang_pink","bintang_coklat","bintang_hitam",
|
|
"krayon_merah","krayon_biru","krayon_hijau","krayon_kuning",
|
|
"krayon_orange","krayon_ungu","krayon_pink","krayon_coklat",
|
|
"lingkaran_merah","lingkaran_biru","lingkaran_hijau","lingkaran_kuning",
|
|
"lingkaran_orange","lingkaran_ungu","lingkaran_pink","lingkaran_coklat",
|
|
"persegi_merah","persegi_biru","persegi_hijau","persegi_kuning",
|
|
"persegi_orange","persegi_ungu","persegi_pink","persegi_coklat",
|
|
"segitiga_merah","segitiga_biru","segitiga_hijau","segitiga_kuning",
|
|
"segitiga_orange","segitiga_ungu","segitiga_pink","segitiga_coklat",
|
|
"tomat","matahari","rumput","payung_biru","payung_merah",
|
|
"pita","terong","balon","kursi","pelampung"
|
|
};
|
|
|
|
void Start()
|
|
{
|
|
audioSource = GetComponent<AudioSource>();
|
|
if (audioSource == null)
|
|
audioSource = gameObject.AddComponent<AudioSource>();
|
|
|
|
string rawKategori = PlayerPrefs.GetString("kategori", "Hewan");
|
|
kategoriAktif = rawKategori.ToLower();
|
|
|
|
if (btnHome != null)
|
|
{
|
|
btnHome.onClick.RemoveAllListeners();
|
|
btnHome.onClick.AddListener(KembaliKeMenu);
|
|
}
|
|
|
|
panelHasil.SetActive(false);
|
|
panelSoal.SetActive(false);
|
|
panelPopup.SetActive(false);
|
|
panelJawabanGambar.SetActive(false);
|
|
frameSoalGambar.SetActive(false);
|
|
|
|
// Tampilkan loading dulu
|
|
if (panelLoading != null) panelLoading.SetActive(true);
|
|
if (textLoading != null) textLoading.text = "Memuat soal...";
|
|
|
|
// Inisialisasi Firebase lalu muat soal
|
|
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWithOnMainThread(task =>
|
|
{
|
|
if (task.Result == DependencyStatus.Available)
|
|
{
|
|
dbRef = FirebaseDatabase.DefaultInstance.RootReference;
|
|
Debug.Log("✅ Firebase siap!");
|
|
MuatSoalDariFirebase();
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("❌ Firebase gagal: " + task.Result);
|
|
if (textLoading != null)
|
|
textLoading.text = "Gagal konek ke server.\nCek koneksi internet!";
|
|
}
|
|
});
|
|
}
|
|
|
|
void MuatSoalDariFirebase()
|
|
{
|
|
int level = PlayerPrefs.GetInt("level", 1);
|
|
string kunci = kategoriAktif + "_level" + level; // contoh: "hewan_level1"
|
|
|
|
Debug.Log("📡 Mengambil soal dari Firebase: soal/" + kunci);
|
|
|
|
dbRef.Child("soal").Child(kunci).GetValueAsync().ContinueWithOnMainThread(task =>
|
|
{
|
|
if (task.IsFaulted || task.IsCanceled)
|
|
{
|
|
Debug.LogError("❌ Gagal ambil data dari Firebase: " + task.Exception);
|
|
if (textLoading != null)
|
|
textLoading.text = "Gagal memuat soal.\nCek koneksi internet!";
|
|
return;
|
|
}
|
|
|
|
DataSnapshot snapshot = task.Result;
|
|
|
|
if (!snapshot.Exists)
|
|
{
|
|
Debug.LogError("❌ Data tidak ditemukan di Firebase: soal/" + kunci);
|
|
if (textLoading != null)
|
|
textLoading.text = "Data soal tidak ditemukan!";
|
|
return;
|
|
}
|
|
|
|
// Parse data dari Firebase ke List<SoalData>
|
|
List<SoalData> semua = new List<SoalData>();
|
|
|
|
foreach (DataSnapshot soalSnap in snapshot.Children)
|
|
{
|
|
Debug.Log("proses ambil data soal dari firebase");
|
|
SoalData s = new SoalData();
|
|
|
|
s.id = soalSnap.Child("id").Value != null
|
|
? int.Parse(soalSnap.Child("id").Value.ToString()) : 0;
|
|
s.tipe = soalSnap.Child("tipe").Value?.ToString() ?? "pilihan_ganda";
|
|
s.pertanyaan = soalSnap.Child("pertanyaan").Value?.ToString() ?? "";
|
|
s.gambar = soalSnap.Child("gambar").Value?.ToString() ?? "";
|
|
s.audio_pertanyaan = soalSnap.Child("audio_pertanyaan").Value?.ToString() ?? "";
|
|
s.audio_jawaban = soalSnap.Child("audio_jawaban").Value?.ToString() ?? "";
|
|
s.jawaban = soalSnap.Child("jawaban").Value?.ToString() ?? "";
|
|
Debug.Log("pertanyaan : "+s.pertanyaan.ToString());
|
|
|
|
// Ambil pilihan dari pilihan_0, pilihan_1, pilihan_2
|
|
s.pilihan = new string[]
|
|
{
|
|
soalSnap.Child("pilihan_0").Value?.ToString() ?? "",
|
|
soalSnap.Child("pilihan_1").Value?.ToString() ?? "",
|
|
soalSnap.Child("pilihan_2").Value?.ToString() ?? ""
|
|
};
|
|
|
|
// pilihan_gambar kosong (tidak dipakai di struktur Firebase saat ini)
|
|
//s.pilihan_gambar = new string[0];
|
|
|
|
if (soalSnap.Child("pilihan_gambar_0").Exists)
|
|
{
|
|
s.pilihan_gambar = new string[]
|
|
{
|
|
soalSnap.Child("pilihan_gambar_0").Value?.ToString() ?? "",
|
|
soalSnap.Child("pilihan_gambar_1").Value?.ToString() ?? "",
|
|
soalSnap.Child("pilihan_gambar_2").Value?.ToString() ?? ""
|
|
};
|
|
}
|
|
else
|
|
{
|
|
s.pilihan_gambar = new string[0];
|
|
}
|
|
|
|
// Filter tipe suara_hewan
|
|
if (s.tipe != "suara_hewan")
|
|
semua.Add(s);
|
|
}
|
|
|
|
// Shuffle
|
|
for (int i = semua.Count - 1; i > 0; i--)
|
|
{
|
|
int r = Random.Range(0, i + 1);
|
|
SoalData tmp = semua[i];
|
|
semua[i] = semua[r];
|
|
semua[r] = tmp;
|
|
}
|
|
|
|
daftarSoal.Clear();
|
|
indexSoal = 0;
|
|
skor = 0;
|
|
totalSoal = Mathf.Min(5, semua.Count);
|
|
for (int i = 0; i < totalSoal; i++)
|
|
daftarSoal.Add(semua[i]);
|
|
|
|
Debug.Log("✅ Soal berhasil dimuat: " + totalSoal + " soal dari Firebase");
|
|
|
|
// Sembunyikan loading, tampilkan soal
|
|
if (panelLoading != null) panelLoading.SetActive(false);
|
|
panelSoal.SetActive(true);
|
|
TampilkanSoal();
|
|
});
|
|
}
|
|
|
|
string DeteksiFolderGambar(string namaFile)
|
|
{
|
|
if (listAssetWarna.Contains(namaFile))
|
|
return "warna";
|
|
else if (listHewan.Contains(namaFile))
|
|
return "hewan";
|
|
else
|
|
return "buah";
|
|
}
|
|
|
|
void TampilkanSoal()
|
|
{
|
|
if (indexSoal >= totalSoal)
|
|
{
|
|
TampilkanHasil();
|
|
return;
|
|
}
|
|
|
|
StartCoroutine(TampilkanSoalCoroutine());
|
|
}
|
|
|
|
IEnumerator TampilkanSoalCoroutine()
|
|
{
|
|
SoalData s = daftarSoal[indexSoal];
|
|
jawabanBenar = s.jawaban;
|
|
|
|
bool adaPilihanGambar = s.tipe == "pilihan_gambar";
|
|
bool adaGambar = (s.tipe == "pilihan_ganda" || s.tipe == "drag_drop") && !string.IsNullOrEmpty(s.gambar);
|
|
|
|
// STEP 1: Sembunyikan semua
|
|
btnA.gameObject.SetActive(false);
|
|
btnB.gameObject.SetActive(false);
|
|
btnC.gameObject.SetActive(false);
|
|
panelJawabanGambar.SetActive(false);
|
|
imageSoal.gameObject.SetActive(false);
|
|
frameSoal.SetActive(false);
|
|
frameSoalGambar.SetActive(false);
|
|
|
|
yield return null;
|
|
|
|
// STEP 2: Set teks soal
|
|
textSoal.text = s.pertanyaan;
|
|
textSoalGambar.text = s.pertanyaan;
|
|
|
|
// STEP 3: Aktifkan frame
|
|
frameSoal.SetActive(!adaPilihanGambar);
|
|
frameSoalGambar.SetActive(adaPilihanGambar);
|
|
|
|
// STEP 4: Aktifkan panel jawaban
|
|
if (adaPilihanGambar)
|
|
{
|
|
panelJawabanGambar.SetActive(true);
|
|
}
|
|
else
|
|
{
|
|
btnA.gameObject.SetActive(true);
|
|
btnB.gameObject.SetActive(true);
|
|
btnC.gameObject.SetActive(true);
|
|
}
|
|
|
|
yield return null;
|
|
|
|
// STEP 5: Load gambar soal
|
|
if (adaGambar)
|
|
{
|
|
string folderGambarSoal = kategoriAktif == "warna"
|
|
? DeteksiFolderGambar(s.gambar)
|
|
: kategoriAktif;
|
|
|
|
Sprite sp = Resources.Load<Sprite>("Gambar/" + folderGambarSoal + "/" + s.gambar);
|
|
if (sp != null)
|
|
{
|
|
imageSoal.sprite = sp;
|
|
imageSoal.gameObject.SetActive(true);
|
|
}
|
|
else
|
|
Debug.LogWarning("⚠️ Gambar tidak ditemukan: Gambar/" + folderGambarSoal + "/" + s.gambar);
|
|
}
|
|
|
|
// STEP 6: Setup tombol jawaban gambar
|
|
if (adaPilihanGambar && s.pilihan_gambar != null && s.pilihan_gambar.Length >= 3)
|
|
{
|
|
Button[] btns = { btnGambarA, btnGambarB, btnGambarC };
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
Image img = btns[i].GetComponent<Image>();
|
|
string namaSprite = s.pilihan_gambar[i];
|
|
string folderSprite = kategoriAktif == "warna"
|
|
? DeteksiFolderGambar(namaSprite) : kategoriAktif;
|
|
|
|
Sprite sp = Resources.Load<Sprite>("Gambar/" + folderSprite + "/" + namaSprite);
|
|
if (sp != null) img.sprite = sp;
|
|
|
|
string jwb = s.pilihan[i];
|
|
btns[i].onClick.RemoveAllListeners();
|
|
btns[i].onClick.AddListener(() => CekJawaban(jwb));
|
|
btns[i].interactable = true;
|
|
}
|
|
}
|
|
|
|
// STEP 7: Isi teks & listener tombol pilihan ganda
|
|
if (!adaPilihanGambar)
|
|
{
|
|
textA.text = s.pilihan != null && s.pilihan.Length > 0 ? s.pilihan[0] : "-";
|
|
textB.text = s.pilihan != null && s.pilihan.Length > 1 ? s.pilihan[1] : "-";
|
|
textC.text = s.pilihan != null && s.pilihan.Length > 2 ? s.pilihan[2] : "-";
|
|
|
|
btnA.onClick.RemoveAllListeners();
|
|
btnB.onClick.RemoveAllListeners();
|
|
btnC.onClick.RemoveAllListeners();
|
|
|
|
btnA.onClick.AddListener(() => CekJawaban(textA.text));
|
|
btnB.onClick.AddListener(() => CekJawaban(textB.text));
|
|
btnC.onClick.AddListener(() => CekJawaban(textC.text));
|
|
|
|
btnA.interactable = true;
|
|
btnB.interactable = true;
|
|
btnC.interactable = true;
|
|
|
|
// STEP 8: Force rebuild layout
|
|
yield return null;
|
|
Canvas.ForceUpdateCanvases();
|
|
RectTransform parentLayout = btnA.transform.parent.GetComponent<RectTransform>();
|
|
if (parentLayout != null)
|
|
LayoutRebuilder.ForceRebuildLayoutImmediate(parentLayout);
|
|
if (parentLayout != null && parentLayout.parent != null)
|
|
{
|
|
RectTransform grandParent = parentLayout.parent.GetComponent<RectTransform>();
|
|
if (grandParent != null)
|
|
LayoutRebuilder.ForceRebuildLayoutImmediate(grandParent);
|
|
}
|
|
}
|
|
|
|
// STEP 9: Audio pertanyaan
|
|
clipPertanyaan = null;
|
|
if (!string.IsNullOrEmpty(s.audio_pertanyaan))
|
|
{
|
|
clipPertanyaan = Resources.Load<AudioClip>("Audio/" + s.audio_pertanyaan);
|
|
if (clipPertanyaan != null)
|
|
StartCoroutine(AutoPlayPertanyaan());
|
|
}
|
|
|
|
textSkor.text = "Skor: " + skor;
|
|
Debug.Log("📝 Soal " + (indexSoal + 1) + " | Tipe: " + s.tipe + " | Jawaban: " + s.jawaban);
|
|
}
|
|
|
|
IEnumerator AutoPlayPertanyaan()
|
|
{
|
|
yield return new WaitForSeconds(0.5f);
|
|
if (clipPertanyaan != null)
|
|
{
|
|
audioSource.Stop();
|
|
audioSource.clip = clipPertanyaan;
|
|
audioSource.Play();
|
|
}
|
|
}
|
|
|
|
void CekJawaban(string jawaban)
|
|
{
|
|
btnA.interactable = false;
|
|
btnB.interactable = false;
|
|
btnC.interactable = false;
|
|
btnGambarA.interactable = false;
|
|
btnGambarB.interactable = false;
|
|
btnGambarC.interactable = false;
|
|
|
|
audioSource.Stop();
|
|
StopCoroutine("AutoPlayPertanyaan");
|
|
|
|
bool benar = (jawaban == jawabanBenar);
|
|
|
|
if (benar)
|
|
{
|
|
skor += 10;
|
|
textSkor.text = "Skor: " + skor;
|
|
Debug.Log("✅ Benar! Skor: " + skor);
|
|
StartCoroutine(BenarDenganAudio());
|
|
}
|
|
else
|
|
{
|
|
Debug.Log("❌ Salah! Jawaban benar: " + jawabanBenar);
|
|
StartCoroutine(PopupOtomatis(false));
|
|
}
|
|
}
|
|
|
|
IEnumerator BenarDenganAudio()
|
|
{
|
|
SoalData s = daftarSoal[indexSoal];
|
|
|
|
if (!string.IsNullOrEmpty(s.audio_jawaban))
|
|
{
|
|
AudioClip clipJawaban = Resources.Load<AudioClip>("Audio/" + s.audio_jawaban);
|
|
if (clipJawaban != null)
|
|
{
|
|
audioSource.PlayOneShot(clipJawaban);
|
|
yield return new WaitForSeconds(clipJawaban.length + 0.3f);
|
|
}
|
|
else
|
|
yield return new WaitForSeconds(0.8f);
|
|
}
|
|
else
|
|
yield return new WaitForSeconds(0.5f);
|
|
|
|
StartCoroutine(PopupOtomatis(true));
|
|
}
|
|
|
|
IEnumerator PopupOtomatis(bool benar)
|
|
{
|
|
panelPopup.SetActive(true);
|
|
imgStiker.sprite = benar ? stikerBenar : stikerSalah;
|
|
|
|
string namaAudio = benar ? "suara_benar" : "suara_salah";
|
|
AudioClip clip = Resources.Load<AudioClip>("Audio/" + namaAudio);
|
|
if (clip != null)
|
|
audioSource.PlayOneShot(clip);
|
|
|
|
yield return new WaitForSeconds(1.5f);
|
|
|
|
panelPopup.SetActive(false);
|
|
|
|
btnA.gameObject.SetActive(false);
|
|
btnB.gameObject.SetActive(false);
|
|
btnC.gameObject.SetActive(false);
|
|
btnA.interactable = true;
|
|
btnB.interactable = true;
|
|
btnC.interactable = true;
|
|
|
|
btnGambarA.interactable = true;
|
|
btnGambarB.interactable = true;
|
|
btnGambarC.interactable = true;
|
|
panelJawabanGambar.SetActive(false);
|
|
|
|
yield return null;
|
|
|
|
indexSoal++;
|
|
TampilkanSoal();
|
|
}
|
|
|
|
void TampilkanHasil()
|
|
{
|
|
panelSoal.SetActive(false);
|
|
panelHasil.SetActive(true);
|
|
|
|
int skorMax = totalSoal * 10;
|
|
textSkorAkhir.text = "Skor Akhir : " + skor + " / " + skorMax;
|
|
|
|
bool skorBagus = skor >= 30;
|
|
|
|
if (textPesan != null)
|
|
textPesan.text = skorBagus
|
|
? "Hebat! Kamu Luar Biasa!"
|
|
: "Jangan Menyerah! Coba Lagi!";
|
|
|
|
if (imgStikerHasil != null)
|
|
{
|
|
Sprite stikerDipilih = skorBagus ? stikerHasilBagus : stikerHasilKurang;
|
|
if (stikerDipilih != null)
|
|
imgStikerHasil.sprite = stikerDipilih;
|
|
}
|
|
|
|
int level = PlayerPrefs.GetInt("level", 1);
|
|
btnNextLevel.gameObject.SetActive(level < 3);
|
|
|
|
btnUlang.onClick.RemoveAllListeners();
|
|
btnMenu.onClick.RemoveAllListeners();
|
|
btnNextLevel.onClick.RemoveAllListeners();
|
|
|
|
btnUlang.onClick.AddListener(() => SceneManager.LoadScene("Kuis"));
|
|
btnMenu.onClick.AddListener(() => SceneManager.LoadScene("MainMenu"));
|
|
btnNextLevel.onClick.AddListener(() =>
|
|
{
|
|
PlayerPrefs.SetInt("level", level + 1);
|
|
SceneManager.LoadScene("Kuis");
|
|
});
|
|
}
|
|
|
|
public void KembaliKeMenu()
|
|
{
|
|
SceneManager.LoadScene("MainMenu");
|
|
}
|
|
} |