1014 lines
21 KiB
Go
1014 lines
21 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"math/rand"
|
|
|
|
rl "github.com/gen2brain/raylib-go/raylib"
|
|
)
|
|
|
|
const (
|
|
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
|
|
)
|
|
|
|
var gravity = rl.NewVector3(0, -2, 0)
|
|
|
|
// var globalParticles []Particle
|
|
|
|
type object struct {
|
|
pos rl.Vector3
|
|
}
|
|
|
|
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 {
|
|
object
|
|
size rl.Vector3
|
|
color rl.Color
|
|
life int
|
|
boundingBox rl.BoundingBox
|
|
animate animationFunc
|
|
}
|
|
|
|
type healthBar struct {
|
|
life int
|
|
damageTaken float32
|
|
lastUpdate float64
|
|
pos Somewhere
|
|
}
|
|
|
|
type character struct {
|
|
camera rl.Camera
|
|
damage int
|
|
life int
|
|
}
|
|
|
|
type scene struct {
|
|
things []DrawableLivingThing
|
|
// buildings []Building
|
|
healthBar *healthBar
|
|
// projectiles []*projectile
|
|
character Player
|
|
particles []Particle
|
|
// enemies []Enemy
|
|
// emitters []Emitter
|
|
}
|
|
|
|
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 explosionParticle struct {
|
|
simpleParticle
|
|
trailTimer *timer
|
|
}
|
|
|
|
type smokeTrailParticle struct {
|
|
simpleParticle
|
|
}
|
|
|
|
var city *scene
|
|
|
|
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 (e *shootingEnemy) RayCollision(ray rl.Ray) rl.RayCollision {
|
|
return rl.GetRayCollisionSphere(ray, e.pos, 1)
|
|
}
|
|
|
|
func (e *shootingEnemy) SphereCollision(centerSphere rl.Vector3, radiusSphere float32) bool {
|
|
return rl.CheckCollisionSpheres(e.pos, 1, centerSphere, radiusSphere)
|
|
|
|
}
|
|
|
|
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 (c *character) CauseDamage(damage int) {
|
|
c.life -= damage
|
|
}
|
|
|
|
func (c *character) IsAlive() bool {
|
|
return c.life <= 0
|
|
}
|
|
|
|
func (c *character) Pos() rl.Vector3 {
|
|
return c.camera.Position
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
func NewProjectile(pos, direction rl.Vector3) *projectile {
|
|
return &projectile{kinecticObject: kinecticObject{object: object{pos: pos}, speed: direction}}
|
|
}
|
|
|
|
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 {
|
|
return &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,
|
|
},
|
|
damage: 1,
|
|
}
|
|
}
|
|
|
|
func randomShake(amount float32) float32 {
|
|
return rand.Float32()*amount - amount/2
|
|
}
|
|
|
|
func (hb *healthBar) Draw() {
|
|
const BARWIDTH = (WIDTH - MARGIN*2)
|
|
|
|
currentLife := 0
|
|
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
|
|
|
|
size := rl.NewVector2(float32(barWidth), 10)
|
|
|
|
hb.damageTaken += float32(hb.life - currentLife)
|
|
|
|
damageTakenBar := size
|
|
damageTakenBar.X = BARWIDTH *
|
|
(float32(currentLife) + hb.damageTaken) / float32(maxLife)
|
|
|
|
shake := rl.NewVector2(
|
|
randomShake(0.4)*hb.damageTaken,
|
|
randomShake(0.4)*hb.damageTaken,
|
|
)
|
|
position = rl.Vector2Add(position, shake)
|
|
|
|
rl.DrawRectangleV(position, damageTakenBar, rl.Orange)
|
|
rl.DrawRectangleV(position, size, rl.Red)
|
|
|
|
hb.lastUpdate = rl.GetTime()
|
|
hb.life = currentLife
|
|
hb.damageTaken *= 0.95
|
|
}
|
|
|
|
func newHealthBar(buildings []Building) *healthBar {
|
|
return &healthBar{life: buildingsHealth * len(buildings)}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func (b *building) IsAlive() bool {
|
|
return b.life > 0 || b.animate != nil
|
|
}
|
|
|
|
func newBuilding(x, z float32) *building {
|
|
color := rl.NewColor(
|
|
uint8(rl.GetRandomValue(0, 180)),
|
|
uint8(rl.GetRandomValue(0, 180)),
|
|
uint8(rl.GetRandomValue(0, 180)),
|
|
255,
|
|
)
|
|
pos := rl.NewVector3(x*buildingSpacing, 0, z*buildingSpacing)
|
|
size := rl.NewVector3(
|
|
buildingSize,
|
|
float32(rl.GetRandomValue(1, maxBuildingHeight)),
|
|
buildingSize,
|
|
)
|
|
min := rl.NewVector3(
|
|
pos.X-buildingSize/2,
|
|
0,
|
|
pos.Z-buildingSize/2,
|
|
)
|
|
max := rl.NewVector3(
|
|
pos.X+buildingSize/2,
|
|
size.Y/2,
|
|
pos.Z+buildingSize/2,
|
|
)
|
|
return &building{
|
|
color: color,
|
|
life: buildingsHealth,
|
|
object: object{pos: pos},
|
|
size: size,
|
|
boundingBox: rl.NewBoundingBox(min, max),
|
|
}
|
|
}
|
|
|
|
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,
|
|
invertedColor,
|
|
)
|
|
}
|
|
|
|
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 {
|
|
duration := newTimer(0.7)
|
|
t := newTimer(0.08)
|
|
return func() animationOver {
|
|
building := *b
|
|
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)
|
|
rl.DrawCubeV(
|
|
rl.Vector3Add(building.pos, shake),
|
|
building.size,
|
|
building.color,
|
|
)
|
|
return animationOver(duration.ttr())
|
|
}
|
|
}
|
|
|
|
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) laserBeam() {
|
|
if !rl.IsMouseButtonDown(rl.MouseLeftButton) {
|
|
return
|
|
}
|
|
ray := rl.Ray{
|
|
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.IsAlive() {
|
|
continue
|
|
}
|
|
playerDistance := rl.Vector3Distance(c.camera.Position, building.Pos())
|
|
if playerDistance < float32(closestDistance) {
|
|
collision := building.RayCollision(ray)
|
|
if collision.Hit {
|
|
closestDistance = float64(playerDistance)
|
|
closest = building
|
|
}
|
|
}
|
|
}
|
|
if closest != nil {
|
|
closest.CauseDamage(c.damage)
|
|
}
|
|
}
|
|
|
|
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() {
|
|
rl.SetConfigFlags(rl.FlagMsaa4xHint)
|
|
rl.InitWindow(WIDTH, HEIGHT, "raylib [core] example - 3d camera first person")
|
|
rl.SetTargetFPS(120)
|
|
rl.HideCursor()
|
|
rl.DisableCursor()
|
|
rl.SetMousePosition(WIDTH/2, HEIGHT/2)
|
|
|
|
character := newCharacter()
|
|
buildings := newBuildings()
|
|
healthBar := newHealthBar(buildings)
|
|
|
|
city = &scene{healthBar: healthBar, character: character}
|
|
|
|
// Linguagem burra
|
|
var b []DrawableLivingThing = make([]DrawableLivingThing, len(buildings))
|
|
for i, bu := range buildings {
|
|
b[i] = bu
|
|
}
|
|
|
|
city.AppendThing(spawnEnemies()...)
|
|
city.AppendThing(b...)
|
|
city.AppendThing(character)
|
|
|
|
for !rl.WindowShouldClose() {
|
|
city.Update()
|
|
city.Draw()
|
|
}
|
|
|
|
rl.CloseWindow()
|
|
}
|