20201015のC#に関する記事は3件です。

Unityゲーム制作奮闘記 - 設計 - セーブシステム

はじめに

趣味でやっているUnityゲーム制作の理解と備忘の為に記録として残します。(まぁ成果物代わりみたいな。。)
Unityは特に設計関連の記事があまりないので、この機会に書いてみようと思います。

悪戦苦闘しながら作ってるので、内容は色々穴があるかもしれませんが。。
今時点ではこれがベストだと思っていますが、同時にもっと良い方法があるのでは?とも思っています。
素人がなんか頑張ってるな、くらいの温かい面持ちで見て下さい笑
ちなみに「こういう方法もあるよ」などあれば、ぜひ教えて下さい!

1. セーブシステム概要

今回イメージしているのは、冒険の書型セーブシステム(仮称)です。
プレイの断面を自由に複数保存でき、好きなデータをロードして再スタート出来るイメージ。
ロード画面では、セーブ時のプレイ画面をサムネイル表示させて、どんなデータだったか解るようにします。
なお今回作成しているのはオフラインゲームなので、データはローカルに保持させます。

(ざっくりイメージはこんな感じ。まあ割とありそうなやつです)
image.png

2. 検討ポイント

今回のセーブシステムを実装するにあたって、特に検討が必要なのが以下だと思っています。

  • データの検討
  • セーブシステムのクラス設計
  • セーブの検討
  • ロードの検討

それぞれを以下で掘り下げて考えたいと思います。

3. データの検討

まずデータですが、今回のシステムでは大きく2種類必要になると思います。

  1. システムデータ(ゲーム全体の設定/パラメタを保持するクラス)
  2. プレイデータ(それぞれのプレイデータを保持するクラス)

前述のとおり、プレイデータはあくまでのゲーム断面のパラメータなので、
システム全体のパラメータ(例えば音量設定とか)は別に保存しておくべきと考えます。

またプレイデータは、ロード画面でロードした際に読み込まれるイメージなので、
ロード画面に表示する各プレイデータの付随情報(セーブ日時やサムネイル画像)は
別に保存しておく必要があると考えており、これもシステムデータに保持させます。

システムデータは1ファイルのみ。
プレイデータはセーブ数に応じてファイルがどんどん増える。
ただ今回は最大30くらいにします。

image.png

4. セーブシステムのクラス設計

クラス設計、、圧倒的経験不足により正直よく解りません!笑
色々調べてはみましたが、謎は深まるばかりです←
とりあえずメンテナンス性が良い設計を心がけるのが大事というのは解りました。※出来るとは言ってない
なのでまずはダメ元で思うように作ってみて、今後に活かしたいと思います。

なおシングルトンはあまり使わない方が良いって記事も目にしますが、
今回はゲーム内データを一括管理するので、
シングルトンで構わんのだろう?という心持ちで採用しております。

SaveSystem.png

ちなみにこの設計は白黒_unityさんという方の動画を参考にしています。
めっちゃ解りやすいのでそちらも是非見てみてください!←

SaveSystem/PlayData/Systemdataはモノビヘイビアを継承しません。
これはシーン遷移してもインスタンスを保持するためです。
SaveSystemは自分と各データのインスタンスを保持します。

5. セーブの検討

セーブの仕組み自体は色々な方が情報を公開してくれており、
インスタンスをJSONに変換して保存するだけで良さそうです。
圧縮や暗号化もかけられるので、容量節約のために圧縮は組み込みたいと思います。
今回は個人ゲームなので暗号化はどっちでも良いかなという感じです。
(チートしたければすればええやん!たぶん楽しくないけどな!という感じ)

注意点を書いておくと、PlayDataとSystemDataのクラスには[System.Serializable]を記載しておく必要があります。
あと、この方法は辞書型のデータはそのまま保存できないっぽいです。(ごにょごにょすれば保存できます)
また多次元配列もそのままでは保存できないらしい。(こっちは試してないので不明ですが、最初から一次元にしといた方が楽そう。。)

セーブ時の一番の考慮事項は、サムネイルに表示する画像かなと思っています。

この仕組みはセーブ画面への移動ボタンがクリックされた際に、
先にスクリーンショットを取得してから、画面遷移という感じで考えています。
⇒メソッド名は忘れましたが、スクリーンショットはUnityの機能で取れます。

スクリーンショットを取った後ですが、まずは解像度を落とします。
そのままでも勿論使えますが、セーブ数分保存すると結構な容量になりそうなので、
ごにょごにょして解像度を下げて保存します。
(解像度を落とすと色が飛んで色々大変だったんですが、その辺りは機会があれば。。)

次に画像をテキスト化します。
ここは画像をそのまま保存でも良いかもしれませんが、テキスト化してそのままシステムデータに保存しておけば
処理もシンプルだし、画像を別で管理する必要がないので楽かしら?という考えです。

このテキスト化した画像はtmpとしてインスタンスのプロパティに入れておいて、
実際のセーブ実行ボタンが押された際に、システムデータの「プレイデータ一覧」的な所に書き込むイメージです。

なおセーブボタンですが、それぞれのボタンにインデックスを振っておき、
押されたボタンに対応するプレイデータとして保存します。(playData001とか)
このインデックス番号で「サムネイルのゲームオブジェクト」「プレイデータ」「システムデータ」を紐づけて管理します。

【セーブ画面遷移時】
image.png

【データセーブ時】
image.png

6. ロードの検討

ロード自体の処理も簡単で、セーブでやった処理を戻すだけです。
自分の場合は解凍して、テキストをインスタンス化すればOKです。

ロードでの検討項目は、ロード画面にプレイデータの情報をサムネイル表示させる部分。。
まぁゲーム起動時に読み込ませるしかない訳ですが。。(どのくらい時間かかるかな。。)

肝はテキストとして保存されている画像データをスプライトに変換する部分でしょうか。
処理としては変換してサムネイル用のオブジェクトに登録するだけですが、、
今回はプレイデータの最大数が30なので、多分そんなに掛からない筈。。
(遅くなるのを恐れて最大数を減らしたと言っても過言ではない。。)

あ!あと明記してませんでしたが、セーブ/ロード画面は同じ画面を使います。
Flagを立てて、ロードorセーブで処理などを変えるイメージ。
(なのでロード画面にサムネイルを描画すると、必然的にセーブ画面も描画されます)

【起動時内部処理】
image.png

【データロード時】
image.png

おわりに

セーブシステムの検討は以上です。
スマートなやり方なのかは一旦置いておくとして、
これでイメージするセーブシステムは作成出来ます。

でも、もっといいやり方が有る気するなぁ。。
結構メジャーな方式な気はするけれど、他の方々は一体どんな風に設計/実装しているのだろう。。

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#_単項・二項・三項演算子

どのプログラミング言語でもあるように、C#でも値同士の計算や評価の為の "演算子" が存在します。
演算子を有効に活用することでソースコードが簡潔に分かりやすくなるので、積極的に使っていきましょう

本文は以下の記事の内容を前提に記述しています。

演算子

演算子にはいくつかの種類があります。

  • 変数1つだけで作用する「単項演算子

  • 変数2つの間で作用する「二項演算子

  • 変数3つで作用する「三項演算子

普段使用するのはほとんどが二項演算子なので、まずそれから書いていきます。

二項演算子

たくさんありますが、基本的に値型に対して適用できます。

  • 値型に対する二項演算子

    演算子 内容 使用例
    x + y xとyの和 8 + 5 = 13
    x - y xとyの差 8 - 5 = 3
    x * y xとyの積 8 * 5 = 40
    x / y xとyの商 8 / 5 = 1.6 (整数型なら1)
    x % y xとyの剰余 8 % 5 = 3
    x & y xとyの論理積 12 & 5 = (1100) & (0101) = (0100) = 4
    x | y xとyの論理和 12 | 5 = (1100) | (0101) = (1101) = 13
    x ^ y xとyの排他的論理和 12 ^ 5 = (1100) ^ (0101) = (1001) = 9
    x << y xをy分左シフト 12 << 2 = (1100) << 2 = (110000) = 48
    x >> y xをy分右シフト 12 >> 2 = (1100) >> 2 = (11) = 3
    x = y xへyを代入 x = 5 (xに5が入る)
    x == y xとyが等しいかどうか x = 5 ⇒ x == 5 → true / x == 8 → false
    x != y xとyが異なるかどうか x = 5 ⇒ x != 5 → false / x != 8 → true
    x < y xがyより小さいかどうか x = 5 ⇒ x < 5 → false / x < 8 → true
    x <= y xがy以下かどうか x = 5 ⇒ x <= 5 → true / x <= 8 → true
    x > y xがyより大きいかどうか x = 5 ⇒ x > 5 → false / x > 8 → false
    x >= y xがy以上かどうか x = 5 ⇒ x >= 5 → true / x >= 8 → false
    x && y xかつy(AND演算) x, yが共にtrue ⇒ x && y → true / そうでなければfalse
    x || y xまたはy(OR演算) x, yの少なくとも一方がtrue ⇒ x || y → true / そうでなければfalse
  • 文字列型に対する二項演算子

    演算子 内容 使用例
    x + y xとyの文字列結合 "abc" + "def" = "abcdef"

その他、参照型に対しては代入演算子以外は基本的に定義されていません。
ただし、自分で定義することもできます。

次に二項演算子の中でも少し特殊な "複合代入演算子" を列挙していきます
上記二項演算子と代入演算子を組み合わせた内容になっています。

  • 複合代入演算子

    演算子 内容
    x += y x = x + y
    x -= y x = x - y
    x *= y x = x * y
    x /= y x = x / y
    x %= y x = x % y
    x &= y x = x & y
    x |= y x = x | y
    x ^= y x = x ^ y
    x <<= y x = x << y
    x >>= y x = x >> y

単項演算子

次に変数1つに対して作用する演算子を紹介していきます。

  • 単項演算子

    演算子 内容 使用例
    +x xそのまま x = 5 ⇒ +x = 5
    -x xの符号反転 x = 5 ⇒ -x = -5
    x++ x=x+1(後置インクリメント) x = 5 ⇒ x++ = 6
    ++x x=x+1(前置インクリメント) x = 5 ⇒ ++x = 6
    x-- x=x-1(後置デクリメント) x = 5 ⇒ x-- = 4
    --x x=x-1(前置デクリメント) x = 5 ⇒ x-- = 4
    !x xの否定 x = true ⇒ !x = false
    ~x xの補数(ビット反転) int x = 5 ⇒ ~x = -6

さて、インクリメント・デクリメントには前置/後置の2種類がありますが、どちらもそれ単体としては結果が変わらないように見えます。
ではどういう使い分けがあるかというと「結果を返すタイミング」が違います。

  • a++は、aを返してからaに+1します

  • ++aは、aに+1してからaを返します

例).

Program.cs
int a = 5;
Console.WriteLine(a++); // aを表示してからa = a + 1
Console.WriteLine(++a); // a = a + 1してからaを表示
出力
> 5
> 7

デクリメントについても全く同じ動作になります。

三項演算子

次に、C#における唯一の三項演算子を紹介します。

  • 三項演算子

    演算子 内容 使用例
    x ? y : z x = true ⇒ yを返す / x = false ⇒ zを返す x = 5 ⇒ x == 5 ? "a" : "b" → "a" / x == 8 ? "a" : "b" → "b"

三項演算子は適度に使用することで有効に活用することができますが、
使い方や頻度によっては著しくソースコードの可読性を落とすこともある諸刃の剣です。

使う場面は慎重に選んで使っていきましょう

null合体演算子

最後に、nullという特殊な概念を扱う為の演算子を紹介します。

"null" とは、他の言語ではnullptrやNothing, nilなどとも表現される、「何ものでもない」「何も指していない」ことを表現します。
プログラムにおいてnullに対してアクセスするとエラーになる為、慎重に扱う必要がありますが、それを簡潔にする演算子がnull合体演算子になります。

三項演算子でnullかどうかのチェックを使用したパターンを1つにまとめたものです。

  • null合体演算子

    演算子 内容
    x ?? y xがnullでなければxを、xがnullならばyを返す
    x ??= y xがnullならば、xにyを代入する(xがnullじゃなければxのまま)

なお、このnull合体演算子は古い開発環境では認識できない場合もあるので注意しましょう(.NET Coreであれば大丈夫のはず)

C#で使用される代表的な演算子は以上になります。
最後に参考URLを貼っておきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

OpenCVでプレイ動画の切り出しを自動化した話・OpenCVSharp導入ドハマり編

記事シリーズの目的

最近ゲームの解説動画を作っているのですが、その編集が面倒だったため自分の知ってる知識の寄せ集めでツールを作りました。

そのツールの応用性が高そうなので、ツールとソースを公開しておきたいというのと、広範囲の知識を寄せ集めたので応用例として残しておく価値があると思ったことから、ある程度分野ごとにまとめて記事にします。

ツールの目的

Oxygen Not Incluidedのプレイ動画を今作っているのですが、その動画はポーズ画面を全カットする編集を行う予定にしています。

試しに1.5時間のプレイ動画に対してカット編集を行ったら5時間かかったこと、今後もシリーズが続くことから手作業によるカット編集は無理があると判断し、ツールを作ることにしました。

記事シリーズの予定

以下のような3つの記事を作成予定。

  1. OpenCVSharp導入編(これ)
  2. OpenCVでの動画解析編
  3. GUI実装編

この記事の目的

OpenCVSharpの導入にドハマリしたのでそのメモ。
適当な解説記事を確認して導入しようとしたら記事の情報が古かった上にトラブった時の情報が無かった。

VisualStudioにOpenCVをインストールする時の手順

要約

Nugetのパッケージの説明文はよく読んで、記事投稿時点ではOpenCvSharp4.Windowsをインストールすればいいよ。

インストール方法

ソリューションエクスプローラの背景で右クリックし、NuGetパッケージの管理を選択する。
nuget_1.PNG
「参照」が選択されているから、検索欄にopencvsharpと入力して、説明文をよく読んで必要なものをインストールすること。
nuget_2.PNG
2020年10月時点では、WindowsならOpenCvSharp4.Windowsを選択すればオールインワンのパッケージがインストールできて手間がない。

よく読まなかった人の末路

自分はよく説明文を読まず、他の記事だけパッと見てOpenCvSharp4だけインストールしてしまった。
するとMat m = new Mat();を実行するだけで
System.TypeInitializationExceptionDllNotFoundExceptionが発生する。
また、これらの解決のために検索したが情報がかなり古く、NuGetパッケージの見直しの情報を示している人はいなかった。
よく説明文を読まずに導入するマヌケは自分だけだったのかもしれない。

例外をよく読んだらNuGetパッケージ内に含まれてないDLLを参照しようとしていたので、OpenCVSharpのインストールが完全ではなかったことに気が付き、どうにか導入できました。

こんなことにも数日ハマってしまったけど、とりあえず導入はできた。
次回、実際にOpenCVを使ってカット予定の場所を特定するアルゴリズムを組むところまで。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む