I'm trying to send a POST request in Unity, but I can't do that as the function that is supposed to do that is skipped, I'm not sure why.
I have added logs to have an idea of what happens and "(post json) called" never appears even though the text saying that has gone past the function ((send game played): after postjson) appears in the logs.
private static void SendGamePlayed() {
int incrementedGamePlayed = GetGamePlayed () + 1;
EventObject anEvent = CreateEvent(EVENT_GAME_PLAYED, incrementedGamePlayed, null, null);
AbcEvent aAbcEvent = new AbcEvent (bundleID, appToken, anEvent);
string json = JsonUtility.ToJson (aAbcEvent);
// test
Debug.Log("(send game played): " + json);
PostJSON (json, EVENT_ROUTE);
Debug.Log ("(send game played): after postjson");
SetGamePlayed (incrementedGamePlayed);
}
private static IEnumerator PostJSON(string jsonString, string route) {
// test
Debug.Log("(post json) called");
string url = SERVER_URL + route;
var request = new UnityWebRequest(url, "POST");
byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonString);
request.uploadHandler = (UploadHandler) new UploadHandlerRaw(bodyRaw);
request.downloadHandler = (DownloadHandler) new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
Debug.Log ("(post json) before sending");
yield return request.Send();
Debug.Log ("(post json) after sending");
if (request.error != null) {
Debug.Log("request error: " + request.error);
} else {
Debug.Log("request success: " + request.downloadHandler.text);
}
}
Thanks for your help!
You're using Unity's feature for asynchronous operations. To understand the concepts, read up on generators and Unity coroutines. To run your function, call the StartCoroutine function like this:
StartCoroutine(PostJSON (json, EVENT_ROUTE));
Edit:
More specifically: You're creating a coroutine (PostJson) that calls an asynchronous function (Send).
Related
I'm working on a blackjack game in Unity. I have a coroutine called InitialDeal() which handles the flow when a player clicks a BET button. It should fetch a random number from the backend API and display the dealing of the cards one by one.
Because fetching data from the API must use UnityWebRequest because the game is browser based, it is done in another coroutine called UnityApiCall.
So basically I start coroutine A (InitialDeal()) in which I start coroutine B (UnityWebRequest), and I need that every time I get the response from API, I stop the coroutine A for a number of seconds while the card is dealt.
This is what I tried. This is coroutine B that calls my API:
private IEnumerator UnityApiCall(String method, String path, String pathParams, String body, Action<JObject> result)
{
Debug.Log("In UnityApiCall...");
using UnityWebRequest www = new UnityWebRequest(apiUrl + path);
www.method = method;
if(www.method == "POST") {
byte[] byteArray = Encoding.UTF8.GetBytes(body);
UploadHandlerRaw uH = new UploadHandlerRaw(byteArray);
www.uploadHandler = uH;
}
www.downloadHandler = new DownloadHandlerBuffer();
www.SetRequestHeader("Session-ID", sessionId);
yield return www.SendWebRequest();
if (www.result == UnityWebRequest.Result.ConnectionError)
{
Debug.Log(www.error);
if (result != null)
result(new JObject("{error: " + www.error + "}"));
}
else
{
Debug.Log(www.method + " successful!");
String str = System.Text.Encoding.Default.GetString(www.downloadHandler.data);
JObject jsonResponse = JObject.Parse(str);
if (result != null)
result(jsonResponse);
}
}
This is the coroutine A:
private IEnumerator InitialDeal()
{
// initial deal
ChangeGameState(GameState.OnDealing);
int betAmount = (int) ChipManager._Instance.stacks[(int) StackType.Standard].GetValue();
Debug.Log("Bet value: " + betAmount);
// int betAmount = 10;
BetRequest betRequest = new BetRequest();
betRequest.amount = betAmount;
string pathParams = "";
string body = JsonConvert.SerializeObject(betRequest);
yield return UnityApiCall("POST", "/bet", pathParams, body, (JObject response) =>
{
Debug.Log("response: ");
Debug.Log(response);
for (int i = 0; i < 2; i++)
{
// CardData cardData = new CardData(CardData.Suit.Hearts, CardData.Rank.Four);
// CardData cardData2 = new CardData(CardData.Suit.Spades, CardData.Rank.Five);
var playerNum = response["data"]["playerCards"]["0"]["cards"][i]["number"];
var playerSymbol = response["data"]["playerCards"]["0"]["cards"][i]["symbol"]["symbol"];
var dealerNum = response["data"]["dealerCards"][i]["number"];
var dealerSymbol = response["data"]["dealerCards"][i]["symbol"]["symbol"];
var playerCard = MapCard(playerNum.ToObject<int>(), playerSymbol.ToObject<string>());
var dealerCard = MapCard(dealerNum.ToObject<int>(), dealerSymbol.ToObject<string>());
DealQueue.DealCard(player.DealCard(playerCard));
yield return new WaitForSeconds(DEAL_WAIT_TIME);
if (i == 0)
{
DealQueue.DealCard(dealer.DealCard(dealerCard, FlipType.FlipDown));
}
else
{
DealQueue.DealCard(dealer.DealCard(dealerCard, FlipType.FlipUp));
}
yield return new WaitForSeconds(DEAL_WAIT_TIME);
}
ChangeGameState(GameState.OnPlay);
});
}
This is how I tried to stop the execution for a few seconds (in the callback of UnityApiCall in the snippet above):
yield return new WaitForSeconds(DEAL_WAIT_TIME);
However, this doesn't work since it's in the callback function, and I cannot use yield there.
This is how I called the coroutine A:
StartCoroutine(InitialDeal());
You are right in the way you wait for the UnityApiCall. The callback does get the JObject but it is only local to the callback. Instead, assign it to another JObject that is method scope:
JObject jObject = null;
yield return UnityApiCall("POST", "/bet", pathParams, body,
(response) => jObject = response);
// You can use the jObject
Debug.Log("Response " + jObject );
// Your code here processing jObject
// You are in the coroutine so you can yield all you need
yield return new WaitForSeconds(DEAL_WAIT_TIME);
ChangeGameState(GameState.OnPlay);
I want to generate a shorten dynamic link from a long dynamic link
I followed the steps shown here: https://firebase.google.com/docs/dynamic-links/unity/create
yet I get the same long URL when shortening process is completed.
public void CreateInviteLink()
{
var components = new Firebase.DynamicLinks.DynamicLinkComponents(
// The base Link.
new System.Uri("https://myapp.com"),
// The dynamic link URI prefix.
"https://myapp.page.link")
{
IOSParameters = new Firebase.DynamicLinks.IOSParameters("com.myapp.myapp"),
AndroidParameters = new Firebase.DynamicLinks.AndroidParameters("com.myapp.myApp"),
};
Debug.Log("Long Dynamic Link: " + components.LongDynamicLink);
var options = new Firebase.DynamicLinks.DynamicLinkOptions
{
PathLength = DynamicLinkPathLength.Unguessable
};
Firebase.DynamicLinks.DynamicLinks.GetShortLinkAsync(components, options).ContinueWith(task => {
if (task.IsCanceled)
{
Debug.LogError("GetShortLinkAsync was canceled.");
return;
}
if (task.IsFaulted)
{
Debug.LogError("GetShortLinkAsync encountered an error: " + task.Exception);
return;
}
// Short Link has been created.
Firebase.DynamicLinks.ShortDynamicLink link = task.Result;
Debug.LogFormat("Generated Short Dynamic Link: {0}", link.Url);
text.text = link.Url.ToString();
var warnings = new System.Collections.Generic.List<string>(link.Warnings);
if (warnings.Count > 0)
{
// Debug logging for warnings generating the short link.
}
});
}
result Logs
Long Dynamic Link: https://myapp.page.link/?afl=&amv=0&apn=com.myapp.myApp&ibi=com.myapp.myapp&ifl=&ipfl=&link=https://myapp.com
UnityEngine.Debug:Log(Object)
Generated Short Dynamic Link: https://myapp.page.link/?afl=&amv=0&apn=com.myapp.myApp&ibi=com.myapp.myapp&ifl=&ipfl=&link=https://myapp.com
UnityEngine.Debug:LogFormat(String, Object[])
EDIT:
How I did it:
You can't shorten dynamic links with Firebase SDK in the editor but I used REST API and UnityWebRequest for testing short links inside the editor and it worked
IEnumerator HTTPRequestShortLink(string longDynamicLink)
{
WWWForm form = new WWWForm();
form.AddField("longDynamicLink", longDynamicLink);
// trigger of function is HTTP request. this link is the trigger for that func
UnityWebRequest www = UnityWebRequest.Post("https://firebasedynamiclinks.googleapis.com/v1/shortLinks?key=[YOUR_API_KEY]", form);
//for getting response
www.downloadHandler = new DownloadHandlerBuffer();
yield return www.SendWebRequest();
if (www.result != UnityWebRequest.Result.Success)
{
Debug.Log(www.error);
}
else
{
Debug.Log("Form upload complete! " + www.GetResponseHeader("response"));
string responseText = www.downloadHandler.text;
// Parse returned json
parsedJsonObject obj = parsedJsonObject.CreateFromJSON(responseText);
Debug.Log("shortLink: " + obj.shortLink);
Debug.Log("HTTP Response text: " + responseText);
}
}
I cannot figure out how to make asynchronous Get requests correctly. I have a coroutine with get request. It returns me the room ID that I need when I record a player, but the problem is that all this is done in one function, when creating a room and because of this, the coroutine does not have time to write data. Then this Get request is used in real time to get the necessary IDs that change and the same problem is there.
My Get request:
IEnumerator GetRequest(string uri)
{
using (UnityWebRequest webRequest = UnityWebRequest.Get(Host + uri))
{
yield return webRequest.SendWebRequest();
response = webRequest.downloadHandler.text;
}
}
method:
public int GetID(string entity, string identify, string ID)
{
StartCoroutine(GetRequest(entity + identify));
var json = ParseJson(response);
return int.Parse(json[ID]);
}
And where i call him
public void CreateRoom()
{
TypedLobby sqlLobby = new TypedLobby("myLobby", LobbyType.SqlLobby);
string sqlLobbyFilter = "C0";
SetNickName(userName.text);
RoomOptions roomOptions = new RoomOptions
{
MaxPlayers = 5,
CustomRoomProperties = new ExitGames.Client.Photon.Hashtable() { { sqlLobbyFilter, "0" } },
CustomRoomPropertiesForLobby = new string[] { sqlLobbyFilter }
};
if (roomName.text.Length != 0)
{
client.PostRoom(roomName.text);
var roomID =client.GetID("Rooms/", roomName.text, "IDRoom");
client.PostPlayer(PhotonNetwork.NickName, roomID);
PhotonNetwork.CreateRoom(roomName.text, roomOptions, sqlLobby);
}
else notice.EmptyRoomName();
}
It looks like you're trying to linearize a coroutine. "StartCoroutine" does not function like await.
Let's first go over how your GetRequest coroutine works.
IEnumerator GetRequest(string uri)
{
using (UnityWebRequest webRequest = UnityWebRequest.Get(Host + uri))
{
yield return webRequest.SendWebRequest();
response = webRequest.downloadHandler.text;
}
}
The above code creates a new web request, and returns control back to Unity every single frame until the web request completes.
Think of yield return webRequest.SendWebRequest(); like this:
while(!webRequest.isDone)
{
//suspend the coroutine until next frame
yield return null;
}
It's simply suspending the coroutine every frame until there's a response.
Now let's step through your code and see what's happening:
var roomID =client.GetID("Rooms/", roomName.text, "IDRoom");
which calls:
public int GetID(string entity, string identify, string ID)
{
StartCoroutine(GetRequest(entity + identify));
var json = ParseJson(response);
return int.Parse(json[ID]);
}
Since webRequest.SendWebRequest() in your GetRequest method is not instant, response will not contain parseable JSON data until at least the next frame.
Since GetID is called on the frame (and is not a coroutine itself), it performs all of its logic in the frame that it's called. It attempts to ParseJson(response) on an unset response, which is why your coroutine does not have time to write.
How do we fix this?
There's not a one-size-fits-all solution, unfortunately. You'll need to delinearize your logic so that it can occur over multiple frames. A clever use of Queues could serve well here, or putting more logic inside of the CoRoutine could solve it as well.
Here's an example of using CoRoutines, since Queues would likely require more code to monitor/process them:
Move your room configuration logic a coroutine (And rather than nest Coroutines, just call the webrequest from within this one):
public IEnumerator ConfigureRoom(string roomNameText, RoomOptions roomOptions, TypedLobby sqlLobby)
{
client.PostRoom(roomNameText)
using (UnityWebRequest webRequest = UnityWebRequest.Get("Rooms/" + roomNameText))
{
yield return webRequest.SendWebRequest();
response = webRequest.downloadHandler.text;
}
var json = ParseJson(response);
var roomId = int.Parse(json["IDRoom"]);
client.PostPlayer(PhotonNetwork.NickName, roomID);
PhotonNetwork.CreateRoom(roomNameText, roomOptions, sqlLobby);
}
//Inside of your CreateRoom() method
if (roomName.text.Length != 0)
{
StartCoroutine(ConfigureRoom(roomName.text, roomOptions, sqlLobby));
}
This allows your room to be created and the data to be updated all over the course of multiple frames.
Disclaimer: I've typed all of this in browser. There may be minor syntax errors. Please let me know so I may correct them.
Change GetID into a coroutine, give it an Action to use the int in, and use yield return GetRequest(...); instead of StartCoroutine(GetRequest(...));.
IEnumerator GetRequest(string uri)
{
using (UnityWebRequest webRequest = UnityWebRequest.Get(Host + uri))
{
yield return webRequest.SendWebRequest();
response = webRequest.downloadHandler.text;
}
}
method:
public IEnumerator GetID(string entity, string identify, string ID, Action<int> onComplete)
{
yield return GetRequest(entity + identify);
var json = ParseJson(response);
onComplete(int.Parse(json[ID]));
}
And call him
public void CreateRoom()
{
TypedLobby sqlLobby = new TypedLobby("myLobby", LobbyType.SqlLobby);
string sqlLobbyFilter = "C0";
SetNickName(userName.text);
RoomOptions roomOptions = new RoomOptions
{
MaxPlayers = 5,
CustomRoomProperties = new ExitGames.Client.Photon.Hashtable() { { sqlLobbyFilter, "0" } },
CustomRoomPropertiesForLobby = new string[] { sqlLobbyFilter }
};
if (roomName.text.Length != 0)
{
client.PostRoom(roomName.text);
StartCoroutine(
client.GetID("Rooms/", roomName.text, "IDRoom",
delegate(int roomID) {
client.PostPlayer(PhotonNetwork.NickName, roomID);
PhotonNetwork.CreateRoom(roomName.text, roomOptions, sqlLobby);
}));
}
else notice.EmptyRoomName();
}
I'm pretty new to the concept of Coroutines. now I got this problem, when I call my Coroutine function it gives an empty object back when I DON'T use
System.Threading.Thread.Sleep();. But when I use System.Threading.Thread.Sleep(int); It waits until the sleep method is completed before continuing the code. I get that it probably has something to do with that they work on the same thread but even my debug functions before the sleep method won't print untill the sleep function is done. could really use some help here!
I tried it with synchronous calls but this just makes my layout freeze.
IEnumerator PostRequestBearerToken(string uri)
{
string authorization = APIHelpers.authenticate("user", "pass");
WWWForm form = new WWWForm();
form.AddField("grant_type", "client_credentials");
using (UnityWebRequest www = UnityWebRequest.Post(uri, form))
{
www.SetRequestHeader("AUTHORIZATION", authorization);
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
Debug.Log(www.error);
}
else
{
Debug.Log("Post request complete!" + " Response Code: " + www.responseCode);
string responseText = www.downloadHandler.text;
Debug.Log("Response Text:" + responseText);
Debug.Log("Form upload complete!");
//deserialize bearertoken
BearerObject myObject = JsonUtility.FromJson<BearerObject>(www.downloadHandler.text);
Debug.Log("json : " + myObject.ToString());
//BearerTokenSingleton.getInstance();
BearerTokenSingleton.getInstance().SetBearerToken(myObject);
Debug.Log(BearerTokenSingleton.getInstance().GetBearerToken().ToString());
}
}
}
void Update()
{
if (!loadFirstFrame)
{
loadFirstFrame = true;
}
else if (loadFirstFrame)
{
StartCoroutine(PostRequestBearerToken(APIHelpers.bearerString));
//go to next scene
System.Threading.Thread.Sleep(5000);
SceneManager.LoadScene(2);
}
}
It is because of the SceneManager.LoadScene method. Coroutines run in separate threads. Since you start the coroutine and immediately load a new scene, there is no time to run the coroutine before the current scene (and script object) is unloaded. The Thread.Sleep call introduces a delay that gives enough time for the coroutine to finish before the scene load.
To get around this, you can use a flag (actually I use two, so you don't kick off the coroutine multiple times):
private bool _postComplete = false;
private bool _startedPost = false;
IEnumerator PostRequestBearerToken(string uri)
{
string authorization = APIHelpers.authenticate("user", "pass");
WWWForm form = new WWWForm();
form.AddField("grant_type", "client_credentials");
using (UnityWebRequest www = UnityWebRequest.Post(uri, form))
{
www.SetRequestHeader("AUTHORIZATION", authorization);
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
Debug.Log(www.error);
}
else
{
Debug.Log("Post request complete!" + " Response Code: " + www.responseCode);
string responseText = www.downloadHandler.text;
Debug.Log("Response Text:" + responseText);
Debug.Log("Form upload complete!");
//deserialize bearertoken
BearerObject myObject = JsonUtility.FromJson<BearerObject>(www.downloadHandler.text);
Debug.Log("json : " + myObject.ToString());
//BearerTokenSingleton.getInstance();
BearerTokenSingleton.getInstance().SetBearerToken(myObject);
Debug.Log(BearerTokenSingleton.getInstance().GetBearerToken().ToString());
}
_postComplete = true;
}
}
And then use that in the Update method:
void Update()
{
if (!loadFirstFrame)
{
loadFirstFrame = true;
}
else if (loadFirstFrame)
{
if (!_startedPost)
{
StartCoroutine(PostRequestBearerToken(APIHelpers.bearerString));
_startedPost = true;
}
if (_postComplete)
SceneManager.LoadScene(2);
}
}
i am making a game in unity
it's quite challenging game to my normal skills
i've successfully done the registration code :D
but i am struggling at uploading players pics to server
i've already tried imgur and followed all steps at this github repo
but it seems like it's not uploading. then i did some debugging i found out that
it's uploading but anomalously that's why i didn't get info such as:(title, description, tags ... etc) back.
here is what i've got so far >> here <<
and here what i think that is the buggy part
public void UploadImage(string base64Image){
Upload(base64Image, (response) =>{
if (OnImageUploaded != null){
OnImageUploaded(this, new OnImageUploadedEventArgs(response));
Debug.Log("uploading completed!");
}else{
Debug.Log("OnImageUploaded = null");
}
});
}
private void Upload(string base64Image, Action<ImgurUploadResponse> OnUploadCompleted){
Thread t = new Thread(() =>{
using (WebClient wclient = new WebClient()){
wclient.Headers.Add("Authorization", "Client-ID " + _clientId);
NameValueCollection parameters = new NameValueCollection(){
{ "image", base64Image }
};
byte[] response = wclient.UploadValues(_baseUploadUrl, parameters);
string json = Encoding.UTF8.GetString(response);
Debug.Log("completed "+json); // it's here this debug never called
OnUploadCompleted(JsonUtility.FromJson<ImgurUploadResponse>(json));
}
})
{IsBackground = true};
t.Start();
Debug.Log("uploading started!");
}
Unity works with coroutines, not with Tasks.
You can change your script like so:
private IEnumerator Upload(string base64Image, Action<ImgurUploadResponse> OnUploadCompleted){
using (WebClient wclient = new WebClient()){
wclient.Headers.Add("Authorization", "Client-ID " + _clientId);
NameValueCollection parameters = new NameValueCollection(){
{ "image", base64Image }
};
byte[] response = wclient.UploadValues(_baseUploadUrl, parameters);
string json = Encoding.UTF8.GetString(response);
Debug.Log("completed "+json);
OnUploadCompleted(JsonUtility.FromJson<ImgurUploadResponse>(json));
}
}
And call it using StartCoroutine
public void UploadImage(string base64Image){
StartCoroutine(
Upload(base64Image, (response) =>{
if (OnImageUploaded != null){
OnImageUploaded(this, new OnImageUploadedEventArgs(response));
Debug.Log("uploading completed!");
}else{
Debug.Log("OnImageUploaded = null");
}
}));
}
If this does not work, then you might want to take a look at WWWForm