前回の続きです。キャラクターとオブジェクトの衝突判定を実装したので、次は重力とジャンプを実装します。
ジャンプ
ジャンプは単に画面上方への移動です。
ただし、""ジャンプ"" なので以下の点を考慮する必要があります。
- 落下する
- 重力のようなものが必要
- 落下するまで次のジャンプはできない
- 無限にジャンプできないようにする
ソースコード
以下は 前回 と同じなので省略。
- 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軸での衝突判定に落下判定処理を追加しています。
実行結果
まとめ
単純なジャンプ処理は、前回からあまり差分が発生しませんでしたが、ゲームとして見たときの操作感はあまりよろしくないように感じました。
その辺は、より細かい処理を追加していく必要がありそうです。