ゲームを作りながら学ぶ Java実践④

マインクラフト ゲーム開発

  * 要件の中には、検証してみないとできるかわからない要件も含む

設計

機能要件

  • 体力や空腹ゲージは最大化されること
    • 前提条件を合わせるため
  • 一定のエリア内でしか敵は発生しないこと
    • エリア外で発生しても倒せない
  • 敵の種類はランダムであること
  • 装備や武器はプレイするたびにおなじになること
    • 今の装備を取得して保存しといて、ゲームが始まったら指定の装備にして、終わったらもとの装備に戻す等
    • 対象のプレイヤーのインベントリの中身を直接書き換えることで実現する。
    • コマンド実行時に差し替えて、最終的にはコマンド実行前の装備の状況を保存しておき、ゲーム終了後戻したい

  • 敵を倒すと点数が手に入ること

  • スコア(合計点数)ボードが表示できること
    • 画面上にバーンと出るようにする
    • スコアボードにするなど
  • 時間制限を設定できること
  • 敵の種類によって手に入る点数が異なること
    • ゲーム性を高めるため
    • ゲーム性をしてどうあるべきか
  • 時間制限が来たらエリア内の敵は消滅すること
    • 終わったことをはっきりさせるため
  • 時間制限が来たら合計の点数が保存されること
    • できればデータベースなんかに保存して取り出せるようにしておきたい
  • 保存する情報はスコアとプレイヤー名と日時
  • 新しい情報が入った場合は上書きではなく、すべて保存すること
    • 追加されていく

非機能要件

  • コマンドでゲームを開始できる
    • コマンドでできる方が簡単だから
  • ゲーム中のエリア内のブロックは何があっても破壊されない
    • ゲームモードの変更をしなくてもプラグインで制御できればというもの
  • ゲーム中のオプションでプレイヤーの強さ、敵の種類をある程度コントロールできる
  • 敵の出現数が一定数を超えたときにゲームが重たくならないようにする。
    • マシンのスペックは人によって違うので考慮する
    • 敵の数を制御する
    • 超えたら前に出現したものから消える
  • プラグインを導入すればSpigotを使っていればどのサーバーでも導入できる
  • プログラムへの変更を加えずに、時間やスコアの項目などの設定値をある程度変更できる
    • お客さんはプログラムをいじることができない前提
    • プログラムをいじるのではなく、プログラム内の設定などのできることのモードを作っておく
  • 複数のプレイヤーが同時に実行しても動作すること

機能要件

  • 体力や空腹ゲージは最大化されること
    • コマンドを実行したら、体力と空腹値に20を設定する
  • 敵を倒すと点数が手に入ること
    EntityのSpawnの仕組みを使って敵を出現させる
    Entityが倒れたときのイベントを使って、点数を設定する。
    • Entityが死亡する際のイベントを取得して、そのときに点数を加算する。
    • その際にプレイヤーが倒したEntityであるという条件を付け加えて加算する。
    • プレイヤーは、コマンドを実行したプレイヤーとする。

敵を倒すと点数を獲得して合計を加算する

実装

まず、「Main」classの

public final class Main extends JavaPlugin implements Listener, org.bukkit.event.Listener {

    @Override
    public void onEnable() {
        Bukkit.getPluginManager().registerEvents(this, this);
        getCommand("enemyDown").setExecutor(new EnemyDownCommand());
    }
    
    @EventHandler
    public void onEnemyDeath(EntityDeathEvent e){
        
   }
}

ここに「EntityDeathEvent」を入力。
ここでは、すべてのEntityを示してしまっている。

しかし、ここで問題があり、ここにイベントを発生させてしまうと、誰でも良いとなってしまうので、コマンドを実行したプレイヤーとはならないため、ここには記述せずコードは消しておく。

そこで、「EnemyDownCommand」クラスに移って

public class EnemyDownCommand implements CommandExecutor {

の部分に

public class EnemyDownCommand implements CommandExecutor, Listrner {

を追加する。
(カンマ区切りですると「インプリメンツ」は複数もたせることができる)

これをしたところで「Main」classに移る。

そこで

「Main」classは、実は何も受け取っていないので変更を加えていく

public final class Main extends JavaPlugin implements Listener, org.bukkit.event.Listener 

の部分は、

public final class Main extends JavaPlugin 

今はいらないところを削除してこうしておく

   @Override
    public void onEnable() {
        Bukkit.getPluginManager().registerEvents(new EnemyDownCommand(), this);
        getCommand("enemyDown").setExecutor(new EnemyDownCommand());
    }

とするが、エラーが出てしまっているが、画像に従って進めてみる。

ここで、「EnemyDownCommand enemyDownCommand = new EnemyDownCommand();」

を挿入すると、newしたものが一個となるので、上下の「EnemyDownCommand()」が違うものにならないようになるので、

    @Override
    public void onEnable() {
        EnemyDownCommand enemyDownCommand = new EnemyDownCommand();
        Bukkit.getPluginManager().registerEvents( enemyDownCommand, this);
        getCommand("enemyDown").setExecutor( enemyDownCommand);
    }

としておく

そして、ここで「EnemyDownCommand」クラスに移って「イベントハンドラーを追加していく」

  @EventHandler
  public void onEnemyDeath(EntityDeathEvent e){
    e.getEntity().getKiller();
  }

ここまで入力したところで、変数で受けるために「getKiller」にカーソルをあてて「リファクタリング」→「変数の導入」→「player」を選択
ここでは、コマンドを実行したプレイヤーかどうかは判別していない。

そこで、まず仮に
プライヤー情報を取ってくるために

フィールドの部分に「private Player player;」を記述し、

「if(sender instanceof Player player)」の下に記述すると

コマンドを実行したプレイヤーがフィールドに記述したプレイヤー「private Player player(この部分)」に保存される。

ここで、作っているイベントハンドラーに移り

  public void onEnemyDeath(EntityDeathEvent e){
    Player player = e.getEntity().getKiller();
    if (this.player.getName().equals(player)){
      
    }
  }

とする。(プレイヤーの名前が一致したときだけとする。つまり、コマンドを実行したプレイヤーということになる)

そして、スコアを出すために、フィールドに「private int score;」を入力し

その後、イベントハンドラーを

  @EventHandler
  public void onEnemyDeath(EntityDeathEvent e){
    Player player = e.getEntity().getKiller();
    if (this.player.getName().equals(player.getName())){
      score += 10;
      player.sendMessage("敵を倒した! 現在のスコアは" + score + "点");
    }
  }

こうする。

ここで、やはり、エラーが出てビルドができない。

エラー内容を見てみると、どこか実装しているものが、うまく動かせていないような感じなので、エラー候補からえらんで「Main」classの一番上の部分を

public class EnemyDownCommand implements CommandExecutor, Listener, org.bukkit.event.Listener 

こうしてみました。

ここで実証!!

成功!!!

画像では、エラーが出るとされているが、エラーは出ていないので、そのままにしてみる。

が、やはり

    if (Objects.isNull(player)){
      return;
    }
    if (Objects.isNull(this.player)){
      return;
    }

を追記して実行してみる。

変更しても同じように動いたので、そのままにしておきます。

最終的なコードとしては

「Main class」

package plugin.enemydown;

import java.net.http.WebSocket.Listener;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.plugin.java.JavaPlugin;
import plugin.enemydown.command.EnemyDownCommand;

public final class Main extends JavaPlugin {

    @Override
    public void onEnable() {
        EnemyDownCommand enemyDownCommand = new EnemyDownCommand();
        Bukkit.getPluginManager().registerEvents( enemyDownCommand, this);
        getCommand("enemyDown").setExecutor( enemyDownCommand);
    }


}

「EnemyDownCommand class」

package plugin.enemydown.command;

import java.net.http.WebSocket.Listener;
import java.util.List;
import java.util.Objects;
import java.util.SplittableRandom;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;

public class EnemyDownCommand implements CommandExecutor, Listener, org.bukkit.event.Listener {

  private Player player;
  private int score;

  @Override
  public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
    if(sender instanceof Player player){
      this.player = player;

      //前提条件
      World world = player.getWorld();

      initPlayerStatus(player);

      world.spawnEntity(getEnemySpawnlocation(player, world), getEnemy());
    }
      return false;
  }

  @EventHandler
  public void onEnemyDeath(EntityDeathEvent e){
    Player player = e.getEntity().getKiller();
    if (Objects.isNull(player)){
      return;
    }
    if (Objects.isNull(this.player)){
      return;
    }

    if (this.player.getName().equals(player.getName())){
      score += 10;
      player.sendMessage("敵を倒した! 現在のスコアは" + score + "点");
    }
  }

  /**
   * ゲームを始める前にプレイヤーの状態を設定する。
   * 体力と空腹度を最大にして、装備はネザライト一式になる。
   *
   * @param player コマンドを実行したプレイヤー
   */
  private void initPlayerStatus(Player player) {

    player.setHealth(20);
    player.setFoodLevel(20);

    PlayerInventory inventory = player.getInventory();
    inventory.setHelmet(new ItemStack(Material.NETHERITE_HELMET));
    inventory.setChestplate(new ItemStack(Material.NETHERITE_CHESTPLATE));
    inventory.setLeggings(new ItemStack(Material.NETHERITE_LEGGINGS));
    inventory.setBoots(new ItemStack(Material.NETHERITE_BOOTS));
    inventory.setItemInMainHand(new ItemStack(Material.NETHERITE_SWORD));
  }

  /**
   *敵の出現場所を取得します。
   * 出現エリアはX軸とZ軸は、自分の一からプラス、ランダムで−10〜9の値がsettingされます。
   * y軸はプレイヤーと同じ位置になります。
   *
   * @param player コマンドを実行したプレイヤー
   * @param world コマンドを実行したプレヤーが所属するワールド。
   * @return 敵の出現場所
   */

  private Location getEnemySpawnlocation(Player player, World world) {
    Location playerLocation = player.getLocation();
    int randomX= new SplittableRandom().nextInt(20) - 10;
    int randomZ= new SplittableRandom().nextInt(20) - 10;

    double x = playerLocation.getX()+randomX;
    double y = playerLocation.getY();
    double z = playerLocation.getZ()+randomZ;

    return new Location(world, x, y, z);
  }

  /**
   * ランダムで敵を抽出して、その結果の敵を取得します。
   * <p>
   * @return 敵
   */
  private EntityType getEnemy() {
    List<EntityType> enemyList =  List.of(EntityType.ZOMBIE, EntityType.SKELETON);
    int random= new SplittableRandom().nextInt(2);
    return enemyList.get(random);
  }
}

となりました。

ここは、プレイヤーが一個しか持ててないので、マルチになってないので変更が必要。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次