Add ghosts

This commit is contained in:
Trevor Slocum 2021-10-26 19:21:26 -07:00
parent 066573dbf2
commit 4a155efa14
14 changed files with 359 additions and 142 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 KiB

After

Width:  |  Height:  |  Size: 34 KiB

131
creep.go
View File

@ -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
}

180
game.go
View File

@ -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
}
tileV := g.level.Tile(int(x), int(y)).colorScale
sDB := sD
if dx > 4 {
sDB *= 0.6 / (dx / 4)
}
if dy > 4 {
sDB *= 0.6 / (dy / 4)
}
s := math.Min(1, v+tileV)
sD = sD * 2 * sDB
if sD > 1 {
sD = 1
}
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.

8
img.go
View File

@ -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",

View File

@ -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
}

View File

@ -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

View File

@ -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")
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 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)
// Dungeon sprites
dungeonFile, err := assetsFS.Open("assets/sandstone-dungeon/Tiles-SandstoneDungeons.png")
if err != nil {
return nil, err
}
defer dungeonFile.Close()
dungeonImg, _, err := image.Decode(dungeonFile)
if err != nil {
return nil, err
}
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)
// 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
}

View File

@ -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

View File

@ -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.