下载依赖
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)
}
}
