さけのさかなのブログ

同人ゲーム開発やってます。Unity使ったりする。

OpenXMLのラッピング

ゲーム開発にExcelを使うケースは結構ある。

そこでOpenXML…となるんだけど、これがそのまま使うにはだいぶしんどい。まずラップしないとやってられない。

というわけでそのためのクラスを作った。

github.com

細かい機能はない。Excelからの読み出しはシーケンシャルにできればとりあえず足りる。(まずは全セルをコピーすれば良い)

ファイルの書き出し機能は一応あるものの、今まで必要になったことはないんよな。

usage

using var fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using var document = SpreadsheetDocument.Open(fs, isEditable: false);
var doc = new Bonn.Document(document);
foreach (var sheet in doc.Sheets)
{
    foreach (var cell in sheet.Cells())
    {
        var c = cell.position.columnIndex;
        var r = cell.position.rowIndex;
        var value = cell.value;
    }
}

ClosedXML使えばいいんじゃんという話ではあり。

個人開発者はSteamの最小収益額を目いっぱい大きく設定しろ

 海外送金額はバカにならないぐらい高いので、負担を小さくするために下記のような機能がある。

 Q. Steam 売上の受取額の大部分が銀行手数料で消えてしまいます。 支払額が一定額に達するまで支払いを保留することはできますか?

 A. 収益がしきい値を超えるまで支払いの送金を行わないように、最小しきい値を選択できる機能を追加しました。 銀行手数料によって受取額が大幅に減少する場合があることを認識しています。

Steamworks にログインして「ツールとリンク」セクションから「会社情報の確認と編集」をクリックし、「支払いの保留」を見つけてください。 弊社からの支払いを保留するためのしきい値を選択するドロップダウンメニューがあります。

partner.steamgames.com

InputActionの各種コールバック購読をIDisposableで扱う拡張メソッド

InputActionのperformedstartedcanceledコールバックはマルチキャストっぽいインターフェースになっており、+演算子Action追加、-演算子で削除を行うようになっている。

inputAction.performed += _ => {};

このままでは使いづらいので作ったのがこの拡張メソッド。IDisposableが返ってくるので、あとはUniRxに任せてAddToで解放するなりなんなり。

inputAction
    .SubscribePerform(_ => { })
    .AddTo(this)

コード

github.com

【Unity】InputSystem使ってみた

導入するうえで検索しづらかった機能について。

コードで入力を受け付けたい : InputActionReference

実は自動生成クラスは不要で、インスペクタで参照設定すればできる。

[SerializeField]
InputActionReference submit = default;

void Start()
{
    submit.ToInputAction().Enable();
}

void Update()
{
    if (submit.ToInputAction().triggered)
    {
        hogehoge
    }

    // 追記:ToInputAction()はactionプロパティでもいい
    if (submit.action.triggered)
    {
        fugafuga
    }

}

ランタイムで挙動を切り替えたい : MaskByGroup

InputBiding.MaskByGroupを使うことで、ControlSchemeごとに有効/無効を設定することができる。(有効にするgroupを指定する)

渡す文字列はControl SchemebindingGroupに対応している。これはおそらくnameと等しい。inputactionsファイルを開くとjsonになっているので確認できる。

[SerializeField]
InputActionAsset inputActionAsset = default;

public void EnableKeyboard()
{
    inputActionAsset.bindingMask = InputBinding.MaskByGroup("Keyboard");
}
public void EnableGamepad()
{
    inputActionAsset.bindingMask = InputBinding.MaskByGroup("Gamepad");
}

PS4コントローラを振動させたい

UnityEngine.InputSystem.Gamepad.current.SetMotorSpeeds(0.5f, 1f);

参考

gamedev65535.com

inoookov.hatenablog.com

Unityでnull許容参照型を使ってみた

Unityでもnull許容参照型。どう書くか好みの別れそうな部分があったので、個人的な方針。

Unity 2021.1.7f1。

1.どう有効にするか

コンパイラオプションでプロジェクト全体を有効にできるけど、それではアセットのコードなんかも影響して警告を出し始めるのでちょっとやってらんない。

対応

ファイルごとに#nullable enableを書く方式でやります。(まあusingだって毎回やってることですし。)

2.Unityのnullチェックでは警告が出る問題

UnityEngine.Objectのnullチェックはコンパイラが追えず、チェック後も警告が出てしまう。

対応

!演算子を使う。

if (hoge) // Unityはnullチェックをこう書けるが
{
    hoge.Piyo(); // 警告が出る。
}

if (fuga)
{
    fuga!.Piyo(); // !演算子を使えば警告は出ない。
}

3.Linqはちょっと不便なところがある

たとえばWhereでnullチェックを行っても、null許容型が非nullになったりはしない。

ついでにToArray()の戻り値はnull許容型。

対応

!演算子を使う。

List<string?> list;
var array1 = list.Where(_ => _ != null).ToArray(); //string?[]?型
var array2 = list.Where(_ => _ != null).Select(_ => _!).ToArray()!; //string[]型

4.GetComponentキャッシュ書き方

Text? text;
Text Text => text ? text! : text = GetComponent<Text>();

5.SerializeField フィールドは非nullにするのが丸い

SerializeFieldの配列やクラスはUnityの仕様上nullにならない。なので非null指定にしてしまうのがよさげ。

対応

default!で初期化する。

[SerializeField]
int[] array = default!;

[SerializeField]
int[] array = new int[0]; // これでも良い。

[SerializeField]
int[] array = default; // これは警告がでる

[SerializeField]
int[]? array = default; // 警告はでないけど、せっかくだから非null指定したい。

6.SerializeField オブジェクト参照で非null指定したいケース

SerializeFieldで参照しているコンポーネントなどがnullかどうかはユーザーの設定次第。よってnull許容にするのが自然。

なんだけど、値が入ってないとエラーにするしかないコアな部分なら、非null指定にしても支障はない。

対応

必須のフィールドなら非null指定にする。

// 動作に必要なフィールドは非nullにしてしまう
[SerializeField]
GameObject prefab = default!;

// nullでも動くならnull許容型にする。
[SerializeField]
AudioClip? sound = default;

7.ジェネリクスはどうしようもない場合がある

参照型か値型かの制限が要る。これはcsharpのバージョンアップを待つことになりそう。

対応

諦めて#nullable disableする。

参考

ufcpp.net

椅子とモニタを買う

椅子がボロボロになってきたので新しく椅子を買った。

せっかくなのでちょっと奮発して良いやつを。腰はまだ問題ないんだけど、壊れた時には手遅れだし。

で、新しい椅子にすると姿勢も変わる。背筋が伸びることで視点が高くなり…モニタの位置が合わなくなった。低い。見下ろす角度は首がつらい。

まあモニタを適当な足場に乗せれば済む話なんだけど(vesa規格には対応してなかった)、せっかくなのでモニタも新しい奴にしてやれ、ということ購入。出費とは芋づる式に増えるもので。

せっかくなので4kモニタにしたわけだけど、

  • モニタが大きいと画面端を見る時の角度がけっこうきつくなる。曲面モニタは色物かと思ってたけど確かに欲しくなる。
  • windowsのスナップ機能で四分割して使うのが正解。
  • とはいえちょっと字が小さすぎるので結局125%に拡大して表示してる。100%表示で使いたいけどそれにはメガネを替えたい。沼。
  • 綺麗に見えるというよりはアラが見えるようになる。
  • COMIC FUZでまちカドを見開きで読めるようになる。
    • 何せ書き込みが細かい。これまでは縦置きのFHDモニタで1Pずつ表示してたけどこれで見開き表示ができる。

SRPGのボスAI

1. はしがき

 SRPGのボス戦は得てして単調なものになりかねない。それをAIのカスタマイズで解決する。

2. ザコ戦とボス戦

 ザコとボスの違いは何か。数である。とりあえず、ここではそういうものとする。

  • ザコは徒党を組み、プレイヤーになで斬りにされる。
  • ボスは単騎で戦う。プレイヤーは一体のボス相手に対してじっくりと向かい合うことになる。

 バトルシステムを設計する際には、この2種類の敵を置けるように機能を作ることになる。

3. ボスの要件

 プレイヤーに飽きられないよう、バトルには起伏が求められる。ザコ戦はまあいいだろう。ウェーブや倒す順番などによって状況が変化するからだ。問題はボスだ。素朴に作れば、HPバーの残量を少しずつ減らすだけの退屈なボス戦になりかねない。

 ではどうするかというと、ボスユニットが多彩な行動をとれば良い。「毎ターン違うスキルを使ってくる」とか、「戦闘が始まってもしばらく待機して、動き始めたら自分にバフをうち、ピンチになったら奥の手で粘る」といった具合だ。

 だが、これを純粋なAIのロジックで実現するのは難しい。AIはふつう最善手を打つように組むからだ。とりあえず作ってみると、単調に最強技を連発してプレイヤーを全滅させる(もしくは、息切れを起こしてジリ貧になる)味気ないボスができあがるだろう。毎ターンはどうほう連発はファミコン時代の話である。

4. AIをカスタマイズする

 簡単な方法を考える。AIの思考を制限してやればよい。同じ行動を連続してほしくないなら、しないように組めばいいのだ。結果として「強スキルがあるのになぜか使ってこない舐めプAI」となってしまうが、ここは妥協する。

 ポイントとしては、スキルデータなどの共通部分ではなくてユニットごとのプロパティで設定できるようにすること。これは変更時の副作用を小さくする意図がある。悪い実装だと、「ある敵を調整したら別の敵の動きが変になった」といった現象が起こり得る。

 MSTでは以下のような制限を組めるようにした。

  • 起動制限:ターンが回ってきても何回かは行動せずその場で待機するよう設定できる。この設定はダメージを受けると解除して即行動開始する。
  • HP制限:HPが一定以上だとそのスキルを使わないように設定できる。ピンチになった時に使う「奥の手」だ。ついでに条件を満たしたらセリフでも言わせると良い感じになる。
  • 連発制限:そのスキルを使った後、数ターンは使わないように設定できる。強力すぎて連発させられないスキルに適用する。また、とにかくいろんなスキルを(なんならランダムっぽく)使わせたいとき。

 AIに制限を設けると、ひるがえって数値としては強くすることができる。「強力な攻撃がとんできてパーティが半壊したが、次のターンはデレてなんとか立て直した」みたいな九死に一生の展開は、開発者が意図して組めるものである。

5. その他のアプローチ

 正攻法でやるなら、最善手をうっていれば千変万化する派手な動きになるようにゲームのルールが敷かれていれば良い。が、これはとてつもなく難しい。これができたらひょっとすると対人戦に堪えるシステムになっているのではないだろうか。とはいえある程度は意識する価値はある。うまくいけばプレイヤーが動かしたときも楽しい動きになっているだろう。(多分だけどバフ関係がかなり複雑なシステムになるのではと思う。また、単純なMP制ではなく別のリソース方式を考えたほうがよさそうだ。)

 もちろん、そもそもボスの動きなんて単調でも良くない?という考え方はある。たとえば育成パートで思考させるゲームなら、バトルパートはわりと単純でも良かったりするし。