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

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

Unity SpriteEditorを利用した画像の分割





はじめに

SpriteEditorを利用した画像の分割方法についてまとめました。



画像ファイルの設定

使用する画像をプロジェクトにインポートしたら、インスペクター上でSpriteModeを「Multiple」に変更します。設定したら「Apply」ボタンを押して設定を適用させます。




SpriteEditor上の設定

1. 先ほどのインスペクターの画面から「Sprite Editor」をクリックしてSprite Editorを開きます。

2. SpriteEditor上で、「Slice」をクリックしたら「Type」を「Grid By Cell Count」に変更します。「Type」による分割方法の違いは下の表に示します。

Automatic 画像から判断して自動で分割をします
Grid By Cell Size 分割後の1枚ごとの画像のサイズをピクセル単位で指定します
Grid By Cell Count 縦と横にそれぞれ何分割するか設定して分割


3. 「Column & Row」の「C」と「R」をそれぞれ2にして、「Slice」をクリックします。設定が完了したら、SpriteEditorの右上にある「Apply」ボタンを押して設定を適用。


4. 正しく処理されていればインポートした画像がアセット内でこのように表示されます。





関連記事

分割したファイルを出力する場合については別記事にまとめました。
kiironomidori.hatenablog.com



Unity UIボタンを押した(離した)瞬間の処理(Event Trigger)





UIのボタンの問題点

Unityでは標準でUIのボタンが用意されており、ボタンを押したときの処理はonClickから設定することができます。


しかし、このonClickが呼び出されるタイミングは少し癖があり自分で実際に調べると「あるボタンをタッチしてからそのボタンの範囲内でタッチを離したとき」に呼び出されていました。

onClickにDebug.Log("OnClick")を搭載した関数を割り当てた例(分かりやすくするためにマウスをクリックしている間は星が赤く、そうでない間は緑色になるようにしています)


実際のゲームではボタンを使うときは押した瞬間などに処理をしてほしいことなどが多いのでその方法を調べまとめてみました。




解決策と使用例

解決策

結論としてはUIボタンにEvent Triggerを追加して使用するということで解決できました。


使用例(インスペクターから割り当て)

1. UIボタンの追加

まず、Hierarchyのプラスボタンのようなものを押してから「UI」⇒「Button」としてシーン上に任意のUIボタンを追加。


2. Event Triggerの追加

Add Componentから「Event Trigger」を追加します。


3. Event Typeの追加

追加したEvent Triggerに「Add New Event Type」というボタンがあるのでこれをクリックして「Pointer Up」(ボタンを押した瞬間に呼び出す)と「Pointer Down」(ボタンを話した瞬間に呼び出す)を追加します。
他のEvent Typeについては公式ドキュメントを参考にしてください。


4. スクリプトの作成

以下のスクリプト「Sample.cs」を作成します(比較のためにOnClick()も記述しています)。

using UnityEngine;

public class Sample : MonoBehaviour
{
    public void PointerDown()
    {
        Debug.Log("PointerDown");
    }

    public void PointerUp()
    {
        Debug.Log("PointerUp");
    }

    public void OnClick()
    {
        Debug.Log("OnClick");
    }
}



5. 処理の割り当て

先ほど作成したスクリプトをアタッチして、シーン上のボタンのonClickとPointer Up、Pointer Downにそれぞれ処理を割り当てます。



6. 実行

下の動画が実行結果です
onClickとPointer Downはともに離した瞬間に呼び出されているけど、ボタンの範囲外で離したときはonClickが呼び出されていないことも確認できます。





スクリプトから処理を割り当てる方法

先ほどの例ではインスペクターから

  • Event Triggerを追加
  • Event Triggerの「Add New Event Type」からPointer UpとPointer Downを追加してこれらに処理を割り当てる

という方法をとりましたが、ここではこれらの処理をスクリプトから行う方法を紹介します(公式ドキュメント参照)。

先ほどの「1. UIボタンの追加」の処理をしてボタンを追加したらそのボタンに以下のスクリプトをアタッチします。

using UnityEngine;
using UnityEngine.EventSystems;

public class Sample2 : MonoBehaviour
{
    void Start()
    {
        //add EventTrigger
        EventTrigger eventTrigger = gameObject.AddComponent<EventTrigger>();

        //Pointer Downの追加
        EventTrigger.Entry entry = new EventTrigger.Entry();
        entry.eventID = EventTriggerType.PointerDown;
        entry.callback.AddListener((data) => { PointerDown((PointerEventData)data); });
        eventTrigger.triggers.Add(entry);

        //PointerUpの追加
        EventTrigger.Entry entry2 = new EventTrigger.Entry();
        entry2.eventID = EventTriggerType.PointerUp;
        entry2.callback.AddListener((data) => { PointerUp((PointerEventData)data); });
        eventTrigger.triggers.Add(entry2);
    }

    void PointerDown(PointerEventData data)
    {
        Debug.Log("PointerDown from Script");
    }

    void PointerUp(PointerEventData data)
    {
        Debug.Log("PointerUp from Script");
    }
}


ゲームを実行したらこんな感じになります。




Unity Jsonファイルを利用したセーブ機能の実装





はじめに

Unityで作成したゲームのセーブデータは標準搭載のPlayerPrefsを用いる以外にも
Json形式で保存する方法があるそうなので今回はこれを紹介します。




用意したスクリプト

Jsonファイルの書き込み・読み込みを行うクラス

  • ジェネリッククラスにして汎用性が高めました。
  • Application.persistentDataPathはセーブするデータのフォルダ名を示しています(処理系により異なる)。
  • Save関数でセーブします。
  • Load関数でセーブしたデータをロードできますが、セーブデータが存在しない場合はdefaultを返却します。
using UnityEngine;
using System.IO;

public static class JsonSaveManager<T>
{
    static string SavePath(string path)
        => $"{Application.persistentDataPath}/{path}.json";

    public static void Save(T data, string path)
    {
        using (StreamWriter sw = new StreamWriter(SavePath(path), false))
        {
            string jsonstr = JsonUtility.ToJson(data, true);
            sw.Write(jsonstr);
            sw.Flush();
        }
    }

    public static T Load(string path)
    {
        if (File.Exists(SavePath(path)))//データが存在する場合は返す
        {
            using (StreamReader sr = new StreamReader(SavePath(path)))
            {
                string datastr = sr.ReadToEnd();
                return JsonUtility.FromJson<T>(datastr);
            }
        }
        //存在しない場合はdefaultを返却
        return default;
    }
}



ゲーム開始と同時にデータを読み込み、終了と同時にセーブするためのクラス

  • このクラスは記述例であり、実際はプログラム中のTをセーブデータを格納するクラス名に変更するなどの必要があります。
  • 先ほど作成したクラス「JsonSaveManager」をゲーム実行中の適切なタイミングで呼び出すためのクラスです。
  • Start関数内でLoad関数を呼び出していますが、もちろん他のタイミングでも呼び出せます。
  • ゲーム終了と同時にセーブするためにOnApplicationPause(bool isPaused)OnApplicationQuit()関数を使用しています。両方とも詳細はよくわかりませんが、前者はアンドロイドスマホ、後者はエディタ上での動作の時に必要でした。
using UnityEngine;

public class SaveControllerTemplate : MonoBehaviour
{
    public static SaveControllerTemplate I { get; private set; }

    string saveDataPath = "save-data";

    private void Awake()
    {
        I = this;
    }

    private void Start()
    {
        //セーブデータの読み込み
        T saveData = JsonSaveManager<T>.Load(saveDataPath);

        if (saveData == null)
        {
            //セーブデータが存在しない場合の処理
        }
        else
        {
            //セーブデータが存在する場合の処理
        }
    }

    private void OnApplicationPause(bool isPaused)//アンドロイドスマホではこっちが必要だった
    {
        if (isPaused)
        {
            OverWriteSaveData();
        }
    }

    private void OnApplicationQuit()//エディタ上で確認するときはこっちが必要だった
    {
        OverWriteSaveData();
    }

    //セーブデータの上書き
    void OverWriteSaveData()
    {
        //新たなセーブデータを作成処理
        T saveData = new T();

        //既存のセーブデータを上書き
        JsonSaveManager<TestSaveData>.Save(T, saveDataPath);
    }
}





スポンサーリンク






使用例

用意するスクリプト

用意したスクリプトはシーン上の任意のゲームオブジェクトにアタッチします。

①セーブしたいクラス
  • セーブしたいデータを格納したクラスがTestSaveDataです。
  • Json形式でデータを保存する場合はセーブしたい変数がいずれもインスペクター上に表示されている必要があるみたいです。そのため変数notSaveは保存されません。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

[Serializable]
public class TestSaveData
{
    public int num;
    public string str;
    public Vector3 vec;
    string notSave = "セーブされない";
}


public class TestClass : MonoBehaviour
{
    public int num;
    public string str;
    public Vector3 vec;

    public void SetValue(TestSaveData saveData)
    {
        //値TestSaveDataからセット
        num = saveData.num;
        str = saveData.str;
        vec = saveData.vec;
    }

    public static TestClass I { get; private set; }
    private void Awake()
    {
        I = this;
    }
}



②「SaveControllerTemplate」を書き換えたクラス
using UnityEngine;

public class SaveController : MonoBehaviour
{
    public static SaveController I { get; private set; }

    string testSaveDataPath = "test-save-data";

    private void Awake()
    {
        I = this;
    }

    private void Start()
    {
        TestSaveData saveData = JsonSaveManager<TestSaveData>.Load(testSaveDataPath);

        if (saveData == null)//セーブデータが存在しない場合は任意の値で初期化
        {
            //新たなセーブデータを作成
            saveData = new TestSaveData()
            {
                num = 3,
                str = "テスト",
                vec = Vector3.one
            };
        }

        TestClass.I.SetValue(saveData);
    }

    private void OnApplicationPause(bool isPaused)
    {
        if (isPaused)
        {
            OverWriteSaveData();
        }
    }

    private void OnApplicationQuit()//アプリケーション終了時に呼び出す
    {
        OverWriteSaveData();
    }

    //セーブデータの上書き
    void OverWriteSaveData()
    {
        TestSaveData testSaveData = new TestSaveData()
        {
            num = TestClass.I.num,
            str = TestClass.I.str,
            vec = TestClass.I.vec,
        };
        JsonSaveManager<TestSaveData>.Save(testSaveData, testSaveDataPath);
    }
}





実行結果の確認

まず、セーブデータがない状態で実行すると「TestClass」は次のようになる。


ゲームを中断するとJsonファイルが生成されており、中身を確認すると次のようになっている。確かに、変数notSaveは保存されていないことが確認できる。

{
    "num": 3,
    "str": "テスト",
    "vec": {
        "x": 1.0,
        "y": 1.0,
        "z": 1.0
    }
}


次にこの状態からゲームを再実行して「TestClass」を次のように書き換えると、jsonファイルの中身も変化することが確認できる。



{
    "num": -1,
    "str": "書き換え",
    "vec": {
        "x": 2.0,
        "y": -1.0,
        "z": 5.0
    }
}


そのあと再実行すると先ほどの画像のように「TestClass」に値が格納される。



Unity Shader2D 基本(色反転、グレースケール)





使用した素材

使用した素材はベイツ・イメージズ様のものです。それぞれの素材のリンクは以下の通りです。
激しく燃え上がる赤い炎
スプラッシュペイントと黒背景の壁紙

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





Shaderの使用方法

UnityでShaderを使うためにまず次の2つを作成します。

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

作成したら次のようになります。


そうしたらTestMaterialにドラッグ&ドロップでTestShaderを追加します。すると、マテリアルが変化している様子が確認できます。





とりあえず使ってみる

まず、先ほど作成した「TestShader.shader」を開くとデフォルトでプログラムが記述されています。
このプログラムのPropertiesとSubShader内のPass以外の部分を消すと次のようになります。

Shader "Hidden/TestShader"
{
    Properties
    {
        _MainTex("Base (RGB)", 2D) = "white" {}
    }

    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            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
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                //r, g, bの平均値をとっている
                fixed gray = (col.r + col.g * col.b) / 3;
                col.rgb = gray;
                return col;
            }
            ENDCG
        }
    }
}


次に用意した素材をシーン中に追加します。


ここで、追加した素材に対して「TestMaterial」をドラッグ&ドロップで追加すると画像の様に色が反転することが分かります。





先ほどのプログラムの解説

先ほどのプログラムの解説を行っていきます。


ファイルの構成

自分も青字の部分の詳細はよくわかっていません
なので、それ以外の番号を振った箇所の解説を行っていきます。。

番号 内容
デフォルトでインクルードされているファイル
公式ドキュメント「ビルトインのシェーダー include ファイル」に詳しく書かれています。
実際にダウンロードされているファイルを除いてもいいと思います。
sample2D _MainTex
という宣言を行うとシーン上のスプライトが操作できるようになるみたいです
メインとなる処理やそれに必要な変数の宣言などを行います。
変数colにはテクスチャーの色が格納されているので、それを1から引くことで色を反転させることができます。
ここの返り値が出力になるみたいです。





グレースケール化

先ほどのプログラムを書き換えてグレースケールに変更します。
書き換える箇所はfrag関数内のみで次のようになります。

fixed4 frag(v2f i) : SV_Target
{
    fixed4 col = tex2D(_MainTex, i.uv);
    //r, g, bの平均値をとっている
    fixed gray = (col.r + col.g * col.b) / 3;
    col.rgb = gray;
    return col;
}


適用すると次のようになっています。




Unity Shader3D 画像の貼り付けと色反転





画像の貼り付け

概要

テクスチャーを張り付けるにはShaderのProperiesで画像用の変数を用意する必要があります。宣言は

_MainTex ("Texture", 2D) = "white" {}

のようにする必要があります。

また、surf関数で利用するためにはPropertiesで宣言した変数と同じ名前の変数をSubShader内で

sampler2D _MainTex;

というように宣言する必要があります。


プログラム

ここで、実際にShaderファイル内にどのようなプログラムを書き込めばよいかを紹介していきます。
実はUnityでShaderファイルを作成したときに記述されているプログラムを書き換えるだけで大丈夫です。

Shader "Custom/Inverter"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };


        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
            o.Albedo = c;
        }
        ENDCG
    }
    FallBack "Diffuse"
}


プログラム内の

fixed4 c = tex2D(_MainTex, IN.uv_MainTex)

という処理でインスペクターで設定したテクスチャーを表示することができます。


動作確認

使用するテクスチャーはベイツ・イメージズ様のものを使用させていただきました。

まず、用意した画像をアセットに追加してShaderファイルとマテリアルを作成します


Shaderに先ほどのプログラムを記述したらマテリアルにドラッグ&ドロップで追加して、マテリアルに用意した画像をセットします。


このマテリアルを例えばSphereにアタッチすると以下の様になっていることが確認できます。





張り付けた画像の色反転

続いて先ほどのマテリアルの色を反転させて画像の様に表示させる方法を紹介します。


先ほどのプログラムの

o.Albedo = c;

の箇所を

o.Albedo = 1 - c;

とするだけです。



C言語 アドレスとポインタ(④関数に対して配列を引数として渡したとき)





C言語のアドレスとポインタ」シリーズ

1. アドレスについて
2. ポインタについて
3. 関数での利用
4. 関数に配列を引数として渡したとき(本記事)
5. 用法のまとめ




関数に対して配列を引数として渡したとき

前回の記事で関数に引数を渡したときは通常値渡しとなることを書きました。しかし、配列については例外で通常状態で参照渡しとなるようです

実際にプログラムで確かめてみます。

#include <stdio.h>

#define N 5

//受け取った配列の数値を2倍にする
void test(int a[N]) {
	for (int i = 0; i < N; i++)
		a[i] *= 2;
}

int main() {
	int x[N] = { 1,2,3,4,5 };

	//元の配列の表示
	puts("元の配列");
	for (int i = 0; i < N; i++)
		printf("%d ", x[i]);
	puts("\n");

	//関数の呼び出し
	test(x);

	//変更後の配列の表示
	puts("変更後の配列");
	for (int i = 0; i < N; i++)
		printf("%d ", x[i]);

	return 0;
}


実行結果

元の配列
1 2 3 4 5 

変更後の配列
2 4 6 8 10 





引数として渡した配列を書き換えられないようにする方法

参照渡しの時にアドレスなどについて考えなくてもいいのは便利ですが、逆に関数内で配列が意図せず書き換えられてしまう必要があります。
その対策としては配列の変数の前にconstをつければよいです。例えば、先ほどの関数なら

void test(const int a[N]) {
	for (int i = 0; i < N; i++)
		a[i] *= 2;
}

とすればよいです。関数内で配列を書き換えようとしているのでこの状態で実行しようとするとエラーが表示されるようになります。



Python Pandas DataFrameとnumpy配列の相互変換





>DataFrame⇒numpy配列の変換

dataframe.to_numpy()
メソッドを使用します。

プログラム

import pandas as pd
import numpy as np
df = pd.DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]], 
    index=["Zero", "One", "Two", "Three"], 
    columns=["X", "Y", "Z"])

#全て変換
a = df.to_numpy()
print(f'全て変換\n{a}', end = '\n\n')

#指定したcolumnのみ変換
b = df['X'].to_numpy()
print(f'指定したcolumnのみ変換\n{b}', end = '\n\n')

#指定した範囲のみ変換
c = df.iloc[1:3, :].to_numpy()
print(f'指定した範囲のみ変換\n{c}', end = '\n\n')


実行結果

全て変換
[[ 1  2  3]
 [ 4  5  6]
 [ 7  8  9]
 [10 11 12]]

指定したcolumnのみ変換
[ 1  4  7 10]

指定した範囲のみ変換
[[4 5 6]
 [7 8 9]]





numpy配列⇒DataFrameの変換

プログラム

import pandas as pd
import numpy as np

arr1 = np.arange(1, 13)
arr1 = arr1.reshape(-1, 3)

df = pd.DataFrame(arr1, 
    index=["Zero", "One", "Two", "Three"], 
    columns=["X", "Y", "Z"])

df


実行結果