Commit c336f670 authored by Mahmoud Aglan's avatar Mahmoud Aglan

SAAAAADDDD START WORKING

parent 9833ad6e
This diff is collapsed.
......@@ -20,7 +20,7 @@ namespace com.al_arcade.cs
var shader = Shader.Find("Universal Render Pipeline/Lit")
?? Shader.Find("Standard") ?? Shader.Find("Unlit/Color");
// ── Body ─────────────────────────────────────────────────
_body = GameObject.CreatePrimitive(PrimitiveType.Capsule);
_body.name = "Body";
_body.transform.SetParent(transform);
......@@ -30,7 +30,7 @@ namespace com.al_arcade.cs
_bodyMat = new Material(shader) { color = SSColorPalette.Primary };
_body.GetComponent<Renderer>().material = _bodyMat;
// ── Head ─────────────────────────────────────────────────
_head = GameObject.CreatePrimitive(PrimitiveType.Sphere);
_head.name = "Head";
_head.transform.SetParent(transform);
......@@ -40,7 +40,7 @@ namespace com.al_arcade.cs
_headMat = new Material(shader) { color = SSColorPalette.Accent };
_head.GetComponent<Renderer>().material = _headMat;
// ── Eyes ─────────────────────────────────────────────────
var eyeMat = new Material(shader) { color = Color.white };
var pupilMat = new Material(shader) { color = SSColorPalette.TextDark };
......@@ -65,7 +65,7 @@ namespace com.al_arcade.cs
pupil.GetComponent<Renderer>().material = new Material(pupilMat);
}
// ── Mouth ────────────────────────────────────────────────
_mouth = GameObject.CreatePrimitive(PrimitiveType.Cube);
_mouth.name = "Mouth";
_mouth.transform.SetParent(_head.transform);
......@@ -75,7 +75,7 @@ namespace com.al_arcade.cs
_mouthMat = new Material(shader) { color = SSColorPalette.Danger };
_mouth.GetComponent<Renderer>().material = _mouthMat;
// ── Arms ─────────────────────────────────────────────────
for (int side = -1; side <= 1; side += 2)
{
var arm = GameObject.CreatePrimitive(PrimitiveType.Capsule);
......@@ -89,7 +89,7 @@ namespace com.al_arcade.cs
new Material(shader) { color = SSColorPalette.PrimaryDark };
}
// ── Feet ─────────────────────────────────────────────────
for (int side = -1; side <= 1; side += 2)
{
var foot = GameObject.CreatePrimitive(PrimitiveType.Cube);
......@@ -160,7 +160,6 @@ namespace com.al_arcade.cs
}
}
// ── Reactions ────────────────────────────────────────────────
public void PlayHappy()
{
......@@ -183,7 +182,7 @@ namespace com.al_arcade.cs
.OnComplete(() => _headMat.DOColor(SSColorPalette.Accent, 0.4f));
}
// Reset mouth after delay
DOTween.Sequence().SetDelay(1f).OnComplete(() =>
{
if (_mouth == null) return;
......
......@@ -44,7 +44,7 @@ namespace com.al_arcade.cs
Debug.Log(" Correct-the-Sentence Game");
Debug.Log("═══════════════════════════════════════════");
// ── EventSystem ──────────────────────────────────────────
if (FindObjectOfType<EventSystem>() == null)
{
var es = new GameObject("EventSystem");
......@@ -52,7 +52,7 @@ namespace com.al_arcade.cs
es.AddComponent<StandaloneInputModule>();
}
// ── Managers ─────────────────────────────────────────────
SSApiManager.EnsureInstance();
SSAudioManager.EnsureInstance();
SSParticleManager.EnsureInstance();
......@@ -64,49 +64,49 @@ namespace com.al_arcade.cs
session.classCode = classCode;
yield return null;
// ── Scene ────────────────────────────────────────────────
BuildScene();
yield return null;
// ── Camera ───────────────────────────────────────────────
SetupCamera();
yield return null;
// ── Bot ──────────────────────────────────────────────────
var botObj = new GameObject("Bot");
botObj.transform.position = new Vector3(0, 0, 0);
_bot = botObj.AddComponent<CsBotController>();
_bot.Build();
yield return null;
// ── Word Container ───────────────────────────────────────
var wordContainer = new GameObject("WordContainer");
wordContainer.transform.position = Vector3.zero;
yield return null;
// ── UI ───────────────────────────────────────────────────
var uiObj = new GameObject("CsUI");
_uiManager = uiObj.AddComponent<CsUIManager>();
_uiManager.BuildUI();
// ★ FIX: Initialize the UnityEvent if null
if (_uiManager.onRestartClicked == null)
_uiManager.onRestartClicked = new UnityEvent();
yield return null;
// ── Game Manager ─────────────────────────────────────────
var gmObj = new GameObject("CsGameManager");
_gm = gmObj.AddComponent<CsGameManager>();
_gm.bot = _bot;
_gm.uiManager = _uiManager;
_gm.wordContainer = wordContainer.transform;
// ★ FIX: Safe listener via named method
_uiManager.onRestartClicked.AddListener(OnRestartClicked);
yield return null;
// ── Lighting ─────────────────────────────────────────────
SetupLighting();
yield return null;
......@@ -118,16 +118,13 @@ namespace com.al_arcade.cs
yield return new WaitForSeconds(0.5f);
// ── Start ────────────────────────────────────────────────
if (useOfflineTestData)
_gm.StartWithQuestions(GetTestQuestions());
else
_gm.StartGame();
}
// ════════════════════════════════════════════════════════════
// RESTART HANDLER
// ════════════════════════════════════════════════════════════
private void OnRestartClicked()
{
......@@ -135,9 +132,6 @@ namespace com.al_arcade.cs
if (_gm != null) _gm.StartGame();
}
// ════════════════════════════════════════════════════════════
// SCENE
// ════════════════════════════════════════════════════════════
private void BuildScene()
{
......@@ -146,7 +140,7 @@ namespace com.al_arcade.cs
var shader = GetShader();
// Ground
var ground = GameObject.CreatePrimitive(PrimitiveType.Cube);
ground.name = "Ground";
ground.transform.position = new Vector3(0, -0.05f, 0);
......@@ -154,7 +148,7 @@ namespace com.al_arcade.cs
ground.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(40, 40, 65, 255) };
// Circular platform
var platform = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
platform.name = "Platform";
platform.transform.position = new Vector3(0, 0.05f, 0);
......@@ -163,7 +157,7 @@ namespace com.al_arcade.cs
platform.GetComponent<Renderer>().material =
new Material(shader) { color = SSColorPalette.WithAlpha(SSColorPalette.Primary, 0.6f) };
// Outer ring with pulse
var ring = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
ring.name = "Ring";
ring.transform.position = new Vector3(0, 0.04f, 0);
......@@ -175,7 +169,7 @@ namespace com.al_arcade.cs
ringMat.DOFade(0.1f, 1.5f).SetEase(Ease.InOutSine)
.SetLoops(-1, LoopType.Yoyo);
// Background decorations
for (int i = 0; i < 20; i++)
{
var deco = GameObject.CreatePrimitive(
......@@ -213,7 +207,7 @@ namespace com.al_arcade.cs
.SetLoops(-1, LoopType.Incremental).SetEase(Ease.Linear);
}
// Grid lines on ground
for (float x = -9; x <= 9; x += 3)
{
var line = GameObject.CreatePrimitive(PrimitiveType.Cube);
......@@ -235,7 +229,7 @@ namespace com.al_arcade.cs
{ color = SSColorPalette.WithAlpha(SSColorPalette.Primary, 0.08f) };
}
// Floor label
var floorText = new GameObject("FloorLabel");
floorText.transform.position = new Vector3(0, 0.02f, -3);
floorText.transform.rotation = Quaternion.Euler(90, 0, 0);
......@@ -276,7 +270,7 @@ namespace com.al_arcade.cs
dirLight.intensity = 0.9f;
dirLight.shadows = LightShadows.Soft;
// Accent light on bot
var accent = new GameObject("AccentLight");
var aLight = accent.AddComponent<Light>();
aLight.type = LightType.Point;
......@@ -285,7 +279,7 @@ namespace com.al_arcade.cs
aLight.intensity = 0.8f;
aLight.color = SSColorPalette.Accent;
// Blue back light
var back = new GameObject("BackLight");
var bLight = back.AddComponent<Light>();
bLight.type = LightType.Point;
......@@ -298,9 +292,6 @@ namespace com.al_arcade.cs
RenderSettings.ambientLight = new Color32(40, 42, 60, 255);
}
// ════════════════════════════════════════════════════════════
// TEST DATA
// ════════════════════════════════════════════════════════════
private CsQuestion[] GetTestQuestions()
{
......@@ -418,4 +409,4 @@ namespace com.al_arcade.cs
?? Shader.Find("Unlit/Color");
}
}
}
\ No newline at end of file
}
......@@ -29,7 +29,7 @@ namespace com.al_arcade.cs
public CsUIManager uiManager;
public Transform wordContainer;
// State
private CsGameState _state = CsGameState.Idle;
private CsQuestion[] _questions;
private int _currentIndex;
......@@ -52,9 +52,6 @@ namespace com.al_arcade.cs
Instance = this;
}
// ═══════════════════════════════════════════════════════════
// PUBLIC API
// ═══════════════════════════════════════════════════════════
public void StartGame() => StartCoroutine(StartGameRoutine());
......@@ -112,9 +109,6 @@ namespace com.al_arcade.cs
_onOptionDropped?.Invoke(isCorrectOption);
}
// ═══════════════════════════════════════════════════════════
// GAME FLOW
// ═══════════════════════════════════════════════════════════
private IEnumerator StartGameRoutine()
{
......@@ -277,9 +271,6 @@ namespace com.al_arcade.cs
yield return new WaitForSeconds(0.8f);
}
// ═══════════════════════════════════════════════════════════
// WORD SPAWNING — ★ Dynamic sizing, no overlaps
// ═══════════════════════════════════════════════════════════
private void SpawnWords(CsQuestion question)
{
......@@ -288,23 +279,23 @@ namespace com.al_arcade.cs
int wordCount = question.words.Length;
// ★ Calculate total width needed based on actual text lengths
float padding = 0.6f; // extra padding per word card
float gapBetween = 0.25f; // gap between cards
float charWidth = 0.32f; // approx width per character
float padding = 0.6f;
float gapBetween = 0.25f;
float charWidth = 0.32f;
float[] wordWidths = new float[wordCount];
float totalWidth = 0;
for (int i = 0; i < wordCount; i++)
{
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;
totalWidth += w;
}
totalWidth += (wordCount - 1) * gapBetween;
// ★ If total width exceeds max, scale everything down
float maxAllowedWidth = 14f;
float scaleFactor = 1f;
if (totalWidth > maxAllowedWidth)
......@@ -315,12 +306,12 @@ namespace com.al_arcade.cs
wordWidths[i] *= scaleFactor;
}
// Base position above bot
Vector3 basePos = bot != null
? bot.transform.position + Vector3.up * 3.5f
: Vector3.up * 4f;
// ★ RTL layout: start from right edge
float startX = totalWidth / 2f;
float currentX = startX;
......@@ -339,7 +330,7 @@ namespace com.al_arcade.cs
wb.Setup(word.word_text, word.is_wrong, i, wordWidths[i], scaleFactor);
_wordButtons.Add(wb);
// Entrance animation
wordObj.transform.localScale = Vector3.zero;
wordObj.transform.DOScale(Vector3.one, 0.4f)
.SetDelay(i * 0.08f)
......@@ -378,7 +369,6 @@ namespace com.al_arcade.cs
_wordClickLocked = false;
}
// ── Victory ──────────────────────────────────────────────────
private IEnumerator VictorySequence()
{
......
......@@ -23,7 +23,7 @@ namespace com.al_arcade.cs
private CsWordButton _targetWord;
private CanvasGroup _canvasGroup;
// ★ Drag offset: distance from pointer to rect center at grab time
private Vector2 _dragOffset;
private bool _originalPosCaptured;
private Camera _canvasCamera;
......@@ -38,20 +38,20 @@ namespace com.al_arcade.cs
_rect = GetComponent<RectTransform>();
if (_rect == null) _rect = gameObject.AddComponent<RectTransform>();
// ★ CanvasGroup so we can make it not block raycasts while dragging
_canvasGroup = gameObject.AddComponent<CanvasGroup>();
// Determine canvas camera (null for ScreenSpaceOverlay)
_canvasCamera = null;
if (_canvas != null && _canvas.renderMode != RenderMode.ScreenSpaceOverlay)
_canvasCamera = _canvas.worldCamera;
// Background
_bg = gameObject.AddComponent<Image>();
_bg.color = SSColorPalette.Card;
_bg.raycastTarget = true;
// Text
var textObj = new GameObject("Text");
textObj.transform.SetParent(transform, false);
var textRect = textObj.AddComponent<RectTransform>();
......@@ -72,41 +72,37 @@ namespace com.al_arcade.cs
_text.fontSizeMax = 22;
SSFontManager.Apply(_text);
// Entrance animation
transform.localScale = Vector3.one * 0.5f;
transform.DOScale(Vector3.one, 0.3f).SetEase(Ease.OutBack);
_originalPosCaptured = false;
}
// ════════════════════════════════════════════════════════════
// DRAG HANDLERS — ★ Cursor-accurate positioning
// ════════════════════════════════════════════════════════════
public void OnBeginDrag(PointerEventData eventData)
{
// ★ Capture the original anchored position for return-to-origin
_originalAnchoredPos = _rect.anchoredPosition;
_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;
RectTransformUtility.ScreenPointToLocalPointInRectangle(
_rect.parent as RectTransform, // parent rect
eventData.position, // screen pointer
_canvasCamera, // camera (null for overlay)
_rect.parent as RectTransform,
eventData.position,
_canvasCamera,
out localPointerPos);
_dragOffset = _rect.anchoredPosition - localPointerPos;
// Visual feedback
_bg.color = SSColorPalette.WithAlpha(SSColorPalette.Primary, 0.3f);
DOTween.Kill(transform, "optScale");
transform.DOScale(Vector3.one * 1.08f, 0.12f).SetId("optScale");
transform.SetAsLastSibling();
// ★ Let raycasts pass through while dragging (optional, helps with drop detection)
_canvasGroup.blocksRaycasts = false;
var audio = SSAudioManager.Instance;
......@@ -117,7 +113,7 @@ namespace com.al_arcade.cs
{
if (_rect == null || _canvas == null) return;
// ★ Convert screen pointer to local position in parent
Vector2 localPointerPos;
RectTransformUtility.ScreenPointToLocalPointInRectangle(
_rect.parent as RectTransform,
......@@ -125,16 +121,16 @@ namespace com.al_arcade.cs
_canvasCamera,
out localPointerPos);
// ★ Apply with the initial offset so the card stays under the finger
_rect.anchoredPosition = localPointerPos + _dragOffset;
}
public void OnEndDrag(PointerEventData eventData)
{
// Re-enable raycasts
_canvasGroup.blocksRaycasts = true;
// ★ Check if dropped near the target word
bool droppedOnTarget = false;
if (_targetWord != null && Camera.main != null)
{
......@@ -151,7 +147,7 @@ namespace com.al_arcade.cs
if (IsCorrect)
{
// Snap to target and disappear
DOTween.Kill(transform);
transform.DOScale(Vector3.zero, 0.3f)
.SetEase(Ease.InBack)
......@@ -163,14 +159,14 @@ namespace com.al_arcade.cs
}
else
{
// Wrong — shake then return
DOTween.Kill(transform, "optShake");
transform.DOShakePosition(0.3f, 10f, 15).SetId("optShake");
}
}
}
// ★ Return to original position (always, unless correct + destroyed)
ReturnToOrigin();
}
......@@ -188,9 +184,6 @@ namespace com.al_arcade.cs
}
}
// ════════════════════════════════════════════════════════════
// HOVER
// ════════════════════════════════════════════════════════════
public void OnPointerEnter(PointerEventData eventData)
{
......@@ -205,9 +198,6 @@ namespace com.al_arcade.cs
_bg.DOColor(SSColorPalette.Card, 0.15f).SetId("optHover");
}
// ════════════════════════════════════════════════════════════
// CLEANUP
// ════════════════════════════════════════════════════════════
private void OnDestroy()
{
......@@ -216,4 +206,4 @@ namespace com.al_arcade.cs
if (_bg != null) DOTween.Kill(_bg);
}
}
}
\ No newline at end of file
}
......@@ -28,9 +28,6 @@ namespace com.al_arcade.cs
[Header("Events")]
public UnityEvent onRestartClicked;
// ════════════════════════════════════════════════════════════
// BUILD
// ════════════════════════════════════════════════════════════
public void BuildUI()
{
......@@ -61,14 +58,13 @@ namespace com.al_arcade.cs
_feedbackGroup.alpha = 0;
}
// ── Game HUD ─────────────────────────────────────────────────
private void BuildGameHUD(Transform parent)
{
var go = MkFull(parent, "GameUI");
_gameUI = go.AddComponent<CanvasGroup>();
// Top bar
var topBar = new GameObject("TopBar");
topBar.transform.SetParent(go.transform, false);
var tbr = topBar.AddComponent<RectTransform>();
......@@ -93,7 +89,7 @@ namespace com.al_arcade.cs
_streakText.color = SSColorPalette.Warning;
_streakText.alignment = TMPro.TextAlignmentOptions.Center;
// Progress bar
var pbg = MkFull(go.transform, "ProgressBg");
var pr = pbg.GetComponent<RectTransform>();
pr.anchorMin = new Vector2(0.25f, 1); pr.anchorMax = new Vector2(0.75f, 1);
......@@ -115,7 +111,7 @@ namespace com.al_arcade.cs
_progressText.alignment = TMPro.TextAlignmentOptions.Center;
_progressText.color = SSColorPalette.WithAlpha(Color.white, 0.5f);
// Hint
_hintText = MkTxt(go.transform, "Hint", "", 18,
new Vector2(0.5f, 0), new Vector2(0, 200), new Vector2(600, 40));
_hintText.alignment = TMPro.TextAlignmentOptions.Center;
......@@ -123,7 +119,6 @@ namespace com.al_arcade.cs
_hintText.fontStyle = TMPro.FontStyles.Italic;
}
// ── Options Panel ────────────────────────────────────────────
private void BuildOptionsPanel(Transform parent)
{
......@@ -155,7 +150,6 @@ namespace com.al_arcade.cs
.color = SSColorPalette.WithAlpha(SSColorPalette.Accent, 0.6f);
}
// ── Feedback Overlay ─────────────────────────────────────────
private void BuildFeedbackOverlay(Transform parent)
{
......@@ -185,7 +179,6 @@ namespace com.al_arcade.cs
_feedbackText.enableWordWrapping = true;
}
// ── Loading ──────────────────────────────────────────────────
private void BuildLoadingPanel(Transform parent)
{
......@@ -199,7 +192,6 @@ namespace com.al_arcade.cs
_loadingText.color = Color.white;
}
// ── Error ────────────────────────────────────────────────────
private void BuildErrorPanel(Transform parent)
{
......@@ -214,7 +206,6 @@ namespace com.al_arcade.cs
_errorText.enableWordWrapping = true;
}
// ── Results ──────────────────────────────────────────────────
private void BuildResultsPanel(Transform parent)
{
......@@ -241,7 +232,7 @@ namespace com.al_arcade.cs
_resultStats.color = SSColorPalette.WithAlpha(Color.white, 0.7f);
_resultStats.enableWordWrapping = true;
// Restart button
var btn = new GameObject("RestartBtn");
btn.transform.SetParent(go.transform, false);
var br = btn.AddComponent<RectTransform>();
......@@ -264,9 +255,6 @@ namespace com.al_arcade.cs
bt.fontStyle = TMPro.FontStyles.Bold;
}
// ════════════════════════════════════════════════════════════
// PUBLIC METHODS
// ════════════════════════════════════════════════════════════
public void ShowGameUI()
{ _gameUI.gameObject.SetActive(true); _gameUI.DOFade(1f, 0.3f); }
......@@ -333,7 +321,6 @@ namespace com.al_arcade.cs
}
}
// ── Options ──────────────────────────────────────────────────
public void ShowOptions(CsOption[] options, CsWordButton targetWord)
{
......@@ -387,7 +374,6 @@ namespace com.al_arcade.cs
_activeOptions.Clear();
}
// ── Loading / Error / Results ────────────────────────────────
public void ShowLoading(string msg)
{ _loadingUI.gameObject.SetActive(true); if (_loadingText != null) _loadingText.arabicText = msg; _loadingUI.DOFade(1f, 0.3f); }
......@@ -440,7 +426,6 @@ namespace com.al_arcade.cs
ClearOptions();
}
// ── Helpers ──────────────────────────────────────────────────
private GameObject MkFull(Transform p, string n)
{
......
......@@ -22,9 +22,7 @@ namespace com.al_arcade.cs
private float _cardWidth;
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,
float cardWidth = -1f, float scaleFactor = 1f)
{
......@@ -32,7 +30,7 @@ namespace com.al_arcade.cs
IsWrong = isWrong;
Index = index;
// ★ If width not provided, calculate from text
if (cardWidth <= 0)
_cardWidth = text.Length * 0.32f + 0.6f;
else
......@@ -49,7 +47,7 @@ namespace com.al_arcade.cs
var shader = Shader.Find("Universal Render Pipeline/Lit")
?? Shader.Find("Standard") ?? Shader.Find("Unlit/Color");
// ── Background card ──────────────────────────────────────
_background = GameObject.CreatePrimitive(PrimitiveType.Cube);
_background.name = "WordBg";
_background.transform.SetParent(transform);
......@@ -60,11 +58,11 @@ namespace com.al_arcade.cs
_bgMaterial = new Material(shader) { color = SSColorPalette.NeutralWord };
_background.GetComponent<Renderer>().material = _bgMaterial;
// ── Text ─────────────────────────────────────────────────
var textObj = new GameObject("WordText");
textObj.transform.SetParent(transform);
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;
_text = textObj.AddComponent<ArabicTextMeshPro>();
......@@ -81,11 +79,11 @@ namespace com.al_arcade.cs
_text.overflowMode = TMPro.TextOverflowModes.Ellipsis;
SSFontManager.Apply(_text);
// ── Collider (matches card exactly) ──────────────────────
_collider = GetComponent<BoxCollider>();
_collider.size = new Vector3(_cardWidth, _cardHeight, 0.2f);
// ── Shadow ───────────────────────────────────────────────
var shadow = GameObject.CreatePrimitive(PrimitiveType.Cube);
shadow.name = "Shadow";
shadow.transform.SetParent(transform);
......@@ -120,7 +118,7 @@ namespace com.al_arcade.cs
if (_bgMaterial != null) DOTween.Kill(_bgMaterial);
}
// ── Billboard towards camera ─────────────────────────────────
private void LateUpdate()
{
if (Camera.main == null) return;
......@@ -130,7 +128,6 @@ namespace com.al_arcade.cs
transform.rotation = Quaternion.LookRotation(awayFromCam);
}
// ── Mouse Interaction ────────────────────────────────────────
private void OnMouseEnter()
{
......@@ -158,7 +155,6 @@ namespace com.al_arcade.cs
CsGameManager.Instance.OnWordClicked(this);
}
// ── Public Methods ───────────────────────────────────────────
public void Highlight(bool isWrongWord)
{
......@@ -173,7 +169,7 @@ namespace com.al_arcade.cs
if (_text != null) _text.color = Color.white;
transform.DOPunchScale(Vector3.one * 0.2f, 0.4f, 8, 0.3f);
// Pulsing glow
DOTween.Sequence()
.Append(_bgMaterial.DOColor(Color.Lerp(targetColor, Color.white, 0.3f), 0.5f))
.Append(_bgMaterial.DOColor(targetColor, 0.5f))
......
......@@ -46,7 +46,7 @@ namespace com.al_arcade.mcq
{
Debug.Log("══════ MCQ Runner Demo Builder ══════");
// ── EventSystem ──────────────────────────────────────────
if (FindObjectOfType<EventSystem>() == null)
{
var es = new GameObject("EventSystem");
......@@ -54,7 +54,7 @@ namespace com.al_arcade.mcq
es.AddComponent<StandaloneInputModule>();
}
// ── Managers ─────────────────────────────────────────────
SSApiManager.EnsureInstance();
SSAudioManager.EnsureInstance();
SSParticleManager.EnsureInstance();
......@@ -66,31 +66,31 @@ namespace com.al_arcade.mcq
session.classCode = classCode;
yield return null;
// ── Scene ────────────────────────────────────────────────
BuildScene();
yield return null;
// ── Player ───────────────────────────────────────────────
BuildPlayer();
yield return null;
// ── Question Display ─────────────────────────────────────
BuildQuestionDisplay();
yield return null;
// ── UI ───────────────────────────────────────────────────
BuildUI();
yield return null;
// ── Game Manager ─────────────────────────────────────────
BuildGameManager();
yield return null;
// ── Camera ───────────────────────────────────────────────
SetupCamera();
yield return null;
// ── Lighting ─────────────────────────────────────────────
SetupLighting();
yield return null;
......@@ -98,16 +98,13 @@ namespace com.al_arcade.mcq
yield return new WaitForSeconds(0.3f);
// ── Start ────────────────────────────────────────────────
if (useOfflineTestData)
_gm.StartWithQuestions(GetTestQuestions());
else
_gm.StartGame();
}
// ════════════════════════════════════════════════════════════
// BUILD METHODS
// ════════════════════════════════════════════════════════════
private void BuildScene()
{
......@@ -116,7 +113,7 @@ namespace com.al_arcade.mcq
var shader = GetLitShader();
// Road (centered at x=0, extends along +Z)
var road = GameObject.CreatePrimitive(PrimitiveType.Cube);
road.name = "Road";
road.transform.position = new Vector3(0, -0.05f, 200);
......@@ -124,7 +121,7 @@ namespace com.al_arcade.mcq
road.GetComponent<Renderer>().material = new Material(shader)
{ color = new Color32(60, 60, 80, 255) };
// Lane dividers
for (int i = -1; i <= 1; i += 2)
{
var line = GameObject.CreatePrimitive(PrimitiveType.Cube);
......@@ -136,7 +133,7 @@ namespace com.al_arcade.mcq
{ color = SSColorPalette.WithAlpha(SSColorPalette.Accent, 0.4f) };
}
// Center dashes
for (float z = 0; z < 400; z += 4)
{
var dash = GameObject.CreatePrimitive(PrimitiveType.Cube);
......@@ -148,7 +145,7 @@ namespace com.al_arcade.mcq
{ color = SSColorPalette.WithAlpha(Color.white, 0.2f) };
}
// Side decorations
for (int side = -1; side <= 1; side += 2)
{
for (float z = 0; z < 400; z += 20)
......@@ -167,7 +164,7 @@ namespace com.al_arcade.mcq
}
}
// Horizon
var horizon = GameObject.CreatePrimitive(PrimitiveType.Cube);
horizon.name = "Horizon";
horizon.transform.position = new Vector3(0, 2f, 500);
......@@ -207,7 +204,7 @@ namespace com.al_arcade.mcq
_uiManager = obj.AddComponent<McqUIManager>();
_uiManager.BuildUI();
// ★ FIX: Initialize the UnityEvent if null
if (_uiManager.onRestartClicked == null)
_uiManager.onRestartClicked = new UnityEvent();
}
......@@ -221,7 +218,7 @@ namespace com.al_arcade.mcq
_gm.uiManager = _uiManager;
_gm.gateParent = new GameObject("Gates").transform;
// ★ FIX: Safe listener — all refs captured after they exist
_uiManager.onRestartClicked.AddListener(OnRestartClicked);
}
......@@ -272,9 +269,6 @@ namespace com.al_arcade.mcq
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
}
// ════════════════════════════════════════════════════════════
// TEST DATA
// ════════════════════════════════════════════════════════════
private McqQuestion[] GetTestQuestions()
{
......@@ -306,9 +300,7 @@ namespace com.al_arcade.mcq
}
}
// ════════════════════════════════════════════════════════════════
// Camera follow — 3rd-person runner (behind + above)
// ════════════════════════════════════════════════════════════════
public class McqCameraFollow : MonoBehaviour
{
public Transform target;
......@@ -328,4 +320,4 @@ namespace com.al_arcade.mcq
Time.deltaTime * smoothSpeed);
}
}
}
\ No newline at end of file
}
......@@ -62,7 +62,6 @@ namespace com.al_arcade.mcq
Instance = this;
}
// ── Public API ───────────────────────────────────────────────
public void StartGame() => StartCoroutine(StartGameRoutine());
......@@ -90,7 +89,6 @@ namespace com.al_arcade.mcq
if (questionDisplay != null) questionDisplay.Hide();
}
// ── Game Flow ────────────────────────────────────────────────
private IEnumerator StartGameRoutine()
{
......@@ -202,7 +200,7 @@ namespace com.al_arcade.mcq
_state = McqGameState.AnswerFeedback;
yield return ProcessAnswer(answered && wasCorrect);
// Cleanup gates
foreach (var gate in _activeGates)
{
if (gate != null)
......@@ -272,9 +270,6 @@ namespace com.al_arcade.mcq
yield return new WaitForSeconds(feedbackDisplayTime);
}
// ══════════════════════════════════════════════════════════
// GATE SPAWNING — always road-center X=0
// ══════════════════════════════════════════════════════════
private void SpawnGates(McqQuestion question)
{
......@@ -284,7 +279,7 @@ namespace com.al_arcade.mcq
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);
float totalWidth = (answers.Length - 1) * gateSpacing;
......@@ -315,7 +310,6 @@ namespace com.al_arcade.mcq
return gate;
}
// ── Feedback ─────────────────────────────────────────────────
private void ShowCorrectFeedback(int points)
{
......
......@@ -42,11 +42,11 @@ namespace com.al_arcade.mcq
{
var shader = GetShader();
// ── Pillars ──────────────────────────────────────────────
CreatePillar(Vector3.left * (GateWidth / 2f), "LeftPillar", shader);
CreatePillar(Vector3.right * (GateWidth / 2f), "RightPillar", shader);
// ── Top bar ──────────────────────────────────────────────
var topBar = GameObject.CreatePrimitive(PrimitiveType.Cube);
topBar.name = "TopBar";
topBar.transform.SetParent(transform);
......@@ -56,7 +56,7 @@ namespace com.al_arcade.mcq
var topMat = new Material(shader) { color = SSColorPalette.Primary };
topBar.GetComponent<Renderer>().material = topMat;
// ── Answer panel — on the +Z side (back wall of gate archway) ──
_backPanel = GameObject.CreatePrimitive(PrimitiveType.Cube);
_backPanel.name = "AnswerPanel";
_backPanel.transform.SetParent(transform);
......@@ -66,12 +66,12 @@ namespace com.al_arcade.mcq
_panelMaterial = new Material(shader) { color = SSColorPalette.GateDefault };
_backPanel.GetComponent<Renderer>().material = _panelMaterial;
// ── Text label — identity rotation, readable from -Z (camera side) ──
var textObj = new GameObject("AnswerLabel");
textObj.transform.SetParent(transform);
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;
_label = textObj.AddComponent<ArabicTextMeshPro>();
......@@ -88,13 +88,13 @@ namespace com.al_arcade.mcq
_label.overflowMode = TMPro.TextOverflowModes.Ellipsis;
SSFontManager.Apply(_label);
// ── Trigger collider ─────────────────────────────────────
_collider = gameObject.AddComponent<BoxCollider>();
_collider.isTrigger = true;
_collider.center = new Vector3(0, GateHeight / 2f, 0);
_collider.size = new Vector3(GateWidth, GateHeight, GateDepth);
// ── Floor highlight ──────────────────────────────────────
var floor = GameObject.CreatePrimitive(PrimitiveType.Cube);
floor.name = "FloorHighlight";
floor.transform.SetParent(transform);
......@@ -147,7 +147,6 @@ namespace com.al_arcade.mcq
if (_label != null) DOTween.Kill(_label.transform);
}
// ── Collision ────────────────────────────────────────────────
private void OnTriggerEnter(Collider other)
{
......@@ -157,7 +156,6 @@ namespace com.al_arcade.mcq
onPlayerEnter?.Invoke(GateIndex);
}
// ── Feedback Animations ──────────────────────────────────────
public void PlayCorrectAnimation()
{
......
......@@ -25,7 +25,7 @@ namespace com.al_arcade.mcq
var shader = Shader.Find("Universal Render Pipeline/Lit") ??
Shader.Find("Standard") ?? Shader.Find("Unlit/Color");
// Background panel (Cube)
_panel = GameObject.CreatePrimitive(PrimitiveType.Cube);
_panel.name = "QuestionPanel";
_panel.transform.SetParent(transform);
......@@ -36,7 +36,7 @@ namespace com.al_arcade.mcq
{ color = new Color(0.12f, 0.12f, 0.5f, 0.95f) };
_panel.GetComponent<Renderer>().material = panelMat;
// Accent bar
var bar = GameObject.CreatePrimitive(PrimitiveType.Cube);
bar.name = "AccentBar";
bar.transform.SetParent(_panel.transform);
......@@ -46,8 +46,7 @@ namespace com.al_arcade.mcq
bar.GetComponent<Renderer>().material = new Material(shader)
{ color = SSColorPalette.Accent };
// ★ Question text — identity local rotation
// Parent will billboard to face camera
var textObj = new GameObject("QuestionText");
textObj.transform.SetParent(transform);
textObj.transform.localPosition = new Vector3(0, 0.2f, -0.06f);
......@@ -65,7 +64,7 @@ namespace com.al_arcade.mcq
_questionText.overflowMode = TMPro.TextOverflowModes.Ellipsis;
SSFontManager.Apply(_questionText);
// Source text
var srcObj = new GameObject("SourceText");
srcObj.transform.SetParent(transform);
srcObj.transform.localPosition = new Vector3(0, -0.85f, -0.06f);
......@@ -104,14 +103,14 @@ namespace com.al_arcade.mcq
{
if (_playerTransform == null) return;
// ★ Follow player, stay between player and gates
Vector3 targetPos = _playerTransform.position
+ Vector3.up * floatHeight
+ Vector3.forward * forwardOffset;
transform.position = Vector3.Lerp(transform.position, targetPos,
Time.deltaTime * followSpeed);
// ★ Billboard: face away from camera so text (-Z face) faces camera
if (Camera.main != null)
{
Vector3 awayFromCam = transform.position - Camera.main.transform.position;
......
......@@ -27,9 +27,6 @@ namespace com.al_arcade.mcq
[Header("Events")]
public UnityEvent onRestartClicked;
// ════════════════════════════════════════════════════════════
// BUILD
// ════════════════════════════════════════════════════════════
public void BuildUI()
{
......@@ -82,7 +79,7 @@ namespace com.al_arcade.mcq
new Vector2(200, -15), 24, TMPro.TextAlignmentOptions.TopLeft);
_streakText.color = SSColorPalette.Warning;
// Hearts
_heartIcons = new Image[5];
for (int i = 0; i < 5; i++)
{
......@@ -98,7 +95,7 @@ namespace com.al_arcade.mcq
h.SetActive(false);
}
// Progress bar
var progBg = MakeImg(go.transform, "ProgressBg",
new Vector2(0, 0), new Vector2(1, 0),
SSColorPalette.WithAlpha(Color.white, 0.1f));
......@@ -208,7 +205,7 @@ namespace com.al_arcade.mcq
new Vector2(200, 0), 24);
_resultStreak.color = SSColorPalette.Accent;
// Restart button
var btnObj = new GameObject("RestartBtn");
btnObj.transform.SetParent(go.transform, false);
var br = btnObj.AddComponent<RectTransform>();
......@@ -229,9 +226,6 @@ namespace com.al_arcade.mcq
btnTxt.fontStyle = TMPro.FontStyles.Bold;
}
// ══════════════════════════════════════════════════════════
// PUBLIC
// ══════════════════════════════════════════════════════════
public void ShowGameUI()
{
......@@ -377,7 +371,6 @@ namespace com.al_arcade.mcq
_feedbackUI.alpha = 0;
}
// ── Helpers ──────────────────────────────────────────────────
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.Collections;
using System.Collections.Generic;
......@@ -21,7 +19,7 @@ namespace com.al_arcade.shared
[SerializeField] private int timeoutSeconds = 15;
// ── Singleton Setup ──────────────────────────────────────────
private void Awake()
{
if (Instance != null && Instance != this)
......@@ -33,7 +31,7 @@ namespace com.al_arcade.shared
DontDestroyOnLoad(gameObject);
}
// ── Ensure Instance Exists ───────────────────────────────────
public static SSApiManager EnsureInstance()
{
if (Instance != null) return Instance;
......@@ -43,9 +41,6 @@ namespace com.al_arcade.shared
return Instance;
}
// ════════════════════════════════════════════════════════════
// GENERIC REQUEST
// ════════════════════════════════════════════════════════════
public IEnumerator GetRequest(string action,
Dictionary<string, string> parameters,
......@@ -79,9 +74,6 @@ namespace com.al_arcade.shared
onSuccess?.Invoke(json);
}
// ════════════════════════════════════════════════════════════
// PING
// ════════════════════════════════════════════════════════════
public IEnumerator Ping(Action<bool> onResult)
{
......@@ -90,9 +82,6 @@ namespace com.al_arcade.shared
err => onResult?.Invoke(false));
}
// ════════════════════════════════════════════════════════════
// VALIDATE CLASS CODE
// ════════════════════════════════════════════════════════════
public IEnumerator ValidateClassCode(string code,
Action<ClassInfo> onSuccess,
......@@ -120,9 +109,6 @@ namespace com.al_arcade.shared
onError);
}
// ════════════════════════════════════════════════════════════
// GET GRADES
// ════════════════════════════════════════════════════════════
public IEnumerator GetGrades(Action<GradeInfo[]> onSuccess,
Action<string> onError)
......@@ -146,9 +132,6 @@ namespace com.al_arcade.shared
onError);
}
// ════════════════════════════════════════════════════════════
// FETCH MCQ QUESTIONS
// ════════════════════════════════════════════════════════════
public IEnumerator FetchMcq(string buildType, string classCode,
int count, int gradeId,
......@@ -183,9 +166,6 @@ namespace com.al_arcade.shared
onError);
}
// ════════════════════════════════════════════════════════════
// FETCH T/F QUESTIONS
// ════════════════════════════════════════════════════════════
public IEnumerator FetchTf(string buildType, string classCode,
int count, int gradeId,
......@@ -220,9 +200,6 @@ namespace com.al_arcade.shared
onError);
}
// ════════════════════════════════════════════════════════════
// FETCH CS QUESTIONS
// ════════════════════════════════════════════════════════════
public IEnumerator FetchCs(string buildType, string classCode,
int count, int gradeId,
......
// ============================================================================
// Science Street — Audio Manager
// Plays SFX with optional AudioClip references.
// Works silently if no clips are assigned.
// ============================================================================
using UnityEngine;
using System.Collections.Generic;
......@@ -77,7 +74,6 @@ namespace com.al_arcade.shared
return src;
}
// ── Play Methods ─────────────────────────────────────────────
public void Play(AudioClip clip, float volumeScale = 1f, float pitch = 1f)
{
......@@ -104,10 +100,7 @@ namespace com.al_arcade.shared
public void PlayCheer(float vol = 1f) => Play(sfxCheer, 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,
float volume = 0.3f)
{
......@@ -119,7 +112,7 @@ namespace com.al_arcade.shared
for (int i = 0; i < sampleCount; i++)
{
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;
}
......@@ -135,10 +128,10 @@ namespace com.al_arcade.shared
public void PlayClickBeep() => PlayProceduralBeep(660f, 0.05f, 0.15f);
public void PlaySuccessJingle()
{
// Three ascending tones
PlayProceduralBeep(523f, 0.12f, 0.2f); // C5
StartCoroutine(DelayedBeep(660f, 0.12f, 0.1f)); // E5
StartCoroutine(DelayedBeep(784f, 0.2f, 0.2f)); // G5
PlayProceduralBeep(523f, 0.12f, 0.2f);
StartCoroutine(DelayedBeep(660f, 0.12f, 0.1f));
StartCoroutine(DelayedBeep(784f, 0.2f, 0.2f));
}
public void PlayFailBuzz()
......
// ============================================================================
// Science Street — Shared Color Palette
// AL-Arcade Game Dev Team
// ============================================================================
using UnityEngine;
namespace com.al_arcade.shared
{
/// <summary>
/// Central color definitions matching the Science Street brand.
/// </summary>
public static class SSColorPalette
{
// ── Brand Colors ─────────────────────────────────────────────
public static readonly Color Primary = new Color32(48, 48, 208, 255); // #3030D0
public static readonly Color PrimaryDark = new Color32(32, 32, 168, 255); // #2020A8
public static readonly Color PrimaryLight = new Color32(80, 80, 220, 255); // #5050DC
public static readonly Color Accent = new Color32(254, 215, 0, 255); // #FED700
public static readonly Color AccentDark = new Color32(212, 180, 0, 255); // #D4B400
// ── Feedback Colors ──────────────────────────────────────────
public static readonly Color Success = new Color32(16, 185, 129, 255); // #10B981
public static readonly Color SuccessLight = new Color32(209, 250, 229, 255); // #D1FAE5
public static readonly Color Danger = new Color32(239, 68, 68, 255); // #EF4444
public static readonly Color DangerLight = new Color32(254, 226, 226, 255); // #FEE2E2
public static readonly Color Warning = new Color32(245, 158, 11, 255); // #F59E0B
public static readonly Color Info = new Color32(59, 130, 246, 255); // #3B82F6
// ── UI Colors ────────────────────────────────────────────────
public static readonly Color Background = new Color32(238, 240, 248, 255); // #EEF0F8
public static readonly Color Primary = new Color32(48, 48, 208, 255);
public static readonly Color PrimaryDark = new Color32(32, 32, 168, 255);
public static readonly Color PrimaryLight = new Color32(80, 80, 220, 255);
public static readonly Color Accent = new Color32(254, 215, 0, 255);
public static readonly Color AccentDark = new Color32(212, 180, 0, 255);
public static readonly Color Success = new Color32(16, 185, 129, 255);
public static readonly Color SuccessLight = new Color32(209, 250, 229, 255);
public static readonly Color Danger = new Color32(239, 68, 68, 255);
public static readonly Color DangerLight = new Color32(254, 226, 226, 255);
public static readonly Color Warning = new Color32(245, 158, 11, 255);
public static readonly Color Info = new Color32(59, 130, 246, 255);
public static readonly Color Background = new Color32(238, 240, 248, 255);
public static readonly Color Card = Color.white;
public static readonly Color TextDark = new Color32(26, 26, 46, 255); // #1A1A2E
public static readonly Color TextMuted = new Color32(107, 114, 128, 255); // #6B7280
public static readonly Color Border = new Color32(229, 231, 235, 255); // #E5E7EB
public static readonly Color TextDark = new Color32(26, 26, 46, 255);
public static readonly Color TextMuted = new Color32(107, 114, 128, 255);
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 GateWrong = new Color32(239, 68, 68, 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 FalseRed = new Color32(239, 68, 68, 255); // #EF4444
public static readonly Color TrueGreen = new Color32(34, 197, 94, 255);
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 CorrectWord = new Color32(16, 185, 129, 255);
public static readonly Color NeutralWord = new Color32(248, 249, 252, 255);
// ── Helpers ──────────────────────────────────────────────────
public static Color WithAlpha(Color c, float 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 UnityEngine;
using Newtonsoft.Json;
namespace com.al_arcade.shared
{
// ── API Wrapper Responses ────────────────────────────────────────
[Serializable]
public class ApiResponse<T>
......@@ -37,7 +35,6 @@ namespace com.al_arcade.shared
public ClassInfo classInfo;
}
// ── Core Data Types ──────────────────────────────────────────────
[Serializable]
public class GradeInfo
......@@ -58,30 +55,26 @@ namespace com.al_arcade.shared
public string teacher_name;
}
// ── MCQ ──────────────────────────────────────────────────────────
[Serializable]
public class McqQuestion
{
public string id;
public string question_text;
public string answer1; // always correct
public string answer1;
public string answer2;
public string answer3;
public string answer4;
public string source;
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)
{
string[] arr = { answer1, answer2, answer3, answer4 };
string correct = answer1;
// Fisher-Yates shuffle
for (int i = arr.Length - 1; i > 0; i--)
{
int j = UnityEngine.Random.Range(0, i + 1);
......@@ -93,7 +86,6 @@ namespace com.al_arcade.shared
}
}
// ── True/False ───────────────────────────────────────────────────
[Serializable]
public class TfQuestion
......@@ -105,7 +97,6 @@ namespace com.al_arcade.shared
public string grade_name;
}
// ── Correct Sentence ─────────────────────────────────────────────
[Serializable]
public class CsQuestion
......
......@@ -2,10 +2,8 @@ using UnityEngine;
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
{
private static TMPro.TMP_FontAsset _font;
......@@ -26,4 +24,4 @@ namespace com.al_arcade.shared
if (_font != null && tmp != null) tmp.font = _font;
}
}
}
\ No newline at end of file
}
// ============================================================================
// Science Street — Game Session Manager
// Holds current game session state (build type, class code, etc.)
// ============================================================================
using System;
using System.Collections;
using UnityEngine;
......@@ -23,14 +21,14 @@ namespace com.al_arcade.shared
[Tooltip("Number of questions to fetch")]
public int questionCount = 10;
// ── Runtime State (Teacher Build) ────────────────────────────
[HideInInspector] public string classCode = "";
[HideInInspector] public string className = "";
[HideInInspector] public string teacherName = "";
[HideInInspector] public string gradeName = "";
[HideInInspector] public bool isClassValidated = false;
// ── Events ───────────────────────────────────────────────────
[Header("Events")]
public UnityEvent onSessionReady;
public UnityEvent<string> onSessionError;
......@@ -55,9 +53,7 @@ namespace com.al_arcade.shared
return Instance;
}
/// <summary>
/// Validates a teacher class code. On success, stores all class info.
/// </summary>
public IEnumerator ValidateAndStartTeacherSession(string code,
Action onSuccess, Action<string> onError)
{
......@@ -89,9 +85,7 @@ namespace com.al_arcade.shared
});
}
/// <summary>
/// Starts a Science Street (public) session.
/// </summary>
public void StartSciStreetSession(int grade = 0)
{
buildType = "scistreet";
......@@ -102,9 +96,7 @@ namespace com.al_arcade.shared
onSessionReady?.Invoke();
}
/// <summary>
/// Resets the session to defaults.
/// </summary>
public void ResetSession()
{
buildType = "scistreet";
......
// ============================================================================
// Science Street — Particle Effect Manager
// Creates particle effects programmatically. Works without any prefabs.
// ============================================================================
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
......@@ -41,7 +39,6 @@ namespace com.al_arcade.shared
return Instance;
}
// ── Public Play Methods ──────────────────────────────────────
public void PlayCorrectBurst(Vector3 position)
{
......@@ -105,7 +102,6 @@ namespace com.al_arcade.shared
PlayConfetti(pos);
}
// ── Procedural Particle Creation ─────────────────────────────
private void PlayProceduralBurst(Vector3 pos, Color startColor,
Color endColor, int count, float lifetime)
......@@ -141,7 +137,7 @@ namespace com.al_arcade.shared
new Keyframe(0f, 1f),
new Keyframe(1f, 0f)));
// Use default material
var renderer = go.GetComponent<ParticleSystemRenderer>();
renderer.material = GetParticleMaterial();
......@@ -169,7 +165,7 @@ namespace com.al_arcade.shared
main.loop = false;
main.duration = 0.2f;
// Random color from our palette
var gradient = new Gradient();
gradient.SetKeys(
new[] {
......@@ -208,7 +204,6 @@ namespace com.al_arcade.shared
Destroy(go, 5f);
}
// ── Helpers ──────────────────────────────────────────────────
private void SpawnPrefab(ParticleSystem prefab, Vector3 pos)
{
......@@ -225,7 +220,7 @@ namespace com.al_arcade.shared
{
if (_cachedMat != null) return _cachedMat;
// Try to find the default particle shader
Shader shader = Shader.Find("Particles/Standard Unlit");
if (shader == null) shader = Shader.Find("Universal Render Pipeline/Particles/Unlit");
if (shader == null) shader = Shader.Find("Mobile/Particles/Alpha Blended");
......@@ -233,7 +228,7 @@ namespace com.al_arcade.shared
if (shader == null) shader = Shader.Find("Unlit/Color");
_cachedMat = new Material(shader);
_cachedMat.SetFloat("_Mode", 2); // Fade
_cachedMat.SetFloat("_Mode", 2);
return _cachedMat;
}
}
......
......@@ -43,13 +43,13 @@ namespace com.al_arcade.tf
float side = isLeft ? -1f : 1f;
var shader = GetShader();
// ── Hand root ────────────────────────────────────────────
hand = new GameObject(isLeft ? "LeftHand" : "RightHand");
hand.transform.SetParent(transform);
hand.transform.localPosition = new Vector3(side * 0.35f, -0.3f, 0.55f);
hand.transform.localRotation = Quaternion.Euler(12f, -side * 8f, side * 4f);
// ── Arm (smaller, more proportional) ─────────────────────
var arm = GameObject.CreatePrimitive(PrimitiveType.Capsule);
arm.name = "Arm";
arm.transform.SetParent(hand.transform);
......@@ -59,7 +59,7 @@ namespace com.al_arcade.tf
arm.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(255, 220, 180, 255) };
// ── Button (sized for close-up FPS view) ─────────────────
btn = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
btn.name = isLeft ? "TrueButton" : "FalseButton";
btn.transform.SetParent(hand.transform);
......@@ -73,17 +73,17 @@ namespace com.al_arcade.tf
};
btn.GetComponent<Renderer>().material = mat;
// ── Label — ★ small font, tight rect, on top of button ───
var labelObj = new GameObject("Label");
labelObj.transform.SetParent(btn.transform);
labelObj.transform.localPosition = new Vector3(0, 0.8f, 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);
label = labelObj.AddComponent<ArabicTextMeshPro>();
label.arabicText = isLeft ? "صح ✓" : "خطأ ✗";
// ★ fontSize 2 in a small world-space rect = readable not giant
label.fontSize = 2.2f;
label.fontSizeMin = 1.5f;
label.fontSizeMax = 2.5f;
......@@ -91,12 +91,12 @@ namespace com.al_arcade.tf
label.alignment = TMPro.TextAlignmentOptions.Center;
label.color = Color.white;
label.fontStyle = TMPro.FontStyles.Bold;
// ★ Tight rect so text doesn't overflow
label.rectTransform.sizeDelta = new Vector2(1.2f, 0.5f);
label.overflowMode = TMPro.TextOverflowModes.Overflow;
SSFontManager.Apply(label);
// ── Glow ring ────────────────────────────────────────────
var ring = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
ring.name = "GlowRing";
ring.transform.SetParent(btn.transform);
......@@ -110,7 +110,6 @@ namespace com.al_arcade.tf
};
}
// ── Idle Animation ───────────────────────────────────────────
private void StartIdleAnimation()
{
......@@ -139,7 +138,6 @@ namespace com.al_arcade.tf
_idleSeq = null;
}
// ── Input ────────────────────────────────────────────────────
public void SetReady(bool ready) => _isReady = ready;
......@@ -161,14 +159,14 @@ namespace com.al_arcade.tf
var restPos = isTrue ? _leftRestPos : _rightRestPos;
var btnBaseScale = isTrue ? _leftBtnBaseScale : _rightBtnBaseScale;
// ★ Kill ALL tweens, reset to known state
KillIdleTween();
DOTween.Kill(hand.transform);
DOTween.Kill(btn.transform);
hand.transform.localPosition = restPos;
btn.transform.localScale = btnBaseScale;
// ★ Press animation — always returns to restPos
var pressSeq = DOTween.Sequence();
pressSeq.Append(hand.transform
.DOLocalMoveY(restPos.y + 0.06f, 0.07f)
......@@ -181,7 +179,7 @@ namespace com.al_arcade.tf
.SetEase(Ease.OutBounce));
pressSeq.OnComplete(() => StartIdleAnimation());
// ★ Button squash (Y axis is thickness for cylinder)
btn.transform.DOScaleY(btnBaseScale.y * 0.3f, 0.05f)
.SetEase(Ease.InQuad)
.OnComplete(() =>
......@@ -195,7 +193,6 @@ namespace com.al_arcade.tf
TfGameManager.Instance.SubmitAnswer(isTrue);
}
// ── Feedback ─────────────────────────────────────────────────
public void PlayCorrectFeedback(bool wasTrue)
{
......@@ -270,4 +267,4 @@ namespace com.al_arcade.tf
Shader.Find("Universal Render Pipeline/Lit")
?? Shader.Find("Standard") ?? Shader.Find("Unlit/Color");
}
}
\ No newline at end of file
}
......@@ -12,9 +12,7 @@ namespace com.al_arcade.tf
[AddComponentMenu("Science Street/TF Prefab Builder")]
public class TfPrefabBuilder : MonoBehaviour
{
// ════════════════════════════════════════════════════════════
// SCENE
// ════════════════════════════════════════════════════════════
[Header("Scene Environment")]
[Tooltip("Your full factory environment prefab.")]
......@@ -26,18 +24,12 @@ namespace com.al_arcade.tf
[SerializeField] private float cameraFOV = 55f;
[SerializeField] private Color cameraBgColor = new Color32(70, 80, 110, 255);
// ════════════════════════════════════════════════════════════
// LIGHTING
// ════════════════════════════════════════════════════════════
[Header("Lighting")]
[SerializeField] private GameObject directionalLightPrefab;
[SerializeField] private GameObject[] extraLightPrefabs;
[SerializeField] private Color ambientColor = new Color32(75, 78, 95, 255);
// ════════════════════════════════════════════════════════════
// PRODUCTION LINE
// ════════════════════════════════════════════════════════════
[Header("Production Line")]
[Tooltip("Your production line prefab. MUST have TfProductionLine.\n" +
......@@ -45,9 +37,6 @@ namespace com.al_arcade.tf
[SerializeField] private GameObject productionLinePrefab;
[SerializeField] private Vector3 productionLinePosition = new Vector3(0, 0, 2);
// ════════════════════════════════════════════════════════════
// QUESTION SCREEN
// ════════════════════════════════════════════════════════════
[Header("Question Screen")]
[Tooltip("Your question screen prefab. MUST have TfQuestionScreen.\n" +
......@@ -55,27 +44,18 @@ namespace com.al_arcade.tf
[SerializeField] private GameObject questionScreenPrefab;
[SerializeField] private Vector3 questionScreenPosition = new Vector3(0, 5f, 12f);
// ════════════════════════════════════════════════════════════
// HANDS
// ════════════════════════════════════════════════════════════
[Header("Hands / Buttons")]
[Tooltip("Your hands prefab. MUST have TfHandController.\n" +
"If null, procedural hands are created.")]
[SerializeField] private GameObject handsPrefab;
// ════════════════════════════════════════════════════════════
// UI
// ════════════════════════════════════════════════════════════
[Header("UI Canvas")]
[Tooltip("Your Canvas prefab. MUST have TfUIManager with all references wired.\n" +
"If null, procedural UI is created.")]
[SerializeField] private GameObject canvasPrefab;
// ════════════════════════════════════════════════════════════
// AUDIO
// ════════════════════════════════════════════════════════════
[Header("Audio — SFX Clips")]
[SerializeField] private AudioClip sfxCorrect;
......@@ -87,9 +67,6 @@ namespace com.al_arcade.tf
[SerializeField] private AudioClip sfxPop;
[SerializeField] private AudioClip sfxSlam;
// ════════════════════════════════════════════════════════════
// PARTICLES
// ════════════════════════════════════════════════════════════
[Header("Particles")]
[SerializeField] private ParticleSystem correctBurstParticle;
......@@ -98,16 +75,10 @@ namespace com.al_arcade.tf
[SerializeField] private ParticleSystem sparksParticle;
[SerializeField] private ParticleSystem starBurstParticle;
// ════════════════════════════════════════════════════════════
// FONT
// ════════════════════════════════════════════════════════════
[Header("Arabic Font")]
[SerializeField] private TMP_FontAsset arabicFont;
// ════════════════════════════════════════════════════════════
// GAME SETTINGS
// ════════════════════════════════════════════════════════════
[Header("Game Settings")]
[SerializeField] private string buildType = "scistreet";
......@@ -120,9 +91,6 @@ namespace com.al_arcade.tf
[Header("Debug")]
[SerializeField] private bool useOfflineTestData = false;
// ════════════════════════════════════════════════════════════
// RUNTIME
// ════════════════════════════════════════════════════════════
private TfGameManager _gm;
private TfHandController _handController;
......@@ -159,20 +127,20 @@ namespace com.al_arcade.tf
session.classCode = classCode;
yield return null;
// ── Camera ───────────────────────────────────────────
SetupCamera();
yield return null;
// ── Environment ──────────────────────────────────────
if (environmentPrefab != null)
Instantiate(environmentPrefab, Vector3.zero, Quaternion.identity);
yield return null;
// ── Lighting ─────────────────────────────────────────
SetupLighting();
yield return null;
// ── Production Line ──────────────────────────────────
if (productionLinePrefab != null)
{
var lineObj = Instantiate(productionLinePrefab,
......@@ -194,7 +162,7 @@ namespace com.al_arcade.tf
}
yield return null;
// ── Question Screen ──────────────────────────────────
if (questionScreenPrefab != null)
{
var screenObj = Instantiate(questionScreenPrefab,
......@@ -215,7 +183,7 @@ namespace com.al_arcade.tf
}
yield return null;
// ── Hands ────────────────────────────────────────────
if (handsPrefab != null)
{
var handsObj = Instantiate(handsPrefab);
......@@ -235,7 +203,7 @@ namespace com.al_arcade.tf
}
yield return null;
// ── UI ───────────────────────────────────────────────
if (canvasPrefab != null)
{
var canvasObj = Instantiate(canvasPrefab);
......@@ -258,7 +226,7 @@ namespace com.al_arcade.tf
_uiManager.onRestartClicked = new UnityEvent();
yield return null;
// ── Game Manager ─────────────────────────────────────
var gmObj = new GameObject("TfGameManager");
_gm = gmObj.AddComponent<TfGameManager>();
_gm.handController = _handController;
......@@ -348,4 +316,4 @@ namespace com.al_arcade.tf
};
}
}
}
\ No newline at end of file
}
......@@ -21,7 +21,7 @@ namespace com.al_arcade.tf
float totalLength = stepsToWin * stepDistance + 10f;
var shader = GetShader();
// ── Belt surface ─────────────────────────────────────────
var belt = GameObject.CreatePrimitive(PrimitiveType.Cube);
belt.name = "Belt";
belt.transform.SetParent(transform);
......@@ -31,7 +31,7 @@ namespace com.al_arcade.tf
belt.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(55, 55, 65, 255) };
// ── Belt top highlight ───────────────────────────────────
var beltTop = GameObject.CreatePrimitive(PrimitiveType.Cube);
beltTop.name = "BeltTop";
beltTop.transform.SetParent(transform);
......@@ -41,7 +41,7 @@ namespace com.al_arcade.tf
beltTop.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(70, 70, 80, 255) };
// ── Rollers ──────────────────────────────────────────────
int rc = Mathf.CeilToInt(totalLength / 2f);
_rollers = new GameObject[rc];
for (int i = 0; i < rc; i++)
......@@ -58,7 +58,7 @@ namespace com.al_arcade.tf
_rollers[i] = r;
}
// ── Side rails ───────────────────────────────────────────
for (int s = -1; s <= 1; s += 2)
{
var rail = GameObject.CreatePrimitive(PrimitiveType.Cube);
......@@ -71,7 +71,7 @@ namespace com.al_arcade.tf
new Material(shader) { color = SSColorPalette.Primary };
}
// ── Legs ─────────────────────────────────────────────────
for (float z = 1; z < totalLength; z += 4f)
{
for (int s = -1; s <= 1; s += 2)
......@@ -87,12 +87,12 @@ namespace com.al_arcade.tf
}
}
// ── Progress markers ─────────────────────────────────────
for (int i = 0; i < stepsToWin; i++)
{
float mz = (i + 1) * stepDistance;
// Line marker
var marker = GameObject.CreatePrimitive(PrimitiveType.Cube);
marker.name = $"Marker_{i}";
marker.transform.SetParent(transform);
......@@ -105,13 +105,13 @@ namespace com.al_arcade.tf
color = SSColorPalette.WithAlpha(SSColorPalette.Accent, 0.5f)
};
// Step label — facing camera
var labelObj = new GameObject("StepLabel");
labelObj.transform.SetParent(marker.transform);
labelObj.transform.position =
marker.transform.position + new Vector3(1.6f, 0.5f, 0);
// Billboard toward camera
Vector3 away = labelObj.transform.position - cameraPos;
away.y = 0;
if (away.sqrMagnitude > 0.01f)
......@@ -119,7 +119,7 @@ namespace com.al_arcade.tf
var lbl = labelObj.AddComponent<ArabicTextMeshPro>();
lbl.arabicText = $"✦ {i + 1}";
// ★ Small readable size
lbl.fontSize = 2f;
lbl.alignment = TMPro.TextAlignmentOptions.Center;
lbl.color = SSColorPalette.WithAlpha(SSColorPalette.Accent, 0.7f);
......@@ -127,7 +127,7 @@ namespace com.al_arcade.tf
SSFontManager.Apply(lbl);
}
// ── End zone ─────────────────────────────────────────────
float endZ = stepsToWin * stepDistance + 2f;
var ez = GameObject.CreatePrimitive(PrimitiveType.Cube);
ez.name = "EndZone";
......@@ -138,7 +138,7 @@ namespace com.al_arcade.tf
ez.GetComponent<Renderer>().material =
new Material(shader) { color = SSColorPalette.WithAlpha(SSColorPalette.Success, 0.35f) };
// ── Product ──────────────────────────────────────────────
_product = new GameObject("Product");
_product.transform.SetParent(transform);
_product.transform.localPosition = new Vector3(0, 0.7f, 0);
......@@ -152,7 +152,7 @@ namespace com.al_arcade.tf
_productMat = new Material(shader) { color = SSColorPalette.Accent };
box.GetComponent<Renderer>().material = _productMat;
// Stripe on box
var stripe = GameObject.CreatePrimitive(PrimitiveType.Cube);
stripe.name = "Stripe";
stripe.transform.SetParent(_product.transform);
......@@ -162,7 +162,7 @@ namespace com.al_arcade.tf
stripe.GetComponent<Renderer>().material =
new Material(shader) { color = SSColorPalette.Primary };
// Star on top
var star = GameObject.CreatePrimitive(PrimitiveType.Sphere);
star.name = "Star";
star.transform.SetParent(_product.transform);
......@@ -175,7 +175,6 @@ namespace com.al_arcade.tf
_currentZ = 0;
}
// ── Movement ─────────────────────────────────────────────────
public IEnumerator MoveForward(float dist)
{
......@@ -265,4 +264,4 @@ namespace com.al_arcade.tf
Shader.Find("Universal Render Pipeline/Lit")
?? Shader.Find("Standard") ?? Shader.Find("Unlit/Color");
}
}
\ No newline at end of file
}
......@@ -15,15 +15,13 @@ namespace com.al_arcade.tf
private Material _feedbackMat;
private GameObject _feedbackPanel;
/// <summary>
/// Build screen at position, rotated to face cameraPos.
/// </summary>
public void Build(Vector3 position, Vector3 cameraPos)
{
transform.position = position;
var shader = GetShader();
// ── Frame ────────────────────────────────────────────────
var frame = GameObject.CreatePrimitive(PrimitiveType.Cube);
frame.name = "Frame";
frame.transform.SetParent(transform);
......@@ -33,7 +31,7 @@ namespace com.al_arcade.tf
frame.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(25, 25, 35, 255) };
// ── Screen border highlight ──────────────────────────────
var borderGlow = GameObject.CreatePrimitive(PrimitiveType.Cube);
borderGlow.name = "BorderGlow";
borderGlow.transform.SetParent(transform);
......@@ -43,7 +41,7 @@ namespace com.al_arcade.tf
borderGlow.GetComponent<Renderer>().material =
new Material(shader) { color = SSColorPalette.WithAlpha(SSColorPalette.Primary, 0.3f) };
// ── Screen surface ───────────────────────────────────────
var surface = GameObject.CreatePrimitive(PrimitiveType.Cube);
surface.name = "Surface";
surface.transform.SetParent(transform);
......@@ -53,7 +51,7 @@ namespace com.al_arcade.tf
_screenMat = new Material(shader) { color = new Color32(15, 15, 50, 255) };
surface.GetComponent<Renderer>().material = _screenMat;
// ── Pole ─────────────────────────────────────────────────
var pole = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
pole.name = "Pole";
pole.transform.SetParent(transform);
......@@ -63,7 +61,7 @@ namespace com.al_arcade.tf
pole.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(70, 70, 85, 255) };
// ── Pole base ────────────────────────────────────────────
var poleBase = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
poleBase.name = "PoleBase";
poleBase.transform.SetParent(transform);
......@@ -73,7 +71,7 @@ namespace com.al_arcade.tf
poleBase.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(60, 60, 75, 255) };
// ── Question text ★ z = -0.25 (pushed out toward camera) ─
var textObj = new GameObject("QuestionText");
textObj.transform.SetParent(transform);
textObj.transform.localPosition = new Vector3(0, 0.25f, -0.25f);
......@@ -84,7 +82,7 @@ namespace com.al_arcade.tf
_questionText.color = Color.white;
_questionText.fontStyle = TMPro.FontStyles.Bold;
_questionText.enableAutoSizing = true;
// ★ Sensible font range for a 7-unit-wide screen at ~12m distance
_questionText.fontSizeMin = 1.8f;
_questionText.fontSizeMax = 3.8f;
_questionText.rectTransform.sizeDelta = new Vector2(6.2f, 2.2f);
......@@ -92,7 +90,7 @@ namespace com.al_arcade.tf
_questionText.overflowMode = TMPro.TextOverflowModes.Ellipsis;
SSFontManager.Apply(_questionText);
// ── Source text ──────────────────────────────────────────
var srcObj = new GameObject("SourceText");
srcObj.transform.SetParent(transform);
srcObj.transform.localPosition = new Vector3(0, -1.15f, -0.25f);
......@@ -105,7 +103,7 @@ namespace com.al_arcade.tf
_sourceText.rectTransform.sizeDelta = new Vector2(6f, 0.5f);
SSFontManager.Apply(_sourceText);
// ── Feedback panel (overlays question) ───────────────────
_feedbackPanel = GameObject.CreatePrimitive(PrimitiveType.Cube);
_feedbackPanel.name = "FeedbackPanel";
_feedbackPanel.transform.SetParent(transform);
......@@ -122,14 +120,14 @@ namespace com.al_arcade.tf
fbObj.transform.SetParent(_feedbackPanel.transform);
fbObj.transform.localPosition = new Vector3(0, 0, -0.15f);
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);
_feedbackText = fbObj.AddComponent<ArabicTextMeshPro>();
_feedbackText.alignment = TMPro.TextAlignmentOptions.Center;
_feedbackText.color = Color.white;
// ★ Moderate feedback font size
_feedbackText.fontSize = 4f;
_feedbackText.fontSizeMin = 2.5f;
_feedbackText.fontSizeMax = 5f;
......@@ -140,7 +138,7 @@ namespace com.al_arcade.tf
_feedbackText.arabicText = "";
SSFontManager.Apply(_feedbackText);
// ── Accent strips (top + bottom) ─────────────────────────
var topStrip = GameObject.CreatePrimitive(PrimitiveType.Cube);
topStrip.name = "TopStrip";
topStrip.transform.SetParent(transform);
......@@ -159,7 +157,7 @@ namespace com.al_arcade.tf
botStrip.GetComponent<Renderer>().material =
new Material(shader) { color = SSColorPalette.WithAlpha(SSColorPalette.Accent, 0.5f) };
// ── Status indicator lights ──────────────────────────────
for (int i = -1; i <= 1; i += 2)
{
var light = GameObject.CreatePrimitive(PrimitiveType.Sphere);
......@@ -174,18 +172,17 @@ namespace com.al_arcade.tf
};
}
// ★ Rotate entire screen so -Z faces camera
Vector3 awayFromCamera = transform.position - cameraPos;
awayFromCamera.y = 0;
if (awayFromCamera.sqrMagnitude > 0.01f)
transform.rotation = Quaternion.LookRotation(awayFromCamera);
}
// ── Show Question ────────────────────────────────────────────
public void ShowQuestion(string text, string source = "")
{
// Hide feedback
if (_feedbackMat != null)
{
DOTween.Kill(_feedbackMat, "fbMat");
......@@ -207,7 +204,7 @@ namespace com.al_arcade.tf
if (_sourceText != null)
_sourceText.arabicText = !string.IsNullOrEmpty(source) ? $"📖 {source}" : "";
// Screen flash effect
if (_screenMat != null)
{
DOTween.Kill(_screenMat, "screenFlash");
......@@ -219,7 +216,6 @@ namespace com.al_arcade.tf
}
}
// ── Show Feedback ────────────────────────────────────────────
public void ShowFeedback(bool correct, int streak)
{
......@@ -246,7 +242,6 @@ namespace com.al_arcade.tf
}
}
// ── Clear ────────────────────────────────────────────────────
public void Clear()
{
......@@ -264,4 +259,4 @@ namespace com.al_arcade.tf
Shader.Find("Universal Render Pipeline/Lit")
?? Shader.Find("Standard") ?? Shader.Find("Unlit/Color");
}
}
\ No newline at end of file
}
......@@ -21,9 +21,6 @@ namespace com.al_arcade.tf
[Header("Events")]
public UnityEvent onRestartClicked;
// ════════════════════════════════════════════════════════════
// BUILD — all sizes tuned for 1920×1080 reference
// ════════════════════════════════════════════════════════════
public void BuildUI()
{
......@@ -50,14 +47,13 @@ namespace com.al_arcade.tf
_resultsUI.alpha = 0; _resultsUI.gameObject.SetActive(false);
}
// ── Game HUD ─────────────────────────────────────────────────
private void BuildGameHUD(Transform parent)
{
var go = MkPanel(parent, "GameUI");
_gameUI = go.AddComponent<CanvasGroup>();
// ── Top bar ──────────────────────────────────────────────
var topBarObj = new GameObject("TopBar");
topBarObj.transform.SetParent(go.transform, false);
var tbr = topBarObj.AddComponent<RectTransform>();
......@@ -69,26 +65,26 @@ namespace com.al_arcade.tf
topBarObj.AddComponent<Image>().color =
SSColorPalette.WithAlpha(SSColorPalette.PrimaryDark, 0.82f);
// Score — top-left
_scoreText = MkTxt(topBarObj.transform, "Score", "0",
32, new Vector2(0, 0.5f), new Vector2(36, 6), new Vector2(180, 44));
_scoreText.color = SSColorPalette.Accent;
_scoreText.fontStyle = TMPro.FontStyles.Bold;
_scoreText.alignment = TMPro.TextAlignmentOptions.MidlineLeft;
// Score label
var scoreLbl = MkTxt(topBarObj.transform, "ScoreLbl", "النقاط",
14, new Vector2(0, 0.5f), new Vector2(36, -20), new Vector2(100, 22));
scoreLbl.color = SSColorPalette.WithAlpha(Color.white, 0.5f);
scoreLbl.alignment = TMPro.TextAlignmentOptions.MidlineLeft;
// Streak — center
_streakText = MkTxt(topBarObj.transform, "Streak", "",
26, new Vector2(0.5f, 0.5f), new Vector2(0, 0), new Vector2(200, 44));
_streakText.color = SSColorPalette.Warning;
_streakText.alignment = TMPro.TextAlignmentOptions.Center;
// ── Progress bar ─────────────────────────────────────────
var progBg = new GameObject("ProgressBg");
progBg.transform.SetParent(go.transform, false);
var pr = progBg.AddComponent<RectTransform>();
......@@ -111,13 +107,13 @@ namespace com.al_arcade.tf
_progressFill = fill.AddComponent<Image>();
_progressFill.color = SSColorPalette.Accent;
// Progress label
_progressLabel = MkTxt(go.transform, "ProgLbl", "0 / 5",
16, new Vector2(0.5f, 1), new Vector2(0, -98), new Vector2(200, 22));
_progressLabel.alignment = TMPro.TextAlignmentOptions.Center;
_progressLabel.color = SSColorPalette.WithAlpha(Color.white, 0.55f);
// ── Bottom hint ──────────────────────────────────────────
var hintObj = new GameObject("HintBg");
hintObj.transform.SetParent(go.transform, false);
var hbr = hintObj.AddComponent<RectTransform>();
......@@ -136,7 +132,6 @@ namespace com.al_arcade.tf
hint.color = SSColorPalette.WithAlpha(Color.white, 0.5f);
}
// ── Loading ──────────────────────────────────────────────────
private void BuildLoadingPanel(Transform parent)
{
......@@ -150,14 +145,13 @@ namespace com.al_arcade.tf
_loadingText.alignment = TMPro.TextAlignmentOptions.Center;
_loadingText.color = Color.white;
// Spinner dots
var dots = MkTxt(go.transform, "Dots", "● ● ●",
18, new Vector2(0.5f, 0.5f), new Vector2(0, -36), new Vector2(200, 30));
dots.alignment = TMPro.TextAlignmentOptions.Center;
dots.color = SSColorPalette.Accent;
}
// ── Error ────────────────────────────────────────────────────
private void BuildErrorPanel(Transform parent)
{
......@@ -173,18 +167,17 @@ namespace com.al_arcade.tf
_errorText.enableWordWrapping = true;
}
// ── Results ──────────────────────────────────────────────────
private void BuildResultsPanel(Transform parent)
{
var go = MkPanel(parent, "ResultsUI");
_resultsUI = go.AddComponent<CanvasGroup>();
// Semi-transparent background
var bg = go.AddComponent<Image>();
bg.color = SSColorPalette.WithAlpha(SSColorPalette.PrimaryDark, 0.94f);
// Card panel (centered box)
var card = new GameObject("Card");
card.transform.SetParent(go.transform, false);
var cr = card.AddComponent<RectTransform>();
......@@ -194,33 +187,33 @@ namespace com.al_arcade.tf
var cardImg = card.AddComponent<Image>();
cardImg.color = SSColorPalette.WithAlpha(new Color32(30, 30, 55, 255), 0.95f);
// Title
_resultTitle = MkTxt(card.transform, "Title", "تم التصنيع! 🎉",
42, new Vector2(0.5f, 1), new Vector2(0, -50), new Vector2(500, 55));
_resultTitle.alignment = TMPro.TextAlignmentOptions.Center;
_resultTitle.color = SSColorPalette.Accent;
_resultTitle.fontStyle = TMPro.FontStyles.Bold;
// Score
_resultScore = MkTxt(card.transform, "Score", "0",
56, new Vector2(0.5f, 1), new Vector2(0, -130), new Vector2(300, 70));
_resultScore.alignment = TMPro.TextAlignmentOptions.Center;
_resultScore.color = Color.white;
_resultScore.fontStyle = TMPro.FontStyles.Bold;
// Score label
MkTxt(card.transform, "ScoreLbl", "النقاط",
16, new Vector2(0.5f, 1), new Vector2(0, -185), new Vector2(100, 25))
.color = SSColorPalette.WithAlpha(Color.white, 0.4f);
// Stats
_resultStats = MkTxt(card.transform, "Stats", "",
20, new Vector2(0.5f, 0.5f), new Vector2(0, 10), new Vector2(450, 60));
_resultStats.alignment = TMPro.TextAlignmentOptions.Center;
_resultStats.color = SSColorPalette.WithAlpha(Color.white, 0.65f);
_resultStats.enableWordWrapping = true;
// Restart button
var btnObj = new GameObject("RestartBtn");
btnObj.transform.SetParent(card.transform, false);
var br = btnObj.AddComponent<RectTransform>();
......@@ -251,9 +244,6 @@ namespace com.al_arcade.tf
btnTxt.fontStyle = TMPro.FontStyles.Bold;
}
// ════════════════════════════════════════════════════════════
// PUBLIC METHODS
// ════════════════════════════════════════════════════════════
public void ShowGameUI()
{
......@@ -325,7 +315,7 @@ namespace com.al_arcade.tf
if (_resultStats != null)
_resultStats.arabicText = $"✓ صحيح: {correct} | ✗ خطأ: {wrong}";
// Animated entrance
var seq = DOTween.Sequence();
seq.Append(_resultsUI.DOFade(1, 0.4f));
if (_resultTitle != null)
......@@ -340,7 +330,7 @@ namespace com.al_arcade.tf
seq.Append(_resultScore.transform
.DOScale(1, 0.35f).SetEase(Ease.OutBack));
// Count-up
int ds = 0;
DOTween.To(() => ds, x =>
{
......@@ -358,9 +348,6 @@ namespace com.al_arcade.tf
if (_resultsUI != null) _resultsUI.gameObject.SetActive(false);
}
// ════════════════════════════════════════════════════════════
// HELPERS
// ════════════════════════════════════════════════════════════
private GameObject MkPanel(Transform p, string n)
{
......@@ -391,4 +378,4 @@ namespace com.al_arcade.tf
return t;
}
}
}
\ No newline at end of file
}
This diff is collapsed.
fileFormatVersion: 2
guid: 5fe070f9e16c540beb7eae4411def4a3
guid: 172cb1724b3354adbba5be5427c4ab94
folderAsset: yes
DefaultImporter:
externalObjects: {}
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