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
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
}
......@@ -12,9 +12,7 @@ namespace com.al_arcade.cs
[AddComponentMenu("Science Street/CS Prefab Builder")]
public class CsPrefabBuilder : MonoBehaviour
{
// ════════════════════════════════════════════════════════════
// SCENE
// ════════════════════════════════════════════════════════════
[Header("Scene Environment")]
[Tooltip("Your full environment prefab (ground, platform, decorations, etc). Spawned at origin.")]
......@@ -28,9 +26,6 @@ namespace com.al_arcade.cs
[SerializeField] private float cameraFOV = 60f;
[SerializeField] private Color cameraBgColor = new Color32(30, 30, 60, 255);
// ════════════════════════════════════════════════════════════
// LIGHTING
// ════════════════════════════════════════════════════════════
[Header("Lighting (leave null to skip)")]
[Tooltip("Drag in your own directional light prefab, or leave null to auto-create.")]
......@@ -38,9 +33,6 @@ namespace com.al_arcade.cs
[SerializeField] private GameObject[] extraLightPrefabs;
[SerializeField] private Color ambientColor = new Color32(40, 42, 60, 255);
// ════════════════════════════════════════════════════════════
// BOT
// ════════════════════════════════════════════════════════════
[Header("Bot")]
[Tooltip("Your bot prefab. MUST have CsBotController attached.\n" +
......@@ -48,18 +40,12 @@ namespace com.al_arcade.cs
[SerializeField] private GameObject botPrefab;
[SerializeField] private Vector3 botSpawnPosition = Vector3.zero;
// ════════════════════════════════════════════════════════════
// WORD CARD (optional override)
// ════════════════════════════════════════════════════════════
[Header("Word Card (optional)")]
[Tooltip("A prefab for the word card. MUST have CsWordButton.\n" +
"If null, the default procedural card is used.")]
[SerializeField] private GameObject wordCardPrefab;
// ════════════════════════════════════════════════════════════
// UI
// ════════════════════════════════════════════════════════════
[Header("UI Canvas")]
[Tooltip("Your Canvas prefab. MUST have CsUIManager with ALL internal " +
......@@ -67,9 +53,6 @@ namespace com.al_arcade.cs
"If null, a procedural UI is created.")]
[SerializeField] private GameObject canvasPrefab;
// ════════════════════════════════════════════════════════════
// AUDIO
// ════════════════════════════════════════════════════════════
[Header("Audio — SFX Clips (all optional)")]
[SerializeField] private AudioClip sfxCorrect;
......@@ -82,9 +65,6 @@ namespace com.al_arcade.cs
[SerializeField] private AudioClip sfxSlam;
[SerializeField] private AudioClip sfxCheer;
// ════════════════════════════════════════════════════════════
// PARTICLES
// ════════════════════════════════════════════════════════════
[Header("Particles (all optional)")]
[SerializeField] private ParticleSystem correctBurstParticle;
......@@ -93,16 +73,10 @@ namespace com.al_arcade.cs
[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";
......@@ -113,17 +87,11 @@ namespace com.al_arcade.cs
[Header("Debug")]
[SerializeField] private bool useOfflineTestData = false;
// ════════════════════════════════════════════════════════════
// RUNTIME REFS
// ════════════════════════════════════════════════════════════
private CsGameManager _gm;
private CsBotController _bot;
private CsUIManager _uiManager;
// ════════════════════════════════════════════════════════════
// STARTUP
// ════════════════════════════════════════════════════════════
private void Start()
{
......@@ -136,7 +104,7 @@ namespace com.al_arcade.cs
{
Debug.Log("═══ CS Prefab Builder — Starting ═══");
// ── EventSystem ──────────────────────────────────────
if (FindObjectOfType<EventSystem>() == null)
{
var es = new GameObject("EventSystem");
......@@ -144,7 +112,7 @@ namespace com.al_arcade.cs
es.AddComponent<StandaloneInputModule>();
}
// ── Shared Managers ──────────────────────────────────
SSApiManager.EnsureInstance();
SetupAudioManager();
SetupParticleManager();
......@@ -156,20 +124,20 @@ namespace com.al_arcade.cs
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;
// ── Bot ──────────────────────────────────────────────
if (botPrefab != null)
{
var botObj = Instantiate(botPrefab, botSpawnPosition, Quaternion.identity);
......@@ -180,12 +148,12 @@ namespace com.al_arcade.cs
Debug.LogError("[CS] Bot prefab is missing CsBotController!");
yield break;
}
// If your prefab's Build() still needs calling (e.g. for animations):
// _bot.Build(); ← uncomment if the prefab needs runtime init
}
else
{
// Fallback: procedural bot
var botObj = new GameObject("Bot");
botObj.transform.position = botSpawnPosition;
_bot = botObj.AddComponent<CsBotController>();
......@@ -193,12 +161,12 @@ namespace com.al_arcade.cs
}
yield return null;
// ── Word Container ───────────────────────────────────
var wordContainer = new GameObject("WordContainer");
wordContainer.transform.position = Vector3.zero;
yield return null;
// ── UI ───────────────────────────────────────────────
if (canvasPrefab != null)
{
var canvasObj = Instantiate(canvasPrefab);
......@@ -209,11 +177,11 @@ namespace com.al_arcade.cs
Debug.LogError("[CS] Canvas prefab is missing CsUIManager!");
yield break;
}
// Don't call BuildUI — prefab is already set up
}
else
{
// Fallback: procedural UI
var uiObj = new GameObject("CsUI");
_uiManager = uiObj.AddComponent<CsUIManager>();
_uiManager.BuildUI();
......@@ -223,7 +191,7 @@ namespace com.al_arcade.cs
_uiManager.onRestartClicked = new UnityEvent();
yield return null;
// ── Game Manager ─────────────────────────────────────
var gmObj = new GameObject("CsGameManager");
_gm = gmObj.AddComponent<CsGameManager>();
_gm.bot = _bot;
......@@ -241,9 +209,6 @@ namespace com.al_arcade.cs
_gm.StartGame();
}
// ════════════════════════════════════════════════════════════
// SETUP HELPERS
// ════════════════════════════════════════════════════════════
private void SetupCamera()
{
......@@ -302,9 +267,6 @@ namespace com.al_arcade.cs
if (_gm != null) _gm.StartGame();
}
// ════════════════════════════════════════════════════════════
// OFFLINE TEST DATA
// ════════════════════════════════════════════════════════════
private CsQuestion[] GetTestQuestions()
{
......@@ -351,4 +313,4 @@ namespace com.al_arcade.cs
};
}
}
}
\ 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()
{
......
......@@ -12,9 +12,7 @@ namespace com.al_arcade.mcq
[AddComponentMenu("Science Street/MCQ Prefab Builder")]
public class McqPrefabBuilder : MonoBehaviour
{
// ════════════════════════════════════════════════════════════
// SCENE
// ════════════════════════════════════════════════════════════
[Header("Scene Environment")]
[Tooltip("Your full road/environment prefab. Spawned at origin.")]
......@@ -26,18 +24,12 @@ namespace com.al_arcade.mcq
[SerializeField] private float cameraSmoothSpeed = 6f;
[SerializeField] private Color cameraBgColor = new Color32(180, 200, 240, 255);
// ════════════════════════════════════════════════════════════
// LIGHTING
// ════════════════════════════════════════════════════════════
[Header("Lighting")]
[SerializeField] private GameObject directionalLightPrefab;
[SerializeField] private GameObject[] extraLightPrefabs;
[SerializeField] private Color ambientColor = new Color32(80, 85, 110, 255);
// ════════════════════════════════════════════════════════════
// PLAYER
// ════════════════════════════════════════════════════════════
[Header("Player")]
[Tooltip("Your player prefab. MUST have McqPlayerRunner + Rigidbody + Collider.\n" +
......@@ -45,18 +37,12 @@ namespace com.al_arcade.mcq
[SerializeField] private GameObject playerPrefab;
[SerializeField] private Vector3 playerSpawnPosition = Vector3.zero;
// ════════════════════════════════════════════════════════════
// GATE
// ════════════════════════════════════════════════════════════
[Header("Answer Gate")]
[Tooltip("Your gate prefab. MUST have McqGateController.\n" +
"If null, the procedural gate is used.")]
[SerializeField] private GameObject gatePrefab;
// ════════════════════════════════════════════════════════════
// QUESTION DISPLAY
// ════════════════════════════════════════════════════════════
[Header("Question Display")]
[Tooltip("Your question display prefab. MUST have McqQuestionDisplay.\n" +
......@@ -64,18 +50,12 @@ namespace com.al_arcade.mcq
[SerializeField] private GameObject questionDisplayPrefab;
[SerializeField] private Vector3 questionDisplayOffset = new Vector3(0, 8, 10);
// ════════════════════════════════════════════════════════════
// UI
// ════════════════════════════════════════════════════════════
[Header("UI Canvas")]
[Tooltip("Your Canvas prefab. MUST have McqUIManager with all references wired.\n" +
"If null, procedural UI is created.")]
[SerializeField] private GameObject canvasPrefab;
// ════════════════════════════════════════════════════════════
// AUDIO
// ════════════════════════════════════════════════════════════
[Header("Audio — SFX Clips")]
[SerializeField] private AudioClip sfxCorrect;
......@@ -88,9 +68,6 @@ namespace com.al_arcade.mcq
[SerializeField] private AudioClip sfxCheer;
[SerializeField] private AudioClip sfxCountdown;
// ════════════════════════════════════════════════════════════
// PARTICLES
// ════════════════════════════════════════════════════════════
[Header("Particles")]
[SerializeField] private ParticleSystem correctBurstParticle;
......@@ -99,16 +76,10 @@ namespace com.al_arcade.mcq
[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";
......@@ -121,9 +92,6 @@ namespace com.al_arcade.mcq
[Header("Debug")]
[SerializeField] private bool useOfflineTestData = false;
// ════════════════════════════════════════════════════════════
// RUNTIME
// ════════════════════════════════════════════════════════════
private McqGameManager _gm;
private McqPlayerRunner _player;
......@@ -141,7 +109,7 @@ namespace com.al_arcade.mcq
{
Debug.Log("═══ MCQ Prefab Builder — Starting ═══");
// ── EventSystem ──────────────────────────────────────
if (FindObjectOfType<EventSystem>() == null)
{
var es = new GameObject("EventSystem");
......@@ -149,7 +117,7 @@ namespace com.al_arcade.mcq
es.AddComponent<StandaloneInputModule>();
}
// ── Shared Managers ──────────────────────────────────
SSApiManager.EnsureInstance();
SetupAudioManager();
SetupParticleManager();
......@@ -161,12 +129,12 @@ namespace com.al_arcade.mcq
session.classCode = classCode;
yield return null;
// ── Environment ──────────────────────────────────────
if (environmentPrefab != null)
Instantiate(environmentPrefab, Vector3.zero, Quaternion.identity);
yield return null;
// ── Player ───────────────────────────────────────────
if (playerPrefab != null)
{
var playerObj = Instantiate(playerPrefab, playerSpawnPosition, Quaternion.identity);
......@@ -181,7 +149,7 @@ namespace com.al_arcade.mcq
yield break;
}
// Ensure Rigidbody
var rb = playerObj.GetComponent<Rigidbody>();
if (rb == null) rb = playerObj.AddComponent<Rigidbody>();
rb.isKinematic = true;
......@@ -201,7 +169,7 @@ namespace com.al_arcade.mcq
}
yield return null;
// ── Question Display ─────────────────────────────────
if (questionDisplayPrefab != null)
{
var qdObj = Instantiate(questionDisplayPrefab,
......@@ -224,7 +192,7 @@ namespace com.al_arcade.mcq
}
yield return null;
// ── UI ───────────────────────────────────────────────
if (canvasPrefab != null)
{
var canvasObj = Instantiate(canvasPrefab);
......@@ -247,7 +215,7 @@ namespace com.al_arcade.mcq
_uiManager.onRestartClicked = new UnityEvent();
yield return null;
// ── Game Manager ─────────────────────────────────────
var gmObj = new GameObject("McqGameManager");
_gm = gmObj.AddComponent<McqGameManager>();
_gm.player = _player;
......@@ -258,11 +226,11 @@ namespace com.al_arcade.mcq
_uiManager.onRestartClicked.AddListener(OnRestartClicked);
yield return null;
// ── Camera ───────────────────────────────────────────
SetupCamera();
yield return null;
// ── Lighting ─────────────────────────────────────────
SetupLighting();
yield return null;
......@@ -275,9 +243,6 @@ namespace com.al_arcade.mcq
_gm.StartGame();
}
// ════════════════════════════════════════════════════════════
// SETUP
// ════════════════════════════════════════════════════════════
private void SetupCamera()
{
......@@ -357,4 +322,4 @@ namespace com.al_arcade.mcq
};
}
}
}
\ No newline at end of file
}
......@@ -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;
}
}
......
......@@ -70,40 +70,40 @@ namespace com.al_arcade.tf
session.classCode = classCode;
yield return null;
// Layout computation
float totalLength = stepsToWin * stepDistance + 10f;
_lineCenterZ = 2f + totalLength / 2f;
_camPos = new Vector3(-11f, 3.5f, _lineCenterZ);
_camLookAt = new Vector3(0f, 1.5f, _lineCenterZ);
// ── Build everything ─────────────────────────────────────
BuildScene(totalLength, _lineCenterZ);
yield return null;
SetupCamera();
yield return null;
// Production line
var lineObj = new GameObject("ProductionLine");
lineObj.transform.position = new Vector3(0, 0, 2);
_productionLine = lineObj.AddComponent<TfProductionLine>();
_productionLine.Build(stepsToWin, stepDistance, _camPos);
yield return null;
// Question screen
var screenObj = new GameObject("QuestionScreen");
_questionScreen = screenObj.AddComponent<TfQuestionScreen>();
Vector3 screenPos = new Vector3(0, 5f, _lineCenterZ);
_questionScreen.Build(screenPos, _camPos);
yield return null;
// Hands
var handsObj = new GameObject("Hands");
_handController = handsObj.AddComponent<TfHandController>();
_handController.Build(Camera.main);
yield return null;
// UI
var uiObj = new GameObject("TfUI");
_uiManager = uiObj.AddComponent<TfUIManager>();
_uiManager.BuildUI();
......@@ -111,7 +111,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;
......@@ -153,13 +153,10 @@ namespace com.al_arcade.tf
cam.nearClipPlane = 0.1f;
}
// ════════════════════════════════════════════════════════════
// ★ BRIGHT FACTORY SCENE
// ════════════════════════════════════════════════════════════
private void BuildScene(float totalLength, float lineCenterZ)
{
// ★ Bright blue-gray sky instead of dark
Camera.main.clearFlags = CameraClearFlags.SolidColor;
Camera.main.backgroundColor = new Color32(70, 80, 110, 255);
......@@ -167,7 +164,7 @@ namespace com.al_arcade.tf
float sceneDepth = totalLength + 12f;
float halfDepth = sceneDepth / 2f;
// ── Floor — lighter ──────────────────────────────────────
var floor = GameObject.CreatePrimitive(PrimitiveType.Cube);
floor.name = "Floor";
floor.transform.position = new Vector3(0, -0.05f, lineCenterZ);
......@@ -175,7 +172,7 @@ namespace com.al_arcade.tf
floor.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(65, 65, 80, 255) };
// ── Floor tile pattern ───────────────────────────────────
for (float x = -12; x <= 12; x += 6f)
{
for (float z = lineCenterZ - halfDepth; z < lineCenterZ + halfDepth; z += 6f)
......@@ -195,7 +192,7 @@ namespace com.al_arcade.tf
}
}
// ── Walls — brighter, with color accent ──────────────────
for (int side = -1; side <= 1; side += 2)
{
var wall = GameObject.CreatePrimitive(PrimitiveType.Cube);
......@@ -206,7 +203,7 @@ namespace com.al_arcade.tf
wall.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(75, 75, 95, 255) };
// ★ Accent stripe on walls
var stripe = GameObject.CreatePrimitive(PrimitiveType.Cube);
stripe.name = "WallStripe";
stripe.transform.position = new Vector3(side * 14.7f, 1.5f, lineCenterZ);
......@@ -218,7 +215,7 @@ namespace com.al_arcade.tf
};
}
// ── End walls ────────────────────────────────────────────
foreach (float z in new[] { lineCenterZ + halfDepth, lineCenterZ - halfDepth })
{
var bw = GameObject.CreatePrimitive(PrimitiveType.Cube);
......@@ -230,7 +227,7 @@ namespace com.al_arcade.tf
new Material(shader) { color = new Color32(70, 70, 88, 255) };
}
// ── Ceiling — slightly reflective look ───────────────────
var ceiling = GameObject.CreatePrimitive(PrimitiveType.Cube);
ceiling.name = "Ceiling";
ceiling.transform.position = new Vector3(0, 10, lineCenterZ);
......@@ -239,7 +236,7 @@ namespace com.al_arcade.tf
ceiling.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(55, 58, 72, 255) };
// ── Industrial pipes — more colorful ─────────────────────
Color[] pipeColors =
{
new Color32(90, 90, 110, 255),
......@@ -268,7 +265,7 @@ namespace com.al_arcade.tf
};
}
// ── Warning stripes — brighter accent ────────────────────
for (float z = lineCenterZ - halfDepth; z < lineCenterZ + halfDepth; z += 5f)
{
var ws = GameObject.CreatePrimitive(PrimitiveType.Cube);
......@@ -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)
{
// Pole
var pole = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
pole.name = "LampPole";
pole.transform.position = new Vector3(0, 9.3f, z);
......@@ -294,7 +291,7 @@ namespace com.al_arcade.tf
pole.GetComponent<Renderer>().material =
new Material(shader) { color = new Color32(90, 90, 100, 255) };
// Lamp head
var head = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
head.name = "LampHead";
head.transform.position = new Vector3(0, 8.7f, z);
......@@ -305,17 +302,17 @@ namespace com.al_arcade.tf
color = SSColorPalette.WithAlpha(SSColorPalette.Accent, 0.6f)
};
// ★ Actual point light per lamp
var lampLight = new GameObject($"LampLight_{z:F0}");
var lt = lampLight.AddComponent<Light>();
lt.type = LightType.Point;
lt.range = 8;
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);
}
// ── Equipment boxes (decorative) ─────────────────────────
for (int i = 0; i < 6; i++)
{
float side = (i % 2 == 0) ? -1 : 1;
......@@ -342,13 +339,10 @@ namespace com.al_arcade.tf
}
}
// ════════════════════════════════════════════════════════════
// ★ BRIGHTER LIGHTING
// ════════════════════════════════════════════════════════════
private void SetupLighting()
{
// Main directional — brighter, warmer
var existing = FindObjectOfType<Light>();
Light dirLight;
if (existing != null)
......@@ -360,12 +354,12 @@ namespace com.al_arcade.tf
}
dirLight.type = LightType.Directional;
dirLight.transform.rotation = Quaternion.Euler(45, -30, 0);
// ★ Brighter, warmer color
dirLight.color = new Color32(240, 235, 220, 255);
dirLight.intensity = 1.1f;
dirLight.shadows = LightShadows.Soft;
// ★ Strong fill light from camera side
var fillObj = new GameObject("FillLight");
var fill = fillObj.AddComponent<Light>();
fill.type = LightType.Point;
......@@ -374,7 +368,7 @@ namespace com.al_arcade.tf
fill.color = new Color32(210, 220, 240, 255);
fillObj.transform.position = new Vector3(-10, 5, _lineCenterZ);
// ★ Accent colored lights along line
Color[] accentColors = {
SSColorPalette.Accent, SSColorPalette.Info,
SSColorPalette.Primary, SSColorPalette.Success
......@@ -392,14 +386,11 @@ namespace com.al_arcade.tf
(i % 2 == 0 ? -1 : 1) * 6f, 6f, z);
}
// ★ Brighter ambient
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
RenderSettings.ambientLight = new Color32(75, 78, 95, 255);
}
// ════════════════════════════════════════════════════════════
// TEST DATA
// ════════════════════════════════════════════════════════════
private TfQuestion[] GetTestQuestions()
{
......@@ -431,4 +422,4 @@ namespace com.al_arcade.tf
?? Shader.Find("Unlit/Color");
}
}
}
\ No newline at end of file
}
......@@ -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
}
#!/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
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