Commit 9c2d7323 authored by Yousef Sameh's avatar Yousef Sameh
parents bdba0ba4 d4cfc2fc
......@@ -31,6 +31,26 @@ public class AppRouter : MonoBehaviour
DontDestroyOnLoad(gameObject);
Application.targetFrameRate = Screen.currentResolution.refreshRate;
Screen.orientation = ScreenOrientation.Portrait;
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
HandleBackButton();
}
private void HandleBackButton()
{
// Don't interrupt active challenges
if (ChallengeManager.Instance != null && ChallengeManager.Instance.IsChallengeRunning)
return;
var scene = SceneManager.GetActiveScene().name;
if (scene == "MainMenu")
Application.Quit();
else if (scene != "Login" && scene != "Boot")
GoToHome();
}
private async void Start()
......
......@@ -30,6 +30,9 @@ public class HomeController : MonoBehaviour
private VisualElement menuPanel;
// Navigation guard — prevents concurrent Challenge + Practice clicks
public static bool IsNavigating { get; private set; }
void Start()
{
var root = mainMenuDocument.rootVisualElement.Q("Home");
......@@ -141,6 +144,12 @@ public class HomeController : MonoBehaviour
private void OnChallengeButtonClicked()
{
if (IsNavigating) return;
IsNavigating = true;
challengeButton.SetEnabled(false);
if (practiceButton != null) practiceButton.SetEnabled(false);
var challenge = Instantiate(challengePrefab);
var challengeManager = challenge.GetComponent<ChallengeManager>();
......@@ -149,6 +158,7 @@ public class HomeController : MonoBehaviour
private void OnDestroy()
{
IsNavigating = false;
UserService.Instance.OnUserChanged -= OnUserChange;
challengeButton.clicked -= OnChallengeButtonClicked;
}
......
......@@ -105,6 +105,7 @@ public class MainmenuAnimation : MonoBehaviour
openPracticeButton.clicked += () =>
{
if (HomeController.IsNavigating) return;
SelectGamePanel.style.display = DisplayStyle.Flex;
SelectGamePanel.experimental.animation.Start(0, 1, 200, (v, t) =>
{
......
......@@ -398,6 +398,8 @@ namespace com.al_arcade.cs
// ─── End sequences ────────────────────────────────────────────────────
protected override IEnumerator SharedVictorySequence()
{
if (_isTicking) { _isTicking = false; SSAudioManager.Instance?.Tick(false); }
var audio = SSAudioManager.Instance;
audio.PlayVictory();
......@@ -427,6 +429,8 @@ namespace com.al_arcade.cs
protected override IEnumerator SharedLoseSequence()
{
if (_isTicking) { _isTicking = false; SSAudioManager.Instance?.Tick(false); }
SSAudioManager.Instance.PlayDefeat();
onGameComplete?.Invoke(_score);
SSAudioManager.Instance.StopMusic();
......
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
......@@ -134,36 +135,70 @@ namespace com.al_arcade.mcq
}
// Combined dashes into a single mesh (100 objects → 1)
{
var tempCube = GameObject.CreatePrimitive(PrimitiveType.Cube);
var cubeMesh = tempCube.GetComponent<MeshFilter>().sharedMesh;
Destroy(tempCube);
var combines = new List<CombineInstance>();
for (float z = 0; z < 400; z += 4)
{
var dash = GameObject.CreatePrimitive(PrimitiveType.Cube);
dash.name = "Dash";
dash.transform.position = new Vector3(0, 0.01f, z);
dash.transform.localScale = new Vector3(0.05f, 0.01f, 2f);
Destroy(dash.GetComponent<Collider>());
dash.GetComponent<Renderer>().material = new Material(shader)
combines.Add(new CombineInstance
{
mesh = cubeMesh,
transform = Matrix4x4.TRS(
new Vector3(0, 0.01f, z),
Quaternion.identity,
new Vector3(0.05f, 0.01f, 2f))
});
}
var dashObj = new GameObject("RoadDashes");
var mf = dashObj.AddComponent<MeshFilter>();
var mr = dashObj.AddComponent<MeshRenderer>();
var combined = new Mesh();
combined.CombineMeshes(combines.ToArray(), true, true);
mf.mesh = combined;
mr.material = new Material(shader)
{ color = SSColorPalette.WithAlpha(Color.white, 0.2f) };
}
// Combined side blocks into a single mesh (40 objects → 1)
{
var tempCube = GameObject.CreatePrimitive(PrimitiveType.Cube);
var cubeMesh = tempCube.GetComponent<MeshFilter>().sharedMesh;
Destroy(tempCube);
var combines = new List<CombineInstance>();
for (int side = -1; side <= 1; side += 2)
{
for (float z = 0; z < 400; z += 20)
{
var block = GameObject.CreatePrimitive(PrimitiveType.Cube);
block.name = "SideBlock";
float h = Random.Range(2f, 5f);
block.transform.position = new Vector3(side * 12f, h / 2f, z);
block.transform.localScale = new Vector3(
Random.Range(2f, 4f), h, Random.Range(3f, 8f));
Destroy(block.GetComponent<Collider>());
var c = Color.Lerp(SSColorPalette.Primary,
SSColorPalette.PrimaryLight, Random.value);
block.GetComponent<Renderer>().material = new Material(shader)
{ color = SSColorPalette.WithAlpha(c, 0.4f) };
combines.Add(new CombineInstance
{
mesh = cubeMesh,
transform = Matrix4x4.TRS(
new Vector3(side * 12f, h / 2f, z),
Quaternion.identity,
new Vector3(Random.Range(2f, 4f), h, Random.Range(3f, 8f)))
});
}
}
var blocksObj = new GameObject("SideBlocks");
var mf = blocksObj.AddComponent<MeshFilter>();
var mr = blocksObj.AddComponent<MeshRenderer>();
var combined = new Mesh();
combined.CombineMeshes(combines.ToArray(), true, true);
mf.mesh = combined;
var midColor = Color.Lerp(SSColorPalette.Primary, SSColorPalette.PrimaryLight, 0.5f);
mr.material = new Material(shader)
{ color = SSColorPalette.WithAlpha(midColor, 0.4f) };
}
var horizon = GameObject.CreatePrimitive(PrimitiveType.Cube);
horizon.name = "Horizon";
......
......@@ -50,6 +50,10 @@ namespace com.al_arcade.mcq
private Camera _mainCamera;
private bool _isTicking;
// Gate object pool — reuse instead of Instantiate/Destroy per question
private List<McqGateController> _gatePool = new();
private const int GatePoolSize = 6;
// ✅ NEW: CS-style progress counter
// goes +1 on correct, -1 on wrong, clamped to [0, pointsToWin]
private int _deltaChangeInSize;
......@@ -176,6 +180,7 @@ namespace com.al_arcade.mcq
// ─── BeginGameplay ───────────────────────────────────────────────────
protected override void BeginGameplay()
{
InitGatePool();
_currentIndex = _score = _streak = _correctCount = _wrongCount = 0;
_bestStreak = 0;
......@@ -327,7 +332,7 @@ namespace com.al_arcade.mcq
DOTween.Kill(gate.transform);
gate.transform.DOScale(Vector3.zero, 0.3f)
.SetEase(Ease.InBack)
.OnComplete(() => { if (gate != null) Destroy(gate.gameObject); });
.OnComplete(() => { if (gate != null) gate.Recycle(); });
}
}
_activeGates.Clear();
......@@ -455,12 +460,82 @@ namespace com.al_arcade.mcq
if (g != null)
{
DOTween.Kill(g.transform);
Destroy(g.gameObject);
g.Recycle();
}
}
_activeGates.Clear();
}
// ─── Gate Pool ──────────────────────────────────────────────────────
private void InitGatePool()
{
if (_gatePool.Count > 0) return;
GameObject prefab = prefabBuilder != null ? prefabBuilder.GetGatePrefab() : null;
for (int i = 0; i < GatePoolSize; i++)
{
GameObject go;
McqGateController gate;
if (prefab == null)
{
go = new GameObject($"Gate_Pool_{i}");
gate = go.AddComponent<McqGateController>();
}
else
{
go = Instantiate(prefab);
gate = go.GetComponent<McqGateController>();
}
if (gateParent != null) go.transform.SetParent(gateParent);
go.SetActive(false);
_gatePool.Add(gate);
}
}
private McqGateController GetGateFromPool(Vector3 position, int index,
string answerText, bool isCorrect)
{
McqGateController gate = null;
for (int i = 0; i < _gatePool.Count; i++)
{
if (!_gatePool[i].gameObject.activeSelf)
{
gate = _gatePool[i];
break;
}
}
// Pool exhausted — expand
if (gate == null)
{
GameObject prefab = prefabBuilder != null ? prefabBuilder.GetGatePrefab() : null;
GameObject go;
if (prefab == null)
{
go = new GameObject($"Gate_Pool_{_gatePool.Count}");
gate = go.AddComponent<McqGateController>();
}
else
{
go = Instantiate(prefab);
gate = go.GetComponent<McqGateController>();
}
if (gateParent != null) go.transform.SetParent(gateParent);
_gatePool.Add(gate);
}
gate.transform.position = position;
gate.gameObject.SetActive(true);
gate.Setup(index, answerText, isCorrect);
return gate;
}
// ─── Gate Spawning ───────────────────────────────────────────────────
private void SpawnGates(McqQuestion question)
{
......@@ -481,7 +556,7 @@ namespace com.al_arcade.mcq
for (int i = 0; i < answers.Length; i++)
{
Vector3 gatePos = basePos + Vector3.right * (startX + i * gateSpacing);
var gate = CreateGate(gatePos, i, answers[i], i == _correctGateIndex);
var gate = GetGateFromPool(gatePos, i, answers[i], i == _correctGateIndex);
_activeGates.Add(gate);
gate.transform.DOScale(Vector3.one, 0.5f)
......@@ -491,34 +566,6 @@ namespace com.al_arcade.mcq
}
}
private McqGateController CreateGate(Vector3 position, int index,
string answerText, bool isCorrect)
{
GameObject gatePrefab = prefabBuilder != null ? prefabBuilder.GetGatePrefab() : null;
GameObject go;
if (gatePrefab == null)
{
go = new GameObject($"Gate_{index}");
go.transform.position = position;
}
else
{
go = Instantiate(gatePrefab, position, Quaternion.identity);
}
if (gateParent != null) go.transform.SetParent(gateParent);
McqGateController gate;
if (gatePrefab == null)
gate = go.AddComponent<McqGateController>();
else
gate = go.GetComponent<McqGateController>();
gate.Setup(index, answerText, isCorrect);
return gate;
}
// ─── Feedback ────────────────────────────────────────────────────────
private void ShowCorrectFeedback(int points)
{
......@@ -582,6 +629,8 @@ namespace com.al_arcade.mcq
// ─── End Sequences ───────────────────────────────────────────────────
protected override IEnumerator SharedVictorySequence()
{
if (_isTicking) { _isTicking = false; SSAudioManager.Instance?.Tick(false); }
var audio = SSAudioManager.Instance;
if (audio != null)
{
......@@ -606,6 +655,8 @@ namespace com.al_arcade.mcq
protected override IEnumerator SharedLoseSequence()
{
if (_isTicking) { _isTicking = false; SSAudioManager.Instance?.Tick(false); }
StopPlayerAndCompetitor();
var audio = SSAudioManager.Instance;
......
......@@ -24,6 +24,7 @@ namespace com.al_arcade.mcq
private MaterialPropertyBlock _mpb;
private Sequence _idleAnim;
private Sequence _glowAnim;
private bool _built;
private const float GateWidth = 2.5f;
private const float GateHeight = 3.5f;
......@@ -48,12 +49,33 @@ namespace com.al_arcade.mcq
IsCorrect = correct;
WasEntered = false;
EnsureMaterial();
if (_built)
UpdateContent();
else
{
BuildVisuals();
_built = true;
}
if (_mpb != null)
SetPanelColor(SSColorPalette.GateDefault);
StartIdleAnimation();
}
public void Start()
{
EnsureMaterial();
if (_mpb != null)
SetPanelColor(SSColorPalette.GateDefault);
}
private void EnsureMaterial()
{
if (_panelRenderer == null || _mpb != null) return;
if (_sharedGateMaterial == null)
{
_sharedGateMaterial = new Material(_panelRenderer.material.shader);
......@@ -61,9 +83,7 @@ namespace com.al_arcade.mcq
}
_panelRenderer.sharedMaterial = _sharedGateMaterial;
_panelMaterial = _sharedGateMaterial;
_mpb = new MaterialPropertyBlock();
SetPanelColor(SSColorPalette.GateDefault);
}
private void SetPanelColor(Color color)
......@@ -73,6 +93,30 @@ namespace com.al_arcade.mcq
_panelRenderer.SetPropertyBlock(_mpb);
}
private void UpdateContent()
{
if (_answerText != null)
{
_answerText.Text = AnswerText;
_answerTextShadow.Text = AnswerText;
}
else if (_label != null)
{
_label.arabicText = AnswerText;
}
}
public void Recycle()
{
KillAllTweens();
WasEntered = false;
onPlayerEnter = null;
transform.localScale = Vector3.one;
if (_mpb != null)
SetPanelColor(SSColorPalette.GateDefault);
gameObject.SetActive(false);
}
private void BuildVisuals()
{
var shader = GetShader();
......
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