Benvenuto Visitatore(Log In|Registrati)

2 Pagine V  < 1 2  
Reply to this topicStart new topic
> Unity tips, il topic dove si postano risoluzioni a problemi quotidiani
Lief
messaggio16 Apr 2019, 23:50
Messaggio #26





Gruppo: Gamer
Messaggi: 498
Iscritto il: 29 November 16
Utente Nr.: 21.351
BGE Deus Ex 1
Playing Fallout New Vegas
SO Altro




Ciao, non ho molta esperienza con i particellari quindi direi che in questo caso sei tu ad avere più esperienza.
Però la tua domanda mi ha incuriosito e alla fin fine ho voluto fare una breve ricerca su youtube (dove di solito prendo ispirazione se non ho idea di come partire):


Prova a dare un'occhiata a questo, se è quel che vuoi magari potresti poi fare anche tu un breve tips per suggerire come rendere la procedura più semplice e a cosa è necessario stare attenti. In fondo questo topic nasce apposta per dare a tutti l'opportunità di imparare e di poter condividere le proprie conoscenze (anzi, se hai altro che vuoi condividere ti invito a farlo, io sono interessato ad approfondire ancora le mie conoscenze in unity che non sono ancora così complete (ad esempio il nuovo Entity System Component non l'ho quasi utilizzato).

Di base penso che quel breve tutorial usi la tua seconda idea (e personalmente parlando sono d'accordo... userei i particellari per sangue, esplosioni, portali ecc... non per le impronte). Dopo aver instanziato un'immagine dell'impronta corretta alla distanza corretta la fa distruggere dopo X secondi. Semplice ed efficace.

Messaggio modificato da Lief il 16 Apr 2019, 23:58
 
Lief
messaggio28 Jul 2019, 06:59
Messaggio #27





Gruppo: Gamer
Messaggi: 498
Iscritto il: 29 November 16
Utente Nr.: 21.351
BGE Deus Ex 1
Playing Fallout New Vegas
SO Altro




Piccolo aggiornamento: Ho ripreso finalmente a lavorare sul gioco (approfitto del fatto che sto cambiando lavoro e inizio tra un mesetto dall'altra parte).
Per l'occasione un brevissimo tip

Come gestire una camera per i dialoghi: posizione e funzionamento di base
Più che la codifica vera e propria (che è banale) mi concentrerò sul come fare questa tipologia di camera (che a prima vista potrebbe rivelarsi complessa).
L'idea di base è:
- Ogni volta che si parla ad un NPC la camera deve spostarsi da dove si trova normalmente (dietro il personaggio) ad un primo piano del personaggio che sta parlando.
- A conversazione conclusa la camera deve tornare a funzionare normalmente
- Durante la conversazione non dev'essere possibile aprire il menù di salvataggio e il nostro personaggio non deve potersi muovere.
- Se l'NPC fa una domanda la camera deve fare un primo piano del nostro personaggio.

Il primo punto è quello che ha più mi ha messo alla prova, la prima idea è stata: faccio muovere la camera alla posizione del personaggio poi la sposto di un certo "tot" indietro e la ruoto di un certo tot in modo che faccia vedere il personaggio e la alzo in modo che faccia vedere la faccia del personaggio. Inutile dire che quest'idea è praticamente irrealizzabile... pensare di prevedere tutte le 360 rotazioni previste, un'altezza diversa per ogni personaggio e un diverso "indietro" per ogni NPC è roba da perderci il sonno.
La soluzione è quasi banale, creare un Empty Game Object all'interno di ogni NPC e posizionarlo nella posizione in cui si vuole la camera. Questo funziona perché, anche se necessario creare un punto di ancoraggio della camera per ogni NPC, lo script da scrivere rimane unico (a differenza dell'idea precedente).

La camera ovviamente deve spostarsi solo quando l'NPC parla e spostarsi nuovamente quando ha finito di parlare... come fare questa cosa?
L'idea di base è altrettanto semplice: usare il riferimento alla camera principale Camera.main per trovare lo script principale e disabilitarlo quando si inizia a parlare, riabilitarlo quando si ha finito.

Discorso simile per disabilitare l'apertura del menù di salvataggio e lo script di movimento.

Per il punto finale basta riconoscere le domande e spostare la camera al punto di ancoraggio nel personaggio e viceversa.

4 cose da tenere in mente:
- Controllare se il personaggio tocca terra, disabilitando lo script di movimento se si parla in aria il personaggio continuerà a cadere fino a fine conversazione.
- Mettere a zero la velocità del personaggio e attivare per un momento kinematic del rigidbody, questo eviterà che il nostro personaggio continui a camminare per inerzia se arriviamo di corsa.
- Disabilitare questo script se il menù di salvataggio è già aperto.
- La camera isometrica richiede l'uso dell'orthographic, esso va però disabilitato quando si usa la dialog camera e riattivato dopo. Si fa con un semplice booleano (se ortografica, set false, nel bool metto valore di orthographic del Camera.main, se variabile true disattivo orthographic).

Per oggi è tutto

Messaggio modificato da Lief il 28 Jul 2019, 07:23
 
Lief
messaggio28 Jul 2019, 13:46
Messaggio #28





Gruppo: Gamer
Messaggi: 498
Iscritto il: 29 November 16
Utente Nr.: 21.351
BGE Deus Ex 1
Playing Fallout New Vegas
SO Altro




Avevo detto di aver finito per oggi ma ho fatto una piccola modifica al sistema di Input dopo aver studiato un po' come funziona il nuovo sistema di input.

Prima di tutto la classe:
VostraClasseInput

Ora contiene i metodi
GetKey GetKeyDown GetKeyUp GetAxis
che hanno come return i valori della classe di Input attuale (esempio return Input.GetKeyDown(code);)
In questa maniera cambiando il sistema di input con il nuovo InputSystem l'unica classe da modificare sarà la VostraClasseInput (dove avrete usato il codice potrete mantenere quel che avete scritto).

In secondo luogo per quanto riguarda il nuovo sistema di Input:
- Al momento non supporta decentemente il rebinding (ma è ancora in sviluppo). Non è possibile leggere un input generico e quindi rimapparlo. Non è quindi utilizzabile in produzione.
- L'equivalente di GetKey/Down/Up esiste. Usa un differente Enum chiamato Key al posto di KeyCode (non so se sia equivalente ma penso di no visto che il vecchio sistema prevedeva anche i KeyCode dei Gamepad). Probabilmente sarà semplicemente necessario aggiungere uno switch case per decidere se l'enum è Keyboard o Gamepad e, nel peggiore dei casi, copiare il KeyCode Enum attuale e importarlo per la conversione dei vecchi KeyCode nei nuovi Key. Sperando ovviamente che ci sia modo di distinguere i Key per tastiera da quelli derivati da Gamepad (se così non fosse il nuovo sistema non avrebbe senso quindi ho fiducia che sarà possibile).
- Per gli Axis basterà un Handler per ogni coppia di assi mappata in Unity che varierà x e y di una singola variabile. Con un piccolo switch case si potrà capire se restituire x o y a seconda degli assi mappati in precedenza (l'alternativa sarebbe eliminare completamente gli assi mappati... in precedenza infatti si doveva controllare asse x per mouse, asse x per joystick destro, sinistro ecc.... con il nuovo sistema ogni asse avrà un Handler differente ma cambierà il valore di una singola variabile). Potete farvi un'idea più precisa qui:

e qui:
New Input System temp Docs migration

Prossimo messaggio code flow di Dropbox

Messaggio modificato da Lief il 28 Jul 2019, 13:49
 
Lief
messaggio28 Jul 2019, 15:01
Messaggio #29





Gruppo: Gamer
Messaggi: 498
Iscritto il: 29 November 16
Utente Nr.: 21.351
BGE Deus Ex 1
Playing Fallout New Vegas
SO Altro




Ora finalmente vediamo cosa nasconde dietro le quinte la libreria di google.

Prima di tutto, a differenza di google non possiamo autorizzare tutte le porte di localhost con una sola linea. Il mio consiglio è di creare una lista di un centinaio di porte massimo e usare quelle.
int count = 0;
int numtries = portList.Count;
while (numtries-- != 0) {
try {
var listener = new TcpListener(IPAddress.Loopback, portList[count]);
listener.Start();
port = ((IPEndPoint) listener.LocalEndpoint).Port;
listener.Stop();
} catch (Exception e) {
Debug.Log(e);
count++;
continue;
}
}
Se va in eccezione la porta si continua con il ciclo e si prova con la porta successiva, con 100 porte a disposizione non ci saranno mai problemi a trovarne una.

Usate la vostra porta scelta su localhost per aprire il link di autorizzazione oauth2 code di dropbox con redirect link sul link in localhost sulla porta prescelta.
Ascoltando quel link il vostro gioco potrà attendere che l'utente faccia l'accesso via browser a dropbox e che, dopo l'accesso, venga reindirizzato proprio sul link che state ascoltando.
Al posto di vostroclientid sarà necessario mettere l'informazione trovata nel vostro account da sviluppatori dropbox.
var httpListener = new HttpListener();
httpListener.Prefixes.Add("http://"+ localhost +":" + port + '/');
httpListener.Start();
Application.OpenURL("https://www.dropbox.com/oauth2/authorize?response_type=code&client_id=vostroclientid&redirect_uri=http://" + localhost + ":" + port + '/');
httpListener.BeginGetContext(HandleHttpListenerCallback, httpListener);

l'handler HandleHttpListenerCallback verrà eseguito quando l'utente avrà finito di fare l'accesso e verrà reindirizzato sul link localhost sulla porta che state ascoltando.
oltre a quello avrà un campo ?code=certonumero che corrisponde al codice di autorizzazione.

Prendete var response = context.Response; e token = context.Request.QueryString.Get("code"); il primo vi dirà se è andato tutto a buon fine, il secondo vi darà il codice di autorizzazione se esiste.
In ogni caso prendete il risultato
var httpListener = (HttpListener) result.AsyncState;
var context = httpListener.EndGetContext(result);

e mandatelo a
unitySyncContext.Send(HandleHttpListenerCallbackOnUnityThread, result);

/il codice completo è una cosa del genere
private void HandleHttpListenerCallback(IAsyncResult result) {
var httpListener = (HttpListener) result.AsyncState;
var context = httpListener.EndGetContext(result);

// la risposta può essere scritta nel browser per informare l'utente
var response = context.Response;
var responseString = "<html><h1>Puoi tornare all'app</h1></html>";
var buffer = Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
var responseOutput = response.OutputStream;
responseOutput.Write(buffer, 0, buffer.Length);
responseOutput.Close();
httpListener.Close();

// Extract the authorization code.
token = context.Request.QueryString.Get("code");
if (token == null) {
return;
}
unitySyncContext.Send(HandleHttpListenerCallbackOnUnityThread, result);
}

nell'Handler HandleHttpListenerCallbackOnUnityThread usate una Coroutine
StartCoroutine(Dropbox());

per richiamare un metodo dove userete il token ricevuto per concludere l'autorizzazione

Dictionary<string, string> form = new Dictionary<string, string>();
form.Add("code", token);
form.Add("grant_type", "authorization_code");
form.Add("client_id", "vostroclientid");
form.Add("client_secret", "vostrosecretid");
form.Add("redirect_uri", "http://" + localhost + ":" + port + '/');
using (UnityWebRequest www = UnityWebRequest.Post("https://api.dropboxapi.com/oauth2/token", form)) {

www.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded");

yield return www.SendWebRequest();

if (www.error != null) {
Debug.Log(www.error + " " + www.downloadHandler.text);
} else {
//scrivete la risposta dell'autorizzazione dove volete per poterla utilizzare dopo per esempio in un json
tw.Close();
}
}

Il json di risposta di dropbox si deserializza con questi campi:
[SerializeField] string access_token;
[SerializeField] string token_type;
[SerializeField] string uid;
[SerializeField] string account_id;


Questa prima autorizzazione ci permette di ottenere l'access_token che verrà usato per tutte le altre chiamate. Più in particolare:

Il codice di base che ci consente di capire se siamo ancora loggati (ossia se l'access_token è ancora valido) ad esempio facendo una lista delle cartelle
byte[] myData = Encoding.UTF8.GetBytes("{\"path\": \"\",\"recursive\": false,\"include_media_info\": false,\"include_deleted\": false,\"include_has_explicit_shared_members\": false,\"include_mounted_folders\": true}");
//facciamo una Put che trasformiamo poi in Post
using (UnityWebRequest www = UnityWebRequest.Put("https://api.dropboxapi.com/2/files/list_folder", myData)) {
www.method = "POST";
//usiamo l'access token
www.SetRequestHeader("Authorization", "Bearer " + response.Access_token);
www.SetRequestHeader("Content-Type", "application/json");
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError) {
Debug.Log(www.downloadHandler.text);
} else {
//deserializziamo la risposta
entries = DropboxResponseEntries.SaveFromString(www.downloadHandler.text);
}
}

Per deserializzare abbiamo un oggetto composto da
[SerializeField] DropboxResponseEntry[] entries;
[SerializeField] string cursor;
[SerializeField] bool has_more;

e ovviamente l'oggetto figlio
//non potendo usare .tag come nome di variabile usiamo FormerlySerializedAs per associare nome della variabile al nome nel json
[FormerlySerializedAs(".tag")]
[SerializeField] string tag;
[SerializeField] string name;
[SerializeField] string path_lower;
[SerializeField] string path_display;
[SerializeField] string id;
[SerializeField] string client_modified;
[SerializeField] string server_modified;
[SerializeField] string rev;
[SerializeField] int size;
[SerializeField] string content_hash;


Possiamo poi usare entries (il risultato della deserializzazione) per il download di tutti i file di salvataggio:

foreach (DropboxResponseEntry entry in entries.Entries) {
if (entry.Name.EndsWith(".json")) {
using (UnityWebRequest www = UnityWebRequest.Get("https://content.dropboxapi.com/2/files/download")) {
www.SetRequestHeader("Authorization", "Bearer " + response.Access_token);
www.SetRequestHeader("Dropbox-API-Arg", "{\"path\": \"" + "/" + entry.Name + "\"}");
www.SetRequestHeader("Content-Type", "");
yield return www.SendWebRequest();
if (www.error != null) {
} else {
//scrivo il nuovo file di salvataggio nella cartella Saves all'interno del persistentDataPath, ad esempio
TextWriter tw = new StreamWriter(Application.persistentDataPath + "/Saves/" + entry.Name);
tw.Write(www.downloadHandler.text);
tw.Close();
}
}
}
}

Per l'upload invece:
//lista di nomi di file con estensione .json all'interno della cartella selezionata
List<string> salvataggiFile = dirInf.GetFiles().OrderByDescending(f => f.LastWriteTime).Where(f => f.Extension == ".json").Select(f => f.Name).ToList();
//per ogni salvataggio
foreach (var salvataggio in salvataggiFile) {
//converto il salvataggio con quel nome nella cartella Saves nel persistentDataPath (esempio) in un array di byte
byte[] myData = Encoding.UTF8.GetBytes(File.ReadAllText(Application.persistentDataPath + "/Saves/" + salvataggio));
//uso una post per fare l'upload dell'array di byte
using (UnityWebRequest www = UnityWebRequest.Post("https://content.dropboxapi.com/2/files/upload", "")) {
var upload = new UploadHandlerRaw(myData);
www.uploadHandler = upload;
www.SetRequestHeader("Authorization", "Bearer " + response.Access_token);
www.SetRequestHeader("Dropbox-API-Arg", "{\"path\": \"/" + salvataggio + "\",\"mode\": \"overwrite\",\"autorename\": true,\"mute\": false}");
www.SetRequestHeader("Content-Type", "application/octet-stream");
yield return www.SendWebRequest();
}
}

Il codice non è semplicissimo ma una volta studiato e utilizzato per bene diventa abbastanza chiaro.
Con questo possiamo fare upload e download di salvataggi sia da google sia da dropbox.
Cosa cambia per OneDrive?
In realtà veramente poco:
- In One drive la lista di porte è ancora più piccola (non più di una decina)
- Su mobile non potendo utilizzare localhost (né l'equivalente 127.0.0.1) dovremo usare un sito custom che reindirizza su localhost (o su 127.0.0.1) conservando il code di autorizzazione. Inoltre dovrà essere su https (fuori da localhost oauth 2 richiede giustamente https). Si fa facilmente con una semplice pagina altervista gratuita, wordpress e un semplice piccolo codice.
- Ovviamente cambiano le response da deserializzare e i link a cui fare le stesse chiamate (autorizzazione primaria, secondaria, get e put per download upload e check).

Ma quel che cambia rompe parecchio le balle a noi programmatori visto che la documentazione di One Drive è molto più confusionaria di quella di Dropbox, oltretutto l'autorizzazione che si ottiene da dropbox dura molto di più di quella di One Drive.
Detto questo funziona quindi la prossima volta vedremo le differenze, poi ci dedicheremo a Mega, servizio che necessita di una libreria esterna ma che non usa oauth 2 e che è quindi un'alternativa per fare un sistema che non necessita di uscire dal gioco (le autorizzazioni oauth 2, per quanto sicure, sono scomode perché richiedono di usare il browser).

Alla prossima (stavolta per davvero).
 
Lief
messaggio29 Jul 2019, 19:08
Messaggio #30





Gruppo: Gamer
Messaggi: 498
Iscritto il: 29 November 16
Utente Nr.: 21.351
BGE Deus Ex 1
Playing Fallout New Vegas
SO Altro




Piccolo tip:
Non è possibile assegnare a runtime il target di uno UnityEvent EventTrigger (esempio: ho due GameObject con lo stesso script che dovrebbero usare lo stesso metodo nell'evento PointerDown. Non posso fare in modo di assegnare uno o l'altro oggetto a seconda di alcune condizioni (esempio OnTriggerEnter)).

La soluzione può essere usare l'interfaccia IPointerDownHandler (o IPointerUpHandler o IDragHandler a seconda dell'evento) e implementare il metodo corrispondente:
public virtual void OnPointerDown(PointerEventData ped) ad esempio.

A quel punto si può mettere nella classe del bottone un riferimento al GameObject da utilizzare (vuoto) public oppure con un Setter

Nello script del nostro GameObject
public void OnTriggerEnter(Collider other) {
riferimentoAlBottone.SetGameObjectTarget = this.gameObject;
}

public void OnTriggerExit(Collider other) {
riferimentoAlBottone.SetGameObjectTarget = null;
}

Nell'OnPointerDown sarà quindi possibile chiamare tutti i metodi del nostro gameobject corrente.

In linea di massima è un buon workaround (si assegna l'oggetto reale su cui usare lo stesso identico metodo a runtime mantenendo un riferimento del bottone nello script del nostro oggetto), ma non sarebbe stato male avere un metodo tipo:
SetPersistentTarget(index, gameObject);
così come si può già usare
SetPersistentListenerState(index, nuovoStato);
per attivare/disattivare eventi a runtime.
 
Lief
messaggio1 Aug 2019, 15:01
Messaggio #31





Gruppo: Gamer
Messaggi: 498
Iscritto il: 29 November 16
Utente Nr.: 21.351
BGE Deus Ex 1
Playing Fallout New Vegas
SO Altro




E ora diamo un'occhiata alle API di One Drive.

Come anticipato OneDrive può conservare solo un numero molto limitato di porte, quindi possiamo semplificare il codice con una semplice lista/array di porte.

int[] portsList = new int[] { 52342, 52987 ecc... };

int count = 0;
int numtries = portsList.Length;
while (numtries-- != 0) {
try {
var listener = new TcpListener(IPAddress.Loopback, portsList[count]);
listener.Start();
port = ((IPEndPoint) listener.LocalEndpoint).Port;
listener.Stop();
} catch (Exception e) {
count++;
continue;
}
}

Semplice e indolore.

Su android però visto che avremo un redirect fisso dobbiamo fissare una porta unica

if (Application.platform == RuntimePlatform.Android)
port = 52344;

Sperando che sia libera.

L'open url andrà fatto in questo modo

if (Application.platform == RuntimePlatform.Android)
Application.OpenURL("https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=vostroclientid&response_type=code&redirect_uri=https%3A%2F%2FindirizzoAltervistaVostroBlog.altervista.org%2Findirizzopostsulvostroblog%2F&response_mode=query&scope=user.read%20files.readwrite.appfolder%20files.readwrite%20files.readwrite.all");
else
Application.OpenURL("https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=vostroclientid&response_type=code&redirect_uri=http%3A%2F%2F" + localhost + "%3A" + port + "&response_mode=query&scope=user.read%20files.readwrite.appfolder%20files.readwrite%20files.readwrite.all");
httpListener.BeginGetContext(HandleHttpListenerCallback, httpListener);

Come potete leggere dal codice l'url su one drive è molto più complesso per due ragioni:
1. Dovrete utilizzare %2F e altri caratteri speciali al posto di / e : per l'url di redirect
2. Dovrete già dare diverse autorizzazioni che sul sito nella documentazione non sono chiarissime.
Il vostro client id si ricava sempre nel vostro account online ma a differenza di dropbox raggiungere il link corretto per la registrazione dell'app è un parto quindi ve lo metto qui:
https://portal.azure.com/#blade/Microsoft_A...ationsListBlade

Sarà poi vostra cura dover scoprire come utilizzare il sito, vi posso dire solo che client id lo create in certificates and secrets e gli endpoints li potete mettere in Autentication, i permessi in Permissions (dopo aver creato l'app).

Da qui in poi il resto è tutto uguale tranne nel token:
Dictionary<string, string> form = new Dictionary<string, string>();
form.Add("client_id", vostroclientid);
form.Add("scope", "https://graph.microsoft.com/files.readwrite.all");
form.Add("code", token);
if (Application.platform == RuntimePlatform.Android)
form.Add("redirect_uri", "https://vostroblogsualtervista.altervista.org/indirizzopostblog");
else
form.Add("redirect_uri", "http://" + localhost + ":" + port);
form.Add("grant_type", "authorization_code");
form.Add("client_secret", vostroclientsegreto);
using (UnityWebRequest www = UnityWebRequest.Post("https://login.microsoftonline.com/common/oauth2/v2.0/token", form)) {
www.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded");
yield return www.SendWebRequest();

if (www.error != null) {
} else {
//scrivete l'autorizzazione dove volete ad esempio in un file json
}
}

Molto simile, cambiano in pratica solo gli url e i parametri.

Il codice da usare sul vostro blog altervista fatto con wordpress è decisamente molto semplice (ricordatevi di attivare https prima, altrimenti non potrete mettere l'url tra quelli autorizzati visto che l'autorizzazione senza https funziona solo per localhost, inoltre installate il plugin Insert Headers and Footers per poter scrivere il codice che vi servirà):
<script>
if(window.location.href.startsWith("https://yourblogaddress.altervista.org/blog/yourpostpath/")){
var stringPartUrl = window.location.href.substring(numberofcharinyoururl);
window.location.replace("http://localhostor127.0.0.1:yourport/" + stringPartUrl);
}
</script>

Praticamente numberofcharinyoururl è un numero (esempio 45) che rappresenta il numero di caratteri da eliminare il nuovo url sarà quindi solo la parte aggiunta da one drive ossia "?code=token_di_autorizzazione_alfanumerico"
lo script sostituirà https://yourblogaddress.altervista.org/blog/yourpostpath/ con http://127.0.0.1:52344/?code=token_di_auto...ne_alfanumerico
dove 127.0.0.1 è localhost su android, http quindi resta sicuro (è solo one drive che non lo riconosce come tale per qualche stramba decisione/bug), la porta 52344 è quella fissa che abbiamo deciso di utilizzare su android (può essere una porta qualsiasi purché libera), e "?code=token_di_autorizzazione_alfanumerico" contiene il token che utilizzeremo per fare la chiamata successiva.

La risposta in json di onedrive è serializzabile così:
[SerializeField] string token_type;
[SerializeField] string scope;
[SerializeField] int expires_in;
[SerializeField] int ext_expires_in;
[SerializeField] string access_token;

contiene il token che useremo nelle successive chiamate (chiamato access_token), il tipo (che però non ci serve visto che sappiamo già che è bearer, in quanto scade e qual'è lo scope (ossia le autorizzazioni che abbiamo chiesto). Come per dropbox useremo solo access_token.

L'autorizzazione one drive è scritta in questa maniera:
using (UnityWebRequest www = UnityWebRequest.Get("https://graph.microsoft.com/v1.0/me/drives")) {
www.SetRequestHeader("Authorization", "Bearer " + response.Access_token);
www.SetRequestHeader("Content-Type", "application/json");
yield return www.SendWebRequest();
}
Notare che per il semplice check dell'autorizzazione one drive richiede get
L'upload del file si fa in questo modo:

List<string> salvataggiFile = dirInf.GetFiles().OrderByDescending(f => f.LastWriteTime).Where(f => f.Extension == ".json").Select(f => f.Name).ToList();

foreach (var salvataggio in salvataggiFile) {
byte[] myData = Encoding.UTF8.GetBytes(File.ReadAllText(Application.persistentDataPath + "/Saves/" + salvataggio));
using (UnityWebRequest www = UnityWebRequest.Put("https://graph.microsoft.com/v1.0/me/drive/root:/NOMECARTELLASUCLOUD/" + salvataggio + ":/content", myData)) {
www.SetRequestHeader("Authorization", "Bearer " + response.Access_token);
www.SetRequestHeader("Content-Type", "application/json");
yield return www.SendWebRequest();
}
}

Come possiamo vedere qui per creare la cartella su cloud condivisa basta scriverlo nell'url (dropbox richiede la creazione di una cartella condivisa che di conseguenza non verrà segnata nell'url). Notare anche che OneDrive richiede la put qui

Il download è invece la parte più complessa:
prima bisogna prendere la lista di tutti i file nella cartella scelta
using (UnityWebRequest www = UnityWebRequest.Get("https://graph.microsoft.com/v1.0/me/drive/root:/NOMECARTELLASUCLOUD:/children")) {
www.SetRequestHeader("Authorization", "Bearer " + response.Access_token);
www.SetRequestHeader("Content-Type", "application/json");
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError) {
} else {
values = OneDriveResponseList.SaveFromString(www.downloadHandler.text);
}
}
//poi fare la get di ogni singolo file che ha come estensione json
foreach (var salvataggio in values.Value) {
if (salvataggio.Name.EndsWith(".json")) {
using (UnityWebRequest www = UnityWebRequest.Get("https://graph.microsoft.com/v1.0/me/drive/root:/NOMECARTELLASUCLOUD/" + salvataggio.Name + ":/content")) {
www.SetRequestHeader("Authorization", "Bearer " + response.Access_token);
yield return www.SendWebRequest();

if (www.isNetworkError || www.isHttpError) {
} else {
//scrivo il mio salvataggio in una cartella locale
}
}
}
}

La serializzazione è un'altra cosa che OneDrive fa complicata
OneDriveResponseList ha come variabili:
[FormerlySerializedAs("@odata.context")]
[SerializeField] string context;
[SerializeField] OneDriveResponseValue[] value;

OneDriveResponseValue ha:
[FormerlySerializedAs("@microsoft.graph.downloadUrl")]
[SerializeField] string downloadUrl;
[SerializeField] string createdDateTime;
[SerializeField] string cTag;
[SerializeField] string eTag;
[SerializeField] string id;
[SerializeField] string lastModifiedDateTime;
[SerializeField] string name;
[SerializeField] int size;
[SerializeField] string webUrl;
[SerializeField] OneDriveResponseAppUser createdBy;
[SerializeField] OneDriveResponseAppUser lastModifiedBy;
[SerializeField] OneDriveResponseIdType parentReference;
[SerializeField] OneDriveResponseFile file;
[SerializeField] OneDriveResponseFileSystem fileSystemInfo;

OneDriveResponseAppUser
[SerializeField] OneDriveResponseAU application;
[SerializeField] OneDriveResponseAU user;

OneDriveResponseAU
[SerializeField] string displayName;
[SerializeField] string id;

OneDriveResponseIdType
[SerializeField] string driveId;
[SerializeField] string driveType;
[SerializeField] string id;
[SerializeField] string name;
[SerializeField] string path;

OneDriveResponseFile
[SerializeField] string mimeType;
[SerializeField] OneDriveResponseHash hashes;

OneDriveResponseHash
[SerializeField] string sha1Hash;

OneDriveResponseFileSystem
[SerializeField] string createdDateTime;
[SerializeField] string lastModifiedDateTime;

Come vedete è una risposta molto più elaborata di quella di Dropbox.
È però necessaria per ricavare il Nome del nostro salvataggio.

È tutto, il prossimo messaggio sarà dedicato a Mega (molto più semplice).
 
Lief
messaggio1 Aug 2019, 15:41
Messaggio #32





Gruppo: Gamer
Messaggi: 498
Iscritto il: 29 November 16
Utente Nr.: 21.351
BGE Deus Ex 1
Playing Fallout New Vegas
SO Altro




Ultimo capitolo dedicato ai salvataggi su cloud: Mega.

Per implementare mega è necessario utilizzare il plugin apposito creato per c#
https://gpailler.github.io/MegaApiClient/
A quell'indirizzo trovate praticamente tutto.

Per l'installazione vi consiglio di scaricare semplicemente MegaApiClient.dll versione net46 e metterla dentro una cartella Plugins/lib su Unity. Potrete così importarla nel vostro codice senza problemi.
Ricordatevi inoltre di utilizzare solo una versione di JsonNet-Lite. Quindi se avete importato UnityGoogleDrive sostituite la versione inclusa in quel pacchetto (meno aggiornata) con quella di MegaApiClient (che è comunque compatibile anche con UnityGoogleDrive).
AGGIORNAMENTO 10-08-2019
Quello che scrivo qua sopra per l'installazione è valido solo se si usa come Scripting Backend Mono senza stripping del codice:
https://docs.unity3d.com/Manual/ManagedCodeStripping.html

IL2CPP (il futuro di Unity e al momento l'unico utilizzabile per iOS) necessità però di uno stripping minimo a Low (non si può disabilitare).
Inoltre IL2CPP essendo compilato in anticipo (AOT) non è compatibile con la classe Emit utilizzata da JsonNet di MegaApiClient che utilizza compilazione JIT (just in time ovvero al primo avvio).
Per risolvere questi problemi ho passato 2 notti in bianco e contattato l'autore di MegaApiClient e quello di JsonNet for Unity... ma alla fin fine ho trovato una soluzione funzionante:
1. Scaricate Json .Net for Unity da qui:
https://assetstore.unity.com/packages/tools...for-unity-11347
vi serve solo lo zip con i sorgenti. Apriteli in Visual Studio, aggiungete un riferimento a UnityEngine.dll (dovete fare sfoglia e scegliere la cartella d'installazione di Unity \Editor\Data\Managed).
Cambiate la versione da 8.3 a 10.0.0.0.
Compilate come Release.
Copiate il nuovo dll e xml ottenuto.
Né MegaApiClient, né UnityGoogleDrive dovrebbero lamentarsi.
Questa nuova versione non usando JIT è compatibile con la compilazione AOT, quindi può essere utilizzata con IL2CPP (che appunto trasforma il codice in C++ e quindi compila in codice macchina e non è quindi compatibile con compilazione al primo avvio come l'interprete mono).
2. Create un file link.xml (basta che sia nella cartella Assets o in una sottocartella):
<linker>
<assembly fullname="MegaApiClient"> // the name of the assembly
<type fullname="CG.Web.MegaApiClient.*" preserve="all"/> // excludes all namespaces and classes recursively under MyNamespace
</assembly>
</linker>
Questo file dice a IL2CPP di Unity di non fare stripping del codice della libreria MegaApiClient nel namespace CG.Web.MegaApiClient e in tutti i figli in maniera ricorsiva. Lo stripping del codice elimina infatti alcuni getter utilizzati da json .net per la serializzazione/deserializzazione il che manda a quel paese il funzionamento di MegaApiClient.
Notare che ora, se selezioniamo .NET Standard 2.0 in Unity e usassimo MegaApiClient .Net 2 il codice funziona.
3. Per rendere compatibile il tutto con .Net 4.x al momento è necessario scaricare il codice sorgente di MegaApiClient:
https://github.com/gpailler/MegaApiClient
Aprire il progetto (potete eliminare quello di test) in visual studio

Compilate per release e copiate il nuovo dll e xml (non è più necessario fare modifiche al codice perché la mia modifica è stata accettata dall'autore di megaapiclient. inoltre è probabile che alla prossima release non dovrete neanche più compilare il codice).

Spiegato così sembra semplice ma debuggare il tutto è stato un vero e proprio inferno (anche perché non funzionava solo su android quindi ho dovuto usare adb che però non legge i debug ma solo i messaggi lanciati dalle eccezioni). Oltretutto su codice non mio. Ma gli autori dei plugin gratuiti e open source hanno fatto di tutto per aiutarmi. Se avete problemi a fare questa procedura quindi contattatemi pure.
FINE AGGIORNAMENTO 10-08-2019

Ok tempo di codificare.
importate le librerie

using CG.Web.MegaApiClient;
using static CG.Web.MegaApiClient.MegaApiClient;

fate il login
var client = new MegaApiClient();
//richiede email e password
AuthInfos auth = GenerateAuthInfos(potresteprenderequestocampodauncampoInputfield.GetComponent<InputField>().text, stessacosaperlapasswordmagariconl'opzionepasswordattivainmodochevenganomostratisoloasterischi.GetComponent<InputField>().text);
//serializziamo la risposta in json
/*
[SerializeField] string email;
[SerializeField] string hash;
[SerializeField] byte[] passwordAesKey;*/
MegaResponse authorization = new MegaResponse();
authorization.Email = auth.Email;
authorization.Hash = auth.Hash;
authorization.PasswordAesKey = auth.PasswordAesKey;

//richiamiamo un metodo per aspettare la risposta
LoginToMega(client, auth);


Il nostro metodo sarà asincrono (in modo che durante il login l'utente non veda il gioco bloccato)
private async void LoginToMega(MegaApiClient client, AuthInfos auth) {
try {
//prova e attende il login asincrono
await client.LoginAsync(auth);
} catch (ApiException e) {
}
//poi fa il log out
await client.LogoutAsync();
}

In pratica se abbiamo avuto successo le credenziali inserite sono corrette.
Il file json che abbiamo salvato si utilizzerà nello stesso modo per il check del login iniziale (se le nostre credenziali sono ancora valide oppure la sessione è scaduta si vedrà qui).


Per l'upload (sempre asincrono)
private async void UploadSavesMega() {
var client = new MegaApiClient();
//prendiamo i dati salvati e dopo aver fatto il log in
MegaApiClient.AuthInfos authInfos = new MegaApiClient.AuthInfos(response.Email, response.Hash, response.PasswordAesKey);
await client.LoginAsync(authInfos);
//prendiamo tutti i nodi (ossia tutte le cartelle
IEnumerable<INode> nodes = await client.GetNodesAsync();
bool cartellaTrovata = false;
//controlliamo tutti i nodi (ossia tutte le cartelle
foreach (var child in nodes)
//e se troviamo una cartella con nome uguale a quello della nostra cartella che vogliamo riempire di salvataggi
if (child != null && child.Name != null && child.Name.Equals("NOMECARTELLASUCLOUD")) {
cartellaTrovata = true;
//riempiamo la cartella
UploadSaveFilesMega(client, child);
return;
}
//se no creiamo la cartella prima di riempirla
if (!cartellaTrovata) {
INode root = nodes.Single(x => x.Type == NodeType.Root);
INode myFolder = await client.CreateFolderAsync("NOMECARTELLASUCLOUD", root);
UploadSaveFilesMega(client, myFolder);
}

//questo è il metodo asincrono che riempe effettivamente la cartella (già presente o creata)
private async void UploadSaveFilesMega(MegaApiClient client, INode folder) {
List<string> salvataggiFile = dirInf.GetFiles().OrderByDescending(f => f.LastWriteTime).Where(f => f.Extension == ".json").Select(f => f.Name).ToList();

foreach (var salvataggio in salvataggiFile) {
//prima cosa cicliamo sui salvataggi in locale e controlliamo se sono su cloud
IEnumerable<INode> nodes = await client.GetNodesAsync();
foreach (var child in nodes)
//se sono su cloud cancelliamo i vecchi salvataggi
if (child != null && child.Name != null && child.Name.Equals(salvataggio))
await client.DeleteAsync(child, false);

string path = Path.GetFullPath("pathlocalealsalvataggio" + salvataggio);
IProgress<double> progress = new Progress<double>();
//upload del file asincrono sulla cartella che abbiamo deciso del file al path locale che abbiamo deciso
INode uploadedFile = await client.UploadFileAsync(@path, folder, progress);
//possiamo anche rinominare il file dopo averlo uploadato (possiamo anche farlo prima ma poi dovremmo cambiare quello locale)
await client.RenameAsync(uploadedFile, salvataggio.Substring(0, salvataggio.Length - 5) + "-to-mega.json");
}
await client.LogoutAsync();
}

Nota bene, a differenza di dropbox e one drive e google drive, mega non ha il replace quindi dobbiamo prima cancellare i file già presenti, poi possiamo mettere quelli nuovi.
Nota bene 2, altra differenza sta nel fatto che mega accetta come parametro un path al file di cui fare l'upload, non un'array di byte, quindi il nome sarà quello originario, per questo dobbiamo fare (se vogliamo) il rename dopo (o prima).

Il download è un po' più semplice in questo caso:
var client = new MegaApiClient();
//login
MegaApiClient.AuthInfos authInfos = new MegaApiClient.AuthInfos(response.Email, response.Hash, response.PasswordAesKey);
await client.LoginAsync(authInfos);
//prendo tutti i nodi
IEnumerable<INode> nodes = await client.GetNodesAsync();

foreach (var child in nodes)
//nella cartella dei salvataggi
if (child != null && child.Name != null && child.Name.Equals("NOMECARTELLASUCLOUD")) {
DownloadSaveFilesMega(client, child);
return;
}

private async void DownloadSaveFilesMega(MegaApiClient client, INode folder) {
IEnumerable<INode> nodes = await client.GetNodesAsync();
foreach (var child in nodes)
//prendo tutti i file che finiscono in json in cui l'id del parent (ossia della cartella che abbiamo passato come parametro) è uguale alla cartella che stiamo analizzando
//è come guardare il nome, solo più preciso perché l'id delle cartelle create su mega è unico
if (child != null && child.Name != null && child.Name.EndsWith(".json") && child.ParentId == folder.Id) {
//creiamo un link per il download
Uri fileLink = await client.GetDownloadLinkAsync(child);
IProgress<double> progress = new Progress<double>();
//faccio il download e lo salvo in una cartella locale con il nome che mi pare
await client.DownloadFileAsync(fileLink, cartellalocale + nuovoNome, progress);
}
}


Con questo abbiamo concluso la sezione sui salvataggi su Cloud. Mi auguro che sia utile a qualcuno.
Il prossimo argomento devo ancora deciderlo ma visto che mi occuperò di quest e inventario oppure combat system potrebbe essere qualcosa di collegato.
Nell'attesa auguro a tutti buon coding e spero qualcuno abbia idee da condividere.

Messaggio modificato da Lief il 12 Aug 2019, 16:16
 

2 Pagine V  < 1 2
Reply to this topicStart new topic
1 utenti stanno leggendo questa discussione (1 visitatori e 0 utenti anonimi)
0 utenti:

 

Modalità di visualizzazione: Normale · Passa a: Lineare · Passa a: Outline


Versione Lo-Fi Oggi è il: 23rd October 2019 - 03:18