<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一起用Go做一個(gè)小游戲(中):飛機(jī)大戰(zhàn)

          共 15683字,需瀏覽 32分鐘

           ·

          2022-12-16 06:41

          限制飛船的活動(dòng)范圍

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

          590e474fc780cedcbaa7a5b354a0e4f5.webp

          很容易計(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é)果如下:

          6744f16485d0a167817ec3b72a92a768.webp

          發(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)行:

          374ad5deaf81bc5210674a9dc6c56dcc.webp

          數(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)行:

          bc5fc564156c1dbdc4c8933df2e58b4f.webp

          又出現(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)行:

          3f754d61c68f57abf3c9eb904a469794.webp

          外星人來了

          外星人圖片如下:

          14ac7da4bc5d8fbd4dc47d1162850dc7.webp

          同飛船一樣,編寫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)行:

          88126d15fbdc457b9aee48e60876e1dc.webp

          再創(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
          ??}
          ??//?-------省略-------
          }

          872da971c772634cdc14edaf171cf463.webp

          射擊??!

          當(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)行:

          be767563c875d0bc271fc1ae7bc0fdad.webp

          增加主界面和結(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)行:

          7ac5a4dd307c866d61080c8b23d3af18.webp

          總結(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??

          參考

          1. Go 每日一庫(kù) GitHub:https://github.com/darjun/go-daily-lib
          2. ebitengine 官網(wǎng):https://ebitengine.org/
          3. 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í)。

          瀏覽 95
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久视频黄片视频大全 | 在线观看无码高清 | 波多野结衣中文字幕乱码 | 青青草肏逼视频 | 超碰免费成人 |