From 4d1ab58ca40edad43c1293dcc783c5c1970ea883 Mon Sep 17 00:00:00 2001 From: stevenhowes <38082088+stevenhowes@users.noreply.github.com> Date: Mon, 3 Jan 2022 22:13:34 +0000 Subject: [PATCH] Move all engine stuff to GoRetro. Still many interactions between game and engine code that there should not be. --- component_animator.go | 147 ++++++++++++++++++++++++++ component_bounder_screen.go | 39 +++++++ component_bounder_screen_resetting.go | 46 ++++++++ component_damage_giver.go | 58 ++++++++++ component_damage_receiver.go | 69 ++++++++++++ component_mover_keyboard.go | 90 ++++++++++++++++ component_mover_linear.go | 40 +++++++ component_mover_rotator.go | 33 ++++++ component_shooter_interval.go | 64 +++++++++++ component_shooter_keyboard.go | 64 +++++++++++ component_sprite_renderer.go | 57 ++++++++++ element.go | 95 +++++++++++++++++ go.mod | 5 + go.sum | 2 + goretro.go | 17 +++ util_maths.go | 12 +++ utils_collisions.go | 65 ++++++++++++ utils_texture.go | 53 ++++++++++ utils_vector.go | 20 ++++ 19 files changed, 976 insertions(+) create mode 100644 component_animator.go create mode 100644 component_bounder_screen.go create mode 100644 component_bounder_screen_resetting.go create mode 100644 component_damage_giver.go create mode 100644 component_damage_receiver.go create mode 100644 component_mover_keyboard.go create mode 100644 component_mover_linear.go create mode 100644 component_mover_rotator.go create mode 100644 component_shooter_interval.go create mode 100644 component_shooter_keyboard.go create mode 100644 component_sprite_renderer.go create mode 100644 element.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 goretro.go create mode 100644 util_maths.go create mode 100644 utils_collisions.go create mode 100644 utils_texture.go create mode 100644 utils_vector.go diff --git a/component_animator.go b/component_animator.go new file mode 100644 index 0000000..e39df27 --- /dev/null +++ b/component_animator.go @@ -0,0 +1,147 @@ +package GoRetro + +/* + * -------------------- + * Animator + * -------------------- + * Handles sprites and their associated animation sequences + */ + +import ( + "fmt" + "io/ioutil" + "path" + "time" + + "github.com/veandco/go-sdl2/sdl" +) + +type animator struct { + container *Element + sequences map[string]*Sequence // All of the available sequences + current string // The current sequence + lastFrameChange time.Time // When we last ticked between frames + finished bool // We're on the last frame +} + +func NewAnimator( + container *Element, + sequences map[string]*Sequence, + defaultSequence string) *animator { + var an animator + + an.container = container + an.sequences = sequences + an.current = defaultSequence + an.lastFrameChange = time.Now() + + return &an +} + +func (an *animator) onUpdate() error { + sequence := an.sequences[an.current] + + // Calculate time per frame + frameInterval := float64(time.Second) / sequence.sampleRate + + // If we've exceeded that time since the last frame, bump it along one + if time.Since(an.lastFrameChange) >= time.Duration(frameInterval) { + an.finished = sequence.nextFrame() + an.lastFrameChange = time.Now() + } + + return nil +} + +func (an *animator) onDraw() error { + tex := an.sequences[an.current].texture() + + return drawTexture( + tex, + an.container.Position, + an.container.Rotation, + an.container.Renderer) +} + +func (an *animator) onCollision(other *Element) error { + return nil +} + +func (an *animator) setSequence(name string) bool { + _, ok := an.sequences[name] + if ok { + // If we *are* changing sequence, change the name and reset the frame + if an.current != name { + // Reset the old sequence to frame 0 + sequence := an.sequences[an.current] + sequence.resetFrame() + + // Use the new sequence + an.current = name + an.lastFrameChange = time.Now() + } + } + return ok +} + +type Sequence struct { + textures []*sdl.Texture // The frames + frame int // Current frame + sampleRate float64 // Frames per second + loop bool // Does this sequence play continuously? +} + +func NewSequence( + filepath string, // Path to the folder for this sequence + sampleRate float64, + loop bool, + renderer *sdl.Renderer) (*Sequence, error) { + + var seq Sequence + + // Get a list of frames + files, err := ioutil.ReadDir(filepath) + if err != nil { + return nil, fmt.Errorf("reading directory %v: %v", filepath, err) + } + + for _, file := range files { + filename := path.Join(filepath, file.Name()) + + // Load this frame and turn it into a texture + tex, err := loadTextureFromBMP(filename, renderer) + if err != nil { + return nil, fmt.Errorf("loading sequence frame: %v", err) + } + seq.textures = append(seq.textures, tex) + } + + seq.sampleRate = sampleRate + seq.loop = loop + + return &seq, nil +} + +func (seq *Sequence) texture() *sdl.Texture { + return seq.textures[seq.frame] +} + +func (seq *Sequence) resetFrame() { + seq.frame = 0 +} + +func (seq *Sequence) nextFrame() bool { + // If we're at the end + if seq.frame == len(seq.textures)-1 { + if seq.loop { + // Reset for a looping sequence + seq.resetFrame() + } else { + return true + } + } else { + seq.frame++ + } + + return false +} diff --git a/component_bounder_screen.go b/component_bounder_screen.go new file mode 100644 index 0000000..859b2b8 --- /dev/null +++ b/component_bounder_screen.go @@ -0,0 +1,39 @@ +package GoRetro + +/* + * -------------------- + * BounderScreen + * -------------------- + * A bounder which sets active = false on anything which + * has left the screen + */ + +type bounderScreen struct { + container *Element +} + +func NewBounderScreen(container *Element) *bounderScreen { + return &bounderScreen{container: container} +} + +func (bounder *bounderScreen) onDraw() error { + return nil +} + +func (bounder *bounderScreen) onUpdate() error { + b := bounder.container + + // If the position is outside the screen bounds then set it as inactive + // and mark for deletion + if b.Position.X > ScreenWidth || b.Position.X < 0 || + b.Position.Y > ScreenHeight || b.Position.Y < 0 { + b.Active = false + b.Delete = true + } + + return nil +} + +func (bounder *bounderScreen) onCollision(other *Element) error { + return nil +} diff --git a/component_bounder_screen_resetting.go b/component_bounder_screen_resetting.go new file mode 100644 index 0000000..621ebe1 --- /dev/null +++ b/component_bounder_screen_resetting.go @@ -0,0 +1,46 @@ +package GoRetro + +/* + * -------------------- + * BounderScreen + * -------------------- + * A bounder which resets the position to the opposite of + * whichever bound was hit + */ + +type bounderScreenResetting struct { + container *Element +} + +func NewBounderScreenResetting(container *Element) *bounderScreenResetting { + return &bounderScreenResetting{container: container} +} + +func (bounder *bounderScreenResetting) onDraw() error { + return nil +} + +func (bounder *bounderScreenResetting) onUpdate() error { + b := bounder.container + + // If any position exceeds the screen dimensions, wrap it to the + // opposite side + if b.Position.X > ScreenWidth { + b.Position.X = 0 + } + if b.Position.X < 0 { + b.Position.X = ScreenWidth + } + if b.Position.Y > ScreenHeight { + b.Position.Y = 0 + } + if b.Position.Y < 0 { + b.Position.Y = ScreenWidth + } + + return nil +} + +func (bounder *bounderScreenResetting) onCollision(other *Element) error { + return nil +} diff --git a/component_damage_giver.go b/component_damage_giver.go new file mode 100644 index 0000000..8b2a197 --- /dev/null +++ b/component_damage_giver.go @@ -0,0 +1,58 @@ +package GoRetro + +/* + * -------------------- + * damageGiver + * -------------------- + * During a collision a damageReciever handles damage from + * a damageGiver + */ + +type damageGiver struct { + container *Element + damage float64 // Damage per incident (or tick if damagePerists) + damageActive bool // Is currently able to issue damage + damagePersists bool // Can this continue to damage once it's been hit +} + +func NewDamageGiver(container *Element, damage float64, damagePersists bool) *damageGiver { + return &damageGiver{ + container: container, + damage: damage, + damageActive: true, + damagePersists: damagePersists, + } +} + +func (dg *damageGiver) onDraw() error { + return nil +} + +func (dg *damageGiver) onUpdate() error { + if dg.container.checkComponentIsPresent(&damageReceiver{}) { + dr := dg.container.getComponent(&damageReceiver{}).(*damageReceiver) + if dr.health <= 0 { + // We can't give damage any more if our reciever is at 0 (i.e. container is dead) + dg.damageActive = false + } + } + return nil +} + +func (dg *damageGiver) onCollision(other *Element) error { + if !dg.damageActive { + return nil + } + + if other.checkComponentIsPresent(&damageReceiver{}) { + // Find our victim and subtract damage + dr := other.getComponent(&damageReceiver{}).(*damageReceiver) + dr.health -= dg.damage + + // If we don't continue to hand out damage then disable ourselves + if !dg.damagePersists { + dg.damageActive = false + } + } + return nil +} diff --git a/component_damage_receiver.go b/component_damage_receiver.go new file mode 100644 index 0000000..c86afce --- /dev/null +++ b/component_damage_receiver.go @@ -0,0 +1,69 @@ +package GoRetro + +/* + * -------------------- + * damageReceiver + * -------------------- + * During a collision a damageReciever handles damage from + * a damageGiver + */ + +type damageReceiver struct { + container *Element + health float64 +} + +func NewDamageReceiver(container *Element, health float64) *damageReceiver { + return &damageReceiver{ + container: container, + health: health, + } +} + +func (dr *damageReceiver) onDraw() error { + return nil +} + +func (dr *damageReceiver) onUpdate() error { + // If we're out of health + if dr.health <= 0 { + // If we have an animator, run the destroy sequence + if dr.container.checkComponentIsPresent(&animator{}) { + ani := dr.container.getComponent(&animator{}).(*animator) + + // If the sequence can't get set (doesn't exist) just remove us + if !ani.setSequence("destroy") { + dr.container.Active = false + dr.container.Delete = true + } + } + + // Stop any movers we have + if dr.container.checkComponentIsPresent(&moverLinear{}) { + lm := dr.container.getComponent(&moverLinear{}).(*moverLinear) + lm.speed = 0 + } + if dr.container.checkComponentIsPresent(&moverKeyboard{}) { + km := dr.container.getComponent(&moverKeyboard{}).(*moverKeyboard) + km.speed = 0 + } + } + + // If we've finished our destroy animation then we're not active and can be removed + if dr.container.checkComponentIsPresent(&animator{}) { + ani := dr.container.getComponent(&animator{}).(*animator) + if ani.finished && ani.current == "destroy" { + dr.container.Active = false + dr.container.Delete = true + } + } + /*if debugTick { + fmt.Printf("health is %f\n", dr.health) + }*/ + + return nil +} + +func (dr *damageReceiver) onCollision(other *Element) error { + return nil +} diff --git a/component_mover_keyboard.go b/component_mover_keyboard.go new file mode 100644 index 0000000..c247bf6 --- /dev/null +++ b/component_mover_keyboard.go @@ -0,0 +1,90 @@ +package GoRetro + +/* + * -------------------- + * moverKeyboard + * -------------------- + * A simple mover that moves the container a fixed + * distance each tick when arrow keys are used + * + * NOTE: Container must have a spriteRenderer to + * read dimesions from! + */ + +import ( + "github.com/veandco/go-sdl2/sdl" +) + +type moverKeyboard struct { + container *Element + speed float64 +} + +func NewMoverKeyboard(container *Element, speed float64) *moverKeyboard { + return &moverKeyboard{ + container: container, + speed: speed, + } +} + +func (mover *moverKeyboard) onDraw() error { + return nil +} + +func (mover *moverKeyboard) onUpdate() error { + keys := sdl.GetKeyboardState() + + // For now, spoof a 1 radius circle above, below, left and right of the player + // to keep them within the screen. bounder_screen would make the player cease + // to exist if we did that + cLeft := Circle{ + Radius: 1, + Center: Vector{X: 0, Y: mover.container.Position.Y}, + } + cRight := Circle{ + Radius: 1, + Center: Vector{X: ScreenWidth, Y: mover.container.Position.Y}, + } + cTop := Circle{ + Radius: 1, + Center: Vector{X: mover.container.Position.X, Y: 0}, + } + cBottom := Circle{ + Radius: 1, + Center: Vector{X: mover.container.Position.X, Y: ScreenHeight}, + } + + // Handle direction keys and check of we collide. + if keys[sdl.SCANCODE_LEFT] == 1 { + for _, c2 := range mover.container.Collisions { + if !collides(cLeft, circleOffset(c2, mover.container.Position)) { + mover.container.Position.X -= mover.speed * Delta + } + } + } else if keys[sdl.SCANCODE_RIGHT] == 1 { + for _, c2 := range mover.container.Collisions { + if !collides(cRight, circleOffset(c2, mover.container.Position)) { + mover.container.Position.X += mover.speed * Delta + } + } + } + if keys[sdl.SCANCODE_UP] == 1 { + for _, c2 := range mover.container.Collisions { + if !collides(cTop, circleOffset(c2, mover.container.Position)) { + mover.container.Position.Y -= mover.speed * Delta + } + } + } else if keys[sdl.SCANCODE_DOWN] == 1 { + for _, c2 := range mover.container.Collisions { + if !collides(cBottom, circleOffset(c2, mover.container.Position)) { + mover.container.Position.Y += mover.speed * Delta + } + } + } + + return nil +} + +func (mover *moverKeyboard) onCollision(other *Element) error { + return nil +} diff --git a/component_mover_linear.go b/component_mover_linear.go new file mode 100644 index 0000000..466a941 --- /dev/null +++ b/component_mover_linear.go @@ -0,0 +1,40 @@ +package GoRetro + +/* + * -------------------- + * moverLinear + * -------------------- + * A simple mover that moves the container a fixed + * distance each tick in the direction of its rotation + */ + +import ( + "math" +) + +type moverLinear struct { + container *Element + speed float64 +} + +func NewMoverLinear(container *Element, speed float64) *moverLinear { + return &moverLinear{container: container, speed: speed} +} + +func (mover *moverLinear) onDraw() error { + return nil +} + +func (mover *moverLinear) onUpdate() error { + c := mover.container + + // Move, taking into account rotation in degrees + c.Position.X += mover.speed * math.Sin(c.Rotation*(math.Pi/180)) * Delta + c.Position.Y += mover.speed * math.Cos(c.Rotation*(math.Pi/180)) * Delta + + return nil +} + +func (mover *moverLinear) onCollision(other *Element) error { + return nil +} diff --git a/component_mover_rotator.go b/component_mover_rotator.go new file mode 100644 index 0000000..2262eed --- /dev/null +++ b/component_mover_rotator.go @@ -0,0 +1,33 @@ +package GoRetro + +/* + * -------------------- + * moverRotator + * -------------------- + * A simple mover that rotates the container a fixed + * amount each tick + */ + +type moverRotator struct { + container *Element + speed float64 +} + +func newMoverRotator(container *Element, speed float64) *moverRotator { + return &moverRotator{container: container, speed: speed} +} + +func (mover *moverRotator) onDraw() error { + return nil +} + +func (mover *moverRotator) onUpdate() error { + c := mover.container + c.Rotation += mover.speed * Delta + + return nil +} + +func (mover *moverRotator) onCollision(other *Element) error { + return nil +} diff --git a/component_shooter_interval.go b/component_shooter_interval.go new file mode 100644 index 0000000..9c83101 --- /dev/null +++ b/component_shooter_interval.go @@ -0,0 +1,64 @@ +package GoRetro + +/* + * -------------------- + * intervalShooter + * -------------------- + * Fire projectiles at fixed intervals + */ + +import ( + "time" + + "github.com/veandco/go-sdl2/sdl" +) + +type intervalShooter struct { + container *Element + cooldown time.Duration // Time between shots + lastShot time.Time // Last shot + shootFunc func(renderer *sdl.Renderer, collisionLayer int) *Element +} + +func NewIntervalShooter(container *Element, cooldown time.Duration, lastShot time.Time, NewBullet func(renderer *sdl.Renderer, collisionLayer int) *Element) *intervalShooter { + return &intervalShooter{ + container: container, + cooldown: cooldown, + lastShot: lastShot, + shootFunc: NewBullet} + +} + +func (shooter *intervalShooter) onDraw() error { + return nil +} + +func (shooter *intervalShooter) onUpdate() error { + + //pos := shooter.container.Position + + if time.Since(shooter.lastShot) >= shooter.cooldown { + // TODO: These positions should not be hard coded. Store as offset from + // container (i.e. gun positions) + shooter.shoot(shooter.container.Position.X+15, shooter.container.Position.Y-10, shooter.container.Rotation, shooter.container) + shooter.shoot(shooter.container.Position.X-15, shooter.container.Position.Y-10, shooter.container.Rotation, shooter.container) + + shooter.lastShot = time.Now() + } + + return nil +} + +func (shooter *intervalShooter) shoot(x, y, rotation float64, parent *Element) { + bul := shooter.shootFunc(parent.Renderer, parent.CollisionLayer+1) + bul.Active = true + bul.Position.X = x + bul.Position.Y = y + bul.Rotation = rotation + bul.parentElement = parent + +} + +func (shooter *intervalShooter) onCollision(other *Element) error { + return nil +} diff --git a/component_shooter_keyboard.go b/component_shooter_keyboard.go new file mode 100644 index 0000000..936c8c5 --- /dev/null +++ b/component_shooter_keyboard.go @@ -0,0 +1,64 @@ +package GoRetro + +/* + * -------------------- + * keyboardShooter + * -------------------- + * Fire projectiles on keypress, rate limited + */ + +import ( + "time" + + "github.com/veandco/go-sdl2/sdl" +) + +type keyboardShooter struct { + container *Element + cooldown time.Duration // Time between shots + lastShot time.Time // Last shot + shootFunc func(renderer *sdl.Renderer, collisionLayer int) *Element +} + +func NewKeyboardShooter(container *Element, cooldown time.Duration, NewBullet func(renderer *sdl.Renderer, collisionLayer int) *Element) *keyboardShooter { + return &keyboardShooter{ + container: container, + cooldown: cooldown, + shootFunc: NewBullet} +} + +func (shooter *keyboardShooter) onDraw() error { + return nil +} + +func (shooter *keyboardShooter) onUpdate() error { + keys := sdl.GetKeyboardState() + + //pos := shooter.container.Position + + if keys[sdl.SCANCODE_SPACE] == 1 { + if time.Since(shooter.lastShot) >= shooter.cooldown { + // TODO: These positions should not be hard coded. Store as offset from + // container (i.e. gun positions) + shooter.shoot(shooter.container.Position.X+15, shooter.container.Position.Y-10, shooter.container.Rotation, shooter.container) + shooter.shoot(shooter.container.Position.X-15, shooter.container.Position.Y-10, shooter.container.Rotation, shooter.container) + + shooter.lastShot = time.Now() + } + } + + return nil +} + +func (shooter *keyboardShooter) shoot(x, y, rotation float64, parent *Element) { + bul := shooter.shootFunc(parent.Renderer, parent.CollisionLayer+1) + bul.Active = true + bul.Position.X = x + bul.Position.Y = y + bul.Rotation = rotation + bul.parentElement = parent +} + +func (shooter *keyboardShooter) onCollision(other *Element) error { + return nil +} diff --git a/component_sprite_renderer.go b/component_sprite_renderer.go new file mode 100644 index 0000000..a3f96eb --- /dev/null +++ b/component_sprite_renderer.go @@ -0,0 +1,57 @@ +package GoRetro + +/* + * -------------------- + * spriteRenderer + * -------------------- + * Loads a BMP into a texture and stores dimensions + */ + +import ( + "fmt" + + "github.com/veandco/go-sdl2/sdl" +) + +type spriteRenderer struct { + container *Element + tex *sdl.Texture + width, height int +} + +func NewSpriteRenderer(container *Element, renderer *sdl.Renderer, filename string) *spriteRenderer { + sr := &spriteRenderer{} + var err error + + sr.tex, err = loadTextureFromBMP(filename, renderer) + if err != nil { + panic(err) + } + + _, _, width, height, err := sr.tex.Query() + if err != nil { + panic(fmt.Errorf("querying texture: %v", err)) + } + sr.width = int(width) + sr.height = int(height) + + sr.container = container + + return sr +} + +func (sr *spriteRenderer) onUpdate() error { + return nil +} + +func (sr *spriteRenderer) onDraw() error { + return drawTexture( + sr.tex, + sr.container.Position, + sr.container.Rotation, + sr.container.Renderer) +} + +func (sr *spriteRenderer) onCollision(other *Element) error { + return nil +} diff --git a/element.go b/element.go new file mode 100644 index 0000000..37fed33 --- /dev/null +++ b/element.go @@ -0,0 +1,95 @@ +package GoRetro + +import ( + "fmt" + "reflect" + + "github.com/veandco/go-sdl2/sdl" +) + +type component interface { + onUpdate() error + onDraw() error + onCollision(other *Element) error +} + +type Element struct { + Renderer *sdl.Renderer + Position Vector + Rotation float64 + Active bool + Delete bool + Collisions []Circle + components []component + parentElement *Element + ZIndex int + CollisionLayer int +} + +var Elements []*Element + +func (elem *Element) Draw() error { + for _, comp := range elem.components { + err := comp.onDraw() + if err != nil { + return err + } + } + + return nil +} + +func (elem *Element) Update() error { + for _, comp := range elem.components { + err := comp.onUpdate() + if err != nil { + return err + } + } + + return nil +} + +func (elem *Element) collision(other *Element) error { + for _, comp := range elem.components { + err := comp.onCollision(other) + if err != nil { + return err + } + } + + return nil +} + +func (elem *Element) AddComponent(new component) { + for _, existing := range elem.components { + if reflect.TypeOf(new) == reflect.TypeOf(existing) { + panic(fmt.Sprintf( + "attempt to add new component with existing type %v", + reflect.TypeOf(new))) + } + } + elem.components = append(elem.components, new) +} + +func (elem *Element) getComponent(withType component) component { + typ := reflect.TypeOf(withType) + for _, comp := range elem.components { + if reflect.TypeOf(comp) == typ { + return comp + } + } + + panic(fmt.Sprintf("no component with type %v", reflect.TypeOf(withType))) +} + +func (elem *Element) checkComponentIsPresent(withType component) bool { + typ := reflect.TypeOf(withType) + for _, comp := range elem.components { + if reflect.TypeOf(comp) == typ { + return true + } + } + + return false +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a9d8db8 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/stevenhowes/GoRetro + +go 1.17 + +require github.com/veandco/go-sdl2 v0.4.10 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e190134 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/veandco/go-sdl2 v0.4.10 h1:8QoD2bhWl7SbQDflIAUYWfl9Vq+mT8/boJFAUzAScgY= +github.com/veandco/go-sdl2 v0.4.10/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY= diff --git a/goretro.go b/goretro.go new file mode 100644 index 0000000..d4dcade --- /dev/null +++ b/goretro.go @@ -0,0 +1,17 @@ +package GoRetro + +import "time" + +const ( + ScreenWidth = 1024 + ScreenHeight = 768 + + TargetTicksPerSecond = 60 + DebugStatePrintSeconds = 1 + + dataDir = "data/" +) + +var Delta float64 +var LastDebugStatePrint time.Time +var DebugTick bool diff --git a/util_maths.go b/util_maths.go new file mode 100644 index 0000000..6b0cc04 --- /dev/null +++ b/util_maths.go @@ -0,0 +1,12 @@ +package GoRetro + +func absInt(x int) int { + return absDiffInt(x, 0) +} + +func absDiffInt(x, y int) int { + if x < y { + return y - x + } + return x - y +} diff --git a/utils_collisions.go b/utils_collisions.go new file mode 100644 index 0000000..4cf7d79 --- /dev/null +++ b/utils_collisions.go @@ -0,0 +1,65 @@ +package GoRetro + +// Anything > 1 apart will collide (i.e. player wont collide with own projectile) +const ( + _ = 0 + LayerPlayer = 1 + LayerPlayerProjectile = 2 + _ = 3 + LayerEnemy = 5 + LayerEnemyProjectile = 6 + _ = 7 + LayerScreenBounds = 9 +) + +type Circle struct { + Center Vector + Radius float64 + Layer int +} + +func circleOffset(c1 Circle, offset Vector) Circle { + return Circle{ + Radius: c1.Radius, + Center: vectorAdd(c1.Center, offset), + Layer: c1.Layer, + } +} + +func collides(c1, c2 Circle) bool { + dist := vectorDistance(c1.Center, c2.Center) + collides := dist <= c1.Radius+c2.Radius + seperation := absInt(c1.Layer - c2.Layer) + + // Don't collide elements that are on the same layer, or an adjacent one + if collides && (seperation > 1) { + return collides + } + + return false +} + +func CheckCollisions() error { + for i := 0; i < len(Elements)-1; i++ { + for j := i + 1; j < len(Elements); j++ { + for _, c1 := range Elements[i].Collisions { + for _, c2 := range Elements[j].Collisions { + if collides(circleOffset(c1, Elements[i].Position), circleOffset(c2, Elements[j].Position)) && Elements[i].Active && Elements[j].Active { + if (Elements[i].parentElement != Elements[j]) && (Elements[j].parentElement != Elements[i]) { + err := Elements[i].collision(Elements[j]) + if err != nil { + return err + } + err = Elements[j].collision(Elements[i]) + if err != nil { + return err + } + } + } + } + } + } + } + + return nil +} diff --git a/utils_texture.go b/utils_texture.go new file mode 100644 index 0000000..3026404 --- /dev/null +++ b/utils_texture.go @@ -0,0 +1,53 @@ +package GoRetro + +import ( + "fmt" + + "github.com/veandco/go-sdl2/sdl" +) + +var TexList map[string]*sdl.Texture + +func drawTexture( + tex *sdl.Texture, + position Vector, + rotation float64, + renderer *sdl.Renderer) error { + + _, _, width, height, err := tex.Query() + if err != nil { + return fmt.Errorf("querying texture: %v", err) + } + + // Convert coordinates to the top left of the sprite + position.X -= float64(width) / 2.0 + position.Y -= float64(height) / 2.0 + + return renderer.CopyEx( + tex, + &sdl.Rect{X: 0, Y: 0, W: width, H: height}, + &sdl.Rect{X: int32(position.X), Y: int32(position.Y), W: width, H: height}, + rotation, + &sdl.Point{X: width / 2, Y: height / 2}, + sdl.FLIP_NONE) +} + +func loadTextureFromBMP(filename string, renderer *sdl.Renderer) (*sdl.Texture, error) { + if val, ok := TexList[filename]; ok { + return val, nil + } + + img, err := sdl.LoadBMP(filename) + if err != nil { + return nil, fmt.Errorf("loading %v: %v", filename, err) + } + defer img.Free() + tex, err := renderer.CreateTextureFromSurface(img) + if err != nil { + return nil, fmt.Errorf("creating texture from %v: %v", filename, err) + } + + TexList[filename] = tex + fmt.Printf("Caching %s\n", filename) + return tex, nil +} diff --git a/utils_vector.go b/utils_vector.go new file mode 100644 index 0000000..0dc4acf --- /dev/null +++ b/utils_vector.go @@ -0,0 +1,20 @@ +package GoRetro + +import "math" + +type Vector struct { + X float64 + Y float64 +} + +func vectorAdd(v1, v2 Vector) Vector { + return Vector{ + X: v1.X + v2.X, + Y: v1.Y + v2.Y, + } +} + +func vectorDistance(v1, v2 Vector) float64 { + return math.Sqrt(math.Pow(v2.X-v1.X, 2) + + math.Pow(v2.Y-v1.Y, 2)) +}