ミドリ黄のプログラミングメモノート

主にUnity(C#)を中心としたプログラムの備忘録

Unity Shader2D 画像にストライプの模様をつける





使用した素材

前回と同様に使用した素材はベイツ・イメージズ様のものです。素材のリンクは以下の通りです。
スプラッシュペイントと黒背景の壁紙

これをアセット内に追加しておきます。




画像にストライプを付ける

前回と同様にShaderを使うためにまず次の2つを作成します。

  1. Materialの作成(「Create」⇒「Material」)
  2. Shaderの作成(「Create」⇒「Shader」⇒「Image Effect Shader」)

作成したら次のようになります。さらに、StripeMaterialにドラッグ&ドロップでStripeShaderを追加するのを忘れずに


次に、StripeShaderのプログラムを以下の様に変更します。frag関数を書き換えるだけで大丈夫だと思います。

Shader "Hidden/StripeShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            #define PI 3.14159265359

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            sampler2D _MainTex;

            fixed4 frag(v2f i) : SV_Target
            {
                if (abs(sin(i.uv.x * 2 * PI)) > 0.96) {
                    return 1;
                }
                else {
                    fixed4 col = tex2D(_MainTex, i.uv);
                    return col;
                }
            }
            ENDCG
        }
    }
}


ここでシーン上に用意した素材を追加します。


これにStripeMaterialを追加すると以下の様になることが確認できます。





プログラムの解説

ここでは先ほどのプログラムの解説を行っていきます。
重要なのは

if (abs(sin(i.uv.x * 2 * PI)) > 0.96) {
    return 1;
}

という部分です。
これは、画像のx座標を変数としたサイン関数の絶対値が0.96以上の時に画像を白くするというものです。
図で表すと画像のようなアルゴリズムとなっています。





ストライプを動かす

次は画像に表示したストライプを動かす方法を紹介します。
そのためには先ほど示したサイン関数を時間軸に沿って動かせばよいです。下の画像はsin(x-t)のtを変化させたときのグラフですが、山の位置が動いていることが確認できます。


そのためにはゲーム実行からの経過時間を取得する必要があるのですが、すでにUnityが用意している変数である_Timeを利用すればOKです。詳細は公式ドキュメント「ビルトインのシェーダー変数」に書かれていますが、

_Time.y

とすればゲーム実行からの経過時間を秒単位で取得できそうです。

以上を踏まえると先ほどのプログラムのif文を

if (abs(sin((i.uv.x - _Time.y) * 2 * PI)) > 0.96)

を書き換えれば目的の動作を行いそうです。

実際に動かしてみた結果が下の動画です。




C++ 可変長配列vectorを使ってみる





1次元配列について

宣言と初期化

可変長の1次元配列の宣言は

std::vector<型名> 変数名;

というようにできます。

また、宣言と同時に初期化をすることも可能です。通常の配列の様に

//中身が{3, 4, 2}の可変長配列
std::vector<int> intVec{ 3, 4, 2 };

と宣言することができます。

また、要素をすべて同じNで初期化(全て0にするなど)の場合は(要素数, N)と初期化することもできます。

//中身が{0, 0, 0}の可変長配列
std::vector<int> zeroVec(3, 0);



要素の追加

「可変長」とついているので要素を追加することが可能です。追加する場合は「変数名.push_back(追加したい要素)」とすることで末尾に要素を追加することが可能です。


for文でループする(プログラム例)

vectorは「変数名.size()」とすることで配列の大きさを簡単に取得ができます。
これを利用すれば配列に対するfor文が容易に実装できます。

#include<iostream>
#include<vector>

int main() {
	//中身が{3, 4, 2}の可変長配列
	std::vector<int> intVec{ 3, 4,2 };

	//配列のサイズを取得
	std::cout << "配列のサイズ:" << intVec.size() << std::endl;

	//中身を列挙
	for (int i = 0; i < intVec.size(); i++) {
		printf("intVec[%d] = %d\n", i, intVec[i]);
	}

	//末尾に7を追加
	std::cout << "\n末尾に7を追加" << std::endl;
	intVec.push_back(7);

	//配列のサイズを取得
	std::cout << "配列のサイズ:" << intVec.size() << std::endl;

	//中身を列挙
	for (int i = 0; i < intVec.size(); i++) {
		printf("intVec[%d] = %d\n", i, intVec[i]);
	}
}


実行結果

配列のサイズ:3
intVec[0] = 3
intVec[1] = 4
intVec[2] = 2

末尾に7を追加
配列のサイズ:4
intVec[0] = 3
intVec[1] = 4
intVec[2] = 2
intVec[3] = 7





2次元配列について

宣言と初期化

2次元配列となると宣言が少し複雑になり、

std::vector<std::vector<型名>> 変数名;

と宣言します。

1次元配列と同様の初期化が可能であり、通常の配列の様にする場合は、

std::vector<std::vector<int>> intVec2{ {3, 2}, {1, 9} };

と宣言し、要素をすべて同じ0で初期化する場合は、

//3行2列の零行列
std::vector<std::vector<int>> zeroVec2(3, std::vector<int>(2, 0));

とします。


for文でのループ

2次元の可変長配列vecを行列として考えると

  • vec.size()で行数
  • vec[0].size()で列数

を取得できるので、これを利用すれば2次元の可変長配列に対しても簡単にfor文を実装できます。

#include<iostream>
#include<vector>

int main() {
	//2次元の可変長配列
	std::vector<std::vector<int>> intVec2{ {1, 2}, {3, 4}, {5, 6} };

	//2行2列の零行列
	std::vector<std::vector<int>> zeroVec2(2, std::vector<int>(2, 0));

	//配列のサイズを取得
	printf("%d%d列の行列\n", intVec2.size(), intVec2[0].size());

	//中身を列挙
	for (int i = 0; i < intVec2.size(); i++) {
		for (int j = 0; j < intVec2[0].size(); j++) {
			std::cout << intVec2[i][j] << ", ";
		}
		puts("");
	}
}



32列の行列
1, 2,
3, 4,
5, 6,




Unity アニメーション① (Spriteアニメーターの作成)





はじめに

UnityのAnimationの基本的な使い方を学びたかったのでSpriteアニメーターを作成しました。




準備(画像ファイルのインポートと分割)


上記の画像をインポートしてSprite Editorから縦・横それぞれ2分割して使用しました。分割の方法については以下の記事を参考にしてください。


kiironomidori.hatenablog.com




AnimationClipの準備

まず、右クリック「Create」⇒「Animation」でAnimation Clipを作成します。
作成したAnimation Clipの名前は「SpriteClip」としました。


さらに、「SpriteClip」したら「Loop Time」にクリックを入れました。これによりアニメーションをループさせることが可能となるみたいです。





Animator Controllerの準備

右クリック「Create」⇒「Animator Controller」でAnimator Controllerを作成します。
作成したAnimator Controllerの名前は「TestController」としました。


次に「TestController」をダブルクリックすると「Animator」ウィンドウが開くので「SpriteClip」をドラッグアンドドロップで追加します。




ゲームオブジェクトの準備

「準備」で用意した「export_0」(黄色い円)をシーンに追加します。
さらに、これに「Animator」を追加して「Controller」に「TestController」を割り当てます。





Animation Clipの編集

「Sprite Clip」をダブルクリックしてAnimationウィンドウを開きます。
そして、先ほどシーンに追加した「export_0」を選択し「Add Propety」⇒「SpriteRenderer」⇒「Sprite」と選択。


そしたら、アニメーションの最後のフレームを選択して削除する。


「Samples」が初期状態だと60になっているので10に変更する。この数値は1秒間に何コマのアニメーションを処理するかを示している
更に、アニメーションに使用する図形を選択してドラッグアンドドロップで追加していく。


最期にアニメーションを終了したいフレームを選択して「Add KeyFrame」を押す。





Animationウィンドウでの動作確認





CSSでのクラスの利用




クラスについて

例えば、h2タグの字を赤くしようとした場合は、CSSファイルで

h2{
    color: red;
}

とすれば、HTMLで

<h2>h2</h2>

と記述したとき

h2

と表示されます。

しかし、この方法ではあるh2タグでは文字を青く、別のh2タグでは緑色に表示したいなどができません。




クラスの利用(タグ指定なし)

基本

クラスというものを利用すればこの問題を解決できます。CSSでは

.クラス名{
/*処理*/
}

と記述することでクラスごとに表示を変えることができます。

このクラスをHTML内で利用するためには

<p class="クラス名"></p>

とすればよいです。


使用例

CSSファイルの記述
.red{
    color: red;
}

.blue{
    color: blue;
}



HTMLファイルの記述
<p class="red">赤く</p>
<p class="blue">青く</p>



ブラウザでの表示


赤く

青く





クラスの利用(タグ指定あり)

先ほどのように

.クラス名{
/*処理*/
}

としたクラスはすべてのタグで利用できます。しかし、特定のタグでのみ利用できるクラスを作成することも可能です。


タグ名.クラス名{
/*処理*/
}

とすることでこの処理が可能になります。HTMLでの利用方法はタグ指定がない場合と同じです。


使用例

CSSファイルの記述
p.red{
    color: red;
}

p.blue{
    color: blue;
}



HTMLファイルの記述
<p class="red">赤く</p>
<p class="blue">青く</p>



ブラウザでの表示


赤く

青く




タグ指定のクラスの利点

タグ指定のメリットは
同名のクラスでもタグが違うと別のクラスとみなせる。
ということです。

例えば、pとdivでそれぞれ「red」というクラスを作成することが可能となりますCSS

p.red{
    color: red;
}

div.red{
    color: green;
}


と記述すれば、HTMLファイルで

<p class="red">p red</p>
<div class="red">div red</div>


と記述したときブラウザでは


p red

div red

と同じクラス名にもかかわらず別々の表示をするようになります。



Python pandas read_excelで読み込んだデータのindexとcolumnsを変更する




前提

以下の例では下の画像のようなエクセルファイルを読み込みました
(ファイル名:sample.xlsx、シート名:Sheet1)。




indexの変更

以下の処理を用いてExcelファイルを読み込み、表示します。

import pandas as pd
df = pd.read_excel('sample.xlsx', 'Sheet1')

実行結果


ここで、実行結果の左端に表示されている0, 1, 2という数字はindexですが、これを任意のものに変更するプログラムを紹介します。
今回はindexの変更に辞書型を用います。0, 1, 2という数字をkeyとしてそれぞれに対応する文字列をvalueとしたものを作成すればよいです。
実際に変更した例がこちらになります。

import pandas as pd
df = pd.read_excel('sample.xlsx', 'Sheet1')

#keyとなるリスト(a)とvalueとなるリスト(s)から辞書型を作成
a = list(range(len(df)))
s = ["Zero", "One", "Two" , "Three", "Four"]
dic = dict(zip(a, s))

#dicに従い名前を変更
df.rename(index =dic, inplace=True)
df

実行結果




columnsの変更

先ほどのシートにおいて「名前」、「英語」、「数学」、「国語」はcolumnsと呼ばれます。
indexと同様の方法でこれらを変更することができます。

import pandas as pd
df = pd.read_excel('sample.xlsx', 'Sheet1')

#keyとなるリスト(a)とvalueとなるリスト(s)から辞書型を作成
a = ["名前", "英語", "数学", "国語"]
s = ["Name", "Eng", "Math" , "Jan"]
dic = dict(zip(a, s))

#dicに従い名前を変更
df.rename(columns =dic, inplace=True)
df

実行結果




C++ OpenCV ルックアップテーブル(LUT)の使い方





使用した素材

以下のプログラムで使用した画像はベイツ・イメージズ様のものです。それぞれの素材のリンクは以下の通りです。
綿雲が浮かぶ穏やかな青空
カラフルな原色系のかっこいい背景




概要

LUTはルックアップテーブル(Look Up Table)の略であり、入力された値に対して出力した値を割り当てた配列のことです。
OpenCVではこれを利用することで入力画像の画素値が0の部分を出力画像において255に変換するといった処理が可能になります。
for文を用いて処理するよりも処理時間が速くなったりするなどのメリットがあります




アルゴリズム

後述の様にLUTはC/C++では多次元配列のcv::Matを使うのですが、ここでは分かりやすくするために1次元配列(長さ256)で考えます
LUTの配列は入力画像の画素値をindexとして受け取り、そのindexのvalueを出力画像の画素値とします例えば、以下のようなLUTなら入力画像を画素値125を閾値として2値化するといった処理が可能になります。

index 0 1 124 125 126 254 255
value 0 0 0 255 255 255 255





C/C++での利用方法

LUTそのものは高さ1、幅256のcv::Mat型で宣言します。

cv::Mat lut = cv::Mat(1, 256, CV_8U);


入出力画像をsrc、dstとした場合先ほどの配列を用いると

cv::LUT(src, lut, dst);

とすることでLUTを適用することができます



使用例①(グレースケール画像の2値化)

プログラム

#include <opencv2/opencv.hpp>

int main(void)
{
	//入力画像と出力画像の宣言
	cv::Mat src, dst;

	//画像をグレースケールで読み込む
	src = cv::imread("読み込む画像のパス", 0);


	//LUTの作成
	cv::Mat lut = cv::Mat(1, 256, CV_8U);
	int thre = 125;
	for (int i = 0; i < lut.cols; i++) {
		lut.at<uchar>(0, i) = (i >= thre) ? 255 : 0;
	}

	//LUTの適用
	cv::LUT(src, lut, dst);

	//画像を表示
	cv::imshow("src", src);
	cv::imshow("dst", dst);

	//入力を受け付けたら終了
	cv::waitKey(0);

	return 0;
}



実行結果






カラー画像での使用方法

カラー画像に対してLUTを作成する場合は

cv::Mat lut = cv::Mat(1, 256, CV_8UC3);

というように3チャンネルで宣言する必要があります。


プログラム例②(カラー画像のR成分のみを取り出す)

#include <opencv2/opencv.hpp>

int main(void)
{
	//入力画像と出力画像の宣言
	cv::Mat src, dst;

	//画像をカラーで読み込む
	src = cv::imread("画像のパス");


	//LUTの作成
	cv::Mat lut = cv::Mat(1, 256, CV_8UC3);
	int thre = 125;
	for (int i = 0; i < lut.cols; i++) {
		//BGRの成分のうち、BとGを0にして、Rはそのまま
		lut.at<uchar>(0, i * 3) = 0;
		lut.at<uchar>(0, i * 3 + 1) = 0;
		lut.at<uchar>(0, i * 3 + 2) = i;
	}

	//LUTの適用
	cv::LUT(src, lut, dst);

	//画像を表示
	cv::imshow("src", src);
	cv::imshow("dst", dst);

	//入力を受け付けたら終了
	cv::waitKey(0);

	return 0;
}


実行結果



Unity 音楽再生アプリの改善(スライダーから再生位置の変更)





はじめに

以前の記事で作成した下の音楽再生アプリもどきでは再生位置をスライダーにより変更ができなかったので本記事でその方法を紹介します。






解決案

具体的には

  • TimeSliderのスライダーの値を変更している間は、その値により音楽の再生位置を変更する。

という処理をできるようにすればよさそうです。




プログラムの書き換え

以前の記事で紹介した「TimeLineController.cs」を次のように書き換えればよいです。
Pointer Downが呼び出し~Pointer Upが呼び出されるまでの間がスライダーの値を変更している時間なのでこれを利用しています。
EventTriggerの追加や設定の方法についてはこの記事を参考にしてください。
kiironomidori.hatenablog.com


using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;//追加

public class TimeLineController : MonoBehaviour
{
    [SerializeField] Text text;
    [SerializeField] Slider slider;
    float length;

    //追加
    bool isTouching = false;//タッチしているかどうか
    bool isPlaying = false;//タッチする直前に再生されていたかどうか

    string Str(float length)
    {
        int min = (int)length / 60;
        int sec = (int)length % 60;
        return $"{min}:{sec.ToString("D2")}";
    }

    //追加
    void EditSlider()
    {
        //add EventTrigger
        EventTrigger eventTrigger = slider.gameObject.AddComponent<EventTrigger>();

        //Pointer Downの追加
        EventTrigger.Entry entry = new EventTrigger.Entry() 
        { 
            eventID = EventTriggerType.PointerDown 
        };
        entry.callback.AddListener((data) => 
        { 
            isTouching = true;
            if (MusicController.I.audioSource.isPlaying)//再生されていたら停止
            {
                isPlaying = true;
                MusicController.I.audioSource.Pause();
            }
        });
        eventTrigger.triggers.Add(entry);

        //PointerUpの追加
        EventTrigger.Entry entry2 = new EventTrigger.Entry()
        {
            eventID = EventTriggerType.PointerUp
        };
        entry2.callback.AddListener((data) => 
        { 
            isTouching = false;
            if (isPlaying)//スライダーを操作する直前が再生状態なら再開する
            {
                MusicController.I.audioSource.Play();
            }
            isPlaying = false;
        });
        eventTrigger.triggers.Add(entry2);
    }

    private void Start()
    {
        //曲の全体の長さを取得
        length = MusicController.I.audioSource.clip.length;

        //追加
        EditSlider();
    }

    void Update()
    {
        //現在の再生個所を表示
        float nowTime = MusicController.I.audioSource.time;

        //テキスト表示
        text.text = $"{Str(nowTime)}/{Str(length)}";

        if (!isTouching)
        {
            //スライダーの設定
            slider.value = nowTime / length;
        }

        else
        {
            MusicController.I.audioSource.time = length * slider.value;
        }
    }
}





実行

今回の変更を加えると挙動が改善されていることが確認できる。