Knowledge Base
雑多な知識を書き込むところです。Wikiの編集の方法がわからない人はコメントに残してください。
Someone please translate this page.
Someone please translate this page.
Craftopia関連
言語設定の切り替え
LanguageManager.ChangeLanguage
Unity関連
押しっぱなしでマイフレーム反応しちゃう問題
.isPressed()を.wasPressedThisFrame()にしたら押した瞬間だけ判定をとれる
音声読み込み
using (UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip("file://" + fileName, AudioType.WAV)) { yield return www.SendWebRequest(); if (www.result == UnityWebRequest.Result.ConnectionError) { Debug.Log(www.error); } else { Clip = DownloadHandlerAudioClip.GetContent(www); } }
AudioSourceは少なくともOcCharacterが持っている。
画像読み込み
LibCraftopiaに実装してある
パッドの入力
var gamepad = Gamepad.current; // ゲームパッドが接続されていなければこれ以降 if (gamepad == null) { return; } // ゲームパッドの〇ボタンが押された時に出力 if (gamepad.buttonEast.isPressed) { UnityEngine.Debug.Log("PS4の〇ボタンが押された"); }
マルチ接続のキャラタ名取得
//NetPlId(1~8)で該当プレイヤー名が取れる private const int NET_PL_ID_OFFSET = 1;//NetPlIdが0からではなく1から始まるのでずらすための値 UnityEngine.Debug.Log("var selfId = NetPlId_Master = " + OcNetMng.Inst.NetPlId_Master); UnityEngine.Debug.Log("getPlName(NetPlId_Master) = " + SingletonMonoBehaviour<OcNetMng>.Inst.getPlName(OcNetMng.Inst.NetPlId_Master)); UnityEngine.Debug.Log("NetworkStreamConnection_Count_Clt + NET_PL_ID_OFFSET = " + (SingletonMonoBehaviour<OcNetMng>.Inst.NetworkStreamConnection_Count_Clt + NET_PL_ID_OFFSET)); UnityEngine.Debug.Log("getPlName(NetworkStreamConnection_Count_Clt + NET_PL_ID_OFFSET) = " + SingletonMonoBehaviour<OcNetMng>.Inst.getPlName(SingletonMonoBehaviour<OcNetMng>.Inst.NetworkStreamConnection_Count_Clt + NET_PL_ID_OFFSET)); UnityEngine.Debug.Log("ConnectPlayerNum = " + SingletonMonoBehaviour<OcNetMng>.Inst.ConnectPlayerNum); for (int i = 1; i <= OcNetMng.MAX_PLAYER_NUM; i++) { UnityEngine.Debug.Log("getPlName(" + i + ") = " + SingletonMonoBehaviour<OcNetMng>.Inst.getPlName(i)); }
dnSpy関連
クラフトピア本体側でラムダ式が使用されている場合、dnSpyでは読みづらく変換されてしまうことがある。
他ソフト(ILSpyなど)を使用することで解読可能となることがある。
dnSpy
他ソフト(ILSpyなど)を使用することで解読可能となることがある。
dnSpy
public void StartNativeAlchemy(in Vector3 plPos, int skillLevel){ OcDropItemPool.<>c__DisplayClass12_0 CS$<>8__locals1 = new OcDropItemPool.<>c__DisplayClass12_0(); CS$<>8__locals1.skillLevel = skillLevel; OcFxMng.GenaralEfcType successEfcType = OcFxMng.GenaralEfcType.NativeAlchemy_Success; this.TryAlchemy(plPos, new Func<ItemData[]>(OcDropItemPool.<StartNativeAlchemy>g__GetCategoryItems|12_0), new Func<bool>(CS$<>8__locals1.<StartNativeAlchemy>g__CanAlchemy|1), new Func<int>(OcDropItemPool.<StartNativeAlchemy>g__GetConvertableItemId|12_2), successEfcType); }
ILSpy
public void StartNativeAlchemy([IsReadOnly] [In] ref Vector3 plPos, int skillLevel) { OcFxMng.GenaralEfcType successEfcType = OcFxMng.GenaralEfcType.NativeAlchemy_Success; this.TryAlchemy(ref plPos, delegate { int nativeAlchemy_CategoryId = OcPlMaster.Inst.SkillCtrl.NativeAlchemy_CategoryId; int nativeAlchemy_Rank = OcPlMaster.Inst.SkillCtrl.NativeAlchemy_Rank; return SingletonMonoBehaviour<OcItemDataMng>.Inst.GetItemsByCategoryAndRank( nativeAlchemy_CategoryId, nativeAlchemy_Rank); }, () => OcPlMaster.Inst.SkillCtrl.CanNativeAlchemy(skillLevel), () => OcPlMaster.Inst.SkillCtrl.NativeAlchemy_ConvertableId, successEfcType); }
Harmony関連
パッチの当て方いろいろ
namespace AnyHook { [BepInPlugin(PluginGuid, PluginName, PluginVersion)] public class ExamplePlugin : BaseUnityPlugin { private const string PluginGuid = "me.hoge.craftopia.mod.AnyHook"; private const string PluginName = "AnyHook"; private const string PluginVersion = "0.0.1"; void Awake() { new Harmony(PluginGuid).PatchAll(); } // 動作しない [HarmonyPatch(typeof(AsPl_AttackSword0), "enter")] [HarmonyPostfix] static void AsPl_AttackSword0_enter() { UnityEngine.Debug.Log($"Sword!!!!"); } // 動作する [HarmonyPostfix, HarmonyPatch(typeof(SomeClass), "enter")] public static void AsPl_AttackSword0_enter() { } // 動作する [HarmonyPatch(typeof(AsPl_AttackSword0), "enter")] public class AsPl_AttackSword0 { static void Postfix() { UnityEngine.Debug.Log($"Sword!!!!"); } } // 動作する [HarmonyPatch(typeof(AsPl_AttackSword0))] public class AsPl_AttackSword0 { [HarmonyPatch("enter")] static void Postfix() { UnityEngine.Debug.Log($"Sword!!!!"); } } // 動作する [HarmonyPatch(typeof(AsPl_AttackSword0))] public class AsPl_AttackSword0 { [HarmonyPatch("enter")] [HarmonyPostfix] static void test() { UnityEngine.Debug.Log($"Sword!!!!"); } } // 動作する [HarmonyPatch(typeof(AsPl_Attacksword0))] [HarmonyPatch("enter")] public class AsPl_AttackSword0 { static void Postfix() { UnityEngine.Debug.Log("Hello!"); } } } }
Configの追加
private static ConfigEntry<bool> IsEnabled; private void Awake() { IsEnabled = Config.Bind<bool>("General", "Enabled", true, "Enable this mod"); Harmony.CreateAndPatchAll(System.Reflection.Assembly.GetExecutingAssembly()); }
同じメソッド名がある場合に明示的にフックするメソッドを指定する
同じメソッド名がある場合に以下のように書くと、フックできるメソッドが複数あるためフックに失敗します。
[HarmonyPostfix, HarmonyPatch(typeof(SomeClass), "enter")] public static void AsPl_AttackSword0_enter() { }
明示的にどの関数にフックするのかを示すためにTypeを定義します。
[HarmonyPostfix, HarmonyPatch(typeof(SomeClass), "enter"), new Type[] { typeof(string), typeof(int), typeof(bool) })] public static void AsPl_AttackSword0_enter() { }
インスタンスの取得(アンダースコア2つ)
このメソッドを呼び出しているオブジェクトのインスタンスを取得します。
静的メソッドではthisが使用できないため__instanceを使用します。
静的メソッドではthisが使用できないため__instanceを使用します。
public static void Prefix(OcEm __instance) { }
戻り値の上書き(アンダースコア2つ)
フックするメソッドが返す値を変更します。
public static void Prefix(ref string __result) { __result = "hogehoge"; }
PrefixからPostfixに値を受け渡し(アンダースコア2つ)
[HarmonyPrefix] public static void Prefix(ref object[] __state) { __state = new object[] { "1", "two", 3 }; } [HarmonyPostfix] public static void Postfix(object[] __state) { foreach (var obj in __state) { Debug.Log(obj.ToString()); // Prints "1", "two", "3" } }
フィールド変数の取得(アンダースコア3つ)
メソッドの定義にアンダースコア3つの変数を含めることで、手軽にフィールド変数にアクセスできます。
フィールド変数を書き換える場合は「ref」を付けます。
フィールド変数にアンダースコアを含む場合は+3つのアンダースコアを付けます。
例えば、フィールド変数が「_Test」の場合は「____Test」を引数とします。
フィールド変数を書き換える場合は「ref」を付けます。
フィールド変数にアンダースコアを含む場合は+3つのアンダースコアを付けます。
例えば、フィールド変数が「_Test」の場合は「____Test」を引数とします。
[HarmonyPostfix] public static void Postfix(ref bool ___IsEnable) { ___IsEnable = true; }
既存の処理を実行させない
Prefixで実行するときに、その処理後に既存の処理を行わせない方法があります。
これは競合やゲームの変更に弱いため可能な限り避けるべきです。
ただし、他のMODで同じメソッドにフックしている場合はそのMODの処理が行われる場合があります。
処理が行われる条件は「ref」が付く引数を持つことです。
これは競合やゲームの変更に弱いため可能な限り避けるべきです。
ただし、他のMODで同じメソッドにフックしている場合はそのMODの処理が行われる場合があります。
処理が行われる条件は「ref」が付く引数を持つことです。
[HarmonyPrefix] public static bool Prefix() { return false; // falseを返すと後の処理を行いません // trueを返すまたは、voidで定義された場合は既存の処理を継続して行います }
ConfigurationManagerのUI関連
ConfigurationManagerでmodの設定値を表示出来るようにするためのサンプルです。
※同セクション内の項目はOrderを設定することで順番を入れ替えられるが、セクション自体は入れ替え不可で名前順にしか並べられない?方法があれば追記求ム。
※同セクション内の項目はOrderを設定することで順番を入れ替えられるが、セクション自体は入れ替え不可で名前順にしか並べられない?方法があれば追記求ム。
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using UnityEngine;// このサンプルでは上記のusingが必要でしゅ。namespace ConfigurationManagerSample
{[BepInPlugin("Sample", "Sample", "0.0.0")] public class CMSample : BaseUnityPlugin { /// <summary> /// キーバインド設定のサンプル /// </summary> public static ConfigEntry<KeyboardShortcut> KeyBindSample { get; set; }
/// <summary> /// 数値テキストボックスサンプル /// </summary> public static ConfigEntry<int> IntTextSample { get; set; }
/// <summary> /// スライダーのサンプル /// </summary> public static ConfigEntry<int> SliderSample { get; set; }
/// <summary> /// 文字列テキストボックスのサンプル /// </summary> public static ConfigEntry<string> StringTextSample { get; set; }
/// <summary> /// コンボボックス(文字列配列)のサンプルです。 /// </summary> public static ConfigEntry<string> StringArraySample { get; set; }
/// <summary> /// コンボボックス(Enum)のサンプルです。 /// </summary> public static ConfigEntry<sampleComboEnum> EnumSample { get; set; }
/// <summary> /// Enable/Disable切り替えボタンのサンプル /// </summary> public static ConfigEntry<bool> BoolCheckbox { get; set; }
/// <summary> /// コンボボックス用の文字列配列 /// 下記のConfig.Bindより前に外部ファイル等から値を読み込んでセットしておくことで /// ゲーム起動時点でコンボボックスに値をいれることが可能です。 /// (サンプルでは固定値で以下の文字列が入ってます。) /// </summary> private string[] sampleComboString = new string[] { "コンボ", "ボックス", "の", "中身だよ" };
/// <summary> /// コンボボックス用のEnum /// 配列と同じくコンボボックスに項目を表示出来ます。 /// 選択値がenumとして取得できるので条件分岐やswitchなんかでお役立ち /// </summary> public enum sampleComboEnum { value1, value2, value3, value4, value5, }
private void Awake() { // ConfigurationManagerAttributesはConfigurationManagerの設定ウィンドウ内で設定をどのように表示するかを指定するクラスです。 // ConfigurationManagerのGithubからダウンロードしてご利用ください。
// キーバインド設定のサンプルです。 // デフォルト値として「左Shift + M」を指定しています。 KeyBindSample = Config.Bind<KeyboardShortcut>("KeyboardBindセクション名", "KeyboardBindキー名", new KeyboardShortcut(KeyCode.LeftShift, KeyCode.M), new ConfigDescription("ここに設定の説明を入力", null));
// int設定のサンプルです。 // デフォルト値として0を指定しています。 // isAdvancedを「true」に設定すると、ConfigurrationManager上部の「Advanced setting」をオンにしないと表示されない項目となります。 // Orderを設定することで数値が大きいものが同セクション内においてより上に並ぶよう指定します。 IntTextSample = Config.Bind<int>("intセクション名", "intキー名", 0, new ConfigDescription("ここに設定の説明を入力", null, new ConfigurationManagerAttributes { Order = 1, IsAdvanced = true }));
// スライダーで調整出来るタイプの設定のサンプルです。 // AcceptableValueRangeを追加することでスライドバーを表示できます。 // デフォルト値として50を指定しています。 // スライド出来る設定幅を0~50に指定しています。 // またセクション名を同じにすることで同じセクショングループ内に設定を配置することが出来ます。 SliderSample = Config.Bind<int>("intセクション名", "sliderキー名", 50, new ConfigDescription("ここに設定の説明を入力", new AcceptableValueRange<int>(0, 100), new ConfigurationManagerAttributes { Order = 0 }));
// 文字列設定のサンプルです。 StringTextSample = Config.Bind<string>("stringセクション名", "stringキー名", "サンプルだよ", new ConfigDescription("ここに設定の説明を入力", null, new ConfigurationManagerAttributes { Order = 0 }));
// コンボボックス(コンボボックス(文字列配列)のサンプルです。 // new AcceptableValueList<string>(配列名)と追加することで配列の中身がコンボボックスから選択することが出来ます。 StringArraySample = Config.Bind<string>("stringセクション名", "コンボボックスキー名", "コンボ", new ConfigDescription("ここに設定の説明を入力", new AcceptableValueList<string>(sampleComboString), new ConfigurationManagerAttributes { Order = 1 }));
// コンボボックス(Enum)のサンプルです。 // EnumSample.valueで取得できる値がsampleComboEnumのいずれかになります。 EnumSample = Config.Bind<sampleComboEnum>("Enumセクション名", "Enumキー名", sampleComboEnum.value1, new ConfigDescription("ここに設定の説明を入力", null));
// Enable/Disable切り替えボタンのサンプルです。 // デフォルト値はEnable(true)を指定してます。 BoolCheckboxSample = Config.Bind<bool>("boolセクション名", "boolキー名", true, new ConfigDescription("ここに設定の説明を入力", null));
// 独自ボタンのサンプルです。 // ConfigurationManagerAttributesのCustomDrawerに独自に描画させたいボタンやラベルなどの表示処理を記述したメソッドを渡すことで // メニュー上にオリジナルのボタンを配置出来ます。 Config.Bind("ボタンセクション名", "ボタンキー名", "", new ConfigDescription("ここに設定の説明を入力", null));
var harmony = new Harmony("com.example.patch"); harmony.PatchAll(); }
/// <summary> /// Configuration Managerにボタンを描画するためのカスタムドロワー /// </summary> /// <param name="entry"></param> public static void CustomDrawer(ConfigEntryBase entry) { if (GUILayout.Button("ボタンのサンプルです")) { UnityEngine.Debug.Log("We can fly!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); }; } } }
その他
Discordのシンタックスハイライト
Discordにコード載せるときは、シンタックスハイライト(```cs)を先頭につけると見やすい
VisualStudioでビルドする時のテクニック(ビルドイベント)
プロジェクト→プロパティ→ビルドイベントの「ビルド後イベントのコマンドライン」に
※workshopにアップロード前の場合 xcopy $(TargetPath) {Steamインストールパス}\steamapps\workshop\content\1307550\2519948110\BepInEx\plugins /Y
※workshopにアップロード後の場合 xcopy $(TargetPath) {Steamインストールパス}\steamapps\workshop\content\1307550\{MODの番号}\plugins /Y
のどちらかを記載しておくと、ビルド後自動的にpluginsフォルダに格納してくれるので楽。