迷走テクノロギア

妄言に限りなく近い何か

【関係者様向け】今後のスケジュールについて

パスワードがかかった記事です。

新年のご挨拶とか

皆様、新年如何お過ごしでしょうか。 SANAは食って寝てゲームして酒飲んで寝ています。

結論から言うと

2021年はゲームをリリースすることが叶いませんでした

2022年夏頃にリリース&イベント出展を予定しています

簡単に言い訳させて頂くと、

感染症により出展予定が立てられなかったり、 結婚長距離引っ越しと目まぐるしい環境の変化

どちらも落ち着いてはきましたが、特に後者に関しては昨年の夏頃から常に

  • 指輪どうする
  • プロポーズどうする
  • フォトウェディングどうする
  • 相手の実家への挨拶どうする
  • 新居どうする
  • 両家の顔合わせどうする
  • 引っ越しどうする

などに追われて、

正直ゲーム一本リリースできるくらいスタミナ使った気がします。

これに加えて結婚式まで上げる人は化け物だと思いました(失礼)

リリース時期延期の話の最中に惚気とは何事かと思われた方申し訳ナス

f:id:sanaftg:20220102101637p:plain

【Unity】アセット内全てのLICENSE.txtをまとめる【エディタ拡張】

メリークリスマス。

ライセンス表示を実装し忘れて大慌てした記憶があるので作ってみました~

using System.IO;
using System.Text;
using System.Linq;
using UnityEngine;
using UnityEditor;

/// Put it in the Editor folder and click Edit->CreateLicenseFile
/// @author sana@f-tg.net
/// https://f-tg.net

public class LicenseFileGenerator : MonoBehaviour
{
    [MenuItem("Edit/CreateLicenseFile", false, 10)]
    static void CreateLicense(MenuCommand command)
    {
        string path = Application.dataPath + "/";
        string[] files = Directory.GetFiles(path, "LICENSE.txt", SearchOption.AllDirectories);
        string[] licenseArr = new string[files.Length];
        for (int i = 0; i < files.Length; i++)
        {
            char delim = '/';
#if UNITY_EDITOR_WIN
            delim = '\\';
#endif
            string header = $"--------------------------------\n{files[i].Split(delim).Reverse().ToArray()[1]}\n--------------------------------\n";

            StreamReader sr = new StreamReader(files[i], Encoding.UTF8);
            licenseArr[i] = header + sr.ReadToEnd();
            sr.Close();
        }
        string savePath = Application.dataPath + "/Resources/AllLicense.txt";
        File.WriteAllText(savePath, string.Join("\n\n", licenseArr));
        AssetDatabase.Refresh();
    }
}

f:id:sanaftg:20211226041649p:plain

EditタブのCreateLicenseFileを実行するとAssets内全てのLICENSE.txtをまとめたAssets/Resources/AllLicense.txtが生成されます。

f:id:sanaftg:20211226041816p:plain

...好きだからUtf8Json使ってるけどJsonUtilityでええやんと言われがち

ソースコードの使用は自己責任でご自由にどぞ

【祝】サークルロゴができました!

ロゴできたぞ~~👏👏👏

突然ですが、遂に幻想テクノロギアにロゴができました!!

パターン色々用意してもらったのですが基本の形は以下のようなものです~

はい可愛い!

ホームページだったりもろもろ順次差し替えていきますのでこのロゴで覚えてくださいね!

▼ロゴはM△RR*さんにデザインして頂きました! twitter.com

その他近況報告

エアコンから水がぽたぽた垂れてきていたのでドレンホース用のポンプを買いました。

【ミドルウェア?】スマートフォンをゲームコントローラー化する

お久しぶりですSANAです。

突然ですが、

僕ってWiiとかSwitchのコントローラー振ったりするゲーム好きなんですよね!

まぁ、WiiもSwitchも持ってないですけど👐

ハードを買うのも起動するのも開発するのもハードルが高くてVeryHard(ハードだけに)

ハードや専用コントローラー買いたくないけど
コントローラー振ったりして遊びたいんだあああ

...

あ、スマホ振ればええやん

とまぁそんな訳で作りました

適当な設計

f:id:sanaftg:20210626162300p:plain
設計書()

開発者向けにするかプレイヤー向けにするか

ちょっと悩んだんですけど、普通に世の中に出てるPCゲームでも使えたほうが絶対良い。

→中継アプリが必要

懐かしのWindowsForm

PC側の中継アプリはタスクトレイに入れて常駐させたかったので一番手軽なWindowsFormを選択

昔はよく使ってましたねw

[:]
μ'sの曲が数秒流れて、どのメンバーが歌っているか当てる謎のゲーム
f:id:sanaftg:20210626164546p:plain
実行ファイルの名前がHageなの何

スマホアプリ側

脳死Unityを選択

JoyStickは有名なJoyStickPackを使用致しました。 assetstore.unity.com

コントローラー画面
中継アプリとの接続画面

スマホ → 中継アプリ

UDP通信を使用しました。

参考サイト

[Unity]非同期処理(Task)の利用例(UDP送受信,SerialPortのRead) - Qiita

送る値は大した量ないので

通信種別 / イベントの種類(ダウン、アップ)/ キーの種類

みたいな文字列送ってます

※キーコードはUnityとWindowsで値が異なる為、WindowsのKeyCode(System.Windows.Forms.Keys)に合わせてます

   private void InputDown(InputData data)
    {
        StringBuilder sb = new StringBuilder();
        switch(data.type){
            case Type.KeyCode:
                sb.Append("k/");
                sb.Append(WM_KEYDOWN).Append("/");
                int keyInt = (int)data.keyCode;
                sb.Append(keyInt);
                break;
            case Type.Mouse:
                sb.Append("m/");
                if(data.mouse == MouseType.LeftButton){
                    sb.Append(WM.WM_LBUTTONDOWN);
                }else if(data.mouse == MouseType.RightButton){
                    sb.Append(WM.WM_RBUTTONDOWN);
                }
                sb.Append("/");
                sb.Append(0);
                break;
        }
        sendEvent.Invoke(sb.ToString());
    }

中継アプリ → ゲーム

調査した結果SendKeysかSendMessage、PostMessageという選択肢がありました。

しかし、SendKeysは押した情報しか送らず、離したという情報を送る方法がなかったので却下 docs.microsoft.com

       /// <summary>
        /// 受診時イベント
        /// </summary>
        /// <param name="text"></param>
        private void ReceiveEvent(string text)
        {
            if (window == IntPtr.Zero)
            {
                return;
            }
            Invoke(new Action(() =>
            {
                AddLog(text);
            }));
            string[] args = text.Split('/');
            switch (args[0])
            {
                case "s":
                    Invoke(new Action(() => { DeviceName.Text = args[1]; }));
                    break;

                case "k":
                    KeyEvent(args[1], args[2]);
                    break;

                case "g":
                    GyroEvent(args[1]);
                    break;

                case "m":
                    MouseEvent(args[1], args[2]);
                    break;

                case "e":
                    Invoke(new Action(() => { DeviceName.Text = "None"; }));
                    break;
            }

        }

        private void KeyEvent(string typeStr, string keyStr)
        {
            int type = int.Parse(typeStr);
            int key = int.Parse(keyStr);
            uint repeatCount = 0;
            uint scanCode = 0x2D;
            uint extended = 0;
            uint context = 0;
            uint previousState = 0;
            uint transition = 0;
            uint lParam = 0;
            // combine the parameters above according to the bit
            // fields described in the MSDN page for WM_KEYDOWN
            if (type == 256)
            {
                lParam = repeatCount
                    | (scanCode << 16)
                    | (extended << 24)
                    | (context << 29)
                    | (previousState << 30)
                    | (transition << 31);
            }
            if (type == 257)
            {
                previousState = 1;
                transition = 1;

                lParam = repeatCount
                    | (scanCode << 16)
                    | (extended << 24)
                    | (context << 29)
                    | (previousState << 30)
                    | (transition << 31);
            }

            PostMessage(window, type, key, lParam);
        }

参考サイト キーストロークをウインドウに送信 | C#

このlParamってあるじゃないですか

キーボード入力の概要 - Win32 apps | Microsoft Docs

キーストロークメッセージフラグっていう奴らしいんですけど、押したばっかりかどうか?とか色々送ってるみたいなんですよね

ここが一番難所でしたがコピペで解決しました()

参考サイト c# - Setting WM_KEYDOWN lParam parameters - Stack Overflow

送信先のゲーム選択

最初はアクティブな画面に送っていたのですが、送信先が選べないのが不便なの選択式にしました。 f:id:sanaftg:20210626171306p:plain

       private void LoadWindow()
        {
            window = GetForegroundWindow();
            windowList = new List<IntPtr>();
            target.Items.Clear();
            foreach (Process p in Process.GetProcesses())
            {
                if (p.MainWindowTitle.Length == 0) continue;
                if (p.MainWindowHandle == IntPtr.Zero) continue;
                windowList.Add(p.MainWindowHandle);
                target.Items.Add($"{p.MainWindowTitle}");
                if (window == p.MainWindowHandle)
                {
                    target.SelectedIndex = target.Items.Count - 1;
                }
            }
        }

イクラでテスト

案外あっさり動きました。作業時間は6時間弱?

サブのプロジェクトとしてちょっとづつ進めようかなと思ってます~!

アプリ側でキー設定や配置の変更ができるようになったらまだまだ夢広がりそうですね。

では今回は長くなってしまいましたが最後までお付き合い頂きありがとうございました~!

【Spine】SpineでAnimator使えるんかいッ!【Unity】

幻想テクノロギアのSANAです。

今年もよろしくおねがいします。

さてさて今週はデザイナーの憂さんからぽつぽつとSpineデータが届いておりまして組み込みを行っていました。

実はこれまで勝手にSpineデータにUnityのAnimator使えないと思いこんでたんですが、

SpineアニメーションにUnityのAnimatorは使えます(今更)

ブレンドもちゃんとやってくれるしレイヤー分けもいけるっぽいです。

ドキュメントは少ないがAnimatorは使用できる

f:id:sanaftg:20210113131345g:plain ※画面は開発中のものですし、GIFにした時になんか色おかしくなりました!

SkeletonアセットのIspector一番下のSkeletonMecanimに新規作成したAnimatorControllerを付けて下のボタンを押すと、

f:id:sanaftg:20210113131550p:plain

AnimatorControllerにAnimationが取り込まれます。

f:id:sanaftg:20210113131814p:plain

※SpineデータをExportしなおしたりした時も同様にこのボタンを押す必要はあるみたいです

SkeletonAnimationの代わりにSkeletonMecanimコンポーネントを使用すれば完了です。

これでStateMachineBehaviourも使えるので無理にデザイナーにイベントキーを打ってもらう必要もなくなりました!

UnityEngine.StateMachineBehaviour - Unity スクリプトリファレンス

ブロレディの進捗

テーマ曲のマスタリングが終わりました!

※2021年の夏~秋にあたりに公開予定です

【MirrorNetwork】SteamNetworkingの利用【FacePunch】

皆様お久しぶりです。SANAです。

さてさて、ブロレディ(怪盗探偵ブロンドレディ)の開発も中盤になって参りました。

これまでPhoton頼りでマルチプレイ開発してきた私ですが、今回Steamにて配信ということでSteamNetworkingの導入を検討して四苦八苦しておりました。

最終的に辿り着いた構成を共有致します。

Mirrorネットワーク

様々な通信規格にコンポーネント変えるだけでKCPやWebsocket、SteamNetworkingに変換してくれるライブラリ

Mirror Networking – Open Source Networking for Unity

FacePunchSteamworks

SteamworksSDKのラッピング

GitHub - Facepunch/Facepunch.Steamworks: Another fucking c# Steamworks implementation

FizzyFacePuch

FacePunchSteamworksとMirrorの仲介

Releases · Chykary/FizzyFacepunch · GitHub

この構成に辿り着いた理由はといいますと

1.SteamNetworkingではローカルデバッグできない

f:id:sanaftg:20201230162440p:plain

Steamにログインしている状態でなければSteamNetworkingは利用ができません。

これまで私はシンボリックリンクで同じプロジェクトを2つUnityで立ち上げてデバッグする手法を取っていましたが、

これができません!!

f:id:sanaftg:20201230162921p:plain

2台PCがあって、Steamアカウント2つ持ってればいけるのかもしれませんが非常に手間です。

しかし、Mirrorを使用しておけば、ローカルデバッグの間はKCPにしておいてSteam上でテストしたい時にはコンポーネントを差し替えるのみでいけます!

2.推奨環境が利用できない

Steam ネットワーキング (Steamworks ドキュメント)

↑こちらを読む限りISteamNetworkingMessagesというのが推奨されているようですが、

Steamworks.NET(C#用SteamworksSDK)ではまだ対応していません。

なので仲介ライブラリ(Mirror)を使っておけば今後推奨環境も対応してくれるんじゃないか??という算段です。

内部までの調査はまだまこれからですが、これからSteamでマルチプレイのゲームを作りたい方の参考になれば嬉しいです!

今年もまもなく終わりですがブロレディ開発を通して関係者の皆様大変お世話になりました!

来年から本格的にゲームを形にしていきますので、来年も宜しくお願い致します。

f:id:sanaftg:20201230163934p:plain