Birlik İçinde Prosedürel Dünya Üretimi

Unity'teki dünya oluşturma, Unity oyun motoru içinde sanal dünyalar, araziler, manzaralar veya ortamlar oluşturma veya prosedürel olarak oluşturma sürecini ifade eder. Bu teknik, geniş ve çeşitli oyun dünyalarını dinamik olarak yaratmak için açık dünya oyunları, RPG'ler, simülasyonlar ve daha fazlası gibi çeşitli oyun türlerinde yaygın olarak kullanılır.

Unity bu dünya çapındaki tekniklerin uygulanması için esnek bir çerçeve ve çok çeşitli araçlar ve API'ler sağlar. Oyun dünyasını oluşturmak ve yönetmek için C# kullanarak özel komut dosyaları yazılabilir veya istenen sonuçları elde etmek için Terrain sistemi, gürültü işlevleri ve komut dosyası oluşturma arayüzleri gibi Unity yerleşik özelliklerden yararlanılabilir. Ek olarak, Unity Asset Store'da dünya oluşturma görevlerine yardımcı olabilecek üçüncü taraf varlıklar ve eklentiler de mevcuttur.

Unity'de dünya oluşturma konusunda çeşitli yaklaşımlar vardır ve seçim oyunun özel gereksinimlerine bağlıdır. İşte yaygın olarak kullanılan birkaç yöntem:

  • Perlin Gürültüsüyle Prosedürel Arazi Oluşturma
  • Hücresel Otomatlar
  • Voronoi Diyagramları
  • Prosedürel Nesne Yerleştirme

Perlin Gürültüsüyle Prosedürel Arazi Oluşturma

Unity'de prosedürel arazi üretimi, çeşitli algoritmalar ve teknikler kullanılarak gerçekleştirilebilir. Popüler yaklaşımlardan biri, yükseklik haritasını oluşturmak için Perlin gürültüsünü kullanmak ve ardından gerçekçi veya stilize bir arazi oluşturmak için çeşitli dokulandırma ve bitki örtüsü tekniklerini uygulamaktır.

Perlin gürültüsü, Ken Perlin tarafından geliştirilen bir tür gradyan gürültüsüdür. Rastgele görünen ancak tutarlı bir yapıya sahip olan, düzgün ve sürekli bir değer modeli üretir. Perlin gürültüsü, doğal görünümlü araziler, bulutlar, dokular ve diğer organik şekiller oluşturmak için yaygın olarak kullanılır.

Unity'de Perlin gürültüsü oluşturmak için 'Mathf.PerlinNoise()' fonksiyonu kullanılabilir. Giriş olarak iki koordinat alır ve 0 ile 1 arasında bir değer döndürür. Perlin gürültüsünü farklı frekanslarda ve genliklerde örnekleyerek prosedür içeriğinde farklı düzeylerde ayrıntı ve karmaşıklık yaratmak mümkündür.

İşte bunun Unity:'de nasıl uygulanacağına dair bir örnek:

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" komut dosyasını Unity Düzenleyici'deki Terrain nesnesine ekleyin.
  • Arazi nesnesinin Denetleyici penceresinde, oluşturulan arazinin görünümünü değiştirmek için genişliği, yüksekliği, ölçeği, uzaklıkları ve gürültü yoğunluğunu ayarlayın.
  • Unity Düzenleyicide Oynat düğmesine basın; prosedürel alan Perlin gürültü algoritmasına göre oluşturulmalıdır.

Perlin gürültüsüyle Unity Terrain üretimi.

Not: Bu komut dosyası Perlin gürültüsünü kullanarak temel bir arazi yükseklik haritası oluşturur. Daha karmaşık araziler oluşturmak için, senaryoyu ek gürültü algoritmaları içerecek şekilde değiştirin, erozyon veya yumuşatma teknikleri uygulayın, doku ekleyin veya arazinin özelliklerine göre bitki örtüsü ve nesneleri yerleştirin.

Hücresel Otomatlar

Hücresel otomata, her bir hücrenin önceden tanımlanmış bir dizi kurala ve komşu hücrelerin durumlarına göre geliştiği bir hücre ızgarasından oluşan bir hesaplama modelidir. Bilgisayar bilimi, matematik ve fizik dahil olmak üzere çeşitli alanlarda kullanılan güçlü bir kavramdır. Hücresel otomatlar, basit kurallardan ortaya çıkan karmaşık davranış kalıpları sergileyebilir, bu da onları doğal olayları simüle etmek ve prosedürel içerik oluşturmak için yararlı kılar.

Hücresel otomatın arkasındaki temel teori aşağıdaki unsurları içerir:

  1. Izgara: Izgara, kare veya altıgen kafes gibi düzenli bir düzende düzenlenmiş hücreler topluluğudur. Her hücre sonlu sayıda duruma sahip olabilir.
  2. Komşular: Her hücrenin, tipik olarak hemen bitişik hücreleri olan komşu hücreleri vardır. Mahalle, von Neumann (yukarı, aşağı, sol, sağ) veya Moore (çapraz dahil) mahalleleri gibi farklı bağlantı modellerine göre tanımlanabilir.
  3. Kurallar: Her hücrenin davranışı, mevcut durumuna ve komşu hücrelerin durumlarına göre nasıl geliştiğini belirleyen bir dizi kural tarafından belirlenir. Bu kurallar genellikle koşullu ifadeler veya arama tabloları kullanılarak tanımlanır.
  4. Güncelleme: Hücresel otomat, kurallara göre her hücrenin durumunu aynı anda güncelleyerek gelişir. Bu süreç tekrarlanarak tekrarlanarak bir nesiller dizisi oluşturulur.

Hücresel otomatların gerçek dünyada çeşitli uygulamaları vardır:

  1. Doğal Olayların Simülasyonu: Hücresel otomatlar, akışkan dinamiği, orman yangınları, trafik akışı ve nüfus dinamiği gibi fiziksel sistemlerin davranışını simüle edebilir. Uygun kuralları tanımlayarak hücresel otomatlar, gerçek dünya sistemlerinde gözlemlenen ortaya çıkan modelleri ve dinamikleri yakalayabilir.
  2. Prosedürel İçerik Oluşturma: Hücresel otomatlar, oyunlarda ve simülasyonlarda prosedürel içerik oluşturmak için kullanılabilir. Örneğin arazi, mağara sistemleri, bitki örtüsü dağılımı ve diğer organik yapıları oluşturmak için kullanılabilirler. Hücrelerin büyümesini ve etkileşimini yöneten kuralların belirlenmesiyle karmaşık ve gerçekçi ortamlar oluşturulabilir.

İşte hayat oyununu simüle etmek için Unity'de temel bir hücresel otomat uygulamasının basit bir örneği:

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" komut dosyasını Unity sahnesindeki bir GameObject'e ekleyin ve denetçideki 'cellPrefab' alanına bir hücre hazır yapısı atayın.

Unity'de hücresel otomat.

Bu örnekte, hücrelerden oluşan bir ızgara bir boole dizisi ile temsil edilir; burada 'true' canlı bir hücreyi, 'false' ise ölü bir hücreyi temsil eder. Izgarayı güncellemek için yaşam oyununun kuralları uygulanır ve hücrelerin görsel temsili buna göre güncellenir. 'CreateCells()' yöntemi, her hücre için bir GameObject oluşturur ve 'UpdateCells()' yöntemi, ızgara durumuna göre her GameObject'in rengini günceller.

Not: Bu yalnızca temel bir örnektir ve hücresel otomatların keşfedilebilecek birçok varyasyonu ve uzantısı vardır. Kurallar, hücre davranışları ve ızgara konfigürasyonları, farklı simülasyonlar oluşturmak ve çeşitli modeller ve davranışlar oluşturmak için değiştirilebilir.

Voronoi Diyagramları

Voronoi mozaikleri veya Voronoi bölümleri olarak da bilinen Voronoi diyagramları, bir alanı tohumlar veya alanlar adı verilen bir dizi noktaya yakınlığa dayalı olarak bölgelere bölen geometrik yapılardır. Voronoi diyagramındaki her bölge, uzayda belirli bir tohuma diğer tohumlardan daha yakın olan tüm noktalardan oluşur.

Voronoi diyagramlarının ardındaki temel teori aşağıdaki unsurları içerir:

  1. Tohumlar/Siteler: Tohumlar veya siteler uzaydaki bir dizi noktadır. Bu noktalar rastgele oluşturulabileceği gibi manuel olarak da yerleştirilebilir. Her tohum bir Voronoi bölgesinin merkez noktasını temsil ediyor.
  2. Voronoi Hücreleri/Bölgeleri: Her Voronoi hücresi veya bölgesi, uzayın belirli bir tohuma diğer tohumlardan daha yakın olan bir alanına karşılık gelir. Bölgelerin sınırları, komşu tohumları birbirine bağlayan çizgi bölümlerinin dik açıortayları tarafından oluşturulur.
  3. Delaunay Üçgenlemesi: Voronoi diyagramları Delaunay üçgenlemesi ile yakından ilişkilidir. Delaunay üçgenlemesi, herhangi bir üçgenin çevrel çemberi içinde hiçbir tohum olmayacak şekilde tohum noktalarının üçgenlenmesidir. Delaunay üçgenlemesi Voronoi diyagramlarını oluşturmak için kullanılabilir ve bunun tersi de geçerlidir.

Voronoi diyagramlarının gerçek dünyada çeşitli uygulamaları vardır:

  1. Prosedürel İçerik Oluşturma: Voronoi diyagramları prosedürel arazi, doğal manzaralar ve organik şekiller oluşturmak için kullanılabilir. Tohumları kontrol noktaları olarak kullanarak ve Voronoi hücrelerine nitelikler (yükseklik veya biyom türü gibi) atayarak gerçekçi ve çeşitli ortamlar yaratılabilir.
  2. Oyun Tasarımı: Voronoi diyagramları, oyun tasarımında alanı oyun amaçları doğrultusunda bölmek için kullanılabilir. Örneğin strateji oyunlarında, oyun haritasını farklı gruplar tarafından kontrol edilen bölgelere veya bölgelere bölmek için Voronoi diyagramları kullanılabilir.
  3. Yol Bulma ve AI: Voronoi diyagramları, en yakın tohum veya bölgenin verimli bir şekilde hesaplanmasına olanak tanıyan alanın bir temsilini sağlayarak yol bulmaya ve AI navigasyonuna yardımcı olabilir. Yapay zeka aracıları için gezinme ağlarını tanımlamak veya haritaları etkilemek için kullanılabilirler.

Unity'de Voronoi diyagramlarını oluşturmanın ve kullanmanın birkaç yolu vardır:

  1. Prosedürel Üretim: Geliştiriciler, Unity'deki bir dizi başlangıç ​​noktasından Voronoi diyagramları oluşturmak için algoritmalar uygulayabilir. Voronoi diyagramlarını oluşturmak için Fortune algoritması veya Lloyd gevşeme algoritması gibi çeşitli algoritmalar kullanılabilir.
  2. Arazi Oluşturma: Voronoi diyagramları, çeşitli ve gerçekçi manzaralar oluşturmak için arazi oluşturmada kullanılabilir. Her Voronoi hücresi dağlar, vadiler veya ovalar gibi farklı bir arazi özelliğini temsil edebilir. Her hücreye yükseklik, nem veya bitki örtüsü gibi özellikler atanabilir, bu da çeşitli ve görsel olarak çekici bir arazi elde edilmesini sağlar.
  3. Harita Bölümleme: Voronoi diyagramları, oyun haritalarını oyun amaçları doğrultusunda bölgelere bölmek için kullanılabilir. Farklı oyun bölgeleri oluşturmak için her bölgeye farklı nitelikler veya özellikler atamak mümkündür. Bu, strateji oyunları, bölgesel kontrol mekaniği veya seviye tasarımı için yararlı olabilir.

Voronoi diyagramı işlevselliği sağlayan, Voronoi tabanlı özelliklerin Unity projelerine dahil edilmesini kolaylaştıran Unity paketleri ve varlıkları mevcuttur. Bu paketler genellikle Voronoi diyagramı oluşturma algoritmalarını, görselleştirme araçlarını ve Unity oluşturma sistemiyle entegrasyonu içerir.

Fortune'un algoritmasını kullanarak Unity formatında 2 boyutlu Voronoi diyagramı oluşturmanın bir örneğini burada bulabilirsiniz:

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;
            }
        }
    }
}
  • Bu kodu kullanmak için bir küre prefabrik oluşturun ve bunu Unity denetçisindeki tohumPrefab alanına atayın. Diyagramın çekirdek sayısını ve boyutunu kontrol etmek için numSeeds ve diyagramSize değişkenlerini ayarlayın.

Unity'deki Voronoi diyagramı.

Bu örnekte, VoronoiDiagram betiği, belirtilen diyagram boyutuna rastgele çekirdek noktaları yerleştirerek bir Voronoi diyagramı oluşturur. 'GenerateVoronoiDiagram()' yöntemi, Voronoi hücrelerini tohum noktalarına göre hesaplar ve 'VisualizeVoronoiDiagram()' yöntemi, Voronoi hücrelerinin her noktasında bir küre GameObject örneği oluşturarak diyagramı görselleştirir.

Not: Bu örnek, Voronoi diyagramının temel bir görselleştirmesini sağlar, ancak hücre noktalarını çizgilerle birleştirmek veya arazi oluşturma veya oyun amaçları için her hücreye farklı nitelikler atamak gibi ek özellikler ekleyerek bunu daha da genişletmek mümkündür.

Genel olarak, Voronoi diyagramları Unity'de prosedürel içerik oluşturmak, alanı bölümlere ayırmak ve ilginç ve çeşitli ortamlar yaratmak için çok yönlü ve güçlü bir araç sunar.

Prosedürel Nesne Yerleştirme

Unity'deki prosedürel nesne yerleştirme, nesneleri manuel olarak konumlandırmak yerine, bir sahneye algoritmik olarak oluşturmayı ve yerleştirmeyi içerir. Ortamları ağaçlar, kayalar, binalar veya diğer nesnelerle doğal ve dinamik bir şekilde doldurmak gibi çeşitli amaçlar için kullanılan güçlü bir tekniktir.

Unity:'de prosedürel nesne yerleşimine bir örnek:

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;
    }
}
  • Bu betiği kullanmak için Unity sahnesinde boş bir GameObject oluşturun ve buna attach "ObjectPlacement" betiğini ekleyin. Nesne prefab'ını atayın ve denetçideki 'numObjects' ve 'spawnArea' parametrelerini gereksinimlere uyacak şekilde ayarlayın. Sahneyi çalıştırırken nesneler prosedüre uygun olarak tanımlanan ortaya çıkma alanına yerleştirilecektir.

Unity'de prosedürel nesne yerleştirme.

Bu örnekte 'ObjectPlacement' betiği, nesnelerin sahneye prosedürel olarak yerleştirilmesinden sorumludur. 'objectPrefab' alanı yerleştirilecek nesnenin prefabrik yapısıyla atanmalıdır. 'numObjects' değişkeni yerleştirilecek nesne sayısını belirler, 'spawnArea' değişkeni ise nesnelerin rastgele konumlandırılacağı alanı tanımlar.

'PlaceObjects()' yöntemi, istenen sayıda nesne arasında döngü yapar ve tanımlanan ortaya çıkma alanı içinde rastgele ortaya çıkma konumları oluşturur. Daha sonra nesne prefabrikasyonunu her rastgele konumda rastgele bir dönüşle başlatır.

Not: Projenin özel gereksinimlerine bağlı olarak ızgara tabanlı yerleştirme, yoğunluk tabanlı yerleştirme veya kurallara dayalı yerleştirme gibi çeşitli yerleştirme algoritmalarını birleştirerek bu kodu daha da geliştirmek mümkündür.

Çözüm

Unity'deki prosedür oluşturma teknikleri, dinamik ve sürükleyici deneyimler yaratmak için güçlü araçlar sağlar. Perlin gürültüsü veya fraktal algoritmaları kullanarak araziler oluşturmak, Voronoi diyagramlarıyla çeşitli ortamlar oluşturmak, hücresel otomatlarla karmaşık davranışları simüle etmek veya sahneleri prosedürel olarak yerleştirilmiş nesnelerle doldurmak olsun, bu teknikler içerik üretimi için esneklik, verimlilik ve sonsuz olanaklar sunar. Geliştiriciler, bu algoritmalardan yararlanarak ve bunları Unity projelerine entegre ederek gerçekçi arazi oluşturma, gerçeğe yakın simülasyonlar, görsel olarak çekici ortamlar ve ilgi çekici oyun mekaniği elde edebilirler. Prosedürel oluşturma yalnızca zamandan ve emekten tasarruf sağlamakla kalmaz, aynı zamanda oyuncuları büyüleyen ve sanal dünyalara hayat veren benzersiz ve sürekli değişen deneyimlerin yaratılmasına da olanak tanır.

Önerilen Makaleler
Unity Oyun Geliştirmede Hikaye Anlatımının Önemi
Birlik İçin Sahip Olmanız Gereken Genel Amaçlı Varlıklar
Unity'de Arazideki Ağaçlar Nasıl Boyanır?
Nesneye Dayalı Programlama (OOP) Kavramlarını Unity'de Uygulamak
Unity'nin Dönüşüm Bileşeninde Uzmanlaşmak
Unity Komut Dosyası API'si ve Unity Pro
Unity Cinemachine ve Zaman Çizelgesi Eğitimi