Add win screen

This commit is contained in:
Trevor Slocum 2021-10-27 20:52:02 -07:00
parent 0ff6da430b
commit 5f011b53ba
24 changed files with 572 additions and 90 deletions

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 744 B

After

Width:  |  Height:  |  Size: 744 B

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 633 B

After

Width:  |  Height:  |  Size: 633 B

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 157 B

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 255 KiB

After

Width:  |  Height:  |  Size: 255 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -50,6 +50,7 @@ func newCreep(creepType int, l *Level, p *gamePlayer) *gameCreep {
imageAtlas[ImageVampire3],
imageAtlas[ImageVampire2],
}
startingHealth := 1
if creepType == TypeBat {
sprites = []*ebiten.Image{
batSS.Frame1,
@ -60,10 +61,12 @@ func newCreep(creepType int, l *Level, p *gamePlayer) *gameCreep {
batSS.Frame6,
batSS.Frame7,
}
startingHealth = 2
} else if creepType == TypeGhost {
sprites = []*ebiten.Image{
imageAtlas[ImageGhost1],
}
startingHealth = 1
} else if creepType == TypeTorch {
sprites = []*ebiten.Image{
sandstoneSS.TorchTop1,
@ -82,7 +85,11 @@ func newCreep(creepType int, l *Level, p *gamePlayer) *gameCreep {
startingFrame = rand.Intn(len(sprites))
}
x, y := l.newSpawnLocation()
var x, y float64
if creepType != TypeTorch {
x, y = l.newSpawnLocation()
}
return &gameCreep{
creepType: creepType,
x: x,
@ -92,7 +99,7 @@ func newCreep(creepType int, l *Level, p *gamePlayer) *gameCreep {
frame: startingFrame,
level: l,
player: p,
health: 1,
health: startingHealth,
}
}

17
flags.go Normal file
View File

@ -0,0 +1,17 @@
//go:build !js || !wasm
// +build !js !wasm
package main
import (
"flag"
)
func parseFlags(g *game) {
flag.BoolVar(&g.godMode, "god", false, "Enable God mode")
flag.BoolVar(&g.noclipMode, "noclip", false, "Enable noclip mode")
flag.BoolVar(&g.fullBrightMode, "fullbright", false, "Enable fullbright mode")
flag.BoolVar(&g.debugMode, "debug", false, "Enable debug mode")
flag.BoolVar(&g.muteAudio, "mute", false, "Mute audio")
flag.Parse()
}

8
flags_web.go Normal file
View File

@ -0,0 +1,8 @@
//go:build js && wasm
// +build js,wasm
package main
func parseFlags(g *game) {
// Do nothing
}

263
game.go
View File

@ -87,6 +87,7 @@ type game struct {
gameStartTime time.Time
gameOverTime time.Time
gameWon bool
camScale float64
camScaleTo float64
@ -113,8 +114,11 @@ type game struct {
flashMessageText string
flashMessageUntil time.Time
forceColorScale float64
godMode bool
noclipMode bool
muteAudio bool
debugMode bool
fullBrightMode bool
cpuProfile *os.File
@ -125,11 +129,12 @@ 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,
camScale: 2,
camScaleTo: 2,
mousePanX: math.MinInt32,
mousePanY: math.MinInt32,
activeGamepad: -1,
forceColorScale: -1,
op: &ebiten.DrawImageOptions{},
}
@ -166,7 +171,12 @@ func (g *game) flashMessage(message string) {
func (g *game) loadAssets() error {
var err error
// Load SpriteSheets.
ojasSS, err = LoadPlayerSpriteSheet()
ojasDungeonSS, err = LoadOjasDungeonSpriteSheet()
if err != nil {
return fmt.Errorf("failed to load embedded spritesheet: %s", err)
}
playerSS, err = LoadPlayerSpriteSheet()
if err != nil {
return fmt.Errorf("failed to load embedded spritesheet: %s", err)
}
@ -268,6 +278,16 @@ func (g *game) reset() error {
g.levelNum = 1
g.gameStartTime = time.Now()
g.gameOverTime = time.Time{}
g.gameWon = false
g.forceColorScale = -1
g.player.hasTorch = true
g.player.weapon = weaponUzi
err := g.generateLevel()
if err != nil {
return err
@ -295,7 +315,7 @@ func (g *game) Layout(outsideWidth, outsideHeight int) (int, int) {
debugBox := image.NewRGBA(image.Rect(0, 0, g.w, 200))
g.overlayImg = ebiten.NewImageFromImage(debugBox)
}
if g.player.weapon.spriteFlipped == nil {
if g.player.weapon != nil && g.player.weapon.spriteFlipped == nil {
op := &ebiten.DrawImageOptions{}
op.GeoM.Scale(-1, 1)
op.GeoM.Translate(32, 0)
@ -307,6 +327,14 @@ func (g *game) Layout(outsideWidth, outsideHeight int) (int, int) {
return g.w, g.h
}
func (g *game) updateCursor() {
if g.activeGamepad == -1 || g.gameWon {
ebiten.SetCursorMode(ebiten.CursorModeHidden)
return
}
ebiten.SetCursorMode(ebiten.CursorModeVisible)
}
// Update reads current user input and updates the game state.
func (g *game) Update() error {
gamepadDeadZone := 0.1
@ -316,15 +344,16 @@ func (g *game) Update() error {
return nil
}
if g.player.health <= 0 && !g.godMode {
if !g.gameOverTime.IsZero() {
if g.gameWon {
return nil
}
// Game over.
if ebiten.IsKeyPressed(ebiten.KeyEnter) || (g.activeGamepad != -1 && ebiten.IsStandardGamepadButtonPressed(g.activeGamepad, ebiten.StandardGamepadButtonCenterRight)) {
err := g.reset()
if err != nil {
return err
}
g.gameOverTime = time.Time{}
}
return nil
}
@ -345,7 +374,7 @@ func (g *game) Update() error {
if ebiten.IsStandardGamepadButtonPressed(id, button) {
log.Printf("gamepad activated: %d", id)
g.activeGamepad = id
ebiten.SetCursorMode(ebiten.CursorModeHidden)
g.updateCursor()
break
}
}
@ -595,7 +624,7 @@ UPDATEPROJECTILES:
}
// Fire boolets.
if fire && time.Since(g.player.weapon.lastFire) >= g.player.weapon.cooldown {
if fire && g.player.weapon != nil && time.Since(g.player.weapon.lastFire) >= g.player.weapon.cooldown {
p := &projectile{
x: g.player.x,
y: g.player.y,
@ -715,17 +744,34 @@ UPDATEPROJECTILES:
g.flashMessage("NOCLIP MODE DEACTIVATED")
}
}
if inpututil.IsKeyJustPressed(ebiten.KeyH) {
g.player.holyWaters++
g.flashMessage("+ HOLY WATER")
spawnAmount := 13
if ebiten.IsKeyPressed(ebiten.KeyControl) && inpututil.IsKeyJustPressed(ebiten.Key1) {
for i := 0; i < spawnAmount; i++ {
g.level.addCreep(TypeVampire)
}
g.flashMessage(fmt.Sprintf("SPAWNED %d VAMPIRES", spawnAmount))
}
if ebiten.IsKeyPressed(ebiten.KeyControl) && inpututil.IsKeyJustPressed(ebiten.Key2) {
for i := 0; i < 13; i++ {
for i := 0; i < spawnAmount; i++ {
g.level.addCreep(TypeBat)
}
g.flashMessage(fmt.Sprintf("SPAWNED %d BATS", spawnAmount))
}
if ebiten.IsKeyPressed(ebiten.KeyControl) && inpututil.IsKeyJustPressed(ebiten.Key3) {
for i := 0; i < spawnAmount; i++ {
g.level.addCreep(TypeGhost)
}
g.flashMessage("+ GHOST")
g.flashMessage(fmt.Sprintf("SPAWNED %d GHOST", spawnAmount))
}
if ebiten.IsKeyPressed(ebiten.KeyControl) && inpututil.IsKeyJustPressed(ebiten.Key6) {
if inpututil.IsKeyJustPressed(ebiten.Key7) {
g.player.holyWaters++
g.flashMessage("SPAWNED HOLY WATER")
}
if inpututil.IsKeyJustPressed(ebiten.Key8) {
// TODO Add garlic to inventory
//g.flashMessage("+ GARLIC")
}
if ebiten.IsKeyPressed(ebiten.KeyControl) && inpututil.IsKeyJustPressed(ebiten.KeyDigit0) {
g.fullBrightMode = !g.fullBrightMode
if g.fullBrightMode {
g.flashMessage("FULLBRIGHT MODE ACTIVATED")
@ -733,7 +779,10 @@ UPDATEPROJECTILES:
g.flashMessage("FULLBRIGHT MODE DEACTIVATED")
}
}
if inpututil.IsKeyJustPressed(ebiten.KeyEqual) {
if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyShift) && inpututil.IsKeyJustPressed(ebiten.KeyEqual) {
g.showWinScreen()
g.flashMessage("WARPED TO WIN SCREEN")
} else if ebiten.IsKeyPressed(ebiten.KeyControl) && inpututil.IsKeyJustPressed(ebiten.KeyEqual) {
err := g.nextLevel()
if err != nil {
return err
@ -805,41 +854,51 @@ func (g *game) Draw(screen *ebiten.Image) {
return
}
gameOver := g.player.health <= 0 && !g.godMode
var drawn int
if !gameOver {
if g.gameOverTime.IsZero() || g.gameWon {
drawn = g.renderLevel(screen)
} else {
// Game over.
screen.Fill(colorBlood)
// Draw game over screen.
img := ebiten.NewImage(g.w, g.h)
img.Fill(colorBlood)
g.drawText(screen, float64(g.h/2)-150, 16, 1.0, "GAME OVER")
a := g.forceColorScale
if a == -1 {
a = 1
}
g.op.GeoM.Reset()
g.op.ColorM.Reset()
g.op.ColorM.Scale(a, a, a, 1)
screen.DrawImage(img, g.op)
g.op.ColorM.Reset()
g.drawText(screen, float64(g.h/2)-150, 16, a, "GAME OVER")
if time.Since(g.gameOverTime).Milliseconds()%2000 < 1500 {
g.drawText(screen, 8, 4, 1.0, "PRESS ENTER OR START TO PLAY AGAIN")
g.drawText(screen, 8, 4, a, "PRESS ENTER OR START TO PLAY AGAIN")
}
}
if g.gameOverTime.IsZero() {
heartSpace := 32
heartX := (g.w / 2) - ((heartSpace * g.player.health) / 2) + 8
for i := 0; i < g.player.health; i++ {
g.op.GeoM.Reset()
g.op.GeoM.Translate(float64(heartX+(i*heartSpace)), 32)
screen.DrawImage(imageAtlas[ImageHeart], g.op)
}
holyWaterSpace := 16
holyWaterX := (g.w / 2) - ((holyWaterSpace * g.player.holyWaters) / 2)
for i := 0; i < g.player.holyWaters; i++ {
g.op.GeoM.Reset()
g.op.GeoM.Translate(float64(holyWaterX+(i*holyWaterSpace)), 76)
screen.DrawImage(imageAtlas[ImageHolyWater], g.op)
}
}
heartSpace := 32
heartX := (g.w / 2) - ((heartSpace * g.player.health) / 2) + 8
for i := 0; i < g.player.health; i++ {
g.op.GeoM.Reset()
g.op.GeoM.Translate(float64(heartX+(i*heartSpace)), 32)
screen.DrawImage(imageAtlas[ImageHeart], g.op)
}
holyWaterSpace := 16
holyWaterX := (g.w / 2) - ((holyWaterSpace * g.player.holyWaters) / 2)
for i := 0; i < g.player.holyWaters; i++ {
g.op.GeoM.Reset()
g.op.GeoM.Translate(float64(holyWaterX+(i*holyWaterSpace)), 76)
screen.DrawImage(imageAtlas[ImageHolyWater], g.op)
}
scoreLabel := numberPrinter.Sprintf("%d", g.player.score)
g.drawText(screen, float64(g.h-150), 8, 1.0, scoreLabel)
flashTime := g.flashMessageUntil.Sub(time.Now())
if flashTime > 0 {
alpha := flashTime.Seconds() * 4
@ -849,6 +908,15 @@ func (g *game) Draw(screen *ebiten.Image) {
g.drawText(screen, float64(g.h-40), 2, alpha, g.flashMessageText)
}
if !g.gameWon {
a := g.forceColorScale
if a == -1 {
a = 1
}
scoreLabel := numberPrinter.Sprintf("%d", g.player.score)
g.drawText(screen, float64(g.h-150), 8, a, scoreLabel)
}
if !g.debugMode {
return
}
@ -870,6 +938,10 @@ 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
}
if alpha < .01 || colorScale < .01 {
return 0
}
@ -910,7 +982,7 @@ 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.fullBrightMode {
if g.gameWon || g.fullBrightMode {
return 1
}
@ -927,6 +999,24 @@ func (g *game) colorScale(x, y float64) float64 {
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
}
drawn += g.renderSprite(c.x, c.y, 0, 0, c.angle, 1.0, g.colorScale(c.x, c.y), 1.0, 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++ {
@ -949,19 +1039,8 @@ func (g *game) renderLevel(screen *ebiten.Image) int {
drawn += g.renderSprite(item.x, item.y, 0, 0, 0, 1.0, g.colorScale(item.x, item.y), 1.0, item.sprite, screen)
}
for _, c := range g.level.creeps {
if c.health == 0 && c.creepType != TypeTorch {
continue
}
drawn += g.renderSprite(c.x, c.y, 0, 0, c.angle, 1.0, g.colorScale(c.x, c.y), 1.0, 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 {
drawCreeps()
}
for _, p := range g.projectiles {
@ -995,27 +1074,39 @@ 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)
}
playerSprite := ojasSS.Frame1
var weaponSprite *ebiten.Image
playerSprite := playerSS.Frame1
playerAngle := g.player.angle
weaponSprite := g.player.weapon.spriteFlipped
mul := float64(1)
if g.player.weapon != nil {
weaponSprite = g.player.weapon.spriteFlipped
}
if g.player.angle > math.Pi/2 || g.player.angle < -1*math.Pi/2 {
playerSprite = ojasSS.Frame2
playerSprite = playerSS.Frame2
playerAngle = playerAngle - math.Pi
weaponSprite = g.player.weapon.sprite
mul = -1
if g.player.weapon != nil {
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)
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, -10*mul, 2, playerAngle, 1.0, 1.0, 1.0, sandstoneSS.TorchMulti, 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)
}
flashDuration := 40 * time.Millisecond
if time.Since(g.player.weapon.lastFire) < flashDuration {
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)
}
if g.gameWon {
drawCreeps()
}
return drawn
}
@ -1029,6 +1120,10 @@ func (g *game) resetExpiredTimers() {
}
func (g *game) playSound(sound int, volume float64) error {
if g.muteAudio {
return nil
}
player := soundAtlas[sound][nextSound[sound]]
nextSound[sound]++
if nextSound[sound] > 3 {
@ -1148,6 +1243,44 @@ func (g *game) addBloodSplatter(x, y float64) {
}
}
func (g *game) showWinScreen() {
if !g.gameOverTime.IsZero() {
return
}
g.gameWon = true
g.gameOverTime = time.Now()
g.updateCursor()
g.player.health = 0
g.level = newWinLevel(g.player)
go func() {
time.Sleep(10 * time.Second)
for i := 1.0; i > 0.001; i *= 0.99 {
g.forceColorScale = i
time.Sleep(time.Second / 144)
}
g.gameWon = false
defer func() {
g.forceColorScale = -1
g.updateCursor()
}()
for i := 0.01; i < 1; i *= 1.02 {
if g.player.health > 0 {
return
}
g.forceColorScale = i
time.Sleep(time.Second / 144)
}
}()
}
func (g *game) exit() {
os.Exit(0)
}

9
go.mod
View File

@ -10,12 +10,11 @@ require (
)
require (
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be // indirect
github.com/hajimehoshi/go-mp3 v0.3.2 // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be // indirect
github.com/hajimehoshi/oto/v2 v2.1.0-alpha.2 // indirect
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 // indirect
golang.org/x/exp v0.0.0-20211012155715-ffe10e552389 // indirect
golang.org/x/mobile v0.0.0-20210924032853-1c027f395ef7 // indirect
golang.org/x/exp v0.0.0-20211025140241-8418b01e8c3b // indirect
golang.org/x/mobile v0.0.0-20211027134744-eb3c0abee20a // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 // indirect
)

17
go.sum
View File

@ -58,8 +58,9 @@ github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be h1:vEIVIuBApEBQTEJt19GfhoU+zFSV+sNTa9E9FdnRYfk=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be h1:Z28GdQBfKOL8tNHjvaDn3wHDO7AzTRkmAXvHvnopp98=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
@ -102,7 +103,6 @@ github.com/hajimehoshi/bitmapfont/v2 v2.1.3/go.mod h1:2BnYrkTQGThpr/CY6LorYtt/zE
github.com/hajimehoshi/ebiten/v2 v2.2.1 h1:YhITMaBQmnwb4kzAXCCfSkSZmeQX7pfQ7BnC32cnPjc=
github.com/hajimehoshi/ebiten/v2 v2.2.1/go.mod h1:olKl/qqhMBBAm2oI7Zy292nCtE+nitlmYKNF3UpbFn0=
github.com/hajimehoshi/file2byteslice v0.0.0-20210813153925-5340248a8f41/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE=
github.com/hajimehoshi/go-mp3 v0.3.2 h1:xSYNE2F3lxtOu9BRjCWHHceg7S91IHfXfXp5+LYQI7s=
github.com/hajimehoshi/go-mp3 v0.3.2/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
github.com/hajimehoshi/oto v0.6.1 h1:7cJz/zRQV4aJvMSSRqzN2TImoVVMpE0BCY4nrNJaDOM=
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
@ -290,8 +290,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/exp v0.0.0-20211012155715-ffe10e552389 h1:qFfBYVpJAdBCk6Nmd7ZbcyhGmKmv8fps+OyoOfpjvu8=
golang.org/x/exp v0.0.0-20211012155715-ffe10e552389/go.mod h1:a3o/VtDNHN+dCVLEpzjjUHOzR+Ln3DHX056ZPzoZGGA=
golang.org/x/exp v0.0.0-20211025140241-8418b01e8c3b h1:AHIXEGqpPGg/yJCO803Q+UbBiq/vkCe/OPwMSVE7psI=
golang.org/x/exp v0.0.0-20211025140241-8418b01e8c3b/go.mod h1:a3o/VtDNHN+dCVLEpzjjUHOzR+Ln3DHX056ZPzoZGGA=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@ -306,8 +306,8 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mobile v0.0.0-20210902104108-5d9a33257ab5/go.mod h1:c4YKU3ZylDmvbw+H/PSvm42vhdWbuxCzbonauEAP9B8=
golang.org/x/mobile v0.0.0-20210924032853-1c027f395ef7 h1:CyFUjc175y/mbMjxe+WdqI72jguLyjQChKCDe9mfTvg=
golang.org/x/mobile v0.0.0-20210924032853-1c027f395ef7/go.mod h1:c4YKU3ZylDmvbw+H/PSvm42vhdWbuxCzbonauEAP9B8=
golang.org/x/mobile v0.0.0-20211027134744-eb3c0abee20a h1:AVqU+D7dTd7kx8xFcaRktB8LbDTIA29vxySTqiZ7A1w=
golang.org/x/mobile v0.0.0-20211027134744-eb3c0abee20a/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
@ -371,8 +371,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@ -401,6 +401,7 @@ golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

4
img.go
View File

@ -32,11 +32,11 @@ var imageMap = map[int]string{
ImageGhost2: "assets/creeps/ghost/ghost2.png",
ImageGhost2R: "assets/creeps/ghost/ghost2r.png",
ImageGarlic: "assets/items/garlic.png",
ImageHolyWater: "assets/items/holywater.png",
ImageHolyWater: "assets/items/holy-water.png",
ImageHeart: "assets/ui/heart.png",
ImageUzi: "assets/weapons/uzi.png",
ImageBullet: "assets/weapons/bullet.png",
ImageMuzzleFlash: "assets/weapons/flash.png",
ImageMuzzleFlash: "assets/weapons/muzzle-flash.png",
}
var imageAtlas = loadAtlas()

View File

@ -28,6 +28,8 @@ func main() {
log.Fatal(err)
}
parseFlags(g)
sigc := make(chan os.Signal, 1)
signal.Notify(sigc,
syscall.SIGINT,

View File

@ -4,6 +4,11 @@ import (
"time"
)
var weaponUzi = &playerWeapon{
sprite: imageAtlas[ImageUzi],
cooldown: 100 * time.Millisecond,
}
type gamePlayer struct {
x, y float64
@ -11,6 +16,8 @@ type gamePlayer struct {
weapon *playerWeapon
hasTorch bool
score int
soulsRescued int
@ -25,11 +32,9 @@ type gamePlayer struct {
func NewPlayer() (*gamePlayer, error) {
p := &gamePlayer{
weapon: &playerWeapon{
sprite: imageAtlas[ImageUzi],
cooldown: 100 * time.Millisecond,
},
health: 3,
weapon: weaponUzi,
hasTorch: true,
health: 3,
}
return p, nil
}

View File

@ -43,7 +43,7 @@ func LoadEnvironmentSpriteSheet() (*EnvironmentSpriteSheet, error) {
s := &EnvironmentSpriteSheet{}
// Dungeon sprites
dungeonFile, err := assetsFS.Open("assets/sandstone-dungeon/Tiles-SandstoneDungeons.png")
dungeonFile, err := assetsFS.Open("assets/sandstone-dungeon/Tiles-Sandstone-Dungeons.png")
if err != nil {
return nil, err
}

80
ss_ojas_dungeon.go Normal file
View File

@ -0,0 +1,80 @@
package main
import (
"image"
_ "image/png"
"github.com/hajimehoshi/ebiten/v2"
)
var ojasDungeonSS *OjasDungeonSpriteSheet
// OjasDungeonSpriteSheet represents a collection of sprite images.
type OjasDungeonSpriteSheet struct {
Grass11 *ebiten.Image
Grass12 *ebiten.Image
Grass13 *ebiten.Image
Grass14 *ebiten.Image
Grass15 *ebiten.Image
Grass16 *ebiten.Image
Grass21 *ebiten.Image
Grass31 *ebiten.Image
Grass41 *ebiten.Image
Grass42 *ebiten.Image
Grass51 *ebiten.Image
Grass61 *ebiten.Image
Grass71 *ebiten.Image
Grass81 *ebiten.Image
Grass82 *ebiten.Image
Grass91 *ebiten.Image
Wall1 *ebiten.Image
Vent1 *ebiten.Image
Door11 *ebiten.Image
Door12 *ebiten.Image
}
// LoadOjasDungeonSpriteSheet loads the embedded PlayerSpriteSheet.
func LoadOjasDungeonSpriteSheet() (*OjasDungeonSpriteSheet, error) {
tileSize := 32
f, err := assetsFS.Open("assets/ojas-dungeon/dungeon-tileset-1.png")
if err != nil {
return nil, err
}
img, _, err := image.Decode(f)
if err != nil {
return nil, err
}
sheet := ebiten.NewImageFromImage(img)
// spriteAt returns a sprite at the provided coordinates.
spriteAt := func(x, y int) *ebiten.Image {
return sheet.SubImage(image.Rect(x*tileSize, (y+1)*tileSize, (x+1)*tileSize, y*tileSize)).(*ebiten.Image)
}
// Populate spritesheet.
s := &OjasDungeonSpriteSheet{}
s.Grass11 = spriteAt(7, 7)
s.Grass12 = spriteAt(8, 7)
s.Grass13 = spriteAt(9, 7)
s.Grass14 = spriteAt(10, 7)
s.Grass15 = spriteAt(11, 7)
s.Grass16 = spriteAt(12, 7)
s.Grass21 = spriteAt(10, 8)
s.Grass31 = spriteAt(10, 9)
s.Grass41 = spriteAt(15, 9)
s.Grass42 = spriteAt(10, 10)
s.Grass51 = spriteAt(10, 11)
s.Grass61 = spriteAt(10, 12)
s.Grass71 = spriteAt(10, 13)
s.Grass81 = spriteAt(14, 11)
s.Grass82 = spriteAt(10, 14)
s.Grass91 = spriteAt(10, 15)
s.Wall1 = spriteAt(7, 5)
s.Vent1 = spriteAt(9, 13)
s.Door11 = spriteAt(3, 6)
s.Door12 = spriteAt(3, 7)
return s, nil
}

View File

@ -8,7 +8,7 @@ import (
"github.com/hajimehoshi/ebiten/v2"
)
var ojasSS *PlayerSpriteSheet
var playerSS *PlayerSpriteSheet
// PlayerSpriteSheet represents a collection of sprite images.
type PlayerSpriteSheet struct {
@ -23,7 +23,7 @@ var assetsFS embed.FS
func LoadPlayerSpriteSheet() (*PlayerSpriteSheet, error) {
tileSize := 32
f, err := assetsFS.Open("assets/ojas-dungeon/character_run.png")
f, err := assetsFS.Open("assets/ojas-dungeon/character-run.png")
if err != nil {
return nil, err
}

230
win.go Normal file
View File

@ -0,0 +1,230 @@
package main
import (
"math"
"math/rand"
"time"
"github.com/hajimehoshi/ebiten/v2"
)
func newWinLevel(p *gamePlayer) *Level {
l := &Level{
w: 256,
h: 256,
tileSize: 32,
player: p,
}
startX, startY := 108, 108
grid := make([][][]*ebiten.Image, l.w)
for x := 0; x < l.w; x++ {
grid[x] = make([][]*ebiten.Image, l.h)
}
// Add ground.
var lastBones int
bonesDistance := 7
for x := 0; x < l.w; x++ {
excludeBones := x-lastBones < bonesDistance
r := rand.Intn(33)
switch r {
case 0:
grid[x][startY] = []*ebiten.Image{
ojasDungeonSS.Grass11,
}
case 1:
grid[x][startY] = []*ebiten.Image{
ojasDungeonSS.Grass12,
}
case 2:
grid[x][startY] = []*ebiten.Image{
ojasDungeonSS.Grass13,
}
case 3:
grid[x][startY] = []*ebiten.Image{
ojasDungeonSS.Grass14,
}
case 4:
grid[x][startY] = []*ebiten.Image{
ojasDungeonSS.Grass15,
}
case 5:
grid[x][startY] = []*ebiten.Image{
ojasDungeonSS.Grass16,
}
}
grid[x][startY+1] = []*ebiten.Image{
ojasDungeonSS.Grass21,
}
grid[x][startY+2] = []*ebiten.Image{
ojasDungeonSS.Grass31,
}
if rand.Intn(33) != 0 || excludeBones {
grid[x][startY+3] = []*ebiten.Image{
ojasDungeonSS.Grass41,
}
} else {
grid[x][startY+3] = []*ebiten.Image{
ojasDungeonSS.Grass42,
}
}
grid[x][startY+4] = []*ebiten.Image{
ojasDungeonSS.Grass51,
}
grid[x][startY+5] = []*ebiten.Image{
ojasDungeonSS.Grass61,
}
grid[x][startY+6] = []*ebiten.Image{
ojasDungeonSS.Grass71,
}
if rand.Intn(33) != 0 || excludeBones {
grid[x][startY+7] = []*ebiten.Image{
ojasDungeonSS.Grass81,
}
} else {
grid[x][startY+7] = []*ebiten.Image{
ojasDungeonSS.Grass82,
}
}
grid[x][startY+8] = []*ebiten.Image{
ojasDungeonSS.Grass91,
}
}
// Add dungeon.
for x := 0; x < 64; x++ {
for y := 0; y < 64; y++ {
grid[startX-x-1][startY-y] = []*ebiten.Image{
ojasDungeonSS.Wall1,
}
if y == 0 && x%8 == 0 {
grid[startX-x-1][startY-y] = append(grid[startX-x-1][startY-y], ojasDungeonSS.Vent1)
}
}
}
grid[startX-1][startY-1] = append(grid[startX-1][startY-1], ojasDungeonSS.Door11)
grid[startX-1][startY] = append(grid[startX-1][startY], ojasDungeonSS.Door12)
// Add sprites to tiles.
l.tiles = make([][]*Tile, l.h)
for y := 0; y < l.h; y++ {
l.tiles[y] = make([]*Tile, l.w)
for x := 0; x < l.w; x++ {
t := &Tile{}
for _, sprite := range grid[x][y] {
t.AddSprite(sprite)
}
l.tiles[y][x] = t
}
}
l.bakeLightmap()
p.angle = 0
p.x, p.y = float64(startX), float64(startY)
go func() {
// Walk away.
for i := 0; i < 144; i++ {
p.x += 0.05 * (float64(144-i) / 144)
time.Sleep(time.Second / 144)
}
time.Sleep(time.Second / 2)
// Turn around.
p.angle = math.Pi
time.Sleep(time.Second / 2)
throwEnd := float64(startX) - 0.4
// 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)
go func() {
for i := 0; i < 144*3; i++ {
if torchSprite.x < throwEnd {
for i, c := range l.creeps {
if c == torchSprite {
l.creeps = append(l.creeps[:i], l.creeps[i+1:]...)
}
}
}
torchSprite.x -= 0.05
torchSprite.angle -= .1
time.Sleep(time.Second / 144)
}
}()
time.Sleep(time.Second / 2)
// 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)
go func() {
for i := 0; i < 144*3; i++ {
if weaponSprite.x < throwEnd {
for i, c := range l.creeps {
if c == weaponSprite {
l.creeps = append(l.creeps[:i], l.creeps[i+1:]...)
}
}
}
weaponSprite.x -= 0.05
weaponSprite.angle -= .1
time.Sleep(time.Second / 144)
}
}()
// Walk away.
time.Sleep(time.Second / 2)
p.angle = 0
time.Sleep(time.Second / 2)
for i := 0; i < 144; i++ {
p.x += 0.05 * (float64(i) / 144)
time.Sleep(time.Second / 144)
}
for i := 0; i < 144*12; i++ {
if p.health > 0 {
// Game has restarted.
return
}
p.x += 0.05
time.Sleep(time.Second / 144)
}
return
t := time.NewTicker(time.Second / 144)
for range t.C {
p.x += 0.05
p.angle += 0.1
}
}()
return l
}