約 2,564,687 件
https://w.atwiki.jp/krieg-clique/pages/9.html
WoTをプレイする上でゲーム画面を観やすくしたり、自由に機能を拡張できるMODは大変便利です。 我がクランでもXVMをはじめ、各々がカスタムしたMODを使用しているかと思います。また、バニラ(MODを入れない素の状態)の人もいるでしょう。 さて、そのMODですが、MODパック等の複数のMODが統括されている物は事前にその内容を把握していないと、Wargaming.netが定めるレギュレーションに違反する恐れがあります。詳細はコチラ(https //asia.wargaming.net/developers/documentation/rules/rules/)のContent Created by the Participantsの第5項がそれに当たります。 原文は全て英語ですので、該当項目をここに訳しておきます。 ※注意:以下は当方の意訳を含みますので、自身でも原文を必読の事 1.貫通するオブジェクトを透明なテクスチャで表示すること 2.衝突判定のあるモデルの交換・変更 3.危険物の可視化、ゲーム内オブジェクトの明示的なハイライト効果の禁止(例:破壊された戦車を白いテクスチャで表示する) 4.自動照準への変更(例:オートエイム、敵戦車以外への自動ターゲティング等のエイムアシスト機能) 5.ダメージを与えていない敵プレイヤーの名前の表示の禁止(例:マップ上から消えた選手若しくは、未発見のプレイヤーの位置を残りHPやヒットエフェクト等で表示する) 6.ミニマップ上に破壊可能オブジェクトの破壊を修正して表示 7.プレイヤーの位置からカメラを三次元(車両から切り離して)で移動することができる、自走砲モードの使用時に車両と接続されていないカメラ移動 8.ゲームの状況に応じたキーボードマクロの使用(例:「手動消火器」の使用の自動化) 9.標準の位置に基づいた視覚的表現とは異なる他のプレイヤーの照準点の表示は及び、プレイヤが操作する未変性の三次元車両モデルの表示。(例:エイムポイントをレーザーで可視化する。minimap上に他プレイヤーの視野角を表示等) 10.完全または部分的な自動化(ボットの使用) 以上、Creation of Derivative Worksより抜粋 この件で特に注意が必要なのは、破壊された戦車を白く表示する等のテクスチャやオブジェクトに関するものでしょうか。 以前は様々なMODパックにオプションとして収録されていた事の多いMODなので注意が必要です。 その他についても、基本的にゲームのオブジェクトやモデルに影響を及ぼすものは基本的に使用を控えた方が良いでしょう。 公式のレギュレーションをしっかり守って楽しくWoTをプレイしましょう!
https://w.atwiki.jp/minecraftwars22/pages/16.html
現在、サーバーMODは導入しておりません。 プレイヤーの皆様のほとんどは、日本語MODを入れていらっしゃるようです。 そのほうが会話もしやすいのでぜひオススメいたします。
https://w.atwiki.jp/phoenix-feather/pages/101.html
Mod9 The Plane of Battle クエストのメモ(まだ作成途中) 2つのキャンペーンが追加された。また、野外のセルリアン・ヒルにクエスト2つ「The Captives」「Where There s Smoke...」が追加され、レストレス諸島が荒野化された。 キャンペーンは、Lv4のクエスト6つで構成された「The Sharn Syndicate」(犯罪シンジケート 新規の新興勢力)マーケットプレース内に追加された。Stand Your Ground Dirty Laundr The Stormreaver Fresco The Bookbinder Rescue Repossession Come Out and Slayで、Favorは全てコイン・ロード。 もう一つのキャンペーンは 「The Devils of Shavarath」。新規に作られた街Amrathと、荒野Devil Battlefield内のLv19のクエスト6つとLv20のレイド1つ。The Weapons Shipment Wrath of the Flame A New Invasion Bastion of Power Genesis Point Sins of Attrition Tower of Despair(Raid)FavorはWeponとWrathはトゥエルヴ、他は新規のYugoloth。Raid参加にはYugoloth系のクエスト4つをクリアする必要有り。 情報源はwiki(米国)からのと、実際の経験を元にしています。 Mod9 The Plane of Battle クエストのメモ(まだ作成途中) Mod9クエの概要セルリアン・ヒル「The Captives」The Captives概要 「Where There's Smoke...」Where There's Smoke...概要 シャーン・シンジケートStand Your GroundStand Your Ground概要 Dirty LaundryDirty Laundry概要 The Stormreaver FrescoThe Stormreaver Fresco概要 RepossessionRepossession概要 Come Out and SlayCome Out and Slay概要 *概要 Mod9クエの概要 ストーリーは和訳してくれてるサイト等で。 ユニーク品についてはwiki(米国)で調べよう( ∀`) セルリアン・ヒル mod9で追加された新規クエスト。さっくりやるには丁度いいボリューム。 「The Captives」 Level 3 長さ medium 入口 Grul Tribe Outpost クエ受け Pearl Drumling パトロン The Free Agents The Captives概要 クエ受けNPCは、ハーバーで船長とセルリアンヒルの間を忙しなく往復しているNPCから。 入口は、セルリアンヒル東部、ライオンのいる手前辺り。 オークの巣窟の中、囚われたNPC達を助け(戦いに参加させることも可能)、オーク達を殲滅させる。 「Where There s Smoke...」 Level 3 長さ medium 入口 Nash Braza s Farmstead クエ受け Nash Braza パトロン The Free Agents Where There s Smoke...概要 入口はセルリアンヒルに入って右手(西方)にある。クエ受けNPCもそこにいる。 Nash Brazaの農場にオーク達が( ∀`)捕まっちゃった飼い犬を救出してオークを農場から駆逐するクエスト。 ワンコたちと共に戦う事も出来るし殺しちゃうことも出来る。そこは選択だったかな。ワンコ救出自体はオプション。 シャーン・シンジケート 数が少なく物足りなさが否めなかったマーケットプレース内を舞台にした待望の新クエスト。しかも一応キャンペーン。 ちょっとした仕掛け等で楽しく気軽に遊べそうな反面、F2Pではないのが残念な気が。 連続クエストになっていて、最初はハーバーからマーケットプレースに入ってすぐの左手にいる、Yorrick Amanatsuに話しかけることで始まる。 Stand Your Ground Level 4 長さ short 入口 The Dragontail Saloon クエ受け Maxwell Statler パトロン The Coin Lords Stand Your Ground概要 Dragontail Saloonに進入してくるギャング達を蹴散らす。 MaxwellとRanceを守りつつになる。Maxwellが死ぬとクエ失敗、Ranceを守るのはオプション扱い。 Dirty Laundry Level 4 長さ short 入口 The Shiny Shilling クエ受け Guard Kayd パトロン The Coin Lords Dirty Laundry概要 シャーン・シンジケートのマネーロンダリングをしてる奴らをプチ殺すクエスト…? The Stormreaver Fresco Level 4 長さ short 入口 Lordsmarch Bank(勝手口) クエ受け Burgundy Tir パトロン The Coin Lords The Stormreaver Fresco概要 依頼を受けてロボのZirconとともにBankからストームリーヴァーのフレスコ画を取り出すが、セキュリティが発動、Zirconフレスコ画を持って立ち去り、プレイヤー達は置き去りに…。 Repossession Level 4 長さ short 入口 Goldstone Manor クエ受け Kear パトロン The Coin Lords Repossession概要 Zirconからフレスコ画を取り返す。 Come Out and Slay Level 4 長さ short 入口 The Tenements クエ受け Madhand Mulvaney パトロン The Coin Lords Come Out and Slay概要 シャーン・シンジケートが開いた各ギャング達の会合に潜入し、一掃する。 ネームドの1人を味方にする事が出来る(条件ありみたい) WWのクラン・ナッシュトゥース、ハザディル、クイックフットなどのネームドがいる。 終了後にYorrick Amanatsuに話しかけると、Nickedまたは+2の武器が貰えるぽい。 * Level 4 長さ short 入口 クエ受け パトロン The Coin Lords 概要
https://w.atwiki.jp/gunjibu/pages/41.html
改造ヘリ導入 前提MOD 1.MOD Loader、MOD Loader MPをダウンロード。 2.1でダウンロードしたzipを解凍後 フォルダの中のファイルを C \Users\ユーザー名\AppData\Roaming \.minecraftにあるminecraft.jarに入れる (minecraft.jarの開き方は調べてください) 改造ヘリ導入 1.改造ヘリをダウンロード 2.ダウンロードしたzipを、 C \Users\ユーザー名\AppData\ Roaming\.minecraftにある modsフォルダに入れる。
https://w.atwiki.jp/mod-jplang/pages/79.html
MC1.6.2 最新版(Charge Pads-2.8.0.93)の日本語langファイルです # Language Japanese (JP) # Author Reiga # # Creative Tab # itemGroup.ChargePads=Charge Pads # # Damage sources # death.attack.chargePads.electrocution=%1$s は電気ショックで死亡した death.attack.chargePads.emitter.radiation=%1$s はエミッター放射で死亡した # # Charge Pads # item.chargePad.LV.name=スタティックチャージパッド item.chargePad.MV.name=クリスタライザーチャージパッド item.chargePad.HV.name=ラポトロニックチャージパッド item.chargePad.EV.name=フィッションチャージパッド # # Upgrade Kits # chargepads.upgradeKit.wrongside=充電パッドの下部で使用する必要があります。 chargepads.upgradeKit.zap=パッドはアクティブで、感電します! item.upgradeKit.crystalizor.name=クリスタライザーアップグレードキット item.upgradeKit.mfs.name=MFSUアップグレード item.upgradeKit.lapotronic.name=ラポトロニックアップグレードキット item.upgradeKit.fission.name=フィッションアップグレードキット item.upgradeKit.unknown.name=不明なアップグレード # # Upgrade Modules # item.chargePads.upgBasic.efficiency.name=効率強化アップグレード item.chargePads.upgEmitter.drain.name=吸収変換モジュール item.chargePads.upgEmitter.damage.name=ダメージ変換モジュール item.chargePads.upgEmitter.armour_priority.name=アーマー特化モジュール item.chargePads.upgProjector.proximity.name=近接充電モジュール item.chargePads.upgProjector.wide_band.name=広域充電モジュール item.chargePads.upgProjector.field_i.name=フィールド拡張モジュール・アルファ item.chargePads.upgProjector.field_ii.name=フィールド拡張モジュール・デルタ item.chargePads.upgProjector.field_iii.name=フィールド拡張モジュール・シータ item.chargePads.upgEmitter.unknown.name=不明なモジュール # # Charge Pads GUI # gui.chargePads.current=EU %d gui.chargePads.maximum=最大 %d # # Upgrade Modules Tooltip (strength colours are net.minecraft.util.EnumChatFormatting enumerates) # chargePads.emitter.restricted=制限中 chargePads.emitter.restricted.slot=エミッタースロット chargePads.emitter.restricted.nopvp=無効 chargePads.emitter.modulation=変調 chargePads.emitter.modulation.projector=プロジェクター chargePads.emitter.modulation.field=領域 chargePads.emitter.strength=強度 chargePads.emitter.strength.1.name=アルファ chargePads.emitter.strength.2.name=デルタ chargePads.emitter.strength.3.name=シータ chargePads.emitter.strength.1.colour=灰色 chargePads.emitter.strength.2.colour=水色 chargePads.emitter.strength.3.colour=赤色 コメント コメント欄の運営・編集方針に関してはコメント欄方針を参照してください。 このコメント欄はwikiの情報充実のため、追記がしやすいよう設けた物なので、編集が苦手な方は以下のコメントフォームへ書き込んでください。後に編集者が追記等をします。 表示される親コメには限りがあります。返信の際は返信したいコメント横のチェックを付けて返信するようご協力お願いします。 コメログ:工業系modアドオン/IC2add/charge/MC1.7.10 名前 バージョン選択に戻る トップページに戻る
https://w.atwiki.jp/akasatanahama/pages/65.html
概要 メタデータを使用し、一つのIDで複数のブロックを追加したり、向きを持つブロックを追加したりする。 ソースコード AluminiumMod.java package tutorial.aluminiummod; import net.minecraft.block.Block; import cpw.mods.fml.common.Mod; import cpw.mods.fml.common.Mod.EventHandler; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.registry.GameRegistry; @Mod(modid = AluminiumMod.MODID, name = AluminiumMod.MODNAME, version = AluminiumMod.VERSION) public class AluminiumMod { public static final String MODID = "AluminiumMod"; public static final String MODNAME = "Aluminium Mod"; public static final String VERSION = "1.0.0"; public static Block blockAluminiumColored; @EventHandler public void perInit(FMLPreInitializationEvent event) { //ここは通常のブロックと同様。 blockAluminiumColored = new ColoredAluminiumBlock() .setBlockName("blockAluminiumColored") .setBlockTextureName("aluminiummod colored_aluminium_block"); GameRegistry.registerBlock(blockAluminiumColored, ItemColoredAluminiumBlock.class, "blockAluminiumColored"); } } ColoredAluminiumBlock.java package tutorial.aluminiummod; import java.util.List; import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.client.renderer.texture.IIconRegister; import net.minecraft.creativetab.CreativeTabs; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.util.IIcon; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; public class ColoredAluminiumBlock extends Block { private IIcon[] iicon = new IIcon[16]; protected ColoredAluminiumBlock() { super(Material.rock); this.setCreativeTab(CreativeTabs.tabBlock); this.setHardness(5.0F); this.setResistance(10.0F); this.setStepSound(Block.soundTypeMetal); this.setHarvestLevel("pickaxe", 2); this.setLightLevel(0.0F); } @Override @SideOnly(Side.CLIENT) public void registerBlockIcons(IIconRegister register) { for (int i = 0; i 16; i ++) { this.iicon[i] = register.registerIcon(this.getTextureName() + "-" + i); } } @Override @SideOnly(Side.CLIENT) public IIcon getIcon(int side, int meta) { return iicon[meta]; } @Override @SideOnly(Side.CLIENT) public void getSubBlocks(Item item, CreativeTabs creativeTab, List list) { for (int i = 0; i 16; i ++) { list.add(new ItemStack(item, 1, i)); } } @Override public int damageDropped(int meta) { return meta; } } ItemColoredAluminiumBlock.java package tutorial.aluminiummod; import net.minecraft.block.Block; import net.minecraft.item.ItemBlockWithMetadata; import net.minecraft.item.ItemStack; public class ItemColoredAluminiumBlock extends ItemBlockWithMetadata { public ItemColoredAluminiumBlock(Block block) { super(block, block); } @Override public String getUnlocalizedName(ItemStack itemStack) { return this.getUnlocalizedName() + "." + itemStack.getItemDamage(); } } 解説 GameRegistry Block registerBlock(Block block, Class ? extends ItemBlock itemclass, String name) GameRegistryに追加ブロックを登録するメソッド。 対応するItemBlockを指定できる。 デフォルトではItemBlockを指定している。 Block void registerBlockIcons(IIconRegister register) ブロックのテクスチャを指定するメソッド。 Itemと同じ。 IIcon getIcon(int side, int meta) 描画時に呼ばれる。 第一引数では面を、第二引数ではメタデータを取得できる。 このメソッドを使えば面によって違うテクスチャを返したり、かまどのように向きを変えたりできる。 第一引数については下の「sideについて」で。 void getSubBlocks(Item item, CreativeTabs creativeTab, List list) クリエイティブタブに登録するメソッド。 Itemと同じ。 int damageDropped(int meta) ドロップアイテムのダメージを指定するメソッド。 Blockでは常に0を返しているのでオーバーライドする。 ItemBlockWithMetadata ItemBlockのサブクラスで、メタデータを使用するブロック用のクラス。 コンストラクタ(Block block, Block localBlock) 第一引数はスーパークラスのコンストラクタに渡し、第二引数はこのクラスで保持する。 String getUnlocalizedName(ItemStack itemStack) アイテムと同じ。 sideについて getIconの第一引数などのintは、面の方角を表している。 向きと座標の関係は、net.minecraft.util.FacingのoffsetsXForSide/offsetsYForSide/offsetsZForSideを使えばわかる side 向き 座標 0 下 y- 1 上 y+ 2 北 z- 3 南 z+ 4 西 x- 5 東 x+ 使用例 オファレンブロックを追加している部分。 +オファレンMOD OfalenModCore.java package nahama.ofalenmod; /*略*/ /**@author Akasata Nahama*/ @Mod(modid = OfalenModCore.MODID, name = OfalenModCore.MODNAME, version = OfalenModCore.VERSION) public class OfalenModCore { public static final String MODID = "OfalenMod"; public static final String MODNAME = "Ofalen Mod"; public static final String VERSION = "[1.7.10]1.0.0"; /*略*/ /**最初に行われる処理。アイテム・ブロックの追加などを行う*/ @EventHandler public void preInit(FMLPreInitializationEvent event) { /*略*/ //ブロックを設定するメソッドを実行 OfalenModBlockCore.registerBlock(); /*略*/ } /*略*/ } OfalenModBlockCore.java package nahama.ofalenmod.core; /*略*/ public class OfalenModBlockCore { *略*/ public static Block blockOfalen; /*略*/ /**ブロックを設定する*/ public static void registerBlock () { /*略*/ blockOfalen = new OfalenBlock() .setBlockName("blockOfalen") .setBlockTextureName("ofalenmod ofalen_block-"); GameRegistry.registerBlock(blockOfalen, ItemOfalenBlock.class, "blockOfalen"); /*略*/ } } OfalenBlock.java package nahama.ofalenmod.block; import java.util.List; import nahama.ofalenmod.OfalenModCore; import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.client.renderer.texture.IIconRegister; import net.minecraft.creativetab.CreativeTabs; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.util.IIcon; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; public class OfalenBlock extends Block { private IIcon[] iicon = new IIcon[4]; public OfalenBlock() { super(Material.rock); this.setCreativeTab(OfalenModCore.tabOfalen); this.setHardness(7.5F); this.setResistance(15.0F); this.setStepSound(Block.soundTypeMetal); this.setLightLevel(1.0F); this.setHarvestLevel("pickaxe", 3); } /**メタデータ違いのテクスチャを登録する*/ @Override @SideOnly(Side.CLIENT) public void registerBlockIcons(IIconRegister register) { for (int i = 0; i 4; i ++) { this.iicon[i] = register.registerIcon(this.getTextureName() + i); } } /**メタデータにより返すIIconを変える*/ @Override @SideOnly(Side.CLIENT) public IIcon getIcon(int side, int meta) { return iicon[meta 3]; } /**メタデータ違いのブロックを登録する*/ @Override @SideOnly(Side.CLIENT) public void getSubBlocks(Item item, CreativeTabs creativeTab, List list) { for (int i = 0; i 4; i ++) { list.add(new ItemStack(item, 1, i)); } } /**メタデータによりドロップ品を変える*/ @Override public int damageDropped(int meta) { return meta 3; } } ItemOfalenBlock.java package nahama.ofalenmod.itemblock; import net.minecraft.block.Block; import net.minecraft.item.ItemBlockWithMetadata; import net.minecraft.item.ItemStack; public class ItemOfalenBlock extends ItemBlockWithMetadata { public ItemOfalenBlock(Block block) { super(block, block); } /**メタデータにより内部名を変える*/ @Override public String getUnlocalizedName(ItemStack itemStack) { return this.getUnlocalizedName() + "." + itemStack.getItemDamage(); } } コメント この項目に関する質問などをどうぞ。 かまどみたいに置く向きによって変わるのってどうして作るんですか? - 名無しさん 2015-10-12 21 07 02 onBlockPlacedByをオーバーライドし、置いたプレイヤーの向きによって、メタデータを設定します。バニラのBlockFurnaceや、オファレンMODのBlockSmeltingMachineなどが参考になるかと思います。 - 赤砂蛇凪浜 2015-10-13 08 36 08 メタデータによって上面のテクスチャだけを変えるにはどうすればいいですか? - 名無しさん 2016-06-10 23 03 47 IIconをあらかじめ用意しておき、getIconでsideとmetadataの判定をして返すIIconをかえればよいです。 - 赤砂蛇凪浜 2016-06-11 06 52 52 Optifineのように隣り合ったガラスの縁を消すのにはメタデータの違うブロックを置き換えればよいですか? - 名無しさん 2016-06-12 00 31 06 BFOのフチなしガラスでは、getIcon(IBlockAccess,int,int,int,int)で判定を行い、IIconを返しています。メタデータを使う必要はありません。 - 赤砂蛇凪浜 2016-06-12 06 59 29 通常のブロック追加では一つ画像のみでいくらブロックをおいても同じ柄ですが、8×8の64枚の画像をrepeatの形で適応させるにはどう作れば良いのですか?? - 名無しさん 2016-06-14 17 45 47 返答遅くなりまして申し訳ありません。アニメーションさせたい場合は、まず、アニメーションさせたいブロックのテクスチャを、アニメーション順に縦に並べてください。次に、ブロックのテクスチャと同じ階層に[ブロック名].png.mcmeta というファイルを作って、以下のように記述してください。 { "animation" { "frametime" 2 } } アニメーションの速度が速ければ、"frametime"の数値を上げてください。バニラのリソースでは、火や溶岩が参考になるかと思います。- Tom Kate 2016-06-25 11 36 47 メタ付の半ブロックを追加するにはどうすればいいのでしょうか? - 名無しさん 2017-04-20 19 50 42 返信が遅くなってしまい申し訳ありません。コメントを別のページから移動させて頂きました。ご了承ください。単純なものであれば、BlockStoneSlabやBlockWoodSlabをコピペし、各メソッド内でインスタンスの呼び出しをしている部分を置き換えればよいです。ただし、BlockSlabの一部メソッドやItemなど他のクラスでもインスタンスを呼び出しているので、それらも考慮する必要があると思われます。今後、ハーフブロックの追加に関するチュートリアルを作成する予定です。 こちらはマイクラバージョン1.10や1.11などでも使えるのでしょうか...? - 名無しさん 2017-06-08 23 17 07 ver1.8以降はBlockStateと言うものを利用いたしますのでこのチュートリアルは使用できません。 - Tom Kate 2017-06-09 19 13 51 かまどではなく、原木や柱状クオーツのように横から置くとtopの部分が横を向くようにするには、どうすればよいでしょうか? - 名無しさん 2017-06-21 21 22 17 返信が遅くなってしまい申し訳ありません。単純なものでしたら、BlockRotatedPillarを継承し、BlockHayを参考にgetSideIconとregisterBlockIconsをオーバーライドすればできます。継承関係があり少々複雑ですが、原木のクラス(BlockLog, BlockOldLog, BlockNewLog)を見れば一つのIDで四種類まで追加できるかと思います。BlockRotatedPillarでは、getRenderTypeで柱状のレンダーを指定し、横向き設置時にテクスチャを回転させています。また、onBlockPlacedで設置する支えとなったブロックの面によりメタデータで向きを設定し、getIconでメタデータにより断面のテクスチャの向きを変更しています。 - 赤砂蛇凪浜 2017-07-08 18 06 57 名前
https://w.atwiki.jp/wot_agrwiki/pages/18.html
xvmの最新版 カスタマイズ0.8.9_2956 XVM5.0.2用視界サークル設定ファイル「res_mods/xvm内のコンフィグ系フォルダ内に入れてください」 おっさんボイス「解凍してresフォルダ上書きでいけるはずです(元フォルダのバックアップは忘れずに)」
https://w.atwiki.jp/gtamod/pages/36.html
GTASA MOD Wikiは移行しました 移行先はこちら→http //www18.atwiki.jp/gtamod2/ |A||CENTER Alfa Romeo Montreal作者:Redrum備考:コンバートURL| |C||CENTER Cadillac DTS 2008作者:nelly備考:URL| |I||CENTER Infiniti M45作者:nelly備考:URL| |K||CENTER 神奈川中央交通バス作者:ヤマド コンバート:nelly備考:MMDからのコンバートですURL| |L||CENTER Lexus LS600hl作者:Redrum備考:TDUからコンバートURL| |M||CENTER Mazda Familia S-wagon作者:Redrum備考:コンバートURL| |N||CENTER Nissan Fuga 2006作者:nelly備考:URL| |H||CENTER HINA-891 YAKUGAMI作者:みるちお備考:URL| |COLOR(#f9f9f9) |COLOR(#f9f9f9) |COLOR(#f9f9f9) 表の整形のための文字です表の整形のための文字です|
https://w.atwiki.jp/bannerlord/pages/92.html
基本事項用語 実装パターン レイヤー追加パターンの実装例Example UI Opponent Health Bar 基本事項 用語 Gauntlet UI Bannerlord のユーザーインターフェースに用いられているフレームワークです。 Model-View-ViewModel アーキテクチャー Gauntlet UI は Model-View-ViewModel (MVVM) と呼ばれるアーキテクチャーパターンを採用しています。要は、ゲームの領域、UI の領域、その2つを仲介する領域、の3層に分離させることで、情報を整理しやすく、また分業しやすくするという発想のようです。UI の MOD の場合、実装が必要なのはこの内の View と ViewModel になります。 この辺は、WPF や XAML を触ったことのある方ならお馴染みなのかもしれませんが、そうでない方も、あまり深く考えずに概念として何となく把握していれば大丈夫です。 ウィジェット GUI を構成する部品です。TaleWorlds.GauntletUI や TaleWorlds.MountAndBlade.GauntletUI.Widgets あたりに一通り揃っています。基本的にはこれらで事足りるので、自分でウィジェットを定義するということはあまり無いのではないかと思われます。 Prefab GUI のレイアウトを定義する XML ファイルです。プレハブ建築のプレハブと同じで、出来合いの部品 (ウィジェットや他の Prefab) をどういう風に組み合わせて配置するかを指定し、なにがしかの UI 機能を提供する大きめの部品として独立させたものです。 Prefab は TaleWorlds.Engine.GauntletUI.GauntletLayer.LoadMovie() するたびに読み込まれるので、ゲーム起動中にレイアウトを確認しながら編集していくなんてこともできます。(もちろん、構文エラー等があればその時点でクラッシュしますが) Brush GUI のスタイルを定義する XML ファイルです。Prefab と Brush の関係は、HTML と CSS の関係と似たようなものだそうです。 Widget に複数の状態が定義されていれば、それぞれの状態に応じたスプライト画像やフォント等を Brush に持たせることで、Widget に視覚的変化を与えることができます。例えば、Wiget がデフォルト状態にある時のスプライト、マウスオーバー時のスプライト、クリックした時のスプライト、Disable 時のスプライトなどをまとめられる、と言えばわかりやすいでしょうか。 ブラシは、Native 等の公式モジュールの GUI\Brushes に多数定義されています。 実装パターン 一部のパターンでしか実験していません。「たぶんこういうことだろう」レベルの推測が混じっています。 追加 既存の UI スクリーンにレイヤーを追加し、そこに自前の GUI を描画する (AddLayer)。 標準的な方法です。自分で View も ViewModel も用意するので好きなようにデザインできますが、既存の GUI そのものを書き換えることはできません。 モジュールとしての競合は起こりませんが、意図せず他の MOD の GUI と表示位置がかぶってしまうことはあり得ます。 改変 (未検証) 既存の UI スクリーンのレイアウトだけを変更する (XML 改変)。 使用するデータソース (ViewModel) はそのままで、XML の書き換えによって GUI の配置なんかを変える方法です。簡単だと思いますが、おそらくやれることは限定的です。また、MOD の競合が発生しうる方法だと思われます。 上書き (未検証) 既存の UI スクリーン全体をオーバーライドする (OverrideView)。 上書きする UI スクリーンが提供していた機能を自分で実装することになるため難易度は高いでしょう。また、MOD の競合が発生しうる方法だと思われます。 新規作成 自前の UI スクリーンを作成し、何かのイベントに伴ってそのスクリーンを呼び出す (PushScreen)。 Push/Pop のスタック形式なのでモーダルなポップアップとかに向いていると思います。 一部改変 たとえばオプション項目の追加などがこれに当たりますが、現状ではできないようです。 ただ、可能にする方法は検討されているとのこと。 Beyond that, we continue to work on Adding support for adding options to the options screen レイヤー追加パターンの実装例 Example UI ExampleUI プロジェクトSubModule.cs ViewsSampleMapView.cs SampleMissionView.cs ViewModelsTestWindowVM.cs ExampleUI.Window.xml View と ViewModel の関係がはっきりするように階層 (名前空間) を分けています。 ExampleUI.Window.xml は、 [Bannerlord インストールフォルダー]\Modules\ExampleUI\GUI\Prefabs フォルダーを作成し、その中に保存します。 MVVM では疎結合 (各層の結びつきが緩やか) なのが望ましいとされているそうなので、参照はできるだけ一方向になるようにしましょう。 すなわち、 View が ViewModel のインスタンスを持ち、その逆方向のアクセスはしない Model (ゲーム内の要素) に属するデータの加工は View の中では行わず、ViewModel で行う という感じです。 コード +SubModule.cs モジュールのエントリーポイントです。 using ExampleUI.Views;using SandBox.View.Map;using System;using TaleWorlds.Core;using TaleWorlds.Engine.Screens;using TaleWorlds.MountAndBlade;using TaleWorlds.MountAndBlade.View.Missions;using TaleWorlds.MountAndBlade.View.Screen; namespace ExampleUI{ public class SubModule MBSubModuleBase { private MissionView _missionView; private MapView _mapView; // Mission (平たく言えば、キャラクターが動き回ったり攻撃したりできる状況) に対する // MOD の処理 (ビヘイビアー) の登録はこのメソッドで行います。 // OnBeforeMissionBehaviourInitialize() と OnMissionBehaviourInitialize() の違いは // MissionBehaviour.OnBehaviourInitialize() の前に呼ばれるか後によばれるかです。 public override void OnBeforeMissionBehaviourInitialize(Mission mission) { base.OnBeforeMissionBehaviourInitialize(mission); // ViewCreatorManager.CreateMissionView() は一応ファクトリーメソッドっぽいのですが、 // 公式のコードには、これを介さず単に new MissionView() しているものもあったりして // よく分かりません。どっちにしろ動くのは動きます。 _missionView = ViewCreatorManager.CreateMissionView SampleMissionView (false, mission, Array.Empty object ()); mission.AddMissionBehaviour(_missionView); /* mission.AddMissionBehaviour(_missionView = new SampleMissionView()); */ // このサンプルでは、全ての Mission でテスト GUI が表示されてしまいますが、 // 戦闘シーンだけに表示したいなどといった場合、何らかの工夫が必要になるでしょう。 // // ちなみに、OnBeforeMissionBehaviourInitialize() 時点で mission.Mode は MissionMode.StartUp 固定なので、 // 以下の方法は使えませんでした。 /* if (mission.Mode == MissionMode.Battle) { _missionView = ViewCreatorManager.CreateMissionView SampleMissionView (false, mission, Array.Empty object ()); mission.AddMissionBehaviour(_missionView); } */ } // MapScreen (ワールドマップの描画スクリーン) には OnBeforeMissionBehaviourInitialize() に相当する // メソッドが用意されていないので、スクリーンの Push/Pop イベントを利用してレイヤーを挿入しています。 public override void OnGameInitializationFinished(Game game) { base.OnGameInitializationFinished(game); ScreenManager.OnPushScreen += OnScreenManagerPushScreen; // Obsolete /* ScreenManager.OnPopScreen += OnScreenManagerPopScreen; */ } public override void OnGameEnd(Game game) { ScreenManager.OnPushScreen -= OnScreenManagerPushScreen; // Obsolete /* ScreenManager.OnPopScreen -= OnScreenManagerPopScreen; */ base.OnGameEnd(game); } private void OnScreenManagerPushScreen(ScreenBase pushedScreen) { if (pushedScreen is MapScreen mapScreen) { _mapView = mapScreen.AddMapView SampleMapView (Array.Empty object ()); } /* else if (pushedScreen is MissionScreen missionScreen missionScreen.Mission != null) { // MissionScreen は、まず空の MissionScreen を Push してから、ローディング画面を // 表示しつつスクリーンを初期化していく感じなので、MapScreen と同じ手法は使えません。 // // この時点で missionScreen.Mission には中身がないので、CreateMissionView() は失敗します。 _missionView = ViewCreatorManager.CreateMissionView SampleMissionView (false, missionScreen.Mission, Array.Empty object ()); missionScreen.AddMissionView(_missionView); } */ } [Obsolete("1.6.0より前の手法")] private void OnScreenManagerPopScreen(ScreenBase poppedScreen) { if (_mapView != null poppedScreen is MapScreen mapScreen) { // 1.6.0 からは MapScreen が破棄される際に勝手に MapView.OnFinalize() を呼んでくれるようになったので // MapView と MapScreen の寿命を同じにする限りでは、MapScreen.RemoveMapView() をする必要がなくなりました。 mapScreen.RemoveMapView(_mapView); _mapView = null; } else if (_missionView != null poppedScreen is MissionScreen) { // MissionView については、MissionScreen.OnEndMission() が OnMissionScreenFinalize() の呼び出しから // RemoveMissionBehaviour() まで勝手にやってくれます。 _missionView = null; } // したがって、_mapView と _missionView はもはや必要のないフィールドなのですが、説明用に残してあります。 } }} +SampleMapView.cs using ExampleUI.ViewModels;using SandBox.View.Map;using TaleWorlds.Engine.GauntletUI;using TaleWorlds.GauntletUI.Data;using TaleWorlds.InputSystem;using TaleWorlds.Library; namespace ExampleUI.Views{ public class SampleMapView MapView { private TestWindowVM _dataSource; private GauntletLayer _gauntletLayer; private IGauntletMovie _gauntletMovie; protected override void CreateLayout() { base.CreateLayout(); _dataSource = new TestWindowVM(); // localOrder (レイヤーの優先度) が小さいほど、他のレイヤーより下 (画面奥) に描画されます。 // // 例えば、部隊が町などに入った際に左に表示されるメニュー (GauntletMenuBase) のレイヤー優先度は 100 なので、 // それより小さい値に設定すればテストウィンドウがメニューの下に表示されるようになります。 // ESC メニュー (GauntletMapEscapeMenu) のレイヤー優先度が 4400 なので、これよりは小さい値に // した方がよさそうです。あるいは、ESC メニュー表示中は GUI を非表示にする処理を追加しましょう。 _gauntletLayer = new GauntletLayer(4000); Layer = _gauntletLayer; // Movie (= Screen に投影するもの) とは、XML から取得した UI の構造と、ViewModel (表示するデータ) を合わせた概念です。 _gauntletMovie = _gauntletLayer.LoadMovie("ExampleUI.Window", _dataSource); // このレイヤーの描画領域に対して行われた入力のうち、受け取るものをビットフラグによって管理しています。 _gauntletLayer.InputRestrictions.SetInputRestrictions(true, InputUsageMask.MouseButtons); MapScreen.AddLayer(_gauntletLayer); } // MapScreen.OnFinalize() によって呼び出されます。 protected override void OnFinalize() { MapScreen.RemoveLayer(_gauntletLayer); _gauntletLayer.InputRestrictions.ResetInputRestrictions(); _gauntletLayer.ReleaseMovie(_gauntletMovie); _gauntletLayer = null; Layer = null; _dataSource.OnFinalize(); base.OnFinalize(); } // 毎ティック行う処理を記述します。 // // dt デルタタイム。前回のティックからの経過時間です。 protected override void OnFrameTick(float dt) { base.OnFrameTick(dt); if (MapScreen.Input.IsKeyPressed(InputKey.Home)) { _dataSource.IsVisible ^= true; // bool 反転 } } }} +SampleMissionView.cs SampleMapView とは使用するメソッドが違うだけで、やっていることは全く同じです。 using ExampleUI.ViewModels;using TaleWorlds.Engine.GauntletUI;using TaleWorlds.GauntletUI.Data;using TaleWorlds.InputSystem;using TaleWorlds.MountAndBlade.View.Missions; namespace ExampleUI.Views{ public class SampleMissionView MissionView { private TestWindowVM _dataSource; private GauntletLayer _gauntletLayer; private IGauntletMovie _gauntletMovie; public override void OnMissionScreenInitialize() { base.OnMissionScreenInitialize(); _dataSource = new TestWindowVM(); // localOrder (レイヤーの優先度) が小さいほど、他のレイヤーより下 (画面奥) に描画されます。 // // ESC メニュー (GauntletMissionEscapeMenu) のレイヤー優先度が 50 なので、 これよりは小さい // した方がよさそうです。あるいは、ESC メニュー表示中は GUI を非表示にする処理を追加しましょう。 _gauntletLayer = new GauntletLayer(40); // Movie (= Screen に投影するもの) とは、XML から取得した UI の構造と、ViewModel (表示するデータ) を合わせた概念です。 _gauntletMovie = _gauntletLayer.LoadMovie("ExampleUI.Window", _dataSource); // このレイヤーの描画領域に対して行われた入力のうち、受け取るものをビットフラグによって管理しています。 /* _gauntletLayer.InputRestrictions.SetInputRestrictions(true, InputUsageMask.MouseButtons); */ // Mission 中はマウスを視点移動や攻撃に使うため、ポインティングデバイスとして使うには、 // 戦闘結果画面において、Tab を押す - リザルトとマウスカーソル表示 - ボタンが押せるようになる // とやっているような感じで、キーボード操作を挟んで切り替える必要があります。 // 今回はそこまで実装していないため、Mission 中は閉じるボタンをクリックできません。 MissionScreen.AddLayer(_gauntletLayer); } // MissionScreen.OnEndMission() によって呼び出されます。 public override void OnMissionScreenFinalize() { MissionScreen.RemoveLayer(_gauntletLayer); _gauntletLayer.ReleaseMovie(_gauntletMovie); _gauntletLayer = null; _dataSource.OnFinalize(); base.OnMissionScreenFinalize(); } // 毎ティック行う処理を記述します。 // // dt デルタタイム。前回のティックからの経過時間です。 public override void OnMissionScreenTick(float dt) { base.OnMissionScreenTick(dt); if (Input.IsKeyPressed(InputKey.Home)) { _dataSource.IsVisible ^= true; // bool 反転 } } }} +TestWindowVM.cs ViewModel とは View を抽象化したものです。今回の例では、 画面左上にウィンドウを表示 ウィンドウの中には閉じるボタン1つだけ 閉じるボタンを押すとウィンドウが閉じる Home キーでも開いたり閉じたりできる というモデルで行きます。 TestWindowVM は View から完全に分離されているので、今回使う2つの View どちらにもそのまま使用できます。 using TaleWorlds.Core;using TaleWorlds.Library;// View-ViewModel の参照を一方向にするため using ExampleUI.Views をやっていません。 namespace ExampleUI.ViewModels{ // ViewModel は全て TaleWorlds.Library.ViewModel を継承して作ります。 // クラス名に VM を付けなければならないという決まりがあるわけではないのですが、 // 公式の ViewModel は全て VM を付けているようなので踏襲しています。 public class TestWindowVM ViewModel { private bool _isVisible; public TestWindowVM() { IsVisible = true; } // ViewModel のプロパティやメソッドは、View の LoadMovie で関連付けられた XML Prefab から参照できます。 // XML の方を見てもらえば、それらがどのように使われているか分かるかと思います。 [DataSourceProperty] public bool IsVisible { get = _isVisible; set { if (_isVisible != value) { _isVisible = value; // このメソッドを呼ぶことで、TestWindowVM にバインディングされた View に対して // TestWindowVM.IsVisible プロパティの変更通知が行き、プロパティを参照している // ウィジェットの方も状態が変化するという仕組みです。 OnPropertyChangedWithValue(value, "IsVisible"); } } } // こちらは値の変更がない固定ラベルなので、set アクセサーや OnPropertyChangedWithValue() は使っていません。 [DataSourceProperty] public string WindowTitle = "Test Window"; // 以前はラベルにも日本語が使えたのですが、1.6.0 から文字化けするようになってしまいました。 [DataSourceProperty] public string CloseButtonLabel = "Close"; // ButtonWidget のクリックイベントに応じて呼ばれるよう、ExampleUI.Window.xml に記述してあります。 public void OnCloseButtonClick() { IsVisible = false; InformationManager.DisplayMessage(new InformationMessage("ウィンドウを閉じました。\n再度開くには Home キーを押してください。")); } }} DataSourceProperty 属性 公式の ViewModel 派生クラスのプロパティにはこの属性を与えられたものが多く見受けられますが、ソースコードを見ても特に何か定義されているわけでもなく、実際、属性を付けても付けなくても動作は変わらないように見えます。唯一、ViewModel.Properties プロパティで DataSourceProperty のリストを作るのに使われているようですが、このプロパティは他からは特に参照されていないようです。一応、XML から参照されるプロパティには全てこの属性を付けてあります。 +ExampleUI.Window.xml ExampleUI で使うウィンドウの Prefab です。 Prefab Window Widget WidthSizePolicy="Fixed" HeightSizePolicy="Fixed" SuggestedWidth="400" SuggestedHeight="300" IsVisible="@IsVisible" Children Standard.Window Parameter.Title="@WindowTitle" Children Standard.PopupCloseButton Parameter.ButtonText="@CloseButtonLabel" Parameter.ButtonAction="OnCloseButtonClick" / /Children /Standard.Window /Children /Widget /Window /Prefab Widget TaleWorlds.GauntletUI.Widget クラスです。Widget クラスは基底クラスとしてデータを保持しているだけで、UI としての機能はありません。ここでは、他の UI をまとめるコンテナとして使っています。 SizePolicy Fixed 指定の幅にしますStretchToParent 親要素の幅に合わせますCoverChildren 子要素を全て表示できる幅にします IsVisible="@IsVisible" 名前が同じで分かりにくいですが、最初の方が Widget クラスのプロパティ、@付きの方がデータソースとなる ViewModel 派生クラス (今回の例で言えば TestWindowVM) のプロパティとなります。このように指定することで、TestWindowVM.IsVisible の中で呼んである ViewModel.OnPropertyChangedWithValue() によって発せられたイベントが Widget に届くようになり、TestWindowVM.IsVisible と Widget.IsVisible が連動するわけです。 Standard.Window と Standard.PopupCloseButton 公式の Prefab を使っています。公式の Prefab も大半は各シチュエーションに特化した大型のものばかりで、そのままでの再利用はしにくいですが、Native\GUI\Prefabs\Standard にあるものは比較的小型で汎用性が高くなっています。 Parameter Standard.Window 等の Prefab では Parameter が宣言されているものがあり、Widget のプロパティに値を渡すのと同じ感覚で、Prefab 自体に値を渡すことができます。Prefab 側では、自分の中の Widget に受け取った値を渡すよう記述されています。 結果 MapScreen MissionScreen 左上にウィンドウっぽいものが表示されています。Prefab を適当に選んだのでデザインがめちゃくちゃですが、画面上にとりあえず何かを表示する例ということで大目に見てください。 手順をおおまかにまとめると、 作りたい UI を考える その構想に沿った GUI の構造 (Prefab) と表示するデータ (ViewModel) を用意する 用意したものを、表示先がワールドマップなら MapView、戦闘などの Mission なら MissionView で LoadMovie() する View をスクリーンに追加する となります。 Opponent Health Bar 今度はもう少し実践的な MOD を作っていきます。見出しが示す通り、戦っている相手の HP バーを表示する MOD です。 イメージとしてはこんな感じ。 OpponentHealthBar プロジェクトSubModule.cs MissionOpponentHealthBar.cs OpponentHealthVM.cs OpponentHealthBar.xml OpponentHealthBar.xml は、 [Bannerlord インストールフォルダー]\Modules\OpponentHealthBar\GUI\Prefabs の中に保存します。 バージョン1 それでは、とりあえず簡単に文字で表示するところから始めてみます。 +SubModule.cs using System;using TaleWorlds.MountAndBlade;using TaleWorlds.MountAndBlade.View.Missions; namespace OpponentHealthBar{ public class SubModule MBSubModuleBase { public override void OnBeforeMissionBehaviourInitialize(Mission mission) { base.OnBeforeMissionBehaviourInitialize(mission); mission.AddMissionBehaviour( ViewCreatorManager.CreateMissionView MissionOpponentHealthBar (false, mission, Array.Empty object ())); } }} +MissionOpponentHealthBar.cs using TaleWorlds.Core;using TaleWorlds.Engine.GauntletUI;using TaleWorlds.GauntletUI.Data;using TaleWorlds.MountAndBlade;using TaleWorlds.MountAndBlade.View.Missions; namespace OpponentHealthBar{ public class MissionOpponentHealthBar MissionView { private OpponentHealthVM _dataSource; private GauntletLayer _gauntletLayer; private IGauntletMovie _gauntletMovie; public MissionOpponentHealthBar() { ViewOrderPriorty = 20; } public override void OnMissionScreenInitialize() { base.OnMissionScreenInitialize(); _dataSource = new OpponentHealthVM(); _gauntletLayer = new GauntletLayer(ViewOrderPriorty); _gauntletMovie = _gauntletLayer.LoadMovie("OpponentHealthBar", _dataSource); MissionScreen.AddLayer(_gauntletLayer); } public override void OnMissionScreenFinalize() { MissionScreen.RemoveLayer(_gauntletLayer); _gauntletLayer.ReleaseMovie(_gauntletMovie); _gauntletLayer = null; _dataSource.OnFinalize(); base.OnMissionScreenFinalize(); } // 画面中央 (マウスカーソルの位置) と、一番手前にある IFocusable なオブジェクトとが交差した瞬間に呼ばれます。 // // agent フォーカスした Agent。シングルプレイだと、おそらく常に Agent.Main (プレイヤーキャラクター) です。 // focusableObject フォーカスされたもの // isInteractable Talk や Use などのインタラクトが可能か // // フォーカスが 10m までしか利かないのは MissionMainAgentInteractionComponent.FocusTick() で // 決められた仕様なのでどうしようもありません。 public override void OnFocusGained(Agent agent, IFocusable focusableObject, bool isInteractable) { base.OnFocusGained(agent, focusableObject, isInteractable); _dataSource.OnFocusChanged(focusableObject); } // 今までフォーカスされていた IFocusable なオブジェクトから、フォーカスが外れた瞬間に呼ばれます。 // // agent フォーカスしていた Agent。シングルプレイだと、おそらく常に Agent.Main (プレイヤーキャラクター) です。 // focusableObject フォーカスされていたもの public override void OnFocusLost(Agent agent, IFocusable focusableObject) { base.OnFocusLost(agent, focusableObject); _dataSource.OnFocusChanged(focusableObject); } // Agent が何らかの攻撃を受けた際に呼ばれます。 // // affectedAgent 攻撃を受けた Agent // affectorAgent 攻撃を行った Agent // damage ダメージ量 // affectorWeapon 使用された武器 public override void OnAgentHit(Agent affectedAgent, Agent affectorAgent, int damage, in MissionWeapon affectorWeapon) { base.OnAgentHit(affectedAgent, affectorAgent, damage, affectorWeapon); _dataSource.OnAgentHit(affectedAgent); } // Agent が戦闘不能になった際に呼ばれます。 // // affectedAgent 攻撃を受けた Agent // affectorAgent 攻撃を行った Agent // agentState Agent の状態 // blow とどめの一撃の内容 // // 似たようなメソッドに OnAgentDeleted がありますが、そちらは // 戦闘不能になって倒れている Agent が時間経過や表示限界により消滅する際に呼ばれるようです。 public override void OnAgentRemoved(Agent affectedAgent, Agent affectorAgent, AgentState agentState, KillingBlow blow) { base.OnAgentRemoved(affectedAgent, affectorAgent, agentState, blow); _dataSource.OnAgentRemoved(affectedAgent); } }} +OpponentHealthVM.cs 今度のモデルは、 Agent (Mission 中に動きうるもの) の HP 現在値/最大値 を文字で表示する Agent にフォーカスが合うと表示、フォーカスが外れると非表示 とします。 using System;using TaleWorlds.Library;using TaleWorlds.MountAndBlade; namespace OpponentHealthBar{ public class OpponentHealthVM ViewModel { private Agent _opponent; private string _strValues; private bool _isVisible; private const string ValueFormat = "{0} / {1}"; public OpponentHealthVM() { _opponent = null; _strValues = string.Empty; _isVisible = false; } [DataSourceProperty] public string StrValues { get = _strValues; private set { if (_strValues != value) { _strValues = value; OnPropertyChangedWithValue(value, "StrValues"); } } } [DataSourceProperty] public bool IsVisible { get = _isVisible; set { if (_isVisible != value) { _isVisible = value; OnPropertyChangedWithValue(value, "IsVisible"); } } } // GUI に表示するデータの更新はこのメソッドで行います。 // ただし、ここに記述すればあとは勝手に更新されていくというものではないので、 // 自分が必要な場所では自分で呼び出さなければなりません。 public override void RefreshValues() { base.RefreshValues(); int currentValue = (int)Math.Ceiling(_opponent?.Health ?? 0f); int maxValue = (int)Math.Ceiling(_opponent?.HealthLimit ?? 0f); StrValues = string.Format(ValueFormat, currentValue, maxValue); IsVisible = _opponent != null; } public void OnFocusChanged(IFocusable focusableObject) { if (focusableObject is Agent possibleOpponent) { _opponent = (_opponent == null || _opponent != possibleOpponent) ? possibleOpponent null; RefreshValues(); } } public void OnAgentHit(Agent affectedAgent) { if (_opponent == affectedAgent) { RefreshValues(); } } public void OnAgentRemoved(Agent removedAgent) { if (_opponent == removedAgent) { // 先にフォーカスロストが発生してここには入らないかも。 OnFocusChanged(removedAgent); } } }} +OpponentHealthBar.xml Prefab Window Widget WidthSizePolicy="Fixed" HeightSizePolicy="Fixed" SuggestedWidth="430" SuggestedHeight="50" HorizontalAlignment="Center" VerticalAlignment="Top" MarginTop="250" IsVisible="@IsVisible" Children TextWidget Text="@StrValues" WidthSizePolicy="CoverChildren" HeightSizePolicy="CoverChildren" HorizontalAlignment="Center" VerticalAlignment="Center" Brush="Tooltip.Text" Brush.FontSize="24" / /Children /Widget /Window /Prefab Alignment 親要素の領域内のどちら側に寄せるかです。親要素より幅が小さい場合にしか効果がありません。この例の TextWidget で言うと、親要素は幅 430、自身は CoverChildren、つまり StrValues が入りきるだけの幅なので機能していますが、これを StretchToParent とかにすると、左寄せや右寄せが機能しなくなります。 Margin Left, Top, Right, Bottom のマージンです。幅+マージンが要素の実際の幅になります。 以上を実行した状態が下の画像です。 ちょっと見にくいですが、画面上部中央に相手の HP 100/100 が表示されていますね。フォーカスを外せば数字は消えます。しかし、今のところ町人だろうが馬だろうが Agent なら何にでも表示されてしまいますので、そこは改良が必要です。 バージョン2 次はバーの導入とその他の改良を行っていきたいと思います。 バー表示に使うのは FillBarWidget です。ソースコードや、FillBarWidget を使用している公式の Prefab を見てみると、FillBarWidget.MaxAmount プロパティと FillBarWidget.InitialAmmount プロパティに値を渡せばとりあえず動きそうです。よって、ViewModel 側でその値を用意してやる必要があります。 +OpponentHealthVM.cs [DataSourceProperty] public int CurrentValue { get = _currentValue; private set { if (_currentValue != value) { _currentValue = value; OnPropertyChangedWithValue(value, "CurrentValue"); } } } [DataSourceProperty] public int MaxValue { get = _maxValue; private set { if (_maxValue != value) { _maxValue = value; OnPropertyChangedWithValue(value, "MaxValue"); } } } // GUI に表示するデータの更新はこのメソッドで行います。 // ただし、ここに記述すればあとは勝手に更新されていくというものではないので、 // 自分が必要な場所では自分で呼び出さなければなりません。 public override void RefreshValues() { base.RefreshValues(); // 元々ローカル変数だったものを、View から参照できるようにプロパティに昇格しました。 CurrentValue = (int)Math.Ceiling(_opponent?.Health ?? 0f); MaxValue = (int)Math.Ceiling(_opponent?.HealthLimit ?? 0f); StrValues = string.Format(ValueFormat, CurrentValue, MaxValue); IsVisible = _opponent != null; } public void OnFocusChanged(IFocusable focusableObject) { // 適切な相手に表示されるよう条件を追加しました。 if (focusableObject is Agent possibleOpponent possibleOpponent.IsHuman possibleOpponent.IsEnemyOf(Agent.Main)) { _opponent = (_opponent == null || _opponent != possibleOpponent) ? possibleOpponent null; RefreshValues(); } } ViewModel にプロパティを追加したので、今度は Prefab を通じてそれらを FillBarWidget に渡します。 +OpponentHealthBar.xml Prefab Window FillBarWidget WidthSizePolicy="Fixed" HeightSizePolicy="Fixed" SuggestedWidth="430" SuggestedHeight="50" HorizontalAlignment="Center" VerticalAlignment="Top" MarginTop="250" ContainerWidget="FillBarContainer" FillWidget="FillVisualParent\FillVisual" MaxAmount="@MaxValue" InitialAmount="@CurrentValue" IsVisible="@IsVisible" Children Widget Id="FillVisualParent" WidthSizePolicy="Fixed" HeightSizePolicy="StretchToParent" SuggestedWidth="400" HorizontalAlignment="Center" VerticalAlignment="Center" MarginTop="10" MarginBottom="10" Sprite="BlankWhiteSquare" Color="#202020A0" Children BrushWidget Id="FillVisual" WidthSizePolicy="Fixed" HeightSizePolicy="StretchToParent" SuggestedWidth="400" HorizontalAlignment="Left" Brush="Mission.MainAgentHUD.HeroHealthBar.Fill" / /Children /Widget Widget Id="FillBarContainer" WidthSizePolicy="StretchToParent" HeightSizePolicy="StretchToParent" Sprite="options_memory_progress_frame" / TextWidget Text="@StrValues" WidthSizePolicy="CoverChildren" HeightSizePolicy="CoverChildren" HorizontalAlignment="Center" VerticalAlignment="Center" PositionYOffset="3" Brush="Tooltip.Text" Brush.FontSize="24" / /Children /FillBarWidget /Window /Prefab FillBarWidget バージョン1ではただの Widget だったトップレベルのウィジェットを FillBarWidget に変更してあります (3行目)。 FillVisual 赤ゲージの Brush を保持した BrushWidget の ID です (9行目)。このように、Widget には任意の ID がつけられます。それによって、他のウィジェットを操作するタイプのウィジェットに対し、操作対象がどれなのか伝えることができます。 ここでは、FillBarWidget.FillWidget プロパティに渡して、FillBarWidget が "FillVisual" の幅を操作できるようにしています。 親要素の "FillVisualParent" はゲージが減った部分を埋める背景画像です。"BlankWhiteSquare" という無地画像を、暗い半透明に着色・透過させて使っています。 FillBarContainer 14行目にある、スプライト画像を保持した Widget の ID です。これを FillBarWidget.ContainerWidget プロパティに渡してバーの枠としています。 ところで、Mission 用の GUI はそのままだとフォトモード中でも表示されてしまいます。構図を決めている間に GUI が表示されてしまうのは邪魔ですから、MissionView を使うときは、フォトモード中 GUI を非表示にする処理を追加しておきましょう。 +MissionOpponentHealthBar.cs // フォトモードで構図を決めている最中に GUI が表示されないようにするための処理です。 // この処理を入れても入れなくても、どちらにしろ出力される画像ファイルに GUI は写りません。 public override void OnPhotoModeActivated() { base.OnPhotoModeActivated(); _gauntletLayer._gauntletUIContext.ContextAlpha = 0f; } public override void OnPhotoModeDeactivated() { base.OnPhotoModeDeactivated(); _gauntletLayer._gauntletUIContext.ContextAlpha = 1f; } 結果 攻撃前 攻撃後 どうでしょう、一応 HP バーらしくなったのではないでしょうか。 他にも、与えたダメージ分のバーをスライドさせる視覚効果とか、フォーカスが外れてからバーが消えるまでに時間差を設けるとか、改良はいろいろ考えられますが、サンプルではここまでです。興味があったら自分の MOD として実装してみてください。 +コード最終形 SubModule.cs using System;using TaleWorlds.MountAndBlade;using TaleWorlds.MountAndBlade.View.Missions; namespace OpponentHealthBar{ public class SubModule MBSubModuleBase { public override void OnBeforeMissionBehaviourInitialize(Mission mission) { base.OnBeforeMissionBehaviourInitialize(mission); mission.AddMissionBehaviour( ViewCreatorManager.CreateMissionView MissionOpponentHealthBar (false, mission, Array.Empty object ())); } }} MissionOpponentHealthBar.cs using TaleWorlds.Core;using TaleWorlds.Engine.GauntletUI;using TaleWorlds.GauntletUI.Data;using TaleWorlds.MountAndBlade;using TaleWorlds.MountAndBlade.View.Missions; namespace OpponentHealthBar{ public class MissionOpponentHealthBar MissionView { private OpponentHealthVM _dataSource; private GauntletLayer _gauntletLayer; private IGauntletMovie _gauntletMovie; public MissionOpponentHealthBar() { ViewOrderPriorty = 20; } public override void OnMissionScreenInitialize() { base.OnMissionScreenInitialize(); _dataSource = new OpponentHealthVM(); _gauntletLayer = new GauntletLayer(ViewOrderPriorty); _gauntletMovie = _gauntletLayer.LoadMovie("OpponentHealthBar", _dataSource); MissionScreen.AddLayer(_gauntletLayer); } public override void OnMissionScreenFinalize() { MissionScreen.RemoveLayer(_gauntletLayer); _gauntletLayer.ReleaseMovie(_gauntletMovie); _gauntletLayer = null; _dataSource.OnFinalize(); base.OnMissionScreenFinalize(); } // 画面中央 (マウスカーソルの位置) と、一番手前にある IFocusable なオブジェクトとが交差した瞬間に呼ばれます。 // // agent フォーカスした Agent。シングルプレイだと、おそらく常に Agent.Main (プレイヤーキャラクター) です。 // focusableObject フォーカスされたもの // isInteractable Talk や Use などのインタラクトが可能か // // フォーカスが 10m までしか利かないのは MissionMainAgentInteractionComponent.FocusTick() で // 決められた仕様なのでどうしようもありません。 public override void OnFocusGained(Agent agent, IFocusable focusableObject, bool isInteractable) { base.OnFocusGained(agent, focusableObject, isInteractable); _dataSource.OnFocusChanged(focusableObject); } // 今までフォーカスされていた IFocusable なオブジェクトから、フォーカスが外れた瞬間に呼ばれます。 // // agent フォーカスしていた Agent。シングルプレイだと、おそらく常に Agent.Main (プレイヤーキャラクター) です。 // focusableObject フォーカスされていたもの public override void OnFocusLost(Agent agent, IFocusable focusableObject) { base.OnFocusLost(agent, focusableObject); _dataSource.OnFocusChanged(focusableObject); } // Agent が何らかの攻撃を受けた際に呼ばれます。 // // affectedAgent 攻撃を受けた Agent // affectorAgent 攻撃を行った Agent // damage ダメージ量 // affectorWeapon 使用された武器 public override void OnAgentHit(Agent affectedAgent, Agent affectorAgent, int damage, in MissionWeapon affectorWeapon) { base.OnAgentHit(affectedAgent, affectorAgent, damage, affectorWeapon); _dataSource.OnAgentHit(affectedAgent); } // Agent が戦闘不能になった際に呼ばれます。 // // affectedAgent 攻撃を受けた Agent // affectorAgent 攻撃を行った Agent // agentState Agent の状態 // blow とどめの一撃の内容 // // 似たようなメソッドに OnAgentDeleted がありますが、そちらは // 戦闘不能になって倒れている Agent が時間経過や表示限界により消滅する際に呼ばれるようです。 public override void OnAgentRemoved(Agent affectedAgent, Agent affectorAgent, AgentState agentState, KillingBlow blow) { base.OnAgentRemoved(affectedAgent, affectorAgent, agentState, blow); _dataSource.OnAgentRemoved(affectedAgent); } // フォトモードで構図を決めている最中に GUI が表示されないようにするための処理です。 // この処理を入れても入れなくても、どちらにしろ出力される画像ファイルに GUI は写りません。 public override void OnPhotoModeActivated() { base.OnPhotoModeActivated(); _gauntletLayer._gauntletUIContext.ContextAlpha = 0f; } public override void OnPhotoModeDeactivated() { base.OnPhotoModeDeactivated(); _gauntletLayer._gauntletUIContext.ContextAlpha = 1f; } }} OpponentHealthVM.cs using System;using TaleWorlds.Library;using TaleWorlds.MountAndBlade; namespace OpponentHealthBar{ public class OpponentHealthVM ViewModel { private Agent _opponent; private int _currentValue; private int _maxValue; private string _strValues; private bool _isVisible; private const string ValueFormat = "{0} / {1}"; public OpponentHealthVM() { _opponent = null; _strValues = string.Empty; _isVisible = false; } [DataSourceProperty] public int CurrentValue { get = _currentValue; private set { if (_currentValue != value) { _currentValue = value; OnPropertyChangedWithValue(value, "CurrentValue"); } } } [DataSourceProperty] public int MaxValue { get = _maxValue; private set { if (_maxValue != value) { _maxValue = value; OnPropertyChangedWithValue(value, "MaxValue"); } } } [DataSourceProperty] public string StrValues { get = _strValues; private set { if (_strValues != value) { _strValues = value; OnPropertyChangedWithValue(value, "StrValues"); } } } [DataSourceProperty] public bool IsVisible { get = _isVisible; set { if (_isVisible != value) { _isVisible = value; OnPropertyChangedWithValue(value, "IsVisible"); } } } // GUI に表示するデータの更新はこのメソッドで行います。 // ただし、ここに記述すればあとは勝手に更新されていくというものではないので、 // 自分が必要な場所では自分で呼び出さなければなりません。 public override void RefreshValues() { base.RefreshValues(); // 元々ローカル変数だったものを、View から参照できるようにプロパティに昇格しました。 CurrentValue = (int)Math.Ceiling(_opponent?.Health ?? 0f); MaxValue = (int)Math.Ceiling(_opponent?.HealthLimit ?? 0f); StrValues = string.Format(ValueFormat, CurrentValue, MaxValue); IsVisible = _opponent != null; } public void OnFocusChanged(IFocusable focusableObject) { // 適切な相手に表示されるよう条件を追加しました。 if (focusableObject is Agent possibleOpponent possibleOpponent.IsHuman possibleOpponent.IsEnemyOf(Agent.Main)) { _opponent = (_opponent == null || _opponent != possibleOpponent) ? possibleOpponent null; RefreshValues(); } } public void OnAgentHit(Agent affectedAgent) { if (_opponent == affectedAgent) { RefreshValues(); } } public void OnAgentRemoved(Agent removedAgent) { if (_opponent == removedAgent) { // 先にフォーカスロストが発生してここには入らないかも。 OnFocusChanged(removedAgent); } } }} OpponentHealthBar.xml Prefab Window FillBarWidget WidthSizePolicy="Fixed" HeightSizePolicy="Fixed" SuggestedWidth="430" SuggestedHeight="50" HorizontalAlignment="Center" VerticalAlignment="Top" MarginTop="250" ContainerWidget="FillBarContainer" FillWidget="FillVisualParent\FillVisual" MaxAmount="@MaxValue" InitialAmount="@CurrentValue" IsVisible="@IsVisible" Children Widget Id="FillVisualParent" WidthSizePolicy="Fixed" HeightSizePolicy="StretchToParent" SuggestedWidth="400" HorizontalAlignment="Center" VerticalAlignment="Center" MarginTop="10" MarginBottom="10" Sprite="BlankWhiteSquare" Color="#202020A0" Children BrushWidget Id="FillVisual" WidthSizePolicy="Fixed" HeightSizePolicy="StretchToParent" SuggestedWidth="400" HorizontalAlignment="Left" Brush="Mission.MainAgentHUD.HeroHealthBar.Fill" / /Children /Widget Widget Id="FillBarContainer" WidthSizePolicy="StretchToParent" HeightSizePolicy="StretchToParent" Sprite="options_memory_progress_frame" / TextWidget Text="@StrValues" WidthSizePolicy="CoverChildren" HeightSizePolicy="CoverChildren" HorizontalAlignment="Center" VerticalAlignment="Center" PositionYOffset="3" Brush="Tooltip.Text" Brush.FontSize="24" / /Children /FillBarWidget /Window /Prefab 名前
https://w.atwiki.jp/wot_tif/pages/22.html
MODは好きな様に入れてもいいのですが、公式が禁止しているMODがあります なお、ここに書いてあるMODを入れてる場合。BANされる可能性があります 1.レーザーサイトMOD 砲口から伸びる赤い線を表示し、どこを照準しているか一目瞭然になるため 2.フリーカメラMOD ※フリーカメラ≠ズームアウトMOD 自走砲でスナイパーモードにできたり、視点を自由に移動できるなど、ゲーム性が破綻するため こんなのやこんなの(YouTubeへリンクします) クソゲーである 3.モジュール当たり判定を3Dモデルで可視化するMOD 乗務員や弾薬庫などの当たり判定を表示させるため ・例 4.木の葉や茂みを消すMOD このムロバンカを見てくれ、こいつはどう思う すごく・・スッキリしてます・・ マジカルフォレストなんてなかった!! 5.ズームアウトMOD 自走砲視点でカメラを引ける範囲を拡張するMOD。(通常視点はセーフ) カウンターがやりやすくなるね、やったねt 6.トレーサーMOD 自走砲視点で、見えない敵の履帯跡を表示させるMODのこと