From e26acb0df8986915a73230f56bb8529746b6d291 Mon Sep 17 00:00:00 2001 From: toapuro <164835903+toapuro@users.noreply.github.com> Date: Fri, 6 Feb 2026 22:24:13 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat(mkdocs):=20=E5=85=A8=E3=83=9E=E3=83=BC?= =?UTF-8?q?=E3=82=AF=E3=83=80=E3=82=A6=E3=83=B3=E3=82=92=E7=B5=90=E5=90=88?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=83=95=E3=83=83=E3=82=AF=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/all.txt | 2896 ++++++++++++++++++++++++++++++++++++++++++++++ hooks/gather.py | 12 + mkdocs.yml | 3 + requirements.txt | 3 +- 4 files changed, 2913 insertions(+), 1 deletion(-) create mode 100644 docs/all.txt create mode 100644 hooks/gather.py diff --git a/docs/all.txt b/docs/all.txt new file mode 100644 index 0000000..bc066c7 --- /dev/null +++ b/docs/all.txt @@ -0,0 +1,2896 @@ + + +# [advanced\gradle.md] + +# Gradle + +Gradle とは、Javaビルドに使用されるツールです。 + +そして、そのGradleの設定ファイルを記述できるのがGroovy(もしくはKotlin DSL)です。 + +Groovyもプログラミング言語であるので、一つ一つ見ていくとブラックボックスではなくどれも意味があることがわかります。 + +1.20.1の Forge の [build.gradle](https://github.com/MinecraftForge/MinecraftForge/blob/1.20.1/mdk/build.gradle) を例に説明していきます。 + +コード中に出てくる `mod_id` や `minecraft_version` などの変数は、同階層にある `gradle.properties` ファイルで定義されている値を参照しています。 + +### プラグイン設定 + +```gradle title="build.gradle" +plugins { + id 'eclipse' // Eclipse IDE + id 'idea' // Intellij IDEA + id 'maven-publish' // Maven公開用 + id 'net.minecraftforge.gradle' version '[6.0,6.2)' // ForgeGradle +} +``` +Gradle プラグインという、Gradle を拡張するツールを記述する部分です。 + +マイクラの関連では ForgeGradle, MixinGradle 等があります。 + +例えば ForgeGradle では + +- Minecaft のソースコードのダウンロード +- 難読化の解除(リマップ) +- 開発用クライアントの起動設定 + +などの作業を、Gradle が自動で行ってくれます。 + +```gradle title="build.gradle" +minecraft { + mappings channel: mapping_channel, version: mapping_version +} +``` +ここは開発環境のマッピングを指定しています。 +[#マッピング](./mapping.md)で解説していますが、 + +クラス名やメソッド名、フィールド名等を読みやすくするための物です。 + +### 開発環境でのマイクラの設定 + +```gradle title="build.gradle" +minecraft { + runs { + configureEach { + workingDirectory project.file('run') + + property 'forge.logging.markers', 'REGISTRIES' + + property 'forge.logging.console.level', 'debug' + + mods { + "${mod_id}" { + source sourceSets.main + } + } + } + + client { + property 'forge.enabledGameTestNamespaces', mod_id + } + + server { + property 'forge.enabledGameTestNamespaces', mod_id + args '--nogui' + } + + gameTestServer { + property 'forge.enabledGameTestNamespaces', mod_id + } + + data { + workingDirectory project.file('run-data') + + args '--mod', mod_id, '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/') + } + } +} +``` + +ここは開発環境の実行の設定について書いてある部分です。 + +`client` なら `runClient` タスク、 `server` なら `runServer` タスクに対応します。他も同様です。 + +```gradle title="build.gradle" +sourceSets.main.resources { srcDir 'src/generated/resources' } +``` + +Datagen の生成結果をリソースとして追加する処理です。 + +```gradle title="build.gradle" +repositories { + maven { + url "https://cursemaven.com" + } +} + +dependencies { + minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" + + implementation fg.deobf("curse.maven:jei-238222:7391695") +} +``` + +依存関係を設定する部分です。 + +`dependencies` に記述された依存関係は、`repositories`ブロックの中に記述されたレポジトリから参照してきます。 + +この例では Cursemaven をリポジトリとして登録し、JEI を依存関係として登録しています。 + +詳しくは [#依存関係](../getting-started/dependency.md) を参照してください。 + +以下他の設定 +```gradle title="build.gradle" +/** + mods.tomlにあるテンプレートリテラルを実際の値に置き換える処理 +*/ +tasks.named('processResources', ProcessResources).configure { + var replaceProperties = [ + minecraft_version: minecraft_version, minecraft_version_range: minecraft_version_range, + forge_version: forge_version, forge_version_range: forge_version_range, + loader_version_range: loader_version_range, + mod_id: mod_id, mod_name: mod_name, mod_license: mod_license, mod_version: mod_version, + mod_authors: mod_authors, mod_description: mod_description, + ] + inputs.properties replaceProperties + + filesMatching(['META-INF/mods.toml', 'pack.mcmeta']) { + expand replaceProperties + [project: project] + } +} + +/** + Jarファイルのメタデータを設定 +*/ +tasks.named('jar', Jar).configure { + manifest { + attributes([ + 'Specification-Title' : mod_id, + 'Specification-Vendor' : mod_authors, + 'Specification-Version' : '1', // We are version 1 of ourselves + 'Implementation-Title' : project.name, + 'Implementation-Version' : project.jar.archiveVersion, + 'Implementation-Vendor' : mod_authors, + 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") + ]) + } + + finalizedBy 'reobfJar' +} + +/** + パブリッシング設定 +*/ +publishing { + publications { + register('mavenJava', MavenPublication) { + artifact jar + } + } + repositories { + maven { + url "file://${project.projectDir}/mcmodsrepo" + } + } +} + +/** + Javaコンパイル時のエンコーディングをUTF-8に設定 +*/ +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' +} +``` + + +# [advanced\mapping.md] + +# マッピング + +## 資料 +[Neoforge - What are Mappings](https://neoforged.net/personal/sciwhiz12/what-are-mappings/) + +[SpongePowered - MCP](https://docs.spongepowered.org/stable/en/plugin/internals/mcp.html) + +[マッピング - FabricMC](https://wiki.fabricmc.net/ja:tutorial:mappings) + +## 難読化とマッピング +Minecraft(Java版)のjarファイルは**難読化**されており、クラス名やメソッド名などが読解困難な名前になっています。 + +これらを開発者が理解できる名前に変換するための対応表を**マッピング**と呼びます。 + +Forgeでは、本番環境ではSRGマッピング、開発環境では指定したマッピングを使用します。 + +このマッピングの違いにより、Mixinなどの低レイヤー機能が開発環境と本番環境で異なる動作をすることがあります。 + +そのため開発環境だけでなく、ビルドしたjarファイルでの本番環境テストも必須です。 + +## SRG + +バージョン間で名前が変わってしまうため、共通化するのが目的の中間マッピングです。 + +`m_286052_`や`f_90981_`等のように表記されます。 + +## マッピングの種類(Forge) + +### MCP (Mod Coder Pack) +古くから使われているコミュニティ主導のマッピングです。SRG名を経由する仕組みが特徴です。 + +Forge等は内部処理でSRG名を使用しているため、エラーログ等で見かけることがあります。 + +### Official Mappings (Mojang Mappings) +Mojang公式のマッピングです。現在のMOD開発の主流ですが、引数名やローカル変数名までは復元されません。 + +### Parchment +Official Mappingsを拡張し、**引数名やローカル変数名を読みやすくした**マッピングです。 + +Official Mappingsと互換性があるため、開発の途中から導入しても基本的に問題ありません。 + +## Parchmentの導入方法(1.20.1) + +1. プロジェクト直下の `settings.gradle` に以下の maven リポジトリを追加します。 + ```diff title="settings.gradle" + pluginManagement { + repositories { + + maven { url = 'https://maven.parchmentmc.org' } + } + } + ``` +2. `build.gradle` でプラグインを適用します。 + ```diff title="build.gradle" + plugins { + id 'net.minecraftforge.gradle' version '[6.0.16,6.2)' + + id 'org.parchmentmc.librarian.forgegradle' version '1.+' + } + ``` + + !!! warning + 必ず `net.minecraftforge.gradle` の下に追加してください。 + +3. マッピング設定を変更します。 +```diff title="build.gradle" +minecraft { +- mappings channel: 'official', version: '1.20.1' ++ mappings channel: 'parchment', version: '2023.09.03-1.20.1' +} +``` +`mapping_channel` などと変数で指定されている場合は、`gradle.properties` から編集してください。 + +!!! note "バージョンの指定について" + + 上記は1.20.1の例(`2023.09.03-1.20.1`)です。 + [ParchmentMC 公式サイト](https://parchmentmc.org) や [Mavenブラウザ](https://ldtteam.jfrog.io/ui/native/parchmentmc-public/org/parchmentmc/data/) で、使用しているMinecraftバージョンに対応するParchmentバージョンを確認して設定してください。 + +4. Gradleの更新 + 設定を変更したら、Gradleプロジェクトをリフレッシュ、もしくは同期してください。IDE上で変更が反映されるはずです。 + +## Parchmentの導入方法(Neoforge) + +以下を参照してください。 + +https://docs.neoforged.net/toolchain/docs/parchment/ + + +# [getting-started\dependency.md] + +# 依存関係 + +他の Mod やライブラリをプロジェクトに読み込むための設定は `build.gradle` の `dependencies` ブロックで行います。 + +## 依存関係の構成 + +Gradle では依存関係を `dependencies` ブロックで指定します。 + +指定の方法はいくつかあります。 + +| 構成 | 説明 | 用途の例 | +| :--- | :--- | :--- | +| `implementation` | コンパイル時と実行時の両方で依存関係を必要 | 必須の前提 Mod、常時使用するライブラリ | +| `compileOnly` | コンパイル時にのみ必要 | コンパイルにだけ必要な API、注釈プロセッサ | +| `runtimeOnly` | コンパイル時には不要、実行時にのみ必要 | 連携確認用の Mod(JEI など)、コンパイルコードには触れないが動作確認に必要な Mod | +| `annotationProcessor` | コンパイル時の注釈処理に使用される | Mixin などのプロセッサ | + +## マッピングについて + +Minecraft の Mod は通常、難読化されています。 + +開発環境でこれらを扱うために、ForgeGradle は `fg.deobf` という特別なメソッドを提供しています。 + +これを依存関係の宣言時に噛ませることで、指定した Jar ファイルを開発環境のマッピングに合わせて再マッピングして読み込んでくれます。 + +```gradle title="build.gradle" +dependencies { + // JEI + implementation fg.deobf("curse.maven:jei-238222:4712866") +} +``` + +## Mod の依存関係を追加する + +### CurseMaven を使用する + +[CurseMaven](https://www.cursemaven.com/) は、CurseForge 上のファイルを Maven 依存関係として簡単に扱えるようにする非公式サービスです。 + +1. **リポジトリの追加** + + `repositories` ブロックに以下を追加します。 + ```gradle + repositories { + maven { + url "https://cursemaven.com" + content { + includeGroup "curse.maven" + } + } + } + ``` + !!! info + + content指定はなくても良いですが、Cursemavenへの無駄なリクエストを減らすことができます。 + +3. **依存関係の記述** + フォーマット: `curse.maven:-:` + * **description**: 任意の識別用文字列(実際の解決には使われません) + * **projectID**: CurseForge プロジェクト ID (About Project 欄などに記載) + * **fileID**: `Files` タブで特定のファイルを開いた際の URL 末尾の数字 + + ```gradle + dependencies { + // JEI の例 (Project ID: 238222, File ID: 4712866) + compileOnly fg.deobf("curse.maven:jei-238222:4712866") + runtimeOnly fg.deobf("curse.maven:jei-238222:4712866") + } + ``` + + 以上のように記述できますが、手間がかかります。 + + Curseforgeでファイルを開くと、`Curse Maven Snippet`という欄に依存関係として使用する記述があります。(例: [JEI](https://www.curseforge.com/minecraft/mc-mods/jei/files/7391695)) + + これをそのままコピーすると楽です。 + +### Modrinth Maven を使用する + +Modrinth も公式で Maven リポジトリを提供しています。([Modrinth Maven](https://support.modrinth.com/en/articles/8801191-modrinth-maven)) + +1. **リポジトリの追加** + + ```gradle + repositories { + exclusiveContent { + forRepository { + maven { + name = "Modrinth" + url = "https://api.modrinth.com/maven" + } + } + forRepositories(fg.repository) // Only add this if you're using ForgeGradle, otherwise remove this line + filter { + includeGroup "maven.modrinth" + } + } + } + ``` + +2. **依存関係の記述** + + フォーマット: `maven.modrinth::` + + * ProjectID: プロジェクトのID。URLにある。 + * Version: バージョン、ファイルを開いて`Version number`の欄にある + + 例: [JEI](https://modrinth.com/mod/jei/version/p7yZWpEg) + + ```gradle + dependencies { + implementation fg.deobf("maven.modrinth:jei:15.20.0.129") + } + ``` + +### ローカルの Jar ファイルを使用する + +Maven リポジトリに公開されていない Mod を開発環境に入れたい場合などは、プロジェクト内のフォルダから読み込むこともできます。 + +1. プロジェクトルートに `libs` フォルダを作成し、そこに `.jar` ファイルを入れます。 + +2. リポジトリの設定 + + ```gradle + repositories { + flatDir { + dirs "libs" + } + } + ``` +3. `dependencies` に以下のように記述します。 + + `group:name:version`の形式である必要があります。 + `group`は何でも良いので、ここでは`blank`としています。 + + ファイル名は `name-version.jar` の形式になっている必要があります。 + + ```gradle + dependencies { + // libs/jei-1.20.1-forge-15.20.0.129.jar + implementation fg.deobf("blank:jei-1.20.1-forge:15.20.0.129") + } + ``` + + +# [getting-started\mod-files.md] + +# Modファイル構造 + +最小構成テンプレートのファイルを解説します。 + +``` yaml +src +└─main + ├─java/com/example/examplemod + │ └─ExampleMod.java + │ + └─resources + └─META-INF + └─mods.toml + +gradle.properties +build.gradle +settings.gradle +.gitignore +``` + +## gradle.properties + +`build.gradle`等で使用する設定値を定義するファイルです。 + +テンプレートではModIDやModの名前を定義しています。 + +```properties title="gradle.properties" +minecraft_version=1.20.1 +forge_version=47.4.10 + +... + +mod_id=examplemod +mod_name=Example Mod +mod_license=All Rights Reserved +mod_version=1.0.0 +``` + +使用箇所(例) + +```gradle title="build.gradle" +dependencies { + minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" +} +``` + +## build.gradle + +ビルド処理の内容を定義するファイルです。 + +[#Gradle](../advanced/gradle.md) で詳しく解説しています。 + +### settings.gradle + +プロジェクト全体の設定を定義するファイルです。 + + +## ExampleMod.java + +Modの起点となるメインクラスです。 + + + +## mods.toml + +Modの情報をローダーに伝えるためのファイルです。 + +[Forge Wiki](https://docs.minecraftforge.net/en/1.20.1/gettingstarted/modfiles/) にフォーマットや詳しい情報があります。 + +ここにも `${...}` となっている部分がありますが、これは `gradle.properties` で定義した値を参照しています。 + +## .gitignore + +Git が管理対象から除外するファイルを定義するファイルです。 + +例えば、リポジトリには不要な `run` ディレクトリは管理対象から除外する必要があります。 +Gradleプラグインのリポジトリなどを記述します。 + +# [getting-started\mod.md] + +# Modクラス + +## 資料 + +- [Neoforge Wiki](https://docs.neoforged.net/docs/gettingstarted/modfiles#mod-entrypoints) +- [Forge Wiki (Newer)](https://docs.minecraftforge.net/en/latest/gettingstarted/modfiles/#mod-entrypoints) +- [Forge Wiki (Older)](https://docs.minecraftforge.net/en/1.20.1/gettingstarted/modfiles/) + +Modクラスは、Modの起点となるクラスで、必要なデータの初期化やレジストリ登録を行います。 + +=== "Neoforge/Forge1.20.1(47.3.10以降)" + + ```java title="ExampleMod.java" + @Mod(ExampleMod.MODID) + public class ExampleMod { + public static final String MODID = "examplemod"; + + public ExampleMod(FMLJavaModLoadingContext context) { + IEventBus modBus = context.getModEventBus(); + } + } + ``` + +=== "Forge(47.3.10以前)" + + ```java title="ExampleMod.java" + @Mod(ExampleMod.MODID) + public class ExampleMod { + public static final String MODID = "examplemod"; + + @SuppressWarnings("removal") + public ExampleMod() { + IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus(); + } + } + ``` + +!!! info + + 1.20.1以降で開発しているのであれば、基本的にコンストラクタに `FMLJavaModLoadingContext` を入れる書き方の方で大丈夫です。 + 元々Neoforgeの機能だったものがForge(47.3.10)で導入されています。 + + + +# [getting-started\setup.md] + +# 環境構築 + +## Intellijのセットアップ + +!!! info "エディタについて" + Eclipse IDE や VSCode でも開発できますが、この章では触れません。 + +1. **Intellij IDEAのインストール** + + [公式ダウンロードページ](https://www.jetbrains.com/ja-jp/idea/download/?section=windows) + +2. **日本語化 (任意)** + + Intellijを起動してランチャーメニューになったら、サイドバーのPluginsタブから {==Japanese Language Pack==} と検索してインストールします。 + インストール後に Intellij を再起動すれば日本語化が反映されます。 + ![](../../assets/intellij/intellij-japanese-ext.png) + +3. **Minecraft Development プラグインをインストール** + + 同じくPluginsタブから {==Minecraft Development==} と検索してインストールし、完了したら一度 Intellij を再起動してください。 + + +## 前提知識 + +スキップしても大丈夫ですが、迷ったときに軽く振り返ってもらえると理解しやすくなります + +| 用語 | 備考 | +| --- | --- | +| Mixin | これをオンにしない場合、依存関係を設定する時に面倒なので、オンにしておくことをおすすめします | +| License | その Mod の著作権の扱い方を宣言するもので、依存関係を設定する時、確認する必要があります | +| Group ID | パッケージの指定。ドメインを逆に書く。ドメインを持っていなければ `io.github.作者名` のような形式が良い | +| Package | パッケージ。クラスをグループで管理できる仕組みで、実態としてはフォルダとほぼ同じ | +| Artifact ID | 基本的にModのIDを書いておく | +| Class | Java でコードを書く基本単位。1クラス1ファイルで機能ごとに分割できる | + +**Modローダー** + +| プラットフォーム | 備考 | +| --- | --- | +| NeoForge | 1.20.4 以降はこちら。1.20.4 以降のバージョンを開発する、ほとんどの開発者が移行済み。 | +| Forge | 1.20.4 未満ならこちら | + +1.20.1は特別にNeoForgeとForgeどちらも対応しています + +## Mod開発環境のセットアップ + +いくつか方法があります + +NeoForgeであれば [1, (NeoForge/Fabric) Mod ジェネレータの利用](#1-neoforgefabric-mod-ジェネレータの利用) を推奨 + +Fabricであれば [1, (NeoForge/Fabric) Mod ジェネレータの利用](#1-neoforgefabric-mod-ジェネレータの利用) を推奨 + +Forgeであれば [4, (NeoForge/Forge/Fabric) Intellijプラグイン経由で生成](#4-neoforgeforgefabric-intellijプラグイン経由で生成) を推奨 + +### 1, (NeoForge/Fabric) Mod ジェネレータの利用 + +NeoForge (1.20.4 以降) +[NeoForge Mod Generator](https://neoforged.net/mod-generator/) + +Fabricならこちら +[Fabric Template](https://fabricmc.net/develop/template/) + +### 2, (NeoForge) テンプレートをクローン + +[NeoForge テンプレート一覧](https://github.com/orgs/NeoForgeMDKs/repositories) + +ここにあるリポジトリの中から対象のバージョンを探して、 +右上の Code->Download ZIP からダウンロード・解凍し、build.gradle を開けば IDE が立ち上がるはずです。 + +!!! tip + `git clone --depth 1 https://github.com/~~` でクローンしても大丈夫です(depth=1 は不要なコミットを省くため)。 + +### 3, (Forge) Forge MDK[^1]の利用 + +[Forge MDK](https://files.minecraftforge.net/net/minecraftforge/forge/) + +こちらからバージョンを選択して MDK をダウンロードし解凍、build.gradle を開きます。 + +!!! tip + LatestとRecommendedの違いは基本的にあまりありません。どちらでもよし。 + +[^1]: MDKはMod Developer Kitの略で、いわゆるテンプレートです。 + +### 4, (NeoForge/Forge/Fabric) Intellijプラグイン経由で生成 + +プロジェクトを新規作成するとき左下にあるジェネレータから Minecraft を選択し、各項目を入力して作成を押してください。 + +!!! note + JDKの指定が必要な場合は [Java JDK(Intellij IDE)](#java-jdkintellij-ide) を参考にしてみてください。 + +## Java JDK(Intellij IDE) + +JDK は Java を実行するためのキットのようなもの、と捉えてもらえればOKです。 + +以下のテーブルのように、マイクラバージョンごとに JDK が異なり、基本的に開発環境もこれに合わせます。 + +| MC バージョン | JDK バージョン | +| ------------- | -------------- | +| 1.16.x | JDK 8 | +| 1.17.x | JDK 16 | +| 1.18.x | JDK 17 | +| 1.19.x | JDK 17 | +| 1.20.x | JDK 17 | +| 1.21.x | JDK 21 | + +### ダウンロード + +Intellij であれば、左上の ≡ メニュー->ファイル->プロジェクト構成->プロジェクトと進み、SDK[^2]を指定できると思いますが、そこで{==JDKのダウンロード...==}を選択することでダウンロードできます。 + +![JDKのダウンロード](../../assets/intellij/jdk-download.png) + +バージョンは先程のテーブルを参考に設定してください。 + +ベンダー[^3]の選択ができると思いますが、特にこだわりがなければ {==JetBrains Runtime==} がおすすめです。 + +!!! info + Jetbrains Runtime ではホットスワップという、実行中にコードを変更して適用させられる機能が使えます。 + +また、デスクトップにJDKをいれておいても損はないです。 +面倒くさかったら必要になったときにダウンロードしておきましょう。 + +少ないですがいくつかベンダーを紹介しておきます。 + +- [Adoptium](https://adoptium.net/temurin/releases) + +- [OpenJDK](https://jdk.java.net/25/) + +[^2]: JavaではJDKのこと +[^3]: Java関連のベースキットを提供する企業やサービス + +## テンプレートの編集 + +ジェネレータや、プラグインから生成した場合でも変更する箇所があります。 + +`gradle.properties` に `mod_id` や、 `mod_name` 等がテンプレートのままなので、それを適切な値に変更してください + +`src/main/java`の下にある`*Mod.java`も同様に `MODID` を変更してください。 + +以下例 +```gradle +minecraft_version_range=[1.20.1] // 単一バージョンのみ対応の場合[バージョン]のように記述 +... +mod_id=modding_example // Mod ID +mod_name=ModdingExample // Mod名 +mod_license=MIT // 好きなライセンスを指定 +mod_group_id=dev.toapuro // グループID。#前提知識を参照 +mod_authors=toapuro, another_author // 作者一覧 +mod_description=A example mod // Modの説明 +``` + +分からない用語は [#前提知識](#前提知識) を参照してください + +## ビルド・実行の方法 + +右側にある以下のような gradle アイコンを押すと、ビルドに関連する操作ができます。 + +![](../../assets/intellij/gradle.png) + +`build.gradle`を変更した場合、サイドバーの左上にループするようなアイコンがあるので、それを押すと変更が適用されます。 + +jarへビルドする場合は `Tasks->build->build` + +開発環境で実際に動作を確認したいのであれば `Tasks->forgegradle runs->runClient` にあるかと思います。 + +初心者向けの解説は一旦ここまでです。 + +# [index.md] + +# Forge Modding Notes + +Minecraft Forge 1.20.1 のMod開発に関する情報をまとめたサイトです。 + +このサイトは公式ドキュメントの補完を目的としています。 + +基本的な内容については [NeoForge](https://docs.neoforged.net/docs/gettingstarted/) / [Forge](https://docs.minecraftforge.net/en/1.21.x/gettingstarted/) の公式ドキュメントを参照してください。 + +## フォーマット + +``のように`<>`で囲まれた部分は適切なものに置き換えてください。 + +## 初心者の方へ +[#環境構築](./getting-started/setup.md) +から始めてみてください。 + +# [mixin\index.md] + +# Mixin + +MinecraftまたはMODのコードを一部改変できる仕組みです。 + +## 概要 + +実際に何ができるかコードで解説します。 + +Mixinの対象クラス +```java title="SomethingLogic.java" +public class SomethingLogic { + public void process() { + System.out.println("Proceed"); + } +} +``` + +Mixinクラス +``` java title="MixinSomethingLogic.java" +// リマッピングオフでSomethingLogicにMixin +@Mixin(value = SomethingLogic.class, remap = false) +public class MixinSomethingLogic { + + // メソッドの最初に注入 + @Inject(method = "process()V", at = @At("HEAD")) + private void onProcess(CallbackInfo ci) { + System.out.println("Injected"); + } +} +``` + +このMixinがSomethingLogicに適用されると、**概念的には**[^1]以下のようになります。 +``` java title="SomethingLogic(Injected).java" +public class SomethingLogic { + public void process() { + onProcess(new CallbackInfo(...)) + System.out.println("Proceed"); + } + + private void onProcess(CallbackInfo ci) { + System.out.println("Injected"); + } +} +``` +[^1]: 実際はonProcessのメソッド名やアノテーションなどが異なる。 + +## Mixinセットアップ + +以下を`build.gradle`の冒頭に追加。 +```gradle title="build.gradle" +buildscript { + repositories { + maven { url = 'https://repo.spongepowered.org/repository/maven-public/' } + mavenCentral() + } + dependencies { + classpath 'org.spongepowered:mixingradle:0.7-SNAPSHOT' + } +} +``` + +以下を `plugins {}` ブロックの下に追加。 +```diff title="build.gradle" +plugins { + ... +} ++ apply plugin: 'org.spongepowered.mixin' +``` + +以下を`build.gradle`の`dependencies {}`ブロックの下に追加。 +```diff title="build.gradle" +dependencies { ++ annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' +} +``` + +以下を`src/main/resources/.mixins.json`に +```json title=".mixins.json" +{ + "required": true, + "minVersion": "0.8", + "package": ".mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [], + "client": [], + "server": [], + "injectors": { + "defaultRequire": 1 + } +} +``` +``や``は適切なものに置き換えてください。 + +`compatibilityLevel`はJDKバージョンに合わせましょう。(例は1.20.1) + +次に、`build.gradle`の`minecraft {}`ブロックの下に以下を追加。 +```gradle +mixin { + add sourceSets.main, "${mod_id}.refmap.json" + + config "${mod_id}.mixins.json" +} +``` +`${mod_id}`はそのままで大丈夫です + +## Mixinの使い方 + +実践的なコードですが、以下が参考になるかと思います。 + +[Mixin Examples - Fabric Wiki](https://wiki.fabricmc.net/tutorial:mixin_examples) + +### Mixinクラスの書き方 + +クラスの前に`@Mixin`を付けることでMixinクラスとなります。 + +```java +@Mixin(Example.class) +public class ExampleMixin { +} +``` + +その後、`src/main/resources/.mixins.json` にクラスを追加しなければいけません。 + +!!! warning + サーバー側ではクライアントクラスが読み込まれないので、クライアントとサーバーのMixinを分ける必要があります。 + +* `mixins`: クライアントとサーバー両方 +* `client`: クライアントのみ +* `server`: サーバーのみ + +それぞれのMixinに対応する場所にクラスを指定する必要があります。 + +クラスの指定は、`.mixins.json`で指定した`"package"`で指定したパッケージからの相対的な場所を指定します。 + +```java +package io.github.toapuro.example.mixins; + +@Mixin(Example.class) +public class ExampleMixin { +} +``` + +この例では以下のようになります +```json title=".mixins.json" +{ + "package": "io.github.toapuro.example.mixins", + "mixins": [ + "ExampleMixin" + ], + "client": [], + "server": [] +} +``` + +# [mixin\injections.md] + +# Mixin + +## インジェクション + +まずどこにコードを注入するか、どこのコードを改変するかを指定するための@Atを解説します + +### @At + +`@At` はどこにコードを注入するかを指定します。 + +`@At("HEAD")`のように使用します + +#### 引数 + +* `value`(必須): 場所の種類 +* `remap`: マッピングを適用するかどうか。バニラクラスではない場合`remap = false`を指定する必要があります。ここで指定した値はMixinクラス内のすべてのインジェクション、`@At`にも影響します。 +* `targets`: 対象のクラスが非公開な時や、コンパイル時存在しない場合にFQCN[^2]で指定します +* `priority`: Mixinの優先度。高いほど後に適用される。 +* `ordinal`: マッチした中で何番目か + +以下が`value`引数の取りうる主な値です。 + +| 値 | 説明 | +| :--- | :--- | +| `HEAD` | メソッドの最初 | +| `TAIL` | メソッドの最後の `return` の前 | +| `RETURN` | `return` 文の前 | +| `INVOKE` | 特定のメソッドを呼び出す前 (`target`引数必須) | +| `FIELD` | フィールドアクセス (`target`引数必須) | +| `STORE` | 変数代入 (`@ModifyVariable`のみ) | +| `LOAD` | 変数の取得 (`@ModifyVariable`のみ) | + +[^2]: FQCNは `パッケージ.クラス名` の形式でクラスを指定する。 + +以下詳細な説明が必要なものを解説します。 + +#### INVOKE + +`@At(value = "INVOKE", target = "")` + +このように使います。 + +`target`引数はメソッドのデスクリプタを指定します。 + +!!! info + + IntellijのMinecraft Developmentプラグインが補完してくれるので、 + すぐ覚える必要はないです。 + +??? デスクリプタの解説 + + **デスクリプタで指定するパッケージは全て区切り文字が`.`ではなく`/`であることに注意** + + **クラスの指定** + + クラスは + `Lパッケージ/クラス名;` + + このようなフォーマットで記述します。 + + 例えばオブジェクトであれば以下の様に記述します + + `Ljava/lang/Object;` + + **メソッドの指定** + + ```java + package io.github.toapuro.example; + + class Example { + public int add(int a, int b) + { + return a + b; + } + } + ``` + + `Lio/github/toapuro/example/Example;add(II)I` + + この様に対応します。 + + ```java + package io.github.toapuro.example; + + class Example { + public String concat(String a, String b) + { + return a + b; + } + } + ``` + + `Lio/github/toapuro/example/Example;concat(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;` + + この様に対応します。 + + なんとなくイメージできたかと思います。 + + | 型 | Descriptor | + | :--- | :--- | + | byte | B | + | char | C | + | double | D | + | float | F | + | int | I | + | long | J | + | short | S | + | boolean | Z | + | Object | Ljava/lang/Object; | + + 配列は`[`を先頭につけます。 + `int[]`であれば`[I`となります。 + + 後は`boolean` が `Z`、`long` が `J` であることに気をつけておけば大丈夫です。 + + +#### FIELD + +`@At(value = "FIELD", target = "")` + +このように使用します + +これもまたIntellijのMinecraft Developmentプラグインが補完してくれます。 + +??? フィールドのデクスリプタ指定 + + フォーマットは以下の通りです。 + + `Lパッケージ/クラス名;フィールド名:フィールド型` + + `Boolean.TRUE`であれば + + `Ljava/lang/Boolean;TRUE:Ljava/lang/Boolean;` + + となります + +### インジェクション + +インジェクションには種類が幾つかあります。 + +`@Inject`, `@Redirect`, `@ModifyArg`, `@ModifyArgs`, `@ModifyVariable`, `@ModifyConstant`, `@Overwrite` + +これらを使い分けることで、様々なコードの改変が可能になります。 + +| 名前 | 用法 | 説明 | +| :--- | :--- | :--- | +| `@Inject` | コールバック | 元のメソッドの特定位置に処理を挿入します。 | +| `@Redirect` | リダイレクト | メソッド呼び出しやフィールドアクセスなど、特定の処理を完全に置き換えます。 | +| `@ModifyArg` | 引数変更 | 他のメソッドを呼び出す際の、特定の引数を変更します。 | +| `@ModifyArgs` | 引数変更 | 他のメソッドを呼び出す際の、引数をまとめて変更します。 | +| `@ModifyVariable` | 変数変更 | メソッド内のローカル変数を変更します。 | +| `@ModifyConstant` | 定数変更 | メソッド内の定数値を変更します。 | +| `@Overwrite` | 上書き | メソッドの実装を完全に上書きします。競合のリスクが高いため、基本的には使用しません。 | + +引数や用法がほぼ同じものはまとめて解説します。 + +#### 基本的な使用法 + +元のメソッドの特定位置に処理を挿入します。 + +**`method`** + +基本、対象のメソッドを引数`method`で指定します。 + +デスクリプタで指定しますが、引数部分はオーバーロードがない場合省略できます。 + +またワイルドカードが使用できます。 + +**`at`** + +`@At`で指定します + +**`remap`** + +難読化を解除するかどうかのフラグです。 + +バニラクラスが対象ではない場合、`remap = false`を指定する必要があります。 + +`@Mixin`->`@インジェクション` -> `@At` の順番で `remap` は影響されていきます。 + +そのため `@Mixin` で `remap = false` を指定し、`@At` で難読化されたものを使用する場合、`@At` で `remap = true` を再指定する必要があります。 + +これは`@At`のtarget引数を指定する際にも気をつける必要があります。 + +難読化については [#マッピング](../advanced/mapping.md) で解説しています。 + +**`require`** + +対象が何個マッチする必要があるのかを指定します。 + +これを下回る個数しか存在しないような環境の場合、クラッシュします。 + +例えば`require = 0`では、対象が存在しなくてもクラッシュしません。 + +#### @Inject + +基本的に指定した場所の前にコードが注入されます。 + +引数は元のメソッドと`CallbackInfo`を組み合わせたものになります。 +返り値は`void`。 + +元のメソッドで`return`をしたい場合引数の`CallbackInfo ci`を使用して`ci.cancel()`と呼びます。 + +返り値がある場合、 +引数の`CallbackInfoReturnable<返り値の型> cir` + +!!! tips + + Mixinメソッドの引数や返り値は、Minecraft Developmentプラグインの修正機能(Alt+Enterなど)を使うことで自動生成できます。 + +特殊な引数 + +##### **`cancellable`** + +キャンセルできるかどうかを指定します。`CallbackInfo#cancel`や`CallbackInfoReturnable#setReturnValue`を使用する場合`true`を指定する必要があります。 + +##### **`locals`** + +ローカル変数に関して指定する引数。 + +主に以下3つの値を指定できます。(`LocalCapture`) + +* `NO_CAPTURE`: キャプチャしない(デフォルト) +* `CAPTURE_FAILSOFT`: キャプチャしますが、失敗した場合スキップ +* `CAPTURE_FAILHARD`: キャプチャしますが、失敗した場合クラッシュ + +[#MixinExtrasの利用](./mixin-extras.md) で解説する `@Local` を利用するとより可読性が高く、より安全です。 + +##### **`shift`** + +インジェクションの場所をちょっと調節できます。 + +基本指定した場所の前にインジェクションをしますが、`shift`を指定することで後ろにインジェクションをすることができます。 + +主に以下の4つの値を指定できます(`At.Shift`) + +* `NONE`: デフォルト +* `BEFORE`: 指定した場所の前 +* `AFTER`: 指定した場所の後ろ + +#### @Redirect + +対象を丸々置き換える。 + +!!! warning + 特にMixinが被った場合、競合しクラッシュするので使用は控えましょう。 + +[#MixinExtrasの利用](./mixin-extras.md) で解説する `@ModifyExpressionValue` で記述すると安全です。 + +#### @ModifyArg + +他のメソッドを呼び出す際の、特定の引数を変更します。 + +特殊な引数 + +##### **`index`** + +何番目の引数を変更するかを指定します。 + +基本 `void onCall(Args args)` です。 + +#### @ModifyArgs + +`@ModifyArg` をまとめて1メソッドで行うことができます。 + +メソッドの引数 `Args` を使用して操作します + +#### @ModifyVariable + +基本的に`@At("STORE")`や`@At("LOAD")`を指定します。 + +基本 `型 onCall(型 original)` です。 + +基本的に型でしか指定できないため、`index` や `name`、@Atの`ordinal` で正確に指定します。 + +引数(任意) + +* `index`: 変数の絶対インデックスを指定(バイトコードを見る必要がある) +* `name`: 変数の名前を指定 + +#### @Overwrite + +メソッドをすべて置き換え。 + +!!! danger + 互換性が無くなるので、他Modを一切考慮しない場合のみ使用してください。 + +`@Inject`の`HEAD`でキャンセルすると競合が発生せずに、同じ動作を再現できるので安全です。 + +### @Shadow + +Mixinクラス内部でのみ使用できる、対象クラスのフィールドやメソッドにアクセスするためのアノテーションです。 + +アクセス修飾子(`private`等)を無視してアクセスできます。 + +対象が `final` フィールドの場合 `@Final` アノテーションを付ける必要があります。 + +`@Mutable` を付けることによって `final` も無視して代入できるようになります。 + +!!! warning + + @Shadowのフィールドには `final` 修飾子を使用してはいけません[^3]。 + +[^3]: コンパイラの副作用が動作に影響するため + +```java +@Shadow +private int exampleValue; + +@Mutable +@Shadow @Final +private int finalExampleValue; +``` + +### @Unique + +Mixinクラスで使用でき、対象クラスに存在しないフィールドやメソッドを追加定義する際に使用します。 + +初期値を設定することもできます。 + +```java +@Unique +private int example$uniqueValue = 5; + +@Unique +private void example$uniqueMethod() { + +} +``` + +基本的に `$メソッド名` で記述するのが良しとされています。 + +Mixinは実行時、対象のクラスにマージされているので、メソッド名が被ってしまうと競合し、クラッシュする可能性があるためです。 + +[#Mixinトリック](./tricks.md) でこれを応用する方法を解説しています。 + +### アクセッサー(インターフェース) + +資料: [AccessorとInvoker - Fabric](https://wiki.fabricmc.net/ja:tutorial:mixin_accessors) + +アクセッサーとは、アクセス修飾子を無視してフィールドやメソッドにアクセスするために使用するMixinインターフェースです。 + +`@Shadow` とは違って、Mixinクラス以外からもアクセスできます。 + +アクセッサーは`interface`で定義します。 + +以下例 +```java +@Mixin(Example.class, remap = false) +public interface ExampleAccessor { + + @Accessor("exampleValue") + int getExampleValue(); + + @Mutable + @Accessor("exampleValue") + void setExampleValue(int exampleValue); + + @Invoker("handle") + void invokeHandle(int something); +} + +// 使用法 + +Example example = ...; + +((ExampleAccessor)(Object)example).setExampleValue(10); + +// もしくは + +ExampleAccessor accessor = (ExampleAccessor) example; +accessor.setExampleValue(10); +``` + +#### @Accessor + +フィールドの値を取得/設定できます。 + +**ゲッター(取得)** + +`型 メソッド名();`の形式で定義します + +メソッド名はなんでもいいですが、ゲッターメソッドの規則に従って`get`にしておきましょう。 + +**セッター(設定)** + +`void メソッド名(型);`の形式で定義します + +これも同様にメソッド名はセッターメソッドの規則に従って `set` にしておきましょう。 + +!!! warning メソッド名について + + 対象のクラスに存在するメソッド名を使用してはいけません。 + + +# [mixin\mixin-extras.md] + +## MixinExtrasの利用 + +Mixinでは難しい痒いところに手が届くようなライブラリです。 + +MixinExtrasのWikiは丁寧なので、そちらも参考にしてください。 + +[MixinExtras Wiki](https://github.com/LlamaLad7/MixinExtras/wiki) + +主に以下のような機能を提供します。 + +* 追加インジェクション +* 構文糖 +* 高度な@At指定 + +### 導入方法 + +[Setup - Wiki](https://github.com/LlamaLad7/MixinExtras?tab=readme-ov-file#setup) を参照してください。 + +!!! info "注意点" + + MixinExtrasを使用するとMixinExtrasがjarに埋め込まれ、Modの容量が増加するため、むやみに導入するのは控えましょう。 + +### 追加インジェクション + +**`@ModifyExpressionValue`** + +`@Redirect` をより安全に、より汎用に使えるようにしたもの。 + +非常に便利です。 + +対象の式の評価結果を改変できます。 + +```java +@ModifyExpressionValue( + method = "targetMethod", + at = @At(value = "INVOKE", target = "...") +) +private int modifyValue(int original) { + return original + 5; +} +``` + +**`@ModifyReceiver`** + +対象のメソッドコールのレシーバを改変する。 + +`receiver.call()` の `receiver` を改変できるということです + +書き方は省略します。 + +**`@ModifyReturnValue`** + +メソッドの戻り値を改変する。 + +`at` は `@At("RETURN")` を指定します。 + +非常に便利です。 + +```java +@ModifyReturnValue( + method = "targetMethod", + at = @At("RETURN") +) +private float halveSpeed(float original) { + return original / 2f; +} +``` + +**`WrapMethod`** + +メソッドの全てをラムダ式として覆い、操作できるもの。 + +try-catchで覆ってメソッドを実行し、例外を抑制したり、そのメソッドを複数回実行する等に使えます。 + +詳しくは[Wiki](https://github.com/LlamaLad7/MixinExtras/wiki/WrapMethod)で確認してください。 + +**`@WrapWithCondition`** + +ある関数呼び出しをif文で囲って、条件付きで実行させるもの。 + +詳しくは[Wiki](https://github.com/LlamaLad7/MixinExtras/wiki/WrapWithCondition)で確認してください。 + +### 構文糖 + +**`@Cancellable`** + +`@ModifyExpressionValue` 等の `CallbackInfo` を引数として取らないインジェクション関数でもキャンセル・返り値の操作が可能になるものです。 + +引数の最後に `@Cancellable CallbackInfo ci` を追加することで使用できます。(`CallbackInfoReturnable`も可能) + +**`@Local`** + +`@Inject` の `locals` をより便利にしたもの。 + +`@Inject` に限らず様々なインジェクションで使えます。非常に便利です。 + +これも引数の最後に使用します。 + +更に、`LocalRef` や `LocalIntRef`(プリミティブ) を型として使うことで、ローカル変数を操作することもできます。 + +引数(任意) +* `ordinal`: 何番目のローカル変数か +* `name`: 変数の名前 + +詳しくは[Wiki](https://github.com/LlamaLad7/MixinExtras/wiki/Local)で確認してください。 + +**`@Share`** + +Mixin同士でローカル変数を作成しシェアできる機能。 + +詳しくは[Wiki](https://github.com/LlamaLad7/MixinExtras/wiki/Share)で確認してください。 + +# [mixin\tricks.md] + +# Mixin + +## Mixinトリック + +### Mixinで継承する + +対象クラスの継承・実装しているインターフェースをMixinクラスでも継承することで、スーパークラスのメソッドやフィールド等にアクセスすることができます。 + +コンストラクタを生成する必要がありますが、無視されるため実装して大丈夫です。 + +!!! info + + 対象が抽象クラスの場合、メソッドを実装する必要が出てくるので、Mixinクラスも抽象クラスにしましょう。 + +```java +@Mixin(Example.class) +public abstract class ExampleMixin extends ExampleParent implements ExampleInterface { + + public ExampleMixin(...) { + super(...); + } +} +``` + +### Mixinでオーバーライドする + +Mixinクラスにあるメソッドやフィールドは基本的に対象クラスにマージされるため、そのままオーバーライドが可能です。 + +他Modが同じクラスで同じメソッドをオーバーライドした場合競合するため、あまりおすすめできません。 + +できれば [#ソフトオーバーライド](#ソフトオーバーライド) を使用してください。 + +```java +public class ExampleParent { + public void exampleMethod() { + System.out.println("Super"); + } +} + +public class Example extends ExampleParent { +} + +@Mixin(Example.class) +public class ExampleMixin extends ExampleParent { + @Override + public void exampleMethod() { + super.exampleMethod(); + System.out.println("Override"); + } +} +``` + +### Mixinでインターフェースを実装する + +Mixinクラスは対象クラスにマージされるため、独自インターフェースを実装することができます。 + +`@Unique` と組み合わせることで、クラスに任意のデータを付与しアクセスすることができます。 + +```java +public interface MyInterface { + void example$exampleMethod(); + String example$getExampleValue(); +} + +@Mixin(Example.class) +public class ExampleMixin implements MyInterface { + @Unique + private String example$exampleValue = "Hello World"; + + @Unique + @Override + public void example$exampleMethod() { + System.out.println("Mixin"); + } + + @Unique + @Override + public String example$getExampleValue() { + return example$exampleValue; + } +} + +// 使用法 +Example example = ...; +MyInterface myInterface = (MyInterface) example; + +myInterface.example$exampleMethod(); +System.out.println(myInterface.example$getExampleValue()); +``` + +### ソフトオーバーライド + +Mixinメソッドを継承して継承できるシステムを利用したものです。 + +詳しくは [Mixin Inheritance](https://wiki.fabricmc.net/tutorial:mixinheritance) を参照 + +`@SoftOverride` はなくても良いですが、有効か検証してくれるので書いておきましょう。 + +```java +public class ExampleParent { + protected void exampleMethod() { + System.out.println("Original"); + } +} + +public class Example extends ExampleParent { +} + +@Mixin(ExampleParent.class) +public class ExampleParentMixin { + + @Inject(method = "exampleMethod", at = @At("HEAD")) + protected void injectExampleMethod() { + } + + @Unique + protected void example$uniqueMethod() { + System.out.println("Unique"); + } +} + +@Mixin(Example.class) +public class ExampleMixin extends ExampleParentMixin { + + @Override + @SoftOverride + protected void injectExampleMethod() { + System.out.println("Mixin"); + } + + @Override + @SoftOverride + protected void example$uniqueMethod() { + System.out.println("Mixin"); + } +} +``` + +## 内部の仕組みについて + +**Java** + +* ラムダ式は `lambda$メソッド名$番号` のフォーマットの名前で関数に変換されます。 + +**Mixin** + +* Mixinクラスは対象のクラスにマージされる。 +* Mixinクラスは実行時にはロードできない(アクセッサーは可能)。 + + +# [registries\basic.md] + +# レジストリ + +レジストリとは、ID (`ResourceLocation`) とオブジェクト(アイテムやブロックなど)を紐づけて管理する仕組みです。 + +登録されたオブジェクトをレジストリと呼び分けるため、**レジストリエントリ**と呼称します。 + +レジストリエントリのIDは、同じレジストリの中では**一意**である必要があります。 + +どのようにレジストリに登録するか見てみましょう。 +例としてアイテムの登録を挙げてみます。 + +```java title="ModItems.java" +public class ModItems { + public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, ExampleMod.MODID); + + // "example_item"というIDとして仮登録 + public static final RegistryObject EXAMPLE_ITEM = ITEMS.register("example_item", () -> new Item(new Item.Properties())); +} +``` + +```java title="ExampleMod.java" +public class ExampleMod { + public static final String MODID = "examplemod"; + + // Neoforge / 1.20.1 Forge (3.10以降) (1) + public ExampleMod(FMLJavaModLoadingContext context) { + IEventBus modBus = context.getModEventBus(); + + // 登録 + ModItems.ITEMS.register(modBus); + } + + // else + @SuppressWarnings("removal") + public ExampleMod() { + this(FMLJavaModLoadingContext.get()); + } +} +``` + +1. コンストラクタについては [#Modクラス](../getting-started/mod.md) を参照 + +!!! warning + + `.register(modBus)` の書き忘れに注意! + +## 登録方法 + +### DeferredRegister + +`DeferredRegister` はレジストリの登録内容を保持して、レジストリが初期化される `RegistryEvent` のタイミングで登録します。 + +名前通り遅延して登録するものです。 + +```java +public static final DeferredRegister ITEMS = DeferredRegister.create( + // レジストリを指定 + ForgeRegistries.ITEMS, + // MODID + ExampleMod.MODID +); +``` + +第一引数は以下から指定できます。 + +- `ForgeRegistries.*`: Forgeで用意されているレジストリ +- `ForgeRegistries.Keys.*` +- `Registries.*`: バニラレジストリ + +`DeferredRegister` は情報を保持しているだけなので、最後に実際に `RegistryEvent` に紐づける必要があります。 + +```java +ModItems.ITEMS.register(modBus); +``` + +#### RegistryObject + +`DeferredRegister` が扱う、登録後に取得可能になるレジストリエントリへの参照を保持するラッパー。 + +```java +public static final RegistryObject EXAMPLE_ITEM = ITEMS.register("example_item", () -> new Item(new Item.Properties())); +``` + +`EXAMPLE_ITEM.get()` で実体を取得できます。 + +!!! danger + + 実際の登録前に中身にアクセスすると例外が発生するため注意です。 + + 例外: `Registry Object not present: ...` + +### RegisterEvent + +RegisterEvent を使ってレジストリに登録することも可能ですが、通常はあまり使用されません。 + +```java +@SubscribeEvent // Modイベントバスで登録 +public void register(RegisterEvent event) { + event.register( + ForgeRegistries.Keys.BLOCKS, + helper -> { + helper.register(ResourceLocation.fromNamespaceAndPath(MODID, "example_block_1"), new Block(...)); + helper.register(ResourceLocation.fromNamespaceAndPath(MODID, "example_block_2"), new Block(...)); + helper.register(ResourceLocation.fromNamespaceAndPath(MODID, "example_block_3"), new Block(...)); + // ... + } + ); +} +``` +!!! info + + `ResourceLocation.fromNamespaceAndPath` は Forge1.20.1(47.3.10) 以降または Neoforge でサポートされています。未サポートの場合 `new ResourceLocation` を利用してください。 + +参考: + +- [RegisterEvent - Forge](https://docs.minecraftforge.net/en/latest/concepts/registries/#registerevent) + +- [RegisterEvent - Neoforge](https://docs.neoforged.net/docs/concepts/registries#registerevent) + +## レジストリエントリの取得 + +特定のレジストリエントリを取得するためには対象の `ResourceLocation` が必要です。 + +逆の操作も可能で、レジストリエントリから `ResourceLocation` を取得することができます。 + +```java +// ResourceLocation -> Block +BuiltInRegistries.BLOCKS.get(ResourceLocation.fromNamespaceAndPath("minecraft", "stone")); + +// Block -> ResourceLocation +BuiltInRegistries.BLOCKS.getKey(Blocks.STONE); + +// 対象のエントリが存在するか +BuiltInRegistries.BLOCKS.containsKey(ResourceLocation.fromNamespaceAndPath("yourmod", "custom_block")) +``` + +!!! warning + + 必ずレジストリ初期化後に実行してください。 + +レジストリに登録されたすべてのエントリに対して処理をすることもできます。 + +```java +for (ResourceLocation id : BuiltInRegistries.BLOCKS.keySet()) { + // ... +} +for (Map.Entry, Block> entry : BuiltInRegistries.BLOCKS.entrySet()) { + // ... +} +``` + +## カスタムレジストリの作成 + +`RegistryBuilder` を使いレジストリの設定をします。 + +```java +private static final ResourceLocation CUSTOM_REGISTRY_ID = ResourceLocation.fromNamespaceAndPath("yourmodid", "custom_id"); +public static final RegistryBuilder CUSTOM_REGISTRY = RegistryBuilder.of(CUSTOM_REGISTRY_ID) + // 数値IDの割り当てられる範囲 + .setIDRange(0, Integer.MAX_VALUE) // = .setMaxID(Integer.MAX_VALUE) + + // クライアントとの数値IDの同期を無効化 + // .disableSync() + + // 存在しない場合に置き換えられるエントリ。任意 + .setDefaultKey(ResourceLocation.fromNamespaceAndPath("yourmodid", "empty")); +``` + +```java +@SubscribeEvent // Modバスに登録 +public static void registerRegistries(NewRegistryEvent event) { + event.register(CUSTOM_REGISTRY); +} +``` + +### データパックレジストリ + +データパックでエントリを追加することができるレジストリ。 + +```java +@SubscribeEvent // Modバスに登録 +public static void registerDatapackRegistries(DataPackRegistryEvent.NewRegistry event) { + event.dataPackRegistry( + ResourceKey.createRegistryKey(CUSTOM_REGISTRY_ID), + Spell.CODEC + ); + } +``` + + + +# [registries\item.md] + +# アイテム + +## 資料 + +- [Items - Neoforge](https://docs.neoforged.net/docs/items/) +- [Making Items - Forge Community](https://forge.gemwire.uk/wiki/Making_Items) + +## アイテムの追加 + +### レジストリに追加 + +[#レジストリ](./basic.md) を参考に、アイテムを登録します。 + +```java +public class ModItems { + public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, ExampleMod.MODID); + + public static final RegistryObject EXAMPLE_ITEM = ITEMS.register("example_item", () -> + new Item(new Item.Properties()) + ); +} +``` + +これは特に特別な特徴・機能を持たないアイテムを登録します。 + +### Item.Properties + +アイテムに様々な設定を付与するためのプロパティ群。 + +設定できるプロパティとして以下があります。 + +- `food`: 食料としての設定 +- `stacksTo`: 最大スタック数を設定 (必ず耐久値設定の前にする必要がある) +- `defaultDurability`: 未設定の場合のみ耐久値を設定 +- `durability`: 耐久値を設定 +- `craftRemainder`: クラフト時に残るアイテムを設定 +- `rarity`: レアリティを設定 +- `fireResistant`: 火に耐性を持つ +- `setNoRepair`: 修理不可 +- `requiredFeatures`: 追加するうえで必要な`実験的機能`のフラグ + +```java +public static final RegistryObject EXAMPLE_ITEM = ITEMS.register("example_item", () -> + new Item(new Item.Properties().stacksTo(1).defaultDurability(1024).rarity(Rarity.UNCOMMON)) +); +``` + +#### FoodProperties + +`food(FoodProperties)` で指定するプロパティ群。 + +`new FoodProperties.Builder()` でビルダーを生成し、設定後に `.build()` を呼び出す必要があります。 + +設定できるプロパティとして以下があります。 + +- `nutrition`: 回復する満腹ポイントの数を設定 +- `saturationMod`: 隠し満腹度の計算に使用される値。計算式は `min(2 * nutrition * saturationMod, プレイヤーの満腹度)` +- `meat`: 肉かどうか。狼が食べるかどうかに影響する +- `alwaysEat`: 満腹度が最大でも食べられるようにする +- `fast`: 食べる時間を短くする +- `effect`: 食べたときに付与される可能性のあるエフェクト + +バニラの例 +```java +public static final FoodProperties APPLE = new FoodProperties.Builder() + .nutrition(4) + .saturationMod(0.3F) + .build(); + +public static final FoodProperties CHICKEN = new FoodProperties.Builder() + .nutrition(2) + .saturationMod(0.3F) + .effect( + new MobEffectInstance( + MobEffects.HUNGER, + 600, // 持続時間 + 0 // 強さ + ), + 0.3F // 確率 + ) + .meat() + .build(); +``` + +### Itemクラス + +アイテムの実際の動作が定義されているクラス。 + +Modでよく利用されるバニラのアイテム継承クラスは以下の通りです。 + +ツール・武器・装備 + +- `SwordItem` +- `BowItem` +- `PickaxeItem` +- `AxeItem` +- `ShovelItem` +- `HoeItem` +- `FishingRodItem` +- `CrossbowItem` +- `TridentItem` +- `ShieldItem` +- `ArmorItem` + +その他 + +- `BlockItem` +- `PotionItem` +- `BucketItem` +- `SpawnEggItem` + +独自に機能を追加したい場合、`Item` かそれらの継承クラスを継承してオーバーライドなどで実装します。 + +```java +class ExampleItem extends Item { + public ExampleItem(Item.Properties properties) { + super(properties); + } + + /** + * 右クリック時の処理 + */ + @Override + public InteractionResultHolder use(Level level, Player player, InteractionHand hand) { + // ... + + /* + InteractionResultHolder.success: アクション成功 + InteractionResultHolder.consume: アクション処理済み + InteractionResultHolder.fail: アクション失敗 (次のuseコールバックに移る) + InteractionResultHolder.pass: 続行 (次のuseコールバックに移る) + */ + return InteractionResultHolder.success(player.getItemInHand(hand)); + } + + /** + * ホバーテキスト + */ + @Override + public void appendHoverText(ItemStack stack, @Nullable Level level, List tooltip, TooltipFlag flag) { + tooltip.add(Component.translatable("tooltip.examplemod.example_item")); + } +} + +``` + +## ItemStack + +状態を含んだ変更可能なアイテム1枠分の実態データ。 + +ItemStackは以下を持ちます: + +- `Item item`: アイテムの種類 +- `int count`: スタック数 +- `CompoundTag tag`: NBTタグ + +例えば、インベントリの1スロットなどや、手に持っているアイテムなども `ItemStack` で表現されます。 + +## クリエイティブタブへの追加 + +### 既存のクリエイティブタブ + +イベントを使用して、既存のクリエイティブタブにアイテムを追加します。 + +```java +@Mod.EventBusSubscriber(modid = ExampleMod.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) +public class ModCreativeTabs { + @SubscribeEvent + public static void buildContents(BuildCreativeModeTabContentsEvent event) { + if (event.getTabKey() == CreativeModeTabs.INGREDIENTS) { + event.accept(ModItems.EXAMPLE_ITEM); + } + } +} +``` + +### カスタムクリエイティブタブ + +レジストリを使用して独自クリエイティブタブを追加し、アイテムを追加します。 + +```java +public static final DeferredRegister CREATIVE_TABS = + DeferredRegister.create(Registries.CREATIVE_MODE_TAB, ExampleMod.MODID); + +public static final RegistryObject EXAMPLE_TAB = CREATIVE_TABS.register("example", () -> CreativeModeTab.builder() + .title(Component.translatable("item_group." + ExampleMod.MODID + ".example")) + .icon(() -> new ItemStack(ModItems.EXAMPLE_ITEM.get())) + .displayItems((params, output) -> { + output.accept(ModItems.EXAMPLE_ITEM.get()); + // ... + }) + .build() +); +``` + +最後に、通常の通りレジストリを紐づけます。 + +```java +CREATIVE_TABS.register(modBus); +``` + + + +## リソースの追加(モデルやテクスチャ) + +アイテムを追加しただけでは、テクスチャやモデルが存在せず、`Missing Texture` が表示されます。 + +手動で作成する場合の配置場所: + +- モデル定義: `assets//models/item/.json` +- テクスチャ: `assets//textures/item/.png` + +`texture_name` はよく `item_id` と一致させることが多いです。 + +# [rendering\api.md] + +# レンダリングAPI + +## 座標変換と回転行列 + +### PoseStack + +座標変換(移動・回転・拡大縮小)を管理するスタックです。 + +これによってプレイヤーの手に持った剣だけを回転させる、といったことが可能になります。 + +| メソッド名 | 説明 | +| --- | --- | +| `push` | 現在の状態(位置・回転)を保存する。ここから局所的な作業を始める合図。 | +| `translate` | 座標を移動する。 | +| `scale` | 座標を拡大縮小する。 | +| `rotate` | 座標を回転する。 | +| `pop` | 保存した状態に戻す。作業終了。 | + +!!! warning + + `push` と `pop` は必ずセットで行ってください。 + +### Quaternion + +回転を表す数学的概念。 + +`Axis.YP.rotationDegrees(90)` のように軸を指定して `Quaternion` を作成し、`PoseStack` に適用できます。 + +```java +// Y軸中心に+90度回転します +poseStack.mulPose(Axis.YP.rotationDegrees(90)); +// Y軸中心に-90度回転します +poseStack.mulPose(Axis.Y.rotationDegrees(90)); +``` + +## 描画バッファ + +### MultiBufferSource(BufferSource) + +VertexConsumer を RenderType ごとに振り分け、結果的にバッチレンダリングを行うためのクラスです。 + +複数の描画をまとめて処理できるため、レンダリング効率が向上します。 + +`MultiBufferSource#getBuffer(RenderType)` を呼び出すことで、指定した RenderType に対応する VertexConsumer を取得できます。 + +異なる RenderType が渡された場合、それまでのバッチは `endBatch` によって終了され、その時点で描画が実行されます。 + +!!! warning + + バッチが終了したタイミングで描画されるため、`endBatch` が呼ばれない限り描画されません。(GuiGraphics等は自動で`endBatch`を呼ぶ) + +### Tesselator + +即時レンダリング用のクラス。 + +`MultiBufferSource` とは異なり、自動的にバッチレンダリングはされない。 + +`Tesselator.getInstance()` で取得し、 +`.getBuilder(RenderType)` で `VertexConsumer` を取得する。 + +`.end` で明示的に描画します。 + +### VertexConsumer(BufferBuilder) + +基本的な描画は `VertexConsumer` というインターフェースを通して頂点を登録します。 + +頂点は以下の値を持ちます。 + +`RenderType` で指定されている `VertexFormat` によって必要な変数が異なります + +[#VertexFormat](./render-options.md#vertexformat) を参照 + +!!! warning + + `VertexFormat` で指定されている順番通りに `VertexConsumer` に頂点情報を渡す必要があります。 + +!!! danger + + 必要な変数を設定しない場合クラッシュします。 + +| 変数名 | 説明 | +| --- | --- | +| `x` | X座標 | +| `y` | Y座標 | +| `z` | Z座標 | +| `red` | 赤成分 | +| `green` | 緑成分 | +| `blue` | 青成分 | +| `alpha` | 透明度(float0.0~1.0, int0~255) | +| `texU` | テクスチャ座標(U) | +| `texV` | テクスチャ座標(V) | +| `overlayUV` | オーバーレイUV座標(被ダメージ時の赤色等)(パック済み) | +| `lightmapUV` | ライトマップUV | +| `normalX` | 法線ベクトルX | +| `normalY` | 法線ベクトルY | +| `normalZ` | 法線ベクトルZ | + +!!! info + + 線は特殊で、視点から終点へ向かう方向を表すベクトルとして設定する必要があります。 + +```java +public static void drawHorizontalQuad( + PoseStack poseStack, + MultiBufferSource buffer, + float x0, float y, float z0, + float width, float depth, + float u, float v, + int r, int g, int b, int a +) { + Matrix4f matrix4f = poseStack.last().pose(); + + // 単色 + VertexConsumer consumer = buffer.getBuffer(RenderType.lightning()); + + float x1 = x0 + width; + float z1 = z0 + depth; + + consumer.vertex(matrix4f, x0, y, z0) + .color(r, g, b, a) + .endVertex(); + + consumer.vertex(matrix4f, x1, y, z0) + .color(r, g, b, a) + .endVertex(); + + consumer.vertex(matrix4f, x1, y, z1) + .color(r, g, b, a) + .endVertex(); + + consumer.vertex(matrix4f, x0, y, z1) + .color(r, g, b, a) + .endVertex(); +} + +// 線の描画 +public static void drawLine( + PoseStack poseStack, + MultiBufferSource buffer, + float x1, float y1, float z1, + float x2, float y2, float z2, + int r, int g, int b, int a +) { + Matrix4f matrix4f = poseStack.last().pose(); + Matrix3f matrix3f = poseStack.last().normal(); + + VertexConsumer consumer = buffer.getBuffer(RenderType.lines()); + + float dx = x2 - x1; + float dy = y2 - y1; + float dz = z2 - z1; + + consumer.vertex(matrix4f, x1, y1, z1) + .color(r, g, b, a) + .normal(matrix3f, dx, dy, dz) + .endVertex(); + + consumer.vertex(matrix4f, x2, y2, z2) + .color(r, g, b, a) + .normal(matrix3f, dx, dy, dz) + .endVertex(); +} +``` + +## テクスチャアトラス + +### 資料 + +- [Minecraft Wiki](https://ja.minecraft.wiki/w/テクスチャ#テクスチャアトラス) + +OpenGLではテクスチャを切り替える(Bind)処理は比較的重い処理です。 + +そのため、Minecraftでは大量のブロックやアイテムのテクスチャを**1枚の巨大な画像**にまとめて扱うことで、描画時の切り替えコストを削減しています。 + +このまとめられた1枚の画像を**テクスチャアトラス**と呼びます。 + +!!! tips + + エンティティのテクスチャはテクスチャアトラスを使用していません。 + +### 種類 + +以下の静的なテクスチャアトラスが存在します。 + +| 参照 | ID | ディレクトリ | 説明 | +| --- | --- | --- | --- | +| `Sheets.BANNER_SHEET` | banner_patterns | entity/banner_base, entity/banner/\* | バナー | +| `Sheets.BED_SHEET` | beds | entity/bed/\* | ベッド | +| `Sheets.CHEST_SHEET` | chests | entity/chest/\* | チェスト | +| `Sheets.SHIELD_SHEET` | shield_patterns | entity/shield_base, entity/shield_base_nopattern, entity/shield/\* | シールド | +| `Sheets.SIGN_SHEET` | signs | entity/signs/\* | 看板 | +| `Sheets.SHULKER_SHEET` | shulker_boxes | entity/shulker/\* | シャーカーボックス | +| `Sheets.ARMOR_TRIMS_SHEET` | armor_trims | 特殊[^1] | アーマートリム | +| `Sheets.DECORATED_POT_SHEET` | decorated_pot | entity/decorated_pot/\* | 飾り壺 | +| `TextureAtlas.LOCATION_BLOCKS` | blocks | block/\*, item/\*, entity/conduit, ... | ブロック | +| `TextureAtlas.LOCATION_PARTICLES` | particles | particle/\* | パーティクル | +| `textures/atlas/paintings.png` | paintings | painting/\* | 絵画 | +| `textures/atlas/mob_effects.png` | mob_effects | mob_effect/\* | モブエフェクト | + +比較的汎用に使えるのは `TextureAtlas.LOCATION_BLOCKS` です。 + +[^2]: `paletted_permutations` を使用している。`PalettedPermutations` を参照。 + +## RenderType + +[#RenderType](./render-options.md#rendertype) で解説しています。 + +# [rendering\basic.md] + +# レンダリング (1.20.1) + + + +解説する必要のある事柄が多すぎるため、レンダリングするうえでの最小限の知識を解説しています。 + +## 技術スタック + +### GPU + +GPUで描画されている。 + +### OpenGL + +グラフィックスドライバと通信するための規格。 + +ステートマシンとなっているため、設定した値は戻すまで状態が維持されます。 + +そこでバニラのメソッドはうまくラッピングし、カバーしています。 + +### LWJGL + +MinecraftはLWJGLというライブラリを経由してOpenGLで描画しています。 + +基本的にはOpenGLのコマンドを直接叩くことは少なく、用意されたAPIを通して描画します。 + +### Blaze3D + +MinecraftがOpenGLを直接叩く代わりに用意した静的クラス群。 + +Mod制作では、GLコマンドを直接叩くのではなく、用意されたAPI(RenderSystem等)を通して描画します。 + +### JOML + +数学ライブラリとしては主にJOMLが使用されています。 + +## 座標系 + +座標系は大きく3つあり、それぞれ異なる用途を持っています。 +(Minecraftにおいて明確に定義されているわけではない) + +それらの座標系は主に `PoseStack` によって操作、管理されています。 +([レンダリングAPI](./api.md#posestack)で解説) + +様々な座標系がありますが、結果的にはスクリーン座標系に変換され描画されます。 + +### ワールド座標系 + +ゲーム内のブロックやエンティティが存在する絶対的な座標。 + +ゲーム内で通常使うXYZ座標がこれにあたります。 + +**軸** + +- X+: 東 (East) +- Y+: 上 (Up) +- Z+: 南 (South) + +### ビュー座標系 + +カメラを中心とした座標系。 + +### スクリーン座標系 + +インベントリ画面やHUD(ホットバー、体力等)を描画する際の2D座標。 + +**原点(0,0)**: 画面の左上隅。 + +**軸** + +- X+ 右へ +- Y+ 下へ (ワールド座標とYの向きが逆なので注意!) +- Z+: 奥(深度+) + +### ローカル座標系 + +個々のオブジェクトを中心とした相対的な座標系。 + +### 座標変換 + +基本的に以下の流れで座標変換されます。 + +``` mermaid +graph LR + L[ローカル座標系] --> W[ワールド座標系]; + W --> V[ビュー座標系]; + V --> S[スクリーン座標系]; +``` + +## テクスチャ座標(UV) + +モデルの頂点には、テクスチャのどの部分を貼り付けるかという情報(**UV**)を持たせます。 + +スクリーン座標系と同じ軸と原点を持っていて、U,Vどちらも範囲は0.0~1.0です。 + +- 開始位置: (u0, v0) +- 終了位置: (u1, v1) + +を持ちます。 + +!!! info + + JSONモデルでは、UVはピクセル単位で指定しますが、シェーダーに送られる際には、テクスチャアトラス内での位置として変換されたUV値として扱われます。 + +## 描画方法 + +### 頂点 + +OpenGLにおいて、すべての物体は頂点の集まりによって構成されています。 + +頂点とは、3Dモデルを構成する最小単位の点のことです。 + +**主な値(Minecraftにおいて)** + +- Position: 位置情報 +- Color: 色情報 +- UV: テクスチャ座標 +- Normal(法線): 光の当たり方等を計算するために必要 +- ライトマップ: 光のテクスチャ +- オーバーレイテクスチャ: ダメージ表現やTNTの点滅等に使われるテクスチャのUVの指定 + +## レンダリングパイプライン + +### ラスタライズ + +頂点で構成された図形を、フラグメントに変換する処理。 + +塗りつぶすピクセルごとに頂点の情報(色、UV、法線等)を線形補間して割り当てます。 + +### フラグメント(Fragment) + +ピクセルになる前の計算途中のデータです。 + +ピクセル座標、深度値、テクスチャ座標等、線形補間されたデータを持ちます。 + +### Fragment Shader + +フラグメントシェーダーは、フラグメントの情報を元に、最終的な色を決定するシェーダープログラムです。 + +つまり、描画する色を決定するシェーダープログラムです。 + +例 + +```glsl title="position_color.fsh" +#version 150 + +in vec4 vertexColor; + +uniform vec4 ColorModulator; + +out vec4 fragColor; + +void main() { + vec4 color = vertexColor; + if (color.a == 0.0) { + discard; + } + fragColor = color * ColorModulator; +} +``` + +### Vertex Shader + +頂点シェーダーは、頂点の情報を元に、最終的な位置を決定するシェーダープログラムです。 + +例 + +```glsl title="position_color.vsh" +#version 150 + +in vec3 Position; +in vec4 Color; + +uniform mat4 ModelViewMat; +uniform mat4 ProjMat; + +out vec4 vertexColor; + +void main() { + gl_Position = ProjMat * ModelViewMat * vec4(Position, 1.0); + + vertexColor = Color; +} +``` + +# [rendering\render-options.md] + +# 描画設定 + +## RenderType + +描画についての設定。 + +半透明処理や深度テストの挙動を決めます。 + +`RenderType` 内に定数として存在します。 + +詳しくはクラスを参照してください。 + +### RenderTypeでよく使われる用語 + +| RenderType | 説明 | 備考 | +| --- | --- | ---- | +| `solid` | 不透明 | +| `cutout` | 切り抜き | アルファ値が閾値(通常0.1)を下回るピクセルを破棄し、それ以外を不透明として描画する | +| `mipped` | ミップマップ | 距離に応じてテクスチャ解像度を下げる。遠景のノイズを防ぐ | +| `translucent` | 半透明 | 深度書き込みは無効 | +| `entity` | テクスチャやテクスチャアトラスを指定できる | +| `text` | テキスト | GUIやネームタグのテキスト描画用 | +| `lines` | 線 | +| `gui` | GUI | + +### 独自RenderTypeの作成 + +バニラのRenderTypeに必要な設定がない場合作成します。 + +**RenderType.create** + +- `VertexFormat`: 頂点フォーマット。大体 `DefaultVertexFormat` から指定 +- `VertexFormat.Mode`: 頂点モード +- `bufferSize`: レンダリングバッファサイズ。頻度や内容によるが小規模なら256で十分 +- `RenderType.CompositeState`: `CompositeStateBuilder#createCompositeState` でビルドし渡す + +**CompositeStateBuilder** + +`RenderType.CompositeState.builder()` で作成する。 + +- `setTextureState`: [テクスチャアトラス](./api.md#テクスチャアトラス)の指定 +- `setShaderState`: シェーダーの指定 +- `setTransparencyState`: 半透明の処理方法([#ブレンドモード](#ブレンドモード))の指定 +- `setDepthTestState`: 深度テストの指定 +- `setCullState`: カリングの指定(`CULL`:背面を描画しない) +- `setLightmapState`: [ライトマップ](#ライトマップ)の適用の有無 +- `setOverlayState`: [オーバーレイ](#オーバーレイテクスチャ)の適用の有無 +- `setLayeringState`: レイヤリング(ポリゴンオフセットなど)の指定 +- `setOutputState`: 出力先[レンダーターゲット](#レンダーターゲット)の指定 +- `setTexturingState`: テクスチャの準備の指定 +- `setWriteMaskState`: 書き込むバッファのマスク設定(カラーバッファや深度バッファ) +- `setLineState`: 線の太さを指定 +- `setColorLogicState`: 色調整ののモードを指定 +- `createCompositeState`: 設定をビルドする + +バニラのRenderTypeの例 + +```java +// RenderType.SOLID +private static final RenderType SOLID = RenderType.create( + "solid", + DefaultVertexFormat.BLOCK, + VertexFormat.Mode.QUADS, + 2097152, + true, + false, + RenderType.CompositeState.builder() + .setLightmapState(LIGHTMAP) + .setShaderState(RENDERTYPE_SOLID_SHADER) + .setTextureState(BLOCK_SHEET_MIPPED) + .createCompositeState(true) +``` + +## VertexFormat + +GPUに送る情報のレイアウトを決定するためのフォーマット。 + +各要素は Vertex Shader の `attribute` 変数に対応します。 + +基本、`DefaultVertexFormat`から取得します。 + +!!! warning + + `VertexFormat` で指定されている順番通りに `VertexConsumer` に頂点情報を渡す必要があります。 + +**DefaultVertexFormat** の定数 + +| 名前 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | +| --- | --- | --- | --- | --- | --- | --- | --- | +| `BLIT_SCREEN` | Position | UV | Color | +| `BLOCK` | Position | Color | UV0 | UV2 | Normal | Padding | +| `NEW_ENTITY` | Position | Color | UV0 | UV1 | UV2 | Normal | Padding | +| `PARTICLE` | Position | UV0 | Color | UV2 | +| `POSITION` | Position | +| `POSITION_COLOR` | Position | Color | +| `POSITION_COLOR_NORMAL` | Position | Color | Normal | Padding | +| `POSITION_COLOR_LIGHTMAP` | Position | Color | UV2 | +| `POSITION_TEX` | Position | UV0 | +| `POSITION_COLOR_TEX` | Position | Color | UV0 | +| `POSITION_TEX_COLOR` | Position | UV0 | Color | +| `POSITION_COLOR_TEX_LIGHTMAP` | Position | Color | UV0 | UV2 | +| `POSITION_TEX_LIGHTMAP_COLOR` | Position | UV0 | UV2 | Color | +| `POSITION_TEX_COLOR_NORMAL` | Position | UV0 | Color | Normal | Padding | + +`"UV0"` と `"UV"` は同値である。 + +**VertexFormatの要素** + +| 名前 | 説明 | 対応するメソッド(VertexConsumer) | +| --- | --- | --- | +| Position | 頂点座標 | `vertex(x, y, z)` | +| Color | 色 | `color(r, g, b, a)` | +| UV0 | テクスチャUV | `uv(u, v)` | +| UV1 | オーバーレイUV | `overlayCoords(u, v)` | +| UV2 | ライトマップUV | `uv2(u, v)` | +| Normal | 法線 | `normal(x, y, z)` | +| Padding | 余白 | + +## ブレンドモード + +ブレンドモードとは、透明度によってどのように描画色を決定するかの設定の事です。 + +`RenderStateShard` の中の定数としていくつか存在する。 + +- `src`: ソースのRGB色(描画対象) +- `src.a`: ソースの透明度 +- `dst`: デスティネーションのRGB色(既に描画されている色) +- `dst.a`: デスティネーションの透明度 + +| 名称 | 説明 | 式 | 備考 | +| --- | ---- | ---- | --- | +| `NO_TRANSPARENCY` | 不透明 | +| `ADDITIVE_TRANSPARENCY` | 加算合成 | `src + dst` | alpha無視 | +| `LIGHTNING_TRANSPARENCY` | 発光合成 | `src * src.a + dst` | +| `GLINT_TRANSPARENCY` | エンチャントの輝き | `rgb = src * src.a + dst, a = dst.a` | +| `CRUMBLING_TRANSPARENCY` | 破壊表現の合成 | `rgb = 2 * src * dst, a = src.a` | +| `TRANSLUCENT_TRANSPARENCY` | 半透明合成 | `rgb = src * src.a + dst * (1 - src.a), a = src.a + dst.a * (1 - src.a)` | + +## 深度 + +奥行きのこと。 +描画順にかかわらず、奥のオブジェクトが手前のオブジェクトに常に隠れるようにするために存在する。 + +**深度バッファ**というものが存在し、各ピクセルに深度が保存されています。 + +### 深度テスト + +深度値と既存の深度バッファ内の値を比較し、判定をパスしたピクセルのみを書き込むことで、前後関係を再現するためのもの。 + +#### 比較関数 + +| 名称 | 説明 | +| --- | --- | +| `NO_DEPTH_TEST` | 深度テストを行わない | +| `EQUAL_DEPTH_TEST` | 深度値が対象と等しい場合のみ描画 | +| `LEQUAL_DEPTH_TEST`(既定値) | 深度値が対象以下の場合のみ描画 | +| `GREATER_DEPTH_TEST` | 深度値が対象より大きい場合のみ描画 | + +## カリング + +裏面を描画するかどうか。 + +- `CULL`: 裏面を描画しない +- `NO_CULL`: 裏面を描画する + +## ライトマップ + +ブロックライト(U)とスカイライト(V)を組み合わせた結果の明るさの色をキャッシュした16x16のテクスチャ。 + +実際に頂点に格納するUVの値は圧縮されており、 + +その値は `LightTexture.pack(int blockLight, int skyLight)` で指定する値を取得できます。 + +## オーバーレイテクスチャ + +汎用なオーバーレイ用の16x16のテクスチャ。 + +ライトマップと同じく頂点に格納するUVの値は圧縮されており、 + +`OverlayTexture.pack(int u, int v)` で指定する値を取得できます。 + +以下の用途で使われています。 + +- 負傷時/死亡時の赤色表示 +- クリーパー点滅時の白色 +- TNT点滅時の白色 + +## レイヤリング + +Z-Fightingを回避するために使用されるオプション + +| 名称 | 説明 | +| --- | --- | +| `NO_LAYERING` | レイヤリングを行わない | +| `POLYGON_OFFSET_LAYERING` | `glPolygonOffset` を使用し深度値を手前にずらす | +| `VIEW_OFFSET_Z_LAYERING` | `PoseStack` を微妙に内側にスケールする | + +## レンダーターゲット + +描画対象のバッファ。 + +大抵は最終的にmainレンダーターゲットに描画されます。 + +例えば半透明な描画の場合 `translucent` ターゲットに描画され、最終的に `main` レンダーターゲットに合成されます。 + +# [rendering\renderer\item.md] + +# アイテムレンダラー + +## 独自レンダラーを作成する + +### レンダラークラスの作成 + +`BlockEntityWithoutLevelRenderer` の継承クラスを作る。 + +```java +class ExampleItemRenderer extends BlockEntityWithoutLevelRenderer { + + public ExampleItemRenderer(BlockEntityRenderDispatcher dispatcher, EntityModelSet modelSet) { + super(dispatcher, modelSet); + } + + @Override + public void renderByItem(ItemStack itemStackIn, ItemDisplayContext type, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int packedOverlay) { + // レンダリング + } +} +``` + + + +### Itemに登録 + +`Item#initializeClient` メソッドをオーバーライドし、 +`consumer.accept` に `IClientItemExtensions` の実装を渡します。 + +例では匿名クラスを使用して登録しています。 + +```java +public class ExampleItem extends Item { + public ExampleItem(Item.Properties properties) { + super(properties); + } + + @Override + public void initializeClient(Consumer consumer) { + consumer.accept( + new IClientItemExtensions() { + private final BlockEntityWithoutLevelRenderer renderer = new ExampleItemRenderer( + Minecraft.getInstance().getBlockEntityRenderDispatcher(), + Minecraft.getInstance().getEntityModels() + ); + + @Override + public BlockEntityWithoutLevelRenderer getCustomRenderer() { + return renderer; + } + } + ); + } +} +``` + +### モデルの設定 + +[#builtin/entity](./model.md#builtinentity) を参照してください。 + +モデルの `"parent"` を `"builtin/entity"` に設定する必要があります。 + +```json +{ + "parent": "builtin/entity" +} +``` + +# [rendering\renderer\model.md] + +# モデル + +## ビルトイン `parent` モデル + +モデルの `"parent"` に指定される、特別なモデル + +### builtin/generated + +アイテムのテクスチャに合ったモデルが `ItemModelGenerator` によって自動生成されます。 + +多くのフラットなアイテムはこれによって生成されています。 + +`item/generated` は `builtin/generated` を親モデルにしており、同じ効果が得られます。 + +### builtin/entity + +`BuiltInModel` としてモデルが登録され、唯一 `IClientItemExtensions`の `getCustomRenderer` が適用されるモデルです。(`BakedModel#isCustomRenderer`が`true`) + +その代わりに、モデルが一切描画されません。 + +## Forge モデルローダー + +詳しくは [Custom Model Loader - Neoforge](https://docs.neoforged.net/docs/1.20.4/resources/client/models/modelloaders) を参照してください。 + +Neoforgeですがネームスペース以外は仕様がほとんど同じです。 + +| ID | 説明 | +| --- | --- | +| `forge:empty` | 何も描画されない | +| `forge:elements` | `elements`と`transform`で構成されるモデル | +| `forge:obj` | OBJモデルを読み込める。`.obj`と`.mtl`が必要 | +| `forge:fluid_container` | バケツやタンクなど、液体を含むモデル | +| `forge:composite` | 複数のモデルを組み合わせる | +| `forge:item_layers` | レイヤーの数が無制限で、レイヤーごとに `RenderType` を設定可能 | +| `forge:separate_transforms` | 視点によってモデルを変更できる | + +## 独自モデルローダーの作成 + +実行時にモデルを生成して、ロードさせたり、アイテムによってモデルを変更するといったことができます。 + +以下2つの事が可能です。 + +### 1. モデル(Quad)を自動生成する + +`IUnbakedGeometry#bake` を `SimpleUnbakedGeometry` が事前に定義してくれているため、`addQuads` によるQuadの登録をします。 + +```java +public class CubeModel extends SimpleUnbakedGeometry { + + public static final IGeometryLoader LOADER = CubeModel::deserialize; + + // 別クラスに移動しても良い + public static CubeModel deserialize(JsonObject json, JsonDeserializationContext context) { + float size = GsonHelper.getAsFloat(json, "size"); + + return new CubeModel(size); + } + + private final float size; + + public CubeModel(float size) { + this.size = size; + } + + @Override + protected void addQuads(IGeometryBakingContext context, IModelBuilder modelBuilder, ModelBaker modelBaker, Function spriteGetter, ModelState modelState, ResourceLocation modelLocation) { + + List blockElements = new ArrayList<>(); + + Map faces = new EnumMap<>(Direction.class); + + Material baseLocation = context.getMaterial("base"); + TextureAtlasSprite baseSprite = spriteGetter.apply(baseLocation); + SpriteContents contents = baseSprite.contents(); + + for (Direction face : Direction.values()) { + faces.put( + face, + new BlockElementFace(null, -1, baseLocation.texture().toString(), new BlockFaceUV( + new float[]{0f, 0f, contents.width(), contents.height()}, 0 + )) + ); + } + + blockElements.add(new BlockElement( + new Vector3f(8f - size, 8f - size, 8f - size), + new Vector3f(8f + size, 8f + size, 8f + size), + faces, + null, + false + )); + + UnbakedGeometryHelper.bakeElements(modelBuilder, blockElements, spriteGetter, modelState, modelLocation); + } +} +``` + +### 2. アイテムごとにモデルを変更する + +`IUnbakedGeometry#bake` で `ItemOverrides` を入れ替えることによって実現できます。 + +`ItemOverrides#resolve` で動的にモデルを切り替えています。 + +キャッシュなどを使用し、Quad自動生成と組み合わせることで動的に自由にモデルを変更可能です。 + +例のJsonモデル: + +```json +{ + "loader": "examplemod:example", + "small": { + "parent": "examplemod:item/example_small" + }, + "large": { + "parent": "examplemod:item/example_large" + } +} +``` + +この例では +アイテムの個数が32以上の時、`item/example_small` になり、 + +32未満の場合 `item/example_large` になります。 + +```java +public class ExampleModel extends SimpleUnbakedGeometry { + + public static final IGeometryLoader LOADER = ExampleModel::deserialize; + + public final BlockModel smallModel; + public final BlockModel largeModel; + + public ExampleModel(BlockModel smallModel, BlockModel largeModel) { + this.smallModel = smallModel; + this.largeModel = largeModel; + } + + // 別クラスに移動しても良い + public static ExampleModel deserialize(JsonObject json, JsonDeserializationContext context) { + BlockModel smallModel = context.deserialize(GsonHelper.getAsJsonObject(json, "small"), BlockModel.class); + BlockModel largeModel = context.deserialize(GsonHelper.getAsJsonObject(json, "large"), BlockModel.class); + + return new ExampleModel(smallModel, largeModel); + } + + @Override + public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, ItemOverrides overrides, ResourceLocation modelLocation) { + overrides = new ExampleOverrides(); + + BakedModel bakedSmallModel = this.smallModel.bake(baker, this.smallModel, spriteGetter, modelState, modelLocation, context.useBlockLight()); + BakedModel bakedLargeModel = this.largeModel.bake(baker, this.largeModel, spriteGetter, modelState, modelLocation, context.useBlockLight()); + + return new Baked( + context.useAmbientOcclusion(), + context.isGui3d(), + context.useBlockLight(), + spriteGetter.apply(context.getMaterial("particle")), + overrides, + bakedSmallModel, + bakedLargeModel + ); + } + + @Override + public void resolveParents(Function modelGetter, IGeometryBakingContext context) { + this.smallModel.resolveParents(modelGetter); + this.largeModel.resolveParents(modelGetter); + super.resolveParents(modelGetter, context); + } + + @Override + protected void addQuads(IGeometryBakingContext iGeometryBakingContext, IModelBuilder iModelBuilder, ModelBaker modelBaker, Function function, net.minecraft.client.resources.model.ModelState modelState, ResourceLocation resourceLocation) { + + } + + public static class Baked implements IDynamicBakedModel { + private final boolean isAmbientOcclusion; + private final boolean isGui3d; + private final boolean isSideLit; + private final TextureAtlasSprite particle; + private final ItemOverrides overrides; + private final BakedModel smallModel; + private final BakedModel largeModel; + + public Baked(boolean isAmbientOcclusion, boolean isGui3d, boolean isSideLit, TextureAtlasSprite particle, ItemOverrides overrides, BakedModel smallModel, BakedModel largeModel) { + this.isAmbientOcclusion = isAmbientOcclusion; + this.isGui3d = isGui3d; + this.isSideLit = isSideLit; + this.particle = particle; + this.overrides = overrides; + + this.smallModel = smallModel; + this.largeModel = largeModel; + } + + public @NotNull List getQuads(@Nullable BlockState state, @Nullable Direction side, @NotNull RandomSource rand, @NotNull ModelData data, @Nullable RenderType renderType) { + return smallModel.getQuads(state, side, rand, data, renderType); + } + + @Override + public boolean useAmbientOcclusion() { + return this.isAmbientOcclusion; + } + + @Override + public boolean isGui3d() { + return this.isGui3d; + } + + @Override + public boolean usesBlockLight() { + return this.isSideLit; + } + + @Override + public boolean isCustomRenderer() { + return false; + } + + @Override + public @NotNull TextureAtlasSprite getParticleIcon() { + return this.particle; + } + + @Override + public @NotNull ItemOverrides getOverrides() { + return overrides; + } + } + + public static class ExampleOverrides extends ItemOverrides { + + @Override + public @Nullable BakedModel resolve(@NotNull BakedModel model, @NotNull ItemStack itemStack, @Nullable ClientLevel level, @Nullable LivingEntity livingEntity, int partialTicks) { + int count = itemStack.getCount(); + + if(model instanceof Baked baked) { + return count >= 32 ? baked.largeModel : baked.smallModel; + } + return model; + } + } +``` + +### モデルローダーの登録 + +`ModelEvent.RegisterGeometryLoaders` イベントで登録します。 + +MODバスに登録してください。 + +```java +@Mod.EventBusSubscriber(modid = ExampleMod.MODID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD) +public class ModelClientEvents { + + @SubscribeEvent + public static void registerModelLoaders(ModelEvent.RegisterGeometryLoaders loaders) { + loaders.register("cube", CubeModel.LOADER); + loaders.register("example", ExampleModel.LOADER); + } +} +``` + +第一引数がIDとなり、これはJsonモデルの `"loader"` に当たります。 \ No newline at end of file diff --git a/hooks/gather.py b/hooks/gather.py new file mode 100644 index 0000000..0ad1d19 --- /dev/null +++ b/hooks/gather.py @@ -0,0 +1,12 @@ +from pathlib import Path + +src_dir = Path("./docs/content") +out_file = Path("./docs/all.txt") + +def gather_md(*args, **kwargs): + md_files = sorted(src_dir.rglob("*.md")) + + with out_file.open("w", encoding="utf-8") as out: + for md in md_files: + out.write(f"\n\n# [{md.relative_to(src_dir)}]\n\n") + out.write(md.read_text(encoding="utf-8")) \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index a615881..24d7437 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -83,6 +83,9 @@ plugins: - redirects: redirect_maps: 'index.md': 'content/index.md' + - mkdocs-simple-hooks: + hooks: + on_pre_build: "hooks.gather:gather_md" hooks: - hooks/check.py diff --git a/requirements.txt b/requirements.txt index f83fdbf..ee3eed4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ mkdocs mkdocs-material pymdown-extensions -mkdocs-redirects \ No newline at end of file +mkdocs-redirects +mkdocs-simple-hooks \ No newline at end of file From 6ec1442255102de5eb2c319d1b55ab051001f213 Mon Sep 17 00:00:00 2001 From: toapuro <164835903+toapuro@users.noreply.github.com> Date: Fri, 6 Feb 2026 22:27:06 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20all.txt=E6=B7=B7=E5=85=A5=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + docs/all.txt | 2896 -------------------------------------------------- 2 files changed, 1 insertion(+), 2896 deletions(-) delete mode 100644 docs/all.txt diff --git a/.gitignore b/.gitignore index 671a91c..26c926f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ __pycache__/ .cache/ site +docs/all.txt # IDE / Editor .vscode/ diff --git a/docs/all.txt b/docs/all.txt deleted file mode 100644 index bc066c7..0000000 --- a/docs/all.txt +++ /dev/null @@ -1,2896 +0,0 @@ - - -# [advanced\gradle.md] - -# Gradle - -Gradle とは、Javaビルドに使用されるツールです。 - -そして、そのGradleの設定ファイルを記述できるのがGroovy(もしくはKotlin DSL)です。 - -Groovyもプログラミング言語であるので、一つ一つ見ていくとブラックボックスではなくどれも意味があることがわかります。 - -1.20.1の Forge の [build.gradle](https://github.com/MinecraftForge/MinecraftForge/blob/1.20.1/mdk/build.gradle) を例に説明していきます。 - -コード中に出てくる `mod_id` や `minecraft_version` などの変数は、同階層にある `gradle.properties` ファイルで定義されている値を参照しています。 - -### プラグイン設定 - -```gradle title="build.gradle" -plugins { - id 'eclipse' // Eclipse IDE - id 'idea' // Intellij IDEA - id 'maven-publish' // Maven公開用 - id 'net.minecraftforge.gradle' version '[6.0,6.2)' // ForgeGradle -} -``` -Gradle プラグインという、Gradle を拡張するツールを記述する部分です。 - -マイクラの関連では ForgeGradle, MixinGradle 等があります。 - -例えば ForgeGradle では - -- Minecaft のソースコードのダウンロード -- 難読化の解除(リマップ) -- 開発用クライアントの起動設定 - -などの作業を、Gradle が自動で行ってくれます。 - -```gradle title="build.gradle" -minecraft { - mappings channel: mapping_channel, version: mapping_version -} -``` -ここは開発環境のマッピングを指定しています。 -[#マッピング](./mapping.md)で解説していますが、 - -クラス名やメソッド名、フィールド名等を読みやすくするための物です。 - -### 開発環境でのマイクラの設定 - -```gradle title="build.gradle" -minecraft { - runs { - configureEach { - workingDirectory project.file('run') - - property 'forge.logging.markers', 'REGISTRIES' - - property 'forge.logging.console.level', 'debug' - - mods { - "${mod_id}" { - source sourceSets.main - } - } - } - - client { - property 'forge.enabledGameTestNamespaces', mod_id - } - - server { - property 'forge.enabledGameTestNamespaces', mod_id - args '--nogui' - } - - gameTestServer { - property 'forge.enabledGameTestNamespaces', mod_id - } - - data { - workingDirectory project.file('run-data') - - args '--mod', mod_id, '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/') - } - } -} -``` - -ここは開発環境の実行の設定について書いてある部分です。 - -`client` なら `runClient` タスク、 `server` なら `runServer` タスクに対応します。他も同様です。 - -```gradle title="build.gradle" -sourceSets.main.resources { srcDir 'src/generated/resources' } -``` - -Datagen の生成結果をリソースとして追加する処理です。 - -```gradle title="build.gradle" -repositories { - maven { - url "https://cursemaven.com" - } -} - -dependencies { - minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" - - implementation fg.deobf("curse.maven:jei-238222:7391695") -} -``` - -依存関係を設定する部分です。 - -`dependencies` に記述された依存関係は、`repositories`ブロックの中に記述されたレポジトリから参照してきます。 - -この例では Cursemaven をリポジトリとして登録し、JEI を依存関係として登録しています。 - -詳しくは [#依存関係](../getting-started/dependency.md) を参照してください。 - -以下他の設定 -```gradle title="build.gradle" -/** - mods.tomlにあるテンプレートリテラルを実際の値に置き換える処理 -*/ -tasks.named('processResources', ProcessResources).configure { - var replaceProperties = [ - minecraft_version: minecraft_version, minecraft_version_range: minecraft_version_range, - forge_version: forge_version, forge_version_range: forge_version_range, - loader_version_range: loader_version_range, - mod_id: mod_id, mod_name: mod_name, mod_license: mod_license, mod_version: mod_version, - mod_authors: mod_authors, mod_description: mod_description, - ] - inputs.properties replaceProperties - - filesMatching(['META-INF/mods.toml', 'pack.mcmeta']) { - expand replaceProperties + [project: project] - } -} - -/** - Jarファイルのメタデータを設定 -*/ -tasks.named('jar', Jar).configure { - manifest { - attributes([ - 'Specification-Title' : mod_id, - 'Specification-Vendor' : mod_authors, - 'Specification-Version' : '1', // We are version 1 of ourselves - 'Implementation-Title' : project.name, - 'Implementation-Version' : project.jar.archiveVersion, - 'Implementation-Vendor' : mod_authors, - 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") - ]) - } - - finalizedBy 'reobfJar' -} - -/** - パブリッシング設定 -*/ -publishing { - publications { - register('mavenJava', MavenPublication) { - artifact jar - } - } - repositories { - maven { - url "file://${project.projectDir}/mcmodsrepo" - } - } -} - -/** - Javaコンパイル時のエンコーディングをUTF-8に設定 -*/ -tasks.withType(JavaCompile).configureEach { - options.encoding = 'UTF-8' -} -``` - - -# [advanced\mapping.md] - -# マッピング - -## 資料 -[Neoforge - What are Mappings](https://neoforged.net/personal/sciwhiz12/what-are-mappings/) - -[SpongePowered - MCP](https://docs.spongepowered.org/stable/en/plugin/internals/mcp.html) - -[マッピング - FabricMC](https://wiki.fabricmc.net/ja:tutorial:mappings) - -## 難読化とマッピング -Minecraft(Java版)のjarファイルは**難読化**されており、クラス名やメソッド名などが読解困難な名前になっています。 - -これらを開発者が理解できる名前に変換するための対応表を**マッピング**と呼びます。 - -Forgeでは、本番環境ではSRGマッピング、開発環境では指定したマッピングを使用します。 - -このマッピングの違いにより、Mixinなどの低レイヤー機能が開発環境と本番環境で異なる動作をすることがあります。 - -そのため開発環境だけでなく、ビルドしたjarファイルでの本番環境テストも必須です。 - -## SRG - -バージョン間で名前が変わってしまうため、共通化するのが目的の中間マッピングです。 - -`m_286052_`や`f_90981_`等のように表記されます。 - -## マッピングの種類(Forge) - -### MCP (Mod Coder Pack) -古くから使われているコミュニティ主導のマッピングです。SRG名を経由する仕組みが特徴です。 - -Forge等は内部処理でSRG名を使用しているため、エラーログ等で見かけることがあります。 - -### Official Mappings (Mojang Mappings) -Mojang公式のマッピングです。現在のMOD開発の主流ですが、引数名やローカル変数名までは復元されません。 - -### Parchment -Official Mappingsを拡張し、**引数名やローカル変数名を読みやすくした**マッピングです。 - -Official Mappingsと互換性があるため、開発の途中から導入しても基本的に問題ありません。 - -## Parchmentの導入方法(1.20.1) - -1. プロジェクト直下の `settings.gradle` に以下の maven リポジトリを追加します。 - ```diff title="settings.gradle" - pluginManagement { - repositories { - + maven { url = 'https://maven.parchmentmc.org' } - } - } - ``` -2. `build.gradle` でプラグインを適用します。 - ```diff title="build.gradle" - plugins { - id 'net.minecraftforge.gradle' version '[6.0.16,6.2)' - + id 'org.parchmentmc.librarian.forgegradle' version '1.+' - } - ``` - - !!! warning - 必ず `net.minecraftforge.gradle` の下に追加してください。 - -3. マッピング設定を変更します。 -```diff title="build.gradle" -minecraft { -- mappings channel: 'official', version: '1.20.1' -+ mappings channel: 'parchment', version: '2023.09.03-1.20.1' -} -``` -`mapping_channel` などと変数で指定されている場合は、`gradle.properties` から編集してください。 - -!!! note "バージョンの指定について" - - 上記は1.20.1の例(`2023.09.03-1.20.1`)です。 - [ParchmentMC 公式サイト](https://parchmentmc.org) や [Mavenブラウザ](https://ldtteam.jfrog.io/ui/native/parchmentmc-public/org/parchmentmc/data/) で、使用しているMinecraftバージョンに対応するParchmentバージョンを確認して設定してください。 - -4. Gradleの更新 - 設定を変更したら、Gradleプロジェクトをリフレッシュ、もしくは同期してください。IDE上で変更が反映されるはずです。 - -## Parchmentの導入方法(Neoforge) - -以下を参照してください。 - -https://docs.neoforged.net/toolchain/docs/parchment/ - - -# [getting-started\dependency.md] - -# 依存関係 - -他の Mod やライブラリをプロジェクトに読み込むための設定は `build.gradle` の `dependencies` ブロックで行います。 - -## 依存関係の構成 - -Gradle では依存関係を `dependencies` ブロックで指定します。 - -指定の方法はいくつかあります。 - -| 構成 | 説明 | 用途の例 | -| :--- | :--- | :--- | -| `implementation` | コンパイル時と実行時の両方で依存関係を必要 | 必須の前提 Mod、常時使用するライブラリ | -| `compileOnly` | コンパイル時にのみ必要 | コンパイルにだけ必要な API、注釈プロセッサ | -| `runtimeOnly` | コンパイル時には不要、実行時にのみ必要 | 連携確認用の Mod(JEI など)、コンパイルコードには触れないが動作確認に必要な Mod | -| `annotationProcessor` | コンパイル時の注釈処理に使用される | Mixin などのプロセッサ | - -## マッピングについて - -Minecraft の Mod は通常、難読化されています。 - -開発環境でこれらを扱うために、ForgeGradle は `fg.deobf` という特別なメソッドを提供しています。 - -これを依存関係の宣言時に噛ませることで、指定した Jar ファイルを開発環境のマッピングに合わせて再マッピングして読み込んでくれます。 - -```gradle title="build.gradle" -dependencies { - // JEI - implementation fg.deobf("curse.maven:jei-238222:4712866") -} -``` - -## Mod の依存関係を追加する - -### CurseMaven を使用する - -[CurseMaven](https://www.cursemaven.com/) は、CurseForge 上のファイルを Maven 依存関係として簡単に扱えるようにする非公式サービスです。 - -1. **リポジトリの追加** - - `repositories` ブロックに以下を追加します。 - ```gradle - repositories { - maven { - url "https://cursemaven.com" - content { - includeGroup "curse.maven" - } - } - } - ``` - !!! info - - content指定はなくても良いですが、Cursemavenへの無駄なリクエストを減らすことができます。 - -3. **依存関係の記述** - フォーマット: `curse.maven:-:` - * **description**: 任意の識別用文字列(実際の解決には使われません) - * **projectID**: CurseForge プロジェクト ID (About Project 欄などに記載) - * **fileID**: `Files` タブで特定のファイルを開いた際の URL 末尾の数字 - - ```gradle - dependencies { - // JEI の例 (Project ID: 238222, File ID: 4712866) - compileOnly fg.deobf("curse.maven:jei-238222:4712866") - runtimeOnly fg.deobf("curse.maven:jei-238222:4712866") - } - ``` - - 以上のように記述できますが、手間がかかります。 - - Curseforgeでファイルを開くと、`Curse Maven Snippet`という欄に依存関係として使用する記述があります。(例: [JEI](https://www.curseforge.com/minecraft/mc-mods/jei/files/7391695)) - - これをそのままコピーすると楽です。 - -### Modrinth Maven を使用する - -Modrinth も公式で Maven リポジトリを提供しています。([Modrinth Maven](https://support.modrinth.com/en/articles/8801191-modrinth-maven)) - -1. **リポジトリの追加** - - ```gradle - repositories { - exclusiveContent { - forRepository { - maven { - name = "Modrinth" - url = "https://api.modrinth.com/maven" - } - } - forRepositories(fg.repository) // Only add this if you're using ForgeGradle, otherwise remove this line - filter { - includeGroup "maven.modrinth" - } - } - } - ``` - -2. **依存関係の記述** - - フォーマット: `maven.modrinth::` - - * ProjectID: プロジェクトのID。URLにある。 - * Version: バージョン、ファイルを開いて`Version number`の欄にある - - 例: [JEI](https://modrinth.com/mod/jei/version/p7yZWpEg) - - ```gradle - dependencies { - implementation fg.deobf("maven.modrinth:jei:15.20.0.129") - } - ``` - -### ローカルの Jar ファイルを使用する - -Maven リポジトリに公開されていない Mod を開発環境に入れたい場合などは、プロジェクト内のフォルダから読み込むこともできます。 - -1. プロジェクトルートに `libs` フォルダを作成し、そこに `.jar` ファイルを入れます。 - -2. リポジトリの設定 - - ```gradle - repositories { - flatDir { - dirs "libs" - } - } - ``` -3. `dependencies` に以下のように記述します。 - - `group:name:version`の形式である必要があります。 - `group`は何でも良いので、ここでは`blank`としています。 - - ファイル名は `name-version.jar` の形式になっている必要があります。 - - ```gradle - dependencies { - // libs/jei-1.20.1-forge-15.20.0.129.jar - implementation fg.deobf("blank:jei-1.20.1-forge:15.20.0.129") - } - ``` - - -# [getting-started\mod-files.md] - -# Modファイル構造 - -最小構成テンプレートのファイルを解説します。 - -``` yaml -src -└─main - ├─java/com/example/examplemod - │ └─ExampleMod.java - │ - └─resources - └─META-INF - └─mods.toml - -gradle.properties -build.gradle -settings.gradle -.gitignore -``` - -## gradle.properties - -`build.gradle`等で使用する設定値を定義するファイルです。 - -テンプレートではModIDやModの名前を定義しています。 - -```properties title="gradle.properties" -minecraft_version=1.20.1 -forge_version=47.4.10 - -... - -mod_id=examplemod -mod_name=Example Mod -mod_license=All Rights Reserved -mod_version=1.0.0 -``` - -使用箇所(例) - -```gradle title="build.gradle" -dependencies { - minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" -} -``` - -## build.gradle - -ビルド処理の内容を定義するファイルです。 - -[#Gradle](../advanced/gradle.md) で詳しく解説しています。 - -### settings.gradle - -プロジェクト全体の設定を定義するファイルです。 - - -## ExampleMod.java - -Modの起点となるメインクラスです。 - - - -## mods.toml - -Modの情報をローダーに伝えるためのファイルです。 - -[Forge Wiki](https://docs.minecraftforge.net/en/1.20.1/gettingstarted/modfiles/) にフォーマットや詳しい情報があります。 - -ここにも `${...}` となっている部分がありますが、これは `gradle.properties` で定義した値を参照しています。 - -## .gitignore - -Git が管理対象から除外するファイルを定義するファイルです。 - -例えば、リポジトリには不要な `run` ディレクトリは管理対象から除外する必要があります。 -Gradleプラグインのリポジトリなどを記述します。 - -# [getting-started\mod.md] - -# Modクラス - -## 資料 - -- [Neoforge Wiki](https://docs.neoforged.net/docs/gettingstarted/modfiles#mod-entrypoints) -- [Forge Wiki (Newer)](https://docs.minecraftforge.net/en/latest/gettingstarted/modfiles/#mod-entrypoints) -- [Forge Wiki (Older)](https://docs.minecraftforge.net/en/1.20.1/gettingstarted/modfiles/) - -Modクラスは、Modの起点となるクラスで、必要なデータの初期化やレジストリ登録を行います。 - -=== "Neoforge/Forge1.20.1(47.3.10以降)" - - ```java title="ExampleMod.java" - @Mod(ExampleMod.MODID) - public class ExampleMod { - public static final String MODID = "examplemod"; - - public ExampleMod(FMLJavaModLoadingContext context) { - IEventBus modBus = context.getModEventBus(); - } - } - ``` - -=== "Forge(47.3.10以前)" - - ```java title="ExampleMod.java" - @Mod(ExampleMod.MODID) - public class ExampleMod { - public static final String MODID = "examplemod"; - - @SuppressWarnings("removal") - public ExampleMod() { - IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus(); - } - } - ``` - -!!! info - - 1.20.1以降で開発しているのであれば、基本的にコンストラクタに `FMLJavaModLoadingContext` を入れる書き方の方で大丈夫です。 - 元々Neoforgeの機能だったものがForge(47.3.10)で導入されています。 - - - -# [getting-started\setup.md] - -# 環境構築 - -## Intellijのセットアップ - -!!! info "エディタについて" - Eclipse IDE や VSCode でも開発できますが、この章では触れません。 - -1. **Intellij IDEAのインストール** - - [公式ダウンロードページ](https://www.jetbrains.com/ja-jp/idea/download/?section=windows) - -2. **日本語化 (任意)** - - Intellijを起動してランチャーメニューになったら、サイドバーのPluginsタブから {==Japanese Language Pack==} と検索してインストールします。 - インストール後に Intellij を再起動すれば日本語化が反映されます。 - ![](../../assets/intellij/intellij-japanese-ext.png) - -3. **Minecraft Development プラグインをインストール** - - 同じくPluginsタブから {==Minecraft Development==} と検索してインストールし、完了したら一度 Intellij を再起動してください。 - - -## 前提知識 - -スキップしても大丈夫ですが、迷ったときに軽く振り返ってもらえると理解しやすくなります - -| 用語 | 備考 | -| --- | --- | -| Mixin | これをオンにしない場合、依存関係を設定する時に面倒なので、オンにしておくことをおすすめします | -| License | その Mod の著作権の扱い方を宣言するもので、依存関係を設定する時、確認する必要があります | -| Group ID | パッケージの指定。ドメインを逆に書く。ドメインを持っていなければ `io.github.作者名` のような形式が良い | -| Package | パッケージ。クラスをグループで管理できる仕組みで、実態としてはフォルダとほぼ同じ | -| Artifact ID | 基本的にModのIDを書いておく | -| Class | Java でコードを書く基本単位。1クラス1ファイルで機能ごとに分割できる | - -**Modローダー** - -| プラットフォーム | 備考 | -| --- | --- | -| NeoForge | 1.20.4 以降はこちら。1.20.4 以降のバージョンを開発する、ほとんどの開発者が移行済み。 | -| Forge | 1.20.4 未満ならこちら | - -1.20.1は特別にNeoForgeとForgeどちらも対応しています - -## Mod開発環境のセットアップ - -いくつか方法があります - -NeoForgeであれば [1, (NeoForge/Fabric) Mod ジェネレータの利用](#1-neoforgefabric-mod-ジェネレータの利用) を推奨 - -Fabricであれば [1, (NeoForge/Fabric) Mod ジェネレータの利用](#1-neoforgefabric-mod-ジェネレータの利用) を推奨 - -Forgeであれば [4, (NeoForge/Forge/Fabric) Intellijプラグイン経由で生成](#4-neoforgeforgefabric-intellijプラグイン経由で生成) を推奨 - -### 1, (NeoForge/Fabric) Mod ジェネレータの利用 - -NeoForge (1.20.4 以降) -[NeoForge Mod Generator](https://neoforged.net/mod-generator/) - -Fabricならこちら -[Fabric Template](https://fabricmc.net/develop/template/) - -### 2, (NeoForge) テンプレートをクローン - -[NeoForge テンプレート一覧](https://github.com/orgs/NeoForgeMDKs/repositories) - -ここにあるリポジトリの中から対象のバージョンを探して、 -右上の Code->Download ZIP からダウンロード・解凍し、build.gradle を開けば IDE が立ち上がるはずです。 - -!!! tip - `git clone --depth 1 https://github.com/~~` でクローンしても大丈夫です(depth=1 は不要なコミットを省くため)。 - -### 3, (Forge) Forge MDK[^1]の利用 - -[Forge MDK](https://files.minecraftforge.net/net/minecraftforge/forge/) - -こちらからバージョンを選択して MDK をダウンロードし解凍、build.gradle を開きます。 - -!!! tip - LatestとRecommendedの違いは基本的にあまりありません。どちらでもよし。 - -[^1]: MDKはMod Developer Kitの略で、いわゆるテンプレートです。 - -### 4, (NeoForge/Forge/Fabric) Intellijプラグイン経由で生成 - -プロジェクトを新規作成するとき左下にあるジェネレータから Minecraft を選択し、各項目を入力して作成を押してください。 - -!!! note - JDKの指定が必要な場合は [Java JDK(Intellij IDE)](#java-jdkintellij-ide) を参考にしてみてください。 - -## Java JDK(Intellij IDE) - -JDK は Java を実行するためのキットのようなもの、と捉えてもらえればOKです。 - -以下のテーブルのように、マイクラバージョンごとに JDK が異なり、基本的に開発環境もこれに合わせます。 - -| MC バージョン | JDK バージョン | -| ------------- | -------------- | -| 1.16.x | JDK 8 | -| 1.17.x | JDK 16 | -| 1.18.x | JDK 17 | -| 1.19.x | JDK 17 | -| 1.20.x | JDK 17 | -| 1.21.x | JDK 21 | - -### ダウンロード - -Intellij であれば、左上の ≡ メニュー->ファイル->プロジェクト構成->プロジェクトと進み、SDK[^2]を指定できると思いますが、そこで{==JDKのダウンロード...==}を選択することでダウンロードできます。 - -![JDKのダウンロード](../../assets/intellij/jdk-download.png) - -バージョンは先程のテーブルを参考に設定してください。 - -ベンダー[^3]の選択ができると思いますが、特にこだわりがなければ {==JetBrains Runtime==} がおすすめです。 - -!!! info - Jetbrains Runtime ではホットスワップという、実行中にコードを変更して適用させられる機能が使えます。 - -また、デスクトップにJDKをいれておいても損はないです。 -面倒くさかったら必要になったときにダウンロードしておきましょう。 - -少ないですがいくつかベンダーを紹介しておきます。 - -- [Adoptium](https://adoptium.net/temurin/releases) - -- [OpenJDK](https://jdk.java.net/25/) - -[^2]: JavaではJDKのこと -[^3]: Java関連のベースキットを提供する企業やサービス - -## テンプレートの編集 - -ジェネレータや、プラグインから生成した場合でも変更する箇所があります。 - -`gradle.properties` に `mod_id` や、 `mod_name` 等がテンプレートのままなので、それを適切な値に変更してください - -`src/main/java`の下にある`*Mod.java`も同様に `MODID` を変更してください。 - -以下例 -```gradle -minecraft_version_range=[1.20.1] // 単一バージョンのみ対応の場合[バージョン]のように記述 -... -mod_id=modding_example // Mod ID -mod_name=ModdingExample // Mod名 -mod_license=MIT // 好きなライセンスを指定 -mod_group_id=dev.toapuro // グループID。#前提知識を参照 -mod_authors=toapuro, another_author // 作者一覧 -mod_description=A example mod // Modの説明 -``` - -分からない用語は [#前提知識](#前提知識) を参照してください - -## ビルド・実行の方法 - -右側にある以下のような gradle アイコンを押すと、ビルドに関連する操作ができます。 - -![](../../assets/intellij/gradle.png) - -`build.gradle`を変更した場合、サイドバーの左上にループするようなアイコンがあるので、それを押すと変更が適用されます。 - -jarへビルドする場合は `Tasks->build->build` - -開発環境で実際に動作を確認したいのであれば `Tasks->forgegradle runs->runClient` にあるかと思います。 - -初心者向けの解説は一旦ここまでです。 - -# [index.md] - -# Forge Modding Notes - -Minecraft Forge 1.20.1 のMod開発に関する情報をまとめたサイトです。 - -このサイトは公式ドキュメントの補完を目的としています。 - -基本的な内容については [NeoForge](https://docs.neoforged.net/docs/gettingstarted/) / [Forge](https://docs.minecraftforge.net/en/1.21.x/gettingstarted/) の公式ドキュメントを参照してください。 - -## フォーマット - -``のように`<>`で囲まれた部分は適切なものに置き換えてください。 - -## 初心者の方へ -[#環境構築](./getting-started/setup.md) -から始めてみてください。 - -# [mixin\index.md] - -# Mixin - -MinecraftまたはMODのコードを一部改変できる仕組みです。 - -## 概要 - -実際に何ができるかコードで解説します。 - -Mixinの対象クラス -```java title="SomethingLogic.java" -public class SomethingLogic { - public void process() { - System.out.println("Proceed"); - } -} -``` - -Mixinクラス -``` java title="MixinSomethingLogic.java" -// リマッピングオフでSomethingLogicにMixin -@Mixin(value = SomethingLogic.class, remap = false) -public class MixinSomethingLogic { - - // メソッドの最初に注入 - @Inject(method = "process()V", at = @At("HEAD")) - private void onProcess(CallbackInfo ci) { - System.out.println("Injected"); - } -} -``` - -このMixinがSomethingLogicに適用されると、**概念的には**[^1]以下のようになります。 -``` java title="SomethingLogic(Injected).java" -public class SomethingLogic { - public void process() { - onProcess(new CallbackInfo(...)) - System.out.println("Proceed"); - } - - private void onProcess(CallbackInfo ci) { - System.out.println("Injected"); - } -} -``` -[^1]: 実際はonProcessのメソッド名やアノテーションなどが異なる。 - -## Mixinセットアップ - -以下を`build.gradle`の冒頭に追加。 -```gradle title="build.gradle" -buildscript { - repositories { - maven { url = 'https://repo.spongepowered.org/repository/maven-public/' } - mavenCentral() - } - dependencies { - classpath 'org.spongepowered:mixingradle:0.7-SNAPSHOT' - } -} -``` - -以下を `plugins {}` ブロックの下に追加。 -```diff title="build.gradle" -plugins { - ... -} -+ apply plugin: 'org.spongepowered.mixin' -``` - -以下を`build.gradle`の`dependencies {}`ブロックの下に追加。 -```diff title="build.gradle" -dependencies { -+ annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' -} -``` - -以下を`src/main/resources/.mixins.json`に -```json title=".mixins.json" -{ - "required": true, - "minVersion": "0.8", - "package": ".mixin", - "compatibilityLevel": "JAVA_17", - "mixins": [], - "client": [], - "server": [], - "injectors": { - "defaultRequire": 1 - } -} -``` -``や``は適切なものに置き換えてください。 - -`compatibilityLevel`はJDKバージョンに合わせましょう。(例は1.20.1) - -次に、`build.gradle`の`minecraft {}`ブロックの下に以下を追加。 -```gradle -mixin { - add sourceSets.main, "${mod_id}.refmap.json" - - config "${mod_id}.mixins.json" -} -``` -`${mod_id}`はそのままで大丈夫です - -## Mixinの使い方 - -実践的なコードですが、以下が参考になるかと思います。 - -[Mixin Examples - Fabric Wiki](https://wiki.fabricmc.net/tutorial:mixin_examples) - -### Mixinクラスの書き方 - -クラスの前に`@Mixin`を付けることでMixinクラスとなります。 - -```java -@Mixin(Example.class) -public class ExampleMixin { -} -``` - -その後、`src/main/resources/.mixins.json` にクラスを追加しなければいけません。 - -!!! warning - サーバー側ではクライアントクラスが読み込まれないので、クライアントとサーバーのMixinを分ける必要があります。 - -* `mixins`: クライアントとサーバー両方 -* `client`: クライアントのみ -* `server`: サーバーのみ - -それぞれのMixinに対応する場所にクラスを指定する必要があります。 - -クラスの指定は、`.mixins.json`で指定した`"package"`で指定したパッケージからの相対的な場所を指定します。 - -```java -package io.github.toapuro.example.mixins; - -@Mixin(Example.class) -public class ExampleMixin { -} -``` - -この例では以下のようになります -```json title=".mixins.json" -{ - "package": "io.github.toapuro.example.mixins", - "mixins": [ - "ExampleMixin" - ], - "client": [], - "server": [] -} -``` - -# [mixin\injections.md] - -# Mixin - -## インジェクション - -まずどこにコードを注入するか、どこのコードを改変するかを指定するための@Atを解説します - -### @At - -`@At` はどこにコードを注入するかを指定します。 - -`@At("HEAD")`のように使用します - -#### 引数 - -* `value`(必須): 場所の種類 -* `remap`: マッピングを適用するかどうか。バニラクラスではない場合`remap = false`を指定する必要があります。ここで指定した値はMixinクラス内のすべてのインジェクション、`@At`にも影響します。 -* `targets`: 対象のクラスが非公開な時や、コンパイル時存在しない場合にFQCN[^2]で指定します -* `priority`: Mixinの優先度。高いほど後に適用される。 -* `ordinal`: マッチした中で何番目か - -以下が`value`引数の取りうる主な値です。 - -| 値 | 説明 | -| :--- | :--- | -| `HEAD` | メソッドの最初 | -| `TAIL` | メソッドの最後の `return` の前 | -| `RETURN` | `return` 文の前 | -| `INVOKE` | 特定のメソッドを呼び出す前 (`target`引数必須) | -| `FIELD` | フィールドアクセス (`target`引数必須) | -| `STORE` | 変数代入 (`@ModifyVariable`のみ) | -| `LOAD` | 変数の取得 (`@ModifyVariable`のみ) | - -[^2]: FQCNは `パッケージ.クラス名` の形式でクラスを指定する。 - -以下詳細な説明が必要なものを解説します。 - -#### INVOKE - -`@At(value = "INVOKE", target = "")` - -このように使います。 - -`target`引数はメソッドのデスクリプタを指定します。 - -!!! info - - IntellijのMinecraft Developmentプラグインが補完してくれるので、 - すぐ覚える必要はないです。 - -??? デスクリプタの解説 - - **デスクリプタで指定するパッケージは全て区切り文字が`.`ではなく`/`であることに注意** - - **クラスの指定** - - クラスは - `Lパッケージ/クラス名;` - - このようなフォーマットで記述します。 - - 例えばオブジェクトであれば以下の様に記述します - - `Ljava/lang/Object;` - - **メソッドの指定** - - ```java - package io.github.toapuro.example; - - class Example { - public int add(int a, int b) - { - return a + b; - } - } - ``` - - `Lio/github/toapuro/example/Example;add(II)I` - - この様に対応します。 - - ```java - package io.github.toapuro.example; - - class Example { - public String concat(String a, String b) - { - return a + b; - } - } - ``` - - `Lio/github/toapuro/example/Example;concat(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;` - - この様に対応します。 - - なんとなくイメージできたかと思います。 - - | 型 | Descriptor | - | :--- | :--- | - | byte | B | - | char | C | - | double | D | - | float | F | - | int | I | - | long | J | - | short | S | - | boolean | Z | - | Object | Ljava/lang/Object; | - - 配列は`[`を先頭につけます。 - `int[]`であれば`[I`となります。 - - 後は`boolean` が `Z`、`long` が `J` であることに気をつけておけば大丈夫です。 - - -#### FIELD - -`@At(value = "FIELD", target = "")` - -このように使用します - -これもまたIntellijのMinecraft Developmentプラグインが補完してくれます。 - -??? フィールドのデクスリプタ指定 - - フォーマットは以下の通りです。 - - `Lパッケージ/クラス名;フィールド名:フィールド型` - - `Boolean.TRUE`であれば - - `Ljava/lang/Boolean;TRUE:Ljava/lang/Boolean;` - - となります - -### インジェクション - -インジェクションには種類が幾つかあります。 - -`@Inject`, `@Redirect`, `@ModifyArg`, `@ModifyArgs`, `@ModifyVariable`, `@ModifyConstant`, `@Overwrite` - -これらを使い分けることで、様々なコードの改変が可能になります。 - -| 名前 | 用法 | 説明 | -| :--- | :--- | :--- | -| `@Inject` | コールバック | 元のメソッドの特定位置に処理を挿入します。 | -| `@Redirect` | リダイレクト | メソッド呼び出しやフィールドアクセスなど、特定の処理を完全に置き換えます。 | -| `@ModifyArg` | 引数変更 | 他のメソッドを呼び出す際の、特定の引数を変更します。 | -| `@ModifyArgs` | 引数変更 | 他のメソッドを呼び出す際の、引数をまとめて変更します。 | -| `@ModifyVariable` | 変数変更 | メソッド内のローカル変数を変更します。 | -| `@ModifyConstant` | 定数変更 | メソッド内の定数値を変更します。 | -| `@Overwrite` | 上書き | メソッドの実装を完全に上書きします。競合のリスクが高いため、基本的には使用しません。 | - -引数や用法がほぼ同じものはまとめて解説します。 - -#### 基本的な使用法 - -元のメソッドの特定位置に処理を挿入します。 - -**`method`** - -基本、対象のメソッドを引数`method`で指定します。 - -デスクリプタで指定しますが、引数部分はオーバーロードがない場合省略できます。 - -またワイルドカードが使用できます。 - -**`at`** - -`@At`で指定します - -**`remap`** - -難読化を解除するかどうかのフラグです。 - -バニラクラスが対象ではない場合、`remap = false`を指定する必要があります。 - -`@Mixin`->`@インジェクション` -> `@At` の順番で `remap` は影響されていきます。 - -そのため `@Mixin` で `remap = false` を指定し、`@At` で難読化されたものを使用する場合、`@At` で `remap = true` を再指定する必要があります。 - -これは`@At`のtarget引数を指定する際にも気をつける必要があります。 - -難読化については [#マッピング](../advanced/mapping.md) で解説しています。 - -**`require`** - -対象が何個マッチする必要があるのかを指定します。 - -これを下回る個数しか存在しないような環境の場合、クラッシュします。 - -例えば`require = 0`では、対象が存在しなくてもクラッシュしません。 - -#### @Inject - -基本的に指定した場所の前にコードが注入されます。 - -引数は元のメソッドと`CallbackInfo`を組み合わせたものになります。 -返り値は`void`。 - -元のメソッドで`return`をしたい場合引数の`CallbackInfo ci`を使用して`ci.cancel()`と呼びます。 - -返り値がある場合、 -引数の`CallbackInfoReturnable<返り値の型> cir` - -!!! tips - - Mixinメソッドの引数や返り値は、Minecraft Developmentプラグインの修正機能(Alt+Enterなど)を使うことで自動生成できます。 - -特殊な引数 - -##### **`cancellable`** - -キャンセルできるかどうかを指定します。`CallbackInfo#cancel`や`CallbackInfoReturnable#setReturnValue`を使用する場合`true`を指定する必要があります。 - -##### **`locals`** - -ローカル変数に関して指定する引数。 - -主に以下3つの値を指定できます。(`LocalCapture`) - -* `NO_CAPTURE`: キャプチャしない(デフォルト) -* `CAPTURE_FAILSOFT`: キャプチャしますが、失敗した場合スキップ -* `CAPTURE_FAILHARD`: キャプチャしますが、失敗した場合クラッシュ - -[#MixinExtrasの利用](./mixin-extras.md) で解説する `@Local` を利用するとより可読性が高く、より安全です。 - -##### **`shift`** - -インジェクションの場所をちょっと調節できます。 - -基本指定した場所の前にインジェクションをしますが、`shift`を指定することで後ろにインジェクションをすることができます。 - -主に以下の4つの値を指定できます(`At.Shift`) - -* `NONE`: デフォルト -* `BEFORE`: 指定した場所の前 -* `AFTER`: 指定した場所の後ろ - -#### @Redirect - -対象を丸々置き換える。 - -!!! warning - 特にMixinが被った場合、競合しクラッシュするので使用は控えましょう。 - -[#MixinExtrasの利用](./mixin-extras.md) で解説する `@ModifyExpressionValue` で記述すると安全です。 - -#### @ModifyArg - -他のメソッドを呼び出す際の、特定の引数を変更します。 - -特殊な引数 - -##### **`index`** - -何番目の引数を変更するかを指定します。 - -基本 `void onCall(Args args)` です。 - -#### @ModifyArgs - -`@ModifyArg` をまとめて1メソッドで行うことができます。 - -メソッドの引数 `Args` を使用して操作します - -#### @ModifyVariable - -基本的に`@At("STORE")`や`@At("LOAD")`を指定します。 - -基本 `型 onCall(型 original)` です。 - -基本的に型でしか指定できないため、`index` や `name`、@Atの`ordinal` で正確に指定します。 - -引数(任意) - -* `index`: 変数の絶対インデックスを指定(バイトコードを見る必要がある) -* `name`: 変数の名前を指定 - -#### @Overwrite - -メソッドをすべて置き換え。 - -!!! danger - 互換性が無くなるので、他Modを一切考慮しない場合のみ使用してください。 - -`@Inject`の`HEAD`でキャンセルすると競合が発生せずに、同じ動作を再現できるので安全です。 - -### @Shadow - -Mixinクラス内部でのみ使用できる、対象クラスのフィールドやメソッドにアクセスするためのアノテーションです。 - -アクセス修飾子(`private`等)を無視してアクセスできます。 - -対象が `final` フィールドの場合 `@Final` アノテーションを付ける必要があります。 - -`@Mutable` を付けることによって `final` も無視して代入できるようになります。 - -!!! warning - - @Shadowのフィールドには `final` 修飾子を使用してはいけません[^3]。 - -[^3]: コンパイラの副作用が動作に影響するため - -```java -@Shadow -private int exampleValue; - -@Mutable -@Shadow @Final -private int finalExampleValue; -``` - -### @Unique - -Mixinクラスで使用でき、対象クラスに存在しないフィールドやメソッドを追加定義する際に使用します。 - -初期値を設定することもできます。 - -```java -@Unique -private int example$uniqueValue = 5; - -@Unique -private void example$uniqueMethod() { - -} -``` - -基本的に `$メソッド名` で記述するのが良しとされています。 - -Mixinは実行時、対象のクラスにマージされているので、メソッド名が被ってしまうと競合し、クラッシュする可能性があるためです。 - -[#Mixinトリック](./tricks.md) でこれを応用する方法を解説しています。 - -### アクセッサー(インターフェース) - -資料: [AccessorとInvoker - Fabric](https://wiki.fabricmc.net/ja:tutorial:mixin_accessors) - -アクセッサーとは、アクセス修飾子を無視してフィールドやメソッドにアクセスするために使用するMixinインターフェースです。 - -`@Shadow` とは違って、Mixinクラス以外からもアクセスできます。 - -アクセッサーは`interface`で定義します。 - -以下例 -```java -@Mixin(Example.class, remap = false) -public interface ExampleAccessor { - - @Accessor("exampleValue") - int getExampleValue(); - - @Mutable - @Accessor("exampleValue") - void setExampleValue(int exampleValue); - - @Invoker("handle") - void invokeHandle(int something); -} - -// 使用法 - -Example example = ...; - -((ExampleAccessor)(Object)example).setExampleValue(10); - -// もしくは - -ExampleAccessor accessor = (ExampleAccessor) example; -accessor.setExampleValue(10); -``` - -#### @Accessor - -フィールドの値を取得/設定できます。 - -**ゲッター(取得)** - -`型 メソッド名();`の形式で定義します - -メソッド名はなんでもいいですが、ゲッターメソッドの規則に従って`get`にしておきましょう。 - -**セッター(設定)** - -`void メソッド名(型);`の形式で定義します - -これも同様にメソッド名はセッターメソッドの規則に従って `set` にしておきましょう。 - -!!! warning メソッド名について - - 対象のクラスに存在するメソッド名を使用してはいけません。 - - -# [mixin\mixin-extras.md] - -## MixinExtrasの利用 - -Mixinでは難しい痒いところに手が届くようなライブラリです。 - -MixinExtrasのWikiは丁寧なので、そちらも参考にしてください。 - -[MixinExtras Wiki](https://github.com/LlamaLad7/MixinExtras/wiki) - -主に以下のような機能を提供します。 - -* 追加インジェクション -* 構文糖 -* 高度な@At指定 - -### 導入方法 - -[Setup - Wiki](https://github.com/LlamaLad7/MixinExtras?tab=readme-ov-file#setup) を参照してください。 - -!!! info "注意点" - - MixinExtrasを使用するとMixinExtrasがjarに埋め込まれ、Modの容量が増加するため、むやみに導入するのは控えましょう。 - -### 追加インジェクション - -**`@ModifyExpressionValue`** - -`@Redirect` をより安全に、より汎用に使えるようにしたもの。 - -非常に便利です。 - -対象の式の評価結果を改変できます。 - -```java -@ModifyExpressionValue( - method = "targetMethod", - at = @At(value = "INVOKE", target = "...") -) -private int modifyValue(int original) { - return original + 5; -} -``` - -**`@ModifyReceiver`** - -対象のメソッドコールのレシーバを改変する。 - -`receiver.call()` の `receiver` を改変できるということです - -書き方は省略します。 - -**`@ModifyReturnValue`** - -メソッドの戻り値を改変する。 - -`at` は `@At("RETURN")` を指定します。 - -非常に便利です。 - -```java -@ModifyReturnValue( - method = "targetMethod", - at = @At("RETURN") -) -private float halveSpeed(float original) { - return original / 2f; -} -``` - -**`WrapMethod`** - -メソッドの全てをラムダ式として覆い、操作できるもの。 - -try-catchで覆ってメソッドを実行し、例外を抑制したり、そのメソッドを複数回実行する等に使えます。 - -詳しくは[Wiki](https://github.com/LlamaLad7/MixinExtras/wiki/WrapMethod)で確認してください。 - -**`@WrapWithCondition`** - -ある関数呼び出しをif文で囲って、条件付きで実行させるもの。 - -詳しくは[Wiki](https://github.com/LlamaLad7/MixinExtras/wiki/WrapWithCondition)で確認してください。 - -### 構文糖 - -**`@Cancellable`** - -`@ModifyExpressionValue` 等の `CallbackInfo` を引数として取らないインジェクション関数でもキャンセル・返り値の操作が可能になるものです。 - -引数の最後に `@Cancellable CallbackInfo ci` を追加することで使用できます。(`CallbackInfoReturnable`も可能) - -**`@Local`** - -`@Inject` の `locals` をより便利にしたもの。 - -`@Inject` に限らず様々なインジェクションで使えます。非常に便利です。 - -これも引数の最後に使用します。 - -更に、`LocalRef` や `LocalIntRef`(プリミティブ) を型として使うことで、ローカル変数を操作することもできます。 - -引数(任意) -* `ordinal`: 何番目のローカル変数か -* `name`: 変数の名前 - -詳しくは[Wiki](https://github.com/LlamaLad7/MixinExtras/wiki/Local)で確認してください。 - -**`@Share`** - -Mixin同士でローカル変数を作成しシェアできる機能。 - -詳しくは[Wiki](https://github.com/LlamaLad7/MixinExtras/wiki/Share)で確認してください。 - -# [mixin\tricks.md] - -# Mixin - -## Mixinトリック - -### Mixinで継承する - -対象クラスの継承・実装しているインターフェースをMixinクラスでも継承することで、スーパークラスのメソッドやフィールド等にアクセスすることができます。 - -コンストラクタを生成する必要がありますが、無視されるため実装して大丈夫です。 - -!!! info - - 対象が抽象クラスの場合、メソッドを実装する必要が出てくるので、Mixinクラスも抽象クラスにしましょう。 - -```java -@Mixin(Example.class) -public abstract class ExampleMixin extends ExampleParent implements ExampleInterface { - - public ExampleMixin(...) { - super(...); - } -} -``` - -### Mixinでオーバーライドする - -Mixinクラスにあるメソッドやフィールドは基本的に対象クラスにマージされるため、そのままオーバーライドが可能です。 - -他Modが同じクラスで同じメソッドをオーバーライドした場合競合するため、あまりおすすめできません。 - -できれば [#ソフトオーバーライド](#ソフトオーバーライド) を使用してください。 - -```java -public class ExampleParent { - public void exampleMethod() { - System.out.println("Super"); - } -} - -public class Example extends ExampleParent { -} - -@Mixin(Example.class) -public class ExampleMixin extends ExampleParent { - @Override - public void exampleMethod() { - super.exampleMethod(); - System.out.println("Override"); - } -} -``` - -### Mixinでインターフェースを実装する - -Mixinクラスは対象クラスにマージされるため、独自インターフェースを実装することができます。 - -`@Unique` と組み合わせることで、クラスに任意のデータを付与しアクセスすることができます。 - -```java -public interface MyInterface { - void example$exampleMethod(); - String example$getExampleValue(); -} - -@Mixin(Example.class) -public class ExampleMixin implements MyInterface { - @Unique - private String example$exampleValue = "Hello World"; - - @Unique - @Override - public void example$exampleMethod() { - System.out.println("Mixin"); - } - - @Unique - @Override - public String example$getExampleValue() { - return example$exampleValue; - } -} - -// 使用法 -Example example = ...; -MyInterface myInterface = (MyInterface) example; - -myInterface.example$exampleMethod(); -System.out.println(myInterface.example$getExampleValue()); -``` - -### ソフトオーバーライド - -Mixinメソッドを継承して継承できるシステムを利用したものです。 - -詳しくは [Mixin Inheritance](https://wiki.fabricmc.net/tutorial:mixinheritance) を参照 - -`@SoftOverride` はなくても良いですが、有効か検証してくれるので書いておきましょう。 - -```java -public class ExampleParent { - protected void exampleMethod() { - System.out.println("Original"); - } -} - -public class Example extends ExampleParent { -} - -@Mixin(ExampleParent.class) -public class ExampleParentMixin { - - @Inject(method = "exampleMethod", at = @At("HEAD")) - protected void injectExampleMethod() { - } - - @Unique - protected void example$uniqueMethod() { - System.out.println("Unique"); - } -} - -@Mixin(Example.class) -public class ExampleMixin extends ExampleParentMixin { - - @Override - @SoftOverride - protected void injectExampleMethod() { - System.out.println("Mixin"); - } - - @Override - @SoftOverride - protected void example$uniqueMethod() { - System.out.println("Mixin"); - } -} -``` - -## 内部の仕組みについて - -**Java** - -* ラムダ式は `lambda$メソッド名$番号` のフォーマットの名前で関数に変換されます。 - -**Mixin** - -* Mixinクラスは対象のクラスにマージされる。 -* Mixinクラスは実行時にはロードできない(アクセッサーは可能)。 - - -# [registries\basic.md] - -# レジストリ - -レジストリとは、ID (`ResourceLocation`) とオブジェクト(アイテムやブロックなど)を紐づけて管理する仕組みです。 - -登録されたオブジェクトをレジストリと呼び分けるため、**レジストリエントリ**と呼称します。 - -レジストリエントリのIDは、同じレジストリの中では**一意**である必要があります。 - -どのようにレジストリに登録するか見てみましょう。 -例としてアイテムの登録を挙げてみます。 - -```java title="ModItems.java" -public class ModItems { - public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, ExampleMod.MODID); - - // "example_item"というIDとして仮登録 - public static final RegistryObject EXAMPLE_ITEM = ITEMS.register("example_item", () -> new Item(new Item.Properties())); -} -``` - -```java title="ExampleMod.java" -public class ExampleMod { - public static final String MODID = "examplemod"; - - // Neoforge / 1.20.1 Forge (3.10以降) (1) - public ExampleMod(FMLJavaModLoadingContext context) { - IEventBus modBus = context.getModEventBus(); - - // 登録 - ModItems.ITEMS.register(modBus); - } - - // else - @SuppressWarnings("removal") - public ExampleMod() { - this(FMLJavaModLoadingContext.get()); - } -} -``` - -1. コンストラクタについては [#Modクラス](../getting-started/mod.md) を参照 - -!!! warning - - `.register(modBus)` の書き忘れに注意! - -## 登録方法 - -### DeferredRegister - -`DeferredRegister` はレジストリの登録内容を保持して、レジストリが初期化される `RegistryEvent` のタイミングで登録します。 - -名前通り遅延して登録するものです。 - -```java -public static final DeferredRegister ITEMS = DeferredRegister.create( - // レジストリを指定 - ForgeRegistries.ITEMS, - // MODID - ExampleMod.MODID -); -``` - -第一引数は以下から指定できます。 - -- `ForgeRegistries.*`: Forgeで用意されているレジストリ -- `ForgeRegistries.Keys.*` -- `Registries.*`: バニラレジストリ - -`DeferredRegister` は情報を保持しているだけなので、最後に実際に `RegistryEvent` に紐づける必要があります。 - -```java -ModItems.ITEMS.register(modBus); -``` - -#### RegistryObject - -`DeferredRegister` が扱う、登録後に取得可能になるレジストリエントリへの参照を保持するラッパー。 - -```java -public static final RegistryObject EXAMPLE_ITEM = ITEMS.register("example_item", () -> new Item(new Item.Properties())); -``` - -`EXAMPLE_ITEM.get()` で実体を取得できます。 - -!!! danger - - 実際の登録前に中身にアクセスすると例外が発生するため注意です。 - - 例外: `Registry Object not present: ...` - -### RegisterEvent - -RegisterEvent を使ってレジストリに登録することも可能ですが、通常はあまり使用されません。 - -```java -@SubscribeEvent // Modイベントバスで登録 -public void register(RegisterEvent event) { - event.register( - ForgeRegistries.Keys.BLOCKS, - helper -> { - helper.register(ResourceLocation.fromNamespaceAndPath(MODID, "example_block_1"), new Block(...)); - helper.register(ResourceLocation.fromNamespaceAndPath(MODID, "example_block_2"), new Block(...)); - helper.register(ResourceLocation.fromNamespaceAndPath(MODID, "example_block_3"), new Block(...)); - // ... - } - ); -} -``` -!!! info - - `ResourceLocation.fromNamespaceAndPath` は Forge1.20.1(47.3.10) 以降または Neoforge でサポートされています。未サポートの場合 `new ResourceLocation` を利用してください。 - -参考: - -- [RegisterEvent - Forge](https://docs.minecraftforge.net/en/latest/concepts/registries/#registerevent) - -- [RegisterEvent - Neoforge](https://docs.neoforged.net/docs/concepts/registries#registerevent) - -## レジストリエントリの取得 - -特定のレジストリエントリを取得するためには対象の `ResourceLocation` が必要です。 - -逆の操作も可能で、レジストリエントリから `ResourceLocation` を取得することができます。 - -```java -// ResourceLocation -> Block -BuiltInRegistries.BLOCKS.get(ResourceLocation.fromNamespaceAndPath("minecraft", "stone")); - -// Block -> ResourceLocation -BuiltInRegistries.BLOCKS.getKey(Blocks.STONE); - -// 対象のエントリが存在するか -BuiltInRegistries.BLOCKS.containsKey(ResourceLocation.fromNamespaceAndPath("yourmod", "custom_block")) -``` - -!!! warning - - 必ずレジストリ初期化後に実行してください。 - -レジストリに登録されたすべてのエントリに対して処理をすることもできます。 - -```java -for (ResourceLocation id : BuiltInRegistries.BLOCKS.keySet()) { - // ... -} -for (Map.Entry, Block> entry : BuiltInRegistries.BLOCKS.entrySet()) { - // ... -} -``` - -## カスタムレジストリの作成 - -`RegistryBuilder` を使いレジストリの設定をします。 - -```java -private static final ResourceLocation CUSTOM_REGISTRY_ID = ResourceLocation.fromNamespaceAndPath("yourmodid", "custom_id"); -public static final RegistryBuilder CUSTOM_REGISTRY = RegistryBuilder.of(CUSTOM_REGISTRY_ID) - // 数値IDの割り当てられる範囲 - .setIDRange(0, Integer.MAX_VALUE) // = .setMaxID(Integer.MAX_VALUE) - - // クライアントとの数値IDの同期を無効化 - // .disableSync() - - // 存在しない場合に置き換えられるエントリ。任意 - .setDefaultKey(ResourceLocation.fromNamespaceAndPath("yourmodid", "empty")); -``` - -```java -@SubscribeEvent // Modバスに登録 -public static void registerRegistries(NewRegistryEvent event) { - event.register(CUSTOM_REGISTRY); -} -``` - -### データパックレジストリ - -データパックでエントリを追加することができるレジストリ。 - -```java -@SubscribeEvent // Modバスに登録 -public static void registerDatapackRegistries(DataPackRegistryEvent.NewRegistry event) { - event.dataPackRegistry( - ResourceKey.createRegistryKey(CUSTOM_REGISTRY_ID), - Spell.CODEC - ); - } -``` - - - -# [registries\item.md] - -# アイテム - -## 資料 - -- [Items - Neoforge](https://docs.neoforged.net/docs/items/) -- [Making Items - Forge Community](https://forge.gemwire.uk/wiki/Making_Items) - -## アイテムの追加 - -### レジストリに追加 - -[#レジストリ](./basic.md) を参考に、アイテムを登録します。 - -```java -public class ModItems { - public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, ExampleMod.MODID); - - public static final RegistryObject EXAMPLE_ITEM = ITEMS.register("example_item", () -> - new Item(new Item.Properties()) - ); -} -``` - -これは特に特別な特徴・機能を持たないアイテムを登録します。 - -### Item.Properties - -アイテムに様々な設定を付与するためのプロパティ群。 - -設定できるプロパティとして以下があります。 - -- `food`: 食料としての設定 -- `stacksTo`: 最大スタック数を設定 (必ず耐久値設定の前にする必要がある) -- `defaultDurability`: 未設定の場合のみ耐久値を設定 -- `durability`: 耐久値を設定 -- `craftRemainder`: クラフト時に残るアイテムを設定 -- `rarity`: レアリティを設定 -- `fireResistant`: 火に耐性を持つ -- `setNoRepair`: 修理不可 -- `requiredFeatures`: 追加するうえで必要な`実験的機能`のフラグ - -```java -public static final RegistryObject EXAMPLE_ITEM = ITEMS.register("example_item", () -> - new Item(new Item.Properties().stacksTo(1).defaultDurability(1024).rarity(Rarity.UNCOMMON)) -); -``` - -#### FoodProperties - -`food(FoodProperties)` で指定するプロパティ群。 - -`new FoodProperties.Builder()` でビルダーを生成し、設定後に `.build()` を呼び出す必要があります。 - -設定できるプロパティとして以下があります。 - -- `nutrition`: 回復する満腹ポイントの数を設定 -- `saturationMod`: 隠し満腹度の計算に使用される値。計算式は `min(2 * nutrition * saturationMod, プレイヤーの満腹度)` -- `meat`: 肉かどうか。狼が食べるかどうかに影響する -- `alwaysEat`: 満腹度が最大でも食べられるようにする -- `fast`: 食べる時間を短くする -- `effect`: 食べたときに付与される可能性のあるエフェクト - -バニラの例 -```java -public static final FoodProperties APPLE = new FoodProperties.Builder() - .nutrition(4) - .saturationMod(0.3F) - .build(); - -public static final FoodProperties CHICKEN = new FoodProperties.Builder() - .nutrition(2) - .saturationMod(0.3F) - .effect( - new MobEffectInstance( - MobEffects.HUNGER, - 600, // 持続時間 - 0 // 強さ - ), - 0.3F // 確率 - ) - .meat() - .build(); -``` - -### Itemクラス - -アイテムの実際の動作が定義されているクラス。 - -Modでよく利用されるバニラのアイテム継承クラスは以下の通りです。 - -ツール・武器・装備 - -- `SwordItem` -- `BowItem` -- `PickaxeItem` -- `AxeItem` -- `ShovelItem` -- `HoeItem` -- `FishingRodItem` -- `CrossbowItem` -- `TridentItem` -- `ShieldItem` -- `ArmorItem` - -その他 - -- `BlockItem` -- `PotionItem` -- `BucketItem` -- `SpawnEggItem` - -独自に機能を追加したい場合、`Item` かそれらの継承クラスを継承してオーバーライドなどで実装します。 - -```java -class ExampleItem extends Item { - public ExampleItem(Item.Properties properties) { - super(properties); - } - - /** - * 右クリック時の処理 - */ - @Override - public InteractionResultHolder use(Level level, Player player, InteractionHand hand) { - // ... - - /* - InteractionResultHolder.success: アクション成功 - InteractionResultHolder.consume: アクション処理済み - InteractionResultHolder.fail: アクション失敗 (次のuseコールバックに移る) - InteractionResultHolder.pass: 続行 (次のuseコールバックに移る) - */ - return InteractionResultHolder.success(player.getItemInHand(hand)); - } - - /** - * ホバーテキスト - */ - @Override - public void appendHoverText(ItemStack stack, @Nullable Level level, List tooltip, TooltipFlag flag) { - tooltip.add(Component.translatable("tooltip.examplemod.example_item")); - } -} - -``` - -## ItemStack - -状態を含んだ変更可能なアイテム1枠分の実態データ。 - -ItemStackは以下を持ちます: - -- `Item item`: アイテムの種類 -- `int count`: スタック数 -- `CompoundTag tag`: NBTタグ - -例えば、インベントリの1スロットなどや、手に持っているアイテムなども `ItemStack` で表現されます。 - -## クリエイティブタブへの追加 - -### 既存のクリエイティブタブ - -イベントを使用して、既存のクリエイティブタブにアイテムを追加します。 - -```java -@Mod.EventBusSubscriber(modid = ExampleMod.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) -public class ModCreativeTabs { - @SubscribeEvent - public static void buildContents(BuildCreativeModeTabContentsEvent event) { - if (event.getTabKey() == CreativeModeTabs.INGREDIENTS) { - event.accept(ModItems.EXAMPLE_ITEM); - } - } -} -``` - -### カスタムクリエイティブタブ - -レジストリを使用して独自クリエイティブタブを追加し、アイテムを追加します。 - -```java -public static final DeferredRegister CREATIVE_TABS = - DeferredRegister.create(Registries.CREATIVE_MODE_TAB, ExampleMod.MODID); - -public static final RegistryObject EXAMPLE_TAB = CREATIVE_TABS.register("example", () -> CreativeModeTab.builder() - .title(Component.translatable("item_group." + ExampleMod.MODID + ".example")) - .icon(() -> new ItemStack(ModItems.EXAMPLE_ITEM.get())) - .displayItems((params, output) -> { - output.accept(ModItems.EXAMPLE_ITEM.get()); - // ... - }) - .build() -); -``` - -最後に、通常の通りレジストリを紐づけます。 - -```java -CREATIVE_TABS.register(modBus); -``` - - - -## リソースの追加(モデルやテクスチャ) - -アイテムを追加しただけでは、テクスチャやモデルが存在せず、`Missing Texture` が表示されます。 - -手動で作成する場合の配置場所: - -- モデル定義: `assets//models/item/.json` -- テクスチャ: `assets//textures/item/.png` - -`texture_name` はよく `item_id` と一致させることが多いです。 - -# [rendering\api.md] - -# レンダリングAPI - -## 座標変換と回転行列 - -### PoseStack - -座標変換(移動・回転・拡大縮小)を管理するスタックです。 - -これによってプレイヤーの手に持った剣だけを回転させる、といったことが可能になります。 - -| メソッド名 | 説明 | -| --- | --- | -| `push` | 現在の状態(位置・回転)を保存する。ここから局所的な作業を始める合図。 | -| `translate` | 座標を移動する。 | -| `scale` | 座標を拡大縮小する。 | -| `rotate` | 座標を回転する。 | -| `pop` | 保存した状態に戻す。作業終了。 | - -!!! warning - - `push` と `pop` は必ずセットで行ってください。 - -### Quaternion - -回転を表す数学的概念。 - -`Axis.YP.rotationDegrees(90)` のように軸を指定して `Quaternion` を作成し、`PoseStack` に適用できます。 - -```java -// Y軸中心に+90度回転します -poseStack.mulPose(Axis.YP.rotationDegrees(90)); -// Y軸中心に-90度回転します -poseStack.mulPose(Axis.Y.rotationDegrees(90)); -``` - -## 描画バッファ - -### MultiBufferSource(BufferSource) - -VertexConsumer を RenderType ごとに振り分け、結果的にバッチレンダリングを行うためのクラスです。 - -複数の描画をまとめて処理できるため、レンダリング効率が向上します。 - -`MultiBufferSource#getBuffer(RenderType)` を呼び出すことで、指定した RenderType に対応する VertexConsumer を取得できます。 - -異なる RenderType が渡された場合、それまでのバッチは `endBatch` によって終了され、その時点で描画が実行されます。 - -!!! warning - - バッチが終了したタイミングで描画されるため、`endBatch` が呼ばれない限り描画されません。(GuiGraphics等は自動で`endBatch`を呼ぶ) - -### Tesselator - -即時レンダリング用のクラス。 - -`MultiBufferSource` とは異なり、自動的にバッチレンダリングはされない。 - -`Tesselator.getInstance()` で取得し、 -`.getBuilder(RenderType)` で `VertexConsumer` を取得する。 - -`.end` で明示的に描画します。 - -### VertexConsumer(BufferBuilder) - -基本的な描画は `VertexConsumer` というインターフェースを通して頂点を登録します。 - -頂点は以下の値を持ちます。 - -`RenderType` で指定されている `VertexFormat` によって必要な変数が異なります - -[#VertexFormat](./render-options.md#vertexformat) を参照 - -!!! warning - - `VertexFormat` で指定されている順番通りに `VertexConsumer` に頂点情報を渡す必要があります。 - -!!! danger - - 必要な変数を設定しない場合クラッシュします。 - -| 変数名 | 説明 | -| --- | --- | -| `x` | X座標 | -| `y` | Y座標 | -| `z` | Z座標 | -| `red` | 赤成分 | -| `green` | 緑成分 | -| `blue` | 青成分 | -| `alpha` | 透明度(float0.0~1.0, int0~255) | -| `texU` | テクスチャ座標(U) | -| `texV` | テクスチャ座標(V) | -| `overlayUV` | オーバーレイUV座標(被ダメージ時の赤色等)(パック済み) | -| `lightmapUV` | ライトマップUV | -| `normalX` | 法線ベクトルX | -| `normalY` | 法線ベクトルY | -| `normalZ` | 法線ベクトルZ | - -!!! info - - 線は特殊で、視点から終点へ向かう方向を表すベクトルとして設定する必要があります。 - -```java -public static void drawHorizontalQuad( - PoseStack poseStack, - MultiBufferSource buffer, - float x0, float y, float z0, - float width, float depth, - float u, float v, - int r, int g, int b, int a -) { - Matrix4f matrix4f = poseStack.last().pose(); - - // 単色 - VertexConsumer consumer = buffer.getBuffer(RenderType.lightning()); - - float x1 = x0 + width; - float z1 = z0 + depth; - - consumer.vertex(matrix4f, x0, y, z0) - .color(r, g, b, a) - .endVertex(); - - consumer.vertex(matrix4f, x1, y, z0) - .color(r, g, b, a) - .endVertex(); - - consumer.vertex(matrix4f, x1, y, z1) - .color(r, g, b, a) - .endVertex(); - - consumer.vertex(matrix4f, x0, y, z1) - .color(r, g, b, a) - .endVertex(); -} - -// 線の描画 -public static void drawLine( - PoseStack poseStack, - MultiBufferSource buffer, - float x1, float y1, float z1, - float x2, float y2, float z2, - int r, int g, int b, int a -) { - Matrix4f matrix4f = poseStack.last().pose(); - Matrix3f matrix3f = poseStack.last().normal(); - - VertexConsumer consumer = buffer.getBuffer(RenderType.lines()); - - float dx = x2 - x1; - float dy = y2 - y1; - float dz = z2 - z1; - - consumer.vertex(matrix4f, x1, y1, z1) - .color(r, g, b, a) - .normal(matrix3f, dx, dy, dz) - .endVertex(); - - consumer.vertex(matrix4f, x2, y2, z2) - .color(r, g, b, a) - .normal(matrix3f, dx, dy, dz) - .endVertex(); -} -``` - -## テクスチャアトラス - -### 資料 - -- [Minecraft Wiki](https://ja.minecraft.wiki/w/テクスチャ#テクスチャアトラス) - -OpenGLではテクスチャを切り替える(Bind)処理は比較的重い処理です。 - -そのため、Minecraftでは大量のブロックやアイテムのテクスチャを**1枚の巨大な画像**にまとめて扱うことで、描画時の切り替えコストを削減しています。 - -このまとめられた1枚の画像を**テクスチャアトラス**と呼びます。 - -!!! tips - - エンティティのテクスチャはテクスチャアトラスを使用していません。 - -### 種類 - -以下の静的なテクスチャアトラスが存在します。 - -| 参照 | ID | ディレクトリ | 説明 | -| --- | --- | --- | --- | -| `Sheets.BANNER_SHEET` | banner_patterns | entity/banner_base, entity/banner/\* | バナー | -| `Sheets.BED_SHEET` | beds | entity/bed/\* | ベッド | -| `Sheets.CHEST_SHEET` | chests | entity/chest/\* | チェスト | -| `Sheets.SHIELD_SHEET` | shield_patterns | entity/shield_base, entity/shield_base_nopattern, entity/shield/\* | シールド | -| `Sheets.SIGN_SHEET` | signs | entity/signs/\* | 看板 | -| `Sheets.SHULKER_SHEET` | shulker_boxes | entity/shulker/\* | シャーカーボックス | -| `Sheets.ARMOR_TRIMS_SHEET` | armor_trims | 特殊[^1] | アーマートリム | -| `Sheets.DECORATED_POT_SHEET` | decorated_pot | entity/decorated_pot/\* | 飾り壺 | -| `TextureAtlas.LOCATION_BLOCKS` | blocks | block/\*, item/\*, entity/conduit, ... | ブロック | -| `TextureAtlas.LOCATION_PARTICLES` | particles | particle/\* | パーティクル | -| `textures/atlas/paintings.png` | paintings | painting/\* | 絵画 | -| `textures/atlas/mob_effects.png` | mob_effects | mob_effect/\* | モブエフェクト | - -比較的汎用に使えるのは `TextureAtlas.LOCATION_BLOCKS` です。 - -[^2]: `paletted_permutations` を使用している。`PalettedPermutations` を参照。 - -## RenderType - -[#RenderType](./render-options.md#rendertype) で解説しています。 - -# [rendering\basic.md] - -# レンダリング (1.20.1) - - - -解説する必要のある事柄が多すぎるため、レンダリングするうえでの最小限の知識を解説しています。 - -## 技術スタック - -### GPU - -GPUで描画されている。 - -### OpenGL - -グラフィックスドライバと通信するための規格。 - -ステートマシンとなっているため、設定した値は戻すまで状態が維持されます。 - -そこでバニラのメソッドはうまくラッピングし、カバーしています。 - -### LWJGL - -MinecraftはLWJGLというライブラリを経由してOpenGLで描画しています。 - -基本的にはOpenGLのコマンドを直接叩くことは少なく、用意されたAPIを通して描画します。 - -### Blaze3D - -MinecraftがOpenGLを直接叩く代わりに用意した静的クラス群。 - -Mod制作では、GLコマンドを直接叩くのではなく、用意されたAPI(RenderSystem等)を通して描画します。 - -### JOML - -数学ライブラリとしては主にJOMLが使用されています。 - -## 座標系 - -座標系は大きく3つあり、それぞれ異なる用途を持っています。 -(Minecraftにおいて明確に定義されているわけではない) - -それらの座標系は主に `PoseStack` によって操作、管理されています。 -([レンダリングAPI](./api.md#posestack)で解説) - -様々な座標系がありますが、結果的にはスクリーン座標系に変換され描画されます。 - -### ワールド座標系 - -ゲーム内のブロックやエンティティが存在する絶対的な座標。 - -ゲーム内で通常使うXYZ座標がこれにあたります。 - -**軸** - -- X+: 東 (East) -- Y+: 上 (Up) -- Z+: 南 (South) - -### ビュー座標系 - -カメラを中心とした座標系。 - -### スクリーン座標系 - -インベントリ画面やHUD(ホットバー、体力等)を描画する際の2D座標。 - -**原点(0,0)**: 画面の左上隅。 - -**軸** - -- X+ 右へ -- Y+ 下へ (ワールド座標とYの向きが逆なので注意!) -- Z+: 奥(深度+) - -### ローカル座標系 - -個々のオブジェクトを中心とした相対的な座標系。 - -### 座標変換 - -基本的に以下の流れで座標変換されます。 - -``` mermaid -graph LR - L[ローカル座標系] --> W[ワールド座標系]; - W --> V[ビュー座標系]; - V --> S[スクリーン座標系]; -``` - -## テクスチャ座標(UV) - -モデルの頂点には、テクスチャのどの部分を貼り付けるかという情報(**UV**)を持たせます。 - -スクリーン座標系と同じ軸と原点を持っていて、U,Vどちらも範囲は0.0~1.0です。 - -- 開始位置: (u0, v0) -- 終了位置: (u1, v1) - -を持ちます。 - -!!! info - - JSONモデルでは、UVはピクセル単位で指定しますが、シェーダーに送られる際には、テクスチャアトラス内での位置として変換されたUV値として扱われます。 - -## 描画方法 - -### 頂点 - -OpenGLにおいて、すべての物体は頂点の集まりによって構成されています。 - -頂点とは、3Dモデルを構成する最小単位の点のことです。 - -**主な値(Minecraftにおいて)** - -- Position: 位置情報 -- Color: 色情報 -- UV: テクスチャ座標 -- Normal(法線): 光の当たり方等を計算するために必要 -- ライトマップ: 光のテクスチャ -- オーバーレイテクスチャ: ダメージ表現やTNTの点滅等に使われるテクスチャのUVの指定 - -## レンダリングパイプライン - -### ラスタライズ - -頂点で構成された図形を、フラグメントに変換する処理。 - -塗りつぶすピクセルごとに頂点の情報(色、UV、法線等)を線形補間して割り当てます。 - -### フラグメント(Fragment) - -ピクセルになる前の計算途中のデータです。 - -ピクセル座標、深度値、テクスチャ座標等、線形補間されたデータを持ちます。 - -### Fragment Shader - -フラグメントシェーダーは、フラグメントの情報を元に、最終的な色を決定するシェーダープログラムです。 - -つまり、描画する色を決定するシェーダープログラムです。 - -例 - -```glsl title="position_color.fsh" -#version 150 - -in vec4 vertexColor; - -uniform vec4 ColorModulator; - -out vec4 fragColor; - -void main() { - vec4 color = vertexColor; - if (color.a == 0.0) { - discard; - } - fragColor = color * ColorModulator; -} -``` - -### Vertex Shader - -頂点シェーダーは、頂点の情報を元に、最終的な位置を決定するシェーダープログラムです。 - -例 - -```glsl title="position_color.vsh" -#version 150 - -in vec3 Position; -in vec4 Color; - -uniform mat4 ModelViewMat; -uniform mat4 ProjMat; - -out vec4 vertexColor; - -void main() { - gl_Position = ProjMat * ModelViewMat * vec4(Position, 1.0); - - vertexColor = Color; -} -``` - -# [rendering\render-options.md] - -# 描画設定 - -## RenderType - -描画についての設定。 - -半透明処理や深度テストの挙動を決めます。 - -`RenderType` 内に定数として存在します。 - -詳しくはクラスを参照してください。 - -### RenderTypeでよく使われる用語 - -| RenderType | 説明 | 備考 | -| --- | --- | ---- | -| `solid` | 不透明 | -| `cutout` | 切り抜き | アルファ値が閾値(通常0.1)を下回るピクセルを破棄し、それ以外を不透明として描画する | -| `mipped` | ミップマップ | 距離に応じてテクスチャ解像度を下げる。遠景のノイズを防ぐ | -| `translucent` | 半透明 | 深度書き込みは無効 | -| `entity` | テクスチャやテクスチャアトラスを指定できる | -| `text` | テキスト | GUIやネームタグのテキスト描画用 | -| `lines` | 線 | -| `gui` | GUI | - -### 独自RenderTypeの作成 - -バニラのRenderTypeに必要な設定がない場合作成します。 - -**RenderType.create** - -- `VertexFormat`: 頂点フォーマット。大体 `DefaultVertexFormat` から指定 -- `VertexFormat.Mode`: 頂点モード -- `bufferSize`: レンダリングバッファサイズ。頻度や内容によるが小規模なら256で十分 -- `RenderType.CompositeState`: `CompositeStateBuilder#createCompositeState` でビルドし渡す - -**CompositeStateBuilder** - -`RenderType.CompositeState.builder()` で作成する。 - -- `setTextureState`: [テクスチャアトラス](./api.md#テクスチャアトラス)の指定 -- `setShaderState`: シェーダーの指定 -- `setTransparencyState`: 半透明の処理方法([#ブレンドモード](#ブレンドモード))の指定 -- `setDepthTestState`: 深度テストの指定 -- `setCullState`: カリングの指定(`CULL`:背面を描画しない) -- `setLightmapState`: [ライトマップ](#ライトマップ)の適用の有無 -- `setOverlayState`: [オーバーレイ](#オーバーレイテクスチャ)の適用の有無 -- `setLayeringState`: レイヤリング(ポリゴンオフセットなど)の指定 -- `setOutputState`: 出力先[レンダーターゲット](#レンダーターゲット)の指定 -- `setTexturingState`: テクスチャの準備の指定 -- `setWriteMaskState`: 書き込むバッファのマスク設定(カラーバッファや深度バッファ) -- `setLineState`: 線の太さを指定 -- `setColorLogicState`: 色調整ののモードを指定 -- `createCompositeState`: 設定をビルドする - -バニラのRenderTypeの例 - -```java -// RenderType.SOLID -private static final RenderType SOLID = RenderType.create( - "solid", - DefaultVertexFormat.BLOCK, - VertexFormat.Mode.QUADS, - 2097152, - true, - false, - RenderType.CompositeState.builder() - .setLightmapState(LIGHTMAP) - .setShaderState(RENDERTYPE_SOLID_SHADER) - .setTextureState(BLOCK_SHEET_MIPPED) - .createCompositeState(true) -``` - -## VertexFormat - -GPUに送る情報のレイアウトを決定するためのフォーマット。 - -各要素は Vertex Shader の `attribute` 変数に対応します。 - -基本、`DefaultVertexFormat`から取得します。 - -!!! warning - - `VertexFormat` で指定されている順番通りに `VertexConsumer` に頂点情報を渡す必要があります。 - -**DefaultVertexFormat** の定数 - -| 名前 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | -| --- | --- | --- | --- | --- | --- | --- | --- | -| `BLIT_SCREEN` | Position | UV | Color | -| `BLOCK` | Position | Color | UV0 | UV2 | Normal | Padding | -| `NEW_ENTITY` | Position | Color | UV0 | UV1 | UV2 | Normal | Padding | -| `PARTICLE` | Position | UV0 | Color | UV2 | -| `POSITION` | Position | -| `POSITION_COLOR` | Position | Color | -| `POSITION_COLOR_NORMAL` | Position | Color | Normal | Padding | -| `POSITION_COLOR_LIGHTMAP` | Position | Color | UV2 | -| `POSITION_TEX` | Position | UV0 | -| `POSITION_COLOR_TEX` | Position | Color | UV0 | -| `POSITION_TEX_COLOR` | Position | UV0 | Color | -| `POSITION_COLOR_TEX_LIGHTMAP` | Position | Color | UV0 | UV2 | -| `POSITION_TEX_LIGHTMAP_COLOR` | Position | UV0 | UV2 | Color | -| `POSITION_TEX_COLOR_NORMAL` | Position | UV0 | Color | Normal | Padding | - -`"UV0"` と `"UV"` は同値である。 - -**VertexFormatの要素** - -| 名前 | 説明 | 対応するメソッド(VertexConsumer) | -| --- | --- | --- | -| Position | 頂点座標 | `vertex(x, y, z)` | -| Color | 色 | `color(r, g, b, a)` | -| UV0 | テクスチャUV | `uv(u, v)` | -| UV1 | オーバーレイUV | `overlayCoords(u, v)` | -| UV2 | ライトマップUV | `uv2(u, v)` | -| Normal | 法線 | `normal(x, y, z)` | -| Padding | 余白 | - -## ブレンドモード - -ブレンドモードとは、透明度によってどのように描画色を決定するかの設定の事です。 - -`RenderStateShard` の中の定数としていくつか存在する。 - -- `src`: ソースのRGB色(描画対象) -- `src.a`: ソースの透明度 -- `dst`: デスティネーションのRGB色(既に描画されている色) -- `dst.a`: デスティネーションの透明度 - -| 名称 | 説明 | 式 | 備考 | -| --- | ---- | ---- | --- | -| `NO_TRANSPARENCY` | 不透明 | -| `ADDITIVE_TRANSPARENCY` | 加算合成 | `src + dst` | alpha無視 | -| `LIGHTNING_TRANSPARENCY` | 発光合成 | `src * src.a + dst` | -| `GLINT_TRANSPARENCY` | エンチャントの輝き | `rgb = src * src.a + dst, a = dst.a` | -| `CRUMBLING_TRANSPARENCY` | 破壊表現の合成 | `rgb = 2 * src * dst, a = src.a` | -| `TRANSLUCENT_TRANSPARENCY` | 半透明合成 | `rgb = src * src.a + dst * (1 - src.a), a = src.a + dst.a * (1 - src.a)` | - -## 深度 - -奥行きのこと。 -描画順にかかわらず、奥のオブジェクトが手前のオブジェクトに常に隠れるようにするために存在する。 - -**深度バッファ**というものが存在し、各ピクセルに深度が保存されています。 - -### 深度テスト - -深度値と既存の深度バッファ内の値を比較し、判定をパスしたピクセルのみを書き込むことで、前後関係を再現するためのもの。 - -#### 比較関数 - -| 名称 | 説明 | -| --- | --- | -| `NO_DEPTH_TEST` | 深度テストを行わない | -| `EQUAL_DEPTH_TEST` | 深度値が対象と等しい場合のみ描画 | -| `LEQUAL_DEPTH_TEST`(既定値) | 深度値が対象以下の場合のみ描画 | -| `GREATER_DEPTH_TEST` | 深度値が対象より大きい場合のみ描画 | - -## カリング - -裏面を描画するかどうか。 - -- `CULL`: 裏面を描画しない -- `NO_CULL`: 裏面を描画する - -## ライトマップ - -ブロックライト(U)とスカイライト(V)を組み合わせた結果の明るさの色をキャッシュした16x16のテクスチャ。 - -実際に頂点に格納するUVの値は圧縮されており、 - -その値は `LightTexture.pack(int blockLight, int skyLight)` で指定する値を取得できます。 - -## オーバーレイテクスチャ - -汎用なオーバーレイ用の16x16のテクスチャ。 - -ライトマップと同じく頂点に格納するUVの値は圧縮されており、 - -`OverlayTexture.pack(int u, int v)` で指定する値を取得できます。 - -以下の用途で使われています。 - -- 負傷時/死亡時の赤色表示 -- クリーパー点滅時の白色 -- TNT点滅時の白色 - -## レイヤリング - -Z-Fightingを回避するために使用されるオプション - -| 名称 | 説明 | -| --- | --- | -| `NO_LAYERING` | レイヤリングを行わない | -| `POLYGON_OFFSET_LAYERING` | `glPolygonOffset` を使用し深度値を手前にずらす | -| `VIEW_OFFSET_Z_LAYERING` | `PoseStack` を微妙に内側にスケールする | - -## レンダーターゲット - -描画対象のバッファ。 - -大抵は最終的にmainレンダーターゲットに描画されます。 - -例えば半透明な描画の場合 `translucent` ターゲットに描画され、最終的に `main` レンダーターゲットに合成されます。 - -# [rendering\renderer\item.md] - -# アイテムレンダラー - -## 独自レンダラーを作成する - -### レンダラークラスの作成 - -`BlockEntityWithoutLevelRenderer` の継承クラスを作る。 - -```java -class ExampleItemRenderer extends BlockEntityWithoutLevelRenderer { - - public ExampleItemRenderer(BlockEntityRenderDispatcher dispatcher, EntityModelSet modelSet) { - super(dispatcher, modelSet); - } - - @Override - public void renderByItem(ItemStack itemStackIn, ItemDisplayContext type, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int packedOverlay) { - // レンダリング - } -} -``` - - - -### Itemに登録 - -`Item#initializeClient` メソッドをオーバーライドし、 -`consumer.accept` に `IClientItemExtensions` の実装を渡します。 - -例では匿名クラスを使用して登録しています。 - -```java -public class ExampleItem extends Item { - public ExampleItem(Item.Properties properties) { - super(properties); - } - - @Override - public void initializeClient(Consumer consumer) { - consumer.accept( - new IClientItemExtensions() { - private final BlockEntityWithoutLevelRenderer renderer = new ExampleItemRenderer( - Minecraft.getInstance().getBlockEntityRenderDispatcher(), - Minecraft.getInstance().getEntityModels() - ); - - @Override - public BlockEntityWithoutLevelRenderer getCustomRenderer() { - return renderer; - } - } - ); - } -} -``` - -### モデルの設定 - -[#builtin/entity](./model.md#builtinentity) を参照してください。 - -モデルの `"parent"` を `"builtin/entity"` に設定する必要があります。 - -```json -{ - "parent": "builtin/entity" -} -``` - -# [rendering\renderer\model.md] - -# モデル - -## ビルトイン `parent` モデル - -モデルの `"parent"` に指定される、特別なモデル - -### builtin/generated - -アイテムのテクスチャに合ったモデルが `ItemModelGenerator` によって自動生成されます。 - -多くのフラットなアイテムはこれによって生成されています。 - -`item/generated` は `builtin/generated` を親モデルにしており、同じ効果が得られます。 - -### builtin/entity - -`BuiltInModel` としてモデルが登録され、唯一 `IClientItemExtensions`の `getCustomRenderer` が適用されるモデルです。(`BakedModel#isCustomRenderer`が`true`) - -その代わりに、モデルが一切描画されません。 - -## Forge モデルローダー - -詳しくは [Custom Model Loader - Neoforge](https://docs.neoforged.net/docs/1.20.4/resources/client/models/modelloaders) を参照してください。 - -Neoforgeですがネームスペース以外は仕様がほとんど同じです。 - -| ID | 説明 | -| --- | --- | -| `forge:empty` | 何も描画されない | -| `forge:elements` | `elements`と`transform`で構成されるモデル | -| `forge:obj` | OBJモデルを読み込める。`.obj`と`.mtl`が必要 | -| `forge:fluid_container` | バケツやタンクなど、液体を含むモデル | -| `forge:composite` | 複数のモデルを組み合わせる | -| `forge:item_layers` | レイヤーの数が無制限で、レイヤーごとに `RenderType` を設定可能 | -| `forge:separate_transforms` | 視点によってモデルを変更できる | - -## 独自モデルローダーの作成 - -実行時にモデルを生成して、ロードさせたり、アイテムによってモデルを変更するといったことができます。 - -以下2つの事が可能です。 - -### 1. モデル(Quad)を自動生成する - -`IUnbakedGeometry#bake` を `SimpleUnbakedGeometry` が事前に定義してくれているため、`addQuads` によるQuadの登録をします。 - -```java -public class CubeModel extends SimpleUnbakedGeometry { - - public static final IGeometryLoader LOADER = CubeModel::deserialize; - - // 別クラスに移動しても良い - public static CubeModel deserialize(JsonObject json, JsonDeserializationContext context) { - float size = GsonHelper.getAsFloat(json, "size"); - - return new CubeModel(size); - } - - private final float size; - - public CubeModel(float size) { - this.size = size; - } - - @Override - protected void addQuads(IGeometryBakingContext context, IModelBuilder modelBuilder, ModelBaker modelBaker, Function spriteGetter, ModelState modelState, ResourceLocation modelLocation) { - - List blockElements = new ArrayList<>(); - - Map faces = new EnumMap<>(Direction.class); - - Material baseLocation = context.getMaterial("base"); - TextureAtlasSprite baseSprite = spriteGetter.apply(baseLocation); - SpriteContents contents = baseSprite.contents(); - - for (Direction face : Direction.values()) { - faces.put( - face, - new BlockElementFace(null, -1, baseLocation.texture().toString(), new BlockFaceUV( - new float[]{0f, 0f, contents.width(), contents.height()}, 0 - )) - ); - } - - blockElements.add(new BlockElement( - new Vector3f(8f - size, 8f - size, 8f - size), - new Vector3f(8f + size, 8f + size, 8f + size), - faces, - null, - false - )); - - UnbakedGeometryHelper.bakeElements(modelBuilder, blockElements, spriteGetter, modelState, modelLocation); - } -} -``` - -### 2. アイテムごとにモデルを変更する - -`IUnbakedGeometry#bake` で `ItemOverrides` を入れ替えることによって実現できます。 - -`ItemOverrides#resolve` で動的にモデルを切り替えています。 - -キャッシュなどを使用し、Quad自動生成と組み合わせることで動的に自由にモデルを変更可能です。 - -例のJsonモデル: - -```json -{ - "loader": "examplemod:example", - "small": { - "parent": "examplemod:item/example_small" - }, - "large": { - "parent": "examplemod:item/example_large" - } -} -``` - -この例では -アイテムの個数が32以上の時、`item/example_small` になり、 - -32未満の場合 `item/example_large` になります。 - -```java -public class ExampleModel extends SimpleUnbakedGeometry { - - public static final IGeometryLoader LOADER = ExampleModel::deserialize; - - public final BlockModel smallModel; - public final BlockModel largeModel; - - public ExampleModel(BlockModel smallModel, BlockModel largeModel) { - this.smallModel = smallModel; - this.largeModel = largeModel; - } - - // 別クラスに移動しても良い - public static ExampleModel deserialize(JsonObject json, JsonDeserializationContext context) { - BlockModel smallModel = context.deserialize(GsonHelper.getAsJsonObject(json, "small"), BlockModel.class); - BlockModel largeModel = context.deserialize(GsonHelper.getAsJsonObject(json, "large"), BlockModel.class); - - return new ExampleModel(smallModel, largeModel); - } - - @Override - public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, ItemOverrides overrides, ResourceLocation modelLocation) { - overrides = new ExampleOverrides(); - - BakedModel bakedSmallModel = this.smallModel.bake(baker, this.smallModel, spriteGetter, modelState, modelLocation, context.useBlockLight()); - BakedModel bakedLargeModel = this.largeModel.bake(baker, this.largeModel, spriteGetter, modelState, modelLocation, context.useBlockLight()); - - return new Baked( - context.useAmbientOcclusion(), - context.isGui3d(), - context.useBlockLight(), - spriteGetter.apply(context.getMaterial("particle")), - overrides, - bakedSmallModel, - bakedLargeModel - ); - } - - @Override - public void resolveParents(Function modelGetter, IGeometryBakingContext context) { - this.smallModel.resolveParents(modelGetter); - this.largeModel.resolveParents(modelGetter); - super.resolveParents(modelGetter, context); - } - - @Override - protected void addQuads(IGeometryBakingContext iGeometryBakingContext, IModelBuilder iModelBuilder, ModelBaker modelBaker, Function function, net.minecraft.client.resources.model.ModelState modelState, ResourceLocation resourceLocation) { - - } - - public static class Baked implements IDynamicBakedModel { - private final boolean isAmbientOcclusion; - private final boolean isGui3d; - private final boolean isSideLit; - private final TextureAtlasSprite particle; - private final ItemOverrides overrides; - private final BakedModel smallModel; - private final BakedModel largeModel; - - public Baked(boolean isAmbientOcclusion, boolean isGui3d, boolean isSideLit, TextureAtlasSprite particle, ItemOverrides overrides, BakedModel smallModel, BakedModel largeModel) { - this.isAmbientOcclusion = isAmbientOcclusion; - this.isGui3d = isGui3d; - this.isSideLit = isSideLit; - this.particle = particle; - this.overrides = overrides; - - this.smallModel = smallModel; - this.largeModel = largeModel; - } - - public @NotNull List getQuads(@Nullable BlockState state, @Nullable Direction side, @NotNull RandomSource rand, @NotNull ModelData data, @Nullable RenderType renderType) { - return smallModel.getQuads(state, side, rand, data, renderType); - } - - @Override - public boolean useAmbientOcclusion() { - return this.isAmbientOcclusion; - } - - @Override - public boolean isGui3d() { - return this.isGui3d; - } - - @Override - public boolean usesBlockLight() { - return this.isSideLit; - } - - @Override - public boolean isCustomRenderer() { - return false; - } - - @Override - public @NotNull TextureAtlasSprite getParticleIcon() { - return this.particle; - } - - @Override - public @NotNull ItemOverrides getOverrides() { - return overrides; - } - } - - public static class ExampleOverrides extends ItemOverrides { - - @Override - public @Nullable BakedModel resolve(@NotNull BakedModel model, @NotNull ItemStack itemStack, @Nullable ClientLevel level, @Nullable LivingEntity livingEntity, int partialTicks) { - int count = itemStack.getCount(); - - if(model instanceof Baked baked) { - return count >= 32 ? baked.largeModel : baked.smallModel; - } - return model; - } - } -``` - -### モデルローダーの登録 - -`ModelEvent.RegisterGeometryLoaders` イベントで登録します。 - -MODバスに登録してください。 - -```java -@Mod.EventBusSubscriber(modid = ExampleMod.MODID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD) -public class ModelClientEvents { - - @SubscribeEvent - public static void registerModelLoaders(ModelEvent.RegisterGeometryLoaders loaders) { - loaders.register("cube", CubeModel.LOADER); - loaders.register("example", ExampleModel.LOADER); - } -} -``` - -第一引数がIDとなり、これはJsonモデルの `"loader"` に当たります。 \ No newline at end of file