テキストアドベンチャーゲームのコード(The source code of the text adventure game)
ものすごく久しぶりの投稿です。
3Dダンジョンゲームに関してではないのですが、その前に作ったテキストアドベンチャーゲームについて、全体のコードを教えてくれないかという連絡をいただいたので、こちらにコードを載せることにしました。
頼まれたのが外国の方のため、テキストも英語で書いたバージョンです。街に入れてもらうためにゴブリンを討伐する、というシンプルな内容ですが、一応最初から最後までプレイできるようになっています。
Here's the full source code for the text adventure game :-)
import java.util.Scanner;
public class Game {
Scanner myScanner = new Scanner(System.in);
Scanner enterScanner = new Scanner(System.in);
int playerHP;
String playerName;
String playerWeapon;
int choice;
int monsterHP;
int silverRing;
public static void main(String[] args) {
Game dublin;
dublin = new Game();
dublin.playerSetUp();
dublin.townGate();
}
public void playerSetUp(){
playerHP = 10;
monsterHP = 15;
playerWeapon = "Knife";
System.out.println("Your HP: "+ playerHP);
System.out.println("Your Weapon: "+ playerWeapon);
System.out.println("Please enter your name:");
playerName = myScanner.nextLine();
System.out.println("Hello " + playerName + ", let's start the game!");
}
public void townGate(){
System.out.println("\n------------------------------------------------------------------\n");
System.out.println("You are at the gate of the town.");
System.out.println("A guard is standing in front of you.");
System.out.println("");
System.out.println("What do you want to do?");
System.out.println("");
System.out.println("1: Talk to the guard");
System.out.println("2: Attack the guard");
System.out.println("3: Leave");
System.out.println("\n------------------------------------------------------------------\n");
choice = myScanner.nextInt();
if(choice==1){
if(silverRing==1){
ending();
}
else{
System.out.println("Guard: Hello there, stranger. So your name is " + playerName + "? \nSorry but we cannot let stranger enter our town.");
enterScanner.nextLine();
townGate();
}
}
else if(choice==2){
playerHP = playerHP-1;
System.out.println("Guard: Hey don't be stupid.\n\nThe guard hit you so hard and you gave up.\n(You receive 1 damage)\n");
System.out.println("Your HP: " + playerHP);
enterScanner.nextLine();
townGate();
}
else if(choice==3){
crossRoad();
}
else{
townGate();
}
}
public void crossRoad(){
System.out.println("\n------------------------------------------------------------------\n");
System.out.println("You are at a crossroad. If you go south, you will go back to the town.\n\n");
System.out.println("1: Go north");
System.out.println("2: Go east");
System.out.println("3: Go south");
System.out.println("4: Go west");
System.out.println("\n------------------------------------------------------------------\n");
choice = myScanner.nextInt();
if(choice==1){
north();
}
else if(choice==2){
east();
}
else if(choice==3){
townGate();
}
else if(choice==4){
west();
}
else{
crossRoad();
}
}
public void north(){
System.out.println("\n------------------------------------------------------------------\n");
System.out.println("There is a river. You drink the water and rest at the riverside.");
System.out.println("Your HP is recovered.");
playerHP = playerHP + 1;
System.out.println("Your HP: " + playerHP);
System.out.println("\n\n1: Go back to the crossroad");
System.out.println("\n------------------------------------------------------------------\n");
choice = myScanner.nextInt();
if(choice==1){
crossRoad();
}
else{
north();
}
}
public void east(){
System.out.println("\n------------------------------------------------------------------\n");
System.out.println("You walked into a forest and found a Long Sword!");
playerWeapon = "Long Sword";
System.out.println("Your Weapon: "+ playerWeapon);
System.out.println("\n\n1: Go back to the crossroad");
System.out.println("\n------------------------------------------------------------------\n");
choice = myScanner.nextInt();
if(choice==1){
crossRoad();
}
else{
north();
}
}
public void west(){
System.out.println("\n------------------------------------------------------------------\n");
System.out.println("You encounter a goblin!\n");
System.out.println("1: Fight");
System.out.println("2: Run");
System.out.println("\n------------------------------------------------------------------\n");
choice = myScanner.nextInt();
if(choice==1){
fight();
}
else if(choice==2){
crossRoad();
}
else{
west();
}
}
public void fight(){
System.out.println("\n------------------------------------------------------------------\n");
System.out.println("Your HP: "+ playerHP);
System.out.println("Monster HP: " + monsterHP);
System.out.println("\n1: Attack");
System.out.println("2: Run");
System.out.println("\n------------------------------------------------------------------\n");
choice = myScanner.nextInt();
if(choice==1){
attack();
}
else if(choice==2){
crossRoad();
}
else{
fight();
}
}
public void attack(){
int playerDamage =0;
if(playerWeapon.equals("Knife")){
playerDamage = new java.util.Random().nextInt(5);
}
else if(playerWeapon.equals("Long Sword")){
playerDamage = new java.util.Random().nextInt(8);
}
System.out.println("You attacked the monster and gave " + playerDamage + " damage!");
monsterHP = monsterHP - playerDamage;
System.out.println("Monster HP: " + monsterHP);
if(monsterHP<1){
win();
}
else if(monsterHP>0){
int monsterDamage =0;
monsterDamage = new java.util.Random().nextInt(4);
System.out.println("The monster attacked you and gave " + monsterDamage + " damage!");
playerHP = playerHP - monsterDamage;
System.out.println("Player HP: " + playerHP);
if(playerHP<1){
dead();
}
else if(playerHP>0){
fight();
}
}
}
public void dead(){
System.out.println("\n------------------------------------------------------------------\n");
System.out.println("You are dead!!!");
System.out.println("\n\nGAME OVER");
System.out.println("\n------------------------------------------------------------------\n");
}
public void win(){
System.out.println("\n------------------------------------------------------------------\n");
System.out.println("You killed the monster!");
System.out.println("The monster dropped a ring!");
System.out.println("You obtaind a silver ring!\n\n");
System.out.println("1: Go east");
System.out.println("\n------------------------------------------------------------------\n");
silverRing = 1;
choice = myScanner.nextInt();
if(choice==1){
crossRoad();
}
else{
win();
}
}
public void ending(){
System.out.println("\n------------------------------------------------------------------\n");
System.out.println("Guard: Oh you killed that goblin!?? Great!");
System.out.println("Guard: It seems you are a trustworthy guy. Welcome to our town!");
System.out.println("\n\n THE END ");
System.out.println("\n------------------------------------------------------------------\n");
}
}
3DダンジョンRPGの現状 その2
今回このエントリーを書こうと思ってふと思ったこと。
「3DダンジョンRPG」って書いているけど、はたしてこれは「3D」なのか?
ポリゴンを使っているわけでもないし、考えてみれば3Dでもなんでもないじゃないか、ということに気づいたのでした。でも僕が子供の頃は、こういういわゆる一人称視点のゲームを3Dと呼んでいたのです。
自分はまだ当時の認識のもとに生きているのだな、と思ったりしました。
ゲーム制作の近況ですが、最近はプログラミングを学習する本来の目的となったApp制作のほうが軌道に乗り出したため、またやや放置状態ではあります。とはいえ、ゲームのシステム自体はほぼ出来上がっているので、あとは内容を足していくだけとも言えるのですが。
ゲームシステムとしては、典型的なダンジョンRPGです。
以前にも載せたタイトル画面。
ゲームは街からスタート。ウィズが好きなひとはわかる。酒場に宿屋に商店に神殿…「まんま」な感じです。
酒場ではそこにいる人物と会話できるようにしました。
アイテムを買うとインベントリに入る。そして使用するとまたインベントリからそれが消える。このあたりのプログラミングはかなり苦戦しましたが、Array という仕組みを使ってなんとか機能するように書き上げることができました。
ステータス画面はこんな感じ。基本的に画面の操作は右下にあるコマンドを選択しながら行う感じです。
道具屋で購入した薬草がインベントリに入っている。このほか装備の切り替えなど、アイテムまわりは一番苦労したかもしれません。
神殿では祈ることができます。ウィズでは経験値をためて宿屋に泊まるとレベルアップでしたが、このゲームでは経験値がたまった状態で神殿に来て祈るとステータスが上昇するというシステムにしました。力の神、知恵の神、体力の神とおり、自分が高めたい能力を持つ神を選んで祈ることができます。
そして冒険の舞台となるダンジョン。
右下に表示されている十字キーのようなボタンを使って移動します。
画像は Photoshop を使って作成し、それを座標や向いている方向が変わるたびに一枚一枚呼び出しているという原始的な形です。なのでかなりの量の画像を作成しました。
そして戦闘も。
ターン制で、近接攻撃、魔法、道具などを駆使して戦い、勝つと経験値とゴールド(ときにはアイテム)が手に入ります。
しかし…
見てわかるように課題は絵です…
自分は絵がろくに描けないので、とりあえず仮でいいやとバンバン描いて入れたのですが、(さすがにこのクオリティはマズイだろうと思うし)ここだけはどうするべきか方針がさだまっていないところです。
あまりに描けないのでWiz5の攻略本を引っ張り出して、末弥純さんの絵を模写してみたり。でも下手すぎてパクりにすらなれてないような。
モンスターをどうするか、今後の課題です。
ダンジョンは地下7階まであるという設定で、マップはすべてできているので、あとはイベントなどを入れていくだけなのですが、Appのほうに時間を使ったりしてなかなか停滞中です。ある意味完成が見えてしまったことも、こちらの制作が鈍っている原因かもしれません。
このゲームの制作目的は Java を学ぶことで、ここで学んだ知識は実際今 App を作る中で大いに活きていると感じています。というかこれを作ってなかったらやれてなかったと思います。
そういう意味ではたとえ完成させなくとも本来の目的は達成したともいえるのですが、やはりここまで作ったので思い入れもあるし、どうにか最後まで仕上げられたらよいなと思っています。
プログラミングの勉強を始めて約7ヶ月。頭が爆発しそうなことも何度もありましたが、よくここまで来たもんだと思います。
まだまだ学ぶべきことはたくさんあるのですが。
3DダンジョンRPGの現状
この夏は本業の仕事が忙しく、ブログを更新する余裕がない日々が続いていたのですが、Javaでコツコツ制作中の3DダンジョンRPG、現状はこんな感じのところまできています。
グラフィックやステータス、テキストを表示させるウィンドウを作ったり、ダンジョンのグラフィックを描いたり…なんやかやと要素を足してどうにかこんなところまでたどり着きました。一応ダンジョン内を歩き回ることができ、イベントもちょっとは発生したり(まだちょっとしかありませんが)、途中までプレイできる状態にはなっています。
ようやく仕事がひと段落して時間ができそうなので、そのあたりのプロセスなどをちょっとずつ書きながら、さらに制作を進めていけたらいいなと思っています。
3DダンジョンRPGを作る 2
<タイトル画面を作成>
ウィンドウができたので、まずタイトル画面を作ってみました。
ウィンドウを作るのには「JFrame」というものを使いましたが、それに加え、
・Container
・JPanel
・JLabel
・Jbutton
というものをこのタイトル画面を作成するのに使用しました。
まず「RAINDIA」というでっかいタイトル文字ですが、これを「JLabel」というものを使って書いています。JLabel というのはテキストを表示する領域のようなもので、作成にはこのような書き方をしています:
JLabel titleL = new JLabel("RAINDIA");
「titleL」とは任意につけた JLabel の名前で、カッコの中にある「RAINDIA」というのが画面に表示されているテキストです。
一方その下にある「Start」と「Continue」は「JButton」というものを使って書いています。
なぜこっちは JLabel ではなく JButton かというと、JButton というのは名前の通りボタン的な機能を持つもので、押すことでなんらかのアクションが発生するように、いろんな機能を付与することができるからです。
「Start」と「Continue」は押すことでゲームが始まったり、データを読み込んだりする必要があるので「JLabel」ではなく「JButton」を使いました。
次に作成した JLabel と JButton をウィンドウ上のどこに配置するかを設定する必要があるのですが、この位置を決めるのに使用しているのが「JPanel」です。
JPanel というのはテキストやボタンを載せる土台のようなもの(?)で、たとえば
JPanel titleP1 = new JPanel();
titleP1.setBounds(100,300,1000,200);
といった感じで配置する座標を指定します。
カッコ内の数字は左から(X座標、Y座標、横の長さ、縦の長さ)
という意味になるようです。
この場合は「X座標100、Y座標300の地点から横に1000ピクセル、縦に200ピクセルの広さのパネル(titileP1)を置く」といった感じです。
このパネルの上にさっき作成した「RAINDIA」という文字の書かれた JLabel(titleL)を置きます。コードはこんな感じです。
titleP1.add(titleL);
これでパネル「titleP1」の上に「titleL」が載りました。
同様に「Start」と「Continue」を書いた「JButton」に関してもパネルを作成し、その上に追加します。
JPanel titleP2 = new JPanel();
titleP2.setBounds(485, 600, 200, 120);
で、パネルを作成し、
titleP2.add(titleB1);
で、その上にボタンを追加
これで完了…かと思いきやそうではなく、最後の仕上げとしてこの2つのパネルを「Container」というものの上に載せる必要があります。Container というのはすべてのコンテンツを載せるさらなる土台といった感じでしょうか。
Container の作成方法はパネルやラベルとは少し違って、このように書きます。
Container con = window.getContentPane();
「con」は Container に任意につけた名前、「window」は前回作成したウィンドウ(JFrame)に任意でつけた名前です。
そしてこの Container に2つのパネルを載せます。
con.add(titleP1);
con.add(titleP2);
これでようやく画面に「RAINDIA」、「Start」、「Continue」の3つの文字が表示されます。
階層としては
JFrame(ウィンドウ)
↓
Container
↓
JPanel
↓
JLabel または JButton
といった感じで上に重なっています。
あとはデザイン的な微調整となり、背景の色やボタンのデザイン、フォントの種類などの要素を追加していきます。
背景色は黒にし、フォントは「Times New Roman」を使用しました。
このタイトル画面の全体のプログラムはこんな感じです。
----------------------------------------------------------------------------------------------------------------------
import java.awt.*;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Raindia extends JFrame
{
JFrame window;
Font titlefont, basicfont;
Container con;
public static void main(String[ ]args)
{
Raindia game = new Raindia();
game.TitleScreen();
}
public void TitleScreen()
{
window = new JFrame();
window.setBounds(0,0,1200,950);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.getContentPane().setBackground(Color.black);
window.setLayout(null); //Disabling the default layout.
window.setVisible(true);
//FONT Setting
titlefont = new Font("Times New Roman", Font.PLAIN, 146);
basicfont = new Font("Times New Roman", Font.PLAIN, 34);
//TITLE Panel
JPanel titleP1 = new JPanel();
titleP1.setBounds(100,300,1000,200);
titleP1.setBackground(Color.black);
titleP1.setForeground(Color.white);
JPanel titleP2 = new JPanel();
titleP2.setBounds(485, 600, 200, 120);
titleP2.setBackground(Color.black);
titleP2.setForeground(Color.white);
titleP2.setLayout(new GridLayout(2,1));
JPanel titleP3 = new JPanel();
titleP3.setBounds(100, 800, 980, 50);
titleP3.setBackground(Color.black);
titleP3.setForeground(Color.white);
//TITLE Labe
JLabel titleL = new JLabel("RAINDIA");
titleL.setFont(titlefont);
titleL.setForeground(Color.white);
//TITLE Button
JButton titleB1 = new JButton("Start");
titleB1.setBackground(Color.black);
titleB1.setForeground(Color.white);
titleB1.setBorder(null);
titleB1.setFont(basicfont);
titleB1.setFocusPainted(false);
JButton titleB2 = new JButton("Continue");
titleB2.setBackground(Color.black);
titleB2.setForeground(Color.white);
titleB2.setBorder(null);
titleB2.setFont(basicfont);
titleB2.setFocusPainted(false);
//ADD
titleP1.add(titleL);
titleP2.add(titleB1);
titleP2.add(titleB2);
titleP3.add(titleM);
Container con = window.getContentPane();
con.add(titleP1);
con.add(titleP2);
con.add(titleP3);
}
}
----------------------------------------------------------------------------------------------------------------------
・このタイトル画面以外の場所でも使用することが予想される「JFrame(window)」、「Font(titlefontとbasicfont)」、「Container(con)」に関しては「TitleScreen」の中ではなくプログラム冒頭で宣言することにしました。こうすることで「TitleScreen」メソッドの外でもこれらを呼び出して使えるようになります。
・「setBackground(Color.black)」で背景色を黒に、「setForeground(Color.white)」でフォントの色を白に指定しています。
・「JButton」はデフォルトで使用すると四角い縁が表示されるのですが、それらを消してテキストだけを表示するため「titleB1.setBorder(null)」という形で書いています。
3DダンジョンRPGを作る
テキストだけのゲームブック的なゲームは一応作ることができたので、今度はグラフィックなども使ったものに挑戦してみることにしました。
とりあえず目標は「ウィザードリィみたいな3DダンジョンRPGを作る」ことです。
Wizは今までやった中で一番思い入れのあるゲームなので、この決定はわりと自然にされました。自分はWizみたいなシンプルなシステムで今でも十分楽しめるので、Wizの続編みたいなのを作るつもりでやれたらいいなあと。
またこれまでは書いたプログラムを「コマンド プロンプト(ウィンドウズに最初から入っている機能)」上で動作させていましたが、今度はそうではなく、ゲーム用のウィンドウやUIを作成してその中でゲームを展開するようにしたい、と思いました。
グラフィックを使う場合はやはりレイアウトなども考えなくてはならないので、やはりゲーム用のインターフェースというものが必要になります。
ということで最初の課題は「ウィンドウを作成する」ということ。
どうやったらいいのか、いろいろ調べた結果、
Javaの場合「JFrame」というものを使えば作れることがわかりました。
(作成したウィンドウ)
とりあえずこんな感じでウィンドウはできました。
あとはこれにボタンやグラフィックやテキストの表示する場所を設定していくことになります。
ウィンドウを作るために書いたプログラムはこんな感じです。
これを実行すると上の写真のようにウィンドウが出現します。
----------------------------------------------------------------------------------------------------------------------
import java.awt.*;
import javax.swing.JFrame;
public class Raindia extends JFrame
{
JFrame window;
public static void main(String[ ]args)
{
Raindia game = new Raindia();
game.start();
}
public void start()
{
window = new JFrame("ゲームのウィンドウ");
window.setBounds(0,0,1200,950);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.getContentPane().setBackground(Color.white);
window.setVisible(true);
}
}
---------------------------------------------------------------------------------------------------------------------
テキストアドベンチャーゲーム完成
Javaの勉強を始めたのが今年(2015年)の3月初め。
そしてほぼ1ヶ月後となる4月の初めにコマンドプロンプトで動くテキストアドベンチャーゲーム「Raindia」が完成しました。
(タイトル画面)
(最初の衛兵との会話。町に入れてもらうためにクエストを受けるという形に)
主人公は難民で、町に入ろうとするが門番に条件として賊の討伐を依頼される…というストーリー。
その場その場で思いつきで書いていったらなんかそんな話になった、という感じですが。当時戦争や難民関連の本を読んでいたので、その影響がそのまま出てます。
<主に使ったプログラミング技術>
・「String」、「If」構文、「Scanner」で選択肢やアイテムのあるなし、クエストのオン/オフを管理。
・「int」という整数を管理する器を使って所持している金貨の枚数を収納。
・java.util.Random() でランダムな数値を算出(ダイス代わり)
(金貨を手に入れると、所持している枚数が変化する)
(ゲームブックには定番の十字路)
賊を討伐して戻ってくるとクリア。
30分もあれば終わるのですが、テキスト選択肢だけと考えていたのに、やっている内にいろいろなことができることがわかってきて、アイテムを追加したりHPや攻撃力の要素を入れたりと、当初考えていたものよりシステム的に凝ったものになりました。
(賊との対面)
戦闘シーンも作りました。
「int」でHPを管理し、「java.util.Random()」という、ランダムな数字をはじき出す機能を使ってダメージを算出しています(ゲームブックにおけるダイスのようなものですね)。
自分の知識で戦闘が作れるとは思ってなかったので、ちょっと発想を変えれば少ない知識でもいろいろできるのだなあと思いました。
さらに「キャンプ」をすることで装備の変更や休息もできるように。
ウィザードリィが好きなので、やはりステータス画面と言えば「キャンプ」。
(装備が変更されると攻撃力も変化)
こんな感じで初めてのテキストアドベンチャーゲームはなんとか完成しました。
こんなこともできそうだ、これもできるのかも、と可能性が広がっていく行程は楽しかったです。
このブログに書いているプログラム
このブログは「初心者としての自分の知識の変化を記録する」という思いを出発点に書いています。
なのでプログラミング知識の豊富な方からすると恐ろしく効率が悪かったり、概念の捉え方が「そうじゃないんだよな」と思われることもあるかと思います。
自分の知識が深まるにつれてコードの書き方も変わってきており、今の自分から見ても思わず書き直したくなる場所などがあったりしますが、それはそれでその時の自分が試行錯誤の上にたどり着いた回答であるとして、敢えてそのまま残しています。なぜなら当時の自分にはそれ以上高度な書き方を要求されてもハードルが高くて投げ出していた可能性が高いからです。
たとえ恐ろしく効率は悪くとも、書いていることが楽しくて、理解できて、それがちゃんと動いてくれるのであれば、当時の自分にとってはそれがある意味でベストのプログラムだったのだ。そんな風に考えています。
自分はプログラミング言語の学習に何度も挫折してきたので、この「続けられる」ということが大きなテーマになっていたと言えるかもしれません。
プログラミング言語は「わかっている人間がいかに速く書けるか」という目的のもとに表記が進化・簡略化されてきた、という印象があります。そしてそれだけに「プログラミングがうまい人のコードほど、初心者には難解なものになる」という面もあるように感じました。
学習していて感じるのは、プログラムは「自分より一歩先にいるくらいの人の文がわかりやすくて役に立ち」、「うますぎる人の文は見ても結局使えない」ことが多いということです。
ひとつの段階を経ると、ひとつの疑問が出て、その疑問を解決するために次の段階に上る。効率の悪いやり方をたっぷり経験するから、効率よくできたときのありがたみがわかる。あまり先回りしすぎず、そうやって目の前の課題をひとつずつ自分の中で理解・消化し、少しずつ自分のコードを磨いていくやり方が、少なくとも自分には合っているようです。