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

카테고리

분류 전체보기 (2844)
Unity3D (891)
Programming (479)
Server (33)
Unreal (4)
Gamebryo (56)
Tip & Tech (188)
협업 (64)
3DS Max (3)
Game (12)
Utility (141)
Etc (99)
Link (32)
Portfolio (19)
Subject (90)
iOS,OSX (52)
Android (16)
Linux (5)
잉여 프로젝트 (2)
게임이야기 (3)
Memories (20)
Interest (38)
Thinking (38)
한글 (30)
PaperCraft (5)
Animation (408)
Wallpaper (2)
재테크 (19)
Exercise (3)
나만의 맛집 (3)
냥이 (10)
육아 (16)
Total
Today
Yesterday


[링크]

https://themangs.tistory.com/entry/%EC%9C%A0%EB%8B%88%ED%8B%B0%EB%9E%91-nodejs-%EA%B0%84%EB%8B%A8-%EC%97%B0%EB%8F%99-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%95%B4%EB%B3%B4%EA%B8%B0


[링크2]

https://github.com/NetEase/UnitySocketIO

반응형

'Unity3D > Plugins' 카테고리의 다른 글

[링크] FancyScrollView (CoverFlow)  (0) 2019.04.24
[링크] Simple Unity browser  (0) 2019.03.28
[펌] Socket.io-Client for Unity3D 소개  (0) 2019.03.08
[링크] iOSPlugin Sample  (0) 2019.01.29
[펌] Unity Excel Importer  (0) 2019.01.03
Posted by blueasa
, |


image.png

들어가면서

socket.io

웹 환경에서는 클라이언트인 브라우저와 서버 간에 푸쉬나 실시간 데이터를 처리하는데 여러 가지 방법을 활용했습니다. 그중에 Ajax를 이용한 polling, Long polling과 Streaming의 특징은 다음 그림과 같이 비교할 수 있습니다.

image.png
출처 : https://blogs.oracle.com/theaquarium/entry/slideshow_example_using_comet_dojo

위와 같은 방식을 활용하다가 나온 표준이 Websocket입니다. Websocket은 기존에 게임 개발자들이 활용하고 있던 TCP socket과 비슷하게 데이터를 주고 받을 수 있으며 Polling보다 자유롭게 데이터를 주고 받을 수 있게 되었습니다. Webocket에 대해 좀더 자세한 내용을 원하시면 여기 링크(https://ko.wikipedia.org/wiki/웹소켓) 를 참조 부탁드립니다. 하지만 이 좋은 Websocket의 단점은 지원하는 브라우저가 없을 수 있다는 점이 입니다. 이런 단점을 보완 하고자 브라우저와 상관 없이 웹에서 실시간으로 데이터를 처리 할수 있게 도와주는 것이 바로 socket.io입니다. socket.io(http://socket.io)는 Guillermo Rauch에의해 LearnBoost라는 회사가 개발했으며 MIT 라이선스의 오픈소스입니다.

socket.io는 WebSocket, FlashSocket, JSONP과 Long Polling을 하나로 묶어 브라우저에서 Websocket을 지원하지 않더라도 실시간 데이터처리를 돕습니다. 다양한 브라우저에서 데이터를 처리하기 위한 방법 뿐만 아니라 데이터를 주고 받기 위한 메세지 규약, 연결 유지를 위한 핑퐁 등과 같이 socket 연결을 위해 기본으로 구현해야할 부분까지 처리가 되어있기 때문에 서비스 개발자는 실시간 데이터 처리만 집중할 수 있습니다. socket.io의 프로토콜 정의는 링크 https://github.com/socketio/socket.io-protocol와 https://github.com/socketio/engine.io-protocol 참조 하시면 됩니다.

socket.io-Client

요 근래 게임에서도 실시간으로 데이터를 처리 하기 위해 Websocket을 많이 활용하는 추세 입니다. 왜냐하면 TCP socket보다 단순하게 구현할 수 있기 때문입니다. 실제로 Unity3d에서 WebGL로 빌드 할 때에도 데이터를 주고 받는 방법으로 Websocket을 이용하고 있습니다. socket보다 구현하기 쉬운 Websocket도 사실 데이터처리가 용의할 뿐 연결을 체결하거나 유지하는 등의 네트워크 레이어는 개발자가 직접 구현해야 합니다. 하지만 socket.io를 이용하면 연결과 기본적인 프로토콜이 구현되어 있기 때문에 개발자는 컨텐츠 데이터를 주고 받는데만 집중할 수 있습니다.

socket.io는 다양한 언어를 지원합니다. Javascript는 물론이고, Java, Swift와 CPP을 지원합니다. 지원하는 언어는 아래와 같습니다.

개발 하기까지

우리 회사에는 풋볼데이와 야구9단과 같은 웹 게임을 개발하면서 데이터를 push해주기 위해 socket.io를 이용했습니다. 기존의 socket.io구현체는 Node.js를 활용한 구현체 였지만 우리회사는 JAVA를 활용하여 대용량 트래픽 처리와 보안 인증을 추가한 플랫폼으로 구현했습니다. 해당 플랫폼을 사내 공용 플랫폼화 하여 웹 게임 뿐만 아니라 다른 서비스에도 적용하였고, 웹과 네이티브 앱간의 데이터 통신하는데 활용하고 있습니다.

기존의 socket.io 클라이언트 들은 다양한 네이티브 언어를 지원하고 있으나 게임개발에 많이 활용되고 있는 unity3d는 지원하지 않습니다. 우리는 우리의 플랫폼을 게임에도 활용하여 게임 개발자들에게 데이터 처리를 위해 네트워크를 개발하는 수고를 덜어주고 싶었습니다. 서버는 우리 회사의 플랫폼도 있고 오픈소스인 socket.io node.js용 서버도 있으니 클라이언트만 있다면 게임 개발자들도 쉽게 개발 할수 있을 것 같았습니다. 하지만 기존의 unity3d용 라이브러리들은 0.9버전이거나 유지 보수가 되지 않아서 우리가 직접 socket.io-Client Unity3D 라이브러리를 개발하게 되었습니다.

socket.io 사용법

실제로 Node.js + socket.io-Client Unity3D를 이용하여 구현된 샘플 코드를 살펴보면서 socket.io 사용법을 소개하겠습니다. 서버/클라이언트 코드로 구성되며 서버 코드는 socket.io 공식 홈페이지에서 발췌/변경하였습니다. socket.io 공식 홈페이지는 아래 링크를 확인해주세요.
socket.io 공식 홈페이지

추가로 Node.js로 직접 서버 코드를 실행해보고 싶으신 분들은 아래 링크를 참조하여서 서버 개발 환경 셋팅을 할 수 있습니다.
Node.js and Visual Studio Code End to End

예제를 구현하기 위한 소스는 아래 링크에서 찾아 보실 수 있습니다.

Connect to Server

첫번째 해야할 것은 역시나 컨넥션을 맺는 것입니다. 서버는 우선 HTTP 서버를 오픈해야합니다. 이는 socket.io 클라이언트가 최초엔 Polling 모드로 접속을 시도하도록 구현되어 있기 때문입니다.

서버 코드 [https://github.com/nhnent/socket.io-client-unity3d/blob/master/Assets/__Sample/Server~/connection.js]
var app = require('http').createServer(handler)
var fs = require('fs');

app.listen(4444);

function handler (req, res) {
  fs.readFile(__dirname + '/index.html',
  function (err, data) {
      if (err) {
          res.writeHead(500);
          return res.end('Error loading index.html');
      }

      res.writeHead(200);
      res.end(data);
  });
}

// socket.io 스타트
var io = require('socket.io')(app);

// 클라이언트 컨넥션 이벤트 처리
io.on('connection', function (socket) {
  console.log('Hello, socket.io~');
});

Socket.io가 시작되는 것은 'socket.io' 모듈을 가져올 때, 원샷에 처리가 됩니다. 이 때, 인자값으로 HTTP 서버의 인스턴스 (app 변수)를 추가해주면 HTTP 서버의 주소로 Socket.io가 Bind되면서 자동으로 Listen을 시작합니다. 이 때, 리턴하는 io 변수는 일종의 매니저 객체로써 이를 통해서 실제 세션에 해당하는 socket 객체를 획득할 수 있습니다. io.on() 메소드에서 'connection'이라는 이벤트를 핸들링하도록 구현했고, 클라이언트에서 컨넥션이 완료되었을 때, 콜백 함수를 통해서 socket을 획득할 수 있습니다. socket은 일종의 세션 객체라고 생각해도 무방합니다. socket을 통해서 앞으로 각종 패킷을 Send/Receive하게 됩니다.

클라이언트 코드 [https://github.com/nhnent/socket.io-client-unity3d/blob/master/Assets/__Sample/Client/Src/Connection.cs]
using UnityEngine;
using socket.io;

namespace Sample {

    public class Connection : MonoBehaviour {

        void Start() {
            // 접속 Url
            var serverUrl = "http://localhost:4444";

            // 서버로 접속 시도~
            var socket = Socket.Connect(serverUrl);

            // 접속 완료 이벤트 처리
            socket.On("connect", () => {
                Debug.Log("Hello, socket.io~");
            });
        }

    }

}

클라이언트는 socket.io.Socket 클래스의 Connect() 메소드를 호출합니다. 이 때 인자값으로 서버의 Url을 입력합니다. 접속이 성공하면, socket 변수를 리턴하고 서버와 동일하게 각종 패킷을 Send/Receive하게 됩니다. 예제 코드에서는 'connect' 이벤트를 캐치하여 웰컴 메세지를 출력하도록 구현해보았습니다.

Event 핸들링

socket.io는 Event 기반으로써 모든 통신의 처리는 'Event 등록 + Callback 함수 구현'의 형식으로 구현됩니다. Event를 Send하는 메소드는 Emit(), 그리고 Event를 Receive하는 메소드는 On()입니다. 아래 예제를 보면서 좀 더 자세히 살펴보겠습니다.

서버 코드 [https://github.com/nhnent/socket.io-client-unity3d/blob/master/Assets/__Sample/Server~/events.js]
// HTTP 서버 코드는 생략 (...)

// socket.io 스타트
var io = require('socket.io')(app);

// 클라이언트 컨넥션 이벤트 처리
io.on('connection', function (socket) {

    // 'news' 이벤트 send
    socket.emit('news', { hello: 'world' });

    // 'my other event' 이벤트 receive
    socket.on('my other event', function(data) {
        console.log(data);
    });

});
클라이언트 코드 [https://github.com/nhnent/socket.io-client-unity3d/blob/master/Assets/__Sample/Client/Src/Events.cs]
using UnityEngine;
using socket.io;

namespace Sample {

    public class Events : MonoBehaviour {

        void Start() {
            var serverUrl = "http://localhost:4444";
            var socket = Socket.Connect(serverUrl);

            // "news" 이벤트 처리 Receive
            socket.On("news", (string data) => {
                Debug.Log(data);

                // "my other event" 이벤트 Send
                socket.Emit(
                    "my other event",       // 이벤트명
                    "{ \"my\": \"data\" }"  // 데이터 (Json 텍스트)
                    );
            });
        }

    }

}

2개의 이벤트를 정의하여 서버와 클라이언트 간의 패킷 송수신을 구현했습니다. 서버는 'news' 이벤트를 Send하고 'my other event'를 Receive합니다. 클라이언트는 반대입니다.

실행 결과
  • 서버
    image.png
  • 클라이언트
    image.png

Emit() 메소드

Send를 담당하는 Emit() 메소드는 다음과 같은 형식을 같습니다.

public void Emit(string evtName, string data);
  • Emit() 파라메터
evtNamedata
이벤트 이름이벤트 데이터 (주로 Json 객체)

On() 메소드

Receive를 담당하는 On() 메소드의 정의는 다음과 같습니다.

public void On(string evtName, Action<string> callback);
  • On() 파라메터
evtNamecallback
이벤트 이름이벤트 핸들러 함수

System Event

Emit()과 On() 메소드에서 선언한 이벤트 이름 중 일부 문자열은 시스템 예약어이므로 사용을 금지합니다. 서버 용 시스템 이벤트는 아래 링크에서 확인이 가능합니다.

[https://socket\.io/docs/server\-api/\]\(socket\.io Server API)

여기서는 클라이언트 용 시스템 이벤트만 정리해보겠습니다.

이벤트 이름콜백 파라메터비고
connect없음접속 완료
connectTimeOut없음시간 초과로 인한 접속 실패
reconnectAttempt없음재접속 시도
reconnectFailed없음재접속 실패
reconnectint (재접속 횟수)재접속 완료
reconnectingint (재접속 횟수)재접속 중
connectErrorException접속 실패
reconnectErrorException재접속 실패

콜백 파라메터를 기준으로 On() 메소드 호출 형식이 바뀌게 됩니다. 예를 들어 'reconnect' 이벤트를 핸들링하고 싶다면 다음과 같이 코드를 작성해야 합니다.

socket.On("reconnect", (int attempt) => {
    Debug.LogFormat("Reconnected after {0} trials, attempt);
});

Event Ack

이번엔 이벤트를 Send하고 이에 대한 응답을 Receive하는 방법을 알아보겠습니다.

서버 코드 [https://github.com/nhnent/socket.io-client-unity3d/blob/master/Assets/__Sample/Server~/acks.js]
// HTTP 서버 코드는 생략 (...)

// socket.io 스타트
var io = require('socket.io')(app);

// 클라이언트 컨넥션 이벤트 처리
io.on('connection', function (socket) { 

   socket.on('ferret', function (name, fn) {
        // 'woot' 문자열을 Ack 메세지로 보냄
        fn('woot');
    }); 
});
클라이언트 코드 [https://github.com/nhnent/socket.io-client-unity3d/blob/master/Assets/__Sample/Client/Src/Acks.cs]
using UnityEngine;
using socket.io;

namespace Sample {

    public class Acks : MonoBehaviour {

        void Start() {
            var serverUrl = "http://localhost:4444";
            var socket = Socket.Connect(serverUrl);

            socket.On("connect", () => {

                // "ferret" 이벤트 Send
                socket.Emit(
                    "ferret", "\"toby\"", 
                    (string ackData) => { Debug.Log(ackData); } // 3번째 인자로 콜백을 셋팅하면 Ack 모드로 동작하며 Ack 시에 콜백이 호출됨
                    );
            });
        }

    }

}

앞서 소개한 Event 핸들링 예제와 달라진 점은 클라이언트 코드에서 Emit() 메소드에 3번째 인자로 콜백을 추가한 것입니다. 이렇게 호출하게 되면 Ack 모드로 Emit() 메소드가 동작하게 됩니다. 위의 예제에서는 서버 코드에서 Ack 메시지로 'woot' 문자열을 리턴하였고, 클라이언트는 Ack 콜백에서 'woot' 문자열을 인자값으로 받게 됩니다.

Namespace

하나의 소켓을 논리적인 처리 단위로 구분할 수 있습니다. Multiplexing이라고도 불리는 이러한 패턴을 통해서 하나의 커넥션(소켓 인스턴스)에서 여러 개의 독립적인 코드를 개발할 수 있습니다.

서버 코드 [https://github.com/nhnent/socket.io-client-unity3d/blob/master/Assets/__Sample/Server~/namespace.js]
// HTTP 서버 코드는 생략 (...)

// socket.io 스타트
var io = require('socket.io')(app);

// chat 네임스페이스
var chat = io
  .of('/chat')
  .on('connection', function (socket) {
    chat.emit('a message', { everyone: 'in', '/chat': 'will get' });
    socket.emit('a message', { that: 'only', '/chat': 'will get' });
  });

// news 네임스페이스
var news = io
  .of('/news')
  .on('connection', function (socket) {
    socket.emit('item', { news: 'item' });
  });
클라이언트 코드 [https://github.com/nhnent/socket.io-client-unity3d/blob/master/Assets/__Sample/Client/Src/Namespace.cs]
using UnityEngine;
using socket.io;

namespace Sample {

    public class Namespace : MonoBehaviour {

        void Start() {
            var serverUrl = "http://localhost:4444";

            // news 네임스페이스
            var news = Socket.Connect(serverUrl + "/news");
            news.On("connect", () => {
                news.Emit("woot");
            });
            news.On("a message", (string data) => {
                Debug.Log("news => " + data);
            });
            news.On("item", (string data) => {
                Debug.Log(data);
            });

            // chat 네임스페이스
            var chat = Socket.Connect(serverUrl + "/chat");
            chat.On("connect", () => {
                chat.Emit("hi~");
            });
            chat.On("a message", (string data) => {
                Debug.Log("chat => " + data);
            });
        }

    }

}

서버는 of() 메소드를 통해서 네임스페이스를 선언합니다. 클라이언트는 단순히 Url에 주소를 추가하는 것으로 선언이 가능합니다. 위 예제에선 "http://localhost:4444/chat"과 "http://localhost:4444/news"와 같이 chat과 news 네임스페이스를 선언하였습니다. 그 외에 나머지는 일반적인 이벤트 핸들링과 동일합니다.

실행 결과

image.png

위 예제의 포인트는 클라이언트 코드에서 chat과 news이 모두 'a message' 이벤트를 등록하였지만 오직 chat 소켓만이 'a message' 이벤트를 핸들링하게 되는 점입니다. 네임스페이스로 구분하여 이벤트명을 중복하여 사용하는 것이 가능하다라는 것을 시사하며 이를 통해서 채널 혹은 컨텐츠 개발 스코프마다 각각 네임스페이스르 할당해서 작업한다면 하나의 소켓 컨넥션 안에서도 이벤트 이름의 충돌없이 코드 작성이 가능하게 됩니다.

결론

오랫동안 게임 개발에서는 Socket을 이용한 네트워크를 구현해 왔습니다. 하지만 socket을 이용하여 데이터를 주고 받기 위해서는 네트워크의 연결 유지와 데이터 전송을 위해 너무 많은 노력이 필요합니다. 게임 개발에 있어서 어려운 네트워크 구현을 빼고 컨텐츠 개발에 힘을 쓴다면 더 좋은 게임이 나올 것이라 생각됩니다. 더욱이 웹과 같은 다양한 플랫폼을 지원 하는 게임을 개발하는데 있어 네트워크 레이어마다 개발을 해야한다면 좀더 좋은 게임을 개발할 수 있는시간을 빼앗기게 됩니다. 그래서 우리는 게임 개발자들이 데이터 통신에 많은 시간을 투자하지 않고 게임을 개발 할수 있도록 socket.io를 위한 Unity3d를 MIT라이선스로 공개하기로 했습니다. socket.io-client-unity3d를 활용하여 좀더 컨텐츠 개발에 시간을 투자하고, 하나의 서버 플랫폼을 이용해 다양한 클라이언트 플랫폼을 지원 하는데 도움이 되었으면 좋겠습니다.

해당 라이브러리에 관심이 있으신분들은 https://github.com/nhnent/socket.io-client-unity3d에서 확인 하실 수 있습니다. 또한 참여하시고 싶으신 분들은 https://github.com/nhnent/socket.io-client-unity3d/blob/master/CONTRIBUTING.md를 참조하셔서 참여하여 주시면 감사하겠습니다.


[출처]

https://meetup.toast.com/posts/112



[추가]

socket.io v2.x에서 수정해야 될 내용

https://github.com/nhnent/socket.io-client-unity3d/commit/48b1fb6e743ff2228942a6e08f2eb9a73e1ff2a0


[추가2]

socket.io https 대응 (출처: shmhlove)

https://github.com/shmhlove/Asgardium_Client/tree/develop/Assets/Plugins/socket.io

반응형

'Unity3D > Plugins' 카테고리의 다른 글

[링크] Simple Unity browser  (0) 2019.03.28
[링크] 유니티랑 nodejs 간단 연동 테스트 해보기  (0) 2019.03.08
[링크] iOSPlugin Sample  (0) 2019.01.29
[펌] Unity Excel Importer  (0) 2019.01.03
[링크] Emoji_Extension  (0) 2018.06.07
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;
        }
 
        if (task.IsFaulted)
        {
            throw task.Exception;
        }
    }
}

Now we can call async methods from coroutines like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class AsyncExample : MonoBehaviour
{
    public void Start()
    {
        StartCoroutine(RunTask());
    }
 
    IEnumerator RunTask()
    {
        yield return RunTaskAsync().AsIEnumerator();
    }
 
    async Task RunTaskAsync()
    {
        // run async code
    }
}

Multiple Threads

We can also use async-await to execute multiple threads. You can do this in two ways. The first way is to use the ConfigureAwait method like this:

1
2
3
4
5
6
7
8
9
10
11
12
public class AsyncExample : MonoBehaviour
{
    async void Start()
    {
        // Here we are on the unity thread
 
        await Task.Delay(TimeSpan.FromSeconds(1.0f)).ConfigureAwait(false);
 
        // Here we may or may not be on the unity thread depending on how the task that we
        // execute before the ConfigureAwait is implemented
    }
}

As mentioned above, Unity provides something called a default SynchronizationContext, which will execute asynchronous code on the main Unity thread by default. The ConfigureAwait method allows us to override this behaviour, and so the result will be that the code below the await will no longer be guaranteed to run on the main Unity thread and will instead inherit the context from the task that we are executing, which in some cases might be what we want.

If you want to explicitly execute code on a background thread, you can also do this:

1
2
3
4
5
6
7
8
9
10
11
12
public class AsyncExample : MonoBehaviour
{
    async void Start()
    {
        // We are on the unity thread here
 
        await new WaitForBackgroundThread();
 
        // We are now on a background thread
        // NOTE: Do not call any unity objects here or anything in the unity api!
    }
}

WaitForBackgroundThread is a class included in the source code for this post, and will do the work of starting a new thread and also ensuring that Unity’s default SynchronizationContext behaviour is overridden.

What about returning to the Unity thread?

You can do this simply by awaiting any of the Unity specific objects that we created above. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AsyncExample : MonoBehaviour
{
    async void Start()
    {
        // Unity thread
 
        await new WaitForBackgroundThread();
 
        // Background thread
 
        await new WaitForSeconds(1.0f);
 
        // Unity thread again
    }
}

The included source code also provides a class WaitForUpdate() that you can use if you just want to return to the unity thread without any delay:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AsyncExample : MonoBehaviour
{
    async void Start()
    {
        // Unity thread
 
        await new WaitForBackgroundThread();
 
        // Background thread
 
        await new WaitForUpdate();
 
        // Unity thread again
    }
}

Of course, if you do use background threads, you need to be very careful to avoid concurrency issues. However it can be worth it in a lot of cases to improve performance.

Gotchas and Best Practices

  • Avoid async void in favour of async Task, except in ‘fire and forget’ cases where you want to start async code from non-async code
  • Attach the suffix ‘Async’ to all async methods which return Task. This is helpful in conveying the fact that it should always be preceded by an ‘await’ and allows an async void counterpart to be added easily without conflict
  • Debugging async methods using breakpoints in visual studio doesn’t work yet. However the “VS tools for Unity” team says that they are working on it, as indicated here

UniRx

Yet another way to do asynchronous logic is to use reactive programming with a library like UniRx. Personally I am a huge fan of this type of coding and use it extensively in many projects that I’m involved with. And thankfully, it is very easy to use alongside async-await with just another custom awaiter. For example:

1
2
3
4
5
6
7
8
9
10
public class AsyncExample : MonoBehaviour
{
    public Button TestButton;
 
    async void Start()
    {
        await TestButton.OnClickAsObservable();
        Debug.Log("Clicked Button!");
    }
}

I find that UniRx observables serve a different purpose from long-running async methods/coroutines, so they naturally fit alongside a workflow using async-await like in the examples above. I won’t go into detail here, because UniRx and reactive programming is a separate topic in itself, but I will say that once you get comfortable thinking about data flow in your application in terms of UniRx “streams”, there is no going back.

Source Code

You can download the source code that includes async-await support from either asset store or the releases section of the github repo.

Further Reading




[출처] http://www.stevevermeulen.com/index.php/2017/09/using-async-await-in-unity3d-2017/

반응형
Posted by blueasa
, |


[링크] https://github.com/shmhlove/iOSPlugin

반응형
Posted by blueasa
, |

Firebase를 앱에 추가 및 Firebase Console에서 설정을 다 하고나서


Android는 FA에서 제대로 체크가 되고 있는데

iOS는 FA에 뜨질 않아서 삽질 하던 중 알게 된 내용 정리해 놓음.


[참조1] https://firebase.google.com/docs/analytics/ios/start?hl=ko

FA에서는 위 참조1 링크의 설명과 같이 iOS의 XCode에서 작업하는 내용을 설명하고 있다.


설명에는 AppDelegate 파일이라고 돼있지만,

유니티에서 Export 된 XCode 프로젝트는 파일명이 약간 달라서 UnityAppController.mm 파일에서 application:(UIApplication*) didFinishLaunchingWithOptions 를 찾을 수 있다.


내 경우는 OBJECTIVE-C 여서 아래와 같은 소스를 UnityAppController.mm에 추가해서 빌드하니 FA에 잘 뜨는 걸 확인 했다.


@import Firebase;
// Use Firebase library to configure APIs
[FIRApp configure];



근데 유니티로 빌드하는데 네이티브인 XCode에서 항상 소스를 수정해줘야 된다는 건 귀찮기 때문에 유니티에서 그냥 셋팅하는 방법이 없나 하고 삽질하다가 찾아낸 방법이 아래와 같다.



[참조2] https://github.com/firebase/quickstart-unity/issues/91

참조2 링크 내용을 보면 FirebaseAnalytics.SetAnalyticsCollectionEnabled(true); 부분이 있다.

XCode에서 셋팅을 하지 않고, 유니티 실행 시 Firebase Analytics를 켤 수 있는 것 같다.


Firebase.Analytics.FirebaseAnalytics.SetAnalyticsCollectionEnabled(true);


namespace를 포함해서 위와 같이 앱 실행 시작 부분에서 실행하도록 해주고나서 iOS 앱에서도 FA 체크가 잘되는 걸 확인 완료.



반응형
Posted by blueasa
, |

오라클의 유료화 이슈로 OpenJDK를 설치하려는데

현재 버전(Unity v5.6.6f2)에서 좀 편하게 설치할 방법이 없을까 하다가 꼼수로 해놓은 방법 간단 정리해 놓습니다.



[유니티 버전] v5.6.6f2


[OpenJDK 간단(?) 설치 방법]

1. Unity2018.3 이상 버전을 설치


2. Unity2018.3의 Preferences에 보면 아래와 같이 기본 내장 OpenJDK가 설정 돼 있다.


3. Unity2018.3이 설치 된 곳에 가보면 위에 설명된 (AndroidPlayer/Tools/OpenJDK)를 찾을 수 있는데 정확한 경로는 아래와 같다.

   2018.3 설치 위치를 찾아서 아래 경로대로 찾아가자.

  [Unity2018설치경로] ...\2018.3.0f2\Editor\Data\PlaybackEngines\AndroidPlayer\Tools\OpenJDK


4. 3번의 위치까지 찾아가면 아래와 같이 OS별 폴더가 보인다.


5. 유니티 5.6.6의 Preferences에서 JDK를 아래처럼 해당 OS 폴더까지 패스로 골라준다.


나의 경우 Unity Hub를 깔아서 전체 경로는 아래와 같다.


[전체경로]

C:\Program Files\Unity\Hub\Editor\2018.3.0f2\Editor\Data\PlaybackEngines\AndroidPlayer\Tools\OpenJDK\Windows



[추가]

Unity Hub를 쓰면 버전별로 설치가 계속 돼서 Path가 바뀌는 문제 때문에 OpenJDK 폴더를 통째로 복사해서 다른 곳에 옮겨 두고 쓰기로 했다.

(예: C:\OpenJDK)


반응형
Posted by blueasa
, |


[링크1]

https://m.blog.naver.com/PostView.nhn?blogId=syowoo&logNo=220518896040&proxyReferer=https%3A%2F%2Fwww.google.co.kr%2F


[링크2]

http://freecandoall.blogspot.com/2015/10/xcode.html

반응형
Posted by blueasa
, |

[파일] 

unity-excel-importer-master.zip


Unity Excel Importer

Automatically import from xls, xlsx to custom ScriptableObject in Unity Editor

Import Setup

1. Create Excel and add to Unity

Create an Excel file, make the first row the name of the column, and enter the data from the second row. And add it to Unity’s Project view.

create_excel import_excel_to_unity

2. Create Entity Class Script

Create a new script and define a class with Excel column names and public fields of the desired type. Also give the class ‘System.Serializable’ attribute.

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

[System.Serializable]
public class MstItemEntity
{
	public int id;
	public string name;
	public int price; 
}

3. Create Excel Asset Script

After selecting Excel, execute ExcelAssetScript from the Create menu and create a ScriptableObject script for Excel.

create_excel_asset

As for the generated script, the Excel file name and the sheet name are extracted and the part is commented out as below.

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

[ExcelAsset]
public class MstItems : ScriptableObject
{
	//public List<EntityType> Entities; // Replace 'EntityType' to an actual type that is serializable.
}

4. Replace EntityType in created Excel Asset

Uncomment fields and replace the generic type of List with the Entity class defined above.

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

[ExcelAsset]
public class MstItems : ScriptableObject
{
	public List<MstItemEntity> Entities;
}

4. Reimport or re-save Excel

When you import or re-save Excel, a ScriptableObject with the same name as Excel is created in the same directory and the contents of Excel are imported.

reimport_excel imported_entities

After this setting, updating Excel automatically updates ScriptableObject as well.

Advanced

Comment Row

If you enter ‘#’ in the first cell of the row, you can treat it as a comment and skip.

Change Asset Path

You can change the ScriptableObject generation position by specifying AssetPath as the ExcelAssetAttribute as shown below.

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

[ExcelAsset(AssetPath = "Resources/MasterData")]
public class MstItems : ScriptableObject
{
	public List<MstItemEntity> Entities;
}

Use Enum

You can use enum by entering the element name as string in cell. It is also useful to set Data Validation pull down as an element of enum in Excel.

Lisence

This library is under the MIT License. This software includes the work that is distributed in the Apache License 2.0.



[출처] https://unitylist.com/p/28t/Unity-excel-importer

반응형

'Unity3D > Plugins' 카테고리의 다른 글

[펌] Socket.io-Client for Unity3D 소개  (0) 2019.03.08
[링크] iOSPlugin Sample  (0) 2019.01.29
[링크] Emoji_Extension  (0) 2018.06.07
[펌] Background Worker for Unity3D  (0) 2018.06.01
[Link] UnityAppNameLocalizationForIOS  (0) 2018.03.27
Posted by blueasa
, |
웹 페이지를 여는 방법을 찾고 있었는데....조금 다른 것이 들어왔네요 ㅎ
웹 상에 노출되어 있는 이미지 url를 입력하면 유니티 내에서 볼 수 있게 해 주는 코드입니다.
구글 클라우드에 있는 것들을 가져오면 앨범도 만들어 볼 수 있겠습니다.

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

public class example : MonoBehaviour 
{
public string url = "https://community.arm.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/00-00-00-20-66/6180.mali_2D00_shield_2D00_2.png";

IEnumerator Start() 
{
WWW www = new WWW(url);
yield return www;
Renderer renderer = GetComponent<Renderer>();
renderer.material.mainTexture = www.texture;
}
}


반응형
Posted by blueasa
, |


[링크]

https://orcacode.tistory.com/entry/Unity%EC%97%90%EC%84%9C-iPhone-X%EB%A5%BC-%EB%8C%80%EC%9D%91%ED%95%98%EB%8A%94-%EA%B0%84%ED%8E%B8%ED%95%9C-%EB%B0%A9%EB%B2%95


[플러그인]

https://github.com/rlatkdgus500/UnitySafeAreaController


반응형
Posted by blueasa
, |