ChatGPT4シューティングゲーム作って、とやりとりして以下のコードを書いてもらった。 UnityやUE5ほどメジャーでない(と思う)Ebitengine (v2) を題材にした。
以下はChatGPTが作ってくれたソースコード
// /////////////////////
// 1回目回答
// /////////////////////
package main
import (
"image/color"
"log"
"math"
"math/rand"
"strconv"
"time"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"github.com/hajimehoshi/ebiten/v2/inpututil"
)
const (
screenWidth = 640
screenHeight = 480
playerSize = 32
enemySize = 16
bulletSize = 8
)
type Game struct {
player *Player
enemies []*Enemy
bullets []*Bullet
score int
}
type Player struct {
x, y float64
}
type Enemy struct {
x, y float64
}
type Bullet struct {
x, y float64
}
func (g *Game) Update() error {
g.player.Update()
// 弾の生成
if inpututil.IsKeyJustPressed(ebiten.KeySpace) {
g.bullets = append(g.bullets, &Bullet{x: g.player.x + playerSize/2, y: g.player.y})
}
// 敵と弾の移動
for _, e := range g.enemies {
e.y += 2
}
for _, b := range g.bullets {
b.y -= 5
}
// 敵機の生成を少し減らす
if rand.Intn(100) < 5 {
g.enemies = append(g.enemies, &Enemy{x: float64(rand.Intn(screenWidth - enemySize)), y: -float64(enemySize)})
}
// 敵と弾の衝突判定および敵の消滅とスコア加算
for i := len(g.enemies) - 1; i >= 0; i-- {
e := g.enemies[i]
for j := len(g.bullets) - 1; j >= 0; j-- {
b := g.bullets[j]
if hit(e, b) {
g.enemies = append(g.enemies[:i], g.enemies[i+1:]...)
g.bullets = append(g.bullets[:j], g.bullets[j+1:]...)
g.score += 100
break
}
}
}
// 画面外の敵機を削除
for i := len(g.enemies) - 1; i >= 0; i-- {
e := g.enemies[i]
if e.y > screenHeight {
g.enemies = append(g.enemies[:i], g.enemies[i+1:]...)
}
}
// 画面外の弾を削除
for i := len(g.bullets) - 1; i >= 0; i-- {
b := g.bullets[i]
if b.y < 0 {
g.bullets = append(g.bullets[:i], g.bullets[i+1:]...)
}
}
// 自機と敵機の衝突判定およびスコアリセット
for i := len(g.enemies) - 1; i >= 0; i-- {
e := g.enemies[i]
if hitPlayer(g.player, e) {
g.score = 0
}
}
return nil
}
// hitPlayer関数を追加
func hitPlayer(p *Player, e *Enemy) bool {
px, py, pr := p.x+playerSize/2, p.y+playerSize/2, float64(playerSize)/2
ex, ey, er := e.x+enemySize/2, e.y+enemySize/2, float64(enemySize)/2
return math.Sqrt(math.Pow(px-ex, 2)+math.Pow(py-ey, 2)) < pr+er
}
func hit(a, b interface{}) bool {
ax, ay, ar := 0.0, 0.0, 0.0
bx, by, br := 0.0, 0.0, 0.0
switch at := a.(type) {
case *Enemy:
ax, ay, ar = at.x+enemySize/2, at.y+enemySize/2, float64(enemySize)/2
case *Bullet:
ax, ay, ar = at.x+bulletSize/2, at.y+bulletSize/2, float64(bulletSize)/2
}
switch bt := b.(type) {
case *Enemy:
bx, by, br = bt.x+enemySize/2, bt.y+enemySize/2, float64(enemySize)/2
case *Bullet:
bx, by, br = bt.x+bulletSize/2, bt.y+bulletSize/2, float64(bulletSize)/2
}
return math.Sqrt(math.Pow(ax-bx, 2)+math.Pow(ay-by, 2)) < ar+br
}
func (p *Player) Update() {
if ebiten.IsKeyPressed(ebiten.KeyLeft) {
p.x -= 4
}
if ebiten.IsKeyPressed(ebiten.KeyRight) {
p.x += 4
}
if ebiten.IsKeyPressed(ebiten.KeyUp) {
p.y -= 4
}
if ebiten.IsKeyPressed(ebiten.KeyDown) {
p.y += 4
}
if p.x < 0 {
p.x = 0
}
if p.x > screenWidth-playerSize {
p.x = screenWidth - playerSize
}
if p.y < 0 {
p.y = 0
}
if p.y > screenHeight-playerSize {
p.y = screenHeight - playerSize
}
}
// /////////////////////
// 2回目回答
// /////////////////////
func (g *Game) Draw(screen *ebiten.Image) {
screen.Fill(color.RGBA{0, 0, 0, 255})
g.player.Draw(screen)
for _, e := range g.enemies {
e.Draw(screen)
}
for _, b := range g.bullets {
b.Draw(screen)
}
// スコア表示
scoreString := "Score: " + strconv.Itoa(g.score)
ebitenutil.DebugPrintAt(screen, scoreString, 10, 30)
}
func (p *Player) Draw(screen *ebiten.Image) {
rect := ebiten.NewImage(playerSize, playerSize)
rect.Fill(color.RGBA{0, 0, 255, 255})
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(p.x, p.y)
screen.DrawImage(rect, op)
}
func (e *Enemy) Draw(screen *ebiten.Image) {
circle := ebiten.NewImage(enemySize, enemySize)
circle.Fill(color.RGBA{255, 0, 0, 255})
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(e.x, e.y)
screen.DrawImage(circle, op)
}
func (b *Bullet) Draw(screen *ebiten.Image) {
rect := ebiten.NewImage(bulletSize, bulletSize)
rect.Fill(color.RGBA{255, 255, 255, 255})
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(b.x, b.y)
screen.DrawImage(rect, op)
}
// /////////////////////
// 3回目回答
// /////////////////////
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
return screenWidth, screenHeight
}
func main() {
rand.Seed(time.Now().UnixNano())
ebiten.SetWindowSize(screenWidth*2, screenHeight*2)
ebiten.SetWindowTitle("Simple Shooter")
game := &Game{
player: &Player{
x: float64(screenWidth/2 - playerSize/2),
y: float64(screenHeight - playerSize*2),
},
enemies: []*Enemy{},
bullets: []*Bullet{},
}
if err := ebiten.RunGame(game); err != nil {
log.Fatal(err)
}
}
Makefileは自分で書いた。 WebAssemblyビルドでブラウザ上で動作するようにした。 これもChatGPT4さんによしなに書いてほしかったが、こっちは会話するより自分で書いた方が速そうだったので。
default: game1.wasm
game1.wasm: main.go
env GOOS=js GOARCH=wasm go build -o game1.wasm example.com
.PHONY: clean
clean:
rm game1.wasm
結論:普通に動作した。。。
以下のコマンドで動作確認した。 普通にゲームができていてびっくりする。 一体、どこからソースコード取ってきたのだろう。。。
$ make
$ python3 -m http.server 8080
ついでにUMLで設計書の納品もしてもらった。
クラス図は以下
・オブジェクトクラスのPosition()はどこから出てきたのだろう? ・ソースコードの全体を添付して、修正を依頼したら正しいクラス図に直してくれた。これは私の依頼の仕方が悪かったと思っている。
シーケンス図は以下
普通に設計・コーディング作業に関しては、使い道がありそう。 (著作権的には大きな懸念があるのだが、、、)