diff --git a/go.mod b/go.mod index e286172..54d4f2e 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module app -go 1.21.0 +go 1.24.2 require github.com/gen2brain/raylib-go/raylib v0.0.0-20230826160040-f770ca098119 diff --git a/main.go b/main.go index c4160dc..19558e0 100644 --- a/main.go +++ b/main.go @@ -4,33 +4,78 @@ import ( "fmt" "math" "math/rand" - "slices" rl "github.com/gen2brain/raylib-go/raylib" ) const ( - WIDTH = 1100 - HEIGHT = 700 - buildingSpacing = 2.5 - buildingSize = 1.2 - maxBuildingHeight = 10 - maxColumns = 5 - enemyAcc = 0.01 - maxSpeed = 0.1 - buildingsHealth = 3 + blocksNumber = 10 + buildsPerBlock = 10 + totalBuildings = 1024 + // totalBuildings = 1 + MARGIN = 20 + WIDTH = 1100 + HEIGHT = 700 + buildingSpacing = 18 + buildingSize = 8 + maxBuildingHeight = 60 + maxColumns = 5 + enemyAcc = 0.01 + maxSpeed = 0.1 + buildingsHealth = 20 + oneSecond second = 1 ) -type enemy struct { - pos rl.Vector3 - speed float32 +var gravity = rl.NewVector3(0, -2, 0) + +// var globalParticles []Particle + +type object struct { + pos rl.Vector3 } -type remainingFrames int -type animationFunc func() remainingFrames +func (b *object) Pos() rl.Vector3 { return b.pos } + +type livingObject struct { + object + health int +} + +func (o *livingObject) IsAlive() bool { return o.health <= 0 } +func (o *livingObject) Life() int { return o.health } +func (o *livingObject) CauseDamage(damage int) { o.health -= damage } + +type kinecticObject struct { + object + speed rl.Vector3 +} + +type shootingEnemy struct { + livingObject + dodgeDelay float64 + animate animationFunc + color rl.Color + fireRate *timer +} + +type projectile struct { + kinecticObject + destroyed bool +} + +type bombProjectile struct { + projectile +} + +type missileProjectile struct { + projectile +} + +type animationOver bool +type animationFunc func() animationOver type building struct { - pos rl.Vector3 + object size rl.Vector3 color rl.Color life int @@ -42,214 +87,655 @@ type healthBar struct { life int damageTaken float32 lastUpdate float64 + pos Somewhere } type character struct { camera rl.Camera damage int -} - -type projectile struct { - pos rl.Vector3 - speed rl.Vector3 + life int } type scene struct { - buildings []*building - healthBar *healthBar - projectiles []*projectile - character *character - emitters []*emitter + things []DrawableLivingThing + // buildings []Building + healthBar *healthBar + // projectiles []*projectile + character Player + particles []Particle + // enemies []Enemy + // emitters []Emitter } -type particle struct { +func (s *scene) AppendThing(t ...DrawableLivingThing) { s.things = append(s.things, t...) } +func (s *scene) AppendParticle(p ...Particle) { s.particles = append(s.particles, p...) } + +func GetThing[T DrawableLivingThing](s *scene) []T { + var ret []T + for _, thing := range s.things { + if conv, ok := thing.(T); ok && conv.IsAlive() { + ret = append(ret, conv) + } + } + return ret +} + +func (s *scene) Buildings() []Building { + b := GetThing[*building](s) + var r []Building = make([]Building, len(b)) + for i := range b { + r[i] = b[i] + } + return r +} + +func (s *scene) Enemies() []Enemy { + return GetThing[Enemy](s) +} + +type second float64 + +type timer struct { + ttl second + time float32 + start float64 +} + +func (t *timer) tick() { + t.time += rl.GetFrameTime() +} + +// tick, timeout, reset +func (t *timer) ttr() bool { + t.tick() + if t.isTimeout() { + t.reset() + return true + } + return false +} + +func (t *timer) isTimeout() bool { + return t.time >= float32(t.ttl) +} + +func (t *timer) reset() { + t.start = float64(t.time) + t.time = 0 +} + +func (t *timer) unit() float32 { + return t.time / float32(t.ttl) +} + +func newTimer(duration second) *timer { + return &timer{time: 0, ttl: duration, start: rl.GetTime()} +} + +func newTimerWithStartDelay(duration, delay second) *timer { + return &timer{time: float32(delay), ttl: duration, start: rl.GetTime()} +} + +type Thing interface { + Update() +} + +type Somewhere interface { + Pos() rl.Vector3 +} + +type Drawable interface { + Draw() +} + +type Living interface { + IsAlive() bool + Life() int + CauseDamage(int) +} + +type DrawableLivingThing interface { + Collide + Drawable + Living + Thing + Somewhere +} + +type Animate interface { + Animation() animationFunc + Animate() animationOver + SetAnimation(animationFunc) +} + +type Collide interface { + RayCollision(rl.Ray) rl.RayCollision + SphereCollision(centerSphere rl.Vector3, radiusSphere float32) bool +} + +type Particle interface { + Thing + Living + Drawable +} + +type Enemy interface { + Collide + Thing + Drawable + Living + Somewhere + Animate +} + +type Player interface { + Somewhere + Thing + // Drawable + Living +} + +type Building interface { + Somewhere + Thing + Drawable + Living + Animate + Collide +} + +// type Emitter interface { +// Alive +// Drawable +// Update() int +// } + +type simpleEmitter struct { + particles []Particle + pos rl.Vector3 + particleAmount int +} + +type explosionEmitter struct { + simpleEmitter +} + +type simpleParticle struct { // birth float32 lifetime float64 pos rl.Vector3 speed rl.Vector3 } -type emitter struct { - particles []*particle - pos rl.Vector3 - particleAmount int +type explosionParticle struct { + simpleParticle + trailTimer *timer +} + +type smokeTrailParticle struct { + simpleParticle } var city *scene -func update_emitters() { - for i := 0; i < len(city.emitters); i++ { - - e := city.emitters[i] - remainingParticles := e.update() - - if remainingParticles == 0 { - city.emitters = slices.Delete(city.emitters, i, i+1) - i-- - } +func NewHealthBar(where Somewhere) *healthBar { + return &healthBar{pos: where} +} +func NewShootingEnemy(pos rl.Vector3, health int) Enemy { + return &shootingEnemy{ + dodgeDelay: rand.Float64() * 3.1, + fireRate: newTimerWithStartDelay(0.5, second(rand.Float64()*10.1)), + livingObject: livingObject{object: object{pos: pos}, health: health}, } } -func newEmitter(pos rl.Vector3, particleAmount int) *emitter { - emitter := emitter{ - pos: pos, - particleAmount: particleAmount, - } - - for i := 0; i < particleAmount; i++ { - direction := rl.NewVector3( - rand.Float32()*2-1, - 1, - rand.Float32()*2-1, - ) - direction = rl.Vector3Normalize(direction) - direction = rl.Vector3Scale(direction, 0.3+rand.Float32()/3) - particle := particle{ - lifetime: rl.GetTime() + 2, - speed: direction, - pos: pos, - } - emitter.particles = append(emitter.particles, &particle) - } - - return &emitter +func (e *shootingEnemy) RayCollision(ray rl.Ray) rl.RayCollision { + return rl.GetRayCollisionSphere(ray, e.pos, 1) } -func (e *emitter) update() int { - gravity := rl.NewVector3(0, -0.02, 0) +func (e *shootingEnemy) SphereCollision(centerSphere rl.Vector3, radiusSphere float32) bool { + return rl.CheckCollisionSpheres(e.pos, 1, centerSphere, radiusSphere) - remainingParticles := 0 - - rl.BeginBlendMode(rl.BlendAdditive) - { - for _, particle := range e.particles { - if rl.GetTime() > particle.lifetime { - continue - } - - particle.speed = rl.Vector3Add(particle.speed, gravity) - newPos := rl.Vector3Add(particle.pos, particle.speed) - if newPos.Y <= 0 { - particle.pos.Y = 0 - } else { - particle.pos = newPos - } - - rl.DrawCubeV(particle.pos, rl.NewVector3(.1, .1, .1), rl.Yellow) - - remainingParticles += 1 - } - } - rl.EndBlendMode() - - return remainingParticles } -// retona true caso projétil precise ser destruido -func (p *projectile) isHit() bool { - gravity := rl.NewVector3(0, -0.02, 0) - p.speed = rl.Vector3Add(p.speed, gravity) - p.pos = rl.Vector3Add(p.pos, p.speed) - - if p.pos.Y < 0 { - city.emitters = append(city.emitters, newEmitter(p.pos, 500)) - for _, building := range city.buildings { - if dist := rl.Vector3Distance(p.pos, building.pos); dist < 10 { - building.causeDamage(3) - } - } +func (e *shootingEnemy) Animate() animationOver { + if e.animate != nil { + return e.animate() + } else { return true } +} +func (e *shootingEnemy) Animation() animationFunc { + return e.animate +} + +func (e *shootingEnemy) SetAnimation(f animationFunc) { + e.animate = f +} + +func (e *shootingEnemy) CauseDamage(damage int) { + e.health -= damage + e.animate = shakeEnemy(e) +} + +func (e *shootingEnemy) IsAlive() bool { + return e.health > 0 || e.animate != nil +} + +func (e *shootingEnemy) Draw() { + if e.Animation() != nil { + if over := e.Animate(); over { + e.SetAnimation(nil) + } + return + } + playerDirection := rl.NewVector3(0, 1, 0) + rl.DrawSphere(e.Pos(), 1, rl.Red) + rl.DrawLine3D(e.pos, rl.Vector3Add(e.pos, rl.Vector3Scale(playerDirection, 40)), rl.Blue) +} + +func raycast(pos, direction rl.Vector3) (DrawableLivingThing, bool) { + var ( + closestDistance = math.Inf(1) + closest DrawableLivingThing + ) + for _, thing := range city.things { + if !thing.IsAlive() { + continue + } + thingDistance := rl.Vector3Distance(pos, thing.Pos()) + if thingDistance > float32(closestDistance) { + continue + } + collision := thing.RayCollision(rl.NewRay(pos, direction)) + if collision.Hit { + closestDistance = float64(thingDistance) + closest = thing + } + } + return closest, closest != nil +} + +func (e *shootingEnemy) Update() { + if e.health <= 0 { + return + } + e.fireRate.tick() + playerDistance := rl.Vector3Subtract(city.character.Pos(), e.Pos()) + playerDirection := rl.Vector3Normalize(playerDistance) + if hitThing, ok := raycast(e.pos, playerDirection); ok { + if _, ok := hitThing.(*character); ok && e.fireRate.isTimeout() { + spawnPos := rl.Vector3Add(e.Pos(), rl.Vector3Scale(playerDirection, 2)) + speed := rl.Vector3Scale(playerDirection, 1.5) + city.AppendParticle(NewMissileProjectile(spawnPos, speed)) + e.fireRate.reset() + } + } + var speed, sideways rl.Vector3 + if rl.Vector3Length(playerDistance) < 50 { + distance := rl.Vector3Negate(playerDirection) + speed = rl.Vector3Add(speed, distance) + } + { + sideways = rl.Vector3Transform(playerDirection, rl.MatrixRotateY(90)) + sideways = rl.Vector3Scale(sideways, float32(math.Sin(rl.GetTime()+e.dodgeDelay))) + speed = rl.Vector3Add(speed, sideways) + } + speed = rl.Vector3Normalize(speed) + e.pos = rl.Vector3Add(e.pos, rl.Vector3Scale(speed, 0.1)) +} + +func (m *missileProjectile) CauseDamage(int) { +} + +func (m *missileProjectile) Update() { + m.pos = rl.Vector3Add(m.pos, m.speed) + var ( + closestDistance = math.Inf(1) + closest Building + ) + for _, building := range city.Buildings() { + if !building.IsAlive() { + continue + } + hitDistance := rl.Vector3Distance(m.Pos(), building.Pos()) + if hitDistance < float32(closestDistance) { + if building.SphereCollision(m.Pos(), 0.5) { + closest = building + } + } + } + if closest != nil { + closest.CauseDamage(3) + m.destroyed = true + } +} + +func (m *missileProjectile) Life() int { + return 999 +} + +func (m *missileProjectile) IsAlive() bool { + return !m.destroyed +} + +func (m *missileProjectile) Draw() { + n := rl.Vector3Normalize(m.speed) + rl.PushMatrix() + { + rl.Translatef(m.pos.X, m.pos.Y, m.pos.Z) + rl.Rotatef(270, n.X, n.Y, n.Z) + rl.DrawCylinder(rl.Vector3Zero(), 0.1, 0.2, 0.5, 8, rl.White) + } + rl.PopMatrix() +} + +func (particle *simpleParticle) IsAlive() bool { + return rl.GetTime() < particle.lifetime +} + +func (p *simpleParticle) Update() { + p.pos = rl.Vector3Add(p.pos, p.speed) +} + +func (p *simpleParticle) Pos() rl.Vector3 { + return p.pos +} + +func (particle *simpleParticle) Draw() { + panic("não implementado") +} + +func NewExplosionParticle(direction, pos rl.Vector3) *explosionParticle { + return &explosionParticle{ + simpleParticle: simpleParticle{ + lifetime: rl.GetTime() + 10 + rand.Float64()*5, speed: direction, pos: pos, + }, + trailTimer: newTimer(oneSecond * 0.01), + } +} + +func (p *explosionParticle) CauseDamage(int) {} +func (p *explosionParticle) Life() int { return -1 } + +func (p *explosionParticle) Update() { + timer := p.trailTimer + timer.tick() + p.speed = rl.Vector3Add(p.speed, gravity) + newPos := rl.Vector3Add(p.pos, rl.Vector3Scale(p.speed, rl.GetFrameTime())) + if newPos.Y <= 0 { + p.pos.Y = 0 + } else { + p.pos = newPos + if timer.isTimeout() { + // addParticles(NewSmokeTrailParticle(p.pos, p.speed)) + timer.reset() + } + } +} + +func (p *explosionParticle) Draw() { + rl.DrawCubeV(p.pos, rl.NewVector3(.1, .1, .1), rl.Gray) +} + +// func NewSmokeTrailParticle(pos, speed rl.Vector3) *smokeTrailParticle { +// return &smokeTrailParticle{ +// simpleParticle: simpleParticle{ +// lifetime: rl.GetTime() + 1, +// speed: rl.Vector3Add(rl.NewVector3(0, .1, 0), rl.Vector3Scale(speed, 0.1)), pos: pos, +// }, +// } +// } + +func (p *smokeTrailParticle) Draw() { + rl.BeginBlendMode(rl.BlendAdditive) + { + rl.DrawCubeV(p.pos, rl.NewVector3(.05, .05, .05), rl.Gray) + } + rl.EndBlendMode() +} + +func (p *bombProjectile) Pos() rl.Vector3 { + return p.pos +} + +func (p *bombProjectile) Draw() { rl.DrawSphere(p.pos, 0.5, rl.Gray) +} + +func (p *bombProjectile) IsAlive() bool { + return !p.destroyed +} + +func (p *bombProjectile) CauseDamage(int) { + p.destroyed = true +} + +func (p *bombProjectile) Life() int { + return 1 +} + +func (p *bombProjectile) Update() { + p.speed = rl.Vector3Add(p.speed, gravity) + // FIXME: Voa mais do que deveria em frame rates mais baixos. + p.pos = rl.Vector3Add(p.pos, rl.Vector3Scale(p.speed, rl.GetFrameTime())) + if rl.Vector3Length(p.pos) > 1000 { + p.destroyed = true + } + explode := func() { + p.destroyed = true + for range 500 { + direction := rl.NewVector3(rand.Float32()*2-1, 1, rand.Float32()*2-1) + direction = rl.Vector3Normalize(direction) + direction = rl.Vector3Scale(direction, 35+rand.Float32()*35) + city.AppendParticle(NewExplosionParticle(direction, p.pos)) + } + for _, thing := range city.things { + if dist := rl.Vector3Distance(p.pos, thing.Pos()); dist < 40 { + thing.CauseDamage(10) + } + } + } + if p.pos.Y < 0 { + explode() + return + } + var ( + closestDistance = math.Inf(1) + closest Building + ) + for _, building := range city.Buildings() { + if !building.IsAlive() { + continue + } + hitDistance := rl.Vector3Distance(p.pos, building.Pos()) + if hitDistance < float32(closestDistance) { + if building.SphereCollision(p.pos, 0.5) { + closest = building + } + } + } + if closest != nil { + explode() + return + } +} + +func drawPlane() { + rl.DrawPlane( + rl.NewVector3(16, 0.0, 16), + rl.NewVector2(2048, 2048), + rl.NewColor(50, 50, 50, 255), + ) +} + +func (s *scene) Draw() { + character, ok := s.character.(*character) + if !ok { + panic(ok) + } + + rl.BeginDrawing() + rl.ClearBackground(rl.LightGray) + + rl.BeginMode3D(character.camera) + { + for _, thing := range s.things { + // FIXME: + if drawable, ok := thing.(Drawable); ok { + drawable.Draw() + } + } + for _, particle := range s.particles { + particle.Draw() + } + drawPlane() + // direction = rl.Vector3Subtract( + // character.camera.Target, character.camera.Position) + // aim = rl.Vector3Lerp(direction, aim, 0.95) + // rl.DrawSphere(rl.Vector3Add(character.camera.Position, aim), 0.1, rl.Red) + } + rl.EndMode3D() + + drawHUD() + + rl.DrawLineV( + rl.NewVector2(WIDTH/2-5, HEIGHT/2-5), + rl.NewVector2(WIDTH/2+5, HEIGHT/2+5), + rl.Red, + ) + rl.DrawLineV( + rl.NewVector2(WIDTH/2-5, HEIGHT/2+5), + rl.NewVector2(WIDTH/2+5, HEIGHT/2-5), + rl.Red, + ) + rl.DrawFPS(0, HEIGHT-30) + + rl.EndDrawing() +} + +func (s *scene) Update() { + for i := 0; i < len(s.particles); i++ { + particle := s.particles[i] + if !particle.IsAlive() { + city.particles[i] = city.particles[len(city.particles)-1] + city.particles = city.particles[:len(city.particles)-1] + i-- + continue + } + particle.Update() + } + for i := 0; i < len(s.things); i++ { + thing := s.things[i] + if alive, ok := thing.(Living); ok && !alive.IsAlive() { + city.things[i] = city.things[len(city.things)-1] + city.things = city.things[:len(city.things)-1] + i-- + continue + } + thing.Update() + } +} + +func (c *character) Draw() { + +} + +func (c *character) Life() int { + return c.life +} + +func (c *character) RayCollision(rl.Ray) rl.RayCollision { + return rl.RayCollision{} +} + +func (c *character) SphereCollision(centerSphere rl.Vector3, radiusSphere float32) bool { return false } -func update_projectiles() { - for i, p := range city.projectiles { - if hit := p.isHit(); hit { - city.projectiles[i] = city.projectiles[len(city.projectiles)-1] - city.projectiles = city.projectiles[:len(city.projectiles)-1] - } - } +func (c *character) CauseDamage(damage int) { + c.life -= damage } -func (c *character) kill_aurea() { - const min = 10 - for _, b := range city.buildings { - if distance := rl.Vector3Distance(c.camera.Position, b.pos); distance < min { - b.causeDamage(c.damage) - } - } +func (c *character) IsAlive() bool { + return c.life <= 0 } -func (c *character) throw_bomb() { - if !rl.IsMouseButtonPressed(rl.MouseRightButton) { - return - } - - direction := rl.Vector3Subtract(c.camera.Target, c.camera.Position) - - projectile := projectile{ - pos: c.camera.Position, - speed: rl.Vector3Scale(direction, 0.15), - } - - city.projectiles = append(city.projectiles, &projectile) +func (c *character) Pos() rl.Vector3 { + return c.camera.Position } -func (c *character) update() { +func (c *character) Update() { shake := rl.NewVector3( randomShake(0.003)*city.healthBar.damageTaken, randomShake(0.003)*city.healthBar.damageTaken, randomShake(0.003)*city.healthBar.damageTaken, ) c.camera.Target = rl.Vector3Add(c.camera.Target, shake) + rl.UpdateCamera(&c.camera, rl.CameraFirstPerson) + // c.laserBeam() + // c.killAurea() + c.throwBomb() +} - c.laser_beam() +func (c *character) killAurea() { + const min = 10 + for _, b := range city.Buildings() { + if distance := rl.Vector3Distance(c.camera.Position, b.Pos()); distance < min { + b.CauseDamage(c.damage) + } + } +} - // c.kill_aurea() +func NewProjectile(pos, direction rl.Vector3) *projectile { + return &projectile{kinecticObject: kinecticObject{object: object{pos: pos}, speed: direction}} +} - c.throw_bomb() +func NewBombProjectile(pos, direction rl.Vector3) *bombProjectile { + return &bombProjectile{projectile: *NewProjectile(pos, direction)} +} + +func NewMissileProjectile(pos, direction rl.Vector3) *missileProjectile { + return &missileProjectile{projectile: *NewProjectile(pos, direction)} +} + +func (c *character) throwBomb() { + if !rl.IsMouseButtonPressed(rl.MouseRightButton) { + return + } + direction := rl.Vector3Normalize(rl.Vector3Subtract(c.camera.Target, c.camera.Position)) + city.AppendParticle(NewBombProjectile(c.camera.Position, rl.Vector3Scale(direction, 130))) } func newCharacter() *character { - - camera := rl.Camera3D{ - Position: rl.NewVector3(4.0, 2.0, 4.0), - Target: rl.NewVector3(0.0, 1.8, 0.0), - Up: rl.NewVector3(0.0, 1.0, 0.0), - Fovy: 80.0, - Projection: rl.CameraPerspective, - } return &character{ - camera: camera, + camera: rl.Camera3D{ + Position: rl.NewVector3(4.0, 2.0, 4.0), + Target: rl.NewVector3(0.0, 1.8, 0.0), + Up: rl.NewVector3(0.0, 1.0, 0.0), + Fovy: 80.0, + Projection: rl.CameraPerspective, + }, damage: 1, } } -func draw_plane() { - rl.DrawPlane( - rl.NewVector3(16, 0.0, 16), - rl.NewVector2(128.0, 128.0), - rl.NewColor(50, 50, 50, 255), - ) -} - func randomShake(amount float32) float32 { return rand.Float32()*amount - amount/2 } -func (hb *healthBar) update() { - const MARGIN = 20 +func (hb *healthBar) Draw() { const BARWIDTH = (WIDTH - MARGIN*2) currentLife := 0 - maxLife := buildingsHealth * len(city.buildings) - for _, b := range city.buildings { - currentLife += b.life + for _, thing := range city.things { + if building, ok := thing.(*building); ok { + currentLife += building.Life() + } } + maxLife := buildingsHealth * totalBuildings position := rl.NewVector2(MARGIN, 10) barWidth := BARWIDTH * currentLife / maxLife @@ -275,19 +761,46 @@ func (hb *healthBar) update() { hb.damageTaken *= 0.95 } -func newHealthBar(buildings []*building) *healthBar { - return &healthBar{ - life: buildingsHealth * len(buildings), - } +func newHealthBar(buildings []Building) *healthBar { + return &healthBar{life: buildingsHealth * len(buildings)} } -func (b *building) causeDamage(damage int) { +func (b *building) Update() {} + +func (b *building) SphereCollision(centerSphere rl.Vector3, radiusSphere float32) bool { + return rl.CheckCollisionBoxSphere(b.boundingBox, centerSphere, radiusSphere) +} + +func (b *building) RayCollision(ray rl.Ray) rl.RayCollision { + return rl.GetRayCollisionBox(ray, b.boundingBox) +} + +func (b *building) Life() int { + return b.life +} + +func (b *building) Animation() animationFunc { + return b.animate +} + +func (b *building) Animate() animationOver { + return b.animate() +} + +func (b *building) SetAnimation(f animationFunc) { + b.animate = f +} + +func (b *building) CauseDamage(damage int) { if b.animate != nil || b.life <= 0 { return } b.animate = shakeBuilding(b) b.life -= damage - // fmt.Println("damage:", damage, "life:", b.life) +} + +func (b *building) IsAlive() bool { + return b.life > 0 || b.animate != nil } func newBuilding(x, z float32) *building { @@ -297,7 +810,6 @@ func newBuilding(x, z float32) *building { uint8(rl.GetRandomValue(0, 180)), 255, ) - pos := rl.NewVector3(x*buildingSpacing, 0, z*buildingSpacing) size := rl.NewVector3( buildingSize, @@ -314,11 +826,10 @@ func newBuilding(x, z float32) *building { size.Y/2, pos.Z+buildingSize/2, ) - return &building{ color: color, life: buildingsHealth, - pos: pos, + object: object{pos: pos}, size: size, boundingBox: rl.NewBoundingBox(min, max), } @@ -326,12 +837,10 @@ func newBuilding(x, z float32) *building { func drawWireframe(building building) { increasedSize := rl.Vector3Scale(building.size, 1.05) - invertedColor := building.color invertedColor.R += 127 invertedColor.G += 127 invertedColor.B += 127 - rl.DrawCubeWiresV( building.pos, increasedSize, @@ -339,65 +848,70 @@ func drawWireframe(building building) { ) } +func shakeEnemy(enemy *shootingEnemy) animationFunc { + duration := newTimer(0.7) + return func() animationOver { + if 0 == 0 { + if !enemy.IsAlive() { + enemy.color = rl.NewColor(255, 0, 0, 255) + } else { + enemy.color = rl.RayWhite + } + } + shake := rl.NewVector3( + rand.Float32()*.2-.1, + rand.Float32()*.1-.05, + rand.Float32()*.2-.1, + ) + rl.DrawSphere(rl.Vector3Add(enemy.Pos(), shake), 1, rl.Red) + return animationOver(duration.ttr()) + } +} + func shakeBuilding(b *building) animationFunc { - var frame remainingFrames = 60 * 0.7 - return func() remainingFrames { - + duration := newTimer(0.7) + t := newTimer(0.08) + return func() animationOver { building := *b - - if frame%4 == 0 { + if t.ttr() { if b.life == 0 { building.color = rl.NewColor(255, 0, 0, 255) } else { building.color = rl.RayWhite } } - drawWireframe(building) - shake := rl.NewVector3( rand.Float32()*.2-.1, rand.Float32()*.1-.05, rand.Float32()*.2-.1, ) - // shake = rl.Vector3Scale(shake, float32(math.Sin(rl.GetTime()) + 1)/3) + shake = rl.Vector3Scale(shake, float32(math.Sin(rl.GetTime())+1)/3) rl.DrawCubeV( rl.Vector3Add(building.pos, shake), building.size, building.color, ) - - frame -= 1 - return frame + return animationOver(duration.ttr()) } } -func draw_buildings() { - for _, building := range city.buildings { - - hasAnimation := building.animate != nil - - if building.life <= 0 && !hasAnimation { - continue - } - - if hasAnimation { - - remainingFrames := building.animate() - - if remainingFrames == 0 { - building.animate = nil - } - - continue - } - - drawWireframe(*building) - rl.DrawCubeV(building.pos, building.size, building.color) +func (b *building) Draw() { + hasAnimation := b.Animation() != nil + if b.Life() <= 0 && !hasAnimation { + return } + if hasAnimation { + if over := b.Animate(); over { + b.SetAnimation(nil) + } + return + } + drawWireframe(*b) + rl.DrawCubeV(b.pos, b.size, b.color) } -func (c *character) laser_beam() { +func (c *character) laserBeam() { if !rl.IsMouseButtonDown(rl.MouseLeftButton) { return } @@ -405,52 +919,72 @@ func (c *character) laser_beam() { Position: c.camera.Position, Direction: rl.Vector3Subtract(c.camera.Target, c.camera.Position), } - closestDistance := math.Inf(1) - var closest *building - - for _, building := range city.buildings { - - if building.life == 0 { + var closest Building + for _, building := range city.Buildings() { + if !building.IsAlive() { continue } - - playerDistance := rl.Vector3Distance(c.camera.Position, building.pos) - + playerDistance := rl.Vector3Distance(c.camera.Position, building.Pos()) if playerDistance < float32(closestDistance) { - - collision := rl.GetRayCollisionBox(ray, building.boundingBox) - + collision := building.RayCollision(ray) if collision.Hit { - closestDistance = float64(playerDistance) closest = building } } } - if closest != nil { - closest.causeDamage(c.damage) + closest.CauseDamage(c.damage) } } -func newBuildings() []*building { - var buildings []*building - - for i := 0; i < 32; i++ { - for j := 0; j < 32; j++ { - buildings = append(buildings, newBuilding(float32(i), float32(j))) +func newBuildings() []Building { + var buildings []Building + mb := int(math.Sqrt(totalBuildings)) + for i := 0; i < mb; i++ { + for j := 0; j < mb; j++ { + buildings = append(buildings, newBuilding(float32(i)-10, float32(j)-10)) } } - return buildings } +func newBuildingBlocks() []Building { + var buildings []Building + for i := 0; i < blocksNumber; i++ { + for j := 0; j < blocksNumber; j++ { + for k := 0; k < buildsPerBlock; k++ { + for l := 0; l < buildsPerBlock; l++ { + buildings = append(buildings, newBuilding(float32(i), float32(j))) + } + } + } + } + return buildings +} + +func drawHUD() { + city.healthBar.Draw() + rl.DrawText( + fmt.Sprintf("buildings destroyed: %d", 1024-len(city.Buildings())), MARGIN, MARGIN, 20, rl.White, + ) + rl.DrawText(fmt.Sprintf("frame time: %f", rl.GetFrameTime()), MARGIN, MARGIN+MARGIN, 20, rl.White) +} + +func spawnEnemies() []DrawableLivingThing { + var ret []DrawableLivingThing + for range 20 { + e := NewShootingEnemy(rl.NewVector3(rand.Float32()*400-200, 2, rand.Float32()*400-200), 6) + ret = append(ret, e) + } + return ret +} + func main() { - fmt.Println("hello world") rl.SetConfigFlags(rl.FlagMsaa4xHint) rl.InitWindow(WIDTH, HEIGHT, "raylib [core] example - 3d camera first person") - rl.SetTargetFPS(60) + rl.SetTargetFPS(120) rl.HideCursor() rl.DisableCursor() rl.SetMousePosition(WIDTH/2, HEIGHT/2) @@ -459,56 +993,21 @@ func main() { buildings := newBuildings() healthBar := newHealthBar(buildings) - city = &scene{ - buildings: buildings, - healthBar: healthBar, - character: character, + city = &scene{healthBar: healthBar, character: character} + + // Linguagem burra + var b []DrawableLivingThing = make([]DrawableLivingThing, len(buildings)) + for i, bu := range buildings { + b[i] = bu } - // direction := rl.Vector3Subtract(character.camera.Target, character.camera.Position) - // aim := direction + city.AppendThing(spawnEnemies()...) + city.AppendThing(b...) + city.AppendThing(character) for !rl.WindowShouldClose() { - - rl.UpdateCamera(&character.camera, rl.CameraFirstPerson) - rl.BeginDrawing() - rl.ClearBackground(rl.LightGray) - - rl.BeginMode3D(character.camera) - { - character.update() - - draw_plane() - - update_projectiles() - - draw_buildings() - - update_emitters() - - // direction = rl.Vector3Subtract( - // character.camera.Target, character.camera.Position) - // aim = rl.Vector3Lerp(direction, aim, 0.95) - // rl.DrawSphere(rl.Vector3Add(character.camera.Position, aim), 0.1, rl.Red) - } - rl.EndMode3D() - - rl.DrawLineV( - rl.NewVector2(WIDTH/2-5, HEIGHT/2-5), - rl.NewVector2(WIDTH/2+5, HEIGHT/2+5), - rl.Red, - ) - rl.DrawLineV( - rl.NewVector2(WIDTH/2-5, HEIGHT/2+5), - rl.NewVector2(WIDTH/2+5, HEIGHT/2-5), - rl.Red, - ) - - rl.DrawFPS(0, HEIGHT-30) - - healthBar.update() - - rl.EndDrawing() + city.Update() + city.Draw() } rl.CloseWindow()