すべては main.cpp の GamePlay() 関数のためだけに。
ダウンロード
http://hiyayakko.sarashi.com/SimpleSoccer/SC14.zip (2009.08.22)
どこから手をつけたっけ?
main.cpp
} else {
// ゴールキーパーは自分のエリアからある程度ボールが離れれば、自分の
ポジションに戻る
if((ball.getBx()>team[tm].KeepArea_Left())
&&(ball.getBx()<team[tm].KeepArea_Right())){
player[i].GoLocate(SUPPORT, ball.getKBx(), ball.getKBy());
} else {
player[i].GoHomePos();
}
/*
if(player[i].getTeam()==RED){
if(ball.getBx()<400)player[i].GoHomePos();
if(ball.getArea() == RedArea)
player[i].GoLocate(SUPPORT, ball.getKBx(), ball.getKBy());
} else {
if(ball.getBx()>200)player[i].GoHomePos();
if(ball.getArea() == BlueArea)
player[i].GoLocate(SUPPORT, ball.getKBx(), ball.getKBy());
}*/
}
この部分は、昨日のTeamクラスで追加した部分だけで変更が利く。
・ゴールキーパーは自分のキープエリア内にボールが入ってきたら
・ボール予想位置と守備円上の交点をキープする。
・自軍キープエリアから外れたらホームポジションに戻る
赤青まとめた上で定数の追い出しに成功している。ように見えるがどうか。
プレイヤークラスに、これから必要になりそうな蹴り返し判定の半径・蹴り返しのキック力・
シュート時のキック力・ボールへの反応速度を追加。
スタート座標(300,150)はあまり意味が無いので Init() 関数から追い出し。
player.h
// Player.h
#pragma once
extern Game game;
:
:
class Player
{
TeamColor Team; // どちらのチームに所属しているか
Position Pos; // ポジション
double Px,Py; // プレイヤー座標
// double Yaw; // プレイヤーの向き
PlayerStatus Status; // プレイヤーの状態
int HomeX,HomeY; // 守備位置(戻るべき座標)
int SetX, SetY; // 目標地点
int Dist; // 蹴り返す(足の届く)範囲
double KickPower; // 蹴力
double ShootPower; // シュート蹴力
double ReturnSpeed; // 蹴り返しに反応できるスピード
public:
void Init(TeamColor Team,Position Pos, int HomeX, int HomeY,
int Dist, double KickPower, double ShootPower, double ReturnSpeed);
int getPx(){return static_cast<int>(Px);}
int getPy(){return static_cast<int>(Py);}
int getPos(){return Pos;}
PlayerStatus getStatus(){return Status;}
void setStatus(PlayerStatus Status){this->Status = Status;}
TeamColor getTeam(){return Team;}
void GoLocate(PlayerStatus st, int LocX, int LocY);
void GoHomePos();
void Move();
int getDist(){return Dist;}
double getKickPower(){return KickPower;}
double getShootPower(){return ShootPower;}
double getReturnSpeed(){return ReturnSpeed;}
};
void Player::Init(TeamColor Team,Position Pos, int HomeX, int HomeY,
int Dist, double KickPower, double ShootPower, double ReturnSpeed)
{
this->Team = Team;
this->Pos = Pos;
this->Px = game.getCenterX();
this->Py = game.getCenterY();
this->HomeX = HomeX;
this->HomeY = HomeY;
this->Dist = Dist;
this->KickPower = KickPower;
this->ShootPower = ShootPower;
this->ReturnSpeed = ReturnSpeed;
GoLocate( GO_HOME,HomeX,HomeY);
}
Gameクラスの中央座標から取ればいいやと、軽い気持ちでGameクラスインスタンスを
連れ込んだけど、設計的にはかなりよくない。
座標(300,150)をどこから持ってくればいいか判断つかなかったんで、やむをえず
Gameクラスから持ってきてしまったけど、この1行、定数ひとつサボる目的のためだけで
PlayerクラスはGameクラス無しじゃ生きられないカラダになってしまったわけだ。
かといって、無意味に定数残しておくのも居心地悪いし。
やっぱ、今回の場合は元のとおり初期化時に全員同じ値でも与えておくべきだったか。
今は中央から散らばるような感じだけど、自軍ゴールから入場するようにとか変更利くし。
余談だけど、この extern class 宣言、ついこないだまでやり方わからなくて真剣に悩んでた。
何の疑問もなく書いて警告なくコンパイル通るんだから、自身のレベルアップを実感する。
なんやかやで若干の不満を残しつつ、メインに反映してみる。
main.cpp
int GameInit()
{
game.Init(600, 300);
team[BLUE].Init( 600, // EnemyGoalLine
400, 600, // AttackArea Left-Right
0, 200 ); // KeepArea Left-Right
team[ RED].Init( 0, // EnemyGoalLine
0, 200, // AttackArea Left-Right
400, 600 ); // KeepArea Left-Right
player[0].Init(BLUE, ATTACKER, 250, 50, 25, 4.0, 6.0, 2.0);
player[1].Init(BLUE, ATTACKER, 250, 250, 25, 4.0, 6.0, 2.0);
player[2].Init(BLUE, DEFENDER, 150, 50, 50, 5.0, 4.0, 3.0);
player[3].Init(BLUE, DEFENDER, 150, 250, 50, 5.0, 4.0, 3.0);
player[4].Init(BLUE, GOALKEEPER, 50, 150, 100, 7.0, 7.0,10.0);
player[5].Init( RED, ATTACKER, 350, 50, 25, 4.0, 6.0, 2.0);
player[6].Init( RED, ATTACKER, 350, 250, 25, 4.0, 6.0, 2.0);
player[7].Init( RED, DEFENDER, 450, 50, 50, 5.0, 4.0, 3.0);
player[8].Init( RED, DEFENDER, 450, 250, 50, 5.0, 4.0, 3.0);
player[9].Init( RED, GOALKEEPER, 550, 150, 100, 7.0, 7.0,10.0);
for(int i = 0; i<10;i++)player[i].GoHomePos();
ball.Init(300,150);
return 0;
}
アタッカー・ディフェンダーに数値で個性をつけてみた。
アタッカーはシュート力が強い。ディフェンダーは土壇場のシュート力は弱いが
通常の蹴り返しも蹴り返し範囲もボールスピード反応速度も優遇。
ゴールキーパーはさらにその上の性能だけど、ゴール付近から離れられない。
準備は整ったか。
この設定で使ってみる。
main.cpp
void GamePlay()
{
:
:
player[i].Move();
// ボールを蹴られる位置にいるか。
tm = player[i].getTeam();
dist = Distance(player[i], ball);
// ボールがプレイヤー[i]の足の届く範囲にあり、なおかつ
// ボールスピードがプレイヤーの反応速度より遅いとき、そのボールを
蹴り返すことができる。
if((dist < player[i].getDist())&&(ball.getSpd()<player[i].getReturnSpeed())){
// ボールがアタックエリア内、相手ゴールにじゅうぶん近いときはシュートパワーで
蹴り、
// 相手ゴールから離れているときは通常パワーで蹴り返す。
if((ball.getBx() > team[tm].AttackArea_Left())
&&(ball.getBx() < team[tm].AttackArea_Right()))
ball.Kick(team[tm].EnemyGoalLine(), GetRand(80)+110, player[i].getShootPower());
else ball.Kick(team[tm].EnemyGoalLine(), GetRand(300), player[i].getKickPower());
}
/*
if(((player[i].getPos()==GOALKEEPER)&&(dist<81))||(dist<25)){
// ゴールキーパーは距離81、一般プレイヤーは距離25以下で蹴り返すことができる
if((player[i].getPos()==GOALKEEPER)||(ball.getSpd()<2.0)){
// ボールの近くにいて、なおかつボールスピードが低いとき
// スピード判定は、重なってるプレイヤー同士でもみもみして後攻有利になるので。
if(player[i].getPos()==GOALKEEPER)
ball.Kick(team[tm].EnemyGoalLine(),GetRand(300),7.0);
else {
if((ball.getBx() > team[tm].AttackArea_Left())
&&(ball.getBx() < team[tm].AttackArea_Right()))
ball.Kick(team[tm].EnemyGoalLine(), GetRand(80)+110, 6.0);
else ball.Kick(team[tm].EnemyGoalLine(), GetRand(300),4.0);
}
}
}
*/
}
うむ。懸念してた、3連続(player[i].getPos()==GOALKEEPER) 判定が見事に消えた。
そのために色々なクラスに長ったらしいメンバ変数を用意してるんだけど
これを使う段階になったら、すごく長く感じる。
数字1個で済んでたのを、わざわざ player[i].getKickPower() なんかに置き換えてるから
長くなるのは当然の話なんだけど。
この長々とした1行が見づらいのは俺の感性が古いのかな?
GetRand(定数) の部分は、近いうちにAI整備して、そのときに廃止になる予定だから
今は目をつぶってる。
ブログ1エントリには長くなりすぎてる気がするけど、もう一息。
main.cpp
void GamePlay()
{
int i,dist,tm;
for(i=0;i<2;i++)team[i].setControlPlayer(-1,999999);
for(i=0;i<10;i++)player[i].setStatus(WAIT);
for(i=0;i<10;i++){
tm = player[i].getTeam();
// ゴールキーパー以外で、両軍ともにボール予測位置に一番近いプレイヤーを探す
if(player[i].getPos()!=GOALKEEPER){
dist = DistanceY(player[i], ball);
if(dist < team[tm].Distance()) // このプレイヤーが今のところ一番近い
team[tm].setControlPlayer(i,dist);
/* dist = DistanceY(player[i], ball);
if(player[i].getTeam()==RED){
if(dist<rc_dist){
rc=i; // このプレイヤーが今のところ一番近い
rc_dist=dist;
}
} else {
if(dist<bc_dist){
bc=i; // このプレイヤーが今のところ一番近い
bc_dist=dist;
}
}*/
} else {
// ゴールキーパーは自分のエリアからある程度ボールが離れれば、
自分のポジションに戻る
if((ball.getBx()>team[tm].KeepArea_Left())
&&(ball.getBx()<team[tm].KeepArea_Right())){
player[i].GoLocate(SUPPORT, ball.getKBx(), ball.getKBy());
} else {
player[i].GoHomePos();
}
/*
if(player[i].getTeam()==RED){
if(ball.getBx()<400)player[i].GoHomePos();
if(ball.getArea() == RedArea)
player[i].GoLocate(SUPPORT, ball.getKBx(), ball.getKBy());
} else {
if(ball.getBx()>200)player[i].GoHomePos();
if(ball.getArea() == BlueArea)
player[i].GoLocate(SUPPORT, ball.getKBx(), ball.getKBy());
}*/
}
}
// いちばん近いプレイヤーに、目標地点に向かう指令を出す
for(i=0;i<2;i++)
player[team[i].ControlPlayer()].GoLocate(CHASE_BALL,ball.getYBx(),ball.getYBy());
/*
if(bc!=-1) player[bc].GoLocate(CHASE_BALL,ball.getYBx(),ball.getYBy());
if(rc!=-1) player[rc].GoLocate(CHASE_BALL,ball.getYBx(),ball.getYBy());
*/
最初の、赤軍青軍で場合分けしてたいちばんボールに近いプレイヤー選出部分を
まとめてみる。
rc, bc で変数分けしてたのを、Teamクラスにメンバ変数作って、そこで統一して管理。
チームは0(BLUE)と1(RED)のふたつだけなんだけど、いちいちfor文でまわすのは
ちょっとイヤミにも見えるか。
team.h
// Team.h
#pragma once
class Team
{
int m_EnemyGoalLine; // 相手ゴールライン
int m_KeepArea_Left; // 自陣左端
int m_KeepArea_Right; // 自陣右端
int m_AttackArea_Left; // 攻撃ポイント左端
int m_AttackArea_Right; // 攻撃ポイント右端
int m_ControlPlayer; // 自軍でボールに一番近いプレイヤー番号
int m_Distance; // ControlPlayer とボールとの距離
public:
void Init(int EnemyGoalLine,
int AttackArea_Left,
int AttackArea_Right,
int KeepArea_Left,
int KeepArea_Right);
int EnemyGoalLine(){return m_EnemyGoalLine;}
int KeepArea_Left(){return m_KeepArea_Left;}
int KeepArea_Right(){return m_KeepArea_Right;}
int AttackArea_Left(){return m_AttackArea_Left;}
int AttackArea_Right(){return m_AttackArea_Right;}
void setControlPlayer(int ControlPlayer,int Dist){
m_ControlPlayer = ControlPlayer;
m_Distance = Distance;
}
int ControlPlayer(){return m_ControlPlayer;}
int Distance(){return m_Distance;}
};
なにはともあれ、ずいぶんすっきりした。
今のところは変更点確認のために前のプログラムをコメントアウトで残してるけど
これを削ったら、すごく短くなる。
ブログエントリ シンプルサッカー(1)
http://hiyayakkoprog.blog.shinobi.jp/Entry/38/
ダウンロードHP
http://hiyayakko.sarashi.com/SimpleSoccer/SimpleSoccer.html
したらば掲示板 シンプルサッカー
http://jbbs.livedoor.jp/bbs/read.cgi/computer/42268/1250213377/l50
PR