Split off each entity as a separate go routine
parent
a3dc1b7b5c
commit
b148699a58
|
@ -1,4 +1,5 @@
|
|||
game.title = "Project Ely"
|
||||
game.framerate = 60.0
|
||||
|
||||
log.file = "output.log"
|
||||
log.writeToFile = false
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package channels
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
func RunWithTimeout(timeout time.Duration, function func() error) error {
|
||||
var err error
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
// Launch a goroutine with a channel
|
||||
finished := make(chan bool)
|
||||
go func() {
|
||||
err = function()
|
||||
finished <- true
|
||||
}()
|
||||
|
||||
// Then wait for completion or timeout
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Timed out
|
||||
return ctx.Err()
|
||||
case <-finished:
|
||||
// Completed
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
var defaultConfig gosimpleconf.ConfigMap = gosimpleconf.ConfigMap{
|
||||
"game.title": "Project Ely",
|
||||
"game.framerate": "60.0",
|
||||
"log.writeToFile": "false",
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package entity
|
||||
|
||||
// List of keys supported by all entities
|
||||
const (
|
||||
COMMAND_DRAW int = iota
|
||||
COMMAND_UPDATE
|
||||
COMMAND_MOVE_X
|
||||
COMMAND_MOVE_Y
|
||||
COMMAND_SET_SPEED
|
||||
)
|
||||
|
||||
type EntityCommand struct {
|
||||
key int
|
||||
value float64
|
||||
}
|
||||
|
||||
func NewEntityCommand(key int, value float64) EntityCommand {
|
||||
return EntityCommand{
|
||||
key: key,
|
||||
value: value,
|
||||
}
|
||||
}
|
|
@ -43,3 +43,7 @@ func NewEntityAnimation(
|
|||
func (e *entityAnimation) Draw(frame int32, windowPosition *sdl.Point) error {
|
||||
return e.spriteAnimation.Draw(frame, windowPosition, e.angle, e.center, e.flip)
|
||||
}
|
||||
|
||||
func DefineAnimations() {
|
||||
DefinePenguinAnimations()
|
||||
}
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/channels"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/vector"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
type penguin struct {
|
||||
ctx context.Context
|
||||
timeout time.Duration
|
||||
commandChan chan EntityCommand
|
||||
|
||||
// Animation parameters
|
||||
currentAnimation *entityAnimation
|
||||
animationStep int32
|
||||
|
@ -21,17 +28,25 @@ type penguin struct {
|
|||
speed float64
|
||||
}
|
||||
|
||||
func NewPenguin(renderer *sdl.Renderer) *penguin {
|
||||
func NewPenguin(ctx context.Context, renderer *sdl.Renderer) *penguin {
|
||||
commandChan := make(chan EntityCommand, 10)
|
||||
defaultTimeout := time.Second
|
||||
|
||||
position := vector.Vec2F{}
|
||||
direction := vector.Vec2F{}
|
||||
speed := 1.0
|
||||
|
||||
p := penguin{
|
||||
ctx: ctx,
|
||||
commandChan: commandChan,
|
||||
timeout: defaultTimeout,
|
||||
|
||||
currentAnimation: penguinAnimations[PENGUIN_DEFAULT],
|
||||
animationStep: 0,
|
||||
facingRight: true,
|
||||
|
||||
worldPosition: &position,
|
||||
speed: 2.0,
|
||||
speed: speed,
|
||||
direction: &direction,
|
||||
}
|
||||
|
||||
|
@ -61,11 +76,11 @@ func (p *penguin) SetPosition(vec *vector.Vec2F) {
|
|||
p.worldPosition = vec
|
||||
}
|
||||
|
||||
func (p *penguin) SetAnimation(name string) {
|
||||
a, exists := penguinAnimations[name]
|
||||
func (p *penguin) SetAnimation(id int) {
|
||||
a, exists := penguinAnimations[id]
|
||||
|
||||
if !exists {
|
||||
log.Printf("animation does not exist: %v", name)
|
||||
log.Printf("animation does not exist: %v", id)
|
||||
a = penguinAnimations[PENGUIN_DEFAULT]
|
||||
}
|
||||
|
||||
|
@ -78,15 +93,25 @@ func (p *penguin) SetAnimation(name string) {
|
|||
|
||||
func (p *penguin) MoveX(x float64) {
|
||||
p.direction.X = x
|
||||
p.direction.Normalize()
|
||||
p.updateAnimation = true
|
||||
}
|
||||
|
||||
func (p *penguin) MoveY(y float64) {
|
||||
// (0,0) is the top left, so negative y moves up
|
||||
p.direction.Y = y
|
||||
p.direction.Normalize()
|
||||
p.updateAnimation = true
|
||||
}
|
||||
|
||||
func (p *penguin) SetSpeed(s float64) {
|
||||
p.speed = s
|
||||
}
|
||||
|
||||
func (p *penguin) GetSpeed() float64 {
|
||||
return p.speed
|
||||
}
|
||||
|
||||
func (p *penguin) SetMoveAnimation() {
|
||||
x := p.direction.X
|
||||
y := p.direction.Y
|
||||
|
@ -124,12 +149,70 @@ func (p *penguin) Update() error {
|
|||
p.updateAnimation = false
|
||||
}
|
||||
|
||||
p.direction.Normalize()
|
||||
x := p.direction.X * p.speed
|
||||
y := p.direction.Y * p.speed
|
||||
|
||||
log.Printf("X: %v, Y: %v\n", x, y)
|
||||
p.worldPosition.X += x
|
||||
p.worldPosition.Y += y
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *penguin) GetCommandChan() chan EntityCommand {
|
||||
return p.commandChan
|
||||
}
|
||||
|
||||
func (p *penguin) Run() {
|
||||
running := true
|
||||
for running {
|
||||
select {
|
||||
case c := <-p.commandChan:
|
||||
go func(cmd EntityCommand) {
|
||||
p.HandleWithTimeout(cmd)
|
||||
}(c)
|
||||
case <-p.ctx.Done():
|
||||
// Graceful shutdown
|
||||
running = false
|
||||
// Finish up anything in the queue
|
||||
for c := range p.commandChan {
|
||||
p.HandleWithTimeout(c)
|
||||
}
|
||||
log.Printf("entity shutdown\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *penguin) HandleWithTimeout(c EntityCommand) {
|
||||
err := channels.RunWithTimeout(p.timeout, func() error {
|
||||
return p.Handle(c)
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("%v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *penguin) Handle(c EntityCommand) error {
|
||||
var err error
|
||||
switch c.key {
|
||||
case COMMAND_DRAW:
|
||||
err = p.Draw()
|
||||
if err != nil {
|
||||
log.Printf("error drawing entity: %v", err)
|
||||
}
|
||||
case COMMAND_UPDATE:
|
||||
err = p.Update()
|
||||
if err != nil {
|
||||
log.Printf("error updating entity: %v", err)
|
||||
}
|
||||
case COMMAND_MOVE_X:
|
||||
p.MoveX(c.value)
|
||||
case COMMAND_MOVE_Y:
|
||||
p.MoveY(c.value)
|
||||
case COMMAND_SET_SPEED:
|
||||
p.SetSpeed(c.value)
|
||||
default:
|
||||
log.Printf("unknown entity command: %v", c.key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -5,13 +5,13 @@ import (
|
|||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
var penguinAnimations map[string]*entityAnimation
|
||||
var penguinAnimations map[int]*entityAnimation
|
||||
|
||||
const (
|
||||
PENGUIN_WALK_RIGHT = "walk-right"
|
||||
PENGUIN_WALK_LEFT = "walk-left"
|
||||
PENGUIN_STATIONARY_RIGHT = "stationary-right"
|
||||
PENGUIN_STATIONARY_LEFT = "stationary-left"
|
||||
PENGUIN_WALK_RIGHT int = iota
|
||||
PENGUIN_WALK_LEFT
|
||||
PENGUIN_STATIONARY_RIGHT
|
||||
PENGUIN_STATIONARY_LEFT
|
||||
|
||||
PENGUIN_DEFAULT = PENGUIN_STATIONARY_RIGHT
|
||||
)
|
||||
|
@ -28,7 +28,7 @@ func DefinePenguinAnimations() {
|
|||
)
|
||||
|
||||
dimensions = sdl.Point{X: 32, Y: 32}
|
||||
penguinAnimations = make(map[string]*entityAnimation)
|
||||
penguinAnimations = make(map[int]*entityAnimation)
|
||||
|
||||
// Walking Right is in the spritesheet.
|
||||
speed = 5
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
package player
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/channels"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/entity"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
// player represents a collection of stuff controlled by the user's input.
|
||||
// It contains the camera used to view the world,
|
||||
// the viewport for the part of the screen to draw to (splitscreen support),
|
||||
// as well as the entity the player is currently controlling.
|
||||
type player struct {
|
||||
ctx context.Context
|
||||
timeout time.Duration
|
||||
sdlEventsChan chan sdl.KeyboardEvent
|
||||
|
||||
entityChan chan entity.EntityCommand
|
||||
keystates map[sdl.Keycode]bool
|
||||
}
|
||||
|
||||
func NewPlayer(ctx context.Context) *player {
|
||||
sdlEventsChan := make(chan sdl.KeyboardEvent, 10)
|
||||
defaultTimeout := time.Second
|
||||
|
||||
keystates := make(map[sdl.Keycode]bool)
|
||||
p := player{
|
||||
ctx: ctx,
|
||||
timeout: defaultTimeout,
|
||||
sdlEventsChan: sdlEventsChan,
|
||||
keystates: keystates,
|
||||
}
|
||||
return &p
|
||||
}
|
||||
|
||||
func (p *player) SetEntityChan(e chan entity.EntityCommand) {
|
||||
p.entityChan = e
|
||||
}
|
||||
|
||||
func (p *player) GetSdlEventsChan() chan sdl.KeyboardEvent {
|
||||
return p.sdlEventsChan
|
||||
}
|
||||
|
||||
func (p *player) Run() {
|
||||
running := true
|
||||
for running {
|
||||
select {
|
||||
case e := <-p.sdlEventsChan:
|
||||
go func(event sdl.KeyboardEvent) {
|
||||
p.HandleWithTimeout(event)
|
||||
}(e)
|
||||
case <-p.ctx.Done():
|
||||
// Graceful shutdown
|
||||
running = false
|
||||
// Finish up anything in the queue
|
||||
for e := range p.sdlEventsChan {
|
||||
p.HandleWithTimeout(e)
|
||||
}
|
||||
log.Printf("player shutdown\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *player) HandleWithTimeout(event sdl.KeyboardEvent) {
|
||||
err := channels.RunWithTimeout(p.timeout, func() error {
|
||||
return p.Handle(event)
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("%v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *player) Handle(e sdl.KeyboardEvent) error {
|
||||
log.Printf("%v", e)
|
||||
// Key states (just set a boolean whether the key is actively being pressed)
|
||||
keystateChanged := false
|
||||
if e.Type == sdl.KEYDOWN {
|
||||
if !p.keystates[e.Keysym.Sym] {
|
||||
keystateChanged = true
|
||||
}
|
||||
p.keystates[e.Keysym.Sym] = true
|
||||
} else if e.Type == sdl.KEYUP {
|
||||
if p.keystates[e.Keysym.Sym] {
|
||||
keystateChanged = true
|
||||
}
|
||||
p.keystates[e.Keysym.Sym] = false
|
||||
}
|
||||
|
||||
// Only send events to the entity if something actually changed
|
||||
if !keystateChanged {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Speed
|
||||
if p.keystates[sdl.K_LSHIFT] {
|
||||
p.entityChan <- entity.NewEntityCommand(entity.COMMAND_SET_SPEED, 4)
|
||||
} else {
|
||||
p.entityChan <- entity.NewEntityCommand(entity.COMMAND_SET_SPEED, 2)
|
||||
}
|
||||
|
||||
// Move X
|
||||
if p.keystates[sdl.K_d] {
|
||||
p.entityChan <- entity.NewEntityCommand(entity.COMMAND_MOVE_X, 1.0)
|
||||
} else if p.keystates[sdl.K_a] {
|
||||
p.entityChan <- entity.NewEntityCommand(entity.COMMAND_MOVE_X, -1.0)
|
||||
} else if !p.keystates[sdl.K_d] && !p.keystates[sdl.K_a] {
|
||||
p.entityChan <- entity.NewEntityCommand(entity.COMMAND_MOVE_X, 0.0)
|
||||
}
|
||||
|
||||
// Move Y
|
||||
if p.keystates[sdl.K_w] {
|
||||
p.entityChan <- entity.NewEntityCommand(entity.COMMAND_MOVE_Y, -1.0)
|
||||
} else if p.keystates[sdl.K_s] {
|
||||
p.entityChan <- entity.NewEntityCommand(entity.COMMAND_MOVE_Y, 1.0)
|
||||
} else if !p.keystates[sdl.K_w] && !p.keystates[sdl.K_s] {
|
||||
p.entityChan <- entity.NewEntityCommand(entity.COMMAND_MOVE_Y, 0.0)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
package game
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/entity"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/player"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/sprite"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/vector"
|
||||
"gitea.wisellama.rocks/Wisellama/gosimpleconf"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
type Entity interface {
|
||||
Run()
|
||||
GetCommandChan() chan entity.EntityCommand
|
||||
Draw() error
|
||||
Update() error
|
||||
}
|
||||
|
||||
// Run is the main function to start the game.
|
||||
func Run(ctx context.Context, configMap gosimpleconf.ConfigMap) error {
|
||||
var err error
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
framerate, err := strconv.ParseFloat(configMap["game.framerate"], 64)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error parsing framerate: %w", err)
|
||||
return err
|
||||
}
|
||||
framerateDelay := uint32(1000 / framerate)
|
||||
|
||||
err = SdlInit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer SdlQuit()
|
||||
|
||||
gameWindow, err := NewWindow(configMap["game.title"])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed creating GameWindow: %w", err)
|
||||
return err
|
||||
}
|
||||
defer gameWindow.Cleanup()
|
||||
|
||||
var renderer *sdl.Renderer
|
||||
sdl.Do(func() {
|
||||
renderer, err = sdl.CreateRenderer(gameWindow.SdlWindow, -1, sdl.RENDERER_ACCELERATED)
|
||||
})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed creating SDL renderer: %w", err)
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
sdl.Do(func() {
|
||||
err = renderer.Destroy()
|
||||
if err != nil {
|
||||
log.Printf("error destroying renderer: %v\n", err)
|
||||
}
|
||||
})
|
||||
}()
|
||||
|
||||
err = sprite.InitSpriteCache(renderer)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed in InitSpriteCache: %w", err)
|
||||
return err
|
||||
}
|
||||
defer sprite.CleanupSpriteCache()
|
||||
|
||||
entity.DefinePenguinAnimations()
|
||||
|
||||
// Done with main setup, now moving on to creating specific entities
|
||||
|
||||
entityList := make([]Entity, 0)
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
// Setup Player 1
|
||||
// Let them control a penguin to start with
|
||||
player1 := player.NewPlayer(ctx)
|
||||
penguin := entity.NewPenguin(ctx, renderer)
|
||||
penguin.SetSpeed(2.0)
|
||||
entityList = append(entityList, penguin)
|
||||
player1.SetEntityChan(penguin.GetCommandChan())
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
penguin.Run()
|
||||
}()
|
||||
|
||||
p2 := entity.NewPenguin(ctx, renderer)
|
||||
p2.SetPosition(&vector.Vec2F{X: 100, Y: 100})
|
||||
p2.SetAnimation(entity.PENGUIN_WALK_LEFT)
|
||||
entityList = append(entityList, p2)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
p2.Run()
|
||||
}()
|
||||
|
||||
// And now starting the main loop
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
player1.Run()
|
||||
}()
|
||||
|
||||
running := true
|
||||
for running {
|
||||
|
||||
// Allow us to exit gracefully if the context is done
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
running = false
|
||||
default:
|
||||
// Keep running
|
||||
}
|
||||
|
||||
// Poll for SDL events
|
||||
var event sdl.Event
|
||||
sdl.Do(func() {
|
||||
event = sdl.PollEvent()
|
||||
|
||||
for event != nil {
|
||||
switch e := event.(type) {
|
||||
case *sdl.QuitEvent:
|
||||
log.Println("QuitEvent quitting")
|
||||
cancel()
|
||||
case *sdl.KeyboardEvent:
|
||||
if e.Keysym.Sym == sdl.K_ESCAPE {
|
||||
log.Println("Esc quitting")
|
||||
cancel()
|
||||
} else {
|
||||
// Publish the event so other components can do whatever they need with it
|
||||
player1.GetSdlEventsChan() <- *e
|
||||
}
|
||||
}
|
||||
|
||||
event = sdl.PollEvent()
|
||||
}
|
||||
})
|
||||
|
||||
// Background
|
||||
sdl.Do(func() {
|
||||
err = renderer.SetDrawColor(0, 120, 0, 255)
|
||||
if err != nil {
|
||||
log.Printf("error in renderer.SetDrawColor: %v\n", err)
|
||||
}
|
||||
|
||||
err = renderer.Clear()
|
||||
if err != nil {
|
||||
log.Printf("error in renderer.Clear: %v\n", err)
|
||||
}
|
||||
})
|
||||
|
||||
// Everything else
|
||||
for _, e := range entityList {
|
||||
//e.GetCommandChan() <- entity.NewEntityCommand(entity.COMMAND_UPDATE, 0)
|
||||
//e.GetCommandChan() <- entity.NewEntityCommand(entity.COMMAND_DRAW, 0)
|
||||
err = e.Update()
|
||||
if err != nil {
|
||||
log.Printf("error updating: %v", err)
|
||||
}
|
||||
|
||||
err = e.Draw()
|
||||
if err != nil {
|
||||
log.Printf("error drawing: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Draw
|
||||
sdl.Do(func() {
|
||||
renderer.Present()
|
||||
sdl.Delay(framerateDelay)
|
||||
})
|
||||
}
|
||||
|
||||
close(player1.GetSdlEventsChan())
|
||||
for _, e := range entityList {
|
||||
close(e.GetCommandChan())
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package game
|
||||
|
||||
// player represents a collection of stuff controlled by the user's input.
|
||||
// It contains the camera used to view the world,
|
||||
// the viewport for the part of the screen to draw to (splitscreen support),
|
||||
// as well as the entity the player is currently controlling.
|
||||
type player struct {
|
||||
}
|
||||
|
||||
func NewPlayer() *player {
|
||||
p := player{}
|
||||
return &p
|
||||
}
|
186
main.go
186
main.go
|
@ -1,20 +1,53 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/config"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/entity"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/sprite"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/vector"
|
||||
"gitea.wisellama.rocks/Wisellama/gosimpleconf"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Setup some initial context for gracefully killing
|
||||
// the program with system interrupt signals like ctrl+c
|
||||
// https://pace.dev/blog/2020/02/17/repond-to-ctrl-c-interrupt-signals-gracefully-with-context-in-golang-by-mat-ryer.html
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, os.Interrupt)
|
||||
defer func() {
|
||||
signal.Stop(signalChan)
|
||||
cancel()
|
||||
}()
|
||||
go func() {
|
||||
select {
|
||||
case <-signalChan:
|
||||
// Graceful exit on ctrl+c
|
||||
log.Printf("Attempting to exit gracefully...\n")
|
||||
cancel()
|
||||
case <-ctx.Done():
|
||||
}
|
||||
<-signalChan // Hard exit on second ctrl+c
|
||||
log.Printf("Hard kill\n")
|
||||
os.Exit(2)
|
||||
}()
|
||||
|
||||
// Run the program
|
||||
err := run(ctx)
|
||||
if err != nil {
|
||||
log.Printf("%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
log.Printf("Exited gracefully!\n")
|
||||
}
|
||||
|
||||
func run(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
// Read configuration
|
||||
|
@ -35,155 +68,12 @@ func main() {
|
|||
// Start everything with the SDL goroutine context
|
||||
log.Printf("=== Starting %v ===", configMap["game.title"])
|
||||
sdl.Main(func() {
|
||||
err = run(configMap)
|
||||
err = game.Run(ctx, configMap)
|
||||
})
|
||||
|
||||
exitcode := 0
|
||||
if err != nil {
|
||||
log.Printf("ERROR: %v\n", err)
|
||||
exitcode = 1
|
||||
}
|
||||
|
||||
os.Exit(exitcode)
|
||||
}
|
||||
|
||||
func run(configMap gosimpleconf.ConfigMap) error {
|
||||
var err error
|
||||
|
||||
err = game.SdlInit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer game.SdlQuit()
|
||||
|
||||
gameWindow, err := game.NewWindow(configMap["game.title"])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed creating GameWindow: %w", err)
|
||||
return err
|
||||
}
|
||||
defer gameWindow.Cleanup()
|
||||
|
||||
var renderer *sdl.Renderer
|
||||
sdl.Do(func() {
|
||||
renderer, err = sdl.CreateRenderer(gameWindow.SdlWindow, -1, sdl.RENDERER_ACCELERATED)
|
||||
})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed creating SDL renderer: %w", err)
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
sdl.Do(func() {
|
||||
err = renderer.Destroy()
|
||||
if err != nil {
|
||||
log.Printf("error destroying renderer: %v\n", err)
|
||||
}
|
||||
})
|
||||
}()
|
||||
|
||||
err = sprite.InitSpriteCache(renderer)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed in InitSpriteCache: %w", err)
|
||||
return err
|
||||
}
|
||||
defer sprite.CleanupSpriteCache()
|
||||
|
||||
entity.DefinePenguinAnimations()
|
||||
|
||||
penguin := entity.NewPenguin(renderer)
|
||||
p2 := entity.NewPenguin(renderer)
|
||||
p2.SetPosition(&vector.Vec2F{X: 100, Y: 100})
|
||||
p2.SetAnimation(entity.PENGUIN_WALK_LEFT)
|
||||
|
||||
keystates := make(map[sdl.Keycode]bool)
|
||||
|
||||
running := true
|
||||
for running {
|
||||
var event sdl.Event
|
||||
sdl.Do(func() {
|
||||
event = sdl.PollEvent()
|
||||
})
|
||||
for event != nil {
|
||||
switch e := event.(type) {
|
||||
case *sdl.QuitEvent:
|
||||
running = false
|
||||
case *sdl.KeyboardEvent:
|
||||
if e.Keysym.Sym == sdl.K_ESCAPE {
|
||||
log.Println("Esc quitting")
|
||||
running = false
|
||||
}
|
||||
|
||||
// Key states (just set a boolean whether the key is actively being pressed)
|
||||
if e.Type == sdl.KEYDOWN {
|
||||
keystates[e.Keysym.Sym] = true
|
||||
} else if e.Type == sdl.KEYUP {
|
||||
keystates[e.Keysym.Sym] = false
|
||||
}
|
||||
}
|
||||
|
||||
sdl.Do(func() {
|
||||
event = sdl.PollEvent()
|
||||
})
|
||||
}
|
||||
|
||||
if keystates[sdl.K_d] {
|
||||
penguin.MoveX(1)
|
||||
} else if keystates[sdl.K_a] {
|
||||
penguin.MoveX(-1)
|
||||
} else {
|
||||
penguin.MoveX(0)
|
||||
}
|
||||
|
||||
if keystates[sdl.K_w] {
|
||||
penguin.MoveY(-1)
|
||||
} else if keystates[sdl.K_s] {
|
||||
penguin.MoveY(1)
|
||||
} else {
|
||||
penguin.MoveY(0)
|
||||
}
|
||||
|
||||
if !keystates[sdl.K_a] && !keystates[sdl.K_d] && !keystates[sdl.K_w] && !keystates[sdl.K_s] {
|
||||
penguin.MoveX(0)
|
||||
penguin.MoveY(0)
|
||||
}
|
||||
|
||||
// Background
|
||||
sdl.Do(func() {
|
||||
err = renderer.SetDrawColor(0, 120, 0, 255)
|
||||
if err != nil {
|
||||
log.Printf("error in renderer.SetDrawColor: %v\n", err)
|
||||
}
|
||||
|
||||
err = renderer.Clear()
|
||||
if err != nil {
|
||||
log.Printf("error in renderer.Clear: %v\n", err)
|
||||
}
|
||||
})
|
||||
|
||||
// Everything else
|
||||
err = penguin.Update()
|
||||
if err != nil {
|
||||
log.Printf("error updating: %v\n", err)
|
||||
}
|
||||
err = penguin.Draw()
|
||||
if err != nil {
|
||||
log.Printf("error drawing: %v\n", err)
|
||||
}
|
||||
|
||||
err = p2.Update()
|
||||
if err != nil {
|
||||
log.Printf("error updating: %v\n", err)
|
||||
}
|
||||
err = p2.Draw()
|
||||
if err != nil {
|
||||
log.Printf("error drawing: %v\n", err)
|
||||
}
|
||||
|
||||
// Draw
|
||||
sdl.Do(func() {
|
||||
renderer.Present()
|
||||
sdl.Delay(16) // 60 FPS, well ok 62.5 FPS
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue