Go SDL2 游戏开发入门13-碰撞检测
前言
我决定这一节作为这个系列的最后一节了, 基本上把这些学会了应该能入门了 一些更深入的游戏开发, 就要靠你自己了 这一节就把碰撞检测讲一下
代码
这一节我主要实现了坦克大战里的两个坦克移动, 以及移动碰撞 一共四个文件
texture.go
package main import ( "github.com/veandco/go-sdl2/img" "github.com/veandco/go-sdl2/sdl" ) type Texture struct { t *sdl.Texture } func (this *Texture) LoadFromFile(path string) bool { img, err := img.Load(path) if err != nil { panic(err) } this.t, err = Grender.CreateTextureFromSurface(img) if err != nil { panic(err) } img.Free() return true } func (this *Texture) Render(src *sdl.Rect, dst *sdl.Rect) { Grender.Copy(this.t, src, dst) } func (this *Texture) setColor(r, g, b uint8) { this.t.SetColorMod(r, g, b) } func (this *Texture) SetAlpha(a uint8) { this.t.SetAlphaMod(a) } func (this *Texture) SetBlendMode(blend sdl.BlendMode) { this.t.SetBlendMode(blend) } func (this *Texture) Free() { this.t.Destroy() }
- 这个文件和之前的差不多, 没什么改动, 主要 texture 的封装
sprite.go
package main import ( "github.com/veandco/go-sdl2/sdl" ) type Sprite struct { W int32 H int32 X int32 Y int32 t *Texture } func NewSprite(x, y, w, h int32, t *Texture) *Sprite { return &Sprite{ W: w, H: h, X: x, Y: y, t: t, } } func (this *Sprite) Render(src *sdl.Rect, dst *sdl.Rect) { this.t.Render(src, dst) } func (this *Sprite) SetWH(w, h int32) { this.W = w this.H = h } func (this *Sprite) GetWH() (int32, int32) { return this.W, this.H } func (this *Sprite) SetXY(x, y int32) { this.X = x this.Y = y } func (this *Sprite) GetXY() (int32, int32) { return this.X, this.Y } func (this *Sprite) GetRect() *sdl.Rect { return &sdl.Rect{ X: this.X, Y: this.Y, W: this.W, H: this.H, } } type PlayerSprite struct { *Sprite rects [4][4][2]*sdl.Rect rectIdx int frame int lv int direct int } func NewPlayer1Sprite(t *Texture) *PlayerSprite { p := &PlayerSprite{ Sprite: NewSprite(0, 0, 40, 40, t), } p.rects[0][0][0] = &sdl.Rect{643, 3, 26, 26} p.rects[0][0][1] = &sdl.Rect{643, 35, 26, 26} p.rects[0][1][0] = &sdl.Rect{675, 3, 26, 26} p.rects[0][1][1] = &sdl.Rect{675, 35, 26, 26} p.rects[0][2][0] = &sdl.Rect{707, 3, 26, 26} p.rects[0][2][1] = &sdl.Rect{707, 35, 26, 26} p.rects[0][3][0] = &sdl.Rect{739, 3, 26, 26} p.rects[0][3][1] = &sdl.Rect{739, 35, 26, 26} return p } func NewPlayer2Sprite(t *Texture) *PlayerSprite { p := &PlayerSprite{ Sprite: NewSprite(200, 200, 40, 40, t), } p.rects[0][0][0] = &sdl.Rect{643, 3, 26, 26} p.rects[0][0][1] = &sdl.Rect{643, 35, 26, 26} p.rects[0][1][0] = &sdl.Rect{675, 3, 26, 26} p.rects[0][1][1] = &sdl.Rect{675, 35, 26, 26} p.rects[0][2][0] = &sdl.Rect{707, 3, 26, 26} p.rects[0][2][1] = &sdl.Rect{707, 35, 26, 26} p.rects[0][3][0] = &sdl.Rect{739, 3, 26, 26} p.rects[0][3][1] = &sdl.Rect{739, 35, 26, 26} return p } func (this *PlayerSprite) SetDirect(d int) { this.direct = d } func (this *PlayerSprite) GetDirect() int { return this.direct } func (this *PlayerSprite) Render() { if this.frame < 10 { this.frame += 1 } else { this.frame = 0 this.rectIdx += 1 if this.rectIdx == len(this.rects[this.lv][this.direct]) { this.rectIdx = 0 } } dst := &sdl.Rect{X: this.X, Y: this.Y, W: this.W, H: this.H} this.Sprite.Render(this.rects[this.lv][this.direct][this.rectIdx], dst) } func CheckCollision(a, b *sdl.Rect, lead sdl.Rect) bool { leftA := a.X + lead.X rightA := a.X + lead.X + a.W topA := a.Y + lead.Y bottomA := a.Y + lead.Y + a.H leftB := b.X rightB := b.X + b.W topB := b.Y bottomB := b.Y + b.H if bottomA < topB { return false } if topA > bottomB { return false } if rightA < leftB { return false } if leftA > rightB { return false } return true }
- 这里我实现了一个 sprite 基类, 和两个 player 类
- sprite 作为游戏中每个单位最底层的结构, 其他的结构都由它派生
- PlayerSprite 代表一个玩家坦克, rects 是从 texture 大图中抠出来的每个小图, frame 和 rectIdx 结合起来作为帧索引, 用来显示动画效果, 具体可参考动画那一节, lv 表示等级, direct 表示方向
- 之后的NewPlayer1Sprite, NewPlayer2Sprite, SetDirect, GetDirect, Render 这几个函数都很好理解
- 最后的 CheckCollision 就是碰撞检测函数, 一共三个参数, 第 1 个和第 2 个参数分别是将要检测的两个单位当前的 Rect 结构, 第 3 个参数作为一个提前量, 提前量的作用是检测下一个时段将要到达的位置, 而不是当前的位置
- 这里主要还是运用了投影法来检测, 做过游戏的应该都能理解吧, 不理解的可以联系我, 我给你开小灶
game.go
package main import ( "github.com/veandco/go-sdl2/sdl" ) type Game struct { Player1 *PlayerSprite Player2 *PlayerSprite } func NewGame(t *Texture) *Game { g := &Game{ Player1: NewPlayer1Sprite(t), Player2: NewPlayer2Sprite(t), } return g } func (this *Game) Render() { this.Player1.Render() this.Player2.Render() } func (this *Game) Event() { this.Player1Event() this.Player2Event() } func (this *Game) Player1Event() { keyState := sdl.GetKeyboardState() x, y := this.Player1.GetXY() d := this.Player1.GetDirect() if keyState[sdl.SCANCODE_W] > 0 { if !CheckCollision(this.Player1.GetRect(), this.Player2.GetRect(), sdl.Rect{Y: -2}) { y -= 2 } d = 0 } else if keyState[sdl.SCANCODE_A] > 0 { if !CheckCollision(this.Player1.GetRect(), this.Player2.GetRect(), sdl.Rect{X: -2}) { x -= 2 } d = 3 } else if keyState[sdl.SCANCODE_S] > 0 { if !CheckCollision(this.Player1.GetRect(), this.Player2.GetRect(), sdl.Rect{Y: 2}) { y += 2 } d = 2 } else if keyState[sdl.SCANCODE_D] > 0 { if !CheckCollision(this.Player1.GetRect(), this.Player2.GetRect(), sdl.Rect{X: 2}) { x += 2 } d = 1 } this.Player1.SetDirect(d) this.Player1.SetXY(x, y) } func (this *Game) Player2Event() { keyState := sdl.GetKeyboardState() x, y := this.Player2.GetXY() d := this.Player2.GetDirect() if keyState[sdl.SCANCODE_UP] > 0 { if !CheckCollision(this.Player2.GetRect(), this.Player1.GetRect(), sdl.Rect{Y: -2}) { y -= 2 } d = 0 } else if keyState[sdl.SCANCODE_LEFT] > 0 { if !CheckCollision(this.Player2.GetRect(), this.Player1.GetRect(), sdl.Rect{X: -2}) { x -= 2 } d = 3 } else if keyState[sdl.SCANCODE_DOWN] > 0 { if !CheckCollision(this.Player2.GetRect(), this.Player1.GetRect(), sdl.Rect{Y: 2}) { y += 2 } d = 2 } else if keyState[sdl.SCANCODE_RIGHT] > 0 { if !CheckCollision(this.Player2.GetRect(), this.Player1.GetRect(), sdl.Rect{X: 2}) { x += 2 } d = 1 } this.Player2.SetDirect(d) this.Player2.SetXY(x, y) }
- 将游戏逻辑封装到 game.go 当中, 这里主要做 player 的按键事件监测, 根据按键的不同, 对坦克进行移动和转向, 8 个按键分别是 wasd 和 上下左右
- 主要是两个函数 PlayerEvent, 首先 GetXY 获取当前位置, GetDirect 获取当前方向, 然后根据 keyState 获取按键状态, 然后进行碰撞检测, 都成功之后, 修改位置, 然后进行渲染
main.go
package main import ( "github.com/veandco/go-sdl2/sdl" ) const ( W = 670 H = 620 ) var ( Gwindow *sdl.Window Grender *sdl.Renderer Gtexture *Texture ) func init() { sdl.Init(sdl.INIT_EVERYTHING) Gwindow, err := sdl.CreateWindow("test", sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, W, H, sdl.WINDOW_SHOWN) if err != nil { panic(err) } Grender, err = sdl.CreateRenderer(Gwindow, -1, sdl.RENDERER_ACCELERATED) if err != nil { panic(err) } Grender.SetDrawColor(128, 128, 128, 255) Gtexture = &Texture{} Gtexture.LoadFromFile("../asserts/image/texture.png") } func main() { running := true game := NewGame(Gtexture) for running { for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() { switch event.(type) { case *sdl.QuitEvent: println("Quit") running = false break } } Grender.SetDrawColor(255, 255, 255, 255) Grender.Clear() game.Event() game.Render() Grender.Present() sdl.Delay(16) } free() } func free() { Gtexture.Free() Grender.Destroy() Gwindow.Destroy() sdl.Quit() }
- init 初始化一些必要的结构
- main 中创建一个 game 游戏逻辑, 然后在主循环中调用 game.Event 进行事件检测, 最后渲染
好了, 这个系列的教程就要告一段落了, 后续的我还可能在此基础上再完善一下坦克的功能, 再说了…