マインクラフトmodding キー、画面、Guiの搭載

この記事はマインクラフトforge 1.19 ~ 1.19.4について書いてあります。

参考程度にどうぞ。

### 記事を書いた理由
1.19は新しくて参考になる記事がなかったため

## 前提
MODIDは`examplemod`とし、languageファイルは以下の通り
```json:en_us.json
{
  "key.examplemod.category": "Key category",
  "key.examplemod.setting": "Key setting",
  "screen.examplemod.setting": "Example screen",
  "screen.examplemod.sub": "Sub screen",
  "button.examplemod.setting": "Button!"
}
```
`resources/assets/examplemod/lang/`下に置く(MODIDは小文字)

**継承元クラス、イベントクラス以外のクラス名に決まりはありません。ただ、分かりやすくすることをお勧めします。**

## キー設定
![キー設定](https://gyazo.com/9ad62102126a3874bc1f6f685090594e.png)
```java:KeyManager.java
package examplemod;

import com.mojang.blaze3d.platform.InputConstants;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraftforge.client.event.InputEvent;
import net.minecraftforge.client.event.RegisterKeyMappingsEvent;
import net.minecraftforge.client.settings.KeyConflictContext;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

@Mod.EventBusSubscriber(modid = "examplemod")
public class KeyManager {
  static KeyMapping setting = new KeyMapping(
      "key.examplemod.setting",
      KeyConflictContext.IN_GAME,
      InputConstants.Type.KEYSYM,
      'J', // char型
      "key.examplemod.category"
  );

  @SubscribeEvent
  public static void keyRegister(RegisterKeyMappingsEvent event) { // 引数の型は変更しない。
    event.register(setting);  // キー設定を登録
  }

  @SubscribeEvent
  public static void keyPressed(InputEvent.Key event) {
    if (setting.consumeClick()) {  // もし J キーが押されたら
      Minecraft.getInstance().setScreen(new SettingScreen());  // SettingScreenを開く
    }
  }
}
```


## スクリーン

**init()** 内に初期内容を書く。今回は、SettingScreenからボタンを押してSubScreenに移動するスクリプトを書く

![スクリーン](https://gyazo.com/81e531dd9cfba87d3edf4558938531a1.png)
---

```java:SettingScreen.java
package examplemod;

import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;

public class SettingScreen extends Screen {
  public SettingScreen() {
    super(Component.translatable("screen.examplemod.setting"));
  }

  @Override
  protected void init() {
    Button button = Button.builder(
            Component.translatable("button.examplemod.setting"), // Button! と表示
            b -> Minecraft.getInstance().setScreen(new SubScreen(this))  // ボタンを押したらSubScreenに移動
        )
        // スクリーンの幅の半分から左に75、高さの半分から10上から描画する
        .pos(this.width / 2 - 75, this.height / 2 - 10)
        .size(150, 20)  // 横150、高さ20のボタンを描画
        .build();  // インスタンス化
    addRenderableWidget(button);
  }
}
```

```java:SubScreen.java
package examplemod;

import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;

public class SubScreen extends Screen {
  Screen parent;
  public SubScreen(Screen parent) {
    super(Component.translatable("screen.examplemod.sub"));
    this.parent = parent;
  }
  @Override
  protected void init() {
    // ここに描画内容を書く
  }
}
```

## GUI

左上にプレイヤーの現在座標を表示

![座標](https://gyazo.com/b2efbfad749e38877cecccb163ebc8df.png)

--- 
```java:MainGui.java
package examplemod;

import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Gui;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.client.event.RenderGuiOverlayEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

import java.awt.*;

@Mod.EventBusSubscriber(modid = "examplemod")
public class MainGui extends Gui {
  static Minecraft mc = Minecraft.getInstance();
  public MainGui() {
    super(Minecraft.getInstance(), Minecraft.getInstance().getItemRenderer());
  }

  @SubscribeEvent
  public static void onPostRenderGuiOverlayEvent(RenderGuiOverlayEvent.Post event) { // 画面が表示されたとき
    if (mc.player == null) return;
    RenderSystem.setShaderColor(
        1f,
        1f,
        1f,
        1f
    );
    PoseStack stack = event.getPoseStack();
    Vec3 vec3 = mc.player.position();
    String pos = String.format("X: %.4f / Y: %.4f / Z: %.4f", vec3.x, vec3.y, vec3.z);  // 下4桁まで
    stack.pushPose();
    mc.font.draw(
        stack,
        pos,  // 表示する文字列
        0,  // 座標 0, 0 に描画
        0,
        Color.GREEN.getRGB()  // 緑色
    );
    stack.popPose();
  }
}
```

## 最後に

`@net.minecraftforge.eventbus.api.SubscribeEvent`はイベント検知をするときに使う。
このアノテーションのみを使用する場合、`Mod`アノテーションの付けられたクラスのコンストラクタ内で、
```java:ExampleMod.java
MinecraftForge.EVENT_BUS.register(new Hoge());
```
register内に`SubscribeEvent`アノテーションの付けられたクラスのインスタンスを登録する必要がある。

これが面倒くさい場合は、
`@net.minecraftforge.fml.common.Mod.EventBusSubscriber`アノテーションを使うこともできる。
クラスにこれを付けると、register()に登録する必要がなくなる。
ただ、`@SubscribeEvent`を付けられたメソッドは**static**を付ける必要がある。

mdkをダウンロードしたサンプルコードの内部に
```java
// You can use EventBusSubscriber to automatically register all static methods in the class annotated with @SubscribeEvent
```
とある。