Commit c336f670 authored by Mahmoud Aglan's avatar Mahmoud Aglan

SAAAAADDDD START WORKING

parent 9833ad6e
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -20,7 +20,7 @@ namespace com.al_arcade.cs ...@@ -20,7 +20,7 @@ namespace com.al_arcade.cs
var shader = Shader.Find("Universal Render Pipeline/Lit") var shader = Shader.Find("Universal Render Pipeline/Lit")
?? Shader.Find("Standard") ?? Shader.Find("Unlit/Color"); ?? Shader.Find("Standard") ?? Shader.Find("Unlit/Color");
// ── Body ─────────────────────────────────────────────────
_body = GameObject.CreatePrimitive(PrimitiveType.Capsule); _body = GameObject.CreatePrimitive(PrimitiveType.Capsule);
_body.name = "Body"; _body.name = "Body";
_body.transform.SetParent(transform); _body.transform.SetParent(transform);
...@@ -30,7 +30,7 @@ namespace com.al_arcade.cs ...@@ -30,7 +30,7 @@ namespace com.al_arcade.cs
_bodyMat = new Material(shader) { color = SSColorPalette.Primary }; _bodyMat = new Material(shader) { color = SSColorPalette.Primary };
_body.GetComponent<Renderer>().material = _bodyMat; _body.GetComponent<Renderer>().material = _bodyMat;
// ── Head ─────────────────────────────────────────────────
_head = GameObject.CreatePrimitive(PrimitiveType.Sphere); _head = GameObject.CreatePrimitive(PrimitiveType.Sphere);
_head.name = "Head"; _head.name = "Head";
_head.transform.SetParent(transform); _head.transform.SetParent(transform);
...@@ -40,7 +40,7 @@ namespace com.al_arcade.cs ...@@ -40,7 +40,7 @@ namespace com.al_arcade.cs
_headMat = new Material(shader) { color = SSColorPalette.Accent }; _headMat = new Material(shader) { color = SSColorPalette.Accent };
_head.GetComponent<Renderer>().material = _headMat; _head.GetComponent<Renderer>().material = _headMat;
// ── Eyes ─────────────────────────────────────────────────
var eyeMat = new Material(shader) { color = Color.white }; var eyeMat = new Material(shader) { color = Color.white };
var pupilMat = new Material(shader) { color = SSColorPalette.TextDark }; var pupilMat = new Material(shader) { color = SSColorPalette.TextDark };
...@@ -65,7 +65,7 @@ namespace com.al_arcade.cs ...@@ -65,7 +65,7 @@ namespace com.al_arcade.cs
pupil.GetComponent<Renderer>().material = new Material(pupilMat); pupil.GetComponent<Renderer>().material = new Material(pupilMat);
} }
// ── Mouth ────────────────────────────────────────────────
_mouth = GameObject.CreatePrimitive(PrimitiveType.Cube); _mouth = GameObject.CreatePrimitive(PrimitiveType.Cube);
_mouth.name = "Mouth"; _mouth.name = "Mouth";
_mouth.transform.SetParent(_head.transform); _mouth.transform.SetParent(_head.transform);
...@@ -75,7 +75,7 @@ namespace com.al_arcade.cs ...@@ -75,7 +75,7 @@ namespace com.al_arcade.cs
_mouthMat = new Material(shader) { color = SSColorPalette.Danger }; _mouthMat = new Material(shader) { color = SSColorPalette.Danger };
_mouth.GetComponent<Renderer>().material = _mouthMat; _mouth.GetComponent<Renderer>().material = _mouthMat;
// ── Arms ─────────────────────────────────────────────────
for (int side = -1; side <= 1; side += 2) for (int side = -1; side <= 1; side += 2)
{ {
var arm = GameObject.CreatePrimitive(PrimitiveType.Capsule); var arm = GameObject.CreatePrimitive(PrimitiveType.Capsule);
...@@ -89,7 +89,7 @@ namespace com.al_arcade.cs ...@@ -89,7 +89,7 @@ namespace com.al_arcade.cs
new Material(shader) { color = SSColorPalette.PrimaryDark }; new Material(shader) { color = SSColorPalette.PrimaryDark };
} }
// ── Feet ─────────────────────────────────────────────────
for (int side = -1; side <= 1; side += 2) for (int side = -1; side <= 1; side += 2)
{ {
var foot = GameObject.CreatePrimitive(PrimitiveType.Cube); var foot = GameObject.CreatePrimitive(PrimitiveType.Cube);
...@@ -160,7 +160,6 @@ namespace com.al_arcade.cs ...@@ -160,7 +160,6 @@ namespace com.al_arcade.cs
} }
} }
// ── Reactions ────────────────────────────────────────────────
public void PlayHappy() public void PlayHappy()
{ {
...@@ -183,7 +182,7 @@ namespace com.al_arcade.cs ...@@ -183,7 +182,7 @@ namespace com.al_arcade.cs
.OnComplete(() => _headMat.DOColor(SSColorPalette.Accent, 0.4f)); .OnComplete(() => _headMat.DOColor(SSColorPalette.Accent, 0.4f));
} }
// Reset mouth after delay
DOTween.Sequence().SetDelay(1f).OnComplete(() => DOTween.Sequence().SetDelay(1f).OnComplete(() =>
{ {
if (_mouth == null) return; if (_mouth == null) return;
......
...@@ -44,7 +44,7 @@ namespace com.al_arcade.cs ...@@ -44,7 +44,7 @@ namespace com.al_arcade.cs
Debug.Log(" Correct-the-Sentence Game"); Debug.Log(" Correct-the-Sentence Game");
Debug.Log("═══════════════════════════════════════════"); Debug.Log("═══════════════════════════════════════════");
// ── EventSystem ──────────────────────────────────────────
if (FindObjectOfType<EventSystem>() == null) if (FindObjectOfType<EventSystem>() == null)
{ {
var es = new GameObject("EventSystem"); var es = new GameObject("EventSystem");
...@@ -52,7 +52,7 @@ namespace com.al_arcade.cs ...@@ -52,7 +52,7 @@ namespace com.al_arcade.cs
es.AddComponent<StandaloneInputModule>(); es.AddComponent<StandaloneInputModule>();
} }
// ── Managers ─────────────────────────────────────────────
SSApiManager.EnsureInstance(); SSApiManager.EnsureInstance();
SSAudioManager.EnsureInstance(); SSAudioManager.EnsureInstance();
SSParticleManager.EnsureInstance(); SSParticleManager.EnsureInstance();
...@@ -64,49 +64,49 @@ namespace com.al_arcade.cs ...@@ -64,49 +64,49 @@ namespace com.al_arcade.cs
session.classCode = classCode; session.classCode = classCode;
yield return null; yield return null;
// ── Scene ────────────────────────────────────────────────
BuildScene(); BuildScene();
yield return null; yield return null;
// ── Camera ───────────────────────────────────────────────
SetupCamera(); SetupCamera();
yield return null; yield return null;
// ── Bot ──────────────────────────────────────────────────
var botObj = new GameObject("Bot"); var botObj = new GameObject("Bot");
botObj.transform.position = new Vector3(0, 0, 0); botObj.transform.position = new Vector3(0, 0, 0);
_bot = botObj.AddComponent<CsBotController>(); _bot = botObj.AddComponent<CsBotController>();
_bot.Build(); _bot.Build();
yield return null; yield return null;
// ── Word Container ───────────────────────────────────────
var wordContainer = new GameObject("WordContainer"); var wordContainer = new GameObject("WordContainer");
wordContainer.transform.position = Vector3.zero; wordContainer.transform.position = Vector3.zero;
yield return null; yield return null;
// ── UI ───────────────────────────────────────────────────
var uiObj = new GameObject("CsUI"); var uiObj = new GameObject("CsUI");
_uiManager = uiObj.AddComponent<CsUIManager>(); _uiManager = uiObj.AddComponent<CsUIManager>();
_uiManager.BuildUI(); _uiManager.BuildUI();
// ★ FIX: Initialize the UnityEvent if null
if (_uiManager.onRestartClicked == null) if (_uiManager.onRestartClicked == null)
_uiManager.onRestartClicked = new UnityEvent(); _uiManager.onRestartClicked = new UnityEvent();
yield return null; yield return null;
// ── Game Manager ─────────────────────────────────────────
var gmObj = new GameObject("CsGameManager"); var gmObj = new GameObject("CsGameManager");
_gm = gmObj.AddComponent<CsGameManager>(); _gm = gmObj.AddComponent<CsGameManager>();
_gm.bot = _bot; _gm.bot = _bot;
_gm.uiManager = _uiManager; _gm.uiManager = _uiManager;
_gm.wordContainer = wordContainer.transform; _gm.wordContainer = wordContainer.transform;
// ★ FIX: Safe listener via named method
_uiManager.onRestartClicked.AddListener(OnRestartClicked); _uiManager.onRestartClicked.AddListener(OnRestartClicked);
yield return null; yield return null;
// ── Lighting ─────────────────────────────────────────────
SetupLighting(); SetupLighting();
yield return null; yield return null;
...@@ -118,16 +118,13 @@ namespace com.al_arcade.cs ...@@ -118,16 +118,13 @@ namespace com.al_arcade.cs
yield return new WaitForSeconds(0.5f); yield return new WaitForSeconds(0.5f);
// ── Start ────────────────────────────────────────────────
if (useOfflineTestData) if (useOfflineTestData)
_gm.StartWithQuestions(GetTestQuestions()); _gm.StartWithQuestions(GetTestQuestions());
else else
_gm.StartGame(); _gm.StartGame();
} }
// ════════════════════════════════════════════════════════════
// RESTART HANDLER
// ════════════════════════════════════════════════════════════
private void OnRestartClicked() private void OnRestartClicked()
{ {
...@@ -135,9 +132,6 @@ namespace com.al_arcade.cs ...@@ -135,9 +132,6 @@ namespace com.al_arcade.cs
if (_gm != null) _gm.StartGame(); if (_gm != null) _gm.StartGame();
} }
// ════════════════════════════════════════════════════════════
// SCENE
// ════════════════════════════════════════════════════════════
private void BuildScene() private void BuildScene()
{ {
...@@ -146,7 +140,7 @@ namespace com.al_arcade.cs ...@@ -146,7 +140,7 @@ namespace com.al_arcade.cs
var shader = GetShader(); var shader = GetShader();
// Ground
var ground = GameObject.CreatePrimitive(PrimitiveType.Cube); var ground = GameObject.CreatePrimitive(PrimitiveType.Cube);
ground.name = "Ground"; ground.name = "Ground";
ground.transform.position = new Vector3(0, -0.05f, 0); ground.transform.position = new Vector3(0, -0.05f, 0);
...@@ -154,7 +148,7 @@ namespace com.al_arcade.cs ...@@ -154,7 +148,7 @@ namespace com.al_arcade.cs
ground.GetComponent<Renderer>().material = ground.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(40, 40, 65, 255) }; new Material(shader) { color = new Color32(40, 40, 65, 255) };
// Circular platform
var platform = GameObject.CreatePrimitive(PrimitiveType.Cylinder); var platform = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
platform.name = "Platform"; platform.name = "Platform";
platform.transform.position = new Vector3(0, 0.05f, 0); platform.transform.position = new Vector3(0, 0.05f, 0);
...@@ -163,7 +157,7 @@ namespace com.al_arcade.cs ...@@ -163,7 +157,7 @@ namespace com.al_arcade.cs
platform.GetComponent<Renderer>().material = platform.GetComponent<Renderer>().material =
new Material(shader) { color = SSColorPalette.WithAlpha(SSColorPalette.Primary, 0.6f) }; new Material(shader) { color = SSColorPalette.WithAlpha(SSColorPalette.Primary, 0.6f) };
// Outer ring with pulse
var ring = GameObject.CreatePrimitive(PrimitiveType.Cylinder); var ring = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
ring.name = "Ring"; ring.name = "Ring";
ring.transform.position = new Vector3(0, 0.04f, 0); ring.transform.position = new Vector3(0, 0.04f, 0);
...@@ -175,7 +169,7 @@ namespace com.al_arcade.cs ...@@ -175,7 +169,7 @@ namespace com.al_arcade.cs
ringMat.DOFade(0.1f, 1.5f).SetEase(Ease.InOutSine) ringMat.DOFade(0.1f, 1.5f).SetEase(Ease.InOutSine)
.SetLoops(-1, LoopType.Yoyo); .SetLoops(-1, LoopType.Yoyo);
// Background decorations
for (int i = 0; i < 20; i++) for (int i = 0; i < 20; i++)
{ {
var deco = GameObject.CreatePrimitive( var deco = GameObject.CreatePrimitive(
...@@ -213,7 +207,7 @@ namespace com.al_arcade.cs ...@@ -213,7 +207,7 @@ namespace com.al_arcade.cs
.SetLoops(-1, LoopType.Incremental).SetEase(Ease.Linear); .SetLoops(-1, LoopType.Incremental).SetEase(Ease.Linear);
} }
// Grid lines on ground
for (float x = -9; x <= 9; x += 3) for (float x = -9; x <= 9; x += 3)
{ {
var line = GameObject.CreatePrimitive(PrimitiveType.Cube); var line = GameObject.CreatePrimitive(PrimitiveType.Cube);
...@@ -235,7 +229,7 @@ namespace com.al_arcade.cs ...@@ -235,7 +229,7 @@ namespace com.al_arcade.cs
{ color = SSColorPalette.WithAlpha(SSColorPalette.Primary, 0.08f) }; { color = SSColorPalette.WithAlpha(SSColorPalette.Primary, 0.08f) };
} }
// Floor label
var floorText = new GameObject("FloorLabel"); var floorText = new GameObject("FloorLabel");
floorText.transform.position = new Vector3(0, 0.02f, -3); floorText.transform.position = new Vector3(0, 0.02f, -3);
floorText.transform.rotation = Quaternion.Euler(90, 0, 0); floorText.transform.rotation = Quaternion.Euler(90, 0, 0);
...@@ -276,7 +270,7 @@ namespace com.al_arcade.cs ...@@ -276,7 +270,7 @@ namespace com.al_arcade.cs
dirLight.intensity = 0.9f; dirLight.intensity = 0.9f;
dirLight.shadows = LightShadows.Soft; dirLight.shadows = LightShadows.Soft;
// Accent light on bot
var accent = new GameObject("AccentLight"); var accent = new GameObject("AccentLight");
var aLight = accent.AddComponent<Light>(); var aLight = accent.AddComponent<Light>();
aLight.type = LightType.Point; aLight.type = LightType.Point;
...@@ -285,7 +279,7 @@ namespace com.al_arcade.cs ...@@ -285,7 +279,7 @@ namespace com.al_arcade.cs
aLight.intensity = 0.8f; aLight.intensity = 0.8f;
aLight.color = SSColorPalette.Accent; aLight.color = SSColorPalette.Accent;
// Blue back light
var back = new GameObject("BackLight"); var back = new GameObject("BackLight");
var bLight = back.AddComponent<Light>(); var bLight = back.AddComponent<Light>();
bLight.type = LightType.Point; bLight.type = LightType.Point;
...@@ -298,9 +292,6 @@ namespace com.al_arcade.cs ...@@ -298,9 +292,6 @@ namespace com.al_arcade.cs
RenderSettings.ambientLight = new Color32(40, 42, 60, 255); RenderSettings.ambientLight = new Color32(40, 42, 60, 255);
} }
// ════════════════════════════════════════════════════════════
// TEST DATA
// ════════════════════════════════════════════════════════════
private CsQuestion[] GetTestQuestions() private CsQuestion[] GetTestQuestions()
{ {
......
...@@ -29,7 +29,7 @@ namespace com.al_arcade.cs ...@@ -29,7 +29,7 @@ namespace com.al_arcade.cs
public CsUIManager uiManager; public CsUIManager uiManager;
public Transform wordContainer; public Transform wordContainer;
// State
private CsGameState _state = CsGameState.Idle; private CsGameState _state = CsGameState.Idle;
private CsQuestion[] _questions; private CsQuestion[] _questions;
private int _currentIndex; private int _currentIndex;
...@@ -52,9 +52,6 @@ namespace com.al_arcade.cs ...@@ -52,9 +52,6 @@ namespace com.al_arcade.cs
Instance = this; Instance = this;
} }
// ═══════════════════════════════════════════════════════════
// PUBLIC API
// ═══════════════════════════════════════════════════════════
public void StartGame() => StartCoroutine(StartGameRoutine()); public void StartGame() => StartCoroutine(StartGameRoutine());
...@@ -112,9 +109,6 @@ namespace com.al_arcade.cs ...@@ -112,9 +109,6 @@ namespace com.al_arcade.cs
_onOptionDropped?.Invoke(isCorrectOption); _onOptionDropped?.Invoke(isCorrectOption);
} }
// ═══════════════════════════════════════════════════════════
// GAME FLOW
// ═══════════════════════════════════════════════════════════
private IEnumerator StartGameRoutine() private IEnumerator StartGameRoutine()
{ {
...@@ -277,9 +271,6 @@ namespace com.al_arcade.cs ...@@ -277,9 +271,6 @@ namespace com.al_arcade.cs
yield return new WaitForSeconds(0.8f); yield return new WaitForSeconds(0.8f);
} }
// ═══════════════════════════════════════════════════════════
// WORD SPAWNING — ★ Dynamic sizing, no overlaps
// ═══════════════════════════════════════════════════════════
private void SpawnWords(CsQuestion question) private void SpawnWords(CsQuestion question)
{ {
...@@ -288,23 +279,23 @@ namespace com.al_arcade.cs ...@@ -288,23 +279,23 @@ namespace com.al_arcade.cs
int wordCount = question.words.Length; int wordCount = question.words.Length;
// ★ Calculate total width needed based on actual text lengths
float padding = 0.6f; // extra padding per word card float padding = 0.6f;
float gapBetween = 0.25f; // gap between cards float gapBetween = 0.25f;
float charWidth = 0.32f; // approx width per character float charWidth = 0.32f;
float[] wordWidths = new float[wordCount]; float[] wordWidths = new float[wordCount];
float totalWidth = 0; float totalWidth = 0;
for (int i = 0; i < wordCount; i++) for (int i = 0; i < wordCount; i++)
{ {
float w = question.words[i].word_text.Length * charWidth + padding; float w = question.words[i].word_text.Length * charWidth + padding;
w = Mathf.Max(w, 1.0f); // minimum width w = Mathf.Max(w, 1.0f);
wordWidths[i] = w; wordWidths[i] = w;
totalWidth += w; totalWidth += w;
} }
totalWidth += (wordCount - 1) * gapBetween; totalWidth += (wordCount - 1) * gapBetween;
// ★ If total width exceeds max, scale everything down
float maxAllowedWidth = 14f; float maxAllowedWidth = 14f;
float scaleFactor = 1f; float scaleFactor = 1f;
if (totalWidth > maxAllowedWidth) if (totalWidth > maxAllowedWidth)
...@@ -315,12 +306,12 @@ namespace com.al_arcade.cs ...@@ -315,12 +306,12 @@ namespace com.al_arcade.cs
wordWidths[i] *= scaleFactor; wordWidths[i] *= scaleFactor;
} }
// Base position above bot
Vector3 basePos = bot != null Vector3 basePos = bot != null
? bot.transform.position + Vector3.up * 3.5f ? bot.transform.position + Vector3.up * 3.5f
: Vector3.up * 4f; : Vector3.up * 4f;
// ★ RTL layout: start from right edge
float startX = totalWidth / 2f; float startX = totalWidth / 2f;
float currentX = startX; float currentX = startX;
...@@ -339,7 +330,7 @@ namespace com.al_arcade.cs ...@@ -339,7 +330,7 @@ namespace com.al_arcade.cs
wb.Setup(word.word_text, word.is_wrong, i, wordWidths[i], scaleFactor); wb.Setup(word.word_text, word.is_wrong, i, wordWidths[i], scaleFactor);
_wordButtons.Add(wb); _wordButtons.Add(wb);
// Entrance animation
wordObj.transform.localScale = Vector3.zero; wordObj.transform.localScale = Vector3.zero;
wordObj.transform.DOScale(Vector3.one, 0.4f) wordObj.transform.DOScale(Vector3.one, 0.4f)
.SetDelay(i * 0.08f) .SetDelay(i * 0.08f)
...@@ -378,7 +369,6 @@ namespace com.al_arcade.cs ...@@ -378,7 +369,6 @@ namespace com.al_arcade.cs
_wordClickLocked = false; _wordClickLocked = false;
} }
// ── Victory ──────────────────────────────────────────────────
private IEnumerator VictorySequence() private IEnumerator VictorySequence()
{ {
......
...@@ -23,7 +23,7 @@ namespace com.al_arcade.cs ...@@ -23,7 +23,7 @@ namespace com.al_arcade.cs
private CsWordButton _targetWord; private CsWordButton _targetWord;
private CanvasGroup _canvasGroup; private CanvasGroup _canvasGroup;
// ★ Drag offset: distance from pointer to rect center at grab time
private Vector2 _dragOffset; private Vector2 _dragOffset;
private bool _originalPosCaptured; private bool _originalPosCaptured;
private Camera _canvasCamera; private Camera _canvasCamera;
...@@ -38,20 +38,20 @@ namespace com.al_arcade.cs ...@@ -38,20 +38,20 @@ namespace com.al_arcade.cs
_rect = GetComponent<RectTransform>(); _rect = GetComponent<RectTransform>();
if (_rect == null) _rect = gameObject.AddComponent<RectTransform>(); if (_rect == null) _rect = gameObject.AddComponent<RectTransform>();
// ★ CanvasGroup so we can make it not block raycasts while dragging
_canvasGroup = gameObject.AddComponent<CanvasGroup>(); _canvasGroup = gameObject.AddComponent<CanvasGroup>();
// Determine canvas camera (null for ScreenSpaceOverlay)
_canvasCamera = null; _canvasCamera = null;
if (_canvas != null && _canvas.renderMode != RenderMode.ScreenSpaceOverlay) if (_canvas != null && _canvas.renderMode != RenderMode.ScreenSpaceOverlay)
_canvasCamera = _canvas.worldCamera; _canvasCamera = _canvas.worldCamera;
// Background
_bg = gameObject.AddComponent<Image>(); _bg = gameObject.AddComponent<Image>();
_bg.color = SSColorPalette.Card; _bg.color = SSColorPalette.Card;
_bg.raycastTarget = true; _bg.raycastTarget = true;
// Text
var textObj = new GameObject("Text"); var textObj = new GameObject("Text");
textObj.transform.SetParent(transform, false); textObj.transform.SetParent(transform, false);
var textRect = textObj.AddComponent<RectTransform>(); var textRect = textObj.AddComponent<RectTransform>();
...@@ -72,41 +72,37 @@ namespace com.al_arcade.cs ...@@ -72,41 +72,37 @@ namespace com.al_arcade.cs
_text.fontSizeMax = 22; _text.fontSizeMax = 22;
SSFontManager.Apply(_text); SSFontManager.Apply(_text);
// Entrance animation
transform.localScale = Vector3.one * 0.5f; transform.localScale = Vector3.one * 0.5f;
transform.DOScale(Vector3.one, 0.3f).SetEase(Ease.OutBack); transform.DOScale(Vector3.one, 0.3f).SetEase(Ease.OutBack);
_originalPosCaptured = false; _originalPosCaptured = false;
} }
// ════════════════════════════════════════════════════════════
// DRAG HANDLERS — ★ Cursor-accurate positioning
// ════════════════════════════════════════════════════════════
public void OnBeginDrag(PointerEventData eventData) public void OnBeginDrag(PointerEventData eventData)
{ {
// ★ Capture the original anchored position for return-to-origin
_originalAnchoredPos = _rect.anchoredPosition; _originalAnchoredPos = _rect.anchoredPosition;
_originalPosCaptured = true; _originalPosCaptured = true;
// ★ Calculate offset: where the pointer is relative to the rect's center
// so the card doesn't jump — it stays exactly where you grabbed it
Vector2 localPointerPos; Vector2 localPointerPos;
RectTransformUtility.ScreenPointToLocalPointInRectangle( RectTransformUtility.ScreenPointToLocalPointInRectangle(
_rect.parent as RectTransform, // parent rect _rect.parent as RectTransform,
eventData.position, // screen pointer eventData.position,
_canvasCamera, // camera (null for overlay) _canvasCamera,
out localPointerPos); out localPointerPos);
_dragOffset = _rect.anchoredPosition - localPointerPos; _dragOffset = _rect.anchoredPosition - localPointerPos;
// Visual feedback
_bg.color = SSColorPalette.WithAlpha(SSColorPalette.Primary, 0.3f); _bg.color = SSColorPalette.WithAlpha(SSColorPalette.Primary, 0.3f);
DOTween.Kill(transform, "optScale"); DOTween.Kill(transform, "optScale");
transform.DOScale(Vector3.one * 1.08f, 0.12f).SetId("optScale"); transform.DOScale(Vector3.one * 1.08f, 0.12f).SetId("optScale");
transform.SetAsLastSibling(); transform.SetAsLastSibling();
// ★ Let raycasts pass through while dragging (optional, helps with drop detection)
_canvasGroup.blocksRaycasts = false; _canvasGroup.blocksRaycasts = false;
var audio = SSAudioManager.Instance; var audio = SSAudioManager.Instance;
...@@ -117,7 +113,7 @@ namespace com.al_arcade.cs ...@@ -117,7 +113,7 @@ namespace com.al_arcade.cs
{ {
if (_rect == null || _canvas == null) return; if (_rect == null || _canvas == null) return;
// ★ Convert screen pointer to local position in parent
Vector2 localPointerPos; Vector2 localPointerPos;
RectTransformUtility.ScreenPointToLocalPointInRectangle( RectTransformUtility.ScreenPointToLocalPointInRectangle(
_rect.parent as RectTransform, _rect.parent as RectTransform,
...@@ -125,16 +121,16 @@ namespace com.al_arcade.cs ...@@ -125,16 +121,16 @@ namespace com.al_arcade.cs
_canvasCamera, _canvasCamera,
out localPointerPos); out localPointerPos);
// ★ Apply with the initial offset so the card stays under the finger
_rect.anchoredPosition = localPointerPos + _dragOffset; _rect.anchoredPosition = localPointerPos + _dragOffset;
} }
public void OnEndDrag(PointerEventData eventData) public void OnEndDrag(PointerEventData eventData)
{ {
// Re-enable raycasts
_canvasGroup.blocksRaycasts = true; _canvasGroup.blocksRaycasts = true;
// ★ Check if dropped near the target word
bool droppedOnTarget = false; bool droppedOnTarget = false;
if (_targetWord != null && Camera.main != null) if (_targetWord != null && Camera.main != null)
{ {
...@@ -151,7 +147,7 @@ namespace com.al_arcade.cs ...@@ -151,7 +147,7 @@ namespace com.al_arcade.cs
if (IsCorrect) if (IsCorrect)
{ {
// Snap to target and disappear
DOTween.Kill(transform); DOTween.Kill(transform);
transform.DOScale(Vector3.zero, 0.3f) transform.DOScale(Vector3.zero, 0.3f)
.SetEase(Ease.InBack) .SetEase(Ease.InBack)
...@@ -163,14 +159,14 @@ namespace com.al_arcade.cs ...@@ -163,14 +159,14 @@ namespace com.al_arcade.cs
} }
else else
{ {
// Wrong — shake then return
DOTween.Kill(transform, "optShake"); DOTween.Kill(transform, "optShake");
transform.DOShakePosition(0.3f, 10f, 15).SetId("optShake"); transform.DOShakePosition(0.3f, 10f, 15).SetId("optShake");
} }
} }
} }
// ★ Return to original position (always, unless correct + destroyed)
ReturnToOrigin(); ReturnToOrigin();
} }
...@@ -188,9 +184,6 @@ namespace com.al_arcade.cs ...@@ -188,9 +184,6 @@ namespace com.al_arcade.cs
} }
} }
// ════════════════════════════════════════════════════════════
// HOVER
// ════════════════════════════════════════════════════════════
public void OnPointerEnter(PointerEventData eventData) public void OnPointerEnter(PointerEventData eventData)
{ {
...@@ -205,9 +198,6 @@ namespace com.al_arcade.cs ...@@ -205,9 +198,6 @@ namespace com.al_arcade.cs
_bg.DOColor(SSColorPalette.Card, 0.15f).SetId("optHover"); _bg.DOColor(SSColorPalette.Card, 0.15f).SetId("optHover");
} }
// ════════════════════════════════════════════════════════════
// CLEANUP
// ════════════════════════════════════════════════════════════
private void OnDestroy() private void OnDestroy()
{ {
......
...@@ -12,9 +12,7 @@ namespace com.al_arcade.cs ...@@ -12,9 +12,7 @@ namespace com.al_arcade.cs
[AddComponentMenu("Science Street/CS Prefab Builder")] [AddComponentMenu("Science Street/CS Prefab Builder")]
public class CsPrefabBuilder : MonoBehaviour public class CsPrefabBuilder : MonoBehaviour
{ {
// ════════════════════════════════════════════════════════════
// SCENE
// ════════════════════════════════════════════════════════════
[Header("Scene Environment")] [Header("Scene Environment")]
[Tooltip("Your full environment prefab (ground, platform, decorations, etc). Spawned at origin.")] [Tooltip("Your full environment prefab (ground, platform, decorations, etc). Spawned at origin.")]
...@@ -28,9 +26,6 @@ namespace com.al_arcade.cs ...@@ -28,9 +26,6 @@ namespace com.al_arcade.cs
[SerializeField] private float cameraFOV = 60f; [SerializeField] private float cameraFOV = 60f;
[SerializeField] private Color cameraBgColor = new Color32(30, 30, 60, 255); [SerializeField] private Color cameraBgColor = new Color32(30, 30, 60, 255);
// ════════════════════════════════════════════════════════════
// LIGHTING
// ════════════════════════════════════════════════════════════
[Header("Lighting (leave null to skip)")] [Header("Lighting (leave null to skip)")]
[Tooltip("Drag in your own directional light prefab, or leave null to auto-create.")] [Tooltip("Drag in your own directional light prefab, or leave null to auto-create.")]
...@@ -38,9 +33,6 @@ namespace com.al_arcade.cs ...@@ -38,9 +33,6 @@ namespace com.al_arcade.cs
[SerializeField] private GameObject[] extraLightPrefabs; [SerializeField] private GameObject[] extraLightPrefabs;
[SerializeField] private Color ambientColor = new Color32(40, 42, 60, 255); [SerializeField] private Color ambientColor = new Color32(40, 42, 60, 255);
// ════════════════════════════════════════════════════════════
// BOT
// ════════════════════════════════════════════════════════════
[Header("Bot")] [Header("Bot")]
[Tooltip("Your bot prefab. MUST have CsBotController attached.\n" + [Tooltip("Your bot prefab. MUST have CsBotController attached.\n" +
...@@ -48,18 +40,12 @@ namespace com.al_arcade.cs ...@@ -48,18 +40,12 @@ namespace com.al_arcade.cs
[SerializeField] private GameObject botPrefab; [SerializeField] private GameObject botPrefab;
[SerializeField] private Vector3 botSpawnPosition = Vector3.zero; [SerializeField] private Vector3 botSpawnPosition = Vector3.zero;
// ════════════════════════════════════════════════════════════
// WORD CARD (optional override)
// ════════════════════════════════════════════════════════════
[Header("Word Card (optional)")] [Header("Word Card (optional)")]
[Tooltip("A prefab for the word card. MUST have CsWordButton.\n" + [Tooltip("A prefab for the word card. MUST have CsWordButton.\n" +
"If null, the default procedural card is used.")] "If null, the default procedural card is used.")]
[SerializeField] private GameObject wordCardPrefab; [SerializeField] private GameObject wordCardPrefab;
// ════════════════════════════════════════════════════════════
// UI
// ════════════════════════════════════════════════════════════
[Header("UI Canvas")] [Header("UI Canvas")]
[Tooltip("Your Canvas prefab. MUST have CsUIManager with ALL internal " + [Tooltip("Your Canvas prefab. MUST have CsUIManager with ALL internal " +
...@@ -67,9 +53,6 @@ namespace com.al_arcade.cs ...@@ -67,9 +53,6 @@ namespace com.al_arcade.cs
"If null, a procedural UI is created.")] "If null, a procedural UI is created.")]
[SerializeField] private GameObject canvasPrefab; [SerializeField] private GameObject canvasPrefab;
// ════════════════════════════════════════════════════════════
// AUDIO
// ════════════════════════════════════════════════════════════
[Header("Audio — SFX Clips (all optional)")] [Header("Audio — SFX Clips (all optional)")]
[SerializeField] private AudioClip sfxCorrect; [SerializeField] private AudioClip sfxCorrect;
...@@ -82,9 +65,6 @@ namespace com.al_arcade.cs ...@@ -82,9 +65,6 @@ namespace com.al_arcade.cs
[SerializeField] private AudioClip sfxSlam; [SerializeField] private AudioClip sfxSlam;
[SerializeField] private AudioClip sfxCheer; [SerializeField] private AudioClip sfxCheer;
// ════════════════════════════════════════════════════════════
// PARTICLES
// ════════════════════════════════════════════════════════════
[Header("Particles (all optional)")] [Header("Particles (all optional)")]
[SerializeField] private ParticleSystem correctBurstParticle; [SerializeField] private ParticleSystem correctBurstParticle;
...@@ -93,16 +73,10 @@ namespace com.al_arcade.cs ...@@ -93,16 +73,10 @@ namespace com.al_arcade.cs
[SerializeField] private ParticleSystem sparksParticle; [SerializeField] private ParticleSystem sparksParticle;
[SerializeField] private ParticleSystem starBurstParticle; [SerializeField] private ParticleSystem starBurstParticle;
// ════════════════════════════════════════════════════════════
// FONT
// ════════════════════════════════════════════════════════════
[Header("Arabic Font")] [Header("Arabic Font")]
[SerializeField] private TMP_FontAsset arabicFont; [SerializeField] private TMP_FontAsset arabicFont;
// ════════════════════════════════════════════════════════════
// GAME SETTINGS
// ════════════════════════════════════════════════════════════
[Header("Game Settings")] [Header("Game Settings")]
[SerializeField] private string buildType = "scistreet"; [SerializeField] private string buildType = "scistreet";
...@@ -113,17 +87,11 @@ namespace com.al_arcade.cs ...@@ -113,17 +87,11 @@ namespace com.al_arcade.cs
[Header("Debug")] [Header("Debug")]
[SerializeField] private bool useOfflineTestData = false; [SerializeField] private bool useOfflineTestData = false;
// ════════════════════════════════════════════════════════════
// RUNTIME REFS
// ════════════════════════════════════════════════════════════
private CsGameManager _gm; private CsGameManager _gm;
private CsBotController _bot; private CsBotController _bot;
private CsUIManager _uiManager; private CsUIManager _uiManager;
// ════════════════════════════════════════════════════════════
// STARTUP
// ════════════════════════════════════════════════════════════
private void Start() private void Start()
{ {
...@@ -136,7 +104,7 @@ namespace com.al_arcade.cs ...@@ -136,7 +104,7 @@ namespace com.al_arcade.cs
{ {
Debug.Log("═══ CS Prefab Builder — Starting ═══"); Debug.Log("═══ CS Prefab Builder — Starting ═══");
// ── EventSystem ──────────────────────────────────────
if (FindObjectOfType<EventSystem>() == null) if (FindObjectOfType<EventSystem>() == null)
{ {
var es = new GameObject("EventSystem"); var es = new GameObject("EventSystem");
...@@ -144,7 +112,7 @@ namespace com.al_arcade.cs ...@@ -144,7 +112,7 @@ namespace com.al_arcade.cs
es.AddComponent<StandaloneInputModule>(); es.AddComponent<StandaloneInputModule>();
} }
// ── Shared Managers ──────────────────────────────────
SSApiManager.EnsureInstance(); SSApiManager.EnsureInstance();
SetupAudioManager(); SetupAudioManager();
SetupParticleManager(); SetupParticleManager();
...@@ -156,20 +124,20 @@ namespace com.al_arcade.cs ...@@ -156,20 +124,20 @@ namespace com.al_arcade.cs
session.classCode = classCode; session.classCode = classCode;
yield return null; yield return null;
// ── Camera ───────────────────────────────────────────
SetupCamera(); SetupCamera();
yield return null; yield return null;
// ── Environment ──────────────────────────────────────
if (environmentPrefab != null) if (environmentPrefab != null)
Instantiate(environmentPrefab, Vector3.zero, Quaternion.identity); Instantiate(environmentPrefab, Vector3.zero, Quaternion.identity);
yield return null; yield return null;
// ── Lighting ─────────────────────────────────────────
SetupLighting(); SetupLighting();
yield return null; yield return null;
// ── Bot ──────────────────────────────────────────────
if (botPrefab != null) if (botPrefab != null)
{ {
var botObj = Instantiate(botPrefab, botSpawnPosition, Quaternion.identity); var botObj = Instantiate(botPrefab, botSpawnPosition, Quaternion.identity);
...@@ -180,12 +148,12 @@ namespace com.al_arcade.cs ...@@ -180,12 +148,12 @@ namespace com.al_arcade.cs
Debug.LogError("[CS] Bot prefab is missing CsBotController!"); Debug.LogError("[CS] Bot prefab is missing CsBotController!");
yield break; yield break;
} }
// If your prefab's Build() still needs calling (e.g. for animations):
// _bot.Build(); ← uncomment if the prefab needs runtime init
} }
else else
{ {
// Fallback: procedural bot
var botObj = new GameObject("Bot"); var botObj = new GameObject("Bot");
botObj.transform.position = botSpawnPosition; botObj.transform.position = botSpawnPosition;
_bot = botObj.AddComponent<CsBotController>(); _bot = botObj.AddComponent<CsBotController>();
...@@ -193,12 +161,12 @@ namespace com.al_arcade.cs ...@@ -193,12 +161,12 @@ namespace com.al_arcade.cs
} }
yield return null; yield return null;
// ── Word Container ───────────────────────────────────
var wordContainer = new GameObject("WordContainer"); var wordContainer = new GameObject("WordContainer");
wordContainer.transform.position = Vector3.zero; wordContainer.transform.position = Vector3.zero;
yield return null; yield return null;
// ── UI ───────────────────────────────────────────────
if (canvasPrefab != null) if (canvasPrefab != null)
{ {
var canvasObj = Instantiate(canvasPrefab); var canvasObj = Instantiate(canvasPrefab);
...@@ -209,11 +177,11 @@ namespace com.al_arcade.cs ...@@ -209,11 +177,11 @@ namespace com.al_arcade.cs
Debug.LogError("[CS] Canvas prefab is missing CsUIManager!"); Debug.LogError("[CS] Canvas prefab is missing CsUIManager!");
yield break; yield break;
} }
// Don't call BuildUI — prefab is already set up
} }
else else
{ {
// Fallback: procedural UI
var uiObj = new GameObject("CsUI"); var uiObj = new GameObject("CsUI");
_uiManager = uiObj.AddComponent<CsUIManager>(); _uiManager = uiObj.AddComponent<CsUIManager>();
_uiManager.BuildUI(); _uiManager.BuildUI();
...@@ -223,7 +191,7 @@ namespace com.al_arcade.cs ...@@ -223,7 +191,7 @@ namespace com.al_arcade.cs
_uiManager.onRestartClicked = new UnityEvent(); _uiManager.onRestartClicked = new UnityEvent();
yield return null; yield return null;
// ── Game Manager ─────────────────────────────────────
var gmObj = new GameObject("CsGameManager"); var gmObj = new GameObject("CsGameManager");
_gm = gmObj.AddComponent<CsGameManager>(); _gm = gmObj.AddComponent<CsGameManager>();
_gm.bot = _bot; _gm.bot = _bot;
...@@ -241,9 +209,6 @@ namespace com.al_arcade.cs ...@@ -241,9 +209,6 @@ namespace com.al_arcade.cs
_gm.StartGame(); _gm.StartGame();
} }
// ════════════════════════════════════════════════════════════
// SETUP HELPERS
// ════════════════════════════════════════════════════════════
private void SetupCamera() private void SetupCamera()
{ {
...@@ -302,9 +267,6 @@ namespace com.al_arcade.cs ...@@ -302,9 +267,6 @@ namespace com.al_arcade.cs
if (_gm != null) _gm.StartGame(); if (_gm != null) _gm.StartGame();
} }
// ════════════════════════════════════════════════════════════
// OFFLINE TEST DATA
// ════════════════════════════════════════════════════════════
private CsQuestion[] GetTestQuestions() private CsQuestion[] GetTestQuestions()
{ {
......
...@@ -28,9 +28,6 @@ namespace com.al_arcade.cs ...@@ -28,9 +28,6 @@ namespace com.al_arcade.cs
[Header("Events")] [Header("Events")]
public UnityEvent onRestartClicked; public UnityEvent onRestartClicked;
// ════════════════════════════════════════════════════════════
// BUILD
// ════════════════════════════════════════════════════════════
public void BuildUI() public void BuildUI()
{ {
...@@ -61,14 +58,13 @@ namespace com.al_arcade.cs ...@@ -61,14 +58,13 @@ namespace com.al_arcade.cs
_feedbackGroup.alpha = 0; _feedbackGroup.alpha = 0;
} }
// ── Game HUD ─────────────────────────────────────────────────
private void BuildGameHUD(Transform parent) private void BuildGameHUD(Transform parent)
{ {
var go = MkFull(parent, "GameUI"); var go = MkFull(parent, "GameUI");
_gameUI = go.AddComponent<CanvasGroup>(); _gameUI = go.AddComponent<CanvasGroup>();
// Top bar
var topBar = new GameObject("TopBar"); var topBar = new GameObject("TopBar");
topBar.transform.SetParent(go.transform, false); topBar.transform.SetParent(go.transform, false);
var tbr = topBar.AddComponent<RectTransform>(); var tbr = topBar.AddComponent<RectTransform>();
...@@ -93,7 +89,7 @@ namespace com.al_arcade.cs ...@@ -93,7 +89,7 @@ namespace com.al_arcade.cs
_streakText.color = SSColorPalette.Warning; _streakText.color = SSColorPalette.Warning;
_streakText.alignment = TMPro.TextAlignmentOptions.Center; _streakText.alignment = TMPro.TextAlignmentOptions.Center;
// Progress bar
var pbg = MkFull(go.transform, "ProgressBg"); var pbg = MkFull(go.transform, "ProgressBg");
var pr = pbg.GetComponent<RectTransform>(); var pr = pbg.GetComponent<RectTransform>();
pr.anchorMin = new Vector2(0.25f, 1); pr.anchorMax = new Vector2(0.75f, 1); pr.anchorMin = new Vector2(0.25f, 1); pr.anchorMax = new Vector2(0.75f, 1);
...@@ -115,7 +111,7 @@ namespace com.al_arcade.cs ...@@ -115,7 +111,7 @@ namespace com.al_arcade.cs
_progressText.alignment = TMPro.TextAlignmentOptions.Center; _progressText.alignment = TMPro.TextAlignmentOptions.Center;
_progressText.color = SSColorPalette.WithAlpha(Color.white, 0.5f); _progressText.color = SSColorPalette.WithAlpha(Color.white, 0.5f);
// Hint
_hintText = MkTxt(go.transform, "Hint", "", 18, _hintText = MkTxt(go.transform, "Hint", "", 18,
new Vector2(0.5f, 0), new Vector2(0, 200), new Vector2(600, 40)); new Vector2(0.5f, 0), new Vector2(0, 200), new Vector2(600, 40));
_hintText.alignment = TMPro.TextAlignmentOptions.Center; _hintText.alignment = TMPro.TextAlignmentOptions.Center;
...@@ -123,7 +119,6 @@ namespace com.al_arcade.cs ...@@ -123,7 +119,6 @@ namespace com.al_arcade.cs
_hintText.fontStyle = TMPro.FontStyles.Italic; _hintText.fontStyle = TMPro.FontStyles.Italic;
} }
// ── Options Panel ────────────────────────────────────────────
private void BuildOptionsPanel(Transform parent) private void BuildOptionsPanel(Transform parent)
{ {
...@@ -155,7 +150,6 @@ namespace com.al_arcade.cs ...@@ -155,7 +150,6 @@ namespace com.al_arcade.cs
.color = SSColorPalette.WithAlpha(SSColorPalette.Accent, 0.6f); .color = SSColorPalette.WithAlpha(SSColorPalette.Accent, 0.6f);
} }
// ── Feedback Overlay ─────────────────────────────────────────
private void BuildFeedbackOverlay(Transform parent) private void BuildFeedbackOverlay(Transform parent)
{ {
...@@ -185,7 +179,6 @@ namespace com.al_arcade.cs ...@@ -185,7 +179,6 @@ namespace com.al_arcade.cs
_feedbackText.enableWordWrapping = true; _feedbackText.enableWordWrapping = true;
} }
// ── Loading ──────────────────────────────────────────────────
private void BuildLoadingPanel(Transform parent) private void BuildLoadingPanel(Transform parent)
{ {
...@@ -199,7 +192,6 @@ namespace com.al_arcade.cs ...@@ -199,7 +192,6 @@ namespace com.al_arcade.cs
_loadingText.color = Color.white; _loadingText.color = Color.white;
} }
// ── Error ────────────────────────────────────────────────────
private void BuildErrorPanel(Transform parent) private void BuildErrorPanel(Transform parent)
{ {
...@@ -214,7 +206,6 @@ namespace com.al_arcade.cs ...@@ -214,7 +206,6 @@ namespace com.al_arcade.cs
_errorText.enableWordWrapping = true; _errorText.enableWordWrapping = true;
} }
// ── Results ──────────────────────────────────────────────────
private void BuildResultsPanel(Transform parent) private void BuildResultsPanel(Transform parent)
{ {
...@@ -241,7 +232,7 @@ namespace com.al_arcade.cs ...@@ -241,7 +232,7 @@ namespace com.al_arcade.cs
_resultStats.color = SSColorPalette.WithAlpha(Color.white, 0.7f); _resultStats.color = SSColorPalette.WithAlpha(Color.white, 0.7f);
_resultStats.enableWordWrapping = true; _resultStats.enableWordWrapping = true;
// Restart button
var btn = new GameObject("RestartBtn"); var btn = new GameObject("RestartBtn");
btn.transform.SetParent(go.transform, false); btn.transform.SetParent(go.transform, false);
var br = btn.AddComponent<RectTransform>(); var br = btn.AddComponent<RectTransform>();
...@@ -264,9 +255,6 @@ namespace com.al_arcade.cs ...@@ -264,9 +255,6 @@ namespace com.al_arcade.cs
bt.fontStyle = TMPro.FontStyles.Bold; bt.fontStyle = TMPro.FontStyles.Bold;
} }
// ════════════════════════════════════════════════════════════
// PUBLIC METHODS
// ════════════════════════════════════════════════════════════
public void ShowGameUI() public void ShowGameUI()
{ _gameUI.gameObject.SetActive(true); _gameUI.DOFade(1f, 0.3f); } { _gameUI.gameObject.SetActive(true); _gameUI.DOFade(1f, 0.3f); }
...@@ -333,7 +321,6 @@ namespace com.al_arcade.cs ...@@ -333,7 +321,6 @@ namespace com.al_arcade.cs
} }
} }
// ── Options ──────────────────────────────────────────────────
public void ShowOptions(CsOption[] options, CsWordButton targetWord) public void ShowOptions(CsOption[] options, CsWordButton targetWord)
{ {
...@@ -387,7 +374,6 @@ namespace com.al_arcade.cs ...@@ -387,7 +374,6 @@ namespace com.al_arcade.cs
_activeOptions.Clear(); _activeOptions.Clear();
} }
// ── Loading / Error / Results ────────────────────────────────
public void ShowLoading(string msg) public void ShowLoading(string msg)
{ _loadingUI.gameObject.SetActive(true); if (_loadingText != null) _loadingText.arabicText = msg; _loadingUI.DOFade(1f, 0.3f); } { _loadingUI.gameObject.SetActive(true); if (_loadingText != null) _loadingText.arabicText = msg; _loadingUI.DOFade(1f, 0.3f); }
...@@ -440,7 +426,6 @@ namespace com.al_arcade.cs ...@@ -440,7 +426,6 @@ namespace com.al_arcade.cs
ClearOptions(); ClearOptions();
} }
// ── Helpers ──────────────────────────────────────────────────
private GameObject MkFull(Transform p, string n) private GameObject MkFull(Transform p, string n)
{ {
......
...@@ -22,9 +22,7 @@ namespace com.al_arcade.cs ...@@ -22,9 +22,7 @@ namespace com.al_arcade.cs
private float _cardWidth; private float _cardWidth;
private float _cardHeight = 0.8f; private float _cardHeight = 0.8f;
/// <summary>
/// Setup with explicit width and optional scale factor to prevent overlaps.
/// </summary>
public void Setup(string text, bool isWrong, int index, public void Setup(string text, bool isWrong, int index,
float cardWidth = -1f, float scaleFactor = 1f) float cardWidth = -1f, float scaleFactor = 1f)
{ {
...@@ -32,7 +30,7 @@ namespace com.al_arcade.cs ...@@ -32,7 +30,7 @@ namespace com.al_arcade.cs
IsWrong = isWrong; IsWrong = isWrong;
Index = index; Index = index;
// ★ If width not provided, calculate from text
if (cardWidth <= 0) if (cardWidth <= 0)
_cardWidth = text.Length * 0.32f + 0.6f; _cardWidth = text.Length * 0.32f + 0.6f;
else else
...@@ -49,7 +47,7 @@ namespace com.al_arcade.cs ...@@ -49,7 +47,7 @@ namespace com.al_arcade.cs
var shader = Shader.Find("Universal Render Pipeline/Lit") var shader = Shader.Find("Universal Render Pipeline/Lit")
?? Shader.Find("Standard") ?? Shader.Find("Unlit/Color"); ?? Shader.Find("Standard") ?? Shader.Find("Unlit/Color");
// ── Background card ──────────────────────────────────────
_background = GameObject.CreatePrimitive(PrimitiveType.Cube); _background = GameObject.CreatePrimitive(PrimitiveType.Cube);
_background.name = "WordBg"; _background.name = "WordBg";
_background.transform.SetParent(transform); _background.transform.SetParent(transform);
...@@ -60,11 +58,11 @@ namespace com.al_arcade.cs ...@@ -60,11 +58,11 @@ namespace com.al_arcade.cs
_bgMaterial = new Material(shader) { color = SSColorPalette.NeutralWord }; _bgMaterial = new Material(shader) { color = SSColorPalette.NeutralWord };
_background.GetComponent<Renderer>().material = _bgMaterial; _background.GetComponent<Renderer>().material = _bgMaterial;
// ── Text ─────────────────────────────────────────────────
var textObj = new GameObject("WordText"); var textObj = new GameObject("WordText");
textObj.transform.SetParent(transform); textObj.transform.SetParent(transform);
textObj.transform.localPosition = new Vector3(0, 0, -0.05f); textObj.transform.localPosition = new Vector3(0, 0, -0.05f);
// ★ Face camera (identity rotation — parent handles billboard in LateUpdate if needed)
textObj.transform.localRotation = Quaternion.identity; textObj.transform.localRotation = Quaternion.identity;
_text = textObj.AddComponent<ArabicTextMeshPro>(); _text = textObj.AddComponent<ArabicTextMeshPro>();
...@@ -81,11 +79,11 @@ namespace com.al_arcade.cs ...@@ -81,11 +79,11 @@ namespace com.al_arcade.cs
_text.overflowMode = TMPro.TextOverflowModes.Ellipsis; _text.overflowMode = TMPro.TextOverflowModes.Ellipsis;
SSFontManager.Apply(_text); SSFontManager.Apply(_text);
// ── Collider (matches card exactly) ──────────────────────
_collider = GetComponent<BoxCollider>(); _collider = GetComponent<BoxCollider>();
_collider.size = new Vector3(_cardWidth, _cardHeight, 0.2f); _collider.size = new Vector3(_cardWidth, _cardHeight, 0.2f);
// ── Shadow ───────────────────────────────────────────────
var shadow = GameObject.CreatePrimitive(PrimitiveType.Cube); var shadow = GameObject.CreatePrimitive(PrimitiveType.Cube);
shadow.name = "Shadow"; shadow.name = "Shadow";
shadow.transform.SetParent(transform); shadow.transform.SetParent(transform);
...@@ -120,7 +118,7 @@ namespace com.al_arcade.cs ...@@ -120,7 +118,7 @@ namespace com.al_arcade.cs
if (_bgMaterial != null) DOTween.Kill(_bgMaterial); if (_bgMaterial != null) DOTween.Kill(_bgMaterial);
} }
// ── Billboard towards camera ─────────────────────────────────
private void LateUpdate() private void LateUpdate()
{ {
if (Camera.main == null) return; if (Camera.main == null) return;
...@@ -130,7 +128,6 @@ namespace com.al_arcade.cs ...@@ -130,7 +128,6 @@ namespace com.al_arcade.cs
transform.rotation = Quaternion.LookRotation(awayFromCam); transform.rotation = Quaternion.LookRotation(awayFromCam);
} }
// ── Mouse Interaction ────────────────────────────────────────
private void OnMouseEnter() private void OnMouseEnter()
{ {
...@@ -158,7 +155,6 @@ namespace com.al_arcade.cs ...@@ -158,7 +155,6 @@ namespace com.al_arcade.cs
CsGameManager.Instance.OnWordClicked(this); CsGameManager.Instance.OnWordClicked(this);
} }
// ── Public Methods ───────────────────────────────────────────
public void Highlight(bool isWrongWord) public void Highlight(bool isWrongWord)
{ {
...@@ -173,7 +169,7 @@ namespace com.al_arcade.cs ...@@ -173,7 +169,7 @@ namespace com.al_arcade.cs
if (_text != null) _text.color = Color.white; if (_text != null) _text.color = Color.white;
transform.DOPunchScale(Vector3.one * 0.2f, 0.4f, 8, 0.3f); transform.DOPunchScale(Vector3.one * 0.2f, 0.4f, 8, 0.3f);
// Pulsing glow
DOTween.Sequence() DOTween.Sequence()
.Append(_bgMaterial.DOColor(Color.Lerp(targetColor, Color.white, 0.3f), 0.5f)) .Append(_bgMaterial.DOColor(Color.Lerp(targetColor, Color.white, 0.3f), 0.5f))
.Append(_bgMaterial.DOColor(targetColor, 0.5f)) .Append(_bgMaterial.DOColor(targetColor, 0.5f))
......
...@@ -46,7 +46,7 @@ namespace com.al_arcade.mcq ...@@ -46,7 +46,7 @@ namespace com.al_arcade.mcq
{ {
Debug.Log("══════ MCQ Runner Demo Builder ══════"); Debug.Log("══════ MCQ Runner Demo Builder ══════");
// ── EventSystem ──────────────────────────────────────────
if (FindObjectOfType<EventSystem>() == null) if (FindObjectOfType<EventSystem>() == null)
{ {
var es = new GameObject("EventSystem"); var es = new GameObject("EventSystem");
...@@ -54,7 +54,7 @@ namespace com.al_arcade.mcq ...@@ -54,7 +54,7 @@ namespace com.al_arcade.mcq
es.AddComponent<StandaloneInputModule>(); es.AddComponent<StandaloneInputModule>();
} }
// ── Managers ─────────────────────────────────────────────
SSApiManager.EnsureInstance(); SSApiManager.EnsureInstance();
SSAudioManager.EnsureInstance(); SSAudioManager.EnsureInstance();
SSParticleManager.EnsureInstance(); SSParticleManager.EnsureInstance();
...@@ -66,31 +66,31 @@ namespace com.al_arcade.mcq ...@@ -66,31 +66,31 @@ namespace com.al_arcade.mcq
session.classCode = classCode; session.classCode = classCode;
yield return null; yield return null;
// ── Scene ────────────────────────────────────────────────
BuildScene(); BuildScene();
yield return null; yield return null;
// ── Player ───────────────────────────────────────────────
BuildPlayer(); BuildPlayer();
yield return null; yield return null;
// ── Question Display ─────────────────────────────────────
BuildQuestionDisplay(); BuildQuestionDisplay();
yield return null; yield return null;
// ── UI ───────────────────────────────────────────────────
BuildUI(); BuildUI();
yield return null; yield return null;
// ── Game Manager ─────────────────────────────────────────
BuildGameManager(); BuildGameManager();
yield return null; yield return null;
// ── Camera ───────────────────────────────────────────────
SetupCamera(); SetupCamera();
yield return null; yield return null;
// ── Lighting ─────────────────────────────────────────────
SetupLighting(); SetupLighting();
yield return null; yield return null;
...@@ -98,16 +98,13 @@ namespace com.al_arcade.mcq ...@@ -98,16 +98,13 @@ namespace com.al_arcade.mcq
yield return new WaitForSeconds(0.3f); yield return new WaitForSeconds(0.3f);
// ── Start ────────────────────────────────────────────────
if (useOfflineTestData) if (useOfflineTestData)
_gm.StartWithQuestions(GetTestQuestions()); _gm.StartWithQuestions(GetTestQuestions());
else else
_gm.StartGame(); _gm.StartGame();
} }
// ════════════════════════════════════════════════════════════
// BUILD METHODS
// ════════════════════════════════════════════════════════════
private void BuildScene() private void BuildScene()
{ {
...@@ -116,7 +113,7 @@ namespace com.al_arcade.mcq ...@@ -116,7 +113,7 @@ namespace com.al_arcade.mcq
var shader = GetLitShader(); var shader = GetLitShader();
// Road (centered at x=0, extends along +Z)
var road = GameObject.CreatePrimitive(PrimitiveType.Cube); var road = GameObject.CreatePrimitive(PrimitiveType.Cube);
road.name = "Road"; road.name = "Road";
road.transform.position = new Vector3(0, -0.05f, 200); road.transform.position = new Vector3(0, -0.05f, 200);
...@@ -124,7 +121,7 @@ namespace com.al_arcade.mcq ...@@ -124,7 +121,7 @@ namespace com.al_arcade.mcq
road.GetComponent<Renderer>().material = new Material(shader) road.GetComponent<Renderer>().material = new Material(shader)
{ color = new Color32(60, 60, 80, 255) }; { color = new Color32(60, 60, 80, 255) };
// Lane dividers
for (int i = -1; i <= 1; i += 2) for (int i = -1; i <= 1; i += 2)
{ {
var line = GameObject.CreatePrimitive(PrimitiveType.Cube); var line = GameObject.CreatePrimitive(PrimitiveType.Cube);
...@@ -136,7 +133,7 @@ namespace com.al_arcade.mcq ...@@ -136,7 +133,7 @@ namespace com.al_arcade.mcq
{ color = SSColorPalette.WithAlpha(SSColorPalette.Accent, 0.4f) }; { color = SSColorPalette.WithAlpha(SSColorPalette.Accent, 0.4f) };
} }
// Center dashes
for (float z = 0; z < 400; z += 4) for (float z = 0; z < 400; z += 4)
{ {
var dash = GameObject.CreatePrimitive(PrimitiveType.Cube); var dash = GameObject.CreatePrimitive(PrimitiveType.Cube);
...@@ -148,7 +145,7 @@ namespace com.al_arcade.mcq ...@@ -148,7 +145,7 @@ namespace com.al_arcade.mcq
{ color = SSColorPalette.WithAlpha(Color.white, 0.2f) }; { color = SSColorPalette.WithAlpha(Color.white, 0.2f) };
} }
// Side decorations
for (int side = -1; side <= 1; side += 2) for (int side = -1; side <= 1; side += 2)
{ {
for (float z = 0; z < 400; z += 20) for (float z = 0; z < 400; z += 20)
...@@ -167,7 +164,7 @@ namespace com.al_arcade.mcq ...@@ -167,7 +164,7 @@ namespace com.al_arcade.mcq
} }
} }
// Horizon
var horizon = GameObject.CreatePrimitive(PrimitiveType.Cube); var horizon = GameObject.CreatePrimitive(PrimitiveType.Cube);
horizon.name = "Horizon"; horizon.name = "Horizon";
horizon.transform.position = new Vector3(0, 2f, 500); horizon.transform.position = new Vector3(0, 2f, 500);
...@@ -207,7 +204,7 @@ namespace com.al_arcade.mcq ...@@ -207,7 +204,7 @@ namespace com.al_arcade.mcq
_uiManager = obj.AddComponent<McqUIManager>(); _uiManager = obj.AddComponent<McqUIManager>();
_uiManager.BuildUI(); _uiManager.BuildUI();
// ★ FIX: Initialize the UnityEvent if null
if (_uiManager.onRestartClicked == null) if (_uiManager.onRestartClicked == null)
_uiManager.onRestartClicked = new UnityEvent(); _uiManager.onRestartClicked = new UnityEvent();
} }
...@@ -221,7 +218,7 @@ namespace com.al_arcade.mcq ...@@ -221,7 +218,7 @@ namespace com.al_arcade.mcq
_gm.uiManager = _uiManager; _gm.uiManager = _uiManager;
_gm.gateParent = new GameObject("Gates").transform; _gm.gateParent = new GameObject("Gates").transform;
// ★ FIX: Safe listener — all refs captured after they exist
_uiManager.onRestartClicked.AddListener(OnRestartClicked); _uiManager.onRestartClicked.AddListener(OnRestartClicked);
} }
...@@ -272,9 +269,6 @@ namespace com.al_arcade.mcq ...@@ -272,9 +269,6 @@ namespace com.al_arcade.mcq
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat; RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
} }
// ════════════════════════════════════════════════════════════
// TEST DATA
// ════════════════════════════════════════════════════════════
private McqQuestion[] GetTestQuestions() private McqQuestion[] GetTestQuestions()
{ {
...@@ -306,9 +300,7 @@ namespace com.al_arcade.mcq ...@@ -306,9 +300,7 @@ namespace com.al_arcade.mcq
} }
} }
// ════════════════════════════════════════════════════════════════
// Camera follow — 3rd-person runner (behind + above)
// ════════════════════════════════════════════════════════════════
public class McqCameraFollow : MonoBehaviour public class McqCameraFollow : MonoBehaviour
{ {
public Transform target; public Transform target;
......
...@@ -62,7 +62,6 @@ namespace com.al_arcade.mcq ...@@ -62,7 +62,6 @@ namespace com.al_arcade.mcq
Instance = this; Instance = this;
} }
// ── Public API ───────────────────────────────────────────────
public void StartGame() => StartCoroutine(StartGameRoutine()); public void StartGame() => StartCoroutine(StartGameRoutine());
...@@ -90,7 +89,6 @@ namespace com.al_arcade.mcq ...@@ -90,7 +89,6 @@ namespace com.al_arcade.mcq
if (questionDisplay != null) questionDisplay.Hide(); if (questionDisplay != null) questionDisplay.Hide();
} }
// ── Game Flow ────────────────────────────────────────────────
private IEnumerator StartGameRoutine() private IEnumerator StartGameRoutine()
{ {
...@@ -202,7 +200,7 @@ namespace com.al_arcade.mcq ...@@ -202,7 +200,7 @@ namespace com.al_arcade.mcq
_state = McqGameState.AnswerFeedback; _state = McqGameState.AnswerFeedback;
yield return ProcessAnswer(answered && wasCorrect); yield return ProcessAnswer(answered && wasCorrect);
// Cleanup gates
foreach (var gate in _activeGates) foreach (var gate in _activeGates)
{ {
if (gate != null) if (gate != null)
...@@ -272,9 +270,6 @@ namespace com.al_arcade.mcq ...@@ -272,9 +270,6 @@ namespace com.al_arcade.mcq
yield return new WaitForSeconds(feedbackDisplayTime); yield return new WaitForSeconds(feedbackDisplayTime);
} }
// ══════════════════════════════════════════════════════════
// GATE SPAWNING — always road-center X=0
// ══════════════════════════════════════════════════════════
private void SpawnGates(McqQuestion question) private void SpawnGates(McqQuestion question)
{ {
...@@ -284,7 +279,7 @@ namespace com.al_arcade.mcq ...@@ -284,7 +279,7 @@ namespace com.al_arcade.mcq
float playerZ = player != null ? player.transform.position.z : 0f; float playerZ = player != null ? player.transform.position.z : 0f;
// ★ Gates spawn relative to ROAD center (x=0), NOT player x
Vector3 basePos = new Vector3(0f, 0f, playerZ + gateSpawnDistance); Vector3 basePos = new Vector3(0f, 0f, playerZ + gateSpawnDistance);
float totalWidth = (answers.Length - 1) * gateSpacing; float totalWidth = (answers.Length - 1) * gateSpacing;
...@@ -315,7 +310,6 @@ namespace com.al_arcade.mcq ...@@ -315,7 +310,6 @@ namespace com.al_arcade.mcq
return gate; return gate;
} }
// ── Feedback ─────────────────────────────────────────────────
private void ShowCorrectFeedback(int points) private void ShowCorrectFeedback(int points)
{ {
......
...@@ -42,11 +42,11 @@ namespace com.al_arcade.mcq ...@@ -42,11 +42,11 @@ namespace com.al_arcade.mcq
{ {
var shader = GetShader(); var shader = GetShader();
// ── Pillars ──────────────────────────────────────────────
CreatePillar(Vector3.left * (GateWidth / 2f), "LeftPillar", shader); CreatePillar(Vector3.left * (GateWidth / 2f), "LeftPillar", shader);
CreatePillar(Vector3.right * (GateWidth / 2f), "RightPillar", shader); CreatePillar(Vector3.right * (GateWidth / 2f), "RightPillar", shader);
// ── Top bar ──────────────────────────────────────────────
var topBar = GameObject.CreatePrimitive(PrimitiveType.Cube); var topBar = GameObject.CreatePrimitive(PrimitiveType.Cube);
topBar.name = "TopBar"; topBar.name = "TopBar";
topBar.transform.SetParent(transform); topBar.transform.SetParent(transform);
...@@ -56,7 +56,7 @@ namespace com.al_arcade.mcq ...@@ -56,7 +56,7 @@ namespace com.al_arcade.mcq
var topMat = new Material(shader) { color = SSColorPalette.Primary }; var topMat = new Material(shader) { color = SSColorPalette.Primary };
topBar.GetComponent<Renderer>().material = topMat; topBar.GetComponent<Renderer>().material = topMat;
// ── Answer panel — on the +Z side (back wall of gate archway) ──
_backPanel = GameObject.CreatePrimitive(PrimitiveType.Cube); _backPanel = GameObject.CreatePrimitive(PrimitiveType.Cube);
_backPanel.name = "AnswerPanel"; _backPanel.name = "AnswerPanel";
_backPanel.transform.SetParent(transform); _backPanel.transform.SetParent(transform);
...@@ -66,12 +66,12 @@ namespace com.al_arcade.mcq ...@@ -66,12 +66,12 @@ namespace com.al_arcade.mcq
_panelMaterial = new Material(shader) { color = SSColorPalette.GateDefault }; _panelMaterial = new Material(shader) { color = SSColorPalette.GateDefault };
_backPanel.GetComponent<Renderer>().material = _panelMaterial; _backPanel.GetComponent<Renderer>().material = _panelMaterial;
// ── Text label — identity rotation, readable from -Z (camera side) ──
var textObj = new GameObject("AnswerLabel"); var textObj = new GameObject("AnswerLabel");
textObj.transform.SetParent(transform); textObj.transform.SetParent(transform);
textObj.transform.localPosition = new Vector3(0, GateHeight / 2f, GateDepth / 2f - 0.08f); textObj.transform.localPosition = new Vector3(0, GateHeight / 2f, GateDepth / 2f - 0.08f);
// ★ NO Euler(0,180,0) — TMP 3D text is readable from -Z by default,
// camera is behind player at -Z. Identity rotation = correct.
textObj.transform.localRotation = Quaternion.identity; textObj.transform.localRotation = Quaternion.identity;
_label = textObj.AddComponent<ArabicTextMeshPro>(); _label = textObj.AddComponent<ArabicTextMeshPro>();
...@@ -88,13 +88,13 @@ namespace com.al_arcade.mcq ...@@ -88,13 +88,13 @@ namespace com.al_arcade.mcq
_label.overflowMode = TMPro.TextOverflowModes.Ellipsis; _label.overflowMode = TMPro.TextOverflowModes.Ellipsis;
SSFontManager.Apply(_label); SSFontManager.Apply(_label);
// ── Trigger collider ─────────────────────────────────────
_collider = gameObject.AddComponent<BoxCollider>(); _collider = gameObject.AddComponent<BoxCollider>();
_collider.isTrigger = true; _collider.isTrigger = true;
_collider.center = new Vector3(0, GateHeight / 2f, 0); _collider.center = new Vector3(0, GateHeight / 2f, 0);
_collider.size = new Vector3(GateWidth, GateHeight, GateDepth); _collider.size = new Vector3(GateWidth, GateHeight, GateDepth);
// ── Floor highlight ──────────────────────────────────────
var floor = GameObject.CreatePrimitive(PrimitiveType.Cube); var floor = GameObject.CreatePrimitive(PrimitiveType.Cube);
floor.name = "FloorHighlight"; floor.name = "FloorHighlight";
floor.transform.SetParent(transform); floor.transform.SetParent(transform);
...@@ -147,7 +147,6 @@ namespace com.al_arcade.mcq ...@@ -147,7 +147,6 @@ namespace com.al_arcade.mcq
if (_label != null) DOTween.Kill(_label.transform); if (_label != null) DOTween.Kill(_label.transform);
} }
// ── Collision ────────────────────────────────────────────────
private void OnTriggerEnter(Collider other) private void OnTriggerEnter(Collider other)
{ {
...@@ -157,7 +156,6 @@ namespace com.al_arcade.mcq ...@@ -157,7 +156,6 @@ namespace com.al_arcade.mcq
onPlayerEnter?.Invoke(GateIndex); onPlayerEnter?.Invoke(GateIndex);
} }
// ── Feedback Animations ──────────────────────────────────────
public void PlayCorrectAnimation() public void PlayCorrectAnimation()
{ {
......
...@@ -12,9 +12,7 @@ namespace com.al_arcade.mcq ...@@ -12,9 +12,7 @@ namespace com.al_arcade.mcq
[AddComponentMenu("Science Street/MCQ Prefab Builder")] [AddComponentMenu("Science Street/MCQ Prefab Builder")]
public class McqPrefabBuilder : MonoBehaviour public class McqPrefabBuilder : MonoBehaviour
{ {
// ════════════════════════════════════════════════════════════
// SCENE
// ════════════════════════════════════════════════════════════
[Header("Scene Environment")] [Header("Scene Environment")]
[Tooltip("Your full road/environment prefab. Spawned at origin.")] [Tooltip("Your full road/environment prefab. Spawned at origin.")]
...@@ -26,18 +24,12 @@ namespace com.al_arcade.mcq ...@@ -26,18 +24,12 @@ namespace com.al_arcade.mcq
[SerializeField] private float cameraSmoothSpeed = 6f; [SerializeField] private float cameraSmoothSpeed = 6f;
[SerializeField] private Color cameraBgColor = new Color32(180, 200, 240, 255); [SerializeField] private Color cameraBgColor = new Color32(180, 200, 240, 255);
// ════════════════════════════════════════════════════════════
// LIGHTING
// ════════════════════════════════════════════════════════════
[Header("Lighting")] [Header("Lighting")]
[SerializeField] private GameObject directionalLightPrefab; [SerializeField] private GameObject directionalLightPrefab;
[SerializeField] private GameObject[] extraLightPrefabs; [SerializeField] private GameObject[] extraLightPrefabs;
[SerializeField] private Color ambientColor = new Color32(80, 85, 110, 255); [SerializeField] private Color ambientColor = new Color32(80, 85, 110, 255);
// ════════════════════════════════════════════════════════════
// PLAYER
// ════════════════════════════════════════════════════════════
[Header("Player")] [Header("Player")]
[Tooltip("Your player prefab. MUST have McqPlayerRunner + Rigidbody + Collider.\n" + [Tooltip("Your player prefab. MUST have McqPlayerRunner + Rigidbody + Collider.\n" +
...@@ -45,18 +37,12 @@ namespace com.al_arcade.mcq ...@@ -45,18 +37,12 @@ namespace com.al_arcade.mcq
[SerializeField] private GameObject playerPrefab; [SerializeField] private GameObject playerPrefab;
[SerializeField] private Vector3 playerSpawnPosition = Vector3.zero; [SerializeField] private Vector3 playerSpawnPosition = Vector3.zero;
// ════════════════════════════════════════════════════════════
// GATE
// ════════════════════════════════════════════════════════════
[Header("Answer Gate")] [Header("Answer Gate")]
[Tooltip("Your gate prefab. MUST have McqGateController.\n" + [Tooltip("Your gate prefab. MUST have McqGateController.\n" +
"If null, the procedural gate is used.")] "If null, the procedural gate is used.")]
[SerializeField] private GameObject gatePrefab; [SerializeField] private GameObject gatePrefab;
// ════════════════════════════════════════════════════════════
// QUESTION DISPLAY
// ════════════════════════════════════════════════════════════
[Header("Question Display")] [Header("Question Display")]
[Tooltip("Your question display prefab. MUST have McqQuestionDisplay.\n" + [Tooltip("Your question display prefab. MUST have McqQuestionDisplay.\n" +
...@@ -64,18 +50,12 @@ namespace com.al_arcade.mcq ...@@ -64,18 +50,12 @@ namespace com.al_arcade.mcq
[SerializeField] private GameObject questionDisplayPrefab; [SerializeField] private GameObject questionDisplayPrefab;
[SerializeField] private Vector3 questionDisplayOffset = new Vector3(0, 8, 10); [SerializeField] private Vector3 questionDisplayOffset = new Vector3(0, 8, 10);
// ════════════════════════════════════════════════════════════
// UI
// ════════════════════════════════════════════════════════════
[Header("UI Canvas")] [Header("UI Canvas")]
[Tooltip("Your Canvas prefab. MUST have McqUIManager with all references wired.\n" + [Tooltip("Your Canvas prefab. MUST have McqUIManager with all references wired.\n" +
"If null, procedural UI is created.")] "If null, procedural UI is created.")]
[SerializeField] private GameObject canvasPrefab; [SerializeField] private GameObject canvasPrefab;
// ════════════════════════════════════════════════════════════
// AUDIO
// ════════════════════════════════════════════════════════════
[Header("Audio — SFX Clips")] [Header("Audio — SFX Clips")]
[SerializeField] private AudioClip sfxCorrect; [SerializeField] private AudioClip sfxCorrect;
...@@ -88,9 +68,6 @@ namespace com.al_arcade.mcq ...@@ -88,9 +68,6 @@ namespace com.al_arcade.mcq
[SerializeField] private AudioClip sfxCheer; [SerializeField] private AudioClip sfxCheer;
[SerializeField] private AudioClip sfxCountdown; [SerializeField] private AudioClip sfxCountdown;
// ════════════════════════════════════════════════════════════
// PARTICLES
// ════════════════════════════════════════════════════════════
[Header("Particles")] [Header("Particles")]
[SerializeField] private ParticleSystem correctBurstParticle; [SerializeField] private ParticleSystem correctBurstParticle;
...@@ -99,16 +76,10 @@ namespace com.al_arcade.mcq ...@@ -99,16 +76,10 @@ namespace com.al_arcade.mcq
[SerializeField] private ParticleSystem sparksParticle; [SerializeField] private ParticleSystem sparksParticle;
[SerializeField] private ParticleSystem starBurstParticle; [SerializeField] private ParticleSystem starBurstParticle;
// ════════════════════════════════════════════════════════════
// FONT
// ════════════════════════════════════════════════════════════
[Header("Arabic Font")] [Header("Arabic Font")]
[SerializeField] private TMP_FontAsset arabicFont; [SerializeField] private TMP_FontAsset arabicFont;
// ════════════════════════════════════════════════════════════
// GAME SETTINGS
// ════════════════════════════════════════════════════════════
[Header("Game Settings")] [Header("Game Settings")]
[SerializeField] private string buildType = "scistreet"; [SerializeField] private string buildType = "scistreet";
...@@ -121,9 +92,6 @@ namespace com.al_arcade.mcq ...@@ -121,9 +92,6 @@ namespace com.al_arcade.mcq
[Header("Debug")] [Header("Debug")]
[SerializeField] private bool useOfflineTestData = false; [SerializeField] private bool useOfflineTestData = false;
// ════════════════════════════════════════════════════════════
// RUNTIME
// ════════════════════════════════════════════════════════════
private McqGameManager _gm; private McqGameManager _gm;
private McqPlayerRunner _player; private McqPlayerRunner _player;
...@@ -141,7 +109,7 @@ namespace com.al_arcade.mcq ...@@ -141,7 +109,7 @@ namespace com.al_arcade.mcq
{ {
Debug.Log("═══ MCQ Prefab Builder — Starting ═══"); Debug.Log("═══ MCQ Prefab Builder — Starting ═══");
// ── EventSystem ──────────────────────────────────────
if (FindObjectOfType<EventSystem>() == null) if (FindObjectOfType<EventSystem>() == null)
{ {
var es = new GameObject("EventSystem"); var es = new GameObject("EventSystem");
...@@ -149,7 +117,7 @@ namespace com.al_arcade.mcq ...@@ -149,7 +117,7 @@ namespace com.al_arcade.mcq
es.AddComponent<StandaloneInputModule>(); es.AddComponent<StandaloneInputModule>();
} }
// ── Shared Managers ──────────────────────────────────
SSApiManager.EnsureInstance(); SSApiManager.EnsureInstance();
SetupAudioManager(); SetupAudioManager();
SetupParticleManager(); SetupParticleManager();
...@@ -161,12 +129,12 @@ namespace com.al_arcade.mcq ...@@ -161,12 +129,12 @@ namespace com.al_arcade.mcq
session.classCode = classCode; session.classCode = classCode;
yield return null; yield return null;
// ── Environment ──────────────────────────────────────
if (environmentPrefab != null) if (environmentPrefab != null)
Instantiate(environmentPrefab, Vector3.zero, Quaternion.identity); Instantiate(environmentPrefab, Vector3.zero, Quaternion.identity);
yield return null; yield return null;
// ── Player ───────────────────────────────────────────
if (playerPrefab != null) if (playerPrefab != null)
{ {
var playerObj = Instantiate(playerPrefab, playerSpawnPosition, Quaternion.identity); var playerObj = Instantiate(playerPrefab, playerSpawnPosition, Quaternion.identity);
...@@ -181,7 +149,7 @@ namespace com.al_arcade.mcq ...@@ -181,7 +149,7 @@ namespace com.al_arcade.mcq
yield break; yield break;
} }
// Ensure Rigidbody
var rb = playerObj.GetComponent<Rigidbody>(); var rb = playerObj.GetComponent<Rigidbody>();
if (rb == null) rb = playerObj.AddComponent<Rigidbody>(); if (rb == null) rb = playerObj.AddComponent<Rigidbody>();
rb.isKinematic = true; rb.isKinematic = true;
...@@ -201,7 +169,7 @@ namespace com.al_arcade.mcq ...@@ -201,7 +169,7 @@ namespace com.al_arcade.mcq
} }
yield return null; yield return null;
// ── Question Display ─────────────────────────────────
if (questionDisplayPrefab != null) if (questionDisplayPrefab != null)
{ {
var qdObj = Instantiate(questionDisplayPrefab, var qdObj = Instantiate(questionDisplayPrefab,
...@@ -224,7 +192,7 @@ namespace com.al_arcade.mcq ...@@ -224,7 +192,7 @@ namespace com.al_arcade.mcq
} }
yield return null; yield return null;
// ── UI ───────────────────────────────────────────────
if (canvasPrefab != null) if (canvasPrefab != null)
{ {
var canvasObj = Instantiate(canvasPrefab); var canvasObj = Instantiate(canvasPrefab);
...@@ -247,7 +215,7 @@ namespace com.al_arcade.mcq ...@@ -247,7 +215,7 @@ namespace com.al_arcade.mcq
_uiManager.onRestartClicked = new UnityEvent(); _uiManager.onRestartClicked = new UnityEvent();
yield return null; yield return null;
// ── Game Manager ─────────────────────────────────────
var gmObj = new GameObject("McqGameManager"); var gmObj = new GameObject("McqGameManager");
_gm = gmObj.AddComponent<McqGameManager>(); _gm = gmObj.AddComponent<McqGameManager>();
_gm.player = _player; _gm.player = _player;
...@@ -258,11 +226,11 @@ namespace com.al_arcade.mcq ...@@ -258,11 +226,11 @@ namespace com.al_arcade.mcq
_uiManager.onRestartClicked.AddListener(OnRestartClicked); _uiManager.onRestartClicked.AddListener(OnRestartClicked);
yield return null; yield return null;
// ── Camera ───────────────────────────────────────────
SetupCamera(); SetupCamera();
yield return null; yield return null;
// ── Lighting ─────────────────────────────────────────
SetupLighting(); SetupLighting();
yield return null; yield return null;
...@@ -275,9 +243,6 @@ namespace com.al_arcade.mcq ...@@ -275,9 +243,6 @@ namespace com.al_arcade.mcq
_gm.StartGame(); _gm.StartGame();
} }
// ════════════════════════════════════════════════════════════
// SETUP
// ════════════════════════════════════════════════════════════
private void SetupCamera() private void SetupCamera()
{ {
......
...@@ -25,7 +25,7 @@ namespace com.al_arcade.mcq ...@@ -25,7 +25,7 @@ namespace com.al_arcade.mcq
var shader = Shader.Find("Universal Render Pipeline/Lit") ?? var shader = Shader.Find("Universal Render Pipeline/Lit") ??
Shader.Find("Standard") ?? Shader.Find("Unlit/Color"); Shader.Find("Standard") ?? Shader.Find("Unlit/Color");
// Background panel (Cube)
_panel = GameObject.CreatePrimitive(PrimitiveType.Cube); _panel = GameObject.CreatePrimitive(PrimitiveType.Cube);
_panel.name = "QuestionPanel"; _panel.name = "QuestionPanel";
_panel.transform.SetParent(transform); _panel.transform.SetParent(transform);
...@@ -36,7 +36,7 @@ namespace com.al_arcade.mcq ...@@ -36,7 +36,7 @@ namespace com.al_arcade.mcq
{ color = new Color(0.12f, 0.12f, 0.5f, 0.95f) }; { color = new Color(0.12f, 0.12f, 0.5f, 0.95f) };
_panel.GetComponent<Renderer>().material = panelMat; _panel.GetComponent<Renderer>().material = panelMat;
// Accent bar
var bar = GameObject.CreatePrimitive(PrimitiveType.Cube); var bar = GameObject.CreatePrimitive(PrimitiveType.Cube);
bar.name = "AccentBar"; bar.name = "AccentBar";
bar.transform.SetParent(_panel.transform); bar.transform.SetParent(_panel.transform);
...@@ -46,8 +46,7 @@ namespace com.al_arcade.mcq ...@@ -46,8 +46,7 @@ namespace com.al_arcade.mcq
bar.GetComponent<Renderer>().material = new Material(shader) bar.GetComponent<Renderer>().material = new Material(shader)
{ color = SSColorPalette.Accent }; { color = SSColorPalette.Accent };
// ★ Question text — identity local rotation
// Parent will billboard to face camera
var textObj = new GameObject("QuestionText"); var textObj = new GameObject("QuestionText");
textObj.transform.SetParent(transform); textObj.transform.SetParent(transform);
textObj.transform.localPosition = new Vector3(0, 0.2f, -0.06f); textObj.transform.localPosition = new Vector3(0, 0.2f, -0.06f);
...@@ -65,7 +64,7 @@ namespace com.al_arcade.mcq ...@@ -65,7 +64,7 @@ namespace com.al_arcade.mcq
_questionText.overflowMode = TMPro.TextOverflowModes.Ellipsis; _questionText.overflowMode = TMPro.TextOverflowModes.Ellipsis;
SSFontManager.Apply(_questionText); SSFontManager.Apply(_questionText);
// Source text
var srcObj = new GameObject("SourceText"); var srcObj = new GameObject("SourceText");
srcObj.transform.SetParent(transform); srcObj.transform.SetParent(transform);
srcObj.transform.localPosition = new Vector3(0, -0.85f, -0.06f); srcObj.transform.localPosition = new Vector3(0, -0.85f, -0.06f);
...@@ -104,14 +103,14 @@ namespace com.al_arcade.mcq ...@@ -104,14 +103,14 @@ namespace com.al_arcade.mcq
{ {
if (_playerTransform == null) return; if (_playerTransform == null) return;
// ★ Follow player, stay between player and gates
Vector3 targetPos = _playerTransform.position Vector3 targetPos = _playerTransform.position
+ Vector3.up * floatHeight + Vector3.up * floatHeight
+ Vector3.forward * forwardOffset; + Vector3.forward * forwardOffset;
transform.position = Vector3.Lerp(transform.position, targetPos, transform.position = Vector3.Lerp(transform.position, targetPos,
Time.deltaTime * followSpeed); Time.deltaTime * followSpeed);
// ★ Billboard: face away from camera so text (-Z face) faces camera
if (Camera.main != null) if (Camera.main != null)
{ {
Vector3 awayFromCam = transform.position - Camera.main.transform.position; Vector3 awayFromCam = transform.position - Camera.main.transform.position;
......
...@@ -27,9 +27,6 @@ namespace com.al_arcade.mcq ...@@ -27,9 +27,6 @@ namespace com.al_arcade.mcq
[Header("Events")] [Header("Events")]
public UnityEvent onRestartClicked; public UnityEvent onRestartClicked;
// ════════════════════════════════════════════════════════════
// BUILD
// ════════════════════════════════════════════════════════════
public void BuildUI() public void BuildUI()
{ {
...@@ -82,7 +79,7 @@ namespace com.al_arcade.mcq ...@@ -82,7 +79,7 @@ namespace com.al_arcade.mcq
new Vector2(200, -15), 24, TMPro.TextAlignmentOptions.TopLeft); new Vector2(200, -15), 24, TMPro.TextAlignmentOptions.TopLeft);
_streakText.color = SSColorPalette.Warning; _streakText.color = SSColorPalette.Warning;
// Hearts
_heartIcons = new Image[5]; _heartIcons = new Image[5];
for (int i = 0; i < 5; i++) for (int i = 0; i < 5; i++)
{ {
...@@ -98,7 +95,7 @@ namespace com.al_arcade.mcq ...@@ -98,7 +95,7 @@ namespace com.al_arcade.mcq
h.SetActive(false); h.SetActive(false);
} }
// Progress bar
var progBg = MakeImg(go.transform, "ProgressBg", var progBg = MakeImg(go.transform, "ProgressBg",
new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, 0), new Vector2(1, 0),
SSColorPalette.WithAlpha(Color.white, 0.1f)); SSColorPalette.WithAlpha(Color.white, 0.1f));
...@@ -208,7 +205,7 @@ namespace com.al_arcade.mcq ...@@ -208,7 +205,7 @@ namespace com.al_arcade.mcq
new Vector2(200, 0), 24); new Vector2(200, 0), 24);
_resultStreak.color = SSColorPalette.Accent; _resultStreak.color = SSColorPalette.Accent;
// Restart button
var btnObj = new GameObject("RestartBtn"); var btnObj = new GameObject("RestartBtn");
btnObj.transform.SetParent(go.transform, false); btnObj.transform.SetParent(go.transform, false);
var br = btnObj.AddComponent<RectTransform>(); var br = btnObj.AddComponent<RectTransform>();
...@@ -229,9 +226,6 @@ namespace com.al_arcade.mcq ...@@ -229,9 +226,6 @@ namespace com.al_arcade.mcq
btnTxt.fontStyle = TMPro.FontStyles.Bold; btnTxt.fontStyle = TMPro.FontStyles.Bold;
} }
// ══════════════════════════════════════════════════════════
// PUBLIC
// ══════════════════════════════════════════════════════════
public void ShowGameUI() public void ShowGameUI()
{ {
...@@ -377,7 +371,6 @@ namespace com.al_arcade.mcq ...@@ -377,7 +371,6 @@ namespace com.al_arcade.mcq
_feedbackUI.alpha = 0; _feedbackUI.alpha = 0;
} }
// ── Helpers ──────────────────────────────────────────────────
private GameObject MakePanel(Transform p, string n, Vector2 amin, Vector2 amax) private GameObject MakePanel(Transform p, string n, Vector2 amin, Vector2 amax)
{ {
......
// ============================================================================
// Science Street — API Manager (Singleton)
// Handles all communication with the PHP backend
// ============================================================================
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
...@@ -21,7 +19,7 @@ namespace com.al_arcade.shared ...@@ -21,7 +19,7 @@ namespace com.al_arcade.shared
[SerializeField] private int timeoutSeconds = 15; [SerializeField] private int timeoutSeconds = 15;
// ── Singleton Setup ──────────────────────────────────────────
private void Awake() private void Awake()
{ {
if (Instance != null && Instance != this) if (Instance != null && Instance != this)
...@@ -33,7 +31,7 @@ namespace com.al_arcade.shared ...@@ -33,7 +31,7 @@ namespace com.al_arcade.shared
DontDestroyOnLoad(gameObject); DontDestroyOnLoad(gameObject);
} }
// ── Ensure Instance Exists ───────────────────────────────────
public static SSApiManager EnsureInstance() public static SSApiManager EnsureInstance()
{ {
if (Instance != null) return Instance; if (Instance != null) return Instance;
...@@ -43,9 +41,6 @@ namespace com.al_arcade.shared ...@@ -43,9 +41,6 @@ namespace com.al_arcade.shared
return Instance; return Instance;
} }
// ════════════════════════════════════════════════════════════
// GENERIC REQUEST
// ════════════════════════════════════════════════════════════
public IEnumerator GetRequest(string action, public IEnumerator GetRequest(string action,
Dictionary<string, string> parameters, Dictionary<string, string> parameters,
...@@ -79,9 +74,6 @@ namespace com.al_arcade.shared ...@@ -79,9 +74,6 @@ namespace com.al_arcade.shared
onSuccess?.Invoke(json); onSuccess?.Invoke(json);
} }
// ════════════════════════════════════════════════════════════
// PING
// ════════════════════════════════════════════════════════════
public IEnumerator Ping(Action<bool> onResult) public IEnumerator Ping(Action<bool> onResult)
{ {
...@@ -90,9 +82,6 @@ namespace com.al_arcade.shared ...@@ -90,9 +82,6 @@ namespace com.al_arcade.shared
err => onResult?.Invoke(false)); err => onResult?.Invoke(false));
} }
// ════════════════════════════════════════════════════════════
// VALIDATE CLASS CODE
// ════════════════════════════════════════════════════════════
public IEnumerator ValidateClassCode(string code, public IEnumerator ValidateClassCode(string code,
Action<ClassInfo> onSuccess, Action<ClassInfo> onSuccess,
...@@ -120,9 +109,6 @@ namespace com.al_arcade.shared ...@@ -120,9 +109,6 @@ namespace com.al_arcade.shared
onError); onError);
} }
// ════════════════════════════════════════════════════════════
// GET GRADES
// ════════════════════════════════════════════════════════════
public IEnumerator GetGrades(Action<GradeInfo[]> onSuccess, public IEnumerator GetGrades(Action<GradeInfo[]> onSuccess,
Action<string> onError) Action<string> onError)
...@@ -146,9 +132,6 @@ namespace com.al_arcade.shared ...@@ -146,9 +132,6 @@ namespace com.al_arcade.shared
onError); onError);
} }
// ════════════════════════════════════════════════════════════
// FETCH MCQ QUESTIONS
// ════════════════════════════════════════════════════════════
public IEnumerator FetchMcq(string buildType, string classCode, public IEnumerator FetchMcq(string buildType, string classCode,
int count, int gradeId, int count, int gradeId,
...@@ -183,9 +166,6 @@ namespace com.al_arcade.shared ...@@ -183,9 +166,6 @@ namespace com.al_arcade.shared
onError); onError);
} }
// ════════════════════════════════════════════════════════════
// FETCH T/F QUESTIONS
// ════════════════════════════════════════════════════════════
public IEnumerator FetchTf(string buildType, string classCode, public IEnumerator FetchTf(string buildType, string classCode,
int count, int gradeId, int count, int gradeId,
...@@ -220,9 +200,6 @@ namespace com.al_arcade.shared ...@@ -220,9 +200,6 @@ namespace com.al_arcade.shared
onError); onError);
} }
// ════════════════════════════════════════════════════════════
// FETCH CS QUESTIONS
// ════════════════════════════════════════════════════════════
public IEnumerator FetchCs(string buildType, string classCode, public IEnumerator FetchCs(string buildType, string classCode,
int count, int gradeId, int count, int gradeId,
......
// ============================================================================
// Science Street — Audio Manager
// Plays SFX with optional AudioClip references.
// Works silently if no clips are assigned.
// ============================================================================
using UnityEngine; using UnityEngine;
using System.Collections.Generic; using System.Collections.Generic;
...@@ -77,7 +74,6 @@ namespace com.al_arcade.shared ...@@ -77,7 +74,6 @@ namespace com.al_arcade.shared
return src; return src;
} }
// ── Play Methods ─────────────────────────────────────────────
public void Play(AudioClip clip, float volumeScale = 1f, float pitch = 1f) public void Play(AudioClip clip, float volumeScale = 1f, float pitch = 1f)
{ {
...@@ -104,10 +100,7 @@ namespace com.al_arcade.shared ...@@ -104,10 +100,7 @@ namespace com.al_arcade.shared
public void PlayCheer(float vol = 1f) => Play(sfxCheer, vol); public void PlayCheer(float vol = 1f) => Play(sfxCheer, vol);
public void PlayCountdown(float vol = 0.8f) => Play(sfxCountdown, vol); public void PlayCountdown(float vol = 0.8f) => Play(sfxCountdown, vol);
/// <summary>
/// Plays a generated procedural beep (no AudioClip needed).
/// Useful for demo/prototype mode.
/// </summary>
public void PlayProceduralBeep(float frequency = 440f, float duration = 0.1f, public void PlayProceduralBeep(float frequency = 440f, float duration = 0.1f,
float volume = 0.3f) float volume = 0.3f)
{ {
...@@ -119,7 +112,7 @@ namespace com.al_arcade.shared ...@@ -119,7 +112,7 @@ namespace com.al_arcade.shared
for (int i = 0; i < sampleCount; i++) for (int i = 0; i < sampleCount; i++)
{ {
float t = (float)i / sampleRate; float t = (float)i / sampleRate;
float envelope = 1f - ((float)i / sampleCount); // fade out float envelope = 1f - ((float)i / sampleCount);
data[i] = Mathf.Sin(2f * Mathf.PI * frequency * t) * envelope * volume; data[i] = Mathf.Sin(2f * Mathf.PI * frequency * t) * envelope * volume;
} }
...@@ -135,10 +128,10 @@ namespace com.al_arcade.shared ...@@ -135,10 +128,10 @@ namespace com.al_arcade.shared
public void PlayClickBeep() => PlayProceduralBeep(660f, 0.05f, 0.15f); public void PlayClickBeep() => PlayProceduralBeep(660f, 0.05f, 0.15f);
public void PlaySuccessJingle() public void PlaySuccessJingle()
{ {
// Three ascending tones
PlayProceduralBeep(523f, 0.12f, 0.2f); // C5 PlayProceduralBeep(523f, 0.12f, 0.2f);
StartCoroutine(DelayedBeep(660f, 0.12f, 0.1f)); // E5 StartCoroutine(DelayedBeep(660f, 0.12f, 0.1f));
StartCoroutine(DelayedBeep(784f, 0.2f, 0.2f)); // G5 StartCoroutine(DelayedBeep(784f, 0.2f, 0.2f));
} }
public void PlayFailBuzz() public void PlayFailBuzz()
......
// ============================================================================
// Science Street — Shared Color Palette
// AL-Arcade Game Dev Team
// ============================================================================
using UnityEngine; using UnityEngine;
namespace com.al_arcade.shared namespace com.al_arcade.shared
{ {
/// <summary>
/// Central color definitions matching the Science Street brand.
/// </summary>
public static class SSColorPalette public static class SSColorPalette
{ {
// ── Brand Colors ─────────────────────────────────────────────
public static readonly Color Primary = new Color32(48, 48, 208, 255); // #3030D0 public static readonly Color Primary = new Color32(48, 48, 208, 255);
public static readonly Color PrimaryDark = new Color32(32, 32, 168, 255); // #2020A8 public static readonly Color PrimaryDark = new Color32(32, 32, 168, 255);
public static readonly Color PrimaryLight = new Color32(80, 80, 220, 255); // #5050DC public static readonly Color PrimaryLight = new Color32(80, 80, 220, 255);
public static readonly Color Accent = new Color32(254, 215, 0, 255); // #FED700 public static readonly Color Accent = new Color32(254, 215, 0, 255);
public static readonly Color AccentDark = new Color32(212, 180, 0, 255); // #D4B400 public static readonly Color AccentDark = new Color32(212, 180, 0, 255);
// ── Feedback Colors ──────────────────────────────────────────
public static readonly Color Success = new Color32(16, 185, 129, 255); // #10B981 public static readonly Color Success = new Color32(16, 185, 129, 255);
public static readonly Color SuccessLight = new Color32(209, 250, 229, 255); // #D1FAE5 public static readonly Color SuccessLight = new Color32(209, 250, 229, 255);
public static readonly Color Danger = new Color32(239, 68, 68, 255); // #EF4444 public static readonly Color Danger = new Color32(239, 68, 68, 255);
public static readonly Color DangerLight = new Color32(254, 226, 226, 255); // #FEE2E2 public static readonly Color DangerLight = new Color32(254, 226, 226, 255);
public static readonly Color Warning = new Color32(245, 158, 11, 255); // #F59E0B public static readonly Color Warning = new Color32(245, 158, 11, 255);
public static readonly Color Info = new Color32(59, 130, 246, 255); // #3B82F6 public static readonly Color Info = new Color32(59, 130, 246, 255);
// ── UI Colors ────────────────────────────────────────────────
public static readonly Color Background = new Color32(238, 240, 248, 255); // #EEF0F8 public static readonly Color Background = new Color32(238, 240, 248, 255);
public static readonly Color Card = Color.white; public static readonly Color Card = Color.white;
public static readonly Color TextDark = new Color32(26, 26, 46, 255); // #1A1A2E public static readonly Color TextDark = new Color32(26, 26, 46, 255);
public static readonly Color TextMuted = new Color32(107, 114, 128, 255); // #6B7280 public static readonly Color TextMuted = new Color32(107, 114, 128, 255);
public static readonly Color Border = new Color32(229, 231, 235, 255); // #E5E7EB public static readonly Color Border = new Color32(229, 231, 235, 255);
// ── Game-specific ────────────────────────────────────────────
public static readonly Color GateCorrect = new Color32(16, 185, 129, 255); public static readonly Color GateCorrect = new Color32(16, 185, 129, 255);
public static readonly Color GateWrong = new Color32(239, 68, 68, 255); public static readonly Color GateWrong = new Color32(239, 68, 68, 255);
public static readonly Color GateDefault = new Color32(80, 80, 220, 255); public static readonly Color GateDefault = new Color32(80, 80, 220, 255);
public static readonly Color TrueGreen = new Color32(34, 197, 94, 255); // #22C55E public static readonly Color TrueGreen = new Color32(34, 197, 94, 255);
public static readonly Color FalseRed = new Color32(239, 68, 68, 255); // #EF4444 public static readonly Color FalseRed = new Color32(239, 68, 68, 255);
public static readonly Color WrongWord = new Color32(239, 68, 68, 255); public static readonly Color WrongWord = new Color32(239, 68, 68, 255);
public static readonly Color CorrectWord = new Color32(16, 185, 129, 255); public static readonly Color CorrectWord = new Color32(16, 185, 129, 255);
public static readonly Color NeutralWord = new Color32(248, 249, 252, 255); public static readonly Color NeutralWord = new Color32(248, 249, 252, 255);
// ── Helpers ──────────────────────────────────────────────────
public static Color WithAlpha(Color c, float a) public static Color WithAlpha(Color c, float a)
{ {
return new Color(c.r, c.g, c.b, a); return new Color(c.r, c.g, c.b, a);
......
// ============================================================================
// Science Street — Shared Data Models
// JSON deserialization targets for API responses
// ============================================================================
using System; using System;
using UnityEngine; using UnityEngine;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace com.al_arcade.shared namespace com.al_arcade.shared
{ {
// ── API Wrapper Responses ────────────────────────────────────────
[Serializable] [Serializable]
public class ApiResponse<T> public class ApiResponse<T>
...@@ -37,7 +35,6 @@ namespace com.al_arcade.shared ...@@ -37,7 +35,6 @@ namespace com.al_arcade.shared
public ClassInfo classInfo; public ClassInfo classInfo;
} }
// ── Core Data Types ──────────────────────────────────────────────
[Serializable] [Serializable]
public class GradeInfo public class GradeInfo
...@@ -58,30 +55,26 @@ namespace com.al_arcade.shared ...@@ -58,30 +55,26 @@ namespace com.al_arcade.shared
public string teacher_name; public string teacher_name;
} }
// ── MCQ ──────────────────────────────────────────────────────────
[Serializable] [Serializable]
public class McqQuestion public class McqQuestion
{ {
public string id; public string id;
public string question_text; public string question_text;
public string answer1; // always correct public string answer1;
public string answer2; public string answer2;
public string answer3; public string answer3;
public string answer4; public string answer4;
public string source; public string source;
public string grade_name; public string grade_name;
/// <summary>
/// Returns all 4 answers shuffled. Sets correctIndex to the
/// position of the correct answer in the shuffled array.
/// </summary>
public string[] GetShuffledAnswers(out int correctIndex) public string[] GetShuffledAnswers(out int correctIndex)
{ {
string[] arr = { answer1, answer2, answer3, answer4 }; string[] arr = { answer1, answer2, answer3, answer4 };
string correct = answer1; string correct = answer1;
// Fisher-Yates shuffle
for (int i = arr.Length - 1; i > 0; i--) for (int i = arr.Length - 1; i > 0; i--)
{ {
int j = UnityEngine.Random.Range(0, i + 1); int j = UnityEngine.Random.Range(0, i + 1);
...@@ -93,7 +86,6 @@ namespace com.al_arcade.shared ...@@ -93,7 +86,6 @@ namespace com.al_arcade.shared
} }
} }
// ── True/False ───────────────────────────────────────────────────
[Serializable] [Serializable]
public class TfQuestion public class TfQuestion
...@@ -105,7 +97,6 @@ namespace com.al_arcade.shared ...@@ -105,7 +97,6 @@ namespace com.al_arcade.shared
public string grade_name; public string grade_name;
} }
// ── Correct Sentence ─────────────────────────────────────────────
[Serializable] [Serializable]
public class CsQuestion public class CsQuestion
......
...@@ -2,10 +2,8 @@ using UnityEngine; ...@@ -2,10 +2,8 @@ using UnityEngine;
namespace com.al_arcade.shared namespace com.al_arcade.shared
{ {
/// <summary>
/// Global Arabic font manager. Set the font once from any DemoBuilder,
/// then all text components pick it up via Apply().
/// </summary>
public static class SSFontManager public static class SSFontManager
{ {
private static TMPro.TMP_FontAsset _font; private static TMPro.TMP_FontAsset _font;
......
// ============================================================================
// Science Street — Game Session Manager
// Holds current game session state (build type, class code, etc.)
// ============================================================================
using System; using System;
using System.Collections; using System.Collections;
using UnityEngine; using UnityEngine;
...@@ -23,14 +21,14 @@ namespace com.al_arcade.shared ...@@ -23,14 +21,14 @@ namespace com.al_arcade.shared
[Tooltip("Number of questions to fetch")] [Tooltip("Number of questions to fetch")]
public int questionCount = 10; public int questionCount = 10;
// ── Runtime State (Teacher Build) ────────────────────────────
[HideInInspector] public string classCode = ""; [HideInInspector] public string classCode = "";
[HideInInspector] public string className = ""; [HideInInspector] public string className = "";
[HideInInspector] public string teacherName = ""; [HideInInspector] public string teacherName = "";
[HideInInspector] public string gradeName = ""; [HideInInspector] public string gradeName = "";
[HideInInspector] public bool isClassValidated = false; [HideInInspector] public bool isClassValidated = false;
// ── Events ───────────────────────────────────────────────────
[Header("Events")] [Header("Events")]
public UnityEvent onSessionReady; public UnityEvent onSessionReady;
public UnityEvent<string> onSessionError; public UnityEvent<string> onSessionError;
...@@ -55,9 +53,7 @@ namespace com.al_arcade.shared ...@@ -55,9 +53,7 @@ namespace com.al_arcade.shared
return Instance; return Instance;
} }
/// <summary>
/// Validates a teacher class code. On success, stores all class info.
/// </summary>
public IEnumerator ValidateAndStartTeacherSession(string code, public IEnumerator ValidateAndStartTeacherSession(string code,
Action onSuccess, Action<string> onError) Action onSuccess, Action<string> onError)
{ {
...@@ -89,9 +85,7 @@ namespace com.al_arcade.shared ...@@ -89,9 +85,7 @@ namespace com.al_arcade.shared
}); });
} }
/// <summary>
/// Starts a Science Street (public) session.
/// </summary>
public void StartSciStreetSession(int grade = 0) public void StartSciStreetSession(int grade = 0)
{ {
buildType = "scistreet"; buildType = "scistreet";
...@@ -102,9 +96,7 @@ namespace com.al_arcade.shared ...@@ -102,9 +96,7 @@ namespace com.al_arcade.shared
onSessionReady?.Invoke(); onSessionReady?.Invoke();
} }
/// <summary>
/// Resets the session to defaults.
/// </summary>
public void ResetSession() public void ResetSession()
{ {
buildType = "scistreet"; buildType = "scistreet";
......
// ============================================================================
// Science Street — Particle Effect Manager
// Creates particle effects programmatically. Works without any prefabs.
// ============================================================================
using UnityEngine; using UnityEngine;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
...@@ -41,7 +39,6 @@ namespace com.al_arcade.shared ...@@ -41,7 +39,6 @@ namespace com.al_arcade.shared
return Instance; return Instance;
} }
// ── Public Play Methods ──────────────────────────────────────
public void PlayCorrectBurst(Vector3 position) public void PlayCorrectBurst(Vector3 position)
{ {
...@@ -105,7 +102,6 @@ namespace com.al_arcade.shared ...@@ -105,7 +102,6 @@ namespace com.al_arcade.shared
PlayConfetti(pos); PlayConfetti(pos);
} }
// ── Procedural Particle Creation ─────────────────────────────
private void PlayProceduralBurst(Vector3 pos, Color startColor, private void PlayProceduralBurst(Vector3 pos, Color startColor,
Color endColor, int count, float lifetime) Color endColor, int count, float lifetime)
...@@ -141,7 +137,7 @@ namespace com.al_arcade.shared ...@@ -141,7 +137,7 @@ namespace com.al_arcade.shared
new Keyframe(0f, 1f), new Keyframe(0f, 1f),
new Keyframe(1f, 0f))); new Keyframe(1f, 0f)));
// Use default material
var renderer = go.GetComponent<ParticleSystemRenderer>(); var renderer = go.GetComponent<ParticleSystemRenderer>();
renderer.material = GetParticleMaterial(); renderer.material = GetParticleMaterial();
...@@ -169,7 +165,7 @@ namespace com.al_arcade.shared ...@@ -169,7 +165,7 @@ namespace com.al_arcade.shared
main.loop = false; main.loop = false;
main.duration = 0.2f; main.duration = 0.2f;
// Random color from our palette
var gradient = new Gradient(); var gradient = new Gradient();
gradient.SetKeys( gradient.SetKeys(
new[] { new[] {
...@@ -208,7 +204,6 @@ namespace com.al_arcade.shared ...@@ -208,7 +204,6 @@ namespace com.al_arcade.shared
Destroy(go, 5f); Destroy(go, 5f);
} }
// ── Helpers ──────────────────────────────────────────────────
private void SpawnPrefab(ParticleSystem prefab, Vector3 pos) private void SpawnPrefab(ParticleSystem prefab, Vector3 pos)
{ {
...@@ -225,7 +220,7 @@ namespace com.al_arcade.shared ...@@ -225,7 +220,7 @@ namespace com.al_arcade.shared
{ {
if (_cachedMat != null) return _cachedMat; if (_cachedMat != null) return _cachedMat;
// Try to find the default particle shader
Shader shader = Shader.Find("Particles/Standard Unlit"); Shader shader = Shader.Find("Particles/Standard Unlit");
if (shader == null) shader = Shader.Find("Universal Render Pipeline/Particles/Unlit"); if (shader == null) shader = Shader.Find("Universal Render Pipeline/Particles/Unlit");
if (shader == null) shader = Shader.Find("Mobile/Particles/Alpha Blended"); if (shader == null) shader = Shader.Find("Mobile/Particles/Alpha Blended");
...@@ -233,7 +228,7 @@ namespace com.al_arcade.shared ...@@ -233,7 +228,7 @@ namespace com.al_arcade.shared
if (shader == null) shader = Shader.Find("Unlit/Color"); if (shader == null) shader = Shader.Find("Unlit/Color");
_cachedMat = new Material(shader); _cachedMat = new Material(shader);
_cachedMat.SetFloat("_Mode", 2); // Fade _cachedMat.SetFloat("_Mode", 2);
return _cachedMat; return _cachedMat;
} }
} }
......
...@@ -70,40 +70,40 @@ namespace com.al_arcade.tf ...@@ -70,40 +70,40 @@ namespace com.al_arcade.tf
session.classCode = classCode; session.classCode = classCode;
yield return null; yield return null;
// Layout computation
float totalLength = stepsToWin * stepDistance + 10f; float totalLength = stepsToWin * stepDistance + 10f;
_lineCenterZ = 2f + totalLength / 2f; _lineCenterZ = 2f + totalLength / 2f;
_camPos = new Vector3(-11f, 3.5f, _lineCenterZ); _camPos = new Vector3(-11f, 3.5f, _lineCenterZ);
_camLookAt = new Vector3(0f, 1.5f, _lineCenterZ); _camLookAt = new Vector3(0f, 1.5f, _lineCenterZ);
// ── Build everything ─────────────────────────────────────
BuildScene(totalLength, _lineCenterZ); BuildScene(totalLength, _lineCenterZ);
yield return null; yield return null;
SetupCamera(); SetupCamera();
yield return null; yield return null;
// Production line
var lineObj = new GameObject("ProductionLine"); var lineObj = new GameObject("ProductionLine");
lineObj.transform.position = new Vector3(0, 0, 2); lineObj.transform.position = new Vector3(0, 0, 2);
_productionLine = lineObj.AddComponent<TfProductionLine>(); _productionLine = lineObj.AddComponent<TfProductionLine>();
_productionLine.Build(stepsToWin, stepDistance, _camPos); _productionLine.Build(stepsToWin, stepDistance, _camPos);
yield return null; yield return null;
// Question screen
var screenObj = new GameObject("QuestionScreen"); var screenObj = new GameObject("QuestionScreen");
_questionScreen = screenObj.AddComponent<TfQuestionScreen>(); _questionScreen = screenObj.AddComponent<TfQuestionScreen>();
Vector3 screenPos = new Vector3(0, 5f, _lineCenterZ); Vector3 screenPos = new Vector3(0, 5f, _lineCenterZ);
_questionScreen.Build(screenPos, _camPos); _questionScreen.Build(screenPos, _camPos);
yield return null; yield return null;
// Hands
var handsObj = new GameObject("Hands"); var handsObj = new GameObject("Hands");
_handController = handsObj.AddComponent<TfHandController>(); _handController = handsObj.AddComponent<TfHandController>();
_handController.Build(Camera.main); _handController.Build(Camera.main);
yield return null; yield return null;
// UI
var uiObj = new GameObject("TfUI"); var uiObj = new GameObject("TfUI");
_uiManager = uiObj.AddComponent<TfUIManager>(); _uiManager = uiObj.AddComponent<TfUIManager>();
_uiManager.BuildUI(); _uiManager.BuildUI();
...@@ -111,7 +111,7 @@ namespace com.al_arcade.tf ...@@ -111,7 +111,7 @@ namespace com.al_arcade.tf
_uiManager.onRestartClicked = new UnityEvent(); _uiManager.onRestartClicked = new UnityEvent();
yield return null; yield return null;
// Game Manager
var gmObj = new GameObject("TfGameManager"); var gmObj = new GameObject("TfGameManager");
_gm = gmObj.AddComponent<TfGameManager>(); _gm = gmObj.AddComponent<TfGameManager>();
_gm.handController = _handController; _gm.handController = _handController;
...@@ -153,13 +153,10 @@ namespace com.al_arcade.tf ...@@ -153,13 +153,10 @@ namespace com.al_arcade.tf
cam.nearClipPlane = 0.1f; cam.nearClipPlane = 0.1f;
} }
// ════════════════════════════════════════════════════════════
// ★ BRIGHT FACTORY SCENE
// ════════════════════════════════════════════════════════════
private void BuildScene(float totalLength, float lineCenterZ) private void BuildScene(float totalLength, float lineCenterZ)
{ {
// ★ Bright blue-gray sky instead of dark
Camera.main.clearFlags = CameraClearFlags.SolidColor; Camera.main.clearFlags = CameraClearFlags.SolidColor;
Camera.main.backgroundColor = new Color32(70, 80, 110, 255); Camera.main.backgroundColor = new Color32(70, 80, 110, 255);
...@@ -167,7 +164,7 @@ namespace com.al_arcade.tf ...@@ -167,7 +164,7 @@ namespace com.al_arcade.tf
float sceneDepth = totalLength + 12f; float sceneDepth = totalLength + 12f;
float halfDepth = sceneDepth / 2f; float halfDepth = sceneDepth / 2f;
// ── Floor — lighter ──────────────────────────────────────
var floor = GameObject.CreatePrimitive(PrimitiveType.Cube); var floor = GameObject.CreatePrimitive(PrimitiveType.Cube);
floor.name = "Floor"; floor.name = "Floor";
floor.transform.position = new Vector3(0, -0.05f, lineCenterZ); floor.transform.position = new Vector3(0, -0.05f, lineCenterZ);
...@@ -175,7 +172,7 @@ namespace com.al_arcade.tf ...@@ -175,7 +172,7 @@ namespace com.al_arcade.tf
floor.GetComponent<Renderer>().material = floor.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(65, 65, 80, 255) }; new Material(shader) { color = new Color32(65, 65, 80, 255) };
// ── Floor tile pattern ───────────────────────────────────
for (float x = -12; x <= 12; x += 6f) for (float x = -12; x <= 12; x += 6f)
{ {
for (float z = lineCenterZ - halfDepth; z < lineCenterZ + halfDepth; z += 6f) for (float z = lineCenterZ - halfDepth; z < lineCenterZ + halfDepth; z += 6f)
...@@ -195,7 +192,7 @@ namespace com.al_arcade.tf ...@@ -195,7 +192,7 @@ namespace com.al_arcade.tf
} }
} }
// ── Walls — brighter, with color accent ──────────────────
for (int side = -1; side <= 1; side += 2) for (int side = -1; side <= 1; side += 2)
{ {
var wall = GameObject.CreatePrimitive(PrimitiveType.Cube); var wall = GameObject.CreatePrimitive(PrimitiveType.Cube);
...@@ -206,7 +203,7 @@ namespace com.al_arcade.tf ...@@ -206,7 +203,7 @@ namespace com.al_arcade.tf
wall.GetComponent<Renderer>().material = wall.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(75, 75, 95, 255) }; new Material(shader) { color = new Color32(75, 75, 95, 255) };
// ★ Accent stripe on walls
var stripe = GameObject.CreatePrimitive(PrimitiveType.Cube); var stripe = GameObject.CreatePrimitive(PrimitiveType.Cube);
stripe.name = "WallStripe"; stripe.name = "WallStripe";
stripe.transform.position = new Vector3(side * 14.7f, 1.5f, lineCenterZ); stripe.transform.position = new Vector3(side * 14.7f, 1.5f, lineCenterZ);
...@@ -218,7 +215,7 @@ namespace com.al_arcade.tf ...@@ -218,7 +215,7 @@ namespace com.al_arcade.tf
}; };
} }
// ── End walls ────────────────────────────────────────────
foreach (float z in new[] { lineCenterZ + halfDepth, lineCenterZ - halfDepth }) foreach (float z in new[] { lineCenterZ + halfDepth, lineCenterZ - halfDepth })
{ {
var bw = GameObject.CreatePrimitive(PrimitiveType.Cube); var bw = GameObject.CreatePrimitive(PrimitiveType.Cube);
...@@ -230,7 +227,7 @@ namespace com.al_arcade.tf ...@@ -230,7 +227,7 @@ namespace com.al_arcade.tf
new Material(shader) { color = new Color32(70, 70, 88, 255) }; new Material(shader) { color = new Color32(70, 70, 88, 255) };
} }
// ── Ceiling — slightly reflective look ───────────────────
var ceiling = GameObject.CreatePrimitive(PrimitiveType.Cube); var ceiling = GameObject.CreatePrimitive(PrimitiveType.Cube);
ceiling.name = "Ceiling"; ceiling.name = "Ceiling";
ceiling.transform.position = new Vector3(0, 10, lineCenterZ); ceiling.transform.position = new Vector3(0, 10, lineCenterZ);
...@@ -239,7 +236,7 @@ namespace com.al_arcade.tf ...@@ -239,7 +236,7 @@ namespace com.al_arcade.tf
ceiling.GetComponent<Renderer>().material = ceiling.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(55, 58, 72, 255) }; new Material(shader) { color = new Color32(55, 58, 72, 255) };
// ── Industrial pipes — more colorful ─────────────────────
Color[] pipeColors = Color[] pipeColors =
{ {
new Color32(90, 90, 110, 255), new Color32(90, 90, 110, 255),
...@@ -268,7 +265,7 @@ namespace com.al_arcade.tf ...@@ -268,7 +265,7 @@ namespace com.al_arcade.tf
}; };
} }
// ── Warning stripes — brighter accent ────────────────────
for (float z = lineCenterZ - halfDepth; z < lineCenterZ + halfDepth; z += 5f) for (float z = lineCenterZ - halfDepth; z < lineCenterZ + halfDepth; z += 5f)
{ {
var ws = GameObject.CreatePrimitive(PrimitiveType.Cube); var ws = GameObject.CreatePrimitive(PrimitiveType.Cube);
...@@ -282,10 +279,10 @@ namespace com.al_arcade.tf ...@@ -282,10 +279,10 @@ namespace com.al_arcade.tf
}; };
} }
// ── Hanging lamps — ★ brighter, more of them ─────────────
for (float z = lineCenterZ - halfDepth + 3; z < lineCenterZ + halfDepth; z += 5f) for (float z = lineCenterZ - halfDepth + 3; z < lineCenterZ + halfDepth; z += 5f)
{ {
// Pole
var pole = GameObject.CreatePrimitive(PrimitiveType.Cylinder); var pole = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
pole.name = "LampPole"; pole.name = "LampPole";
pole.transform.position = new Vector3(0, 9.3f, z); pole.transform.position = new Vector3(0, 9.3f, z);
...@@ -294,7 +291,7 @@ namespace com.al_arcade.tf ...@@ -294,7 +291,7 @@ namespace com.al_arcade.tf
pole.GetComponent<Renderer>().material = pole.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(90, 90, 100, 255) }; new Material(shader) { color = new Color32(90, 90, 100, 255) };
// Lamp head
var head = GameObject.CreatePrimitive(PrimitiveType.Cylinder); var head = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
head.name = "LampHead"; head.name = "LampHead";
head.transform.position = new Vector3(0, 8.7f, z); head.transform.position = new Vector3(0, 8.7f, z);
...@@ -305,17 +302,17 @@ namespace com.al_arcade.tf ...@@ -305,17 +302,17 @@ namespace com.al_arcade.tf
color = SSColorPalette.WithAlpha(SSColorPalette.Accent, 0.6f) color = SSColorPalette.WithAlpha(SSColorPalette.Accent, 0.6f)
}; };
// ★ Actual point light per lamp
var lampLight = new GameObject($"LampLight_{z:F0}"); var lampLight = new GameObject($"LampLight_{z:F0}");
var lt = lampLight.AddComponent<Light>(); var lt = lampLight.AddComponent<Light>();
lt.type = LightType.Point; lt.type = LightType.Point;
lt.range = 8; lt.range = 8;
lt.intensity = 0.4f; lt.intensity = 0.4f;
lt.color = new Color32(255, 240, 210, 255); // warm white lt.color = new Color32(255, 240, 210, 255);
lampLight.transform.position = new Vector3(0, 8.5f, z); lampLight.transform.position = new Vector3(0, 8.5f, z);
} }
// ── Equipment boxes (decorative) ─────────────────────────
for (int i = 0; i < 6; i++) for (int i = 0; i < 6; i++)
{ {
float side = (i % 2 == 0) ? -1 : 1; float side = (i % 2 == 0) ? -1 : 1;
...@@ -342,13 +339,10 @@ namespace com.al_arcade.tf ...@@ -342,13 +339,10 @@ namespace com.al_arcade.tf
} }
} }
// ════════════════════════════════════════════════════════════
// ★ BRIGHTER LIGHTING
// ════════════════════════════════════════════════════════════
private void SetupLighting() private void SetupLighting()
{ {
// Main directional — brighter, warmer
var existing = FindObjectOfType<Light>(); var existing = FindObjectOfType<Light>();
Light dirLight; Light dirLight;
if (existing != null) if (existing != null)
...@@ -360,12 +354,12 @@ namespace com.al_arcade.tf ...@@ -360,12 +354,12 @@ namespace com.al_arcade.tf
} }
dirLight.type = LightType.Directional; dirLight.type = LightType.Directional;
dirLight.transform.rotation = Quaternion.Euler(45, -30, 0); dirLight.transform.rotation = Quaternion.Euler(45, -30, 0);
// ★ Brighter, warmer color
dirLight.color = new Color32(240, 235, 220, 255); dirLight.color = new Color32(240, 235, 220, 255);
dirLight.intensity = 1.1f; dirLight.intensity = 1.1f;
dirLight.shadows = LightShadows.Soft; dirLight.shadows = LightShadows.Soft;
// ★ Strong fill light from camera side
var fillObj = new GameObject("FillLight"); var fillObj = new GameObject("FillLight");
var fill = fillObj.AddComponent<Light>(); var fill = fillObj.AddComponent<Light>();
fill.type = LightType.Point; fill.type = LightType.Point;
...@@ -374,7 +368,7 @@ namespace com.al_arcade.tf ...@@ -374,7 +368,7 @@ namespace com.al_arcade.tf
fill.color = new Color32(210, 220, 240, 255); fill.color = new Color32(210, 220, 240, 255);
fillObj.transform.position = new Vector3(-10, 5, _lineCenterZ); fillObj.transform.position = new Vector3(-10, 5, _lineCenterZ);
// ★ Accent colored lights along line
Color[] accentColors = { Color[] accentColors = {
SSColorPalette.Accent, SSColorPalette.Info, SSColorPalette.Accent, SSColorPalette.Info,
SSColorPalette.Primary, SSColorPalette.Success SSColorPalette.Primary, SSColorPalette.Success
...@@ -392,14 +386,11 @@ namespace com.al_arcade.tf ...@@ -392,14 +386,11 @@ namespace com.al_arcade.tf
(i % 2 == 0 ? -1 : 1) * 6f, 6f, z); (i % 2 == 0 ? -1 : 1) * 6f, 6f, z);
} }
// ★ Brighter ambient
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat; RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
RenderSettings.ambientLight = new Color32(75, 78, 95, 255); RenderSettings.ambientLight = new Color32(75, 78, 95, 255);
} }
// ════════════════════════════════════════════════════════════
// TEST DATA
// ════════════════════════════════════════════════════════════
private TfQuestion[] GetTestQuestions() private TfQuestion[] GetTestQuestions()
{ {
......
...@@ -43,13 +43,13 @@ namespace com.al_arcade.tf ...@@ -43,13 +43,13 @@ namespace com.al_arcade.tf
float side = isLeft ? -1f : 1f; float side = isLeft ? -1f : 1f;
var shader = GetShader(); var shader = GetShader();
// ── Hand root ────────────────────────────────────────────
hand = new GameObject(isLeft ? "LeftHand" : "RightHand"); hand = new GameObject(isLeft ? "LeftHand" : "RightHand");
hand.transform.SetParent(transform); hand.transform.SetParent(transform);
hand.transform.localPosition = new Vector3(side * 0.35f, -0.3f, 0.55f); hand.transform.localPosition = new Vector3(side * 0.35f, -0.3f, 0.55f);
hand.transform.localRotation = Quaternion.Euler(12f, -side * 8f, side * 4f); hand.transform.localRotation = Quaternion.Euler(12f, -side * 8f, side * 4f);
// ── Arm (smaller, more proportional) ─────────────────────
var arm = GameObject.CreatePrimitive(PrimitiveType.Capsule); var arm = GameObject.CreatePrimitive(PrimitiveType.Capsule);
arm.name = "Arm"; arm.name = "Arm";
arm.transform.SetParent(hand.transform); arm.transform.SetParent(hand.transform);
...@@ -59,7 +59,7 @@ namespace com.al_arcade.tf ...@@ -59,7 +59,7 @@ namespace com.al_arcade.tf
arm.GetComponent<Renderer>().material = arm.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(255, 220, 180, 255) }; new Material(shader) { color = new Color32(255, 220, 180, 255) };
// ── Button (sized for close-up FPS view) ─────────────────
btn = GameObject.CreatePrimitive(PrimitiveType.Cylinder); btn = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
btn.name = isLeft ? "TrueButton" : "FalseButton"; btn.name = isLeft ? "TrueButton" : "FalseButton";
btn.transform.SetParent(hand.transform); btn.transform.SetParent(hand.transform);
...@@ -73,17 +73,17 @@ namespace com.al_arcade.tf ...@@ -73,17 +73,17 @@ namespace com.al_arcade.tf
}; };
btn.GetComponent<Renderer>().material = mat; btn.GetComponent<Renderer>().material = mat;
// ── Label — ★ small font, tight rect, on top of button ───
var labelObj = new GameObject("Label"); var labelObj = new GameObject("Label");
labelObj.transform.SetParent(btn.transform); labelObj.transform.SetParent(btn.transform);
labelObj.transform.localPosition = new Vector3(0, 0.8f, 0); labelObj.transform.localPosition = new Vector3(0, 0.8f, 0);
labelObj.transform.localRotation = Quaternion.Euler(90, 0, 0); labelObj.transform.localRotation = Quaternion.Euler(90, 0, 0);
// ★ Scale the label container down so the text is proportional
labelObj.transform.localScale = new Vector3(0.6f, 0.6f, 0.6f); labelObj.transform.localScale = new Vector3(0.6f, 0.6f, 0.6f);
label = labelObj.AddComponent<ArabicTextMeshPro>(); label = labelObj.AddComponent<ArabicTextMeshPro>();
label.arabicText = isLeft ? "صح ✓" : "خطأ ✗"; label.arabicText = isLeft ? "صح ✓" : "خطأ ✗";
// ★ fontSize 2 in a small world-space rect = readable not giant
label.fontSize = 2.2f; label.fontSize = 2.2f;
label.fontSizeMin = 1.5f; label.fontSizeMin = 1.5f;
label.fontSizeMax = 2.5f; label.fontSizeMax = 2.5f;
...@@ -91,12 +91,12 @@ namespace com.al_arcade.tf ...@@ -91,12 +91,12 @@ namespace com.al_arcade.tf
label.alignment = TMPro.TextAlignmentOptions.Center; label.alignment = TMPro.TextAlignmentOptions.Center;
label.color = Color.white; label.color = Color.white;
label.fontStyle = TMPro.FontStyles.Bold; label.fontStyle = TMPro.FontStyles.Bold;
// ★ Tight rect so text doesn't overflow
label.rectTransform.sizeDelta = new Vector2(1.2f, 0.5f); label.rectTransform.sizeDelta = new Vector2(1.2f, 0.5f);
label.overflowMode = TMPro.TextOverflowModes.Overflow; label.overflowMode = TMPro.TextOverflowModes.Overflow;
SSFontManager.Apply(label); SSFontManager.Apply(label);
// ── Glow ring ────────────────────────────────────────────
var ring = GameObject.CreatePrimitive(PrimitiveType.Cylinder); var ring = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
ring.name = "GlowRing"; ring.name = "GlowRing";
ring.transform.SetParent(btn.transform); ring.transform.SetParent(btn.transform);
...@@ -110,7 +110,6 @@ namespace com.al_arcade.tf ...@@ -110,7 +110,6 @@ namespace com.al_arcade.tf
}; };
} }
// ── Idle Animation ───────────────────────────────────────────
private void StartIdleAnimation() private void StartIdleAnimation()
{ {
...@@ -139,7 +138,6 @@ namespace com.al_arcade.tf ...@@ -139,7 +138,6 @@ namespace com.al_arcade.tf
_idleSeq = null; _idleSeq = null;
} }
// ── Input ────────────────────────────────────────────────────
public void SetReady(bool ready) => _isReady = ready; public void SetReady(bool ready) => _isReady = ready;
...@@ -161,14 +159,14 @@ namespace com.al_arcade.tf ...@@ -161,14 +159,14 @@ namespace com.al_arcade.tf
var restPos = isTrue ? _leftRestPos : _rightRestPos; var restPos = isTrue ? _leftRestPos : _rightRestPos;
var btnBaseScale = isTrue ? _leftBtnBaseScale : _rightBtnBaseScale; var btnBaseScale = isTrue ? _leftBtnBaseScale : _rightBtnBaseScale;
// ★ Kill ALL tweens, reset to known state
KillIdleTween(); KillIdleTween();
DOTween.Kill(hand.transform); DOTween.Kill(hand.transform);
DOTween.Kill(btn.transform); DOTween.Kill(btn.transform);
hand.transform.localPosition = restPos; hand.transform.localPosition = restPos;
btn.transform.localScale = btnBaseScale; btn.transform.localScale = btnBaseScale;
// ★ Press animation — always returns to restPos
var pressSeq = DOTween.Sequence(); var pressSeq = DOTween.Sequence();
pressSeq.Append(hand.transform pressSeq.Append(hand.transform
.DOLocalMoveY(restPos.y + 0.06f, 0.07f) .DOLocalMoveY(restPos.y + 0.06f, 0.07f)
...@@ -181,7 +179,7 @@ namespace com.al_arcade.tf ...@@ -181,7 +179,7 @@ namespace com.al_arcade.tf
.SetEase(Ease.OutBounce)); .SetEase(Ease.OutBounce));
pressSeq.OnComplete(() => StartIdleAnimation()); pressSeq.OnComplete(() => StartIdleAnimation());
// ★ Button squash (Y axis is thickness for cylinder)
btn.transform.DOScaleY(btnBaseScale.y * 0.3f, 0.05f) btn.transform.DOScaleY(btnBaseScale.y * 0.3f, 0.05f)
.SetEase(Ease.InQuad) .SetEase(Ease.InQuad)
.OnComplete(() => .OnComplete(() =>
...@@ -195,7 +193,6 @@ namespace com.al_arcade.tf ...@@ -195,7 +193,6 @@ namespace com.al_arcade.tf
TfGameManager.Instance.SubmitAnswer(isTrue); TfGameManager.Instance.SubmitAnswer(isTrue);
} }
// ── Feedback ─────────────────────────────────────────────────
public void PlayCorrectFeedback(bool wasTrue) public void PlayCorrectFeedback(bool wasTrue)
{ {
......
...@@ -12,9 +12,7 @@ namespace com.al_arcade.tf ...@@ -12,9 +12,7 @@ namespace com.al_arcade.tf
[AddComponentMenu("Science Street/TF Prefab Builder")] [AddComponentMenu("Science Street/TF Prefab Builder")]
public class TfPrefabBuilder : MonoBehaviour public class TfPrefabBuilder : MonoBehaviour
{ {
// ════════════════════════════════════════════════════════════
// SCENE
// ════════════════════════════════════════════════════════════
[Header("Scene Environment")] [Header("Scene Environment")]
[Tooltip("Your full factory environment prefab.")] [Tooltip("Your full factory environment prefab.")]
...@@ -26,18 +24,12 @@ namespace com.al_arcade.tf ...@@ -26,18 +24,12 @@ namespace com.al_arcade.tf
[SerializeField] private float cameraFOV = 55f; [SerializeField] private float cameraFOV = 55f;
[SerializeField] private Color cameraBgColor = new Color32(70, 80, 110, 255); [SerializeField] private Color cameraBgColor = new Color32(70, 80, 110, 255);
// ════════════════════════════════════════════════════════════
// LIGHTING
// ════════════════════════════════════════════════════════════
[Header("Lighting")] [Header("Lighting")]
[SerializeField] private GameObject directionalLightPrefab; [SerializeField] private GameObject directionalLightPrefab;
[SerializeField] private GameObject[] extraLightPrefabs; [SerializeField] private GameObject[] extraLightPrefabs;
[SerializeField] private Color ambientColor = new Color32(75, 78, 95, 255); [SerializeField] private Color ambientColor = new Color32(75, 78, 95, 255);
// ════════════════════════════════════════════════════════════
// PRODUCTION LINE
// ════════════════════════════════════════════════════════════
[Header("Production Line")] [Header("Production Line")]
[Tooltip("Your production line prefab. MUST have TfProductionLine.\n" + [Tooltip("Your production line prefab. MUST have TfProductionLine.\n" +
...@@ -45,9 +37,6 @@ namespace com.al_arcade.tf ...@@ -45,9 +37,6 @@ namespace com.al_arcade.tf
[SerializeField] private GameObject productionLinePrefab; [SerializeField] private GameObject productionLinePrefab;
[SerializeField] private Vector3 productionLinePosition = new Vector3(0, 0, 2); [SerializeField] private Vector3 productionLinePosition = new Vector3(0, 0, 2);
// ════════════════════════════════════════════════════════════
// QUESTION SCREEN
// ════════════════════════════════════════════════════════════
[Header("Question Screen")] [Header("Question Screen")]
[Tooltip("Your question screen prefab. MUST have TfQuestionScreen.\n" + [Tooltip("Your question screen prefab. MUST have TfQuestionScreen.\n" +
...@@ -55,27 +44,18 @@ namespace com.al_arcade.tf ...@@ -55,27 +44,18 @@ namespace com.al_arcade.tf
[SerializeField] private GameObject questionScreenPrefab; [SerializeField] private GameObject questionScreenPrefab;
[SerializeField] private Vector3 questionScreenPosition = new Vector3(0, 5f, 12f); [SerializeField] private Vector3 questionScreenPosition = new Vector3(0, 5f, 12f);
// ════════════════════════════════════════════════════════════
// HANDS
// ════════════════════════════════════════════════════════════
[Header("Hands / Buttons")] [Header("Hands / Buttons")]
[Tooltip("Your hands prefab. MUST have TfHandController.\n" + [Tooltip("Your hands prefab. MUST have TfHandController.\n" +
"If null, procedural hands are created.")] "If null, procedural hands are created.")]
[SerializeField] private GameObject handsPrefab; [SerializeField] private GameObject handsPrefab;
// ════════════════════════════════════════════════════════════
// UI
// ════════════════════════════════════════════════════════════
[Header("UI Canvas")] [Header("UI Canvas")]
[Tooltip("Your Canvas prefab. MUST have TfUIManager with all references wired.\n" + [Tooltip("Your Canvas prefab. MUST have TfUIManager with all references wired.\n" +
"If null, procedural UI is created.")] "If null, procedural UI is created.")]
[SerializeField] private GameObject canvasPrefab; [SerializeField] private GameObject canvasPrefab;
// ════════════════════════════════════════════════════════════
// AUDIO
// ════════════════════════════════════════════════════════════
[Header("Audio — SFX Clips")] [Header("Audio — SFX Clips")]
[SerializeField] private AudioClip sfxCorrect; [SerializeField] private AudioClip sfxCorrect;
...@@ -87,9 +67,6 @@ namespace com.al_arcade.tf ...@@ -87,9 +67,6 @@ namespace com.al_arcade.tf
[SerializeField] private AudioClip sfxPop; [SerializeField] private AudioClip sfxPop;
[SerializeField] private AudioClip sfxSlam; [SerializeField] private AudioClip sfxSlam;
// ════════════════════════════════════════════════════════════
// PARTICLES
// ════════════════════════════════════════════════════════════
[Header("Particles")] [Header("Particles")]
[SerializeField] private ParticleSystem correctBurstParticle; [SerializeField] private ParticleSystem correctBurstParticle;
...@@ -98,16 +75,10 @@ namespace com.al_arcade.tf ...@@ -98,16 +75,10 @@ namespace com.al_arcade.tf
[SerializeField] private ParticleSystem sparksParticle; [SerializeField] private ParticleSystem sparksParticle;
[SerializeField] private ParticleSystem starBurstParticle; [SerializeField] private ParticleSystem starBurstParticle;
// ════════════════════════════════════════════════════════════
// FONT
// ════════════════════════════════════════════════════════════
[Header("Arabic Font")] [Header("Arabic Font")]
[SerializeField] private TMP_FontAsset arabicFont; [SerializeField] private TMP_FontAsset arabicFont;
// ════════════════════════════════════════════════════════════
// GAME SETTINGS
// ════════════════════════════════════════════════════════════
[Header("Game Settings")] [Header("Game Settings")]
[SerializeField] private string buildType = "scistreet"; [SerializeField] private string buildType = "scistreet";
...@@ -120,9 +91,6 @@ namespace com.al_arcade.tf ...@@ -120,9 +91,6 @@ namespace com.al_arcade.tf
[Header("Debug")] [Header("Debug")]
[SerializeField] private bool useOfflineTestData = false; [SerializeField] private bool useOfflineTestData = false;
// ════════════════════════════════════════════════════════════
// RUNTIME
// ════════════════════════════════════════════════════════════
private TfGameManager _gm; private TfGameManager _gm;
private TfHandController _handController; private TfHandController _handController;
...@@ -159,20 +127,20 @@ namespace com.al_arcade.tf ...@@ -159,20 +127,20 @@ namespace com.al_arcade.tf
session.classCode = classCode; session.classCode = classCode;
yield return null; yield return null;
// ── Camera ───────────────────────────────────────────
SetupCamera(); SetupCamera();
yield return null; yield return null;
// ── Environment ──────────────────────────────────────
if (environmentPrefab != null) if (environmentPrefab != null)
Instantiate(environmentPrefab, Vector3.zero, Quaternion.identity); Instantiate(environmentPrefab, Vector3.zero, Quaternion.identity);
yield return null; yield return null;
// ── Lighting ─────────────────────────────────────────
SetupLighting(); SetupLighting();
yield return null; yield return null;
// ── Production Line ──────────────────────────────────
if (productionLinePrefab != null) if (productionLinePrefab != null)
{ {
var lineObj = Instantiate(productionLinePrefab, var lineObj = Instantiate(productionLinePrefab,
...@@ -194,7 +162,7 @@ namespace com.al_arcade.tf ...@@ -194,7 +162,7 @@ namespace com.al_arcade.tf
} }
yield return null; yield return null;
// ── Question Screen ──────────────────────────────────
if (questionScreenPrefab != null) if (questionScreenPrefab != null)
{ {
var screenObj = Instantiate(questionScreenPrefab, var screenObj = Instantiate(questionScreenPrefab,
...@@ -215,7 +183,7 @@ namespace com.al_arcade.tf ...@@ -215,7 +183,7 @@ namespace com.al_arcade.tf
} }
yield return null; yield return null;
// ── Hands ────────────────────────────────────────────
if (handsPrefab != null) if (handsPrefab != null)
{ {
var handsObj = Instantiate(handsPrefab); var handsObj = Instantiate(handsPrefab);
...@@ -235,7 +203,7 @@ namespace com.al_arcade.tf ...@@ -235,7 +203,7 @@ namespace com.al_arcade.tf
} }
yield return null; yield return null;
// ── UI ───────────────────────────────────────────────
if (canvasPrefab != null) if (canvasPrefab != null)
{ {
var canvasObj = Instantiate(canvasPrefab); var canvasObj = Instantiate(canvasPrefab);
...@@ -258,7 +226,7 @@ namespace com.al_arcade.tf ...@@ -258,7 +226,7 @@ namespace com.al_arcade.tf
_uiManager.onRestartClicked = new UnityEvent(); _uiManager.onRestartClicked = new UnityEvent();
yield return null; yield return null;
// ── Game Manager ─────────────────────────────────────
var gmObj = new GameObject("TfGameManager"); var gmObj = new GameObject("TfGameManager");
_gm = gmObj.AddComponent<TfGameManager>(); _gm = gmObj.AddComponent<TfGameManager>();
_gm.handController = _handController; _gm.handController = _handController;
......
...@@ -21,7 +21,7 @@ namespace com.al_arcade.tf ...@@ -21,7 +21,7 @@ namespace com.al_arcade.tf
float totalLength = stepsToWin * stepDistance + 10f; float totalLength = stepsToWin * stepDistance + 10f;
var shader = GetShader(); var shader = GetShader();
// ── Belt surface ─────────────────────────────────────────
var belt = GameObject.CreatePrimitive(PrimitiveType.Cube); var belt = GameObject.CreatePrimitive(PrimitiveType.Cube);
belt.name = "Belt"; belt.name = "Belt";
belt.transform.SetParent(transform); belt.transform.SetParent(transform);
...@@ -31,7 +31,7 @@ namespace com.al_arcade.tf ...@@ -31,7 +31,7 @@ namespace com.al_arcade.tf
belt.GetComponent<Renderer>().material = belt.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(55, 55, 65, 255) }; new Material(shader) { color = new Color32(55, 55, 65, 255) };
// ── Belt top highlight ───────────────────────────────────
var beltTop = GameObject.CreatePrimitive(PrimitiveType.Cube); var beltTop = GameObject.CreatePrimitive(PrimitiveType.Cube);
beltTop.name = "BeltTop"; beltTop.name = "BeltTop";
beltTop.transform.SetParent(transform); beltTop.transform.SetParent(transform);
...@@ -41,7 +41,7 @@ namespace com.al_arcade.tf ...@@ -41,7 +41,7 @@ namespace com.al_arcade.tf
beltTop.GetComponent<Renderer>().material = beltTop.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(70, 70, 80, 255) }; new Material(shader) { color = new Color32(70, 70, 80, 255) };
// ── Rollers ──────────────────────────────────────────────
int rc = Mathf.CeilToInt(totalLength / 2f); int rc = Mathf.CeilToInt(totalLength / 2f);
_rollers = new GameObject[rc]; _rollers = new GameObject[rc];
for (int i = 0; i < rc; i++) for (int i = 0; i < rc; i++)
...@@ -58,7 +58,7 @@ namespace com.al_arcade.tf ...@@ -58,7 +58,7 @@ namespace com.al_arcade.tf
_rollers[i] = r; _rollers[i] = r;
} }
// ── Side rails ───────────────────────────────────────────
for (int s = -1; s <= 1; s += 2) for (int s = -1; s <= 1; s += 2)
{ {
var rail = GameObject.CreatePrimitive(PrimitiveType.Cube); var rail = GameObject.CreatePrimitive(PrimitiveType.Cube);
...@@ -71,7 +71,7 @@ namespace com.al_arcade.tf ...@@ -71,7 +71,7 @@ namespace com.al_arcade.tf
new Material(shader) { color = SSColorPalette.Primary }; new Material(shader) { color = SSColorPalette.Primary };
} }
// ── Legs ─────────────────────────────────────────────────
for (float z = 1; z < totalLength; z += 4f) for (float z = 1; z < totalLength; z += 4f)
{ {
for (int s = -1; s <= 1; s += 2) for (int s = -1; s <= 1; s += 2)
...@@ -87,12 +87,12 @@ namespace com.al_arcade.tf ...@@ -87,12 +87,12 @@ namespace com.al_arcade.tf
} }
} }
// ── Progress markers ─────────────────────────────────────
for (int i = 0; i < stepsToWin; i++) for (int i = 0; i < stepsToWin; i++)
{ {
float mz = (i + 1) * stepDistance; float mz = (i + 1) * stepDistance;
// Line marker
var marker = GameObject.CreatePrimitive(PrimitiveType.Cube); var marker = GameObject.CreatePrimitive(PrimitiveType.Cube);
marker.name = $"Marker_{i}"; marker.name = $"Marker_{i}";
marker.transform.SetParent(transform); marker.transform.SetParent(transform);
...@@ -105,13 +105,13 @@ namespace com.al_arcade.tf ...@@ -105,13 +105,13 @@ namespace com.al_arcade.tf
color = SSColorPalette.WithAlpha(SSColorPalette.Accent, 0.5f) color = SSColorPalette.WithAlpha(SSColorPalette.Accent, 0.5f)
}; };
// Step label — facing camera
var labelObj = new GameObject("StepLabel"); var labelObj = new GameObject("StepLabel");
labelObj.transform.SetParent(marker.transform); labelObj.transform.SetParent(marker.transform);
labelObj.transform.position = labelObj.transform.position =
marker.transform.position + new Vector3(1.6f, 0.5f, 0); marker.transform.position + new Vector3(1.6f, 0.5f, 0);
// Billboard toward camera
Vector3 away = labelObj.transform.position - cameraPos; Vector3 away = labelObj.transform.position - cameraPos;
away.y = 0; away.y = 0;
if (away.sqrMagnitude > 0.01f) if (away.sqrMagnitude > 0.01f)
...@@ -119,7 +119,7 @@ namespace com.al_arcade.tf ...@@ -119,7 +119,7 @@ namespace com.al_arcade.tf
var lbl = labelObj.AddComponent<ArabicTextMeshPro>(); var lbl = labelObj.AddComponent<ArabicTextMeshPro>();
lbl.arabicText = $"✦ {i + 1}"; lbl.arabicText = $"✦ {i + 1}";
// ★ Small readable size
lbl.fontSize = 2f; lbl.fontSize = 2f;
lbl.alignment = TMPro.TextAlignmentOptions.Center; lbl.alignment = TMPro.TextAlignmentOptions.Center;
lbl.color = SSColorPalette.WithAlpha(SSColorPalette.Accent, 0.7f); lbl.color = SSColorPalette.WithAlpha(SSColorPalette.Accent, 0.7f);
...@@ -127,7 +127,7 @@ namespace com.al_arcade.tf ...@@ -127,7 +127,7 @@ namespace com.al_arcade.tf
SSFontManager.Apply(lbl); SSFontManager.Apply(lbl);
} }
// ── End zone ─────────────────────────────────────────────
float endZ = stepsToWin * stepDistance + 2f; float endZ = stepsToWin * stepDistance + 2f;
var ez = GameObject.CreatePrimitive(PrimitiveType.Cube); var ez = GameObject.CreatePrimitive(PrimitiveType.Cube);
ez.name = "EndZone"; ez.name = "EndZone";
...@@ -138,7 +138,7 @@ namespace com.al_arcade.tf ...@@ -138,7 +138,7 @@ namespace com.al_arcade.tf
ez.GetComponent<Renderer>().material = ez.GetComponent<Renderer>().material =
new Material(shader) { color = SSColorPalette.WithAlpha(SSColorPalette.Success, 0.35f) }; new Material(shader) { color = SSColorPalette.WithAlpha(SSColorPalette.Success, 0.35f) };
// ── Product ──────────────────────────────────────────────
_product = new GameObject("Product"); _product = new GameObject("Product");
_product.transform.SetParent(transform); _product.transform.SetParent(transform);
_product.transform.localPosition = new Vector3(0, 0.7f, 0); _product.transform.localPosition = new Vector3(0, 0.7f, 0);
...@@ -152,7 +152,7 @@ namespace com.al_arcade.tf ...@@ -152,7 +152,7 @@ namespace com.al_arcade.tf
_productMat = new Material(shader) { color = SSColorPalette.Accent }; _productMat = new Material(shader) { color = SSColorPalette.Accent };
box.GetComponent<Renderer>().material = _productMat; box.GetComponent<Renderer>().material = _productMat;
// Stripe on box
var stripe = GameObject.CreatePrimitive(PrimitiveType.Cube); var stripe = GameObject.CreatePrimitive(PrimitiveType.Cube);
stripe.name = "Stripe"; stripe.name = "Stripe";
stripe.transform.SetParent(_product.transform); stripe.transform.SetParent(_product.transform);
...@@ -162,7 +162,7 @@ namespace com.al_arcade.tf ...@@ -162,7 +162,7 @@ namespace com.al_arcade.tf
stripe.GetComponent<Renderer>().material = stripe.GetComponent<Renderer>().material =
new Material(shader) { color = SSColorPalette.Primary }; new Material(shader) { color = SSColorPalette.Primary };
// Star on top
var star = GameObject.CreatePrimitive(PrimitiveType.Sphere); var star = GameObject.CreatePrimitive(PrimitiveType.Sphere);
star.name = "Star"; star.name = "Star";
star.transform.SetParent(_product.transform); star.transform.SetParent(_product.transform);
...@@ -175,7 +175,6 @@ namespace com.al_arcade.tf ...@@ -175,7 +175,6 @@ namespace com.al_arcade.tf
_currentZ = 0; _currentZ = 0;
} }
// ── Movement ─────────────────────────────────────────────────
public IEnumerator MoveForward(float dist) public IEnumerator MoveForward(float dist)
{ {
......
...@@ -15,15 +15,13 @@ namespace com.al_arcade.tf ...@@ -15,15 +15,13 @@ namespace com.al_arcade.tf
private Material _feedbackMat; private Material _feedbackMat;
private GameObject _feedbackPanel; private GameObject _feedbackPanel;
/// <summary>
/// Build screen at position, rotated to face cameraPos.
/// </summary>
public void Build(Vector3 position, Vector3 cameraPos) public void Build(Vector3 position, Vector3 cameraPos)
{ {
transform.position = position; transform.position = position;
var shader = GetShader(); var shader = GetShader();
// ── Frame ────────────────────────────────────────────────
var frame = GameObject.CreatePrimitive(PrimitiveType.Cube); var frame = GameObject.CreatePrimitive(PrimitiveType.Cube);
frame.name = "Frame"; frame.name = "Frame";
frame.transform.SetParent(transform); frame.transform.SetParent(transform);
...@@ -33,7 +31,7 @@ namespace com.al_arcade.tf ...@@ -33,7 +31,7 @@ namespace com.al_arcade.tf
frame.GetComponent<Renderer>().material = frame.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(25, 25, 35, 255) }; new Material(shader) { color = new Color32(25, 25, 35, 255) };
// ── Screen border highlight ──────────────────────────────
var borderGlow = GameObject.CreatePrimitive(PrimitiveType.Cube); var borderGlow = GameObject.CreatePrimitive(PrimitiveType.Cube);
borderGlow.name = "BorderGlow"; borderGlow.name = "BorderGlow";
borderGlow.transform.SetParent(transform); borderGlow.transform.SetParent(transform);
...@@ -43,7 +41,7 @@ namespace com.al_arcade.tf ...@@ -43,7 +41,7 @@ namespace com.al_arcade.tf
borderGlow.GetComponent<Renderer>().material = borderGlow.GetComponent<Renderer>().material =
new Material(shader) { color = SSColorPalette.WithAlpha(SSColorPalette.Primary, 0.3f) }; new Material(shader) { color = SSColorPalette.WithAlpha(SSColorPalette.Primary, 0.3f) };
// ── Screen surface ───────────────────────────────────────
var surface = GameObject.CreatePrimitive(PrimitiveType.Cube); var surface = GameObject.CreatePrimitive(PrimitiveType.Cube);
surface.name = "Surface"; surface.name = "Surface";
surface.transform.SetParent(transform); surface.transform.SetParent(transform);
...@@ -53,7 +51,7 @@ namespace com.al_arcade.tf ...@@ -53,7 +51,7 @@ namespace com.al_arcade.tf
_screenMat = new Material(shader) { color = new Color32(15, 15, 50, 255) }; _screenMat = new Material(shader) { color = new Color32(15, 15, 50, 255) };
surface.GetComponent<Renderer>().material = _screenMat; surface.GetComponent<Renderer>().material = _screenMat;
// ── Pole ─────────────────────────────────────────────────
var pole = GameObject.CreatePrimitive(PrimitiveType.Cylinder); var pole = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
pole.name = "Pole"; pole.name = "Pole";
pole.transform.SetParent(transform); pole.transform.SetParent(transform);
...@@ -63,7 +61,7 @@ namespace com.al_arcade.tf ...@@ -63,7 +61,7 @@ namespace com.al_arcade.tf
pole.GetComponent<Renderer>().material = pole.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(70, 70, 85, 255) }; new Material(shader) { color = new Color32(70, 70, 85, 255) };
// ── Pole base ────────────────────────────────────────────
var poleBase = GameObject.CreatePrimitive(PrimitiveType.Cylinder); var poleBase = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
poleBase.name = "PoleBase"; poleBase.name = "PoleBase";
poleBase.transform.SetParent(transform); poleBase.transform.SetParent(transform);
...@@ -73,7 +71,7 @@ namespace com.al_arcade.tf ...@@ -73,7 +71,7 @@ namespace com.al_arcade.tf
poleBase.GetComponent<Renderer>().material = poleBase.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(60, 60, 75, 255) }; new Material(shader) { color = new Color32(60, 60, 75, 255) };
// ── Question text ★ z = -0.25 (pushed out toward camera) ─
var textObj = new GameObject("QuestionText"); var textObj = new GameObject("QuestionText");
textObj.transform.SetParent(transform); textObj.transform.SetParent(transform);
textObj.transform.localPosition = new Vector3(0, 0.25f, -0.25f); textObj.transform.localPosition = new Vector3(0, 0.25f, -0.25f);
...@@ -84,7 +82,7 @@ namespace com.al_arcade.tf ...@@ -84,7 +82,7 @@ namespace com.al_arcade.tf
_questionText.color = Color.white; _questionText.color = Color.white;
_questionText.fontStyle = TMPro.FontStyles.Bold; _questionText.fontStyle = TMPro.FontStyles.Bold;
_questionText.enableAutoSizing = true; _questionText.enableAutoSizing = true;
// ★ Sensible font range for a 7-unit-wide screen at ~12m distance
_questionText.fontSizeMin = 1.8f; _questionText.fontSizeMin = 1.8f;
_questionText.fontSizeMax = 3.8f; _questionText.fontSizeMax = 3.8f;
_questionText.rectTransform.sizeDelta = new Vector2(6.2f, 2.2f); _questionText.rectTransform.sizeDelta = new Vector2(6.2f, 2.2f);
...@@ -92,7 +90,7 @@ namespace com.al_arcade.tf ...@@ -92,7 +90,7 @@ namespace com.al_arcade.tf
_questionText.overflowMode = TMPro.TextOverflowModes.Ellipsis; _questionText.overflowMode = TMPro.TextOverflowModes.Ellipsis;
SSFontManager.Apply(_questionText); SSFontManager.Apply(_questionText);
// ── Source text ──────────────────────────────────────────
var srcObj = new GameObject("SourceText"); var srcObj = new GameObject("SourceText");
srcObj.transform.SetParent(transform); srcObj.transform.SetParent(transform);
srcObj.transform.localPosition = new Vector3(0, -1.15f, -0.25f); srcObj.transform.localPosition = new Vector3(0, -1.15f, -0.25f);
...@@ -105,7 +103,7 @@ namespace com.al_arcade.tf ...@@ -105,7 +103,7 @@ namespace com.al_arcade.tf
_sourceText.rectTransform.sizeDelta = new Vector2(6f, 0.5f); _sourceText.rectTransform.sizeDelta = new Vector2(6f, 0.5f);
SSFontManager.Apply(_sourceText); SSFontManager.Apply(_sourceText);
// ── Feedback panel (overlays question) ───────────────────
_feedbackPanel = GameObject.CreatePrimitive(PrimitiveType.Cube); _feedbackPanel = GameObject.CreatePrimitive(PrimitiveType.Cube);
_feedbackPanel.name = "FeedbackPanel"; _feedbackPanel.name = "FeedbackPanel";
_feedbackPanel.transform.SetParent(transform); _feedbackPanel.transform.SetParent(transform);
...@@ -122,14 +120,14 @@ namespace com.al_arcade.tf ...@@ -122,14 +120,14 @@ namespace com.al_arcade.tf
fbObj.transform.SetParent(_feedbackPanel.transform); fbObj.transform.SetParent(_feedbackPanel.transform);
fbObj.transform.localPosition = new Vector3(0, 0, -0.15f); fbObj.transform.localPosition = new Vector3(0, 0, -0.15f);
fbObj.transform.localRotation = Quaternion.identity; fbObj.transform.localRotation = Quaternion.identity;
// ★ Scale container so feedback text isn't giant
// FeedbackPanel is 6.6 wide but we want text around 3 units
fbObj.transform.localScale = new Vector3(0.5f, 0.5f, 1f); fbObj.transform.localScale = new Vector3(0.5f, 0.5f, 1f);
_feedbackText = fbObj.AddComponent<ArabicTextMeshPro>(); _feedbackText = fbObj.AddComponent<ArabicTextMeshPro>();
_feedbackText.alignment = TMPro.TextAlignmentOptions.Center; _feedbackText.alignment = TMPro.TextAlignmentOptions.Center;
_feedbackText.color = Color.white; _feedbackText.color = Color.white;
// ★ Moderate feedback font size
_feedbackText.fontSize = 4f; _feedbackText.fontSize = 4f;
_feedbackText.fontSizeMin = 2.5f; _feedbackText.fontSizeMin = 2.5f;
_feedbackText.fontSizeMax = 5f; _feedbackText.fontSizeMax = 5f;
...@@ -140,7 +138,7 @@ namespace com.al_arcade.tf ...@@ -140,7 +138,7 @@ namespace com.al_arcade.tf
_feedbackText.arabicText = ""; _feedbackText.arabicText = "";
SSFontManager.Apply(_feedbackText); SSFontManager.Apply(_feedbackText);
// ── Accent strips (top + bottom) ─────────────────────────
var topStrip = GameObject.CreatePrimitive(PrimitiveType.Cube); var topStrip = GameObject.CreatePrimitive(PrimitiveType.Cube);
topStrip.name = "TopStrip"; topStrip.name = "TopStrip";
topStrip.transform.SetParent(transform); topStrip.transform.SetParent(transform);
...@@ -159,7 +157,7 @@ namespace com.al_arcade.tf ...@@ -159,7 +157,7 @@ namespace com.al_arcade.tf
botStrip.GetComponent<Renderer>().material = botStrip.GetComponent<Renderer>().material =
new Material(shader) { color = SSColorPalette.WithAlpha(SSColorPalette.Accent, 0.5f) }; new Material(shader) { color = SSColorPalette.WithAlpha(SSColorPalette.Accent, 0.5f) };
// ── Status indicator lights ──────────────────────────────
for (int i = -1; i <= 1; i += 2) for (int i = -1; i <= 1; i += 2)
{ {
var light = GameObject.CreatePrimitive(PrimitiveType.Sphere); var light = GameObject.CreatePrimitive(PrimitiveType.Sphere);
...@@ -174,18 +172,17 @@ namespace com.al_arcade.tf ...@@ -174,18 +172,17 @@ namespace com.al_arcade.tf
}; };
} }
// ★ Rotate entire screen so -Z faces camera
Vector3 awayFromCamera = transform.position - cameraPos; Vector3 awayFromCamera = transform.position - cameraPos;
awayFromCamera.y = 0; awayFromCamera.y = 0;
if (awayFromCamera.sqrMagnitude > 0.01f) if (awayFromCamera.sqrMagnitude > 0.01f)
transform.rotation = Quaternion.LookRotation(awayFromCamera); transform.rotation = Quaternion.LookRotation(awayFromCamera);
} }
// ── Show Question ────────────────────────────────────────────
public void ShowQuestion(string text, string source = "") public void ShowQuestion(string text, string source = "")
{ {
// Hide feedback
if (_feedbackMat != null) if (_feedbackMat != null)
{ {
DOTween.Kill(_feedbackMat, "fbMat"); DOTween.Kill(_feedbackMat, "fbMat");
...@@ -207,7 +204,7 @@ namespace com.al_arcade.tf ...@@ -207,7 +204,7 @@ namespace com.al_arcade.tf
if (_sourceText != null) if (_sourceText != null)
_sourceText.arabicText = !string.IsNullOrEmpty(source) ? $"📖 {source}" : ""; _sourceText.arabicText = !string.IsNullOrEmpty(source) ? $"📖 {source}" : "";
// Screen flash effect
if (_screenMat != null) if (_screenMat != null)
{ {
DOTween.Kill(_screenMat, "screenFlash"); DOTween.Kill(_screenMat, "screenFlash");
...@@ -219,7 +216,6 @@ namespace com.al_arcade.tf ...@@ -219,7 +216,6 @@ namespace com.al_arcade.tf
} }
} }
// ── Show Feedback ────────────────────────────────────────────
public void ShowFeedback(bool correct, int streak) public void ShowFeedback(bool correct, int streak)
{ {
...@@ -246,7 +242,6 @@ namespace com.al_arcade.tf ...@@ -246,7 +242,6 @@ namespace com.al_arcade.tf
} }
} }
// ── Clear ────────────────────────────────────────────────────
public void Clear() public void Clear()
{ {
......
...@@ -21,9 +21,6 @@ namespace com.al_arcade.tf ...@@ -21,9 +21,6 @@ namespace com.al_arcade.tf
[Header("Events")] [Header("Events")]
public UnityEvent onRestartClicked; public UnityEvent onRestartClicked;
// ════════════════════════════════════════════════════════════
// BUILD — all sizes tuned for 1920×1080 reference
// ════════════════════════════════════════════════════════════
public void BuildUI() public void BuildUI()
{ {
...@@ -50,14 +47,13 @@ namespace com.al_arcade.tf ...@@ -50,14 +47,13 @@ namespace com.al_arcade.tf
_resultsUI.alpha = 0; _resultsUI.gameObject.SetActive(false); _resultsUI.alpha = 0; _resultsUI.gameObject.SetActive(false);
} }
// ── Game HUD ─────────────────────────────────────────────────
private void BuildGameHUD(Transform parent) private void BuildGameHUD(Transform parent)
{ {
var go = MkPanel(parent, "GameUI"); var go = MkPanel(parent, "GameUI");
_gameUI = go.AddComponent<CanvasGroup>(); _gameUI = go.AddComponent<CanvasGroup>();
// ── Top bar ──────────────────────────────────────────────
var topBarObj = new GameObject("TopBar"); var topBarObj = new GameObject("TopBar");
topBarObj.transform.SetParent(go.transform, false); topBarObj.transform.SetParent(go.transform, false);
var tbr = topBarObj.AddComponent<RectTransform>(); var tbr = topBarObj.AddComponent<RectTransform>();
...@@ -69,26 +65,26 @@ namespace com.al_arcade.tf ...@@ -69,26 +65,26 @@ namespace com.al_arcade.tf
topBarObj.AddComponent<Image>().color = topBarObj.AddComponent<Image>().color =
SSColorPalette.WithAlpha(SSColorPalette.PrimaryDark, 0.82f); SSColorPalette.WithAlpha(SSColorPalette.PrimaryDark, 0.82f);
// Score — top-left
_scoreText = MkTxt(topBarObj.transform, "Score", "0", _scoreText = MkTxt(topBarObj.transform, "Score", "0",
32, new Vector2(0, 0.5f), new Vector2(36, 6), new Vector2(180, 44)); 32, new Vector2(0, 0.5f), new Vector2(36, 6), new Vector2(180, 44));
_scoreText.color = SSColorPalette.Accent; _scoreText.color = SSColorPalette.Accent;
_scoreText.fontStyle = TMPro.FontStyles.Bold; _scoreText.fontStyle = TMPro.FontStyles.Bold;
_scoreText.alignment = TMPro.TextAlignmentOptions.MidlineLeft; _scoreText.alignment = TMPro.TextAlignmentOptions.MidlineLeft;
// Score label
var scoreLbl = MkTxt(topBarObj.transform, "ScoreLbl", "النقاط", var scoreLbl = MkTxt(topBarObj.transform, "ScoreLbl", "النقاط",
14, new Vector2(0, 0.5f), new Vector2(36, -20), new Vector2(100, 22)); 14, new Vector2(0, 0.5f), new Vector2(36, -20), new Vector2(100, 22));
scoreLbl.color = SSColorPalette.WithAlpha(Color.white, 0.5f); scoreLbl.color = SSColorPalette.WithAlpha(Color.white, 0.5f);
scoreLbl.alignment = TMPro.TextAlignmentOptions.MidlineLeft; scoreLbl.alignment = TMPro.TextAlignmentOptions.MidlineLeft;
// Streak — center
_streakText = MkTxt(topBarObj.transform, "Streak", "", _streakText = MkTxt(topBarObj.transform, "Streak", "",
26, new Vector2(0.5f, 0.5f), new Vector2(0, 0), new Vector2(200, 44)); 26, new Vector2(0.5f, 0.5f), new Vector2(0, 0), new Vector2(200, 44));
_streakText.color = SSColorPalette.Warning; _streakText.color = SSColorPalette.Warning;
_streakText.alignment = TMPro.TextAlignmentOptions.Center; _streakText.alignment = TMPro.TextAlignmentOptions.Center;
// ── Progress bar ─────────────────────────────────────────
var progBg = new GameObject("ProgressBg"); var progBg = new GameObject("ProgressBg");
progBg.transform.SetParent(go.transform, false); progBg.transform.SetParent(go.transform, false);
var pr = progBg.AddComponent<RectTransform>(); var pr = progBg.AddComponent<RectTransform>();
...@@ -111,13 +107,13 @@ namespace com.al_arcade.tf ...@@ -111,13 +107,13 @@ namespace com.al_arcade.tf
_progressFill = fill.AddComponent<Image>(); _progressFill = fill.AddComponent<Image>();
_progressFill.color = SSColorPalette.Accent; _progressFill.color = SSColorPalette.Accent;
// Progress label
_progressLabel = MkTxt(go.transform, "ProgLbl", "0 / 5", _progressLabel = MkTxt(go.transform, "ProgLbl", "0 / 5",
16, new Vector2(0.5f, 1), new Vector2(0, -98), new Vector2(200, 22)); 16, new Vector2(0.5f, 1), new Vector2(0, -98), new Vector2(200, 22));
_progressLabel.alignment = TMPro.TextAlignmentOptions.Center; _progressLabel.alignment = TMPro.TextAlignmentOptions.Center;
_progressLabel.color = SSColorPalette.WithAlpha(Color.white, 0.55f); _progressLabel.color = SSColorPalette.WithAlpha(Color.white, 0.55f);
// ── Bottom hint ──────────────────────────────────────────
var hintObj = new GameObject("HintBg"); var hintObj = new GameObject("HintBg");
hintObj.transform.SetParent(go.transform, false); hintObj.transform.SetParent(go.transform, false);
var hbr = hintObj.AddComponent<RectTransform>(); var hbr = hintObj.AddComponent<RectTransform>();
...@@ -136,7 +132,6 @@ namespace com.al_arcade.tf ...@@ -136,7 +132,6 @@ namespace com.al_arcade.tf
hint.color = SSColorPalette.WithAlpha(Color.white, 0.5f); hint.color = SSColorPalette.WithAlpha(Color.white, 0.5f);
} }
// ── Loading ──────────────────────────────────────────────────
private void BuildLoadingPanel(Transform parent) private void BuildLoadingPanel(Transform parent)
{ {
...@@ -150,14 +145,13 @@ namespace com.al_arcade.tf ...@@ -150,14 +145,13 @@ namespace com.al_arcade.tf
_loadingText.alignment = TMPro.TextAlignmentOptions.Center; _loadingText.alignment = TMPro.TextAlignmentOptions.Center;
_loadingText.color = Color.white; _loadingText.color = Color.white;
// Spinner dots
var dots = MkTxt(go.transform, "Dots", "● ● ●", var dots = MkTxt(go.transform, "Dots", "● ● ●",
18, new Vector2(0.5f, 0.5f), new Vector2(0, -36), new Vector2(200, 30)); 18, new Vector2(0.5f, 0.5f), new Vector2(0, -36), new Vector2(200, 30));
dots.alignment = TMPro.TextAlignmentOptions.Center; dots.alignment = TMPro.TextAlignmentOptions.Center;
dots.color = SSColorPalette.Accent; dots.color = SSColorPalette.Accent;
} }
// ── Error ────────────────────────────────────────────────────
private void BuildErrorPanel(Transform parent) private void BuildErrorPanel(Transform parent)
{ {
...@@ -173,18 +167,17 @@ namespace com.al_arcade.tf ...@@ -173,18 +167,17 @@ namespace com.al_arcade.tf
_errorText.enableWordWrapping = true; _errorText.enableWordWrapping = true;
} }
// ── Results ──────────────────────────────────────────────────
private void BuildResultsPanel(Transform parent) private void BuildResultsPanel(Transform parent)
{ {
var go = MkPanel(parent, "ResultsUI"); var go = MkPanel(parent, "ResultsUI");
_resultsUI = go.AddComponent<CanvasGroup>(); _resultsUI = go.AddComponent<CanvasGroup>();
// Semi-transparent background
var bg = go.AddComponent<Image>(); var bg = go.AddComponent<Image>();
bg.color = SSColorPalette.WithAlpha(SSColorPalette.PrimaryDark, 0.94f); bg.color = SSColorPalette.WithAlpha(SSColorPalette.PrimaryDark, 0.94f);
// Card panel (centered box)
var card = new GameObject("Card"); var card = new GameObject("Card");
card.transform.SetParent(go.transform, false); card.transform.SetParent(go.transform, false);
var cr = card.AddComponent<RectTransform>(); var cr = card.AddComponent<RectTransform>();
...@@ -194,33 +187,33 @@ namespace com.al_arcade.tf ...@@ -194,33 +187,33 @@ namespace com.al_arcade.tf
var cardImg = card.AddComponent<Image>(); var cardImg = card.AddComponent<Image>();
cardImg.color = SSColorPalette.WithAlpha(new Color32(30, 30, 55, 255), 0.95f); cardImg.color = SSColorPalette.WithAlpha(new Color32(30, 30, 55, 255), 0.95f);
// Title
_resultTitle = MkTxt(card.transform, "Title", "تم التصنيع! 🎉", _resultTitle = MkTxt(card.transform, "Title", "تم التصنيع! 🎉",
42, new Vector2(0.5f, 1), new Vector2(0, -50), new Vector2(500, 55)); 42, new Vector2(0.5f, 1), new Vector2(0, -50), new Vector2(500, 55));
_resultTitle.alignment = TMPro.TextAlignmentOptions.Center; _resultTitle.alignment = TMPro.TextAlignmentOptions.Center;
_resultTitle.color = SSColorPalette.Accent; _resultTitle.color = SSColorPalette.Accent;
_resultTitle.fontStyle = TMPro.FontStyles.Bold; _resultTitle.fontStyle = TMPro.FontStyles.Bold;
// Score
_resultScore = MkTxt(card.transform, "Score", "0", _resultScore = MkTxt(card.transform, "Score", "0",
56, new Vector2(0.5f, 1), new Vector2(0, -130), new Vector2(300, 70)); 56, new Vector2(0.5f, 1), new Vector2(0, -130), new Vector2(300, 70));
_resultScore.alignment = TMPro.TextAlignmentOptions.Center; _resultScore.alignment = TMPro.TextAlignmentOptions.Center;
_resultScore.color = Color.white; _resultScore.color = Color.white;
_resultScore.fontStyle = TMPro.FontStyles.Bold; _resultScore.fontStyle = TMPro.FontStyles.Bold;
// Score label
MkTxt(card.transform, "ScoreLbl", "النقاط", MkTxt(card.transform, "ScoreLbl", "النقاط",
16, new Vector2(0.5f, 1), new Vector2(0, -185), new Vector2(100, 25)) 16, new Vector2(0.5f, 1), new Vector2(0, -185), new Vector2(100, 25))
.color = SSColorPalette.WithAlpha(Color.white, 0.4f); .color = SSColorPalette.WithAlpha(Color.white, 0.4f);
// Stats
_resultStats = MkTxt(card.transform, "Stats", "", _resultStats = MkTxt(card.transform, "Stats", "",
20, new Vector2(0.5f, 0.5f), new Vector2(0, 10), new Vector2(450, 60)); 20, new Vector2(0.5f, 0.5f), new Vector2(0, 10), new Vector2(450, 60));
_resultStats.alignment = TMPro.TextAlignmentOptions.Center; _resultStats.alignment = TMPro.TextAlignmentOptions.Center;
_resultStats.color = SSColorPalette.WithAlpha(Color.white, 0.65f); _resultStats.color = SSColorPalette.WithAlpha(Color.white, 0.65f);
_resultStats.enableWordWrapping = true; _resultStats.enableWordWrapping = true;
// Restart button
var btnObj = new GameObject("RestartBtn"); var btnObj = new GameObject("RestartBtn");
btnObj.transform.SetParent(card.transform, false); btnObj.transform.SetParent(card.transform, false);
var br = btnObj.AddComponent<RectTransform>(); var br = btnObj.AddComponent<RectTransform>();
...@@ -251,9 +244,6 @@ namespace com.al_arcade.tf ...@@ -251,9 +244,6 @@ namespace com.al_arcade.tf
btnTxt.fontStyle = TMPro.FontStyles.Bold; btnTxt.fontStyle = TMPro.FontStyles.Bold;
} }
// ════════════════════════════════════════════════════════════
// PUBLIC METHODS
// ════════════════════════════════════════════════════════════
public void ShowGameUI() public void ShowGameUI()
{ {
...@@ -325,7 +315,7 @@ namespace com.al_arcade.tf ...@@ -325,7 +315,7 @@ namespace com.al_arcade.tf
if (_resultStats != null) if (_resultStats != null)
_resultStats.arabicText = $"✓ صحيح: {correct} | ✗ خطأ: {wrong}"; _resultStats.arabicText = $"✓ صحيح: {correct} | ✗ خطأ: {wrong}";
// Animated entrance
var seq = DOTween.Sequence(); var seq = DOTween.Sequence();
seq.Append(_resultsUI.DOFade(1, 0.4f)); seq.Append(_resultsUI.DOFade(1, 0.4f));
if (_resultTitle != null) if (_resultTitle != null)
...@@ -340,7 +330,7 @@ namespace com.al_arcade.tf ...@@ -340,7 +330,7 @@ namespace com.al_arcade.tf
seq.Append(_resultScore.transform seq.Append(_resultScore.transform
.DOScale(1, 0.35f).SetEase(Ease.OutBack)); .DOScale(1, 0.35f).SetEase(Ease.OutBack));
// Count-up
int ds = 0; int ds = 0;
DOTween.To(() => ds, x => DOTween.To(() => ds, x =>
{ {
...@@ -358,9 +348,6 @@ namespace com.al_arcade.tf ...@@ -358,9 +348,6 @@ namespace com.al_arcade.tf
if (_resultsUI != null) _resultsUI.gameObject.SetActive(false); if (_resultsUI != null) _resultsUI.gameObject.SetActive(false);
} }
// ════════════════════════════════════════════════════════════
// HELPERS
// ════════════════════════════════════════════════════════════
private GameObject MkPanel(Transform p, string n) private GameObject MkPanel(Transform p, string n)
{ {
......
#!/bin/bash
# ============================================================================
# CODEBASE COLLECTOR v4 — macOS M1/ARM Safe
# Drop into any project root, then run: bash collect_codebase.sh
# ============================================================================
if [ -z "$BASH_VERSION" ]; then
echo "ERROR: Run with: bash $0"
exit 1
fi
set -uo pipefail
# ── Config ──────────────────────────────────────────────────────────────────
OUTFILE="CODEBASE.md"
PROJECT="$(basename "$(pwd)")"
NOW="$(date "+%Y-%m-%d %H:%M:%S")"
MAX_KB=500
# Directories to skip (all levels)
SKIP_DIRS=(
.git .svn .hg
node_modules vendor
venv .venv env .env
__pycache__ .pytest_cache .mypy_cache .tox
dist build .build DerivedData
.next .nuxt .output .cache .parcel-cache
coverage .nyc_output .terraform
target Pods .cocoapods Carthage
.gradle .idea .vscode .vs
obj .sass-cache tmp .tmp logs
xcuserdata .swiftpm .expo
bower_components .serverless .amplify
cdk.out .turbo .angular
)
# File name patterns to skip
SKIP_NAMES=(
".DS_Store"
"Thumbs.db"
"package-lock.json"
"yarn.lock"
"pnpm-lock.yaml"
"Podfile.lock"
"Gemfile.lock"
"composer.lock"
"Cargo.lock"
)
# Extensions to skip (binary / generated)
SKIP_EXT=(
min.js min.css map
pyc pyo class o a so dylib dll exe
bin dat db sqlite sqlite3
ico icns png jpg jpeg gif bmp svg webp avif
mp3 mp4 wav avi mov mkv flac ogg
pdf zip tar gz rar 7z bz2 xz
woff woff2 ttf eot otf
pb tfstate
lock
)
# Extensions to include as code
CODE_EXT=(
sh bash zsh fish
html htm css scss sass less styl
js jsx ts tsx mjs cjs
vue svelte astro
py pyi pyw pyx
rb erb rake gemspec
php phtml
java kt kts scala groovy gradle
c h cpp hpp cc cxx hxx hh
m mm
cs csx fs fsx vb
swift rs go dart lua
pl pm t
r R Rmd
hs lhs
ex exs erl hrl
clj cljs cljc edn
zig nim v
json jsonc json5
yaml yml toml
xml xsl xsd plist
ini cfg conf properties
md mdx rst txt adoc
sql graphql gql proto
tf tfvars hcl nix
cmake mk csv
gitignore dockerignore editorconfig
env
)
# Exact filenames to always include
EXACT_NAMES=(
Makefile makefile GNUmakefile CMakeLists.txt
Dockerfile Containerfile Vagrantfile
Rakefile Gemfile Procfile Brewfile
Podfile Fastfile Dangerfile Jenkinsfile
Justfile Taskfile Earthfile Caddyfile Tiltfile
.gitignore .gitattributes .editorconfig
.eslintrc .prettierrc .babelrc .nvmrc
.ruby-version .python-version .tool-versions
.env.example .env.sample .flake8 .pylintrc
.swiftlint.yml .rubocop.yml
setup.cfg setup.py pyproject.toml
requirements.txt constraints.txt
docker-compose.yml docker-compose.yaml
tsconfig.json jsconfig.json
webpack.config.js vite.config.ts vite.config.js
rollup.config.js esbuild.config.js
tailwind.config.js tailwind.config.ts
postcss.config.js postcss.config.cjs
next.config.js next.config.mjs next.config.ts
nuxt.config.ts svelte.config.js astro.config.mjs
angular.json nx.json turbo.json lerna.json
jest.config.js jest.config.ts vitest.config.ts
playwright.config.ts cypress.config.ts
Cargo.toml go.mod go.sum
Package.swift Package.resolved pubspec.yaml
build.gradle settings.gradle pom.xml
mix.exs rebar.config stack.yaml
flake.nix shell.nix default.nix
deno.json deno.jsonc bun.lockb
nginx.conf project.pbxproj
)
# ── Temp files ──────────────────────────────────────────────────────────────
TMP_FILES="$(mktemp /tmp/cbc_files.XXXXXX)"
TMP_OUT="$(mktemp /tmp/cbc_out.XXXXXX)"
cleanup() { rm -f "$TMP_FILES" "$TMP_OUT"; }
trap cleanup EXIT
# ── Helpers ─────────────────────────────────────────────────────────────────
get_ext() {
local name="$1"
case "$name" in
*.*) echo "${name##*.}" ;;
*) echo "" ;;
esac
}
in_array() {
local needle="$1"
shift
local item
for item in "$@"; do
if [ "$needle" = "$item" ]; then
return 0
fi
done
return 1
}
is_binary_file() {
local mime
mime="$(file --mime-encoding -- "$1" 2>/dev/null || echo "binary")"
case "$mime" in
*binary*) return 0 ;;
*) return 1 ;;
esac
}
file_bytes() {
wc -c < "$1" 2>/dev/null | tr -d '[:space:]'
}
file_lines() {
wc -l < "$1" 2>/dev/null | tr -d '[:space:]'
}
human_size() {
echo "$1" | awk '{
if ($1 > 1048576) printf "%.1fMB", $1/1048576
else if ($1 > 1024) printf "%.1fKB", $1/1024
else printf "%dB", $1
}'
}
get_lang() {
local base ext
base="$(basename "$1")"
ext="$(get_ext "$base")"
# Special filenames first
case "$base" in
Makefile|makefile|GNUmakefile) echo "makefile" ; return ;;
Dockerfile*|Containerfile) echo "dockerfile" ; return ;;
Vagrantfile|Gemfile|Rakefile) echo "ruby" ; return ;;
Podfile|Fastfile|Dangerfile) echo "ruby" ; return ;;
Jenkinsfile) echo "groovy" ; return ;;
Justfile) echo "just" ; return ;;
Tiltfile|Earthfile) echo "python" ; return ;;
.gitignore|.dockerignore) echo "gitignore" ; return ;;
.editorconfig) echo "ini" ; return ;;
.env*) echo "bash" ; return ;;
nginx.conf) echo "nginx" ; return ;;
*.config.js|*.config.ts) echo "javascript" ; return ;;
esac
# By extension
case "$ext" in
sh|bash|zsh|fish) echo "bash" ;;
py|pyi|pyw|pyx) echo "python" ;;
js|mjs|cjs) echo "javascript" ;;
jsx) echo "jsx" ;;
ts) echo "typescript" ;;
tsx) echo "tsx" ;;
rb|rake|gemspec|erb) echo "ruby" ;;
php|phtml) echo "php" ;;
java) echo "java" ;;
kt|kts) echo "kotlin" ;;
scala) echo "scala" ;;
groovy|gradle) echo "groovy" ;;
c|h) echo "c" ;;
cpp|hpp|cc|cxx|hxx|hh) echo "cpp" ;;
m|mm) echo "objectivec" ;;
cs|csx) echo "csharp" ;;
fs|fsx) echo "fsharp" ;;
vb) echo "vb" ;;
swift) echo "swift" ;;
rs) echo "rust" ;;
go) echo "go" ;;
dart) echo "dart" ;;
lua) echo "lua" ;;
pl|pm|t) echo "perl" ;;
r|R) echo "r" ;;
Rmd) echo "rmarkdown" ;;
hs|lhs) echo "haskell" ;;
ex|exs) echo "elixir" ;;
erl|hrl) echo "erlang" ;;
clj|cljs|cljc|edn) echo "clojure" ;;
zig) echo "zig" ;;
nim) echo "nim" ;;
v) echo "v" ;;
html|htm) echo "html" ;;
css) echo "css" ;;
scss) echo "scss" ;;
sass) echo "sass" ;;
less) echo "less" ;;
styl) echo "stylus" ;;
vue) echo "vue" ;;
svelte) echo "svelte" ;;
astro) echo "astro" ;;
json|jsonc|json5) echo "json" ;;
yaml|yml) echo "yaml" ;;
toml) echo "toml" ;;
xml|xsl|xsd|plist) echo "xml" ;;
ini|cfg|conf|properties) echo "ini" ;;
env) echo "bash" ;;
md|mdx) echo "markdown" ;;
rst) echo "rst" ;;
txt|adoc) echo "text" ;;
sql) echo "sql" ;;
graphql|gql) echo "graphql" ;;
proto) echo "protobuf" ;;
tf|tfvars|hcl) echo "hcl" ;;
nix) echo "nix" ;;
cmake) echo "cmake" ;;
*) echo "" ;;
esac
}
# ── Check: should we process this file? ─────────────────────────────────────
check_file() {
local filepath="$1"
local filename ext bytes kb
filename="$(basename "$filepath")"
ext="$(get_ext "$filename")"
# Skip self and output
[ "$filename" = "$OUTFILE" ] && return 1
[ "$filename" = "collect_codebase.sh" ] && return 1
# Skip by exact name
if in_array "$filename" "${SKIP_NAMES[@]}"; then
return 1
fi
# Skip by extension
if [ -n "$ext" ] && in_array "$ext" "${SKIP_EXT[@]}"; then
return 1
fi
# Check size
bytes="$(file_bytes "$filepath")"
bytes="${bytes:-0}"
kb=$((bytes / 1024))
if [ "$kb" -gt "$MAX_KB" ]; then
return 1
fi
# Must match code extension OR exact filename
local match=false
if in_array "$filename" "${EXACT_NAMES[@]}"; then
match=true
fi
if [ "$match" = false ] && [ -n "$ext" ] && in_array "$ext" "${CODE_EXT[@]}"; then
match=true
fi
if [ "$match" = false ]; then
return 1
fi
# Skip binary
if is_binary_file "$filepath"; then
return 2
fi
return 0
}
# ── Build find command to get all files ─────────────────────────────────────
find_all_files() {
# Build prune expression for directories
local prune_args=""
local d
for d in "${SKIP_DIRS[@]}"; do
if [ -n "$prune_args" ]; then
prune_args="$prune_args -o -name $d"
else
prune_args="-name $d"
fi
done
# Find all regular files, pruning skip dirs, sorted
eval "find . \\( $prune_args \\) -prune -o -type f -print" 2>/dev/null | sort
}
# ── Build directory tree ────────────────────────────────────────────────────
build_tree() {
if command -v tree >/dev/null 2>&1; then
local ignore
ignore="$(IFS='|'; echo "${SKIP_DIRS[*]}")"
tree -a -I "$ignore" --charset utf-8 --dirsfirst -F 2>/dev/null
else
echo "."
# Simple fallback using find
local prune_args=""
local d
for d in "${SKIP_DIRS[@]}"; do
if [ -n "$prune_args" ]; then
prune_args="$prune_args -o -name $d"
else
prune_args="-name $d"
fi
done
eval "find . \\( $prune_args \\) -prune -o -print" 2>/dev/null \
| sed 1d \
| sort \
| awk '{
# Count depth by slashes
n = gsub(/\//, "/")
indent = ""
for (i = 0; i < n; i++) indent = indent " "
# Get basename
split($0, parts, "/")
name = parts[length(parts)]
print indent "├── " name
}' \
| head -800
fi
}
# ── Banner ──────────────────────────────────────────────────────────────────
echo ""
echo "╔══════════════════════════════════════════════════════╗"
echo "║ CODEBASE COLLECTOR v4 ║"
echo "╚══════════════════════════════════════════════════════════╝"
echo ""
echo " Project: $PROJECT"
echo " Output: $OUTFILE"
echo ""
echo " Scanning all folders and subfolders..."
echo ""
# ── Collect all file paths ──────────────────────────────────────────────────
find_all_files > "$TMP_FILES"
total_found="$(wc -l < "$TMP_FILES" | tr -d '[:space:]')"
echo " Found $total_found files total. Filtering..."
# ── Write markdown ──────────────────────────────────────────────────────────
file_count=0
skipped_count=0
binary_count=0
total_lines=0
# --- Header ---
cat > "$TMP_OUT" <<HEADER
# Codebase: \`${PROJECT}\`
> Generated on ${NOW}
---
## Directory Structure
\`\`\`
HEADER
build_tree >> "$TMP_OUT"
echo '```' >> "$TMP_OUT"
echo "" >> "$TMP_OUT"
echo "---" >> "$TMP_OUT"
echo "" >> "$TMP_OUT"
echo "## File Contents" >> "$TMP_OUT"
echo "" >> "$TMP_OUT"
# --- Each file ---
while IFS= read -r fpath; do
# Strip leading ./
fpath="${fpath#./}"
# Skip empty lines
[ -z "$fpath" ] && continue
# Check file
check_file "$fpath"
result=$?
if [ $result -eq 1 ]; then
skipped_count=$((skipped_count + 1))
continue
fi
if [ $result -eq 2 ]; then
binary_count=$((binary_count + 1))
continue
fi
# Get metadata
lang="$(get_lang "$fpath")"
lc="$(file_lines "$fpath")"
lc="${lc:-0}"
total_lines=$((total_lines + lc))
bytes="$(file_bytes "$fpath")"
bytes="${bytes:-0}"
sz="$(human_size "$bytes")"
# Write separator and header
echo "" >> "$TMP_OUT"
echo "---" >> "$TMP_OUT"
echo "" >> "$TMP_OUT"
echo "### \`$fpath\`" >> "$TMP_OUT"
echo "" >> "$TMP_OUT"
echo "> Lines: $lc | Size: $sz" >> "$TMP_OUT"
echo "" >> "$TMP_OUT"
# Opening code fence
echo "\`\`\`$lang" >> "$TMP_OUT"
# Dump file contents safely with cat
cat -- "$fpath" >> "$TMP_OUT" 2>/dev/null
# Ensure trailing newline before closing fence
echo "" >> "$TMP_OUT"
# Closing code fence
echo '```' >> "$TMP_OUT"
file_count=$((file_count + 1))
# Progress indicator
if [ $((file_count % 10)) -eq 0 ]; then
echo " ... processed $file_count files" >&2
fi
done < "$TMP_FILES"
# --- Footer ---
echo "" >> "$TMP_OUT"
echo "---" >> "$TMP_OUT"
echo "" >> "$TMP_OUT"
echo "## Summary" >> "$TMP_OUT"
echo "" >> "$TMP_OUT"
echo "| Metric | Value |" >> "$TMP_OUT"
echo "|--------|-------|" >> "$TMP_OUT"
echo "| Files included | $file_count |" >> "$TMP_OUT"
echo "| Files skipped | $skipped_count |" >> "$TMP_OUT"
echo "| Binary skipped | $binary_count |" >> "$TMP_OUT"
echo "| Total lines | $total_lines |" >> "$TMP_OUT"
echo "| Generated | $NOW |" >> "$TMP_OUT"
echo "" >> "$TMP_OUT"
echo "---" >> "$TMP_OUT"
# ── Move output ─────────────────────────────────────────────────────────────
mv -f "$TMP_OUT" "$OUTFILE"
# ── Results ─────────────────────────────────────────────────────────────────
out_bytes="$(file_bytes "$OUTFILE")"
out_bytes="${out_bytes:-0}"
out_sz="$(human_size "$out_bytes")"
echo ""
echo " ================================================"
echo " DONE"
echo " ================================================"
echo ""
echo " Output: $OUTFILE"
echo " Size: $out_sz"
echo " Files collected: $file_count"
echo " Files skipped: $skipped_count"
echo " Binary skipped: $binary_count"
echo " Total lines: $total_lines"
echo ""
echo " ================================================"
echo ""
if [ "$out_bytes" -gt 10485760 ]; then
echo " WARNING: Output exceeds 10MB."
echo " Add more dirs to SKIP_DIRS or lower MAX_KB."
echo ""
fi
fileFormatVersion: 2 fileFormatVersion: 2
guid: 5fe070f9e16c540beb7eae4411def4a3 guid: 172cb1724b3354adbba5be5427c4ab94
folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}
userData: userData:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment