AI時代のプログラミング
こちらより引用します。
これまでの学習は、「文法→アルゴリズム→設計」というピラミッドでした。
しかし、AI時代は、このピラミッドが逆になります。
まず、
「何を作りたいか?」という情熱(Vision)と、
「どう構成すべきか?」という設計(Architecture)から始める。
そして、
その実現に必要な文法や手続き(Implementation)は、
その都度、AIに聞けばいい。
というわけで、何を作りたいかを考えます。
リスクとリターン
こちらのゲームでは、イナヅマを避けて、落雷場所のアイテムを取ります。
アイテムは、すぐに消えてしまうので、すぐに取る必要があります。
しかし、すぐ取るにはイナヅマに近づく必要があり、危険です。
そういう意味で、このゲームは、
「ハイリスク・ハイリターン」「ローリスク・ローリターン」
の設計がよくできています。
さて、これをこのまま作るのは大変そうなので、このエッセンスを抜き出して、シンプルなゲームを設計します。
タイトルは「神回避」
でも、このタイトルはすでに使われているので「Dodge Game」(避けゲー)とします。
コンセプトは、
「ギリギリで避けるほど、ハイスコア」
「当たったら、アウト」
01 キャラクタ操作のみ
chatGPTとのやり取りは上記のとおりです。
まず、適当にゲームの概要を伝えて、最初の一歩のプログラムを書いてもらいます。
適当な絵でも、だいたい理解してくれます。
弾を避けるゲーム「Dodge Game」をprocessingで作ります。 まずは1000,1000のウィンドウを開いて、カーソルキーで円を動かしたいです。 circleクラスを用意して、位置と半径、速度を変数に持ち、移動のmoveメソッドを持たせて、動くようにしたいです。キーボードの押下状態を保持するboolean変数を使って、キーを押している間のみ動くようにしたい。弾はまだ作らなくていいです。自分が動くのみの実装。
Circle player;
boolean upPressed = false;
boolean downPressed = false;
boolean leftPressed = false;
boolean rightPressed = false;
void setup() {
size(1000, 1000);
player = new Circle(width/2, height/2, 20);
}
void draw() {
background(255);
// 操作キーに応じて速度を更新
if (upPressed) player.vy = -5;
else if (downPressed) player.vy = 5;
else player.vy = 0;
if (leftPressed) player.vx = -5;
else if (rightPressed) player.vx = 5;
else player.vx = 0;
player.move();
player.display();
}
// -------------------------------------------
// キー押下・離したときの処理
// -------------------------------------------
void keyPressed() {
if (keyCode == UP) upPressed = true;
if (keyCode == DOWN) downPressed = true;
if (keyCode == LEFT) leftPressed = true;
if (keyCode == RIGHT) rightPressed = true;
}
void keyReleased() {
if (keyCode == UP) upPressed = false;
if (keyCode == DOWN) downPressed = false;
if (keyCode == LEFT) leftPressed = false;
if (keyCode == RIGHT) rightPressed = false;
}
// -------------------------------------------
// Circleクラス
// -------------------------------------------
class Circle {
float x, y;
float r;
float vx, vy;
Circle(float x, float y, float r) {
this.x = x;
this.y = y;
this.r = r;
this.vx = 0;
this.vy = 0;
}
void move() {
x += vx;
y += vy;
// 画面外に出ないよう制限
x = constrain(x, r, width - r);
y = constrain(y, r, height - r);
}
void display() {
fill(255, 100, 100);
noStroke();
circle(x, y, r*2);
}
}
ここで、例えば、カーソルキーの↑と↓を同時に押すと、上に進んでしまうので、下記のように修正しました。
修正コードは自分で考えました。
void draw() {
background(255);
// 操作キーに応じて速度を更新
if (upPressed) player.vy -= 5;
if (downPressed) player.vy += 5;
if (leftPressed) player.vx -= 5;
if (rightPressed) player.vx += 5;
player.move();
player.display();
player.vy = 0;
player.vx = 0;
}
キーの同時押下時に相殺して動かなくなるように、しました。
以後は、これを前提に続けます。
02 周囲から飛んでくる弾の生成
chatGPTへの指示は以下のとおりです。
次は、弾を生成します。
衝突判定は、まだ考えません。
弾の発生場所は、ウィンドウを中心とした半径500の円周上のランダムな位置です。circleクラスを使いまわしてください。プレーヤーと色は分けてください。色はメンバ変数にしたほうがいいかもしれません。飛ぶ方向はその時のプレーヤーの場所に向かってください。移動速度は一定です。いずれかのタイミングで正規化処理が必要だと思います。画面外にいったら、消しましょう。arraylistがいいかな。
Circle player;
ArrayList<Circle> bullets;
boolean upPressed = false;
boolean downPressed = false;
boolean leftPressed = false;
boolean rightPressed = false;
void setup() {
size(1000, 1000);
player = new Circle(width/2, height/2, 20, true, color(255, 100, 100)); // 赤いプレーヤー
bullets = new ArrayList<Circle>();
}
void draw() {
background(255);
// 操作キーに応じて速度を更新(同時押し相殺方式)
if (upPressed) player.vy -= 5;
if (downPressed) player.vy += 5;
if (leftPressed) player.vx -= 5;
if (rightPressed) player.vx += 5;
// 一定間隔で弾を生成(ここでは6フレームごと)
if (frameCount % 6 == 0) {
spawnBullet();
}
// プレーヤー更新
player.move();
player.display();
// 速度リセット(次フレーム用)
player.vx = 0;
player.vy = 0;
// 弾の更新
for (int i = bullets.size()-1; i >= 0; i--) {
Circle b = bullets.get(i);
b.move();
b.display();
if (b.isOutOfScreen()) {
bullets.remove(i);
}
}
}
// -------------------------------------------------
// 弾生成:円周上ランダム+プレーヤー方向
// -------------------------------------------------
void spawnBullet() {
float cx = width/2;
float cy = height/2;
float radius = 500;
float ang = random(TWO_PI);
float bx = cx + radius * cos(ang);
float by = cy + radius * sin(ang);
Circle bullet = new Circle(bx, by, 8, false, color(0)); // 黒い弾
// プレーヤーへの方向ベクトルを正規化して速度に
float dx = player.x - bullet.x;
float dy = player.y - bullet.y;
float len = sqrt(dx*dx + dy*dy);
float speed = 5;
if (len != 0) {
bullet.vx = dx / len * speed;
bullet.vy = dy / len * speed;
}
bullets.add(bullet);
}
// -------------------------------------------------
// キー入力(押している間だけ true )
// -------------------------------------------------
void keyPressed() {
if (keyCode == UP) upPressed = true;
if (keyCode == DOWN) downPressed = true;
if (keyCode == LEFT) leftPressed = true;
if (keyCode == RIGHT) rightPressed = true;
}
void keyReleased() {
if (keyCode == UP) upPressed = false;
if (keyCode == DOWN) downPressed = false;
if (keyCode == LEFT) leftPressed = false;
if (keyCode == RIGHT) rightPressed = false;
}
// -------------------------------------------------
// Circle クラス(プレーヤー・弾兼用)
// -------------------------------------------------
class Circle {
float x, y;
float r;
float vx, vy;
boolean isPlayer;
color col;
Circle(float x, float y, float r, boolean isPlayer, color col) {
this.x = x;
this.y = y;
this.r = r;
this.vx = 0;
this.vy = 0;
this.isPlayer = isPlayer;
this.col = col;
}
void move() {
x += vx;
y += vy;
// プレーヤーだけ画面内に制限
if (isPlayer) {
x = constrain(x, r, width - r);
y = constrain(y, r, height - r);
}
}
void display() {
noStroke();
fill(col);
circle(x, y, r*2);
}
// 画面外に出たかどうか(弾用)
boolean isOutOfScreen() {
return (x < -r || x > width + r || y < -r || y > height + r);
}
}
イメージどおりです。
03 衝突判定とスコア計算
chatGPTへの指示は以下のとおりです。
if (frameCount % 6 == 0) {
を
if (frameCount % 60 == 0) {
に変えて、難易度を下げます。
点数は、弾との距離を計算して、プレーヤーの半径と球の半径を加算した数に10を加算してXとします。プレーヤーと弾がその距離Xよりも近づいていたら、スコアを(10-X)加算します。
プレーヤーと弾がぶつかったら、スコアを0点に戻します。
その弾は、削除してください。
そのとき、ハイスコアの保持と表示をお願いします。
Circle player;
ArrayList<Circle> bullets;
boolean upPressed = false;
boolean downPressed = false;
boolean leftPressed = false;
boolean rightPressed = false;
int score = 0;
int highScore = 0;
void setup() {
size(1000, 1000);
player = new Circle(width/2, height/2, 20, true, color(255, 100, 100)); // 赤いプレーヤー
bullets = new ArrayList<Circle>();
textAlign(LEFT, TOP);
textSize(24);
}
void draw() {
background(255);
// 操作キーに応じて速度を更新(同時押し相殺方式)
if (upPressed) player.vy -= 5;
if (downPressed) player.vy += 5;
if (leftPressed) player.vx -= 5;
if (rightPressed) player.vx += 5;
// 一定間隔で弾を生成(難易度低め:60フレームごと)
if (frameCount % 60 == 0) {
spawnBullet();
}
// プレーヤー更新
player.move();
player.display();
player.vx = 0;
player.vy = 0;
// 弾の更新(当たり判定+スコア処理付き)
for (int i = bullets.size()-1; i >= 0; i--) {
Circle b = bullets.get(i);
// ==== スコア・当たり判定 ====
float d = dist(player.x, player.y, b.x, b.y);
float sumR = player.r + b.r;
float nearThreshold = sumR + 10; // プレーヤー半径+弾半径+10
// ぶつかった(接触以下)
if (d <= sumR) {
// ハイスコア更新
if (score > highScore) {
highScore = score;
}
score = 0; // スコアを0に戻す
bullets.remove(i); // ぶつかった弾は削除
continue; // 次の弾へ
}
// 「ギリギリまで近づいた」分だけスコア加算
// d が nearThreshold より小さいほど、近づき度合いが大きい
if (d < nearThreshold) {
float add = nearThreshold - d; // 最大10点(接触ギリギリのとき)
score += int(add);
}
// ==== 移動・表示 ====
b.move();
b.display();
// 画面外に出た弾は削除
if (b.isOutOfScreen()) {
bullets.remove(i);
}
}
// ハイスコアは常に最新にしておく(念のため)
if (score > highScore) {
highScore = score;
}
// スコア表示
fill(0);
text("Score: " + score, 20, 20);
text("High Score: " + highScore, 20, 50);
}
// -------------------------------------------------
// 弾生成:円周上ランダム+プレーヤー方向
// -------------------------------------------------
void spawnBullet() {
float cx = width/2;
float cy = height/2;
float radius = 500;
float ang = random(TWO_PI);
float bx = cx + radius * cos(ang);
float by = cy + radius * sin(ang);
Circle bullet = new Circle(bx, by, 8, false, color(0)); // 黒い弾
// プレーヤーへの方向ベクトルを正規化して速度に
float dx = player.x - bullet.x;
float dy = player.y - bullet.y;
float len = sqrt(dx*dx + dy*dy);
float speed = 5;
if (len != 0) {
bullet.vx = dx / len * speed;
bullet.vy = dy / len * speed;
}
bullets.add(bullet);
}
// -------------------------------------------------
// キー入力
// -------------------------------------------------
void keyPressed() {
if (keyCode == UP) upPressed = true;
if (keyCode == DOWN) downPressed = true;
if (keyCode == LEFT) leftPressed = true;
if (keyCode == RIGHT) rightPressed = true;
}
void keyReleased() {
if (keyCode == UP) upPressed = false;
if (keyCode == DOWN) downPressed = false;
if (keyCode == LEFT) leftPressed = false;
if (keyCode == RIGHT) rightPressed = false;
}
// -------------------------------------------------
// Circle クラス(プレーヤー・弾兼用)
// -------------------------------------------------
class Circle {
float x, y;
float r;
float vx, vy;
boolean isPlayer;
color col;
Circle(float x, float y, float r, boolean isPlayer, color col) {
this.x = x;
this.y = y;
this.r = r;
this.vx = 0;
this.vy = 0;
this.isPlayer = isPlayer;
this.col = col;
}
void move() {
x += vx;
y += vy;
// プレーヤーだけ画面内に制限
if (isPlayer) {
x = constrain(x, r, width - r);
y = constrain(y, r, height - r);
}
}
void display() {
noStroke();
fill(col);
circle(x, y, r*2);
}
// 画面外に出たかどうか(弾用)
boolean isOutOfScreen() {
return (x < -r || x > width + r || y < -r || y > height + r);
}
}
04 スレスレの可視化
点滅します。
目を近づけすぎないこと
点滅に弱い人は、試さないこと。
スレスレであるかが、わかりにくいので、スレスレ度合いに応じて、背景色が変化するようにしました。ちょっと目が痛いです。
chatGPTへの指示は以下のとおりです。
if (frameCount % 20 == 0) {
こうして、60から20に変更
float nearThreshold = sumR + 20; // プレーヤー半径+弾半径+20 調整
こうする。10から20に変更
点数が加算されているときは、backgroundを赤くしたい。
少しの加算なら、少し赤く。
多い加算なら、かなり赤く。
スコアの表示位置の調整をした、最終プログラムが以下です。
Circle player;
ArrayList<Circle> bullets;
boolean upPressed = false;
boolean downPressed = false;
boolean leftPressed = false;
boolean rightPressed = false;
int score = 0;
int highScore = 0;
float flashStrength = 0; // 背景赤の強さ(0〜255)
void setup() {
size(1000, 1000);
player = new Circle(width/2, height/2, 20, true, color(255, 100, 100));
bullets = new ArrayList<Circle>();
textAlign(LEFT, TOP);
textSize(24);
}
void draw() {
// 背景(スコア加算があるほど赤く)
background(255, 255 - flashStrength, 255 - flashStrength * 0.7);
flashStrength *= 0.9; // 徐々にフェードアウト
// 操作キー
if (upPressed) player.vy -= 5;
if (downPressed) player.vy += 5;
if (leftPressed) player.vx -= 5;
if (rightPressed) player.vx += 5;
// 弾生成間隔(20フレーム)
if (frameCount % 20 == 0) {
spawnBullet();
}
player.move();
player.display();
player.vx = 0;
player.vy = 0;
// スコア加算の合計をこのフレームで計算
float totalAdd = 0;
for (int i = bullets.size()-1; i >= 0; i--) {
Circle b = bullets.get(i);
float d = dist(player.x, player.y, b.x, b.y);
float sumR = player.r + b.r;
float nearThreshold = sumR + 20; // 距離20以内を判定範囲
if (d <= sumR) {
// 接触:スコアリセット・弾削除
if (score > highScore) highScore = score;
score = 0;
bullets.remove(i);
continue;
}
// 距離が近いほどスコア加算
if (d < nearThreshold) {
float add = nearThreshold - d;
score += int(add);
totalAdd += add; // 背景色用に蓄積
}
b.move();
b.display();
if (b.isOutOfScreen()) bullets.remove(i);
}
if (score > highScore) highScore = score;
// 背景赤さ更新(加算量に応じて強く)
flashStrength = constrain(totalAdd * 10, 0, 255);
// スコア表示
fill(0);
text(score, player.x+20, player.y+20);
text("High Score: " + highScore, 20, 50);
}
// -------------------------------------------------
void spawnBullet() {
float cx = width/2;
float cy = height/2;
float radius = 500;
float ang = random(TWO_PI);
float bx = cx + radius * cos(ang);
float by = cy + radius * sin(ang);
Circle bullet = new Circle(bx, by, 8, false, color(0));
float dx = player.x - bullet.x;
float dy = player.y - bullet.y;
float len = sqrt(dx*dx + dy*dy);
float speed = 5;
if (len != 0) {
bullet.vx = dx / len * speed;
bullet.vy = dy / len * speed;
}
bullets.add(bullet);
}
// -------------------------------------------------
void keyPressed() {
if (keyCode == UP) upPressed = true;
if (keyCode == DOWN) downPressed = true;
if (keyCode == LEFT) leftPressed = true;
if (keyCode == RIGHT) rightPressed = true;
}
void keyReleased() {
if (keyCode == UP) upPressed = false;
if (keyCode == DOWN) downPressed = false;
if (keyCode == LEFT) leftPressed = false;
if (keyCode == RIGHT) rightPressed = false;
}
// -------------------------------------------------
class Circle {
float x, y;
float r;
float vx, vy;
boolean isPlayer;
color col;
Circle(float x, float y, float r, boolean isPlayer, color col) {
this.x = x;
this.y = y;
this.r = r;
this.vx = 0;
this.vy = 0;
this.isPlayer = isPlayer;
this.col = col;
}
void move() {
x += vx;
y += vy;
if (isPlayer) {
x = constrain(x, r, width - r);
y = constrain(y, r, height - r);
}
}
void display() {
noStroke();
fill(col);
circle(x, y, r*2);
}
boolean isOutOfScreen() {
return (x < -r || x > width + r || y < -r || y > height + r);
}
}
まとめ
リスクとリターンの設計思想で、AIを使ったゲーム「Dodge Game」を作りました。
情熱と設計を人間が行い、
文法はAIにまかせました。
本稿は、これで終わりです。
グラフィックを差し替えていけば、それなりなゲームの体裁は整うでしょう。
簡単なアレンジも、AI任せで、行けるはずです。


