블로그 이미지
Every unexpected event is a path to learning for you.

카테고리

분류 전체보기 (2307)
Unity3D (564)
Programming (470)
Unreal (4)
Gamebryo (56)
Tip & Tech (182)
협업 (34)
3DS Max (3)
Game (12)
Utility (114)
Etc (92)
Link (31)
Portfolio (19)
Subject (90)
iOS,OSX (37)
Android (12)
Linux (5)
잉여 프로젝트 (2)
게임이야기 (1)
Memories (19)
Interest (37)
Thinking (36)
한글 (26)
PaperCraft (5)
Animation (408)
Wallpaper (2)
재테크 (19)
Exercise (3)
나만의 맛집 (2)
냥이 (9)
육아 (5)
Total1,331,910
Today13
Yesterday172
Statistics Graph

달력

« » 2019.10
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    

공지사항

태그목록

hello,

From that post : http://forum.unity3d.com/threads/how-can-you-add-items-to-the-xcode-project-targets-info-plist-using-the-xcodeapi.330574/ by modifying a bit the script i got that :

     [PostProcessBuild]
     public static void ChangeXcodePlist(BuildTarget buildTarget, string pathToBuiltProject) 
     {  
         if (buildTarget == BuildTarget.iOS) 
         {
             // Get plist
             string plistPath = pathToBuiltProject + "/Info.plist";
             PlistDocument plist = new PlistDocument();
             plist.ReadFromString(File.ReadAllText(plistPath));
             
             // Get root
             PlistElementDict rootDict = plist.root;
             
             // Change value of CFBundleVersion in Xcode plist
             var buildKey = "UIBackgroundModes";
             rootDict.CreateArray (buildKey).AddString ("remote-notification");
             
             // Write to file
             File.WriteAllText(plistPath, plist.WriteToString());
         }
     }

 

i just did it, it seems to work, need further test though.

Edit : Note that the script should be in Assets/Editor folder. Edit 2 : i created i string it should be a array, i changed the code.

 

[출처] https://answers.unity.com/questions/1066927/postprocessing-ios-activate-background-mode-for-pu.html

 

PostProcessing iOS Activate Background Mode for Push Notifications - Unity Answers

 

answers.unity.com

 

Posted by blueasa

댓글을 달아 주세요

[사용도구]

Unity 5.6.7

Xcode 10.2.1

 

최근 iOS빌드에서 XCode-Capabilities-Push Notifications를 수동으로 ON 시켜야 되는 문제가 귀찮아서 자동으로 하는 방법을 찾아보고 정리해둠.

(유니티 5.x에서 되는 방법을 찾느라 이래저래 삽질을..)

 

[순서]

1) https://bitbucket.org/Unity-Technologies/xcodeapi 소스 다운로드.

 

2) 다운로드 받은 소스를 유니티 해당 프로젝트의 Editor 폴더 아래 추가

   나의 경우는 ../Assets/Editor/XCodeApi 아래에 추가. 

   2-1) Xcode.Tests는 에러나서 제거 함.(2017 이후 버전 API를 사용하는 듯 함)

 

3) iOS PostProcessBuild 소스에 PBXCapabilityType.PushNotifications 추가(AddCapability)

using System.IO;
using UnityEngine;
using UnityEditor;
using UnityEditor.iOS.Xcode;
using UnityEditor.Callbacks;
using System.Collections;

public class XCodeSettingsPostProcesser
{

    [PostProcessBuild]
    public static void OnPostprocessBuild(BuildTarget buildTarget, string pathToBuiltProject)
    {
        // Stop processing if targe is NOT iOS
        if (buildTarget != BuildTarget.iOS)
        {
            return;
        }

        // Initialize PbxProject
        var projectPath = pathToBuiltProject + "/Unity-iPhone.xcodeproj/project.pbxproj";
        UnityEditor.iOS.Xcode.Custom.PBXProject pbxProject = new UnityEditor.iOS.Xcode.Custom.PBXProject();
        pbxProject.ReadFromFile(projectPath);
        string targetGuid = pbxProject.TargetGuidByName("Unity-iPhone");

        // Add push notifications as a capability on the target
        pbxProject.AddCapability(targetGuid, UnityEditor.iOS.Xcode.Custom.PBXCapabilityType.PushNotifications);

        // Apply settings
        File.WriteAllText(projectPath, pbxProject.WriteToString());
    }
}

    3-1) 주의사항

         기존 PBXProjectUnityEditor.iOS.Xcode.PBXProject인데, 추가 된 플러그인을 사용하기 위해 UnityEditor.iOS.Xcode.Custom.PBXProject로 변경필요.

         예1) UnityEditor.iOS.Xcode.PBXProject -> UnityEditor.iOS.Xcode.Custom.PBXProject

         예2) UnityEditor.iOS.Xcode.PBXCapabilityType -> UnityEditor.iOS.Xcode.Custom.PBXCapabilityType

 

4) entitlements 생성을 위해 아래 클래스를 Editor 폴더에 추가

    (entitlements 파일 내용을 스크립트 내부에 넣어서 생성해서 별도의 파일 추가를 안해도 돼서 편한 듯)

using UnityEditor;
 using UnityEditor.Callbacks;
 using UnityEngine;
 using System.IO;
 using UnityEditor.iOS.Xcode;
 
 public class EntitlementsPostProcess
 {
     private const string entitlements = @"
     <?xml version=""1.0"" encoding=""UTF-8\""?>
     <!DOCTYPE plist PUBLIC ""-//Apple//DTD PLIST 1.0//EN"" ""http://www.apple.com/DTDs/PropertyList-1.0.dtd"">
     <plist version=""1.0"">
         <dict>
             <key>aps-environment</key>
             <string>development</string>
         </dict>
     </plist>";
     
     [PostProcessBuild]
     public static void OnPostProcess(BuildTarget buildTarget, string buildPath)
     {
         if (buildTarget != BuildTarget.iOS) return;
 
         var file_name = "unity.entitlements";
         var proj_path = PBXProject.GetPBXProjectPath(buildPath);
         var proj = new PBXProject();
         proj.ReadFromFile(proj_path);
 
         // target_name = "Unity-iPhone"
         var target_name = PBXProject.GetUnityTargetName();
         var target_guid = proj.TargetGuidByName(target_name);        
         var dst = buildPath + "/" + target_name + "/" + file_name;
         try
         {
             File.WriteAllText(dst, entitlements);
             proj.AddFile(target_name + "/" + file_name, file_name);
             proj.AddBuildProperty(target_guid, "CODE_SIGN_ENTITLEMENTS", target_name + "/" + file_name);
             proj.WriteToFile(proj_path);
         }
         catch (IOException e)
         {
             Debug.LogWarning($"Could not copy entitlements. Probably already exists. ({e})");
         }
     }
 }

 

[1) 참조] https://bitbucket.org/Unity-Technologies/xcodeapi

 

Bitbucket

 

bitbucket.org

[3) 참조] https://qiita.com/fullcorder/items/b82c48022acd0b4ff957

 

Unity5のPostProcessBuildでXcode Capabilityの設定する方法 - Qiita

XcodeのCapabilityの各種設定を`PostProcessBuild`で行う方法です。 Pushの設定などを有効にするアレです。 # ざっくり流れ ### In-App Purchaseなどentitlementsフ...

qiita.com

[4) 참조] https://answers.unity.com/questions/1224123/enable-push-notification-in-xcode-project-by-defau.html

 

Enable Push Notification in XCode project by default? - Unity Answers

 

answers.unity.com

 

Posted by blueasa

댓글을 달아 주세요

XcodeのCapabilityの各種設定をPostProcessBuildで行う方法です。
Pushの設定などを有効にするアレです。

ざっくり流れ

In-App Purchaseなどentitlementsファイルが不要なcapabilityの場合

  1. Unityのリポジトリから, 最新のXcode Manipulation API(以下Xcode API)を持ってくる
  2. 既存のプロジェクトにインポートできるように修正する
  3. 最新のAPIでPostProcessBuildのScriptを書く

iCloudなどentitlementsファイルが必要なcapabilityの場合

  1. 一旦先にビルドしてXcodeProjectを作成する
  2. capabilityを設定し, entitlementsファイルを作成する
  3. PostProcessBuildでentitlementsファイルとビルドプロパティを追加する
  4. 以後entitlements file不要な場合に合流する

詳細な流れ

entitlementsファイルのいらないcapabilityの場合

Unityのリポジトリから, 最新のXcode APIを持ってくる

  • リポジトリ内のXcodeフォルダをデスクトップなど作業環境にコピーします
  • ターミナルでXcodeディレクトリに移動し, 下記のコマンドでC#のソースコードのnamespaceを変更します.仮に変更後のnamespaceをUnity2017.iOS.Xcodeとします

 

$ pwd
Users/fullcorder/Desktop/Xcode 
$ find . -name '*.cs' | xargs sed -i "" 's/UnityEditor.iOS.Xcode/Unity2017.iOS.Xcode/g'

 

  • Xcode/Propertiesディレクトリをは不要なので削除します
  • UnityプロジェクトのEditorディレクトリにXcodeフォルダごとインポートします

PostProcessBuildスクリプトを作成し下記のようにPBXProject#AddCapabilityを使って追加します

Unity2017.1のリファレンス
https://docs.unity3d.com/2017.1/Documentation/ScriptReference/iOS.Xcode.PBXProject.AddCapability.html

 

[PostProcessBuild]
public static void AddCapability(BuildTarget buildTarget, string path)
{
    if(EditorUserBuildSettings.activeBuildTarget != BuildTarget.iOS)
    {
        return;
    }

    string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";

    var proj = new PBXProject();
    proj.ReadFromString(File.ReadAllText(projPath));

    string target = proj.TargetGuidByName(TargetGroup);

    proj.AddCapability(target, PBXCapabilityType.PushNotifications);
    proj.AddCapability(target, PBXCapabilityType.InAppPurchase);

    File.WriteAllText(projPath, proj.WriteToString());
}

 

  • 以上ですあとはビルドすればOKです

entitlementsファイルが必要なcapabilityの場合

entitlementsファイルが必要な場合は, entitlementsファイルを作成したものをPostProcessBuildで追加し、あとはentitlementsファイルが不要な場合に合流します

entitlementsファイルを作成

  • UnityでiOSビルドしてXcodeProjectを吐き出します
  • Capabilityタブに行き, Pushなどをこうなりたい状態, PostProcessBuild後で設定後の形にします
  • entitlementsファイルがXcodeプロジェクト内に作成されるので取り出してUnityProjectにインポートします
  • Unityフォーラムの下記ポストからEntitlementsPostProcess.csをDLしてUnityにインポートします

https://forum.unity3d.com/threads/how-to-put-ios-entitlements-file-in-a-unity-project.442277/

  • EntitlementsPostProcess.csのSerializeFieldであるEntitlements Fileにentitlementsファイルを設定します
  • 以上でentitlementsファイル追加が完了します

以後、前述の手順と合わせてビルドすると無事, 設定反映したになっていると思います
(Plist編集は別途スクリプト書いてください)

その他

Unity2017からは多分使えるAPIなので、長いプロジェクトの場合, プラットフォーム依存コンパイルでUnity5のみ限定して今回変更したnamespaceを使うようにすると良いと思います。

環境

macOS Sierra
Unity 5.5.3f1
Xcode 8.3.2

参考 
https://bitbucket.org/Unity-Technologies/xcodeapi
https://forum.unity3d.com/threads/how-to-put-ios-entitlements-file-in-a-unity-project.442277/

 

 

[출처] https://qiita.com/fullcorder/items/b82c48022acd0b4ff957

 

Unity5のPostProcessBuildでXcode Capabilityの設定する方法 - Qiita

XcodeのCapabilityの各種設定を`PostProcessBuild`で行う方法です。 Pushの設定などを有効にするアレです。 # ざっくり流れ ### In-App Purchaseなどentitlementsフ...

qiita.com

 

Posted by blueasa

댓글을 달아 주세요

I manage to get it working by a C# script, but since this build setting need an .entitlementfile in your xcode project, first you will need to create a text file called [projectName].entitlement anywhere in your project with this content:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. http://www.apple.com/DTDs/PropertyList-1.0.dtd">
  3. <plist version="1.0">
  4. <dict>
  5. <key>aps-environment</key>
  6. <string>development</string>
  7. </dict>
  8. </plist>

Now you can add this script on the Editor folder:

  1. using System.IO;
  2. using JetBrains.Annotations;
  3. using UnityEditor;
  4. using UnityEditor.Callbacks;
  5. using UnityEditor.iOS.Xcode;
  6. using UnityEngine;
  7.  
  8. public class PostProcessBuildScript : ScriptableObject
  9. {
  10. public DefaultAsset entitlementsFile;
  11.  
  12. [PostProcessBuild, UsedImplicitly]
  13. public static void ChangesToXcode(BuildTarget buildTarget, string pathToBuiltProject)
  14. {
  15. // Get project PBX file
  16. var projPath = PBXProject.GetPBXProjectPath(pathToBuiltProject);
  17. var proj = new PBXProject();
  18. proj.ReadFromString(File.ReadAllText(projPath));
  19. var target = proj.TargetGuidByName("Unity-iPhone");
  20.  
  21. // Create an instance of this ScriptableObject so we can point to its entitlements file
  22. var dummy = CreateInstance<PostProcessBuildScript>();
  23. var entitlementsFile = dummy.entitlementsFile;
  24. DestroyImmediate(dummy);
  25. if (entitlementsFile != null)
  26. {
  27. // Copy the entitlement file to the xcode project
  28. var entitlementPath = AssetDatabase.GetAssetPath(entitlementsFile);
  29. var entitlementFileName = Path.GetFileName(entitlementPath);
  30. var unityTarget = PBXProject.GetUnityTargetName();
  31. var relativeDestination = unityTarget + "/" + entitlementFileName;
  32. FileUtil.CopyFileOrDirectory(entitlementPath, pathToBuiltProject + "/" + relativeDestination);
  33.  
  34. // Add the pbx configs to include the entitlements files on the project
  35. proj.AddFile(relativeDestination, entitlementFileName);
  36. proj.AddBuildProperty(target, "CODE_SIGN_ENTITLEMENTS", relativeDestination);
  37.  
  38. // Add push notifications as a capability on the target
  39. proj.AddBuildProperty(target, "SystemCapabilities", "{com.apple.Push = {enabled = 1;};}");
  40. }
  41.  
  42. // Save the changed configs to the file
  43. File.WriteAllText(projPath, proj.WriteToString());
  44. }
  45. }

After that you just define the entitlementsFile variable for the script in the inspector window and you should now have the push notification activated by default when you build for iOS.

 

 

 

[출처] https://answers.unity.com/questions/1224123/enable-push-notification-in-xcode-project-by-defau.html

 

Enable Push Notification in XCode project by default? - Unity Answers

 

answers.unity.com

 

Posted by blueasa

댓글을 달아 주세요

It forces to sample current state of animations. "Sample animations" means: put character in the position defined by animation states. Sampling always happens between Update and LateUpdate automatically, but in some cases you might want to sample animations yourself. My most common case: I want to put character in pose of first frame some specific animation on Start, so I would do something like this:

 

void Start() 
{ 
	AnimationState state = animation["Aim"]; 
	state.enabled = true; 
	state.weight = 1; 
	state.normalizedTime = 0;

	animation.Sample();
}

 

[출처] https://answers.unity.com/questions/46869/animationsample-usage.html

Posted by blueasa

댓글을 달아 주세요

NIST 타임서버에서 시간가져오기 (NST)

아래와 같은 코드로 가져올 수 있다.
Unity 에서 사용할 수 있다.

using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TimeServer : MonoBehaviour {

    [SerializeField]
    private string _comment = "만료시킬 날짜를 적으세요 (한국시각 기준)";

    public int _yyyy, _mm, _dd;
    private DateTime _expireDateTime, _nowServerDateTime, _nowLocalDateTime;
    private TimeSpan _duration;

 // Use this for initialization
 void Start () {

        // 한국 시각
        _duration = System.TimeSpan.FromHours(9);
        _expireDateTime = new DateTime(Mathf.Clamp(_yyyy, 1900, 3000), Mathf.Clamp(_mm, 1,12), Mathf.Clamp(_dd, 1, 31));

        _nowLocalDateTime = DateTime.Now;
        _nowServerDateTime = GetNISTDate().Add(_duration);

        if (Debug.isDebugBuild)
        {
            Debug.LogWarning("만료지정일 : " + _expireDateTime);
            Debug.LogWarning("현재 로컬 시각 :" + _nowLocalDateTime);
            Debug.LogWarning("현재 서버 시각 :" + _nowServerDateTime);
        }

        if (_nowLocalDateTime < _expireDateTime)
        {
            if (_nowServerDateTime < _expireDateTime)
            {
                // Debug.Log("실행");
            }
            else
            {
                // Debug.Log("서버 체크 결과 만료 됨");
            }
        }
        else
        {
            // Debug.Log("로컬 체크 결과 만료 됨");
        }


    }

#region NTPTIME

    //NTP time 을 NIST 에서 가져오기
    // 4초 이내에 한번 이상 요청 하면, ip가 차단됩니다.

    public static DateTime GetDummyDate()
    {
        return new DateTime(2017, 12, 24); //to check if we have an online date or not.
    }

    public static DateTime GetNISTDate()
    {
        System.Random ran = new System.Random(DateTime.Now.Millisecond);
        DateTime date = GetDummyDate();
        string serverResponse = string.Empty;

        // NIST 서버 목록
        string[] servers = new string[] {
        "time.bora.net",
        //"time.nuri.net",
        //"ntp.kornet.net",
        //"time.kriss.re.kr",
        //"time.nist.gov",
        //"maths.kaist.ac.kr",
        "nist1-ny.ustiming.org",
        "time-a.nist.gov",
        "nist1-chi.ustiming.org",
        "time.nist.gov",
        "ntp-nist.ldsbc.edu",
        "nist1-la.ustiming.org"
    };

        // 너무 많은 요청으로 인한 차단을 피하기 위해 한 서버씩 순환한다. 5번만 시도한다.
        for (int i = 0; i < 5; i++)
        {
            try
            {
                // StreamReader(무작위 서버)
                StreamReader reader = new StreamReader(new System.Net.Sockets.TcpClient(servers[ran.Next(0, servers.Length)], 13).GetStream());
                serverResponse = reader.ReadToEnd();
                reader.Close();

                // 서버 리스폰스를 표시한다. (디버그 확인용)
                if (Debug.isDebugBuild)
                    Debug.Log(serverResponse);

                // 시그니처가 있는지 확인한다.
                if (serverResponse.Length > 47 && serverResponse.Substring(38, 9).Equals("UTC(NIST)"))
                {
                    // 날짜 파싱
                    int jd = int.Parse(serverResponse.Substring(1, 5));
                    int yr = int.Parse(serverResponse.Substring(7, 2));
                    int mo = int.Parse(serverResponse.Substring(10, 2));
                    int dy = int.Parse(serverResponse.Substring(13, 2));
                    int hr = int.Parse(serverResponse.Substring(16, 2));
                    int mm = int.Parse(serverResponse.Substring(19, 2));
                    int sc = int.Parse(serverResponse.Substring(22, 2));

                    if (jd > 51544)
                        yr += 2000;
                    else
                        yr += 1999;

                    date = new DateTime(yr, mo, dy, hr, mm, sc);

                    // Exit the loop
                    break;
                }
            }
            catch (Exception e)
            {
                /* 아무것도 하지 않고 다음 서버를 시도한다. */
            }
        }
        return date;
    }
    #endregion
}

아래 코드 원본에서 주석 변경 및 내가 사용하기 위해 수정함.
경로 : https://stackoverflow.com/questions/516788/getting-current-gmt-time

 

[출처] http://blog.kpaper.com/2017/10/unity-nst-nist.html

Posted by blueasa

댓글을 달아 주세요

Using coroutines in Unity is often a great way to solve certain problems, however it comes with certain drawbacks as well:

  1. Coroutines can’t return values. This encourages programmers to create huge monolithic coroutine methods instead of composing them out of many smaller methods. Some workarounds exist, such as passing a callback parameter of type Action<> to the coroutine, or casting the final untyped value that is yielded from the coroutine after it completes, but these approaches are awkward to use and error prone.
  2. Coroutines make error handling difficult. You cannot put a yield inside a try-catch, so it is not possible to handle exceptions. Also, when exceptions do occur the stack trace only tells you the coroutine where the exception was thrown, so you have to guess which other coroutines it might have been called from.

With the release of Unity 2017, it is now possible to use a new C# feature called async-await for our asynchronous methods instead. This comes with a lot of nice features compared to coroutines.

To enable this feature, all you need to do is open your player settings (Edit -> Project Settings -> Player) and change “Scripting Runtime Version” to “Experimental (.NET 4.6 Equivalent).

Let’s look at a simple example. Given the following coroutine:

1
2
3
4
5
6
7
8
9
public class AsyncExample : MonoBehaviour
{
    IEnumerator Start()
    {
        Debug.Log("Waiting 1 second...");
        yield return new WaitForSeconds(1.0f);
        Debug.Log("Done!");
    }
}

The equivalent way to do this using async-await would be the following:

1
2
3
4
5
6
7
8
9
public class AsyncExample : MonoBehaviour
{
    async void Start()
    {
        Debug.Log("Waiting 1 second...");
        await Task.Delay(TimeSpan.FromSeconds(1));
        Debug.Log("Done!");
    }
}

It’s helpful to be somewhat aware of what’s happening under-the-hood in both these cases.

In short, Unity coroutines are implemented using C#’s built-in support for iterator blocks. The IEnumerator iterator object that you provide to the StartCoroutine method is saved by Unity and each frame this iterator object is advanced forward to get new values that are yielded by your coroutine. The different values that you ‘yield return’ are then read by Unity to trigger special case behaviour, like executing a nested coroutine (when returning another IEnumerator), delaying by some number of seconds (when returning an instance of type WaitForSeconds), or just waiting until the next frame (when returning null).

Unfortunately, due to the fact that async-await is quite new within Unity, this built-in support for coroutines as explained above does not exist in a similar fashion for async-await. Which means that we have to add a lot of this support ourselves.

Unity does provide one important piece for us however. As you can see in the above example, our async methods will be run on the main unity thread by default. In non-unity C# applications, async methods are often automatically run on separate threads, which would be a big problem in Unity since we would not always be able to interact with the Unity API in these cases. Without this support from the Unity engine, our calls to Unity methods/objects inside our async methods would sometimes fail because they would be executed on a separate thread. Under the hood it works this way because Unity has provided a default SynchronizationContext called UnitySynchronizationContext which automatically collects any async code that is queued each frame and continues running them on the main unity thread.

As it turns out, however, this is enough to get us started with using async-await! We just need a bit of helper code to allow us to do some more interesting things than just simple time delays.

Custom Awaiters

Currently, there’s not a lot of interesting async code we can write. We can call other async methods, and we can use Task.Delay, like in the example above, but not much else.

As a simple example, let’s add the ability to directly ‘await’ on a TimeSpan instead of always having to call Task.Delay every time like the example above. Like this:

1
2
3
4
5
6
7
public class AsyncExample : MonoBehaviour
{
    async void Start()
    {
        await TimeSpan.FromSeconds(1);
    }
}

All we need to do to support this is to simply add a custom GetAwaiter extension method to the TimeSpan class:

1
2
3
4
5
6
7
    public static class AwaitExtensions
{
    public static TaskAwaiter GetAwaiter(this TimeSpan timeSpan)
    {
        return Task.Delay(timeSpan).GetAwaiter();
    }
}

This works because in order to support ‘awaiting’ a given object in newer versions of C#, all that’s needed is that the object has a method named GetAwaiter that returns an Awaiter object. This is great because it allows us to await anything we want, by using an extension method like above, without needing to change the actual TimeSpan class.

We can use this same approach to support awaiting other types of objects too, including all of the classes that Unity uses for coroutine instructions! We can make WaitForSeconds, WaitForFixedUpdate, WWW, etc all awaitable in the same way that they are yieldable within coroutines. We can also add a GetAwaiter method to IEnumerator to support awaiting coroutines to allow interchanging async code with old IEnumerator code.

The code to make all this happen can be downloaded from either asset store or the releases section of the github repo. This allows you to do things like the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class AsyncExample : MonoBehaviour
{
    public async void Start()
    {
        // Wait one second
        await new WaitForSeconds(1.0f);
 
        // Wait for IEnumerator to complete
        await CustomCoroutineAsync();
 
        await LoadModelAsync();
 
        // You can also get the final yielded value from the coroutine
        var value = (string)(await CustomCoroutineWithReturnValue());
        // value is equal to "asdf" here
 
        // Open notepad and wait for the user to exit
        var returnCode = await Process.Start("notepad.exe");
 
        // Load another scene and wait for it to finish loading
        await SceneManager.LoadSceneAsync("scene2");
    }
 
    async Task LoadModelAsync()
    {
        var assetBundle = await GetAssetBundle("www.my-server.com/myfile");
        var prefab = await assetBundle.LoadAssetAsync<GameObject>("myasset");
        GameObject.Instantiate(prefab);
        assetBundle.Unload(false);
    }
 
    async Task<AssetBundle> GetAssetBundle(string url)
    {
        return (await new WWW(url)).assetBundle
    }
 
    IEnumerator CustomCoroutineAsync()
    {
        yield return new WaitForSeconds(1.0f);
    }
 
    IEnumerator CustomCoroutineWithReturnValue()
    {
        yield return new WaitForSeconds(1.0f);
        yield return "asdf";
    }
}

As you can see, using async await like this can be very powerful, especially when you start composing multiple async methods together like in the LoadModelAsync method above.

Note that for async methods that return values, we use the generic version of Task and pass our return type as the generic argument like with the GetAssetBundle above.

Note also that using WaitForSeconds above is actually preferable to our TimeSpan extension method in most cases because WaitForSeconds will use the Unity game time whereas our TimeSpan extension method will always use real time (so it would not be affected by changes to Time.timeScale)

Triggering Async Code and Exception Handling

One thing you might have noticed with our code above is that some methods are defined ‘async void’ and some are defined ‘async Task’. So when should you use one over the other?

The main difference here is that methods that are defined ‘async void’ cannot be waited on by other async methods. This would suggest that we should always prefer to define our async methods with return type Task so that we can ‘await’ on them.

The only exception to this rule is when you want to call an async method from non-async code. Take the following example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class AsyncExample : MonoBehaviour
{
    public void OnGUI()
    {
        if (GUI.Button(new Rect(100, 100, 100, 100), "Start Task"))
        {
            RunTaskAsync();
        }
    }
 
    async Task RunTaskAsync()
    {
        Debug.Log("Started task...");
        await new WaitForSeconds(1.0f);
        throw new Exception();
    }
}

In this example, when the user clicks the button, we want to start our async method. This code will compile and run, however there is a major issue with it. If any exceptions occur within the RunTaskAsync method, they will happen silently. The exception will not be logged to the unity console.

This is because when exceptions occur in async methods returning Task, they are captured by the returned Task object instead of being thrown and handled by Unity. This behaviour exists for a good reason: To allow async code to work properly with try-catch blocks. Take the following code for example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
async Task DoSomethingAsync()
{
    var task = DoSomethingElseAsync();
 
    try
    {
        await task;
    }
    catch (Exception e)
    {
        // do something
    }
}
 
async Task DoSomethingElseAsync()
{
    throw new Exception();
}

Here, the exception is captured by the Task returned by the DoSomethingElseAsync method and is only re-thrown when it is ‘awaited’. As you can see, invoking async methods is distinct from awaiting on them, which is why it’s necessary to have the Task object capture the exceptions.

So in our OnGUI example above, when the exception is thrown inside the RunTaskAsync method, it is captured by the returned Task object, and since nothing awaits on this Task, the exception does not get bubbled up to Unity and therefore is never logged to the console.

But that leaves us with the question of what to do in these cases where we want to call async methods from non-async code. In our example above, we want to start the RunTaskAsync async method from inside the OnGUI method and we don’t care about waiting for it to complete, so we don’t want to have to add an await just so that exceptions can be logged.

The rule of thumb to remember here is:

Never call `async Task` methods without also awaiting on the returned Task. If you don’t want to wait for the async behaviour to complete, you should call an `async void` method instead.

So our example becomes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class AsyncExample : MonoBehaviour
{
    public void OnGUI()
    {
        if (GUI.Button(new Rect(100, 100, 100, 100), "Start Task"))
        {
            RunTask();
        }
    }
 
    async void RunTask()
    {
        await RunTaskAsync();
    }
 
    async Task RunTaskAsync()
    {
        Debug.Log("Started task...");
        await new WaitForSeconds(1.0f);
        throw new Exception();
    }
}

If you run this code again, you should now see that the exception is logged. This is because when the exception gets thrown during the await in the RunTask method, it bubbles up to Unity and gets logged to the console, because in that case there is no Task object to capture it instead.

Methods that are marked as `async void` represent the root level ‘entry point’ for some async behaviour. A good way to think about them is that they are ‘fire and forget’ tasks that go off and execute some number of things in the background while any calling code immediately continues on.

By the way, this is also a good reason to follow the convention of always using the suffix ‘Async’ on async methods that return Task. This is standard practice in most code bases that use async-await. It is helpful in conveying the fact that the method should always be preceded by an ‘await’, but also allows you to create an `async void` counterpart for the method that does not include the suffix.

Also worth mentioning is that if you are compiling your code in visual studio, then you should receive warnings when you attempt to call an `async Task` method without an associated await, which is a great way to avoid this mistake.

As an alternative to creating your own ‘async void’ method, you can also use a helper method (included with the source code associated with this article) that will perform the await for you. In this case our example would become:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class AsyncExample : MonoBehaviour
{
    public void OnGUI()
    {
        if (GUI.Button(new Rect(100, 100, 100, 100), "Start Task"))
        {
            RunTaskAsync().WrapErrors();
        }
    }
 
    async Task RunTaskAsync()
    {
        Debug.Log("Started task...");
        await new WaitForSeconds(1.0f);
        throw new Exception();
    }
}

The WrapErrors() method is simply a generic way to ensure that the Task gets awaited on, so that Unity will always receive any exceptions that are thrown. It simply does an await and that’s it:

1
2
3
4
public static async void WrapErrors(this Task task)
{
    await task;
}

Calling async from coroutines

For some code bases, migrating away from coroutines to use async-await might seem like a daunting task. We can make this process simpler by allowing async-await to be adopted incrementally. In order to do this however, we not only need the ability to call IEnumerator code from async code but we also need to be able to call async code from IEnumerator code. Thankfully, we can add this very easily with yet another extension method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static class TaskExtensions
{
    public static IEnumerator AsIEnumerator(this Task task)
    {
        while (!task.IsCompleted)
        {
            yield return null;
        }