👋

Javaの基本概念をポケットモンスター風育成ゲームでオブジェクト指向を理解する

に公開

更新中最新記事はこちら:https://qiita.com/nakamoto/items/2f376266988176cf2f7c

画像は ChatGPT4o で生成したものです。

Javaの初学者の方向けに、Javaを好きになりながら基本概念をイメージしてもらう目的で書いています。

  1. ゲームを起動する
  2. 変数というボール
  3. 配列というボールケース
  4. データの種類(データ型)
  5. もし〇〇だったらというif文
  6. ◯回繰り返すfor文
  7. 表示させる
  8. 入力する
  9. メソッドというモンスターのわざ

ゲームを起動する

コード内の日本語表記はプログラマーが変更するところだと思ってください

// アプリを起動した瞬間
package モンスター育成パッケージ;
import java.util;
public class App {
  // ゲーム画面が表示される瞬間
  public static void main(String[] args) {
    String ボール = "黄色いピカピカしたモンスター";
    }
  }
}

オブジェクト

プログラミングを学習する時、私がずっと腑に落ちなかった概念にオブジェクトというものがあります。
学習時、オブジェクトというのは物であると教わりました。
英語でいうとものです。
いまだからわかります、物です。
でも、イメージが付きませんよね。
「ここにゲーム画面の画像をいれる」
ゲームをしていると、それより先に進めない木や岩があると思います。
それが、オブジェクトです。
私の中で、最もイメージが付いたのが、ゲーム内の木や岩でした。
通れないからオブジェクトということでは有りません。
フォートナイトのように壊せる木や岩もオブジェクトです。
壊せるかどうかが重要ではなく、手に持っているボールもオブジェクトです。
プレイヤーのキャラクターも敵キャラクターもオブジェクトです。
つまり、ゲーム世界に存在するもすべてがオブジェクトです。
オブジェクトは、存在するものひとつひとつ、すべてのことを意味します
もちろん、モンスターもオブジェクトです。
image.png

変数

変数はモンスターを入れておくボールだと思ってください。
モンスター育成ゲームでは、モンスターと一緒に旅をします。
モンスターは100や200に増えるため、すべてのモンスターと二人三脚で歩くのも大変です。
そこで、モンスターを変数というボールに入れておきます。
image.png
次のコードは、 ボール 変数に 黄色いピカピカしたモンスター を入れた状態です。
ボール という変数名は自由に変更可能です。

// アプリを起動した瞬間
package モンスター育成パッケージ;
import java.util;
public class App {
  // ゲーム画面が表示される瞬間
  public static void main(String[] args) {
    String ボール = "黄色いピカピカしたモンスター";
    }
  }
}

これにより、次の 黄色いピカピカしたモンスター の歩幅に合わせて歩く必要がなくなり、 黄色いピカピカしたモンスター が疲れたから抱っこし続ける必要もなく、 ボール を持ち運べば良く、旅の足取りが軽くなります。
image.png

配列

配列とは、ボールを管理する箱です。
黄色いピカピカしたモンスターボール に入れて旅をする方法もありますが、 ボール という変数が10個ぐらいになってくると、どのボールに何が入っているか管理するのが大変になります。
そこで、 ボール変数 をまとめて管理する専用のケースである 配列 を使います。
今回は ボールを8個まとめる専用ケースの配列 をイメージして考えましょう。
黄色の2つのボール変数には、どちらも 黄色いピカピカしたモンスター が入っています。
他の色のボールには、それぞれ、水色のそーなんだーって感じるモンスター赤色のファイヤーボール出しそうなモンスター が入っていたりします。
配列という専用のケースに入れているだけで、一気にすべてのモンスターを回復させることができます。
image.png
また、 0 番目に入っているモンスターだけを個別に指定して取り出すことも可能です。
回復カプセル配列.png

インデックス

0 番目に入っているモンスターだけを個別に指定して扱うことを配列といいます。
注意点として、1番最初の値が0番目から始まるという考え方です。
つまり、ボールが8個あったら、 0番目から7番目までの配列 と言う呼び方で指定します。
この番号をインデックスと呼びます。
日本語では添字と呼ばれますが、最初は理解しやすい用語を使うと良いので 番号 でよいです。

クラス

クラス利用前の事情

あなたはモンスター育成ゲームの管理者です。
管理者はゲームの世界をただ見ているだけでなく、プレイヤーの動きに応じて、モンスターを適切に配置する必要があります。
そうしないと、最初の町から出た時に伝説のモンスターとであったり、そもそもモンスターが出なかったりするからです。
モンスターを配置するに当たり、 ピカモン というモンスターを作ります。
ピカモン は皆がよく知っているモンスターなので、積極的に配置したいです。
しかし、 ピカモン を配置するのに、1ピカモンあたり、100行程度のコードを実装する必要があります。
100行書けばよいのですが、ミスすることもありますし、仕様が変わることもあります。
毎回100行書いたコードで、世界に1000体の ピカモン を配置したあと、 ピカモン の攻撃力を一律で上げようとすると、1000箇所のコードを書き換える必要があります。
この課題を解決するのがクラスです。

クラスは設計図

クラスはピカモンを作るための設計図です。
ピカモンの設計図というクラスには100行のコードが実装されています。
つまり、ピカモンの設計図を実行することで、だれでもきれいな100行のコードを利用でき、確実にピカモンを作り出すことができます。

フィールド

オブジェクトがもっているデータです。
モンスター育成ゲームであれば、モンスターが持っているデータです。
クラスを利用せず、都度モンスターを配置するということは、次のふぃーるどを都度実装する必要があるということになります。

    private int number = 0; // モンスター番号
    private String name = null; // モンスター名称
    private String category = null; // モンスター分類
    private String type1 = null; // モンスタータイプ1
    private String type2 = null; // モンスタータイプ2
    private double height = 0; // モンスターの身長
    private double weight = 0; // モンスターの体重
    private String description = null; // モンスター説明
    private String sing = null; // モンスターの鳴き声
    private int vitality = 0; // モンスターの体力
    private int agility = 0; // モンスターの素早さ
    private int attach = 0; // モンスターの攻撃力
    private int defence = 0; // モンスターの防御力
    private int exp = 0; // モンスターの経験値
    private int specialAttack = 0; // モンスターの特攻
    private int specialDevence = 0; // モンスターの特防

命名規則:https://qiita.com/rkonno/items/1b30daf83854fecbb814

メソッド

「 攻撃する 」、「 鳴く 」、「 逃げる 」のような、オブジェクトの行動を処理として実装したものです。
モンスター育成ゲームであれば、モンスターが使う技です。
クラスを利用せず、都度モンスターを配置するということは、次のメソッドを都度実装する必要があるということになります。

    // --- staticメソッド(クラスメソッド) ---
    public static int getMonsterCount() {
        return monsterCount;
    }
    public static void showMonsterInfoFormat() {
        System.out.println("【モンスター情報フォーマット】");
        System.out.println("名前 / 分類 / タイプ1, タイプ2 / ステータス...");
    }
    // --- インスタンスメソッド ---
    public void cry() {
        System.out.println(name + " は鳴いた: \"" + sing + "\"");
    }
    public void showStatus() {
        System.out.println("==== " + name + " のステータス ====");
        System.out.println("分類: " + category + " / タイプ: " + type1 + (type2 != null ? ", " + type2 : ""));
        System.out.println("体力: " + vitality + " 攻撃: " + attack + " 防御: " + defence);
        System.out.println("特攻: " + specialAttack + " 特防: " + specialDefence + " 素早さ: " + agility);
        System.out.println("経験値: " + exp);
    }
    public void levelUp() {
        if (exp >= 100) {
            System.out.println(name + " はレベルアップした!");
            vitality += 5;
            attack += 3;
            defence += 3;
            specialAttack += 2;
            specialDefence += 2;
            agility += 2;
            exp = 0;
        } else {
            System.out.println(name + " はまだレベルアップできない。");
        }
    }
    public void gainExp(int gainedExp) {
        System.out.println(name + " は " + gainedExp + " の経験値を得た!");
        this.exp += gainedExp;
    }

クラス利用後の世界

ピカモンという設計図さえしっかりしていれば、本日加入されたエンジニアであっても、適切にピカモンを配置することが可能です。
それだけでなく、クラスを利用した配置したピカモンの攻撃力だけ上げたいなって時には、ピカモンクラスの攻撃力だけ上げておけば、その後のピカモンはあがった攻撃力のピカ門になります。
もちろん、以前のクラスで配置したピカモンの攻撃力だけ上げることも可能です。

クラス宣言

public class Monster {
    // --- フィールド(インスタンス変数) ---
    // クラスの各インスタンスに固有のデータを保持する変数

    private int number = 0;
    private String name = null;
    private String category = null;
    private String type1 = null;
    private String type2 = null;
    private double height = 0;
    private double weight = 0;
    private String description = null;
    private String sing = null;
    private int vitality = 0;
    private int agility = 0;
    private int attack = 0;
    private int defence = 0;
    private int exp = 0;
    private int specialAttack = 0;
    private int specialDefence = 0;
    // --- クラス変数(static変数) ---
    // この変数は全インスタンスで共有される(個々のオブジェクトではなく、クラス全体で管理)
    private static int monsterCount = 0; // 生成されたモンスターの数を記録
    // --- コンストラクタ ---
    // クラスのインスタンスが生成されるときに実行される

    public Monster(String name) {
        this.name = name; // 名前を設定
        monsterCount++;   // 生成されたモンスターの総数を更新
    }
    // --- staticメソッド(クラスメソッド) ---
    // staticメソッドはインスタンスを生成しなくても呼び出せる
    public static int getMonsterCount() {
        return monsterCount; // 現在のモンスター数を返す

    }
    public static void showMonsterInfoFormat() {
        System.out.println("【モンスター情報フォーマット】");
        System.out.println("名前 / 分類 / タイプ1, タイプ2 / ステータス...");
    }
    // --- インスタンスメソッド ---
    // インスタンスごとに実行されるメソッド(個別のオブジェクトに依存)

    public void cry() {
        System.out.println(name + " は鳴いた: \"" + sing + "\"");
    }
    public void showStatus() {
        System.out.println("==== " + name + " のステータス ====");
        System.out.println("分類: " + category + " / タイプ: " + type1 + (type2 != null ? ", " + type2 : ""));
        System.out.println("体力: " + vitality + " 攻撃: " + attack + " 防御: " + defence);
        System.out.println("特攻: " + specialAttack + " 特防: " + specialDefence + " 素早さ: " + agility);
        System.out.println("経験値: " + exp);
    }
    public void levelUp() {
        if (exp >= 100) { // 経験値が 100 以上ならレベルアップ

            System.out.println(name + " はレベルアップした!");
            vitality += 5;
            attack += 3;
            defence += 3;
            specialAttack += 2;
            specialDefence += 2;
            agility += 2;
            exp = 0; // レベルアップ後、経験値をリセット

        } else {
            System.out.println(name + " はまだレベルアップできない。");
        }
    }
    public void gainExp(int gainedExp) {
        System.out.println(name + " は " + gainedExp + " の経験値を得た!");
        this.exp += gainedExp; // 経験値を加算

    }
    // --- プロパティ(getter/setter) ---
    // フィールドへの安全なアクセスを提供するメソッド

    public String getName() { return name; } // name フィールドの値を取得
    public void setSing(String sing) { this.sing = sing; } // sing フィールドの値を設定
    public void setExp(int exp) { this.exp = exp; } // exp フィールドの値を設定
}

クラス実行

public class Main {
    public static void main(String[] args) {
        // staticメソッドの呼び出し(インスタンス不要)
        // staticメソッドは Monster.getMonsterCount() のように呼び出す
        Monster.showMonsterInfoFormat();
        System.out.println("モンスター数(初期): " + Monster.getMonsterCount());
        // モンスター生成(インスタンスメソッドを使用するために)
        // インスタンスメソッドは new Monster() 後に m1.cry() のように使う
        Monster m1 = new Monster("ドラコ");
        m1.setSing("グワァァァ!");
        m1.setExp(120);
        // インスタンスメソッドの呼び出し
        m1.cry();          // 鳴く
        m1.showStatus();   // ステータス表示
        m1.levelUp();      // レベルアップ確認
        // staticで数の確認
        System.out.println("モンスター数(生成後): " + Monster.getMonsterCount());
    }
}

クラス実行結果簡略例

【モンスター情報フォーマット】
名前 / 分類 / タイプ1, タイプ2 / ステータス...
モンスター数(初期): 0
ドラコ は鳴いた: "グワァァァ!"
==== ドラコ のステータス ====
分類: null / タイプ: null
体力: 0 攻撃: 0 防御: 0
特攻: 0 特防: 0 素早さ: 0
経験値: 120
ドラコ はレベルアップした!
モンスター数(生成後): 1

継承

クラスのお陰でピカモンを簡単に配置することができました。
育成ゲーム管理者としての仕事もだいぶ楽になりました。
楽になったのでピカモンと似たような、ライモンというモンスターを作ることになりました。
ゲーム管理者「この前ピカモンを100行コード書いて作ったのに・・・またゼロから考えるのか・・・」
そう思いますよね。
でも、ご安心ください。
Javaの継承を利用して簡単にライモンを配置できます。
継承というのは、ライモンとにているピカモンのコードを受け継いで、ライモンを作ることです。
ピカモンには無い性質を、ライモンクラスに追記することで、ゲームとしての拡張性を高めることが可能です。

extends

class ピカモン {
    100行のコードの一部
    public String 万ボルト() {
        100行のコードの一部
	    return "効果バツグンだったりそうでなかったり";
    }
}
class ライモン extends ピカモン {
    100行のコードの一部
    public String 電気玉半減() {
        100行のコードの一部
	    return "電気玉保有時に攻撃と特功がピカモンの半分";
    }
}

image.png

リファクタリング

さて、ピカモンを継承して、ライモンを配置しました。
ただ、よく考えたら、ピカモンを元に継承するより、モンスター共通のクラスを作って、継承した方が良い事に気づきました。
何故なら、モンスターは、⚡️雷モンスターのピカモンだけでは無く、💦水を使うモンスターや、🔥火を使うモンスターもいるからです。
いろいろなモンスターの親とも言える、スーパー凄い元になる設計図となるモンスタークラスを、親クラスやスーパークラスと言います。
親クラスやスーパークラスを元に作成されるモンスターのクラスを、子クラスやサブクラスと言います。
親↔︎子
スーパー↔︎サブ
意味は同じです。
それでは、モンスタークラスを利用して継承する例を書いてみましょう。

モンスターゲームでの実装例

public class Pikamon extends Monster {
    // コンストラクタ
    public Pikamon() {
        super("ピカモン"); // 名前は固定で渡す
        this.setSing("ピカァァ!");
        this.setCategory("でんきネズミ");
        this.setType1("でんき");
        this.setHeight(0.4);
        this.setWeight(6.0);
        this.setExp(50);
        // ステータスの初期値を設定
        this.setVitality(35);
        this.setAttack(55);
        this.setDefence(30);
        this.setSpecialAttack(50);
        this.setSpecialDefence(40);
        this.setAgility(90);
    }
    // ピカモン固有の動作
    public void thunderShock() {
        System.out.println(this.getName() + " は でんきショック を放った!");
    }
    // オーバーライド:鳴き方を特別に
    @Override
    public void cry() {
        System.out.println("⚡️ " + this.getName() + " の鳴き声: \"" + this.getSing() + "\" ⚡️");
    }
}

モンスターゲームでの実行例

public class Main {
    public static void main(String[] args) {
        // モンスターのテンプレート呼び出し
        Monster.showMonsterInfoFormat();
        // Pikamonのインスタンス生成
        Pikamon pika = new Pikamon();
        // 継承したメソッド
        pika.cry();          // オーバーライドされた鳴き方
        pika.showStatus();   // 継承したステータス表示
        pika.gainExp(60);    // 経験値追加
        pika.levelUp();      // レベルアップ処理
        // Pikamon独自のメソッド
        pika.thunderShock();
        // staticメソッドの呼び出し
        System.out.println("モンスター数: " + Monster.getMonsterCount());
    }
}

モンスターゲームでの実行結果例

【モンスター情報フォーマット】
名前 / 分類 / タイプ1, タイプ2 / ステータス...
⚡️ ピカモン の鳴き声: "ピカァァ!" ⚡️
==== ピカモン のステータス ====
分類: でんきネズミ / タイプ: でんき
体力: 35 攻撃: 55 防御: 30
特攻: 50 特防: 40 素早さ: 90
経験値: 50
ピカモン は 60 の経験値を得た!
ピカモン はレベルアップした!
ピカモン は でんきショック を放った!
モンスター数: 1

💡 学習ポイントまとめ

extends: Monsterクラスを継承してPikamonを定義
super(): 親クラスのコンストラクタを呼び出して名前設定
@Override: cry() をピカモン専用に上書き

ポリモーフィズム

Discussion