JAVA

最終更新日: 2022.12.28 (公開: 2022.12.19)

Javaでゲーム開発!ゲームの作り方・必要知識をサンプルプログラム付きで解説

Javaでゲーム開発!ゲームの作り方・必要知識をサンプルプログラム付きで解説

「Java」は世界中で広く使われているプログラミング言語のひとつで、さまざまな用途に用いられています。そんな「Java」ですが、ゲーム開発ができるのです。Javaでゲーム開発する詳しい方法やポイントなどを紹介します。

「Java」は最もメジャーなプログラミング言語のひとつです。ソフトウェアやアプリ、Webシステム開発など幅広く活用されているJavaですが、実はゲームの開発もできます。

「C++」や「C#」などの言語と比べると一般的ではないものの、じゃんけんゲームやカードゲーム、迷路ゲームなどであれば、比較的簡単に作れます。ただし、Javaでゲームを作るためには、Javaの基本的な知識は必須です。

また、Javaの標準機能だけではグラフィカルなゲームは作れないので、ゲーム開発に適した「GUIライブラリ(グラフィックライブラリ)」を使用する必要があります。本記事では、Javaでゲームを作るために必要な知識や、簡単なゲームの作り方をサンプルプログラム・サンプルコード付きで解説します。

目次

Javaでゲーム開発は可能だが、本格3Dゲームの開発は困難

Javaでゲーム開発はできますが、Javaはゲーム開発に特化したプログラミング言語ではないため、ゲーム開発に適したライブラリやフレームワークなどの環境が整備されていません。さらに、Javaは「C++」や「C#」などの言語と比べると実行速度が遅めで、ゲーム開発の現場で積極的に採用されてこなかったことが理由です。

一方で、じゃんけんゲームやカードゲーム、迷路ゲームなどの簡単なゲームなら、Javaで開発できます。これらのゲームは、単純なグラフィックやボタンの配置で構成でき、プログラム自体も物理演算や複雑なグラフィックス処理が不要だからです。少し工夫すれば、2Dの横スクロール・縦スクロールのシューティングゲームも開発できます。

「Minecraft」はJavaで開発された代表的なゲーム!

Javaはゲーム開発であまり使われることがないプログラミング言語ですが、実は非常に有名なゲームがJavaで開発された事例があります。それが「Minecraft」です。Minecraftは、マルクス・ペルソンらが開発したサンドボックス(箱庭型シミュレーション)ゲームで、全世界で爆発的なブームを巻き起こしました。

MinecraftがJavaで開発された背景には、Javaが「クロスプラットフォーム開発」に適しており、さまざまなデバイスに対応できるプログラミング言語であることが考えられます。実際に「Minecraft Java Edition」は、Windows・Linux・MacOSなどはもちろんのこと、Nintendo Switchやスマホアプリでも快適にプレイできるゲームです。

Javaによるゲーム開発が一般的ではない理由3つ

Javaによるゲーム開発が一般的ではない理由は以下3つです。これらの点を理解しておくと、Javaのゲーム開発が円滑に進行できるようになります。

  • Javaの実行速度はC++やC#などと比べると控えめ
  • ゲーム開発に特化したグラフィックライブラリがない
  • ほかのゲームエンジンの方が容易にゲームを開発できる

Javaの実行速度はC++やC#などと比べると控えめ

ゲーム業界にJavaが浸透していない大きな理由は、Javaの実行速度にあります。ゲームには高度な演算処理が必要なので、「いかに動作速度」を高めるかが重要です。演算処理において抜群のパフォーマンスを発揮できる「C++」は、登場から40年が経過した現在でも最先端の分野で活躍しています。

その一方でJavaは、「JVM(Java Virtual Machine)」という仮想マシンで動くなどの理由から、実行速度がC++と比べて3分の2ほどに低下します。つまり、C++で開発されたゲームは、Javaよりも1.5倍ほど速く動くということです。

C#も仮想マシン上で動くという点ではJavaと同じですが、C#にはC++に近いコードが書ける「unsafe」機能があり、C++と同等の速度が出せます。

これらの点から、ゲーム開発においてJavaは実行速度の点で不利なのです。一方で、単純なゲームであれば実行速度は重要ではないので、簡単なゲームならJavaで問題なく作れます。

ゲーム開発に特化したグラフィックライブラリがない

Javaにはゲーム開発に特化した環境が整っていません。ゲーム開発において、高度な3D映像を表示するための「GUIライブラリ(グラフィックライブラリ)」は極めて重要ですが、Javaにはそのための専用プラットフォームが存在しません。

たとえば、C++は「Unreal Engine」、C#は「Unity」と組み合わせて、高度な3Dゲームを手軽に開発できる環境が整っています。

Javaにもウィンドウアプリケーションを開発するための、2Dグラフィックライブラリは存在します。しかし、あくまでボタンやテキストボックスなど、データ入力や管理のための業務ソフトウェアを想定した機能なので、ゲーム開発にはあまり適していません。
一方で、ボタンやテキストなどで表現できるゲームであれば、Javaでも効率的に開発できます。

ほかのゲームエンジンの方が容易にゲームを開発できる

Javaでゲームが作れる「ゲームエンジン」が存在しないことも、Javaによるゲーム開発が一般的ではない理由です。「ゲームエンジン」とは、キャラクターの動作やAI、グラフィックスの描画やアニメーションなど、ゲーム制作に必要なあらゆる機能が搭載されたプラットフォームです。前述したように、C++にはUnreal Engline、C#にはUnityがあります。

Javaにも2DグラフィックスのGUIライブラリはありますが、ゲームのように多数のグラフィックスを高速描画する用途には向いていません。Javaで高度な3Dゲームを開発することは、Minecraftの事例もあるため対応できますが、ほかのプログラミング言語でUnreal EnglineやUnityを活用したほうが、より効率的にゲーム開発ができるのです。

「Unreal EnglineやUnityには手を出しづらい」「Javaで簡単なゲームを作ってみたい」などの場合は、Javaでのゲーム開発も選択肢のひとつです。

Javaによるゲーム開発を学習するメリット3選

Javaによるゲーム開発を学習するメリット3選

Javaはゲーム開発に最適なプログラミング言語ではありません。一方で、Javaによるゲーム開発を学習することで、以下3つのメリットが得られます。

  • Javaの高度なプログラミング技術が身につく
  • クロスプラットフォーム対応のゲームが作れる
  • 他のプログラミング言語でのゲーム開発に役立つ

Javaの高度なプログラミング技術が身につく

ゲーム開発は容易ではありません。Javaの基本的な知識はもちろん、応用テクニックやノウハウが必要です。たとえば、カードゲームという単純なものを作る場合は、手札の管理をプログラムで実現する必要があります。

トランプには4種類のマークごとに13枚、合計52枚(ジョーカーは除く)のカードがありますが、これはどのように管理すれば良いでしょうか。4つのListを作って、それぞれに「1~13」の数字を入れる方法を思いつくかもしれませんね。しかし、これではカードのディール(配布)やシャッフル(並べ替え)に手間がかかります。

そのほかにも、カードのランダム性をどのように実現するか、相手の思考AIをどう実現するかなど、ゲームプログラミング特有の課題があります。こうした課題を解決できるプログラムを書くうちに、Javaの応用技術が自然と身についていくのです。ステップアップでより高度なゲームも作れるようになるため、大きなやりがいもあります。

クロスプラットフォーム対応のゲームが作れる

そもそもJavaというプログラミング言語は、「どのプラットフォームでも同じように動く」ことが魅力です。たとえばJavaで作ったプログラムは、基本的にはWindows・iOSどちらでも動きます。これは、Javaは仮想マシン上で動作するという特性上、「JVM(Java Virtual Machine)」がマシンや環境ごとの違いを吸収してくれるからです。

つまりJavaで開発したゲームは、ハードに縛られにくいということ。近年では、さまざまな端末でゲームがプレイできるようになっているため、Javaの「クロスプラットフォーム」への対応力は魅力的ですよね。後述する「libGDX」や「LWJGL」などは、とくに「クロスプラットフォーム対応」に適したGUIライブラリであり、Minecraftも活用しています。

他のプログラミング言語でのゲーム開発に役立つ

ゲームの作り方は、どのプログラミング言語でも基本的な部分はほとんど同じです。たとえば、カードゲームの手札管理やランダム性の実現などは、プログラミング言語が変わっても実装の方向性は変わりません。アルゴリズムや効率化の手法などは、どのプログラミング言語にも同じように通用するからです。

さらに、Javaの専門的な知識を身につけておけば、C++やC#でのゲーム開発にも役立ちます。ゲーム分野で主流となっているC++やC#などのプログラミング言語は、Javaとよく似た部分が多いからです。

これらの理由から、Javaのゲーム開発で学んだことは、他のプログラミング言語でも必ず活かせます。あとでUnreal EnglineやUnityで本格的な3Dゲームを作りたくなったときに、Javaで学んだことは大きな力になるでしょう。

Javaでゲーム開発するために必要な知識3選

Javaでゲームを開発するために必要な知識を以下3つご紹介します。

  • Javaの基本的な構文とオブジェクト指向の知識
  • ゲームを開発するための応用テクニックやスキル
  • ゲーム開発に適したグラフィックライブラリの知識

1. Javaの基本的な構文とオブジェクト指向の知識

Javaの基本構文を知らなければ何も作れません。クラスやメソッドをどのように扱えばいいのか、大量のデータを配列やリストで管理する方法をしっかり理解しておかなければ、簡単なゲームを作ることも困難です。つまり、Javaの基本知識をある程度はマスターしておかないと、Javaでゲーム制作はできないということ。

「オブジェクト指向」の知識は、キャラクターやアイテムなどゲームを構成する各要素を、効率的に管理するために必要です。Mainクラスにすべての要素を詰め込んでも、プログラム自体は動きます。しかし、これではプログラムの仕様変更が困難になり、他のゲームを作るときもソースコードの使いまわしができません。

ゲームプログラミングでは、仕様変更や拡張を頻繁に行います。オブジェクト指向を意識したプログラムを書くことで、柔軟なコーディングができるようになるので、ゲーム制作の効率が飛躍的に高まるのです。

2. ゲームを開発するための応用テクニックやスキル

ゲームプログラミングの学習は、Javaの基本知識をマスターしておけば、問題なく進めることができます。ただし、キャラクターやアイテムの管理、AIやグラフィックスの処理なども欠かせないので、順次習得していく必要があるでしょう。

こうしたゲームプログラミング特有の応用テクニックについては、事前に学んでおくというよりも、必要に応じて身につけていくのが基本。たとえば、GUIライブラリ(グラフィックライブラリ)の使い方は、そのたびにネットで調べながら進めても問題ありません。

3. ゲーム開発に適したグラフィックライブラリの知識

通常のプログラムのようなコンソール画面ではなく、グラフィカルな画面でゲームを作りたいのであれば、GUIライブラリ(グラフィックライブラリ)が必須です。ちなみに、GUI(Graphical User Interface)は「グラフィカルなユーザーインターフェース」を意味します。

Javaの基本機能では、白黒のコンソール画面にテキストを表示することしかできません。GUIライブラリを使えば、普段PCで使っているソフトのようにウインドウを表示して、ボタンやテキストなどでさまざまなことが表現できるようになります。

GUIライブラリの機能を活用することで、ユーザーに入力や選択を求めることや、リアルタイムなキャラクターの移動や描画が実現できるので、より「ゲームらしいゲーム」が作れるようになります。

Javaでゲームを作れるGUI・グラフィックライブラリ5選

Javaの標準機能では、一般的なソフトウェアのような「グラフィカル」なゲームを作ることはできません。そのため、Javaでグラフィックスを扱うための各種機能を搭載した、「GUIライブラリ(グラフィックライブラリ)」の使用が必須です。ここからは、Javaでゲームを制作するためによく使われているライブラリを5つご紹介します。

  • Swing
  • JavaFX
  • AWT
  • libGDX
  • LWJGL

1. Swing

JavaのGUI開発現場において、最も一般的に使用されてきたライブラリです。一時期、JavaのGUIは後述する「JavaFX」がメインになった時期もありましたが、使い勝手の良さから現在では再びSwingが使われるようになっています。

Swingは2Dグラフィックスに特化しているため、ハイレベルな3Dゲームの制作は困難ですが、古典的な2Dゲームの開発には十分な機能があります。さらに、SwingはJavaの標準機能として搭載されているため、ライブラリのインストールや設定などの手間がかからず、手軽に使えることが大きな魅力です。

また、Swingは「Java仮想マシン」上で動作するため、どのOSでも動作できるゲームが作れます。GUI部品にOSのネイティブ部品は使わないため、どのOSで実行しても「同じ見た目」になることもメリットです。

ちなみにSwingでは、ウインドウを構成する「フレーム」をベースとして、ボタンやテキストなどの「GUI部品」を配置していくことで、グラフィカルなアプリケーションを制作していきます。本記事の後半では、Swingで実際にゲームを開発する方法をご紹介します。

2. JavaFX

一時期は前述した「Swing」に変わって使用されていたGUIライブラリです。端的にいうとSwingを進化させたグラフィックライブラリで、基本的なプログラミング方法はSwingと同じ。一方、ボタンやテキストなどのGUI部品にアクションイベントを設定できるなど、使い勝手は向上しています。

しかし、JavaFXは現在では再び「非標準」のGUIライブラリになったため、導入に多くの手間がかかることが難点。できること自体はSwingとほとんど変わらないため、現在あえてJavaFXを使うメリットは少ないといえます。そのため、基本的には標準機能の「Swing」を使うことをおすすめします。

3. AWT

AWT(Abstract Windowing Toolkit)は、Java初期のころから使用されてきたGUIフレームワークです。しかし機能面での制約が多く、さまざまな処理を行うアプリ開発には向いていないため、ゲーム開発目的にはおすすめできません。

さらに、AWTはGUI部品にOSのネイティブ部品を使うため、OSによって見た目が異なるプログラムになります。Javaは「どのOSでも同じように動作すること」が基本なので、これはデメリットだといえるでしょう。

そのため、現在では前述した「Swing」を使用するのが一般的ですが、ごく単純なGUIアプリを作るのであれば現在でもAWTは使用されています。いずれにせよ、ゲーム開発には「Swing」を使うのがおすすめです。

4. libGDX

Javaでクロスプラットフォームなゲーム開発ができるGUIフレームワークです。グラフィック関連の機能が充実しているため、複雑な3Dゲームも開発できることが魅力。とくにAndroid端末用のゲームをJavaで開発する場合は、libGDXを使うことが多いです。

ただし、libGDXを利用するためには、さまざまなライブラリを導入する必要があるため手軽ではありません。初心者の方はまず「Swing」で基本的な2Dゲームの開発に慣れてから、「libGDX」や「LWJGL」へ移行するのがいいでしょう。

5. LWJGL

冒頭でご紹介した「マインクラフト」で採用されたGUIフレームワークです。「libGDX」と同様に、クロスプラットフォームなゲームを開発できるライブラリで、OpenGLのようなハードウェアに根付いた部分も操れるため、高度な3Dゲームも開発できることが魅力。

一方、LWJGLは日本語のドキュメントや情報が少ないため、手軽ではなく導入にも手間がかかります。初心者の方がゲーム制作を学ぶときは、手軽さが最も重要なポイントなので、まずはLWJGLではなく「Swing」から始めることをおすすめします。

Javaでゲーム開発を学習する近道は「とにかく作ること」

Javaでゲーム開発を学習する近道は、まずは基本知識だけ身につけて、あとは「とにかく作ること」です。C++やC#などのプログラミング言語とは異なり、Javaはゲーム開発分野ではほとんど使われていません。そのため、書籍・参考書やWebサイトなどでJavaのゲーム開発を学習するのは、実は容易なことではありません。

そのため、GUI・グラフィックライブラリの基本的な使い方を学び、あとはゲームプログラミングの要素を追加していくという手法がおすすめです。本記事では、GUI・グラフィックライブラリの基本知識と、初歩的なゲームのサンプルコードをご紹介します。そのうえで、じゃんけんゲームとカードゲームを作り、ステップアップを目指しましょう。

【基本編】Javaで「Swing」を使ってゲームを開発する方法

前述したように、Javaでゲーム開発に使えるGUI・グラフィックライブラリは複数あります。しかし、導入や使い方が複雑なものや、国内では情報が不足していてゲームを制作しづらいものもあります。続いては、最も一般的な「Swing」でゲームを開発するための、基本的な知識を以下3つのステップに分けてご紹介します。

  1. ウインドウを表示する
  2. パネル(描画領域)を追加する
  3. メッセージボックス(ラベル)とボタンを追加する

ただし、これ以降の項目は「Javaの基本知識はすでにある」ことを前提に記載しています。前述したように、ゲーム開発にはJavaの基礎が必要不可欠なので、あまり自信がない方はJavaの基本構文を復習してからやってみましょう。

1. ウインドウを表示する

グラフィカルなゲームを作るためには、まず「ウインドウ」を作成する必要があります。通常のコンソールプログラムでは、黒い背景のウインドウが自動的に表示されました。しかし、Swingでゲームを制作する場合は、「JFrameクラス」で「フレーム」と呼ばれるウインドウの部品を自分で作成します。

//サンプルプログラム

// ライブラリ
import javax.swing.JFrame;

// メインクラス
public class Main {
  
  public static void main(String[] args) {
    // ウインドウを生成する
    GameWindow gameWindow = new GameWindow("サンプルゲーム", 1280, 720);
    
    // ウインドウを表示する
    gameWindow.setVisible(true);
  }
  
}

// ウインドウ描画クラス
class GameWindow extends JFrame {
  
  public GameWindow(String title, int width, int height) {
    // ウインドウ名を設定する
    super(title);
    
    // ウインドウのサイズを設定する
    setSize(width, height);
    
    // ウインドウサイズの変更を禁止する
    setResizable(false);
    
    // ウインドウを画面中央に表示する
    setLocationRelativeTo(null);
    
    // ウインドウを閉じる動作を設定する(「×」ボタンで終了)
    setDefaultCloseOperation(EXIT_ON_CLOSE);
  }
  
}

//実行結果

サンプルコードを実行すると、何も表示されないウインドウが登場し、「×」ボタンをクリックすると閉じます。なおウインドウが正常に表示されない場合は、「MSI Afterburner」や「NVIDIA GeForce Experience」のオンスクリーンディスプレイをオフにしましょう。

上記のサンプルコードのポイントは、「JFrameクラス」を継承した「GameWindowクラス」を定義し、それをMainメソッドで生成していることです。JFrameクラスを継承することで、クラス内でJFrameのメソッドを自由に使えるようになります。GUIにおいては、この流れが基本となるため、必ず覚えておきましょう。

なお、Swingは2022年8月現在Javaの標準機能となっているため、IDE(統合開発環境)の設定を変えなくても使えます。プロジェクトやクラスの作成方法も、通常のコンソールアプリケーションの場合と同じで、JFrameライブラリをimportするだけで完了です。

2. パネル(描画領域)を追加する

ウインドウを表示したら、さっそく何かを表示してみたくなりますよね。ただし、先ほど作成したフレーム(ウインドウ)自体には描画できないので、グラフィックスを表示するための「描画領域」を作る必要があります。Swingでは「JPanelクラス」がその役割を果たすので、サンプルコードで詳しく見ていきましょう。

//サンプルプログラム

// ライブラリ
import java.awt.Graphics;

import javax.swing.JFrame;
import javax.swing.JPanel;

// メインクラス
public class Main {

  public static void main(String[] args) {
    // ウインドウを生成する
    GameWindow gameWindow = new GameWindow("サンプルゲーム", 1280, 720);

    // ウインドウを表示する
    gameWindow.setVisible(true);

    // 描画領域を追加する
    gameWindow.add(new Canvas());
  }

}

// ウインドウ描画クラス
class GameWindow extends JFrame {

  public GameWindow(String title, int width, int height) {
    // ウインドウ名を設定する
    super(title);

    // ウインドウのサイズを設定する
    setSize(width, height);

    // ウインドウサイズの変更を禁止する
    setResizable(false);

    // ウインドウを画面中央に表示する
    setLocationRelativeTo(null);

    // ウインドウを閉じる動作を設定する(「×」ボタンで終了)
    setDefaultCloseOperation(EXIT_ON_CLOSE);
  }

}

// 描画領域を作成するクラス
class Canvas extends JPanel {

  @Override
  public void paintComponent(Graphics graphics) {
    // 親クラスのpaintComponentメソッドを呼び出す
    super.paintComponent(graphics);

    // 2本の線をパネルに描画する
    // drawLineメソッドの引数は
    // 始点X座標・始点Y座標・終点X座標・終点Y座標
    graphics.drawLine(0, 0, 1280, 720);
    graphics.drawLine(0, 720, 1280, 0);

    // 矩形を描画する
    // drawRectメソッドの引数は
    // 始点X座標・始点Y座標・横サイズ・縦サイズ
    graphics.drawRect(440, 310, 100, 100);

    // 矩形を塗りつぶし描画する
    // fillRectメソッドの引数は
    // 始点X座標・始点Y座標・横サイズ・縦サイズ
    graphics.fillRect(740, 310, 100, 100);
  }

}

//実行結果

上記のサンプルコードを実行すると、線や矩形(四角形)が表示されます。まず、JPanelクラスを継承した「Canvasクラス」を作ります。JFrameと同様に、基本パーツのクラスを継承することで、さまざまな機能が使いやすくなるので便利です。

実際にJPanelを継承することにより、「paintComponentメソッド」をオーバーライドできます。paintComponentメソッド内部では、まず親クラスの同名メソッドを呼び出し、線や矩形を描画しています。各メソッドの使い方は、コメントに記載しているとおりです。

Mainメソッドでは、「gameWindow.add(new Canvas());」の形で、gameWindowにCanvasクラスを登録しています。こうすることで、ウインドウに描画領域が追加され、グラフィックが表示されるようになるのです。なお、paintComponentメソッドは自動的に呼び出されるため、プログラマーが明示的に呼び出す必要はありません。

3. メッセージボックス(ラベル)とボタンを追加する

ゲーム制作に欠かせないのが「メッセージボックス」と「ボタン」です。ゲームではさまざまな場面で、「ユーザーにメッセージを表示したい」ときや「選択を求めたい」ときがあります。そんなときは「JLabel」でメッセージ・「JButton」でボタンを表示できます。詳細をサンプルコードで確認していきましょう。

//サンプルプログラム

// ライブラリ
import java.awt.Font;
import java.awt.Graphics;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

// メインクラス
public class Main {
  // ウインドウ部品を定義する
  static GameWindow gameWindow;

  // 各GUI部品を定義する
  static Canvas canvas;
  static Label label;
  static Button button;

  public static void main(String[] args) {
    // ウインドウを生成する
    gameWindow = new GameWindow("サンプルゲーム", 1280, 720);

    // カンバスを生成する
    canvas = new Canvas();

    // カンバスを「自由レイアウト」に変更する
    canvas.setLayout(null);
    
    // ラベルを生成する
    label = new Label("テストラベル");

    // ボタンを生成する
    button = new Button("テストボタン");

    // ボタンが押されたときの動作を定義する
    // 詳しい動作を定義したメソッドを「ラムダ式」で引き渡す
    button.addActionListener((e) -> button.result(label));

    // ウインドウを表示する
    gameWindow.setVisible(true);

    // 描画領域をウインドウに追加する
    gameWindow.add(canvas);

    // ラベルを描画領域に追加する
    canvas.add(label);

    // ボタンを描画領域に追加する
    canvas.add(button);
  }

}

// ウインドウ描画クラス
class GameWindow extends JFrame {

  public GameWindow(String title, int width, int height) {
    // ウインドウ名を設定する
    super(title);

    // ウインドウのサイズを設定する
    setSize(width, height);

    // ウインドウサイズの変更を禁止する
    setResizable(false);

    // ウインドウを画面中央に表示する
    setLocationRelativeTo(null);

    // ウインドウを閉じる動作を設定する(「×」ボタンで終了)
    setDefaultCloseOperation(EXIT_ON_CLOSE);
  }

}

// 描画領域を作成するクラス
class Canvas extends JPanel {

  @Override
  public void paintComponent(Graphics graphics) {
    // 親クラスのpaintComponentメソッドを呼び出す
    super.paintComponent(graphics);

    // 2本の線をパネルに描画する
    // drawLineメソッドの引数は
    // 始点X座標・始点Y座標・終点X座標・終点Y座標
    graphics.drawLine(0, 0, 1280, 720);
    graphics.drawLine(0, 720, 1280, 0);

    // 矩形を描画する
    // drawRectメソッドの引数は
    // 始点X座標・始点Y座標・横サイズ・縦サイズ
    graphics.drawRect(440, 310, 100, 100);

    // 矩形を塗りつぶし描画する
    // fillRectメソッドの引数は
    // 始点X座標・始点Y座標・横サイズ・縦サイズ
    graphics.fillRect(740, 310, 100, 100);
  }

}

// ラベルを表示するクラス
class Label extends JLabel {

  public Label(String string) {
    // ラベルのテキストを設定する
    super(string);

    // テキストのフォントを変更する
    // setFontメソッドの引数はFontオブジェクトであり、
    // インスタンス生成時にフォント名・スタイル・サイズを指定する。
    // なお、スタイルは普通が「Font.PLAIN」で太字が「Font.BOLD」となる。
    setFont(new Font("メイリオ", Font.BOLD, 50));

    // ラベルの位置とサイズを設定する
    // setBoundsメソッドの引数は
    // 始点X座標・始点Y座標・横サイズ・縦サイズ
    setBounds(0, 550, 1280, 100);
    
    // テキストを中央揃えにする
    setHorizontalAlignment(Label.CENTER);
  }

}

// ボタンを表示するクラス
class Button extends JButton {

  public Button(String string) {
    // ボタンのテキストを設定する
    super(string);

    // ボタンテキストのフォントを変更する
    // setFontメソッドの引数はFontオブジェクトであり、
    // インスタンス生成時にフォント名・スタイル・サイズを指定する。
    // なお、スタイルは普通が「Font.PLAIN」で太字が「Font.BOLD」となる。
    setFont(new Font("メイリオ", Font.PLAIN, 50));

    // ボタンの位置とサイズを設定する
    // setBoundsメソッドの引数は
    // 始点X座標・始点Y座標・横サイズ・縦サイズ
    setBounds(390, 100, 500, 100);
  }

  public void result(Label label) {
    // ボタンが押されたときは、ラベルのテキストを変更する
    label.setText("ボタンがクリックされました!");
  }

}

//実行結果

ボタンをクリックすると、以下のようにラベルのテキストが変わります!

今回は、各GUIパーツをフィールド(クラスのメンバ変数)として定義し、より自由にデータのやり取りができるようにしています。重要なポイントは、JLabelクラスとJButtonクラスを継承して「Labelクラス」「Buttonクラス」を定義していることです。

内部ではテキスト・フォント・サイズなどを設定しており、各メソッドの使い方や引数はコメントに記載しているとおりです。また、Mainメソッド内で「button.addActionListener((e) -> button.result(label));」と記載している部分は、ボタンの挙動を決めるキーポイントになります。

addActionListenerメソッドは「ボタンが押されたときの動作」を決めるものですが、引数に「関数型インターフェース」を取ります。これは、詳しい動作を記載したメソッドを自分で定義し、そのメソッドと引数を渡すというものです。基本的にはサンプルコードのように記載すればOKですが、今回はresultメソッドの引数に「label(ラベル)」を渡しています。

resultメソッド内部では、labelのテキストを変更して、「ボタンが押されたこと」をプレイヤーに通知します。少し複雑なプログラムになりましたが、ひとつずつ確認していけば理解が深まります。以上の内容を理解しておけば、じゃんけんゲームやカードゲームのような簡単なゲームを、Javaで制作できるようになります。

【基本編】簡単なじゃんけんゲームを作ってみよう!

今までご紹介した基本知識を使って、簡単なじゃんけんゲームを作ってみましょう。「簡単なゲーム」といっても、ソースコードはかなり長くなり、一部は応用テクニックも必要です。しかし、いずれもIDE(統合開発環境)にコピー&ペーストすれば動作するので、ひとつずつ確認すれば理解できるようになります。

//サンプルプログラム

// ライブラリ
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.WindowEvent;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

// メインクラス
public class Main {

  public static void main(String[] args) {
    // ウインドウを生成する
    new GameWindow("サンプルゲーム", 1280, 720);
  }

}

// ウインドウ描画クラス
class GameWindow extends JFrame {

  //各GUI部品を定義する
  static Canvas canvas;
  static Label labelA;
  static Label labelB;

  // じゃんけんボタンを定義する
  static Button rockButton;
  static Button scissorsButton;
  static Button paperButton;

  // システムボタンを定義する
  static Button resumeButton;
  static Button exitButton;

  public GameWindow(String title, int width, int height) {
    // ウインドウ名を設定する
    super(title);

    // ウインドウのサイズを設定する
    setSize(width, height);

    // ウインドウサイズの変更を禁止する
    setResizable(false);

    // ウインドウを画面中央に表示する
    setLocationRelativeTo(null);

    // ウインドウを閉じる動作を設定する(「×」ボタンで終了)
    setDefaultCloseOperation(EXIT_ON_CLOSE);

    // カンバスを生成する
    canvas = new Canvas();

    // ラベルやボタンなどの初期化処理を行う
    init();

    // ウインドウを表示する
    setVisible(true);

    // 描画領域をウインドウに追加する
    add(canvas);

    // 各ラベルを描画領域に追加する
    canvas.add(labelA);
    canvas.add(labelB);
  }

  public void init() {
    // 各じゃんけんボタンを生成する
    rockButton = new Button("グー", 90, 500);
    scissorsButton = new Button("チョキ", 490, 500);
    paperButton = new Button("パー", 890, 500);

    // 各システムボタンを生成する
    resumeButton = new Button("もう一度!", 200, 400);
    exitButton = new Button("終了する", 790, 400);

    // じゃんけんボタンが押されたときの動作を定義する
    rockButton.addActionListener(e -> judge(Hand.Rock));
    scissorsButton.addActionListener(e -> judge(Hand.Scissors));
    paperButton.addActionListener(e -> judge(Hand.Paper));

    // システムボタンが押されたときの動作を定義する
    resumeButton.addActionListener(e -> resume());
    exitButton.addActionListener(e -> exit());

    // 各じゃんけんボタンを描画領域に追加する
    canvas.add(rockButton);
    canvas.add(scissorsButton);
    canvas.add(paperButton);

    // 各システムボタンを描画領域に追加する
    canvas.add(resumeButton);
    canvas.add(exitButton);

    // 各ラベルを生成する
    labelA = new Label("", 0, 50);
    labelB = new Label("", 0, 200);

    // ゲームを初期状態にする
    reset();
  }

  public void reset() {
    // 各ラベルを設定する
    labelA.setText("じゃんけんゲーム");
    labelB.setText("手を選んでください");

    // 各じゃんけんボタンを表示する
    rockButton.setVisible(true);
    scissorsButton.setVisible(true);
    paperButton.setVisible(true);

    // システムボタンは表示しない
    resumeButton.setVisible(false);
    exitButton.setVisible(false);
  }

  public void done() {
    // 各ラベルを設定する
    labelB.setText("もう一度プレイしますか?");

    // システムボタンを表示する
    resumeButton.setVisible(true);
    exitButton.setVisible(true);

    // 各じゃんけんボタンは表示しない
    rockButton.setVisible(false);
    scissorsButton.setVisible(false);
    paperButton.setVisible(false);
  }

  public void judge(Hand hand) {
    // 相手がどの手を出すかランダムで決める
    Hand enemy = Hand.getHand();

    // 相手の手を表示するメッセージを作成する
    String message = "相手は" + enemy.getName() + "を出しました…";

    // 勝ち負けの判定を行う
    switch (Hand.getDeterminant(hand, enemy)) {
      case 0:
        // あいこの場合はじゃんけんをやり直すため、すぐメソッドを終了する
        labelA.setText(message + "あいこでしょ!");
        return;
      case 1:
        labelA.setText(message + "あなたの負けです!");
        break;
      case 2:
        labelA.setText(message + "あなたの勝ちです!");
        break;
    }

    // 勝ち負けが決まった場合はじゃんけん終了処理を行う
    done();
  }

  public void resume() {
    // 初期状態に戻してじゃんけんをやり直す
    reset();
  }

  public void exit() {
    // アプリケーション終了イベントを送出する
    dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
  }
}

//じゃんけんの手に関するクラス
enum Hand {

  // 3つの手を列挙する
  Rock(0, "グー"), Scissors(1, "チョキ"), Paper(2, "パー");

  // 詳細データ
  private final int num; // 手の番号
  private final String name; // 手の名前

  // 乱数オブジェクト
  static Random rand = new Random();

  Hand(int num, String name) {
    this.name = name;
    this.num = num;
  }

  // ランダムな手を生成する
  public static Hand getHand() {
    return Hand.values()[rand.nextInt(3)];
  }

  // 判定用の数値を算出する
  public static int getDeterminant(Hand player, Hand enemy) {
    // グーを「0」・チョキを「1」・パーを「2」として、
    // 「自分の手 - 相手の手」に3を足して3で割った余りを求めると、
    // 「0」があいこ・「1」が負け・「2」が勝ちになる!
    return (player.getNum() - enemy.getNum() + 3) % 3;
  }

  // 手の番号を取得する
  public int getNum() {
    return num;
  }

  // 手の名前を取得する
  public String getName() {
    return name;
  }

}

// 描画領域を作成するクラス
class Canvas extends JPanel {

  Canvas() {
    // カンバスを「自由レイアウト」に変更する
    setLayout(null);
  }

  @Override
  public void paintComponent(Graphics graphics) {
    // 親クラスのpaintComponentメソッドを呼び出す
    super.paintComponent(graphics);
  }

}

// ラベルを表示するクラス
class Label extends JLabel {

  public Label(String string, int posX, int posY) {
    // ラベルのテキストを設定する
    super(string);

    // テキストのフォントを変更する
    setFont(new Font("メイリオ", Font.BOLD, 50));

    // ラベルの位置とサイズを設定する
    setBounds(posX, posY, 1280, 100);

    // テキストを中央揃えにする
    setHorizontalAlignment(Label.CENTER);
  }

}

// ボタンを表示するクラス
class Button extends JButton {

  public Button(String string, int posX, int posY) {
    // ボタンのテキストを設定する
    super(string);

    // ボタンテキストのフォントを変更する
    setFont(new Font("メイリオ", Font.PLAIN, 50));

    // ボタンの位置とサイズを設定する
    setBounds(posX, posY, 300, 100);
  }

}

//実行結果

ボタンをクリックして勝敗が決まると、以下のような画面に切り替わります。

プレイヤーは3つのボタン「rockButton」「scissorsButton」「paperButton」のいずれかをクリックし、コンピューターと勝負します。ただし、じゃんけんが完了したあとはユーザーに「再勝負」か「終了」か選択してもらうために、「resumeButton」「exitButton」を表示させていることがポイントです。ボタンの表示・非表示は「setVisibleメソッド」で切り替えます。

また、列挙型(enum)として「Hand」を定義し、それぞれの手に応じた数字・文字列を設定している部分も重要です。グーを「0」・チョキを「1」・パーを「2」に関連付けることで、手の管理や勝敗判定が行いやすくなります。特定の要素に数字や文字列を関連付けることは、ゲームプログラミングでよく使うテクニックなので覚えておきましょう。

さらに、Hand列挙型の内部では、「nextIntメソッド」で乱数を発生させて、コンピューターの手をランダムに決定しています。「getDeterminant」メソッドでは、コメントに記載しているように計算式であいこ・負け・勝ちを判定しています。

一見すると複雑なプログラムのように見えるかもしれませんが、行っている処理自体はこれまで紹介した基本知識の組み合わせです。このあとのカードゲームも基本的な部分は同じなので、ボタンやメッセージなどを自分なりに変更してみて、少しずつプログラムに慣れていきましょう。

【基本編】簡単なカードゲームを作ってみよう!

続いて「High&Lowゲーム」を作ってみましょう。これは、プレイヤーと相手がそれぞれカードを1枚ずつ引き、自分のカードが相手より「大きい」か「小さい」か選ぶものです。基本的な部分は、先ほどの「じゃんけんゲーム」と同じように作れるため、難易度は同じくらいです。ただし、一部のクラスは異なるので、サンプルコードをチェックしていきましょう。

//サンプルプログラム

// ライブラリ
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

// メインクラス
public class Main {

  public static void main(String[] args) {
    // ウインドウを生成する
    new GameWindow("サンプルゲーム", 1280, 720);
  }

}

// ウインドウ描画クラス
class GameWindow extends JFrame {

  // 各GUI部品を定義する
  static Canvas canvas;

  // 各ラベルを定義する
  static Label labelA;
  static Label labelB;

  // 自分と敵のカードを表示するラベルを定義する
  static Label playerLabel;
  static Label enemyLabel;

  // High&Lowボタンを定義する
  static Button highButton;
  static Button lowButton;

  // システムボタンを定義する
  static Button resumeButton;
  static Button exitButton;

  // カードクラス
  static Card card;

  // 自分と敵のカード番号
  static int player;
  static int enemy;

  public GameWindow(String title, int width, int height) {
    // ウインドウ名を設定する
    super(title);

    // ウインドウのサイズを設定する
    setSize(width, height);

    // ウインドウサイズの変更を禁止する
    setResizable(false);

    // ウインドウを画面中央に表示する
    setLocationRelativeTo(null);

    // ウインドウを閉じる動作を設定する(「×」ボタンで終了)
    setDefaultCloseOperation(EXIT_ON_CLOSE);

    // カンバスを生成する
    canvas = new Canvas();

    // ラベルやボタンなどの初期化処理を行う
    init();

    // ウインドウを表示する
    setVisible(true);

    // 描画領域をウインドウに追加する
    add(canvas);
  }

  public void init() {
    // 各High&Lowボタンを生成する
    highButton = new Button("HIGH", 90, 500);
    lowButton = new Button("LOW", 890, 500);

    // 各システムボタンを生成する
    resumeButton = new Button("もう一度!", 90, 500);
    exitButton = new Button("終了する", 890, 500);

    // High&Lowボタンが押されたときの動作を定義する
    // Highを選んだ場合は「true」、Lowの場合は「false」とする
    highButton.addActionListener(e -> judge(true));
    lowButton.addActionListener(e -> judge(false));

    // システムボタンが押されたときの動作を定義する
    resumeButton.addActionListener(e -> resume());
    exitButton.addActionListener(e -> exit());

    // 各High&Lowボタンを描画領域に追加する
    canvas.add(highButton);
    canvas.add(lowButton);

    // 各システムボタンを描画領域に追加する
    canvas.add(resumeButton);
    canvas.add(exitButton);

    // 各ラベルを生成する
    labelA = new Label("", 0, 50, 1280, 100);
    labelB = new Label("", 0, 150, 1280, 100);
    playerLabel = new Label("", 90, 300, 300, 100);
    enemyLabel = new Label("", 890, 300, 300, 100);

    // 各ラベルを描画領域に追加する
    canvas.add(labelA);
    canvas.add(labelB);
    canvas.add(playerLabel);
    canvas.add(enemyLabel);

    // プレイヤーと敵それぞれのカードを表示する
    playerLabel.setVisible(true);
    enemyLabel.setVisible(true);

    // カードオブジェクトを生成する
    card = new Card();

    // ゲームを初期状態にする
    reset();
  }

  public void reset() {
    // 各ラベルを設定する
    labelA.setText("High&Lowゲーム!");
    labelB.setText("「High」か「Low」か選んでください");

    // 各High&Lowボタンを表示する
    highButton.setVisible(true);
    lowButton.setVisible(true);

    // システムボタンは表示しない
    resumeButton.setVisible(false);
    exitButton.setVisible(false);

    // カードを1枚ずつ配る
    player = card.getCard();
    enemy = card.getCard();

    // プレイヤーと敵それぞれのカードのマークと番号を表示する
    // ただし、敵のカード情報はまだ伏せておく
    playerLabel.setText(card.getInfo(player));
    enemyLabel.setText("???");
  }

  public void done() {
    // 各ラベルを設定する
    labelB.setText("もう一度プレイしますか?");

    // システムボタンを表示する
    resumeButton.setVisible(true);
    exitButton.setVisible(true);

    // 各High&Lowボタンは表示しない
    highButton.setVisible(false);
    lowButton.setVisible(false);
  }

  public void judge(boolean choise) {
    // それぞれのカードの番号を取得する
    int playerNum = card.getNum(player);
    int enemyNum = card.getNum(enemy);

    // 敵カードのマークと番号を表示する
    enemyLabel.setText(card.getInfo(enemy));

    // 勝利判定を行う
    if (playerNum == enemyNum) {
      labelA.setText("ドローです!");
    } else if (playerNum > enemyNum == choise || playerNum < enemyNum == !choise) {
      // 自分が相手より大きい場合は「High」、
      // 逆の場合は「Low」を選んでいれば勝ち
      labelA.setText("あなたの勝ちです!");
    } else {
      labelA.setText("あなたの負けです!");
    }

    // High&Low終了処理を行う
    done();
  }

  public void resume() {
    // 初期状態に戻してHigh&Lowをやり直す
    reset();
  }

  public void exit() {
    // アプリケーション終了イベントを送出する
    dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
  }
}

class Card {
  // カード総数
  private final int numCards = 52;

  // カードリスト
  // 内部では0~51の番号で管理し、
  // スペード「0~12」、ハート「13~25」、ダイヤ「26~38」、クラブ「39~51」となる
  private List cardList;

  // カードインデックス
  // 1枚取るごとに加算し、51になったら0に戻す
  private int cardIndex;

  public Card() {
    // カードリストを生成する
    cardList = new ArrayList<>();

    // ひとつずつカードを追加する
    for (int i = 0; i < numCards; i++) { cardList.add(i); } // リセットする reset(); } public int getCard() { // カードを山札から1枚取り、インデックスを加算する int card = cardList.get(cardIndex++); // すべてのカードを取ったらリセットする if (cardIndex >= numCards) {
      reset();
    }

    return card;
  }

  // カードのマークを取得する
  public String getMark(int card) {
    // カード番号を13で割ると、その答えは0~3のいずれかになる
    // それぞれの番号をカードのマークに振り分ける
    switch (card / 13) {
      case 0:
        return "スペード";
      case 1:
        return "ハート";
      case 2:
        return "ダイヤ";
      case 3:
        return "クラブ";
      default:
        return "エラー";
    }
  }

  // カードの番号を取得する
  public int getNum(int card) {
    // カード番号を13で割った余りがカード番号になるが、
    // 内部では「0」から始まっているため、1足しておく必要がある
    return (card % 13) + 1;
  }

  // カード情報を文字列形式で取得する
  public String getInfo(int card) {
    // カード情報は「マーク」+「番号」の形式で表示する
    return getMark(card) + getNum(card);
  }

  private void reset() {
    // カードインデックスを初期化する
    cardIndex = 0;

    // 山札をシャッフルする
    Collections.shuffle(cardList);
  }
}

// 描画領域を作成するクラス
class Canvas extends JPanel {

  Canvas() {
    // カンバスを「自由レイアウト」に変更する
    setLayout(null);
  }

  @Override
  public void paintComponent(Graphics graphics) {
    // 親クラスのpaintComponentメソッドを呼び出す
    super.paintComponent(graphics);
  }

}

// ラベルを表示するクラス
class Label extends JLabel {

  public Label(String string, int posX, int posY, int width, int height) {
    // ラベルのテキストを設定する
    super(string);

    // テキストのフォントを変更する
    setFont(new Font("メイリオ", Font.BOLD, 50));

    // ラベルの位置とサイズを設定する
    setBounds(posX, posY, width, height);

    // テキストを中央揃えにする
    setHorizontalAlignment(Label.CENTER);
  }

}

// ボタンを表示するクラス
class Button extends JButton {

  public Button(String string, int posX, int posY) {
    // ボタンのテキストを設定する
    super(string);

    // ボタンテキストのフォントを変更する
    setFont(new Font("メイリオ", Font.PLAIN, 50));

    // ボタンの位置とサイズを設定する
    setBounds(posX, posY, 300, 100);
  }

}

//実行結果

「HIGH」か「LOW」を選択して勝負がつくと、以下の画面に切り替わります。

自分と相手のカードは、ラベルを使って文字列で表示しています。ただし、敵のカードを見せていはいけないため「???」として隠しています。プレイヤーが「High」「Low」を選んだ時点で、敵のカードを公開して勝敗判定を行うという流れです。勝利判定についてはコメントで記載しているため、ひとつずつ確認しましょう。

なお、52枚のカードをひとつのリストで管理することがポイントです。マークごとにリストを分けると煩雑になるからです。カードは4種類のマークごとに13枚ずつあるため、カード番号を13で割るとカードの種類、13で割ったものに1を足すとカード番号になります。こうしたテクニックは、ゲームプログラミングでよく使うので覚えておきましょう。

また、先ほどの「じゃんけんゲーム」と「High&Lowゲーム」では、プログラムの基本構造自体はほとんど同じです。これは、オブジェクト指向を意識したコーディングを行ったことで、プログラムの「再利用性」が高まったため。ゲーム開発の学習では、さまざまなプログラムを作っていくので、「使いまわせる」コードを書くことも重要です。

【応用編】さらに高度なゲームを作るために必要な知識

先ほどご紹介したテクニックを組み合わせれば、単純なゲームは十分に作れます。しかし、迷路ゲームやアクションゲーム、シューティングゲームなどは作れません。なぜなら、「リアルタイムの描画・入力処理」が行えないからです。リアルタイムとは、ユーザーの入力に応じた処理を繰り返すことを指します。

先ほどのサンプルプログラムでは、ユーザーの入力を受けられるのは、あくまでボタンのクリックに限られており、クリックされたときも1回きりの処理しかできません。しかし、実際のゲームの大半は、ユーザーは常にキーボードやコントローラーで入力を繰り返し、画面はそれを反映したものに変わります。

Javaでは「スレッド」や「KeyListenerオブジェクト」の活用により、リアルタイム処理を実現できます。かなり高度な内容になっているため、Javaの基本知識をマスターしておく必要がありますが、これを活用すればアクションゲームやシューティングゲームなど、高度なゲームも作れるようになります。

//サンプルプログラム

// ライブラリ
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

//メインクラス
public class Main {

  public static void main(String[] args) {
    // ウインドウを生成する
    new GameWindow("サンプルゲーム", 1280, 720);
  }

}

//ウインドウ描画クラス
class GameWindow extends JFrame {

  // 各GUI部品を定義する
  Canvas canvas;

  public GameWindow(String title, int width, int height) {
    // ウインドウ名を設定する
    super(title);

    // ウインドウのサイズを設定する
    setSize(width, height);

    // ウインドウサイズの変更を禁止する
    setResizable(false);

    // ウインドウを画面中央に表示する
    setLocationRelativeTo(null);

    // ウインドウを閉じる動作を設定する(「×」ボタンで終了)
    setDefaultCloseOperation(EXIT_ON_CLOSE);

    // カンバスを生成する
    canvas = new Canvas(width, height);

    // 描画領域をウインドウに追加する
    add(canvas);

    // ウインドウを表示する
    setVisible(true);

    // プレイヤーオブジェクトを生成して、
    // KeyListenerをプレイヤーオブジェクトにセットする
    addKeyListener((Player) BaseObject.add("Player"));
  }

}

//描画領域を作成するクラス
class Canvas extends JPanel implements Runnable {

  // スレッドオブジェクト
  Thread thread;

  // メッセージ(ラベル)オブジェクト
  Message message;

  // ウインドウサイズ
  int width;
  int height;

  Canvas(int width, int height) {
    // ウインドウサイズを保存する
    this.width = width;
    this.height = height;

    // カンバスを「自由レイアウト」に変更する
    setLayout(null);

    // 背景色を黒に設定する
    setBackground(Color.BLACK);

    // メッセージ(ラベル)オブジェクトを生成する
    message = new Message();

    // メッセージをパネルに追加する
    add(message);

    // スレッドオブジェクトを生成する
    thread = new Thread(this);

    // スレッドを開始する
    thread.start();
  }

  // スレッドで実行されるメソッド
  // Runnableインターフェースのメソッドを実装する
  public void run() {
    // 無限ループでThreadを実行し続ける
    while (true) {
      // Thread.sleepは「検査例外」なのでtry-catch文が必須
      try {
        // ゲームで一般的な60fps(毎秒60フレーム描画)を
        // 実現するために、16ミリ秒ほど処理を止めて待機する
        // ただし厳密な60fpsを実現するためには、タイマーなどによる処理が必要
        Thread.sleep(1000 / 16);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }

      // オブジェクト処理を行う
      BaseObject.processAll();

      // パネルを再描画する
      repaint();
    }
  }

  public void paintComponent(Graphics graphics) {
    // パネルを背景色で塗りつぶす
    // これを行わないと前フレームの描画内容が残ってしまう
    graphics.setColor(getBackground());
    graphics.fillRect(0, 0, width, height);

    // すべてのキャラクターオブジェクトを描画する
    BaseObject.drawAll(graphics);

    // ラベルにプレイヤーの座標を表示する
    message.setText("X=" + BaseObject.getPlayerPosX() +
        ", Y=" + BaseObject.getPlayerPosY());
  }

}

// メッセージを表示するクラス
class Message extends JLabel {

  public Message() {
    // テキストのフォントを変更する
    // setFontメソッドの引数はFontオブジェクトであり、
    // インスタンス生成時にフォント名・スタイル・サイズを指定する。
    // なお、スタイルは普通が「Font.PLAIN」で太字が「Font.BOLD」となる。
    setFont(new Font("メイリオ", Font.BOLD, 50));

    // テキスト色を「白」に設定する
    setForeground(Color.WHITE);

    // ラベルの位置とサイズを設定する
    // setBoundsメソッドの引数は
    // 始点X座標・始点Y座標・横サイズ・縦サイズ
    setBounds(0, 550, 1280, 100);

    // テキストを中央揃えにする
    setHorizontalAlignment(JLabel.CENTER);
  }

}

abstract class BaseObject {

  // オブジェクトリスト
  private static List objects = new ArrayList<>();

  // プレイヤーオブジェクト
  private static Player player;

  // オブジェクト座標
  protected int posX;
  protected int posY;

  // 移動スピード
  protected int speed;

  // オブジェクトを生成する
  public static BaseObject add(String type) {
    BaseObject object = create(type);
    objects.add(object);
    return object;
  }

  // 指定されたオブジェクトの種類に応じて、適切なオブジェクトを生成する
  private static BaseObject create(String type) {
    // このように、パラメーター(引数)に応じて生成する
    // オブジェクトを変える手法を「ファクトリーメソッド」と呼ぶ
    switch (type) {
      case "Player":
        player = new Player();
        return player;
      default:
        return null;
    }
  }

  // すべてのオブジェクトを順番に処理する
  public static void processAll() {
    for (BaseObject object : objects) {
      object.process();
    }
  }

  // すべてのオブジェクトを順番に描画する
  public static void drawAll(Graphics graphics) {
    for (BaseObject object : objects) {
      object.draw(graphics);
    }
  }

  public static int getPlayerPosX() {
    return player.posX;
  }

  public static int getPlayerPosY() {
    return player.posY;
  }

  // オブジェクトを処理する
  // 抽象メソッドのため、子クラスで実装する必要あり
  abstract void process();

  // オブジェクトを描画する
  // 抽象メソッドのため、子クラスで実装する必要あり
  abstract void draw(Graphics graphics);

}

class Player extends BaseObject implements KeyListener {

  // キー入力状態を保持するための配列
  // 入力中が「true」・押していない状態が「false」となる
  boolean[] keys = new boolean[256];

  Player() {
    // 各要素を初期化する
    posX = 100;
    posY = 100;
    speed = 10;
  }

  // キーイベントを実行
  // 「KeyListenerインターフェース」のメソッドを実装する
  // Keyが押された場合
  public void keyPressed(KeyEvent e) {
    // キー入力状態を「true」にする
    // キーイベントは1秒間に数回しか発生しないため、
    // keyPressedメソッド内でキャラ移動処理を行うと、
    // キャラの移動がカクカクする感じになってしまう。
    // このように変数に入力状態を保持しておくと、キャラ移動が滑らかになる!
    keys[e.getKeyCode()] = true;
  }

  // Keyが離された場合
  public void keyReleased(KeyEvent e) {
    // キー入力状態を「false」に戻す
    keys[e.getKeyCode()] = false;
  }

  // Keyが入力された場合
  public void keyTyped(KeyEvent e) {
    // 特にないため現時点では空実装
  }

  // キー入力の状態に応じた処理を行う
  void process() {
    // 配列の並びは「仮想キーコード」に関連付けている。
    // たとえば「VK_LEFT」の値は「0x25(10進数で37)」なので、
    // keys[KeyEvent.VK_LEFT]は37番目の要素を参照することになる
    if (keys[KeyEvent.VK_LEFT]) {
      // speedの値だけキャラを移動させる
      // speedの値を大きくすると、キャラの移動量も大きくなります
      posX -= speed;
    }

    if (keys[KeyEvent.VK_RIGHT]) {
      posX += speed;
    }
    
    if (keys[KeyEvent.VK_UP]) {
      posY -= speed;
    }
    
    if (keys[KeyEvent.VK_DOWN]) {
      posY += speed;
    }
  }

  void draw(Graphics graphics) {
    // 赤い矩形を描画してプレイヤーを表現する
    graphics.setColor(Color.red);
    graphics.fillRect(posX, posY, 30, 30);
  }

}

//実行結果

キーボードの矢印キーを押すと、以下のようにプレイヤーキャラが移動します。

これまでのサンプルコードとは多くの点で異なっており、以下3つのポイントがとくに重要です。

  • 「KeyListenerインターフェース」でキー入力を受け付けている
  • 「Threadオブジェクト」を活用して別スレッドで処理を実行している
  • キャラクターの移動や描画などを「専用クラス」で管理している</li<

「KeyListenerインターフェース」でキー入力を受け付けている

先ほどのサンプルプログラムでは、「GameWindowクラス」のコンストラクタで、「addKeyListenerメソッド」を定義しています。これは、引数に「KeyListenerインターフェース」を実装したオブジェクトを指定することで、キー入力が発生したときに「keyPressed」などのメソッドが呼び出されるというシステムです。

addKeyListenerメソッドに指定するオブジェクトとして、「Playerオブジェクト」を指定しています。これは「BaseObject抽象クラス」を継承し、内部ではキー入力時に呼び出される「keyPressedメソッド」などを実装していることがポイント。Playerクラスでは、「keys」というフィールドを定義し、最大256個のキー入力状態を管理しています。キーイベントの処理やキャラ移動については、すでに記載してあるとおりです。

「Threadオブジェクト」を活用して別スレッドで処理を実行している

また、このプログラムでは「Threadオブジェクト」を活用して、別スレッドでゲームの基本処理を行っています。「Runnableインターフェース」を実装したクラスで「Thread.startメソッド」でスレッドを起動すると、「runメソッド」内部でスレッド処理が行えるようになります。runメソッドでは、無限ループを回してオブジェクトの処理と描画を行っています。こうすることで、リアルタイムのゲーム処理を実現できます。

さらに、runメソッドでは「Thread.sleepメソッド」を呼び出し、1000/16ミリ秒だけスレッドを待機させています。試しにここのtry-catch文をコメントアウトすると、プレイヤーがものすごい速さで移動するはずです。Thread.sleepメソッドを使うことで、毎秒60フレーム描画となり、適切なスピードでゲームを進行させることができます。

キャラクターの移動や描画などを「専用クラス」で管理している

BaseObject抽象クラスでは、全オブジェクトを管理するリストや、指定したオブジェクトを生成する「addメソッド」などがあります。これは内部で「createメソッド」を呼び出し、引数で指定された文字列に該当するオブジェクトを作成します。このような方式を「ファクトリーメソッド」と呼び、オブジェクト指向プログラミングのテクニックです。

上記のサンプルプログラムで、BaseObjectを継承したクラスを追加して、BaseObjectクラスの「createメソッド」を変更すると、ゲームに登場するキャラクラーを簡単に増やせます。たとえば、BaseObjectを継承した「Characterクラス」「Enemyクラス」を作るなどです。複雑なプログラムですが、以上のひな型を使えば高度なゲームも制作できます。

簡単な2DゲームならJavaで十分に開発できる!

Javaでゲームを開発する方法や必要な知識、ゲームプログラミングのテクニックなどを解説しました。Javaはゲーム開発現場で一般的に活用されているプログラミング言語ではありませんが、今回ご紹介したGUIライブラリ「Swing」を活用すれば、単純な2Dゲームなら十分に作れます。

アクションゲームやシューティングゲームなど高度なゲームも、「リアルタイム処理」を行うことで制作可能です。高度なプログラミング知識は必要になりますが、今回ご紹介したサンプルコードを少しずつ追っていけば、ゲーム開発に必要な知識が身に付きます。この機会にぜひJavaのゲーム開発にチャレンジしてみてください。

アクセスランキング 人気のある記事をピックアップ!

    コードカキタイがオススメする記事!

    1. 子供におすすめのプログラミングスクール10選!学習メリットや教室選びのコツも紹介

      2024.01.26

      子供におすすめのプログラミングスクール10選!学習メリットや教室選びのコツも紹介

      #プログラミングスクール

    2. 【完全版】大学生におすすめのプログラミングスクール13選!選ぶコツも詳しく解説

      2022.01.06

      【完全版】大学生におすすめのプログラミングスクール13選!選ぶコツも詳しく解説

      #プログラミングスクール

    3. 【未経験でも転職可】30代におすすめプログラミングスクール8選!

      2024.01.26

      【未経験でも転職可】30代におすすめプログラミングスクール8選!

      #プログラミングスクール

    4. 初心者必見!独学のJava学習方法とおすすめ本、アプリを詳しく解説

      2024.01.26

      初心者必見!独学のJava学習方法とおすすめ本、アプリを詳しく解説

      #JAVA

    5. 忙しい社会人におすすめプログラミングスクール15選!失敗しない選び方も詳しく解説

      2024.01.26

      忙しい社会人におすすめプログラミングスクール15選!失敗しない選び方も詳しく解説

      #プログラミングスクール

    1. 【無料あり】大阪のおすすめプログラミングスクール14選!スクール選びのコツも紹介

      2022.01.06

      【無料あり】大阪のおすすめプログラミングスクール14選!スクール選びのコツも紹介

      #プログラミングスクール

    2. 【目的別】東京のおすすめプログラミングスクール20選!スクール選びのコツも徹底解説

      2024.01.26

      【目的別】東京のおすすめプログラミングスクール20選!スクール選びのコツも徹底解説

      #プログラミングスクール

    3. 【無料あり】福岡のおすすめプログラミングスクール13選!選び方も詳しく解説

      2024.01.26

      【無料あり】福岡のおすすめプログラミングスクール13選!選び方も詳しく解説

      #プログラミングスクール

    4. 【徹底比較】名古屋のおすすめプログラミングスクール13選!選び方も詳しく解説

      2024.01.26

      【徹底比較】名古屋のおすすめプログラミングスクール13選!選び方も詳しく解説

      #プログラミングスクール

    5. 【徹底比較】おすすめのプログラミングスクール18選!失敗しない選び方も徹底解説

      2024.01.26

      【徹底比較】おすすめのプログラミングスクール18選!失敗しない選び方も徹底解説

      #プログラミングスクール