UnityでArtNetを受け取りたかった話

# 前置き
現在所属しているライブサークルで、今まで照明をdmxで光らせていたのをUnity上の仮装灯台を光らせるようにしなくちゃいけなくなった。そこでArtnetを使ったプラグインをつくったので投稿します。([今回のソースコードはこちら](https://github.com/Taku3939/ArtNetForUnity))
※注意: DaLight4でArtnetを使うには専用のデバイスの購入が必要です

# 内容
ArtNetはUDP通信を用いてdmxのデータを送信できる照明界隈で使われているプロトコル。これ使えば実際の照明もUnityとかUE4なんかで作ったバーチャル照明を同様に光らせることができます。

今回やりたかったことを図で説明するとこんな感じ。現在サークルで使っているDasLightというソフト側からイーサネット経由でUnityに信号を送った。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505843/37fd3498-cf21-b3cf-69bf-5593b67d2643.png)

### 実装について
ArtNetのプロトコルについてはwikiとドキュメントを読みました

https://artisticlicence.com/WebSiteMaster/User%20Guides/art-net.pdf

こいつによると受け取ったバイト列は次の表のようになるとのこと

| index |  中身 |
|---|---|
| 0 ～ 7 | Protocol (Art-Net) |
| 8, 9  | Opcode ArtDMX (0x5000) little endian|
| 10 | Protocol Version Hi (0)
| 11 | Protocol Version Lo (14)
| 12 | Sequence (255周期)
| 13 | Physical
| 14,15 | Universe little endian
| 16 | Length Hi
| 17 | Length Lo (2 to 512, even)	
| 18 ～  | Data

あとはこの表に従ってUdp通信で受け取ったデータをArtnetDataクラスを作成し、コトラクタで初期化を行いました。

```c#
/*!
 * Copyright (c) 2021 Takuya Isaki
 *
 * Released under the MIT license.
 * see https://opensource.org/licenses/MIT
 */

using System;
using UnityEditorInternal;
using UnityEngine;

namespace ArtNet.Runtime
{
    public readonly struct ArtNetData
    {
        #region public filed

        public readonly ArtNetOpCode OpCode;
        public readonly int Sequence, Physical, Universe;
        public readonly int[] Channels;
        public readonly int ProtocolVersionHi, ProtocolVersionLo, LengthHi, LengthLo;

        #endregion

        #region public method

        /// <summary>
        /// コンストラクタ
        /// Art-Net Structure
        /// 0 ～ 7 : id 「Art-Net」
        /// 8 ～ 9 : Opcode ArtDMX (0x5000) little endian
        /// 10 : Protocol Version Hi (0)
        /// 11 : Protocol Version Lo (14)
        /// 12 : Sequence (255周期)
        /// 13 : Physical
        /// 14 ～ 15 : Universe little endian
        /// 16 : Length Hi
        /// 17 : Length Lo (2 to 512, even)	
        /// 18 ～ 530 : Data
        /// </summary>
        /// <param name="buf">受信したbyte列</param>
        public ArtNetData(byte[] buf)
        {
            var buffer = new byte[530];
            Buffer.BlockCopy(buf, 0, buffer, 0, buf.Length);
            OpCode = (ArtNetOpCode)BitConverter.ToInt16(new byte[2] { buffer[8], buffer[9] }, 0);
            ProtocolVersionHi = buffer[10];
            ProtocolVersionLo = buffer[11];
            Sequence = buffer[12];
            Physical = buffer[13];
            Universe = BitConverter.ToInt16(new byte[2] { buffer[14], buffer[15] }, 0);
            LengthHi = buffer[16];
            LengthLo = buffer[17];
            Channels = new int[512];
            for (int i = 18; i < buffer.Length; i++)
                Channels[i - 18] = buffer[i];
        }

        public static bool IsArtNet(byte[] buffer)
            => buffer[0] == 'A' &&
               buffer[1] == 'r' &&
               buffer[2] == 't' &&
               buffer[3] == '-' &&
               buffer[4] == 'N' &&
               buffer[5] == 'e' &&
               buffer[6] == 't' &&
               buffer[7] == 0x00;

        public void Logger()
        {
            string str = "";
            str += $"Opcode : {this.OpCode}\n";
            str += $"ProtocolVersionHi : {ProtocolVersionHi}\n";
            str += $"ProtocolVersionLo : {ProtocolVersionLo}\n";
            str += $"Sequence : {Sequence}\n";
            str += $"Physical : {Physical}\n";
            str += $"Universe : {Universe}\n";
            str += $"LengthHi : {LengthHi}\n";
            str += $"LengthLo : {LengthLo}\n";
            foreach (var t in Channels) str += t + "\n";
            Debug.Log(str);
        }

        #endregion
    }
}
```

受信時はOpCodeをOpDmxのものを扱いたいので、下のようにコードを書く。

```c#
using ArtNet.Runtime;
using UnityEngine;

public class SampleLightReceiver : MonoBehaviour
{
    [SerializeField] private ArtNetClient client;
    [SerializeField] private new Light light;

    private void Start()
    {
        this.client.onDataReceived += data =>
        {
            if (data.OpCode == ArtNetOpCode.OpDmx)
            {
                light.color = new Color(
                    data.Channels[0],
                    data.Channels[1],
                    data.Channels[2]);
            }
        };
    }
}
```

DasLight4からArtnetを送れた
![dzfY3oK8wE.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505843/8be915dd-c332-71d1-06c9-474b5ea13017.gif)

## 参考文献
https://en.wikipedia.org/wiki/Art-Net

https://artisticlicence.com/WebSiteMaster/User%20Guides/art-net.pdf