Restructure significantly, remove command/events for now.
parent
cee1d19f36
commit
460316aaf6
|
@ -0,0 +1,21 @@
|
|||
package animation
|
||||
|
||||
// AnimationMap maps each animation ID to the actual animation
|
||||
var AnimationMap map[int]entityAnimation
|
||||
|
||||
// Enum containing IDs of all animations that exist to make them easy to reference.
|
||||
const (
|
||||
PENGUIN_WALK_RIGHT int = iota
|
||||
PENGUIN_WALK_LEFT
|
||||
PENGUIN_WALK_UP
|
||||
PENGUIN_WALK_DOWN
|
||||
PENGUIN_STATIONARY_RIGHT
|
||||
PENGUIN_STATIONARY_LEFT
|
||||
PENGUIN_STATIONARY_UP
|
||||
PENGUIN_STATIONARY_DOWN
|
||||
)
|
||||
|
||||
func DefineAnimations() {
|
||||
AnimationMap = make(map[int]entityAnimation)
|
||||
DefinePenguinAnimations()
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package animation
|
||||
|
||||
import rl "github.com/gen2brain/raylib-go/raylib"
|
||||
|
||||
type animationTracker struct {
|
||||
currentAnimation entityAnimation
|
||||
animationStep int
|
||||
}
|
||||
|
||||
func NewAnimationTracker() *animationTracker {
|
||||
return &animationTracker{}
|
||||
}
|
||||
|
||||
func (a *animationTracker) Draw(windowPosition rl.Vector2, color rl.Color) error {
|
||||
step := a.animationStep / a.currentAnimation.GetSpeed()
|
||||
|
||||
err := a.currentAnimation.Draw(step, windowPosition, color)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.animationStep = 1 + a.animationStep
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *animationTracker) SetAnimation(id int) {
|
||||
newAnim := AnimationMap[id]
|
||||
|
||||
if newAnim != a.currentAnimation {
|
||||
a.animationStep = 0
|
||||
}
|
||||
|
||||
a.currentAnimation = newAnim
|
||||
}
|
|
@ -60,10 +60,6 @@ func (e entityAnimation) GetSpeed() int {
|
|||
return e.speed
|
||||
}
|
||||
|
||||
func DefineAnimations() {
|
||||
DefinePenguinAnimations()
|
||||
}
|
||||
|
||||
func getCenter(dimensions rl.Vector2) rl.Vector2 {
|
||||
x := dimensions.X / 2.0
|
||||
y := dimensions.Y / 2.0
|
|
@ -0,0 +1,85 @@
|
|||
package animation
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/sprite"
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
const (
|
||||
PENGUIN_DEFAULT = PENGUIN_STATIONARY_RIGHT
|
||||
)
|
||||
|
||||
var penguinAnimations []int
|
||||
|
||||
func DefinePenguinAnimations() {
|
||||
filename := sprite.DELILAHWALKING
|
||||
|
||||
penguinAnimations = make([]int, 0)
|
||||
penguinAnimations = append(penguinAnimations, PENGUIN_WALK_RIGHT)
|
||||
penguinAnimations = append(penguinAnimations, PENGUIN_WALK_LEFT)
|
||||
penguinAnimations = append(penguinAnimations, PENGUIN_WALK_UP)
|
||||
penguinAnimations = append(penguinAnimations, PENGUIN_WALK_DOWN)
|
||||
penguinAnimations = append(penguinAnimations, PENGUIN_STATIONARY_RIGHT)
|
||||
penguinAnimations = append(penguinAnimations, PENGUIN_STATIONARY_LEFT)
|
||||
penguinAnimations = append(penguinAnimations, PENGUIN_STATIONARY_UP)
|
||||
penguinAnimations = append(penguinAnimations, PENGUIN_STATIONARY_DOWN)
|
||||
|
||||
var (
|
||||
dimensions rl.Vector2
|
||||
offset rl.Vector2
|
||||
center rl.Vector2
|
||||
length int
|
||||
speed int
|
||||
border int
|
||||
)
|
||||
|
||||
dimensions = rl.Vector2{X: 13, Y: 17}
|
||||
|
||||
// Walking Right is in the spritesheet.
|
||||
speed = 5
|
||||
offset = rl.Vector2{X: 0, Y: 1}
|
||||
length = 5
|
||||
border = 1 // optional border around each sprite
|
||||
center = getCenter(dimensions) // center is for rotation, nil will default to w/2 h/2
|
||||
walkRight := sprite.NewAnimation(filename, dimensions, offset, length, border)
|
||||
AnimationMap[PENGUIN_WALK_RIGHT] = NewEntityAnimation(walkRight, speed, length, 0, center, FLIP_NONE)
|
||||
|
||||
// Walking Left is just that flipped.
|
||||
AnimationMap[PENGUIN_WALK_LEFT] = NewEntityAnimation(walkRight, speed, length, 0, center, FLIP_HORIZONTAL)
|
||||
|
||||
// Stationary Right/Left is just the first frame.
|
||||
length = 1
|
||||
stationaryRight := sprite.NewAnimation(filename, dimensions, offset, length, border)
|
||||
AnimationMap[PENGUIN_STATIONARY_RIGHT] = NewEntityAnimation(stationaryRight, speed, length, 0, center, FLIP_NONE)
|
||||
AnimationMap[PENGUIN_STATIONARY_LEFT] = NewEntityAnimation(stationaryRight, speed, length, 0, center, FLIP_HORIZONTAL)
|
||||
|
||||
// Walk Up
|
||||
length = 4
|
||||
offset = rl.Vector2{X: 0, Y: 3}
|
||||
walkUp := sprite.NewAnimation(filename, dimensions, offset, length, border)
|
||||
AnimationMap[PENGUIN_WALK_UP] = NewEntityAnimation(walkUp, speed, length, 0, center, FLIP_NONE)
|
||||
|
||||
// Stationary Up
|
||||
length = 1
|
||||
stationaryUp := sprite.NewAnimation(filename, dimensions, offset, length, border)
|
||||
AnimationMap[PENGUIN_STATIONARY_UP] = NewEntityAnimation(stationaryUp, speed, length, 0, center, FLIP_NONE)
|
||||
|
||||
// Walk Down
|
||||
length = 4
|
||||
offset = rl.Vector2{X: 0, Y: 0}
|
||||
walkDown := sprite.NewAnimation(filename, dimensions, offset, length, border)
|
||||
AnimationMap[PENGUIN_WALK_DOWN] = NewEntityAnimation(walkDown, speed, length, 0, center, FLIP_NONE)
|
||||
|
||||
// Stationary Down
|
||||
length = 1
|
||||
stationaryDown := sprite.NewAnimation(filename, dimensions, offset, length, border)
|
||||
AnimationMap[PENGUIN_STATIONARY_DOWN] = NewEntityAnimation(stationaryDown, speed, length, 0, center, FLIP_NONE)
|
||||
}
|
||||
|
||||
func RandomPenguinAnimation() int {
|
||||
n := len(penguinAnimations)
|
||||
i := rand.Intn(n)
|
||||
return penguinAnimations[i]
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package types
|
||||
package entity
|
||||
|
||||
import (
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
|
@ -9,6 +9,11 @@ type EntityAnimation interface {
|
|||
GetSpeed() int
|
||||
}
|
||||
|
||||
type AnimationTracker interface {
|
||||
Draw(windowPosition rl.Vector2, color rl.Color) error
|
||||
SetAnimation(id int)
|
||||
}
|
||||
|
||||
const (
|
||||
// These line up with the VEC_DIRECTIONS slice
|
||||
DIR_LEFT int = iota
|
|
@ -0,0 +1,114 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math"
|
||||
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/animation"
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/physics"
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
type penguin struct {
|
||||
animation AnimationTracker
|
||||
object physics.Object
|
||||
direction int
|
||||
staticAnimation bool
|
||||
color rl.Color
|
||||
}
|
||||
|
||||
func NewPenguin() *penguin {
|
||||
object := physics.NewObject()
|
||||
anim := animation.NewAnimationTracker()
|
||||
anim.SetAnimation(animation.PENGUIN_DEFAULT)
|
||||
|
||||
return &penguin{
|
||||
animation: anim,
|
||||
object: object,
|
||||
staticAnimation: false,
|
||||
color: rl.White,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *penguin) Draw() error {
|
||||
|
||||
position := p.object.GetPosition()
|
||||
|
||||
// TODO?
|
||||
//windowPosition := worldPosToWindowPos()
|
||||
windowPosition := rl.Vector2{
|
||||
X: float32(math.Round(float64(position.X))),
|
||||
Y: float32(-1 * math.Round(float64(position.Y))),
|
||||
}
|
||||
|
||||
return p.animation.Draw(windowPosition, p.color)
|
||||
}
|
||||
|
||||
func (p *penguin) Update() error {
|
||||
p.SetMoveAnimation()
|
||||
|
||||
return p.object.Update()
|
||||
}
|
||||
|
||||
func (p *penguin) GetObject() physics.Object {
|
||||
return p.object
|
||||
}
|
||||
|
||||
func (p *penguin) GetAnimationTracker() AnimationTracker {
|
||||
return p.animation
|
||||
}
|
||||
|
||||
// ToggleStaticAnimation will set whether or not this entity should ever update its animation loop or not.
|
||||
// True means this will never update, it is static.
|
||||
// False means this will update the animation loop based on physical properties of this entity.
|
||||
func (p *penguin) ToggleStaticAnimation() {
|
||||
p.staticAnimation = !p.staticAnimation
|
||||
}
|
||||
|
||||
func (p *penguin) SetMoveAnimation() {
|
||||
if p.staticAnimation {
|
||||
return
|
||||
}
|
||||
|
||||
velocity := p.object.GetVelocity()
|
||||
if velocity.X == 0 && velocity.Y == 0 {
|
||||
// Stay facing whatever direction we were facing
|
||||
direction := p.direction
|
||||
switch direction {
|
||||
case DIR_LEFT:
|
||||
p.animation.SetAnimation(animation.PENGUIN_STATIONARY_LEFT)
|
||||
case DIR_RIGHT:
|
||||
p.animation.SetAnimation(animation.PENGUIN_STATIONARY_RIGHT)
|
||||
case DIR_UP:
|
||||
p.animation.SetAnimation(animation.PENGUIN_STATIONARY_UP)
|
||||
case DIR_DOWN:
|
||||
p.animation.SetAnimation(animation.PENGUIN_STATIONARY_DOWN)
|
||||
default:
|
||||
log.Printf("unknown direction: %v", direction)
|
||||
p.animation.SetAnimation(animation.PENGUIN_DEFAULT)
|
||||
}
|
||||
|
||||
} else {
|
||||
// Figure out which way we are facing now that we're moving
|
||||
direction := determineClosestDirection(velocity)
|
||||
p.direction = direction
|
||||
|
||||
switch direction {
|
||||
case DIR_LEFT:
|
||||
p.animation.SetAnimation(animation.PENGUIN_WALK_LEFT)
|
||||
case DIR_RIGHT:
|
||||
p.animation.SetAnimation(animation.PENGUIN_WALK_RIGHT)
|
||||
case DIR_UP:
|
||||
p.animation.SetAnimation(animation.PENGUIN_WALK_UP)
|
||||
case DIR_DOWN:
|
||||
p.animation.SetAnimation(animation.PENGUIN_WALK_DOWN)
|
||||
default:
|
||||
log.Printf("unknown direction: %v", direction)
|
||||
p.animation.SetAnimation(animation.PENGUIN_DEFAULT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *penguin) SetColor(color rl.Color) {
|
||||
p.color = color
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/animation"
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/channels"
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/entity"
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/player"
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/sprite"
|
||||
"git.wisellama.rocks/Wisellama/gosimpleconf"
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
// Drawables can be rendered with OpenGL. See the 'entity' and 'animation' packages.
|
||||
type Drawable interface {
|
||||
Draw() error
|
||||
}
|
||||
|
||||
// Objects can have physical interactions. See the 'entity' and 'physics' packages.
|
||||
type Object interface {
|
||||
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()
|
||||
|
||||
framerate64 := gosimpleconf.Int64(configMap["game.framerate"])
|
||||
framerate := int32(framerate64)
|
||||
|
||||
// Initialize the RayLib window
|
||||
channels.RL.Do(func() {
|
||||
rl.InitWindow(800, 600, configMap["game.title"])
|
||||
rl.SetTargetFPS(framerate)
|
||||
})
|
||||
defer func() {
|
||||
channels.RL.Do(func() {
|
||||
rl.CloseWindow()
|
||||
})
|
||||
}()
|
||||
|
||||
// Initialize our sprites and animations
|
||||
sprite.InitSpriteCache()
|
||||
animation.DefineAnimations()
|
||||
|
||||
drawables := make([]Drawable, 0)
|
||||
objects := make([]Object, 0)
|
||||
|
||||
center := rl.Vector2{X: float32(rl.GetScreenWidth()) / 2, Y: float32(rl.GetScreenHeight()) / 2 * -1}
|
||||
player1 := player.NewPlayer(ctx)
|
||||
player1Penguin := entity.NewPenguin()
|
||||
player1Penguin.SetColor(rl.Gold)
|
||||
player1Penguin.GetObject().SetPosition(center)
|
||||
player1.SetControlledObject(player1Penguin.GetObject())
|
||||
drawables = append(drawables, player1Penguin)
|
||||
objects = append(objects, player1Penguin)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
p := entity.NewPenguin()
|
||||
p.GetObject().SetPosition(rl.Vector2{
|
||||
X: rand.Float32() * float32(rl.GetScreenWidth()),
|
||||
Y: rand.Float32() * float32(rl.GetScreenHeight()*-1),
|
||||
})
|
||||
p.GetAnimationTracker().SetAnimation(animation.RandomPenguinAnimation())
|
||||
p.ToggleStaticAnimation()
|
||||
|
||||
drawables = append(drawables, p)
|
||||
objects = append(objects, p)
|
||||
}
|
||||
|
||||
// And now starting the main loop
|
||||
running := true
|
||||
for running {
|
||||
channels.RL.Do(func() {
|
||||
if rl.WindowShouldClose() {
|
||||
cancel()
|
||||
}
|
||||
})
|
||||
|
||||
// Allow us to exit early if the context is done
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
running = false
|
||||
default:
|
||||
// Keep running
|
||||
}
|
||||
if !running {
|
||||
break
|
||||
}
|
||||
|
||||
player1.Update()
|
||||
|
||||
// Update physics
|
||||
// TODO probably move out to something else to handle collisions.
|
||||
for _, o := range objects {
|
||||
err = o.Update()
|
||||
if err != nil {
|
||||
log.Printf("error updating physics: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
channels.RL.Do(func() {
|
||||
rl.BeginDrawing()
|
||||
})
|
||||
|
||||
// Draw stuff
|
||||
for _, d := range drawables {
|
||||
err = d.Draw()
|
||||
if err != nil {
|
||||
log.Printf("error drawing: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
channels.RL.Do(func() {
|
||||
rl.ClearBackground(rl.Black)
|
||||
rl.DrawText("Some Text!", 190, 200, 20, rl.Blue)
|
||||
rl.DrawText(fmt.Sprintf("%v FPS", rl.GetFPS()), 190, 250, 20, rl.Blue)
|
||||
})
|
||||
|
||||
channels.RL.Do(func() {
|
||||
rl.EndDrawing()
|
||||
})
|
||||
}
|
||||
|
||||
sprite.CleanupSpriteCache()
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
package animation
|
||||
|
||||
import (
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/game/sprite"
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
var PenguinAnimations map[int]entityAnimation
|
||||
|
||||
const (
|
||||
PENGUIN_WALK_RIGHT int = iota
|
||||
PENGUIN_WALK_LEFT
|
||||
PENGUIN_WALK_UP
|
||||
PENGUIN_WALK_DOWN
|
||||
PENGUIN_STATIONARY_RIGHT
|
||||
PENGUIN_STATIONARY_LEFT
|
||||
PENGUIN_STATIONARY_UP
|
||||
PENGUIN_STATIONARY_DOWN
|
||||
)
|
||||
|
||||
const (
|
||||
PENGUIN_NUM_ANIMS = 8
|
||||
PENGUIN_DEFAULT = PENGUIN_STATIONARY_RIGHT
|
||||
)
|
||||
|
||||
func DefinePenguinAnimations() {
|
||||
filename := sprite.DELILAHWALKING
|
||||
|
||||
var (
|
||||
dimensions rl.Vector2
|
||||
offset rl.Vector2
|
||||
center rl.Vector2
|
||||
length int
|
||||
speed int
|
||||
border int
|
||||
)
|
||||
|
||||
dimensions = rl.Vector2{X: 13, Y: 17}
|
||||
PenguinAnimations = make(map[int]entityAnimation)
|
||||
|
||||
// Walking Right is in the spritesheet.
|
||||
speed = 5
|
||||
offset = rl.Vector2{X: 0, Y: 1}
|
||||
length = 5
|
||||
border = 1 // optional border around each sprite
|
||||
center = getCenter(dimensions) // center is for rotation, nil will default to w/2 h/2
|
||||
walkRight := sprite.NewAnimation(filename, dimensions, offset, length, border)
|
||||
PenguinAnimations[PENGUIN_WALK_RIGHT] = NewEntityAnimation(walkRight, speed, length, 0, center, FLIP_NONE)
|
||||
|
||||
// Walking Left is just that flipped.
|
||||
PenguinAnimations[PENGUIN_WALK_LEFT] = NewEntityAnimation(walkRight, speed, length, 0, center, FLIP_HORIZONTAL)
|
||||
|
||||
// Stationary Right/Left is just the first frame.
|
||||
length = 1
|
||||
stationaryRight := sprite.NewAnimation(filename, dimensions, offset, length, border)
|
||||
PenguinAnimations[PENGUIN_STATIONARY_RIGHT] = NewEntityAnimation(stationaryRight, speed, length, 0, center, FLIP_NONE)
|
||||
PenguinAnimations[PENGUIN_STATIONARY_LEFT] = NewEntityAnimation(stationaryRight, speed, length, 0, center, FLIP_HORIZONTAL)
|
||||
|
||||
// Walk Up
|
||||
length = 4
|
||||
offset = rl.Vector2{X: 0, Y: 3}
|
||||
walkUp := sprite.NewAnimation(filename, dimensions, offset, length, border)
|
||||
PenguinAnimations[PENGUIN_WALK_UP] = NewEntityAnimation(walkUp, speed, length, 0, center, FLIP_NONE)
|
||||
|
||||
// Stationary Up
|
||||
length = 1
|
||||
stationaryUp := sprite.NewAnimation(filename, dimensions, offset, length, border)
|
||||
PenguinAnimations[PENGUIN_STATIONARY_UP] = NewEntityAnimation(stationaryUp, speed, length, 0, center, FLIP_NONE)
|
||||
|
||||
// Walk Down
|
||||
length = 4
|
||||
offset = rl.Vector2{X: 0, Y: 0}
|
||||
walkDown := sprite.NewAnimation(filename, dimensions, offset, length, border)
|
||||
PenguinAnimations[PENGUIN_WALK_DOWN] = NewEntityAnimation(walkDown, speed, length, 0, center, FLIP_NONE)
|
||||
|
||||
// Stationary Down
|
||||
length = 1
|
||||
stationaryDown := sprite.NewAnimation(filename, dimensions, offset, length, border)
|
||||
PenguinAnimations[PENGUIN_STATIONARY_DOWN] = NewEntityAnimation(stationaryDown, speed, length, 0, center, FLIP_NONE)
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package command
|
||||
|
||||
// List of keys supported by all entities
|
||||
const (
|
||||
MOVE_X int = iota
|
||||
MOVE_Y
|
||||
SET_SPEED
|
||||
SET_POSITION
|
||||
SET_ANIMATION
|
||||
)
|
||||
|
||||
type Command struct {
|
||||
key int
|
||||
value float32
|
||||
}
|
||||
|
||||
func NewCommand(key int, value float32) Command {
|
||||
return Command{
|
||||
key: key,
|
||||
value: value,
|
||||
}
|
||||
}
|
|
@ -1,170 +0,0 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/channels"
|
||||
)
|
||||
|
||||
type Entity interface {
|
||||
Draw() error
|
||||
Update() error
|
||||
MoveX(x float32)
|
||||
MoveY(y float32)
|
||||
SetSpeed(s float32)
|
||||
}
|
||||
|
||||
type CommandHandler struct {
|
||||
ctx context.Context
|
||||
timeout time.Duration
|
||||
entity Entity
|
||||
commandChan chan Command
|
||||
drawRequestChan chan struct{}
|
||||
drawResponseChan chan struct{}
|
||||
updateRequestChan chan struct{}
|
||||
updateResponseChan chan struct{}
|
||||
}
|
||||
|
||||
func NewCommandHandler(ctx context.Context, entity Entity) *CommandHandler {
|
||||
commandChan := make(chan Command, 10)
|
||||
drawRequestChan := make(chan struct{})
|
||||
drawResponseChan := make(chan struct{})
|
||||
updateRequestChan := make(chan struct{})
|
||||
updateResponseChan := make(chan struct{})
|
||||
|
||||
defaultTimeout := time.Second
|
||||
return &CommandHandler{
|
||||
ctx: ctx,
|
||||
timeout: defaultTimeout,
|
||||
entity: entity,
|
||||
commandChan: commandChan,
|
||||
drawRequestChan: drawRequestChan,
|
||||
drawResponseChan: drawResponseChan,
|
||||
updateRequestChan: updateRequestChan,
|
||||
updateResponseChan: updateResponseChan,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandHandler) closeResponses() {
|
||||
close(c.drawResponseChan)
|
||||
close(c.updateResponseChan)
|
||||
}
|
||||
|
||||
func (c *CommandHandler) CloseRequests() {
|
||||
close(c.drawRequestChan)
|
||||
close(c.updateRequestChan)
|
||||
close(c.commandChan)
|
||||
}
|
||||
|
||||
func (c *CommandHandler) CommandRequest() chan Command {
|
||||
return c.commandChan
|
||||
}
|
||||
|
||||
func (c *CommandHandler) DrawRequest() chan struct{} {
|
||||
return c.drawRequestChan
|
||||
}
|
||||
|
||||
func (c *CommandHandler) DrawResponse() chan struct{} {
|
||||
return c.drawResponseChan
|
||||
}
|
||||
|
||||
func (c *CommandHandler) UpdateRequest() chan struct{} {
|
||||
return c.updateRequestChan
|
||||
}
|
||||
|
||||
func (c *CommandHandler) UpdateResponse() chan struct{} {
|
||||
return c.updateResponseChan
|
||||
}
|
||||
|
||||
func (c *CommandHandler) Run() {
|
||||
defer c.closeResponses()
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
running := true
|
||||
for running {
|
||||
select {
|
||||
case <-c.drawRequestChan:
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
c.DrawWithTimeout()
|
||||
c.drawResponseChan <- struct{}{}
|
||||
}()
|
||||
case <-c.updateRequestChan:
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
c.UpdateWithTimeout()
|
||||
c.updateResponseChan <- struct{}{}
|
||||
}()
|
||||
case cmd := <-c.commandChan:
|
||||
wg.Add(1)
|
||||
go func(cmd Command) {
|
||||
defer wg.Done()
|
||||
c.HandleWithTimeout(cmd)
|
||||
}(cmd)
|
||||
case <-c.ctx.Done():
|
||||
// Graceful shutdown
|
||||
running = false
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Finish up anything in the queues
|
||||
for cmd := range c.commandChan {
|
||||
c.HandleWithTimeout(cmd)
|
||||
}
|
||||
for range c.drawRequestChan {
|
||||
c.UpdateWithTimeout()
|
||||
}
|
||||
for range c.drawRequestChan {
|
||||
c.DrawWithTimeout()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandHandler) HandleWithTimeout(cmd Command) {
|
||||
err := channels.RunWithTimeout(c.timeout, func() error {
|
||||
return c.Handle(cmd)
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("handle with timeout error: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandHandler) Handle(cmd Command) error {
|
||||
switch cmd.key {
|
||||
case MOVE_X:
|
||||
c.entity.MoveX(cmd.value)
|
||||
case MOVE_Y:
|
||||
c.entity.MoveY(cmd.value)
|
||||
case SET_SPEED:
|
||||
c.entity.SetSpeed(cmd.value)
|
||||
default:
|
||||
log.Printf("unknown entity command: %v", cmd.key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CommandHandler) DrawWithTimeout() {
|
||||
err := channels.RunWithTimeout(c.timeout, func() error {
|
||||
return c.entity.Draw()
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("draw with timeout error: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandHandler) UpdateWithTimeout() {
|
||||
err := channels.RunWithTimeout(c.timeout, func() error {
|
||||
return c.entity.Update()
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("update with timeout error: %v\n", err)
|
||||
}
|
||||
}
|
|
@ -1,166 +0,0 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/animation"
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
type penguin struct {
|
||||
mx sync.RWMutex
|
||||
|
||||
// Animation parameters
|
||||
currentAnimation EntityAnimation // pointer to current animation loop
|
||||
animationStep int // index of animation loop
|
||||
direction int // direction facing for sprite animations
|
||||
updateAnimation bool // if false, don't change the animation
|
||||
|
||||
// Physical parameters
|
||||
worldPosition rl.Vector2 // where is the center of this object
|
||||
velocity rl.Vector2 // movement direction to be applied each tick
|
||||
speed float32 // movement magnitude to multiply with the velocity
|
||||
}
|
||||
|
||||
func NewPenguin() penguin {
|
||||
position := rl.Vector2{}
|
||||
velocity := rl.Vector2{}
|
||||
speed := float32(1)
|
||||
|
||||
return penguin{
|
||||
currentAnimation: animation.PenguinAnimations[animation.PENGUIN_DEFAULT],
|
||||
animationStep: 0,
|
||||
direction: DIR_RIGHT,
|
||||
|
||||
worldPosition: position,
|
||||
speed: speed,
|
||||
velocity: velocity,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *penguin) Draw() error {
|
||||
step := p.animationStep / p.currentAnimation.GetSpeed()
|
||||
|
||||
// TODO
|
||||
//windowPosition := worldPosToWindowPos()
|
||||
windowPosition := rl.Vector2{
|
||||
X: float32(math.Round(float64(p.worldPosition.X))),
|
||||
Y: float32(math.Round(float64(-1 * p.worldPosition.Y))),
|
||||
}
|
||||
|
||||
err := p.currentAnimation.Draw(step, windowPosition, rl.White)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.animationStep = 1 + p.animationStep
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *penguin) SetPosition(vec rl.Vector2) {
|
||||
p.worldPosition = vec
|
||||
}
|
||||
|
||||
func (p *penguin) SetDirection(dir int) {
|
||||
p.direction = dir
|
||||
}
|
||||
|
||||
func (p *penguin) SetAnimation(id int) {
|
||||
a, exists := animation.PenguinAnimations[id]
|
||||
|
||||
if !exists {
|
||||
log.Printf("animation does not exist: %v", id)
|
||||
a = animation.PenguinAnimations[animation.PENGUIN_DEFAULT]
|
||||
}
|
||||
|
||||
if a != p.currentAnimation {
|
||||
p.animationStep = 0
|
||||
}
|
||||
|
||||
p.currentAnimation = a
|
||||
}
|
||||
|
||||
func (p *penguin) MoveX(x float32) {
|
||||
p.mx.Lock()
|
||||
defer p.mx.Unlock()
|
||||
|
||||
p.velocity.X = x
|
||||
if p.velocity.X != 0 || p.velocity.Y != 0 {
|
||||
p.velocity = rl.Vector2Normalize(p.velocity)
|
||||
}
|
||||
p.updateAnimation = true
|
||||
}
|
||||
|
||||
func (p *penguin) MoveY(y float32) {
|
||||
p.mx.Lock()
|
||||
defer p.mx.Unlock()
|
||||
|
||||
p.velocity.Y = y
|
||||
if p.velocity.X != 0 || p.velocity.Y != 0 {
|
||||
p.velocity = rl.Vector2Normalize(p.velocity)
|
||||
}
|
||||
p.updateAnimation = true
|
||||
}
|
||||
|
||||
func (p *penguin) SetSpeed(s float32) {
|
||||
p.speed = s
|
||||
}
|
||||
|
||||
func (p *penguin) GetSpeed() float32 {
|
||||
return p.speed
|
||||
}
|
||||
|
||||
func (p *penguin) SetMoveAnimation() {
|
||||
if p.velocity.X == 0 && p.velocity.Y == 0 {
|
||||
// Stay facing whatever direction we were facing
|
||||
switch p.direction {
|
||||
case DIR_LEFT:
|
||||
p.SetAnimation(animation.PENGUIN_STATIONARY_LEFT)
|
||||
case DIR_RIGHT:
|
||||
p.SetAnimation(animation.PENGUIN_STATIONARY_RIGHT)
|
||||
case DIR_UP:
|
||||
p.SetAnimation(animation.PENGUIN_STATIONARY_UP)
|
||||
case DIR_DOWN:
|
||||
p.SetAnimation(animation.PENGUIN_STATIONARY_DOWN)
|
||||
default:
|
||||
log.Printf("unknown direction: %v", p.direction)
|
||||
p.SetAnimation(animation.PENGUIN_DEFAULT)
|
||||
}
|
||||
|
||||
} else {
|
||||
// Figure out which way we are facing now that we're moving
|
||||
p.direction = determineClosestDirection(p.velocity)
|
||||
|
||||
switch p.direction {
|
||||
case DIR_LEFT:
|
||||
p.SetAnimation(animation.PENGUIN_WALK_LEFT)
|
||||
case DIR_RIGHT:
|
||||
p.SetAnimation(animation.PENGUIN_WALK_RIGHT)
|
||||
case DIR_UP:
|
||||
p.SetAnimation(animation.PENGUIN_WALK_UP)
|
||||
case DIR_DOWN:
|
||||
p.SetAnimation(animation.PENGUIN_WALK_DOWN)
|
||||
default:
|
||||
log.Printf("unknown direction: %v", p.direction)
|
||||
p.SetAnimation(animation.PENGUIN_DEFAULT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *penguin) Update() error {
|
||||
if p.updateAnimation {
|
||||
p.SetMoveAnimation()
|
||||
p.updateAnimation = false
|
||||
}
|
||||
|
||||
x := p.velocity.X * p.speed
|
||||
y := p.velocity.Y * p.speed
|
||||
|
||||
p.worldPosition.X += x
|
||||
p.worldPosition.Y += y
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
package game
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/channels"
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/animation"
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/command"
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/types"
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/game/player"
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/game/sprite"
|
||||
"git.wisellama.rocks/Wisellama/gosimpleconf"
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
type EntityCmdHandler interface {
|
||||
Run()
|
||||
CloseRequests()
|
||||
CommandRequest() chan command.Command
|
||||
DrawRequest() chan struct{}
|
||||
DrawResponse() chan struct{}
|
||||
UpdateRequest() chan struct{}
|
||||
UpdateResponse() chan struct{}
|
||||
}
|
||||
|
||||
// Run is the main function to start the game.
|
||||
func Run(ctx context.Context, configMap gosimpleconf.ConfigMap) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
framerate64 := gosimpleconf.Int64(configMap["game.framerate"])
|
||||
framerate := int32(framerate64)
|
||||
|
||||
// Initialize the RayLib window
|
||||
channels.RL.Do(func() {
|
||||
rl.InitWindow(800, 600, configMap["game.title"])
|
||||
rl.SetTargetFPS(framerate)
|
||||
})
|
||||
defer func() {
|
||||
channels.RL.Do(func() {
|
||||
rl.CloseWindow()
|
||||
})
|
||||
}()
|
||||
|
||||
// Initialize our sprites and animations
|
||||
sprite.InitSpriteCache()
|
||||
animation.DefineAnimations()
|
||||
|
||||
entityList := make([]EntityCmdHandler, 0)
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
playerPenguinEntity := types.NewPenguin()
|
||||
playerPenguinEntity.SetSpeed(2)
|
||||
playerPenguinEntity.SetPosition(rl.Vector2{X: 100, Y: -100})
|
||||
playerPenguinCmd := command.NewCommandHandler(ctx, &playerPenguinEntity)
|
||||
playerPenguin := player.NewPlayer(ctx)
|
||||
playerPenguin.SetEntityChan(playerPenguinCmd.CommandRequest())
|
||||
entityList = append(entityList, playerPenguinCmd)
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
playerPenguinCmd.Run()
|
||||
}()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
entity := types.NewPenguin()
|
||||
entityCmd := command.NewCommandHandler(ctx, &entity)
|
||||
randomPos := rl.Vector2{X: rand.Float32() * 500, Y: rand.Float32() * -500}
|
||||
entity.SetPosition(randomPos)
|
||||
entity.SetAnimation(rand.Intn(animation.PENGUIN_NUM_ANIMS))
|
||||
entityList = append(entityList, entityCmd)
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
entityCmd.Run()
|
||||
}()
|
||||
}
|
||||
|
||||
// And now starting the main loop
|
||||
running := true
|
||||
for running {
|
||||
channels.RL.Do(func() {
|
||||
if rl.WindowShouldClose() {
|
||||
cancel()
|
||||
}
|
||||
})
|
||||
|
||||
// Allow us to exit early if the context is done
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
running = false
|
||||
default:
|
||||
// Keep running
|
||||
}
|
||||
if !running {
|
||||
break
|
||||
}
|
||||
|
||||
// Update players
|
||||
playerPenguin.Update()
|
||||
|
||||
// Start drawing
|
||||
channels.RL.Do(func() {
|
||||
rl.BeginDrawing()
|
||||
})
|
||||
|
||||
// Tell everything to Update and Draw
|
||||
for _, e := range entityList {
|
||||
e.UpdateRequest() <- struct{}{}
|
||||
e.DrawRequest() <- struct{}{}
|
||||
}
|
||||
// Wait for each entity to finish their Draw and Update commands before proceeding
|
||||
for _, e := range entityList {
|
||||
<-e.UpdateResponse()
|
||||
<-e.DrawResponse()
|
||||
}
|
||||
|
||||
// Finish drawing
|
||||
channels.RL.Do(func() {
|
||||
rl.ClearBackground(rl.Black)
|
||||
rl.DrawText("Some Text!", 190, 200, 20, rl.Blue)
|
||||
rl.DrawText(fmt.Sprintf("%v FPS", rl.GetFPS()), 190, 250, 20, rl.Blue)
|
||||
|
||||
rl.EndDrawing()
|
||||
})
|
||||
}
|
||||
|
||||
for _, e := range entityList {
|
||||
e.CloseRequests()
|
||||
}
|
||||
sprite.CleanupSpriteCache()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package physics
|
||||
|
||||
import rl "github.com/gen2brain/raylib-go/raylib"
|
||||
|
||||
// Object defines the interface for physical objects.
|
||||
//
|
||||
// This is defined here in the object.go file because after
|
||||
// passing an 'object' to an 'entity' (e.g. 'penguin'),
|
||||
// then to a 'player', as well as the main function,
|
||||
// we needed almost all of the functions for the whole
|
||||
// interface defined in each place.
|
||||
type Object interface {
|
||||
Update() error
|
||||
|
||||
GetPosition() rl.Vector2
|
||||
GetVelocity() rl.Vector2
|
||||
GetAcceleration() rl.Vector2
|
||||
GetBaseAcceleration() rl.Vector2
|
||||
|
||||
SetPosition(pos rl.Vector2)
|
||||
SetVelocity(vel rl.Vector2)
|
||||
SetAcceleration(acc rl.Vector2)
|
||||
SetBaseAcceleration(base rl.Vector2)
|
||||
}
|
||||
|
||||
type object struct {
|
||||
position rl.Vector2
|
||||
velocity rl.Vector2
|
||||
acceleration rl.Vector2
|
||||
baseAcceleration rl.Vector2 // some default speed for this object
|
||||
}
|
||||
|
||||
func NewObject() *object {
|
||||
pos := rl.Vector2{X: 0.0, Y: 0.0}
|
||||
vel := rl.Vector2{X: 0.0, Y: 0.0}
|
||||
acc := rl.Vector2{X: 0.0, Y: 0.0}
|
||||
base := rl.Vector2{X: 1.0, Y: 1.0}
|
||||
|
||||
return &object{
|
||||
position: pos,
|
||||
velocity: vel,
|
||||
acceleration: acc,
|
||||
baseAcceleration: base,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *object) Update() error {
|
||||
p := o.position
|
||||
v := o.velocity
|
||||
a := o.acceleration
|
||||
|
||||
v = rl.Vector2Add(v, a)
|
||||
o.velocity = v
|
||||
|
||||
p = rl.Vector2Add(p, v)
|
||||
o.position = p
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *object) SetPosition(pos rl.Vector2) {
|
||||
o.position = pos
|
||||
}
|
||||
|
||||
func (o *object) SetVelocity(vel rl.Vector2) {
|
||||
o.velocity = vel
|
||||
}
|
||||
func (o *object) SetAcceleration(acc rl.Vector2) {
|
||||
o.acceleration = acc
|
||||
}
|
||||
|
||||
func (o *object) SetBaseAcceleration(base rl.Vector2) {
|
||||
o.baseAcceleration = base
|
||||
}
|
||||
|
||||
func (o *object) GetPosition() rl.Vector2 {
|
||||
return o.position
|
||||
}
|
||||
func (o *object) GetVelocity() rl.Vector2 {
|
||||
return o.velocity
|
||||
}
|
||||
func (o *object) GetAcceleration() rl.Vector2 {
|
||||
return o.acceleration
|
||||
}
|
||||
func (o *object) GetBaseAcceleration() rl.Vector2 {
|
||||
return o.baseAcceleration
|
||||
}
|
|
@ -5,10 +5,13 @@ import (
|
|||
"time"
|
||||
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/channels"
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/command"
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
type PhysicalObject interface {
|
||||
SetVelocity(vel rl.Vector2)
|
||||
}
|
||||
|
||||
// A 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),
|
||||
|
@ -17,7 +20,7 @@ type player struct {
|
|||
ctx context.Context
|
||||
timeout time.Duration
|
||||
|
||||
entityChan chan command.Command
|
||||
controlledObject PhysicalObject
|
||||
}
|
||||
|
||||
func NewPlayer(ctx context.Context) *player {
|
||||
|
@ -30,8 +33,8 @@ func NewPlayer(ctx context.Context) *player {
|
|||
return &p
|
||||
}
|
||||
|
||||
func (p *player) SetEntityChan(e chan command.Command) {
|
||||
p.entityChan = e
|
||||
func (p *player) SetControlledObject(c PhysicalObject) {
|
||||
p.controlledObject = c
|
||||
}
|
||||
|
||||
func (p *player) Update() {
|
||||
|
@ -40,38 +43,43 @@ func (p *player) Update() {
|
|||
down bool
|
||||
left bool
|
||||
right bool
|
||||
speed bool
|
||||
fast bool
|
||||
)
|
||||
channels.RL.Do(func() {
|
||||
speed = rl.IsKeyDown(rl.KeyLeftShift)
|
||||
fast = rl.IsKeyDown(rl.KeyLeftShift)
|
||||
left = rl.IsKeyDown(rl.KeyA) || rl.IsKeyDown(rl.KeyLeft)
|
||||
right = rl.IsKeyDown(rl.KeyD) || rl.IsKeyDown(rl.KeyRight)
|
||||
up = rl.IsKeyDown(rl.KeyW) || rl.IsKeyDown(rl.KeyUp)
|
||||
down = rl.IsKeyDown(rl.KeyS) || rl.IsKeyDown(rl.KeyDown)
|
||||
})
|
||||
|
||||
// Speed
|
||||
if speed {
|
||||
p.entityChan <- command.NewCommand(command.SET_SPEED, 4)
|
||||
} else {
|
||||
p.entityChan <- command.NewCommand(command.SET_SPEED, 2)
|
||||
}
|
||||
baseVel := rl.Vector2{X: 1.0, Y: 1.0}
|
||||
velocity := rl.Vector2{X: 0.0, Y: 0.0}
|
||||
|
||||
// Move horizontal
|
||||
if right {
|
||||
p.entityChan <- command.NewCommand(command.MOVE_X, 1.0)
|
||||
velocity.X = baseVel.X
|
||||
} else if left {
|
||||
p.entityChan <- command.NewCommand(command.MOVE_X, -1.0)
|
||||
} else if !right && !left {
|
||||
p.entityChan <- command.NewCommand(command.MOVE_X, 0.0)
|
||||
velocity.X = baseVel.X * -1
|
||||
}
|
||||
|
||||
// Move vertical
|
||||
if up {
|
||||
p.entityChan <- command.NewCommand(command.MOVE_Y, 1.0)
|
||||
velocity.Y = baseVel.Y
|
||||
} else if down {
|
||||
p.entityChan <- command.NewCommand(command.MOVE_Y, -1.0)
|
||||
} else if !up && !down {
|
||||
p.entityChan <- command.NewCommand(command.MOVE_Y, 0.0)
|
||||
velocity.Y = baseVel.Y * -1
|
||||
}
|
||||
|
||||
// If we're moving diagonal, normalize the vector to avoid weird diagonal super-speed
|
||||
if velocity.X != 0 || velocity.Y != 0 {
|
||||
velocity = rl.Vector2Normalize(velocity)
|
||||
}
|
||||
|
||||
// Faster speed
|
||||
if fast {
|
||||
velocity.X *= 2
|
||||
velocity.Y *= 2
|
||||
}
|
||||
|
||||
p.controlledObject.SetVelocity(velocity)
|
||||
}
|
4
main.go
4
main.go
|
@ -4,8 +4,8 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal"
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/channels"
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/game"
|
||||
"git.wisellama.rocks/Wisellama/gopackagebase"
|
||||
"git.wisellama.rocks/Wisellama/gosimpleconf"
|
||||
)
|
||||
|
@ -36,7 +36,7 @@ func main() {
|
|||
// Run the program
|
||||
log.Printf("=== Starting %v ===", baseConfig.ConfigMap["game.title"])
|
||||
channels.RL.Main(func() {
|
||||
err = game.Run(baseConfig.Ctx, baseConfig.ConfigMap)
|
||||
err = internal.Run(baseConfig.Ctx, baseConfig.ConfigMap)
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("%v\n", err)
|
||||
|
|
Loading…
Reference in New Issue