Aizu-Progressive xr Lab blog

会津大学のVR部であるA-PxLの部員が持ち回りで投稿していくブログです。部員がそれぞれVRに関する出来事やVRにちなんだことについて学んだことを書いていきます。

連絡はサークルTwitterのDMへお願いします。
「面白法人カヤックVR分室」としても活動しています。詳細はこちら

【Odin Inspector】NPCの振る舞いをインスペクターで設定する

はじめに

こんにちは、学部3年の木村です。

タイトルの通り、ゲームに登場するNPCの行動をインスペクターで設定できるようにします。

想定するゲームにはNPCとしてMonster(獅子舞や仏像)が存在し、 各NPCは現在の状態に応じて探索や追跡などの行動をとることができます。
この場合、Dictionaryを使って状態と行動を対応付けたいのですがUnityのInspectorではGenericsを扱えないため、アクセス修飾子をpublicにしたり属性にSerializeFieledを付けたりしてもInspectorには表示されません。抽象クラスやインターフェースも同様です。

そこで、Odin - Inspector and Serializerというアセットを使用しました。

Odinとは

OdinはUnityのInspectorを使いやすくするプラグインです。

導入するだけでList等の表示がいい感じになったり、 (多分)何でもシリアライズできるようになったり、便利な属性が80以上使えるようになります。odininspector.comではどんなことができるようになるかがアニメーション付きで紹介されています。

$55の有料アセットですが、ゲーム制作中のパラメータ調整などが圧倒的に快適になるとても便利なアセットです。

本編

一部省略していますが、 大体以下のような構成です。

Monsterが継承しているSerializedMonoBehaviourは、Dictionary等もシリアライズできる(Inspectorで設定できる)MonoBehaviourです。元のMonoBehaviourの機能はそのまま使えます。

Inspectorでは次のように表示されます。(クラス図ではMonsterに定義しているActionsTableMonsterActionRepositoryで定義されています。)

DictionaryがInspectorに表示されており、Keyに状態(MonsterState)、Valueにその状態のときにとる行動(抽象クラスNpcActionの実装)を指定することでNPCの振る舞いを設定できます。

ついでに

OdinのButton属性を使って、InspectorのボタンからMonsterの状態を変更できるようにしています。

f:id:aizu-vr:20200921050826g:plain

public bool _enableDebug;

[Button, ShowIf(nameof(_enableDebug)), EnableIf(nameof(_enableDebug))]
private void ChangeState(MonsterState state)
{
    // 状態をstateに変更
}

終わり

Odinの紹介と活用例でした。
ゲーム制作中にUnityのインスペクターを使っていると微妙に手の届かないかゆいところがあり、自分でエディタ拡張を書いていたら沼にハマり肝心のゲーム制作が進まないということがあるかもしれません。そんなときにOdinはかゆいところをいい感じにかいてくれます。
ちょっと高めのアセットですがUnityがとても快適になるので、よかったら導入してみてはいかがでしょうか。

三次元極座標系でパーティクル

学部4年のユムルです。

最近Shaderをかいたり、VRのMV (Music Video) を作ったりして遊んでます。
作った物をVRSNSのVRChatにて人に見せたりして楽しんでいるのですが、最近ブタジエン(https://twitter.com/butadiene121)さんのシェーダーパーティクルに魅せられて、真似して次のようなシェーダーパーティクルを作りました。

これの解説をします。

パーティクルとして使うメッシュと表現の決定

今回はCubeのメッシュをもとに、次の表現を使うことにします。

f:id:aizu-vr:20200904223913p:plain
一つのパーティクル表現

書いたShaderは次のものです。

Shader "PolarParticle/Cube1" {
  Properties { }
  SubShader {
    Tags { "RenderType"="Transparent" "Queue"="Transparent" } // 透明
    Cull off
    Blend SrcAlpha One
    ZWrite Off
    LOD 100

    Pass {
      CGPROGRAM
      #pragma target 5.0
      #pragma vertex VS
      #pragma fragment FS
      
      #include "UnityCG.cginc"

      struct VS_OUT {
        float4 vertex : SV_Position;
        float4 texcoord : TEXCOORD0;
        float4 color : COLOR;
      };

      VS_OUT VS(appdata_full v) { 
        VS_OUT o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.texcoord = v.texcoord;
        o.color = float4(0, 1, 1, 1);
        return o;
      }

      float4 FS(VS_OUT i) : SV_Target {
        float2 uv = i.texcoord.xy;
        float2 p = uv * 2.0 - 1.0;
        float t = 0.01 / min(abs(abs(p.x) - 1.0), abs(abs(p.y) - 1.0));
        t = saturate(t);
        return float4(i.color.xyz, t);
      }
      ENDCG
    }
  }
}

メッシュの大量複製

メッシュをパーティクルとして扱うために、メッシュを大量にコピーしたいです。

私はこれをするために2種類の方法を知っています。 普通は次の方法1を使えば良いと思いますが、VRChatではそれをすることはできないので方法2を使いました。

それぞれの方法で試しに100万個のCubeを表示してみますが、後につくる極座標のパーティクルは1024個で作成します。

方法1

次の記事を参考にします。 shitakami.hatenablog.com feelingames.hatenablog.com

次のコードを書きます。

using UnityEngine;

public class GPUInstance : MonoBehaviour {
  public Mesh mesh;
  public Material material;
  public Bounds bounds;
  public int instanceNum;
  public ComputeBuffer buffer;

  void Start() {
    var args = new uint[5] { 0, 0, 0, 0, 0 };
    args[0] = mesh != null ? (uint) mesh.GetIndexCount(0) : 0;
    args[1] = (uint) instanceNum;
    buffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments);
    buffer.SetData(args);
  }

  void Update() {
    Graphics.DrawMeshInstancedIndirect(
      mesh,
      0,
      material,
      bounds,
      buffer);
  }

  void OnDestroy() {
    if (buffer != null) buffer.Release();
  }
}

取り敢えず大量のCubeを規則正しく並べるシェーダーを書きます、

Shader "PolarParticle/ManyCube" {
  Properties { }
  SubShader {
    Tags { "RenderType"="Transparent" "Queue"="Transparent" }
    Cull off
    Blend SrcAlpha One
    ZWrite Off
    LOD 100

    Pass {
      CGPROGRAM
      #pragma target 5.0
      #pragma vertex VS
      #pragma fragment FS
      
      #include "UnityCG.cginc"

      struct VS_OUT {
        float4 vertex : SV_Position;
        float4 texcoord : TEXCOORD0;
        float4 color : COLOR;
      };

      static uint particleNum = 1000000; // パーティクルの数
      VS_OUT VS(appdata_base v, uint instanceId : SV_InstanceID) {
        VS_OUT o;
        uint id = instanceId;  // パーティクルのID
        uint n = pow(particleNum, 1.0 / 3.0);
        float particleSize = 0.1; // パーティクルのサイズ
        float interval = 1; // パーティクル間の距離
        float zoneLength = (n-1) * interval; // 全体の範囲の長さ
        uint3 axisId = uint3( // 極ごとのID
          id % n,
          id / n % n,
          id / (n * n));
        float3 pos = (float3)(-zoneLength / 2) + axisId * interval;
        v.vertex.xyz = v.vertex.xyz * particleSize + pos;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.texcoord = v.texcoord;
        o.color = float4(0, 1, 1, 1);
        return o;
      }

      float4 FS(VS_OUT i) : SV_Target {
        float2 uv = i.texcoord.xy;
        float2 p = uv * 2.0 - 1.0;
        float a = 0.01 / min(abs(abs(p.x) - 1.0), abs(abs(p.y) - 1.0));
        a = saturate(a);
        return float4(i.color.rgb, a);
      }
      ENDCG
    }
  }
}

書いたシェーダからマテリアルを作成し、GameObjectに先ほどのC#のコードをアタッチして、次のように100万個のCubeが出るように設定します。

f:id:aizu-vr:20200905185451p:plain

シーンを再生すると、100万個のCubeの位置が頂点シェーダーによって決められているのを確認できます。 f:id:aizu-vr:20200905185554p:plain

後のパーティクルの為、今回はInstance Numを1024に設定してください。

方法2

VRChatでメッシュのパーティクルを使う場合は、C#によるスクリプトを組み込んだ作品をアップロードできません。したがって、方法1も使えません。 代わりに間接的にC#を用いて、Meshを複製してアセットファイル化するスクリプトを書き、更にジオメトリシェーダーの[instance(31)]にて31倍にメッシュを増やします。

メッシュを複製したアセットファイルを作成するため、C#のエディタ拡張を書きます

using System.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;

public class MeshCopy : EditorWindow {
  [MenuItem("MyEditor/MeshCopy")]
  static void Open() {
    GetWindow<MeshCopy>("MeshCopy");
  }

  Mesh useMesh;  // コピー元のメッシュ
  int copyN; // 複製する数

  void OnGUI() {
    useMesh = (Mesh) EditorGUILayout.ObjectField("Use Mesh", useMesh, typeof(Mesh), false);
    copyN = EditorGUILayout.IntField("Copy Num", copyN);
    if (GUILayout.Button("Mesh N Copy")) {
      MeshNCopy();
    }
  }

  void MeshNCopy() {
    var vertices = new List<Vector3>();
    var texcoord = new List<Vector3>();
    var triangles = new List<int>();
    var normals = new List<Vector3>();
    var tangents = new List<Vector4>();
    var colors = new List<Color>();
    // ここのループでメッシュをコピー、texcoordのz値にidを入れておく
    for (int i = 0; i < copyN; i++) {
      vertices.AddRange(useMesh.vertices);
      texcoord.AddRange(useMesh.uv.Select(v => new Vector3(v.x, v.y, i)));
      triangles.AddRange(useMesh.triangles
        .Select(index => index + i * useMesh.vertexCount));
      normals.AddRange(useMesh.normals);
      tangents.AddRange(useMesh.tangents);
      colors.AddRange(useMesh.colors);
    }
    var mesh = new Mesh();
    mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
        // indexFormatを変えておかないとtrianglesの値の上限を余裕で超えてしまう
    mesh.SetUVs(0, texcoord);
    mesh.triangles = triangles.ToArray();
    mesh.normals = normals.ToArray();
    mesh.tangents = tangents.ToArray();
    mesh.colors = colors.ToArray();
    string path = string.Format("Assets/{0}x{1}.asset", useMesh.name, copyN);
    AssetDatabase.CreateAsset(mesh, path);
    AssetDatabase.SaveAssets();
  }
}
#endif

Unity上部のメニューにある、MyEditor -> MeshCopyを開き、Cubeのメッシュを設定し、後でメッシュを更に31倍するので、Copy Numを100万 / 31 = 約33000個 に設定しボタンを押します。
すると、Assets直下に33000個のCubeメッシュのアセットファイルが生成されます。
とはいえこの時点で、メッシュファイルのサイズは81MBになってしまうので、実用的に考えるならCube1024個で2MBくらいのものになるでしょう。

f:id:aizu-vr:20200904234839p:plain

Shaderの方は次のようです。こちらもcubeを規則正しく並べておきます。 メッシュのサイズを抑え、ジオメトリシェーダのinstanceにて更に31倍までポリゴンを増やすことができます。 頂点シェーダーVSは値をそのまま返すのみにし、ジオメトリシェーダGSにて頂点位置を決定します。

Shader "PolarParticle/ManyCube2" {
  Properties { }
  SubShader {
    Tags { "RenderType"="Transparent" "Queue"="Transparent" }
    Cull off
    Blend SrcAlpha One
    ZWrite Off
    LOD 100

    Pass {
      CGPROGRAM
      #pragma target 5.0
      #pragma vertex VS
      #pragma geometry GS
      #pragma fragment FS
      
      #include "UnityCG.cginc"

      struct GS_OUT {
        float4 vertex : SV_Position;
        float4 texcoord : TEXCOORD0;
        float4 color : COLOR;
      };

      appdata_base VS(appdata_base v) { return v; }

      #define instanceN 31
      [instance(instanceN)]
      [maxvertexcount(3)]
      void GS(triangle appdata_base i[3], inout TriangleStream<GS_OUT> stream, uint gsid : SV_GSInstanceID) {
        for (int j = 0; j < 3; j++) {
          appdata_base v = i[j];
          GS_OUT o;
          uint id = gsid + instanceN * v.texcoord.z;  // パーティクルのIDを計算
          uint n = pow(33000 * instanceN, 1.0/3.0);
          float particleSize = 0.1;
          float interval = 1;
          float zoneLength = (n-1) * interval;
          uint3 axisId = uint3(
            id % n,
            id / n % n,
            id / (n * n));
          float3 pos = (float3)(-zoneLength / 2) + axisId * interval;
          v.vertex.xyz = v.vertex.xyz * particleSize + pos;
          o.vertex = UnityObjectToClipPos(v.vertex);
          o.texcoord = v.texcoord;
          o.color = float4(0, 1, 1, 1);
          stream.Append(o);
        }
        stream.RestartStrip();
      }

      float4 FS(GS_OUT i) : SV_Target {
        float2 uv = i.texcoord.xy;
        float2 p = uv * 2.0 - 1.0;
        float a = 0.01 / min(abs(abs(p.x) - 1.0), abs(abs(p.y) - 1.0));
        a = saturate(a);
        return float4(i.color.rgb, a);
      }
      ENDCG
    }
  }
}

SkinnedMeshRendererにてBoundsを設定しつつ使います。

方法1との違いは、頂点シェーダーでなくジオメトリシェーダを使うこと、idの設定に一手間加えることです。
以降のシェーダーのコードは方法1を使う場合のコードとします。もし、VRChatのために方法2を使う人は読み変えてください。

後のパーティクルの為、今回は1024個のパーティクルを表示する為に1024/16 = 64個のCubeメッシュを生成し、後でジオメトリのinstanceにて16倍するように設定してください。

極座標系によるパーティクルの位置決定

x座標、y座標をもとに、座標を決定する座標系を直交座標と言いますが、
極座標系という座標系も存在します。

極座標系は原点からの距離rと、x軸から反時計回りへの角度θによる(r, θ)で座標を決定します。

mathtrain.jp

原点からのrをsinやcosなどの三角関数と共に角度によって適当に決めるようにすると、綺麗な模様が描けます

www.desmos.com

二次元の極座標系(r, θ)を直交座標系(x, y)に変換する式は次の通りです

x = r cosθ
y = r sinθ

今回のパーティクルではこれを三次元で考えます。

mathtrain.jp

三次元極座標は、原点からの距離r、角度はz軸からの角度θとx軸からの角度φによる(r, θ, Φ)によって決まるようです。 今回はUnityのシェーダーで実装しますから、数学でz軸であるところはy軸、y軸のところがz軸であると考えます。 したがって、原点からの距離r、y軸からの角度θ, x軸からの角度Φで考え、 三次元直交座標系(x, y, z)への変換は次のようです。

x = r sinθ cosφ
y = r cosθ
z = r sinθ sinφ

ではシェーダを書いていきます。
パーティクルのidをもとに、パーティクルの位置を極座標系を用いて算出する関数を用意します。
最初に極座標系(r, θ, Φ)の変数と直交座標系への変換のコードを書いておきます。

#define PI UNITY_PI
void particleTransform(uint id, out float3 pos) {
  float r = 1;
  float theta = 0;
  float phi = 0;
  pos = float3(
    r * sin(theta) * cos(phi),
    r * cos(theta),
    r * sin(theta) * sin(phi));
}

さて、二次元極座標において適当なグラフを示したときはr をθをもとに決定していましたが、
今回はidの値が元になります。
したがって、idの値を適当に変数 tに入れて扱うことにします。 `` float t = id * 0.01; θをπ / 2にしておくと、phiの値によりxz座標上の値になります

float t = id * 0.01;
float r = 1;
float theta = PI / 2;
float phi = t;

また、作成した関数を頂点シェーダに適応します。

VS_OUT VS(appdata_base v, uint instanceId : SV_InstanceID) {
  VS_OUT o;
  uint id = instanceId;
  float particleSize = 0.05;
  float3 pos;
  particleTransform(id, pos);
  v.vertex.xyz = v.vertex.xyz * particleSize + pos;
  o.vertex = UnityObjectToClipPos(v.vertex);
  o.texcoord = v.texcoord;
  o.color = float4(0, 1, 1, 1);
  return o;
}

f:id:aizu-vr:20200905191523p:plain

θの値を適当なサイン波にしてみます。
float theta = PI / 2 + sin(5.5 * t) * 0.2;

f:id:aizu-vr:20200905191558p:plain

Φの値も適当なサイン波にしてみます。
float phi = sin(t) * 3 * PI;

f:id:aizu-vr:20200905191621p:plain

θのサイン波の大きさを敢えて小さくしていましたが、試しに大きくしてみます。
float theta = PI / 2 + sin(5.5 * t) * 0.7;

f:id:aizu-vr:20200905191832p:plain

止まっていてもつまらないので、時間によって変化するようにします。
Φの値を時間によって変化させてみます。
float phi = sin(t) * 3 * PI + sin(t + _Time.y * 0.3) * 3;

f:id:aizu-vr:20200905193044g:plain

なかなかかっこよくなってきました。

tの値、rの値も時間によって変化させてみます。
float t = id * 0.01 + _Time.y * 0.1;
float r = 0.2 + sin(3 * t);

f:id:aizu-vr:20200905193744g:plain

パーティクルが全て同じ方向を向いているので、回転の設定をします。

wgld.org

この記事を参考に、回転の関数を書きます。行列のままの方が扱いやすいので行列のまま値の受け渡しを行います。

float3x3 rotate(float angle, float3 axis) {
  float3 a = normalize(axis);
  float s = sin(angle);
  float c = cos(angle);
  float r = 1.0 - c;
  return float3x3(
    a.x * a.x * r + c, a.y * a.x * r + a.z * s, a.z * a.x * r - a.y * s,
    a.x * a.y * r - a.z * s, a.y * a.y * r + c, a.z * a.y * r + a.x * s,
    a.x * a.z * r + a.y * s, a.y * a.z * r - a.x * s, a.z * a.z * r + c);
}

また、サイズと色の設定も適当に設定します。

#define PI UNITY_PI
void particleTransform(uint id, out float3 pos, out float3x3 rot, out float size, out float3 color) {
    // out 変数追加
  float t = id * 0.01 + _Time.y * 0.1;;
  float r = 0.2 + sin(3 * t);
  float theta = PI / 2 + sin(5.5 * t) * 0.7;
  float phi = sin(t) * 3 * PI + sin(t + _Time.y * 0.3) * 3;
  pos = float3(
    r * sin(theta) * cos(phi),
    r * cos(theta),
    r * sin(theta) * sin(phi));
  // rot, size, color設定
  rot = mul(rotate(theta, float3(1, 0, 0)), rotate(phi - _Time.y * 3, float3(0, 1, 0)));
  size = cos(5.3 * t + _Time.y * 3.0) * 0.05;
  color = float3(sin(2.4 + t + _Time.y * 0.6), sin(0.7 + 2 * t - _Time.y * 0.6), sin(3.0 * t + _Time.y * 0.7)) / 2 + 0.5;
}

頂点シェーダも書き変えます。

VS_OUT VS(appdata_base v, uint instanceId : SV_InstanceID) {
  VS_OUT o;
  uint id = instanceId;
  float particleSize = 0.05;
  float3 pos;
  float3x3 rot;
  float size;
  float3 color;
  particleTransform(id, pos, rot, size, color);
  v.vertex.xyz = mul(rot, v.vertex.xyz * size) + pos;
  o.vertex = UnityObjectToClipPos(v.vertex);
  o.texcoord = v.texcoord;
  o.color = float4(color, 1);
  return o;
}

f:id:aizu-vr:20200905194539g:plain

かっこいいのができあがりました。

コードの全体は次のようです。

Shader "PolarParticle/Test" {
  Properties { }
  SubShader {
    Tags { "RenderType"="Transparent" "Queue"="Transparent" }
    Cull off
    Blend SrcAlpha One
    ZWrite Off
    LOD 100

    Pass {
      CGPROGRAM
      #pragma target 5.0
      #pragma vertex VS
      #pragma fragment FS
      
      #include "UnityCG.cginc"

      struct VS_OUT {
        float4 vertex : SV_Position;
        float4 texcoord : TEXCOORD0;
        float4 color : COLOR;
      };

      float3x3 rotate(float angle, float3 axis) {
        float3 a = normalize(axis);
        float s = sin(angle);
        float c = cos(angle);
        float r = 1.0 - c;
        return float3x3(
          a.x * a.x * r + c, a.y * a.x * r + a.z * s, a.z * a.x * r - a.y * s,
          a.x * a.y * r - a.z * s, a.y * a.y * r + c, a.z * a.y * r + a.x * s,
          a.x * a.z * r + a.y * s, a.y * a.z * r - a.x * s, a.z * a.z * r + c);
      }

      #define PI UNITY_PI
      void particleTransform(uint id, out float3 pos, out float3x3 rot, out float size, out float3 color) {
        float t = id * 0.01 + _Time.y * 0.1;;
        float r = 0.2 + sin(3 * t);
        float theta = PI / 2 + sin(5.5 * t) * 0.7;
        float phi = sin(t) * 3 * PI + sin(t + _Time.y * 0.3) * 3;
        pos = float3(
          r * sin(theta) * cos(phi),
          r * cos(theta),
          r * sin(theta) * sin(phi));
        rot = mul(rotate(theta, float3(1, 0, 0)), rotate(phi - _Time.y * 3, float3(0, 1, 0)));
        size = cos(5.3 * t + _Time.y * 3.0) * 0.05;
        color = float3(sin(2.4 + t + _Time.y * 0.6), sin(0.7 + 2 * t - _Time.y * 0.6), sin(3.0 * t + _Time.y * 0.7)) / 2 + 0.5;
      }

      VS_OUT VS(appdata_base v, uint instanceId : SV_InstanceID) {
        VS_OUT o;
        uint id = instanceId;
        float particleSize = 0.05;
        float3 pos;
        float3x3 rot;
        float size;
        float3 color;
        particleTransform(id, pos, rot, size, color);
        v.vertex.xyz = mul(rot, v.vertex.xyz * size) + pos;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.texcoord = v.texcoord;
        o.color = float4(color, 1);
        return o;
      }

      float4 FS(VS_OUT i) : SV_Target {
        float2 uv = i.texcoord.xy;
        float2 p = uv * 2.0 - 1.0;
        float a = 0.01 / min(abs(abs(p.x) - 1.0), abs(abs(p.y) - 1.0));
        a = saturate(a);
        return float4(i.color.rgb, a);
      }
      ENDCG
    }
  }
}

A-PxLが誕生してから、今まで。

f:id:aizu-vr:20200907194002j:plain

こんにちは、ぶろっくのいずです。

初代部長である秋山さん達がこの部を作ってくださった時から3年以上が経ちました。 A-PxLの成り立ちを知る人もいなくなってきた事もあり、今までの活動をまとめました。 (※この記事について修正、追記出来る部員の方がいましたら気軽に変更をお願いします)

目次

A-PxL(旧 会津大学VR部)のこれまでの活動

2016年度

会津大VR部(会津大学) - Japan-VR-circles

3月5日:はてなブログ開設

2017年度

4月6日:会津大学内にカヤックVR分室を設立しました! | 面白法人カヤック

4月28日:Twitter開設

8月11日:会津大学オープンキャンパス

8月中旬:面白法人カヤックインターン(学部1年生2人)

9月上旬:面白法人カヤックインターン(学部3年生3人)

9月29日〜10月1日:VR部 開発合宿

10月7日〜10月8日:蒼翔祭(学祭)

12月:初代部長秋山さんが株式会社AnostVRを設立

2018年度

4月10日:新入生説明会

4月17日:今年の方針の説明 VR概論1

4月24日:VR概論2

5月8日:ゲームサウンドワークショップ

5月15日:3Dモデルワークショップ

5月22日:Git勉強会1

5月29日:Git勉強会2

5月27日:会津マルシェにてVR雪合戦を展示

6月30日:ブレスト研修会

7月3日:チーム進捗報告及びチーム活動

8月11日:オープンキャンパス

9月14日〜9月16日:開発合宿(南会津の宿泊施設CloudCamp)

10月6日〜10月7日:蒼翔祭(学祭)

10月16日:チーム進捗報告及びチーム活動

11月4日:デジゲー博2018(秋葉原UDX)

11月16日:IVRC決勝大会ユース部門銀賞を獲得

youtu.be

11月18日:パソコン甲子園にてゲームの展示

12月1日〜12月22日:健康づくりハッカソン

12月:部内面談

1月:部内面談

2月:次年度の役員決め

2月16日〜2月17日:新潟・山形・会津大学共同ハッカソンに参加naoto80840.github.io

2019年度

4月:役員の分担(代表者の他に副代表者、会計、自治会担当者を増員)

4月5日:部名を「Aizu-Progressive xr Lab(略称:A-PxL)」に変更

5月18日〜5月19日:VRキャラバン活動

6月16日:Oculus Quest体験会

7月13日〜7月14日:新入生対抗ハッカソン

8月11日:オープンキャンパス VR作品体験会

8月23日〜9月14日:学祭に向けてのPR動画制作

10月12日〜10月13日:蒼翔祭(学祭)は台風により中止

12月:部内VRコンテンツ体験会

1月31日:来年度の活動についてミーティング

2月14日:勉強会についての会議

3月1日:追いコン

2020年度

4月5日:clusterにて会津大学VRサークル説明会

4月6日:部室があるAiCTを3Dスキャン

4月15日:clusterにてA-PxL新入生向けの説明会

4月29日〜5月10日:バーチャルマーケット4に出展

5月14日〜6月11日毎週木曜日:新入生向けのプログラミング勉強会

5月15日〜6月12日毎週金曜日:新入生向けのUnity勉強会

5月16日〜5月30日毎週土曜日:新入生向けのBlender勉強会

6月6日〜6月13日毎週土曜日:新入生向けのVFX勉強会

6月17日:OpenAppLab "2020"

7月11日:Git勉強会1

7月14日:部内でVR体験会

7月16日:Git勉強会2

7月17日:Git勉強会3

7月21日:チーム決め

9月:夏休み

10月10日:オンラインの蒼翔祭(学祭)

活動内容の変化

2016年度:?

2017年度:?

2018年度:チームに別れてゲームを制作。

2019年度:ゲーム部と新規開発部(NDD)に別れて活動。ゲーム部は毎週火曜日19:00〜21:00に中講義室M5にて定例会。新規開発部は毎週金曜日19:00〜21:00に研究棟北ラウンジにて定例会。

2020年度:ゲーム部と新規開発部を統合。毎週火曜日19:00からUBICの3Dシアターにて定例会(大学のテスト期間や大型の休みを除く)。周一でチームごとに進捗を報告しゲームを制作。

部員数の推移

(最大アクティブ数はSlackのアナリティクスより集計)

2016年度:総数11人→13人

2017年度:総数33人、最大アクティブ数33人、新部員20?人

2018年度:総数49?人、最大アクティブ数44人、新部員16人

2019年度:総数63?人、最大アクティブ数43人、新部員6人

2020年度:総数77人、最大アクティブ数38人、新部員6人

役員の推移

顧問:マイケル・コーエン先生

2017年度

代表者:Achu→Achu (@Achu_retro) | Twitter

2018年度

代表者:ゆべねこ→ゆべねこ (@yubeshineko) | Twitter

2019年度

代表者(新規開発部リーダー):Bigdra→bigdra (@bigdra50) | Twitter

副代表者(ゲーム部リーダー):なめろうNameChung (@sketch_namerou) | Twitter

会計:ヤミみみ→ヤミみみ (@MtYamaniko) | Twitter

自治会担当者:ぶろっくのいず→ぶろっくのいず (@blocknoise26) | Twitter

2020年度前半(学祭前)

代表者:Bigdra→bigdra (@bigdra50) | Twitter

副代表者:TC→TC (@Meancore226) | Twitter

会計:ヤミみみ→ヤミみみ (@MtYamaniko) | Twitter

自治会担当者:ぶろっくのいず→ぶろっくのいず (@blocknoise26) | Twitter

2020年度後半(学祭後)

???

A-PxLメンバー達のTwitter

twitter.com

部室の変化

2017年度:?

2018年度:会津大学UBIC分室

2019年度:AiCT(あゆむCafe)の展示スペース

2020年度:無し

部名変更の経緯

最初は会津大学VR部という名であったA-PxL。(結局今もVR部と呼んだり呼ばれることも) 2019年3月頃、VRだけでなくARなどxRを扱う部であるのに対し、部名がそぐわないという意見が出る。(※部名の変更がちょうど年度始まりから可能) 「VRやxR関連であること」、「他と被らないオリジナリティのあること」という条件を元に30個?ほどの部名案が出る。その中から「なんちゃらxRなんちゃら(どんな名前だっけ?)」という部名がアンケートで選ばれ、最終的に「Aizu-Progressive xr Lab(略してA-PxL)」という部名になる。A-PxLの読み方は「アイヅピクセル」。

(2020年9月7日更新)

スクリプトからゲームオブジェクトのマテリアルの色を変える覚え書き

こんにちは、学部4年の杉山です。ネタが尽きたので覚え書きです。

はじめに

最近論文執筆を始めまして、その際にオブジェクトのマテリアルの色をスクリプトから変えているのでせっかくなのでメモ書き程度に紹介したいと思います。

f:id:aizu-vr:20200830135547p:plain

カラーネームを使う

Unityには9(10)のカラーネームがあります。

  1. black
  2. blue
  3. (clear)
  4. cyan
  5. gray(grey)
  6. green
  7. magenta
  8. red
  9. white
  10. yellow

clearは透明であるから色として含めるかはおいておくとして、これらのカラーネームを使うなら。

 GetComponent<Renderer>().material.color = Color.black

 のように記述すれば好きな色に変更できます。

RGBAを使う

RGBAを使うこともできます。

Color(float r, float g, float b, float a); //それぞれ r=赤、g=緑、b=青、a=アルファ値

RGBだけでも使えます。

Color(float r, float g, float b);

例文としては

GetComponent<Renderer>().material.color = Color( 0.1f, 0.2f, 0.3f, 0.4f );

アルファ値は指定しなければ1として扱われます。

おまけ

色を変更する際に便利なメソッドがあったので紹介します

Color Lerp( Color a, Color b, float t); // t は0~1の間、0の時はa、1の時はbを返す

使うときは

GetComponent<Renderer>().material.color = Color.Lerp( Color.white, Color.black, t); // t は任意の数

最後に

わざわざスクリプトから色を変更する機会もそうそうないとは思いますが、お役に立てば幸いです。色を変えるのが億劫なときなどにどうぞ。

参考

docs.unity3d.com

docs.unity3d.com

docs.unity3d.com

docs.unity3d.com

Githubで一貫してタスク管理

皆さんこんにちは学部4年の森口です。 8月に入り急に気温が上がったことで外出自粛が促進されていてある意味感染症対策になっておりますww

さて今回はGithubの機能であるissueとprojectなどを使ってGithub内で完結したタスク管理方法についてまとめていきたいと思います。

Githubでタスク管理

Githubにおけるタスク管理で主に使う機能はissueとprojectの二つになります。これらの二つでタスクの管理をしつつPullRequestなどを組み合わせることでさらに効果的にプロジェクトの管理を進めることができます

issue

issueはGithubのタスク管理機能の一つであり、バグ報告や機能追加などのチケットを作成したり編集することができます

project

projectはいわゆるカンバン方式のタスク管理機能でTrelloに近い使用感で誰がどのタスクを行なっているのか、対応中やレビュー待ちといったタスクの進捗状況を確認することができます

無論TrelloやGoogleスプレッドシートなど他サービスを使用してのタスク管理もできますが可能な部分はGithub内で完結させることでサービスを跨いだときに起こる弊害をなくすことができより快適に開発ができると考えています。

ではここからGithubでタスク管理をしていく一連の流れと手順を説明していきます。

projectの作成

まずはprojectを新しく作成します。Githubのページ上でProjectsタブをクリックすると以下のような画面になります。 f:id:aizu-vr:20200813152912p:plain そして右上にあるCreate a ProjectをクリックするとProjectの詳細を設定する画面に移ります(ここの内容は後から変更できます) f:id:aizu-vr:20200813153116p:plain Project templateの部分はGithub側から予め用意されているテンプレートを使用することができます。
今回はAutomated kanbanを使用していますがこれはissueを登録したり、完了した際などに自動的にカードを移動してくれます(自分で設定することで同じような物を作れますがこっちの方が楽です)
名前や概要などを記述したらCreateProjectをクリックしてProjectを作成しましょう
projectを作成するとカンバン形式の画面が表示されます f:id:aizu-vr:20200813153916p:plain この画面でタスクの進捗状況などを確認できます。初めはTo Doにあるカードを開発を開始するときはInProgressに移動させるといった感じです。
また、かくカラムの右上にある3つの点をクリックしManage automationを選択するとどのアクションを行なったときにカードを自動的に移動させるか設定することができます。

f:id:aizu-vr:20200813154605p:plainf:id:aizu-vr:20200813154601p:plain

これでprojectの設定は完了したので次に移りましょう

Milestone

issueの設定をする前にMilestoneという機能について紹介したいと思います。これはその名の如くマイルストーンを設定することができてこれをissueに紐づけることでマイルストーン毎にタスクを管理することができます。
マイルストーンの作成はIssueタブをクリックして画像内で赤く囲ったMilestonesを選択します。
f:id:aizu-vr:20200813155614p:plain そうするとマイルストーンの管理画面になるのでNewmilestoneを選択してマイルストーンを作成します。
f:id:aizu-vr:20200813155938p:plain 作成画面ではタイトルと期限、詳細を設定するように言われるので記述します。
f:id:aizu-vr:20200813155944p:plain

それでは今回の本題となるissueを作成していきましょう!

issueの作成

先ほどマイルストーンを作成した時と同様にIssueタブを選択し、今度はNewIssueを選択します
f:id:aizu-vr:20200813160553p:plain Issueの作成画面は以下のようになっておりここでバグ報告や新機能追加のためのissueを作成します。
GithubではMarkdownが対応しているので見出しなどを利用して概要、issue内の細々としたタスクなどを分けて記述するとよりわかりやすいと思います。
また、以下の画像でタスクリストの部分で使用している記法はGithubのタスクリストの書き方で、この記法で記述しておくとこのタスクリストの進捗状況がissueに表示されるので便利です。 f:id:aizu-vr:20200813160654p:plain 詳細が書き終わったらProjectやマイルストーンを紐付けましょう。紐付けは画面右側のProjectやMilestoneの欄にある歯車を選択して該当の物を選択します。 その他の項目はAssigneesはそのチケットに関わる人、Labelはどのような種類のissueなのかを指定できます。
設定が完了したらSubmit new issueを選択してissueを作成しましょう
f:id:aizu-vr:20200813161637p:plain 作成したissueには番号が割り当てられ(タイトル横の#1)この番号をコミットメッセージやプルリクエストの詳細部分に記述するとそれらからこのissueへのリンクが作成されます。

いざ開発

自分宛のIssueができたらいざ開発に移ります。まずは、自分が特定のタスクを進行中であることを他のメンバーにもわかるようにProjectのカードを移動させます
Projectを見にいくとTodoのところにカードが作成されています。これは先ほどIssueを作成したときにProjectを紐づけたので自動的に生成されました。これをドラッグアンドドロップでInProgressへ移動させましょう。
f:id:aizu-vr:20200813162255p:plain

プルリクエス

さて、開発が終わったらあとは自分の成果をマージしてもらうためプルリクエストを作成します。
プルリクエストとは自分の作業ブランチにおけるコミットをマージしてくださいと要請する機能です。個人開発では特別なものではありませんが、チーム開発ではコードレビューなどが行われるタイミングでもあり、自分のコミットに対し問題がなければある程度権限のある人が承認しマージされます。
この工程では特別することは変わらないのでプルリクエストの大まかな説明は省略し、今回のタスク管理と関係のある部分のみ説明します。
と言ってもポイントは一点だけで詳細を記述する部分でCloses (issue番号) のように記述するとそのissueはマージされた時点でCloseされます。もちろん手動でもCloseはできるので絶対描かなければならないわけではないですがそのIssueの内容が全て完了しているのであれば書いておいた方がいいでしょう。
また、プルリクエストを作成しレビューを待つ場合はProjectにRevewingのようなカラムを作成しカードを移動しておきましょう。 f:id:aizu-vr:20200813163751p:plain

クローズ&リオープン

プルリクエストが承認されIssueの内容を完了したらIssueをクローズしましょう。Issueタブを選択し自分の担当していたIssueを選択したらCloseIssueを選択することでクローズすることができます。また、IssueをクローズするとProjectのカードはDoneへ移動します。 f:id:aizu-vr:20200813164229p:plain

f:id:aizu-vr:20200813164229p:plainf:id:aizu-vr:20200813164233p:plain
また、後々の仕様変更で以前クローズしたIssueの部分を変更したりしたい場合が出てくるかもしれません。新しくIssueを作成しても問題ありませんが一度クローズしたissueを再度開くこともできます。その場合はクローズしたIssueを選択しReopen issueを選択します。 f:id:aizu-vr:20200813164240p:plain

最後に

いかがでしたでしょうか。現在私の所属しているチームでタスク管理をするのに前々から気になっていたので使ってみようと思い、使い方などを調べたことを記事にしましたが非常に使い勝手が良さそうな印象を受けました。
また、今回紹介した方法は一例ですのでさらに使いやすい方法もあるかと思いますのでチームの形態によって色々とカスタマイズしてチームとして最も使いやすい形で管理を行うのが一番いいのかなと思います。それでは今回はこの辺で失礼します。

参考

docs.github.com

www.atmarkit.co.jp www.atmarkit.co.jp

qiita.com

会津大学VR部の部員が持ち回りで投稿していくブログです。特にテーマに縛りを設けずに書いていきます!