golang小游戏:飞机大战

编程入门 行业动态 更新时间:2024-10-10 10:26:25

golang<a href=https://www.elefans.com/category/jswz/34/1769974.html style=小游戏:飞机大战"/>

golang小游戏:飞机大战

目录

一、准备阶段:游戏引擎介绍准

安装ebiten游戏引擎

 打开窗口

二、游戏开发阶段

(1)、添加我飞机

先创建一个Object类   

 创建一个Model类

   将飞机的shipAdd()方法添加到main.go的Update()方法里,并在main.go方法里用ship调用objectDraw()方法

(2)使我方飞机去移动

在Model.go添加飞机移动的方法shipMove()

(3)添加敌方第一种飞机

enemy1Add 第一种敌方飞机生成的方法

 enmy1Move() 第一种敌方飞机的移动方法

objectSliceDraw() 遍历enemy1List切片并将里面的敌方飞机原素画出

最后将enemy1Add()和enemy1Move()添加到main.go的Update方法里,将objectSliceDraw(screen,enemy1List)添加到Draw()方法里

(4)添加我方飞机的子弹

(5)添加碰撞检测的方法

是否发生碰撞的条件

(6)给游戏添加星空背景

 运行效果

(7)添加敌方小boss及子弹

运行效果

(8)添加敌方大boss

运行效果

(9)添加我方子弹和飞机与敌方元素的碰撞

(10)游戏的暂停功能

三、游戏架构

文件目录

go在运行上有点特殊须在终端运行go run .  即运行Plane2中的所有文件

 运行效果

四、总结

源码


一、准备阶段:游戏引擎介绍准

安装ebiten游戏引擎

          制作游戏我们首先需要一个图形化界面,我使用的是ebiten,安装及其简单只需在自己终端运行以下代码,即可完成安装

go run -tags=example github/hajimehoshi/ebiten/v2/examples/2048@latest

 打开窗口

创建main.go

package mainimport (/*这段代码首先导入了必要的包,其中包括 github/hajimehoshi/ebiten/v2 和github/hajimehoshi/ebiten/v2/ebitenutil。这些包提供了 Ebiten 游戏引擎的功能和实用工具。*/"log""github/hajimehoshi/ebiten/v2""github/hajimehoshi/ebiten/v2/ebitenutil"
)
//定义了一个Game结构体 实现了游戏引擎里的Game接口
type Game struct{}//下面实现了Game结构体里三个方法 Update()  Draw()   Layout()//Update()相当与MVC里面的C control  用于将来控制我们飞机元素的添加和移动
func (g *Game) Update() error {return nil
}//Draw()相当于MVC架构里面的V view  用于渲染显示图形画面
func (g *Game) Draw(screen *ebiten.Image) {ebitenutil.DebugPrint(screen, "Hello, Plane")//画出一句话 Hello, Plane
}//设置窗口大小  宽600 高800
func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {return 600, 800
}func main() {//设置窗口大小ebiten.SetWindowSize(600, 800)//设置窗口的标题ebiten.SetWindowTitle("飞机大战")/*接着调用 ebiten.RunGame(&Game{}) 启动游戏循环,并将 &Game{} 作为参数传递给 RunGame() 函数以每秒60帧循环执行Game接口的 Update()方法 Draw()方法,以创建并运行游戏对象。如果在运行游戏过程中出现错误,将通过 log.Fatal() 打印错误信息并终止程序的执行。*/if err := ebiten.RunGame(&Game{}); err != nil {log.Fatal(err)}
}

运行效果

二、游戏开发阶段

(1)、添加我飞机

首先我们要明白我们的需求:我方飞机有自己的坐标 、可以移动 、有自己的图象属性

先创建一个Object类   

Object类里有名字  图片 X,Y坐标 HP生命值 宽高属性

init初始化方法 objectDraw()画出对象的方法

package mainimport (//"fmt""github/hajimehoshi/ebiten/v2""github/hajimehoshi/ebiten/v2/ebitenutil""log"
)
//planeHp 根据飞机名称映射飞机的生命值
var planeHp = map[string]int{"myplane":1,
}
type Object struct {name   string   //表示对象的名称image  *ebiten.Image //表示对象对应的图片X      float64//对象的X坐标Y      float64//对象的Y坐标HP   int//对象的生命值width  int//对象的宽度  将来用于碰撞检测height int//对象的高度
}
func (object *Object) init(name string, X, Y float64) {//对象的初始化方法object.name = nameobject.X = Xobject.Y = Yobject.HP = planeHp[object.name]//从planeHp map集合里映射飞机对象的生命值object.getImg()//调用getImg方法初始化对象的图片}
func (object *Object) getImg() {//使用ebitenutil.NewImageFromFile函数从文件系统中加载图像,并将图像赋值给对象的image字段img, _, err1 := ebitenutil.NewImageFromFile("image/" + object.name+".png")object.width, object.height = img.Size()//初始化对象的宽高if err1 != nil {log.Fatal(err1)}object.image = img
}//对象的图片绘画方法
func (object *Object)ObjectDraw(screen *ebiten.Image){var op_objecte ebiten.DrawImageOptions//ebiten.DrawImageOptions变量,用于配置绘制图像的选项op_objecte.GeoM.Translate(object.X, object.Y)//设置图象的位置使用screen.DrawImage方法将对象的图像绘制到目标图像上,传递了对象的图像object.image和绘制选项&op_objectescreen.DrawImage(object.image, &op_objecte)
}

 创建一个Model类

Model用于写飞机的模型 和后续飞机的移动

package main//声明我方飞机变量
var   ship *Object=nil//初始化我方飞机对象
func shipAdd(){if ship==nil {var myplane Objectmyplane.init("myplane",267,750)ship=&myplane}
}

   将飞机的shipAdd()方法添加到main.go的Update()方法里,并在main.go方法里用ship调用objectDraw()方法

       

package mainimport (/*这段代码首先导入了必要的包,其中包括 github/hajimehoshi/ebiten/v2 和github/hajimehoshi/ebiten/v2/ebitenutil。这些包提供了 Ebiten 游戏引擎的功能和实用工具。*/"log""github/hajimehoshi/ebiten/v2""github/hajimehoshi/ebiten/v2/ebitenutil"
)
//定义了一个Game结构体 实现了游戏引擎里的Game接口
type Game struct{}//下面实现了Game结构体里三个方法 Update()  Draw()   Layout()//Update()相当与MVC里面的C control  用于将来控制我们飞机元素的添加和移动
func (g *Game) Update() error {shipAdd()return nil
}//Draw()相当于MVC架构里面的V view  用于渲染显示图形画面
func (g *Game) Draw(screen *ebiten.Image) {ebitenutil.DebugPrint(screen, "Hello, Plane")//画出一句话 Hello, Planeship.ObjectDraw(screen)}//设置窗口大小  宽600 高800
func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {return 600, 800
}func main() {//设置窗口大小ebiten.SetWindowSize(600, 800)//设置窗口的标题ebiten.SetWindowTitle("飞机大战")/*接着调用 ebiten.RunGame(&Game{}) 启动游戏循环,并将 &Game{} 作为参数传递给 RunGame() 函数以每秒60帧循环执行Game接口的 Update()方法 Draw()方法,以创建并运行游戏对象。如果在运行游戏过程中出现错误,将通过 log.Fatal() 打印错误信息并终止程序的执行。*/if err := ebiten.RunGame(&Game{}); err != nil {log.Fatal(err)}
}

打开终端运行

go run main.go Model.go Object.go

运行效果

(2)使我方飞机去移动

在Model.go添加飞机移动的方法shipMove()

package mainimport ("github/hajimehoshi/ebiten/v2"
)//声明我方飞机变量
var   ship *Object=nil//初始化我方飞机对象
func shipAdd(){if ship==nil {var myplane Objectmyplane.init("myplane",267,750)ship=&myplane}
}
//飞机根据键盘移动
func shipMove(){//如果按下键盘左方向键我方飞机X减小 画面显示向左移动 if ebiten.IsKeyPressed(ebiten.KeyLeft) {ship.X -= 8if ship.X < 0 {//防止飞机飞出窗口左边界ship.X = 0}//如果按下键盘右方向键我方飞机X增大 画面显示向右移动 } else if ebiten.IsKeyPressed(ebiten.KeyRight) {ship.X+= 8if ship.X >= 534 {//防止飞机飞出窗口右边界ship.X = 534}//如果按下键盘上方向键我方飞机Y坐标增大 画面显示向上移动 }  else if ebiten.IsKeyPressed(ebiten.KeyUp) {ship.Y -= 8if ship.Y <= 0 {//防止飞机飞出窗口上边界ship.Y = 0}//如果按下键盘下方向键我方飞机Y减小 画面显示向下移动 } else if ebiten.IsKeyPressed(ebiten.KeyDown) {ship.Y += 8if ship.Y >= 750 {//防止飞机飞出窗口下边界ship.Y = 750}}
}

    在main.go 的Update()中添加shipMove()

func (g *Game) Update() error {shipAdd()shipMove()return nil
}

(3)添加敌方第一种飞机

 第一种敌方飞机x坐标位置随机初始化,且可以一直向下移动 

在Model.go中新声明一个enemy1List 切片存储我们的第一种飞机的对象

var enemy1List []*Object

enemy1Add 第一种敌方飞机生成的方法

(用随机数控制敌方飞机生成的频率和生成的位置)

func enemy1Add() {Seed(time.Now().UnixNano())//是一个用于设置随机数种子random2 := Intn(200) // 生成0到200之间的随机数if (int)(random2) == 13 {//当生成的随机数为13时添加敌方飞机,也就是说有1/200的概率添加敌方飞机1
/*
上面提到Update方法每秒运行60次,我们将enemy1Add方法加入到Update方法后,一秒被调用60次,随机数random2是13的概率为1/200,也就是说大概三秒半添加一个敌方飞机
*/var enemy1 Objectrandom1 := Intn(550) // 生成0到550之间的随机数  确定敌机生成位置enemy1.init("enemy_new1",float64(random1),0)enemy1List = append(enemy1List,&enemy1 )}}

 enmy1Move() 第一种敌方飞机的移动方法

func enemy1Move(){for _,enemy1 := range enemy1List {//遍历敌方第一钟飞机的切片enemy1.Y++//使敌方飞机的Y坐标增加表现为敌方飞机向下移动}
}

objectSliceDraw() 遍历enemy1List切片并将里面的敌方飞机原素画出

func objectSliceDraw(screen *ebiten.Image, sl []*Object){//当想画出第一种飞机时,sl参数传为ennemy1Listfor _, sli := range sl {//遍历切片var op_slice ebiten.DrawImageOptionsop_slice.GeoM.Translate(sli.X, sli.Y)//根据切片元素的x,y坐标来确定切片的位置screen.DrawImage(sli.image, &op_slice)//画出元素的图片}
}

最后将enemy1Add()和enemy1Move()添加到main.go的Update方法里,将objectSliceDraw(screen,enemy1List)添加到Draw()方法里

func (g *Game) Update() error {shipAdd()shipMove()enemy1Add()enemy1Move()return nil
}//Draw()相当于MVC架构里面的V view  用于渲染显示图形画面
func (g *Game) Draw(screen *ebiten.Image) {ebitenutil.DebugPrint(screen, "Hello, Plane")//画出一句话 Hello, Planeship.ObjectDraw(screen)objectSliceDraw(screen,enemy1List)}

运行效果

(4)添加我方飞机的子弹

在Model.go中定义切片bulletList  []*Object 用于存储飞机的子弹

在Model.go中写bulletAdd() bullletMove()分别控制我方飞机子弹的增添和移动

最后将bullletAdd()和bulletMove()添加到main.go的Update方法里,将objectSliceDraw(screen,bulletList)添加到Draw()方法里

Model.go

var bulletList []*Object //定义存储子弹的切片
func bulletAdd() {if inpututil.IsKeyJustReleased(ebiten.KeySpace) {//如果按下空格键就进入产生子弹的代码块//	按下空格子弹根据飞机确定位置var bullet1 Objectif ship.HP >= 5 {//当我方飞机的血量大于等于5时 飞机一次发射两发子弹var bullet2 Objectbullet1.init("bulletplus", ship.X+16, ship.Y)bullet2.init("bulletplus", ship.X+1, ship.Y)bulletList = append(bulletList, &bullet1)bulletList = append(bulletList, &bullet2)} else {//反之就发射一发子弹Ybullet1.init("bulletplus", ship.X+1, ship.Y-80)//ship.X+1  ,ship.Y-80 是子弹的x y 坐标使子弹恰好在飞机中间生成//子弹的坐标为 (ship.X-bullet.X)/2   ship.Y-bullet.YbulletList = append(bulletList, &bullet1)//将新生成的飞机子弹添加到切片bulletList里}}}
func bulletMove(){for _,bullet := range bulletList{bullet.Y-=5}
}

main.go

func (g *Game) Update() error {shipAdd()shipMove()enemy1Add()enemy1Move()bulletAdd()bulletMove()return nil
}//Draw()相当于MVC架构里面的V view  用于渲染显示图形画面
func (g *Game) Draw(screen *ebiten.Image) {ebitenutil.DebugPrint(screen, "Hello, Plane")//画出一句话 Hello, Planeship.ObjectDraw(screen)objectSliceDraw(screen,enemy1List)objectSliceDraw(screen,bulletList)}

运行效果 

(5)添加碰撞检测的方法

新建hit.go里面写hit()方法

package mainimport "math"
//两个物体碰撞的方法
func hit(MyList *[]*Object, enemyList *[]*Object) {//假如传的参数为&bulletList  &enemy1Listfor i, my := range *MyList {//遍历我方子弹的切片for j, enemy := range *enemyList {//遍历敌方飞机1的切片/*(x1,y1)为某个子弹的中心坐标  (x1,y2)为某个敌方飞机的中心坐标*/x1 := my.X + float64(my.width)/2y1 := my.Y + float64(my.height)/2x2 := enemy.X + float64(enemy.width)/2y2 := enemy.Y + float64(enemy.height)/2//判断是否碰撞的条件if math.Abs(x2-x1) < float64(my.width+enemy.width)/2 && math.Abs(y2-y1) < float64(my.height+enemy.height)/2 {*enemyList = append((*enemyList)[:j], (*enemyList)[j+1:]...)//发生碰撞将该飞机从切片中移除*MyList = append((*MyList)[:i], (*MyList)[i+1:]...)//发生碰撞后将该子弹从切片中移除return}}}
}

是否发生碰撞的条件

在main.go的Update()中加入

hit(&bulletList,&enemy1List)//传入我方子弹切片的地址  敌方飞机1的切片地址
//Update()相当与MVC里面的C control  用于将来控制我们飞机元素的添加和移动
func (g *Game) Update() error {shipAdd()shipMove()enemy1Add()enemy1Move()bulletAdd()bulletMove()hit(&bulletList,&enemy1List)//传入我方子弹切片的地址  敌方飞机1的切片地址return nil
}

如果看到这一步基本上游戏开发的流程已经完成了90%,剩下的几乎都是重复利用上面的代码来写新的游戏元素。

(6)给游戏添加星空背景

在Model中添加背景的创造及移动方法

var bg *Object//定义背景的对象func bgInit(){//背景产生的方法if bg==nil {var object Objectobject.init("bg",0,-700)bg=&object}
}
func bgMove() {//背景循环向下移动 营造我方飞机一直向前飞的效果bg.Y++if bg.Y >= -10 {bg.Y = -790}}

更改之后的Update(0

/*
mian.go
*/
//Update()相当与MVC里面的C control  用于将来控制我们飞机元素的添加和移动
func (g *Game) Update() error {bgInit()bgMove()shipAdd()shipMove()enemy1Add()enemy1Move()bulletAdd()bulletMove()hit(&bulletList,&enemy1List)//传入我方子弹切片的地址  敌方飞机1的切片地址return nil
}//Draw()相当于MVC架构里面的V view  用于渲染显示图形画面
func (g *Game) Draw(screen *ebiten.Image) {bg.ObjectDraw(screen)//将背景的Draw方法写在Draw的最上面一行,因为在画图时最上面的代码图象在最里面一层ship.ObjectDraw(screen)objectSliceDraw(screen,enemy1List)objectSliceDraw(screen,bulletList)}

(7)添加第二种敌机

//Model.govar enemy2List []*Object//敌机2的切片
var enemy2Bullet []*Object//敌机2子弹的切片
var count=0//记录游戏的刷新次数
// 添加第二种敌机
func enemy2Add() {Seed(time.Now().UnixNano())random2 := Intn(600) // 生成0到1000之间的随机数if (int)(random2) == 13 {//	fmt.Println("画出了敌机2")var e Objectrandom3 := Intn(300)//随机产生敌机2的x坐标e.init("enemy_new2", float64(random3), 120)enemy2List = append(enemy2List, &e)}}// 给第二种敌机添加子弹
func enemy2BulletAdd(){if count%60 == 0 {count++for _, e := range enemy2List {var enemybullet2 Objectenemybullet2.init("enemybullet3", e.X+15, e.Y+67)enemy2Bullet = append(enemy2Bullet, &enemybullet2)}}
}func enemy2BulletMove() {for _, b := range enemy2Bullet {b.Y += 1.5}
}

mian.go

//Update()相当与MVC里面的C control  用于将来控制我们飞机元素的添加和移动
func (g *Game) Update() error {count++//每执行一次Update()刷新次数加一bgInit()bgMove()shipAdd()shipMove()enemy1Add()enemy1Move()bulletAdd()bulletMove()enemy2Add()enemy2BulletAdd()enemy2BulletMove()hit(&bulletList,&enemy1List)//传入我方子弹切片的地址  敌方飞机1的切片地址return nil
}//Draw()相当于MVC架构里面的V view  用于渲染显示图形画面
func (g *Game) Draw(screen *ebiten.Image) {bg.ObjectDraw(screen)//将背景的Draw方法写在Draw的最上面一行,因为在画图时最上面的代码图象在最里面一层ship.ObjectDraw(screen)objectSliceDraw(screen,enemy1List)objectSliceDraw(screen,bulletList)objectSliceDraw(screen,enemy2List)//enemy2List 调用Draw方法objectSliceDraw(screen,enemy2Bullet)//enemy2Bullet 调用Draw方法}//设置窗口大小  宽600 高800
func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {return 600, 800
}

 运行效果

(7)添加敌方小boss及子弹

小boss的子弹可以追踪我方飞机

当我方飞机移动到子弹的上方时可以把追踪子弹消除

//Model.govar boss2List []*Object//boss2的切片
var boss2Bullet []*Object //boss2的子弹切片
func boss2Add() {Seed(time.Now().UnixNano())if count%1000 == 0 &&count!=0{ //20秒左右产生一个boss2count++random4 := Intn(400) // 生成0到200之间的随机数var boss2 Objectboss2.init("boos_new4", float64(random4), 112)boss2List = append(boss2List, &boss2)}
}
func boss2BulletAdd() {if count%200 == 0 {//生成了子弹count++for _, v := range boss2List {var bs2_bullet Objectbs2_bullet.init("enemy_bullet2", v.X+75.5, v.Y+112)boss2Bullet = append(boss2Bullet, &bs2_bullet)}}
}func boss2BulletMove() {for i, b := range boss2Bullet {//改变子弹坐标if ship.Y >= b.Y {//子弹会追踪飞机b.Y += 2} else {b.Y -= 2}if ship.X+float64(ship.width/2) >= b.X {b.X += 2} else {b.X -= 2}if b.Y+float64(b.height)-20 > ship.Y {//如果子弹在飞机的下方 子弹消失boss2Bullet = append(boss2Bullet[:i], boss2Bullet[i+1:]...)break}}
}

//main.go
func (g *Game) Update() error {count++//每执行一次Update()刷新次数加一bgInit()bgMove()shipAdd()shipMove()enemy1Add()enemy1Move()bulletAdd()bulletMove()enemy2Add()enemy2BulletAdd()enemy2BulletMove()boss2Add()boss2BulletAdd()boss2BulletMove()hit(&bulletList,&enemy1List)//传入我方子弹切片的地址  敌方飞机1的切片地址return nil
}//Draw()相当于MVC架构里面的V view  用于渲染显示图形画面
func (g *Game) Draw(screen *ebiten.Image) {bg.ObjectDraw(screen)//将背景的Draw方法写在Draw的最上面一行,因为在画图时最上面的代码图象在最里面一层ship.ObjectDraw(screen)objectSliceDraw(screen,enemy1List)objectSliceDraw(screen,bulletList)objectSliceDraw(screen,enemy2List)//enemy2List 调用Draw方法objectSliceDraw(screen,enemy2Bullet)//enemy2Bullet 调用Draw方法objectSliceDraw(screen,boss2List)//boss2List 调用Draw方法objectSliceDraw(screen,boss2Bullet)//boss2Bullet 调用Draw方法
}

运行效果

(8)添加敌方大boss

//Model.go
var boss1 *Object//定义大boss对象
var boss1Bullet []*Object//定义boss1子弹的切片
var score=0 //定义游戏分数
var boss1_speed=3.0
func Boss1Add() {if score >= 30 && boss1 == nil {//当游戏分数大于10时boss产生Seed(time.Now().UnixNano())var boss Objectx := Intn(359) + 1boss.init("boos_new1", float64(x), 10)boss1 = &boss}if boss1 != nil {//改变boss1的坐标boss1.X += boss1_speedif boss1.X <= 0 || boss1.X >= 360 {boss1_speed = -boss1_speed}}
}// 添加boss1子弹
func boss1BulletAdd() {if count%70 == 0 && boss1 != nil {count++var bos1Bullet Objectbos1Bullet.init("bullet_new2", boss1.X+110, boss1.Y+174)boss1Bullet = append(boss1Bullet, &bos1Bullet)}
}
func boss1BulletMove() {for i, bos1 := range boss1Bullet {bos1.Y += 2.0if bos1.Y > 800 {boss1Bullet = append(boss1Bullet[:i], boss1Bullet[i+1:]...)break}}}
//main.go//Update()相当与MVC里面的C control  用于将来控制我们飞机元素的添加和移动
func (g *Game) Update() error {count++//每执行一次Update()刷新次数加一bgInit()bgMove()shipAdd()shipMove()enemy1Add()enemy1Move()bulletAdd()bulletMove()enemy2Add()enemy2BulletAdd()enemy2BulletMove()boss2Add()boss2BulletAdd()boss2BulletMove()Boss1Add()boss1BulletAdd()boss1BulletMove()hit(&bulletList,&enemy1List)//传入我方子弹切片的地址  敌方飞机1的切片地址return nil
}//Draw()相当于MVC架构里面的V view  用于渲染显示图形画面
func (g *Game) Draw(screen *ebiten.Image) {bg.ObjectDraw(screen)//将背景的Draw方法写在Draw的最上面一行,因为在画图时最上面的代码图象在最里面一层ship.ObjectDraw(screen)objectSliceDraw(screen,enemy1List)objectSliceDraw(screen,bulletList)objectSliceDraw(screen,enemy2List)//enemy2List 调用Draw方法objectSliceDraw(screen,enemy2Bullet)//enemy2Bullet 调用Draw方法objectSliceDraw(screen,boss2List)//boss2List 调用Draw方法objectSliceDraw(screen,boss2Bullet)//boss2Bullet 调用Draw方法objectSliceDraw(screen,boss1Bullet)if boss1!=nil{boss1.ObjectDraw(screen)}}

运行效果

(9)添加我方子弹和飞机与敌方元素的碰撞

在hit.go中再添加一个panleHit()方法用于我方飞机与敌方子弹的碰撞,我方子弹与boss1的碰撞

//写飞机(我方飞机或boss1)与其他子弹的碰撞func planeHit(plane *Object,slice *[]*Object){for j, sl := range *slice {//遍历敌方飞机1的切片/*(x1,y1)为某个子弹的中心坐标  (x1,y2)为某个敌方飞机的中心坐标*/x1 := plane.X + float64(plane.width)/2y1 := plane.Y + float64(plane.height)/2x2 := sl.X + float64(sl.width)/2y2 := sl.Y + float64(sl.height)/2//判断是否碰撞的条件if math.Abs(x2-x1) < float64(plane.width+sl.width)/2 && math.Abs(y2-y1) < float64(plane.height+sl.height)/2 {*slice = append((*slice)[:j], (*slice)[j+1:]...)//发生碰撞将该元素从切片中移除plane.HP--if plane.HP<=0 {start=!start}return}}}

然后直接再main.go Update()中调用hit 和planeHit方法

/*main.go Update()
*/hit(&bulletList,&enemy1List)//传入我方子弹切片的地址  敌方飞机1的切片地址hit(&bulletList,&enemy2List)hit(&bulletList,&boss2List)hit(&bulletList,&boss2Bullet)hit(&bulletList,&boss1Bullet)planeHit(ship,&enemy2Bullet)planeHit(ship,&boss2Bullet)planeHit(ship,&boss1Bullet)if boss1!=nil {planeHit(boss1,&bulletList)}

然后几乎所有功能就写完了

(10)游戏的暂停功能

先说游戏如何运行的,我看到的游戏开始就是我们一直在调用Draw()方法,如果让Draw()方法一直画这一瞬间的内容那么游戏就是暂停

而让游戏只画一瞬间便是让你游戏里的所有元素的位置都不再更新,因此我们就需要让Update()里的方法都不再调用

pause()if !state {//pause()return nil}count++//每执行一次Update()刷新次数加一bgInit()shipAdd()bgMove()

三、游戏架构

文件目录

image包存放的图片

hit.go 写的是两种碰撞方法

package mainimport ("math"
)
//两个物体碰撞的方法
func hit(MyList *[]*Object, enemyList *[]*Object) {//假如传的参数为&bulletList  &enemy1Listfor i, my := range *MyList {//遍历我方子弹的切片for j, enemy := range *enemyList {//遍历敌方飞机1的切片/*(x1,y1)为某个子弹的中心坐标  (x1,y2)为某个敌方飞机的中心坐标*/x1 := my.X + float64(my.width)/2y1 := my.Y + float64(my.height)/2x2 := enemy.X + float64(enemy.width)/2y2 := enemy.Y + float64(enemy.height)/2//判断是否碰撞的条件if math.Abs(x2-x1) < float64(my.width+enemy.width)/2 && math.Abs(y2-y1) < float64(my.height+enemy.height)/2 {enemy.HP--if enemy.HP<=0{*enemyList = append((*enemyList)[:j], (*enemyList)[j+1:]...)//发生碰撞将该飞机从切片中移除}*MyList = append((*MyList)[:i], (*MyList)[i+1:]...)//发生碰撞后将该子弹从切片中移除score++return}}}
}
//写飞机(我方飞机或boss1)与其他子弹的碰撞func planeHit(plane *Object,slice *[]*Object){for j, sl := range *slice {//遍历敌方飞机1的切片/*(x1,y1)为某个子弹的中心坐标  (x1,y2)为某个敌方飞机的中心坐标*/x1 := plane.X + float64(plane.width)/2y1 := plane.Y + float64(plane.height)/2x2 := sl.X + float64(sl.width)/2y2 := sl.Y + float64(sl.height)/2//判断是否碰撞的条件if math.Abs(x2-x1) < float64(plane.width+sl.width)/2 && math.Abs(y2-y1) < float64(plane.height+sl.height)/2 {*slice = append((*slice)[:j], (*slice)[j+1:]...)//发生碰撞将该元素从切片中移除plane.HP--if plane.HP<=0 {start=!start}return}}}

mian.go 是游戏的主体,负责调用游戏元素的产生移动的方法

package mainimport ("github/hajimehoshi/ebiten/examples/resources/fonts""strconv"//"github/hajimehoshi/ebiten/ebitenutil"//"github/hajimehoshi/ebiten/examples/resources/fonts"//"github/hajimehoshi/ebiten/text"//"golang/x/image/font"//"golang/x/image/font/opentype"//// */"image/color"//. "strconv"//////"log"////"github/hajimehoshi/ebiten/v2"//"github/hajimehoshi/ebiten/v2"_ "github/hajimehoshi/ebiten/v2"_ "github/hajimehoshi/ebiten/v2/ebitenutil""github/hajimehoshi/ebiten/v2/text""golang/x/image/font"_ "golang/x/image/font""golang/x/image/font/opentype""image/color""log"_ "strconv"
)func init() {tt, err := opentype.Parse(fonts.MPlus1pRegular_ttf)if err != nil {log.Fatal(err)}const dpi = 72oneFont, err = opentype.NewFace(tt, &opentype.FaceOptions{Size:    24,DPI:     dpi,Hinting: font.HintingVertical,})if err != nil {log.Fatal(err)}
}
//定义了一个Game结构体 实现了游戏引擎里的Game接口
type Game struct{}//下面实现了Game结构体里三个方法 Update()  Draw()   Layout()//Draw()相当于MVC架构里面的V view  用于渲染显示图形画面
func (g *Game) Draw(screen *ebiten.Image) {if !start { // start=false 游戏未开始 游戏失败 游戏胜利if ship!=nil&&ship.HP<=0 {gameOver(screen)} else if boss1!=nil&&boss1.HP<=0 {victory(screen)}else {startBg(screen)}}else {if bg!=nil {bg.ObjectDraw(screen) //将背景的Draw方法写在Draw的最上面一行,因为在画图时最上面的代码图象在最里面一层}if ship!=nil {ship.ObjectDraw(screen)
}objectSliceDraw(screen, enemy1List)objectSliceDraw(screen, bulletList)objectSliceDraw(screen, enemy2List)   //enemy2List 调用Draw方法objectSliceDraw(screen, enemy2Bullet) //enemy2Bullet 调用Draw方法objectSliceDraw(screen, boss2List)    //boss2List 调用Draw方法objectSliceDraw(screen, boss2Bullet)  //boss2Bullet 调用Draw方法objectSliceDraw(screen, boss1Bullet)if boss1 != nil {boss1.ObjectDraw(screen)}//画出分数str := strconv.Itoa(score)text.Draw(screen, "score:"+str, oneFont, 10, 20, color.White)// text.Draw((screen),"score:"+str, oneFont, 10, 20, color.White)//text.Draw(screen, "score:"+str, oneFont, 10, 20, color.White)}
}
//Update()相当与MVC里面的C control  用于将来控制我们飞机元素的添加和移动
func (g *Game) Update() error {if !start {startGame()return nil}pause()if !state {//pause()return nil}count++//每执行一次Update()刷新次数加一bgInit()shipAdd()bgMove()shipMove()enemy1Add()enemy1Move()bulletAdd()bulletMove()enemy2Add()enemy2BulletAdd()enemy2BulletMove()boss2Add()boss2BulletAdd()boss2BulletMove()Boss1Add()boss1BulletAdd()boss1BulletMove()hit(&bulletList,&enemy1List)//传入我方子弹切片的地址  敌方飞机1的切片地址hit(&bulletList,&enemy2List)hit(&bulletList,&boss2List)hit(&bulletList,&boss2Bullet)hit(&bulletList,&boss1Bullet)planeHit(ship,&enemy2Bullet)planeHit(ship,&boss2Bullet)planeHit(ship,&boss1Bullet)if boss1!=nil {planeHit(boss1,&bulletList)}return nil
}//设置窗口大小  宽600 高800
func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {return 600, 800
}func main() {//设置窗口大小ebiten.SetWindowSize(600, 800)//设置窗口的标题ebiten.SetWindowTitle("飞机大战")/*接着调用 ebiten.RunGame(&Game{}) 启动游戏循环,并将 &Game{} 作为参数传递给 RunGame() 函数以每秒60帧循环执行Game接口的 Update()方法 Draw()方法,以创建并运行游戏对象。如果在运行游戏过程中出现错误,将通过 log.Fatal() 打印错误信息并终止程序的执行。*/if err := ebiten.RunGame(&Game{}); err != nil {log.Fatal(err)}
}

Model.go 写的是 游戏元素的产生移动的方法

package mainimport ("fmt""github/hajimehoshi/ebiten/v2""github/hajimehoshi/ebiten/v2/inpututil""golang/x/image/font"// "github/hajimehoshi/ebiten/v2/ebitenutil". "math/rand""time"
)//声明我方飞机变量
var   ship *Object=nil
var oneFont           font.Face//初始化我方飞机对象
func shipAdd(){if ship==nil {var myplane Objectmyplane.init("myplane",267,750)ship=&myplane}
}
//飞机根据键盘移动
func shipMove(){//如果按下键盘左方向键我方飞机X减小 画面显示向左移动if ebiten.IsKeyPressed(ebiten.KeyLeft) {ship.X -= 8if ship.X < 0 {//防止飞机飞出窗口左边界ship.X = 0}//如果按下键盘右方向键我方飞机X增大 画面显示向右移动} else if ebiten.IsKeyPressed(ebiten.KeyRight) {ship.X+= 8if ship.X >= 534 {//防止飞机飞出窗口右边界ship.X = 534}//如果按下键盘上方向键我方飞机Y坐标增大 画面显示向上移动}  else if ebiten.IsKeyPressed(ebiten.KeyUp) {ship.Y -= 8if ship.Y <= 0 {//防止飞机飞出窗口上边界ship.Y = 0}//如果按下键盘下方向键我方飞机Y减小 画面显示向下移动} else if ebiten.IsKeyPressed(ebiten.KeyDown) {ship.Y += 8if ship.Y >= 750 {//防止飞机飞出窗口下边界ship.Y = 750}}
}
var enemy1List []*Object//声明敌机1的切片用于存储敌机1的对象
func enemy1Add() {Seed(time.Now().UnixNano())//是一个用于设置随机数种子random2 := Intn(200) // 生成0到200之间的随机数if (int)(random2) == 13 {//当生成的随机数为13时添加敌方飞机,也就是说有1/200的概率添加敌方飞机1var enemy1 Objectrandom1 := Intn(550) // 生成0到550之间的随机数  确定敌机生成位置enemy1.init("enemy_new1",float64(random1),0)enemy1List = append(enemy1List,&enemy1 )}}
func enemy1Move(){for _,enemy1 := range enemy1List {//遍历敌方第一钟飞机的切片enemy1.Y++//使敌方飞机的Y坐标增加表现为敌方飞机向下移动}
}
func objectSliceDraw(screen *ebiten.Image, sl []*Object){//当想画出第一种飞机时,sl参数传为ennemy1Listfor _, sli := range sl {//遍历切片var op_slice ebiten.DrawImageOptionsop_slice.GeoM.Translate(sli.X, sli.Y)//根据切片元素的x,y坐标来确定切片的位置screen.DrawImage(sli.image, &op_slice)//画出元素的图片}
}
var bulletList []*Object //定义存储子弹的切片
func bulletAdd() {if inpututil.IsKeyJustReleased(ebiten.KeySpace) {//如果按下空格键就进入产生子弹的代码块//	按下空格子弹根据飞机确定位置var bullet1 Objectif ship.HP >= 5 {//当我方飞机的血量大于等于5时 飞机一次发射两发子弹var bullet2 Objectbullet1.init("bulletplus", ship.X+16, ship.Y)bullet2.init("bulletplus", ship.X+1, ship.Y)bulletList = append(bulletList, &bullet1)bulletList = append(bulletList, &bullet2)} else {//反之就发射一发子弹Ybullet1.init("bulletplus", ship.X+1, ship.Y-80)//ship.X+1  ,ship.Y-80 是子弹的x y 坐标使子弹恰好在飞机中间生成//子弹的坐标为 (ship.X-bullet.X)/2   ship.Y-bullet.YbulletList = append(bulletList, &bullet1)//将新生成的飞机子弹添加到切片bulletList里}}}
func bulletMove(){for _,bullet := range bulletList{bullet.Y-=5}
}
var bg *Object//定义背景的对象func bgInit(){//背景产生的方法if bg==nil {var object Objectobject.init("bg",0,-700)bg=&object}
}
func bgMove() {//背景循环向下移动 营造我方飞机一直向前飞的效果bg.Y++if bg.Y >= -10 {bg.Y = -790}}
var enemy2List []*Object//敌机2的切片
var enemy2Bullet []*Object//敌机2子弹的切片
var count=0//记录游戏的刷新次数
// 添加第二种敌机
func enemy2Add() {Seed(time.Now().UnixNano())random2 := Intn(600) // 生成0到1000之间的随机数if (int)(random2) == 13 {//	fmt.Println("画出了敌机2")var e Objectrandom3 := Intn(300)//随机产生敌机2的x坐标e.init("enemy_new2", float64(random3), 120)enemy2List = append(enemy2List, &e)}}// 给第二种敌机添加子弹
func enemy2BulletAdd(){if count%60 == 0 {count++for _, e := range enemy2List {var enemybullet2 Objectenemybullet2.init("enemybullet3", e.X+15, e.Y+67)enemy2Bullet = append(enemy2Bullet, &enemybullet2)}}
}func enemy2BulletMove() {for _, b := range enemy2Bullet {b.Y += 1.5}
}
var boss2List []*Object//boss2的切片
var boss2Bullet []*Object //boss2的子弹切片
func boss2Add() {Seed(time.Now().UnixNano())if count%1000 == 0 &&count!=0{ //20秒左右产生一个boss2count++random4 := Intn(400) // 生成0到200之间的随机数var boss2 Objectboss2.init("boos_new4", float64(random4), 112)boss2List = append(boss2List, &boss2)}
}
func boss2BulletAdd() {if count%200 == 0 {//生成了子弹count++for _, v := range boss2List {var bs2_bullet Objectbs2_bullet.init("enemy_bullet2", v.X+75.5, v.Y+112)boss2Bullet = append(boss2Bullet, &bs2_bullet)}}
}func boss2BulletMove() {for i, b := range boss2Bullet {//改变子弹坐标if ship.Y >= b.Y {//子弹会追踪飞机b.Y += 2} else {b.Y -= 2}if ship.X+float64(ship.width/2) >= b.X {b.X += 2} else {b.X -= 2}if b.Y+float64(b.height)-20 > ship.Y {//如果子弹在飞机的下方 子弹消失boss2Bullet = append(boss2Bullet[:i], boss2Bullet[i+1:]...)break}}
}
var boss1 *Object//定义大boss对象
var boss1Bullet []*Object//定义boss1子弹的切片
var score=0 //定义游戏分数
var boss1_speed=3.0
func Boss1Add() {if score >= 10 && boss1 == nil {Seed(time.Now().UnixNano())var boss Objectx := Intn(359) + 1boss.init("boos_new1", float64(x), 10)boss1 = &boss}if boss1 != nil {//改变boss1的坐标boss1.X += boss1_speedif boss1.X <= 0 || boss1.X >= 360 {boss1_speed = -boss1_speed}}
}// 添加boss1子弹
func boss1BulletAdd() {if count%70 == 0 && boss1 != nil {count++var bos1Bullet Objectbos1Bullet.init("bullet_new2", boss1.X+110, boss1.Y+174)boss1Bullet = append(boss1Bullet, &bos1Bullet)}
}
func boss1BulletMove() {for i, bos1 := range boss1Bullet {bos1.Y += 2.0if bos1.Y > 800 {boss1Bullet = append(boss1Bullet[:i], boss1Bullet[i+1:]...)break}}}
var  state             = true
var  start             = false
func pause() {if inpututil.IsKeyJustReleased(ebiten.KeyQ) {fmt.Println(state)state = !state}
}func startGame() {if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {start = trueship=nilshipAdd()score = 0count = 0boss2List = boss2List[:0]enemy2Bullet = enemy2Bullet[:0]boss2Bullet = boss2Bullet[:0]boss1 = nilenemy2List = enemy2List[:0]boss1Bullet = boss1Bullet[:0]enemy1List = enemy1List[:0]bulletList = bulletList[:0]}
}// 游戏开始界面
func startBg(screen *ebiten.Image) {var start Objectstart.init("start1", 0, 0)var op_bullet ebiten.DrawImageOptionsop_bullet.GeoM.Translate(start.X, start.Y)screen.DrawImage(start.image, &op_bullet)
}// 游戏结束
func gameOver(screen *ebiten.Image) {var over Objectover.init("失败01", 22, 150)var op_bullet ebiten.DrawImageOptionsop_bullet.GeoM.Translate(over.X, over.Y)screen.DrawImage(over.image, &op_bullet)}
func victory(screen *ebiten.Image) {var victory Objectvictory.init("奖杯01", 50, 115)var op ebiten.DrawImageOptionsop.GeoM.Translate(victory.X, victory.Y)screen.DrawImage(victory.image, &op)
}

Object.go 里面声明了一个结构体(所有的飞机元素都用这个结构体)里面有初始化,以及绘方法

package mainimport (//"fmt""github/hajimehoshi/ebiten/v2""github/hajimehoshi/ebiten/v2/ebitenutil""log"
)
//planeHp 根据飞机名称映射飞机的生命值
var planeHp = map[string]int{"myplane":3,"boos_new1":10,"boos_new4":5,
}
type Object struct {name   string   //表示对象的名称image  *ebiten.Image //表示对象对应的图片X      float64//对象的X坐标Y      float64//对象的Y坐标HP   int//对象的生命值width  int//对象的宽度  将来用于碰撞检测height int//对象的高度
}
func (object *Object) init(name string, X, Y float64) {//对象的初始化方法object.name = nameobject.X = Xobject.Y = Yobject.HP = planeHp[object.name]//从planeHp map集合里映射飞机对象的生命值object.getImg()//调用getImg方法初始化对象的图片}
func (object *Object) getImg() {//使用ebitenutil.NewImageFromFile函数从文件系统中加载图像,并将图像赋值给对象的image字段img, _, err1 := ebitenutil.NewImageFromFile("image/" + object.name+".png")object.width, object.height = img.Size()//初始化对象的宽高if err1 != nil {log.Fatal(err1)}object.image = img
}//对象的图片绘画方法
func (object *Object)ObjectDraw(screen *ebiten.Image){var op_objecte ebiten.DrawImageOptions//ebiten.DrawImageOptions变量,用于配置绘制图像的选项op_objecte.GeoM.Translate(object.X, object.Y)//设置图象的位置使用screen.DrawImage方法将对象的图像绘制到目标图像上,传递了对象的图像object.image和绘制选项&op_objectescreen.DrawImage(object.image, &op_objecte)
}
go在运行上有点特殊须在终端运行go run .  即运行Plane2中的所有文件

 运行效果

四、总结

通过做小游戏,熟悉了面向过程的开发,更加熟悉了切片指针的使用,学会了游戏引擎ebiten的使用。

 go语言小白,如果有建议欢迎大佬评论

参考链接:一起用Go做一个小游戏(上) - 知乎

源码

飞机大战源码

更多推荐

golang小游戏:飞机大战

本文发布于:2024-03-23 01:54:04,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1739068.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:小游戏   大战   飞机   golang

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!