#3 - Unity C#で自分が良く使うLinqの紹介 | 駆け出しゲームプログラマーの備忘録

1. そもそもLinq( リンク )とはなんぞや

Linq はズバリ、「統合言語クエリ」です!



.......何それ????????



正直、統合言語クエリが何者なのかは知らなくても、書き方・挙動さえ知っていれば Linq は実践レベルで使用できます。
なので、割愛!!! (気になる人は自分で調べてみてネ)

2. Linq の基本的な機能・書式・使用例

はい、統合言語クエリが全くわからない状態でもこの項目さえ見れば普通に使えます。
まず、このLinqが使用できるのは、普段よく使うものだと 配列、 List<>、Dictionary<>あたりが基本です。

2-1. Linq の基本的な書式

まずこれ、いつものやつで必須です。

using System.Linq;

そして、下記コードに例として Linq を使用して特定の条件に合う要素をピックアップしています。

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Linq;

public class Example : MonoBehaviour
{
    private List<int> numberList = new List<int>()
    {
        1,4,5,7,8,9,10,20,25,40,100,101
    };

    /// <summary>
    /// 奇数の数字だけピックアップしてリストにして取得
    /// </summary>
    public List<int> GetOddNumberList()
    {
        return this.numberList.Where(v => v % 2 == 1).ToList();
    }
}

今回は例で、あるリストから奇数の数字のみを取得しリストにして取得するコードです。
この中で Linq と言われている部分は

this.numberList.Where(v => v % 2 == 1).ToList();

ハイ、ここです。
基本的には 変数.Linq(条件) のような書き方で書いていきます。
今回の処理を言葉に起こすと

numberList の中の数字を見ていって、2で割ったあまりが1になる数字のみ集めて、それをList化するで~

って感じです。
WhereとかTolistとかはまだわからなくてokです。とりあえず書き方とコードの読み方さえ分かれば問題ないです。
次はよく使うLinqの紹介をしていきます。

2-2. Where

はい、さっき例で紹介したやつです。個人的な体感ですが、一番Whereが使われがちかなと思います。

上の例で使用したものを引用して説明します。
this.numberList.Where(v => v % 2 == 1) の部分をLinqなしのコードで書くと

    /// <summary>
    /// 奇数の数字だけピックアップしてリストにして取得
    /// </summary>
    public List<int> GetOddNumberList()
    {
        List<int> oddNumList = new List<int>();

        for(int i = 0; i < this.numberList.Count; i++)
        {
            if(this.numberList[i] % 2 == 1)
            {
                oddNumList.Add(this.numberList[i]);
            }
        }

        return oddNumList;
    }

よくありがちな感じですね~。
Linqを知らない人でも、この書き方ならほとんどの人が理解できますね。
はい、というわけで Where括弧内の条件がtrueになった要素のみを取得する ものになります。
そこでわからなくなるポイント1、Whereの括弧内の式、何?
結論から言ってしまえば ラムダ式 です。

        .Where( v => v % 2 == 1) 

この v は上の Linqなしver.で言うthis.numberList[i] の部分です。
つまり v => v % 2 == 1 というのは
Whereがリスト内すべての要素にアクセスし、その各要素ごとに v % 2 == 1 の条件で調べていき、すべての要素が調べ終わった際に該当したもののみ取得している
ということなのです。

Whereを使用するメリット

・コードを書く量が圧倒的に減る
Linqは基本バグらないので、自身でコードを書くよりも圧倒的にバグりにくい
・ゲームの持ち物リストなどで、「回復アイテムのみ表示」「武器のみ表示」などの ~のみ表示系のソートが一発で作れる

デメリット

・これはLinqを使う上では共通だが、Linqの書式や仕組みを知らないと意味が分からない
・基本的に対象リストすべての要素にアクセス→条件分岐を使用しているため、要素数や条件によっては処理に時間がかかる
・.Where(~~~).Where(~~~).Where(~~~)...... などで絞込をいくつもつけられるが、コードが横長になって見にくい( .の手前で改行とかがオススメ)




他にも .Select() .ToList() .Any() .All() .FirstOrDefault() などなど便利なやつがたくさんありますが、今回は疲れたので一旦ここまでにします

#2 - unityでnullチェックを簡単にするc#の書き方 | 駆け出しゲームプログラマーの備忘録

最近納期に追われて全然書く時間が取れなかったので久しぶりに


C#には「null条件演算子」「null合体演算子」というめっちゃ便利なnullチェックできるものがあります

■null条件演算子( ?. )

string name = this.player.name;

このコード、もし this.player が null だったとしたらエラーが出て止まってしまいます
そんな時に使えるのが null条件演算子 です

・null条件演算子の例

string name = this.player?.name;

this.playerの横に「?」がつきました
これは this.player が null だった場合はそれより右の変数、関数は呼び出さずに null を返却するものです

これが null条件演算子 と言われているもので、処理的には

if( this.player == null )
{
    name = null;
}
else
{
    name = this.player.name;
}

と同じことをしています
ただ、 null条件演算子を使用できない場面もあり例えば

int hp = this.player?.hp;

これなんかだと int型は null を許容していないのでエラーになります
これだけ見ると null許容している型しか使えなさそうですが、この次に出てくる「null合体演算子」と組み合わせて使うことでめちゃくちゃ便利になります


■null合体演算子( ?? )

int hp = this.player?.hp ?? 0;

this.player?.hp の右にある 「??」が null合体演算子 です

上に処理は

int hp;
if( this,player == null ) 
{
    hp = 0;
}
else
{
    hp = this,player.hp;
}

これと全く同じことをしています

?? を区切りに左辺、右辺と分けたとすると、左辺は「this.player?.hp」右辺は「0」となりますが、
null合体演算子はこの左辺が null ではなかった場合は左辺を、nullだった場合は右辺の値を返します

null条件演算子の挙動として、もし ?. を付けた変数が null だった場合は null を返すので、これと合わせると
「もし this.player 変数が null ではなかった時には this,player から hp を取得し、null だった場合は右辺の 0 を変わりに代入する」
という真面目に書くと数行にもなるコードが1行で分かりやすく書くことができます

一応入れ子にも対応しており

var hp = this.player?.hp ?? ( DataManager.Instance?.GetDefaultHp() ?? 0 ); 

こんな感じで書くこともできますが個人的には少しわかりにくいので
?? は1行に1つまでとしておいて、それ以上になるなら if で分割したほうが見やすいと思います

#1 - unityでフェード処理を効率よく見やすくする | 駆け出しゲームプログラマーの備忘録

前提としてUnityで使われている透明度( Color.a )は 0~1 の数値で決められており、0が完全に透明の状態。
下記コードのFade()メソッドを自身のUpdateや親オブジェクトに更新してもらうことでフェードします。
enumとかで状態分けをした方がわかりやすくて管理しやすいと思う。


■画像やテキストを徐々に透明にしていきたい場合

    /// <summary>
    /// フェード進捗度カウント
    /// </summary>
    private float count = 0.0f;

    /// <summary>
    /// 何秒かけてフェードしていくか
    /// </summary>
    private readonly float time = 2.0f;

    /// <summary>
    /// フェード
    /// </summary>
    private void Fade()
    {
        this.count += Time.deltaTime / this.time;

        float alpha = Mathf.Lerp( 1 , 0 , Mathf.Clamp(this.count , 0 , 1 ) );

        if( this.count >= 1.0f )
        {
            // フェード終わり
        }

        ○○.SetAlpha( alpha ); 
    }

逆に徐々にalpha値を上げていきたい場合は、Mathf.Lerp( 1 , 0 , ~~ ) の1と0の位置を入れ替えるだけでok


■ 一度だけ透明になってから再度出現させたい場合

    /// <summary>
    /// フェード進捗度カウント
    /// </summary>
    private float count = 0.0f;

    /// <summary>
    /// 何秒かけてフェードしていくか
    /// </summary>
    private readonly float time = 2.0f;

    /// <summary>
    /// フェード
    /// </summary>
    private void Fade()
    {
        this.count += Time.deltaTime / this.time;

        float alpha = Mathf.Cos( Mathf.Clamp( this.count , 0 , 1 ) * 360 * Mathf.Deg2Rad ) / 2 + 0.5f;

        if( this.count >= 1.0f )
        {
            // フェード終わり
        }

        〇〇.SetAlpha( alpha );
    }

this.countは0~1で制限されていて、*360にすることでフェードが終わるまでにCosが一周するようになります。
その値を /2 することで cosの値を 0.5~-0.5に制限、そこに +0.5fすることでcosの値は 1~0 になるのでうまくフェードできます。


もっと効率よくできるものがあったら教えてください。