ページ 1 / 1
Javaでクラスの考え方について
Posted: 2013年5月04日(土) 06:16
by 海Sea
いつもありがとうございます。
現在Java用ビデオゲームライブラリを使用して
シューティングゲームを構築しようとしています。
言語と環境は
Java:SE7u21 eclipse4.2
ライブラリ:JavaSE向けビデオゲームライブラリ
ISLeのビデオゲーム工房(カテゴリ:JavaSE)
► スポイラーを表示
コード:
package testgame;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import videogame.VGAudioClip;
import videogame.VGCanvas;
import videogame.VGFrame;
import videogame.image.VGImage;
import videogame.image.VGImageArray;
public class Hello extends VGCanvas
{
VGImage[] image;
VGImageArray image2;
VGAudioClip shoot;
int pad_states_old;
final static int window_with = 640;
final static int window_height = 480;
int jiki_x = 320;
int jiki_y = 400;
boolean key_left;
boolean key_right;
boolean key_no=false;
public Hello()
{
//Image img = getResourceImage("image/jiki.png");
Image img2 = getResourceImage("image/jiki_toumei.png");
image = new VGImage[3];
image[0] = new VGImage(img2,0,0,32,32);
image[1] = new VGImage(img2,0,64,32,32);
image[2] = new VGImage(img2,0,128,32,32);
image2 = new VGImageArray(image);
shoot = new VGAudioClip("image/metal30.wav");
}
protected void frameUpdate(int skipped)
{
//
// ゲームの内部状態を1フレーム分進行させる処理をここに書く
//
int pad_states = getPadStates();
int pad_trigger = (pad_states^pad_states_old) & pad_states;
pad_states_old = pad_states;
if ((pad_states & (1<<PAD_LEFT)) != 0&&jiki_x>=10)
{
key_left=true;
key_no=false;
key_right = false;
jiki_x-=2;
}
else if ((pad_states & (1<<PAD_RIGHT)) != 0&&jiki_x<=600)
{
key_left=false;
key_no=false;
key_right = true;
jiki_x+=2;
}
else if ((pad_states & (1<<PAD_UP)) != 0&&jiki_y>=10)
{
key_left=false;
key_no=true;
key_right = false;
jiki_y-=2;
}
else if ((pad_states & (1<<PAD_DOWN)) != 0&&jiki_y<=440)
{
key_left=false;
key_no=true;
key_right = false;
jiki_y+=2;
}
else
{
key_left=false;
key_right = false;
key_no = true;
}
if ((pad_trigger & (1<<PAD_BUTTON2)) != 0)
{
shoot.play();
}
}
protected void frameRender(Graphics g)
{
//
// ゲーム画面を描く処理をここに書く
//
g.setColor(Color.BLACK);
g.fillRect(0, 0, 640, 480);
if(key_no)
{
image2.paint(g,0,jiki_x,jiki_y, 0);
}
if(key_right)
{
image2.paint(g,1,jiki_x,jiki_y, 0);
}
if(key_left)
{
image2.paint(g,2,jiki_x,jiki_y, 0);
}
}
public static void main(String[] args)
{
// 640×480の画面サイズでウインドウに載せて起動する
new VGFrame(new Hello(), window_with,window_height, "ゲーム画面");
}
}
ゲーム用コードは現在この部分だけなのですが、
例えばショットクラスや自機クラスにわける場合
掲載したコード内の描画部分との関連付けを
どうすべきか、イメージがよくわかりません。
たとえば、
コード:
public class jiki
{
public jiki()
{
jiki_x = 320;//初期値X
jiki_y = 400;//初期表示Y
}
}
というものがあって、
そのクラスをインスタンス化した際に
描画するタイミングでjiki.jiki_xなどを代入する分け方で良いでしょうか。
ゲームプログラミングで描画と値をわけるという考え方で言うと
こういうことだと思うのですが。
アドバイス等ありましたら、よろしくお願いします。
Re: Javaでクラスの考え方について
Posted: 2013年5月05日(日) 00:25
by ISLe
このサイトのAndroidの館のイライラ棒アプリの作成講座が参考になると思います。
VGCanvasはイライラ棒アプリのGameMgrに相当します。
更新と描画のメソッドを持ったインターフェースを継承して、ゲームオブジェクト(タスク)のクラスを作ります。
構築したタスクをリストに登録しておいて、VGCanvas#frameUpdateで各タスクの更新メソッドを呼び出し、VGCanvas#frameRenderで各タスクの描画メソッドを呼び出します。
あと、わたしが言うのもなんですけど、回答者が検証できるように、使用しているライブラリの所在を書いておくと良いと思います。
Re: Javaでクラスの考え方について
Posted: 2013年5月05日(日) 02:37
by 海Sea
ISLe さんが書きました:このサイトのAndroidの館のイライラ棒アプリの作成講座が参考になると思います。
VGCanvasはイライラ棒アプリのGameMgrに相当します。
更新と描画のメソッドを持ったインターフェースを継承して、ゲームオブジェクト(タスク)のクラスを作ります。
構築したタスクをリストに登録しておいて、VGCanvas#frameUpdateで各タスクの更新メソッドを呼び出し、VGCanvas#frameRenderで各タスクの描画メソッドを呼び出します。
あと、わたしが言うのもなんですけど、回答者が検証できるように、使用しているライブラリの所在を書いておくと良いと思います。
構築の仕方は、そちらを参考に手をつけて見ます。
何気に時間がかかりそうですが・・・
あと、ご指摘のとおり、トピックを修正して、
ライブラリの所在を明記しておきました!
Re: Javaでクラスの考え方について
Posted: 2013年5月05日(日) 06:45
by 海Sea
Androidの館を参考に組んで見ました。
ISLeさんのアドバイスではupdate()メソッドなどをもったインターフェースを継承するとあったのですが、
自分でわかりやすいよう、Androidの館同様にタスククラスは抽象クラスとして継承しています。
しかし、自機は初期位置で描画されるのですが、
キーを押しても全く自機が動かず困っています。
コード量が多いかもしれませんが、指摘等ありましたらよろしくお願いします。
環境などはトピックの一つめに記載してあります。
タスククラス
► スポイラーを表示
コード:
package testgame;
import java.awt.Graphics;
public abstract class InTask
{
public abstract void onUpadate();
public abstract void onDraw(Graphics g);
}
ゲームのPadクラス(キーの押下を担うクラス・ライブラリからの流用)
► スポイラーを表示
コード:
package testgame;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class Pad implements KeyListener
{
private int player_x=2,player_y=2,rePlayer_x=-2,rePlayer_y=-2;
//-----------------------------------------------------------
// ゲームパッドをシミュレート
//-----------------------------------------------------------
/**
* 左方向ボタンのビット位置を示す値
* @see #getPadStates
*/
public static final int PAD_LEFT = 0;
/**
* 右方向ボタンのビット位置を示す値
* @see #getPadStates
*/
public static final int PAD_RIGHT = 1;
/**
* 上方向ボタンのビット位置を示す値
* @see #getPadStates
*/
public static final int PAD_UP = 2;
/**
* 下方向ボタンのビット位置を示す値
* @see #getPadStates
*/
public static final int PAD_DOWN = 3;
/**
* ボタン1のビット位置を示す値
* @see #getPadStates
*/
public static final int PAD_BUTTON1 = 4;//Z
/**
* ボタン2のビット位置を示す値
* @see #getPadStates
*/
public static final int PAD_BUTTON2 = 5;//X
/**
* ボタン3のビット位置を示す値
* @see #getPadStates
*/
public static final int PAD_BUTTON3 = 6;//C
/**
* ボタン4のビット位置を示す値
* @see #getPadStates
*/
public static final int PAD_BUTTON4 = 7;//A
/**
* ボタン5のビット位置を示す値
* @see #getPadStates
*/
public static final int PAD_BUTTON5 = 8;//S
/**
* ボタン6のビット位置を示す値
* @see #getPadStates
*/
public static final int PAD_BUTTON6 = 9;//D
/**
* ボタン7のビット位置を示す値
* @see #getPadStates
*/
public static final int PAD_BUTTON7 = 10;//Q
/**
* ボタン8のビット位置を示す値
* @see #getPadStates
*/
public static final int PAD_BUTTON8 = 11;//W
/**
* ボタン9のビット位置を示す値
* @see #getPadStates
*/
public static final int PAD_BUTTON9 = 12;//E
/**
* ゲームパッドのボタンの押下状態
* @see #getPadStates
*/
private int m_padstates = 0;
/**
* ゲームパッドのボタンの押下状態を返します。
*
* @return ゲームパッドのボタンの押下状態
*/
public final int getPadStates()
{
return m_padstates;
}
private int[] vkeymap = {
KeyEvent.VK_LEFT,
KeyEvent.VK_RIGHT,
KeyEvent.VK_UP,
KeyEvent.VK_DOWN,
KeyEvent.VK_Z,
KeyEvent.VK_X,
KeyEvent.VK_C,
KeyEvent.VK_A,
KeyEvent.VK_S,
KeyEvent.VK_D,
KeyEvent.VK_Q,
KeyEvent.VK_W,
KeyEvent.VK_E,
};
@Override
public void keyPressed(KeyEvent e)
{
int mask = 1;
for (int vkey : vkeymap) {
if (e.getKeyCode() == vkey) {
m_padstates |= mask;
}
mask <<= 1;
}
}
@Override
public void keyReleased(KeyEvent e)
{
int mask = 1;
for (int vkey : vkeymap) {
if (e.getKeyCode() == vkey) {
m_padstates &= ~mask;
}
mask <<= 1;
}
}
public int getX()
{
return player_x;
}
public int getY()
{
return player_y;
}
public int getReX()
{
return rePlayer_x;
}
public int getReY()
{
return rePlayer_y;
}
@Override
public void keyTyped(KeyEvent e) {}
/*private static Pad _pad = new Pad();
private Pad()
{
player_x = 2;
player_y=2;
rePlayer_x=-2;
rePlayer_y=-2;
}
public static Pad Inst()
{
return _pad;
}*/
}
プレイヤー(自機)クラス
► スポイラーを表示
コード:
package testgame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.net.URL;
import videogame.image.VGImage;
import videogame.image.VGImageArray;
public class Player extends InTask
{
VGImage[] image;
VGImageArray image2;
Pad pad;
int jiki_x = 320;
int jiki_y = 400;
int pad_states_old;
boolean key_left;
boolean key_right;
boolean key_no;
public Player()
{
Image img2 = getResourceImage("image/jiki_toumei.png");
pad = new Pad();
image = new VGImage[3];
image[0] = new VGImage(img2,0,0,32,32);
image[1] = new VGImage(img2,0,64,32,32);
image[2] = new VGImage(img2,0,128,32,32);
image2 = new VGImageArray(image);
}
@Override
public void onUpadate()
{
int pad_states = pad.getPadStates();
int pad_trigger = (pad_states_old^pad_states) & pad_states;
pad_states_old = pad_states;
if ((pad_states & (1<<Pad.PAD_LEFT)) != 0&&jiki_x>=10)
{
key_left=true;
key_no=false;
key_right = false;
jiki_x=pad.getReX();
}
else if ((pad_states & (1<<Pad.PAD_RIGHT)) != 0&&jiki_x<=600)
{
key_left=false;
key_no=false;
key_right = true;
jiki_x=pad.getReY();
}
else if ((pad_states & (1<<Pad.PAD_UP)) != 0&&jiki_y>=10)
{
key_left=false;
key_no=true;
key_right = false;
jiki_y=pad.getReY();
}
else if ((pad_states & (1<<Pad.PAD_DOWN)) != 0&&jiki_y<=440)
{
key_left=false;
key_no=true;
key_right = false;
jiki_y=pad.getReY();
}
else
{
key_left=false;
key_right = false;
key_no = true;
}
}
@Override
public void onDraw(Graphics g)
{
if(key_no)
{
image2.paint(g,0,jiki_x,jiki_y, 0);
}
if(key_right)
{
image2.paint(g,1,jiki_x,jiki_y, 0);
}
if(key_left)
{
image2.paint(g,2,jiki_x,jiki_y, 0);
}
}
public final URL getResourceURL(String name)
{
return getClass().getClassLoader().getResource(name);
}
/**
* 指定された名前を持つ画像リソースを検索しイメージを返します。
*
* @param name
* - 画像リソースの名前
* @return 見付かったリソースから作成した<code>Image</code>
*/
public final Image getResourceImage(String name)
{
Image img = Toolkit.getDefaultToolkit().getImage(getResourceURL(name));
return img;
}
}
実際のゲーム画面を出すクラス
► スポイラーを表示
コード:
package testgame;
import java.awt.Color;
import java.awt.Graphics;
import java.util.LinkedList;
import videogame.VGAudioClip;
import videogame.VGCanvas;
import videogame.VGFrame;
public class Hello extends VGCanvas
{
VGAudioClip shoot;
int pad_states_old;
final static int window_with = 640;
final static int window_height = 480;
private LinkedList<InTask> _taskList = new LinkedList<InTask>();//タスクリスト
public Hello()
{
//Image img = getResourceImage("image/jiki.png");
_taskList.add( new Player() );
}
protected void frameUpdate(int skipped)
{
//
// ゲームの内部状態を1フレーム分進行させる処理をここに書く
//
_taskList.get(0).onUpadate();
}
protected void frameRender(Graphics g)
{
//
// ゲーム画面を描く処理をここに書く
//
g.setColor(Color.BLACK);
g.fillRect(0, 0, 640, 480);
_taskList.get(0).onDraw(g);
}
public static void main(String[] args)
{
// 640×480の画面サイズでウインドウに載せて起動する
new VGFrame(new Hello(), window_with,window_height, "ゲーム画面");
}
}
Re: Javaでクラスの考え方について
Posted: 2013年5月05日(日) 23:42
by ISLe
interfaceを使うとabstract(のタイプ量)を省略できますけど。
実装を含まないときはclassよりinterfaceをお勧めします。
classとinterfaceでは継承の仕方が違ってきますし。
キーを押しても反応しないのは、リスナーを定義して構築してますけど、登録していないからです。
定義したキーイベントのコールバックメソッドが呼ばれないのでとうぜん動きません。
リスナーはアクティブなコンポーネント(この場合はVGCanvas)に登録する必要があります。
キーボードにアクセスする機能を自機に持たせるとコンポーネントに依存することになるのでキー入力は独立させることをお勧めします。
いわゆるMVCアーキテクチャで言われるコントローラを分離するという考え方です。
自機モジュールがキー入力を見て移動を決めるのではなく、コントローラーの指示で自機が動くように実装します。
そうすることで、自機モジュールはそのままにリプレイなどキー入力以外の指示で自機を動かすことも可能になります。
自機の移動も変数ごとのゲッタ/セッタではなく、移動メソッドのような機能として意味のある振る舞いを持たせてX,Y座標をまとめて処理すべきです。
Re: Javaでクラスの考え方について
Posted: 2013年5月06日(月) 21:20
by 海Sea
ISLe さんが書きました:interfaceを使うとabstract(のタイプ量)を省略できますけど。
実装を含まないときはclassよりinterfaceをお勧めします。
classとinterfaceでは継承の仕方が違ってきますし。
キーを押しても反応しないのは、リスナーを定義して構築してますけど、登録していないからです。
定義したキーイベントのコールバックメソッドが呼ばれないのでとうぜん動きません。
これはよくわかりました。ありがとうございます!
ISLe さんが書きました:
リスナーはアクティブなコンポーネント(この場合はVGCanvas)に登録する必要があります。
キーボードにアクセスする機能を自機に持たせるとコンポーネントに依存することになるのでキー入力は独立させることをお勧めします。
いわゆるMVCアーキテクチャで言われるコントローラを分離するという考え方です。
自機モジュールがキー入力を見て移動を決めるのではなく、コントローラーの指示で自機が動くように実装します。
そうすることで、自機モジュールはそのままにリプレイなどキー入力以外の指示で自機を動かすことも可能になります。
自機の移動も変数ごとのゲッタ/セッタではなく、移動メソッドのような機能として意味のある振る舞いを持たせてX,Y座標をまとめて処理すべきです。
VGCanvasにリスナー登録とあるので、とりあえず先のPadクラスはやめてライブラリのまま使うことにしました。
そして、質問なのですが、
『自機モジュールがキー入力を見て移動を決めるのではなく、コントローラーの指示で自機が動くように実装します。』
ここがいまいちよくわかりません。
キー入力受付用のクラスを作ってそのクラスから自機クラスの座標移動を呼び出してみたのですが
そういうことでしょうか?
キー入力用クラス
コード:
package testgame;
import videogame.VGCanvas;
public class KeyTriger
{
int pad_states;
int pad_states_old;
int pad_trigger;
public void Key()
{
if((pad_states & (1<<VGCanvas.PAD_LEFT))!=0)//左ボタンが押されたら
{
Player.playercontrollerx();//自機移動用メソッド
}
}
}
MVCアーキテクチャはまだちゃんとやったことがないので、
これを機会に学習しようと思います。
Re: Javaでクラスの考え方について
Posted: 2013年5月06日(月) 23:20
by ISLe
例えばゲーム画面を90度回転するモードがあったら、左ボタンが押されたらX座標を更新するとは限りません。
コントローラーは、キーボード入力と自機のあいだに入ります。
まず設計として、コントローラーから入力される方向を定義します。
入力方向は4方向、8方向、アナログなど入力タイプに合わせて抽象化します。
キーボード入力モジュールから入力情報を得て移動方向に変換します。
自機モジュールに移動方向を指示します。
ここまでがコントローラーの仕事です。
そして自機は指示された移動方向から座標を更新します。
こうすることによって画面の座標系とゲーム座標系を分離でき、また、オブジェクト自身が移動量を決められます。
Re: Javaでクラスの考え方について
Posted: 2013年5月07日(火) 06:49
by 海Sea
ISLe さんが書きました:例えばゲーム画面を90度回転するモードがあったら、左ボタンが押されたらX座標を更新するとは限りません。
コントローラーは、キーボード入力と自機のあいだに入ります。
まず設計として、コントローラーから入力される方向を定義します。
入力方向は4方向、8方向、アナログなど入力タイプに合わせて抽象化します。
キーボード入力モジュールから入力情報を得て移動方向に変換します。
自機モジュールに移動方向を指示します。
ここまでがコントローラーの仕事です。
そして自機は指示された移動方向から座標を更新します。
こうすることによって画面の座標系とゲーム座標系を分離でき、また、オブジェクト自身が移動量を決められます。
ひとまず、形にしてみました。
抽象化等足りない部分はありますが、おそらくコントロールが間に来るというのは
こういうことかな?と思ったのですが・・・
・キー入力クラス
► スポイラーを表示
コード:
package testgame;
import videogame.VGCanvas;
public class KeyTriger
{
int pad_states;
int pad_states_old;
int pad_trigger;
public void gameKey()
{
if((pad_states & (1<<VGCanvas.PAD_LEFT))!=0)
{
Player.key_left=true;
}
else
{
Player.key_left=false;
}
}
}
・コントローラークラス
► スポイラーを表示
コード:
package testgame;
public class Controller
{
KeyTriger keytriger;
public Controller()
{
keytriger = new KeyTriger();
}
public void controllerKey(int padstates)
{
keytriger.pad_states = padstates;
keytriger.pad_states_old = keytriger.pad_states;
keytriger.pad_trigger = (keytriger.pad_states_old^keytriger.pad_states) & keytriger.pad_states;
switch(Hello.gamemode)
{
case TITLE:
break;
case GAME:
keytriger.gameKey();
break;
}
}
}
・プレイヤークラス
► スポイラーを表示
コード:
package testgame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.net.URL;
import videogame.image.VGImage;
import videogame.image.VGImageArray;
public class Player implements InTask
{
VGImage[] image;
VGImageArray image2;
//Pad pad;
int jiki_x = 320;
int jiki_y = 400;
//キー入力フラグ
static boolean key_left;
static boolean key_right;
static boolean key_no;
public Player()
{
Image img2 = getResourceImage("image/jiki_toumei.png");
image = new VGImage[3];
image[0] = new VGImage(img2,0,0,32,32);
image[1] = new VGImage(img2,0,64,32,32);
image[2] = new VGImage(img2,0,128,32,32);
image2 = new VGImageArray(image);
}
@Override
public void onUpadate()
{
if (key_left)
{
key_no=false;
key_right = false;
playercontrollerx();
}
else
{
key_left=false;
key_right = false;
key_no = true;
}
}
@Override
public void onDraw(Graphics g)
{
if(key_no)
{
image2.paint(g,0,jiki_x,jiki_y, 0);
}
if(key_right)
{
image2.paint(g,1,jiki_x,jiki_y, 0);
}
if(key_left)
{
image2.paint(g,2,jiki_x,jiki_y, 0);
}
}
public void playercontrollerx()
{
if(jiki_x>=10)
{
jiki_x -=2;
}
}
public final URL getResourceURL(String name)
{
return getClass().getClassLoader().getResource(name);
}
/**
* 指定された名前を持つ画像リソースを検索しイメージを返します。
*
* @param name
* - 画像リソースの名前
* @return 見付かったリソースから作成した<code>Image</code>
*/
public final Image getResourceImage(String name)
{
Image img = Toolkit.getDefaultToolkit().getImage(getResourceURL(name));
return img;
}
}
・ゲーム画面のクラス
► スポイラーを表示
コード:
package testgame;
import java.awt.Color;
import java.awt.Graphics;
import java.util.LinkedList;
import videogame.VGAudioClip;
import videogame.VGCanvas;
import videogame.VGFrame;
public class Hello extends VGCanvas
{
VGAudioClip shoot;
Controller key;
//遷移用列挙
public static enum gameMode
{
TITLE,GAME,END
}
public static gameMode gamemode;
final static int window_with = 640;
final static int window_height = 480;
private LinkedList<InTask> _taskList = new LinkedList<InTask>();//タスクリスト
public Hello()
{
key = new Controller();
gamemode = gameMode.GAME;//便宜上今はGAMEで初期化
_taskList.add( new Player() );
}
protected void frameUpdate(int skipped)
{
//
// ゲームの内部状態を1フレーム分進行させる処理をここに書く
//
switch(gamemode)
{
case TITLE:
break;
case GAME:
key.controllerKey(getPadStates());
_taskList.get(0).onUpadate();
break;
}
}
protected void frameRender(Graphics g)
{
//
// ゲーム画面を描く処理をここに書く
//
g.setColor(Color.BLACK);
g.fillRect(0, 0, 640, 480);
switch(gamemode)
{
case TITLE:
break;
case GAME:
_taskList.get(0).onDraw(g);
break;
}
}
public static void main(String[] args)
{
// 640×480の画面サイズでウインドウに載せて起動する
new VGFrame(new Hello(), window_with,window_height, "ゲーム画面");
}
}
Re: Javaでクラスの考え方について
Posted: 2013年5月07日(火) 23:27
by ISLe
キー入力やコントローラーは機能として独立していなければいけません。
キー入力クラスはコントローラークラスが必要とするキーの押下状態を返す機能を持つだけで良いです。
コントローラークラスの仕事は、入力によって発生するイベントを作ることです。
方向以外にも、決定、キャンセル、自機ショット、自機ボム、などもコントローラーが生成します。
例え自機ショットと決定が同じキーだったとしてもイベントとしては別になります。
イベントの発生によって指示を飛ばす方法はいくつかあります。
1.コントローラーの参照を持って、必要なときにコントローラーのメソッドを呼び出して発生しているイベントを見る方法。
2.リスナーのように、コントローラーに登録してコールバックを使う方法。
メニューの実装などで2.は便利ですが、1.に2.を後付けすることもできるので、とりあえず1.でやるというのでもかまわないと思います。
Re: Javaでクラスの考え方について
Posted: 2013年5月08日(水) 02:14
by 海Sea
ISLe さんが書きました:キー入力やコントローラーは機能として独立していなければいけません。
キー入力クラスはコントローラークラスが必要とするキーの押下状態を返す機能を持つだけで良いです。
コントローラークラスの仕事は、入力によって発生するイベントを作ることです。
方向以外にも、決定、キャンセル、自機ショット、自機ボム、などもコントローラーが生成します。
例え自機ショットと決定が同じキーだったとしてもイベントとしては別になります。
イベントの発生によって指示を飛ばす方法はいくつかあります。
1.コントローラーの参照を持って、必要なときにコントローラーのメソッドを呼び出して発生しているイベントを見る方法。
2.リスナーのように、コントローラーに登録してコールバックを使う方法。
メニューの実装などで2.は便利ですが、1.に2.を後付けすることもできるので、とりあえず1.でやるというのでもかまわないと思います。
何度もありがとうございます。
キーの入力とイベントの状態によって振る舞いが変わるということはわかるのですが、
設計や実装になると、難しいですね。もっとプログラミング力をつけないと駄目なことがよくわかります。
キー入力、コントローラーは独立した状態にできたと思うのですが、どうでしょうか・・・
イベントもアドバイスの1を目指してみました。
・キー入力クラス
► スポイラーを表示
コード:
package testgame;
import videogame.VGCanvas;
public class KeyTriger
{
static int pad_states;
static int pad_states_old;
static int pad_trigger;
static boolean left,right,up,down,no;
public static boolean gameKey()
{
if((pad_states & (1<<VGCanvas.PAD_LEFT))!=0)
{
return left=true;
}
else
{
left=false;
}
if(((pad_states & (1<<VGCanvas.PAD_RIGHT))!=0))
{
return right=true;
}
else
{
right=false;
}
if(((pad_states & (1<<VGCanvas.PAD_UP))!=0))
{
return up=true;
}
else
{
up=false;
}
if(((pad_states & (1<<VGCanvas.PAD_DOWN))!=0))
{
return down=true;
}
else
{
down=false;
}
return no;
}
}
・コントローラークラス
► スポイラーを表示
コード:
package testgame;
public class Controller
{
Event event;
boolean shootingMode,customMode;//仮に二つの状態(イベント?)がゲーム内に存在しているとして
public Controller()
{
event = new Event();
}
public void controllerKey(int padstates)
{
KeyTriger.pad_states = padstates;
KeyTriger.pad_states_old = KeyTriger.pad_states;
KeyTriger.pad_trigger = (KeyTriger.pad_states_old^KeyTriger.pad_states) & KeyTriger.pad_states;
switch(Hello.gamemode)
{
case TITLE:
break;
case GAME:
KeyTriger.gameKey();
if(shootingMode)
{
event.PlayerMove();
}
break;
default:
break;
}
}
public void changeShootingMode()
{
if(!shootingMode)
{
shootingMode=true;
}
else
{
shootingMode = false;
}
}
public boolean getShootingMode()
{
return shootingMode;
}
public boolean getCustomMode()
{
return customMode;
}
}
・イベントクラス(自機の移動メソッドの呼び出しなど)
► スポイラーを表示
コード:
package testgame;
public class Event
{
public void PlayerMove()//自機移動用メソッド
{
if(KeyTriger.up)
{
Player.key_up = true;
}
else
{
Player.key_up=false;
}
if(KeyTriger.down)
{
Player.key_down = true;
}
else
{
Player.key_down=false;
}
if(KeyTriger.left)
{
Player.key_left = true;
}
else
{
Player.key_left=false;
}
if(KeyTriger.right)
{
Player.key_right = true;
}
else
{
Player.key_right=false;
}
}
}
・ゲーム画面クラス(コントローラークラスはここでインスタンス化している)
► スポイラーを表示
コード:
package testgame;
import java.awt.Color;
import java.awt.Graphics;
import java.util.LinkedList;
import videogame.VGAudioClip;
import videogame.VGCanvas;
import videogame.VGFrame;
public class Hello extends VGCanvas
{
VGAudioClip shoot;
Controller key;
//遷移用列挙
public static enum gameMode
{
TITLE,GAME,END
}
public static gameMode gamemode;
final static int window_with = 640;
final static int window_height = 480;
private LinkedList<InTask> _taskList = new LinkedList<InTask>();//タスクリスト
public Hello()
{
key = new Controller();
gamemode = gameMode.GAME;//便宜上今はGAMEで初期化
_taskList.add( new Player() );
}
protected void frameUpdate(int skipped)
{
//
// ゲームの内部状態を1フレーム分進行させる処理をここに書く
//
switch(gamemode)
{
case TITLE:
break;
case GAME:
if(!key.getCustomMode()&&!key.getShootingMode())
{
key.changeShootingMode();
}
key.controllerKey(getPadStates());
if(key.getShootingMode())
{
_taskList.get(0).onUpadate();
}
break;
default:
break;
}
}
protected void frameRender(Graphics g)
{
//
// ゲーム画面を描く処理をここに書く
//
g.setColor(Color.BLACK);
g.fillRect(0, 0, 640, 480);
switch(gamemode)
{
case TITLE:
break;
case GAME:
if(key.getShootingMode())
{
_taskList.get(0).onDraw(g);
}
break;
default:
break;
}
}
public static void main(String[] args)
{
// 640×480の画面サイズでウインドウに載せて起動する
new VGFrame(new Hello(), window_with,window_height, "ゲーム画面");
}
}
Re: Javaでクラスの考え方について
Posted: 2013年5月08日(水) 16:50
by ISLe
うーん、どんどん違う方向に進んでいる気がします。
キー入力クラスもコントローラークラスもゲーム本体とは切り離す必要があります。
要するにゲーム要素を一切含んではいけません。
簡単なサンプルを用意しますのでしばしお待ちください。
Re: Javaでクラスの考え方について
Posted: 2013年5月08日(水) 18:19
by ISLe
キー入力クラスとコントローラークラスの役割分担に重点を置いてサンプルコードを書いてみました。
とりあえずシンプルに、コントローラーのメソッドと定義を使って情報を読み取る形にしました。
► スポイラーを表示
コード:
import java.awt.*;
import java.awt.event.*;
import java.util.LinkedList;
import isle.videogame.*;
interface Task
{
public void Update();
public void Render(Graphics g);
}
class KeyInput implements Task, KeyListener
{
private int[] vkeymap;
private int keystates;
public KeyInput(int[] vkeymap) {
this.vkeymap = vkeymap;
}
public int getKeyStates() {
return keystates;
}
@Override
public void Update() {
}
@Override
public void Render(Graphics g) {
}
@Override
public void keyPressed(KeyEvent e) {
int mask = 1;
for (int vkey : vkeymap) {
if (e.getKeyCode() == vkey) {
keystates |= mask;
}
mask <<= 1;
}
}
@Override
public void keyReleased(KeyEvent e) {
int mask = 1;
for (int vkey : vkeymap) {
if (e.getKeyCode() == vkey) {
keystates &= ~mask;
}
mask <<= 1;
}
}
@Override
public void keyTyped(KeyEvent e) {}
}
class Controller implements Task, KeyListener
{
public static final int RIGHT = (1<<0);
public static final int UP = (1<<1);
public static final int LEFT = (1<<2);
public static final int DOWN = (1<<3);
private int padstates;
private KeyInput[] keyInput = new KeyInput[2];
private boolean turnLeft = false;
public Controller() {
keyInput[0] = new KeyInput(new int[]{
KeyEvent.VK_RIGHT,
KeyEvent.VK_UP,
KeyEvent.VK_LEFT,
KeyEvent.VK_DOWN
});
keyInput[1] = new KeyInput(new int[]{
KeyEvent.VK_D,
KeyEvent.VK_W,
KeyEvent.VK_A,
KeyEvent.VK_S
});
}
public int getPadStates() {
return padstates;
}
@Override
public void Update() {
for (KeyInput ki : keyInput) ki.Update();
padstates = 0;
for (KeyInput ki : keyInput) padstates |= ki.getKeyStates();
if (turnLeft) {
int sb = (padstates & 1) << (4-1);
padstates = (padstates >>> 1) | sb;
}
}
@Override
public void Render(Graphics g) {
for (KeyInput ki : keyInput) ki.Render(g);
}
@Override
public void keyPressed(KeyEvent e) {
for (KeyInput ki : keyInput) ki.keyPressed(e);
if (e.getKeyCode() == KeyEvent.VK_Z) {
turnLeft = !turnLeft;
}
}
@Override
public void keyReleased(KeyEvent e) {
for (KeyInput ki : keyInput) ki.keyReleased(e);
}
@Override
public void keyTyped(KeyEvent e) {
for (KeyInput ki : keyInput) ki.keyTyped(e);
}
}
class Player implements Task
{
private int pos_x = 640/2;
private int pos_y = 480/2;
private Controller ctrlr;
public Player(Controller ctrlr) {
this.ctrlr = ctrlr;
}
@Override
public void Update() {
int padstates = ctrlr.getPadStates();
if ((padstates & Controller.LEFT) != 0) pos_x--;
if ((padstates & Controller.RIGHT) != 0) pos_x++;
if ((padstates & Controller.UP) != 0) pos_y--;
if ((padstates & Controller.DOWN) != 0) pos_y++;
}
@Override
public void Render(Graphics g) {
g.setColor(Color.CYAN);
int[] ptx = { pos_x+20, pos_x-20, pos_x-20 };
int[] pty = { pos_y+ 0, pos_y-10, pos_y+10 };
g.fillPolygon(ptx, pty, 3);
}
}
public class Sample extends VGCanvas
{
private LinkedList<Task> listTask = new LinkedList<Task>();
private Controller ctrlr;
public Sample()
{
ctrlr = new Controller();
addKeyListener(ctrlr);
listTask.add(ctrlr);
listTask.add(new Player(ctrlr));
}
protected void frameUpdate(int skipped)
{
for (Task task : listTask) {
task.Update();
}
}
protected void frameRender(Graphics g)
{
g.setColor(Color.BLACK);
g.fillRect(0, 0, 640, 480);
for (Task task : listTask) {
task.Render(g);
}
}
public static void main(String[] args)
{
new VGFrame(new Sample(), 640, 480, "サンプル");
}
}
キー入力クラス(KeyInput)は、仮想キーコードの配列を与え、各キーの押下状態をビットに反映したint値を取得します。
サンプルではキー入力クラスのインスタンスを2つ作り、カーソルキー以外にAWSDキーでも操作できるようにしています。
仮想キーコードの配列を書き換えたり配列ごと差し替えることでキーコンフィグを実装できます。
コントローラークラス(Controller)は、キー入力クラスから取得した情報をゲーム本体で使う情報に変換します。
サンプルではコントローラーの定義とキー配置が合致するのでそのまま論理和を取って使っています。
サンプルには含めませんでしたが、反対方向同時押しを消去するなど情報の正規化をここで行います。
Zキーを押すと、モニタを左に回転した場合の操作体系に変わります。
コントローラー内部で完結しているのでゲーム本体のプログラムには影響を与えません。
もう一度Zキーを押すと元に戻ります。
Re: Javaでクラスの考え方について
Posted: 2013年5月09日(木) 01:24
by 海Sea
わざわざサンプルを書いて頂いてありがとうございます。
これをもとに実装を施してみます!
キー入力クラスやコントロールクラスにも
Taskインターフェースを実装する考えに至りませんでした。
どちらかというと前のPadクラスのようにわけるほうが
ISLeさんのサンプルにまだ近いですね(^^;
ある程度構築のイメージができましたので、
長々と続いてしまいましたが
ひとまず解決ということにさせて頂きます。
本当にありがとうございます。