◎ Balder 2D 無重力下の二次元シューティングゲーム
(改造版ならTrixieでも実行は可能)
スクリーンショット





ゲーム詳細
https://sourceforge.net/projects/balder
起動:cd balder2d/balder2d-1.0/bin; ./balder2d
●無重力下の二次元シューティングゲーム
小さなプローブを操り、他のプレイヤーを破壊します。
自分は紫のプローブです。
プローブの進む方向や速度は、壁や他のプローブにぶつからない限り変化しません。
弾丸を撃った時、逆方向の小さな反動があります。
【操作】[←]:左回転 [→]:右回転 [↑]:撃つ [↓]:壁にくっつく/離れる
オリジナルではコンピュータ(Python製AI)との対戦が可能です。
改造版では、C++に切り替えてありますので、Trixieでも実行は可能になります。
Brain上での実行では、動作はしますが、
800×600の画面を想定されているようで、全フィールドが表示されません。
ウィンドウを移動して、上側か下側が表示されない状態であれば、プレイできます。
AIとの対戦プレイも楽しめます。
このため、ゲームを改造して遊べるようにしてみました。
そのほか細かい部分も変更できますので、ソースを解析してみるのも面白いです。
Optionsで、Player数を指定できます(AIも含めた人数です)
他に以下の項目が変更できます。
・Game Setup マップの選択、制限時間、残数等が設定できます。
(マップはDefaultとかが遊びやすいかもしれません。)
・Player Setup Human(キーバインド)かAI(3種類から選択)等プレイヤー設定が出来ます。
(キーバインド設定は下のほうが表示されないので、
(改造前に設定するか、)
(config.xmlファイルを直接いじりましょう。)
・Sound & Video Fullscreen/Sound/Musicの設定ができます.
(Fullscreenは現状エラーになります。)
(Musicは速度に影響しますので、無効推奨です。)
守りに徹するか、リスクを覚悟で特攻するか
ゲームスタイルが分かれます。
インストール:改造後にビルドしたものを
zipで固めましたので、Brainuxに展開してみてください
こちらはTrixie上でも実行できるはずです。
https://nsb.homeip.net/Brain/Upload/balder2d/balder2d-bin.zip
展開後、
$ cd bin
$ ./balder2d
で起動できるはずです。
(TrixieではSDL1.2の自ビルドは必要です。)
balder2d改造版のソースを含んだものは以下になります。
https://nsb.homeip.net/Brain/Upload/balder2d/balder2d-kai.zip
インストール:ソースからインストール + 改造
debian11ならビルド可能です。
debian13ではpython2が削除されているため、ビルド不可になります。
(AIがpython2製です)
AIをC++に移植した改造版であれば、
debian13でもビルドしたものの動作はしました。
インストール手順詳細(クリックで表示)
cd
mkdir balder2d
cd balder2d
ソースをダウンロードして、フォルダに保存
https://sourceforge.net/projects/balder/files/Balder%202D/balder2d1.0/balder2d-1.0.tar.gz/download
tar zxvf balder2d-1.0.tar.gz
cd balder2d-1.0
sudo apt install scons \
libsdl1.2-dev libsdl-image1.2-dev libsdl-mixer1.2-dev \
libsdl-ttf2.0-dev libboost-all-dev python2-dev
sudo apt install libguichan-dev
sudo apt install scons
scons
#エラーの対策
vi SConstruct
-includepath = ['/usr/include/python2.4','include']
+includepath = ['/usr/include/python2.7','include']
-libs = Split('guichan_sdl guichan SDL_image SDL_mixer SDL_gfx python2.4 physfs')
+libs = Split('guichan_sdl guichan SDL SDL_image SDL_mixer SDL_gfx python2.7 physfs')
vi bin/scripts/test/
-env = Environment(CPPPATH='/usr/include/python2.4')
+env = Environment(CPPPATH='/usr/include/python2.7')
-env.Program('runtests', 'runtests.cpp', LIBS='python2.4')
+env.Program('runtests', 'runtests.cpp', LIBS='python2.7')
scons -c
scons
cd balder2d/balder2d-1.0/bin/; ./balder2d
このままだと、プレイ画面の解像度が大きすぎてゲーム画面が全部表示されないので、
表示範囲を調整してみます。ついでに、SDLのモードを16bit化
(本来はマップサイズから画面サイズを掲載しているのですが、
強制的に画面サイズを変えているので、不具合が出るかもしれません。)
vi src/main.cpp
-render->SetScreen(800,600);
+render->SetScreen(850,480);vi src/renderer.cpp
-SetScreen(800, 600);
+SetScreen(850, 480);vi src/gamemanager.cpp
mapwidth = statbox_x = mp.GetMapWidth();
+if (mapwidth > 850) mapwidth = statbox_x = 850;
mapheight = statbox_y = mp.GetMapHeight();
+if (mapheight+STATHEIGHT > 480){
+ mapheight = 480-STATHEIGHT-20;
+ statbox_y = 480-STATHEIGHT;
+}
-render->SetScreen(mp.GetMapWidth(), mp.GetMapHeight()+STATHEIGHT,
+render->SetScreen(tatbox_x, statbox_y+STATHEIGHT,vi src/collisionmask.cpp
#include "imageloader.h"
+#include "gamemanager.h"
if (!field) throw "invalid surface used to create collision mask";
width = field->w;
height = field->h;
+ if (width > 850) width = 850;
+ if (height+STATHEIGHT > 480) height= 480-STATHEIGHT-0;vi src/menu/options.cpp # (Backボタンの場所を変更)
-page->add(backButton, page->getWidth() - (backButton->getWidth()+50), page->getHeight() - (50 + backButton->getHeight()));
+page->add(backButton, page->getWidth() - (backButton->getWidth()+50), page->getHeight() - (150 + backButton->getHeight()));vi src/menu/gamesetup.cpp # (Backボタンの場所を変更(場所がなくて不格好))
-page->add(backButton, page->getWidth() - (backButton->getWidth()+50), page->getHeight() - (50 + backButton->getHeight()));
+page->add(backButton, page->getWidth() - (backButton->getWidth()+0), page->getHeight() - (150 + backButton->getHeight()));vi src/menu/playersetup.cpp # (Backボタンの場所を変更)
-page->add(backButton, page->getWidth() - (backButton->getWidth()+50), page->getHeight() - (50 + backButton->getHeight()));
+page->add(backButton, page->getWidth() - (backButton->getWidth()+50), page->getHeight() - (150 + backButton->getHeight()));vi src/menu/soundvideo.cpp # (Backボタンの場所を変更)
-page->add(backButton, page->getWidth() - (backButton->getWidth()+50), page->getHeight() - (50 + backButton->getHeight()));
+page->add(backButton, page->getWidth() - (backButton->getWidth()+50), page->getHeight() - (150 + backButton->getHeight()));vi src/gamemanager.cpp #リスポーン時のバグ修正()
srand(frame); // ・・・・
- int x = rand()%collision_mask->GetWidth();
+ int x = 50 + rand()%(collision_mask->GetWidth()-100);
- int y = rand()%collision_mask->GetHeight();
+ int y = 50 + rand()%(collision_mask->GetHeight()-100);
int radius = p->GetWidth();
// test and make sure this doesn't burry the probe too deeply in a wall
if (collision_mask->TestPixel(x, y + radius)){collision=true;continue;}
+ if (collision_mask->TestPixel(x + radius, y + radius)){collision=true;continue;}
+ if (collision_mask->TestPixel(x - radius, y + radius)){collision=true;continue;}
- if (collision_mask->TestPixel(x, x - radius)){collision=true;continue;}
+ if (collision_mask->TestPixel(x, y - radius)){collision=true;continue;}
+ if (collision_mask->TestPixel(x + radius, y - radius)){collision=true;continue;}
+ if (collision_mask->TestPixel(x - radius, y - radius)){collision=true;continue;}
if (collision_mask->TestPixel(x + radius, y)){collision=true;continue;}
if (collision_mask->TestPixel(x - radius, y)){collision=true;continue;}
p->SetProbeState(x,y, 1-rand()%20/10.0, 1-rand()%20/10.0, false);vi src/probe.cpp
#include "configmanager.h"
+#include "collisionmask.h"
#include <SDL/SDL_gfxPrimitives.h>
// add power to push off with if pushing off
if ( (PUSH_POWERING == pushOffState) && (pushOffSpeed < MAXSPEED) ) {
pushOffSpeed += MAXSPEED/POWERUPTIME;
}
+ // --- 完全に囲まれているかチェック ---
+ int radius = p->GetWidth();
+ bool leftWall = collision_mask->TestPixel((int)(x_position - 25), (int)y_position);
+ bool rightWall = collision_mask->TestPixel((int)(x_position + 25), (int)y_position);
+ bool upWall = collision_mask->TestPixel((int)x_position, (int)(y_position - 25));
+ bool downWall = collision_mask->TestPixel((int)x_position, (int)(y_position + 25));
+
+ if (leftWall && rightWall && upWall && downWall) {
+
+ // --- ランダム安全位置を探す ---
+ for (int i = 0; i < 20; i++) {
+ int rx = 50 + rand() % (collision_mask->GetWidth()-100);
+ int ry = 50 + rand() % (collision_mask->GetHeight()-100);
+
+ // 壁でない位置なら安全
+ if (!collision_mask->TestPixel(rx, ry)) {
+ x_position = rx;
+ y_position = ry;
+
+ // 速度リセット
+ x_velocity = 0;
+ y_velocity = 0;
+
+ return; // テレポート成功
+ }
+ }
+
+ // --- 安全位置が見つからなかった → 自爆 ---
+ Die();
+ return;
+ }
}scons
#高速化設定
#画面、サウンド、CPU最適化
vi src/renderer.cpp
-screen = SDL_SetVideoMode(width, height, 0, flags);
+screen = SDL_SetVideoMode(width, height, 16, flags);vi src/soundmanager.cpp
- Mix_OpenAudio(44100,MIX_DEFAULT_FORMAT,2,512);
+ Mix_OpenAudio(11025,MIX_DEFAULT_FORMAT,2,512);vi SConstruct
-env = Environment(CPPPATH=includepath)
+env = Environment(
+ CPPPATH=includepath,
+ CCFLAGS='-march=armv5te -mtune=arm926ej-s -mfloat-abi=soft -O3 -fomit-frame-pointer -ffast-math'
+)
- env.Append(CCFLAGS = '-g')
+ env.Append(CCFLAGS = ' -g')vi include/probe.h
#define PROBE_H
+extern float sin_table[360];
+extern float cos_table[360];
#define RECHARGE_FRAME_INTERVAL_ON 50
#define RECHARGE_FRAME_INTERVAL_OFF 3000
vi src/main.cpp
#include "log.h"
+#include <math.h>
+float sin_table[360];
+float cos_table[360];
+for (int i = 0; i < 360; i++) {
+ float rad = i * M_PI / 180.0f;
+ sin_table[i] = sin(rad);
+ cos_table[i] = cos(rad);
+}
while(menu->Run())
vi src/probe.cpp
void Probe::DrawOnto(SDL_Surface* destination)
{
・・・・
// Experimental aiming indicator
Uint32 aimerColor = 0xFFD700FF;
- float x_direction = cos(facing_angle);
- float y_direction = sin(facing_angle);
+ // ---- angle → table index ----
+ int ang = (int)(facing_angle * 180.0f / M_PI);
+ ang = (ang % 360 + 360) % 360;
+ float x_direction = cos_table[ang];
+ float y_direction = sin_table[ang];
int x_pos = int(x_position + x_direction*PROBEWIDTH*3.0/5);
int y_pos = int(y_position + y_direction*PROBEHEIGHT*3.0/5);
circleColor(destination, x_pos, y_pos, 1, aimerColor);
if (powerups & EFFECT_SPREADFIRE) { // draw extra guns
- x_direction = cos(facing_angle + M_PI/48);
- y_direction = sin(facing_angle + M_PI/48);
+ // +3.75° → +4°で近似
+ int ang_plus = ang + 4;
+ if (ang_plus >= 360) ang_plus -= 360;
+ x_direction = cos_table[ang_plus];
+ y_direction = sin_table[ang_plus];
- x_pos = int(x_position + cos(facing_angle + M_PI/12)*PROBEWIDTH*3.0/5);
- y_pos = int(y_position + sin(facing_angle + M_PI/12)*PROBEHEIGHT*3.0/5);
+ // muzzle offset +15°
+ int muzzle_plus = ang + 15;
+ if (muzzle_plus >= 360) muzzle_plus -= 360;
+ x_pos = int(x_position + cos_table[muzzle_plus] * PROBEWIDTH * 3.0f / 5);
+ y_pos = int(y_position + sin_table[muzzle_plus] * PROBEHEIGHT * 3.0f / 5);
circleColor(destination, x_pos, y_pos, 1, aimerColor);
+ // -3.75° → -4°で近似
- x_direction = cos(facing_angle - M_PI/48);
- y_direction = sin(facing_angle - M_PI/48);
+ int ang_minus = ang - 4;
+ if (ang_minus < 0) ang_minus += 360;
+ x_direction = cos_table[ang_minus];
+ y_direction = sin_table[ang_minus];
- x_pos = int(x_position + cos(facing_angle - M_PI/12)*PROBEWIDTH*3.0/5);
- y_pos = int(y_position + sin(facing_angle - M_PI/12)*PROBEHEIGHT*3.0/5);
+ // muzzle offset -15°
+ int muzzle_minus = ang - 15;
+ if (muzzle_minus < 0) muzzle_minus += 360;
+ x_pos = int(x_position + cos_table[muzzle_minus] * PROBEWIDTH * 3.0f / 5);
+ y_pos = int(y_position + sin_table[muzzle_minus] * PROBEHEIGHT * 3.0f / 5);
circleColor(destination, x_pos, y_pos, 1, aimerColor);
}
・・・・
}
void Probe::DrawAmmo(SDL_Surface* destination)
{
if (GetCharge() > 0) {
SDL_Surface* s = Renderer::sprite_registry["probe"];
if (!s) throw "no sprite found for entity!";
SDL_Rect sourceRect = {0, PROBEHEIGHT*2, AMMOWIDTH, AMMOHEIGHT};
ammo_angle += M_PI/48;
if (ammo_angle > 2*M_PI) { ammo_angle -= 2*M_PI;}
// initialize calculation variables.
double ammoAngle = 0;
SDL_Rect target_rect;
for (int currentCharge = 0;currentCharge < GetCharge(); currentCharge++) {
ammoAngle = ((2*M_PI)/GetCharge())*currentCharge;
ammoAngle += ammo_angle;
- float x_direction = cos(ammoAngle);
- float y_direction = sin(ammoAngle);
+ // ---- angle → table index ----
+ int ang = (int)(ammoAngle * 180.0f / M_PI);
+ ang = (ang % 360 + 360) % 360;
+ float x_direction = cos_table[ang];
+ float y_direction = sin_table[ang];
float x_pos = x_position + x_direction*PROBEWIDTH*3/8; // 3/4 probe radius
float y_pos = y_position + y_direction*PROBEHEIGHT*3/8;
target_rect.x = Sint16(x_pos - AMMOWIDTH/2);
target_rect.y = Sint16(y_pos - AMMOHEIGHT/2);
// draw ammo frames.
SDL_BlitSurface( s, &sourceRect, destination, &target_rect);
}
}
}
void Probe::PushOff ( )
{
if (stuck){
- x_velocity = pushOffSpeed*cos(facing_angle);
- y_velocity = pushOffSpeed*sin(facing_angle);
+ int ang = (int)(facing_angle * 180.0f / M_PI);
+ ang = (ang % 360 + 360) % 360;
+ if (pushOffSpeed < 1.0f)
+ pushOffSpeed = 1.0f;
+ x_velocity = pushOffSpeed*cos_table[ang];
+ y_velocity = pushOffSpeed*sin_table[ang];
pushOffSpeed = 0;
game_manager->PlaySound("probe push");
}
stuck = false;
pushOffState = 0;
}
void Probe::Fire ( )
{
// make sure you can't fire when you're dead
if (death_frame) { return;}
if(charge_remaining > 1){
charge_remaining -= 1;
+ // ---- angle → table index ----
+ int ang = (int)(facing_angle * 180.0f / M_PI);
+ ang = (ang % 360 + 360) % 360;
// fire the projectile
- float x_direction = cos(facing_angle);
- float y_direction = sin(facing_angle);
+ float x_direction = cos_table[ang];
+ float y_direction = sin_table[ang];
float x_pos = x_position + x_direction*2*PROBEWIDTH/3;
float y_pos = y_position + y_direction*2*PROBEHEIGHT/3;
if (!Projectile::Add_Probe_Velocity) game_manager->CreateProjectile(owner, x_pos, y_pos , x_direction*Projectile::SPEED, y_direction*Projectile::SPEED);
else game_manager->CreateProjectile(owner, x_pos, y_pos , x_direction*Projectile::SPEED+x_velocity, y_direction*Projectile::SPEED+y_velocity);
if (powerups & EFFECT_SPREADFIRE) { // fire 2 bonus shots
- x_direction = cos(facing_angle + M_PI/48);
- y_direction = sin(facing_angle + M_PI/48);
- x_pos = x_position + cos(facing_angle + M_PI/12)*2*PROBEWIDTH/3;
- y_pos = y_position + sin(facing_angle + M_PI/12)*2*PROBEHEIGHT/3;
+ // + angle
+ int ang2 = ang + 4; // M_PI/48 ≒ 3.75°
+ if (ang2 >= 360) ang2 -= 360;
+ x_direction = cos_table[ang2];
+ y_direction = sin_table[ang2];
+ x_pos = x_position + cos_table[(ang + 15) % 360]*2*PROBEWIDTH/3;
+ y_pos = y_position + sin_table[(ang + 15) % 360]*2*PROBEHEIGHT/3;
if (!Projectile::Add_Probe_Velocity) game_manager->CreateProjectile(owner, x_pos, y_pos , x_direction*Projectile::SPEED, y_direction*Projectile::SPEED);
else game_manager->CreateProjectile(owner, x_pos, y_pos , x_direction*4+x_velocity, y_direction*Projectile::SPEED+y_velocity);
- x_direction = cos(facing_angle - M_PI/48);
- y_direction = sin(facing_angle - M_PI/48);
- x_pos = x_position + cos(facing_angle - M_PI/12)*2*PROBEWIDTH/3;
- y_pos = y_position + sin(facing_angle - M_PI/12)*2*PROBEHEIGHT/3;
+ // - angle
+ int ang3 = ang - 4;
+ if (ang3 < 0) ang3 += 360;
+ x_direction = cos_table[ang3];
+ y_direction = sin_table[ang3];
+ x_pos = x_position + cos_table[(ang + 360 - 15) % 360]*2*PROBEWIDTH/3;
+ y_pos = y_position + sin_table[(ang + 360 - 15) % 360]*2*PROBEHEIGHT/3;
if (!Projectile::Add_Probe_Velocity) game_manager->CreateProjectile(owner, x_pos, y_pos , x_direction*Projectile::SPEED, y_direction*Projectile::SPEED);
else game_manager->CreateProjectile(owner, x_pos, y_pos , x_direction*Projectile::SPEED+x_velocity, y_direction*Projectile::SPEED+y_velocity);
}
// now the reaction from firing if not stuck to a wall
if (!stuck){
x_velocity -= x_direction/4;
y_velocity -= y_direction/4;
}
game_manager->PlaySound("fire");
}
}
scons -c
scons
改造のお時間
ここからは改造の時間。必須ではないのでお好みで。
#弾を撃った時の反動を強くする。
#(オリジナルだとほとんど反動が無いので、元の8倍の反動にする場合)
vi src/probe.cpp
if (!stuck){
- x_velocity -= x_direction/4;
- y_velocity -= y_direction/4;
+ x_velocity -= x_direction*2;
+ y_velocity -= y_direction*2;
}
game_manager->PlaySound("fire");
scons
#弾?のチャージ速度を上げる[もしかしたらシールドかも]。
#(デフォルトの2倍のチャージ速度):
vi inclide/probe.h
-#define RECHARGE_FRAME_INTERVAL_ON 50
-#define RECHARGE_FRAME_INTERVAL_OFF 3000
+#define RECHARGE_FRAME_INTERVAL_ON 25
+#define RECHARGE_FRAME_INTERVAL_OFF 1500
scons
#最大弾数、弾のチャージ速度の調整(xmlファイルの変更で対応可能)
maxcharge :最大弾数 例:5->10
rechargetime :弾のチャージ速度 例:400->200
bin/config.xml
<Probe>
<maxhits value="3" />
<maxcharge value="5" />
<rechargetime value="400" />
<maxspeed value="3" />
<poweruptime value="10" />
<turningspeed value="0.041666667" />
</Probe>
#最大弾数の2倍弾を打てるようにする
#(ただし敵の弾も大量に出るようになるので難易度も上がります。)
vi src/probe.cpp
-charge_remaining -= 1
+charge_remaining -= 0.5scons
AIのC++移植
AIは、Python2.4のC APIを利用しているため、
Teixieで手動使用可能なPython2.7では稼働しないようです。
Python2.4は開発用ソースすら依存関係で導入できないので、
このままではAIは利用できません。ですので、Python2.4製AIコードをC++に移植してみます。
balderaiAI(balderai.py の完全 C++ 移植)
パワーアップ優先+射撃間隔調整+スタック脱出RandomAI(randomai.py の C++ 移植)
完全ランダムSimpleAimerAI(simple_aimer.py の C++ 移植)
最も近い敵に向けて撃つ
追加
AggressiveAI(攻撃特化)
敵に積極的に接近し常に射撃を優先。回避より攻撃します。DefensiveAI(回避・生存特化)
敵から距離を取壁や弾を避ける。射撃より生存優先でパワーアップを積極的に取りに行くSniperAI
最も近い 1 体だけをターゲットにする「一点集中型」「視野が狭いが精密」なため近づくと危険
新規追加
mkdir src/ai
vi src/ai/BaseAI.h
#ifndef BALDER_BASEAI_H
#define BALDER_BASEAI_H
#include <vector>
#include "balder2dtypes.h"
namespace Balder {
class Probe;
class Entity;
struct CollisionMask;
class BaseAI {
public:
virtual ~BaseAI() {}
virtual input_states Update(
Probe* self,
const std::vector<Probe*>& probes,
const std::vector<Entity*>& projectiles,
const std::vector<Entity*>& powerups,
CollisionMask* geometry
) = 0;
};
}
#endifvi src/ai/DefaultAI.h
#ifndef BALDER_DEFAULTAI_H
#define BALDER_DEFAULTAI_H
#include "BaseAI.h"
namespace Balder {
class DefaultAI : public BaseAI {
public:
input_states Update(
Probe* self,
const std::vector<Probe*>& probes,
const std::vector<Entity*>& projectiles,
const std::vector<Entity*>& powerups,
CollisionMask* geometry
) override;
};
}
#endifvi src/ai/DefaultAI.cpp
#include "DefaultAI.h"
#include "probe.h"
#include "entity.h"
#include "collisionmask.h"
#include "input.h"
#include "balder2dtypes.h"
#include <cmath>
using namespace Balder;
static Probe* FindClosestProbe(Probe* self, const std::vector<Probe*>& probes) {
Probe* best = nullptr;
float bestDist2 = 0.0f;
float x = self->GetX();
float y = self->GetY();
for (Probe* p : probes) {
if (p->GetOwner() == self->GetOwner()) continue;
float dx = p->GetX() - x;
float dy = p->GetY() - y;
float d2 = dx*dx + dy*dy;
if (!best || d2 < bestDist2) {
best = p;
bestDist2 = d2;
}
}
return best;
}
static Entity* FindClosestEntity(float x, float y, const std::vector<Entity*>& list) {
Entity* best = nullptr;
float bestDist2 = 0.0f;
for (Entity* e : list) {
float dx = e->GetX() - x;
float dy = e->GetY() - y;
float d2 = dx*dx + dy*dy;
if (!best || d2 < bestDist2) {
best = e;
bestDist2 = d2;
}
}
return best;
}
input_states DefaultAI::Update(
Probe* self,
const std::vector<Probe*>& probes,
const std::vector<Entity*>& projectiles,
const std::vector<Entity*>& powerups,
CollisionMask* geometry
) {
float x = self->GetX();
float y = self->GetY();
input_states inputs = 0;
// powerup 優先
if (!powerups.empty() && self->GetStuckStatus()) {
Entity* pu = FindClosestEntity(x, y, powerups);
if (pu) {
float dx = pu->GetX() - x;
float dy = pu->GetY() - y;
float targetAngle = std::atan2(dy, dx);
float currentAngle = self->GetRotationAngle();
float diff = targetAngle - currentAngle;
while (diff > M_PI) diff -= 2*M_PI;
while (diff < -M_PI) diff += 2*M_PI;
if (diff > 0.1f) return RIGHT;
if (diff < -0.1f) return LEFT;
return STICK;
}
}
// 敵を狙う
Probe* target = FindClosestProbe(self, probes);
if (!target) return 0;
float dx = target->GetX() - x;
float dy = target->GetY() - y;
float targetAngle = std::atan2(dy, dx);
float currentAngle = self->GetRotationAngle();
float diff = targetAngle - currentAngle;
while (diff > M_PI) diff -= 2*M_PI;
while (diff < -M_PI) diff += 2*M_PI;
if (diff > 0.1f) return RIGHT;
if (diff < -0.1f) return LEFT;
return FIRE;
}vi src/ai/BalderAI.h
#ifndef BALDER_BALDERAI_H
#define BALDER_BALDERAI_H
#include "BaseAI.h"
namespace Balder {
class BalderAI : public BaseAI {
public:
input_states Update(
Probe* self,
const std::vector<Probe*>& probes,
const std::vector<Entity*>& projectiles,
const std::vector<Entity*>& powerups,
CollisionMask* geometry
) override;
};
}
#endifvi src/ai/BalderAI.cpp
#include "BalderAI.h"
#include "probe.h"
#include "entity.h"
#include "collisionmask.h"
#include "input.h"
#include "balder2dtypes.h"
#include <cmath>
using namespace Balder;
static Probe* FindClosestProbe(Probe* self, const std::vector<Probe*>& probes) {
Probe* best = nullptr;
float bestDist2 = 0.0f;
float x = self->GetX();
float y = self->GetY();
for (Probe* p : probes) {
if (p->GetOwner() == self->GetOwner()) continue;
float dx = p->GetX() - x;
float dy = p->GetY() - y;
float d2 = dx*dx + dy*dy;
if (!best || d2 < bestDist2) {
best = p;
bestDist2 = d2;
}
}
return best;
}
static Entity* FindClosestEntity(float x, float y, const std::vector<Entity*>& list) {
Entity* best = nullptr;
float bestDist2 = 0.0f;
for (Entity* e : list) {
float dx = e->GetX() - x;
float dy = e->GetY() - y;
float d2 = dx*dx + dy*dy;
if (!best || d2 < bestDist2) {
best = e;
bestDist2 = d2;
}
}
return best;
}
input_states BalderAI::Update(
Probe* self,
const std::vector<Probe*>& probes,
const std::vector<Entity*>& projectiles,
const std::vector<Entity*>& powerups,
CollisionMask* geometry
) {
float x = self->GetX();
float y = self->GetY();
input_states inputs = 0;
// powerup 優先
if (!powerups.empty() && self->GetStuckStatus()) {
Entity* pu = FindClosestEntity(x, y, powerups);
if (pu) {
float dx = pu->GetX() - x;
float dy = pu->GetY() - y;
float targetAngle = std::atan2(dy, dx);
float currentAngle = self->GetRotationAngle();
float diff = targetAngle - currentAngle;
while (diff > M_PI) diff -= 2*M_PI;
while (diff < -M_PI) diff += 2*M_PI;
if (diff > 0.1f) return RIGHT;
if (diff < -0.1f) return LEFT;
return STICK;
}
}
// 敵を狙う
Probe* target = FindClosestProbe(self, probes);
if (!target) return 0;
float dx = target->GetX() - x;
float dy = target->GetY() - y;
float targetAngle = std::atan2(dy, dx);
float currentAngle = self->GetRotationAngle();
float diff = targetAngle - currentAngle;
while (diff > M_PI) diff -= 2*M_PI;
while (diff < -M_PI) diff += 2*M_PI;
if (diff > 0.1f) return RIGHT;
if (diff < -0.1f) return LEFT;
return FIRE;
}vi src/ai/RandomAI.h
#ifndef BALDER_RANDOMAI_H
#define BALDER_RANDOMAI_H
#include "BaseAI.h"
#include <cstdlib>
namespace Balder {
class RandomAI : public BaseAI {
public:
input_states Update(
Probe* self,
const std::vector<Probe*>& probes,
const std::vector<Entity*>& projectiles,
const std::vector<Entity*>& powerups,
CollisionMask* geometry
) override {
return rand() % 17;
}
};
}
#endifvi src/ai/SimpleAimerAI.h
#ifndef BALDER_SIMPLEAIMERAI_H
#define BALDER_SIMPLEAIMERAI_H
#include "BaseAI.h"
namespace Balder {
class SimpleAimerAI : public BaseAI {
public:
input_states Update(
Probe* self,
const std::vector<Probe*>& probes,
const std::vector<Entity*>& projectiles,
const std::vector<Entity*>& powerups,
CollisionMask* geometry
) override;
};
}
#endifvi src/ai/SimpleAimerAI.cpp
#include "SimpleAimerAI.h"
#include "probe.h"
#include "input.h"
#include "balder2dtypes.h"
#include <cmath>
using namespace Balder;
input_states SimpleAimerAI::Update(
Probe* self,
const std::vector<Probe*>& probes,
const std::vector<Entity*>&,
const std::vector<Entity*>&,
CollisionMask*
) {
float x = self->GetX();
float y = self->GetY();
Probe* target = nullptr;
float bestDist2 = 0.0f;
for (Probe* p : probes) {
if (p->GetOwner() == self->GetOwner()) continue;
float dx = p->GetX() - x;
float dy = p->GetY() - y;
float d2 = dx*dx + dy*dy;
if (!target || d2 < bestDist2) {
target = p;
bestDist2 = d2;
}
}
if (!target) return 0;
float dx = target->GetX() - x;
float dy = target->GetY() - y;
float targetAngle = std::atan2(dy, dx);
float currentAngle = self->GetRotationAngle();
float diff = targetAngle - currentAngle;
while (diff > M_PI) diff -= 2*M_PI;
while (diff < -M_PI) diff += 2*M_PI;
if (diff > 0.1f) return RIGHT;
if (diff < -0.1f) return LEFT;
return FIRE;
}vi src/ai/AggressiveAI.h
#ifndef BALDER_AGGRESSIVEAI_H
#define BALDER_AGGRESSIVEAI_H
#include "BaseAI.h"
namespace Balder {
class AggressiveAI : public BaseAI {
public:
input_states Update(
Probe* self,
const std::vector<Probe*>& probes,
const std::vector<Entity*>& projectiles,
const std::vector<Entity*>& powerups,
CollisionMask* geometry
) override;
};
}
#endifvi src/ai/AggressiveAI.cpp
#include "AggressiveAI.h"
#include "probe.h"
#include "input.h"
#include "balder2dtypes.h"
#include <cmath>
using namespace Balder;
input_states AggressiveAI::Update(
Probe* self,
const std::vector<Probe*>& probes,
const std::vector<Entity*>&,
const std::vector<Entity*>&,
CollisionMask*
) {
float x = self->GetX();
float y = self->GetY();
Probe* target = nullptr;
float bestDist2 = 0.0f;
for (Probe* p : probes) {
if (p->GetOwner() == self->GetOwner()) continue;
float dx = p->GetX() - x;
float dy = p->GetY() - y;
float d2 = dx*dx + dy*dy;
if (!target || d2 < bestDist2) {
target = p;
bestDist2 = d2;
}
}
if (!target) return 0;
float dx = target->GetX() - x;
float dy = target->GetY() - y;
float targetAngle = std::atan2(dy, dx);
float currentAngle = self->GetRotationAngle();
float diff = targetAngle - currentAngle;
while (diff > M_PI) diff -= 2*M_PI;
while (diff < -M_PI) diff += 2*M_PI;
const float turnThresh = 0.05f;
if (diff > turnThresh) return RIGHT;
if (diff < -turnThresh) return LEFT;
return FIRE;
}vi src/ai/DefensiveAI.h
#ifndef BALDER_DEFENSIVEAI_H
#define BALDER_DEFENSIVEAI_H
#include "BaseAI.h"
namespace Balder {
class DefensiveAI : public BaseAI {
public:
input_states Update(
Probe* self,
const std::vector<Probe*>& probes,
const std::vector<Entity*>& projectiles,
const std::vector<Entity*>& powerups,
CollisionMask* geometry
) override;
};
}
#endifvi src/ai/DefensiveAI.cpp
#include "DefensiveAI.h"
#include "probe.h"
#include "entity.h"
#include "collisionmask.h"
#include "input.h"
#include "balder2dtypes.h"
#include <cmath>
using namespace Balder;
input_states DefensiveAI::Update(
Probe* self,
const std::vector<Probe*>& probes,
const std::vector<Entity*>&,
const std::vector<Entity*>& powerups,
CollisionMask*
) {
float x = self->GetX();
float y = self->GetY();
// パワーアップ優先
if (!powerups.empty()) {
Entity* best = nullptr;
float bestDist2 = 0.0f;
for (Entity* e : powerups) {
float dx = e->GetX() - x;
float dy = e->GetY() - y;
float d2 = dx*dx + dy*dy;
if (!best || d2 < bestDist2) {
best = e;
bestDist2 = d2;
}
}
if (best) {
float dx = best->GetX() - x;
float dy = best->GetY() - y;
float targetAngle = std::atan2(dy, dx);
float currentAngle = self->GetRotationAngle();
float diff = targetAngle - currentAngle;
while (diff > M_PI) diff -= 2*M_PI;
while (diff < -M_PI) diff += 2*M_PI;
if (diff > 0.1f) return RIGHT;
if (diff < -0.1f) return LEFT;
return STICK;
}
}
// 敵から逃げる
Probe* closest = nullptr;
float bestDist2 = 0.0f;
for (Probe* p : probes) {
if (p->GetOwner() == self->GetOwner()) continue;
float dx = p->GetX() - x;
float dy = p->GetY() - y;
float d2 = dx*dx + dy*dy;
if (!closest || d2 < bestDist2) {
closest = p;
bestDist2 = d2;
}
}
if (!closest) return 0;
float dx = x - closest->GetX();
float dy = y - closest->GetY();
float targetAngle = std::atan2(dy, dx);
float currentAngle = self->GetRotationAngle();
float diff = targetAngle - currentAngle;
while (diff > M_PI) diff -= 2*M_PI;
while (diff < -M_PI) diff += 2*M_PI;
if (diff > 0.1f) return RIGHT;
if (diff < -0.1f) return LEFT;
return STICK;
}vi src/ai/WallFollowerAI.h
#ifndef BALDER_WALLFOLLOWERAI_H
#define BALDER_WALLFOLLOWERAI_H
#include "BaseAI.h"
namespace Balder {
class WallFollowerAI : public BaseAI {
public:
WallFollowerAI() {}
virtual input_states Update(
Probe* self,
const std::vector<Probe*>& probes,
const std::vector<Entity*>& projectiles,
const std::vector<Entity*>& powerups,
CollisionMask* geometry
);
};
}
#endifvi src/ai/WallFollowerAI.cpp
#include "WallFollowerAI.h"
#include "probe.h"
#include "entity.h"
#include "collisionmask.h"
#include "input.h"
#include "balder2dtypes.h"
#include <cmath>
using namespace Balder;
input_states WallFollowerAI::Update(
Probe* self,
const std::vector<Probe*>&,
const std::vector<Entity*>&,
const std::vector<Entity*>&,
CollisionMask* geometry
) {
input_states out = 0;
double x = self->GetX();
double y = self->GetY();
// 壁との距離チェック(12px 先を調べる)
bool leftWall = geometry->Collide(x - 12, y);
bool rightWall = geometry->Collide(x + 12, y);
bool upWall = geometry->Collide(x, y - 12);
bool downWall = geometry->Collide(x, y + 12);
// --- 壁沿い移動ロジック(右手法) ---
// 1. 右側に壁がある → 前進(Stick)
// 2. 右側に壁がない → 右に旋回
// 3. 正面に壁がある → 左に旋回
// 4. それ以外 → 前進
// 正面の壁
bool frontWall = geometry->Collide(
x + cos(self->GetRotationAngle()) * 12,
y + sin(self->GetRotationAngle()) * 12
);
// 右側の壁(右方向 90°)
bool rightSideWall = geometry->Collide(
x + cos(self->GetRotationAngle() + M_PI/2) * 12,
y + sin(self->GetRotationAngle() + M_PI/2) * 12
);
if (!rightSideWall) {
// 右に壁が無い → 右へ旋回
out |= RIGHT;
} else if (frontWall) {
// 正面に壁 → 左へ旋回
out |= LEFT;
} else {
// 壁に沿って前進(Stick)
out |= STICK;
}
return out;
}
ソースファイル差し替え
cp include/computerprobecontrol.h include/computerprobecontrol.h.bkpy
vi include/computerprobecontrol.h
#ifndef BALDER_COMPUTERPROBECONTROL_H
#define BALDER_COMPUTERPROBECONTROL_H
#include "balder2dtypes.h"
#include <string>
namespace Balder {
class BaseAI;
struct ComputerProbeControl {
player_id id;
input_states inputs;
BaseAI* ai;
ComputerProbeControl(player_id pid, BaseAI* aiInstance)
: id(pid), inputs(0), ai(aiInstance) {}
~ComputerProbeControl() {
delete ai;
}
};
}
#endifcp src/computerprobecontrol.cpp src/computerprobecontrol.cpp.bkpy
vi src/computerprobecontrol.cpp
#include "computerprobecontrol.h"
using namespace Balder;
// 実装不要。
// すべての処理はヘッダ内のコンストラクタ/デストラクタで完結します。
cp src/artificialcontroller.cpp src/artificialcontroller.cpp.bkpy
vi src/artificialcontroller.cpp
#include "artificialcontroller.h"
#include "computerprobecontrol.h"
#include "gamemanager.h"
#include "collisionmask.h"
#include "entity.h"
#include "probe.h"
#include "ai/BaseAI.h"
#include "ai/DefaultAI.h"
#include "ai/BalderAI.h"
#include "ai/RandomAI.h"
#include "ai/SimpleAimerAI.h"
#include "ai/AggressiveAI.h"
#include "ai/DefensiveAI.h"
#include "ai/SniperAI.h"
#include "ai/WallFollowerAI.h"
#include <cmath>
#include <vector>
using namespace Balder;
GameManager* ArtificialController::game_manager = nullptr;
CollisionMask* ArtificialController::geometry = nullptr;
std::list<ComputerProbeControl*> ArtificialController::controlled_probes;
ArtificialController::ArtificialController() {}
void ArtificialController::Initialize()
{
// Python は完全削除されたため、ここでは何もしない
}
void ArtificialController::ShutDown()
{
for (auto* ctrl : controlled_probes)
delete ctrl;
controlled_probes.clear();
}
void ArtificialController::ControlProbe(player_id id, std::string aiName)
{
BaseAI* ai = nullptr;
if (aiName == "default")
ai = new DefaultAI();
else if (aiName == "balderai")
ai = new BalderAI;
else if (aiName == "randomai")
ai = new RandomAI();
else if (aiName == "simple_aimer")
ai = new SimpleAimerAI();
else if (aiName == "aggressive")
ai = new AggressiveAI();
else if (aiName == "defensive")
ai = new DefensiveAI();
else if (aiName == "sniperai")
ai = new SniperAI();
else if (aiName == "wallfollower")
ai = new WallFollowerAI();
else
ai = new DefaultAI(); // fallback
controlled_probes.push_back(new ComputerProbeControl(id, ai));
}
void ArtificialController::StopControlling(player_id id)
{
for (auto it = controlled_probes.begin(); it != controlled_probes.end(); ++it) {
if ((*it)->id == id) {
delete *it;
controlled_probes.erase(it);
return;
}
}
}
void ArtificialController::DoControl(std::list<Entity*>& entities)
{
// エンティティ分類
std::vector<Probe*> probes;
std::vector<Entity*> projectiles;
std::vector<Entity*> powerups;
for (Entity* e : entities) {
if (e->GetType() == "probe") {
if (Probe* p = dynamic_cast<Probe*>(e))
probes.push_back(p);
}
else if (e->GetType() == "projectile") {
projectiles.push_back(e);
}
else if (e->GetType() == "powerup") {
powerups.push_back(e);
}
}
// 各 AI を実行
for (auto it = controlled_probes.begin(); it != controlled_probes.end(); ++it) {
ComputerProbeControl* ctrl = *it;
// 自分の Probe を探す
Probe* self = nullptr;
for (Probe* p : probes) {
if (p->GetOwner() == ctrl->id) {
self = p;
break;
}
}
if (!self)
continue;
// C++ AI を実行
input_states newInputs =
ctrl->ai->Update(self, probes, projectiles, powerups, geometry);
// 入力が変わったら送信
if (newInputs != ctrl->inputs) {
ctrl->inputs = newInputs;
game_manager->SendInputState(ctrl->id, ctrl->inputs);
}
}
}
void ArtificialController::SetGeometry(CollisionMask* cm)
{
geometry = cm;
}
void ArtificialController::SetGameManager(GameManager* gm)
{
game_manager = gm;
}
ソースの一部変更
vi src/menu/widgets/ailistmodel.cpp
void AIListModel::FindScripts()
{
- std::string script_dir("scripts/");
- // always show Human option as it disables any ai scripting so that a user may play.
- scriptnames.push_back("Human");
- ++numScripts;
- char **files = PHYSFS_enumerateFiles("scripts/");
- char **i;
- std::string script;
- for (i = files; *i != NULL; i++){
- script = *i;
- if (!PHYSFS_isDirectory((script_dir+script).c_str()) && !script.compare(script.size()-3, 3, ".py")){
- scriptnames.push_back(script.substr(0,script.size()-3));
- ++numScripts;
- }
- }
- PHYSFS_freeList(files);
+ scriptnames.clear();
+ numScripts = 0;
+
+ // Human(人間プレイヤー)
+ scriptnames.push_back("Human");
+ ++numScripts;
+
+ // C++ AI を固定で追加
+ scriptnames.push_back("balderai");
+ scriptnames.push_back("randomai");
+ scriptnames.push_back("simple_aimer");
+ scriptnames.push_back("aggressive");
+ scriptnames.push_back("defensive");
+ scriptnames.push_back("sniperai");
+ numScripts += 6;
+ scriptnames.push_back("wallfollower");
+ ++numScripts;
+ scriptnames.push_back("default");
+ ++numScripts;
}vi SConstruct
includepath = ['/usr/include/python2.7','include']
includepath = ['include']
libs = Split('guichan_sdl guichan SDL SDL_image SDL_mixer SDL_gfx python2.7 physfs')
libs = Split('guichan_sdl guichan SDL SDL_image SDL_mixer SDL_gfx physfs')
SConscript('bin/scripts/test/SConscript')
#SConscript('bin/scripts/test/SConscript')
scons -c
scons
Views: 0