Unityでテクスチャ画像をCubeで表現:DrawMeshInstancedIndirect + Compute Shader

前: Unityでテクスチャ画像をCubeで表現:Instantiate - メモの穴


前回はInstantiateを使ってCubeを生成して並べた。

ただこの方法では、実行するととんでもなく遅い。

Cubeを表示せずに実行した場合がこれ。

f:id:memonoana:20180901163316p:plain:w500

FPSが90~100くらいで、SetPass callsが9。

ここから、Cubeを表示すると、

f:id:memonoana:20180901163734p:plain:w500

FPSが8~9くらいで、SetPass callsが32779。

SetPass callsやここでは出ていないDraw callsは、CPUからGPUに向けてどれだけ命令を送っているのかを示す値。

nn-hokuson.hatenablog.com

light11.hatenadiary.com

前回のスクリプトでは、Cubeを表示する際に、テクスチャ画像のピクセル分(128x128)だけInstantiateで生成しているので、当然SetPass callsが多くなる。

パフォーマンスの改善点は他にもあるとは思うけど、今回はこのSetPass callsを減らしてみる。

環境

Windows10
Unity 2018.2.2f1

GPUインスタンシング

同じようなオブジェクトを大量に描画するアプローチとしてGPUインスタンシングというものがある。

docs.unity3d.com

tsubakit1.hateblo.jp

tsubakit1.hateblo.jp


UnityでGPUインスタンシングを扱うにはいくつか方法がある。

gottaniprogramming.seesaa.net

今回はとりあえず上記のサイトに合わせてDrawMeshInstancedIndirectを使ってやってみる。

スクリプト、シェーダー

今回のスクリプトやシェーダーは以下のサイト達を参考にしている。特に1番上のものがベースになっている。同じようなことをやりたい場合はまずこれらのサイトを参考にするといいと思う。

github.com

tips.hecomi.com

gottaniprogramming.seesaa.net

基本的には、制御するためのスクリプトと、描画用のシェーダー(サーフェイスシェーダーや頂点・フラグメントシェーダーなど)が必要で、プラスアルファでコンピュートシェーダーを使う感じ?

今回はCubeの位置をコンピュートシェーダーで計算する。今回みたいにただ並べるだけなら、コンピュートシェーダーを使うメリットは無いような気がするけど、今後の勉強のために使う。

スクリプト

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

public class CopyImage : MonoBehaviour
{
  [SerializeField]
  private Mesh pixelMesh;           // ピクセルを表現するメッシュ
  [SerializeField]
  private Material pixelMaterial;   // ピクセルのマテリアル(描画用シェーダー)
  [SerializeField]
  private ShadowCastingMode castShadows = ShadowCastingMode.Off;
  [SerializeField]
  private bool receiveShadows = false;
  [SerializeField]
  private ComputeShader positionComputeShader;  // コンピュートシェーダー
  [SerializeField]
  private GameObject image;                     // テクスチャを貼ってるオブジェクト

  private int pixelCount;                 // ピクセル数
  private int positionComputeKernelId;    // カーネルID
  private ComputeBuffer positionBuffer;   // ピクセル位置を格納するバッファ
  private ComputeBuffer colorBuffer;      // ピクセルカラーを格納するバッファ
  private ComputeBuffer argsBuffer;       // DrawMeshInstancedIndirect用のバッファ
  private uint[] args = new uint[] { 0, 0, 0, 0, 0 };  // DrawMeshInstancedIndirect用の配列

  void Start()
  {
    CreateBuffers();
  }

  void Update()
  {
    UpdateBuffers();
    Graphics.DrawMeshInstancedIndirect(pixelMesh, 0, pixelMaterial, pixelMesh.bounds, argsBuffer, 0, null, castShadows, receiveShadows);
  }

  private void CreateBuffers()
  {
    // テクスチャ画像の取得
    Texture2D texture = (Texture2D)image.GetComponent<Renderer>().material.mainTexture;
    // ピクセル数の計算
    int dim = texture.width;
    pixelCount = dim * dim;

    // カーネルIDの取得
    positionComputeKernelId = positionComputeShader.FindKernel("CSMain");

    // バッファの初期化
    argsBuffer = new ComputeBuffer(5, sizeof(uint), ComputeBufferType.IndirectArguments);
    positionBuffer = new ComputeBuffer(pixelCount, 16);
    colorBuffer = new ComputeBuffer(pixelCount, 16);
    
    // テクスチャのピクセルカラーをcolorBufferにセット
    colorBuffer.SetData(texture.GetPixels());

    // 描画用シェーダーにバッファをセット
    pixelMaterial.SetBuffer("positionBuffer", positionBuffer);
    pixelMaterial.SetBuffer("colorBuffer", colorBuffer);

    // argsBufferにメッシュの頂点数とメッシュの数を格納
    uint numIndices = (pixelMesh != null) ? (uint)pixelMesh.GetIndexCount(0) : 0;
    args[0] = numIndices;
    args[1] = (uint)pixelCount;
    argsBuffer.SetData(args);

    // コンピュートシェーダーにバッファ、値をセット
    positionComputeShader.SetBuffer(positionComputeKernelId, "positionBuffer", positionBuffer);
    positionComputeShader.SetFloat("_Dim", dim);
    float cubeScale = image.transform.localScale.x * 10 / dim;
    positionComputeShader.SetFloat("_CubeScale", cubeScale);
    positionComputeShader.SetVector("_Pivot", this.transform.position);
  }

  private void UpdateBuffers()
  {
    // カーネルの実行
    positionComputeShader.Dispatch(positionComputeKernelId, pixelCount / 8, 1, 1);
  }

  void OnDisable()
  {
    // バッファの解放
    if (positionBuffer != null) positionBuffer.Release();
    positionBuffer = null;
    if (colorBuffer != null) colorBuffer.Release();
    colorBuffer = null;
    if (argsBuffer != null) argsBuffer.Release();
    argsBuffer = null;
  }
}

スクリプトでは主に各種バッファ、値の用意とシェーダーへの設定、DrawMeshInstancedIndirectの実行を行う。

Updateの中でカーネルを実行してるけど、今回の目的だと、ただCubeの位置を1度計算するだけなので、Startでやれば十分。この後動かしたりする場合は、Updateで更新される情報をコンピュートシェーダーに渡したりする。

テクスチャ画像のピクセルカラーを取得するのは前回同様GetPixelsでやってるけど、コンピュートシェーダー使ってもできると思う。

コンピュートシェーダー

#pragma kernel CSMain

RWStructuredBuffer<float4> positionBuffer;

float _Dim;         // 1辺のピクセル数
float _PixelScale;  // ピクセル1個のスケール
float3 _Pivot;      // 並べる基準点

[numthreads(8, 1, 1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
  // ピクセル位置の計算
  float2 uv = float2((id.x % (int)_Dim) / _Dim, floor(id.x / _Dim) / _Dim);
  float x = _Pivot.x + uv.x * _PixelScale * _Dim;
  float y = _Pivot.y + uv.y * _PixelScale * _Dim;
  float z = _Pivot.z;
  float4 pos = float4(x, y, z, _PixelScale);
  positionBuffer[id.x] = pos;
}

コンピュートシェーダーでは、ピクセル位置を計算してpositionBufferに格納する。ピクセル1個のスケールもここで格納する。

描画用シェーダー(サーフェイスシェーダー)

Shader "Custom/CopyImage" {
  Properties {
    _MainTex ("Albedo (RGB)", 2D) = "white" {}
    _Glossiness ("Smoothness", Range(0,1)) = 0.5
    _Metallic ("Metallic", Range(0,1)) = 0.0
  }
  SubShader {
    Tags { "RenderType"="Opaque" }
    LOD 200

    CGPROGRAM
    #pragma surface surf Standard addshadow
    #pragma multi_compile_instancing
    #pragma instancing_options procedural:setup
    #pragma target 3.0

    sampler2D _MainTex;
    half _Glossiness;
    half _Metallic;

    struct Input {
      float2 uv_MainTex;
    };

    #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
    StructuredBuffer<float4> positionBuffer;
    StructuredBuffer<float4> colorBuffer;
    #endif

    void setup () {
      #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
      // ピクセル位置の反映
      float4 position = positionBuffer[unity_InstanceID];
      float scale = position.w;
      unity_ObjectToWorld._11_21_31_41 = float4(scale, 0, 0, 0);
      unity_ObjectToWorld._12_22_32_42 = float4(0, scale, 0, 0);
      unity_ObjectToWorld._13_23_33_43 = float4(0, 0, scale, 0);
      unity_ObjectToWorld._14_24_34_44 = float4(position.xyz, 1);
      #endif
    }

    void surf (Input IN, inout SurfaceOutputStandard o) {
      float4 col = 1.0f;
      #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
      // ピクセルカラーの反映
      col = colorBuffer[unity_InstanceID];
      #else
      col = float4(0, 0, 1, 1);
      #endif
      fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * col;
      o.Albedo = c.rgb;
      o.Metallic = _Metallic;
      o.Smoothness = _Glossiness;
      o.Alpha = c.a;
    }
    ENDCG
  }
  FallBack "Diffuse"
}

サーフェイスシェーダーではpositionBufferとcolorBufferを元に、ピクセルの位置や色を反映させる。

実行

f:id:memonoana:20180902231737p:plain:w500

FPSが90~100くらいで、SetPass callsが13。

だいぶ改善できた。

Unityでテクスチャ画像をCubeで表現:Instantiate

Unityでテクスチャ画像のピクセルカラーを取得できる。

それを使って、テクスチャ画像のピクセルカラーをCubeに反映させて並べることで画像を表現する。

環境

Windows10
Unity 2018.2.2f1

スクリプト

// 並べるCube
[SerializeField]
private GameObject pixelCube;
// 最後にまとめて動かすために、親オブジェクトを用意
[SerializeField]
private GameObject output;

void Start () {
  // テクスチャを取得
  Texture2D texture = (Texture2D)GetComponent<Renderer>().material.mainTexture;
  // テクスチャのピクセルカラーを取得
  Color[] texturePixels = texture.GetPixels();
  // 出力先の親オブジェクトの位置
  Vector3 outputPosition = output.transform.position;
  // 出力するcubeのスケール Planeに対するピクセルの比率に合わせる
  float cubeScale = this.transform.localScale.x * 10 / texture.width;
  // cubeの出力位置
  Vector3 pixelPosition;
  // 出力したcubeオブジェクト
  GameObject pixel;
  for (int y = 0; y < texture.height; y++) {
    for (int x = 0; x < texture.width; x++) {
      // cubeの出力位置を決定 親オブジェクトの位置を基準にcubeのスケールずつずらす
      pixelPosition = new Vector3(outputPosition.x + x * cubeScale, outputPosition.y - y * cubeScale, outputPosition.z);
      // cubeの出力
      pixel = Instantiate(pixelCube, pixelPosition, Quaternion.identity, output.transform);
      // 出力したcubeの色を、対応するテクスチャのピクセルカラーに変更
      pixel.GetComponent<Renderer>().material.color = texturePixels[x + texture.width * y];
      // 出力したcubeのスケールを変更
      pixel.transform.localScale = Vector3.one * cubeScale;
    }
  }
  // 出力結果が上下逆になってるので反転
  output.transform.Rotate(180f, 0f, 0f);
}

Texture2D.GetPixels()でテクスチャ画像のピクセルカラーを配列で取得できる。今回用いる画像は128x128ピクセルなので、配列のサイズは16384になる。最終的に画像のピクセル分Cubeを並べる。

f:id:memonoana:20180816161947p:plain:w400

左が元画像で右がCubeを並べたもの。


次 : Unityでテクスチャ画像をCubeで表現:DrawMeshInstancedIndirect + Compute Shader - メモの穴

UnityでIWBTGを3Dに -プレイヤー移動-

前: UnityでIWBTGを3Dに -ステージ作成- - メモの穴


前回はステージを作成した。

次はプレイヤーをつくっていく。

環境

Windows10
Unity 2018.2.2f1

プレイヤー作成

イワナのキッド君の判定は縦長の長方形になっているようなので、

f:id:memonoana:20180810160516p:plain:w200

こんな感じ。カメラを追従させたいので、カメラを子要素にしておく。

f:id:memonoana:20180810161233p:plain:w200

プレイヤー移動

次に移動処理を加える。

プレイヤー移動の実装アプローチはいくつかある。

tama-lab.net

他のサイトもいろいろ見てると、大体RigidbodyかCharactorControllerでやってるみたい。

とにかく簡単にやりたい場合はCharactorControllerを使ってる感じだけど、Rigidbody関係はプレイヤー移動以外でも使う機会は多そうなので、今回はRigidbodyを使っていく。途中で変える可能性はある。

Rigidbodyではvelocityを使って移動させていく。現実に近い挙動をさせる場合はAddForceを使うべきだということだけど、今回はそういう挙動は求めてないのでvelocityでやる。(なら別にRigidbodyじゃなくてもいいのでは?)

// 速度
private float velocity = 6f;
// 進む方向
private Vector3 direction = Vector3.zero;

void Update () {
  // WASDで移動
  if (Input.GetKey(KeyCode.W)) {
    direction += transform.forward;
  }
  if (Input.GetKey(KeyCode.S)) {
    direction -= transform.forward;
  }
  if (Input.GetKey(KeyCode.A)) {
    direction -= transform.right;
  }
  if (Input.GetKey(KeyCode.D)) {
    direction += transform.right;
  }
  if (Input.GetKeyUp(KeyCode.W) || Input.GetKeyUp(KeyCode.S) || Input.GetKeyUp(KeyCode.A) || Input.GetKeyUp(KeyCode.D)) {
    direction = Vector3.zero;
  }
}
void FixedUpdate () {
  direction.Normalize();
  // x、z方向の速度を変更
  rigidBody.velocity = new Vector3(direction.x * velocity, rigidBody.velocity.y, direction.z * velocity);
}

WASDで移動する。キー入力に応じて進む方向をdirectionに加算、direction.Normalize()で正規化する。x方向とz方向のdirectionに適当な値のvelocityを掛け、Rigidbody.velocityに反映させる。

f:id:memonoana:20180814004955g:plain:w300

InspectorでRigidbodyのUse Gravityにチェックを入れると、重力の影響を考慮してくれるようになるので、y方向の速度はそれに任せる。

f:id:memonoana:20180814005334p:plain

f:id:memonoana:20180814005225g:plain:w300

ジャンプ

次はジャンプを実装する。

private float velocityJump = 5f;
private bool isJumping = false;

void Update () {
  // スペースキーでジャンプ
  if (Input.GetKeyDown(KeyCode.Space)) {
    isJumping = true;
  }
}

void FixedUpdate () {
  if (isJumping) {
    // y方向の速度を変更
    rigidBody.velocity = new Vector3(rigidBody.velocity.x, velocityJump, rigidBody.velocity.z);
    isJumping = false;
  }
}

スペースキーを押すとジャンプする。シンプルにy方向の速度を変更するだけ。

f:id:memonoana:20180814011336g:plain:w300

ただ、このままだと

f:id:memonoana:20180814011821g:plain:w300

無限にジャンプができてしまう。ここでは2段ジャンプまでにしたい。

接地判定

まずは、空中ではジャンプできず、地上からしかジャンプできないようにしたい。そのためには、地に足がついているかどうかを判定する必要がある(接地判定)。

接地判定の方法はいくつかある。

tsubakit1.hateblo.jp

tsubakit1.hateblo.jp

今回はBoxCastを使う。BoxCastは特定の方向に箱状の判定を飛ばして、物体があるかどうかを判定してくれるもの。

private bool isGround = false;
private RaycastHit hit;
private float rayDistance, rayScale;

void Start () {
  // BoxCastのサイズ
  rayScale = transform.localScale.x * 0.5f;
  // 判定を飛ばす距離
  rayDistance = rayScale;
}

void Update () {
  isGround = Physics.BoxCast(transform.position, Vector3.one * rayScale, -transform.up, out hit, transform.rotation, rayDistance);
}

地上にいる時はisGroundがtrueになる。ちゃんと判定できているか、オブジェクトの色を変えて確かめてみる。

f:id:memonoana:20180814014418g:plain:w300

いけてそう。あとはスペースキーを押した時にisGroundがtrueの時だけisJumpingをtrueにすれば地上からしかジャンプできなくなる。

if (Input.GetKeyDown(KeyCode.Space)) {
  if (isGround) isJumping = true;
}

2段ジャンプ

次に2段ジャンプ。単純にジャンプした回数を記録して、指定回数以上はジャンプできないようにする。

private int jumpCountMax = 2;

void Update () {
  if (Input.GetKeyDown(KeyCode.Space)) {
    jumpCount++;
    // jumpCountがjumpCountMax未満の間だけジャンプ
    if (jumpCount < jumpCountMax) isJumping = true;
  }
}
void FixedUpdate () {
  // 地上でjumpCountを0に
  if (isGround) {
    jumpCount = 0;
  }
}

スペースキーを押した時にjumpCountを増やし、地上にいる時にjumpCountを0にする。そして、jumpCountがjumpCountMax未満の時のみisJumpingをtrueにしてジャンプを行うようにする。

f:id:memonoana:20180814142305g:plain:w300

このgifだとほんとに2段までで制限できてるのか怪しいな?

ジャンプの高さ変更

今のままだと、キーを押したらジャンプ、という判定だけなので、ジャンプの高さは常に一定となっている。次は、キーを押している長さによってジャンプの高さを変えたい。

これまではGetKeyDonwでスペースキーの判定をしていたが、GetKeyを使ってキーを押し続けている間も処理を行えるようにする。

private int jumpTimeMax = 20;

void Update () {
  if (Input.GetKey(KeyCode.Space) && jumpCount < jumpCountMax) {
    // キーを押している間、jumpTimeを増加
    if (jumpTime < jumpTimeMax) {
      jumpTime++;
      isJumping = true;
    // jumpTimeMaxを超えたらジャンプ終了
    } else {
      isJumping = false;
    }
  }
  // キーを上げたらジャンプ回数カウント、jumpTimeリセット
  if (Input.GetKeyUp(KeyCode.Space)) {
    jumpCount++;
    jumpTime = 0;
  }
}

jumpTimeの概念を設けて、キーを押し続けている間、jumpTimeを増やし続ける。jumpTimeがjumpTimeMax未満の間はisJumpingをtrueにし続ける。jumpTimeMax以上になると、キーを押していても強制的にジャンプを終了する。ジャンプ回数のカウントはGetKeyUpでキーを上げた時に行い、この時にjumpTimeもリセットする。

f:id:memonoana:20180814145604g:plain:w300

重力の変更

もう少し落下速度を速くしたい。

qiita.com

そもそもの重力の設定を変えたり、そのオブジェクトに上から力を加えたりとやり方はいろいろあるみたい。今回は後者でやる。

private Vector3 localGravity = new Vector3(0f, -30f, 0f);

void Start () {
  rigidBody.useGravity = false;
}
void FixedUpdate () {
  rigidBody.AddForce(localGravity, ForceMode.Acceleration);
}

AddForceで-y方向に力を加え続ける。RigidbodyのUse Gravityはつけたままでもいけるけど、localGravityだけで制御した方が分かりやすいのかな?

f:id:memonoana:20180814154102g:plain:w300

おわりに

移動とかジャンプは実装方法がいろいろあるので、ちゃんと使い分けられるようになりたい。


次:しばらく先

UnityでIWBTGを3Dに -ステージ作成-

Unityの勉強に、2DアクションゲームI WANNA BE THE GUYを3Dにしていこうと思う。全部つくるつもりは無し。満足するか飽きたらやめる。

3Dにすると言っても、しょぼんのアクション3Dみたいに新しいものをつくるわけではなく、あくまでもGUYを元にしてつくっていく。その過程でUnityでのゲーム作成の基本的なところを押さえていきたい。

環境

Windows10
Unity 2018.2.2f1

ステージ作成


さっそくステージをつくっていく。

f:id:memonoana:20180809154506p:plain:w400

ステージの大きさは上下がブロック19個分、左右が25個分となっている。3Dにする場合、ブロックの奥行きのサイズも考える必要がある。

とりあえず1ブロックをScale1のCubeとしてつくっていく。奥行きは一旦ブロック1個分にしておく。後々つくってみた感じで調整する。


まずは周りをPlaneで囲む。手前と奥は一旦保留。

f:id:memonoana:20180809164507p:plain:w400


次にCubeを置いていく。2Dの並びを再現するだけなので、スクリプトで一気に並べてしまうのが簡単そう。

int[,] isBlock = new int[,] {
  {1, 1, 1, 0, 0, 0, 1, ... },
  ...
}

こんな感じで19×25の配列を用意、Cubeを置く位置を01で指定して、InstantiateでCubeを置く。意味的には01よりtrue/falseの方がいいと思うけど記述が長くなるので01にした。

f:id:memonoana:20180809182129p:plain

テクスチャとかライティングもやっていきたいところだけどそれはそれで長くなりそうなので一旦保留。


次: UnityでIWBTGを3Dに -プレイヤー移動- - メモの穴

UnityでScaleの基準点を変更するメモ

Unityで作成したオブジェクトのScaleを変更すると,

(Y軸方向に伸縮)
f:id:memonoana:20180713112235g:plain:w300

オブジェクトの中心を基準として上下に伸縮する.
この場合,Unityで作成したCubeは基準点がオブジェクトの中心になっているから.

これを,

f:id:memonoana:20180713112921g:plain:w300

こうしたい.

方法1:オブジェクトの親子関係を利用する

参考:UnityでObjectのPivotを変更する - 脳汁portal

  1. 空のオブジェクトを作成して,その子要素にScaleを変更したいオブジェクト(今回はCube)を設定する.

    f:id:memonoana:20180713113817p:plain

  2. EmptyのPositionが新しく基準にしたい位置(今回はCubeの底)に来るように,CubeのPositionを変更する.

  3. Emptyを伸縮させると,Cubeが底を基準に伸縮する.

方法2:モデリングツールでオブジェクトの基準点を変更する

そもそも”Unityで”ではなくて元も子もないんだけど,モデリングツールで基準点を好きな位置に設定すればいい.

Unity側では下の画像のところを「Pivot」にしておく.

f:id:memonoana:20180713125319p:plain

方法3:

何かあれば追加

ディレクトリ構成のツリー図を編集してコピペできるやつをつくった

概要

こういうやつ

aaa
  |--bbb
  |    |--ddd
  |    `--eee
  `--ccc

を,さくっと編集してコピペできるやつが欲しかったのでつくった.
treeコマンドでやるのが一番手っ取り早いとは思うんだけど,自分の好きな構成にいじりたい時もある気がするので.あと,ちゃんと探せばこういうことできるものは既にあるような気もするんだけど,まぁお勉強ということで.

できたもの

URL:https://dirtree.netlify.com/

f:id:memonoana:20180605113304p:plain

左側のエリアで編集したものが,右側のエリアにコピーできる形で表示される.

左側

矢印ボタンでディレクトリの開閉,プラスボタンでディレクトリの追加,マイナスボタンでディレクトリの削除,ディレクトリ名をダブルクリックでディレクトリ名の編集ができる.

閉じたディレクトリの中身は右側には表示されない.

f:id:memonoana:20180605115000p:plain

右側

マウスでテキストを選択してコピーするか,右上のコピーボタンからコピーする.

実装について

Vue.jsでツリー図を表示する際にこちらを参考にした.

jp.vuejs.org

こちらの例では,itemコンポーネント木構造のデータをPropsで受け取り,子要素についてさらにitemコンポーネントを呼ぶことで,再帰的にツリー図を表示している.

<!-- ルート -->
<ul>
  <item :model="treeData"></item>
</ul>
<!-- itemコンポーネント -->
<li>
  {{model.name}}
  <ul>
    <item v-for="(model, index) in model.children" :key="index" :model="model"></item>
  </ul>
</li>

まとめ

ディレクトリ構成のツリー図を編集してコピペできるやつをつくった.

ローカルのディレクトリをインポートしてそれを反映させるっていうのもやりたいんだけど別にいらない気もする.ディレクトリ名を変更する時のデザインとかページの全体的なデザインはもっと考えたい.

超!A&G+の番組一覧を取得するnpmモジュール

概要

超!A&G+で放送されている番組の一覧を取得するnpmモジュールを作成した.非公式.

URL:agqr-list - npm

使い方

getProgramListで番組一覧を取得する.

import agqr from 'agqr-list'

agqr.getProgramList()
  .then((list) => {
    console.log(list)
  })

getProgramList

全ての曜日

getProgramListに引数を与えないか,getProgramList('All')で全曜日の番組一覧を取得する.

// getProgramList(), getProgramList('All')
{ Mon: 
   [ { start: '06:00', time: '60', title: 'A&G ARTIST ZONE Mia REGINAのTHE CATCH' },
     { start: '07:00', time: '30', title: 'ガールズジョッキー ラジオステークス' },
     ... ],
  Tue: 
   [ ... ],
  Wed:
   [ ... ],
  Thu:
   [ ... ],
  Fri:
   [ ... ],
  Sat:
   [ ... ],
  Sun:
   [ ... ] }

それぞれの番組については,開始時間と放送時間(分),タイトルを取得する.

曜日の指定

getProgramList('曜日')でその曜日の番組一覧を取得する.引数として与えられるのは'Mon','Tue','Wed','Thu','Fri','Sat','Sun'のいずれか.

// getProgramList('Mon')
[ { start: '06:00', time: '60', title: 'A&G ARTIST ZONE Mia REGINAのTHE CATCH' },
  { start: '07:00', time: '30', title: 'ガールズジョッキー ラジオステークス' },
  ... ]

まとめ

超!A&G+で放送されている番組の一覧を取得するnpmモジュールを作成した.コードについては触れてないけど,公式サイトから力技でデータを取得してるので,公式サイトの仕様が変わると使えなくなる可能性あり.