マインクラフト ゲーム開発
* 要件の中には、検証してみないとできるかわからない要件も含む
設計
機能要件
- 体力や空腹ゲージは最大化されること
- 前提条件を合わせるため
- 前提条件を合わせるため
- 一定のエリア内でしか敵は発生しないこと
- エリア外で発生しても倒せない
- エリア外で発生しても倒せない
- 敵の種類はランダムであること
- 装備や武器はプレイするたびにおなじになること
- 今の装備を取得して保存しといて、ゲームが始まったら指定の装備にして、終わったらもとの装備に戻す等
- 対象のプレイヤーのインベントリの中身を直接書き換えることで実現する。
- コマンド実行時に差し替えて、最終的にはコマンド実行前の装備の状況を保存しておき、ゲーム終了後戻したい
- 敵を倒すと点数が手に入ること
- スコア(合計点数)ボードが表示できること
- 画面上にバーンと出るようにする
- スコアボードにするなど
- 時間制限を設定できること
- 敵の種類によって手に入る点数が異なること
- ゲーム性を高めるため
- ゲーム性をしてどうあるべきか
- 時間制限が来たらエリア内の敵は消滅すること
- 終わったことをはっきりさせるため
- 終わったことをはっきりさせるため
- 時間制限が来たら合計の点数が保存されること
- できればデータベースなんかに保存して取り出せるようにしておきたい
- できればデータベースなんかに保存して取り出せるようにしておきたい
- 保存する情報はスコアとプレイヤー名と日時
- 新しい情報が入った場合は上書きではなく、すべて保存すること
- 追加されていく
- 追加されていく
非機能要件
- コマンドでゲームを開始できる
- コマンドでできる方が簡単だから
- コマンドでできる方が簡単だから
- ゲーム中のエリア内のブロックは何があっても破壊されない
- ゲームモードの変更をしなくてもプラグインで制御できればというもの
- ゲームモードの変更をしなくてもプラグインで制御できればというもの
- ゲーム中のオプションでプレイヤーの強さ、敵の種類をある程度コントロールできる
- 敵の出現数が一定数を超えたときにゲームが重たくならないようにする。
- マシンのスペックは人によって違うので考慮する
- 敵の数を制御する
- 超えたら前に出現したものから消える
- プラグインを導入すればSpigotを使っていればどのサーバーでも導入できる
- プログラムへの変更を加えずに、時間やスコアの項目などの設定値をある程度変更できる
- お客さんはプログラムをいじることができない前提
- プログラムをいじるのではなく、プログラム内の設定などのできることのモードを作っておく
- 複数のプレイヤーが同時に実行しても動作すること
機能要件
- 体力や空腹ゲージは最大化されること
- コマンドを実行したら、体力と空腹値に20を設定する
- コマンドを実行したら、体力と空腹値に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);
}
}
となりました。
ここは、プレイヤーが一個しか持ててないので、マルチになってないので変更が必要。
コメント