Restructure significantly, remove command/events for now.

main
Sean Hickey 2022-11-26 22:47:42 -08:00
parent cee1d19f36
commit 460316aaf6
19 changed files with 513 additions and 607 deletions

View File

@ -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()
}

View File

@ -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
}

View File

@ -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

View File

@ -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]
}

View File

@ -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

114
internal/entity/penguin.go Normal file
View File

@ -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
}

135
internal/game.go Normal file
View File

@ -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
}

View File

@ -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)
}

View File

@ -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,
}
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)