דור עולם פרוצדורלי באחדות

דור העולם ב-Unity מתייחס לתהליך של יצירה או יצירה פרוצדורלית של עולמות וירטואליים, שטחים, נופים או סביבות בתוך מנוע המשחק Unity. טכניקה זו משמשת בדרך כלל בסוגים שונים של משחקים, כמו משחקי עולם פתוח, משחקי RPG, סימולציות ועוד, כדי ליצור באופן דינמי עולמות משחק עצומים ומגוונים.

Unity מספקת מסגרת גמישה ומגוון רחב של כלים וממשקי API ליישום טכניקות אלו מהדור העולמי. אפשר לכתוב סקריפטים מותאמים אישית באמצעות C# כדי ליצור ולתפעל את עולם המשחק או להשתמש בתכונות מובנות Unity כמו מערכת Terrain, פונקציות רעש וממשקי סקריפטים כדי להשיג את התוצאות הרצויות. בנוסף, ישנם גם נכסים של צד שלישי ו-תוספים זמינים ב-Unity Asset Store שיכולים לסייע במשימות הדור העולמי.

ישנן מספר גישות ליצירת עולם ב-Unity, והבחירה תלויה בדרישות הספציפיות של המשחק. להלן מספר שיטות נפוצות:

  • יצירת שטח פרוצדורלי עם רעש פרלין
  • אוטומט סלולרי
  • דיאגרמות וורונו
  • מיקום אובייקט פרוצדורלי

יצירת שטח פרוצדורלי עם רעש פרלין

ניתן להשיג יצירת שטח פרוצדורלית ב-Unity באמצעות אלגוריתמים וטכניקות שונות. גישה פופולרית אחת היא להשתמש ברעש פרלין כדי ליצור את מפת הגובה ולאחר מכן ליישם טכניקות שונות של טקסטורה ועלווה כדי ליצור שטח מציאותי או מסוגנן.

רעש פרלין הוא סוג של רעש שיפוע שפותח על ידי קן פרלין. הוא יוצר תבנית חלקה ומתמשכת של ערכים הנראים אקראיים אך בעלי מבנה קוהרנטי. רעש פרלין נמצא בשימוש נרחב ליצירת שטחים בעלי מראה טבעי, עננים, מרקמים וצורות אורגניות אחרות.

ב-Unity, אפשר להשתמש בפונקציה 'Mathf.PerlinNoise()' כדי ליצור רעש פרלין. זה לוקח שתי קואורדינטות כקלט ומחזיר ערך בין 0 ל-1. על ידי דגימת רעשי פרלין בתדרים ומשרעות שונים, אפשר ליצור רמות שונות של פירוט ומורכבות בתוכן הפרוצדורלי.

הנה דוגמה כיצד ליישם זאת ב-Unity:

  • בעורך Unity, עבור אל "GameObject -> 3D Object -> Terrain". פעולה זו תיצור שטח ברירת מחדל בסצנה.
  • צור סקריפט C# חדש בשם "TerrainGenerator" ו-צרף אותו לאובייקט השטח. להלן סקריפט לדוגמה שיוצר שטח פרוצדורלי באמצעות רעש פרלין:
using UnityEngine;

public class TerrainGenerator : MonoBehaviour
{
    public int width = 512;       // Width of the terrain
    public int height = 512;      // Height of the terrain
    public float scale = 10f;     // Scale of the terrain
    public float offsetX = 100f;  // X offset for noise
    public float offsetY = 100f;  // Y offset for noise
    public float noiseIntensity = 0.1f; //Intensity of the noise

    private void Start()
    {
        Terrain terrain = GetComponent<Terrain>();

        // Create a new instance of TerrainData
        TerrainData terrainData = new TerrainData();

        // Set the heightmap resolution and size of the TerrainData
        terrainData.heightmapResolution = width;
        terrainData.size = new Vector3(width, 600, height);

        // Generate the terrain heights
        float[,] heights = GenerateHeights();
        terrainData.SetHeights(0, 0, heights);

        // Assign the TerrainData to the Terrain component
        terrain.terrainData = terrainData;
    }

    private float[,] GenerateHeights()
    {
        float[,] heights = new float[width, height];

        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                // Generate Perlin noise value for current position
                float xCoord = (float)x / width * scale + offsetX;
                float yCoord = (float)y / height * scale + offsetY;
                float noiseValue = Mathf.PerlinNoise(xCoord, yCoord);

                // Set terrain height based on noise value
                heights[x, y] = noiseValue * noiseIntensity;
            }
        }

        return heights;
    }
}
  • צרף את הסקריפט "TerrainGenerator" לאובייקט Terrain בעורך Unity.
  • בחלון המפקח של אובייקט השטח, כוונן את הרוחב, הגובה, קנה המידה, ההיסטים ועוצמת הרעש כדי לכוונן את מראה השטח שנוצר.
  • לחץ על הלחצן Play בעורך Unity, ואז יש ליצור את השטח הפרוצדורלי בהתבסס על אלגוריתם הרעש של Perlin.

יצירת Unity Terrain עם רעשי פרלין.

הערה: סקריפט זה יוצר מפת גובה שטח בסיסית באמצעות רעש פרלין. כדי ליצור שטחים מורכבים יותר, שנה את הסקריפט כדי לשלב אלגוריתמי רעש נוספים, החל טכניקות שחיקה או החלקה, הוסף טקסטורה או הצב עלווה ואובייקטים על סמך תכונות השטח.

אוטומט סלולרי

אוטומטיות סלולריות הן מודל חישובי המורכב מרשת של תאים, כאשר כל תא מתפתח על סמך קבוצה של כללים מוגדרים מראש ומצבי התאים השכנים לו. זהו מושג רב עוצמה המשמש בתחומים שונים, כולל מדעי המחשב, מתמטיקה ופיזיקה. אוטומטיות סלולריות יכולות להפגין דפוסי התנהגות מורכבים הנובעים מחוקים פשוטים, מה שהופך אותם לשימושיים להדמיית תופעות טבע ויצירת תוכן פרוצדורלי.

התיאוריה הבסיסית מאחורי האוטומטים הסלולריים כוללת את האלמנטים הבאים:

  1. רשת: רשת היא אוסף של תאים המסודרים בתבנית רגילה, כגון סריג מרובע או משושה. לכל תא יכול להיות מספר סופי של מצבים.
  2. שכנים: לכל תא יש תאים שכנים, שהם בדרך כלל התאים הסמוכים לו. ניתן להגדיר את השכונה על סמך דפוסי קישוריות שונים, כגון שכונות פון נוימן (למעלה, למטה, שמאלה, ימינה) או שכונות מור (כולל אלכסוניות).
  3. כללים: ההתנהגות של כל תא נקבעת על ידי קבוצת כללים המציינת כיצד הוא מתפתח בהתבסס על מצבו הנוכחי ומצבי התאים הסמוכים לו. כללים אלה מוגדרים בדרך כלל באמצעות הצהרות מותנות או טבלאות חיפוש.
  4. עדכון: האוטומט הסלולרי מתפתח על ידי עדכון המצב של כל תא בו זמנית לפי הכללים. תהליך זה חוזר על עצמו באופן איטרטיבי, ויוצר רצף של דורות.

לאוטומטים סלולריים יש יישומים שונים בעולם האמיתי, כולל:

  1. סימולציה של תופעות טבעיות: אוטומטים סלולריים יכולים לדמות התנהגות של מערכות פיזיות, כגון דינמיקת נוזלים, שריפות יער, זרימת תנועה ודינמיקה של אוכלוסיה. על ידי הגדרת כללים מתאימים, אוטומטים סלולריים יכולים ללכוד את הדפוסים והדינמיקה המתעוררים הנצפים במערכות בעולם האמיתי.
  2. יצירת תוכן פרוצדורלי: ניתן להשתמש באוטומטים סלולריים ליצירת תוכן פרוצדורלי במשחקים ובסימולציות. לדוגמה, ניתן להשתמש בהם ליצירת שטח, מערכות מערות, הפצת צמחייה ומבנים אורגניים אחרים. ניתן ליצור סביבות מורכבות ומציאותיות על ידי ציון כללים השולטים בצמיחה ובאינטראקציה של תאים.

הנה דוגמה פשוטה ליישום אוטומט סלולרי בסיסי ב-Unity כדי לדמות את משחק החיים:

using UnityEngine;

public class CellularAutomaton : MonoBehaviour
{
    public int width = 50;
    public int height = 50;
    public float cellSize = 1f;
    public float updateInterval = 0.1f;
    public Renderer cellPrefab;

    private bool[,] grid;
    private Renderer[,] cells;
    private float timer = 0f;
    private bool[,] newGrid;

    private void Start()
    {
        InitializeGrid();
        CreateCells();
    }

    private void Update()
    {
        timer += Time.deltaTime;

        if (timer >= updateInterval)
        {
            UpdateGrid();
            UpdateCells();
            timer = 0f;
        }
    }

    private void InitializeGrid()
    {
        grid = new bool[width, height];
        newGrid = new bool[width, height];

        // Initialize the grid randomly
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                grid[x, y] = Random.value < 0.5f;
            }
        }
    }

    private void CreateCells()
    {
        cells = new Renderer[width, height];

        // Create a GameObject for each cell in the grid
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                Vector3 position = new Vector3(x * cellSize, 0f, y * cellSize);
                Renderer cell = Instantiate(cellPrefab, position, Quaternion.identity);
                cell.material.color = Color.white;
                cells[x, y] = cell;
            }
        }
    }

    private void UpdateGrid()
    {
        // Apply the rules to update the grid
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                int aliveNeighbors = CountAliveNeighbors(x, y);

                if (grid[x, y])
                {
                    // Cell is alive
                    if (aliveNeighbors < 2 || aliveNeighbors > 3)
                        newGrid[x, y] = false; // Die due to underpopulation or overpopulation
                    else
                        newGrid[x, y] = true; // Survive
                }
                else
                {
                    // Cell is dead
                    if (aliveNeighbors == 3)
                        newGrid[x, y] = true; // Revive due to reproduction
                    else
                        newGrid[x, y] = false; // Remain dead
                }
            }
        }

        grid = newGrid;
    }

    private void UpdateCells()
    {
        // Update the visual representation of cells based on the grid
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                Renderer renderer = cells[x, y];
                renderer.sharedMaterial.color = grid[x, y] ? Color.black : Color.white;
            }
        }
    }

    private int CountAliveNeighbors(int x, int y)
    {
        int count = 0;

        for (int i = -1; i <= 1; i++)
        {
            for (int j = -1; j <= 1; j++)
            {
                if (i == 0 && j == 0)
                    continue;

                int neighborX = x + i;
                int neighborY = y + j;

                if (neighborX >= 0 && neighborX < width && neighborY >= 0 && neighborY < height)
                {
                    if (grid[neighborX, neighborY])
                        count++;
                }
            }
        }

        return count;
    }
}
  • צרף את הסקריפט "CellularAutomaton" ל-GameObject בסצנה Unity והקצה תא טרומית לשדה 'cellPrefab' במפקח.

אוטומט סלולרי ב-Unity.

בדוגמה זו, רשת של תאים מיוצגת על ידי מערך בוליאני, כאשר 'true' מציין תא חי ו-'false' מייצג תא מת. כללי משחק החיים מיושמים לעדכון הרשת, והייצוג החזותי של התאים מתעדכן בהתאם. השיטה 'CreateCells()' יוצרת GameObject עבור כל תא, והשיטה 'UpdateCells()' מעדכנת את הצבע של כל GameObject בהתבסס על מצב הרשת.

הערה: זוהי רק דוגמה בסיסית, וישנן וריאציות והרחבות רבות לאוטומטים סלולריים שניתן לחקור. ניתן לשנות את הכללים, התנהגויות התא ותצורות הרשת כדי ליצור סימולציות שונות וליצור דפוסים והתנהגויות שונות.

דיאגרמות וורונו

דיאגרמות Voronoi, הידוע גם בשם Voronoi tessellations או Voronoi מחיצות, הן מבנים גיאומטריים המחלקים חלל לאזורים המבוססים על קרבה לקבוצת נקודות הנקראות זרעים או אתרים. כל אזור בתרשים Voronoi מורכב מכל הנקודות במרחב הקרובות יותר לזרע מסוים מאשר לכל זרע אחר.

התיאוריה הבסיסית מאחורי דיאגרמות Voronoi כוללת את האלמנטים הבאים:

  1. זרעים/אתרים: זרעים או אתרים הם קבוצה של נקודות במרחב. ניתן ליצור נקודות אלו באופן אקראי או למקם באופן ידני. כל זרע מייצג נקודת מרכז עבור אזור וורונו.
  2. Voronoi Cells/Regions: כל תא או אזור Voronoi מתאים לאזור בחלל הקרוב יותר לזרע מסוים מאשר לכל זרע אחר. גבולות האזורים נוצרים על ידי חצויים מאונכים של מקטעי הקו המחברים זרעים שכנים.
  3. Delaunay Triangulation: דיאגרמות Voronoi קשורות קשר הדוק ל Delaunay triangulation. משולש Delaunay הוא משולש של נקודות הזרע כך שאף זרע לא נמצא בתוך המעגל של משולש כלשהו. ניתן להשתמש בטריאנגולציה של Delaunay לבניית דיאגרמות Voronoi, ולהיפך.

לדיאגרמות Voronoi יש יישומים שונים בעולם האמיתי, כולל:

  1. יצירת תוכן פרוצדורלי: ניתן להשתמש בדיאגרמות Voronoi ליצירת שטח פרוצדורלי, נופים טבעיים וצורות אורגניות. על ידי שימוש בזרעים כנקודות בקרה והקצאת תכונות (כגון גובה או סוג ביומה) לתאי Voronoi, ניתן ליצור סביבות מציאותיות ומגוונות.
  2. עיצוב משחק: ניתן להשתמש בדיאגרמות Voronoi בעיצוב משחקים כדי לחלק שטח למטרות משחק. לדוגמה, במשחקי אסטרטגיה, ניתן להשתמש בדיאגרמות Voronoi כדי לחלק את מפת המשחק לטריטוריות או אזורים הנשלטים על ידי פלגים שונים.
  3. איתור נתיבים ו-AI: דיאגרמות Voronoi יכולות לסייע באיתור נתיבים ובניווט בינה מלאכותית על ידי מתן ייצוג של המרחב המאפשר חישוב יעיל של הזרע או האזור הקרובים ביותר. ניתן להשתמש בהם כדי להגדיר רשתות ניווט או להשפיע על מפות עבור סוכני AI.

ב-Unity, ישנן מספר דרכים ליצור ולהשתמש בדיאגרמות Voronoi:

  1. Generation Procedural: מפתחים יכולים ליישם אלגוריתמים ליצירת דיאגרמות Voronoi מקבוצה של נקודות ראשונות ב-Unity. ניתן להשתמש באלגוריתמים שונים, כגון האלגוריתם של Fortune או אלגוריתם ההרפיה של לויד, לבניית דיאגרמות Voronoi.
  2. Generation Terrain: ניתן להשתמש בדיאגרמות Voronoi ביצירת שטח כדי ליצור נופים מגוונים ומציאותיים. כל תא Voronoi יכול לייצג תכונת שטח שונה, כגון הרים, עמקים או מישורים. ניתן להקצות לכל תא תכונות כמו גובה, לחות או צמחייה, וכתוצאה מכך שטח מגוון ומושך מבחינה ויזואלית.
  3. חלוקת מפה: ניתן להשתמש בדיאגרמות Voronoi כדי לחלק מפות משחק לאזורים למטרות משחק. אפשר להקצות תכונות או מאפיינים שונים לכל אזור כדי ליצור אזורי משחק נפרדים. זה יכול להיות שימושי עבור משחקי אסטרטגיה, מכניקת שליטה טריטוריאלית או עיצוב רמות.

ישנן חבילות ונכסים של Unity המספקים פונקציונליות של דיאגרמות Voronoi, מה שמקל על שילוב תכונות מבוססות Voronoi בפרויקטים של Unity. חבילות אלו כוללות לרוב אלגוריתמים ליצירת דיאגרמות של Voronoi, כלי הדמיה ושילוב עם מערכת העיבוד Unity.

הנה דוגמה ליצירת דיאגרמת Voronoi דו-ממדית ב-Unity באמצעות האלגוריתם של Fortune:

using UnityEngine;
using System.Collections.Generic;

public class VoronoiDiagram : MonoBehaviour
{
    public int numSeeds = 50;
    public int diagramSize = 50;
    public GameObject seedPrefab;

    private List<Vector2> seeds = new List<Vector2>();
    private List<List<Vector2>> voronoiCells = new List<List<Vector2>>();

    private void Start()
    {
        GenerateSeeds();
        GenerateVoronoiDiagram();
        VisualizeVoronoiDiagram();
    }

    private void GenerateSeeds()
    {
        // Generate random seeds within the diagram size
        for (int i = 0; i < numSeeds; i++)
        {
            float x = Random.Range(0, diagramSize);
            float y = Random.Range(0, diagramSize);
            seeds.Add(new Vector2(x, y));
        }
    }

    private void GenerateVoronoiDiagram()
    {
        // Compute the Voronoi cells based on the seeds
        for (int i = 0; i < seeds.Count; i++)
        {
            List<Vector2> cell = new List<Vector2>();
            voronoiCells.Add(cell);
        }

        for (int x = 0; x < diagramSize; x++)
        {
            for (int y = 0; y < diagramSize; y++)
            {
                Vector2 point = new Vector2(x, y);
                int closestSeedIndex = FindClosestSeedIndex(point);
                voronoiCells[closestSeedIndex].Add(point);
            }
        }
    }

    private int FindClosestSeedIndex(Vector2 point)
    {
        int closestIndex = 0;
        float closestDistance = Vector2.Distance(point, seeds[0]);

        for (int i = 1; i < seeds.Count; i++)
        {
            float distance = Vector2.Distance(point, seeds[i]);
            if (distance < closestDistance)
            {
                closestDistance = distance;
                closestIndex = i;
            }
        }

        return closestIndex;
    }

    private void VisualizeVoronoiDiagram()
    {
        // Visualize the Voronoi cells by instantiating a sphere for each cell point
        for (int i = 0; i < voronoiCells.Count; i++)
        {
            List<Vector2> cell = voronoiCells[i];
            Color color = Random.ColorHSV();

            foreach (Vector2 point in cell)
            {
                Vector3 position = new Vector3(point.x, 0, point.y);
                GameObject sphere = Instantiate(seedPrefab, position, Quaternion.identity);
                sphere.GetComponent<Renderer>().material.color = color;
            }
        }
    }
}
  • כדי להשתמש בקוד זה, צור sphere prefab והקצה אותו לשדה seedPrefab במפקח Unity. התאם את משתני numSeeds ו-DiagramSize כדי לשלוט במספר הזרעים ובגודל הדיאגרמה.

דיאגרמת Voronoi ב-Unity.

בדוגמה זו, הסקריפט VoronoiDiagram יוצר דיאגרמת Voronoi על ידי מיקום אקראי של נקודות זרע בתוך גודל הדיאגרמה שצוין. השיטה 'GenerateVoronoiDiagram()' מחשבת את תאי Voronoi בהתבסס על נקודות הזרע, והשיטה 'VisualizeVoronoiDiagram()' מדגימה כדור GameObject בכל נקודה של תאי Voronoi, תוך הצגת הדיאגרמה.

הערה: דוגמה זו מספקת הדמיה בסיסית של דיאגרמת Voronoi, אך ניתן להרחיב אותה עוד יותר על ידי הוספת תכונות נוספות, כגון חיבור נקודות התא עם קווים או הקצאת תכונות שונות לכל תא למטרות יצירת שטח או משחק.

בסך הכל, דיאגרמות Voronoi מציעות כלי רב-תכליתי ורב עוצמה ליצירת תוכן פרוצדורלי, חלוקת חלל ויצירת סביבות מעניינות ומגוונות ב-Unity.

מיקום אובייקט פרוצדורלי

מיקום אובייקט פרוצדורלי ב-Unity כולל יצירה והצבה של אובייקטים בסצנה באופן אלגוריתמי, במקום מיקומם ידני. זוהי טכניקה רבת עוצמה המשמשת למטרות שונות, כגון אכלוס סביבות בעצים, סלעים, מבנים או חפצים אחרים באופן טבעי ודינמי.

הנה דוגמה למיקום אובייקט פרוצדורלי ב-Unity:

using UnityEngine;

public class ObjectPlacement : MonoBehaviour
{
    public GameObject objectPrefab;
    public int numObjects = 50;
    public Vector3 spawnArea = new Vector3(10f, 0f, 10f);

    private void Start()
    {
        PlaceObjects();
    }

    private void PlaceObjects()
    {
        for (int i = 0; i < numObjects; i++)
        {
            Vector3 spawnPosition = GetRandomSpawnPosition();
            Quaternion spawnRotation = Quaternion.Euler(0f, Random.Range(0f, 360f), 0f);
            Instantiate(objectPrefab, spawnPosition, spawnRotation);
        }
    }

    private Vector3 GetRandomSpawnPosition()
    {
        Vector3 center = transform.position;
        Vector3 randomPoint = center + new Vector3(
            Random.Range(-spawnArea.x / 2, spawnArea.x / 2),
            0f,
            Random.Range(-spawnArea.z / 2, spawnArea.z / 2)
        );
        return randomPoint;
    }
}
  • כדי להשתמש בסקריפט זה, צור GameObject ריק בסצינת Unity ו-צרף את הסקריפט "ObjectPlacement" אליו. הקצה את האובייקט המוקדם והתאם את הפרמטרים 'numObjects' ו-'spawnArea' במפקח כך שיתאימו לדרישות. בעת הפעלת הסצנה, האובייקטים ימוקמו באופן פרוצדורלי בתוך אזור השרצים המוגדר.

מיקום אובייקט פרוצדורלי באחדות.

בדוגמה זו, הסקריפט 'ObjectPlacement' אחראי על הצבת אובייקטים בסצנה באופן פרוצדורלי. יש להקצות את השדה 'objectPrefab' עם הקוד המוקדם של האובייקט שיש למקם. המשתנה 'numObjects' קובע את מספר האובייקטים שיוצבו, והמשתנה 'spawnArea' מגדיר את השטח בו האובייקטים ימוקמו באופן אקראי.

שיטת 'PlaceObjects()' עוברת בלולאה במספר האובייקטים הרצוי ויוצרת עמדות שרצים אקראיות בתוך אזור השרצים המוגדר. לאחר מכן, הוא מציג את האובייקט הטרומי בכל מיקום אקראי עם סיבוב אקראי.

הערה: ניתן לשפר עוד יותר את הקוד הזה על ידי שילוב אלגוריתמי מיקום שונים, כגון מיקום מבוסס רשת, מיקום מבוסס צפיפות או מיקום מבוסס כללים, בהתאם לדרישות הספציפיות של הפרויקט.

סיכום

טכניקות יצירת פרוצדורליות ב-Unity מספקות כלים רבי עוצמה ליצירת חוויות דינמיות וסוחפות. בין אם מדובר ביצירת שטחים באמצעות רעש פרלין או אלגוריתמים פרקטליים, יצירת סביבות מגוונות עם דיאגרמות של Voronoi, הדמיית התנהגויות מורכבות עם אוטומטים סלולריים, או איכלוס של סצנות באובייקטים הממוקמים באופן פרוצדורלי, טכניקות אלו מציעות גמישות, יעילות ואפשרויות אינסופיות ליצירת תוכן. על ידי מינוף האלגוריתמים הללו ושילובם בפרויקטים Unity, מפתחים יכולים להשיג יצירת שטח מציאותית, סימולציות דמויות חיים, סביבות מושכות ויזואלית ומכניקת משחק מרתקת. יצירת תהליכים לא רק חוסכת זמן ומאמץ אלא גם מאפשרת יצירת חוויות ייחודיות ומשתנות ללא הרף, המושכות את השחקנים ומביאות עולמות וירטואליים לחיים.