Update win screen

This commit is contained in:
Trevor Slocum 2021-10-30 15:25:11 -07:00
parent 5400251f83
commit 9e3be60700
3 changed files with 367 additions and 212 deletions

471
game.go
View File

@ -10,6 +10,7 @@ import (
"os"
"path"
"runtime/pprof"
"sync"
"time"
"github.com/hajimehoshi/ebiten/v2"
@ -116,7 +117,8 @@ type game struct {
flashMessageText string
flashMessageUntil time.Time
forceColorScale float64
minLevelColorScale float64
minPlayerColorScale float64
godMode bool
noclipMode bool
@ -124,6 +126,8 @@ type game struct {
debugMode bool
fullBrightMode bool
cpuProfile *os.File
sync.Mutex
}
const sampleRate = 44100
@ -131,13 +135,14 @@ const sampleRate = 44100
// NewGame returns a new isometric demo game.
func NewGame() (*game, error) {
g := &game{
camScale: 2,
camScaleTo: 2,
mousePanX: math.MinInt32,
mousePanY: math.MinInt32,
activeGamepad: -1,
forceColorScale: -1,
levelNum: 1,
camScale: 2,
camScaleTo: 2,
mousePanX: math.MinInt32,
mousePanY: math.MinInt32,
activeGamepad: -1,
minLevelColorScale: -1,
minPlayerColorScale: -1,
levelNum: 1,
op: &ebiten.DrawImageOptions{},
}
@ -286,7 +291,8 @@ func (g *game) reset() error {
g.updateCursor()
g.forceColorScale = -1
g.minLevelColorScale = -1
g.minPlayerColorScale = -1
g.player.hasTorch = true
g.player.weapon = weaponUzi
@ -359,10 +365,10 @@ func (g *game) handlePlayerDeath() {
}
func (g *game) checkLevelComplete() {
if g.player.soulsRescued < g.level.requiredSouls || g.level.exitOpen {
if g.player.soulsRescued < g.level.requiredSouls || !g.level.exitOpenTime.IsZero() {
return
}
g.level.exitOpen = true
g.level.exitOpenTime = time.Now()
g.level.tiles[g.level.exitY][g.level.exitX].sprites = nil
g.level.tiles[g.level.exitY][g.level.exitX].AddSprite(sandstoneSS.FloorA)
@ -374,6 +380,9 @@ func (g *game) checkLevelComplete() {
// Update reads current user input and updates the game state.
func (g *game) Update() error {
g.Lock()
defer g.Unlock()
gamepadDeadZone := 0.1
if ebiten.IsKeyPressed(ebiten.KeyEscape) || ebiten.IsWindowBeingClosed() {
@ -494,13 +503,6 @@ func (g *game) Update() error {
}
g.level.liveCreeps = liveCreeps
// Clamp target zoom level.
/*if g.camScaleTo < 2 {
g.camScaleTo = 2
} else if g.camScaleTo > 4 {
g.camScaleTo = 4
} TODO */
// Update target zoom level.
if g.debugMode {
var scrollY float64
@ -883,6 +885,9 @@ func (g *game) drawText(target *ebiten.Image, offsetX float64, y float64, scale
// Draw draws the game on the screen.
func (g *game) Draw(screen *ebiten.Image) {
g.Lock()
defer g.Unlock()
if g.gameStartTime.IsZero() {
screen.Fill(colorBlood)
@ -915,18 +920,22 @@ func (g *game) Draw(screen *ebiten.Image) {
}
drawn = g.renderLevel(screen)
} else {
drawn += g.drawProjectiles(screen)
drawn += g.drawPlayer(screen)
// Draw game over screen.
img := ebiten.NewImage(g.w, g.h)
img.Fill(colorBlood)
a := g.forceColorScale
a := g.minLevelColorScale
if a == -1 {
a = 1
}
g.op.GeoM.Reset()
g.op.ColorM.Reset()
g.op.ColorM.Scale(a, a, a, 1)
g.op.ColorM.Scale(a, a, a, a)
screen.DrawImage(img, g.op)
g.op.ColorM.Reset()
@ -935,7 +944,6 @@ func (g *game) Draw(screen *ebiten.Image) {
if time.Since(g.gameOverTime).Milliseconds()%2000 < 1500 {
g.drawText(screen, 0, 8, 4, a, "PRESS ENTER OR START TO PLAY AGAIN")
}
}
if g.gameOverTime.IsZero() {
@ -959,7 +967,7 @@ func (g *game) Draw(screen *ebiten.Image) {
scale := 3.0
soulsY := 104.0
if !g.level.exitOpen {
if g.level.exitOpenTime.IsZero() {
// Draw souls.
soulsLabel := fmt.Sprintf("%d", g.level.requiredSouls-g.player.soulsRescued)
@ -970,8 +978,9 @@ func (g *game) Draw(screen *ebiten.Image) {
g.drawText(screen, 0, soulsY, scale, 1.0, soulsLabel)
} else {
// Draw exit message.
// TODO flash text
g.drawText(screen, 0, soulsY, scale, 1.0, "EXIT OPEN")
if time.Since(g.level.exitOpenTime).Milliseconds()%2000 < 1500 {
g.drawText(screen, 0, soulsY, scale, 1.0, "EXIT OPEN")
}
}
}
@ -985,7 +994,7 @@ func (g *game) Draw(screen *ebiten.Image) {
}
if !g.gameWon {
a := g.forceColorScale
a := g.minLevelColorScale
if a == -1 {
a = 1
}
@ -1013,9 +1022,9 @@ func (g *game) tilePosition(x, y float64) (float64, float64) {
}
// renderSprite renders a sprite on the screen.
func (g *game) renderSprite(x float64, y float64, offsetx float64, offsety float64, angle float64, scale float64, colorScale float64, alpha float64, sprite *ebiten.Image, target *ebiten.Image) int {
if g.forceColorScale != -1 {
colorScale = g.forceColorScale
func (g *game) renderSprite(x float64, y float64, offsetx float64, offsety float64, angle float64, geoScale float64, colorScale float64, alpha float64, sprite *ebiten.Image, target *ebiten.Image) int {
if g.minLevelColorScale != -1 && colorScale < g.minLevelColorScale {
colorScale = g.minLevelColorScale
}
if alpha < .01 || colorScale < .01 {
@ -1033,7 +1042,7 @@ func (g *game) renderSprite(x float64, y float64, offsetx float64, offsety float
g.op.GeoM.Reset()
g.op.GeoM.Scale(scale, scale)
g.op.GeoM.Scale(geoScale, geoScale)
// Rotate
g.op.GeoM.Translate(-16+offsetx, -16+offsety)
g.op.GeoM.Rotate(angle)
@ -1057,81 +1066,48 @@ func (g *game) renderSprite(x float64, y float64, offsetx float64, offsety float
}
// Calculate color scale to apply shadows.
func (g *game) colorScale(x, y float64) float64 {
if g.gameWon || g.fullBrightMode {
func (g *game) levelColorScale(x, y float64) float64 {
if g.fullBrightMode {
return 1
}
v := colorScaleValue(x, y, g.player.x, g.player.y)
var v float64
if g.player.hasTorch {
v = colorScaleValue(x, y, g.player.x, g.player.y)
}
tileV := g.level.Tile(int(x), int(y)).colorScale
t := g.level.Tile(int(x), int(y))
if t == nil {
return 0
}
tileV := t.colorScale
s := math.Min(1, v+tileV)
return s
}
// renderLevel draws the current Level on the screen.
func (g *game) renderLevel(screen *ebiten.Image) int {
func (g *game) drawProjectiles(screen *ebiten.Image) int {
var drawn int
drawCreeps := func() {
for _, c := range g.level.creeps {
if c.health == 0 && c.creepType != TypeTorch {
continue
}
a := 1.0
if c.creepType == TypeSoul {
a = 0.35
}
drawn += g.renderSprite(c.x, c.y, 0, 0, c.angle, 1.0, g.colorScale(c.x, c.y), a, c.sprites[c.frame], screen)
if c.frames > 1 && time.Since(c.lastFrame) >= 75*time.Millisecond {
c.frame++
if c.frame == c.frames {
c.frame = 0
}
c.lastFrame = time.Now()
}
}
}
var t *Tile
for y := 0; y < g.level.h; y++ {
for x := 0; x < g.level.w; x++ {
t = g.level.tiles[y][x]
if t == nil {
continue // No tile at this position.
}
for i := range t.sprites {
drawn += g.renderSprite(float64(x), float64(y), 0, 0, 0, 1.0, g.colorScale(float64(x), float64(y)), 1.0, t.sprites[i], screen)
}
}
}
for _, item := range g.level.items {
if item.health == 0 {
continue
}
drawn += g.renderSprite(item.x, item.y, 0, 0, 0, 1.0, g.colorScale(item.x, item.y), 1.0, item.sprite, screen)
}
if !g.gameWon {
drawCreeps()
}
for _, p := range g.projectiles {
colorScale := p.colorScale
if colorScale == 1 {
colorScale = g.colorScale(p.x, p.y)
colorScale = g.levelColorScale(p.x, p.y)
}
drawn += g.renderSprite(p.x, p.y, 0, 0, p.angle, 1.0, colorScale, 1.0, imageAtlas[ImageBullet], screen)
alpha := 1.0
if g.gameWon {
//alpha = g.minLevelColorScale
}
// TODO if colorscale and gamewon, alpha is colorscale
drawn += g.renderSprite(p.x, p.y, 0, 0, p.angle, 1.0, colorScale, alpha, imageAtlas[ImageBullet], screen)
}
return drawn
}
func (g *game) drawPlayer(screen *ebiten.Image) int {
var drawn int
repelTime := g.player.garlicUntil.Sub(time.Now())
if repelTime > 0 && repelTime < 7*time.Second {
@ -1155,6 +1131,11 @@ func (g *game) renderLevel(screen *ebiten.Image) int {
drawn += g.renderSprite(g.player.x+0.25, g.player.y+0.25, -offset, -offset, 0, scale, 1.0, alpha, imageAtlas[ImageHolyWater], screen)
}
var playerColorScale = g.levelColorScale(g.player.x, g.player.y)
if g.minPlayerColorScale != -1 {
playerColorScale = g.minPlayerColorScale
}
var weaponSprite *ebiten.Image
playerSprite := playerSS.Frame1
@ -1163,7 +1144,7 @@ func (g *game) renderLevel(screen *ebiten.Image) int {
if g.player.weapon != nil {
weaponSprite = g.player.weapon.spriteFlipped
}
if g.player.angle > math.Pi/2 || g.player.angle < -1*math.Pi/2 {
if (g.player.angle > math.Pi/2 || g.player.angle < -1*math.Pi/2) && (g.gameOverTime.IsZero() || time.Since(g.gameOverTime) < 7*time.Second) {
playerSprite = playerSS.Frame2
playerAngle = playerAngle - math.Pi
mul = -1
@ -1171,19 +1152,84 @@ func (g *game) renderLevel(screen *ebiten.Image) int {
weaponSprite = g.player.weapon.sprite
}
}
drawn += g.renderSprite(g.player.x, g.player.y, 0, 0, playerAngle, 1.0, 1.0, 1.0, playerSprite, screen)
drawn += g.renderSprite(g.player.x, g.player.y, 0, 0, playerAngle, 1.0, playerColorScale, 1.0, playerSprite, screen)
if g.player.weapon != nil {
drawn += g.renderSprite(g.player.x, g.player.y, 11*mul, 9, playerAngle, 1.0, 1.0, 1.0, weaponSprite, screen)
drawn += g.renderSprite(g.player.x, g.player.y, 11*mul, 9, playerAngle, 1.0, playerColorScale, 1.0, weaponSprite, screen)
}
if g.player.hasTorch {
drawn += g.renderSprite(g.player.x, g.player.y, -10*mul, 2, playerAngle, 1.0, 1.0, 1.0, sandstoneSS.TorchMulti, screen)
drawn += g.renderSprite(g.player.x, g.player.y, -10*mul, 2, playerAngle, 1.0, playerColorScale, 1.0, sandstoneSS.TorchMulti, screen)
}
flashDuration := 40 * time.Millisecond
if g.player.weapon != nil && time.Since(g.player.weapon.lastFire) < flashDuration {
drawn += g.renderSprite(g.player.x, g.player.y, 39, -1, g.player.angle, 1.0, 1.0, 1.0, imageAtlas[ImageMuzzleFlash], screen)
drawn += g.renderSprite(g.player.x, g.player.y, 39, -1, g.player.angle, 1.0, playerColorScale, 1.0, imageAtlas[ImageMuzzleFlash], screen)
}
return drawn
}
// renderLevel draws the current Level on the screen.
func (g *game) renderLevel(screen *ebiten.Image) int {
var drawn int
drawCreeps := func() {
for _, c := range g.level.creeps {
if c.health == 0 && c.creepType != TypeTorch {
continue
}
a := 1.0
if c.creepType == TypeSoul {
a = 0.35
}
drawn += g.renderSprite(c.x, c.y, 0, 0, c.angle, 1.0, g.levelColorScale(c.x, c.y), a, c.sprites[c.frame], screen)
if c.frames > 1 && time.Since(c.lastFrame) >= 75*time.Millisecond {
c.frame++
if c.frame == c.frames {
c.frame = 0
}
c.lastFrame = time.Now()
}
}
}
if g.gameWon {
drawn += g.drawProjectiles(screen)
}
var t *Tile
for y := 0; y < g.level.h; y++ {
for x := 0; x < g.level.w; x++ {
t = g.level.tiles[y][x]
if t == nil {
continue // No tile at this position.
}
for i := range t.sprites {
drawn += g.renderSprite(float64(x), float64(y), 0, 0, 0, 1.0, g.levelColorScale(float64(x), float64(y)), 1.0, t.sprites[i], screen)
}
}
}
for _, item := range g.level.items {
if item.health == 0 {
continue
}
drawn += g.renderSprite(item.x, item.y, 0, 0, 0, 1.0, g.levelColorScale(item.x, item.y), 1.0, item.sprite, screen)
}
if !g.gameWon {
drawCreeps()
}
if !g.gameWon {
drawn += g.drawProjectiles(screen)
}
drawn += g.drawPlayer(screen)
if g.gameWon {
drawCreeps()
}
@ -1334,9 +1380,7 @@ func (g *game) showWinScreen() {
return
}
// TODO make it a sunrise instead
g.forceColorScale = 0.4
g.minLevelColorScale = 0.4
g.gameWon = true
g.gameOverTime = time.Now()
@ -1349,8 +1393,6 @@ func (g *game) showWinScreen() {
g.level = newWinLevel(g.player)
// TODO move win screen background upward
g.winScreenBackground = ebiten.NewImage(g.w, g.h)
g.winScreenBackground.Fill(colornames.Deepskyblue)
@ -1360,6 +1402,207 @@ func (g *game) showWinScreen() {
g.winScreenSunY = float64(g.h/2) + float64(sunSize/2)
go func() {
p := g.player
l := g.level
var stars []*projectile
addStar := func() {
star := &projectile{
x: p.x + (0.5-rand.Float64())*66,
y: p.y + (0.5-rand.Float64())*66,
colorScale: rand.Float64(),
}
g.projectiles = append(g.projectiles, star)
stars = append(stars, star)
}
lastPlayerX := p.x
updateStars := func() {
if p.x == lastPlayerX {
return
}
for _, star := range stars {
star.x = p.x - (lastPlayerX - star.x)
}
lastPlayerX = p.x
}
// Add stars.
numStars := 666
for i := 0; i < numStars; i++ {
addStar()
}
// Walk away.
for i := 0; i < 36; i++ {
p.x += 0.05
updateStars()
time.Sleep(time.Second / 144)
}
for i := 0; i < 288; i++ {
p.x += 0.05 * (float64(288-i) / 288)
updateStars()
time.Sleep(time.Second / 144)
}
// Turn around.
p.angle = math.Pi
time.Sleep(time.Millisecond * 1750)
// Throw weapon.
weaponSprite := newCreep(TypeTorch, l, p)
weaponSprite.x, weaponSprite.y = p.x, p.y
weaponSprite.frames = 1
weaponSprite.frame = 0
weaponSprite.sprites = []*ebiten.Image{
imageAtlas[ImageUzi],
}
p.weapon = nil
l.creeps = append(l.creeps, weaponSprite)
startX := 108
doorX := float64(startX) - 0.4
go func() {
for i := 0; i < 144*2; i++ {
if weaponSprite.x < doorX {
for i, c := range l.creeps {
if c == weaponSprite {
l.creeps = append(l.creeps[:i], l.creeps[i+1:]...)
}
}
return
}
weaponSprite.x -= 0.05
if i < 100 {
weaponSprite.y -= 0.005 * (float64(144-i) / 144)
} else {
weaponSprite.y += 0.01 * (float64(288-i) / 288)
}
weaponSprite.angle -= .1
time.Sleep(time.Second / 144)
}
}()
time.Sleep(time.Second / 2)
// Throw torch.
torchSprite := newCreep(TypeTorch, l, p)
torchSprite.x, torchSprite.y = p.x, p.y
torchSprite.frames = 1
torchSprite.frame = 0
torchSprite.sprites = []*ebiten.Image{
sandstoneSS.TorchMulti,
}
p.hasTorch = false
l.creeps = append(l.creeps, torchSprite)
l.torches = append(l.torches, torchSprite)
l.bakePartialLightmap(int(torchSprite.x), int(torchSprite.y))
go func() {
lastTorchX := torchSprite.x
for i := 0; i < 144*3; i++ {
if torchSprite.x < doorX {
for i, c := range l.creeps {
if c == torchSprite {
l.creeps = append(l.creeps[:i], l.creeps[i+1:]...)
l.torches = nil
}
}
}
torchSprite.x -= 0.05
if i < 100 {
torchSprite.y -= 0.005 * (float64(144-i) / 144)
} else {
torchSprite.y += 0.01 * (float64(288-i) / 288)
}
if lastTorchX-torchSprite.x >= 0.1 {
l.bakePartialLightmap(int(torchSprite.x), int(torchSprite.y))
lastTorchX = torchSprite.x
}
torchSprite.angle -= .1
time.Sleep(time.Second / 144)
}
}()
// Walk away.
time.Sleep(time.Second)
p.angle = 0
for i := 0; i < 144; i++ {
p.x += 0.05 * (float64(i) / 144)
// Fade out stars.
for _, star := range stars {
star.colorScale -= 0.01
if star.colorScale < 0 {
star.colorScale = 0
}
}
updateStars()
time.Sleep(time.Second / 144)
}
var removedExistingStars bool
var addedStars bool
for i := 0; i < 144*20; i++ {
if p.health > 0 {
// Game has restarted.
return
}
if i > int(144*3.5) {
if !removedExistingStars {
// Remove existing stars.
stars = nil
g.projectiles = nil
removedExistingStars = true
}
}
if i > int(144*12) {
p.angle += 0.0025 * (float64((144*5)-i) / (144 * 5))
addStar()
addStar()
addStar()
addStar()
_ = addedStars
}
p.x += 0.05
updateStars()
if i > 144*11 {
for _, star := range stars {
pct := float64((144*7)-i) / 144 * 7
star.x -= 0.05 * pct / 50
// Apply warp effect.
dx, dy := deltaXY(g.player.x, g.player.y, star.x, star.y)
star.x, star.y = star.x+(dx/100)*pct/1000, star.y+(dy/100)*pct/1000
}
}
time.Sleep(time.Second / 144)
}
}()
go func() {
// Animate sunrise.
go func() {
@ -1368,9 +1611,9 @@ func (g *game) showWinScreen() {
g.winScreenSunY -= 0.035
if i > int(144*3.5) {
g.forceColorScale += 0.0005
if g.forceColorScale > 1 {
g.forceColorScale = 1
g.minLevelColorScale += 0.0005
if g.minLevelColorScale > 1 {
g.minLevelColorScale = 1
}
}
@ -1385,22 +1628,36 @@ func (g *game) showWinScreen() {
time.Sleep(time.Second / 144)
}
time.Sleep(5 * time.Second)
time.Sleep(4 * time.Second)
// Fade out win screen.
for i := 0; i < 144; i++ {
g.winScreenColorScale -= 0.01
g.forceColorScale -= 0.01
for i := 0; i < 144*2; i++ {
g.winScreenColorScale -= 0.005
g.minLevelColorScale -= 0.005
if g.minLevelColorScale > 0.6 {
g.minPlayerColorScale = g.minLevelColorScale
}
time.Sleep(time.Second / 144)
}
g.Lock()
for y := 0; y < g.level.h; y++ {
for x := 0; x < g.level.w; x++ {
g.level.tiles[y][x].sprites = nil
}
}
g.Unlock()
time.Sleep(6 * time.Second)
// Fade in game over screen.
g.gameWon = false
defer func() {
g.forceColorScale = -1
g.minLevelColorScale = -1
g.minPlayerColorScale = -1
g.updateCursor()
}()
@ -1408,7 +1665,9 @@ func (g *game) showWinScreen() {
if g.player.health > 0 {
return
}
g.forceColorScale = i
if i <= 1 {
g.minLevelColorScale = i
}
time.Sleep(time.Second / 144)
}
}()

View File

@ -4,6 +4,7 @@ import (
"fmt"
"math"
"math/rand"
"time"
"github.com/Meshiest/go-dungeon/dungeon"
)
@ -29,7 +30,7 @@ type Level struct {
enterX, enterY int
exitX, exitY int
exitOpen bool
exitOpenTime time.Time
requiredSouls int
}

105
win.go
View File

@ -1,9 +1,7 @@
package main
import (
"math"
"math/rand"
"time"
"github.com/hajimehoshi/ebiten/v2"
)
@ -130,108 +128,5 @@ func newWinLevel(p *gamePlayer) *Level {
p.angle = 0
p.x, p.y = doorX, float64(startY)
go func() {
// Walk away.
for i := 0; i < 36; i++ {
p.x += 0.05
time.Sleep(time.Second / 144)
}
for i := 0; i < 288; i++ {
p.x += 0.05 * (float64(288-i) / 288)
time.Sleep(time.Second / 144)
}
// Turn around.
p.angle = math.Pi
time.Sleep(time.Millisecond * 1750)
// Throw weapon.
weaponSprite := newCreep(TypeTorch, l, p)
weaponSprite.x, weaponSprite.y = p.x, p.y-0.25
weaponSprite.frames = 1
weaponSprite.frame = 0
weaponSprite.sprites = []*ebiten.Image{
imageAtlas[ImageUzi],
}
p.weapon = nil
l.creeps = append(l.creeps, weaponSprite)
go func() {
for i := 0; i < 144*2; i++ {
if weaponSprite.x < doorX {
for i, c := range l.creeps {
if c == weaponSprite {
l.creeps = append(l.creeps[:i], l.creeps[i+1:]...)
}
}
return
}
weaponSprite.x -= 0.05
if i < 100 {
weaponSprite.y -= 0.005 * (float64(144-i) / 144)
} else {
weaponSprite.y += 0.01 * (float64(288-i) / 288)
}
weaponSprite.angle -= .1
time.Sleep(time.Second / 144)
}
}()
time.Sleep(time.Second / 2)
// Throw torch.
torchSprite := newCreep(TypeTorch, l, p)
torchSprite.x, torchSprite.y = p.x, p.y-0.25
torchSprite.frames = 1
torchSprite.frame = 0
torchSprite.sprites = []*ebiten.Image{
sandstoneSS.TorchMulti,
}
p.hasTorch = false
l.creeps = append(l.creeps, torchSprite)
go func() {
for i := 0; i < 144*3; i++ {
if torchSprite.x < doorX {
for i, c := range l.creeps {
if c == torchSprite {
l.creeps = append(l.creeps[:i], l.creeps[i+1:]...)
}
}
}
torchSprite.x -= 0.05
if i < 100 {
torchSprite.y -= 0.005 * (float64(144-i) / 144)
} else {
torchSprite.y += 0.01 * (float64(288-i) / 288)
}
torchSprite.angle -= .1
time.Sleep(time.Second / 144)
}
}()
// Walk away.
time.Sleep(time.Second)
p.angle = 0
for i := 0; i < 144; i++ {
p.x += 0.05 * (float64(i) / 144)
time.Sleep(time.Second / 144)
}
for i := 0; i < 144*15; i++ {
if p.health > 0 {
// Game has restarted.
return
}
p.x += 0.05
time.Sleep(time.Second / 144)
}
}()
return l
}