Commit 0ba46a41 authored by Yousef Sameh's avatar Yousef Sameh

Merge remote-tracking branch 'origin/New-MCQ' into Supabase

parents cae9d3fe 571053a6
...@@ -740,7 +740,7 @@ MonoBehaviour: ...@@ -740,7 +740,7 @@ MonoBehaviour:
button: {fileID: 652543888} button: {fileID: 652543888}
answerImageComponent: {fileID: 505502773} answerImageComponent: {fileID: 505502773}
answerTextComponent: {fileID: 1714205094} answerTextComponent: {fileID: 1714205094}
answerLayout: {fileID: 0} answerLayout: {fileID: 652543886}
buttonBackgroundImage: {fileID: 652543889} buttonBackgroundImage: {fileID: 652543889}
normalColor: {r: 1, g: 1, b: 1, a: 1} normalColor: {r: 1, g: 1, b: 1, a: 1}
normalTextColor: {r: 0, g: 0, b: 0, a: 1} normalTextColor: {r: 0, g: 0, b: 0, a: 1}
...@@ -1614,7 +1614,7 @@ MonoBehaviour: ...@@ -1614,7 +1614,7 @@ MonoBehaviour:
button: {fileID: 1001118205} button: {fileID: 1001118205}
answerImageComponent: {fileID: 36657994} answerImageComponent: {fileID: 36657994}
answerTextComponent: {fileID: 2077715403} answerTextComponent: {fileID: 2077715403}
answerLayout: {fileID: 0} answerLayout: {fileID: 1001118203}
buttonBackgroundImage: {fileID: 1001118206} buttonBackgroundImage: {fileID: 1001118206}
normalColor: {r: 1, g: 1, b: 1, a: 1} normalColor: {r: 1, g: 1, b: 1, a: 1}
normalTextColor: {r: 0, g: 0, b: 0, a: 1} normalTextColor: {r: 0, g: 0, b: 0, a: 1}
...@@ -1956,7 +1956,7 @@ MonoBehaviour: ...@@ -1956,7 +1956,7 @@ MonoBehaviour:
button: {fileID: 1418877171} button: {fileID: 1418877171}
answerImageComponent: {fileID: 1130655416} answerImageComponent: {fileID: 1130655416}
answerTextComponent: {fileID: 1471177263} answerTextComponent: {fileID: 1471177263}
answerLayout: {fileID: 0} answerLayout: {fileID: 1418877169}
buttonBackgroundImage: {fileID: 1418877172} buttonBackgroundImage: {fileID: 1418877172}
normalColor: {r: 1, g: 1, b: 1, a: 1} normalColor: {r: 1, g: 1, b: 1, a: 1}
normalTextColor: {r: 0, g: 0, b: 0, a: 1} normalTextColor: {r: 0, g: 0, b: 0, a: 1}
...@@ -2548,7 +2548,7 @@ MonoBehaviour: ...@@ -2548,7 +2548,7 @@ MonoBehaviour:
button: {fileID: 1951693014} button: {fileID: 1951693014}
answerImageComponent: {fileID: 650788013} answerImageComponent: {fileID: 650788013}
answerTextComponent: {fileID: 922617402} answerTextComponent: {fileID: 922617402}
answerLayout: {fileID: 0} answerLayout: {fileID: 1951693018}
buttonBackgroundImage: {fileID: 1951693015} buttonBackgroundImage: {fileID: 1951693015}
normalColor: {r: 1, g: 1, b: 1, a: 1} normalColor: {r: 1, g: 1, b: 1, a: 1}
normalTextColor: {r: 0, g: 0, b: 0, a: 1} normalTextColor: {r: 0, g: 0, b: 0, a: 1}
...@@ -2689,6 +2689,7 @@ GameObject: ...@@ -2689,6 +2689,7 @@ GameObject:
- component: {fileID: 2061407676} - component: {fileID: 2061407676}
- component: {fileID: 2061407678} - component: {fileID: 2061407678}
- component: {fileID: 2061407677} - component: {fileID: 2061407677}
- component: {fileID: 2061407680}
- component: {fileID: 2061407679} - component: {fileID: 2061407679}
m_Layer: 5 m_Layer: 5
m_Name: Question Panel m_Name: Question Panel
...@@ -2768,6 +2769,8 @@ MonoBehaviour: ...@@ -2768,6 +2769,8 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 68aee830395e108478a9643b20de1689, type: 3} m_Script: {fileID: 11500000, guid: 68aee830395e108478a9643b20de1689, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: Assembly-CSharp::QuestionUi m_EditorClassIdentifier: Assembly-CSharp::QuestionUi
questionPanelCanvasGroup: {fileID: 2061407680}
questionContainer: {fileID: 1427982049}
questionImageComponent: {fileID: 940564276} questionImageComponent: {fileID: 940564276}
questionTextComponent: {fileID: 33964863} questionTextComponent: {fileID: 33964863}
answersGrid: {fileID: 906182116} answersGrid: {fileID: 906182116}
...@@ -2776,8 +2779,25 @@ MonoBehaviour: ...@@ -2776,8 +2779,25 @@ MonoBehaviour:
- {fileID: 1418877170} - {fileID: 1418877170}
- {fileID: 652543887} - {fileID: 652543887}
- {fileID: 1001118204} - {fileID: 1001118204}
questionContainer: {fileID: 1427982049}
answersContainer: {fileID: 906182118} answersContainer: {fileID: 906182118}
gameUICanvasGroup: {fileID: 7013410525514931103}
questionEntranceDuration: 0.6
answerStaggerDelay: 0.1
answerEntranceDuration: 0.4
wrongAnswerShakeDuration: 0.4
wrongAnswerShakeStrength: 15
--- !u!225 &2061407680
CanvasGroup:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2061407675}
m_Enabled: 1
m_Alpha: 1
m_Interactable: 1
m_BlocksRaycasts: 1
m_IgnoreParentGroups: 0
--- !u!1 &2077715401 --- !u!1 &2077715401
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
......
...@@ -3,6 +3,7 @@ using UnityEngine.UI; ...@@ -3,6 +3,7 @@ using UnityEngine.UI;
using TMPro; using TMPro;
using System; using System;
using LightSide; using LightSide;
using DG.Tweening;
public class AnswerButtonUI : MonoBehaviour public class AnswerButtonUI : MonoBehaviour
{ {
...@@ -16,12 +17,14 @@ public class AnswerButtonUI : MonoBehaviour ...@@ -16,12 +17,14 @@ public class AnswerButtonUI : MonoBehaviour
[SerializeField] private Color normalTextColor = Color.black; [SerializeField] private Color normalTextColor = Color.black;
[SerializeField] private Color selectedCorrectColor = Color.green; [SerializeField] private Color selectedCorrectColor = Color.green;
[SerializeField] private Color selectedIncorrectColor = Color.red; [SerializeField] private Color selectedIncorrectColor = Color.red;
[SerializeField] private Color correctAnswerColor = new Color(0.2f, 0.8f, 0.2f, 1f); // Light green [SerializeField] private Color correctAnswerColor = new Color(0.2f, 0.8f, 0.2f, 1f);
[SerializeField] private Color disabledColor = new Color(0.7f, 0.7f, 0.7f, 1f); // Gray [SerializeField] private Color disabledColor = new Color(0.7f, 0.7f, 0.7f, 1f);
private int answerIndex; private int answerIndex;
private Action<int> onClicked; private Action<int> onClicked;
private bool isInteractable = true; private bool isInteractable = true;
private CanvasGroup canvasGroup;
private RectTransform rectTransform;
private void OnEnable() private void OnEnable()
{ {
...@@ -31,16 +34,33 @@ public class AnswerButtonUI : MonoBehaviour ...@@ -31,16 +34,33 @@ public class AnswerButtonUI : MonoBehaviour
} }
} }
private void OnDisable()
{
DOTween.Kill(this);
}
/// <summary> /// <summary>
/// Setup answer button with text and optional image /// Setup answer button with text and optional image
/// </summary> /// </summary>
public void Setup(string answerText, Sprite answerImage, int index, Action<int> callback) public void Setup(string answerText, Sprite answerImage, int index, Action<int> callback)
{ {
if (button != null)
button.Select(); button.Select();
answerIndex = index; answerIndex = index;
onClicked = callback; onClicked = callback;
isInteractable = true; isInteractable = true;
// Cache components
if (rectTransform == null)
rectTransform = GetComponent<RectTransform>();
if (canvasGroup == null)
canvasGroup = GetComponent<CanvasGroup>();
if (canvasGroup == null)
canvasGroup = gameObject.AddComponent<CanvasGroup>();
// Setup text // Setup text
if (answerTextComponent != null) if (answerTextComponent != null)
{ {
...@@ -103,7 +123,11 @@ public class AnswerButtonUI : MonoBehaviour ...@@ -103,7 +123,11 @@ public class AnswerButtonUI : MonoBehaviour
if (buttonBackgroundImage != null) if (buttonBackgroundImage != null)
{ {
buttonBackgroundImage.color = isCorrect ? selectedCorrectColor : selectedIncorrectColor; DOTween.Kill(buttonBackgroundImage, true);
buttonBackgroundImage.DOColor(
isCorrect ? selectedCorrectColor : selectedIncorrectColor,
0.3f
).SetEase(Ease.OutQuad);
} }
if (button != null) if (button != null)
...@@ -121,7 +145,9 @@ public class AnswerButtonUI : MonoBehaviour ...@@ -121,7 +145,9 @@ public class AnswerButtonUI : MonoBehaviour
if (buttonBackgroundImage != null) if (buttonBackgroundImage != null)
{ {
buttonBackgroundImage.color = correctAnswerColor; DOTween.Kill(buttonBackgroundImage, true);
buttonBackgroundImage.DOColor(correctAnswerColor, 0.3f)
.SetEase(Ease.OutQuad);
} }
if (button != null) if (button != null)
...@@ -139,7 +165,9 @@ public class AnswerButtonUI : MonoBehaviour ...@@ -139,7 +165,9 @@ public class AnswerButtonUI : MonoBehaviour
if (buttonBackgroundImage != null) if (buttonBackgroundImage != null)
{ {
buttonBackgroundImage.color = disabledColor; DOTween.Kill(buttonBackgroundImage, true);
buttonBackgroundImage.DOColor(disabledColor, 0.3f)
.SetEase(Ease.OutQuad);
} }
if (button != null) if (button != null)
...@@ -157,6 +185,7 @@ public class AnswerButtonUI : MonoBehaviour ...@@ -157,6 +185,7 @@ public class AnswerButtonUI : MonoBehaviour
if (buttonBackgroundImage != null) if (buttonBackgroundImage != null)
{ {
DOTween.Kill(buttonBackgroundImage, true);
buttonBackgroundImage.color = normalColor; buttonBackgroundImage.color = normalColor;
} }
...@@ -165,6 +194,16 @@ public class AnswerButtonUI : MonoBehaviour ...@@ -165,6 +194,16 @@ public class AnswerButtonUI : MonoBehaviour
answerTextComponent.color = normalTextColor; answerTextComponent.color = normalTextColor;
} }
if (canvasGroup != null)
{
canvasGroup.alpha = 1f;
}
if (rectTransform != null)
{
rectTransform.localScale = Vector3.one;
}
if (button != null) if (button != null)
{ {
button.interactable = true; button.interactable = true;
......
...@@ -193,6 +193,7 @@ namespace com.al_arcade.mcq ...@@ -193,6 +193,7 @@ namespace com.al_arcade.mcq
if (Camera.main != null) if (Camera.main != null)
{ {
Debug.Log("shake");
DOTween.Kill(Camera.main.transform, "camShake"); DOTween.Kill(Camera.main.transform, "camShake");
Camera.main.transform.DOShakePosition(0.4f, 0.3f, 15, 90f, false, true) Camera.main.transform.DOShakePosition(0.4f, 0.3f, 15, 90f, false, true)
.SetEase(Ease.OutQuad).SetId("camShake"); .SetEase(Ease.OutQuad).SetId("camShake");
......
...@@ -4,22 +4,34 @@ using TMPro; ...@@ -4,22 +4,34 @@ using TMPro;
using System; using System;
using com.al_arcade.shared; using com.al_arcade.shared;
using LightSide; using LightSide;
using DG.Tweening;
public class QuestionUi : MonoBehaviour public class QuestionUi : MonoBehaviour
{ {
public static QuestionUi Instance; public static QuestionUi Instance;
[SerializeField] private CanvasGroup questionPanelCanvasGroup;
[SerializeField] private LayoutGroup questionContainer;
[SerializeField] private Image questionImageComponent; [SerializeField] private Image questionImageComponent;
[SerializeField] private UniText questionTextComponent; [SerializeField] private UniText questionTextComponent;
[SerializeField] private Transform answersGrid; [SerializeField] private Transform answersGrid;
[SerializeField] private AnswerButtonUI[] answerButtons = new AnswerButtonUI[4]; [SerializeField] private AnswerButtonUI[] answerButtons = new AnswerButtonUI[4];
[SerializeField] private LayoutGroup questionContainer;
[SerializeField] private LayoutGroup answersContainer; [SerializeField] private LayoutGroup answersContainer;
[SerializeField] private CanvasGroup gameUICanvasGroup;
[Header("Animation Settings")]
[SerializeField] private float questionEntranceDuration = 0.6f;
[SerializeField] private float answerStaggerDelay = 0.1f;
[SerializeField] private float answerEntranceDuration = 0.4f;
[SerializeField] private float wrongAnswerShakeDuration = 0.4f;
[SerializeField] private float wrongAnswerShakeStrength = 15f;
private McqQuestion currentQuestion; private McqQuestion currentQuestion;
private int correctAnswerIndex = -1; private int correctAnswerIndex = -1;
private bool hasAnswered = false; private bool hasAnswered = false;
private Action<int, bool> onAnswerSelected; private Action<int, bool> onAnswerSelected;
private Sequence questionSequence;
private Sequence[] answerSequences = new Sequence[4];
private void Awake() private void Awake()
{ {
...@@ -33,17 +45,27 @@ public class QuestionUi : MonoBehaviour ...@@ -33,17 +45,27 @@ public class QuestionUi : MonoBehaviour
} }
} }
private void OnEnable() private void OnEnable()
{ {
if (answerButtons.Length != 4) if (answerButtons.Length != 4)
{ {
Debug.LogError("MultiChoiceQuestionUI: Must have exactly 4 answer buttons!"); Debug.LogError("MultiChoiceQuestionUI: Must have exactly 4 answer buttons!");
} }
// Auto-find CanvasGroup if not assigned
if (questionPanelCanvasGroup == null)
{
questionPanelCanvasGroup = GetComponent<CanvasGroup>();
}
if (gameUICanvasGroup == null && answersContainer != null)
{
gameUICanvasGroup = answersContainer.GetComponentInParent<CanvasGroup>();
}
} }
/// <summary> /// <summary>
/// Display a multiple choice question with all its data /// Display a multiple choice question with all its data + animations
/// </summary> /// </summary>
public void DisplayQuestion(McqQuestion question, Action<int, bool> onAnswerCallback = null) public void DisplayQuestion(McqQuestion question, Action<int, bool> onAnswerCallback = null)
{ {
...@@ -58,6 +80,13 @@ public class QuestionUi : MonoBehaviour ...@@ -58,6 +80,13 @@ public class QuestionUi : MonoBehaviour
hasAnswered = false; hasAnswered = false;
correctAnswerIndex = -1; correctAnswerIndex = -1;
// Kill previous animations
DOTween.Kill(questionSequence);
for (int i = 0; i < 4; i++)
{
DOTween.Kill(answerSequences[i]);
}
// Reset previous shuffle // Reset previous shuffle
question.ResetShuffle(); question.ResetShuffle();
...@@ -76,11 +105,13 @@ public class QuestionUi : MonoBehaviour ...@@ -76,11 +105,13 @@ public class QuestionUi : MonoBehaviour
// Setup answer buttons with shuffled data // Setup answer buttons with shuffled data
SetupAnswerButtons(shuffledAnswers, shuffledImages); SetupAnswerButtons(shuffledAnswers, shuffledImages);
//RectTransform example = answerButtons[1].GetComponent<RectTransform>();
//(answersContainer as GridLayoutGroup).cellSize = new Vector2(example.rect.width, example.rect.height);
// Rebuild layout hierarchy // Rebuild layout hierarchy
LayoutRebuilder.ForceRebuildLayoutImmediate(questionContainer.GetComponent<RectTransform>()); LayoutRebuilder.ForceRebuildLayoutImmediate(questionContainer.GetComponent<RectTransform>());
LayoutRebuilder.ForceRebuildLayoutImmediate(answersContainer.GetComponent<RectTransform>()); LayoutRebuilder.ForceRebuildLayoutImmediate(answersContainer.GetComponent<RectTransform>());
// Play entrance animations
// AnimateQuestionEntrance();
AnimateAnswersEntrance();
} }
/// <summary> /// <summary>
...@@ -125,6 +156,55 @@ public class QuestionUi : MonoBehaviour ...@@ -125,6 +156,55 @@ public class QuestionUi : MonoBehaviour
} }
} }
/// Animate question entrance (fade only, no position change)
/// </summary>
private void AnimateQuestionEntrance()
{
if (questionPanelCanvasGroup == null)
return;
// Reset state
questionPanelCanvasGroup.alpha = 0;
questionSequence = DOTween.Sequence();
questionSequence.Append(questionPanelCanvasGroup.DOFade(1f, questionEntranceDuration));
}
/// <summary>
/// Animate answers entrance (staggered, spring pop)
/// </summary>
private void AnimateAnswersEntrance()
{
for (int i = 0; i < 4; i++)
{
AnimateAnswerButtonEntrance(i);
}
}
private void AnimateAnswerButtonEntrance(int index)
{
RectTransform answerRect = answerButtons[index].GetComponent<RectTransform>();
CanvasGroup answerCanvasGroup = answerButtons[index].GetComponent<CanvasGroup>();
if (answerCanvasGroup == null)
{
answerCanvasGroup = answerButtons[index].gameObject.AddComponent<CanvasGroup>();
}
// Reset state
answerCanvasGroup.alpha = 0;
answerRect.localScale = Vector3.zero;
float delay = index * answerStaggerDelay;
answerSequences[index] = DOTween.Sequence();
answerSequences[index].AppendInterval(delay);
answerSequences[index].Append(answerCanvasGroup.DOFade(1f, answerEntranceDuration * 0.5f));
answerSequences[index].Join(answerRect.DOScale(1f, answerEntranceDuration)
.SetEase(Ease.OutBack));
}
/// <summary> /// <summary>
/// Called when an answer button is clicked /// Called when an answer button is clicked
/// </summary> /// </summary>
...@@ -154,16 +234,81 @@ public class QuestionUi : MonoBehaviour ...@@ -154,16 +234,81 @@ public class QuestionUi : MonoBehaviour
if (i == selectedIndex) if (i == selectedIndex)
{ {
answerButtons[i].SetSelected(isCorrect); answerButtons[i].SetSelected(isCorrect);
AnimateAnswerSelection(i, isCorrect);
} }
else if (i == correctAnswerIndex && !isCorrect) else if (i == correctAnswerIndex && !isCorrect)
{ {
answerButtons[i].SetCorrect(); answerButtons[i].SetCorrect();
AnimateCorrectAnswer(i);
} }
else else
{ {
answerButtons[i].SetDisabled(); answerButtons[i].SetDisabled();
AnimateDisabledAnswer(i);
} }
} }
// Wrong answer screen shake
if (!isCorrect)
{
ShakeGameUI();
}
}
/// <summary>
/// Animate selected answer (pop + scale)
/// </summary>
private void AnimateAnswerSelection(int index, bool isCorrect)
{
RectTransform answerRect = answerButtons[index].GetComponent<RectTransform>();
DOTween.Sequence()
.Append(answerRect.DOScale(1.15f, 0.15f).SetEase(Ease.OutBack))
.Append(answerRect.DOScale(1f, 0.1f).SetEase(Ease.InQuad));
}
/// <summary>
/// Animate correct answer highlight (glow + pulse)
/// </summary>
private void AnimateCorrectAnswer(int index)
{
RectTransform answerRect = answerButtons[index].GetComponent<RectTransform>();
DOTween.Sequence()
.Append(answerRect.DOScale(1.05f, 0.2f).SetEase(Ease.OutBack))
.SetLoops(-1, LoopType.Yoyo)
.SetId($"correctAnswer_{index}");
}
/// <summary>
/// Animate disabled answer (fade out slightly)
/// </summary>
private void AnimateDisabledAnswer(int index)
{
CanvasGroup answerCanvasGroup = answerButtons[index].GetComponent<CanvasGroup>();
if (answerCanvasGroup == null)
return;
answerCanvasGroup.DOFade(0.5f, 0.3f).SetEase(Ease.InQuad);
}
/// <summary>
/// Screen shake for wrong answer (shake GameUI)
/// </summary>
private void ShakeGameUI()
{
if (gameUICanvasGroup == null)
return;
RectTransform gameUIRect = gameUICanvasGroup.GetComponent<RectTransform>();
gameUIRect.DOShakeAnchorPos(
wrongAnswerShakeDuration,
new Vector2(wrongAnswerShakeStrength, wrongAnswerShakeStrength * 0.5f),
vibrato: 12,
randomness: 90f,
snapping: false
).SetEase(Ease.OutQuad);
} }
/// <summary> /// <summary>
...@@ -174,10 +319,35 @@ public class QuestionUi : MonoBehaviour ...@@ -174,10 +319,35 @@ public class QuestionUi : MonoBehaviour
hasAnswered = false; hasAnswered = false;
correctAnswerIndex = -1; correctAnswerIndex = -1;
foreach (var button in answerButtons) // Kill pulsing animations
for (int i = 0; i < 4; i++)
{ {
button.Reset(); DOTween.Kill($"correctAnswer_{i}");
answerButtons[i].Reset();
} }
// Fade out question
//AnimateQuestionExit();
}
/// <summary>
/// Animate question exit (fade out only, reset position)
/// </summary>
private void AnimateQuestionExit()
{
if (questionPanelCanvasGroup == null)
return;
RectTransform questionRect = questionContainer.GetComponent<RectTransform>();
DOTween.Sequence()
.Append(questionPanelCanvasGroup.DOFade(0f, 0.3f))
.OnComplete(() =>
{
// Reset position back to original after fade
if (questionRect != null)
questionRect.anchoredPosition = Vector2.zero;
});
} }
/// <summary> /// <summary>
......
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