Yeniden merhabalar,
Bu Unity dersinde, oyun esnasında nasıl dosya seçme veya kaydetme diyaloğu gösterebileceğimizi göreceğiz. Bu diyalog, Windows’a ilaveten Android platformunu da desteklemekte. Deneme şansım olmasa da büyük olasılıkla Mac ve Linux platformları da sorunsuz bir şekilde destekleniyordur. Ancak Universal Windows Platform (UWP) ve WebGL desteklenmemekte.
Asset Store: https://assetstore.unity.com/packages/tools/gui/runtime-file-browser-113006
Alternatif link: https://github.com/yasirkula/UnitySimpleFileBrowser/releases

Detaylar için yazının devamını okuyabilirsiniz…
Kurulum
İndirdiğiniz unitypackage‘ı Assets-Import Package yoluyla projenize import edin.
Eğer plugin’i Android’de kullanmayı düşünüyorsanız, Edit-Project Settings-Player‘dan Write Permission‘ı External (SDCard) yapın.
Kullanım
Plugin’i kullanmaya yarayan fonksiyonlar, FileBrowser sınıfının içerisinde bulunmakta. Ancak bu sınıfa erişmeden önce, kodunuzun başına using SimpleFileBrowser; eklemeniz lazım.
A) Diyalog Göstermek
bool ShowSaveDialog( OnSuccess onSuccess, OnCancel onCancel, PickMode pickMode, bool allowMultiSelection = false, string initialPath = null, string initialFilename = null, string title = "Save", string saveButtonText = "Save" )
Dosya kaydetme diyaloğu gösterir. Plugin’in mevcut sürümünde bu fonksiyon sürekli true döndürür. Eğer şu anda gösterilmekte olan aktif bir diyalog var mı kontrol etmek isterseniz, FileBrowser.IsOpen‘ın değerine bakabilirsiniz (true ise aktif bir diyalog vardır).
onSuccess: Dosya(lar)ın kaydedileceği konum(lar) seçildikten sonra bu fonksiyon çağrılır. Buraya string[] parametre alan bir fonksiyon girilmek zorundadır. Kullanıcının dosya kaydetmek için seçtiği konum(lar) bu array’de depolanır. Eğer allowMultiSelection‘ın değeri false ise, bu array sadece 1 elemandan oluşur, yoksa 1 veya daha fazla elemandan oluşur.
onCancel: Dosya kaydetme işlemi iptal edilirse bu fonksiyon çağrılır. Bu fonksiyon bir parametre almaz. Eğer işlem iptal edilince özel bir şey yapmak istemiyorsanız, onCancel’a değer olarak null verebilirsiniz.
pickMode: Değeri PickMode.Files olursa kullanıcı sadece dosyaları seçebilir, PickMode.Folders olursa kullanıcı sadece klasörleri seçebilir, PickMode.FilesAndFolders olursa kullanıcı hem dosyaları hem de klasörleri seçebilir.
allowMultiSelection: Değeri true olursa, kullanıcı birden çok dosya/klasör seçebilir.
initialPath: Diyalog ilk açıldığında hangi klasörün içinde olacağımızı belirler. Varsayılan olarak diyalog Belgelerim ile başlar.
initialFilename: Diyalog ilk açıldığında, kaydedilecek dosya isminin girildiği input field bu değer ile doldurulur.
title: Diyaloğun başlığında yazacak olan yazıyı belirler.
saveButtonText: Diyaloğun kaydet butonunda yazacak olan yazıyı belirler.
bool ShowLoadDialog( OnSuccess onSuccess, OnCancel onCancel, PickMode pickMode, bool allowMultiSelection = false, string initialPath = null, string initialFilename = null, string title = "Load", string loadButtonText = "Select" )
Dosya seçme diyaloğu gösterir. loadButtonText, diyaloğun seç butonunda yazacak olan yazıyı belirler.
IEnumerator WaitForSaveDialog( PickMode pickMode, bool allowMultiSelection = false, string initialPath = null, string initialFilename = null, string title = "Save", string saveButtonText = "Save" )
ShowSaveDialog gibi, bu fonksiyon da dosya kaydetme diyaloğu gösterir. Ancak bu fonksiyon bir IEnumerator döndürdüğü için, yield komutu vasıtasıyla bir coroutine içerisinde bekletilebilir. Diyalog kapanana kadar yield devam eder.
Diyalog kapatıldıktan sonra, işlemin iptal edilip edilmediğini öğrenmek için FileBrowser.Success‘in değeri kontrol edilebilir. Eğer değeri true ise işlem iptal edilmemiştir. Bu durumda dosya(lar)ın kaydedileceği konum(lar) FileBrowser.Result‘ta depolanır.
IEnumerator WaitForLoadDialog( PickMode pickMode, bool allowMultiSelection = false, string initialPath = null, string initialFilename = null, string title = "Load", string loadButtonText = "Select" )
ShowLoadDialog‘un WaitForSaveDialog gibi coroutine destekleyen versiyonudur.
void HideDialog( bool invokeCancelCallback = false )
Aktif bir diyalog varsa onu zorla kapatmaya yarar. Eğer invokeCancelCallback‘in değeri true ise, o diyaloğu açan fonksiyonun onCancel‘ı çalıştırılır.
B) Diyaloğu Kişiselleştirmek
bool AddQuickLink( string name, string path, Sprite icon = null )
Diyaloğun hızlı erişim menüsüne yeni bir klasör ekler. name, bu klasörün ismini belirlerken path ise klasörün konumunu tutar. Eğer bir ikon belirlenmezse, varsayılan olarak klasör ikonu kullanılır.
void SetExcludedExtensions( params string[] excludedExtensions )
Yoksayılacak dosya uzantılarını belirler. Varsayılan olarak, kısayol uzantısı olan .lnk ve sistem geçici dosya uzantısı olan .tmp uzantılı dosyalar yoksayılır.
void SetFilters( bool showAllFilesFilter, params string[] filters )
Dosya uzantılarını filtrelemeye yarar. Eğer showAllFilesFilter’ın değeri true ise, tüm dosya uzantılarını göstermeye yarayan “All Files (.*)” isimli bir filtre de listeye eklenir. Klasör seçme/kaydetme modunda bu filtrelerin bir etkisi yoktur.
void SetFilters( bool showAllFilesFilter, params FileBrowser.Filter[] filters )
Dosya uzantılarını filtrelemenin daha gelişmiş bir yoludur. Bir önceki fonksiyonda her bir dosya uzantısı ayrı bir filtre olurken bu fonksiyonda örneğin .jpeg ve .png uzantılarını tek bir filtrede birleştirmek mümkündür. FileBrowser.Filter objesinin constructor’ı, parametre olarak filtre için bir isim ve ardından filtrelenecek uzantıları alır.
bool SetDefaultFilter( string defaultFilter )
Diyalog açıldığında varsayılan olarak aktif olacak olan filtreyi belirler.
C) Çalışma Zamanı İzinleri (Runtime Permissions) Hakkında
Android 6.0 sürümü itibariyle artık önemli bir Android fonksiyonuna erişmeden önce, çalışma zamanında bu fonksiyona erişim izni istemek zorundayız. FileBrowser fonksiyonları, çalışmadan önce otomatik olarak cihazdaki dosyalara erişim izni isterler ancak dilerseniz kendi başınıza da bu iznin durumunu sorgulayabilir veya izin isteyebilirsiniz.
Permission CheckPermission()
Cihazdaki dosyalara erişim izninin durumunu sorgular ve bir FileBrowser.Permission enum‘u döndürür. Eğer dosyalara erişim iznimiz varsa, bu enum’un değeri Permission.Granted olurken eğer henüz iznimiz yoksa Permission.ShouldAsk olur. Eğer kullanıcı karşısına gelen izin ekranını “Bir daha sorma” seçili bir şekilde reddederse veya kullanıcının cihazında aktif olan bir ebeveyn kontrol sistemi bu iznin verilmesini engelliyorsa, Permission.Denied döndürülür. Bu durumda kullanıcı izni cihazın ayarlar menüsünden elle vermek zorundadır.
Eğer dosyalara erişim iznimiz yoksa, diyalog güvenlik sebebiyle çoğu klasörün içini gösteremeyecektir.
Permission RequestPermission()
Dosyalara erişim izni ister ve sonucu bir FileBrowser.Permission enum’unda döndürür. Diyalog göstermeye yarayan fonksiyonlar, bu fonksiyonu otomatik olarak çağırırlar.
D) FileBrowserHelpers Fonksiyonları Hakkında
Bu plugin, Android 10 ve üzerinde Storage Access Framework (SAF) kullanmaktadır çünkü Android işletim sistemi bu sürümlerde dosya sistemine normal erişimi kısıtlamıştır. SAF sisteminden döndürülen konumlar, aşina olduğumuz “C:\Bir Klasör\Bir dosya.txt” şeklindeki konumlardan farklıdır (örnek: content://com.android.externalstorage.documents/tree/primary%3A/document/primary%3APictures). Bu SAF konumları File.Copy veya File.ReadAllBytes gibi normal File fonksiyonları ile çalışmazlar. Eğer oyununuzu Android’e çıkarmayacaksanız bu önemli değil. Aksi taktirde, File işlemlerinizin Android dahil tüm işletim sistemlerinde çalışması için FileBrowserHelpers sınıfının fonksiyonlarını kullanmalısınız (yani File.ReadAllBytes yerine FileBrowserHelpers.ReadAllBytes gibi). Tüm FileBrowserHelpers fonksiyonlarını şu sayfanın sonunda bulabilirsiniz: https://github.com/yasirkula/UnitySimpleFileBrowser/
E) Sıkça Sorulan Sorular
- Android’e build alırken “error: attribute android:requestLegacyExternalStorage not found” hatası alıyorum
AndroidManifest.xml‘deki android:requestLegacyExternalStorage satırı, Android 10’da cihazın dosya sistemine tam erişim sağlar ve Storage Access Framework’ün Android 10 yerine Android 11 ve üzerinde devreye girmesini sağlar. Ancak bu satırın çalışması için, Android SDK’inizde minimum SDK 29‘un kurulu olması gerekmektedir. Eğer SDK 29 veya üzerini kurmanız kesinlikle mümkün değilse, plugin’in SimpleFileBrowser.aar dosyasını WinRAR veya 7-Zip ile açıp AndroidManifest.xml dosyasına çift tıkladıktan sonra, <application ... /> satırını silmeli ve ardından önce dosyayı, sonra da arşivi kaydetmelisiniz.
- Android’de dosya diyaloğu göstermek istediğimde Logcat’te “java.lang.ClassNotFoundException: com.yasirkula.unity.FileBrowserPermissionReceiver” hatası alıyorum
Projeniz ProGuard kullanıyor olabilir. Bu durumda, Player Settings‘ten User Proguard File seçeneğini açıp, oluşan dosyaya şu satırı ekleyin: -keep class com.yasirkula.unity.* { *; }
- Android 10 ve üzerinde hiçbir dosya göremiyorum
Bu Android sürümlerinde Storage Access Framework kullanıldığı için sistem biraz farklı çalışmakta. Kullanıcının önce diyalogdaki Browse… butonuna tıklayıp ardından hangi klasörün içeriğini gezmek istiyorsa o klasörü seçmesi gerekiyor.
Örnek Kod
using UnityEngine;
using System.Collections;
using System.IO;
using SimpleFileBrowser;
public class FileBrowserTest : MonoBehaviour
{
// Not1: FileBrowser'un döndürdüğü konumların sonunda '\' karakteri yer almaz
// Not2: FileBrowser tek seferde sadece 1 diyalog gösterebilir
void Start()
{
// Filtreleri belirle (opsiyonel)
// Eğer filtreler oyun esnasında hep aynı kalacaksa, filtreleri her seferinde
// tekrar tekrar belirlemek yerine sadece bir kere belirlemek yeterlidir
FileBrowser.SetFilters( true, new FileBrowser.Filter( "Resimler", ".jpg", ".png" ), new FileBrowser.Filter( "Metin Dosyaları", ".txt", ".pdf" ) );
// Varsayılan filtreyi belirle (opsiyonel)
// Eğer varsayılan filtre başarıyla belirlendiyse fonksiyon true döndürür
// Bu örnekte, varsayılan filtre olarak .jpg'i tutan "Resimler"i belirle
FileBrowser.SetDefaultFilter( ".jpg" );
// Yoksayılacak dosya uzantılarını belirle (opsiyonel) (varsayılan olarak .lnk ve .tmp uzantılı dosyalar yoksayılır)
// Bu fonksiyonu çağırırsanız, siz elle eklemediğiniz müddetçe .lnk ve .tmp uzantıları artık yoksayılmaz
FileBrowser.SetExcludedExtensions( ".lnk", ".tmp", ".zip", ".rar", ".exe" );
// Yeni bir hızlı erişim klasörü ekle (opsiyonel) (eğer işlem başarılı olursa true döndürülür)
// Bir hızlı erişim klasörünü sadece bir kere eklemek yeterlidir
// İsim: Kullanıcılar
// Konum: C:\Users
// İkon: varsayılan (klasör ikonu)
FileBrowser.AddQuickLink( "Kullanıcılar", "C:\\Users", null );
// Dosya kaydetme diyaloğu göster
// onSuccess: null, bir şey yapma (bir başka deyişle, bu amaçsız bir diyalog)
// onCancel: null, bir şey yapma
// Kaydetme modu: sadece dosyalar, Birden çok dosya seçebilme: kapalı (false)
// İlk konum: "C:\", Varsayılan dosya ismi: "Resim.png"
// Başlık: "Farklı Kaydet", Kaydet butonu yazısı: "Kaydet"
// FileBrowser.ShowSaveDialog( null, null, FileBrowser.PickMode.Files, false, "C:\\", "Resim.png", "Farklı Kaydet", "Kaydet" );
// Klasör seçme diyaloğu göster
// onSuccess: klasörün konumunu konsola yazdır
// onCancel: konsola "İptal edildi" yazdır
// Dosya seçme modu: sadece klasörler, Birden çok klasör seçebilme: kapalı (false)
// İlk konum: varsayılan (Belgelerim), Varsayılan dosya ismi: boş
// Başlık: "Klasör Seç", Seç butonu yazısı: "Seç"
// FileBrowser.ShowLoadDialog( ( konum ) => { Debug.Log( "Seçilen klasör: " + konum[0] ); },
// () => { Debug.Log( "İptal edildi" ); },
// FileBrowser.PickMode.Folders, false, null, null, "Klasör Seç", "Seç" );
// Coroutine örneğini çalıştır
StartCoroutine( DosyaVeKlasorSecmeDiyaloguGosterCoroutine() );
}
IEnumerator DosyaVeKlasorSecmeDiyaloguGosterCoroutine()
{
// Dosya ve klasör seçme diyaloğu göster ve kullanıcının diyaloğu kapatmasını bekle
// Dosya seçme modu: hem dosyalar hem klasörler, Birden çok dosya/klasör seçebilme: açık (true)
// İlk konum: varsayılan (Belgelerim), Varsayılan dosya ismi: boş
// Başlık: "Yüklenecek Dosya ve Klasörleri Seçin", Seç butonu yazısı: "Yükle"
yield return FileBrowser.WaitForLoadDialog( FileBrowser.PickMode.FilesAndFolders, true, null, null, "Yüklenecek Dosya ve Klasörleri Seçin", "Yükle" );
// Diyalog kapatıldı
// Konsola, kullanıcının en az 1 dosya ve/veya klasör seçip seçmediğini yazdır (FileBrowser.Success)
Debug.Log( FileBrowser.Success );
if( FileBrowser.Success )
{
// Seçilen dosya ve/veya klasör(ler)in konumunu da yazır (FileBrowser.Result) (eğer FileBrowser.Success false ise, değeri null'dır)
for( int i = 0; i < FileBrowser.Result.Length; i++ )
Debug.Log( FileBrowser.Result[i] );
// Seçilen ilk dosyanın byte'larını FileBrowserHelpers vasıtasıyla oku
// File.ReadAllBytes'in aksine, bu fonksiyon Android 10 ve üzerinde de çalışır
byte[] bytes = FileBrowserHelpers.ReadBytesFromFile( FileBrowser.Result[0] );
// Veya, ilk dosyayı persistentDataPath konumuna kopyala
string hedefKonum = Path.Combine( Application.persistentDataPath, FileBrowserHelpers.GetFilename( FileBrowser.Result[0] ) );
FileBrowserHelpers.CopyFile( FileBrowser.Result[0], hedefKonum );
}
}
}
Sonraki derslerde görüşmek dileğiyle!