diff --git a/assets/creeps/ghost/ghost1.png b/assets/creeps/ghost/ghost1.png new file mode 100644 index 0000000..456da04 Binary files /dev/null and b/assets/creeps/ghost/ghost1.png differ diff --git a/assets/creeps/ghost/ghost1r.png b/assets/creeps/ghost/ghost1r.png new file mode 100644 index 0000000..4d01379 Binary files /dev/null and b/assets/creeps/ghost/ghost1r.png differ diff --git a/assets/creeps/ghost/ghost2.png b/assets/creeps/ghost/ghost2.png new file mode 100644 index 0000000..c0272dd Binary files /dev/null and b/assets/creeps/ghost/ghost2.png differ diff --git a/assets/creeps/ghost/ghost2r.png b/assets/creeps/ghost/ghost2r.png new file mode 100644 index 0000000..b6f5c6f Binary files /dev/null and b/assets/creeps/ghost/ghost2r.png differ diff --git a/assets/ojas-dungeon/character_run.png b/assets/ojas-dungeon/character_run.png index 73edf3d..1ca9b11 100644 Binary files a/assets/ojas-dungeon/character_run.png and b/assets/ojas-dungeon/character_run.png differ diff --git a/assets/sandstone-dungeon/Tiles-Props-pack.png b/assets/sandstone-dungeon/Tiles-Props-pack.png index 0203578..fb2b338 100644 Binary files a/assets/sandstone-dungeon/Tiles-Props-pack.png and b/assets/sandstone-dungeon/Tiles-Props-pack.png differ diff --git a/creep.go b/creep.go index d7f34fd..5522ebb 100644 --- a/creep.go +++ b/creep.go @@ -1,6 +1,7 @@ package main import ( + "log" "math" "math/rand" "sync" @@ -13,6 +14,7 @@ const ( TypeVampire = iota TypeBat TypeGhost + TypeTorch ) type gameCreep struct { @@ -36,9 +38,64 @@ type gameCreep struct { health int + angle float64 + sync.Mutex } +func newCreep(creepType int, l *Level, p *gamePlayer) *gameCreep { + sprites := []*ebiten.Image{ + imageAtlas[ImageVampire1], + imageAtlas[ImageVampire2], + imageAtlas[ImageVampire3], + imageAtlas[ImageVampire2], + } + if creepType == TypeBat { + sprites = []*ebiten.Image{ + batSS.Frame1, + batSS.Frame2, + batSS.Frame3, + batSS.Frame4, + batSS.Frame5, + batSS.Frame6, + batSS.Frame7, + } + } else if creepType == TypeGhost { + sprites = []*ebiten.Image{ + imageAtlas[ImageGhost1], + } + } else if creepType == TypeTorch { + sprites = []*ebiten.Image{ + sandstoneSS.TorchTop1, + sandstoneSS.TorchTop2, + sandstoneSS.TorchTop3, + sandstoneSS.TorchTop4, + sandstoneSS.TorchTop5, + sandstoneSS.TorchTop6, + sandstoneSS.TorchTop7, + sandstoneSS.TorchTop8, + } + } + + startingFrame := 0 + if len(sprites) > 1 { + startingFrame = rand.Intn(len(sprites)) + } + + x, y := l.newSpawnLocation() + return &gameCreep{ + creepType: creepType, + x: x, + y: y, + sprites: sprites, + frames: len(sprites), + frame: startingFrame, + level: l, + player: p, + health: 1, + } +} + func (c *gameCreep) queueNextAction() { if c.creepType == TypeBat { c.nextAction = 288 + rand.Intn(288) @@ -100,6 +157,22 @@ func (c *gameCreep) doNextAction() { randMovementA := (rand.Float64() - 0.5) / 12 randMovementB := (rand.Float64() - 0.5) / 12 + if c.creepType == TypeGhost { + c.angle = angle(c.x, c.y, c.player.x, c.player.y) + + // TODO optimize + if c.angle > math.Pi/2 || c.angle < -1*math.Pi/2 { + c.sprites = []*ebiten.Image{ + imageAtlas[ImageGhost1R], + } + c.angle = c.angle - math.Pi + } else { + c.sprites = []*ebiten.Image{ + imageAtlas[ImageGhost1], + } + } + } + repelled := c.repelled() if !repelled && rand.Intn(13) == 0 { c.seekPlayer() @@ -125,6 +198,54 @@ func (c *gameCreep) repelled() bool { return repelled } +// TODO return true when creep is facing player and player is facing creep +func (c *gameCreep) facingPlayer() bool { + mod := func(v float64) float64 { + for v > math.Pi { + v -= math.Pi + } + for v < math.Pi*-1 { + v += math.Pi + } + return v + } + _ = mod + + ca := math.Remainder(c.angle, math.Pi) + ca = math.Remainder(ca, -math.Pi) + if ca < 0 { + ca = math.Pi + ca + } + ca = c.angle + if ca < 0 { + ca = math.Pi*2 + ca + } + pa := math.Remainder(c.player.angle, math.Pi) + pa = math.Remainder(pa, -math.Pi) + if pa < 0 { + pa = math.Pi + pa + } + pa = c.player.angle + if pa < 0 { + pa = math.Pi*2 + pa + } + + a := ca - pa + if pa > ca { + a = pa - ca + } + + if rand.Intn(70) == 0 { + // TODO + log.Println(ca, pa, a) + } + + return a < 2 + //a2 := c.player.angle - c.angle + // TODO + //return a > math.Pi/2*-1 && a2 > math.Pi/2*-1 && a < math.Pi/2 && a2 < math.Pi/2 +} + func (c *gameCreep) Update() { c.Lock() defer c.Unlock() @@ -133,10 +254,18 @@ func (c *gameCreep) Update() { return } + if c.creepType == TypeTorch { + return + } + c.tick++ repelled := c.repelled() + if c.creepType == TypeGhost && c.facingPlayer() { + return + } + dx, dy := deltaXY(c.x, c.y, c.player.x, c.player.y) seekDistance := 2.0 if !repelled && dx < seekDistance && dy < seekDistance { @@ -191,7 +320,7 @@ func (c *gameCreep) killScore() int { case TypeBat: return 125 case TypeGhost: - return 75 + return 150 default: return 0 } diff --git a/game.go b/game.go index ad15c91..34bd53f 100644 --- a/game.go +++ b/game.go @@ -94,10 +94,6 @@ type game struct { projectiles []*projectile - batSS *BatSpriteSheet - - ojasSS *PlayerSpriteSheet - overlayImg *ebiten.Image op *ebiten.DrawImageOptions @@ -160,6 +156,8 @@ func NewGame() (*game, error) { } func (g *game) flashMessage(message string) { + log.Println(message) + g.flashMessageText = message g.flashMessageUntil = time.Now().Add(3 * time.Second) } @@ -167,12 +165,12 @@ func (g *game) flashMessage(message string) { func (g *game) loadAssets() error { var err error // Load SpriteSheets. - g.ojasSS, err = LoadPlayerSpriteSheet() + ojasSS, err = LoadPlayerSpriteSheet() if err != nil { return fmt.Errorf("failed to load embedded spritesheet: %s", err) } - g.batSS, err = LoadBatSpriteSheet() + batSS, err = LoadBatSpriteSheet() if err != nil { return fmt.Errorf("failed to load embedded spritesheet: %s", err) } @@ -199,44 +197,6 @@ func (g *game) newItem(itemType int) *gameItem { } } -func (g *game) newCreep(creepType int) *gameCreep { - sprites := []*ebiten.Image{ - imageAtlas[ImageVampire1], - imageAtlas[ImageVampire2], - imageAtlas[ImageVampire3], - imageAtlas[ImageVampire2], - } - if creepType == TypeBat { - sprites = []*ebiten.Image{ - g.batSS.Frame1, - g.batSS.Frame2, - g.batSS.Frame3, - g.batSS.Frame4, - g.batSS.Frame5, - g.batSS.Frame6, - g.batSS.Frame7, - } - } - - startingFrame := 0 - if len(sprites) > 1 { - startingFrame = rand.Intn(len(sprites)) - } - - x, y := g.level.newSpawnLocation() - return &gameCreep{ - creepType: creepType, - x: x, - y: y, - sprites: sprites, - frames: len(sprites), - frame: startingFrame, - level: g.level, - player: g.player, - health: 1, - } -} - func (g *game) nextLevel() error { g.levelNum++ if g.levelNum > 13 { @@ -255,11 +215,10 @@ func (g *game) generateLevel() error { } var err error - g.level, err = NewLevel(g.levelNum) + g.level, err = NewLevel(g.levelNum, g.player) if err != nil { return fmt.Errorf("failed to create new level: %s", err) } - g.level.player = g.player // Position player. for { @@ -273,20 +232,10 @@ func (g *game) generateLevel() error { // Spawn items. g.level.items = nil - added := make(map[string]bool) for i := 0; i < spawnGarlic; i++ { itemType := itemTypeGarlic c := g.newItem(itemType) - - addedItem := fmt.Sprintf("%0.0f-%0.0f", c.x, c.y) - if added[addedItem] { - // Already added a gameItem here. - i-- - continue - } - g.level.items = append(g.level.items, c) - added[addedItem] = true } // Spawn starting garlic. item := g.newItem(itemTypeGarlic) @@ -305,20 +254,8 @@ func (g *game) generateLevel() error { g.level.items = append(g.level.items, item) // Spawn creeps. - g.level.creeps = make([]*gameCreep, spawnVampire) for i := 0; i < spawnVampire; i++ { - creepType := TypeVampire - c := g.newCreep(creepType) - - addedCreep := fmt.Sprintf("%0.0f-%0.0f", c.x, c.y) - if added[addedCreep] { - // Already added a gameCreep here. - i-- - continue - } - - g.level.creeps[i] = c - added[addedCreep] = true + g.level.addCreep(TypeVampire) } return nil } @@ -434,6 +371,11 @@ func (g *game) Update() error { c.Update() + if c.creepType == TypeTorch { + continue + } + + // TODO can this move into creep? cx, cy := c.Position() dx, dy := deltaXY(g.player.x, g.player.y, cx, cy) if dx <= biteThreshold && dy <= biteThreshold { @@ -565,7 +507,7 @@ func (g *game) Update() error { dx, dy := deltaXY(g.player.x, g.player.y, item.x, item.y) if dx <= 1 && dy <= 1 { item.health = 0 - g.player.score += item.useScore() + g.player.score += item.useScore() * g.levelNum if item.itemType == itemTypeGarlic { g.playSound(SoundMunch, munchVolume) @@ -613,9 +555,14 @@ UPDATEPROJECTILES: continue } + threshold := bulletHitThreshold + if c.creepType == TypeTorch { + threshold = 1 + } + cx, cy := c.Position() dx, dy := deltaXY(p.x, p.y, cx, cy) - if dx > bulletHitThreshold || dy > bulletHitThreshold { + if dx > threshold || dy > threshold { continue } @@ -663,7 +610,7 @@ UPDATEPROJECTILES: if g.tick%200 == 0 { removed = 0 for i, creep := range g.level.creeps { - if creep.health != 0 { + if creep.health != 0 || creep.creepType == TypeTorch { continue } @@ -679,7 +626,7 @@ UPDATEPROJECTILES: g.level.items = append(g.level.items, item) if g.debugMode { - log.Println("SPAWN GARLIC") + g.flashMessage("SPAWN GARLIC") } } @@ -689,7 +636,7 @@ UPDATEPROJECTILES: g.level.items = append(g.level.items, item) if g.debugMode { - log.Println("SPAWN HOLY WATER") + g.flashMessage("SPAWN HOLY WATER") } } @@ -701,13 +648,10 @@ UPDATEPROJECTILES: spawnAmount *= 4 } if g.debugMode && spawnAmount > 0 { - log.Printf("SPAWN %d VAMPIRES", spawnAmount) + g.flashMessage(fmt.Sprintf("SPAWN %d VAMPIRES", spawnAmount)) } for i := 0; i < spawnAmount; i++ { - creepType := TypeVampire - c := g.newCreep(creepType) - - g.level.creeps = append(g.level.creeps, c) + g.level.addCreep(TypeVampire) } } @@ -721,13 +665,27 @@ UPDATEPROJECTILES: } spawnAmount = rand.Intn(spawnAmount) if g.debugMode && spawnAmount > 0 { - log.Printf("SPAWN %d BATS", spawnAmount) + g.flashMessage(fmt.Sprintf("SPAWN %d BATS", spawnAmount)) } for i := 0; i < spawnAmount; i++ { - creepType := TypeBat - c := g.newCreep(creepType) + g.level.addCreep(TypeBat) + } + } - g.level.creeps = append(g.level.creeps, c) + // Spawn ghosts. + if g.tick%1872 == 0 { + spawnAmount := g.tick / 1872 + if spawnAmount < 1 { + spawnAmount = 1 + } else if spawnAmount > 6 { + spawnAmount = 6 + } + spawnAmount = rand.Intn(spawnAmount) + if g.debugMode && spawnAmount > 0 { + g.flashMessage(fmt.Sprintf("SPAWN %d GHOSTS", spawnAmount)) + } + for i := 0; i < spawnAmount; i++ { + g.level.addCreep(TypeGhost) } } } @@ -752,6 +710,12 @@ UPDATEPROJECTILES: g.player.holyWaters++ g.flashMessage("+ HOLY WATER") } + if ebiten.IsKeyPressed(ebiten.KeyControl) && inpututil.IsKeyJustPressed(ebiten.Key2) { + for i := 0; i < 13; i++ { + g.level.addCreep(TypeGhost) + } + g.flashMessage("+ GHOST") + } if ebiten.IsKeyPressed(ebiten.KeyControl) && inpututil.IsKeyJustPressed(ebiten.Key6) { g.fullBrightMode = !g.fullBrightMode if g.fullBrightMode { @@ -777,7 +741,6 @@ UPDATEPROJECTILES: } if inpututil.IsKeyJustPressed(ebiten.KeyP) { if g.cpuProfile == nil { - log.Println("CPU profiling started...") g.flashMessage("CPU PROFILING STARTED") homeDir, err := os.UserHomeDir() @@ -792,7 +755,6 @@ UPDATEPROJECTILES: return err } } else { - log.Println("Profiling stopped") g.flashMessage("CPU PROFILING STOPPED") pprof.StopCPUProfile() @@ -899,6 +861,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 alpha <= .01 || colorScale <= .01 { + return 0 + } + x, y = g.tilePosition(x, y) // Skip drawing off-screen tiles. @@ -939,27 +905,13 @@ func (g *game) colorScale(x, y float64) float64 { return 1 } - dx, dy := deltaXY(x, y, g.player.x, g.player.y) + v := colorScaleValue(x, y, g.player.x, g.player.y) - sD := 7 / (dx + dy) - if sD > 1 { - sD = 1 - } - - sDB := sD - if dx > 4 { - sDB *= 0.6 / (dx / 4) - } - if dy > 4 { - sDB *= 0.6 / (dy / 4) - } + tileV := g.level.Tile(int(x), int(y)).colorScale - sD = sD * 2 * sDB - if sD > 1 { - sD = 1 - } + s := math.Min(1, v+tileV) - return sD + return s } // renderLevel draws the current Level on the screen. @@ -989,11 +941,11 @@ func (g *game) renderLevel(screen *ebiten.Image) int { } for _, c := range g.level.creeps { - if c.health == 0 { + if c.health == 0 && c.creepType != TypeTorch { continue } - drawn += g.renderSprite(c.x, c.y, 0, 0, 0, 1.0, g.colorScale(c.x, c.y), 1.0, c.sprites[c.frame], screen) + 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 { @@ -1029,17 +981,18 @@ 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 := g.ojasSS.Frame1 + playerSprite := ojasSS.Frame1 playerAngle := g.player.angle weaponSprite := g.player.weapon.spriteFlipped mul := float64(1) if g.player.angle > math.Pi/2 || g.player.angle < -1*math.Pi/2 { - playerSprite = g.ojasSS.Frame2 + playerSprite = ojasSS.Frame2 playerAngle = playerAngle - math.Pi weaponSprite = g.player.weapon.sprite mul = -1 } 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, -10*mul, 2, playerAngle, 1.0, 1.0, 1.0, sandstoneSS.TorchMulti, 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) } @@ -1086,7 +1039,18 @@ func (g *game) hurtCreep(c *gameCreep, damage int) error { } // Killed creep. - g.player.score += c.killScore() + g.player.score += c.killScore() * g.levelNum + + if c.creepType == TypeTorch { + // TODO play break sound + c.frames = 1 + c.frame = 0 + c.sprites = []*ebiten.Image{ + sandstoneSS.TorchTop9, + } + g.level.bakePartialLightmap(int(c.x), int(c.y)) + return nil + } // Play vampire die sound. diff --git a/img.go b/img.go index e07aa9f..18affc4 100644 --- a/img.go +++ b/img.go @@ -11,6 +11,10 @@ const ( ImageVampire1 = iota ImageVampire2 ImageVampire3 + ImageGhost1 + ImageGhost1R + ImageGhost2 + ImageGhost2R ImageGarlic ImageHolyWater ImageHeart @@ -23,6 +27,10 @@ var imageMap = map[int]string{ ImageVampire1: "assets/creeps/vampire/vampire1.png", ImageVampire2: "assets/creeps/vampire/vampire2.png", ImageVampire3: "assets/creeps/vampire/vampire3.png", + ImageGhost1: "assets/creeps/ghost/ghost1.png", + ImageGhost1R: "assets/creeps/ghost/ghost1r.png", + ImageGhost2: "assets/creeps/ghost/ghost2.png", + ImageGhost2R: "assets/creeps/ghost/ghost2r.png", ImageGarlic: "assets/items/garlic.png", ImageHolyWater: "assets/items/holywater.png", ImageHeart: "assets/ui/heart.png", diff --git a/level.go b/level.go index 86c3662..483d08e 100644 --- a/level.go +++ b/level.go @@ -23,6 +23,8 @@ type Level struct { liveCreeps int player *gamePlayer + + torches []*gameCreep } // Tile returns the tile at the provided coordinates, or nil. @@ -97,7 +99,7 @@ SPAWNLOCATION: } // NewLevel returns a new randomly generated Level. -func NewLevel(levelNum int) (*Level, error) { +func NewLevel(levelNum int, p *gamePlayer) (*Level, error) { multiplier := levelNum if multiplier > 2 { multiplier = 2 @@ -106,9 +108,11 @@ func NewLevel(levelNum int) (*Level, error) { w: 336 * multiplier, h: 336 * multiplier, tileSize: 32, + player: p, } - sandstoneSS, err := LoadEnvironmentSpriteSheet() + var err error + sandstoneSS, err = LoadEnvironmentSpriteSheet() if err != nil { return nil, fmt.Errorf("failed to load embedded spritesheet: %s", err) } @@ -198,6 +202,10 @@ func NewLevel(levelNum int) (*Level, error) { case spriteTop: if !bottomLeft || !bottomRight || left || right { neighbor.AddSprite(sandstoneSS.WallPillar) + c := newCreep(TypeTorch, l, l.player) + c.x, c.y = float64(nx), float64(ny) + l.creeps = append(l.creeps, c) + l.torches = append(l.torches, c) } else { neighbor.AddSprite(sandstoneSS.WallTop) } @@ -222,9 +230,74 @@ func NewLevel(levelNum int) (*Level, error) { } } + l.bakeLightmap() + return l, nil } +func (l *Level) bakeLightmap() { + for x := 0; x < l.w; x++ { + for y := 0; y < l.h; y++ { + t := l.tiles[y][x] + v := 0.0 + for _, torch := range l.torches { + if torch.health == 0 { + continue + } + torchV := colorScaleValue(float64(x), float64(y), torch.x, torch.y) + v += torchV + } + t.colorScale = v + } + } +} + +func (l *Level) bakePartialLightmap(lx, ly int) { + radius := 16 + for x := lx - radius; x < lx+radius; x++ { + for y := ly - radius; y < ly+radius; y++ { + t := l.Tile(x, y) + if t == nil { + continue + } + v := 0.0 + for _, torch := range l.torches { + if torch.health == 0 { + continue + } + torchV := colorScaleValue(float64(x), float64(y), torch.x, torch.y) + v += torchV + } + t.colorScale = v + } + } +} + +func (l *Level) addCreep(creepType int) { + c := newCreep(creepType, l, l.player) + l.creeps = append(l.creeps, c) +} + func angle(x1, y1, x2, y2 float64) float64 { return math.Atan2(y1-y2, x1-x2) } + +func colorScaleValue(x, y, bx, by float64) float64 { + dx, dy := deltaXY(x, y, bx, by) + sD := 7 / (dx + dy) + if sD > 1 { + sD = 1 + } + sDB := sD + if dx > 4 { + sDB *= 0.6 / (dx / 4) + } + if dy > 4 { + sDB *= 0.6 / (dy / 4) + } + sD = sD * 2 * sDB + if sD > 1 { + sD = 1 + } + return sD +} diff --git a/ss_bat.go b/ss_bat.go index f854833..1ac496e 100644 --- a/ss_bat.go +++ b/ss_bat.go @@ -7,6 +7,8 @@ import ( "github.com/hajimehoshi/ebiten/v2" ) +var batSS *BatSpriteSheet + // BatSpriteSheet represents a collection of sprite images. type BatSpriteSheet struct { Frame1 *ebiten.Image diff --git a/ss_environment.go b/ss_environment.go index db5a7de..6ff44ed 100644 --- a/ss_environment.go +++ b/ss_environment.go @@ -7,6 +7,8 @@ import ( "github.com/hajimehoshi/ebiten/v2" ) +var sandstoneSS *EnvironmentSpriteSheet + // EnvironmentSpriteSheet represents a collection of sprite images. type EnvironmentSpriteSheet struct { FloorA *ebiten.Image @@ -21,42 +23,78 @@ type EnvironmentSpriteSheet struct { WallTopLeft *ebiten.Image WallTopRight *ebiten.Image WallPillar *ebiten.Image + TorchTop1 *ebiten.Image + TorchTop2 *ebiten.Image + TorchTop3 *ebiten.Image + TorchTop4 *ebiten.Image + TorchTop5 *ebiten.Image + TorchTop6 *ebiten.Image + TorchTop7 *ebiten.Image + TorchTop8 *ebiten.Image + TorchTop9 *ebiten.Image + TorchMulti *ebiten.Image } // LoadEnvironmentSpriteSheet loads the embedded EnvironmentSpriteSheet. func LoadEnvironmentSpriteSheet() (*EnvironmentSpriteSheet, error) { tileSize := 32 - f, err := assetsFS.Open("assets/sandstone-dungeon/Tiles-SandstoneDungeons.png") + // Populate EnvironmentSpriteSheet. + s := &EnvironmentSpriteSheet{} + + // Dungeon sprites + dungeonFile, err := assetsFS.Open("assets/sandstone-dungeon/Tiles-SandstoneDungeons.png") if err != nil { return nil, err } - img, _, err := image.Decode(f) + defer dungeonFile.Close() + dungeonImg, _, err := image.Decode(dungeonFile) 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) + dungeonSheet := ebiten.NewImageFromImage(dungeonImg) + // dungeonSpriteAt returns a sprite at the provided coordinates. + dungeonSpriteAt := func(x, y int) *ebiten.Image { + return dungeonSheet.SubImage(image.Rect(x*tileSize, (y+1)*tileSize, (x+1)*tileSize, y*tileSize)).(*ebiten.Image) } + s.FloorA = dungeonSpriteAt(3, 1) + s.FloorB = dungeonSpriteAt(2, 0) + s.FloorC = dungeonSpriteAt(2, 1) + s.WallTop = dungeonSpriteAt(1, 2) + s.WallBottom = dungeonSpriteAt(1, 6) + s.WallBottomLeft = dungeonSpriteAt(2, 6) + s.WallBottomRight = dungeonSpriteAt(0, 6) + s.WallLeft = dungeonSpriteAt(2, 2) + s.WallRight = dungeonSpriteAt(0, 2) + s.WallTopLeft = dungeonSpriteAt(2, 3) + s.WallTopRight = dungeonSpriteAt(0, 3) + s.WallPillar = dungeonSpriteAt(8, 4) - // Populate EnvironmentSpriteSheet. - s := &EnvironmentSpriteSheet{} - s.FloorA = spriteAt(3, 1) - s.FloorB = spriteAt(2, 0) - s.FloorC = spriteAt(2, 1) - s.WallTop = spriteAt(1, 2) - s.WallBottom = spriteAt(1, 6) - s.WallBottomLeft = spriteAt(2, 6) - s.WallBottomRight = spriteAt(0, 6) - s.WallLeft = spriteAt(2, 2) - s.WallRight = spriteAt(0, 2) - s.WallTopLeft = spriteAt(2, 3) - s.WallTopRight = spriteAt(0, 3) - s.WallPillar = spriteAt(8, 4) + // Prop sprites + propFile, err := assetsFS.Open("assets/sandstone-dungeon/Tiles-Props-pack.png") + if err != nil { + return nil, err + } + defer propFile.Close() + propImg, _, err := image.Decode(propFile) + if err != nil { + return nil, err + } + propSheet := ebiten.NewImageFromImage(propImg) + // dungeonSpriteAt returns a sprite at the provided coordinates. + propSpriteAt := func(x, y int) *ebiten.Image { + return propSheet.SubImage(image.Rect(x*tileSize, (y+1)*tileSize, (x+1)*tileSize, y*tileSize)).(*ebiten.Image) + } + s.TorchTop1 = propSpriteAt(0, 2) + s.TorchTop2 = propSpriteAt(1, 2) + s.TorchTop3 = propSpriteAt(2, 2) + s.TorchTop4 = propSpriteAt(3, 2) + s.TorchTop5 = propSpriteAt(4, 2) + s.TorchTop6 = propSpriteAt(5, 2) + s.TorchTop7 = propSpriteAt(6, 2) + s.TorchTop8 = propSpriteAt(7, 2) + s.TorchTop9 = propSpriteAt(8, 2) + s.TorchMulti = propSpriteAt(2, 4) return s, nil } diff --git a/ss_player.go b/ss_player.go index 13092c9..95d870e 100644 --- a/ss_player.go +++ b/ss_player.go @@ -8,6 +8,8 @@ import ( "github.com/hajimehoshi/ebiten/v2" ) +var ojasSS *PlayerSpriteSheet + // PlayerSpriteSheet represents a collection of sprite images. type PlayerSpriteSheet struct { Frame1 *ebiten.Image diff --git a/tile.go b/tile.go index e128438..10e2608 100644 --- a/tile.go +++ b/tile.go @@ -7,9 +7,10 @@ import ( // Tile represents a space with an x,y coordinate within a Level. Any number of // sprites may be added to a Tile. type Tile struct { - sprites []*ebiten.Image - floor bool - wall bool + sprites []*ebiten.Image + floor bool + wall bool + colorScale float64 // Minimum color scale (brightness) } // AddSprite adds a sprite to the Tile.