【Unity】UniRxで列数、行数から動的にセルサイズを調整するGrid Layout Groupを実現する

f:id:Kanchi0914:20200726020736g:plain

Unityのレイアウト用コンポーネントの1つにGrid Layout Groupがあります。 名前の通りグリッド、もといエクセルのような表形式でオブジェクトを配置する場合に使うためのコンポーネントですが、個人的にはいまいち使い勝手が悪いと感じています。Grid Layout Groupでは、基準がCell Sizeで、列数、行数はCell Sizeを基に決定される仕様となっているので、行数、列数を基準にCell Sizeを決定するということがエディタ上では難しいからです。Vertical LayoutやHorizontal Layoutでは親のサイズに合わせて子のサイズを動的に決定してくれるので、それに比べると‥‥という感じです。 一応Constraintの値をFixed Column Sizeなどに変えると列数、行数を自由に決めることはできますが、これは本当に列数、行数を固定するだけで、それに応じてセルのサイズを動的に決定してくれたりはしません。

結局のところ、「〇行〇列の表」をGrid Layout Groupを使って作ろうとすると、Item Sizeを毎度いい感じに調整するしかないというわけです。で、これを毎回設定するのは面倒なので、行数と列数を指定すると自動的にセルのサイズを調整してくれるスクリプトを書いてみました。

gistaf937e71da132d53ed8290f4fd27e25a

セルサイズの変更はUpdate関数内でやってもいいのですが、せっかくなのでUniRxを使って値が変更されたときのみ変更するようにしています。

gameObject.ObserveEveryValueChanged(_ => rectTransform.sizeDelta).Subscribe(_ => UpdateCellSize());
gameObject.ObserveEveryValueChanged(_ => gridLayout.spacing).Subscribe(_ => UpdateCellSize());
gameObject.ObserveEveryValueChanged(_ => gridLayout.padding.GetType().GetProperties()).Subscribe(_ => UpdateCellSize());

UniRxはご存じの方も多いと思いますが、以前紹介したC#のイベントをUnity向けによしなにやってくれるアセットで、今回の場合のように特定のプロパティの値を監視し、値が変更されたときに特定のメソッドを呼び出したいという処理にはピッタリだと思います。

github.com

UniRxでは色々なことができるっぽいですが、全部を使いこなすのは自分には難しそうです。ただ今回のように値の変更を監視するだけであれば、以下の感じで書けるので楽だと思います。

gameObject.ObserveEveryValueChanged(_ = 値を監視したいプロパティ).Subscribe(_ => 呼び出したいメソッド)

難点として上記スクリプトでは rectTransform.sizeDelta を基準にセルサイズを決定しているので、AnchorにStretch(青色のやつ)を含む可変サイズのものを設定すると正しく動作しないという点があります。Stretchの場合、sizeDeltaはマイナスの値を取りうるからです。 個人的な事情で最近RectTransformのあれこれについて勉強してるので、そのうちその辺について記事を書くかもしれません。詳しいことはそちらで。それでは。

DoTweenのOnComplete()が呼ばれなくて困った話

久しぶりにDoTweenを使っていてしょうもないことでハマったので、反省の意味を込めてメモしておきます。 アニメーションが再生中であるかどうかを判定するために、再生中のSequenceをメンバー変数に持たせればいいかと思い以下のように書いていました。

using Assets.Scripts.SGUI.SGameObjects.ComponentScripts;
using DG.Tweening;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class App : MonoBehaviour
{
    [SerializeField]
    private GameObject obj;

    [SerializeField]
    private Button button;

    public Sequence sequence;

    void Start()
    {
        sequence = DOTween.Sequence();
        button.onClick.AddListener(() =>
        {
            Animate();
        });
    }

    public void Animate()
    {
        Debug.Log("STARTED");
        if (sequence.IsPlaying()) { sequence.Complete(); }
        else sequence.Append(obj.transform.DOShakePosition(10f, 100, 100))
            .OnComplete(() =>
            {
                Debug.Log("COMPLETED");
            });
    }
}

が、これはうまくいきません。sequence.IsPlaying() は呼ばれないし OnComplete() も呼ばれません。 理由は単純に自分が勘違いしてただけなのですが、Sequenceはインスタンスを生成して使いまわすものではなく、アニメーションを再生するたびに DOTween.Sequence()で生成するものだからみたいです。なので正しくはこうです。

    public void Animate()
    {
        Debug.Log("STARTED");
        if (sequence.IsPlaying()) { sequence.Complete(); }
        else sequence = DOTween.Sequence().Append(obj.transform.DOShakePosition(10f, 100, 100))
            .OnComplete(() =>
            {
                Debug.Log("COMPLETED");
            });
    }

Append()の意味を考えると当然なのですが、なまじOnComplete以外のアニメーションは動くのでしばらくハマりました‥‥
一度ちゃんとドキュメント読んでおこうと思いました。

Ubuntu 18.04(Zorin OS 15.2)にUnity+C#の開発環境を構築するメモ

はじめに

最近自宅のPCにLinuxを導入しました。仕事でMacを触るようになってから、Windowsコマンドライン関連を含めた使いづらさが目立つようになり、その他の細かい不満点を含めて脱Windowsを目指そうと思ったからです。

LinuxディストリビューションはZorin OS 15.2を選びました。Zorinは以前にも触ったことがあるのですが、特に水色を貴重としたUIのデザインが自分の好みで、Ubuntu 18.04ベースになって安定性が増したらしいということもあり、改めて使ってみることにしてみました。今の所大きな不具合もなく、動作の軽量さも含めてとても快適に使えています。

zorinos.com

もちろんWindowsデュアルブートできる環境にしているので、当初はIntelliJVS CodeでまかなえるコーディングはZorinで行い、UnityはWindowsで触ればいいかと思っていたのですが、LinuxでもUnityが動くらしいことを知り、早速インストールして使ってみました。以下はその導入メモです。

環境構築

1. Linux向けUnity Hubをダウンロードする

公式サイトからダウンロードします。ここでダウンロードされるのはAppImageというもので、異なるLinuxディストリビューション間でもインストール不要で使える形式らしいです。

unity3d.com

ダウンロードしたら権限を与えてコマンドラインから実行します。ダウンロードしたAppImageをクリックすると実行できるかと思いきやそうでもなかったので、コマンドで実行します。

chmod +x UnityHub.AppImage
./UnityHub.AppImage

初回起動時はUnityのライセンスが求められると思うのでログインしてライセンスを更新します。Unity Hubから最新のUnityをダウンロードし、起動できることを確かめます。

2. VS Codeをインストールする

Windowsだと大体の人はVisual Studioを使っていると思いますが、残念なことにLinuxでは使えません。代わりにVS CodeLinux版が公開されているので、それを使います。

Visual Studio Code – コード エディター | Microsoft Azure

また開発に必要な拡張機能をインストールします。必要な拡張機能は以下の通りです。

C#

必須です。

f:id:Kanchi0914:20200713235822p:plain

Debugger for Unity

デバッグに必要です。

f:id:Kanchi0914:20200713235837p:plain

MonoBehaviour Snipetts

MonoBehaviour関連の入力補助を行えるようになります。

f:id:Kanchi0914:20200713235852p:plain

C# FixFormat Fixed

必須ではありませんが、あると良さげです。導入する場合は下記記事の下のほうにある括弧位置の修正を行うといいと思います。
Visual Studio CodeでC#プログラミング - Qiita

f:id:Kanchi0914:20200713235910p:plain

あと個人的な好みですが、Ctrl + ホイールスクロールで文字サイズを変更できるようにするのがおすすめです。
Is it possible to configure control + scroll-wheel to increase/decrease zoom in VS Code? - Stack Overflow
最後にUnityエディタの Edit > preferences > External Tools > External Script Editor をVS Codeに設定します。

f:id:Kanchi0914:20200713235554p:plain

3. .Net Core SDK をインストール

公式ページのUbuntu 18.04の部分に従いインストールします。

Ubuntu に .NET Core をインストールする - .NET Core | Microsoft Docs

wget https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
sudo apt-get update; \
  sudo apt-get install -y apt-transport-https && \
  sudo apt-get update && \
  sudo apt-get install -y dotnet-sdk-3.1

4. Monoのインストール

そのままだと.Net Frameworkの参照が足りない的なエラーが出ます。

The reference assemblies for framework ".NETFramework,Version=v4.7.1" were not found. To resolve this, install the SDK or Targeting Pack for this framework version or retarget your application to a version of the framework for which you have the SDK or Targeting Pack installed. Note that assemblies will be resolved from the Global Assembly Cache (GAC) and will be used in place of reference assemblies. Therefore your Assembly may not be correctly targeted for the framework you intend.

(参照:) c# - Reference assemblies for framework ".NETFramework,Version=v4.7.1" were not found - Stack Overflow

ので、Monoという.Net Framework互換の環境を利用可能にするオープンソースプロジェクトを導入します。公式サイトのUbuntu 18.04の項目を参考にインストールします。

Download - Stable | Mono

sudo apt install gnupg ca-certificates
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
echo "deb https://download.mono-project.com/repo/ubuntu stable-bionic main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list
sudo apt update
sudo apt install mono-devel

5. VS Codeを開き、デバッグができるか確認

4まででC#スクリプトの実行はできるようになっていると思うので、最後にデバッグができるか確認します。Unityエディターを開き適当にスクリプトを追加し、それをVS Codeで開きます。
VS Codeの左のメニューでDebugを選び、 青色になっている「Create a launch.json file」をクリックするとデバッガーの一覧が出るので、 「Unity Debugger」を選択します。

f:id:Kanchi0914:20200714000634p:plain

生成されるlaunch.jsonが以下のようになっていれば大丈夫だと思います。

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Unity Editor",
            "type": "unity",
            "path": "/home/shun/works/Unity/New Unity Project (1)/Library/EditorInstance.json",
            "request": "launch"
        },
        .............

適当にブレークポイントを設定し、デバッグできるか確かめます。

f:id:Kanchi0914:20200714000935p:plain

おわりに

WindowsでのUnity開発環境が100点とすると、UbutnuでのUnityは80点くらいな感じですが、それでもLinux環境でUnityが動くという事実が嬉しいです。LinuxでのUnityはまだほとんど触っていませんが、特に問題がないようなら本格的にWindowsを触る機会が減りそうだなと思いました。

参考にさせていただいたサイト

LinuxにUnity Editorをインストールする方法 | My note

c# - How do I use Visual Studio Code to develop Unity3D projects in Ubuntu - Stack Overflow

【Unity 2D】一番簡単なHPバーの作り方+その他ダメージ演出

よく作るのでメモしておきます。コード全体へのリンクは記事の最後に貼ってあります。

f:id:Kanchi0914:20200312213019g:plain

とりあえず一番簡単なHPバーの作り方

本当に最低限の労力でHPバーを作るなら以下のようになります。前提としてヒエラルキーCanvasが存在し、Render ModeがScreen Space-Cameraに設定されていることを想定しています。

  1. HierarchyのCanvas上で右クリック→UI>Panelを選択しオブジェクトを追加(減少したHPを表す部分)
  2. 適当な大きさに変更し、InspectorビューのImageコンポーネントのColorを調整(赤色で透明度を下げるなど)
  3. 2.のオブジェクトを複製し、複製したものを2.の子オブジェクトにしてColorを調整(HPの現在値を表す部分)
  4. Inspector上で3.のオブジェクトのRect Transform>PivotのXを0にする
  5. 3.のオブジェクトにスクリプトをアタッチし、RectTransformのScaleの値を0~1の値で変化させる(下記)

4.を行うことで、緑色のバーのオブジェクトのScaleの値を変更したとき、その左端を中心にオブジェクトの大きさが変化することになります。

例えばHPバーの対象となるオブジェクトが以下のように定義されているとします。

public class Human : MonoBehaviour
{
    private int maxHP = 100;
    private int currentHP = 100;
    
    public float PerHP
    {
        get
        {
            float value = (float)currentHP / (float)maxHP;
            return Mathf.Clamp(value, 0, 1);
        }
        private set { }
    }

    .....

PerHPは現在のHPの割合を0~1.0の値で返すプロパティです。Mathf.Clamp()は変数に上限値と下限値を設定し、値を超えていればそれらの設定した値を返してくれる便利なメソッドです。なおVisual Studio上では(float)CurrentHPのキャストが冗長だとされるのですが、付けないとint型のまま計算が行われ除算の結果が0か1にしかならなくなるので気を付けましょう。基本的な事ですが自分もよく忘れてバグを生みます。

3.で作った緑色のHPバーにアタッチするスクリプトは以下のように記述します。といってもUpdate内でRectTransformのScaleの値を変化させるだけです。

public class HPBar : MonoBehaviour
{
    [SerializeField]
    Human human;
    RectTransform rect;

    void Start()
    {
        rect = GetComponent<RectTransform>();
    }

    void Update()
    {
        rect.localScale = new Vector2(human.PerHP, 1f);
    }
}

f:id:Kanchi0914:20200312213009g:plain

とりあえずこれだけで見れるものは作れます。

ただこれだけだと物足りないので、今回はもう少し手を加えたものを作ります。

HPが減少したときだけバーを変化させる

上のコードのままだと、たとえば値が変化したときのみ処理を行うといったことができず、アニメーションなども付けられません。よってこれを現在のHPの値が変化したときのみ処理を行うように修正します。このような場合にはeventを使います。具体的には以下のようになります。

public class Human : MonoBehaviour
{
    public int MaxHP = 100;

    //デリゲートの宣言
    //増減した値を取得するために、変更前の値を引数としています
    public delegate void ValueChangedHandler(int preValue);
    //ここに対象の値が変化したときの処理を追加していく
    public event ValueChangedHandler HpChanged;

    public int CurrentHP
    {
        get { return currentHp; }
        set
        {
            if (currentHp != value)
            {
                var pre = currentHp;
                currentHp = value;
                //ここで登録したデリゲートが呼ばれる
                HpChanged(pre);
            }
        }
    }
    int currentHp = 100;

    .....

HPバーのスクリプトは以下のようになります。前に紹介したDOTweenを使用して簡単なアニメーションをつけています。

    void Start()
    {
        //HPが変化したときに呼びたいメソッドを追加
        //メソッドを複数追加することもできます
        human.HpChanged += OnHpChanged;
    }

    void OnHpChanged(int preValue)
    {
        transform.DOScaleX((float)human.PerHP, 0.2f);
    }

eventとはプロパティのデリゲート版ということで、デリゲートの呼び出しや追加・削除に制限を加えたものと自分的には認識しています。

ufcpp.net

実体はただのデリゲートなので、例えば上のコードからevent宣言を抜いても動作します。

    public delegate void ValueChangedHandler(int preValue);
    public ValueChangedHandler HpChanged;

何の原則というのかわかりませんが、実際にはバグを防ぐため機能に制約を設けた方がいいという考えがあるため、このような形になっているのだと思います。

なお今回、値の変更を検知する方法としてイベントを利用しましたが、他にもINotifyPropertyChangedを使うなどの実装方法があります。イベント自体にも色々問題があるらしく、Reactive Extensionsを使うなどしたほうが良いかもしれません。自分もそのうち勉強しようと思います。

ufcpp.net

その他

上の例だとHPバーを上のほうに表示していますが、HPをもつオブジェクトにCanvasを追加し、そこにHPバーオブジェクトを表示することで、キャラクターの動きにHPバーを追従させることもできます。

f:id:Kanchi0914:20200312213014g:plain

その他のダメージ表現などは全てDOTweenで実装しています。コード全体は以下に上げてあるのでよかったら参考にしてください。

github.com

お借りした素材:teddy-plaza様

teddy-plaza.sakura.ne.jp

UnityでTransformのposition.x, y, zの各値を、拡張メソッドで直接変えられるようにする

さんざん言われてることだとは思いますが、UnityでTransformの位置、つまりpositionの値を変更したいと思ったとき、positonのx,y,zという各値を直接変更することはできません。

//これはできない
transform.position.x = 10f;

これはtransform.positionが、メンバー変数ではなくプロパティとして定義されているためです。C#では、構造体がプロパティになっている場合、その各メンバーの値を直接変更することはできない仕様らしいです。そのためpositionの値を変更したいとき、通常は以下のようにすると思います。(positionを別の変数に取り出してから値を変更する、というやり方もあるみたいですが、手間を考えるとそんなに差はない気がします)

transform.position = new Vector3(x, transform.position.y, transform.position.z)

しかし処理の出現頻度の割に、これを毎回書くのは面倒です。拡張メソッドの形にすれば処理を共通化できるのではないかと思い、Transformクラスの拡張メソッドとして実装してみました。

TransformExtensions.cs

//xの値を0に
transform.SetPosX(0f);
//x方向に0.5f移動
transform.AddPosX(0.5f);

という感じで、直接positionのx,y,zの値を書き換えられて便利な気がします。

ここからは余談ですが、けっこうややこしいというか勉強になったことです。上のコードは最初はVector3構造体を拡張する形で書いてみたのですが、

    public static void SetPosX(this Vector3 pos, float x)
    {
        pos = new Vector3(x, pos.y, pos.z);
    }

これは正常に動作しません。エラーは出ませんが、実際には値が書き変わらないという面倒なことになります。このthisで参照される値は実際にはコピーした値であり、その値を変更しても元の構造体には影響がないためです。知らないままだと思わぬところでバグが発生しそうで怖いですね。

Hexo+Typoraではじめる、一番楽な技術ブログの書き方

はじめに

この度新しい技術ブログを始めることにしました。Hexoでサイト作ってNetlifyにデプロイしてます。この記事を書くためにサンプルをそのまま上げていて設定とかなんもやってないのですが、また折をみて更新していきたいと思います。

Kanchiの雑記帳 | サブブログです。日記的な感じで書いていきます

今回、ブログを書く環境をいろいろ試した結果、TyporaでMarkdownを書いてHexoでブログサイトを作るという方向性に至ったのですが、この構成だとかなり書きやすそうというか、自分にとってベストだったのでお勧めします。Markdownでブログ書くなら、多分これが一番楽だと思います。

新規ブログ開設に至った理由

自分はこのブログを技術ブログとして使ってきたのですが、書いてきた記事の内容的には、なにか自分が作ったもので完成までに苦労した点など、自分にとっての復習兼他人へのチュートリアル的な感じなものが多く、自分としてもあえてそうしてきた面があります。しかし最近多方面に勉強する機会が増えてきて、トラブルの解決方法など記事にしたい事が毎日のようにあったのですが、前述の通りこのブログは一つの記事に一つのテーマみたいなものを心掛けているため、書く内容はあるのになかなか記事を書きづらいというもどかしさを感じていました。そういうわけで、わざわざ一本のブログ記事として書くほどではないけど、他に同じ問題で困っている人がいるかもしれないからとりあえず書いておこうみたいなブログが欲しくなり、新規ブログ開設に至ったわけです。もちろんこちらのブログをやめるわけではなく、こちらのブログは今まで通りで、向こうのブログではもっと適当な、日記的な記事を書いていきたいと思います。

Hexo+Typoraのブログ環境

余談はさておき本題に入りましょう。

まずはてなブログを選ばなかった理由として、このブログははてなブログで書いているのですが、今までは記事を書くのにVS CodeMarkDownを書いて、その内容をはてなのエディターにコピペし、画像をアップロードしてリンクを追加する‥‥という七面倒なことをやっていました。もちろん記事ごとにアクセス数解析できたりメリットも大いにあるのですが、今回はそういうの気にしない適当なブログなので大丈夫ということにします。

また他の静的サイトジェネレータとしてVuePressなどがあり、最近になってブログ用プラグインが出たので使ってみようかと思ったのですが…正直手間がかかりすぎるのと思ったより自由度が低そうなのでやめました。手間を考えるとやっぱりHexoを一番お勧めします。

Hexoのインストール

まずはHexoをインストールします。HexoはNode.jsのフレームワークなのでnpmでインストールします。

hexo.io

公式サイトに記載されてる通りにやると5行コマンドを打つだけで動作確認までいけます。便利ですね。

npm install hexo-cli -g
hexo init blog
cd blog
npm install
hexo server

新規記事の追加

新しく記事を書くには以下のコマンドを実行します。

hexo new hoge

すると/source/_posts内に新しくhoge.mdが作られ、ここに記事の内容を書くことになります。

---
title: test
date: 2020-03-08 12:05:35
tags:
---
~~ここに内容を書く~~

hexoではこの_posts内にあるMarkdownファイルが記事としてみなされるようです。

画像の挿入

今回一番苦労した点です。これがうまくいかなかったから他の静的サイトジェネレータを試したくらいです。自分が一番知りたかったのは相対パスで画像を指定する方法で、他のサイトだと configのpost_asset_folderをtrueにしてhexo-asset-linkプラグインを入れる…みたいなことが書かれてあるのですが、自分の環境だとどうやっても正常に画像を表示することができませんでした。

liolok.github.io

実際に生成されているhtmlを見ると、画像のリンクがsrc="/.com//image.jpg"というものになってしまっていることがわかります。これは一応githubのissuesにも上がっているみたいで、Macで試してないのでわかりませんが、Windowsの最新版で起こる問題なのかもしれません。リポジトリアーカイブされているため解決することも難しそうです。

github.com

それで結局どうすればよいかというと、Hexoには画像のグローバルパスというものが存在し、source/imagesがそれにあたります。ここに配置した画像はサイト生成時にimagesフォルダに入れられ、![](/images/image.jpg)のような形でアクセスすることができます。現状、上の問題が発生している場合、相対パスで画像を指定するには他の方法はおそらく無いと思われます。(むしろあったら教えてください。本当に)

Typoraの導入と設定

Typoraは最近知ったのですが、Markdownとして打っている画面がそのままプレビューになり、しかもMarkdown記法のままコピーできる優れものです。Macだと使えるBearがWindowsだと使えないのもあってこれ一択みたいなところがあります。

typora.io

便利なことにTyporaはドラッグ&ドロップで画像を挿入することができ、そのままだと画像の絶対パスが挿入されます。TyporaをHexoのブログ編集用に使うため、以下のように設定を変更します。

f:id:Kanchi0914:20200308143942p:plain

この設定にすると、Hexoプロジェクトのsource/_posts/hoge.mdという記事をTyporaで編集したとき、そこに画像をD&Dすると source/images/hoge/にその画像がコピーされます。Markdownでは以下のように記述されます。

![animals](../images/hoge/animals-1583642380054.jpg)

画面上でもちゃんと表示されています。 f:id:Kanchi0914:20200308144528p:plain

こうするとD&Dでお手軽に画像を貼れて、しかもそのままgit pushすることで記事を更新することができるため、とても楽です。加えて普通だと少し面倒なMarkdown上での画像サイズの編集も、Typoraだと画像を右クリック→画像を拡大から自由にできて、ちゃんと<img src="~~~">の形に直してくれるのですごいです。

ただし上の記述のままだと、TyporaとHexoブログ上の記事一覧画面からは画像を表示することができるのですが、個別の記事画面からは画像が表示されません。Hexoでは記事の個別画面のURLを示すパーマリンクがデフォルトでは以下のようになっていて、記事個別画面からだと画像の相対パス/2020/03/08/images/hoge/image.jpgとかになってしまうためです。

permalink: :year/:month/:day/:title/

これを解決する方法は二つあって、一つは上でも書いた通り画像のパスを/images/hoge/image.jpgにすることです。頭にスラッシュ付けないとダメなので注意してください。

![animals](/images/hoge/animals-1583642380054.jpg)

ただこれだとTypora上からはプレビューできません。もう一つの方法として、_config.yml内のパーマリンクの指定を記事のタイトルだけにするというものがあります。

permalink: :title/

こうすると記事一覧画面と記事個別画面で画像の相対パスが一緒になり、どちらからも../images/hoge/image.jpgで参照できるため、Typoraでプレビューした内容をデプロイ先でそのまま表示でき、面倒な画像の挿入がD&Dだけで解決してしまいまいとても便利な気がします…が、大体の人はおそらくこの部分をいじらないため、よく知らないのですが多分あんまりやらないほうがよいのではないかと思っています。自分は../images/hoge/image.jpgで記事を書きTypora上でプレビューしつつ、デプロイ時にその部分だけ修正することにします。そもそもHexo serverでプレビューできますしね。

テーマの導入

Hexoではブログテーマの導入もすごく簡単です。themesディレクトリ内にダウンロードしたテーマのディレクトリを配置し、_config.ymltheme: ~~~をそのディレクトリ名に変更するだけでOKです。

photo-tea.com

例えば自分はBlueLakeというテーマを使っているのですが、導入したいテーマのgithubリポジトリgithub.com/username/reponameにあるとした場合、以下のコマンドで行うことができます。

git clone https://github.com/username/reponame.git themes/reponame

ただNetlifyを使う場合、デプロイする際にNo url found for submodule path gitmodulesとかで失敗します。直接ダウンロードしてディレクトリを移動させるか.gitディレクトリを削除すると解決します。

stackoverflow.com

自分が使用しているテーマ:https://github.com/chaooo/hexo-theme-BlueLake

デプロイ

デプロイは簡単にできます。github.ioで公開する場合は username.github.ioの形でレポジトリを作り、_config.ymlを編集します。レポジトリ名は必ず自分のユーザー名.github.ioに形にしないといけないので注意してください。

deploy:
  type: git
  repo: https://github.com/username/username.github.io.git
  branch: master

以下のコマンドでデプロイできます。username.github.ioにアクセスして確認してみてください。

hexo deploy -g

自分はNetlifyを使うことにしたのですが、これもレポジトリを登録するだけで簡単にできます。この場合はhexoプロジェクトでgit remote add origin ~~~~でレポジトリを登録してmasterにpushするだけでOKなので、個人的にはこちらが好みですね。

qiita.com

おわりに

今回はHexo+Typoraでお手軽に技術ブログを書く環境の作り方を紹介しました。新しいブログではアクセス数とかあまり気にせず日記的な感じで雑に書いていきたいと思います。

Spring Data JPAの使い方メモ

最近になってJavaのSpring Bootを使ったWeb開発の勉強をはじめました。その中でSpring Boot JRAというデータベースを操作するためのライブラリが便利だったので使い方をまとめておきます。

依存関係

以下はビルドツールにgradleを使用する場合の例です。Spring InitiallizerでSpring Data Jpaにチェックを入れるとbuild.gradleのdependenciesに次の項目が追加されていると思います。

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}

Spring Data JPAとは

そもそもSpring Data JPAとは、Repositoryクラスを便利に実装するためのライブラリです。JavaのWeb開発で理想とされるMVCモデルにおいて、メインのControllerクラスなどからデータベースを直接扱うことは好ましいとされておらず、そのために用いられるデータベース操作のためのクラスがRepositoryクラスです。別の言い方だとDAO(Data Access Object)と言ったりもします。これを自分で実装するとSQLのクエリの種類ごとに記述しないといけないのでやや手間がかかるのですが、Spring Data JPRを使うとこれがごく簡単に書けます。たとえばEmployeeというエンティティクラス(データベースの入れ物となるクラスで、データベースのレコード=一つのデータに相当します)があって、nameというフィールドで指定した文字列に完全一致するものを取得したいときは次のように書けます。

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, String>{
  List<Employee> findByName(String name);
}
@Autowired
EmployeeRepository employeeRepository;
@RequestMapping(value = "/{name}", method = RequestMethod.GET)
public String findEmployeeByName(@PathVariable("name") String name)
{
    return employeeRepository.findByName(name);
}

基本的な書き方はドキュメントを見てもらえればわかるのですが、検索したいフィールド名の最初を大文字にして、findBy~などの特定のフォーマットに合わせてメソッドを定義することで、JPAが自動で実装してくれます。便宜上引数もnameとしていますが、実際には何でも構いません。

docs.spring.io

qiita.com

以下は個人的につまずいた点などをまとめたメモです。

クエリを自分で定義したい

メソッドの前に@Queryアノテーションを付けます。例えば上のfindByNameメソッドのクエリ部分を自分で記述するなら以下のように書きます。?1はメソッドの引数を表し、例えばメソッドの引数を増やしたときは?2, ?3 ...とすることでそれぞれアクセスできます。

@Query("select e from Employee e where e.name = ?1")
public List<Employee> findByName(String name);

このクエリ文はJPQLといい、SQLのクエリ文とは異なることに注意です。上のクエリをSQL上で実行しても当然ですがエラーが出ます。SQL構文をそのまま使いたいときは、アノテーション内でnativeQuery=trueとします。

@Query(value = "SELECT * FROM Employees WHERE name LIKE ?1", nativeQuery=true)
public List<Employee> findByName(String name);

部分一致検索をしたい

findBy(フィールド名)Containingを使います。

public List<Employee> findByNameContaining(String name);

@Query("select e from Employee e where e.name like %?1% ")
public List<Employee> findByLikeName(String name);

特定の引数を省略したい

Spring Data JPAではメソッド名にANDを加えることで複数のフィールド(=カラム)を検索できます。

public List<Employee> findByNameAndStatusContaining(String name, String status);

このとき、特定の引数にnullが入るとうまく検索できなくなります。これを防ぐためには、Controller側でデフォルトの値をワイルドカードである%に指定しておくといいと思います。以下の例ではdefaultValue = "%"がそれにあたります。

@RequestMapping(value = "/", method = RequestMethod.GET)
public String getEmployeesByQuery(@RequestParam(value = "name", required = false, defaultValue = "%") String name, @RequestParam(value = "status", required = false, defaultValue = "%") String status){
    ~~~~
}

Ordinal parameter not bound エラーが出る

@Queryアノテーション内で、?1を省略するとおそらく次のエラーが出ると思います。一番目の引数である?1は必ず使用しなければならないようです。

There was an unexpected error (type=Internal Server Error, status=500).
Ordinal parameter not bound : 5; nested exception is org.hibernate.QueryException: Ordinal parameter not bound : 5
org.springframework.dao.InvalidDataAccessResourceUsageException: Ordinal parameter not bound : 5; nested
https://kaige.org/2019/07/10/org-hibernate-QueryException-Ordinal-parameter-not-bound/