前言

我决定这一节作为这个系列的最后一节了, 基本上把这些学会了应该能入门了 一些更深入的游戏开发, 就要靠你自己了 这一节就把碰撞检测讲一下

代码

这一节我主要实现了坦克大战里的两个坦克移动, 以及移动碰撞 一共四个文件

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()
}
  1. 这个文件和之前的差不多, 没什么改动, 主要 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
}
  1. 这里我实现了一个 sprite 基类, 和两个 player 类
  2. sprite 作为游戏中每个单位最底层的结构, 其他的结构都由它派生
  3. PlayerSprite 代表一个玩家坦克, rects 是从 texture 大图中抠出来的每个小图, frame 和 rectIdx 结合起来作为帧索引, 用来显示动画效果, 具体可参考动画那一节, lv 表示等级, direct 表示方向
  4. 之后的NewPlayer1Sprite, NewPlayer2Sprite, SetDirect, GetDirect, Render 这几个函数都很好理解
  5. 最后的 CheckCollision 就是碰撞检测函数, 一共三个参数, 第 1 个和第 2 个参数分别是将要检测的两个单位当前的 Rect 结构, 第 3 个参数作为一个提前量, 提前量的作用是检测下一个时段将要到达的位置, 而不是当前的位置
  6. 这里主要还是运用了投影法来检测, 做过游戏的应该都能理解吧, 不理解的可以联系我, 我给你开小灶

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)
}
  1. 将游戏逻辑封装到 game.go 当中, 这里主要做 player 的按键事件监测, 根据按键的不同, 对坦克进行移动和转向, 8 个按键分别是 wasd 和 上下左右
  2. 主要是两个函数 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()
}
  1. init 初始化一些必要的结构
  2. main 中创建一个 game 游戏逻辑, 然后在主循环中调用 game.Event 进行事件检测, 最后渲染

好了, 这个系列的教程就要告一段落了, 后续的我还可能在此基础上再完善一下坦克的功能, 再说了…

源码 https://gitee.com/fcsvr/tank.git