Go语言Ebiten坦克大战

下载依赖

复制代码
 go get github.com/hajimehoshi/ebiten/v2
Go 复制代码
package main

import (
	"image/color"
	"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
	tankSize     = 32
	bulletSize   = 4
	wallSize     = 32
)

// 方向定义
const (
	Up = iota
	Right
	Down
	Left
)

// 坦克结构
type Tank struct {
	x, y          float64
	dx, dy        float64
	direction     int
	speed         float64
	isPlayer      bool
	shootCooldown time.Duration
	lastShot      time.Time
}

// 子弹结构
type Bullet struct {
	x, y   float64
	dx, dy float64
	active bool
	owner  *Tank
}

// 墙体结构
type Wall struct {
	x, y         float64
	destructible bool
}

// 爆炸效果
type Explosion struct {
	x, y      float64
	radius    float64
	maxRadius float64
	active    bool
}

// 游戏状态
type Game struct {
	player     *Tank
	enemies    []*Tank
	bullets    []*Bullet
	walls      []*Wall
	explosions []*Explosion
	score      int
	gameOver   bool
}

// 初始化游戏
func NewGame() *Game {
	g := &Game{
		bullets:    make([]*Bullet, 0, 10),
		enemies:    make([]*Tank, 0, 5),
		walls:      make([]*Wall, 0),
		explosions: make([]*Explosion, 0),
	}

	// 创建玩家坦克
	g.player = &Tank{
		x:             screenWidth / 2,
		y:             screenHeight - tankSize*2,
		direction:     Up,
		speed:         2.0,
		isPlayer:      true,
		shootCooldown: 500 * time.Millisecond,
	}

	// 创建墙壁
	g.createWalls()

	// 创建初始敌人
	g.spawnEnemies(3)

	return g
}

// 创建墙壁
func (g *Game) createWalls() {
	// 边界墙
	for x := 0; x < screenWidth; x += wallSize {
		g.walls = append(g.walls, &Wall{x: float64(x), y: 0, destructible: false})
		g.walls = append(g.walls, &Wall{x: float64(x), y: screenHeight - wallSize, destructible: false})
	}

	for y := wallSize; y < screenHeight-wallSize; y += wallSize {
		g.walls = append(g.walls, &Wall{x: 0, y: float64(y), destructible: false})
		g.walls = append(g.walls, &Wall{x: screenWidth - wallSize, y: float64(y), destructible: false})
	}

	// 随机内部墙壁
	rand.Seed(time.Now().UnixNano())
	for i := 0; i < 70; i++ {
		x := float64(rand.Intn(screenWidth/wallSize-2)*wallSize + wallSize)
		y := float64(rand.Intn(screenHeight/wallSize-2)*wallSize + wallSize)

		// 避免玩家初始位置有墙
		if x > g.player.x-tankSize*2 && x < g.player.x+tankSize*2 &&
			y > g.player.y-tankSize*2 && y < g.player.y+tankSize*2 {
			continue
		}

		destructible := rand.Float64() < 0.7 // 70%可破坏墙
		g.walls = append(g.walls, &Wall{x: x, y: y, destructible: destructible})
	}
}

// 生成敌人
func (g *Game) spawnEnemies(count int) {
	for i := 0; i < count; i++ {
		x := float64(rand.Intn(screenWidth/tankSize-4)*tankSize + tankSize*2)
		y := float64(rand.Intn(screenHeight/tankSize-6)*tankSize + tankSize*2)

		enemy := &Tank{
			x:             x,
			y:             y,
			direction:     Down,
			speed:         1.0,
			isPlayer:      false,
			shootCooldown: 1500 * time.Millisecond,
		}
		g.enemies = append(g.enemies, enemy)
	}
}

// 发射子弹
func (g *Game) shoot(t *Tank) {
	now := time.Now()
	if now.Sub(t.lastShot) < t.shootCooldown {
		return
	}

	t.lastShot = now

	b := &Bullet{
		owner:  t,
		active: true,
	}

	// 根据坦克方向设置子弹初始位置和速度
	switch t.direction {
	case Up:
		b.x = t.x + tankSize/2 - bulletSize/2
		b.y = t.y
		b.dy = -5.0
	case Right:
		b.x = t.x + tankSize
		b.y = t.y + tankSize/2 - bulletSize/2
		b.dx = 5.0
	case Down:
		b.x = t.x + tankSize/2 - bulletSize/2
		b.y = t.y + tankSize
		b.dy = 5.0
	case Left:
		b.x = t.x
		b.y = t.y + tankSize/2 - bulletSize/2
		b.dx = -5.0
	}

	g.bullets = append(g.bullets, b)
}

// 创建爆炸效果
func (g *Game) createExplosion(x, y float64) {
	g.explosions = append(g.explosions, &Explosion{
		x:         x,
		y:         y,
		radius:    2,
		maxRadius: 20,
		active:    true,
	})
}

// 检查碰撞
func checkCollision(x1, y1, w1, h1, x2, y2, w2, h2 float64) bool {
	return x1 < x2+w2 &&
		x1+w1 > x2 &&
		y1 < y2+h2 &&
		y1+h1 > y2
}

// 更新游戏状态
func (g *Game) Update() error {
	if g.gameOver {
		// 游戏结束后按R键重新开始
		if inpututil.IsKeyJustPressed(ebiten.KeyR) {
			*g = *NewGame()
		}
		return nil
	}

	// 玩家控制
	g.handlePlayerInput()

	// 更新玩家位置
	g.updateTankPosition(g.player)

	// 敌人AI和更新
	g.updateEnemies()

	// 更新子弹
	g.updateBullets()

	// 更新爆炸效果
	g.updateExplosions()

	// 检查是否所有敌人都被消灭
	if len(g.enemies) == 0 {
		g.spawnEnemies(5) // 生成新一轮敌人
	}

	return nil
}

// 处理玩家输入
func (g *Game) handlePlayerInput() {
	// 重置移动方向
	g.player.dx = 0
	g.player.dy = 0

	// 方向控制
	if ebiten.IsKeyPressed(ebiten.KeyUp) || ebiten.IsKeyPressed(ebiten.KeyW) {
		g.player.direction = Up
		g.player.dy = -g.player.speed
	} else if ebiten.IsKeyPressed(ebiten.KeyRight) || ebiten.IsKeyPressed(ebiten.KeyD) {
		g.player.direction = Right
		g.player.dx = g.player.speed
	} else if ebiten.IsKeyPressed(ebiten.KeyDown) || ebiten.IsKeyPressed(ebiten.KeyS) {
		g.player.direction = Down
		g.player.dy = g.player.speed
	} else if ebiten.IsKeyPressed(ebiten.KeyLeft) || ebiten.IsKeyPressed(ebiten.KeyA) {
		g.player.direction = Left
		g.player.dx = -g.player.speed
	}

	// 射击控制
	if ebiten.IsKeyPressed(ebiten.KeySpace) {
		g.shoot(g.player)
	}
}

// 更新坦克位置(包括碰撞检测)
func (g *Game) updateTankPosition(t *Tank) {
	// 保存当前位置用于碰撞检测失败时恢复
	oldX, oldY := t.x, t.y

	// 移动坦克
	t.x += t.dx
	t.y += t.dy

	// 边界碰撞检测
	if t.x < 0 {
		t.x = 0
	} else if t.x+tankSize > screenWidth {
		t.x = screenWidth - tankSize
	}

	if t.y < 0 {
		t.y = 0
	} else if t.y+tankSize > screenHeight {
		t.y = screenHeight - tankSize
	}

	// 墙壁碰撞检测
	collided := false
	for _, w := range g.walls {
		if checkCollision(t.x, t.y, tankSize, tankSize, w.x, w.y, wallSize, wallSize) {
			collided = true
			break
		}
	}

	// 坦克之间的碰撞检测
	for _, e := range g.enemies {
		if t != e && checkCollision(t.x, t.y, tankSize, tankSize, e.x, e.y, tankSize, tankSize) {
			collided = true
			break
		}
	}

	// 如果发生碰撞,恢复到原来的位置
	if collided {
		t.x, t.y = oldX, oldY
	}
}

// 更新敌人
func (g *Game) updateEnemies() {

	// 随机改变敌人方向和射击
	for _, e := range g.enemies {
		// 随机改变方向
		if rand.Float64() < 0.01 { // 1%概率改变方向
			e.direction = rand.Intn(4)
			switch e.direction {
			case Up:
				e.dx, e.dy = 0, -e.speed
			case Right:
				e.dx, e.dy = e.speed, 0
			case Down:
				e.dx, e.dy = 0, e.speed
			case Left:
				e.dx, e.dy = -e.speed, 0
			}
		}

		// 随机射击
		if rand.Float64() < 0.002 { // 0.2%概率射击
			g.shoot(e)
		}

		// 更新敌人位置
		g.updateTankPosition(e)
	}
}

// 更新子弹
func (g *Game) updateBullets() {
	// 过滤掉不活跃的子弹
	activeBullets := make([]*Bullet, 0, len(g.bullets))

	for _, b := range g.bullets {
		if !b.active {
			continue
		}

		// 移动子弹
		b.x += b.dx
		b.y += b.dy

		// 检查是否超出屏幕
		if b.x < 0 || b.x > screenWidth || b.y < 0 || b.y > screenHeight {
			b.active = false
			continue
		}

		// 检查是否击中墙壁
		wallHit := false
		for i, w := range g.walls {
			if w.destructible && checkCollision(b.x, b.y, bulletSize, bulletSize, w.x, w.y, wallSize, wallSize) {
				// 创建爆炸效果
				g.createExplosion(w.x+wallSize/2, w.y+wallSize/2)

				// 移除被击中的墙
				g.walls = append(g.walls[:i], g.walls[i+1:]...)

				b.active = false
				wallHit = true
				break
			} else if !w.destructible && checkCollision(b.x, b.y, bulletSize, bulletSize, w.x, w.y, wallSize, wallSize) {
				// 击中不可破坏的墙
				g.createExplosion(b.x, b.y)
				b.active = false
				wallHit = true
				break
			}
		}

		if wallHit {
			continue
		}

		// 检查是否击中坦克
		if b.owner.isPlayer {
			// 玩家子弹击中敌人
			for i, e := range g.enemies {
				if checkCollision(b.x, b.y, bulletSize, bulletSize, e.x, e.y, tankSize, tankSize) {
					g.createExplosion(e.x+tankSize/2, e.y+tankSize/2)
					b.active = false
					// 移除被击中的敌人
					g.enemies = append(g.enemies[:i], g.enemies[i+1:]...)
					g.score += 100 // 加分
					break
				}
			}
		} else {
			// 敌人子弹击中玩家
			if checkCollision(b.x, b.y, bulletSize, bulletSize, g.player.x, g.player.y, tankSize, tankSize) {
				g.createExplosion(g.player.x+tankSize/2, g.player.y+tankSize/2)
				b.active = false
				g.gameOver = true // 游戏结束
				break
			}
		}

		if b.active {
			activeBullets = append(activeBullets, b)
		}
	}

	g.bullets = activeBullets
}

// 更新爆炸效果
func (g *Game) updateExplosions() {
	activeExplosions := make([]*Explosion, 0, len(g.explosions))

	for _, e := range g.explosions {
		if !e.active {
			continue
		}

		// 增大爆炸半径
		e.radius += 1.5

		// 如果爆炸达到最大半径,标记为不活跃
		if e.radius >= e.maxRadius {
			e.active = false
		} else {
			activeExplosions = append(activeExplosions, e)
		}
	}

	g.explosions = activeExplosions
}

// 绘制游戏
func (g *Game) Draw(screen *ebiten.Image) {
	// 填充背景色
	screen.Fill(color.RGBA{30, 30, 30, 255})

	// 绘制墙壁
	for _, w := range g.walls {
		var wallColor color.RGBA // 注意这里的正确语法
		if w.destructible {
			wallColor = color.RGBA{0, 128, 0, 255} // 绿色可破坏墙
		} else {
			wallColor = color.RGBA{128, 0, 0, 255} // 红色不可破坏墙
		}
		ebitenutil.DrawRect(screen, w.x, w.y, wallSize, wallSize, wallColor)
	}

	// 绘制玩家坦克
	ebitenutil.DrawRect(screen, g.player.x, g.player.y, tankSize, tankSize, color.RGBA{0, 0, 255, 255}) // 蓝色玩家坦克
	// 绘制坦克炮管
	g.drawCannon(screen, g.player)

	// 绘制敌人坦克
	for _, e := range g.enemies {
		ebitenutil.DrawRect(screen, e.x, e.y, tankSize, tankSize, color.RGBA{255, 0, 0, 255}) // 红色敌人坦克
		g.drawCannon(screen, e)
	}

	// 绘制子弹
	for _, b := range g.bullets {
		ebitenutil.DrawRect(screen, b.x, b.y, bulletSize, bulletSize, color.White)
	}

	// 绘制爆炸效果
	for _, e := range g.explosions {
		// 绘制渐变爆炸效果
		for r := 1.0; r <= e.radius; r += 2 {
			alpha := uint8(255 * (1 - r/e.maxRadius))
			c := color.RGBA{255, 165, 0, alpha} // 橙色爆炸
			ebitenutil.DrawCircle(screen, e.x, e.y, r, c)
		}
	}

	// 绘制分数
	ebitenutil.DebugPrint(screen, "Score: "+strconv.Itoa(g.score))

	// 如果游戏结束,显示游戏结束信息
	if g.gameOver {
		ebitenutil.DebugPrintAt(screen, "GAME OVER", screenWidth/2-50, screenHeight/2)
		ebitenutil.DebugPrintAt(screen, "Press R to restart", screenWidth/2-80, screenHeight/2+20)
	}
}

// 绘制坦克炮管
func (g *Game) drawCannon(screen *ebiten.Image, t *Tank) {
	// 炮管颜色
	cannonColor := color.RGBA{200, 200, 200, 255}

	// 炮管位置和大小
	var cx, cy, cw, ch float64

	switch t.direction {
	case Up:
		cx = t.x + tankSize/2 - 2
		cy = t.y - 8
		cw = 4
		ch = 12
	case Right:
		cx = t.x + tankSize - 4
		cy = t.y + tankSize/2 - 2
		cw = 12
		ch = 4
	case Down:
		cx = t.x + tankSize/2 - 2
		cy = t.y + tankSize - 4
		cw = 4
		ch = 12
	case Left:
		cx = t.x - 8
		cy = t.y + tankSize/2 - 2
		cw = 12
		ch = 4
	}

	ebitenutil.DrawRect(screen, cx, cy, cw, ch, cannonColor)
}

// 布局设置
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
	return screenWidth, screenHeight
}

func main() {
	ebiten.SetWindowSize(screenWidth, screenHeight)
	ebiten.SetWindowTitle("坦克大战")

	// 初始化随机数生成器
	rand.Seed(time.Now().UnixNano())

	game := NewGame()
	if err := ebiten.RunGame(game); err != nil {
		panic(err)
	}
}