diff options
-rw-r--r-- | assets/rewind.wav | bin | 0 -> 913832 bytes | |||
-rw-r--r-- | level.go | 91 | ||||
-rw-r--r-- | main.go | 163 | ||||
-rw-r--r-- | objects.go | 58 |
4 files changed, 219 insertions, 93 deletions
diff --git a/assets/rewind.wav b/assets/rewind.wav Binary files differnew file mode 100644 index 0000000..b7e7b67 --- /dev/null +++ b/assets/rewind.wav @@ -6,8 +6,7 @@ import ( func ReverseLevel(g *Game) { fmt.Printf("pframe %d/%d\n", 0, len(g.playerAi)) - g.state = REVERSING - g.shaderName = "vcr" + g.SetReversing() } func playTheEnd(g *Game) { @@ -23,14 +22,40 @@ func afterReversed(g *Game) { } func levelStart(g *Game) { - + g.ResetAll() + g.playerAiIdx = 0 + g.SetInGame() for _, o := range g.objects { o.movable = false } } func StartLevel1(g *Game ) { - g.state = IN_GAME + g.SetInGame() + + tilemap := NewTilemap([][]int{ + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 0, + 0, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 0, + 0, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + }, 25) + + g.tilemap = &tilemap + g.tilemap.UpdateSurface() // when hit end g.QueueState(func (g *Game){ @@ -47,7 +72,7 @@ func StartLevel1(g *Game ) { } func StartLevel2(g *Game) { - g.state = PLACING + g.SetPlacing() g.toPlace = append(g.toPlace, NewLeftSpike(g, 0, 0)) @@ -59,7 +84,7 @@ func StartLevel2(g *Game) { } func StartLevel3(g *Game) { - g.state = PLACING + g.SetPlacing() g.toPlace = append(g.toPlace, NewSpike(g, 0, 0)) @@ -67,6 +92,58 @@ func StartLevel3(g *Game) { g.QueueState(ReverseLevel) // after reversed g.QueueState(afterReversed) - g.QueueState(StartLevel3) + g.QueueState(StartLevel4) +} + +func StartLevel4(g *Game) { + g.SetPlacing() + g.toPlace = append(g.toPlace, NewSpring(g, 0, 0)) + + g.ClearAll() + tilemap := NewTilemap([][]int{ + { + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 5, 5, + 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, + 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, + 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, + 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, + 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, + 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, + 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 5, 5, + 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + }, + }, 25) + + g.tilemap = &tilemap + g.tilemap.UpdateSurface() + g.exit.startx = 20 * tileSize + g.exit.starty = 5 * tileSize + + g.ResetAll() + g.playerAi = g.playerAi[:0] + + g.QueueState(levelStart) + // after end + g.QueueState(ReverseLevel) + // after reversed + g.QueueState(afterReversed) + g.QueueState(StartLevel5) } +func StartLevel5(g *Game) { + g.SetPlacing() + g.toPlace = append(g.toPlace, NewSpike(g, 0, 0)) + + // after end + g.QueueState(ReverseLevel) + // after reversed + g.QueueState(afterReversed) + g.QueueState(StartLevel5) +} @@ -26,6 +26,10 @@ const ( gravity = 0.16 friction = 0.75 airResistance = 0.98 + + exitTransitionWeight = 0.9 + ghostAlpha = 0.5 + hightlightBorder = 2 ) var ( @@ -54,6 +58,7 @@ type RecPoint struct { y float32 vx float32 vy float32 + alpha float32 } @@ -86,17 +91,36 @@ func (g * Game)RecordPoint() { y: object.y, vx: object.vx, vy: object.vy, + alpha: object.alpha, }) } g.recording = append(g.recording, points) } +func (g * Game)ReplayPoint() { + if len(g.recording) == 0 { + return + } + + var points []RecPoint + points, g.recording = g.recording[len(g.recording)-1], g.recording[:len(g.recording)-1] + for i, point := range points { + obj := g.objects[i] + obj.x = point.x + obj.y = point.y + obj.vx = point.vx + obj.vy = point.vy + obj.alpha = point.alpha + } +} + func (g * Game)ResetPlayerAi() { g.playerAiIdx = 0 g.player.x = g.player.startx g.player.y = g.player.starty g.player.vx = 0 g.player.vy = 0 + g.player.alpha = ghostAlpha } func (g * Game)ReplayPlayerAi() { @@ -129,20 +153,10 @@ func (g * Game)ReplayPlayerAi() { } } - -func (g * Game)ReplayPoint() { - if len(g.recording) == 0 { - return - } - - var points []RecPoint - points, g.recording = g.recording[len(g.recording)-1], g.recording[:len(g.recording)-1] - for i, point := range points { - g.objects[i].x = point.x - g.objects[i].y = point.y - g.objects[i].vx = point.vx - g.objects[i].vy = point.vy - } +func (g * Game) ClearAll() { + g.objects = g.objects[:0] + g.objects = append(g.objects, g.player) + g.objects = append(g.objects, g.exit) } func (g * Game) ResetAll() { @@ -158,30 +172,6 @@ func (g * Game) ResetAll() { func (g *Game) Init() { g.surface = ebiten.NewImage(screenWidth, screenHeight) g.shaderName = "none" - tilemap := NewTilemap([][]int{ - { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 0, - 0, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 0, - 0, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - }, - }, 25) - - g.tilemap = &tilemap - - g.tilemap.UpdateSurface() g.player = NewPlayer(g, 4 * tileSize, 9 * tileSize) g.objects = append(g.objects, g.player) @@ -203,7 +193,7 @@ func (g *Game) Init() { func (g *Game) Update() error { g.time += 1 - //if ebiten.IsKeyPressed(ebiten.KeyR) { + //if ebiten.IsKeyJustPressed(ebiten.KeyR) { // if g.state == IN_GAME { // g.state = REVERSING // g.shaderName = "vcr" @@ -216,6 +206,16 @@ func (g *Game) Update() error { //} if g.state == IN_GAME { + if inpututil.IsKeyJustPressed(ebiten.KeyR) { + g.SetReversing() + next := func (g *Game){ + g.SetInGame() + g.recording = g.recording[:0] + g.playerAi = g.playerAi[:0] + } + g.whenStateFinished = append([]func(*Game){next}, g.whenStateFinished...) + } + var currentState [3]bool if ebiten.IsKeyPressed(ebiten.KeyLeft) || ebiten.IsKeyPressed(ebiten.KeyA) { g.player.MoveLeft() @@ -259,29 +259,7 @@ func (g *Game) Update() error { } if g.state == PLACING { - - for _, obj := range g.objects { - obj.Update(*g.tilemap, g.objects) - } - - g.tilemap.Update() - g.ReplayPlayerAi() - - cx, cy := ebiten.CursorPosition() - - if len(g.toPlace) > 0 { - placeable := g.toPlace[0] - cx = int(math.Floor(float64(cx)/float64(g.tilemap.tileSize)))*g.tilemap.tileSize - cy = int(math.Floor(float64(cy)/float64(g.tilemap.tileSize)))*g.tilemap.tileSize - - cx += placeable.offsetX - cy += placeable.offsetY - placeable.x = float32(cx) - placeable.y = float32(cy) - } - if inpututil.IsMouseButtonJustPressed(ebiten.MouseButton0) { - g.PlaceObject(cx, cy) - } + g.UpdatePlacing() } if g.player.y > screenHeight { g.KillPlayer() @@ -291,6 +269,37 @@ func (g *Game) Update() error { return nil } +func (g *Game) UpdatePlacing() { + for _, obj := range g.objects { + obj.Update(*g.tilemap, g.objects) + } + + g.tilemap.Update() + g.ReplayPlayerAi() + + cx, cy := ebiten.CursorPosition() + + for _, object := range g.objects { + object.highlight = object.CollidePoint(float32(cx), float32(cy)) && object.movable && len(g.toPlace) == 0 + } + + if len(g.toPlace) > 0 { + placeable := g.toPlace[0] + cx = int(math.Floor(float64(cx)/float64(g.tilemap.tileSize)))*g.tilemap.tileSize + cy = int(math.Floor(float64(cy)/float64(g.tilemap.tileSize)))*g.tilemap.tileSize + + cx += placeable.offsetX + cy += placeable.offsetY + placeable.x = float32(cx) + placeable.y = float32(cy) + placeable.alpha = float32(math.Abs(math.Sin(float64(g.time) / 30.0))) + } + + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButton0) { + g.PlaceObject(cx, cy) + } +} + func (g *Game) PlaceObject(cx, cy int) { if len(g.toPlace) == 0 { object := GetObjectAt(g.objects, float32(cx), float32(cy)) @@ -310,11 +319,17 @@ func (g *Game) PlaceObject(cx, cy int) { placeable.startx = float32(cx) placeable.starty = float32(cy) + placeable.highlight = false + placeable.alpha = 1.0 g.objects = append(g.objects, placeable) g.toPlace = g.toPlace[1:len(g.toPlace)] + if len(g.toPlace) == 0 && len(g.playerAi) == 0 { + g.TransitionState() + } + } func (g *Game) Draw(screen *ebiten.Image) { @@ -338,6 +353,7 @@ func (g *Game) Draw(screen *ebiten.Image) { } if g.state == END { + // draw THE END ebitenutil.DebugPrint(screen, fmt.Sprintf("THE END %d", g.time - g.animStart)) @@ -398,7 +414,7 @@ func (g *Game) KillPlayer() { if len(g.toPlace) == 0 { g.ResetAll() g.playerAi = g.playerAi[:0] - g.state = IN_GAME + g.SetInGame() for _, o := range g.objects { o.movable = false @@ -411,7 +427,8 @@ func (g *Game) EndLevel() { if g.state == IN_GAME { g.state = END g.TransitionState() - } else { + } + if g.state == PLACING { g.ResetPlayerAi() } } @@ -434,6 +451,24 @@ func (g *Game)RemoveObject(obj *GameObject) { g.objects = g.objects[:i] } +func (g *Game) SetReversing() { + g.state = REVERSING + g.shaderName = "vcr" + g.player.alpha = 1.0 +} + +func (g *Game) SetInGame() { + g.state = IN_GAME + g.shaderName = "none" + g.player.alpha = 1.0 +} + +func (g *Game) SetPlacing() { + g.state = PLACING + g.shaderName = "none" + g.player.alpha = ghostAlpha +} + func main() { LoadShaders() @@ -1,16 +1,17 @@ package main import ( + "image" + "image/color" "log" - "image" - "image/color" - "github.com/hajimehoshi/ebiten/v2" - "github.com/hajimehoshi/ebiten/v2/vector" + + "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/ebitenutil" + "github.com/hajimehoshi/ebiten/v2/vector" ) const ( - SPRING_FORCE = 8 + SPRING_FORCE = 10 ) type Direction int @@ -30,6 +31,8 @@ type GameObject struct { vx, vy float32 offsetX, offsetY int + alpha float32 + highlight bool friction float32 resistance float32 @@ -88,17 +91,21 @@ func (o * GameObject) Update(tilemap Tilemap, others []*GameObject) { func GetObjectAt(objects []*GameObject, x, y float32) *GameObject { for _, object := range objects { - maxX := object.x + float32(object.image.Bounds().Dx()) - maxY := object.y + float32(object.image.Bounds().Dy()) - minX := object.x - minY := object.y - if x >= minX && x < maxX && y >= minY && y < maxY { + if object.CollidePoint(x, y) { return object } } return nil } +func (object * GameObject) CollidePoint(x, y float32) bool { + maxX := object.x + float32(object.image.Bounds().Dx()) + maxY := object.y + float32(object.image.Bounds().Dy()) + minX := object.x + minY := object.y + return x >= minX && x < maxX && y >= minY && y < maxY + } + func (o * GameObject) HasCollision(tilemap Tilemap, others []*GameObject, dir Direction) bool { if tilemap.CollideObject(o) { return true @@ -128,12 +135,12 @@ func (o * GameObject) HasCollision(tilemap Tilemap, others []*GameObject, dir Di func (o * GameObject) Draw(screen *ebiten.Image, tilemap Tilemap) { op := &ebiten.DrawImageOptions{} + op.ColorScale.ScaleAlpha(o.alpha) op.GeoM.Translate(float64(o.x), float64(o.y)) screen.DrawImage(o.image, op) - - if o.movable { - vector.StrokeRect(screen, o.x, o.y, float32(o.image.Bounds().Dx()), float32(o.image.Bounds().Dy()), 2, color.RGBA{255, 100, 100, 255}, false) + if o.highlight { + vector.StrokeRect(screen, o.x, o.y, float32(o.image.Bounds().Dx()), float32(o.image.Bounds().Dy()), hightlightBorder, color.RGBA{255, 100, 100, 255}, false) } @@ -184,18 +191,18 @@ func OnCollideGeneric(this, other *GameObject) bool { } func NewObject(game *Game, x, y float32) *GameObject{ - obj := &GameObject{ + return &GameObject{ game: game, startx: x, starty: y, + alpha: 1.0, + highlight: false, + friction: friction, + resistance: airResistance, + x: x, + y: y, + movable: true, } - - obj.friction = friction - obj.resistance = airResistance - obj.x = obj.startx - obj.y = obj.starty - obj.movable = true - return obj } func NewPlayer(game *Game, x, y float32) *GameObject{ @@ -264,7 +271,14 @@ func NewLeftSpike(game *Game, x, y float32) *GameObject{ func OnCollideExit(this, other *GameObject) bool { if other == this.game.player { - this.game.EndLevel() + g := other.game + g.player.x = (exitTransitionWeight)*g.player.x + (1-exitTransitionWeight)*g.exit.x + g.player.y = (exitTransitionWeight)*g.player.y + (1-exitTransitionWeight)*g.exit.y + g.player.alpha *= exitTransitionWeight + + if g.player.alpha < 0.001 { + g.EndLevel() + } } return false } |