Javaでサンプルプログラムを組みながらゲーム開発をしてみたい
と疑問に思っている方も多いのではないでしょうか?
そこで今回は、トランプを使った簡単な「HIGH&LOW」ゲームの作り方をサンプルコード付きで解説いたします。この記事を読めば、Javaでゲームを開発する方法が一通りわかります。
この記事で学べること
-
Javaでゲームを開発するための基礎知識
-
Javaでゲームを開発する手順(サンプルコード付き)
プログラミング初心者の方でもイメージしやすいように、ゲームの仕様の決め方から解説していますので、ぜひご一読ください。
- Javaを使えばソーシャルゲームやスマホゲームが開発できる
- Javaを用いたゲーム開発には文法やライブラリの知識が必要
- ゲームの仕様に合わせてJavaのクラス作成が必要
Javaでゲームを開発するために必要な知識とは
Javaは汎用プログラミング言語のため、業務用システムやWebサービスなど、幅広い用途での開発が可能です。もちろんJavaでのゲーム開発も可能で主に以下のゲームを開発できます。
- コンシューマーゲーム
- ソーシャルゲーム
- スマホゲーム
しかしJavaでゲームを開発するには、基本的なプログラミングの知識に加えて、以下の知識も必要です。
- 文法やオブジェクト指向の知識
- Javaの開発環境を構築する知識
- Javaのゲーム開発に役立つライブラリの知識
Javaでのゲーム開発に必要な知識の詳細については、以下の記事でも詳しく解説しています。まずはこちらの記事で、基本的な知識を理解しておきましょう。
Javaで簡単なゲームを開発してみよう【サンプルプログラム付き】
実際の開発がイメージしやすいよう、Javaでゲーム開発する流れをご紹介します。今回は、トランプを使った簡単な「HIGH&LOW」ゲームを開発しましょう。
ただし、Javaの基本文法やオブジェクト指向についてすでに理解している方を対象としています。もし、まだJavaの基礎知識が習得できていない方は、前章に戻って予習してから読んでくださいね。
ステップ1:ゲームの仕様を決める
まずは、ゲームの仕様を決めましょう。ゲームの仕様があやふやな状態で作り始めると手戻りが発生しやすくなるので、最低限以下の事項は固めておく必要があります。
- ゲームに使用するもの
- プレイ人数
- プレイ回数
- ゲームの流れ
- 勝ち負けの基準
- 画面仕様
今回はHIGH&LOWゲームを作成しますので、以下のように仕様を決めました。シンプルにするために、今回は1発勝負としています。
ゲームに使用するもの | ジョーカーを除いた、52枚のトランプを使用する。 |
プレイ人数 | 親と子の2人で行う。 親:ゲームシステム側、子:ゲームプレイヤー側とする。 |
プレイ回数 | 1回(繰り返しのプレイはしない) |
ゲームの流れ&勝ち負けの基準 | 1.親が山札から1枚とり、表側表示する。子も山札から1枚とるが、伏せておく。 2.子(プレイヤー)は、親カードの数字よりも子カードの数字が高い(HIGH)か低い(LOW)か予想し、どちらか選択する。 3.子カードを表にして、予想した結果と合っていればプレイヤーの勝ち、外れていればプレイヤーの負けとする。親と子の数字が同じだった場合は、引き分けとする。 |
画面仕様 | トップパネル(上部)・ミドルパネル(中部)・ボトムパネル(下部)の3部構成とする。 ・トップパネル:ゲームの進行状況や勝敗を伝えるメッセージを表示 ・ミドルパネル:親と子のカードをそれぞれ表示 ・ボトムパネル:プレイヤーが選択するためのボタンを表示 また、トップパネルには状況に応じて以下のメッセージを表示する。 ・初期表示:『一発勝負!HIGHかLOWか当ててください。』 ・正解だった場合:『大正解、あなたの勝ちです!』 ・不正解だった場合:『不正解、あなたの負けです!』 ・引き分けだった場合:『奇遇ですね。引き分けです!』 |
参考までに、画面の完成イメージは以下の通りです。
なお、IT企業への転職や副業での収入獲得を見据え、独学でJavaといったプログラミングスキルを習得できるか不安な人は「侍エンジニア」をお試しください。
侍エンジニアでは、現役エンジニアと学習コーチの2名体制で学習をサポートしてもらえます。
「受講生の学習完了率98%」「累計受講者数4万5,000名以上」という実績からも、侍エンジニアなら未経験からでも挫折なく転職や副業収入の獲得が実現できますよ。
ステップ2:クラス構成を決める
仕様を決めたことで、どのようなクラスを作ればよいかイメージしやすくなりました。次は、Javaプログラミングの鍵とも言えるクラス構成を決めましょう。
クラスは、ゲームに登場する人や物の特徴を元に作る設計図(ひな形)です。クラスを洗い出す際は、以下の点についてそれぞれ考えると良いでしょう。
- クラスの役割
- クラスのデータ(内部変数)
- クラスのメソッド(関数)
- 他クラスとの関係性
今回は、以下5つのクラスを作成します。
クラス名(プログラム上での名称) | ゲームメインクラス(GameMain) |
役割 | ゲームの進行をメインで制御する。 |
データ | なし |
メソッド | main(プログラムの最初に実行するメインメソッド) |
他クラスとの関係 | 表示クラス、プレイヤークラス、山札クラスを呼び出す。 |
クラス名(プログラム上での名称) | 表示クラス(Display) |
役割 | ゲーム画面の作成・表示更新を制御する。 |
データ | ・disp(画面全体) ・top_panel, mid_panel, bottom_Panel (上部/中央部/下部パネル) ・msg_lbl(メッセージ表示ラベル) ・parent_lbl, parent_suit_lbl, parent_no_lbl (親のカード情報ラベル) ・child_lbl, child_suit_lbl, child_no_lbl(子のカード情報ラベル) ・btn_high, btn_low(HIGH/LOWボタン) ・parent, child(親/子オブジェクト) |
メソッド | ・actionPerformed (HIGHボタンかLOWボタンが選択されたときの処理) ・setPanel(パネルのフォント設定を行う) ・setLabelFont(ラベルのフォント設定を行う) ・setButton(ボタンの設定を行う) ・getSuitIcon (マークに応じたアイコンオブジェクトを取得する) ・getNoStr(数字に応じた表示文字列を取得する) |
他クラスとの関係 | ゲームメインクラスから呼び出される。 |
クラス名(プログラム上での名称) | プレイヤークラス(Player) |
役割 | プレイヤーがカードを引いたり、カード情報を管理したりする。 |
データ | card_info(カード情報) |
メソッド | ・Draw(カードを1枚ドローする) ・GetNo(プレイヤーが持っているカードの数字を取得する) ・GetSuit(プレイヤーが持っているカードのマークを取得する) |
他クラスとの関係 | ゲームメインクラスから呼び出され、山札クラスを呼び出す。 |
クラス名(プログラム上での名称) | 山札クラス(Deck) |
役割 | 山札からのカード取り出しや、残り山札の状態管理を行う。 |
データ | ・card_list(カード52枚のリスト) ・card_index(次に取り出すカード番号) |
メソッド | GetCard(山札からカードを1枚取り出す) |
他クラスとの関係 | ゲームメインクラス、プレイヤークラスから呼び出され、 カードクラスを呼び出す。 |
クラス名(プログラム上での名称) | カードクラス(Card) |
役割 | カード1枚の情報(数字/マーク)を管理する。 |
データ | ・suit(マーク) ・no(数字) |
メソッド | なし |
他クラスとの関係 | プレイヤークラス、山札クラスから呼び出される。 |
なお、クラス構成を決める際は「クラス図」と呼ばれる図を作成するのもおすすめです。クラス図の基本について知りたい方は、以下の記事も読んでみてくださいね。
ステップ3:カードクラス/山札クラスを作る
ステップ2で決めたクラス構成を元にクラスを順番に実装していきます。まずは、他のクラスとの関係が最も少ないカードクラスと山札クラスから作成しましょう。
なお、今回はEclipseで「HighAndLow」という名前のプロジェクトを作成してゲームを開発していきます。Eclipseの導入がまだの方は、「Javaの開発環境を構築しよう」の内容を参考に導入しておいてください。
また、Eclipseでのプロジェクト作成方法は以下の記事でご紹介しているので、併せてご覧くださいね。
山札クラスでは、次に取り出すカードをランダムで決めなければなりません。今回は、「ArrayList」というリスト機能を使ってランダム性を表現します。
山札クラスのサンプルコードは以下の通りです。
■ファイル名:Deck.java
import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; // カード情報 class Card { int suit; // マーク(0:スペード/1:ハート/2:ダイヤ/3:クラブ) int no; // 数字(A=1/J=11/Q=12/K=13) } // 山札クラス public class Deck { // 定数定義 final int TOTAL_CARD = 52; // カードの総数 // メンバ変数定義 // カードリスト(山札) 0~51の要素を入れ、それぞれマークと数字を割り当てる // 0 ~12:スペードA~K / 13~25:ハートA~K / 26~38:ダイヤA~K / 39~51:クラブA~K private List card_list; // 次に取り出すカード番号(1枚引くごとに1加算する) private int card_index = 0; // コンストラクタ(初期化処理) public Deck() { // 次に取り出すカード番号を初期化 card_index = 0; // 1枚目 // 山札を初期化 card_list = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51)); // 山札をシャッフルする Collections.shuffle(card_list); } // メソッド定義 // 山札からカードを1枚取り出し、そのカードのマークと数字をリターンする public Card GetCard() { Card card_info = new Card(); // カード情報格納用 int card_no = 0; // カード番号 // カードを一枚取り出して、カード番号(0~51のいずれか)を取得 card_no = card_list.get(card_index); // リストの先頭要素を取り出す // 1枚取り出したのでカード番号を1加算 card_index++; // 山札をすべて引いた場合、山札を初期化する if( TOTAL_CARD <= card_index ) { card_index = 0; // 先頭に戻す Collections.shuffle(card_list); // 山札をシャッフルする } card_info.suit = card_no / 13; // マーク=カード番号を13で割った商 card_info.no = (card_no % 13) + 1; // 数字=カード番号を13で割った余り+1 return card_info; } }
パッケージのインポート
最初の「import」で始まる4行は、必要なライブラリをインポートして使えるようにする処理です。
import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List;
カードクラスの定義
「class CardInfo」の箇所がカードクラスで、トランプのカード情報(マーク/数字)を記録するためのデータを定義しています。ただし、カードクラスは山札クラスの補助的な位置付けなので、山札クラスと同じファイル内に記述しました。
// カード情報 class Card { int suit; // マーク(0:スペード/1:ハート/2:ダイヤ/3:クラブ) int no; // 数字(A=1/J=11/Q=12/K=13) }
山札クラスの定義
「public class Deck」以降の箇所が、山札クラスの定義です。山札の枚数は52枚で固定なので、最初に「final int TOTAL_CARD = 52;」として定数化しています。
// 山札クラス public class Deck { // 定数定義 final int TOTAL_CARD = 52; // カードの総数
その次に山札クラスのメンバ変数を定義しています。具体的には、カードリスト(card_list)と次に取り出すカード番号(card_index)の2つです。
// カードリスト(山札) 0~51の要素を入れ、それぞれマークと数字を割り当てる // 0 ~12:スペードA~K / 13~25:ハートA~K / 26~38:ダイヤA~K / 39~51:クラブA~K private List card_list; // 次に取り出すカード番号(1枚引くごとに1加算する) private int card_index = 0;
山札クラスの初期化処理
「public Deck()」の箇所はコンストラクタで、山札クラスのオブジェクト生成直後に行われる初期化処理を記述しています。「card_index = 0;」の箇所は、次に取り出すカード番号(card_index)を先頭にする処理です。
public Deck() { // 次に取り出すカード番号を初期化 card_index = 0; // 1枚目
次の「card_list = new ArrayList<>」で始まる処理では、ライブラリのArrayListクラスを使用して山札に含まれる要素を決めています。このように52枚のカード番号を列挙することで、リストにカード52枚分のデータを登録できます。
// 山札を初期化 card_list = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51));
最後の「Collections.shuffle(card_list);」という箇所で、山札をシャッフルしています。shuffleメソッドを使うことで、リストに登録された52枚の順序をランダムに入れ替え可能です。
// 山札をシャッフルする Collections.shuffle(card_list);
山札クラスのカード取り出し処理
「public Card GetCard()」の箇所は、山札からカードを1枚取り出しマークと数字をリターンするGetCardメソッドの定義です。まず「Card card_info = new Card();」として、カード情報を保持するためにカードクラスのオブジェクトを生成しています。
// 山札からカードを1枚取り出し、そのカードのマークと数字をリターンする public Card GetCard() { Card card_info = new Card(); // カード情報格納用
「int card_no = 0;」は、新しく引いたカード番号を保持するための変数定義および初期化です。次の処理で「card_no = card_list.get(card_index);」として、山札から取り出したカード番号をこの変数にセットしています。
int card_no = 0; // カード番号 // カードを一枚取り出して、カード番号(0~51のいずれか)を取得 card_no = card_list.get(card_index); // リストの先頭要素を取り出す
カードを1枚引いたので、次の「card_index++;」という箇所で次に取り出すカード番号を1加算しています。この処理はカードを1枚引くたびに行われますが、52回実行すると残り山札がなくなり、それ以上カードを引けません。
// 1枚取り出したのでカード番号を1加算 card_index++;
そこで、次の「if( TOTAL_CARD <= card_index )」という箇所で、山札がなくなった場合に仕切り直す処理を行っています。具体的には、次に引くカード番号(card_index)を先頭に戻す処理と、山札のシャッフル処理の2つです。
// 山札をすべて引いた場合、山札を初期化する if( TOTAL_CARD <= card_index ) { card_index = 0; // 先頭に戻す Collections.shuffle(card_list); // 山札をシャッフルする }
次の「card_info.suit」と「card_info.no」へ代入している箇所は、カード番号を「マーク」と「数字」に変換する処理です。カード番号は0~51の52種類ありますが、以下の通りマークと数字がそれぞれ割り当てられています。
カードの番号付け | 0~12⇒スペードA~K/13~25 ⇒ハートA~K/26~38 ⇒ダイヤA~K/39~51 ⇒クラブA~K |
マーク(card_info.suit)の番号付け | スペード⇒0/ハート ⇒1/ダイヤ ⇒2/クラブ ⇒3 |
数字(card_info.no)の番号付け | A⇒1/J ⇒11/Q ⇒12/K ⇒13/それ以外はカードに書かれた数字と同じ |
よって、マークは「カード番号を13で割った商」、数字は「カード番号を13で割った余り+1」でそれぞれ求められます。たとえば「ハートJ」のカード番号(23)に当てはめて計算するとマーク:23÷13=1(ハート)、23%13+1=11(J)となり、想定通りです。
card_info.suit = card_no / 13; // マーク=カード番号を13で割った商 card_info.no = (card_no % 13) + 1; // 数字=カード番号を13で割った余り+1
最後の「return card_info;」という箇所で、引いたカード情報をメソッドの最終結果としてリターンしています。そうすることで、次に作成するプレイヤークラスでカード情報を取得することが可能です。
return card_info;
ステップ4:プレイヤークラスを作る
プレイヤークラスでは、前章で作った山札クラスからカードをドローする処理や、引いたカード情報を管理する機能を作成します。なお、カードを引いたり情報管理したりするのは親も子も変わらないので、このクラスは親と子で共用可能です。
サンプルコードは以下のようになります。
■ファイル名:Player.java
// プレイヤークラス public class Player { // メンバ変数定義 // プレイヤーが持っているカード情報 private Card card_info; // コンストラクタ(初期化処理) public Player() { card_info = new Card(); // オブジェクトを生成 // カード情報を初期化 card_info.no = 0; // 数字 card_info.suit = 0; // マーク } // メソッド定義 // カードを1枚ドロー public void Draw(Deck deck) { // ドローしたカード情報を取得 card_info = deck.GetCard(); return; } // プレイヤーが持っているカードの数字を取得 public int GetNo() { return card_info.no; } // プレイヤーが持っているカードのマークを取得 public int GetSuit() { return card_info.suit; } }
プレイヤークラスの定義
「public class Player」の箇所が、プレイヤークラスの定義です。まずは「private Card card_info;」として、カード情報の内部データを定義しています。
プレイヤーのカード情報を外部から書き換えられると、トランプゲームが破綻してしまいます。そのため、この変数は「private」を付けてカプセル化することで、プレイヤークラス内部だけで書き換えられるようにしました。
// プレイヤークラス public class Player { // メンバ変数定義 // プレイヤーが持っているカード情報 private Card card_info;
プレイヤークラスの初期化処理
次の「public Player()」という箇所は、プレイヤークラスのコンストラクタ(初期化処理)です。カード情報を保持するためにカードクラスのオブジェクトを生成し、数字とマークに0をセットしています。
public Player() { card_info = new Card(); // オブジェクトを生成 // カード情報を初期化 card_info.no = 0; // 数字 card_info.suit = 0; // マーク }
プレイヤークラスのドローメソッド
次の「public void Draw(Deck deck)」は、カードを1枚引くDrawメソッドの定義です。山札クラスのGetCardメソッドを使うことでカードを1枚引き、そのカード情報を内部データに記録しています。
// カードを1枚ドロー public void Draw(Deck deck) { // ドローしたカード情報を取得 card_info = deck.GetCard(); return; }
プレイヤークラスのカード情報取得メソッド
そして最後の「public int GetNo()」と「public int GetSuit()」は、プレイヤーの引いたカード情報(マークと数字)をそれぞれ取得する関数です。内部データをカプセル化した代わりに、外部からのプレイヤークラス内部データ参照はこのように専用の関数で行います。
// プレイヤーが持っているカードの数字を取得 public int GetNo() { return card_info.no; } // プレイヤーが持っているカードのマークを取得 public int GetSuit() { return card_info.suit; }
ステップ5:ゲームメインクラスを作る
ゲームメインクラスはHIGH&LOWゲームの進行を行う、いわゆる「MC」のような存在です。また、プログラムを実行するとこのクラスが最初に呼ばれます。
サンプルコードは以下の通りです。
■ファイル名:GameMain.java
// ゲームメインクラス public class GameMain { public static void main(String[] args) { // 山札を生成 Deck deck = new Deck(); // 親がカードを引く Player parent = new Player(); parent.Draw(deck); // 子がカードを引く Player child = new Player(); child.Draw(deck); // ゲーム画面を生成 new Display( parent, child ); return; } }
「public static void main(String[] args)」という箇所はこのプログラムのメインメソッドです。Javaのプログラムには必ずこのメソッドが存在し、プログラム実行時には最初に呼ばれます。
public static void main(String[] args)
メインメソッド冒頭の「Deck deck = new Deck();」という箇所は、ゲームに使う山札オブジェクトを生成する処理です。山札クラスのコンストラクタでシャッフル処理を行っているので、この時点で次に取り出すカードがランダムに決まります。
// 山札を生成 Deck deck = new Deck();
次の「Player parent = new Player();」という箇所は、親オブジェクトを生成する処理です。直後にプレイヤークラスのDrawメソッドを実行することで、親がカードを1枚取得しています。
// 親がカードを引く Player parent = new Player(); parent.Draw(deck);
次の「Player child = new Player();」という箇所は、子オブジェクトを生成する処理です。親と同様に、プレイヤークラスのDrawメソッドによって子もカードを1枚取得しています。
// 子がカードを引く Player child = new Player(); child.Draw(deck);
最後の「new Display( parent, child );」という箇所は、表示クラスのオブジェクトを生成する処理です。表示の制御には親と子のデータが必要なので、それぞれ引数に指定することでデータを受け渡しています。
// ゲーム画面を生成 new Display( parent, child );
表示クラスについては、次章で作成します。
ステップ6:表示クラスを作る
最後に、ゲームプレイ画面の作成・更新を行う表示クラスを作ります。今回は、Javaで画面開発を行うためのフレームワーク(便利ツール)である「AWT」と「Swing」の2つを使用します。
AWTやSwingについて良く知らない方は、以下の記事で予習しておくのがおすすめです。
サンプルコードは以下の通りです。ゲーム画面には様々なものを表示するので、ボリュームはかなり多めとなっています。
■ファイル名:Display.java
import java.awt.Color; import java.awt.Font; import java.awt.Dimension; import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JLabel; import javax.swing.JButton; import javax.swing.ImageIcon; // 表示クラス public class Display implements ActionListener { // 画面全体のオブジェクト private JFrame disp; // パネルオブジェクト(上部/中央部/下部) private JPanel top_panel, mid_panel, bottom_Panel; // メッセージ表示ラベルオブジェクト private JLabel msg_lbl; // 親のカード情報ラベルオブジェクト(マーク、数字) private JLabel parent_lbl, parent_suit_lbl, parent_no_lbl; // 子のカード情報ラベルオブジェクト(マーク、数字) private JLabel child_lbl, child_suit_lbl, child_no_lbl; // ボタンオブジェクト(HIGH/LOW) private JButton btn_high, btn_low; // プレイヤーオブジェクト(親、子) private Player parent, child; // コンストラクタ(初期化処理) public Display( Player prn, Player chl ) { // メインクラスから受け取った親と子のオブジェクトを設定 parent = prn; child = chl; // ゲーム画面全体の表示設定 disp = new JFrame("HIGH & LOW"); // 画面を生成 disp.setSize(480, 280); // 表示サイズを設定 disp.setLocationRelativeTo(null); // 画面の表示位置を中央に設定 disp.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 「×」ボタンで画面を閉じるように設定 disp.setResizable(false); // 画面サイズを変更できないように設定 // トップパネルの表示設定 top_panel = new JPanel(); // パネルを生成 setPanel(top_panel, Color.ORANGE, null, new Dimension(480, 50) ); // パネルの背景色、レイアウト、サイズを設定 disp.add( top_panel, BorderLayout.NORTH ); // 画面上部にパネルを追加 // メッセージラベルを表示 msg_lbl = new JLabel("一発勝負!HIGHかLOWか当ててください。"); // ラベルを生成 top_panel.add(msg_lbl); // トップパネルに追加 setLabelFont(msg_lbl, Color.BLACK, 0, 15, 480, 20, 20, false); // ラベルのフォント設定 // ミドルパネルの表示設定 mid_panel = new JPanel(); // パネルを生成 setPanel(mid_panel, Color.CYAN, null, new Dimension(480, 180) ); // パネルの背景色、レイアウト、サイズを設定 disp.add( mid_panel, BorderLayout.CENTER ); // 画面中央部にパネルを追加 // 親カードの情報を表示 parent_lbl = new JLabel("私のカード"); parent_suit_lbl = new JLabel( getSuitIcon( parent.GetSuit() ) ); // マークを表示するラベル parent_no_lbl = new JLabel( getNoStr( parent.GetNo() ) ); // 数字を表示するラベル // ミドルパネルに追加 mid_panel.add(parent_lbl); mid_panel.add(parent_suit_lbl); mid_panel.add(parent_no_lbl); // ラベルのフォント設定 setLabelFont(parent_lbl, Color.WHITE, 90, 10, 100, 20, 14, false ); setLabelFont(parent_suit_lbl, Color.WHITE, 100, 10, 80, 100, 16, false ); setLabelFont(parent_no_lbl, Color.WHITE, 100, 35, 80, 100, 16, true ); // 子カードの情報を表示 child_lbl = new JLabel("あなたのカード"); child_suit_lbl = new JLabel(""); child_no_lbl = new JLabel("?"); // ミドルパネルに追加 mid_panel.add(child_lbl); mid_panel.add(child_suit_lbl); mid_panel.add(child_no_lbl); // ラベルのフォント設定 setLabelFont(child_lbl, Color.WHITE, 265, 10, 150, 20, 14, false ); setLabelFont(child_suit_lbl, Color.LIGHT_GRAY, 300, 10, 80, 100, 16, false ); setLabelFont(child_no_lbl, Color.LIGHT_GRAY, 300, 35, 80, 100, 16, true ); // ボトムパネルの表示設定 bottom_Panel = new JPanel(); setPanel( bottom_Panel, Color.LIGHT_GRAY, new BorderLayout(), new Dimension(480, 50) ); // パネルの背景色、レイアウト、サイズを設定 disp.add( bottom_Panel, BorderLayout.SOUTH ); // 画面下部にパネルを追加 // HIGHボタンを表示 btn_high = new JButton("HIGH"); setButton( btn_high, this, 240, 50, 20 ); // ボタンのフォントやイベント設定 bottom_Panel.add( btn_high, BorderLayout.WEST ); // ボトムパネル左側にボタンを追加 // LOWボタンを表示 btn_low = new JButton("LOW"); setButton( btn_low, this, 240, 50, 20 ); // ボタンのフォントやイベント設定 bottom_Panel.add( btn_low, BorderLayout.EAST ); // ボトムパネル右側にボタンを追加 // ゲーム画面を表示 disp.setVisible(true); } // HIGHかLOWが選択されたときのイベント public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); // アクションコマンド(どのボタンが押されたか) int parent_no = parent.GetNo(); // 親カードの数字 int child_no = child.GetNo(); // 子カードの数字 int child_suit = child.GetSuit(); // 子カードのマーク // 子のカードをオープン child_no_lbl.setBackground(Color.WHITE); // 数字の背景色 child_no_lbl.setText( getNoStr( child.GetNo() ) ); // 数字の表示データ child_suit_lbl.setBackground(Color.WHITE); // マークの背景色 child_suit_lbl.setIcon( getSuitIcon( child_suit ) );// マークの表示データ // 押されたボタンに応じた処理を行う if( cmd.equals("HIGH") ) // HIGHボタンが押された時の処理 { // ボタンの色を変える btn_high.setBackground(Color.GREEN); // 結果を判定してメッセージ更新 if( parent_no < child_no ) // 子の方が大きい msg_lbl.setText("大正解、あなたの勝ちです!"); else if( child_no < parent_no ) // 親の方が大きい msg_lbl.setText("不正解、あなたの負けです!"); else // 親と子の数字が同じ msg_lbl.setText("奇遇ですね。引き分けです!"); } else if( cmd.equals("LOW") ) // LOWボタンが押された時の処理 { btn_low.setBackground(Color.GREEN); // ボタンの色を変える // 結果を判定してメッセージ更新 if( parent_no < child_no ) // 子の方が大きい msg_lbl.setText("不正解、あなたの負けです!"); else if( child_no < parent_no ) // 親の方が大きい msg_lbl.setText("大正解、あなたの勝ちです!"); else // 親と子の数字が同じ msg_lbl.setText("奇遇ですね。引き分けです!"); } return; } // パネルのフォント設定を行うメソッド public static void setPanel(JPanel panel, Color color, BorderLayout layout, Dimension dimension ) { panel.setBackground(color); // 背景色を設定 panel.setLayout(layout); // レイアウトを設定 panel.setPreferredSize(dimension); // 表示サイズを設定 return; } // ラベルのフォント設定を行うメソッド public static void setLabelFont(JLabel label, Color clr, int x_pos, int y_pos, int x_size, int y_size, int strSize, boolean opq ) { label.setBackground(clr); // 背景色を設定 label.setLocation(x_pos, y_pos); // 表示位置を設定 label.setSize(x_size, y_size); // 表示サイズを設定 label.setFont( new Font("MS ゴシック", Font.PLAIN, strSize) ); // 書式、文字サイズを設定 label.setHorizontalAlignment(JLabel.CENTER); // 水平方向中央揃え label.setVerticalAlignment(JLabel.CENTER); // 垂直方向中央揃え label.setOpaque(opq); // ラベルの透明性を設定(true=不透明、false=透明) return; } // ボタンの設定を行うメソッド public static void setButton(JButton btn, ActionListener al, int x_size, int y_size, int strSize ) { btn.setPreferredSize(new Dimension(x_size, y_size)); // 表示サイズを設定 btn.setFont( new Font("MS ゴシック", Font.PLAIN, strSize) ); // 書式、文字サイズを設定 btn.addActionListener(al); // ボタンが押された時のイベントを受け取れるように設定 return; } // マークに応じたアイコンオブジェクトを取得するメソッド public static ImageIcon getSuitIcon( int suit ) { ImageIcon icon; // マークに応じた画像を読み込んでリターンする switch(suit) { case 0: // スペード icon = new ImageIcon("./src/game/highandlow/img/spade.jpg"); return icon; case 1: // ハート icon = new ImageIcon("./src/game/highandlow/img/heart.jpg"); return icon; case 2: // ダイヤ icon = new ImageIcon("./src/game/highandlow/img/diamond.jpg"); return icon; case 3: // クラブ icon = new ImageIcon("./src/game/highandlow/img/clover.jpg"); return icon; default: // マークが不正の場合 return null; } } // 数字に応じた表示文字列を取得するメソッド public static String getNoStr( int no ) { switch(no) { case 1: // エース return "A"; case 11: // ジャック return "J"; case 12: // クイーン return "Q"; case 13: // キング return "K"; default: // 上記以外は数字をそのまま文字列として出力する return String.valueOf(no); } } }
パッケージのインポート・表示クラスの定義
まず「import」から始まる箇所で、今回の画面作成に必要となるAWTとSwingのフレームワークを使えるようにしています。次の「public class Display implements ActionListener」という箇所が、表示クラスの定義です。
import java.awt.Color; import java.awt.Font; import java.awt.Dimension; import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JLabel; import javax.swing.JButton; import javax.swing.ImageIcon; // 表示クラス public class Display implements ActionListener {
今回は「ActionListener」という機能を利用して、「ボタンが押された」というイベントを受けて画面更新できるようにしました。ActionListenerにより画面上の操作をイベントとして受け取るには、このようにクラス名の直後に「implements ActionListener」と付ける必要があります。
クラス定義の冒頭には、表示クラスで管理する画面上の部品データを一通り定義しています。部品データの詳細はサンプルコード内のコメントで説明しているので、参考にしてくださいね。
// 画面全体のオブジェクト private JFrame disp; // パネルオブジェクト(上部/中央部/下部) private JPanel top_panel, mid_panel, bottom_Panel; // メッセージ表示ラベルオブジェクト private JLabel msg_lbl; // 親のカード情報ラベルオブジェクト(マーク、数字) private JLabel parent_lbl, parent_suit_lbl, parent_no_lbl; // 子のカード情報ラベルオブジェクト(マーク、数字) private JLabel child_lbl, child_suit_lbl, child_no_lbl; // ボタンオブジェクト(HIGH/LOW) private JButton btn_high, btn_low; // プレイヤーオブジェクト(親、子) private Player parent, child;
表示クラスの初期化処理
「public Display( Player prn, Player chl )」の箇所は表示クラスのコンストラクタ(初期化処理)です。この中で、初期状態の画面を作成していきます。
public Display( Player prn, Player chl ) {
「parent = prn;」「child = chl;」の箇所は、引数として受け取った親と子のカード情報を、表示クラスの内部データにコピーする処理です。この情報を基にして、画面表示を行っていきます。
// メインクラスから受け取った親と子のオブジェクトを設定 parent = prn; child = chl;
ゲーム画面に盛り込む部品は、それぞれ以下のクラスを使用することで生成、表示設定が可能です。
部品名 | クラス名 |
画面全体 | JFrame |
パネル | JPanel |
ラベル | JLabel |
ボタン | JButton |
「disp = new JFrame(“HIGH & LOW”);」の箇所は、ゲーム画面の作成や表示設定処理です。引数には「HIGH & LOW」のように、画面のタイトルを指定します。
// ゲーム画面全体の表示設定 disp = new JFrame("HIGH & LOW"); // 画面を生成 disp.setSize(480, 280); // 表示サイズを設定 disp.setLocationRelativeTo(null); // 画面の表示位置を中央に設定 disp.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 「×」ボタンで画面を閉じるように設定 disp.setResizable(false); // 画面サイズを変更できないように設定
「top_panel = new JPanel();」の箇所は、画面上部に表示するトップパネルの作成や表示設定処理です。「setPanel」というメソッドはパネルの表示設定処理をまとめたものですが、定義はファイルの終盤で行っています。
// トップパネルの表示設定 top_panel = new JPanel(); // パネルを生成 setPanel(top_panel, Color.ORANGE, null, new Dimension(480, 50) ); // パネルの背景色、レイアウト、サイズを設定 disp.add( top_panel, BorderLayout.NORTH ); // 画面上部にパネルを追加
「msg_lbl = new JLabel」で始まる箇所は、トップパネルに追加するメッセージラベルの作成や表示設定処理です。「setLabelFont」というメソッドはラベルのフォント設定処理をまとめたもので、こちらも最後の方で定義しています。
// メッセージラベルを表示 msg_lbl = new JLabel("一発勝負!HIGHかLOWか当ててください。"); // ラベルを生成 top_panel.add(msg_lbl); // トップパネルに追加 setLabelFont(msg_lbl, Color.BLACK, 0, 15, 480, 20, 20, false); // ラベルのフォント設定
「mid_panel = new JPanel();」の箇所は、画面中央部に表示するミドルパネルの作成や表示設定処理です。トップパネルと同様に、setPanelメソッドで表示設定を行い、addメソッドで画面上にパネルを追加しています。
// ミドルパネルの表示設定 mid_panel = new JPanel(); // パネルを生成 setPanel(mid_panel, Color.CYAN, null, new Dimension(480, 180) ); // パネルの背景色、レイアウト、サイズを設定 disp.add( mid_panel, BorderLayout.CENTER ); // 画面中央部にパネルを追加
「parent_lbl = new JLabel(“私のカード”);」の箇所は、親のカード情報に関連する3つのラベルを作成、表示設定する処理です。親のカードは表向きに表示するので、ラベルオブジェクト生成時にマークと数字を指定しています。
// 親カードの情報を表示 parent_lbl = new JLabel("私のカード"); parent_suit_lbl = new JLabel( getSuitIcon( parent.GetSuit() ) ); // マークを表示するラベル parent_no_lbl = new JLabel( getNoStr( parent.GetNo() ) ); // 数字を表示するラベル // ミドルパネルに追加 mid_panel.add(parent_lbl); mid_panel.add(parent_suit_lbl); mid_panel.add(parent_no_lbl); // ラベルのフォント設定 setLabelFont(parent_lbl, Color.WHITE, 90, 10, 100, 20, 14, false ); setLabelFont(parent_suit_lbl, Color.WHITE, 100, 10, 80, 100, 16, false ); setLabelFont(parent_no_lbl, Color.WHITE, 100, 35, 80, 100, 16, true );
画面上への追加や表示設定の方法は、トップパネルと同様です。親カードのマークや数字を取得するために、「getSuitIcon」「getNoStr」というメソッドをそれぞれ使用していますが、最後の方で定義しています。
「child_lbl = new JLabel(“あなたのカード”);」の箇所で、子のカード情報に関連する3つのラベルを作成、表示設定しています。子カードの表示はこの時点では裏向きですが、画面上への追加や表示設定方法は親カードと同様です。
// 子カードの情報を表示 child_lbl = new JLabel("あなたのカード"); child_suit_lbl = new JLabel(""); child_no_lbl = new JLabel("?"); // ミドルパネルに追加 mid_panel.add(child_lbl); mid_panel.add(child_suit_lbl); mid_panel.add(child_no_lbl); // ラベルのフォント設定 setLabelFont(child_lbl, Color.WHITE, 265, 10, 150, 20, 14, false ); setLabelFont(child_suit_lbl, Color.LIGHT_GRAY, 300, 10, 80, 100, 16, false ); setLabelFont(child_no_lbl, Color.LIGHT_GRAY, 300, 35, 80, 100, 16, true );
「bottom_Panel = new JPanel();」の箇所で、画面下部に表示するボトムパネルの作成や表示設定を行っています。作成や表示設定の方法はトップパネルと同様です。
// ボトムパネルの表示設定 bottom_Panel = new JPanel(); setPanel( bottom_Panel, Color.LIGHT_GRAY, new BorderLayout(), new Dimension(480, 50) ); // パネルの背景色、レイアウト、サイズを設定 disp.add( bottom_Panel, BorderLayout.SOUTH ); // 画面下部にパネルを追加
「btn_high = new JButton(“HIGH”);」の箇所は、ボトムパネルに表示する「HIGH」ボタンの作成や表示設定処理です。ボタンの表示設定は「setButton」というメソッドにまとめましたが、こちらも最後の方で定義しています。
// HIGHボタンを表示 btn_high = new JButton("HIGH"); setButton( btn_high, this, 240, 50, 20 ); // ボタンのフォントやイベント設定 bottom_Panel.add( btn_high, BorderLayout.WEST ); // ボトムパネル左側にボタンを追加
「btn_low = new JButton(“LOW”);」の箇所では、同じくボトムパネルに表示する「LOW」ボタンの作成や表示設定を行っています。作成や表示設定の方法はHIGHボタンと同様です。
// LOWボタンを表示 btn_low = new JButton("LOW"); setButton( btn_low, this, 240, 50, 20 ); // ボタンのフォントやイベント設定 bottom_Panel.add( btn_low, BorderLayout.EAST ); // ボトムパネル右側にボタンを追加
実はこの段階ではまだ、ゲーム画面はパソコン上に表示されていません。コンストラクタ最後の「disp.setVisible(true);」という箇所で、作成したゲーム画面をパソコン上に表示しています。
// ゲーム画面を表示 disp.setVisible(true);
表示クラスのイベント実行メソッド
次の「public void actionPerformed(ActionEvent e)」は、ボタンクリックなどのイベントが発生したときに実行されるactionPerformedメソッドです。クラスの定義に「implements ActionListener」と付けた場合、必ずこのメソッドを定義しなければなりません。
// HIGHかLOWが選択されたときのイベント public void actionPerformed(ActionEvent e) {
まず「String cmd = e.getActionCommand();」という箇所は、イベントの内容を取得する処理です。このデータを用いることで、HIGHボタンとLOWボタンのどちらが押されたか判断できます。
String cmd = e.getActionCommand(); // アクションコマンド(どのボタンが押されたか)
次の3行は、プレイヤークラスのGetNoメソッドやGetSuitメソッドを使用して、親や子のカード情報を取得する処理です。そして「child_no_lbl.setBackground(Color.WHITE);」からの4行は子のカードをオープンする処理で、子カード関連のラベルについて設定変更しています。
int parent_no = parent.GetNo(); // 親カードの数字 int child_no = child.GetNo(); // 子カードの数字 int child_suit = child.GetSuit(); // 子カードのマーク // 子のカードをオープン child_no_lbl.setBackground(Color.WHITE); // 数字の背景色 child_no_lbl.setText( getNoStr( child.GetNo() ) ); // 数字の表示データ child_suit_lbl.setBackground(Color.WHITE); // マークの背景色 child_suit_lbl.setIcon( getSuitIcon( child_suit ) );// マークの表示データ
「if( cmd.equals(“HIGH”) )」の箇所は、HIGHボタンが押された場合の処理です。まず、setBackgroundメソッドを使用して、HIGHボタンの色を緑色に変更しています。
// 押されたボタンに応じた処理を行う if( cmd.equals("HIGH") ) // HIGHボタンが押された時の処理 { // ボタンの色を変える btn_high.setBackground(Color.GREEN);
その後親カードの数字と子カードの数字を比較して、比較結果に応じてsetTextメソッドによりトップパネルのメッセージを変更しています。「else if( cmd.equals(“LOW”) )」の箇所はLOWボタンが押された場合の処理ですが、HIGHボタンと要領は同じです。
// 結果を判定してメッセージ更新 if( parent_no < child_no ) // 子の方が大きい msg_lbl.setText("大正解、あなたの勝ちです!"); else if( child_no < parent_no ) // 親の方が大きい msg_lbl.setText("不正解、あなたの負けです!"); else // 親と子の数字が同じ msg_lbl.setText("奇遇ですね。引き分けです!");
パネルの表示設定メソッド
次の「public static void setPanel」という箇所は、パネルの表示設定をまとめて行うsetPanelメソッドの定義です。JPanelクラスの各メソッドを使用して、背景色・レイアウト・表示サイズをそれぞれ設定しています。
// パネルのフォント設定を行うメソッド public static void setPanel(JPanel panel, Color color, BorderLayout layout, Dimension dimension ) { panel.setBackground(color); // 背景色を設定 panel.setLayout(layout); // レイアウトを設定 panel.setPreferredSize(dimension); // 表示サイズを設定 return; }
ラベルの表示設定メソッド
次の「public static void setLabelFont」という箇所は、ラベルのフォント設定をまとめて行うsetLabelFontメソッドの定義です。JLabelクラスの各メソッドを使用して、背景色・表示位置・表示サイズ・書式・文字位置・透明性をそれぞれ設定しています。
// ラベルのフォント設定を行うメソッド public static void setLabelFont(JLabel label,Color clr, int x_pos,int y_pos, int x_size,int y_size, int strSize,boolean opq ) { label.setBackground(clr); // 背景色を設定 label.setLocation(x_pos, y_pos); // 表示位置を設定 label.setSize(x_size, y_size); // 表示サイズを設定 label.setFont( new Font("MS ゴシック", Font.PLAIN, strSize) ); // 書式、文字サイズを設定 label.setHorizontalAlignment(JLabel.CENTER); // 水平方向中央揃え label.setVerticalAlignment(JLabel.CENTER); // 垂直方向中央揃え label.setOpaque(opq); // ラベルの透明性を設定(true=不透明、false=透明) return; }
ボタンの設定メソッド
次の「public static void setButton」という箇所は、ボタンの設定をまとめて行うsetButtonメソッドの定義です。addActionListenerメソッドを使用することで、ボタンが押された時にイベントを受け取れるようにしています。
// ボタンの設定を行うメソッド public static void setButton(JButton btn, ActionListener al, int x_size, int y_size, int strSize ) { btn.setPreferredSize(new Dimension(x_size, y_size)); // 表示サイズを設定 btn.setFont( new Font("MS ゴシック", Font.PLAIN, strSize) ); // 書式、文字サイズを設定 btn.addActionListener(al); // ボタンが押された時のイベントを受け取れるように設定 return; }
マークのアイコンを取得するメソッド
次の「public static ImageIcon getSuitIcon( int suit )」という箇所は、引数で指定したマークに応じたアイコンを取得するgetSuitIconメソッドの定義です。ImageIconというクラスを使用することで、以下のようにあらかじめ用意したマークの画像をロードしています。
// マークに応じたアイコンオブジェクトを取得するメソッド public static ImageIcon getSuitIcon( int suit ) { ImageIcon icon; // マークに応じた画像を読み込んでリターンする switch(suit) { case 0: // スペード icon = new ImageIcon("./src/game/highandlow/img/spade.jpg"); return icon; case 1: // ハート icon = new ImageIcon("./src/game/highandlow/img/heart.jpg"); return icon; case 2: // ダイヤ icon = new ImageIcon("./src/game/highandlow/img/diamond.jpg"); return icon; case 3: // クラブ icon = new ImageIcon("./src/game/highandlow/img/clover.jpg"); return icon; default: // マークが不正の場合 return null; } }
数字の表示文字列を取得するメソッド
最後の「public static String getNoStr( int no )」という箇所は、数字に応じた表示文字列を取得するgetNoStrメソッドの定義です。「A」のようなアルファベット表記に対応しているだけでなく、数字データを文字列に変換することでラベルに表示できる形式にしています。
// 数字に応じた表示文字列を取得するメソッド public static String getNoStr( int no ) { switch(no) { case 1: // エース return "A"; case 11: // ジャック return "J"; case 12: // クイーン return "Q"; case 13: // キング return "K"; default: // 上記以外は数字をそのまま文字列として出力する return String.valueOf(no); } }
ステップ7:完成品を動かしてみる
これで、すべてのプログラム作成が完了しました。実際にプログラムを実行してみましょう。
初期画面が想定通りに表示されました。LOWボタンを押してみます。
ボタンを押すと画面が正常に更新され、勝敗の結果が表示されました。繰り返しプレイできるようにするにはさらに複雑な改造が必要ですが、今回は1発勝負なのでこの辺りにしておきましょう。
まとめ
ここでは、Javaでゲームを開発するために必要な基礎知識や、サンプルコード付きで実際にゲームを開発する方法について解説しました。
- Javaでゲームを開発するために必要な知識
- Javaでゲームを開発するための方法
この記事の内容を実践してみることで、ゲーム開発の方法が一通り理解いただけたのではないでしょうか?Javaではさまざまなゲームを開発することが可能ですので、ぜひチャレンジしてみましょう。
もし独学が大変であれば、プログラミングスクールに通うのもおすすめです。
侍エンジニア塾では、満足度95%の講師がマンツーマンでプログラミング初心者の方にも分かりやすく教えてくれます。Javaのゲーム開発を習得したい方は、ぜひ無料カウンセリングを受けてみてくださいね。