LoginSignup
5
3

More than 5 years have passed since last update.

ebiten でキャラクターのジャンプ

Posted at

前回の続きです。キャラクターとオブジェクトの衝突判定を実装したので、次は重力とジャンプを実装します。

ジャンプ

ジャンプは単に画面上方への移動です。
ただし、""ジャンプ"" なので以下の点を考慮する必要があります。

  • 落下する
    • 重力のようなものが必要
  • 落下するまで次のジャンプはできない
    • 無限にジャンプできないようにする

ソースコード

以下は 前回 と同じなので省略。

  • sprite/sprite.go
  • sprite/block.go

  • sprite/player.go
package sprite

import (
    "math"

    "github.com/hajimehoshi/ebiten"
)

// 四捨五入関数
func round(f float64) int {
    return int(math.Floor(f + .5))
}

// isOverlap は x1-x2 の範囲の整数が x3-x4 の範囲と重なるかを判定する
func isOverlap(x1, x2, x3, x4 int) bool {
    if x1 <= x4 && x2 >= x3 {
        return true
    }
    return false
}

type Player struct {
    BaseSprite
    jumping   bool    // 現在ジャンプ中か
    jumpSpeed float64 // 現在のジャンプ力
    fallSpeed float64 // 落下速度
}

func NewPlayer(images []*ebiten.Image) *Player {
    player := new(Player)
    player.Images = images
    player.ImageNum = len(images)
    player.jumpSpeed = 0
    player.fallSpeed = 0.4
    return player
}

func (p *Player) jump() {
    if !p.jumping {
        p.jumping = true // 現在ジャンプ中とする
        p.jumpSpeed = -6 // 上方への移動速度を追加
    }
}

func (p *Player) Move(objects []Sprite) {
    // dx, dy はユーザーの移動方向を保存する
    var dx, dy int
    if ebiten.IsKeyPressed(ebiten.KeyLeft) {
        dx = -1
        p.count++
    }
    if ebiten.IsKeyPressed(ebiten.KeyRight) {
        dx = 1
        p.count++
    }
    if ebiten.IsKeyPressed(ebiten.KeyUp) {
        p.jump()
        p.count++
    }

    // 落下速度の計算
    if p.jumpSpeed < 5 {
        p.jumpSpeed += p.fallSpeed
    }
    dy = round(p.jumpSpeed)

    for _, object := range objects {
        dx, dy = p.IsCollide(dx, dy, object)
    }

    p.Position.X += dx
    p.Position.Y += dy
}

// IsCollide はプレイヤーが対象の object と衝突しているか判定する
func (p *Player) IsCollide(dx, dy int, object Sprite) (int, int) {
    // プレイヤーの座標
    x := p.Position.X // x座標の位置
    y := p.Position.Y // y座標の位置
    img := p.currentImage()
    w, h := img.Size() // プレイヤーの幅と高さ

    // 対象のオブジェクトの x,y座標の位置と幅と高さを取得する
    x1, y1, w1, h1 := object.GetCordinates()

    overlappedX := isOverlap(x, x+w, x1, x1+w1) // x軸で重なっているか
    overlappedY := isOverlap(y, y+h, y1, y1+h1) // y軸で重なっているか

    if overlappedY {
        if dx < 0 && x+dx <= x1+w1 && x+w+dx >= x1 {
            // 左方向の移動の衝突判定
            // 衝突していたらx軸の移動速度を 0 にする
            dx = 0
        } else if dx > 0 && x+w+dx >= x1 && x+dx <= x1+w1 {
            // 右方向の移動の衝突判定
            // 衝突していたらx軸の移動速度を 0 にする
            dx = 0
        }
    }
    if overlappedX {
        if dy < 0 && y+dy <= y1+w1 && y+h+dy >= y1 {
            // 上方向の移動の衝突判定
            // 衝突していたらy軸の移動速度を 0 にする
            dy = 0
        } else if dy > 0 && y+h+dy >= y1 && y+dy <= y1+h1 {
            // 下方向の移動の衝突判定
            // 衝突していたらy軸の移動速度を 0 にする
            dy = 0

            // ジャンプ中フラグをオフにする
            p.jumping = false
            p.jumpSpeed = 0
        }
    }

    return dx, dy
}
  • main.go
package main

import (
    "image"
    "log"
    "strings"

    "github.com/hajimehoshi/ebiten"

    "github.com/zenwerk/go-pixelman3/sprite"
)

var player_anim0 = `------+++++-----
----+++++++++---
---+++++++++++--
--+++++++++++++-
--++++--+++--++-
-+++++--+++--++-
+++++++++++++++-
++++++++++++++++
++++++++++++++++
++++++-+++++-+++
+++++++-----++++
+-++++++++++++++
--++++++++++++-+
---++++++++++---
---++-----++----
--+++++--+++++--`

var player_anim1 = `------+++++-----
----+++++++++---
---+++++++++++--
--+++++++++++++-
--++++--+++--++-
-+++++--+++--++-
+++++++++++++++-
++++++++++++++++
++++++++++++++++
++++++-+++++-+++
+++++++-----++++
+-++++++++++++++
--++++++++++++-+
---++++++++++---
--+++++---++----
---------+++++--`

var player_anim2 = `------+++++-----
----+++++++++---
---+++++++++++--
--+++++++++++++-
--++++--+++--++-
-+++++--+++--++-
+++++++++++++++-
++++++++++++++++
++++++++++++++++
++++++-+++++-+++
+++++++-----++++
+-++++++++++++++
--++++++++++++-+
---++++++++++---
---++----+++++--
--+++++---------`

var block_img = `++++++++++++++++
++++++++++++++++
++++++++++++++++
++++++++++++++++
++++++++++++++++
++++++++++++++++
++++++++++++++++
++++++++++++++++
++++++++++++++++
++++++++++++++++
++++++++++++++++
++++++++++++++++
++++++++++++++++
++++++++++++++++
++++++++++++++++
++++++++++++++++`

var (
    charWidth   = 16
    charHeight  = 16
    tmpImage    *image.RGBA
    playerAnim0 *ebiten.Image
    playerAnim1 *ebiten.Image
    playerAnim2 *ebiten.Image
    blockImg    *ebiten.Image
)

func createImageFromString(charString string, img *image.RGBA) {
    for indexY, line := range strings.Split(charString, "\n") {
        for indexX, str := range line {
            pos := 4*indexY*charWidth + 4*indexX
            if string(str) == "+" {
                img.Pix[pos] = 0xff   // R
                img.Pix[pos+1] = 0xff // G
                img.Pix[pos+2] = 0xff // B
                img.Pix[pos+3] = 0xff // A
            } else {
                img.Pix[pos] = 0
                img.Pix[pos+1] = 0
                img.Pix[pos+2] = 0
                img.Pix[pos+3] = 0
            }
        }
    }
}

type Game struct {
    Player *sprite.Player
    Blocks []*sprite.Block
}

func (g *Game) Init() {
    tmpImage = image.NewRGBA(image.Rect(0, 0, charWidth, charHeight))

    createImageFromString(player_anim0, tmpImage)
    playerAnim0, _ = ebiten.NewImage(charWidth, charHeight, ebiten.FilterNearest)
    playerAnim0.ReplacePixels(tmpImage.Pix)

    createImageFromString(player_anim1, tmpImage)
    playerAnim1, _ = ebiten.NewImage(charWidth, charHeight, ebiten.FilterNearest)
    playerAnim1.ReplacePixels(tmpImage.Pix)

    createImageFromString(player_anim2, tmpImage)
    playerAnim2, _ = ebiten.NewImage(charWidth, charHeight, ebiten.FilterNearest)
    playerAnim2.ReplacePixels(tmpImage.Pix)

    createImageFromString(block_img, tmpImage)
    blockImg, _ = ebiten.NewImage(charWidth, charHeight, ebiten.FilterNearest)
    blockImg.ReplacePixels(tmpImage.Pix)

    // プレイヤー
    images := []*ebiten.Image{
        playerAnim0,
        playerAnim1,
        playerAnim2,
    }
    g.Player = sprite.NewPlayer(images)
    g.Player.Position.X = 10
    g.Player.Position.Y = 10

    // ブロック
    // 床
    for x := 0; x < 320; x += 17 {
        block := sprite.NewBlock([]*ebiten.Image{blockImg})
        block.Position.X = x
        block.Position.Y = 200
        g.Blocks = append(g.Blocks, block)
    }
    // 空中の床
    for x := 8 * 17; x < 17*13; x += 17 {
        block := sprite.NewBlock([]*ebiten.Image{blockImg})
        block.Position.X = x
        block.Position.Y = 110
        g.Blocks = append(g.Blocks, block)
    }

    // 階段ブロック
    block1 := sprite.NewBlock([]*ebiten.Image{blockImg})
    block1.Position.X = 60
    block1.Position.Y = 160
    g.Blocks = append(g.Blocks, block1)

    block2 := sprite.NewBlock([]*ebiten.Image{blockImg})
    block2.Position.X = 90
    block2.Position.Y = 130
    g.Blocks = append(g.Blocks, block2)

}

func (g *Game) MainLoop(screen *ebiten.Image) error {
    sprites := []sprite.Sprite{}
    for _, b := range g.Blocks {
        sprites = append(sprites, b)
    }
    g.Player.Move(sprites)

    if ebiten.IsRunningSlowly() {
        return nil
    }

    g.Player.DrawImage(screen)
    for _, block := range g.Blocks {
        block.DrawImage(screen)
    }

    return nil
}

func main() {
    game := Game{}
    game.Init()

    if err := ebiten.Run(game.MainLoop, 320, 240, 2, "go-pixelman3"); err != nil {
        log.Fatal(err)
    }
}
  • Player 構造体の Move 関数にジャンプ移動と落下の計算を追加しています。
  • Player 構造体の IsCollide 関数のY軸での衝突判定に落下判定処理を追加しています。

実行結果

gravity.gif

まとめ

単純なジャンプ処理は、前回からあまり差分が発生しませんでしたが、ゲームとして見たときの操作感はあまりよろしくないように感じました。
その辺は、より細かい処理を追加していく必要がありそうです。

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3