القائمة الرئيسية

الصفحات

كيفية برمجة لعبة متاهات ثلاثية الأبعاد باستخدام Unity

How to create a 3D maze game using Unity، برمجة لعبة، لعبة متاهات، ثلاثية الأبعاد، Unity، أكواد برمجية، إعداد مشروع، أصول اللعبة، إنشاء مواد، كائنات ثلاثية الأبعاد، إنشاء Prefabs، حركة اللاعب، Rigidbody، توليد المتاهة، C# Script، MazeGenerator، الكاميرا، تشغيل اللعبة، ميزات متقدمة، اكتشاف الهدف، تصميم مرئي، واجهة المستخدم، الصوت، مستويات الصعوبة، عناصر تفاعلية، نظام النقاط، حفظ التقدم، تحسين الأداء، كيفية برمجة لعبة متاهات ثلاثية الأبعاد باستخدام Unity، خطوات برمجة لعبة متاهات ثلاثية الأبعاد على Unity، الأكواد البرمجية لبرمجة لعبة متاهات ثلاثية الأبعاد على Unity، إعداد مشروع Unity جديد لعمل لعبة ثلاثية الأبعاد، إنشاء أصول اللعبة الأساسية في Unity (Materials, 3D Objects, Prefabs)، برمجة حركة اللاعب في Unity باستخدام C# و Rigidbody، توليد المتاهة بشكل عشوائي في Unity باستخدام C# و MazeGenerator، استخدام خوارزمية Recursive Backtracker لتوليد المتاهة في Unity، إضافة كاميرا لرؤية لعبة المتاهات ثلاثية الأبعاد في Unity، تشغيل لعبة المتاهات ثلاثية الأبعاد في محرر Unity، ميزات متقدمة يمكن إضافتها إلى لعبة المتاهات على Unity، برمجة اكتشاف الهدف وإنهاء اللعبة في Unity، استخدام نماذج ثلاثية الأبعاد والإضاءة والظلال في تصميم لعبة Unity، إضافة واجهة مستخدم (UI) مثل عداد الوقت والحركات في Unity، إضافة مؤثرات صوتية وموسيقى خلفية إلى لعبة Unity، تطبيق مستويات صعوبة مختلفة في لعبة المتاهات على Unity، إضافة عناصر تفاعلية مثل الفخاخ والمفاتيح والأبواب والأعداء في Unity، برمجة نظام نقاط في لعبة المتاهات على Unity، حفظ تقدم اللاعب وتحميله في لعبة Unity، استكشاف خوارزميات توليد متاهات مختلفة في Unity، طرق تحسين أداء لعبة Unity ثلاثية الأبعاد، MazeGenerator، مقال مفصل مع الأكواد البرمجية لبرمجة لعبة متاهات ثلاثية الأبعاد على Unity، كيفية برمجة لعبة متاهات ثلاثية الأبعاد باستخدام Unity،  Rigidbody، توليد المتاهة، C# Script، MazeGenerator، إنشاء أصول اللعبة الأساسية في Unity Materials, 3D Objects, Prefabs،





كيفية برمجة لعبة متاهات ثلاثية الأبعاد باستخدام Unity



برمجة لعبة ثلاثية الأبعاد (متاهات) باستخدام Unity تتضمن عدة خطوات،
 بدءًا من إعداد المشروع وإنشاء البيئة وصولًا إلى برمجة حركة اللاعب وتوليد المتاهة.
 نظرًا لطول وتعقيد هذا الموضوع، سأقدم لك مقالًا مفصلاً يشرح الخطوات الأساسية
 مع أمثلة للأكواد البرمجية الأساسية لكل خطوة. 
سأركز على المفاهيم الأساسية التي يمكنك البناء عليها.


خطوات برمجة لعبة متاهات ثلاثية الأبعاد على Unity


اليك الأكواد البرمجية لبرمجة لعبة متاهات ثلاثية الأبعاد على Unity:
1. إعداد مشروع Unity جديد :

- افتح Unity Hub، انقر على "New Project"،
- اختر قالب "3D"، قم بتسمية مشروعك (مثل "MazeGame3D")،
- حدد موقع حفظ المشروع وانقر على "Create".

2. إنشاء أصول اللعبة الأساسية (Assets) :

* إنشاء مواد (Materials) :
- في نافذة "Project"، انقر بزر الماوس الأيمن واختر "Create" -> "Material".
- قم بتسمية المادة الأولى "WallMaterial". في نافذة "Inspector"، اختر لونًا للحائط (مثل الرمادي).
- كرر العملية لإنشاء مادة أخرى باسم "FloorMaterial" واختر لونًا للأرضية (مثل البيج).
- أنشئ مادة ثالثة باسم "PlayerMaterial" واختر لونًا للاعب (مثل الأزرق).
- أنشئ مادة رابعة باسم "GoalMaterial" واختر لونًا للهدف (مثل الأخضر).

* إنشاء كائنات ثلاثية الأبعاد أساسية (3D Objects) :
- في نافذة "Hierarchy"، انقر بزر الماوس الأيمن واختر "3D Object" -> "Cube". 
قم بتسميته "WallPrefab".
- في نافذة "Inspector" لكائن "WallPrefab"، قم بتغيير حجمه ليمثل قطعة من الحائط
 (مثل X: 1، Y: 2، Z: 1). قم بتعيين مادة "WallMaterial" إليه عن طريق
 سحبها من نافذة "Project" إلى كائن "WallPrefab" في نافذة "Hierarchy" أو
 إلى مربع "Material" في نافذة "Inspector".
- كرر العملية لإنشاء "Cube" آخر وقم بتسميته "FloorPrefab". 
قم بتغيير حجمه ليمثل قطعة من الأرضية (مثل X: 10، Y: 0.1، Z: 10). 
قم بتعيين مادة "FloorMaterial" إليه.
- كرر العملية لإنشاء "Sphere" (من "3D Object" -> "Sphere") وقم بتسميته 
"PlayerPrefab". قم بتغيير حجمه (مثل 1, 1, 1) وقم بتعيين مادة "PlayerMaterial" إليه.
- كرر العملية لإنشاء "Sphere" آخر وقم بتسميته "GoalPrefab". 
قم بتغيير حجمه (مثل 1, 1, 1) وقم بتعيين مادة "GoalMaterial" إليه.

* إنشاء Prefabs :
- اسحب الكائنات "WallPrefab" و "FloorPrefab" و
 "PlayerPrefab" و "GoalPrefab" من نافذة "Hierarchy" إلى نافذة "Project".
 سيتحولون إلى أصول Prefab زرقاء. يمكنك الآن حذف الكائنات الأصلية من نافذة "Hierarchy".

3. برمجة حركة اللاعب (Player Movement) :

في نافذة "Project"، انقر بزر الماوس الأيمن واختر 
"Create" -> "C# Script". قم بتسميته "PlayerController".
* افتح سكريبت "PlayerController" وقم بإضافة الكود التالي:
C#




using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public float moveSpeed = 5f;

    private Rigidbody rb;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
        if (rb == null)
        {
            Debug.LogError("Rigidbody component not found on the player!");
            enabled = false;
        }
    }

    void FixedUpdate()
    {
        float moveHorizontal = Input.GetAxis("Horizontal");
        float moveVertical = Input.GetAxis("Vertical");

        Vector3 movement = new Vector3(moveHorizontal, 0f, moveVertical).normalized * moveSpeed;

        rb.velocity = movement;
    }
}


--

- في نافذة "Hierarchy"، قم بإنشاء نسخة من "PlayerPrefab"
 عن طريق سحبه من نافذة "Project" إلى نافذة "Hierarchy". 
قم بتسمية النسخة "Player".
- حدد كائن "Player" في نافذة "Hierarchy" وانقر على 
"Add Component" في نافذة "Inspector". 
ابحث عن "Rigidbody" وأضفه.
- اسحب سكريبت "PlayerController" من نافذة "Project" إلى كائن
 "Player" في نافذة "Hierarchy".
- في نافذة "Inspector" لكائن "Player"، يمكنك تعديل قيمة
 "Move Speed" في سكريبت "PlayerController".




4. توليد المتاهة (Maze Generation):

- في نافذة "Project"، انقر بزر الماوس الأيمن واختر 
"Create" -> "C# Script". قم بتسميته "MazeGenerator".
- افتح سكريبت "MazeGenerator" وقم بإضافة الكود التالي
 (هذا مثال بسيط لتوليد متاهة عشوائية باستخدام خوارزمية Recursive Backtracker):
C#




using UnityEngine;
using System.Collections.Generic;
using Random = UnityEngine.Random;

public class MazeGenerator : MonoBehaviour
{
    public GameObject wallPrefab;
    public GameObject floorPrefab;
    public GameObject playerPrefab;
    public GameObject goalPrefab;
    public int width = 10;
    public int height = 10;
    public float cellSize = 2f;

    private Cell[,] grid;
    private List<Cell> stack = new List<Cell>();

    void Start()
    {
        grid = new Cell[width, height];
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                grid[x, y] = new Cell(x, y);
            }
        }

        GenerateMaze();
        SpawnPlayerAndGoal();
    }

    void GenerateMaze()
    {
        Cell current = grid[Random.Range(0, width), Random.Range(0, height)];
        current.visited = true;
        stack.Add(current);

        while (stack.Count > 0)
        {
            current = stack[stack.Count - 1];
            Cell next = GetUnvisitedNeighbor(current);

            if (next != null)
            {
                RemoveWall(current, next);
                next.visited = true;
                stack.Add(next);
            }
            else
            {
                stack.RemoveAt(stack.Count - 1);
            }
        }

        DrawMaze();
    }

    Cell GetUnvisitedNeighbor(Cell cell)
    {
        List<Cell> neighbors = new List<Cell>();

        int x = cell.x;
        int y = cell.y;

        if (x > 0 && !grid[x - 1, y].visited) neighbors.Add(grid[x - 1, y]);
        if (x < width - 1 && !grid[x + 1, y].visited) neighbors.Add(grid[x + 1, y]);
        if (y > 0 && !grid[x, y - 1].visited) neighbors.Add(grid[x, y - 1]);
        if (y < height - 1 && !grid[x, y + 1].visited) neighbors.Add(grid[x, y + 1]);

        if (neighbors.Count > 0)
        {
            return neighbors[Random.Range(0, neighbors.Count)];
        }

        return null;
    }

    void RemoveWall(Cell a, Cell b)
    {
        if (b.x < a.x) a.leftWall = false;
        else if (b.x > a.x) b.leftWall = false;
        else if (b.y < a.y) a.bottomWall = false;
        else if (b.y > a.y) b.bottomWall = false;
    }

    void DrawMaze()
    {
        if (wallPrefab == null || floorPrefab == null)
        {
            Debug.LogError("Wall or Floor Prefab not assigned to Maze Generator!");
            return;
        }

        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                // Spawn floor
                Vector3 floorPos = new Vector3(x * cellSize + cellSize / 2f, 0f, y * cellSize + cellSize / 2f);
                Instantiate(floorPrefab, floorPos, Quaternion.identity, transform);

                // Spawn left wall
                if (grid[x, y].leftWall)
                {
                    Vector3 wallPos = new Vector3(x * cellSize, cellSize / 2f, y * cellSize + cellSize / 2f);
                    Instantiate(wallPrefab, wallPos, Quaternion.Euler(0f, 90f, 0f), transform);
                }

                // Spawn bottom wall
                if (grid[x, y].bottomWall)
                {
                    Vector3 wallPos = new Vector3(x * cellSize + cellSize / 2f, cellSize / 2f, y * cellSize);
                    Instantiate(wallPrefab, wallPos, Quaternion.identity, transform);
                }

                // Spawn right and top edge walls
                if (x == width - 1)
                {
                    Vector3 wallPos = new Vector3((x + 1) * cellSize, cellSize / 2f, y * cellSize + cellSize / 2f);
                    Instantiate(wallPrefab, wallPos, Quaternion.Euler(0f, 90f, 0f), transform);
                }
                if (y == height - 1)
                {
                    Vector3 wallPos = new Vector3(x * cellSize + cellSize / 2f, cellSize / 2f, (y + 1) * cellSize);
                    Instantiate(wallPrefab, wallPos, Quaternion.identity, transform);
                }
            }
        }
    }

    void SpawnPlayerAndGoal()
    {
        if (playerPrefab == null || goalPrefab == null)
        {
            Debug.LogError("Player or Goal Prefab not assigned to Maze Generator!");
            return;
        }

        // Spawn player at the start (0, 0)
        Vector3 playerSpawnPos = new Vector3(cellSize / 2f, 0.5f, cellSize / 2f);
        Instantiate(playerPrefab, playerSpawnPos, Quaternion.identity);

        // Spawn goal at the end (width - 1, height - 1)
        Vector3 goalSpawnPos = new Vector3((width - 1) * cellSize + cellSize / 2f, 0.5f, (height - 1) * cellSize + cellSize / 2f);
        Instantiate(goalPrefab, goalSpawnPos, Quaternion.identity);
    }

    private class Cell
    {
        public int x;
        public int y;
        public bool visited = false;
        public bool leftWall = true;
        public bool bottomWall = true;

        public Cell(int x, int y)
        {
            this.x = x;
            this.y = y;
        }
    }
}


--

- في نافذة "Hierarchy"، انقر بزر الماوس الأيمن واختر 
"Create Empty". قم بتسميته "Maze".
- حدد كائن "Maze" وانقر على "Add Component" في نافذة "Inspector". 
ابحث عن "MazeGenerator" وأضفه.
- في نافذة "Inspector" لكائن "Maze"، اسحب الـ Prefabs "WallPrefab" و
 "FloorPrefab" و "PlayerPrefab" و "GoalPrefab" من نافذة
 "Project" إلى الخانات المقابلة في سكريبت "MazeGenerator".
- يمكنك تعديل قيم "Width" و "Height" و "Cell Size" لتغيير حجم المتاهة.

5. الكاميرا :

- حدد الكاميرا الرئيسية ("Main Camera") في نافذة "Hierarchy".
- عدّل موضعها وزاوية دورانها بحيث ترى المتاهة واللاعب بشكل جيد (مثلًا، ارفعها للأعلى وانظر للأسفل). يمكنك تجربة قيم مختلفة حتى تحصل على المنظر المطلوب.

6. تشغيل اللعبة :

- اضغط على زر "Play" في شريط أدوات Unity لبدء اللعبة. 
يجب أن ترى متاهة تم إنشاؤها عشوائيًا، ويمكنك التحكم في الكرة 
(اللاعب) باستخدام مفاتيح الأسهم أو WASD.


ميزات متقدمة في لعبة المتاهات على يونيتي


يمكنك إضافة مجموعة كبيرة من الميزات المتقدمة إلى لعبة المتاهات، سأقدم لك
 إرشادات وأمثلة أساسية للأكواد والطرق لتنفيذ بعض هذه الإضافات، 
مع التركيز على المفاهيم الرئيسية التي يمكنك البناء عليها.

1. اكتشاف الهدف وإنهاء اللعبة:

- سكريبت مدير اللعبة (Game Manager): يمكنك إنشاء سكريبت 
مدير للعبة لتتبع حالة اللعبة (مثل ما إذا كانت اللعبة قد انتهت) وإدارة شاشة الفوز/الخسارة.
C#




using UnityEngine;
using UnityEngine.SceneManagement;

public class GameManager : MonoBehaviour
{
    public static GameManager Instance;
    public string winSceneName = "WinScene";
    public float delayBeforeLoadWinScene = 2f;
    private bool gameIsOver = false;

    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
        }
        else
        {
            Destroy(gameObject);
            return;
        }
    }

    public void PlayerReachedGoal()
    {
        if (!gameIsOver)
        {
            gameIsOver = true;
            Debug.Log("Game Over - Player Won!");
            // يمكنك هنا تشغيل مؤثر صوتي للفوز
            Invoke("LoadWinScene", delayBeforeLoadWinScene);
        }
    }

    void LoadWinScene()
    {
        SceneManager.LoadScene(winSceneName);
    }

    public void RestartGame()
    {
        gameIsOver = false;
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
    }
}


--

* اضافة سكريبت "Goal":

- إنشاء سكريبت "Goal":
في نافذة "Project"، أنشئ سكريبت C# جديدًا باسم "Goal".
C#

using UnityEngine;

public class Goal : MonoBehaviour
{
    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Player"))
        {
            GameManager.Instance.PlayerReachedGoal();
        }
    }
}
--

* سكريبت "WinSceneUI" (على مشهد الفوز):

C#

using UnityEngine;
using UnityEngine.UI;

public class WinSceneUI : MonoBehaviour
{
    public Button restartButton;

    void Start()
    {
        if (restartButton != null)
        {
            restartButton.onClick.AddListener(GameManager.Instance.RestartGame);
        }
        else
        {
            Debug.LogError("Restart Button not assigned in WinSceneUI!");
        }
    }
}
--

* تطبيق السكريبت على الهدف:

- حدد كائن "GoalPrefab" في نافذة "Project" أو نسخة منه في "Hierarchy".
- أضف Component جديدًا واختر "Box Collider" (أو "Sphere Collider" حسب شكل الهدف).
 تأكد من تفعيل خاصية "Is Trigger".
- اسحب سكريبت "Goal" إلى كائن الهدف.
- في نافذة "Inspector" لكائن الهدف، يمكنك تعيين اسم المشهد الذي
 سيتم تحميله عند الفوز في حقل "Win Scene Name".
- تأكد من وضع علامة "Tag" باسم "Player" على كائن اللاعب
 (في نافذة "Inspector" لكائن "Player"، ابحث عن حقل "Tag" واختر 
"Player" من القائمة أو أنشئ علامة جديدة إذا لم تكن موجودة).




* إنشاء مشهد الفوز ("WinScene"):

في نافذة "Project"، انقر بزر الماوس الأيمن واختر 
"Create" -> "Scene". قم بتسميته "WinScene" وافتحه.
في هذا المشهد، يمكنك إضافة عناصر UI مثل نص "لقد فزت!" 
وزر لإعادة تشغيل اللعبة (تحميل مشهد المتاهة الأصلي).

2. التصميم المرئي المتقدم والصوت:

- نماذج ثلاثية الأبعاد: يمكنك البحث عن نماذج ثلاثية الأبعاد مجانية أو مدفوعة للجدران
 والأرضية واللاعب والهدف على متاجر الأصول مثل Unity Asset Store أو
 Sketchfab. استبدل الكائنات الأولية (Cubes و Spheres) بهذه النماذج.
- الإضاءة والظلال: في نافذة "Hierarchy"، يمكنك إضافة أنواع مختلفة
 من الإضاءة ("Light" -> "Directional Light"، "Point Light"، إلخ.) 
وضبط خصائصها. تأكد من تفعيل الظلال في خصائص الإضاءة وفي 
إعدادات الرندر للمشروع ("Edit" -> "Project Settings" -> "Graphics").
- المؤثرات البصرية: يمكنك استخدام أنظمة الجسيمات ("Particle System") 
لإنشاء مؤثرات مثل الغبار عند حركة اللاعب أو تأثير عند الوصول إلى الهدف.

* الصوت:
- استيراد ملفات صوتية: اسحب ملفات الصوت (مثل .mp3 أو .wav) 
إلى مجلد في نافذة "Project".
- إضافة Audio Source: حدد كائن اللاعب أو كائنًا آخر في المشهد وأضف
 Component جديدًا باسم "Audio Source".
- تعيين ملفات الصوت: في نافذة "Inspector" لمكون "Audio Source"،
 اسحب ملفًا صوتيًا من نافذة "Project" إلى حقل "Audio Clip".
- برمجة تشغيل الصوت: يمكنك استخدام سكريبت للتحكم في تشغيل الأصوات
 في مواقف مختلفة (مثل تشغيل صوت عند تحرك اللاعب في سكريبت "PlayerController"،
 أو صوت اصطدام في سكريبت منفصل على الجدران، أو صوت الفوز في سكريبت "Goal"). استخدم GetComponent<AudioSource>().Play(); لتشغيل الصوت.
إضافة Audio Source إلى اللاعب: حدد كائن "Player" وأضف
 Component "Audio Source". قم بتعطيل خيار "Play On Awake".
 اسحب مقطعًا صوتيًا لحركة القدم إلى حقل "Audio Clip".

* تعديل سكريبت "PlayerController":

C#




using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public float moveSpeed = 5f;
    public AudioSource footstepSound;
    private Rigidbody rb;
    private bool isMoving = false;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
        if (rb == null)
        {
            Debug.LogError("Rigidbody component not found on the player!");
            enabled = false;
        }
        if (footstepSound == null)
        {
            Debug.LogWarning("Footstep AudioSource not assigned on the player!");
        }
    }

    void FixedUpdate()
    {
        float moveHorizontal = Input.GetAxis("Horizontal");
        float moveVertical = Input.GetAxis("Vertical");

        Vector3 movement = new Vector3(moveHorizontal, 0f, moveVertical).normalized * moveSpeed;

        rb.velocity = movement;

        bool moving = movement.magnitude > 0.1f;
        if (moving && !isMoving && footstepSound != null)
        {
            footstepSound.Play();
            isMoving = true;
        }
        else if (!moving && isMoving && footstepSound != null)
        {
            footstepSound.Stop();
            isMoving = false;
        }
    }
}


--

3. واجهة المستخدم (UI):

- إنشاء Canvas: في نافذة "Hierarchy"، انقر بزر الماوس الأيمن واختر "UI" -> "Canvas".
- إضافة عناصر UI: ضمن الـ Canvas، يمكنك إضافة عناصر مثل "Text"
 (لعداد الوقت وعداد الحركات وشاشة الفوز/الخسارة) و "Button" (لزر الإيقاف المؤقت وإعادة التشغيل).
- برمجة وظائف UI: ستحتاج إلى إنشاء سكريبتات للتحكم في تحديث نصوص
 UI بناءً على وقت اللعب وعدد الحركات، وبرمجة وظائف الأزرار 
(مثل إيقاف/استئناف اللعبة باستخدام Time.timeScale = 0f; و Time.timeScale = 1f;) وتحميل المشاهد.

* عداد الوقت:

- إنشاء عنصر Text UI: ضمن الـ Canvas، أنشئ عنصر "Text (TMP Text)"
 (إذا كنت تستخدم TextMeshPro، وهو موصى به) أو "Text" (UI).

* إنشاء سكريبت "TimerUI":

C#




using UnityEngine;
using TMPro; // إذا كنت تستخدم TextMeshPro
using UnityEngine.UI; // إذا كنت تستخدم UI.Text

public class TimerUI : MonoBehaviour
{
    public TMP_Text timerTextTMP; // إذا كنت تستخدم TextMeshPro
    public Text timerTextUI;     // إذا كنت تستخدم UI.Text
    private float startTime;

    void Start()
    {
        startTime = Time.time;
    }

    void Update()
    {
        float elapsedTime = Time.time - startTime;
        string minutes = ((int)elapsedTime / 60).ToString("00");
        string seconds = (elapsedTime % 60).ToString("00.00");

        if (timerTextTMP != null)
        {
            timerTextTMP.text = minutes + ":" + seconds;
        }
        else if (timerTextUI != null)
        {
            timerTextUI.text = minutes + ":" + seconds;
        }
        else
        {
            Debug.LogError("Timer Text UI element not assigned!");
        }
    }
}


--

- تطبيق السكريبت: أضف سكريبت "TimerUI" إلى الـ Canvas أو كائن UI منفصل. 
اسحب عنصر Text UI الذي أنشأته إلى الحقل المناسب في نافذة "Inspector".


4. مستويات الصعوبة:

يمكنك تحقيق ذلك عن طريق إنشاء عدة مشاهد للمتاهات بأحجام أو تعقيدات مختلفة،
 أو عن طريق تعديل قيم "width" و "height" في سكريبت 
"MazeGenerator" بناءً على اختيار اللاعب من واجهة المستخدم.

5. عناصر تفاعلية:

- الفخاخ: إنشاء Prefabs للفخاخ (مثل مناطق تسبب ضررًا عند دخولها) وكتابة 
سكريبتات تتعامل مع تفاعلات اللاعب معها (باستخدام OnTriggerEnter).
- المفاتيح والأبواب: إنشاء Prefabs للمفاتيح والأبواب. برمجة منطق جمع المفاتيح
 (تتبع حالة اللاعب) وبرمجة فتح الأبواب عند محاولة المرور بها والتحقق من امتلاك المفتاح المطلوب.
- الأعداء: إنشاء Prefabs للأعداء وبرمجة حركتهم (مثل الدوران العشوائي أو
 المطاردة البسيطة) وبرمجة تفاعلاتهم مع اللاعب (مثل اكتشاف اللاعب والاصطدام).
* الفخاخ (مثال منطقة ضرر):
- إنشاء Prefab للفخ: أنشئ كائنًا ثلاثي الأبعاد (مثل مكعب أحمر صغير) وقم
 بتسميته "TrapPrefab". أضف إليه "Box Collider" وفعّل "Is Trigger".

* إنشاء سكريبت "DamageOnTouch":

C#

using UnityEngine;

public class DamageOnTouch : MonoBehaviour
{
    public float damageAmount = 10f; // مثال لمقدار الضرر

    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Player"))
        {
            Debug.Log("Player hit a trap! Damage: " + damageAmount);
            // هنا يمكنك إضافة منطق لتطبيق الضرر على اللاعب
            // (مثل استدعاء دالة على سكريبت PlayerHealth)
        }
    }
}
--

- تطبيق السكريبت: أضف سكريبت "DamageOnTouch" إلى "TrapPrefab"
 (أو نسخة منه في المشهد) وقم بتعديل قيمة "Damage Amount" في نافذة "Inspector".


6. نظام النقاط:

إنشاء متغير في سكريبت "PlayerController" أو سكريبت مدير
 اللعبة لتتبع النقاط. قم بتحديث هذه النقاط بناءً على أفعال اللاعب
 (مثل الوقت المستغرق، عدد الحركات، جمع عناصر معينة). اعرض النقاط على واجهة المستخدم.
* تعديل سكريبت مدير اللعبة (GameManager):

C#




using UnityEngine;
using TMPro; // إذا كنت تستخدم TextMeshPro
using UnityEngine.UI; // إذا كنت تستخدم UI.Text

public class GameManager : MonoBehaviour
{
    public static GameManager Instance;
    public TMP_Text scoreTextTMP; // عنصر UI لعرض النقاط (TextMeshPro)
    public Text scoreTextUI;     // عنصر UI لعرض النقاط (UI.Text)
    private int score = 0;

    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
        }
        else
        {
            Destroy(gameObject);
            return;
        }
        UpdateScoreUI();
    }

    public void AddScore(int points)
    {
        score += points;
        UpdateScoreUI();
    }

    public int GetScore()
    {
        return score;
    }

    private void UpdateScoreUI()
    {
        if (scoreTextTMP != null)
        {
            scoreTextTMP.text = "النقاط: " + score;
        }
        else if (scoreTextUI != null)
        {
            scoreTextUI.text = "النقاط: " + score;
        }
        else
        {
            Debug.LogWarning("Score Text UI element not assigned in GameManager!");
        }
    }

    // ... باقي كود GameManager (PlayerReachedGoal, RestartGame, إلخ.)
}


--

* تطبيق التعديلات:

أضف عنصر Text UI جديدًا إلى الـ Canvas وقم بتعيينه لسكريبت 
"GameManager" في نافذة "Inspector".
في أي سكريبت آخر حيث تريد إضافة نقاط 
(مثل عند جمع عنصر معين أو عند الوصول إلى جزء معين من المتاهة)، 
يمكنك استدعاء GameManager.Instance.AddScore(amount);.

7. حفظ التقدم:

يمكن تحقيق ذلك باستخدام طرق مختلفة لحفظ البيانات مثل PlayerPrefs
 (لتخزين بسيط) أو حفظ البيانات إلى ملف JSON أو XML. 
ستحتاج إلى برمجة منطق لحفظ حالة اللعبة (موقع اللاعب، حالة الأبواب، النقاط، إلخ.) 
وتحميلها عند بدء اللعبة مرة أخرى.
* استخدام PlayerPrefs (للتخزين البسيط):

C#




using UnityEngine;

public class SaveLoadManager : MonoBehaviour
{
    private const string SaveKeyScore = "Score";
    private const string SaveKeyLevel = "CurrentLevel";
    private const string SaveKeyPlayerX = "PlayerPositionX";
    private const string SaveKeyPlayerY = "PlayerPositionY";
    private const string SaveKeyPlayerZ = "PlayerPositionZ";

    public static SaveLoadManager Instance;
    public Transform playerTransform; // اسحب كائن اللاعب هنا

    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
        }
        else
        {
            Destroy(gameObject);
            return;
        }
        LoadGame(); // محاولة التحميل عند بدء التشغيل
    }

    public void SaveGame()
    {
        PlayerPrefs.SetInt(SaveKeyScore, GameManager.Instance.GetScore());
        // PlayerPrefs.SetInt(SaveKeyLevel, currentLevel); // إذا كان لديك نظام مستويات
        PlayerPrefs.SetFloat(SaveKeyPlayerX, playerTransform.position.x);
        PlayerPrefs.SetFloat(SaveKeyPlayerY, playerTransform.position.y);
        PlayerPrefs.SetFloat(SaveKeyPlayerZ, playerTransform.position.z);
        PlayerPrefs.Save(); // حفظ البيانات على القرص
        Debug.Log("Game Saved!");
    }

    public void LoadGame()
    {
        if (PlayerPrefs.HasKey(SaveKeyScore))
        {
            GameManager.Instance.AddScore(PlayerPrefs.GetInt(SaveKeyScore) - GameManager.Instance.GetScore()); // تعويض النقاط الحالية
            // currentLevel = PlayerPrefs.GetInt(SaveKeyLevel, 1); // تحميل المستوى الحالي (افتراضيًا 1)
            float x = PlayerPrefs.GetFloat(SaveKeyPlayerX, 0f);
            float y = PlayerPrefs.GetFloat(SaveKeyPlayerY, 0.5f);
            float z = PlayerPrefs.GetFloat(SaveKeyPlayerZ, 0f);
            playerTransform.position = new Vector3(x, y, z);
            Debug.Log("Game Loaded!");
        }
        else
        {
            Debug.Log("No save data found.");
        }
    }
}


--

- تطبيق السكريبت: أضف سكريبت "SaveLoadManager" إلى كائن
 دائم في المشهد (مثل GameManager). اسحب كائن اللاعب إلى حقل
 "Player Transform" في نافذة "Inspector". قم باستدعاء
 SaveLoadManager.Instance.SaveGame() لحفظ اللعبة و 
SaveLoadManager.Instance.LoadGame() لتحميلها.
- حفظ البيانات إلى JSON/XML: هذه الطريقة أكثر تعقيدًا وتتطلب إنشاء هياكل بيانات لحفظ
 حالة اللعبة واستخدام مكتبات مثل System.IO و UnityEngine.JsonUtility (لـ JSON) أو System.Xml.Serialization (لـ XML) لقراءة وكتابة الملفات. 
يمكنك البحث عن دروس محددة حول حفظ البيانات باستخدام JSON أو XML في Unity.

8. توليد أنواع مختلفة من المتاهات:

ابحث عن خوارزميات توليد متاهات أخرى 
(مثل Prim's Algorithm أو Kruskal's Algorithm) وقم بتطبيقها في 
سكريبت "MazeGenerator" أو قم بإنشاء سكريبتات توليد منفصلة وقم بتبديلها.
- Prim's Algorithm: تبدأ هذه الخوارزمية بخلية عشوائية ثم تضيف جدرانًا عشوائية من 
الخلايا المجاورة التي لم تتم زيارتها بعد، مما يؤدي إلى متاهة ذات العديد من الممرات القصيرة. 
يمكنك البحث عن تطبيقات Prim's Algorithm في C# Unity.
- Kruskal's Algorithm: تبدأ هذه الخوارزمية بجميع الجدران موجودة ثم تقوم بإزالة
 الجدران بشكل عشوائي بين الخلايا غير المتصلة حتى يصبح جميع الخلايا متصلة،
 مما ينتج عنه متاهة ذات ممرات أطول وأكثر استقامة. يمكنك البحث عن تطبيقات 
Kruskal's Algorithm في C# Unity.
- Sidewinder Algorithm: خوارزمية أبسط وأسرع تنتج متاهات ذات ممرات أفقية طويلة وقصيرة عمودية.

9. تحسين الأداء:

- Batching: تجميع الكائنات المتشابهة لتقليل عدد عمليات الرسم.
- Occlusion Culling: إخفاء الكائنات التي لا يراها اللاعب.
- LOD (Level of Detail): استخدام نماذج أقل تفصيلاً للكائنات البعيدة.
- تحسين الكود: كتابة كود فعال وتجنب العمليات الحسابية المكلفة في كل إطار.

* ملاحظة هامة:

تنفيذ كل هذه الإضافات يتطلب قدرًا كبيرًا من العمل والبرمجة. 
من المستحسن أن تبدأ بتنفيذ ميزة واحدة أو اثنتين في كل مرة، وتختبرها جيدًا قبل الانتقال إلى الميزات الأخرى. 

الخلاصة :

المقال يوفر قاعدة صلبة لتنفيذ لعبة متاهات ثلاثية الأبعاد أساسية. 
يمكنك اعتبارها نقطة انطلاق ممتازة. لتطوير لعبة أكثر اكتمالًا وجاذبية،
 ستحتاج إلى إضافة المزيد من الميزات والتحسينات بناءً على اهتماماتك وأهدافك.
 بالتوفيق في تطوير لعبتك!



جدول المحتويات