mirror of
https://github.com/stevenhowes/GoRetro.git
synced 2026-05-26 15:53:31 +01:00
Move all engine stuff to GoRetro. Still many interactions between game and engine code that there should not be.
This commit is contained in:
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
+95
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
module github.com/stevenhowes/GoRetro
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
|
||||||
|
require github.com/veandco/go-sdl2 v0.4.10
|
||||||
@@ -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=
|
||||||
+17
@@ -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
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user