Add ghosts
After Width: | Height: | Size: 954 B |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 956 B |
After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 399 KiB After Width: | Height: | Size: 34 KiB |
131
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
|
||||
}
|
||||
|
|
180
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
|
||||
}
|
||||
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
|
@ -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",
|
||||
|
|
77
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
7
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.
|
||||
|
|