RPGでJavaのオブジェクト指向を考える

ターン制とかではなく単純に攻撃・防御だけのプログラムを組んでみる。↓のコードにように最低限のファイル構成で書き連ねていく方法もあるけどそれでは保守性や拡張性が悪い。Javaの利点でもあるオブジェクト指向でそれらをカバーしていく。

@Contents

基本となるコード

package example;

public class Character {

    public String name;
    public Integer hp;
    public Integer offense;
    public Integer defense;

    public Character() {
    }

    public Character(String name, Integer hp, Integer offense, Integer defense) {
        this.name = name;
        this.hp = hp;
        this.offense = offense;
        this.defense = defense;
    }

    public void attack(Character opponent) {

        Integer damage = this.offense - opponent.defense;

        if (damage > 0) {
            opponent.hp = opponent.hp - damage;
            System.out.println(this.name + " dealt " + damage + " damage to " + opponent.name + "!");
        } else {
            System.out.println("Miss! " + this.name + " can't damage " + opponent.name + "!");
        }
    }
}
Kiki dealt 3 damage to Stray metal!
Miss! Stray metal can't damage Kiki!

-> カプセル化

package example;

class Character {

    private String name;
    private Integer hp;
    private Integer offense;
    private Integer defense;

    Character() {
    }

    Character(String name, Integer hp, Integer offense, Integer defense) {
        this.name = name;
        this.hp = hp;
        this.offense = offense;
        this.defense = defense;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getHp() {
        return hp;
    }

    public void setHp(Integer hp) {
        this.hp = hp;
    }

    public Integer getOffense() {
        return offense;
    }

    public void setOffense(Integer offense) {
        this.offense = offense;
    }

    public Integer getDefense() {
        return defense;
    }

    public void setDefense(Integer defense) {
        this.defense = defense;
    }

    public void attack(Character opponent) {
        Integer damage = this.offense - opponent.defense;

        if (damage > 0) {
            opponent.hp = opponent.hp - damage;
            System.out.println(this.name + " dealt " + damage + " damage to " + opponent.name + "!");
        } else {
            System.out.println("Miss! " + this.name + " can't damage " + opponent.name + "!");
        }
    }
}

意図しない書き換えを防ぐ為にもカプセル化は必ず行う。

-> 継承

package example;

public class Hero extends Character {

    private Integer weapon;

    public Integer getWeapon() {
        return weapon;
    }

    public void setWeapon(Integer weapon) {
        this.weapon = weapon;
    }

    public Hero() {
    }

    public Hero(String name, Integer hp, Integer offense, Integer defense, Integer weapon) {
        super(name, hp, offense, defense);
        this.weapon = weapon;
    }
}

以前のクラス(Charactor)では勇者と敵を同じクラス内にまとめていた。しかし、プレーヤーが操作するのは勇者だけで敵を操作することはない。そもそも性質が大きく異なるので両者に共通するプロパティ、メソッドは残しつつ(継承)、クラスを分割した。具体的には勇者には武器のプロパティ(整数)を子クラスに追加、敵には属性を加えて差別化。また、コンストラクタの継承はされないのでそれぞれに記述した。子クラスから親クラスのコンストラクタを呼び出していることに注意。

package example;

public class RPG {
    public static void main(String[] args) {

        Hero hero = new Hero("Kiki", 9, 4, 5, 2);
        Enemy enemy = new Enemy("Stray metal", 6, 3, 1, "slime");

        metal.attack(hero);
        hero.attack(enemy);
    }
}
Kiki dealt 3 damage to Stray metal!
Miss! Stray metal can't damage Kiki!

実行結果自体は前回と変わらないが、より拡張できるようになった。

-> オーバーライド

package example;

public class Hero extends Character {

    private Integer weapon;

    public Integer getWeapon() {
        return weapon;
    }

    public void setWeapon(Integer weapon) {
        this.weapon = weapon;
    }

    public Hero() {
    }

    public Hero(String name, Integer hp, Integer offense, Integer defense, Integer weapon) {
        super(name, hp, offense, defense);
        this.weapon = weapon;
    }

    @Override
    public void attack(Character opponent) {

        Integer damage = this.getOffense() + this.getWeapon() - opponent.getDefense();

        if (damage > 0) {
            opponent.setHp(opponent.getHp() - damage);
            System.out.println(this.getName() + " dealt " + damage + " damage to " + opponent.getName());
        } else {
            System.out.println("Miss! " + this.getName() + " can't damage " + opponent.getName() + "!");
        }
    }
}

Characterクラスではなく、Heroクラスにのみオーバーライドで武器の強さをメソッドに記述した。もし、Characterクラスに武器の強さを記述してしまうと、Enemyにも武器を持たせる必要があるからである。基本的なRPGでは敵は具体的な武器を持っていない。

注意点として、Characterクラスはカプセル化してあるのでCharacter で定義しているプロパティをHero で使うときは、ゲッターセッターを使わなければならない。

-> インターフェイス

全部のキャラクターが魔法攻撃を使えるわけではないのでCharacterクラスではなく、インターフェイスを使ってHeroクラスに実装した。

package example;

public interface MagicAttack {

    public abstract void magic_attack(Character opponent);
}

抽象クラスかインターフェイスのどちらを使うか迷うところだが、今回は「has-a」の関係性なのでインターフェイスを使うべきである(「is-a」の関係性の場合は抽象クラス・メソッドを使用する)。

流れ↓

  1. MagicAttack インターフェイスを作成
  2. MagicAttack を定義
  3. Hero クラスに MagicAttack を implementsさせる
  4. magic_attack() メソッドの定義を記述(@Override
  5. RPG クラスのメインメソッドで magic_attack() を実行

RPGゲームを1つ完成させるのには途方もない時間が掛かる。しかし、やってみる価値はあると思う。下記のサイトが面白かった(JavaではなくC言語だけど)。アルゴリズムの作り方とか参考になる。

To comment

@Contents
閉じる