mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
examples: Add examples/flappy
This commit is contained in:
parent
83f168a010
commit
2a6672cc8b
BIN
examples/_resources/audio/jump.ogg
Executable file
BIN
examples/_resources/audio/jump.ogg
Executable file
Binary file not shown.
@ -18,6 +18,14 @@ Recorded by Mike Koenig
|
||||
Attribution 3.0: https://creativecommons.org/licenses/by/3.0/
|
||||
```
|
||||
|
||||
## jump.ogg
|
||||
|
||||
```
|
||||
https://opengameart.org/content/jumping-man-sounds
|
||||
|
||||
CC0
|
||||
```
|
||||
|
||||
|
||||
## ragtime.ogg
|
||||
|
||||
|
BIN
examples/_resources/images/flappy/gopher.png
Normal file
BIN
examples/_resources/images/flappy/gopher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.8 KiB |
BIN
examples/_resources/images/flappy/tiles.png
Normal file
BIN
examples/_resources/images/flappy/tiles.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
@ -39,6 +39,22 @@ https://opengameart.org/content/orthographic-outdoor-tiles
|
||||
CC0 1.0
|
||||
```
|
||||
|
||||
## flappy/gopher.png
|
||||
|
||||
```
|
||||
The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/)
|
||||
The design is licensed under the Creative Commons 3.0 Attributions license.
|
||||
Read this article for more details: https://blog.golang.org/gopher
|
||||
```
|
||||
|
||||
## flappy/tiles.png
|
||||
|
||||
```
|
||||
https://opengameart.org/content/super-mario-world-style-tiles
|
||||
|
||||
CC0 1.0
|
||||
```
|
||||
|
||||
## platformer/right.png platformer/left.png platformer/mainchar.png
|
||||
|
||||
```
|
||||
|
385
examples/flappy/main.go
Normal file
385
examples/flappy/main.go
Normal file
@ -0,0 +1,385 @@
|
||||
// Copyright 2018 The Ebiten Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build example jsgo
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
_ "image/png"
|
||||
"log"
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
"golang.org/x/image/font"
|
||||
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
"github.com/hajimehoshi/ebiten/audio"
|
||||
"github.com/hajimehoshi/ebiten/audio/vorbis"
|
||||
"github.com/hajimehoshi/ebiten/audio/wav"
|
||||
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||
raudio "github.com/hajimehoshi/ebiten/examples/resources/audio"
|
||||
"github.com/hajimehoshi/ebiten/examples/resources/fonts"
|
||||
resources "github.com/hajimehoshi/ebiten/examples/resources/images/flappy"
|
||||
"github.com/hajimehoshi/ebiten/inpututil"
|
||||
"github.com/hajimehoshi/ebiten/text"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func floorDiv(x, y int) int {
|
||||
d := x / y
|
||||
if d*y == x || x >= 0 {
|
||||
return d
|
||||
}
|
||||
return d - 1
|
||||
}
|
||||
|
||||
func floorMod(x, y int) int {
|
||||
return x - floorDiv(x, y)*y
|
||||
}
|
||||
|
||||
const (
|
||||
screenWidth = 640
|
||||
screenHeight = 480
|
||||
tileSize = 32
|
||||
fontSize = 32
|
||||
pipeWidth = tileSize * 2
|
||||
pipeStartOffsetX = 8
|
||||
pipeIntervalX = 8
|
||||
pipeGapY = 5
|
||||
)
|
||||
|
||||
var (
|
||||
gopherImage *ebiten.Image
|
||||
tilesImage *ebiten.Image
|
||||
arcadeFont font.Face
|
||||
)
|
||||
|
||||
func init() {
|
||||
img, _, err := image.Decode(bytes.NewReader(resources.Gopher_png))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
gopherImage, _ = ebiten.NewImageFromImage(img, ebiten.FilterDefault)
|
||||
|
||||
img, _, err = image.Decode(bytes.NewReader(resources.Tiles_png))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
tilesImage, _ = ebiten.NewImageFromImage(img, ebiten.FilterDefault)
|
||||
}
|
||||
|
||||
func init() {
|
||||
tt, err := truetype.Parse(fonts.ArcadeN_ttf)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
const dpi = 72
|
||||
arcadeFont = truetype.NewFace(tt, &truetype.Options{
|
||||
Size: fontSize,
|
||||
DPI: dpi,
|
||||
Hinting: font.HintingFull,
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
audioContext *audio.Context
|
||||
jumpPlayer *audio.Player
|
||||
hitPlayer *audio.Player
|
||||
)
|
||||
|
||||
func init() {
|
||||
audioContext, _ = audio.NewContext(44100)
|
||||
|
||||
jumpD, err := vorbis.Decode(audioContext, audio.BytesReadSeekCloser(raudio.Jump_ogg))
|
||||
if err != nil {
|
||||
println("!?")
|
||||
log.Fatal(err)
|
||||
}
|
||||
jumpPlayer, err = audio.NewPlayer(audioContext, jumpD)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
jabD, err := wav.Decode(audioContext, audio.BytesReadSeekCloser(raudio.Jab_wav))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
hitPlayer, err = audio.NewPlayer(audioContext, jabD)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
type Mode int
|
||||
|
||||
const (
|
||||
ModeTitle Mode = iota
|
||||
ModeGame
|
||||
ModeGameOver
|
||||
)
|
||||
|
||||
type Game struct {
|
||||
mode Mode
|
||||
|
||||
// The gopher's position
|
||||
x16 int
|
||||
y16 int
|
||||
vy16 int
|
||||
|
||||
// Camera
|
||||
cameraX int
|
||||
cameraY int
|
||||
|
||||
// Pipes
|
||||
pipeTileYs []int
|
||||
|
||||
gameoverCount int
|
||||
}
|
||||
|
||||
func NewGame() *Game {
|
||||
g := &Game{}
|
||||
g.init()
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *Game) init() {
|
||||
g.x16 = 0
|
||||
g.y16 = 100 * 16
|
||||
g.cameraX = -240
|
||||
g.cameraY = 0
|
||||
g.pipeTileYs = make([]int, 256)
|
||||
for i := range g.pipeTileYs {
|
||||
g.pipeTileYs[i] = rand.Intn(6) + 2
|
||||
}
|
||||
}
|
||||
|
||||
func jump() bool {
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeySpace) {
|
||||
return true
|
||||
}
|
||||
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
|
||||
return true
|
||||
}
|
||||
// TODO: Is it correct to use ID '0' here?
|
||||
if inpututil.IsJustTouched(0) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (g *Game) Update(screen *ebiten.Image) error {
|
||||
switch g.mode {
|
||||
case ModeTitle:
|
||||
if jump() {
|
||||
g.mode = ModeGame
|
||||
}
|
||||
case ModeGame:
|
||||
g.x16 += 32
|
||||
g.cameraX += 2
|
||||
if jump() {
|
||||
g.vy16 = -96
|
||||
jumpPlayer.Rewind()
|
||||
jumpPlayer.Play()
|
||||
}
|
||||
g.y16 += g.vy16
|
||||
|
||||
// Gravity
|
||||
g.vy16 += 4
|
||||
if g.vy16 > 96 {
|
||||
g.vy16 = 96
|
||||
}
|
||||
|
||||
if g.hit() {
|
||||
hitPlayer.Rewind()
|
||||
hitPlayer.Play()
|
||||
g.mode = ModeGameOver
|
||||
g.gameoverCount = 30
|
||||
}
|
||||
case ModeGameOver:
|
||||
if g.gameoverCount > 0 {
|
||||
g.gameoverCount--
|
||||
}
|
||||
if g.gameoverCount == 0 && jump() {
|
||||
g.init()
|
||||
g.mode = ModeTitle
|
||||
}
|
||||
}
|
||||
|
||||
if ebiten.IsRunningSlowly() {
|
||||
return nil
|
||||
}
|
||||
|
||||
screen.Fill(color.RGBA{0x80, 0xa0, 0xc0, 0xff})
|
||||
g.drawTiles(screen)
|
||||
if g.mode != ModeTitle {
|
||||
g.drawGopher(screen)
|
||||
}
|
||||
var texts []string
|
||||
switch g.mode {
|
||||
case ModeTitle:
|
||||
texts = []string{"FLAPPY GOPHER", "", "", "", "", "PRESS SPACE"}
|
||||
case ModeGameOver:
|
||||
texts = []string{"", "GAMEOVER!"}
|
||||
}
|
||||
for i, l := range texts {
|
||||
x := (screenWidth - len(l)*fontSize) / 2
|
||||
text.Draw(screen, l, arcadeFont, x, (i+4)*fontSize, color.White)
|
||||
}
|
||||
|
||||
scoreStr := fmt.Sprintf("%04d", g.score())
|
||||
text.Draw(screen, scoreStr, arcadeFont, screenWidth-len(scoreStr)*fontSize, fontSize, color.White)
|
||||
ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %0.2f", ebiten.CurrentFPS()))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Game) pipeAt(tileX int) (tileY int, ok bool) {
|
||||
if (tileX - pipeStartOffsetX) <= 0 {
|
||||
return 0, false
|
||||
}
|
||||
if floorMod(tileX-pipeStartOffsetX, pipeIntervalX) != 0 {
|
||||
return 0, false
|
||||
}
|
||||
idx := floorDiv(tileX-pipeStartOffsetX, pipeIntervalX)
|
||||
return g.pipeTileYs[idx%len(g.pipeTileYs)], true
|
||||
}
|
||||
|
||||
func (g *Game) score() int {
|
||||
x := floorDiv(g.x16, 16) / tileSize
|
||||
if (x - pipeStartOffsetX) <= 0 {
|
||||
return 0
|
||||
}
|
||||
return floorDiv(x-pipeStartOffsetX, pipeIntervalX)
|
||||
}
|
||||
|
||||
func (g *Game) hit() bool {
|
||||
if g.mode != ModeGame {
|
||||
return false
|
||||
}
|
||||
const (
|
||||
gopherWidth = 30
|
||||
gopherHeight = 60
|
||||
)
|
||||
w, h := gopherImage.Size()
|
||||
x0 := floorDiv(g.x16, 16) + (w-gopherWidth)/2
|
||||
y0 := floorDiv(g.y16, 16) + (h-gopherHeight)/2
|
||||
x1 := x0 + gopherWidth
|
||||
y1 := y0 + gopherHeight
|
||||
if y0 < -tileSize*4 {
|
||||
return true
|
||||
}
|
||||
if y1 >= screenHeight-tileSize {
|
||||
return true
|
||||
}
|
||||
xMin := floorDiv(x0-pipeWidth, tileSize)
|
||||
xMax := floorDiv(x0+gopherWidth, tileSize)
|
||||
for x := xMin; x <= xMax; x++ {
|
||||
y, ok := g.pipeAt(x)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if x0 >= x*tileSize+pipeWidth {
|
||||
continue
|
||||
}
|
||||
if x1 < x*tileSize {
|
||||
continue
|
||||
}
|
||||
if y0 < y*tileSize {
|
||||
return true
|
||||
}
|
||||
if y1 >= (y+pipeGapY)*tileSize {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (g *Game) drawTiles(screen *ebiten.Image) {
|
||||
const (
|
||||
nx = screenWidth / tileSize
|
||||
ny = screenHeight / tileSize
|
||||
pipeTileSrcX = 128
|
||||
pipeTileSrcY = 192
|
||||
)
|
||||
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
for i := -2; i < nx+1; i++ {
|
||||
// ground
|
||||
op.GeoM.Reset()
|
||||
op.GeoM.Translate(float64(i*tileSize-floorMod(g.cameraX, tileSize)),
|
||||
float64((ny-1)*tileSize-floorMod(g.cameraY, tileSize)))
|
||||
r := image.Rect(0, 0, tileSize, tileSize)
|
||||
op.SourceRect = &r
|
||||
screen.DrawImage(tilesImage, op)
|
||||
|
||||
// pipe
|
||||
if tileY, ok := g.pipeAt(floorDiv(g.cameraX, tileSize) + i); ok {
|
||||
for j := 0; j < tileY; j++ {
|
||||
op.GeoM.Reset()
|
||||
op.GeoM.Scale(1, -1)
|
||||
op.GeoM.Translate(float64(i*tileSize-floorMod(g.cameraX, tileSize)),
|
||||
float64(j*tileSize-floorMod(g.cameraY, tileSize)))
|
||||
op.GeoM.Translate(0, tileSize)
|
||||
if j == tileY-1 {
|
||||
r := image.Rect(pipeTileSrcX, pipeTileSrcY, pipeTileSrcX+tileSize*2, pipeTileSrcY+tileSize)
|
||||
op.SourceRect = &r
|
||||
} else {
|
||||
r := image.Rect(pipeTileSrcX, pipeTileSrcY+tileSize, pipeTileSrcX+tileSize*2, pipeTileSrcY+tileSize*2)
|
||||
op.SourceRect = &r
|
||||
}
|
||||
screen.DrawImage(tilesImage, op)
|
||||
}
|
||||
for j := tileY + pipeGapY; j < screenHeight/tileSize-1; j++ {
|
||||
op.GeoM.Reset()
|
||||
op.GeoM.Translate(float64(i*tileSize-floorMod(g.cameraX, tileSize)),
|
||||
float64(j*tileSize-floorMod(g.cameraY, tileSize)))
|
||||
if j == tileY+pipeGapY {
|
||||
r := image.Rect(pipeTileSrcX, pipeTileSrcY, pipeTileSrcX+pipeWidth, pipeTileSrcY+tileSize)
|
||||
op.SourceRect = &r
|
||||
} else {
|
||||
r := image.Rect(pipeTileSrcX, pipeTileSrcY+tileSize, pipeTileSrcX+pipeWidth, pipeTileSrcY+tileSize+tileSize)
|
||||
op.SourceRect = &r
|
||||
}
|
||||
screen.DrawImage(tilesImage, op)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) drawGopher(screen *ebiten.Image) {
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
_, h := gopherImage.Size()
|
||||
op.GeoM.Translate(-float64(h)/2.0, -float64(h)/2.0)
|
||||
op.GeoM.Rotate(float64(g.vy16) / 96.0 * math.Pi / 6)
|
||||
op.GeoM.Translate(float64(h)/2.0, float64(h)/2.0)
|
||||
op.GeoM.Translate(float64(g.x16/16.0)-float64(g.cameraX), float64(g.y16/16.0)-float64(g.cameraY))
|
||||
op.Filter = ebiten.FilterLinear
|
||||
screen.DrawImage(gopherImage, op)
|
||||
}
|
||||
|
||||
func main() {
|
||||
g := NewGame()
|
||||
if err := ebiten.Run(g.Update, screenWidth, screenHeight, 1, "Flappy Gopher (Ebiten Demo)"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
6
examples/resources/audio/jump.go
Normal file
6
examples/resources/audio/jump.go
Normal file
File diff suppressed because one or more lines are too long
@ -14,6 +14,7 @@
|
||||
|
||||
//go:generate file2byteslice -package=audio -input=../_resources/audio/classic.mp3 -output=./audio/classic.go -var=Classic_mp3
|
||||
//go:generate file2byteslice -package=audio -input=../_resources/audio/jab.wav -output=./audio/jab.go -var=Jab_wav
|
||||
//go:generate file2byteslice -package=audio -input=../_resources/audio/jump.ogg -output=./audio/jump.go -var=Jump_ogg
|
||||
//go:generate file2byteslice -package=fonts -input=../_resources/fonts/arcade_n.ttf -output=./fonts/arcaden.go -var=ArcadeN_ttf
|
||||
//go:generate file2byteslice -package=fonts -input=../_resources/fonts/mplus-1p-regular.ttf -output=./fonts/mplus1pregular.go -var=MPlus1pRegular_ttf
|
||||
//go:generate file2byteslice -package=images -input=../_resources/images/ebiten.png -output=./images/ebiten.go -var=Ebiten_png
|
||||
@ -24,6 +25,8 @@
|
||||
//go:generate file2byteslice -package=images -input=../_resources/images/ui.png -output=./images/ui.go -var=UI_png
|
||||
//go:generate file2byteslice -package=blocks -input=../_resources/images/blocks/background.png -output=./images/blocks/background.go -var=Background_png
|
||||
//go:generate file2byteslice -package=blocks -input=../_resources/images/blocks/blocks.png -output=./images/blocks/blocks.go -var=Blocks_png
|
||||
//go:generate file2byteslice -package=flappy -input=../_resources/images/flappy/gopher.png -output=./images/flappy/gopher.go -var=Gopher_png
|
||||
//go:generate file2byteslice -package=flappy -input=../_resources/images/flappy/tiles.png -output=./images/flappy/tiles.go -var=Tiles_png
|
||||
//go:generate file2byteslice -package=platformer -input=../_resources/images/platformer/background.png -output=./images/platformer/background.go -var=Background_png
|
||||
//go:generate file2byteslice -package=platformer -input=../_resources/images/platformer/left.png -output=./images/platformer/left.go -var=Left_png
|
||||
//go:generate file2byteslice -package=platformer -input=../_resources/images/platformer/mainchar.png -output=./images/platformer/mainchar.go -var=MainChar_png
|
||||
|
6
examples/resources/images/flappy/gopher.go
Normal file
6
examples/resources/images/flappy/gopher.go
Normal file
File diff suppressed because one or more lines are too long
6
examples/resources/images/flappy/tiles.go
Normal file
6
examples/resources/images/flappy/tiles.go
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user