Java テキストRPG(戦闘シーンのみ)を再作成する~LWJGLを参考に作り直す~

事の発端について

前回作成した、テキストRPG(戦闘シーンのみ)をJavaFXへと移植しようかと考えてたのですが、如何せん、絵とか必要になるだろうと思い保留していました。

しかし、ゲームブックの要領で、ストーリーを進めるのであれば、ずいぶんと楽しくプレーできそうだと思いました。

そんなわけで、その方向でプログラムを組みなおします。

ここで肝になるのが「ゲームループ」という言葉と実装内容です。ループ処理をするのはよいのですが、参考にするソースを見てみるとマルチスレッド使ったりしているので、ちょっと不安になった次第です。。。

LWJGLのゲームループを解析

以前作成した、LWJGLのプログラムサンプルコードを参考に作成したのですが「GameEngine」と「GameLogic」という二つのクラスを使用してうまく作られていました。

登場人物(クラス)

  • Mainクラス
  • DummyGameクラス
  • GameEnginクラス
  • Windowクラス(今回は無視する)
  • Renderクラス(今回は無視する)

今回作成するゲームは、コンソールゲームなのでWindowとか3Dモデルのレンダリングは行わないので今回は無視します。

上記のクラスの関係性を解析

Mainクラス、下のソースを見てみると以下のクラスを呼び出しています。

  1. IGameLogic
  2. GameEngeine

IGameLogicインターフェースはGameLogicに当たる処理を実装したクラス=DummyGameクラスです。
GameEngineはそのままですね。。。

ポイントは、GameEngineクラスに、DummyGameを渡して、インスタンスを生成しているところです。
つまり、DummyGameがGameEngineクラスで使用することができるということです。

DummyGameをGameEngineに渡してしまえば、あとはGameEngineクラスで自由に処理ができます。

なるほど、GameEngineクラスはいちいち改修してなくてもよさそうです。

ということは。。。

今回の実装するテキストRPG(戦闘シーンのみ)は、

  • メインメソッドを持つMainクラス
  • IGameLogicに相当するインターフェースを実装したTextRpgGameクラス
  • GameEngineクラス

上のようなクラスを使用して実装するのがよいと思いましたが、GameEngineクラスは割愛してMainクラスに入れ込んでしまおうと思います。
理由は、いろんなバリエーションでGameEngineクラスを使用するつもりはないので、このようにします。

もし、必要があればその時に追加実装しようと考えています。

マルチスレッドについて

今まで、記載してないのですが、戦闘シーンのみはちょっと寂しいので拡張するということも考慮に入れてマルチスレッドに対応させています。というかLWJGLがすでに対応しているというか、そうのようにする必要があります。

実際LWJGLでは、ウィンドウに描画する(OpenGLの使用)処理があるのでマルチスレッドでの実装が必須です。

しかし、今回のマルチスレッドの実装は自前のものになってしまうので、サンプルの作成と起動確認を行ってみました。

メインにするスレッドとサブにするスレッドを「synchronized」修飾子をつけて区別をつける必要がありました。

サンプルコードは以下になります。
<実装コード(Github)>
ThreadMain
Thread1Cls
Thread2Cls

<処理内容>

  1. メインメソッド(ThreadMain)から処理を起動
  2. 主軸(メイン)になるスレッド(Thread1Cls)と付随するスレッド(Thread2Cls)を起動
  3. 主軸(メイン)になるスレッド(Thread1Cls)で以下の処理を行う
    • ”Thread1Cls: 処理を開始します"を表示
    • 入力受付、入力値の表示
    • 3秒待機(出力には「5秒」とあるが実装ミス)
  4. 付随するスレッド(Thread2Cls)で以下の処理を行う
    • 3秒待機(出力には「5秒」とあるが実装ミス)
    • ”Thread2Cls: 処理を開始します"を表示
    • 入力受付、入力値の表示

テキストRPGの記事

  1. Java ミニゲーム ソース付き 〜RPGの戦闘シーンを作るin Console〜
  2. Java ミニゲーム ソース付き 〜RPGの戦闘シーンを作る2 in Console〜
  3. Java ミニゲーム ソース付き 〜ここまでのまとめ〜
  4. Java ミニゲーム ソース付き 〜コンソールアプリからGUIアプリへ〜
    ここでアイディアに詰まる。。。

TextRpg前回作成したもの1

TextRpg前回作成したもの2
文字をゆっくり表示する実装を行ったものです。

参考にしたソース

Mainクラスのソース
public class Main {

    public static void main(String[] args) {
        try {
            boolean vSync = true;
            IGameLogic gameLogic = new DummyGame();
            GameEngine gameEng = new GameEngine("GAME", 600, 480, vSync, gameLogic);
            gameEng.run();
        } catch (Exception excp) {
            excp.printStackTrace();
            System.exit(-1);
        }
    }
}
DummyGameクラスのソース
public class DummyGame implements IGameLogic {

    private static final float MOUSE_SENSITIVITY = 0.2f;

    private final Vector3f cameraInc;

    private final Renderer renderer;

    private final Camera camera;

    private GameItem[] gameItems;

    private Vector3f ambientLight;

    private PointLight pointLight;

    private static final float CAMERA_POS_STEP = 0.05f;

    public DummyGame() {
        renderer = new Renderer();
        camera = new Camera();
        cameraInc = new Vector3f(0.0f, 0.0f, 0.0f);
    }

    @Override
    public void init(Window window) throws Exception {
        renderer.init(window);

        float reflectance = 1f;
        //Mesh mesh = OBJLoader.loadMesh("/models/bunny.obj");
        //Material material = new Material(new Vector3f(0.2f, 0.5f, 0.5f), reflectance);

        Mesh mesh = OBJLoader.loadMesh("/models/cube.obj");
        Texture texture = new Texture("textures/grassblock.png");
        Material material = new Material(texture, reflectance);

        mesh.setMaterial(material);
        GameItem gameItem = new GameItem(mesh);
        gameItem.setScale(0.5f);
        gameItem.setPosition(0, 0, -2);
        gameItems = new GameItem[]{gameItem};

        ambientLight = new Vector3f(0.3f, 0.3f, 0.3f);
        Vector3f lightColour = new Vector3f(1, 1, 1);
        Vector3f lightPosition = new Vector3f(0, 0, 1);
        float lightIntensity = 1.0f;
        pointLight = new PointLight(lightColour, lightPosition, lightIntensity);
        PointLight.Attenuation att = new PointLight.Attenuation(0.0f, 0.0f, 1.0f);
        pointLight.setAttenuation(att);
    }

    @Override
    public void input(Window window, MouseInput mouseInput) {
        cameraInc.set(0, 0, 0);
        if (window.isKeyPressed(GLFW_KEY_W)) {
            cameraInc.z = -1;
        } else if (window.isKeyPressed(GLFW_KEY_S)) {
            cameraInc.z = 1;
        }
        if (window.isKeyPressed(GLFW_KEY_A)) {
            cameraInc.x = -1;
        } else if (window.isKeyPressed(GLFW_KEY_D)) {
            cameraInc.x = 1;
        }
        if (window.isKeyPressed(GLFW_KEY_Z)) {
            cameraInc.y = -1;
        } else if (window.isKeyPressed(GLFW_KEY_X)) {
            cameraInc.y = 1;
        }
        float lightPos = pointLight.getPosition().z;
        if (window.isKeyPressed(GLFW_KEY_N)) {
            this.pointLight.getPosition().z = lightPos + 0.1f;
        } else if (window.isKeyPressed(GLFW_KEY_M)) {
            this.pointLight.getPosition().z = lightPos - 0.1f;
        }
    }

    @Override
    public void update(float interval, MouseInput mouseInput) {
        // Update camera position
        camera.movePosition(cameraInc.x * CAMERA_POS_STEP, cameraInc.y * CAMERA_POS_STEP, cameraInc.z * CAMERA_POS_STEP);

        // Update camera based on mouse            
        if (mouseInput.isRightButtonPressed()) {
            Vector2f rotVec = mouseInput.getDisplVec();
            camera.moveRotation(rotVec.x * MOUSE_SENSITIVITY, rotVec.y * MOUSE_SENSITIVITY, 0);
        }
    }

    @Override
    public void render(Window window) {
        renderer.render(window, camera, gameItems, ambientLight, pointLight);
    }

    @Override
    public void cleanup() {
        renderer.cleanup();
        for (GameItem gameItem : gameItems) {
            gameItem.getMesh().cleanUp();
        }
    }

}</pre>

### GameEngineクラスのソース
<pre>public class GameEngine implements Runnable {

    public static final int TARGET_FPS = 75;

    public static final int TARGET_UPS = 30;

    private final Window window;

    private final Timer timer;

    private final IGameLogic gameLogic;

    private final MouseInput mouseInput;

    public GameEngine(String windowTitle, int width, int height, boolean vSync, IGameLogic gameLogic) throws Exception {
        window = new Window(windowTitle, width, height, vSync);
        mouseInput = new MouseInput();
        this.gameLogic = gameLogic;
        timer = new Timer();
    }

    @Override
    public void run() {
        try {
            init();
            gameLoop();
        } catch (Exception excp) {
            excp.printStackTrace();
        } finally {
            cleanup();
        }
    }

    protected void init() throws Exception {
        window.init();
        timer.init();
        mouseInput.init(window);
        gameLogic.init(window);
    }

    protected void gameLoop() {
        float elapsedTime;
        float accumulator = 0f;
        float interval = 1f / TARGET_UPS;

        boolean running = true;
        while (running && !window.windowShouldClose()) {
            elapsedTime = timer.getElapsedTime();
            accumulator += elapsedTime;

            input();

            while (accumulator >= interval) {
                update(interval);
                accumulator -= interval;
            }

            render();

            if ( !window.isvSync() ) {
                sync();
            }
        }
    }

    protected void cleanup() {
        gameLogic.cleanup();
    }

    private void sync() {
        float loopSlot = 1f / TARGET_FPS;
        double endTime = timer.getLastLoopTime() + loopSlot;
        while (timer.getTime() > endTime) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException ie) {
            }
        }
    }

    protected void input() {
        mouseInput.input(window);
        gameLogic.input(window, mouseInput);
    }

    protected void update(float interval) {
        gameLogic.update(interval, mouseInput);
    }

    protected void render() {
        gameLogic.render(window);
        window.update();
    }
}

関連ページ一覧

EclipseセットアップWindows版

Eclipse セットアップ

  1. Java Install Eclipse〜開発ツールのインストール〜
  2. TensorFlow C++環境〜EclipseCDTをインストール〜
  3. Setup OpenGL with JavaJOGLを使う準備 for Eclipse
  4. Eclipse Meven 開発手順〜プロジェクトの作成〜
  5. Java OpenCV 環境セットアップ(on Mac)
  6. Eclipse SceneBuilderを追加する
  7. JavaFX SceneBuilder EclipseSceneBuilder連携~

Java Basic一覧

  1. Java Basic Level 1 〜Hello Java〜
  2. Java Basic Level2 〜Arithmetic Calculate〜
  3. Java Basic Level3 〜About String class〜
  4. Java Basic Level 4〜Boolean〜
  5. Java Basic Level 5〜If Statement〜
  6. Java Basic Summary from Level1 to 5
  7. Java Basic Level 6 〜Traning of If statement〜
  8. Java Basic Level8 〜How to use for statement〜
  9. Java Basic Level 8.5 〜Array〜
  10. Java Basic Level 9〜Training of for statement〜
  11. Java Basic Level 10 〜While statement 〜
  12. Java Basic Swing〜オブジェクト指向〜
  13. Java Basic Swing Level 2〜オブジェクト指向2〜
  14. サンプル実装〜コンソールゲーム〜
  15. Java Basic インターフェース・抽象クラスの作り方
  16. Java Basic クラスとは〜Step2_1〜
  17. Java Basic JUnit 〜テストスイートの作り方〜

Git関連

  1. Java Git clone in Eclipse 〜サンプルの取得〜
  2. Eclipse Gitリポジトリの取得 GitからソースをPullしよう〜
  3. IntelliJ IDEA GitGitリポジトリからクローン〜

JavaFX関連ページ

  1. Eclipse SceneBuilderを追加する
  2. JavaFX SceneBuilder 〜EclipseとSceneBuilder連携~
  3. JavaFX SceneBuilder〜ボタンにメソッドを割り当てるワンポイント〜
  4. Java プロコンゲーム 〜見た目の作成(SceneBuilderの使用)〜

ステップアップ関連ページ一覧

  1. Java 初めてでも大丈夫〜ステップアッププログラミングのススメ〜
  2. ステップアッププログラミング〜Java FxでHelloWorld解説〜
  3. Java StepUpPrograming〜JavaFX で四則計算〜
  4. Java StepUpPrograming〜JavaFXで画面切り替えを作る1〜
  5. Java StepUpPrograming〜JavaFXで画面切り替え2ボタン作成〜
  6. Java StepUpPrograming〜JavaFXで画面切り替え3アクション〜
  7. Java StepUpPrograming〜JavaFXで画面切り替え4Pane切り替え〜
  8. Java StepUpPrograming〜JavaFXで画面切り替え5WebEngine

JavaFX + ND4Jで機械学習準備

  1. JavaFX + ND4J〜数学への挑戦1:ND4Jのインストール〜
  2. JavaFX + ND4J〜数学への挑戦2: 行列の計算〜
  3. Java + ND4J 〜数学への挑戦3: ベクトル(配列)の作成方法〜

オブジェクト指向関連ページ

  1. [オブジェクト指向の概念1〜OracleDocのチュートリアル1〜](https://zenryokuservice.com/wp/2019/10/301. /%e3%82%aa%e3%83%96%e3%82%b8%e3%82%a7%e3%82%af%e3%83%88%e6%8c%87%e5%90%91%e3%81%ae%e6%a6%82%e5%bf%b5-%e3%80%9coracledoc%e3%81%ae%e3%83%81%e3%83%a5%e3%83%bc%e3%83%88%e3%83%aa%e3%82%a2%e3%83%ab%ef%bc%91/)
  2. オブジェクト指向の概念2〜クラスとは〜