Önce Unity hub kuruldu. Ardından unity hub içinden unity 2021.3.22f kuruldu (hoca ile uyumlu olması için). Unity hub üzerinden kurulamadı. Unity hub ile archive açılıp manuel kuruldu.
Unity Hub > New Project
İlk proje için template alanından 3D Core seçildi ve proje adı ve proje yeri gösterildi. "Create Project" denilerek ilk proje yaratıldı.
Projemizi açtıktan sonra edit>preferences>external tools segmesi içinde external script editor: microsoft visual studio seçilir.
Kısayol: Duplicate: Ctrl + D
Kısayol: Focus: F + Left Click (Hierarchy)
Ekran Kısayolları
Yaptığımız bir tasarımı alttaki asset menüsünden prefabs (hazır kalıp) olarak ekleyebiliriz. Bunun için asset>new>folder (dosya adı: prefabs) ile dosya oluşturup bu dosyayı açıp hazır şeklimizi içine sürükleyip bıraktık.
Sesler için "Sounds", kaplamalar için "Textures", kodlar için "Scripts" vs klasörler Assets altına açılır.
Right Click basılı iken "WASD" ile ekranda gezinilebilir. "Q" ve "E" ile de yukarı - aşağı hareket edilebilir.
Console: hataların ve bizim istediğimiz durumların sonucunun bildirildiği kısım.
File>Build Settings altından uygulama build edilebilir.
Scene ekranında üstteki menüyü inceliyoruz.
Birden fazla objeyi seçtiğimiz bir senaryoda "center" seçili ise grubun ağırlık merkezine göre işlem yapılır ve bütün grup tek nesne gibi davranır. "pivot" seçili ise her nesne kendi içinde hareket eder.
ikici sembolde "local" seçili ise eksenler objeye göre verilir. "global" seçili ise eksenler sahneye göre verilir.
Üçüncü sembol rehber grid yapısını ayarlar
ekranın sağındakiler görünümler ile ilgi.
Game sekmesi uygulamanın nasıl göründüğü konusunda bize yardımcı olur.
İlk segme "Game/Simılator" kısmı. Simulator mobil cihazlarda sahnenin nasıl görüneceği konusunda bilgi verir. "Simulator" kısmında "safe area" çeltikli telefonlarda hangi alanın aktif kullanılabileceğini gösterir.
"window" > "asset store" > "search online"
Gelen sayfadan istenilen asset seçilir ve alınır. Sonra "Open in unity" ile unity paket yöneticisinde açılır. İndirilir ve import edilir. Gelen paket "Asset" içinde görüntülenir.
Unity içinde C# .NET programlama dili kullanılır
Projemizin içinde asset kısmında sağ click > "create" > "folder" ile adı "Scripts" olan bir klasör oluşturulur. Klasörün içinde sağ click > "create" "c script" ile adı "HelloWorld" olan dosya oluşturulur.
Visual Studio aracılığı ile açılan kod:
using System.Collections;
using System.Collections.Generic;
using UnityEngine; // Bu kısım kütüphaneleri import ettiğimiz kısım.
public class HelloWorld : MonoBehaviour // Dosya ismi ile class ismi aynı olmalı. "MonoBehaivor" klasından kalıtım alınmış.
{
// kod ilk çağırıldığında ilk çelışan fonksiyon
void Start()
{
print("selamlar");
}
// Her frame değiştiğinde çalışır.
void Update()
{
}
}
// c# içinde her kod ";" ile bitirilir. Kod yukarıdan aşağı okunur.
Boş proje alanına bir küp bir de küre oluşturduk.
Daha önce yazdığımız scripti eklemek için eklemek istediğimiz nesne seçiliyken kod asset alanından sürükle bırak ile "Inspector" ekranının en altına bırakılabilir. Veya "Inspector" ekranında "Add component" tıklanarak eklenebilir.
Scripti ayırmak için "Inspector" ekranında bulup en sonundaki üç noktadan "remove component" tıklanır.
Script bir nesneyi değil de ortamı ilgilendiriyorsa "Hierarchy" ekranında "Create empty" seçilir ve script ona eklenir.
Scriptimizin son hali
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloWorld : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
print("start blogunda calisti"); // start aninda "start blogunda calisti" ibaresi konsola yazdirilir
}
// Update is called once per frame
void Update()
{
print("Update bloğunda çalıştı"); // her framede "Update bloğunda çalıştı" ibaresi konsola yazdırılır.
}
}
Değişkenler notları
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloWorld : MonoBehaviour
{
//int: tam sayı
int canSayisi = 4;
int gold = -2;
//float: virgüllü sayılar
float ates = 38.9f;
//string
string oyuncuAdi = "Murat";
string tc_no = "216343216215";
//bool: boolean
bool canli_mi = true;//false
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
Örnek
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloWorld : MonoBehaviour
{
// Burada oluşturulan değişkenler void Start() ve void Update() altında kullanılabilir.
int can;
int ilave_can;
int son_can;
float sicaklik;
bool oyun_bitti_mi = false;
void Start()
// Start is called before the first frame
{// Burada oluşturulan değişkenler sadece void Start() altında kullanılabilir
can = 10;
ilave_can = 3;
sicaklik = 28.2f;
}
// Update is called once per frame
void Update()
{
son_can = can + ilave_can;
print(son_can);
print(sicaklik);
print(oyun_bitti_mi);
}
}
public değişkenlere dışarıdan erişilebilir. private değişkenlere dışarıdan erişilemez.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloWorld : MonoBehaviour
{
// Başında public olan değişkenler unityde "Inspector" alanında görünür ve play sırasında değiştirilebilir.
public int can;
public int ilave_can;
// başında private olanlara dışarıdan ulaşılamaz. Varsayılan olarak tüm değerler private olarak gelir.
private int son_can;
float sicaklik;
bool oyun_bitti_mi = false;
// Start is called before the first frame
void Start()
{
can = 10;
ilave_can = 3;
sicaklik = 28.2f;
}
// Update is called once per frame
void Update()
{
son_can = can + ilave_can;
print(son_can);
print(sicaklik);
print(oyun_bitti_mi);
}
}
if (<şart>) { <şart true ise çalışacak fonksiyon>}
Örnek
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloWorld : MonoBehaviour
{
public int can;
// Start is called before the first frame
void Start()
{
can = 5;
}
// Update is called once per frame
void Update()
{
if (can > 0)
{
print("Oyun devam ediiyor");
}
else
{
print("Oyun bitti");
}
}
}
Birden fazla koşul aynı anda yürütülebilir.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloWorld : MonoBehaviour
{
public int can;
// Start is called before the first frame
void Start()
{
can = 5;
}
// Update is called once per frame
void Update()
{
//Durum 1
if (can > 3)
{
print("Oyun devam ediiyor");
}
else if (can == 3)
{
print("3 cana özel 2 puan");
}
else if (can > 0)
{
print("0-3 arası özel durum");
}
else
{
print("Oyun bitti");
}
//Durum 2
if(can == 5)
{
print("--Can 5 e özel durum--");
}
else
{
print("--Özel Durum Yok--");
}
}
}
Örnek
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloWorld : MonoBehaviour
{
public int sayi1, sayi2, toplam, fark, carpim, kalan; //aynı karakterdeki birden fazla değişken bu şekilde tanımlanabilir.
public float bolum; // bölüm sonucu tam sayı bile olsa değer float olarak tanımlanır.
// Start is called before the first frame
void Start()
{
sayi1 = 10;
sayi2 = 20;
}
// Update is called once per frame
void Update()
{
toplam = sayi1 + sayi2;
fark = sayi1 - sayi2;
carpim = sayi1 * sayi2;
bolum = (float)sayi1 / sayi2;
kalan = sayi1 % sayi2;
print(sayi1 + " ile " + sayi2 + "'nin toplamı: " + toplam);
print(sayi1 + " ile " + sayi2 + "'nin farkı: " + fark);
print(sayi1 + " ile " + sayi2 + "'nin çarpımı: " + carpim);
print(sayi1 + " ile " + sayi2 + "'nin bölümü: " + bolum + " kalanı ise: " + kalan);
}
}
"&&" = ve, "||" = veya
"!" işareti bir önermenin sonucu true ise false'a false ise true'ya çevirir.
Örnek:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloWorld : MonoBehaviour
{
public int sayi1, sayi2;
public string isim;
public bool evliMi;
// Start is called before the first frame
void Start()
{
sayi1 = 10;
sayi2 = 20;
isim = "Murat";
evliMi = false;
}
// Update is called once per frame
void Update()
{
// ve logic mantık örneği
if(sayi1 < sayi2 && !(isim == "Murat")) // sim önermesinden çıkan değer "!" ile tersine çevirildi
{
print("sayi1 sayi2 den küçük ve isim Murat değil");
}
else
{
print("iki önermeden biri yanlış veya ikisi de yanlış");
}
// veya logic mantık örneği
if (sayi1 < sayi2 || isim != "Murat") // isim önermesi eşit değil şeklinde sorgulandı
{
print("___sayi1 sayi2 den küçük veya isim Murat değil");
}
else
{
print("___iki önermeden ikisi de yanlış");
}
if(!evliMi)
{
print("Bekar");
}
else
{
print("Evli");
}
}
}
Örnek:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloWorld : MonoBehaviour
{
public int sayi1, sayi2;
// Start is called before the first frame
void Start()
{
sayi1 = 10;
sayi2 = 20;
}
// Update is called once per frame
void Update()
{
if (sayi1 <= sayi2)
{
print("Ok");
}
}
}
Örnekler:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloWorld : MonoBehaviour
{
public int sayi1, sayi2;
// Start is called before the first frame
void Start()
{
sayi1 = 10;
print(sayi1++); // önce sayi1 yaz sonra arttır (10)
print(++sayi1); // önce arttır sonra yazdır. (12)
print(sayi1--); // önce yaz sonra azalt (12)
print(--sayi1); // önce azalt sonra yaz (10)
sayi1 = sayi1 + 1; // sayi1++ veya sayi1+=1
sayi1 = sayi1 + 3;// sayi1+=3
print(sayi1); //(14)
sayi1 *= 2; //sayi1 = sayi1*2
print(sayi1); //(28)
sayi1 /= 4; //Bölmede int veri tipinde küsürleri atar.
print(sayi1); //(7)
sayi1 %= 2;
print(sayi1); //(1)
}
// Update is called once per frame
void Update()
{
}
}
Birden fazla koşulun sınandığı durumlarda if - else yerine kullanılabilir.
Örnek:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloWorld : MonoBehaviour
{
public int sayi;
// Start is called before the first frame
void Start()
{
sayi = 7;
}
// Update is called once per frame
void Update()
{
int sonuc = sayi % 5;
switch(sonuc)
{
case 0:
print("kalan 0");
break;
case 1:
print("kalan 1");
break;
case 2:
print("kalan 2");
break;
default: // üstteki durumlar dışında kalan her durum için
print("default durum");
break;
}
}
}
while (<şart>) {<şart sürdüğü sürece tekrarlanacak fonksiyon>}
Örnek:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloWorld : MonoBehaviour
{
public int sayi;
// Start is called before the first frame
void Start()
{
sayi = 10;
while (sayi > 0)
{
print("sayi :" + sayi); // 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
sayi--;
}
print("sayının son değeri :" + sayi); // 0
}
// Update is called once per frame
void Update()
{
}
}
for genelde belirli bir sayıda aynı işlemin tekrarlanması için kullanılır.
Örnek:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloWorld : MonoBehaviour
{
// Start is called before the first frame
void Start()
{
for(int i = 10; i>0; i--) //i'ye atanan 10 değeri her turda 1 azaltılır. i>0 şartına uyulduğu sürece işlem tekrarlanır.
{
print("i değeri : " + i); // 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
}
}
// Update is called once per frame
void Update()
{
}
}
void <fonksiyonAdi> (<alınan parametre>) {<uygulanacak fonksiyon>}
Fonsiyon aşağı da yazılsa yukarıda çağırılabilir.
Örnek 1:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloWorld : MonoBehaviour
{
// Start is called before the first frame
void Start()
{
benimFonksiyon(100, 7); // bizim tanımladığımız fonksiyonun kullanımı
}
// Update is called once per frame
void Update()
{
}
void benimFonksiyon(int a, int b) // bizim tanımladığımız fonksiyon
{
print("Yazı 1");
print("Görev 1");
for(int i = a; i>0; i--)
{
print(i + " x " + b + " = " + i*b);
}
}
}
Örnek 2
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloWorld : MonoBehaviour
{
// Start is called before the first frame
void Start()
{
selamla("Ayla", true); // bizim tanımladığımız fonksiyonun kullanımı (Merhaba Ayla Hanım)
selamla("Murat", false); // (Merhaba Murat Bey)
selamlama();
}
// Update is called once per frame
void Update()
{
}
void selamlama()
{
print("Merhaba");
}
void selamla(string isim, bool cinsiyet)
{
if(cinsiyet)
{
print("Merhaba " + isim + " Hanım");
}
else
{
print("Merhaba " + isim + " Bey");
}
}
}
Sonucu bir değişkene atanan fonksiyon örneği:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloWorld : MonoBehaviour
{
// Start is called before the first frame
void Start()
{
int toplam1 = Topla(22, 33); // fonksiyondan alınan değeri değişkene atadık
string selamMesaji = Selamla("Ayla");
int toplam2 = Topla(44, toplam1);
print(selamMesaji + "toplam sonuç: " + toplam2);
}
// Update is called once per frame
void Update()
{
}
int Topla(int sayi1, int sayi2)
{
int sonuc = sayi1 + sayi2;
return sonuc;
}
int Cikar(int sayi1, int sayi2)
{
return sayi1 - sayi2;
}
string Selamla(string name)
{
return "Merhaba " + name + " ";
}
}
Bu fonksiyonlar unity ile gelen "MonoBehavior" klasında tanımlıdır.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloWorld : MonoBehaviour
{
int sayi1;
private void Awake() // starttan önce yapılacak işlemler buraya eklenir.
{
}
private void Start() // Start is called before the first frame
{
}
private void Update() // her framede çalışır. fps(saniyedeki frame sayısı) değiştikçe yapılma sıklığı değişir.
{
}
private void FixedUpdate() // saniyede 60 kere çalışır.
{
}
private void LateUpdate() // her framede çalışır ama biraz gecikmeli çalışır.
{
}
}
Bu liste yapısı sabit kalacak listeler için kullanışlıdır. Ekleme çıkarma vs yapılacak dinamik listeler için aşağıdaki ArrayList yapısı daha uygundur.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloWorld : MonoBehaviour
{
float[] /*"[]" bu yapının liste olduğunu belirtir.*/ liste = {2f, 2.5f, 11f, 11.5f }; //c# yapısında liste tek tip elemanla kurgulanır.
private void Start() // Start is called before the first frame
{
liste[0] = 22.4f; /* listenin ilk elemanı olan index 0 elemanı güncelledik */
print(liste[0]); // ilk elemanı yazdırır (22.4)
string listeString = "";
foreach(float eleman /* "eleman" bizim liste elemanlarına atadığımız isim */ in liste /* "liste" daha önce oluşturduğumuz array yapımız */)
{
listeString += " " + eleman; // liste elemanları string olarak düzenlendi
}
print(listeString); // (22.4 2.5 11 11.5)
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloWorld : MonoBehaviour
{
ArrayList benimListem = new ArrayList(); // benimListem adında boş bir ArrayList oluşturduk.
private void Start() // Start is called before the first frame
{
benimListem.Add(22); // Add yöntemi yeni bir elemanı listenin sonuna ekler
benimListem.Add(33);
benimListem.Add(900);
foreach(int eleman in benimListem)
{
print(eleman);
}
}
}
Unity içinde yerleştirdiğimiz her nesne bir component. Her nesneyi görünür kılan veya davranışını düzenleyen özellikler de onun companentleri.
Mesh: kaplama, yani nasıl göründüğü, Box Collider: Çarpışma durumunda nasıl davrandığı vs.
Bu kısımlar inspector ekranında görünüyor
Nesneye katılık eklemek için "Inspector" ekranında "Add Component" > "Rigitbody" eklenir
Rigitbody nesneye yer çekimi de kazandırır. Rigitbody içinde kütle (mass), sürtünme (drag), açısal sürtünme (angular drag), yerçekimi kullan (use gravity), konumunda sabit dursun mu? (is kinematic) vs seçenekler mevcut.
Rigitbody dahil olduğu nesneye fizik özellikleri tanımlar.
Box Collider nesnenin çarpışma sınırlarını verir. Şekilden bağımsız olarak büyütülebilir. İçindeki "is triger" çarpışma anında bir tetikleme başlatmak için kullanılır. Mevcut collider sonundaki üç nokta menüden kaldırılıp başka bir şeklin colliderı "Add Component" kısmından eklenebilir.
İki nesnenin birbirine çarpabilmesi için ikisinde de collider olmak zorunda.
"GameObject" > "Light" içinde seçenekler mevcut.
Point light: noktasal ışık.
Spot light: spot ışık.
Directional light: güneş ışıği gibi.
"range" ışık menzilini, "intensity" ise ışığın gücünü verir
"Inspector" ekranında "clear flags": arka plan görüntüsünü, "background" arka plan rengini verir. "Field of view" ile kamera açısı ayarlanır. "Projection" kısmında ise perspektif ile izometrik arasında seçim yapılır.
Scene ekranındaki görüntüyü kameraya atamak için kamerayı sağ tıkladık ve "Align with view"i seçtik.
Script ile nesnelerin özelliklerini yönetebiliriz.
Örnek olarak "Create Emty" ile adı Script olan bir nesne oluşturduk. Script alanına da adı Intensity olan bir c# scripti oluşturduk. Scriptin içine:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Intensity : MonoBehaviour
{
// Start is called before the first frame update
public GameObject kup; // kup adında bir GameObject değişkeni public atadık.
void Start()
{
print(kup); // içeriğini başlangıçta yazdırdık.
}
// Update is called once per frame
void Update()
{
}
}
Scriptin çalışması için bir nesneye component olarak atanması gerekiyordu. Script adıyla oluşturduğumuz objeye ekledik. "Inspector" ekranında açılan menüden kup seçeneğini bizim daha önce oluşturduğumuz "kup" adındaki game obje ile eşleştirdik. Konsolda "Kup (UnityEngine.GameObject) UnityEngine.MonoBehaviour:print (object)" çıktısını aldık.
Kup komponente Light özelliği ekledik. Kup'un light komponentine erişip intensity değerini değiştirmek için
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Intensity : MonoBehaviour
{
// Start is called before the first frame update
public GameObject kup;
void Start()
{
kup.GetComponent<Light>().intensity = 5.0f; // kup adlı değişkene atanan komponentin "Light" komponentine eriş ve intensity değerini 5 yap
}
// Update is called once per frame
void Update()
{
}
}
Degisken.cs adında bir script oluşturup komponent olarak Script nesnesine ekledik. İçine bir public değişken ve bir de public fonksiyon ekledik.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Degisken : MonoBehaviour
{
// Start is called before the first frame update
public int intensityDeger;
void Start()
{
intensityDeger = 10;
}
// Update is called once per frame
void Update()
{
}
public void DegerGoster()
{
print("değer" + intensityDeger);
}
}
Aynı nesne altındaki daha önce oluşturduğumuz Intensity.cs dosyasında Degisken.cs içindeki değişkeni ve fonksiyonu kullandık.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Intensity : MonoBehaviour
{
// Start is called before the first frame update
public GameObject kup;
void Start()
{
}
// Update is called once per frame
void Update()
{
kup.GetComponent<Light>().intensity = GetComponent<Degisken>().intensityDeger;
/* kup adlı değişkene atanan komponentin "Light" komponentine eriş ve intensity değerini Degisken komponentindeki "intensityDeger" değişkeninin değeri yap
* Aynı komponentin içindeki değişkeni almak için başında hiçbirşey olmadan "GetComponent<>()" fonksiyonu kullanılır. */
GetComponent<Degisken>().DegerGoster(); // aynı nesnedeki başka bir scriptteki fonksiyonu çalıştırıyoruz.
}
}
GetComponent<KomponentAdi>() kullanmak yerine KomponentAdi bir değişken türü olarak tanımlı olduğundan, buna bağlı bir değişken atanıp işlemler bunun üzerinden yapılabilir. Intensity.cs :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Intensity : MonoBehaviour
{
// Start is called before the first frame
public Light light; // Light değişken tipinde public ve adı "light" olan bir değişken tanımlandı.
void Start()
{
}
// Update is called once per frame
void Update()
{
light.intensity = 7; // değişkene 7 değeri verildi.
}
}
Değişkenin bağlı olduğu nesnenin "Inspector" ekranında light değişkeni nesnelerdeki bir Light kompanentine bağlandı. Bu şekilde run edildiğinde bağlanan Light komponentinin intensity değeri koddaki değeri aldı.
bounds ile nesnenin collider sınır değerlerini bulabiliriz.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BoxCol : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
print(GetComponent<BoxCollider>().bounds.max); // tüm eksenlerde cisim sınırlarının en yüksek değerleri verir.
print(GetComponent<BoxCollider>().bounds.min); // tüm eksenlerde cisim sınırlarının en düşük değerleri verir.
print(GetComponent<BoxCollider>().bounds.max.y); // y eksenine göre cismin en yüksek noktasını verir.
}
// Update is called once per frame
void Update()
{
}
}
Temel matematikteki vektörlerin toplanabilme özelliğini anlatıyor. Bunun için Vektorler.cs oluşturulup bir nesneye atandı.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Vektorler : MonoBehaviour
{
Vector2 vec, vec2, son;
// Start is called before the first frame update
void Start()
{
/*
vec.x = 1.0f;
vec.y = 1.0f;
print(vec);
*/
vec = new Vector2(5, 3);
vec2 = new Vector2(5, 7);
son = 2 * vec + vec2;
print(son);
}
// Update is called once per frame
void Update()
{
}
}
Küpe Rigitbody ekledik.
Adı RigitB.cs olan bir dosya oluşturduk. İçine
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RigitB : MonoBehaviour
{
public Rigidbody rb; // Rigitbody değerini eşlemek için değişken public verilir.
public Vector3 pozisyon; // pozisyon için değişken
public Vector3 hiz; // hız için değişken
void Start()
{
pozisyon = new Vector3(0, 4, 5); // başlangıç için varsayılan pozisyon bilgisi
hiz = new Vector3(1, 1, 1); // başlangıçta hız için varsayılan değer.
}
void Update()
{
// rb.MovePosition(pozisyon); // İnspector ekranında pozisyon değeri değiştirildiğinde bu veriyi işleyecek fonksiyon
rb.velocity = hiz * 5; // İnspector ekranında hiz değeri değiştirildiğinde bu veriyi işleyecek fonksiyon
// Hem hız hem de pozisyon bilgisi sürekli güncellendiğinde nesne hareket edemiyor.
}
}
yazdık ve scripti küpe ekledik. Script alanından Rb değişkeni olarak da küpün Rigitbody özelliğini seçtik
Bu durumda verdiğimiz hız değeri doğrultusunda nesne hareket etti.
Bir adet küp oluşturuldu. Bir adet ismi "TransformKonusu.cs" olan script oluşturulup küpe dahil edildi. Script içine:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TransfomKonusu : MonoBehaviour
{
public Vector3 yon; // nesnenin yön bilgisi için değişken
public Vector3 don; // nesnenin rotate bilgisi için değişken
void Update()
{
// GetComponent<Transform>().Translate(new Vector3(0,1,0)); // nesneyi her update anında 1 birim y ekseninde kareket ettirir.
/*
GetComponent<Transform>().Translate(yon); // nesneyi her update anında yon değişkeni kadar hareket ettirir.
GetComponent<Transform>().Rotate(don); // nesneyi her update anında don değişkeni kadar döndürür.
*/
//yukarıdaki ifadede GetComponent<Transform>() yerine transform yazılabilir.
transform.Rotate(don);
transform.Translate(yon);
}
}
Bir küp oluşturup "Add Component" > "Trail Renderer" ekledik.
Trail renderer nesne hareket ettiğinde ardında bıraktığı kuyruklu yıldız benzeri ize denir.
İzin nasıl olacağını Trail Renderer altındaki material sekmesinden ayarlarız.
Diğer ayarlar deneme yanılma ile yapılabilir.
Dış kaplama
Bir adet "Create Empty" yarattık.
Buna "Mesh Filter" ekleyerek bir şekil tanımladık.
"Mesh Renderer" ekleyerek material kısmından kaplama seçtik.
Assets içine adı "Materials" olan bir klasör oluşturduk. Bunun içine Create > Material ile bir material oluşturduk. Oluşturduğumuz material için renk vs ayarlayıp bunu yeni oluşturduğumuz "GameObject" içinde kullandık.
İnternetten bulduğumuz kaplamayı kullanmak için, dosyayı Assets>Textures içine image (.jpeg) olarak kaydettik. Sonra image dosyasını tıklayıp "Inspector" alanında "Texture Type": "Sprite (2D and IU)" seçildi. Material alanında yeni bir materyal yarattık. Materyalin albedo alanına (alanın başındaki kareye) bu image dosyasını sürükleyip bıraktık.
Günes ve Dunya adında iki küre oluşturup internetten aldığımız kaplamalarla kapladık. Bu nesnelere daha önce yazdığımız "TransformKonusu.cs" eklendi. Buradaki Donus özelliğini kullandık.
Dünyanın güneş etrafında dönüşü için merkezi güneş olan bir boş "GameObject" oluşturuldu. Dünya onun altına bir alt nesne olarak eklendi. Buna da "TransformKonusu.cs" eklendi.
Control.cs Scripts alanında oluşturuldu ve çalışması için bir objeye atandı.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Control : MonoBehaviour
{
void Update()
{
print(Input.GetMouseButton(0)); //0: sol buton, 1: sağ buton, 2: orta buton. Sağ buton tıklıyken çıktı olarak sürekli true verir. Elini çekince false verir.
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Control : MonoBehaviour
{
void Update()
{
//0: sol buton, 1: sağ buton, 2: orta buton.
print(Input.GetMouseButtonDown(0)); //Her tıklamada tıklanma anında 1 kere true verir. Diğer anlarda false verir
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Control : MonoBehaviour
{
void Update()
{
//0: sol buton, 1: sağ buton, 2: orta buton.
print(Input.GetMouseButtonUp(0)); //Her tıklamada tıklanma anından sonra elini çeker çekmez 1 kere true verir. Diğer anlarda false verir
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Control : MonoBehaviour
{
void Update()
{
//Keycode. dan sonrası klavyedeki tuşu verir.
print(Input.GetKey(KeyCode.Space)); //space basılıyken true, basılı değilken false döner.
}
}
Klavyede yaptığımız işlemi fiziksel olarak da görmek için bir küp yarattık ve ona light kompanenti ekledik. Yine kompanent olarak içeriği aşağıda yazan scripti ekledik.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Control : MonoBehaviour
{
void Update()
{
//Keycode. dan sonrası klavyedeki tuşu verir.
if(Input.GetKeyDown(KeyCode.Space)) // Space basılı iken true verir.
{
GetComponent<Light>().intensity = 2; // Space basılı iken light intensity 2 olur
}
else
{
GetComponent<Light>().intensity = 1; // space basılı değilken light intensity 1 e döner.
}
}
}
space tuşuna basılıyken light intensity 2, basılı değilken 1 olur.
Yön tuşlarını ve WASD kullanımını öğreneceğiz.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Control : MonoBehaviour
{
void Update()
{
print(Input.GetAxis("Horizontal")); //Sağ ve sol yön tuşu ile A ve D tuşuna basma durumunda: Sağ ve A için 0 dan başlayarak -1 e kadar değer döndürür. Sol ve D için 1 e kadar değer döndürür. Basılı tuttukça değerler 0 dan uzaklaşır. Bırakınca yine 0 olur.
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Control : MonoBehaviour
{
private float yatay;
private float dikey;
void Update()
{
yatay = Input.GetAxis("Horizontal");
dikey = Input.GetAxis("Vertical");
if(yatay > 0)
{
print("D veya sol ok tusuna bastiniz");
}
else if(yatay < 0)
{
print("A ve sag ok tusuna bastiniz");
}
else
{
print("Tusa basmadiniz");
}
if (dikey > 0)
{
print("W veya yukarı ok tusuna bastiniz");
}
else if (dikey < 0)
{
print("s veya aşağı ok tusuna bastiniz");
}
else
{
print("Tusa basmadiniz");
}
}
}
File > Build Settigns... > Player Settigns... > Axes altında GetAxis() ile ulaşılabilen tüm işlemler ve tuşlar mevcut. Buradan özelleştirilebilirler.
GetAxisRaw(), GetAxis() ten farklı olarak küsürlü değer almaz. Sadece üç değer verir. -1, 0 ve 1
Nesne seçiliyken Inspector ekranında üst tarafta tag seçeneği mevcut. Bu kısımda adı "ozel" olan kendi tagınızı oluşturduk. İçinde light olan iki eşit küp oluşturup birine bu tagı atadık.
FindWithTag.cs dosyasını oluşturduk ve içine:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FindWithTag : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
GameObject.FindGameObjectWithTag("ozel").GetComponent<Light>().intensity = 2; //ozel tagına sahip tek bir gameObjecti bul ve light intensity değerini 2 yap.
}
// Update is called once per frame
void Update()
{
}
}
scriptin çalışması için onu herhangi bir gameobject üzerine ekledik. Play dediğimizde "ozel" tagına sahip küp daha parlak olur.
Aynı tagı başka bir küpe daha atadık ve birden fazla küpe işlem yapmak istiyoruz.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FindWithTag : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
GameObject.FindGameObjectsWithTag("ozel")[1].SetActive(false); //ozel tagına sahip nesnelerden indeksi 1 olanın aktif olma durumunu false yap.
GameObject.FindGameObjectsWithTag("ozel")[0].GetComponent<Light>().intensity = 0; // ozel tagına sahip nesnelerden indeksi 0 olanın light intensity değerini 0 yap.
}
// Update is called once per frame
void Update()
{
}
}
Objenin adı ile de objeye ulaşabiliriz.
Adı "Kapsul" olan bir obje yarattık.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FindWithTag : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
GameObject.Find("Kapsul").SetActive(false); // adı "Kapsul" olanın setActive değerini false yap.
}
}
Birden fazla "Kapsul" adında nesne varsa ilk bulduğuna kodu uygular.
Inspector ekranında tag'ın yanıdaki layer kısmından nesnenin hangi layerın parçası olduğu ayarlanabilir.
Bu kısımdan yeni layer adı da verilebilir.
İleriki derslerde detaylı uygulanacakmış.
Zamana bağlı işlemler için kullanılır.
Adı "Timer" olan bir script oluşturduk.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Timer : MonoBehaviour
{
private float timer;
/*
void Update()
{
timer += 1 * Time.deltaTime;
//Time.deltaTime = 1/fps olduğundan update işlemi de her fps sırasında çalıştığından artışı sn ile eşittir.
print(timer);
}
*/
private void FixedUpdate()
{
timer += 1 * Time.fixedDeltaTime;
//Time.fixedDeltaTime = 1/60 olduğundan ve FixedUpdate işleminde de fps = 60 olduğundan artışı sn ile eşittir.
print(timer);
}
}
Kılıç sallama vs için kullanılıyormuş. Play durumunda game ekranında görünmeyen ama scene ekranında görünen bir birimlik bir çizgi oluşturmuş olduk. Bunun için bir küpe bağladığımız sciptimize
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DrawRay : MonoBehaviour
{
void Update()
{
//Debug.DrawRay(transform.position, Vector3.forward /* = new Vector3(0,0,1) Vector3.dan sonra backi forward vs hazır kısayollar kullanılabiliyor. */, Color.red);
Debug.DrawRay(transform.position, transform.forward /* üstteki ifade globalde yön veriyor. transform.forward ise lokalde yön veriyor. Bu nedenle cisim rotasyon yaptığında drawRay de rotasyona uğruyor. */, Color.red);
}
}
Detaylı kullanımını ileride görecekmişiz.
Bir cisme ateş ettiğimizde onun adını konsola yazdıracak bir demo yaptık. Bunun için ateş edecek küpü yarattık. z eksenine eklediğimiz daha küçük bir küp ile görsel bir ateş etme ucu tasarladık.
Her iki küpün box collider özelliğini kaldırdık. Bu sayede merkez küpün merkezinden çıkan lazer küplere temas etmemiş oldu.
Merkez küpe aşağıdaki kodun yazdığı scripti ekledik.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Laser : MonoBehaviour
{
RaycastHit hit;
void Update()
{
if (Physics.Raycast(transform.position, transform.forward, out hit, Mathf.Infinity)) //ilk parametre çıkış noktası. ikinci parametre yönü, üçüncüsü vurulan cismin bilgisini alan değişken, dördüncü de menzili.
{
print(hit.collider.gameObject); // çarptığı colliderin gameobject'inin adını yazdır
}
}
}
Küpün lokal z eksenini hangi cisme cevirirsek onun adını konsolda alıyoruz.
Çarpma durumlarını kullanmak için aşağıdaki örneği yaptık.
Bir küre oluşturup aşağıdaki script ile bağladık.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CheckSphare : MonoBehaviour
{
void Update()
{
if(Physics.CheckSphere(transform.position, 1f)) // 1. parametre pozisyonu. 2. parametre çapı
{
print("Çarpma Gerçekleşti");
}
}
}
Çarpma durumu için özel fonksiyon: OnCollisionEnter
Bir adet zemin ve bir adet küp yarattık. Her ikisinde de box collider özelliği mevcut.
Küpe rigitbody eklendi.
Aşağıdaki script küpe eklendi.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Carpma : MonoBehaviour
{
private void OnCollisionEnter(Collision col) // Çarpma durumuna özel fonksiyon. 2. parametre çarpılan cisimi verir.
{
print(col.gameObject.name); // Çarptığı cismin gameObject name parametresini yazdır.
}
}
play dediğimizde küpün çarptığı her nesnenin adı konsolda çarpma anında yazar.
Triger durumunda çalışan özel fonksiyonumuz: OnTriggerEnter
Bunun için daha önce script eklediğimiz küp ile zemin arasına isTrigger'ı seçili ve adı perde olan bir nesne oluşturduk.
Bu işlem için de nesnelerden birinde rigitbody olmak zorunda.
Scripti aşağıdaki gibi yazdık. Bu script perdede ise küpün, küpte ise perdenin adını verir.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Carpma : MonoBehaviour
{
private void OnTriggerEnter(Collider other) // ikinci parametrede içinden geçtiği objeyi
{
print(other.gameObject.name);
}
}
İçine spot ışık atadığımız bir küpte klavyeden ışığı aç-kapat yapmak için aşağıdaki scripti ekledik.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FlipFlop : MonoBehaviour
{
private bool aciksa;
void Start()
{
aciksa = true;
}
void Update()
{
if(Input.GetKeyDown(KeyCode.F))
{
GetComponent<Light>().enabled = aciksa;
aciksa = !aciksa;
}
}
}
Aynı işi yapan alternatif kod
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FlipFlop : MonoBehaviour
{
private bool aciksa;
void Start()
{
aciksa = true;
}
void Update()
{
if(Input.GetKeyDown(KeyCode.F))
{
if(aciksa)
{
GetComponent<Light>().enabled = false;
aciksa = false;
}
else
{
GetComponent<Light>().enabled = true;
aciksa = true
}
}
}
}
İlk kodu ben yazdım. İkincisi hocanın eğitimde kullandığı kod.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HareketEtme : MonoBehaviour
{
public float hiz = 1f;
void Update()
{
/*
* Aşağıdaki kodda nesne sadece x= (1, -1) ve z= (1, -1) aralığında hareket eder.
Vector3 playerInput = new Vector3(Input.GetAxis("Horizontal"), 4.34f, Input.GetAxis("Vertical"));
transform.position = playerInput;
*/
Vector3 playerInput = new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical"));
Vector3 konumDegisimi = hiz * Time.deltaTime * playerInput;
transform.position += konumDegisimi; // bu hali ile cisim bütün alanı gezebilir.
}
}
Bu scripti eklediğimiz nesneyi klavyeden yön tuşları ile veya WASD ile x ve z ekseninde yönetebiliriz.
"Main Camera" nesnesini bir kpe bağladık ve kameraya aşağıdaki scripti ekledik.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraController : MonoBehaviour
{
private float xRotation = 0.0f;
public float mouseSenivity = 100f;
public GameObject gamer;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
float mouseX = Input.GetAxis("Mouse X") * mouseSenivity * Time.deltaTime;
float mouseY = Input.GetAxis("Mouse Y") * mouseSenivity * Time.deltaTime;
xRotation += mouseY;
xRotation = Mathf.Clamp(xRotation, -90f, 90f);
transform.localRotation = Quaternion.Euler(-xRotation, 0, 0); // localRotation: parente göre rotasyon.
gamer.transform.Rotate(Vector3.up * mouseX); //mmouse x ekseninde hareket ettiğinde atanan cismi döndürür
}
}
Gamer değişkeni olarak da kamerayı atadığımız küpü seçtik.
Mouse yukarı aşağı hareket ettiğnde kamera da yukar ve aşağı bakar. Mouse sağa ve sola döndüğünde küpü de dönderir.
Daha önce hareket için girdiğimiz kod bizi global eksenlerde hareket ettiriyordu. Kamera kontrolleri ile beraber yaptığımız rotasyon hareketi nedeni ile bu kullanışsız hale geldi. Lokale göre dönüş sağlaması için script aşağıdaki gibi güncellendi
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HareketEtme : MonoBehaviour
{
public float hiz = 1f;
void Update()
{
/*
* Aşağıdaki kodda nesne sadece x= (1, -1) ve z= (1, -1) aralığında hareket eder.
Vector3 playerInput = new Vector3(Input.GetAxis("Horizontal"), 4.34f, Input.GetAxis("Vertical"));
transform.position = playerInput;
*/
// Aşağıdaki hali ile cisim bütün alanı gezebilir.
Vector3 playerInput = new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical"));
Vector3 konumDegisimi = hiz * Time.deltaTime * playerInput;
// transform.position += konumDegisimi; // bu hali ile global yönlerde gider
transform.Translate(konumDegisimi); // bu hali ile lokal yönlerde gider.
}
}
Daha önce yaptığımız kamera ve küp (adı player olarak değiştirildi) kompleksine ek olarak ateş etme fonksiyonu ekliyoruz. Ateş edebilmemiz için hedef olarak tagı "dusman" olan küpler oluşturduk.
Adı player olan küpe aşağıdaki script eklendi.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AtesEt : MonoBehaviour
{
RaycastHit hit;
void Update()
{
if(Input.GetMouseButtonDown(0))
{
if(Physics.Raycast(Camera.main.transform.position, transform.forward, out hit, Mathf.Infinity)) //Başlangıç noktası: Ana kamera, Yön: ileri doğru, vurma bilgisi için değişken: hit, menzil: sonsuz.
{
if (hit.collider.gameObject.tag == "dusman") // vurulanın tagi "dusman" ise
{
Destroy(hit.collider.gameObject); // vurulanı sil.
}
}
}
}
}
Ekrandaki mevcut objeleri her açılışta getirmesi için "file" > "defaults" > "save startup file"
mouse orta top tıklıyken kamera açısı değiştiriliyor. "shift" basılıyken ortamda gezebiliyoruz.
numped 1, 3 ve 7 xyz eksenlerinin birine göre eksendeki görüntüyü ayarlar.
Küp eklemek için üst menüden "Add" > "Mesh" > "Cube". Add kısayolu "Left Shift" + "A"
Unityden farklı olarak z ve y eksenleri yer değiştirmiş durumda. Blenderda x ve y eksenleri zemin verirken z ekseni yüksekliği veriyor.
Nesne seçiliyken "G" (grab) ye basıldığında nesneyi turar ve mouse hareketi ile taşır. Nesnenin tutulduğu halde "X", "Y" veya "Z" tuşuna basılırsa nesne sadece o eksende hareket eder. Taşıma sırasında bir eksendeki hareketi iptal edip sadece diğer iki eksende hareket etmesini istiyorsak nesnenin tutulduğu halde "Shift" + "eksen yönü(x,y veya z)" kombinasyonu kullanılır.
Nesne seçiliyken "R" (rotate) ye basıldığında nesneyi turar ve mouse hareketi ile döndürür. Nesnenin tutulduğu halde "X", "Y" veya "Z" tuşuna basılırsa nesne sadece o eksende döndürür. Dönderme sırasında bir eksendeki döndermeyi iptal edip sadece diğer iki eksende dönmesini istiyorsak nesnenin tutulduğu halde "Shift" + "eksen yönü(x,y veya z)" kombinasyonu kullanılır. Belirli bir eksende belirli bir açıda dönmesi için "R" > "Eksen (x, y veya z)" > "dönme açısı" kombinasyonu kullanılr.
Nesne seçiliyken "S" (Scale) ye basıldığında nesneyi turar ve mouse hareketi boyutlandırır. Nesnenin tutulduğu halde "X", "Y" veya "Z" tuşuna basılırsa nesne sadece o eksende boyutlandırışır. Boyutlandırma sırasında bir eksendeki boyutlandırmayı iptal edip sadece diğer iki eksende dönmesini etmesini istiyorsak nesnenin tutulduğu halde "Shift" + "eksen yönü(x,y veya z)" kombinasyonu kullanılır. Belirli bir eksende belirli bir miktarda boyutlanması için "R" > "Eksen (x, y veya z)" > "dönme açısı" kombinasyonu kullanılr.
Eksen seçimi için örneğin x e ilk bastığımızda globali, tekrar bastağımızda lokal ekseni alır. 3. basışımızda seçimi iptal eder.
Global-lokal seçimi üst menünün ortasından da seçilebilir.
Nesnenin global eksenlerini lokal ekseni haline getirmek için "CTRL + A" > "All transform" seçilir. Bu sırada konum bilgisi de sıfırlanır ve cismin yeni ağırlık merkezi "0, 0, 0" koordinatları olur. Ağırlık merkezini tekrardan cismin içine taşımak için üst menüden "Object" > "Set Origin" > "Origin to Center of Mass(surface/volume)" (surface: yüzey alanına göre, "volume: şeklin hacmine göre ağırlık merkezini ayarlar.")
"Left Shift" + "Space Bar" kombinasyonu kısa yol menüsünü açar.
Edit mode a geçmek için nesne seçiliyken üst menüde "object mode" seçili kısım "edit mode" a getirilir. Kısa yolu: "Tab" tuşu.
Edit mode seçtiğimiz yerin hemen sağında seçimimizin nokta mı, kenar mı yoksa yüzey mi olduğunu belirlediğimiz kısım var. Çoklu seçim için "shift"e basılı iken seçim yapıyoruz.
"Bevel Edges" ile kenarlar yumuşatılabilir. Bunu için kenarı seçtikten sonra sağ click menüden Bevel edges seçilir ve mouse tıklanmadan bir tarafa doğru sürüklenir. Mouse topu ile poligon miktarı ayarlanabilir. Bunun yerine sol bardan da seçim yapılabilir. Sol barda "Bevel" seçildiğinde üst menüde ayarları da görünür.
Edit mode açıkken yukarıdaki tüm kısa yolları aynı şekilde kullanabiliriz.
Edit mode üzerinde yapılan işlemler ağırlık merkezini değiştirmez. Edit sonrası ağırlık merkezini otomatik almak için üst menüden "Object" > "Set Origin" > "Origin to Center of Mass(surface/volume)" (surface: yüzey alanına göre, "volume: şeklin hacmine göre ağırlık merkezini ayarlar.")
Edit modda üst menüde snap aktif ise tüm hareketleri birer birim olacak şekilde yapar. Rotasyonraı da 45° olacak şekilde yapar.
Bir şekil eklediğimizde sol altta "add <nesne_adi>" şeklinde bir menü çıkar. Buradan pek çok ayar yapabiliriz.
Geometrik şekillerde sağ click menüden shade smooth/shade flat seçenekleri ile nesnenin görünümü değiştirilebilir.
Extrude Region: edit modda yüzey seçiliyken "E" tuşu ile veya sol menüden seçilerek kullanılır.Seçili yüzeyi yüzeye dik eksende yükseltir.
"E" den hemen sonra "S" ye basınca yükselecek yüzey ölçeklendirilebilir.
"X" tuşu silme menüsünü açar. Buradan yüzer, kenar, nokta vs silinebilir.
Yüzey silinmiş olsa de etrafındaki noktalar seçilip "E" ile yükseltilebilir.
"Alt" + "Z" yüzeylerin hepsini görünür kılar. Görünümle ilgili seçenekler ekranın sağ üstünde yer alır.
Edit modda yüzey seçip "Left Shift" + "S" dedik. Cursor selected ile cursor yüzeye taşındı. üst menüden "Object" > "Set Origin" > "Origin to 3D Cursor" ile ağırlık merkezi cursorun olduğu yere taşındı.
Yansıma ağırlık merkezine göre alınıyor.
Edit modda ağırlık merkezini taşımak istediğimiz yüzeyi, kenarı veya noktayı seçtik. "Shift" + "S" ile gelen menüden "Cursor to Selected" seçilerek cursor taşınır. Object modda "Object" > "Set Origin" > "Origin to 3D Cursor" seçilerek ağırlık merkezi taşınır.
En sağdaki alandan "modifiers" > "add modifiers" > "mirror" dan axis olarak aynalanacak eksen seçilir. Bu durumda ağırlık merkezine ve seçilen eksene göre objenin simetriği meydana gelir. Kaydettiğimizden emin olmak içim eksen seçtiğimiz menüdeki ters üçgenden açılan menüden apply olduğumuzdan emin olun
"N" kısa yolu transform menüsünü açar veya kapatır.
"Edit mode"da bir şeyi (kenar, yüzey, nokta) seçip "A" ya basınca tümünü seçer.
"Edit mode" içinde yüzey seçip "I" ya basınca aynı yüzeyin farklı boyutlusu yüzeyin içine mouse ile bpyutlandırılarak eklenir.
"Object mode" içinde "sağ click" menüde "shade auto smooth" bazı açıları yumuşatırken bazı açıları normal bırakır. Normal bırakılacak açıların ayarı sağ menüde "data">"normals" içinde yer alır.
"Edit mode" içinde seçili kısmı çoğaltmak için "Shift" + "D" (dublicate) kullanılır. Çoğalan kısım hala orijin aldığı objenin parçasıdır. Bunun hiyerarşide ayrıca seçilebilmesi için seçiliyken "P" ye basıp gelen menüden "selection" seçilir. "F2" ile yeniden adlandırılır. Orijinal parçadan ayrıldığı anda orijinal parçanın ağırlık merkezi nerede ise yeni parçanın ağırlık merkezi de oradadır.
Ekranın sağ üstündeki "show overlays" kısmının sağındaki ters üçgen ile açılan menüde "face orientation" tıklı iken mavi yüzeyler var olan, kırmızılar ise var olmayan yüzeyleri gösterir.
"Loop Cut": "edit mode"da "Ctrl" + "R" ile veya sol menüden tıklanarak çağırılır. Yüzeyi keser. Kesilmiş yüzeyler ayrı ayrı boyutlandırılabilir.
En üst menüden "UV Editing" seçildiğinde ekran ikiye bölünür ve solda seçili cismin 2D açılmış hali görünür. "Edit mode" içinde ne seçiliyse sağ ekranda da o görülür.
Tüm yüzeyleri seçip "Ctrl" + "E" ile açılan edge menüden "mark seam" seçilir. sonra "U" ile açılan "UV mapping" menüden "unwrap" seçildiğinde tüm yüzeyler kesilmiş olarak sağ ekranda belirir.
Birden fazla nesne seçilip "Ctrl" + "J" denilirse seçilen nesneler tek bir nesne olur.
Her bir nesne tek tek mark seam ve unwrap işlemi geçirdikten sonra hepsini "Ctrl" + "J" ile birleştirdik.
"Edit mode" da tüm çizgiler seçiliyken sağ ekrandan "new" > "generated Type: UV Grid" > "OK" yapılır.
En üst menüden "Shading" seçilir. En alttaki ekranda Öence "New" denilir. Ardından "Shift" + "A" ile girilen ekranda "Texture" > "Image Texture" seçilir.
"Image Texture - Color" alanı "Principed BSDF - Base Color" alanı ile eşleştirilir.
Nesnemiz kaplanmaya hazır.
Nesneyi unityde kullanılacak şekilde export etmek için: "File" -> "Export" -> "FBX" seçilir. Çıkan ekranda sağ menüden "mesh" seçilir. Animasyon olmadığından "bake animation" tıkı kaldırılır.
Nesne kaplamak için hoca "Adobe Substance 3D Painter" aadında paralı bir uygulamanın deneme sürümünü kurdu. 5.18 ve 5.19 bunu anlatıyor.
Materyali kaplayan 2 boyutlu resim.
textures.com: Güzel hazır textures dosyaları var.
İndirdiğimiz texture dosyasını projemizin "Assets" > "Textures" dosyasına ekliyoruz ve "Texture Type" özelliğini "Sprite 2D and UI" yapıyoruz. Sonra ilgili texture kullanılacağı materyalin "albedo" kısmına eklenir.
Material içindeki tiding ayarı kaplamanın yenilenme durumunu belirtir. 1'e 1 ise texture tüm materyali kaplar.
Kaplamanın sadece resim değil de bizim yaptığımız üç boyutlu modelle uyumlu ve fizik kurallarına uygun render edilmesine denir. Texture sadece "albeno"da değil diğer katmanlarda da farklı farklı uygulanır. İleri konularda işleyecekmişiz.
Material alanındaki map alanları texture için pürüz vs bilgileri taşır. "albedo" kısmına texture eklediğimiz gibi bunları da ekleyebiliriz.
"Hierarchy" alanında sağ click menü içinde "UI" komponentleri eklenebiliyor.
"UI" elemanı eklediğimizde otomatik olarak "Canvas" ve "Event System" da oluşur ve "UI" elemanı "Canvas" içine yerleşmiş olarak gelir.
Kamera seçiliyken "Ctrl" + "Shift" + "F" kamerayı bizim baktığımız açıya taşır.
Canvas tıklandığında sağdaki "Inspector" alanında "Render Mode" da canvas oyun ögelerine de eklenecekse "world space" seçilir. "Screen space - overlay" ise ögelerin ekrana sabitlenir.
Aynı bölgedeki "Canvas Scaler" de "Constan pixel size" seçili ise ekran boyutu değişse de pixel sabit kalacağından UI elemanların boyutu pixel cinsinden sabit kalacak ancak ekranda kapladığı yer değişecektir. "scale with screen size"ı seçersek UI elemanların boyutu ekrana orantılı olarak değişecektir. Bu seçenğe göre işlem yaparken test ekranımızın çözünürlüğünü referans olarak girersek ayarlamak daha kolay olacaktır.
Aynı bölgede "Graphic Raycaster" dokunmatik ekrandaki bazı işlemlerde kullanılıyormuş
Canvasta UI elemanların yerleşimini görmek için "Scene" modunda "2D" seçilir ve "Hierarchy" alanından "Canvas" seçilir.
"Button" seçiliyken "Inspector" ekranında "Rect transform" altındaki kare şeklini tıklayıp "Alt" basılı iken seçimm yaparsak elemanı o kenara veya köşeye yaslar.
EventSystem canvastaki UI elemanlarına fonksiyon veren kısımdır.
TextMeshPro, klasik text yapısına göre daha fazla özellik eklememizi sağlar. Bunun dışındaki kullanımları aynı.
Image "Hierarchy" içinde eklendikten sonra "Inspector" alanında "Source Image" olarak seçilir.
"Hierarchy" içindeki yeri üstte mi yoksa altta mı görüneceğini belirler.
Image dosyasını UI içinde kullanabilmek için assets içinde ilgili alana ekledikten sonra "Texture Type: Sprite (2D and UI)" seçilir.
Button "Hierarchy" içinden eklenebilir. Buton olarak eklenmemiş herhangi bir UI elemana da "Add Component" olarak buton özellikleri eklenebilir.
Buton rengi, tıklanınca alacağı renk vs. "Inspector" ekranından ayarlanıyor.
Butonların çalışması için script yazıyoruz.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement; // sahneler arası geçiş için import ettik.
public class Butonlar : MonoBehaviour
{
public void CikisButonu()
{
Application.Quit(); // aplikasyondan çıkış sağlar. Build sonrası çalışır.
}
public void YeniOyun()
{
// sahne numarasına göre ayarlamak:
// SceneManager.LoadScene(1 /* build manager içinde kaçıncı sahne olduğunu yazdık*/);
SceneManager.LoadScene("YeniOyun"); //scene adına göre ayarladık
}
public void Anasayfa()
{
SceneManager.LoadScene("altinciHafta");
}
}
Scriptin çalışması için bir komponente bağladık.
Butonun çalışması için "Inspector" ekranında "On Click ()" altındaki "+" butonuna tıklanır. Gelen menüde game object olarak kodları bağladığımız komponenti tanımladık. "No function"yazan kısımıda "butonlar" (scriptin adı) > "CikisButonu" (ilgili kodun adı) seçilir.
Input olarak da output olarak da kullanılabilir.
Sliderdan gelen değeri karşılaması için aşağıdaki script yazılır ve bir komponente atanır.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Butonlar : MonoBehaviour
{
public void On_Value_Changed(float deger)
{
print(deger);
}
}
Slider için "Inspector" ekranında "On Value Changed (Single)" alanının altındaki "+" butonuna tıklanır. Gelen menüde game object olarak kodları bağladığımız komponenti tanımladık. "No function"yazan kısımıda "butonlar" (scriptin adı) > dynamic float alanındaki "On_Value_Changed" (ilgili kodun adı) seçilir.
Slider ile her değişiklik yaptığımızda ilgili kod çalışır.
Toggledan gelen değeri karşılaması için aşağıdaki script yazılır ve bir komponente atanır.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Butonlar : MonoBehaviour
{
public void Ses(bool ses)
{
print(ses);
}
}
Toggle için "Inspector" ekranında "On Value Changed (boolean)" alanının altındaki "+" butonuna tıklanır. Gelen menüde game object olarak kodları bağladığımız komponenti tanımladık. "No function"yazan kısımıda "butonlar" (scriptin adı) > dynamic float alanındaki "Ses" (ilgili kodun adı) seçilir.
Toogle ile her değişiklik yaptığımızda ilgili kod çalışır.
"Inspector" alanındaki "Content Type" kısmı inputun alacağı veri tipini verir.
Inputtan gelen değeri karşılaması için aşağıdaki script yazılır ve bir komponente atanır.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Butonlar : MonoBehaviour
{
public void OnEndEditText(string name)
{
print(name);
}
}
Input için "Inspector" ekranında kodu ekleyebileceğimiz üç alan vardır. "On Value Changed (String) " inputta her değişiklik yaptığımızda, "On Submit (String)" submit anında, "On End Edit (String)" ise değişiklik yapmayı bitirdiğimizde kodu çalıştırır. Biz "On End Edit (String)"i kullanacağız.
"On End Edit (String)" alanının altındaki "+" butonuna tıklanır. Gelen menüde game object olarak kodları bağladığımız komponenti tanımladık. "No function" yazan kısımıda "butonlar" (scriptin adı) > dynamic float alanındaki "OnEndEditText" (ilgili kodun adı) seçilir.
Input ile değişiklik yaptığımızın sonunda ilgili kod çalışır.
Dropdown içinde yer alacak seçenekler "Inspector" ekranında "Options" segmesinden düzenlenir. Dropdown seçilen değerin index numarasını atanan fonksiyona verir.
Toggledan gelen değeri karşılaması için aşağıdaki script yazılır ve bir komponente atanır.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Butonlar : MonoBehaviour
{
public void DropdownValue(int index)
{
switch (index)
{
case 0:
print("araba");
break;
case 1:
print("ev");
break;
case 2:
print("arsa");
break;
}
}
}
Dropdown için "Inspector" ekranında "On Value Changed (Int32)" alanının altındaki "+" butonuna tıklanır. Gelen menüde game object olarak kodları bağladığımız komponenti tanımladık. "No function" yazan kısımıda "butonlar" (scriptin adı) > dynamic float alanındaki "DropdownValue" (ilgili kodun adı) seçilir.
Dropdown ile her değişiklik yaptığımızda ilgili kod çalışır.
Geleneksel texte göre daha fazla seçenek sunuyor.
İndirdiğimiz fontu önce "Assets" altında bir klasöre ekleyip ardından, üst menüden "Window" > "TextMeshPro" > "Asset Font Creator" ile açılan menüden "Source Font File" kısmına sürükleyip "Generate Font Atlas" tıklanarak kendimiz font ekleyebiliyoruz.
Assets altındaki tüm font dosyalarına geleneksel text dosyası için de direk ulaşabiliyoruz.
İç içe ekran (canvas) kullanmak için kullanılır. Kendisine ait UI elemanları eklenir. (Oyun içi menüsü vs.)
Açık olan paneli kapatmak için aşağıdaki kodu yazdık.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement; // sahneler arası geçiş için import ettik.
public class Butonlar : MonoBehaviour
{
public GameObject panel;
public void OyunaGeriDon()
{
panel.SetActive(false);
}
}
Kodun çalışması için scripti bir kompanente ekledik. "panel" adındaki değişkene kapanacak paneli ekledik. Son olarak da kapatacak butona kodu ekledik.
Aşağıdaki kodu yazdık ve çalışması için bir kompanente bağladık.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; // UIlar ile işlem yapmak için
using TMPro; //textMeshPro için
public class YeniYontemler : MonoBehaviour
{
public Slider sl; //slidera ulaşmak için "sl" adında değişken oluşturduk.
public TMP_Text TMmetin; //textMeshPro text verisine ulaşmak için "TMmetin" adında değişken oluşturduk.
public Text ClassicText; //klasik texte ulaşmak için "ClassicText" adında değişken oluşturduk.
void Start()
{
// her iki komponentin de text özelliğini buradan yönetiyoruz.
TMmetin.text = "Merhaba Dünyalı";
ClassicText.text = "Biz Dostuz";
}
void Update()
{
print(sl.value); //slider value değerini yazdırır.
}
}
Her bir değişken için ilgili UI komponentini bağladık.
Üst menüden "window" > "Animation" > "Animation" ile ilgili menü açılır (kısa yolu "Ctrl" + "6"). Sürükle bırak ile ana ekrana eklenir.
Animasyon eklenmek istenen öge seçilir ve "Animation" ekranında "Create" tıklanır. Gelen menüden "Assets" içine "Animations" adında bir klasör oluşturulup bu klasör seçilir. Bu klasöre animasyon ve "animator" için iki dosya oluşturulur. "Animator" animasyonun kumandasıdır.
"Animation" ekranıda record tuşuna bastıktan sonra timeline üzerinde istenilen zamana gelinir ve o zamanda olması istenilen değişiklik gerçekleştirilir. Yazılım araları kendisi doldurur.
Timeline alanındaki noktaların yeri kaydırılarak veya çoklu seçim yapıp alanı uzatıp kısaltarak animasyonu yeniden ayarlayabiliriz.
Bütün özelliklerin animasyonu oluyor gibi görünüyor. Mesh renderer, rotation, position vs. Özelliğin nesnede olması yeterli.
"Animation" ekranında "Add Property" ile de aynı şekilde animasyona özellik eklenebilir.
mixamo.com hazır karakterlerin ve animasyonların olduğu bir web sitesi.
Bir karakter seçip ona animasyonları dahil ediyoruz.
idle: durma konumu
Download > "format: FBX for Unity" > "Download"
walk: yürüme durumu > ilerlemesin, yerinde dursun biz ilerlemeyi kod ile yapalım dersek "In place" seçilir. > "Download"
İndirdiklerimiz "Assets" içinde bir klasöre (derste adı "Anime" idi) atıyoruz.
Modeli "Hierarchy" alanına sürükleyer kullanabiliriz. Bu hali ile kaplama yapılmamış gibi görünüz. Kaplama için aynı klasörün içine materials adında bir klasör daha oluşturulur. Model tıklanıp "Inspector" ekranında "Select" > "Materials" > "Extarct Textures" tıklanır ve hedef olarak az önce oluşturduğumuz klasör seçilir.
Animasyonların aktif olması için, bir klasör daha oluşturup (derste adı: "Anim") içine indirdiğimiz paketlerden "Ctrl" + "D" ile ayırdığımız animasyon dosyalarını atıyoruz.
Yukarıda yaptığımız işleme devam ediyoruz.
Karaktere "Inspector" > "Add Component" ile "Animator" ekliyoruz.
Daha önce animasyon dosyalarımızı attığımız klasörde "Create" > "Animator Controller" yapılır. Adı "Yuruyus" olark değiştirilir ve açılır. "Idle" animasyonunu sürükleyip bırakınca "Entry" kısmına bağlandı.
"Walking" animasyonunu da sürükleyip aynı alana bıraktık. "Idle" üzerinde "Sağ Click" > "Make Transition" seçip "Walking" tıklanarak bağlanır. Aynı işlem tersine de yapılır.
"Animator" ekranında "Parameters" > "+" tıklanır. "Boolean" seçilir. "isWalking" adında bir değişken oluşturulur.
"Idle -> Walking" bağlantısı seçili iken "Inspector" ekranında "Has Exit Time" tikli değilse geçiş aniden olur. "Conditions" > "List is Empty" tıklanır. Parametre olarak "isWalking" otomatik gelir. "True" olarak ayarlanır. Aynı işlem tersine de yapılır. Bu sefer "isWalking = false" seçilir.
Aynı işlemi "Runnung" için yaptık.
Oluşturduğumuz "Animator Controller"ı karaktere eklediğimiz "Animator" içindeki "Contoller"a atıyoruz.
Eklediğimiz animasyonlarda "Inspector" ekranında "Loop Time" seçili ise biz iptal edene kadar döngü devam eder. Yoksa bir sefer oynar ve durur.
Animasyonları kontrol etmesi için aşağıdaki scripti yazdık ve komponent olarak karaktere ekledik.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WalkAndRun : MonoBehaviour
{
Animator animator;
void Start()
{
animator = GetComponent<Animator>(); // içinde olduğu komponentin "Animator" komponentine ulaştık.
}
private void FixedUpdate()
{
if (Input.GetKey(KeyCode.W))
{
animator.SetBool("isWalking", true); // animation controller içinde boolen olarak ayarladığımız "isWalking" parametresini "true" yaptık.
transform.Translate(new Vector3(0, 0, 2f) * Time.deltaTime);
}
else
{
animator.SetBool("isWalking", false);
}
if (Input.GetKey(KeyCode.R))
{
animator.SetBool("isRunning", true);
transform.Translate(new Vector3(0, 0, 4f) * Time.deltaTime);
}
else
{
animator.SetBool("isRunning", false);
}
}
}
Kameramızın kontrol ettiğimiz yapıyı kontrol etmesi için:
"Window" > "Package Manager" > "Packages: Unity Registry" kısmında search alanına "Cinemachine" yazıp gelen paketi kuruyoruz ve projemize import ediyoruz.
"MainCamera"ya component olarak "CinemachineBrain" özelliğini ekledik. Bu sayede Cinemachine mantığı ile çalışacak.
"Hierarchy" ekranında "Sağ Click" menüde "Cinemachine" > "Virtual Camera" yaptık ve oluşturduğumuz komponentin "Inspector" ekranında "Follow" alanında takip etmesini istediğimiz komponenti seçtik.
Aynı alandaki body kameranın takip ettiği nesneye olan mesafesini, transform.rotation ise açısını verir.
Tank oyununda "body: 3rd Person Follow" ayarı çok işime yaradı
Asset store içine joystick araması yapıp bunu indirdik ve import ettik.
"Hierarchy" ekranında bir canvas oluşturduk. Canvasta "Scale with Screen Size" seçilir ve telefona uygun ölçüler girildi. İndirdiğimiz asset içinde "Prefix" içinden "Fixed Joystick"i canvasa ekledik.
"Build Settigs" içinde Android'e "Switch platform" ettik.
Önceki derste animasyon oluşturduğumuz klasörde adı "Yuru" olan bir "Animation Controller" oluşturduk. Default state'i "Idle" onun gideceği bağlantı iki yönlü olarak da "Walking" eklendi. "isWalking" adında bir parametre oluşturduk ve bağlantılara mantık olarak atadık. Bu animation controller'ı karakterin "Inspector" menüsünde "Anımator.Controller" alanına atadık. Karaktere "Rigidbody" ve "Box Collider" eklendi.
Yeni bir script oluşturup karaktere ekledik.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent (typeof(Rigidbody), typeof(BoxCollider))] // bu scriptin eklendiği komponentte "RigitBody" ve "BoxCollider" olmak zorunda. Yoksa kodu ekleyince kendisi ekler.
public class Yuru : MonoBehaviour
{
[SerializeField] private Rigidbody _rigidbody; //SerializeField kod içinde private kalacak değerlerin "Inspector" alanında görüntülenmesi ve kullanılabilmesi için yazılır.
[SerializeField] private FixedJoystick _joystick;
[SerializeField] private Animator _animator;
[SerializeField] private float _movespeed;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
private void FixedUpdate()
{
_rigidbody.velocity = new Vector3(_joystick.Horizontal, 0, _joystick.Vertical);
if(_joystick.Horizontal != 0 || _joystick.Vertical != 0)
{
transform.rotation = Quaternion.LookRotation(_rigidbody.velocity); //Quaternion: dönüşlerin daha yumuşak olmasını sağlayan matematiksel bir ifade
_animator.SetBool("isWalking", true);
transform.Translate(new Vector3(0, 0, _movespeed) * Time.deltaTime);
}
else
{
_animator.SetBool("isWalking", false);
}
}
}
ilgili değişkenleri ekledik.
"Build Setting" > "Player Settigns" > "Player" > "Resolution and Presentation" > "Orientation" > "Default Orientation" kısmı "Landscape Left" seçilirse ekran yan olarak ayarlanmış olur.
Oyundaki bilgileri kullandığımız cihazda saklı tutmamıza yarar.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class KodlarPlayerPref : MonoBehaviour
{
void Start()
{
//SetFloat-GetFloat
PlayerPrefs.SetFloat("EnYuksekScore", 500); // float olarak "EnYuksekScore" keyi ile "500" değerini saklar. Kodun bir kere çalışması yeterlidir.
print(PlayerPrefs.GetFloat("EnYuksekScore")); // "EnYuksekScore" keyindeki float değeri getirir ve yazdırır. Hafızada bu key yoksa "0" verir.
print(PlayerPrefs.GetFloat("EnYuksekScore", 1000)); //"EnYuksekScore" keyi yoksa "1000" verir.
//SetInt-GetInt
PlayerPrefs.SetInt("canSayisi", 5); // integer olarak "canSayisi" keyi ile "5" değerini saklar. Kodun bir kere çalışması yeterlidir.
print(PlayerPrefs.GetInt("canSayisi")); // "canSayisi" keyindeki integer değeri getirir ve yazdırır. Hafızada bu key yoksa "0" verir.
print(PlayerPrefs.GetInt("canSayisi", 10)); //"canSayisi" keyi yoksa "10" verir.
//SetString-GetString
PlayerPrefs.SetString("OyuncuAdi", "Murat Gökduman"); // string olarak "OyuncuAdi" keyi ile "Murat Gökduman" değerini saklar. Kodun bir kere çalışması yeterlidir.
print(PlayerPrefs.GetString("OyuncuAdi")); // "OyuncuAdi" keyindeki string değeri getirir ve yazdırır. Hafızada bu key yoksa "" verir.
print(PlayerPrefs.GetString("OyuncuAdi", "Oyuncu 1")); // "OyuncuAdi" keyi yoksa "Oyuncu 1" verir.
//DeleteKey
PlayerPrefs.DeleteKey("OyuncuAdi"); // "OyuncuAdi" keyi ve verisi silinir.
//DeleteAll
PlayerPrefs.DeleteAll(); // PlayerPrefs içindeki tüm keyleri ve verileri siler.
}
}
Adı "Labirent" olan yeni bir proje oluşturduk.
Labirente duvarlar yapıp material atandı. Tüm duvarlar bir parent altında toplandı. Parentinin tagı "Duvar" olarak atandı. Alt elemanlara da "Duvar" tagı atandı Adı "Baslangic" ve "Bitis" olan iki küçük plane oluşturup başlangıç ve bitişe yerleştirdik.
Başlangıca adı "Top" olan bir küre yerleştirdik ve "rigidbody" ekledik. Topa aşağıdaki script eklendi.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TopKontrol : MonoBehaviour
{
private Rigidbody rb;
public float Hiz = 1.8f;
void Start()
{
rb = GetComponent<Rigidbody>();
}
private void FixedUpdate()
{
float yatay = Input.GetAxis("Horizontal");
float dikey = Input.GetAxis("Vertical");
Vector3 kuvvet = new Vector3(-yatay, 0, -dikey);
rb.AddForce(kuvvet * Hiz);
}
private void OnCollisionEnter(Collision other) // çarpışma sırasında çalışacak fonksiyon
{
string objIsmi = other.gameObject.name;
if (objIsmi.Equals("Bitis"))
{
print("Oyunu Kazandınız");
}
}
}
UI çin "Canvas" oluşturuldu. Ekranın sağ ve sol üst köşeleri için zaman ve can textleri oluşturuldu. Can textinin yanına kalp imajı konuldu. Script aşağıdaki güncellendi.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; // UI için gerekli
public class TopKontrol : MonoBehaviour
{
private Rigidbody rb;
public float Hiz = 1.8f;
public Text zaman, can;
float zamanSayaci = 500f;
float canSayaci = 20;
void Start()
{
rb = GetComponent<Rigidbody>();
}
private void Update()
{
zamanSayaci -= Time.deltaTime;
zaman.text = (int)zamanSayaci + ""; // baştaki "(int)" veriyi integer olarak çevirir. sondaki "" kısmı da dönüşen veriyi string yapar.
can.text = canSayaci + "";
}
private void FixedUpdate() // hareketleri yönetmek için fixedUpdate daha uygun
{
float yatay = Input.GetAxis("Horizontal");
float dikey = Input.GetAxis("Vertical");
Vector3 kuvvet = new Vector3(-yatay, 0, -dikey);
rb.AddForce(kuvvet * Hiz);
}
private void OnCollisionEnter(Collision other) // çarpışma sırasında çalışacak fonksiyon
{
string objIsmi = other.gameObject.name;
if (objIsmi.Equals("Bitis"))
{
print("Oyunu Kazandınız");
}
if(other.gameObject.tag.Equals("Duvar"))
{
canSayaci--;
}
}
}
"zaman" ve "can" text değişkenlerine ilgili UI elemanları atandı.
Oyunun devam edip etmediğini kontrol etmek için "oyunDevam" adında bir boolean değişken başlangıçta true olacak şekilde atandı. Zaman bittiğinde veya can bittiğinde false oluyor. True veya false olduğu durumlar if ile kullanıldı.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; // UI için gerekli
public class TopKontrol : MonoBehaviour
{
private Rigidbody rb;
public float Hiz = 1.8f;
public Text zaman, can;
float zamanSayaci = 500f;
float canSayaci = 20;
bool oyunDevam = true;
void Start()
{
rb = GetComponent<Rigidbody>();
}
private void Update()
{
if(oyunDevam)
{
zamanSayaci -= Time.deltaTime;
zaman.text = (int)zamanSayaci + ""; // baştaki "(int)" veriyi integer olarak çevirir. sondaki "" kısmı da dönüşen veriyi string yapar.
}
can.text = canSayaci + "";
if(zamanSayaci <= 0 || canSayaci <= 0) // can veya zaman biterse
{
oyunDevam = false;
}
}
private void FixedUpdate() // hareketleri yönetmek için fixedUpdate daha uygun
{
if (oyunDevam) // oyunDevam "true" ise ediyorsa top yönetilebilir.
{
float yatay = Input.GetAxis("Horizontal");
float dikey = Input.GetAxis("Vertical");
Vector3 kuvvet = new Vector3(-yatay, 0, -dikey);
rb.AddForce(kuvvet * Hiz);
}
else
{
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
}
}
private void OnCollisionEnter(Collision other) // çarpışma sırasında çalışacak fonksiyon
{
string objIsmi = other.gameObject.name;
if (objIsmi.Equals("Bitis"))
{
print("Oyunu Kazandınız");
}
if(other.gameObject.tag.Equals("Duvar"))
{
canSayaci--;
}
}
}
Bu seferki projeyi "WebGL" olarak build edeceğiz. "Build Settings" içine sahnemizi atadık. Player Settings -> Player -> "WebGL" -> "Others Settings" "Color Space: Gama" ve "Lightmap Encoding: normal quality" olarak ayarlandı.
Oyunu yeniden başlatması için UI kısmına bir buton eklendi. Butona atanmak üzere aşağıdaki script yazıldı.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement; // scene yönetmek için gerekli.
public class Btn : MonoBehaviour
{
public void YenidenBasla()
{
SceneManager.LoadScene(0);
}
}
Butonun gerektiği durumlarda görünmesini sağlayan kod scripte eklendi.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; // UI için gerekli
public class TopKontrol : MonoBehaviour
{
private Rigidbody rb;
public float Hiz = 1.8f;
public Text zaman, can, durum;
float zamanSayaci = 500f;
float canSayaci = 20;
bool oyunDevam = true;
public Button btn;
void Start()
{
rb = GetComponent<Rigidbody>();
}
private void Update()
{
if(oyunDevam)
{
zamanSayaci -= Time.deltaTime;
zaman.text = (int)zamanSayaci + ""; // baştaki "(int)" veriyi integer olarak çevirir. sondaki "" kısmı da dönüşen veriyi string yapar.
}
can.text = canSayaci + "";
if(zamanSayaci <= 0 || canSayaci <= 0) // can veya zaman biterse
{
oyunDevam = false;
durum.text = "Oyun tamamlanamadı.";
btn.gameObject.SetActive(true); // Butonu görünür yapar.
}
}
private void FixedUpdate() // hareketleri yönetmek için fixedUpdate daha uygun
{
if (oyunDevam) // oyunDevam "true" ise ediyorsa top yönetilebilir.
{
float yatay = Input.GetAxis("Horizontal");
float dikey = Input.GetAxis("Vertical");
Vector3 kuvvet = new Vector3(-yatay, 0, -dikey);
rb.AddForce(kuvvet * Hiz);
}
else
{
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
}
}
private void OnCollisionEnter(Collision other) // çarpışma sırasında çalışacak fonksiyon
{
string objIsmi = other.gameObject.name;
if (objIsmi.Equals("Bitis"))
{
durum.text = "Oyunu tamamladınız. Tebrikler.";
oyunDevam = false;
btn.gameObject.SetActive(true);
}
if(other.gameObject.CompareTag("Duvar"))
{
canSayaci--;
}
}
}
"Window" > "Package Manager" içinden "Cinemachine" import edildi. "Hierarchy" ekranından "Cinemachine" -> "FreeLook Camera" eklendi. Bu eklenince otomatik olarak main camera içine "CinemachineBrain" kompanenti eklenmiş oldu.
"FreeLook Camera" seçiliyken "Inspector" ekranında "Follow" ve "Look at" alanına top eklendi. "Axis Control" alanından kameranın yeri ayarlanabilir.
Ben "FreeLook camera" yerine daha önce kullandığımız "Virtual Camera" ile de düzgün sonuç alamadım. Üzerinde tekrar çalışacağım.
"Window" > "Package Manager" içinden "WebGL publisher" import edildi. Sonra "Build Settings" içinden webgl için build edildi.
play.unity.com içine projemizi göndermek için en üst menüden "Publish" ile yayınlayabiliyoruz.
Oyunumuz https://play.unity.com/mg/other/odev8-n8k içinde yayınlandı.
itch.io içinde oyunu ücretli veya ücretsiz olarak yayınlayabiliyoruz.
Audio adında yeni bir proje oluşturduk. Hocanın hazır verdiği "Audio", "Models" ve "Textures" klasörlerini asset klasörüne ekledik.
Hazır texture olarak verilen "Gras"ı materyale ekleyip oluşturduğumuz zemine atadık.
Ekranın sağ altındaki ampul sembolünü tıklayarak "Lighting" menüsünü açtık. "Scene" > "New Lighting Settings" ile yeni ışık ayarı oluşturduk. "Inspector" ekranınan "Auto Generate" açıldı.
Zemine 4 adet küpü köşeleri bir kareye denk gelecek şekilde yerleştirdik.
Tüm bu alanı "_Environment" adında bir parent altında topladık.
Ses konusunda iki önemli component var. Biri "Main Camera" içinde varsayılan olarak gelen "Audio Listener" sesin algılanıp bize iletildiği kısımdır. "Audio source" ise sesin kaynağıdır. "Hierarchy" içinde ayrı bir nesne gibi eklenebilir veya nesneye komponent olarak atanabilir.
Audio Source "Inspector" ekranında
Adı ve tagı "Player" olan boş bir gameobject oluşturduk.
Komponent olarak "Character Controller" eklendi. Boyu çapı ve yüksekliği ayarlandı. Sonra komponent olarak "Audio Source" eklendi. Volume ayarlandı.
Player elementinin altına bir kapsul eklendi ve box collider'ı kaldırıldı. Çünkü player controller kendi colliderına sahip. Kapsül playerin kapsayacağı şekilde ayarlandı.
Main Camera da Player altına eklendi. Kamera tam kapsülün üst noktasına gelecek şekilde ayarlanır.
PlayerController adında bir script oluşturduk.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float moveSpeed;
public CharacterController characterController;
public Transform cam;
public float lookSensivity;
public float maxXRot;
public float minXRot;
private float curXRot;
void Start()
{
Cursor.lockState = CursorLockMode.Locked;
}
// Update is called once per frame
void Update()
{
Move();
Look();
}
void Move()
{
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
Vector3 dir = transform.right * x + transform.forward * z; // vektörel toplama
dir.Normalize(); // vektör yönü aynı kalır değeri 1 olur.
dir *= moveSpeed * Time.deltaTime;
characterController.Move(dir);
}
void Look()
{
float x = Input.GetAxis("Mouse X") * lookSensivity;
float y = Input.GetAxis("Mouse Y") * lookSensivity;
transform.eulerAngles += Vector3.up * x; // "eulerAngles" de "rotation" gibi dönüş işlemlerinde kullanılır. Farkı anlamadım.
curXRot += y;
curXRot = Mathf.Clamp(curXRot, minXRot, maxXRot);
cam.localEulerAngles = new Vector3(-curXRot, 0, 0); // "localEulerAngles" globalde değil lokalde dönüşü kontrol eder.
}
}
"Footsteps" adında bir script yazdık.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Footsteps : MonoBehaviour
{
public AudioClip[] footstepClips; // çalınacak ses dosyaları
public AudioSource audioSource;
public CharacterController controller;
public float footstepTreshold; // hangi hızı aşarsak adım sesi verilecek?
public float footstepRate; // kaç sn aralıklarla adım sesi verilecek?
private float lastFootstepTime;
private void FixedUpdate()
{
if(controller.velocity.magnitude > footstepTreshold) // "velocity.magnitude" vektörün boyutunu ve yönünü göz ardı ederek sadece büyüklüğünü ifade eden skaler bir değerdir.
{
if (Time.time - lastFootstepTime > footstepRate)
{
lastFootstepTime = Time.time;
audioSource.PlayOneShot(footstepClips[Random.Range(0, footstepClips.Length)]);
/*
"audioSource.PlayOneShot" bir sesin bir defa çalıp susmasını sağlar.
"Random.Range" belirli bir aralıkta random tam sayı üretir.
*/
}
}
}
}
Scripti player komponentine bağladık ve ilgili değişkenleri atadık.
Bir koridor oluşturduk. Arasına "Audio" -> "Audio Reverb Zone" elementi "Hierarchy" içinden eklendi. İç içe iki çemberden dıştaki yankı giriş bölgesi, içteki ise yankı bölgesidir. "Inspector" ekranında "ReverbPreset" üzerinden yankı tipi değiştirilebilir.
Doppler etkisi, ses dalgalarının veya ışık dalgalarının bir kaynaktan yayıldığı bir gözlemciye doğru yaklaşırken veya uzaklaşırken frekansındaki değişimi ifade eder. Örneğin, bir ambulans geçerken sesinin yüksek frekanslı başlayıp düşük frekanslı devam etmesi Doppler etkisine bir örnektir.
Örnek için asset store içinden bir uçak modeli indirdik. Komponent olarak "AudioSource" ekledik. "AudioClip" kısmına hocanın verdiği ses dosyalarından "plane" eklendi.
Boş bir gameObject oluşturulup player'dan 500 birim öteye yerleştirildi. Uçak modeli ona alt eleman olarak eklendi. Boş gameObject için aşağıdaki script yazıldı ve eklendi.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rotator : MonoBehaviour
{
public Vector3 axis;
public float speed;
void Update()
{
transform.Rotate(axis, speed * Time.deltaTime);
}
}
AudioSource içindeki "Max - Min Distance" ve "Volume Rolloff" ile mesafeye göre ses işlemlerini yapabiliriz. Belirli mesafelerde sesin duyulması veya duyulmaması için aşağıdaki scripti de dahil edebiliriz.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Crickets : MonoBehaviour
{
public AudioSource audioSource;
public float stopDistance;
private Transform player;
private float defaultVolume;
private void Start()
{
defaultVolume = audioSource.volume;
player = FindAnyObjectByType<PlayerController>().transform; // "FindAnyObjectByType<PlayerController>()" içinde "PlayerController" olan nesneyi bulur
}
private void Update()
{
if(player == null)
{
return;
}
float dist = Vector3.Distance(transform.position, player.position); // "Vector3.Distance()" iki vektör arasındaki mesafeyi bulur.
if (dist > stopDistance)
{
audioSource.volume = defaultVolume;
}
else
{
audioSource.volume = 0.0f;
}
}
}
"OnTriggerEnter" ve "OnTriggerExit" fonksiyonları ile ses açma - kapama işlemi yaptık.
Bunun için boş bir gameObject içine "AudioSource" ve "BoxCollider" ekledik. BoxCollider "isTriger" seçili ve istenilen alanı kapsayacak halde düzenlendi. Trigger durumunu denetlemek için script yazıldı.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MusicZone : MonoBehaviour
{
public AudioSource audioSource;
public float fadeTime;
private float targetVolume;
// Start is called before the first frame update
void Start()
{
targetVolume = 0f;
audioSource.volume = 0f;
}
// Update is called once per frame
void Update()
{
audioSource.volume = Mathf.MoveTowards(audioSource.volume, targetVolume, (1f / fadeTime) * Time.deltaTime);
/*
"Mathf.MoveTowards()" bir değerin başka bir değere yumuşak bir şekilde dönüşmesini sağlar.
ilk parametre başlangıcı, ikincisi hedefi son parametre ise değişim miktarını verir. Değişim miktarı küçüldükçe geçiş yumuşaklaşır.
*/
}
private void OnTriggerEnter(Collider other) // trigger alanına girdiğimizde çalışır.
{
if (other.CompareTag("Player"))
{
targetVolume = 1.0f;
}
}
private void OnTriggerExit(Collider other) // trigger alanından çıktığımızda çalışır.
{
if (other.CompareTag("Player"))
{
targetVolume = 0f;
}
}
}
"Assets" klasöründe "Sağ Click" -> "Create" -> "Audio Mixer" ile yeni bir dosya oluşturup çift tıkladık. "Audio Mixer" ekranı açıldı.
Master: ortamdaki tüm seslerin dB ayarını yapar.
"Groubs" -> "+" ile yeni bir ayar üretilir.
"Inspector" ekranındaki "AudioSource" komponentlerinin altındaki "Output" ile oluşturduğumuz mixerler komponente atanır.
Mixerleri kodla yönetebilmek için, ilgili mixer seçiliyken "Inspector" alanında "Volume" sağ tıklanır. "Expose Volume to Script" tıklanır. Bu işlemden sonra "Audio Mixer" ekranının sağ üst kısmında kodda kullanmamız için parametreler oluştu. Bunları sağ tıklayıp yeniden adlandırabiliriz.
Ses kontrol için 3 adet slider içeren bir UI tasarlandı.
UI dan alınan veri ile mixerleri kontrol etmesi için aşağıdaki script yazıldı ve "Canvas" ögesine atandı.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro; // TextMeshPro için
using UnityEngine.Audio; // ses işlemleri için.
using UnityEngine.UI; // UI için
public class MusicMixerController : MonoBehaviour
{
public AudioMixer mixer;
public GameObject window;
public Slider masterSlider;
public Slider sfxSlider;
public Slider musicSlider;
void SetSliders() // sliderların pozisyonunu ayarlar.
{
masterSlider.value = PlayerPrefs.GetFloat("MasterVolume");
sfxSlider.value = PlayerPrefs.GetFloat("SFXVolume");
musicSlider.value = PlayerPrefs.GetFloat("MusicVolume");
}
private void Start()
{
if(PlayerPrefs.HasKey("MasterVolume")) //ses daha önce ayarlandıysa önceki ayarları mixere atar.
{
mixer.SetFloat("MasterVolume", PlayerPrefs.GetFloat("MasterVolume")); // ilk parametredeki "MasterVolume" Audio Mixer içindeki parametre adını, ikincinin içindeki ise PlayerPref içindeki keyi verir.
mixer.SetFloat("SFXVolume", PlayerPrefs.GetFloat("SFXVolume"));
mixer.SetFloat("MusicVolume", PlayerPrefs.GetFloat("MusicVolume"));
SetSliders();
}
else
{
SetSliders();
}
}
//sliderlara atanacak fonksiyonlar.
public void UpdateMasterVolume()
{
mixer.SetFloat("MasterVolume", masterSlider.value);
PlayerPrefs.SetFloat("MasterVolume", masterSlider.value);
}
public void UpdateSFXVolume()
{
mixer.SetFloat("SFXVolume", sfxSlider.value);
PlayerPrefs.SetFloat("SFXVolume", sfxSlider.value);
}
public void UpdateMusicVolume()
{
mixer.SetFloat("MusicVolume", musicSlider.value);
PlayerPrefs.SetFloat("MusicVolume", musicSlider.value);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.V))
{
window.SetActive(!window.activeInHierarchy); // "activeInHierarchy" hiyerarşide aktif olma durumunu verir. Bu kod durumu tersine çevirir.
if (window.activeInHierarchy)
{
Cursor.lockState = CursorLockMode.None; // window aktifken mouse görünür olur.
}
else
{
Cursor.lockState = CursorLockMode.Locked;
}
}
}
}
ilgili değişkenleri atadık.
Slider Max-min value -20 ve 1 olarak ayarlandı. Slider fonksiyonları sliderlara eklendi.
mixerleri kontrol ettiğimiz kısım açıkken kameranın dönmemesi için, kamera dönüşünü ayarladığımız fonksiyon koşula bağlandı.
...
void Update()
{
Move();
if(Cursor.lockState == CursorLockMode.locked)
{
Look();
}
}
...
Android için ARCore kullanacağız.
Güncelleme sonrası not: AR Session Origin -> XR Origin mobile, Ar Core -> Google AR Core oldu. (bu not ileriki derslerde anlam kazanacak.)
Yeni bir 3D project oluşturduk. Build Settingste çalışacağımız platformu "Android" yaptık.
"Window" -> "Package Manager" içinden önce "AR Foundation" ardından "ARCore XR Plugin" kuruldu.
"Build Settigns" -> "Player Settings" içinde "Player" kısmında üst kısımdaki proje bilgilerini girdik. Projeyi yayınlaycaksak "Icon" başlığı altındaki tüm kısımlar doldurulmalıdır. Yayınlamaycaksak "Default Icon" ayarlamak yeterlidir.
"Build Settigns" -> "Player Settings" içinde "Player" kısmında "Resolution and Presentation" altında "Orientation" kısmında ekranın yatay veya dikey kullanımı ayarlanır.
"Build Settigns" -> "Player Settings" içinde "Player" kısmında "Other Settings" başlığının altında "Multithreaded Rendering" seçimini kaldırdık (düzgün çalışmazsa tekrar ekleyeceğiz). "Package Name" istediğimiz gibi değilse "Override Default Package Name" seçilerek değiştirilebilir. "Minimum Api Level" "... (Api Level 26)" olarak seçilir.
Hocaya ek olarak: "Build Settigns" -> "Player Settings" içinde "Player" kısmında "Other Settings" başlığının altında "Scripting Backend: IL2CPP" seçildi. "ARMv7" seçimi kaldırıldı. "ARM64" seçildi.
"Build Settigns" -> "XR Plug-In Management" kısmında "ARCore" seçilir.
Cep telefonumuz kablo ile bağlıyken "Build And Run" ile uygulama direk çalıştırılabilir. Bunun için telefonun "Geliştirici Seçenekleri" açık olmalıdır. Nasıl açıldığına google'dan baktık. Sonra geliştirici seçeneklerinden "USB debugging" açıldı. Telefon kablo ile bağlıyken "Build Settings" -> "Run Device" olarak telefonumuzu seçebiliriz. Bu şekilde telefonumuza uygulama yüklenmiş ve çalıştırılmış olur.
"Hierarchy" ekranından "XR" -> "AI Session Origin" ve "AR Session" eklendi. "AR Session" ortamımızın AR ile uyumlu çalışmasını sağlar. "AI Session Origin" içinde bize gereken kamera var. Bu nedenle "Main Camera" yı siliyoruz.
AR ile uapacağımız işlemlerin çoğunu "AI Session Origin" içine "Add Component" ile eklenti ekleyerek yapacağız. Zemin tespiti için önce "AR Plane Manager" eklendi. Sadece zemini değil duvarları espit için de kullanılabilir. İçindeki "Detection Mode" kısmı ile bu ayar yapılır.
"Hierarchy" içinden bir adet "AI Default Plane" eklendi. "Asset" içinde "Prefabs" klasörü açılıp "AI Default Plane" sürüklenerek içine bırakıldı. Sonra da "Hierarchy" alanından silindi. "Prefabs" içine eklediğimiz dosyayı "AI Session Origin" içindeki "AR Plane Manager" komponentine "Plane Prefab" olarak atandı.
BuildFailedException: You have enabled the Vulkan graphics API, which is not supported by ARCore...
hatasının çözümü için "Build Settigns" -> "Player" kısmının "Other Settigns" kısmına "Auto Graphics API" seçimi kaldırılır. "Graphic APIs" içinden de "Vulkan" kaldırılır.
Hocanın verdiği hazır png yi "Assets" -> "Textures" klasörüne ekledik. "Inspector" alanından "Alpha is Transparacy"yi seçtik.
"Assets" -> "Materials" klasöründe adı "PlacementsIndicator" olan bir material oluşturduk. "Inspector" alanında "Shader: Unlit/Transparent" olarak ayarladık. Texture olarak da az önce ayarladığımız texture'ı seçtik.
"Hierarchy" alanında adı "PlacementsIndicator" olan bir boş obje oluşturduk. İçine alt obje olarak bir plane objesi atadık. Bunun da "Mesh Collider" komponentini remove edip material olarak oluşturduğumuz materyali atadık. Scale "0.03" ayarladık. "Lighting -> Receive Shadow" seçimini kaldırdık.
"AR Session Origin" içindeki "AR Plane Manager" komponentini kaldırdık. Sonra geri ekledik ama "Plane Prefab"ı şimdilik boş bırakıyoruz. Bir de "AR Raycast Manager" (telefondan gelen veriyi yönetir) komponenti ekledik.
"Assets" -> "Scripts" klasörüne "PlacementsIndicator" adında bir script oluşturduk ve onu da adı "PlacementsIndicator" olan nesnemize ekledik.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems; //zemin tepiti, yüzey tespiti vs
public class PlacementsIndicator : MonoBehaviour
{
private ARRaycastManager rayManager; //telefondan gelen veri
private GameObject visual; // placeholder görselimiz.
private void Start()
{
rayManager = FindObjectOfType<ARRaycastManager>(); // tipi "ARRaycastManager" olanı bul.
visual = transform.GetChild(0).gameObject; // child elemanların ilkini (index 0) getir.
visual.SetActive(false);
}
private void Update()
{
List<ARRaycastHit> hits = new List<ARRaycastHit>();
rayManager.Raycast(new Vector2(Screen.width / 2, Screen.height / 2), hits, TrackableType.Planes); //ilk parametre ekranın neresinde olacağı, ikinci parametre telefondan alınan sonuçları tutan değişken, üçüncüsü ise yapılacak işlem
if(hits.Count > 0) // eğer hits ler 0 dan fazla ise zemin algılanmış demektir.
{
transform.position = hits[0].pose.position; //kodun olduğu nesnenin pozisyonunu alanda ilk yakaladığı noltayla eşitler. Kamera hareket ettikçe nesne de hareket eder.
transform.rotation = hits[0].pose.rotation; //kodun olduğu nesnenin yönünü alanda ilk yakaladığı noltayla eşitler.
if (!visual.activeInHierarchy) visual.SetActive(true);
}
}
}
"ObjectSpawner" adında boş bir gameObject oluşturduk. Bir de aynı adda script oluşturup içine attık.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectSpawner : MonoBehaviour
{
public GameObject objectToSpawn;
private PlacementsIndicator placementsIndicator;
private void Start()
{
placementsIndicator = FindObjectOfType<PlacementsIndicator>(); // Adı "PlacementsIndicator" olan komponenti bulur.
}
private void Update()
{
if(Input.touchCount>0 && Input.touches[0].phase == TouchPhase.Began) // ekrana dokunulduysa ve dokunmalardan ilki başladığı anda
{
GameObject obj = Instantiate(objectToSpawn, placementsIndicator.transform.position, placementsIndicator.transform.rotation); // obj adında bir game objecti "objectToSpawn" örneklendirilerek üret. position ve rotation bilgisi olarak "placementsIndicator" bilgilerini kullan.
}
}
}
"ObjectToSpan" adında boş bir gameObject oluşturup içine AR olarak eklemek istediğimiz nesneyi dahil ettik. Sonra oluşturduğumuz bu yapıyı "Assets" -> "Prefabs" klasörüne taşıdık ve "Hierarchy" alanından sildik. Oluşturduğumuz prefabımızı "ObjectSpawner" içindeki scriptimizdeki gameObject olarak atadık.
Yeni boş bir scene oluşturuldu. "Main Camera" yı sildik. "AI Session Origin" ve "AR Session" eklendi. "AI Session Origin" içine komponent olarak "AR Face Manager" eklendi. "AR Face Manager" -> "Maximum Face Count" bulunacak en fazla yüz sayısını belirtir.
"Hierarchy" alanında "AR Default Face" eklendi ve sonra Prefab klasörüne sürüklenip "Hierarchy" alanından silindi. Bu prefab "AI Session Origin" içine komponent olarak yer alan "AR Face Manager"ın Face Prefab alanına ilişkilendirildi.
"AI Session Origin" -> "AR Camera" nesnesinin "Inspector" ekranında "Facing Direction" ayarı var. Bu ayar açılan kameranın ön mü (user) arka mı (world) olduğuna karar verir.
Daha öncek gibi "Build Settings" alanına sahne eklenip build alındı.
Yüze nesne eklemek için (bıyık vs) internetten incelenecek. Body ve hand tracking konularını da internetten kendimiz araştıracağız.
Yüze nesne eklemek için videoda verilen assetten faydalandık. "AR Default Face" prefabı içine asset içindeki "canonical_face_mesh" dosyasını attık. Bu bize yüz için referans verdi. Daha sonra bunun ve "AR Default Face" in kaplamasının görünmemesi için asset içindek "Occlusion" materyalini atadık. Bu materyali kendimiz de Shader özelliğini "VR/SpatialMapping/Occlusion" olarak oluşturabiliriz.
İmage yakalama için kullanılacak resim formatını png yapın. Fotoğrafın "Inspector" ekranında "Advanced" özelliklerden "Read/Write" seçili olmalı.
Takip ettiğimiz video: youtube.com: Let’s Make an AR App in 15 MINUTES!! (Beginner Friendly)
"ARImageTracking" adında yeni bir proje oluşturuldu. "Window" -> "Package Manager" içinden önce "AR Foundation" ardından "ARCore XR Plugin" kuruldu.
Joystick Pack ve Dragon for Boss Monster : HP paketleri eklendi.
"Build Settings"ten "Android" seçildi.
"Hierarchy" ekranından "XR" -> "AI Session Origin" ve "AR Session" eklendi. "Main Camera" yı silindi.
"AI Session Origin" altına komponent olarak "AR Tracked Image Manager" eklendi.
"Assets" içinde "Create" -> "XR" -> "Reference Image Library" seçip "ReferenceImageLibrary" adında bir dosya oluşturduk. "Inspector" ekranında "Add Image" ile tetikleyici olarak kullanacağımız image dosyasını ekledik.
Kullanacağımız ejderha prefabını üzerinde çalışmak için "Hierarchy" alanına ekledik. Boyutunu yanına eklediğimiz küpe göre orantıladık (Default küpün bir kenarı 1 metre). İlk çıkış yönünü "AR Camera" nesnesinin açısına göre ayarladık. "Inspector" ekranındaki "Controller" olarak atanan Animation controller içinden istemediğimiz animasyonları kaldırdık (sadece "Take Off" ve "Fly Forward" kaldı.). "Rigidbody" eklendi. Rigitbodyden "Gravity" kaldırıldı
"AI Session Origin" -> "AR Tracked Image Manager" içindeki "Serialized Library" ile "ReferenceImageLibrary" dosyası eşleştirildi. "Add Component" -> "New Script" ile adı "PrefabCreator" olan bir script yarattık ve ekledik.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
public class PrefabCreator : MonoBehaviour
{
[SerializeField] private GameObject dragonPrefab; // indirdiğimiz dragon prefablarından istediğimiz eşleştireceğiz.
[SerializeField] private Vector3 prefabOffset;
private GameObject dragon;
private ARTrackedImageManager arTrackedImageManager; // ARTrackedImageManager, ARCore veya ARKit tarafından izlenen görselleri işlemek için kullanılır.
private void OnEnable() // script etkinleştiğinde bu işlevi çağırır.
{
arTrackedImageManager = gameObject.GetComponent<ARTrackedImageManager>();
arTrackedImageManager.trackedImagesChanged += OnImageChanged; // trackedImagesChanged olayına abone olur.
}
private void OnImageChanged(ARTrackedImagesChangedEventArgs obj) // Fonksiyon içinde, eklendiği, güncellendiği veya kaldırıldığı için değişiklik yapılan tüm izlenen görüntülerin listesi bulunur.
{
foreach(ARTrackedImage image in obj.added) // Eklenen her bir yeni izlenen görüntü için döngü başlatır.
{
dragon = Instantiate(dragonPrefab, image.transform);
}
}
}
Scriptin "Dragon Prefab" kısmına ayarladığımız prefabı eşleştirildi. "Prefab Offset" ayarını "y = -0.1" yapıldı.
"DragonController" olan bir script yarattık ve sahnedeki ejderha prefabına ekledik.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DragonController : MonoBehaviour
{
[SerializeField] private float speed;
private FixedJoystick fixedjoystick;
private Rigidbody rigidbody;
private void OnEnable() // script etkin olduğunda çağırılan kod.
{
fixedjoystick = FindObjectOfType<FixedJoystick>();
rigidbody = gameObject.GetComponent<Rigidbody>();
}
private void FixedUpdate()
{
float xVal = fixedjoystick.Horizontal;
float yVal = fixedjoystick.Vertical;
Vector3 movement = new Vector3(xVal, 0, yVal);
rigidbody.velocity = movement * speed;
if (xVal != 0 && yVal != 0)
// Karakterin dönme açısını hesaplar.
transform.eulerAngles = new Vector3(transform.eulerAngles.x, Mathf.Atan2(xVal, yVal) * Mathf.Rad2Deg, transform.eulerAngles.z); // "transform.eulerAngles" lokal eksene göre dönüşleri temsil eder Mathf.Atan2() fonksiyonu, x ve y koordinatları arasındaki açıyı hesaplar. Bu durumda, joystick'in yatay ve dikey girişlerine (xVal ve yVal) dayalı olarak karakterin dönme açısını hesaplamak için kullanılır. Mathf.Rad2Deg ile radianları dereceye dönüştürürüz.
}
}
Prefab düzenlemek için ekrana eklediğimiz ejderhamıza "DragonController" scripti eklendi. Speed 0.5 ayarlandı. Sahneye eklediğimiz prefabın ayarlarının ana prefabda da ayarlanması için "Inspector" ekranında "Overrides" -> "Apply All" seçildi. Ana prefabda da ayarlar kontrol edildi. Aktarılmayanlar düzeltildi.
AR eğitimi için hocanın önerdiği kanallar: @xrmasiso, @KaraDot
İleri eğitim için video: youtube.com: Project-Based Augmented Reality Course with Unity Engine and AR Foundation
Her aşamayı tek tek anlatmayacağım. Kodları ve önemli yerleri yazacağım.
Herhangi bir UI elemana komponent olarak "Button" eklenerek buton özellikleri verilebilir.
Textures materiale atandıktan sonra materyal seçiliyken "Inspector" menüsünde "Shader" ayarı için "Unlit/Texture" veya "Legacy Shaders/Diffuse" da kullanılabilir.
Mobilya projesinin kodları
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class FurniturePlacementManager : MonoBehaviour
{
public GameObject SpawnableFurniture; // Yerleştirilecek mobilya objesi
public ARSessionOrigin sessionOrigin; // ARSessionOrigin bileşeni
public ARRaycastManager raycastManager; // ARRaycastManager bileşeni
public ARPlaneManager planeManager; // ARPlaneManager bileşeni
private List<ARRaycastHit> raycastHits = new(); // Raycast atış sonuçlarını depolamak için liste
void Start()
{
// Ekranın sürekli açık kalmasını sağlar
Screen.sleepTimeout = SleepTimeout.NeverSleep;
}
private void Update()
{
// Ekrana dokunulduğunda
if(Input.touchCount > 0)
{
if(Input.GetTouch(0).phase == TouchPhase.Began) // Dokunma başlangıcı kontrol ediliyor
{
// Ekranın dokunulan noktasında bir düzlem algılanıyor mu kontrol ediliyor
bool collision = raycastManager.Raycast(Input.GetTouch(0).position, raycastHits, TrackableType.PlaneWithinPolygon);
// Çarpışma varsa ve bir düğme basılı değilse
if(collision && isBottonPressed() == false)
{
// Yerleştirilecek mobilya objesi klonlanıyor ve dokunulan noktaya yerleştiriliyor
GameObject _object = Instantiate(SpawnableFurniture);
_object.transform.position = raycastHits[0].pose.position;
_object.transform.rotation = raycastHits[0].pose.rotation;
}
// Tüm düzlemler gizleniyor
foreach(var planes in planeManager.trackables)
{
planes.gameObject.SetActive(false);
}
// ARPlaneManager devre dışı bırakılıyor
planeManager.enabled = false;
}
}
}
// Bir düğme basılı mı kontrol eden fonksiyon
public bool isBottonPressed()
{
// Eğer hiçbir düğme seçili değilse
if(EventSystem.current.currentSelectedGameObject?.GetComponent<Button>() == null)
{
return false; // Düğme basılı değil
}
else
{
return true; // Düğme basılı
}
}
// Mobilya değiştirme fonksiyonu
public void SwitchFurniture(GameObject furniture)
{
SpawnableFurniture = furniture; // Yerleştirilecek mobilya objesini değiştir
}
}
Photon ana sayfası. Bu sayfadan kayıt oldundu.
Dashboard sayfasında "Create a New App" tıklanır.
"Multiplayer Game" ve "Pun" (Photon Unity Network) seçilir. Diğer alanlar doldurulur. "Create" tıklanır.
Unity projesi pluşturduk. "Asset Store" içinden PUN 2 - FREE projeye eklenir. (asset store içindeki diğer photon eklentilerini de incele.)
İmport işleminin bitiminde proton dashboard içinde oluşturduğumuz proje idsi ilgili yere eklenir. "Setup Project" tıklanır.
"Game" adında bir scene yaratıldı. Ortama oyunun oynanacağı alan eklendi.
Menü adında bir sahne yaratıldı. Oyuncular bu sahnede toplandıktan sonra Game sahnesine geçecekler.
Scriptleri tutması için "_NetworManager" adında boş gameobject oluşturduk. "NetworManager" adı ile oluşturduğumuz scripti içine attık.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun; // Photon Unity Networking (PUN) ayarları için
public class NetworkManager : MonoBehaviourPunCallbacks // "MonoBehaviour" kalıtımı Unity için özel fonksiyonlar tanımlar. "MonoBehaviourPunCallbacks" ise hem Unity özelliklerini korur hem de PUN callbacks özellikleri ekler.
{
public static NetworkManager instance;
private void Awake() // starttan önce çalışır. Ortam oluşmadan önce bu fonksiyon çalışır.
{
if (instance != null && instance != this)
gameObject.SetActive(false); // Eğer instance zaten varsa ve bu instance bu değilse, bu nesneyi etkisiz hale getirir.
else
{
instance = this; // Bu instance'ı belirle
DontDestroyOnLoad(gameObject); // Bu oyun nesnesini sahne değiştikçe yok etme.
}
}
void Start()
{
PhotonNetwork.ConnectUsingSettings(); // Oyun ayarları kullanılarak Photon sunucusuna bağlan.
}
public override void OnConnectedToMaster() // "override" mevcut kalıp fonksiyonun üzerine yazmamızı sağlar.
{
Debug.Log("Master sunucuya bağlandı");
CreateRoom("OyunSalonu 1"); // Yeni bir oda oluştur.
}
public void CreateRoom(string roomName) // Oda oluştur.
{
PhotonNetwork.CreateRoom(roomName); // Photon ağı üzerinde belirtilen isimde bir oda oluşturur.
}
public override void OnCreatedRoom()
{
Debug.Log("Belirtilen isimde oda oluşturuldu: " + PhotonNetwork.CurrentRoom.Name); // Oda oluşturulduğunda log olarak oluşturulan oda ismini gösterir.
}
public void JoinRoom(string roomName) // Odaya katıl.
{
PhotonNetwork.JoinRoom(roomName); // Belirtilen isimdeki odaya katılır.
}
public void ChangeScene(string sceneName) // Sahne değiştir.
{
PhotonNetwork.LoadLevel(sceneName); // Belirtilen sahneyi yükler.
}
}
InputField(TMP) eklerken "Inspector" alanından "Content Type: Alphanumeric" seçilir.
Boş bir gameObject oluşturup "Horizontal Layout Group" komponenti eklenirse child ögelerinin yerleşimi horizontal olarak düzenlenebilir. Aynı şekilde "Vertical Layout Group" da kullanılabilir.
Canvas içine "MainScreen" ve "LobbyScreen" adında iki ayrı menü oluşturduk. Hangisinin görüneceğini kodlarla ayarlayacağız.
RPC (Remote Procedure Calls): fonksiyonu başkasının bilgisayarında çalıştırmamızı sağlayan fonksiyondur.
İki şekilde kullanılır.
PhotonView ile işlem yapacaksak NetworkManager scripti için PhotonView oluşturmamız gerekiyor.
Playerların hangi özelliklerinin gitmesini istiyorsak PlayerController scripti için PhotonView metodu ile göndermeliyiz.
"_NetworManager" nesnesine komponent olarak "Photon View" eklendi. Oturum numarası ile oturum açması için gerekiyor.
NetworkManager içindeki override işlemler kaldırıldı. Serverda takip edilmesi istenen işlem için [PunRPC] fonksiyonun hemen öncesine yerleştirildi.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun; // PUN ayarları için
public class NetworkManager : MonoBehaviourPunCallbacks // "MonoBehaviour" kalıtımı unity için özel fonksiyonlar tanımlar. "MonoBehaviourPunCallbacks" ise hem unity özelliklerini korur hem de PUN callbacks özellikleri ekler.
{
public static NetworkManager instance;
private void Awake() // startttan önce çalışır. Daha ortam oluşmadan önce bu fonksiyon çalışır.
{
if (instance != null && instance != this)
gameObject.SetActive(false);
else
{
instance = this; // oturum aç
DontDestroyOnLoad(gameObject); // oyunu kapatmadığın sürece oturumu açık tut
}
}
void Start()
{
PhotonNetwork.ConnectUsingSettings(); //kullanıcı ayarları ile bağlantı kur.
}
public void CreateRoom(string roomName) // oda yarat
{
PhotonNetwork.CreateRoom(roomName);
}
public void JoinRoom(string roomName) // odaya katıl
{
PhotonNetwork.JoinRoom(roomName);
}
[PunRPC] // Bu işaret, bir yöntemin bir Photon ağı çağrısı (network call) olarak işaretlenmesini sağlar. Bir sonraki fonksiyonun çalışmasını Photon ağına iletir.
public void ChangeScene(string sceneName) // sahne değiştir.
{
PhotonNetwork.LoadLevel(sceneName);
}
}
"_Menu" adında boş bir gameObject yaratıldı. "Menu" adında bir script yazılıp bu gameObjecte atıldı. Değişkenler ilgili yerlere atandı.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using Photon.Pun;
using Photon.Realtime;
public class Menu : MonoBehaviourPunCallbacks // PUN Callbacks özelliklerini de almak için.
{
[Header("Sceens")] // Ekranlara erişim için değişkenlerin başlığı
public GameObject mainScreen; // Ana ekran oyun nesnesi
public GameObject lobbyScreen; // Lobby ekranı oyun nesnesi
[Header("Main Screen")] // Ana Ekran için değişkenlerin başlığı
public Button createRoomButton; // Oda oluşturma düğmesi
public Button joinRoomButton; // Odaya katılma düğmesi
[Header("Lobby Screen")] // Lobby Ekranı için değişkenlerin başlığı
public TextMeshProUGUI playerListText; // Oyuncu listesi metni
public Button startGameButton; // Oyun başlatma düğmesi
public Button leaveLobbyButton; // Lobby'den ayrılma düğmesi
private void Start()
{
createRoomButton.interactable = false; // Oda oluşturma düğmesini etkisiz hale getir
joinRoomButton.interactable = false; // Odaya katılma düğmesini etkisiz hale getir
}
public override void OnConnectedToMaster()
{
createRoomButton.interactable = true; // Oda oluşturma düğmesini etkin hale getir
joinRoomButton.interactable = true; // Odaya katılma düğmesini etkin hale getir
}
// Ekranları değiştirmek için bir yardımcı fonksiyon
void SetScreen(GameObject screen)
{
mainScreen.SetActive(false); // Ana ekranı devre dışı bırak
lobbyScreen.SetActive(false); // Lobby ekranını devre dışı bırak
screen.SetActive(true); // Belirtilen ekranı etkinleştir
}
// Oda oluşturma düğmesine tıklandığında çağrılan fonksiyon
public void OnCreateRoomButton(TMP_InputField roomNameInput)
{
NetworkManager.instance.CreateRoom(roomNameInput.text); // Ağ yöneticisine oda oluşturma isteği gönder
}
// Odaya katılma düğmesine tıklandığında çağrılan fonksiyon
public void OnJoinRoomButton(TMP_InputField roomNameInput)
{
NetworkManager.instance.JoinRoom(roomNameInput.text); // Ağ yöneticisine odaya katılma isteği gönder
}
// Oyuncu adı güncellendiğinde çağrılan fonksiyon
public void OnPlayerNameUpdate(TMP_InputField playerNameInput)
{
PhotonNetwork.NickName = playerNameInput.text; // Photon ağı oyuncu adını güncelle
}
// Bir odaya katıldığında çağrılan fonksiyon
public override void OnJoinedRoom()
{
SetScreen(lobbyScreen); // Lobby ekranını göster
photonView.RPC("UpdateLobbyUI", RpcTarget.All); // Lobby arayüzünü güncelleme RPC'si gönder
}
// Lobby arayüzünü güncellemek için RPC
[PunRPC]
public void UpdateLobbyUI()
{
playerListText.text = ""; // Oyuncu listesi metnini temizle
foreach(Player player in PhotonNetwork.PlayerList) // Her oyuncu için döngü
{
playerListText.text += player.NickName + "\n"; // Oyuncu adını oyuncu listesi metnine ekle
}
if(PhotonNetwork.IsMasterClient) // Eğer yerel oyuncu odanın sahibi ise
{
startGameButton.interactable = true; // Oyun başlatma düğmesini etkin hale getir
}
else // Değilse
{
startGameButton.interactable = false; // Oyun başlatma düğmesini etkisiz hale getir
}
}
// Bir oyuncu odayı terk ettiğinde çağrılan fonksiyon
public override void OnPlayerLeftRoom(Player otherPlayer)
{
UpdateLobbyUI(); // Lobby arayüzünü güncelle
}
// Lobby'den ayrılma düğmesine tıklandığında çağrılan fonksiyon
public void OnLeaveLobbyButton()
{
PhotonNetwork.LeaveRoom(); // Odadan ayrıl
SetScreen(mainScreen); // Ana ekranı göster
}
// Oyunu başlatma düğmesine tıklandığında çağrılan fonksiyon
public void OnStrartGameButton()
{
NetworkManager.instance.photonView.RPC("ChangeScene", RpcTarget.All, "Game"); // Oyun sahnesini değiştirme RPC'si gönder
}
}
Butonlara da ilgili fonksiyonlar atandı ve değişkenleri eklendi. "_Menu" elementine komponent olarak "Photon View" eklendi (RPC kullanılan tüm kodların olduğu elementlere eklemek gerekiyormuş.).
"Game" scene içinde işlem yapıyoruz.
Player adında bir küp yaratıldı ve tagı "Player" olarak ayarlandı.
Playerin altında bir boş gameObject (adı: Hat) oluşturup içinde küplerle şapka oluşturuldu. Şapkayı oluşturan küplerin Box Collider'ı kaldırıldı.
Player'a rigidbody eklendi. "Constraints" altından tüm yönlerde "Freeze Rotation" işaretlendi.
Player'a "Photon View" eklendi. "Photon Transform View" eklendi ve "Synchronize Options" kısmından sadece position seçili bırakıldı. "Photon View" -> "Observables" -> "Observable Search" ayarı "manuel" yapıldı ve "Observed Components" alanına "Photon Transform View" sürüklendi. "Observed Components" alanı takip edilecek veriyi belirler.
"PlayerController" adında bir script yazıp "Player" elementine ekledik.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
public class PlayerController : MonoBehaviour
{
[HideInInspector] public int id; // Oyuncu kimliği
[Header("Info")] // Bilgi başlığı
public float moveSpeed; // Hareket hızı
public float jumpForce; // Zıplama kuvveti
public GameObject hatObject; // Şapka nesnesi
[HideInInspector] public float curHatTime; // Mevcut şapka süresi
[Header("Components")] // Bileşenler başlığı
public Rigidbody rig; // Rigidbody bileşeni
public Player photonPlayer; // Photon oyuncu bileşeni
private void Update()
{
Move(); // Hareket fonksiyonu çağrılır
if (Input.GetKeyDown(KeyCode.Space)) // Space tuşuna basıldığında
TryJump(); // Zıplama denenir
}
// Hareket fonksiyonu
void Move()
{
float x = Input.GetAxis("Horizontal") * moveSpeed; // Yatay eksen hareketi
float z = Input.GetAxis("Vertical") * moveSpeed; // Dikey eksen hareketi
rig.velocity = new Vector3(x, rig.velocity.y, z); // Hareket vektörü atanır
}
// Zıplama denemesi fonksiyonu
void TryJump()
{
Ray ray = new Ray(transform.position, Vector3.down); // Aşağıya doğru bir ışın oluşturulur
if(Physics.Raycast(ray, 0.7f)) // Işının belirli bir mesafede bir yere çarptığı kontrol edilir
{
rig.AddForce(Vector3.up * jumpForce, ForceMode.Impulse); // Zıplama kuvveti uygulanır
}
}
}
"_GameManager" adında bir boş gameObject oluşturuldu. "GameManager" adıyla oluşturduğumuz script içine atıldı.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using System.Linq; // Find() çalışsın diye eklendi
public class GameManager : MonoBehaviourPunCallbacks
{
[Header("Stats")]
public bool gameEnded = false; // Oyunun sona erip ermediğini belirten bir işaretçi
public float timeToWin; // Oyunun kazanılması için gereken süre
public float invictableDuration; // Hat özelliğini alınca yenilmezlik süresi
private float hatPickupTime; // Şapkanın alındığı zaman
[Header("Players")]
public string playerPrefabLocation; // Oyuncu prefabının dosya konumu
public Transform[] spawnPoints; // Oyuncuların doğacağı noktalar
public PlayerController[] players; // Oyuncuların listesi
public int playerWithHat; // Şu anda hatta sahip olan oyuncunun kimliği
private int playersInGame; // Oyunda bulunan oyuncuların sayısı
public static GameManager instance; // GameManager'ın tek örneği
private void Awake()
{
instance = this; // GameManager'ı tek örneğe atama
}
private void Start()
{
players = new PlayerController[PhotonNetwork.PlayerList.Length]; // Oyuncu listesini PhotonNetwork'ten alınan oyuncu sayısı kadar ayarlama
photonView.RPC("ImInGame", RpcTarget.All); // Tüm oyunculara "ImInGame" RPC'sini gönderme
}
[PunRPC]
void WinGame(int playerId)
{
gameEnded = true; // Oyunun sona erdiğini belirtme
PlayerController player = GetPlayer(playerId); // Kazanan oyuncuyu al
GameUI.instance.SetWinText(player.photonPlayer.NickName); // Oyun arayüzünde kazanan oyuncuyu gösterme
Invoke("GoBackToMenu", 3.0f); // Belirli bir süre sonra menüye dönme işlemini çağırma
}
void GoBackToMenu()
{
PhotonNetwork.LeaveRoom(); // Odadan ayrılma
NetworkManager.instance.ChangeScene("Menu"); // Sahne değiştirme
}
[PunRPC]
void ImInGame()
{
playersInGame++; // Oyunda olan oyuncu sayısını artırma
if (playersInGame == PhotonNetwork.PlayerList.Length) // Eğer oyundaki oyuncu sayısı PhotonNetwork'ten alınan oyuncu sayısına eşitse
SpawnPlayer(); // Oyuncu doğurma
}
[PunRPC]
public void GiveHat(int playerId, bool initialGive)
{
if(!initialGive) // başlangıçta şapka var mı?
GetPlayer(playerWithHat).SetHat(false); // Eğer başlangıç verilmiyorsa, şapkayı gösterme
playerWithHat = playerId; // şapkalı playerı değiştir
GetPlayer(playerId).SetHat(true); // yeni playerda şapkayı göster
hatPickupTime = Time.time; // şapkanın alındığı zamanı kaydet
}
public bool CanGetHat()
{
if (Time.time > hatPickupTime + invictableDuration) // Eğer geçen süre, alınabilirlik süresinden fazlaysa
return true; // Şapka alınabilir
else
return false; // Şapka alınamaz
}
void SpawnPlayer()
{
GameObject playerObj = PhotonNetwork.Instantiate(playerPrefabLocation, spawnPoints[Random.Range(0, spawnPoints.Length)].position, Quaternion.identity); // Oyuncu objesini rastgele bir noktada oluşturma
PlayerController playerScript = playerObj.GetComponent<PlayerController>(); // Oyuncu kontrolcüsünü al
playerScript.photonView.RPC("Initialize", RpcTarget.All, PhotonNetwork.LocalPlayer); // Tüm oyunculara oyuncuyu başlatma RPC'sini gönderme
}
public PlayerController GetPlayer(int playerId)
{
return players.First(x => x.id == playerId); // Verilen oyuncu kimliğine sahip oyuncuyu döndürme
}
public PlayerController GetPlayer(GameObject playerObj)
{
return players.First(x => x.gameObject == playerObj); // Verilen oyuncu objesine sahip oyuncuyu döndürme
}
}
"PlayerController" scripti aşağıdaki gibi güncellendi.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
public class PlayerController : MonoBehaviourPunCallbacks, IPunObservable
{
[HideInInspector] public int id; // Oyuncunun benzersiz kimliğini saklar.
[Header("Info")]
public float moveSpeed; // Oyuncunun hareket hızı.
public float jumpForce; // Oyuncunun zıplama kuvveti.
public GameObject hatObject; // Oyuncunun şapka nesnesi.
[HideInInspector] public float curHatTime; // Oyuncunun şapka süresi.
[Header("Components")]
public Rigidbody rig; // Oyuncunun fizik bileşeni.
public Player photonPlayer; // Oyuncunun Photon oyuncu nesnesi.
private void Update()
{
if(PhotonNetwork.IsMasterClient)
{
// Eğer bu istemci oyun odasının ana istemcisi ise:
if(curHatTime >= GameManager.instance.timeToWin && !GameManager.instance.gameEnded)
{
// Eğer oyuncunun şapka süresi, oyunu kazanmak için gereken süreyi aştıysa ve oyun henüz bitmediyse:
GameManager.instance.gameEnded = true; // Oyunun bittiğini belirten bayrak true olarak ayarlanır.
GameManager.instance.photonView.RPC("WinGame", RpcTarget.All, id); // Tüm oyunculara oyunu kazananı belirten bir RPC çağrısı yapılır.
}
}
if(photonView.IsMine)
{
// Eğer bu istemci yerel oyuncuya aitse:
Move(); // Oyuncunun hareketini sağlar.
if (Input.GetKeyDown(KeyCode.Space))
TryJump(); // Oyuncunun zıplamasını denetler.
if(hatObject.activeInHierarchy)
{
curHatTime += Time.deltaTime; // Eğer oyuncunun şapkası aktifse, şapka süresini günceller.
}
}
}
void Move()
{
float x = Input.GetAxis("Horizontal") * moveSpeed; // Yatay (x) yöndeki hareketi belirler.
float z = Input.GetAxis("Vertical") * moveSpeed; // Dikey (z) yöndeki hareketi belirler.
rig.velocity = new Vector3(x, rig.velocity.y, z); // Oyuncunun hızını günceller.
}
void TryJump()
{
Ray ray = new Ray(transform.position, Vector3.down); // Aşağıya doğru bir ışın oluşturur.
if(Physics.Raycast(ray, 0.7f)) // Yer altındaki bir yüzey ile temas kontrol edilir.
{
rig.AddForce(Vector3.up * jumpForce, ForceMode.Impulse); // Oyuncuyu yukarı doğru zıplatır.
}
}
public void SetHat(bool hasHat)
{
hatObject.SetActive(hasHat); // Şapka nesnesinin görünürlüğünü belirler.
}
void OnCollisionEnter(Collision collision)
{
if(!photonView.IsMine)
return;
if(collision.gameObject.CompareTag("Player"))
{
// Çarpışma, başka bir oyuncu ile gerçekleştiyse:
if(GameManager.instance.GetPlayer(collision.gameObject).id == GameManager.instance.playerWithHat)
{
// Çarpışma yapan oyuncunun, şu anda şapka taşıyan oyuncu olup olmadığını kontrol eder.
if(GameManager.instance.CanGetHat())
{
// Eğer şapka taşıyan oyuncuysa ve oyuncu şapka alabilir durumdaysa:
GameManager.instance.photonView.RPC("GiveHat", RpcTarget.All, id, false); // Şapka verme işlemini tüm oyunculara bildirir.
}
}
}
}
[PunRPC]
public void Initialize(Player player)
{
// Oyuncunun Photon oyuncu nesnesi atanır.
photonPlayer = player;
// Oyuncunun benzersiz kimliği atanır.
id = player.ActorNumber;
// GameManager'daki oyuncular listesine bu oyuncu eklenir.
GameManager.instance.players[id - 1] = this;
print("id :" + id);
// İlk oyuncu ise, şapka verme işlemi başlatılır.
if (id == 1)
{
GameManager.instance.GiveHat(id, true);
}
if(!photonView.IsMine)
{
rig.isKinematic = true; // Eğer bu istemci yerel oyuncuya ait değilse, fiziksel simulasyon devre dışı bırakılır.
}
}
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if(stream.IsWriting)
{
// Şapka süresi yazılır.
stream.SendNext(curHatTime);
}
else if(stream.IsReading)
{
// Şapka süresi okunur.
curHatTime = (float)stream.ReceiveNext();
}
}
}
"Player" elementi "Assets" -> "Resorces" dosyasına sürüklendi. "Hierarchy" alanından silindi.
"Game" sahnesine "Canvas" eklendi. Kazananı göstermesi için "WinText" ve her bir playeri ve süresini göstermesi için "Player" altında text ve slider oluşturuldu. sliderların "interactable" özelliği kaldırıldı.
"GameIU" adında bir script oluşturuldu ve "_GameManager" nesnesine eklendi.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using Photon.Pun;
public class GameUI : MonoBehaviour
{
public PlayerUIContainer[] playerContainers; // Oyuncu arayüzü için konteynerlerin dizisi.
public TextMeshProUGUI winText; // Oyunu kazananın bildirildiği metin alanı.
public static GameUI instance; // Oyun arayüzü için tekil örnek.
private void Awake()
{
instance = this; // Singleton örneğini oluşturur.
}
private void Start()
{
InitilazePlayerUI(); // Oyuncu arayüzünü başlatır.
}
// Oyuncu arayüzlerini başlatır.
void InitilazePlayerUI()
{
for(int x = 0; x < playerContainers.Length; ++x)
{
PlayerUIContainer container = playerContainers[x];
if(x < PhotonNetwork.PlayerList.Length)
{
container.obj.SetActive(true); // Konteyner nesnesini etkinleştirir.
container.nameText.text = PhotonNetwork.PlayerList[x].NickName; // Oyuncu adını belirtilen metin nesnesine yazar.
container.hatTimeSlider.maxValue = GameManager.instance.timeToWin; // Şapka zamanlayıcısının maksimum değerini ayarlar.
}
else
{
container.obj.SetActive(false); // Konteyner nesnesini devre dışı bırakır.
}
}
}
private void Update()
{
UpdatePlayerUI(); // Oyuncu arayüzlerini günceller.
}
// Oyuncu arayüzlerini günceller.
void UpdatePlayerUI()
{
for(int x = 0; x < GameManager.instance.players.Length; ++x)
{
if(GameManager.instance.players[x] != null)
{
playerContainers[x].hatTimeSlider.value = GameManager.instance.players[x].curHatTime; // Oyuncunun şapka zamanlayıcısını günceller.
}
}
}
// Kazananın adını metin alanında görüntüler.
public void SetWinText(string winnerName)
{
winText.gameObject.SetActive(true); // Kazanan metin alanını etkinleştirir.
winText.text = winnerName + " win"; // Kazananın adını metin alanına yazar.
}
}
// Oyuncu arayüzü öğelerini içeren sınıf.
[System.Serializable]
public class PlayerUIContainer
{
public GameObject obj; // Konteyner nesnesi.
public TextMeshProUGUI nameText; // Oyuncu adı metin nesnesi.
public Slider hatTimeSlider; // Şapka zamanlayıcı kaydırıcısı.
}
ilgili değişkenler yerine eklendi.
"Player" prefabında "Observed Components" olarak "Player Controller" eklendi.
Hocanın verdiği asseti klasörlerimize ekledik. İkonları "Texture Type: Sprite(2D and UI)" olarak ayarladık.
"Models" klasörüne attığımız dosyalardan ilgili "texture" dosyalarını ilgili "material" dosyalarına atadık.
"Scrips" klasörü içinde "Building", "BuildingPlacement", "BuildingPreset", "CameraController", "City" ve "Selector" adında scriptler oluşturduk.
"Ground" adında 5x5 bir plane oluşturduk.
Ekranın en alt sağında "Auto Generate Lighting" tıklanıp "New Lighting Setting"ctıklanarak yeni ışık ayarı yaratıldı. "Inspector" ekranından "Auto Generate" seçildi.
"Materials" klasöründe "Ground" adında bir materyal oluşturuldu. Texture > Grass buna atandı. Tiding (50,50) yapıldı. Offset(0.5, 0.5) yapıldı(bu sayede yeni yaratılan küp tam olarak karelerden merkezdekinin içine yerleşir.). "Ground" elementine atandı.
"CameraAnchor" adında boş bir gameObject oluşturguk. Rotation (-50, 45, 0) ayarladık.
"MainCamera" yı "CameraAnchor" içine attık ve transformunu resetledik. Sonra position (0, 20, 0) rotation (90, 0, 0) ayarladık.
"CameraAnchor" içine komponent olarak "CameraController" scriptini ekledik.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraController : MonoBehaviour
{
public float moveSpeed;
[Header("Rotation")]
public float minXRot;
public float maxXRot;
public float rotateSpeed;
private float curXRot;
[Header("Zoom")]
public float maxZoom;
public float minZoom;
public float zoomSpeed;
private float curZoom;
private Camera cam;
private void Start()
{
cam = Camera.main;
curZoom = cam.transform.localPosition.y;
curXRot = -50;
}
private void Update()
{
// Zoom işlemleri
curZoom += Input.GetAxis("Mouse ScrollWheel") * -zoomSpeed;
curZoom = Mathf.Clamp(curZoom, minZoom, maxZoom);
cam.transform.localPosition = Vector3.up * curZoom;
// Rotation işlemleri
if(Input.GetMouseButton(1))
{
float x = Input.GetAxis("Mouse X");
float y = Input.GetAxis("Mouse Y");
curXRot += -y * rotateSpeed;
curXRot = Mathf.Clamp(curXRot, minXRot, maxXRot);
transform.eulerAngles = new Vector3(curXRot, transform.eulerAngles.y+(x*rotateSpeed), 0.0f);
}
//Movement işlemleri
Vector3 forward = cam.transform.forward;
forward.y = 0.0f;
forward.Normalize(); // y = 0 değerine göre forwardı ayarlar. Normalized() ile Vektörün yönü sabit kalırken boyutu 1 birim olur.
Vector3 right = cam.transform.right;
float moveX = Input.GetAxisRaw("Horizontal");
float moveZ = Input.GetAxisRaw("Vertical");
Vector3 dir = moveX * right + moveZ * forward;
dir.Normalize();
dir *= moveSpeed * Time.deltaTime;
transform.position += dir;
}
}
CameraController komponenti "Move Speed: 10", "Min X Rot: -85", "Max X Rot: -10", "Rotate Speed: 3", "Max Zoom: 50", "Min Zoom: 5" ve "Zoom Speed: 20" olarak ayarlandı.
"BuildingPreset" scripti içine
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
[CreateAssetMenu(fileName ="Building Preset", menuName = "New Building Preset")] // Assets içindeki sağ click menü içinde "New Building Preset" seçeneği eklendi.
public class BuildingPreset : ScriptableObject // scriptable obje oluşturmak için
{
public int cost;
public int costPerTurn;
public GameObject prefab;
public int population;
public int jobs;
public int food;
}
yazıldı. Bu script kullanılarak Assets içindeki sağ click menü içinde "New Building Preset" seçeneği ile dosyalar oluşturabiliyoruz. "Building Preset" klasöründe "House" adında bir "Bulding Preset" dosyası oluşturduk.
Bu preseti kullanmak için "Building" scripti içine
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Building : MonoBehaviour
{
public BuildingPreset preset;
}
yazıldı.
"Models" klasöründeki "house" modeli "Building_House" adıyla oluşturduğumuz boş gameObject içine atıldı ve boyutlandırıldı. Bu modele hazır materyali atandı. GameObjecte("Building_House") "Building" scripti komponent olarak atandı. "Preset: House (Building Preset)" olarak ayarlandı. "Building_House" nesnesi "Prefabs" klasörüne sürüklendi.
"Building Presets" altındaki "House" dosyasında "Prefab: Building_House" atandı ve diğer ayarlar girildi.
Aynı işlem "factory" ve "farm" için de yapıldı.
"road" için kendimiz bir prefab oluşturup aynı işlemleri gerçekleştirdik.
Ekranın altında boydan boya bir panelimiz, panelin solunda nesne ekleme butonlarımız ve silme butonumuz, sağında ise "End Turn" butonumuz mevcut. İki tarafın ortasında ise oyun bilgileri için text oluşturduk.
Scriptleri tutması için "_GameManager" adında boş bir gameObject oluşturuldu. İçine "City", "Selector" ve "Building Placement" scriptleri eklendi.
Selector scripti:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems; // tüm projeden scripte erişilebilmesi için.
public class Selector : MonoBehaviour
{
private Camera cam;
public static Selector instance; // bu scripte erişilebilmesi için
private void Awake()
{
instance = this;
}
private void Start()
{
cam = Camera.main;
}
public Vector3 GetCurTilePosition()
{
if(EventSystem.current.IsPointerOverGameObject()) //bu UI a dokunduysa
{
return new Vector3(0, -99, 0); // alakasız bir pozisyon verdik.
}
Plane plane = new Plane(Vector3.up, Vector3.zero); // yukarı bakan ve positionu (0, 0, 0) olan bir plane oluşturduk.
Ray ray = cam.ScreenPointToRay(Input.mousePosition);
float rayOut = 0.0f;
if(plane.Raycast(ray, out rayOut)) // mouse zemine dokundu ise
{
Vector3 newPos = ray.GetPoint(rayOut) - new Vector3(0.05f, 0.0f, 0.05f); // zeminde dokunulan yerin pozisyonunu aldık.
newPos = new Vector3(Mathf.CeilToInt(newPos.x), 0.0f, Mathf.CeilToInt(newPos.z)); // pozisyonu tam sayıya tamamladık.
return newPos;
}
return new Vector3(0, -99, 0); // alakasız bir pozisyon verdik.
}
}
"PlacementIndicator" adında bir boş gameObject oluşturuldu. Altına bir küp ekledik ve küpün BoxCollider komponentini kaldırıldı. "position.y = 0.5" ayarlandı.
Bu küp için bir materyal yaratıldı. "Rendering Mode: Fade" seçildi. Mavimsi bir renk verildi. Renk seçeneklerinden "A: 118" yapılarak nesne saydamlaştırıldı.
Aynı şekilde "BulldozerIndicator" oluşturuldu. Kırmızımsı bir renk verildi.
Her iki nesne de pasif yapıldı.
"Building Placement" scripti:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BuildingPlacement : MonoBehaviour
{
private bool currentlyPlacing;
private bool currentlyBulldozering;
private BuildingPreset curBuildingPreset;
private float indicatorUpdateTime = 0.05f;
private float lastUpdateTime;
private Vector3 curIndicatorPos;
public GameObject placementIndicator;
public GameObject bulldozerIndicator;
public void BeginNewBuildingPlacement(BuildingPreset preset)
{
/*if(City.instance.money<preset.cost) // city scriptini yazarken money değişkeni oluşturacağız.
{
return;
}*/
currentlyPlacing = true;
curBuildingPreset = preset;
placementIndicator.SetActive(true);
}
void CancelBuildingPlacement()
{
currentlyPlacing = false;
placementIndicator.SetActive(false);
}
public void ToogleBulldozer()
{
currentlyBulldozering = !currentlyBulldozering;
bulldozerIndicator.SetActive(currentlyBulldozering);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
CancelBuildingPlacement();
if(Time.time - lastUpdateTime > indicatorUpdateTime)
{
lastUpdateTime = Time.time;
curIndicatorPos = Selector.instance.GetCurTilePosition();
if (currentlyPlacing)
placementIndicator.transform.position = curIndicatorPos;
else if (currentlyBulldozering)
bulldozerIndicator.transform.position = curIndicatorPos;
}
}
}
Değişkenleri atadık.
"HouseButton", "FactoryButton", "FarmButton" ve "RoadButton" On Click() fonksiyonu için "BeginNewBuildingPlacement(BuildingPreset preset)" fonksiyonunu kullanıldı. ve her birine uygun preset yerleştirildi.
"BulldozerButton" On Click() fonksiyonu için ToogleBulldozer() fonksiyonu kullanıldı.
"City" scripti:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class City : MonoBehaviour
{
public int money;
public int day;
public int curPopulation;
public int curJobs;
public int curFood;
public int maxPopulation;
public int maxJobs;
public int incomePerJob;
public Text statsText;
public List<Building> buildings = new List<Building>();
public static City instance;
private void Awake()
{
instance = this;
}
private void Start()
{
UpdateStatText();
}
public void OnPlaceBuilding(Building building)
{
money -= building.preset.cost;
maxPopulation += building.preset.population;
maxJobs += building.preset.jobs;
buildings.Add(building);
UpdateStatText();
}
public void OnRemoveBuilding(Building building)
{
maxPopulation -= building.preset.population;
maxJobs -= building.preset.jobs;
buildings.Remove(building);
Destroy(building.gameObject);
UpdateStatText();
}
void UpdateStatText()
{
statsText.text = string.Format("Day: {0} Money: {1}$ Pop: {2}/{3} Jobs: {4}/{5} Food: {6}", new object[7] {day, money, curPopulation, maxPopulation, curJobs, maxJobs, curFood});
}
public void EndTurn() // End Turn butonunun fonksiyonu.
{
day++;
CalculateMoney();
CalculatePopulation();
CalculateJobs();
CalculateFoot();
UpdateStatText();
}
// Buradaki işlemler oyunun ekonomisini belirliyor.
void CalculateMoney()
{
money += curJobs * incomePerJob;
foreach (Building building in buildings)
money -= building.preset.costPerTurn;
}
void CalculatePopulation()
{
if(curFood>=curPopulation && curPopulation<maxPopulation)
{
curPopulation = Mathf.Min(curPopulation + (curFood / 4), maxPopulation);
}
else if(curFood<curPopulation)
{
curPopulation = curFood;
}
}
void CalculateJobs()
{
curJobs = Mathf.Min(curPopulation, maxJobs);
}
void CalculateFoot()
{
foreach(Building building in buildings)
{
curFood += building.preset.food;
}
curFood -= curPopulation / 4;
}
}
"EndTurn" butonu ile fonksiyonu eşleştirildi.
"BuildingPlacement" scripti son hali:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BuildingPlacement : MonoBehaviour
{
private bool currentlyPlacing;
private bool currentlyBulldozering;
private BuildingPreset curBuildingPreset;
private float indicatorUpdateTime = 0.05f;
private float lastUpdateTime;
private Vector3 curIndicatorPos;
public GameObject placementIndicator;
public GameObject bulldozerIndicator;
public void BeginNewBuildingPlacement(BuildingPreset preset)
{
if(City.instance.money<preset.cost)
{
return;
}
currentlyPlacing = true;
currentlyBulldozering = false;
curBuildingPreset = preset;
placementIndicator.SetActive(true);
bulldozerIndicator.SetActive(false);
}
void CancelBuildingPlacement()
{
currentlyPlacing = false;
placementIndicator.SetActive(false);
}
public void ToogleBulldozer()
{
currentlyBulldozering = !currentlyBulldozering;
bulldozerIndicator.SetActive(currentlyBulldozering);
CancelBuildingPlacement();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
CancelBuildingPlacement();
if(Time.time - lastUpdateTime > indicatorUpdateTime)
{
lastUpdateTime = Time.time;
curIndicatorPos = Selector.instance.GetCurTilePosition();
if (currentlyPlacing)
placementIndicator.transform.position = curIndicatorPos;
else if (currentlyBulldozering)
bulldozerIndicator.transform.position = curIndicatorPos;
}
if(Input.GetMouseButtonDown(0) && currentlyPlacing && curIndicatorPos.y == 0)
{
PlaceBuilding();
} else if(Input.GetMouseButtonDown(0) && currentlyBulldozering && curIndicatorPos.y == 0)
{
Bulldoze();
}
}
void PlaceBuilding()
{
GameObject buildObj = Instantiate(curBuildingPreset.prefab, curIndicatorPos, Quaternion.identity);
City.instance.OnPlaceBuilding(buildObj.GetComponent<Building>());
CancelBuildingPlacement();
}
void Bulldoze()
{
Building buildingToDestroy = City.instance.buildings.Find(x => x.transform.position == curIndicatorPos);
if(buildingToDestroy!=null)
{
City.instance.OnRemoveBuilding(buildingToDestroy);
}
}
}
"Ctrl" + "Alt" + "numped 0" kamerayı bizim ekranda gördüğümüz açıya taşır.
Normalde dönderme işlemleri ekranın ortası baz alınarak yapılır. Ekranın ortası değil de seçilen nesne baz alınarak dönderilmesi için "Edit" > "Preferences" > "Navigation" > "Orbit around selection" seçilerek yapılır.
Normalde zoom işlemleri ekranın ortası baz alınarak yapılır. Ekranın ortası değil de mouse baz alınarak zoom yapması için "Edit" > "Preferences" > "Navigation" > "Zoom to mouse position" seçilerek yapılır.
Çift ekran çalışırken bazı özellikleri diğer ekrana taşımak için, taşımak istediğimiz alanın sınırında mouse işaretçisi "+" sembolü olduğunda "Shift" + "Sol Click" ile ilgili kısmı yüzer menü haline getirebiliriz. "+" butonu varken "Ctrl" ye basılıylen sürüklersek de ekrandaki pencereler yer değiştirir.
Transform işlemleri sırasında "Shift" e basılı tutarsak işlem hassaslaşır. "Ctrl" ye basılı tutunca da grap ve scale birer tam grid işler. Zoom ile izometrik görüntüde daha minik gridler oluşunca da onlara göre işlerler. Grap sırasında tam gridde değilken de gride yapışması için üst ortadaki "snap" menüsünden "absolude grid snap" seçilir. "Ctrl" ye basılı tutmak yerine üst ortadan "snap" aktif hale getirilebilir(kısa yolu "Shift" + "Tab")
Bir konuda blender dökümantasyondan yardım almak için ilgili alanda mouse ile beklerken "F1" tuşuna basın.
En üstten "Layout" seçiliyken en alttaki "timeline" editöründen "Auto keying" açılır. Timeline üzerinde en sonda başlangıç ve bitiş kare sayıları ayarlanır. En sağdaki "Properties" editörünün "output" alanında videonun fps si seçilir. Timeline üzerinde istenilen kareye gelinip ana kısımda nesneye değişiklik yaptığımızda timeline üzerine otomatik olarak key atılır (auto keying). Yazılım araları kendisi doldurarak animasyonu oluşturur.
"F9" nesneyi oluşturduktan hemen sonra çıkan "Add" menüsünü tekrar aktif eder.
Bir cismin yüzeyine yapışacak şekilde başka bir cisim yerleştirmek için "snap" açılır ve yanındaki ayarlardan "face nearist" seçilir.
Bir yerde açık bir alanı yüzey ile kapatmak için edit modda kapatılacak akan seçilir ve "F" tuşuna basılır.
"Subdivision Surface" seçili nesneye atandığında daha yumuşak geçişli bir görünüm sağlayan bir "modifier"dir. Kısayol: "Ctrl" + "1-5'e herhangi bir sayı". Girilen sayı subdivision leveli verir.