一起用Go做一個(gè)小游戲(中):飛機(jī)大戰(zhàn)
限制飛船的活動(dòng)范圍
上一篇文章還留了個(gè)尾巴,細(xì)心的同學(xué)應(yīng)該發(fā)現(xiàn)了:飛船可以移動(dòng)出屏幕?。?!現(xiàn)在我們就來限制一下飛船的移動(dòng)范圍。我們規(guī)定飛船可以左右超過半個(gè)身位,如下圖所示:

很容易計(jì)算得出,左邊位置的x坐標(biāo)為:
x?=?-W2/2
右邊位置的坐標(biāo)為:
x?=?W1?-?W2/2
修改input.go的代碼如下:
func?(i?*Input)?Update(ship?*Ship,?cfg?*Config)?{
??if?ebiten.IsKeyPressed(ebiten.KeyLeft)?{
????ship.x?-=?cfg.ShipSpeedFactor
????if?ship.x?<?-float64(ship.width)/2?{
??????ship.x?=?-float64(ship.width)?/?2
????}
??}?else?if?ebiten.IsKeyPressed(ebiten.KeyRight)?{
????ship.x?+=?cfg.ShipSpeedFactor
????if?ship.x?>?float64(cfg.ScreenWidth)-float64(ship.width)/2?{
??????ship.x?=?float64(cfg.ScreenWidth)?-?float64(ship.width)/2
????}
??}
}
運(yùn)行結(jié)果如下:

發(fā)射子彈
我們不用另外準(zhǔn)備子彈的圖片,直接畫一個(gè)矩形就ok。為了可以靈活控制,我們將子彈的寬、高、顏色以及速率都用配置文件來控制:
{
??"bulletWidth":?3,
??"bulletHeight":?15,
??"bulletSpeedFactor":?2,
??"bulletColor":?{
????"r":?80,
????"g":?80,
????"b":?80,
????"a":?255
??}
}
新增一個(gè)文件bullet.go,定義子彈的結(jié)構(gòu)類型和New方法:
type?Bullet?struct?{
??image???????*ebiten.Image
??width???????int
??height??????int
??x???????????float64
??y???????????float64
??speedFactor?float64
}
func?NewBullet(cfg?*Config,?ship?*Ship)?*Bullet?{
??rect?:=?image.Rect(0,?0,?cfg.BulletWidth,?cfg.BulletHeight)
??img?:=?ebiten.NewImageWithOptions(rect,?nil)
??img.Fill(cfg.BulletColor)
??return?&Bullet{
????image:???????img,
????width:???????cfg.BulletWidth,
????height:??????cfg.BulletHeight,
????x:???????????ship.x?+?float64(ship.width-cfg.BulletWidth)/2,
????y:???????????float64(cfg.ScreenHeight?-?ship.height?-?cfg.BulletHeight),
????speedFactor:?cfg.BulletSpeedFactor,
??}
}
首先根據(jù)配置的寬高創(chuàng)建一個(gè)rect對(duì)象,然后調(diào)用ebiten.NewImageWithOptions創(chuàng)建一個(gè)*ebiten.Image對(duì)象。
子彈都是從飛船頭部發(fā)出的,所以它的橫坐標(biāo)等于飛船中心的橫坐標(biāo),左上角的縱坐標(biāo)=屏幕高度-飛船高-子彈高。
隨便增加子彈的繪制方法:
func?(bullet?*Bullet)?Draw(screen?*ebiten.Image)?{
??op?:=?&ebiten.DrawImageOptions{}
??op.GeoM.Translate(bullet.x,?bullet.y)
??screen.DrawImage(bullet.image,?op)
}
我們?cè)贕ame對(duì)象中增加一個(gè)map來管理子彈:
type?Game?struct?{
??//?-------省略-------
??bullets?map[*Bullet]struct{}
}
func?NewGame()?*Game?{
??return?&Game{
????//?-------省略-------
????bullets:?make(map[*Bullet]struct{}),
??}
}
然后在Draw方法中,我們需要將子彈也繪制出來:
func?(g?*Game)?Draw(screen?*ebiten.Image)?{
??screen.Fill(g.cfg.BgColor)
??g.ship.Draw(screen)
??for?bullet?:=?range?g.bullets?{
????bullet.Draw(screen)
??}
}
子彈位置如何更新呢?在Game.Update中更新,與飛船類似,只是飛船只能水平移動(dòng),而子彈只能垂直移動(dòng)。
func?(g?*Game)?Update()?error?{
??for?bullet?:=?range?g.bullets?{
????bullet.y?-=?bullet.speedFactor
??}
??//?-------省略-------
}
子彈的更新、繪制邏輯都完成了,可是我們還沒有子彈!現(xiàn)在我們就來實(shí)現(xiàn)按空格發(fā)射子彈的功能。我們需要在Input.Update方法中判斷空格鍵是否按下,由于該方法需要訪問Game對(duì)象的多個(gè)字段,干脆傳入Game對(duì)象:
func?(i?*Input)?Update(g?*Game)?{
??if?ebiten.IsKeyPressed(ebiten.KeyLeft)?{
????//?-------省略-------
??}?else?if?ebiten.IsKeyPressed(ebiten.KeyRight)?{
????//?-------省略-------
??}?else?if?ebiten.IsKeyPressed(ebiten.KeySpace)?{
????bullet?:=?NewBullet(g.cfg,?g.ship)
????g.addBullet(bullet)
??}
}
給Game對(duì)象增加一個(gè)addBullet方法:
func?(g?*Game)?addBullet(bullet?*Bullet)?{
??g.bullets[bullet]?=?struct{}{}
}
目前有兩個(gè)問題:
-
無法一邊移動(dòng)一邊發(fā)射,仔細(xì)看看
Input.Update方法中的代碼,你能發(fā)現(xiàn)什么問題嗎? - 子彈太多了,我們想要限制子彈的數(shù)量。
下面來逐一解決這些問題。
第一個(gè)問題很好解決,因?yàn)樵贙eyLeft/KeyRight/KeySpace這三個(gè)判斷中我們用了if-else。這樣會(huì)優(yōu)先處理移動(dòng)的操作。將KeySpace移到一個(gè)單獨(dú)的if中即可:
func?(i?*Input)?Update(g?*Game)?{
??//?-------省略-------
??if?ebiten.IsKeyPressed(ebiten.KeySpace)?{
????bullet?:=?NewBullet(g.cfg,?g.ship)
????g.addBullet(bullet)
??}
}
第二個(gè)問題,在配置中,增加同時(shí)最多存在的子彈數(shù):
{
??"maxBulletNum":?10
}
type?Config?struct?{
??MaxBulletNum??????int????????`json:"maxBulletNum"`
}
然后我們?cè)?code style="font-size:14px;background-color:rgba(27,31,35,.05);font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;color:rgb(239,112,96);">Input.Update方法中判斷,如果目前存在的子彈數(shù)小于MaxBulletNum才能創(chuàng)建新的子彈:
if?ebiten.IsKeyPressed(ebiten.KeySpace)?{
??if?len(g.bullets)?<?cfg.MaxBulletNum?{
????bullet?:=?NewBullet(g.cfg,?g.ship)
????g.addBullet(bullet)
??}
}
再次運(yùn)行:

數(shù)量好像被限制了,但是不是我們配置的10。原來Input.Update()的調(diào)用間隔太短了,導(dǎo)致我們一次space按鍵會(huì)發(fā)射多個(gè)子彈。我們可以控制兩個(gè)子彈之間的時(shí)間間隔。同樣用配置文件來控制(單位毫秒):
{
??"bulletInterval":?50
}
type?Config?struct?{
??BulletInterval????int64??????`json:"bulletInterval"`
}
在Input結(jié)構(gòu)中增加一個(gè)lastBulletTime字段記錄上次發(fā)射子彈的時(shí)間:
type?Input?struct?{
??lastBulletTime?time.Time
}
距離上次發(fā)射子彈的時(shí)間大于BulletInterval毫秒,才能再次發(fā)射,發(fā)射成功之后更新時(shí)間:
func?(i?*Input)?Update(g?*Game)?{
??//?-------省略-------
??if?ebiten.IsKeyPressed(ebiten.KeySpace)?{
????if?len(g.bullets)?<?g.cfg.MaxBulletNum?&&
??????time.Now().Sub(i.lastBulletTime).Milliseconds()?>?g.cfg.BulletInterval?{
??????bullet?:=?NewBullet(g.cfg,?g.ship)
??????g.addBullet(bullet)
??????i.lastBulletTime?=?time.Now()
????}
??}
}
運(yùn)行:

又出現(xiàn)了一個(gè)問題,10個(gè)子彈飛出屏幕外之后還是不能發(fā)射子彈。我們需要把離開屏幕的子彈刪除。這適合在Game.Update函數(shù)中做:
func?(g?*Game)?Update()?error?{
??g.input.Update(g)
??for?bullet?:=?range?g.bullets?{
????if?bullet.outOfScreen()?{
??????delete(g.bullets,?bullet)
????}
??}
??return?nil
}
為了Bullet添加判斷是否處于屏幕外的方法:
func?(bullet?*Bullet)?outOfScreen()?bool?{
??return?bullet.y?<?-float64(bullet.height)
}
再次運(yùn)行:

外星人來了
外星人圖片如下:

同飛船一樣,編寫Alien類,添加繪制自己的方法:
type?Alien?struct?{
??image???????*ebiten.Image
??width???????int
??height??????int
??x???????????float64
??y???????????float64
??speedFactor?float64
}
func?NewAlien(cfg?*Config)?*Alien?{
??img,?_,?err?:=?ebitenutil.NewImageFromFile("../images/alien.png")
??if?err?!=?nil?{
????log.Fatal(err)
??}
??width,?height?:=?img.Size()
??return?&Alien{
????image:???????img,
????width:???????width,
????height:??????height,
????x:???????????0,
????y:???????????0,
????speedFactor:?cfg.AlienSpeedFactor,
??}
}
func?(alien?*Alien)?Draw(screen?*ebiten.Image)?{
??op?:=?&ebiten.DrawImageOptions{}
??op.GeoM.Translate(alien.x,?alien.y)
??screen.DrawImage(alien.image,?op)
}
游戲開始時(shí)需要?jiǎng)?chuàng)建一組外星人,計(jì)算一行可以容納多少個(gè)外星人,考慮到左右各留一定的空間,兩個(gè)外星人之間留一點(diǎn)空間。在游戲一開始就創(chuàng)建一組外星人:
type?Game?struct?{
??//?Game結(jié)構(gòu)中的map用來存儲(chǔ)外星人對(duì)象
??aliens??map[*Alien]struct{}
}
func?NewGame()?*Game?{
??g?:=?&Game{
????//?創(chuàng)建map
????aliens:??make(map[*Alien]struct{}),
??}
??//?調(diào)用?CreateAliens?創(chuàng)建一組外星人
??g.CreateAliens()
??return?g
}
func?(g?*Game)?CreateAliens()?{
??alien?:=?NewAlien(g.cfg)
??availableSpaceX?:=?g.cfg.ScreenWidth?-?2*alien.width
??numAliens?:=?availableSpaceX?/?(2?*?alien.width)
??for?i?:=?0;?i?<?numAliens;?i++?{
????alien?=?NewAlien(g.cfg)
????alien.x?=?float64(alien.width?+?2*alien.width*i)
????g.addAlien(alien)
??}
}
左右各留一個(gè)外星人寬度的空間:
availableSpaceX?:=?g.cfg.ScreenWidth?-?2*alien.width
然后,兩個(gè)外星人之間留一個(gè)外星人寬度的空間。所以一行可以創(chuàng)建的外星人的數(shù)量為:
numAliens?:=?availableSpaceX?/?(2?*?alien.width)
創(chuàng)建一組外星人,依次排列。
同樣地,我們需要在Game.Draw方法中添加繪制外星人的代碼:
func?(g?*Game)?Draw(screen?*ebiten.Image)?{
??//?-------省略-------
??for?alien?:=?range?g.aliens?{
????alien.Draw(screen)
??}
}
運(yùn)行:

再創(chuàng)建兩行:
func?(g?*Game)?CreateAliens()?{
??//?-------省略-------
??for?row?:=?0;?row?<?2;?row++?{
????for?i?:=?0;?i?<?numAliens;?i++?{
??????alien?=?NewAlien(g.cfg)
??????alien.x?=?float64(alien.width?+?2*alien.width*i)
??????alien.y?=?float64(alien.height*row)?*?1.5
??????g.addAlien(alien)
????}
??}
}
讓外星人都起來,同樣地還是在Game.Update方法中更新位置:
func?(g?*Game)?Update()?error?{
??//?-------省略-------
??for?alien?:=?range?g.aliens?{
????alien.y?+=?alien.speedFactor
??}
??//?-------省略-------
}

射擊??!
當(dāng)前子彈碰到外星人直接穿過去了,我們希望能擊殺外星人。這需要檢查子彈和外星人之間的碰撞。我們新增一個(gè)文件collision.go,并編寫檢查子彈與外星人是否碰撞的函數(shù)。這里我采用最直觀的碰撞檢測(cè)方法,即子彈的4個(gè)頂點(diǎn)只要有一個(gè)位于外星人矩形中,就認(rèn)為它們碰撞。
//?CheckCollision?檢查子彈和外星人之間是否有碰撞
func?CheckCollision(bullet?*Bullet,?alien?*Alien)?bool?{
??alienTop,?alienLeft?:=?alien.y,?alien.x
??alienBottom,?alienRight?:=?alien.y+float64(alien.height),?alien.x+float64(alien.width)
??//?左上角
??x,?y?:=?bullet.x,?bullet.y
??if?y?>?alienTop?&&?y?<?alienBottom?&&?x?>?alienLeft?&&?x?<?alienRight?{
????return?true
??}
??//?右上角
??x,?y?=?bullet.x+float64(bullet.width),?bullet.y
??if?y?>?alienTop?&&?y?<?alienBottom?&&?x?>?alienLeft?&&?x?<?alienRight?{
????return?true
??}
??//?左下角
??x,?y?=?bullet.x,?bullet.y+float64(bullet.height)
??if?y?>?alienTop?&&?y?<?alienBottom?&&?x?>?alienLeft?&&?x?<?alienRight?{
????return?true
??}
??//?右下角
??x,?y?=?bullet.x+float64(bullet.width),?bullet.y+float64(bullet.height)
??if?y?>?alienTop?&&?y?<?alienBottom?&&?x?>?alienLeft?&&?x?<?alienRight?{
????return?true
??}
??return?false
}
接著我們?cè)?code style="font-size:14px;background-color:rgba(27,31,35,.05);font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;color:rgb(239,112,96);">Game.Update方法中調(diào)用這個(gè)方法,并且將碰撞的子彈和外星人刪除。
func?(g?*Game)?CheckCollision()?{
??for?alien?:=?range?g.aliens?{
????for?bullet?:=?range?g.bullets?{
??????if?CheckCollision(bullet,?alien)?{
????????delete(g.aliens,?alien)
????????delete(g.bullets,?bullet)
??????}
????}
??}
}
func?(g?*Game)?Update()?error?{
??//?-------省略-------
??g.CheckCollision()
??//?-------省略-------
??return?nil
}
注意將碰撞檢測(cè)放在位置更新之后。運(yùn)行:

增加主界面和結(jié)束界面
現(xiàn)在一旦運(yùn)行程序,外星人們就開始運(yùn)動(dòng)了。我們想要增加一個(gè)按下空格鍵才開始的功能,并且游戲結(jié)束之后,我們也希望能顯示一個(gè)Game Over的界面。首先,我們定義幾個(gè)常量,表示游戲當(dāng)前所處的狀態(tài):
const?(
??ModeTitle?Mode?=?iota
??ModeGame
??ModeOver
)
Game結(jié)構(gòu)中需要增加mode字段表示當(dāng)前游戲所處的狀態(tài):
type?Game?struct?{
??mode????Mode
??//?...
}
為了顯示開始界面,涉及到文字的顯示,文字顯示和字體處理起來都比較麻煩。ebitengine內(nèi)置了一些字體,我們可以據(jù)此創(chuàng)建幾個(gè)字體對(duì)象:
var?(
??titleArcadeFont?font.Face
??arcadeFont??????font.Face
??smallArcadeFont?font.Face
)
func?(g?*Game)?CreateFonts()?{
??tt,?err?:=?opentype.Parse(fonts.PressStart2P_ttf)
??if?err?!=?nil?{
????log.Fatal(err)
??}
??const?dpi?=?72
??titleArcadeFont,?err?=?opentype.NewFace(tt,?&opentype.FaceOptions{
????Size:????float64(g.cfg.TitleFontSize),
????DPI:?????dpi,
????Hinting:?font.HintingFull,
??})
??if?err?!=?nil?{
????log.Fatal(err)
??}
??arcadeFont,?err?=?opentype.NewFace(tt,?&opentype.FaceOptions{
????Size:????float64(g.cfg.FontSize),
????DPI:?????dpi,
????Hinting:?font.HintingFull,
??})
??if?err?!=?nil?{
????log.Fatal(err)
??}
??smallArcadeFont,?err?=?opentype.NewFace(tt,?&opentype.FaceOptions{
????Size:????float64(g.cfg.SmallFontSize),
????DPI:?????dpi,
????Hinting:?font.HintingFull,
??})
??if?err?!=?nil?{
????log.Fatal(err)
??}
}
fonts.PressStart2P_ttf就是ebitengine提供的字體。創(chuàng)建字體的方法一般在需要的時(shí)候微調(diào)即可。將創(chuàng)建外星人和字體封裝在Game的init方法中:
func?(g?*Game)?init()?{
??g.CreateAliens()
??g.CreateFonts()
}
func?NewGame()?*Game?{
??//?...
??g.init()
??return?g
}
啟動(dòng)時(shí)游戲處于ModeTitle狀態(tài),處于ModeTitle和ModeOver時(shí)只需要在屏幕上顯示一些文字即可。只有在ModeGame狀態(tài)才需要顯示飛船和外星人:
func?(g?*Game)?Draw(screen?*ebiten.Image)?{
??screen.Fill(g.cfg.BgColor)
??var?titleTexts?[]string
??var?texts?[]string
??switch?g.mode?{
??case?ModeTitle:
????titleTexts?=?[]string{"ALIEN?INVASION"}
????texts?=?[]string{"",?"",?"",?"",?"",?"",?"",?"PRESS?SPACE?KEY",?"",?"OR?LEFT?MOUSE"}
??case?ModeGame:
????g.ship.Draw(screen)
????for?bullet?:=?range?g.bullets?{
??????bullet.Draw(screen)
????}
????for?alien?:=?range?g.aliens?{
??????alien.Draw(screen)
????}
??case?ModeOver:
????texts?=?[]string{"",?"GAME?OVER!"}
??}
??for?i,?l?:=?range?titleTexts?{
????x?:=?(g.cfg.ScreenWidth?-?len(l)*g.cfg.TitleFontSize)?/?2
????text.Draw(screen,?l,?titleArcadeFont,?x,?(i+4)*g.cfg.TitleFontSize,?color.White)
??}
??for?i,?l?:=?range?texts?{
????x?:=?(g.cfg.ScreenWidth?-?len(l)*g.cfg.FontSize)?/?2
????text.Draw(screen,?l,?arcadeFont,?x,?(i+4)*g.cfg.FontSize,?color.White)
??}
}
在Game.Update方法中,我們判斷在ModeTitle狀態(tài)下按下空格,鼠標(biāo)左鍵游戲開始,切換為ModeGame狀態(tài)。游戲結(jié)束時(shí)切換為GameOver狀態(tài),在GameOver狀態(tài)后按下空格或鼠標(biāo)左鍵即重新開始游戲。
func?(g?*Game)?Update()?error?{
??switch?g.mode?{
??case?ModeTitle:
????if?g.input.IsKeyPressed()?{
??????g.mode?=?ModeGame
????}
??case?ModeGame:
????for?bullet?:=?range?g.bullets?{
??????bullet.y?-=?bullet.speedFactor
????}
????for?alien?:=?range?g.aliens?{
??????alien.y?+=?alien.speedFactor
????}
????g.input.Update(g)
????g.CheckCollision()
????for?bullet?:=?range?g.bullets?{
??????if?bullet.outOfScreen()?{
????????delete(g.bullets,?bullet)
??????}
????}
??case?ModeOver:
????if?g.input.IsKeyPressed()?{
??????g.init()
??????g.mode?=?ModeTitle
????}
??}
??return?nil
}
input.go中增加IsKeyPressed方法判斷是否有空格或鼠標(biāo)左鍵按下。
判斷游戲勝負(fù)
我們規(guī)定如果擊殺所有外星人則游戲勝利,有3個(gè)外星人移出屏幕外或者碰撞到飛船則游戲失敗。
首先增加一個(gè)字段failCount用于記錄移出屏幕外的外星人數(shù)量和與飛船碰撞的外星人數(shù)量之和:
type?Game?struct?{
??//?-------省略-------
??failCount?int?//?被外星人碰撞和移出屏幕的外星人數(shù)量之和
}
然后我們?cè)?code style="font-size:14px;background-color:rgba(27,31,35,.05);font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;color:rgb(239,112,96);">Game.Update方法中檢測(cè)外星人是否移出屏幕,以及是否與飛船碰撞:
for?alien?:=?range?g.aliens?{
??if?alien.outOfScreen(g.cfg)?{
????g.failCount++
????delete(g.aliens,?alien)
????continue
??}
??if?CheckCollision(alien,?g.ship)?{
????g.failCount++
????delete(g.aliens,?alien)
????continue
??}
}
這里有一個(gè)問題,還記得嗎?我們前面編寫的CheckCollision函數(shù)接受的參數(shù)類型是*Alien和*Bullet,這里我們需要重復(fù)編寫接受參數(shù)為*Ship和*Alien的函數(shù)嗎?不用!
我們將表示游戲中的實(shí)體對(duì)象抽象成一個(gè)GameObject結(jié)構(gòu):
type?GameObject?struct?{
??width??int
??height?int
??x??????float64
??y??????float64
}
func?(gameObj?*GameObject)?Width()?int?{
??return?gameObj.width
}
func?(gameObj?*GameObject)?Height()?int?{
??return?gameObj.height
}
func?(gameObj?*GameObject)?X()?float64?{
??return?gameObj.x
}
func?(gameObj?*GameObject)?Y()?float64?{
??return?gameObj.y
}
然后定義一個(gè)接口Entity:
type?Entity?interface?{
??Width()?int
??Height()?int
??X()?float64
??Y()?float64
}
最后讓我們游戲中的實(shí)體內(nèi)嵌這個(gè)GameObject對(duì)象,即可自動(dòng)實(shí)現(xiàn)Entity接口:
type?Alien?struct?{
??GameObject
??image???????*ebiten.Image
??speedFactor?float64
}
這樣CheckCollision即可改為接受兩個(gè)Entity接口類型的參數(shù):
//?CheckCollision?兩個(gè)物體之間是否碰撞
func?CheckCollision(entityA,?entityB?Entity)?bool?{
??top,?left?:=?entityA.Y(),?entityA.X()
??bottom,?right?:=?entityA.Y()+float64(entityA.Height()),?entityA.X()+float64(entityA.Width())
??//?左上角
??x,?y?:=?entityB.X(),?entityB.Y()
??if?y?>?top?&&?y?<?bottom?&&?x?>?left?&&?x?<?right?{
????return?true
??}
??//?右上角
??x,?y?=?entityB.X()+float64(entityB.Width()),?entityB.Y()
??if?y?>?top?&&?y?<?bottom?&&?x?>?left?&&?x?<?right?{
????return?true
??}
??//?左下角
??x,?y?=?entityB.X(),?entityB.Y()+float64(entityB.Height())
??if?y?>?top?&&?y?<?bottom?&&?x?>?left?&&?x?<?right?{
????return?true
??}
??//?右下角
??x,?y?=?entityB.X()+float64(entityB.Width()),?entityB.Y()+float64(entityB.Height())
??if?y?>?top?&&?y?<?bottom?&&?x?>?left?&&?x?<?right?{
????return?true
??}
??return?false
}
如果游戲失敗則切換為ModeOver模式,屏幕上顯示"Game Over!"。如果游戲勝利,則顯示"You Win!":
if?g.failCount?>=?3?{
??g.overMsg?=?"Game?Over!"
}?else?if?len(g.aliens)?==?0?{
??g.overMsg?=?"You?Win!"
}
if?len(g.overMsg)?>?0?{
??g.mode?=?ModeOver
??g.aliens?=?make(map[*Alien]struct{})
??g.bullets?=?make(map[*Bullet]struct{})
}
注意,為了下次游戲能順序進(jìn)行,這里需要清楚所有的子彈和外星人。運(yùn)行:

總結(jié)
本文接著上篇文章,介紹了發(fā)射子彈,檢測(cè)碰撞,增加主界面和游戲結(jié)束界面等內(nèi)容。至此一個(gè)簡(jiǎn)答的游戲就做出來了??梢钥闯鍪褂胑bitengine做一個(gè)游戲還是很簡(jiǎn)單的,非常推薦嘗試呢!上手之后,建議看看官方倉(cāng)庫(kù)examples目錄中的示例,會(huì)非常有幫助。
大家如果發(fā)現(xiàn)好玩、好用的 Go 語言庫(kù),歡迎到 Go 每日一庫(kù) GitHub 上提交 issue??
參考
- Go 每日一庫(kù) GitHub:https://github.com/darjun/go-daily-lib
- ebitengine 官網(wǎng):https://ebitengine.org/
- Python 編程(從入門到實(shí)踐):https://book.douban.com/subject/35196328/
推薦閱讀
我為大家整理了一份 從入門到進(jìn)階的Go學(xué)習(xí)資料禮包 ,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。 關(guān)注公眾號(hào) 「polarisxu」,回復(fù)? ebook ?獲??;還可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。
