アプリ置き場

アプリ置き場

http://www.moreread.net/

Explorerのビュー設定保存上限を変更する

Windowsエクスプローラーの挙動がうんこなので、ExplorerBrowserControlを使った自前ファイラー(C#, WinForms)を作ってエクスプローラーの挙動を矯正しつつ使っているのだけど、表示設定がおかしくなった。新規ディレクトリを作った際にデフォルトの設定(エクスプローラーで「フォルダーに適用」で保存した表示設定)を反映してくれない。

いろいろ調べるとWin11において、エクスプローラーの表示設定の保存上限は5000件らしいのだけど、この上限を超えたときにExplorerBrowserControlの挙動がおかしくなっているように見える。
実際には実行ホストの違いなどで何かしら明確な意図を持った仕様が存在しているのかもしれないが、さっぱりわからないのでとりあえず上限を増やしてお茶を濁した。5000→12800だから数年持つだろたぶん……
他には保存されたBags全部消してリセットするのも手かもしれない

Set_BagMRUSize_12800.reg

Windows Registry Editor Version 5.00

; Explorer のビュー設定保存数(ShellBags / BagMRU)の上限を 12800 に
; 対象: HKCU(現在のユーザー)
[HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell]
"BagMRU Size"=dword:00003200

TensorFlow LiteをWindowsでビルドしてDLLを作る

 

WindowsでTensorflow LiteのDLLが必要になったとき自前ビルドする方法メモ。

 

  1. (手順書を読む)
  1. Git インストール
入れてなければ最新版をインストール
  1. Python インストール
入れてなければ最新版をインストール
Microsoft Storeでインストールボタン押すだけ)
  1. CMake インストール
下記からWindows x64 Installerをダウンロードしてインストール
  1. Tensorflow ソースコード取得
  1. build
ソースコードディレクトリの隣(同じ階層)にbuild用ディレクトリを作る
mkdir tflite_build
cd tflite_build
build用ディレクトリから下記を実行し、ビルド手順を生成する
公式の手順書通りだとシンボルなくて困ったのでオプションつける
cmake -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE -DCMAKE_BUILD_TYPE=Debug ..\tensorflow_src\tensorflow\lite\c
ビルド(10分~程度)
cmake --build . -j
  1. complete
tflite_build\Debug\tensorflowlite_c.dll に出力される
必要に応じてリネームして使う

FlutterでSteam対応ゲーム(windows)を作った


ゲームエンジンぽいもの

Flutterでピクセルパーフェクトぽいものを実現する簡易的なゲームエンジンを作りました。
数年前からやりたいなと思いつつ放置してたやつ。飽きなければ整理して公開もしたい。

  • Flutterのその他Widgetと共存可
    Widgetツリーの中にゲーム画面を埋め込み可能
  • PSやSwitch系コントローラー対応
    自前でWin32APIを叩いた
  • 指定したフレームレートを維持する制御
    Flutterのデフォ機能では60hzディスプレイなのになぜか秒間90回ほど描画されるクソ動作だったので、高精度タイマーなど使いつつ自前制御。でもいくら時間制御を正確にして描画イベント発火させても、描画処理がFlutter持ちなので滑らかにならなくてびみょい。
  • ピクセルパーフェクトに描画を変換
    昨今のゲームエンジンを使うと移動やら回転やらでドット単位に沿わない偽レトロゲームになりがち
  • クライアント領域サイズ、解像度、フルスクリーンなど制御(外部ライブラリ依存)
  • TiledMap、Spriteクラス、画像のパレット変換ぽいAPIなど
  • AudioplayersをラップしたAudio系クラス
    AudioplayersをWindowsでバイナリからロードするとメモリリークしまくってたのでよい感じの運用でラップ
  • リソースの簡易暗号化と復号
    assetsフォルダがそのまま出ちゃうのでパスワード付きzipでまとめようとしたが、Winでも使えるFlutterのarchiveというライブラリはパスワードがついていると解凍できなかった。IFはあるのに。 なので自前で簡易暗号化と復号。

作ったゲーム

とりあえずエンジンぽいもの作ったので、それを使ったゲームもということで、20年近く前に作ったゲームをベースにミニゲームをつくってSteamに置いておきました。

CrapShoot クソシュー
store.steampowered.com
最初は無料の予定でしたが、登録料に15000円もとられたので一部でも回収したく200円に。よかったら購入してやってください。登録は終わったので近日中に公開される予定です。 ちなみに作業時間は下記くらい。おのれSteam。

  • エンジン部分作成1週間
  • ゲーム部分作成1週間
  • Steam対応2週間

Steam対応の方法

ちなみにFlutterでのSteam対応についてはごく最近に先駆者様がおられました。ありがたや。
steamworksライブラリを使った実績解除の方法など紹介されてます。
midland.hatenadiary.jp

参考にさせていただきつつ、スコアランキングのスコア送信のコードを書いたので置いておきます。
steamworksライブラリはC言語用のライブラリの極薄ラッパーなのでなんとも使い辛い。

import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';
import 'package:steamworks/steamworks.dart';

~~~~~

Pointer<Utf8> _convertStringToUtf8Pointer(String st) {
  final uint8List = Uint8List.fromList(utf8.encode(st));
  final utf8Pointer = calloc.allocate<Utf8>(uint8List.length + 1);
  final nativeString = utf8Pointer.cast<Uint8>().asTypedList(uint8List.length + 1);
  nativeString.setAll(0, uint8List);
  nativeString[uint8List.length] = 0; // null終端
  return utf8Pointer;
}

void rankingSendScore(String boardName, int score) {
  final boardNamePointer = _convertStringToUtf8Pointer(boardName);
  final callId = _steamClient.steamUserStats.findLeaderboard(boardNamePointer);
  _steamClient.registerCallResult<LeaderboardFindResult>(
    asyncCallId: callId,
    cb: (result, hasFailed) {
      calloc.free(boardNamePointer);
      if (hasFailed) {
        print('findLeaderboard failed.');
      } else {
        if (result.leaderboardFound == 0) {
          print('$boardName not found.');
          return;
        }
        print('$boardName found. boardId = ${result.steamLeaderboard}');
        final callIdU = _steamClient.steamUserStats.uploadLeaderboardScore(
          result.steamLeaderboard,
          ELeaderboardUploadScoreMethod.keepBest,
          score,
          Pointer.fromAddress(0),
          0,
        );
        _steamClient.registerCallResult<LeaderboardScoreUploaded>(
          asyncCallId: callIdU,
          cb: (result, hasFailed) {
            print('newRank = ${result.globalRankNew}');
          },
        );
      }
    },
  );
}

// 毎フレーム呼ばないとSteamAPIのイベントキューに積まれた各種処理が進まないぽい
void runFrame() {
  _steamClient.runFrame();
}

C# お手軽にそこそこ高精度なSleep

.NET Framework4.8、2023年10月時点で最新状態のWindows11で確認

通常

Task.Delay(1) 15msくらいの精度
Thread.Sleep(1) 15ms くらいの精度

timeBeginPeriod(1)を実行してから

[DllImport("Winmm.dll")]
public static extern uint timeBeginPeriod(uint uuPeriod);

Task.Delay(1) 15msくらいの精度
Thread.Sleep(1) 1~2msくらいの精度

合わせ技

Thread.Sleepでメインループを回すとスレッドをブロックしてイベント受信が滞るので合わせ技

static hoge{
    timeBeginPeriod(1);
}

async Task Delay(int delay) 
{
    await Task.Run(() => System.Threading.Thread.Sleep(delay)); 
}

タスクスイッチを促しつつ最小2ms程度で返ってくる

補足

ちなみに以前のtimeBeginPeriodは他のアプリへの影響があったが、
いつからかプロセス内に閉じることになったらしいので気にせず使用できる

バージョン 2004 Windows 10以降、この関数はグローバル タイマー解決に影響しなくなりました。

Windowsでライン入力による音声を再生する

ゼルダティアキンのプレイ中にPCで攻略情報を動画で見たい時、Switchの音とPCの音がそれぞれ別の再生デバイス(ワイヤレスイヤホンとか)から出るのめんどくさい。ミキシングしたい。

 

一応PCのライン入力端子(青いやつ)に、Switchのイヤホンジャックから接続することでSwitchの音をPCで聞くことができる。(要両端がオスのケーブル)

 

贅沢を言えば、Switchのイヤホンジャックじゃなくて、1000円くらいのでいいのでUSBオーディオアダプタ買ってきてSwitchに繋げて、そこからとったほうがノイズ少なくてよし。

そのあと、

コントロールパネル>サウンド>録音>(対象の入力デバイス)>プロパティ>聴く>このデバイスを聴く

をONにすることで、入力された音がPCで聞こえるようになる。

 

がしかし、プレイ中はいいけど、プレイが終わったあとそのままにしていると、ライン入力からはいってくるノイズがうるさい。入力音のボリュームは設定しても事あるごとにリセットされるので、必然的に使い始めと使い終わりに「このデバイスを聴く」のON/OFFを切り替えることになるのだけど、奥まった場所にあって不便。ということで、このデバイスを聴くをOFFにしたままでもよいように、入力された音声を再生するだけのソフトを作りました。置いておきます。

 

ダウンロードはこちらから

https://www.moreread.net/

 

PCで処理している以上どうしても遅延は発生しますが、安定して再生できるギリギリを攻められるように一応レイテンシの設定はつけておきました。

 

LightGBMでモデルの作成と保存と読込方法 (python)

モデルの作成

model = lgb.train(
        params,
        trains,
        valid_sets=valids,
        callbacks=[
            lgb.early_stopping(stopping_rounds=100, verbose=True),
            lgb.log_evaluation(10),
        ],
    )

モデルの保存

model.save_model("model.txt", num_iteration=model.best_iteration)

モデルの読込

model = lgb.Booster(model_file="model.txt")

Flutter いろいろ備忘メモ

いろいろメモ。

Flutter Webも含めたPlatform判別

Webブラウザでモバイル用のコンテンツをテストしようとすると、プラットフォーム判別で落ちるのでこれを使う。

pub.dev

bool isIos = UniversalPlatform.isIOS;
bool isWeb = UniversalPlatform.isWeb;



デバッグモードかリリースモードを判断する

以下を使えばよいらしい

kReleaseMode
kProfileMode
kDebugMode

api.flutter.dev



ウィジェットを囲む

f:id:nazenaninadesico:20210914024613p:plain

(VS Code)
ウィジェットにカーソルが合った状態で Ctrl + . を押すと新しいウィジェットで対象ウィジェットをラップしてくれる。 閉じ括弧がどこまでなのかイライラすることがなくなる。



不定サイズのウィジェットを上下(または左右)半分に分割する

f:id:nazenaninadesico:20210914024227p:plain

Expanded2個並列に並べたらいけるぽい

  Widget build(BuildContext context) {
    return Column(
      children: [
        Expanded(
          child: ColoredBox(
            color: Colors.red,
            child: Text(" 上 "),
          ),
        ),
        Expanded(
          child: ColoredBox(
            color: Colors.green,
            child: Text(" 下 "),
          ),
        ),
      ],
    );
  }