MEET SKELETON MAY1
Module / System
Responsibility
Script(s)
Folder Structure
Dependencies
Game Management
Controls game flow, scene loading, pause/restart logic
GameManager.csSceneLoader.cs
/Scripts/Core/
UnityEngine.SceneManagement
Difficulty & Character DB
Difficulty selection; loading character pool; ScriptableObject DB
DifficultyManager.csCharacterDB.cs
/Scripts/Data/
Character ScriptableObject assets
UI Management
Main Menu, Settings, No-Ads, dynamic texts/colors
UIManager.csSettingsManager.cs
/Scripts/UI/
Unity UI, (optional) DOTween
Card Animation
Card flip and collection animations
CardFlipController.csCardCollectAnim.cs
/Scripts/UI/Anim/
Unity Animator or DOTween
Character Info (“Remember”)
Display info panel; timer bar; trigger “Start Quiz”
CharacterInfoUI.cs
/Scripts/GameFlow/
UIManager, CharacterDB
Quiz Mechanic
Pull questions; answer validation; per-question timer
QuizManager.csQuestionController.cs
/Scripts/Quiz/
CharacterDB, UIManager
Score Calculation
Implements BrainScore formula, difficulty & speed multipliers
ScoreCalculator.cs
/Scripts/Scoring/
QuizManager
Result & Retry/Collection
Result screen UI; Retry vs. Collection button logic; stats update
ResultUIController.csStatsManager.cs
/Scripts/Results/
ScoreCalculator, DataPersistence
Collection & Leaderboard
Tracks 100% completions; displays global/friends rankings
CollectionManager.csLeaderboardManager.cs
/Scripts/Social/
AnalyticsManager, (future) Server API
Achievement System
Tracks milestones; unlocks badges; popup management
AchievementManager.cs
/Scripts/GameFlow/
DataPersistence
Ads & IAP
Rewarded video ads; No-Ads IAP; UnityPurchasing integration
AdsManager.csIAPManager.cs
/Scripts/Monetization/
Unity Ads, Unity Purchasing
Data Persistence & Analytics
Saves player data (PlayerPrefs/JSON); logs analytic events
DataPersistence.csAnalyticsManager.cs
/Scripts/Core/
Unity Analytics, System.IO (for JSON)
Additional Best Practices & Tips
Folder Structure
Keep /Scripts/ organized into clear subfolders by feature/module.
Group animations, UI, data, and core code separately for clarity.
Dependency Injection & Singletons
Expose managers (e.g. GameManager, QuizManager) as singletons or via DI for easy access and testing.
SOLID Principles
Single Responsibility: Each script does one thing.
Open/Closed: Add new difficulties or animations without altering existing code.
Priority Order
Core (GameManager & SceneLoader)
Data (CharacterDB & DifficultyManager)
UI & Animations (UIManager & CardFlipController)
Gameplay (QuizManager, ScoreCalculator, ResultUIController)
Social & Monetization (LeaderboardManager, AdsManager, IAPManager)
Rough Timeline (MVP)
Week 1: Core & Data modules
Week 2: UI & Animations
Week 3: Quiz & Scoring
Week 4: Result, Leaderboard & Achievements
Week 5: Ads/IAP, Analytics, Testing & Optimization
// =========================================
// File: GameManager.cs
// =========================================
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
public void RestartCurrentScene()
{
SceneLoader.Load(SceneManager.GetActiveScene().name);
}
}
// =========================================
// File: SceneLoader.cs
// =========================================
using UnityEngine.SceneManagement;
public static class SceneLoader
{
public static void Load(string sceneName)
{
SceneManager.LoadScene(sceneName);
}
}
// =========================================
// File: DifficultyManager.cs
// =========================================
using UnityEngine;
public enum Difficulty { Easy, Medium, Hard }
public class DifficultyManager : MonoBehaviour
{
public static DifficultyManager Instance { get; private set; }
public Difficulty CurrentDifficulty { get; private set; }
private void Awake()
{
if (Instance == null) Instance = this;
else Destroy(gameObject);
DontDestroyOnLoad(gameObject);
}
public void Select(Difficulty difficulty)
{
CurrentDifficulty = difficulty;
SceneLoader.Load("CharacterChoose");
}
}
// ========================================= // File: CharacterDB.cs // ========================================= using UnityEngine; using System.Collections.Generic;
[CreateAssetMenu(menuName = "Database/CharacterDB")] public class CharacterDB : ScriptableObject { public List characters;
public List<CharacterData> GetPool(Difficulty diff)
{
return characters.FindAll(c => c.difficulty == diff);
}
public static Sprite GetAvatar(int id)
{
var data = Instance.characters.Find(c => c.id == id);
return data != null ? data.avatar : null;
}
public static CharacterDB Instance;
private void OnEnable() => Instance = this;
}
[System.Serializable] public class CharacterData { public int id; public Difficulty difficulty; public Sprite avatar; public string characterName; public int age; public string hometown; public string education; public string personality; public string hobby; public string favoriteColor; public string strongestTrait; public string specialItem; public string favoriteFood; public List questions; }
[System.Serializable] public class QuestionData { public string question; public List options; public int correctIndex; }
// ========================================= // File: UIManager.cs // ========================================= using UnityEngine; using UnityEngine.UI;
public class UIManager : MonoBehaviour { public static UIManager Instance { get; private set; } private void Awake() { if (Instance==null) Instance=this; else Destroy(gameObject); }
public void OnNoAdsClicked() { /* Show IAP */ }
public void OnSettingsClicked() { SceneLoader.Load("Settings"); }
}
// ========================================= // File: SettingsManager.cs // ========================================= using UnityEngine;
public class SettingsManager : MonoBehaviour { // Volume, language, toggles... public void SetVolume(float value) { AudioListener.volume = value; } }
// ========================================= // File: CardFlipController.cs // ========================================= using UnityEngine; using UnityEngine.UI; using DG.Tweening;
public class CardFlipController : MonoBehaviour { public RectTransform front, back; public Button button; private bool isFlipped;
private void Start()
{
button.onClick.AddListener(Flip);
}
public void Setup(CharacterData data)
{
// assign back visuals
}
void Flip()
{
if (isFlipped) return;
isFlipped = true;
front.DORotate(new Vector3(0,90,0),0.3f).OnComplete(() =>
{
front.gameObject.SetActive(false);
back.gameObject.SetActive(true);
back.localRotation = new Vector3(0,-90,0);
back.DORotate(Vector3.zero,0.3f).OnComplete(() =>
{
// Animation done, proceed
CharacterInfoUI.Instance.LoadInfo(/* id */);
});
});
}
}
// ========================================= // File: CardCollectAnim.cs // ========================================= using UnityEngine;
public class CardCollectAnim : MonoBehaviour { public void Collect(Transform target) { transform.DOMove(target.position,0.5f).OnComplete(() => Destroy(gameObject)); } }
// ========================================= // File: CharacterInfoUI.cs // ========================================= using UnityEngine; using UnityEngine.UI;
public class CharacterInfoUI : MonoBehaviour { public static CharacterInfoUI Instance; public Slider timerBar; public Text nameField, ageField, hometownField, educationField; // ... other fields public Button startQuizButton; private float timeRemaining;
private void Awake() { Instance=this; }
public void LoadInfo(CharacterData data)
{
nameField.text = data.characterName;
ageField.text = data.age.ToString();
hometownField.text = data.hometown;
// ... assign all fields
timeRemaining = QuizManager.Instance.QuestionTime * data.questions.Count;
startQuizButton.onClick.AddListener(() => QuizManager.Instance.StartQuiz(data));
}
private void Update()
{
if (timeRemaining > 0)
{
timeRemaining -= Time.deltaTime;
timerBar.value = timeRemaining / (QuizManager.Instance.QuestionTime * QuizManager.Instance.TotalQuestions);
if (timeRemaining <= 0) startQuizButton.interactable = true;
}
}
}
// ========================================= // File: QuizManager.cs // ========================================= using UnityEngine; using System.Collections.Generic;
public class QuizManager : MonoBehaviour { public static QuizManager Instance; public float QuestionTime = 10f; public int TotalQuestions { get; private set; } private List questions; private int currentIndex;
private void Awake() { Instance=this; }
public void StartQuiz(CharacterData data)
{
questions = new List<QuestionData>(data.questions);
TotalQuestions = questions.Count;
currentIndex = 0;
ShowQuestion();
}
void ShowQuestion()
{
var q = questions[currentIndex];
QuestionController.Instance.Setup(q, currentIndex+1, TotalQuestions);
}
public void RegisterAnswer(int index)
{
bool correct = questions[currentIndex].correctIndex == index;
ScoreCalculator.Instance.RecordAnswer(correct, remainingTime);
currentIndex++;
if (currentIndex < TotalQuestions) ShowQuestion();
else FinishQuiz();
}
public void Timeout()
{
ScoreCalculator.Instance.RecordAnswer(false, 0);
RegisterAnswer(-1);
}
}
// ========================================= // File: QuestionController.cs // ========================================= using UnityEngine; using UnityEngine.UI;
public class QuestionController : MonoBehaviour { public static QuestionController Instance; public Text questionText; public Button[] optionButtons; public Text[] optionTexts;
private void Awake() { Instance=this; }
public void Setup(QuestionData data, int index, int total)
{
questionText.text = $"{data.question}";
for (int i=0; i<optionButtons.Length; i++)
{
optionTexts[i].text = data.options[i];
int idx = i;
optionButtons[i].onClick.RemoveAllListeners();
optionButtons[i].onClick.AddListener(() => QuizManager.Instance.RegisterAnswer(idx));
}
}
}
// ========================================= // File: ScoreCalculator.cs // ========================================= using UnityEngine; using System.Collections.Generic;
public class ScoreCalculator : MonoBehaviour { public static ScoreCalculator Instance; private List timeRatios = new List(); private int correctCount;
private void Awake() { Instance=this; }
public void RecordAnswer(bool correct, float timeLeft)
{
if (correct) correctCount++;
timeRatios.Add(timeLeft / QuizManager.Instance.QuestionTime);
}
public float CalculateTotal()
{
int N = QuizManager.Instance.TotalQuestions;
float basePoint = 100f / N;
float mult = (DifficultyManager.Instance.CurrentDifficulty == Difficulty.Easy) ? 1f :
DifficultyManager.Instance.CurrentDifficulty == Difficulty.Medium ? 1.2f : 1.5f;
float total = 0;
for (int i = 0; i < N; i++)
{
if (i < correctCount)
{
float speedBonus = 1 + timeRatios[i];
total += basePoint * mult * speedBonus;
}
}
return total;
}
}
// ========================================= // File: ResultUIController.cs // ========================================= using UnityEngine; using UnityEngine.UI;
public class ResultUIController : MonoBehaviour { public Text brainScoreText; public Slider progressBar; public Button retryButton, collectionButton, closeButton;
private void Start()
{
float score = ScoreCalculator.Instance.CalculateTotal();
float max = 100f * ((DifficultyManager.Instance.CurrentDifficulty == Difficulty.Easy) ? 2f :
DifficultyManager.Instance.CurrentDifficulty == Difficulty.Medium ? 2.4f : 3f);
float pct = (score / max) * 100f;
brainScoreText.text = $"+{Mathf.RoundToInt(score)} BRAIN SCORE";
progressBar.value = pct/100f;
bool perfect = pct >= 100f;
collectionButton.gameObject.SetActive(perfect);
retryButton.gameObject.SetActive(!perfect);
retryButton.onClick.AddListener(() => SceneLoader.Load("CharacterChoose"));
collectionButton.onClick.AddListener(() => SceneLoader.Load("Collection"));
closeButton.onClick.AddListener(() => SceneLoader.Load("MainMenu"));
StatsManager.Instance.SaveSession(score, pct, QuizManager.Instance.TotalQuestions);
}
}
// ========================================= // File: StatsManager.cs // ========================================= using UnityEngine; using System.Collections.Generic;
public class StatsManager : MonoBehaviour { public static StatsManager Instance; private List pastScores = new List(); public SessionData LastSession;
private void Awake() { Instance=this; }
public void SaveSession(float score, float pct, int qCount)
{
LastSession = new SessionData { BrainScore=score, AccuracyPercent=pct, QuestionCount=qCount, Difficulty=DifficultyManager.Instance.CurrentDifficulty };
pastScores.Add(score);
if (pastScores.Count > 20) pastScores.RemoveAt(0);
}
public SessionData GetLastSession() => LastSession;
public float GetHighest() => pastScores.Count>0 ? Mathf.Max(pastScores.ToArray()) : 0;
public float GetAverage() => pastScores.Count>0 ? Mathf.Sum(pastScores.ToArray())/pastScores.Count : 0;
}
public class SessionData { public float BrainScore; public float AccuracyPercent; public int QuestionCount; public Difficulty Difficulty; }
// ========================================= // File: CollectionManager.cs // ========================================= using UnityEngine; using System.Collections.Generic;
public class CollectionManager : MonoBehaviour { private HashSet collected = new HashSet();
public void Unlock(int id)
{
if (!collected.Contains(id)) collected.Add(id);
}
public List<int> GetCollected() => new List<int>(collected);
}
// ========================================= // File: LeaderboardManager.cs // ========================================= using UnityEngine; using System.Collections.Generic;
public class LeaderboardManager : MonoBehaviour { public void ShowGlobal() { /* fetch from server / } public void ShowFriends() { / fetch friends list */ } }
// ========================================= // File: AchievementManager.cs // ========================================= using UnityEngine;
public class AchievementManager : MonoBehaviour { public void CheckMilestones() { /* e.g. total meets, perfect plays / } public void Unlock(string key) { / show popup */ } }
// ========================================= // File: AdsManager.cs // ========================================= using UnityEngine; using UnityEngine.Advertisements;
public class AdsManager : MonoBehaviour, IUnityAdsListener { void Start() { Advertisement.AddListener(this); Advertisement.Initialize(""); } public void ShowRewarded() { if (Advertisement.IsReady("rewardedVideo")) Advertisement.Show("rewardedVideo"); } public void OnUnityAdsDidFinish(string surfacingId, ShowResult result) { /* grant reward */ } public void OnUnityAdsReady(string surfacingId) {} public void OnUnityAdsDidError(string message) {} public void OnUnityAdsDidStart(string surfacingId) {} }
// ========================================= // File: IAPManager.cs // ========================================= using UnityEngine; using UnityEngine.Purchasing;
public class IAPManager : MonoBehaviour, IStoreListener { private static IStoreController storeController; void Start() { if (storeController == null) { var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance()); builder.AddProduct("no_ads", ProductType.NonConsumable); UnityPurchasing.Initialize(this, builder); } } public void BuyNoAds() { storeController.InitiatePurchase("no_ads"); } public void OnInitialized(IStoreController controller, IExtensionProvider extensions) { storeController = controller; } public void OnInitializeFailed(InitializationFailureReason error) {} public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args) { if (args.purchasedProduct.definition.id == "no_ads") PlayerPrefs.SetInt("NoAds",1); return PurchaseProcessingResult.Complete; } public void OnPurchaseFailed(Product product, PurchaseFailureReason reason) {} }
// ========================================= // File: DataPersistence.cs // ========================================= using UnityEngine; using System.IO;
public class DataPersistence : MonoBehaviour { private string path; private void Awake() { path = Path.Combine(Application.persistentDataPath, "save.json"); } public void Save(T data) { var json = JsonUtility.ToJson(data); File.WriteAllText(path, json); } public T Load() where T : new() { if (!File.Exists(path)) return new T(); return JsonUtility.FromJson(File.ReadAllText(path)); } }
// ========================================= // File: AnalyticsManager.cs // ========================================= using UnityEngine.Analytics; using System.Collections.Generic;
public class AnalyticsManager : MonoBehaviour { public void LogEvent(string name, Dictionary<string, object> parameters = null) { Analytics.CustomEvent(name, parameters); } }