diff --git a/example/blocks/field.go b/example/blocks/field.go new file mode 100644 index 000000000..7f6b1f7ff --- /dev/null +++ b/example/blocks/field.go @@ -0,0 +1,23 @@ +package blocks + +import ( + "github.com/hajimehoshi/go-ebiten/graphics" + "github.com/hajimehoshi/go-ebiten/graphics/matrix" +) + +type Field struct { + blocks [fieldBlockNumX][fieldBlockNumY]BlockType +} + +func NewField() *Field { + return &Field{} +} + +func (f *Field) Draw(context graphics.Context, geo matrix.Geometry) { + blocks := make([][]BlockType, len(f.blocks)) + for i, blockLine := range f.blocks { + blocks[i] = make([]BlockType, len(blockLine)) + copy(blocks[i], blockLine[:]) + } + drawBlocks(context, blocks, geo) +} diff --git a/example/blocks/font.go b/example/blocks/font.go new file mode 100644 index 000000000..131815eea --- /dev/null +++ b/example/blocks/font.go @@ -0,0 +1,54 @@ +package blocks + +import ( + "github.com/hajimehoshi/go-ebiten/graphics" + "github.com/hajimehoshi/go-ebiten/graphics/matrix" + "image/color" +) + +func init() { + texturePaths["font"] = "images/blocks/font.png" +} + +const charWidth = 8 +const charHeight = 8 + +func textWidth(str string) int { + return charWidth * len(str) +} + +func drawText(context graphics.Context, str string, x, y, scale int, clr color.Color) { + fontTextureId := drawInfo.textures["font"] + parts := []graphics.TexturePart{} + + locationX := 0 + locationY := 0 + for _, c := range str { + if c == '\n' { + locationX = 0 + locationY += charHeight + continue + } + code := int(c) + x := (code % 16) * charWidth + y := ((code - 32) / 16) * charHeight + parts = append(parts, graphics.TexturePart{ + LocationX: locationX, + LocationY: locationY, + Source: graphics.Rect{x, y, charWidth, charHeight}, + }) + locationX += charWidth + } + + geoMat := matrix.IdentityGeometry() + geoMat.Scale(float64(scale), float64(scale)) + geoMat.Translate(float64(x), float64(y)) + clrMat := matrix.IdentityColor() + clrMat.Scale(clr) + context.DrawTextureParts(fontTextureId, parts, geoMat, clrMat) +} + +func drawTextWithShadow(context graphics.Context, str string, x, y, scale int, clr color.Color) { + drawText(context, str, x+1, y+1, scale, color.RGBA{0, 0, 0, 0x80}) + drawText(context, str, x, y, scale, clr) +} diff --git a/example/blocks/game.go b/example/blocks/game.go index ff9fbe86e..48342793f 100644 --- a/example/blocks/game.go +++ b/example/blocks/game.go @@ -1,7 +1,6 @@ package blocks import ( - "fmt" "github.com/hajimehoshi/go-ebiten/graphics" "github.com/hajimehoshi/go-ebiten/ui" "image" @@ -14,18 +13,13 @@ type Size struct { Height int } -var texturePaths = map[string]string{ - "background": "images/blocks/background.png", - "font": "images/blocks/font.png", -} - -var renderTargetSizes = map[string]Size{ - "whole": Size{256, 254}, -} - const ScreenWidth = 256 const ScreenHeight = 240 +var texturePaths = map[string]string{} +var renderTargetSizes = map[string]Size{} + +// TODO: Make this not a global variable. var drawInfo = struct { textures map[string]graphics.TextureId renderTargets map[string]graphics.RenderTargetId @@ -34,8 +28,14 @@ var drawInfo = struct { renderTargets: map[string]graphics.RenderTargetId{}, } +type GameState struct { + SceneManager *SceneManager + Input *Input +} + type Game struct { sceneManager *SceneManager + input *Input } func loadImage(path string) (image.Image, error) { @@ -77,6 +77,7 @@ func (game *Game) startLoadingTextures(textureFactory graphics.TextureFactory) { func NewGame(textureFactory graphics.TextureFactory) *Game { game := &Game{ sceneManager: NewSceneManager(NewTitleScene()), + input: NewInput(), } game.startLoadingTextures(textureFactory) return game @@ -95,7 +96,7 @@ func (game *Game) HandleEvent(e interface{}) { } drawInfo.renderTargets[e.Tag.(string)] = e.Id case ui.KeyStateUpdatedEvent: - fmt.Printf("%v\n", e.Keys) + game.input.UpdateKeys(e.Keys) case ui.MouseStateUpdatedEvent: } } @@ -114,7 +115,11 @@ func (game *Game) Update() { if !game.isInitialized() { return } - game.sceneManager.Update() + game.input.Update() + game.sceneManager.Update(&GameState{ + SceneManager: game.sceneManager, + Input: game.input, + }) } func (game *Game) Draw(context graphics.Context) { @@ -123,36 +128,3 @@ func (game *Game) Draw(context graphics.Context) { } game.sceneManager.Draw(context) } - -/*func (game *Game) drawText(g graphics.Context, text string, x, y int, clr color.Color) { - const letterWidth = 6 - const letterHeight = 16 - - parts := []graphics.TexturePart{} - textX := 0 - textY := 0 - for _, c := range text { - if c == '\n' { - textX = 0 - textY += letterHeight - continue - } - code := int(c) - x := (code % 32) * letterWidth - y := (code / 32) * letterHeight - source := graphics.Rect{x, y, letterWidth, letterHeight} - parts = append(parts, graphics.TexturePart{ - LocationX: textX, - LocationY: textY, - Source: source, - }) - textX += letterWidth - } - - geometryMatrix := matrix.IdentityGeometry() - geometryMatrix.Translate(float64(x), float64(y)) - colorMatrix := matrix.IdentityColor() - colorMatrix.Scale(clr) - g.DrawTextureParts(drawInfo.textures["text"], parts, - geometryMatrix, colorMatrix) -}*/ diff --git a/example/blocks/game_scene.go b/example/blocks/game_scene.go new file mode 100644 index 000000000..9632fc2cd --- /dev/null +++ b/example/blocks/game_scene.go @@ -0,0 +1,66 @@ +package blocks + +import ( + "github.com/hajimehoshi/go-ebiten/graphics" + "github.com/hajimehoshi/go-ebiten/graphics/matrix" + "image/color" + "math/rand" + "time" +) + +func init() { + texturePaths["empty"] = "images/blocks/empty.png" +} + +type GameScene struct { + field *Field + rand *rand.Rand + currentPiece *Piece + nextPiece *Piece +} + +func NewGameScene() *GameScene { + return &GameScene{ + field: NewField(), + rand: rand.New(rand.NewSource(time.Now().UnixNano())), + } +} + +const emptyWidth = 16 +const emptyHeight = 16 +const fieldWidth = blockWidth * fieldBlockNumX +const fieldHeight = blockHeight * fieldBlockNumY + +func (s *GameScene) choosePiece() *Piece { + // Omit BlockTypeNone. + num := int(BlockTypeMax) - 1 + blockType := BlockType(s.rand.Intn(num) + 1) + return Pieces[blockType] +} + +func (s *GameScene) Update(state *GameState) { + if s.currentPiece == nil { + s.currentPiece = s.choosePiece() + } +} + +func (s *GameScene) Draw(context graphics.Context) { + context.Fill(0xff, 0xff, 0xff) + + field := drawInfo.textures["empty"] + geoMat := matrix.IdentityGeometry() + geoMat.Scale(float64(fieldWidth) / float64(emptyWidth), + float64(fieldHeight) / float64(emptyHeight)) + geoMat.Translate(20, 20) // magic number? + colorMat := matrix.IdentityColor() + colorMat.Scale(color.RGBA{0, 0, 0, 0x80}) + context.DrawTexture(field, geoMat, colorMat) + + geoMat = matrix.IdentityGeometry() + geoMat.Translate(20, 20) + s.field.Draw(context, geoMat) + + if s.currentPiece != nil { + s.currentPiece.Draw(context, geoMat) + } +} diff --git a/example/blocks/input.go b/example/blocks/input.go new file mode 100644 index 000000000..62fb74ac0 --- /dev/null +++ b/example/blocks/input.go @@ -0,0 +1,41 @@ +package blocks + +import ( + "github.com/hajimehoshi/go-ebiten/ui" +) + +type Input struct { + states map[ui.Key]int + lastPressedKeys map[ui.Key]struct{} +} + +func NewInput() *Input { + states := map[ui.Key]int{} + for key := ui.Key(0); key < ui.KeyMax; key++ { + states[key] = 0 + } + return &Input{ + states: states, + } +} + +func (i *Input) StateForKey(key ui.Key) int { + return i.states[key] +} + +func (i *Input) Update() { + for key, _ := range i.states { + if _, ok := i.lastPressedKeys[key]; !ok { + i.states[key] = 0 + continue + } + i.states[key] += 1 + } +} + +func (i *Input) UpdateKeys(keys []ui.Key) { + i.lastPressedKeys = map[ui.Key]struct{}{} + for _, key := range keys { + i.lastPressedKeys[key] = struct{}{} + } +} diff --git a/example/blocks/piece.go b/example/blocks/piece.go new file mode 100644 index 000000000..cc5c8d0a0 --- /dev/null +++ b/example/blocks/piece.go @@ -0,0 +1,141 @@ +package blocks + +import ( + "github.com/hajimehoshi/go-ebiten/graphics" + "github.com/hajimehoshi/go-ebiten/graphics/matrix" +) + +func init() { + texturePaths["blocks"] = "images/blocks/blocks.png" +} + +type BlockType int + +const ( + BlockTypeNone BlockType = iota + BlockType1 + BlockType2 + BlockType3 + BlockType4 + BlockType5 + BlockType6 + BlockType7 + BlockTypeMax +) + +type Piece struct { + blockType BlockType + blocks [][]bool +} + +func toBlocks(ints [][]int) [][]bool { + blocks := make([][]bool, len(ints)) + for i, line := range ints { + blocks[i] = make([]bool, len(line)) + for j, v := range line { + blocks[i][j] = v != 0 + } + } + return blocks +} + +var Pieces = map[BlockType]*Piece{ + BlockTypeNone: nil, + BlockType1: &Piece{ + blockType: BlockType1, + blocks: toBlocks([][]int{ + {0, 1, 0, 0}, + {0, 1, 0, 0}, + {0, 1, 0, 0}, + {0, 1, 0, 0}, + }), + }, + BlockType2: &Piece{ + blockType: BlockType2, + blocks: toBlocks([][]int{ + {0, 1, 1}, + {0, 1, 0}, + {0, 1, 0}, + }), + }, + BlockType3: &Piece{ + blockType: BlockType3, + blocks: toBlocks([][]int{ + {0, 1, 0}, + {0, 1, 1}, + {0, 1, 0}, + }), + }, + BlockType4: &Piece{ + blockType: BlockType4, + blocks: toBlocks([][]int{ + {0, 1, 0}, + {0, 1, 0}, + {0, 1, 1}, + }), + }, + BlockType5: &Piece{ + blockType: BlockType5, + blocks: toBlocks([][]int{ + {0, 0, 1}, + {0, 1, 1}, + {0, 1, 0}, + }), + }, + BlockType6: &Piece{ + blockType: BlockType6, + blocks: toBlocks([][]int{ + {0, 1, 0}, + {0, 1, 1}, + {0, 0, 1}, + }), + }, + BlockType7: &Piece{ + blocks: toBlocks([][]int{ + {1, 1}, + {1, 1}, + }), + }, +} + +const blockWidth = 10 +const blockHeight = 10 +const fieldBlockNumX = 10 +const fieldBlockNumY = 20 + +func drawBlocks(context graphics.Context, blocks [][]BlockType, geo matrix.Geometry) { + parts := []graphics.TexturePart{} + for i, blockLine := range blocks { + for j, block := range blockLine { + if block == BlockTypeNone { + continue + } + locationX := j * blockWidth + locationY := i * blockHeight + source := graphics.Rect{ + (int(block) - 1) * blockWidth, 0, + blockWidth, blockHeight} + parts = append(parts, + graphics.TexturePart{ + LocationX: locationX, + LocationY: locationY, + Source: source, + }) + } + } + blocksTexture := drawInfo.textures["blocks"] + context.DrawTextureParts(blocksTexture, parts, geo, matrix.IdentityColor()) +} + +func (p *Piece) Draw(context graphics.Context, geo matrix.Geometry) { + blocks := make([][]BlockType, len(p.blocks)) + for i, blockLine := range p.blocks { + blocks[i] = make([]BlockType, len(blockLine)) + for j, v := range blockLine { + if v { + blocks[i][j] = p.blockType + } + } + } + drawBlocks(context, blocks, geo) +} diff --git a/example/blocks/scene_manager.go b/example/blocks/scene_manager.go index d2f574f7d..5a9612754 100644 --- a/example/blocks/scene_manager.go +++ b/example/blocks/scene_manager.go @@ -2,42 +2,74 @@ package blocks import ( "github.com/hajimehoshi/go-ebiten/graphics" + "github.com/hajimehoshi/go-ebiten/graphics/matrix" ) -type GameState struct { - SceneManager *SceneManager +func init() { + renderTargetSizes["scene_manager_transition_from"] = + Size{ScreenWidth, ScreenHeight} + renderTargetSizes["scene_manager_transition_to"] = + Size{ScreenWidth, ScreenHeight} } type Scene interface { - Update(state GameState) + Update(state *GameState) Draw(context graphics.Context) } +const transitionMaxCount = 20 + type SceneManager struct { - current Scene - next Scene + current Scene + next Scene + transitionCount int } func NewSceneManager(initScene Scene) *SceneManager { return &SceneManager{ - current: initScene, + current: initScene, + transitionCount: -1, } } -func (s *SceneManager) Update() { - if s.next != nil { +func (s *SceneManager) Update(state *GameState) { + if s.transitionCount == -1 { + s.current.Update(state) + return + } + s.transitionCount++ + if transitionMaxCount <= s.transitionCount { s.current = s.next s.next = nil + s.transitionCount = -1 } - s.current.Update(GameState{ - SceneManager: s, - }) } func (s *SceneManager) Draw(context graphics.Context) { + if s.transitionCount == -1 { + s.current.Draw(context) + return + } + from := drawInfo.renderTargets["scene_manager_transition_from"] + context.SetOffscreen(from) + context.Clear() s.current.Draw(context) + + to := drawInfo.renderTargets["scene_manager_transition_to"] + context.SetOffscreen(to) + context.Clear() + s.next.Draw(context) + + context.ResetOffscreen() + color := matrix.IdentityColor() + context.DrawRenderTarget(from, matrix.IdentityGeometry(), color) + + alpha := float64(s.transitionCount) / float64(transitionMaxCount) + color.Elements[3][3] = alpha + context.DrawRenderTarget(to, matrix.IdentityGeometry(), color) } func (s *SceneManager) GoTo(scene Scene) { s.next = scene + s.transitionCount = 0 } diff --git a/example/blocks/title_scene.go b/example/blocks/title_scene.go index 8a6428cef..7e2b33294 100644 --- a/example/blocks/title_scene.go +++ b/example/blocks/title_scene.go @@ -3,9 +3,14 @@ package blocks import ( "github.com/hajimehoshi/go-ebiten/graphics" "github.com/hajimehoshi/go-ebiten/graphics/matrix" + "github.com/hajimehoshi/go-ebiten/ui" "image/color" ) +func init() { + texturePaths["background"] = "images/blocks/background.png" +} + type TitleScene struct { count int } @@ -14,13 +19,21 @@ func NewTitleScene() *TitleScene { return &TitleScene{} } -func (s *TitleScene) Update(state GameState) { +func (s *TitleScene) Update(state *GameState) { s.count++ + if state.Input.StateForKey(ui.KeySpace) == 1 { + state.SceneManager.GoTo(NewGameScene()) + } } func (s *TitleScene) Draw(context graphics.Context) { drawTitleBackground(context, s.count) drawLogo(context, "BLOCKS") + + message := "PRESS SPACE TO START" + x := (ScreenWidth-textWidth(message)) / 2 + y := ScreenHeight-48 + drawTextWithShadow(context, message, x, y, 1, color.RGBA{0x80, 0, 0, 0xff}) } func drawTitleBackground(context graphics.Context, c int) { @@ -32,50 +45,25 @@ func drawTitleBackground(context graphics.Context, c int) { for j := -1; j < ScreenHeight/textureHeight+1; j++ { for i := 0; i < ScreenWidth/textureWidth+1; i++ { parts = append(parts, graphics.TexturePart{ - LocationX: i*textureWidth, - LocationY: j*textureHeight, + LocationX: i * textureWidth, + LocationY: j * textureHeight, Source: graphics.Rect{0, 0, textureWidth, textureHeight}, }) } } - dx := -c % textureWidth / 2 - dy := c % textureHeight / 2 + dx := (-c/4) % textureWidth + dy := (c/4) % textureHeight geo := matrix.IdentityGeometry() geo.Translate(float64(dx), float64(dy)) - context.DrawTextureParts(backgroundTextureId, parts, geo, matrix.IdentityColor()) + clr := matrix.IdentityColor() + context.DrawTextureParts(backgroundTextureId, parts, geo, clr) } func drawLogo(context graphics.Context, str string) { - const charWidth = 8 - const charHeight = 8 - - fontTextureId := drawInfo.textures["font"] - parts := []graphics.TexturePart{} - - locationX := 0 - locationY := 0 - for _, c := range str { - if c == '\n' { - locationX = 0 - locationY += charHeight - continue - } - code := int(c) - x := (code % 16) * charWidth - y := ((code - 32) / 16) * charHeight - parts = append(parts, graphics.TexturePart{ - LocationX: locationX, - LocationY: locationY, - Source: graphics.Rect{x, y, charWidth, charHeight}, - }) - locationX += charWidth - } - - geo := matrix.IdentityGeometry() - geo.Scale(4, 4) - clr := matrix.IdentityColor() - - clr.Scale(color.RGBA{0x00, 0x00, 0x60, 0xff}) - context.DrawTextureParts(fontTextureId, parts, geo, clr) + scale := 4 + textWidth := textWidth(str) * scale + x := (ScreenWidth - textWidth) / 2 + y := 32 + drawTextWithShadow(context, str, x, y, scale, color.RGBA{0x00, 0x00, 0x80, 0xff}) } diff --git a/example/images/blocks/empty.png b/example/images/blocks/empty.png new file mode 100644 index 000000000..e27a7522e Binary files /dev/null and b/example/images/blocks/empty.png differ diff --git a/graphics/matrix/color.go b/graphics/matrix/color.go index 91a9ffa0a..e34c70c66 100644 --- a/graphics/matrix/color.go +++ b/graphics/matrix/color.go @@ -58,15 +58,28 @@ func Monochrome() Color { } } -func (matrix *Color) Scale(clr color.Color) { +func rgba(clr color.Color) (float64, float64, float64, float64) { r, g, b, a := clr.RGBA() rf := float64(r) / float64(math.MaxUint16) gf := float64(g) / float64(math.MaxUint16) bf := float64(b) / float64(math.MaxUint16) af := float64(a) / float64(math.MaxUint16) + return rf, gf, bf, af +} + +func (matrix *Color) Scale(clr color.Color) { + rf, gf, bf, af := rgba(clr) for i, e := range []float64{rf, gf, bf, af} { for j := 0; j < 4; j++ { matrix.Elements[i][j] *= e } } } + +func (matrix *Color) Translate(clr color.Color) { + rf, gf, bf, af := rgba(clr) + matrix.Elements[0][4] = rf + matrix.Elements[1][4] = gf + matrix.Elements[2][4] = bf + matrix.Elements[3][4] = af +} diff --git a/ui/ui.go b/ui/ui.go index 489bae92d..40e9ae320 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -12,6 +12,7 @@ const ( KeyLeft KeyRight KeySpace + KeyMax ) type WindowSizeUpdatedEvent struct {