Rework entire project to use raylib.

Removed SDL entirely. Internally, raylib uses glfw and miniaudio, which
is the audio library I was going to use anyway.

I added a threadChannel that works very similar to the sdl.Do()
functions to support goroutines with a single-threaded library.
main
Sean Hickey 2022-11-22 00:08:00 -08:00
parent f8d47107ce
commit ce5ff1a552
26 changed files with 312 additions and 777 deletions

View File

@ -2,8 +2,7 @@ FROM alpine:latest
RUN apk add --no-cache \
gcc musl-dev make go \
alsa-lib libxrandr \
sdl2-dev sdl2_mixer-dev sdl2_image-dev sdl2_ttf-dev
alsa-lib libxrandr
WORKDIR /home

View File

@ -1,51 +0,0 @@
# Linux Static Release Build
Because we're depending on SDL and X11 libraries, we can't easily
build a completely statically-linked binary using musl unless we
recompile those library dependencies also using musl. I was able to
build something that was mostly statically-linked except for libc, but
then it didn't run in a base Fedora Docker container.
The workaround for this is to use something where everything is
already compiled with musl, like an Alpine Linux Docker container.
To build a fully statically-linked binary, use the `Dockerfile` in the
root directory.
```sh
docker build -t project-ely-build .
```
This should spit out an executable called `project-ely-static`.
## Attempts on Debian
I tried installing musl on Debian.
I installed it to my home directory. I also specified my pkgsrc version of GCC.
```sh
CC=$HOME/pkg/gcc10/bin/gcc
./configure --disabled-shared --prefix=$HOME/musl
make -j4
make install
```
Then I had to symlink the X11 include dir.
```sh
cd musl-install/include
ln -s /usr/include/X11 X11
```
I ran into an issue where it was unable to find many basic libraries
like Xrandr and asound. But then I hit that same issue with the Alpine
container =(
## References
Before giving up and using Alpine, I found these cool links that
suggest you could do this more easily if we only depended on the C std
library stuff, no external dependencies like X11 and SDL.
* https://honnef.co/posts/2015/06/statically_compiled_go_programs__always__even_with_cgo__using_musl/
* https://github.com/golang/go/issues/26492

View File

@ -1,16 +0,0 @@
# Windows Static Release Build
You can build a statically-linked binary on Windows that should run
without requiring the SDL `*.dll` files.
```sh
go build -x -v -trimpath -tags static -ldflags "-s -w -H windowsgui"
```
There is a script called [build-windows-static.sh][1] in the scripts
folder to make this easy from Git Bash.
The Makefile supports cross-compiling for this from Linux with the
`cross_windows` target.
[1]: scripts/build-windows-static.sh

View File

@ -1,4 +1,4 @@
# Go with SDL on everything else
# Go with raylib on everything else
Most any other operating system is Unix-like (e.g. Linux, Mac, BSD, Haiku, etc.), so they will all have very similar setups.
Each of these systems also has their own way of installing software, usually with their own package managers. I don't know all the exact names of each of these packages, but hopefully giving you some examples will help.
@ -8,7 +8,7 @@ You will need the following dependencies:
* Git
* Go (Golang)
* GCC
* SDL2 (with Image, TTF, and Mixer)
* X11 or Wayland
That's all, you may even have most of them installed already.
@ -16,10 +16,12 @@ I also use [golangci-lint][6] as a Go linter. This is used by the Makefile befor
### Debian
The [raylib-go readme][7] has good examples of installing the X11 or Wayland packages as needed. Below I just used the X11 packages as an example.
As the most common example, on Debian or Ubuntu, the following `apt` command should install all the dependencies:
```sh
sudo apt install git golang gcc libsdl2-dev libsdl2-mixer-dev libsdl2-image-dev libsdl2-ttf-dev
sudo apt install git golang gcc libgl1-mesa-dev libxi-dev libxcursor-dev libxrandr-dev libxinerama-dev
```
The default version of Go in the Debian repos might be too old. You can install a newer version using the testing/unstable repos (try [this][3] or [this][4]), or you could [manually install Go from their website][2].
@ -28,16 +30,16 @@ The default version of Go in the Debian repos might be too old. You can install
On Mac, the typical recommendation is to use [Homebrew][5]. The package names are pretty similar:
```sh
brew install git go gcc sdl2 sdl2_image sdl2_ttf sdl2_mixer
brew install git go gcc
```
### Redhat/Fedora/Rocky/CentOS
The Redhat-related distros all have very similar names as well, notably using `devel` instead of `dev` and `SDL2` is capitalized. I ran the following on Fedora successfully.
The Redhat-related distros all have very similar names as well, notably using `devel` instead of `dev`. I ran the following on Fedora successfully.
(these distros may use `dnf` or `yum` as the package manager executable, `dnf` is newer)
```sh
sudo dnf install git golang gcc SDL2-devel SDL2_image-devel SDL2_mixer-devel SDL2_ttf-devel
sudo dnf install git golang gcc mesa-libGL-devel libXi-devel libXcursor-devel libXrandr-devel libXinerama-devel
```
### pkgsrc
@ -48,10 +50,11 @@ As a reference for the BSD crowd, the dependencies in pkgsrc are:
* devel/git
* lang/go119
* lang/gcc12
* devel/SDL2
* graphics/SDL2_image
* fonts/SDL2_ttf
* audio/SDL2_mixer
* graphics/MesaLib
* x11/libXinerama
* x11/libXrandr
* x11/libXcursor
* x11/libXi
### Others
@ -72,3 +75,4 @@ or you can use `go build` directly.
[4]: https://unix.stackexchange.com/q/107689
[5]: https://brew.sh/
[6]: https://golangci-lint.run/usage/install/
[7]: https://github.com/gen2brain/raylib-go

View File

@ -1,7 +1,9 @@
# Go with SDL on Windows
# Go with raylib on Windows
Windows is a bit of a different beast when it comes to setting up a development environment. Hopefully this documentation helps get you started. This guide tries to detail everything you would need to download and configure to get this to work.
I originally wrote this documentation back when we were using SDL for development rather than raylib. I left the SDL install instructions because they might be helpful for future projects, and the set up on Windows is particularly weird. For the new raylib version, you still need to set up GCC, but you do not need to install SDL.
As a side note, there is a cool tool called `Scoop` that you could use to install a lot of these tools. However, I ran into an issue using the Scoop versions of SDL2 because the directory names were different from what the header files were expecting. Below I just installed everything manually instead.
## Install Git

15
go.mod
View File

@ -5,18 +5,5 @@ go 1.18
require (
git.wisellama.rocks/Wisellama/gopackagebase v0.0.4
git.wisellama.rocks/Wisellama/gosimpleconf v0.1.0
github.com/faiface/beep v1.1.0
github.com/veandco/go-sdl2 v0.4.25
)
require (
github.com/hajimehoshi/oto v0.7.1 // indirect
github.com/icza/bitio v1.0.0 // indirect
github.com/mewkiz/flac v1.0.7 // indirect
github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2 // indirect
github.com/pkg/errors v0.9.1 // indirect
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 // indirect
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 // indirect
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 // indirect
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 // indirect
github.com/gen2brain/raylib-go/raylib v0.0.0-20221117130019-ce3c8e83dd6d
)

45
go.sum
View File

@ -2,46 +2,5 @@ git.wisellama.rocks/Wisellama/gopackagebase v0.0.4 h1:EUj/GqcSLJVm4aedSFaleudXlJ
git.wisellama.rocks/Wisellama/gopackagebase v0.0.4/go.mod h1:0xUyJkT61TTIpekmeApB8U0mVwNqX/6Iz85RKUZQYyU=
git.wisellama.rocks/Wisellama/gosimpleconf v0.1.0 h1:Z2FAzARct8ShV4NSueC/y+PyuSQVcyo4WnW7GoZ9L10=
git.wisellama.rocks/Wisellama/gosimpleconf v0.1.0/go.mod h1:Gg1vUTBRZD7qcXvdF8L50PsnL9coLt/XbWa5BwSDN/M=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
github.com/faiface/beep v1.1.0 h1:A2gWP6xf5Rh7RG/p9/VAW2jRSDEGQm5sbOb38sf5d4c=
github.com/faiface/beep v1.1.0/go.mod h1:6I8p6kK2q4opL/eWb+kAkk38ehnTunWeToJB+s51sT4=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE=
github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
github.com/hajimehoshi/oto v0.7.1 h1:I7maFPz5MBCwiutOrz++DLdbr4rTzBsbBuV2VpgU9kk=
github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos=
github.com/icza/bitio v1.0.0 h1:squ/m1SHyFeCA6+6Gyol1AxV9nmPPlJFT8c2vKdj3U8=
github.com/icza/bitio v1.0.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6 h1:8UsGZ2rr2ksmEru6lToqnXgA8Mz1DP11X4zSJ159C3k=
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA=
github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk=
github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mewkiz/flac v1.0.7 h1:uIXEjnuXqdRaZttmSFM5v5Ukp4U6orrZsnYGGR3yow8=
github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU=
github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2 h1:EyTNMdePWaoWsRSGQnXiSoQu0r6RS1eA557AwJhlzHU=
github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/veandco/go-sdl2 v0.4.25 h1:J5ac3KKOccp/0xGJA1PaNYKPUcZm19IxhDGs8lJofPI=
github.com/veandco/go-sdl2 v0.4.25/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 h1:idBdZTd9UioThJp8KpM/rTSinK/ChZFBE43/WtIy8zg=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 h1:vyLBGJPIl9ZYbcQFM2USFmJBK6KI+t+z6jL0lbwjrnc=
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
github.com/gen2brain/raylib-go/raylib v0.0.0-20221117130019-ce3c8e83dd6d h1:X+URitXeDGTT5Ng5LhEG0DPdHq8DDL16dWnJMxgGj+8=
github.com/gen2brain/raylib-go/raylib v0.0.0-20221117130019-ce3c8e83dd6d/go.mod h1:+NbsqGlEQqGqrsgJFF5Yj2dkvn0ML2SQb8RqM2hJsPU=

View File

@ -0,0 +1,49 @@
package channels
import "runtime"
// Inspired by the go-sdl setup for allowing goroutines to coexist with a library that is single-threaded.
var (
// RayLib thread channel
RL = threadChannel{}
)
func init() {
runtime.LockOSThread()
}
// threadChannels allow you to guarantee that the given functions
// are all called sequentially to support a single-threaded library
// while still being able to use goroutines. Any function passed to
// Do() will be added to the call queue and run in order.
type threadChannel struct {
callQueue chan func()
mainFunc func(f func())
}
func (t *threadChannel) Main(main func()) {
t.callQueue = make(chan func())
t.mainFunc = func(f func()) {
done := make(chan bool, 1)
t.callQueue <- func() {
f()
done <- true
}
<-done
}
go func() {
main()
close(t.callQueue)
}()
for f := range t.callQueue {
f()
}
}
func (t *threadChannel) Do(f func()) {
t.mainFunc(f)
}

View File

@ -1,15 +0,0 @@
package game
import "github.com/veandco/go-sdl2/sdl"
// camera represents a view of the world. It's a projection through the window looking at the world.
// Since this is only a 2D game with SDL, the projection is relatively simple: window + camera = world.
// https://gamedev.stackexchange.com/a/123844
type camera struct {
pos sdl.Point
}
func NewCamera() *camera {
c := camera{}
return &c
}

View File

@ -1,31 +1,45 @@
package animation
import (
"github.com/veandco/go-sdl2/sdl"
rl "github.com/gen2brain/raylib-go/raylib"
)
type SpriteAnimation interface {
Draw(frame int, worldPosition sdl.Point, angle float64, center sdl.Point, flip sdl.RendererFlip) error
Draw(
frame int,
windowPosition rl.Vector2,
angle float32,
center rl.Vector2,
flip rl.Vector2,
color rl.Color,
) error
}
var (
FLIP_NONE = rl.Vector2{X: 0, Y: 0}
FLIP_HORIZONTAL = rl.Vector2{X: 1, Y: 0}
FLIP_VERTICAL = rl.Vector2{X: 0, Y: 1}
FLIP_BOTH = rl.Vector2{X: 1, Y: 1}
)
// An entityAnimation will take a SpriteAnimation and may manipulate it somehow (e.g. flipped for walking the other direction)
// This way you may cut down the actual number of pictures needed to define all the different animations.
type entityAnimation struct {
spriteAnimation SpriteAnimation
speed int
length int
angle float64
center sdl.Point
flip sdl.RendererFlip
angle float32
center rl.Vector2
flip rl.Vector2
}
func NewEntityAnimation(
spriteAnimation SpriteAnimation,
speed int,
length int,
angle float64,
center sdl.Point,
flip sdl.RendererFlip,
angle float32,
center rl.Vector2,
flip rl.Vector2,
) entityAnimation {
return entityAnimation{
@ -38,8 +52,8 @@ func NewEntityAnimation(
}
}
func (e entityAnimation) Draw(frame int, windowPosition sdl.Point) error {
return e.spriteAnimation.Draw(frame, windowPosition, e.angle, e.center, e.flip)
func (e entityAnimation) Draw(frame int, windowPosition rl.Vector2, color rl.Color) error {
return e.spriteAnimation.Draw(frame, windowPosition, e.angle, e.center, e.flip, color)
}
func (e entityAnimation) GetSpeed() int {
@ -50,9 +64,9 @@ func DefineAnimations() {
DefinePenguinAnimations()
}
func getCenter(dimensions sdl.Point) sdl.Point {
x := dimensions.X / 2
y := dimensions.Y / 2
func getCenter(dimensions rl.Vector2) rl.Vector2 {
x := dimensions.X / 2.0
y := dimensions.Y / 2.0
return sdl.Point{X: x, Y: y}
return rl.Vector2{X: x, Y: y}
}

View File

@ -2,7 +2,7 @@ package animation
import (
"git.wisellama.rocks/Project-Ely/project-ely/internal/game/sprite"
"github.com/veandco/go-sdl2/sdl"
rl "github.com/gen2brain/raylib-go/raylib"
)
var PenguinAnimations map[int]entityAnimation
@ -27,54 +27,54 @@ func DefinePenguinAnimations() {
filename := sprite.DELILAHWALKING
var (
dimensions sdl.Point
offset sdl.Point
center sdl.Point
dimensions rl.Vector2
offset rl.Vector2
center rl.Vector2
length int
speed int
border int
)
dimensions = sdl.Point{X: 13, Y: 17}
dimensions = rl.Vector2{X: 13, Y: 17}
PenguinAnimations = make(map[int]entityAnimation)
// Walking Right is in the spritesheet.
speed = 5
offset = sdl.Point{X: 0, Y: 1}
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, sdl.FLIP_NONE)
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, sdl.FLIP_HORIZONTAL)
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, sdl.FLIP_NONE)
PenguinAnimations[PENGUIN_STATIONARY_LEFT] = NewEntityAnimation(stationaryRight, speed, length, 0, center, sdl.FLIP_HORIZONTAL)
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 = sdl.Point{X: 0, Y: 3}
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, sdl.FLIP_NONE)
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, sdl.FLIP_NONE)
PenguinAnimations[PENGUIN_STATIONARY_UP] = NewEntityAnimation(stationaryUp, speed, length, 0, center, FLIP_NONE)
// Walk Down
length = 4
offset = sdl.Point{X: 0, Y: 0}
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, sdl.FLIP_NONE)
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, sdl.FLIP_NONE)
PenguinAnimations[PENGUIN_STATIONARY_DOWN] = NewEntityAnimation(stationaryDown, speed, length, 0, center, FLIP_NONE)
}

View File

@ -11,10 +11,10 @@ const (
type Command struct {
key int
value float64
value float32
}
func NewCommand(key int, value float64) Command {
func NewCommand(key int, value float32) Command {
return Command{
key: key,
value: value,

View File

@ -12,9 +12,9 @@ import (
type Entity interface {
Draw() error
Update() error
MoveX(x float64)
MoveY(y float64)
SetSpeed(s float64)
MoveX(x float32)
MoveY(y float32)
SetSpeed(s float32)
}
type CommandHandler struct {
@ -22,18 +22,18 @@ type CommandHandler struct {
timeout time.Duration
entity Entity
commandChan chan Command
drawRequestChan chan bool
drawResponseChan chan bool
updateRequestChan chan bool
updateResponseChan chan bool
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 bool)
drawResponseChan := make(chan bool)
updateRequestChan := make(chan bool)
updateResponseChan := make(chan bool)
drawRequestChan := make(chan struct{})
drawResponseChan := make(chan struct{})
updateRequestChan := make(chan struct{})
updateResponseChan := make(chan struct{})
defaultTimeout := time.Second
return &CommandHandler{
@ -63,19 +63,19 @@ func (c *CommandHandler) CommandRequest() chan Command {
return c.commandChan
}
func (c *CommandHandler) DrawRequest() chan bool {
func (c *CommandHandler) DrawRequest() chan struct{} {
return c.drawRequestChan
}
func (c *CommandHandler) DrawResponse() chan bool {
func (c *CommandHandler) DrawResponse() chan struct{} {
return c.drawResponseChan
}
func (c *CommandHandler) UpdateRequest() chan bool {
func (c *CommandHandler) UpdateRequest() chan struct{} {
return c.updateRequestChan
}
func (c *CommandHandler) UpdateResponse() chan bool {
func (c *CommandHandler) UpdateResponse() chan struct{} {
return c.updateResponseChan
}
@ -92,14 +92,14 @@ func (c *CommandHandler) Run() {
go func() {
defer wg.Done()
c.DrawWithTimeout()
c.drawResponseChan <- true
c.drawResponseChan <- struct{}{}
}()
case <-c.updateRequestChan:
wg.Add(1)
go func() {
defer wg.Done()
c.UpdateWithTimeout()
c.updateResponseChan <- true
c.updateResponseChan <- struct{}{}
}()
case cmd := <-c.commandChan:
wg.Add(1)

View File

@ -1,12 +1,11 @@
package types
import (
"git.wisellama.rocks/Project-Ely/project-ely/internal/vector"
"github.com/veandco/go-sdl2/sdl"
rl "github.com/gen2brain/raylib-go/raylib"
)
type EntityAnimation interface {
Draw(step int, windowPosition sdl.Point) error
Draw(step int, windowPosition rl.Vector2, color rl.Color) error
GetSpeed() int
}
@ -21,12 +20,12 @@ const (
// The following are axis direction vectors based on world coordinates.
// UP/DOWN is intentionally UP = positive (which is different from window coordinates)
var (
VEC_LEFT = vector.Vec2F{X: -1, Y: 0}
VEC_RIGHT = vector.Vec2F{X: 1, Y: 0}
VEC_UP = vector.Vec2F{X: 0, Y: 1}
VEC_DOWN = vector.Vec2F{X: 0, Y: -1}
VEC_LEFT = rl.Vector2{X: -1, Y: 0}
VEC_RIGHT = rl.Vector2{X: 1, Y: 0}
VEC_UP = rl.Vector2{X: 0, Y: 1}
VEC_DOWN = rl.Vector2{X: 0, Y: -1}
VEC_DIRECTIONS = []vector.Vec2F{
VEC_DIRECTIONS = []rl.Vector2{
// Prefer left/right animations by checking them first in the list
VEC_LEFT,
VEC_RIGHT,
@ -35,13 +34,13 @@ var (
}
)
func determineClosestDirection(velocity vector.Vec2F) int {
func determineClosestDirection(velocity rl.Vector2) int {
closest := DIR_RIGHT
max := 0.0
buffer := 0.5 // This buffer lets us stick to the left/right animations for diagonal movement
max := float32(0)
buffer := float32(0.5) // This buffer lets us stick to the left/right animations for diagonal movement
for i, dir := range VEC_DIRECTIONS {
dot := velocity.Dot(dir)
dot := rl.Vector2DotProduct(velocity, dir)
if dot > max+buffer {
max = dot
closest = i

View File

@ -6,8 +6,7 @@ import (
"sync"
"git.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/animation"
"git.wisellama.rocks/Project-Ely/project-ely/internal/vector"
"github.com/veandco/go-sdl2/sdl"
rl "github.com/gen2brain/raylib-go/raylib"
)
type penguin struct {
@ -20,15 +19,15 @@ type penguin struct {
updateAnimation bool // if false, don't change the animation
// Physical parameters
worldPosition vector.Vec2F // where is the center of this object
velocity vector.Vec2F // movement direction to be applied each tick
speed float64 // movement magnitude to multiply with the velocity
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(renderer *sdl.Renderer) penguin {
position := vector.Vec2F{}
velocity := vector.Vec2F{}
speed := 1.0
func NewPenguin() penguin {
position := rl.Vector2{}
velocity := rl.Vector2{}
speed := float32(1)
return penguin{
currentAnimation: animation.PenguinAnimations[animation.PENGUIN_DEFAULT],
@ -46,12 +45,12 @@ func (p *penguin) Draw() error {
// TODO
//windowPosition := worldPosToWindowPos()
windowPosition := sdl.Point{
X: int32(math.Round(p.worldPosition.X)),
Y: int32(math.Round(-1 * p.worldPosition.Y)),
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)
err := p.currentAnimation.Draw(step, windowPosition, rl.White)
if err != nil {
return err
}
@ -61,7 +60,7 @@ func (p *penguin) Draw() error {
return nil
}
func (p *penguin) SetPosition(vec vector.Vec2F) {
func (p *penguin) SetPosition(vec rl.Vector2) {
p.worldPosition = vec
}
@ -84,35 +83,38 @@ func (p *penguin) SetAnimation(id int) {
p.currentAnimation = a
}
func (p *penguin) MoveX(x float64) {
func (p *penguin) MoveX(x float32) {
p.mx.Lock()
defer p.mx.Unlock()
p.velocity.X = x
p.velocity = p.velocity.Normalized()
if p.velocity.X != 0 || p.velocity.Y != 0 {
p.velocity = rl.Vector2Normalize(p.velocity)
}
p.updateAnimation = true
}
func (p *penguin) MoveY(y float64) {
func (p *penguin) MoveY(y float32) {
p.mx.Lock()
defer p.mx.Unlock()
// (0,0) is the top left, so negative y moves up
p.velocity.Y = y
p.velocity.Normalized()
if p.velocity.X != 0 || p.velocity.Y != 0 {
p.velocity = rl.Vector2Normalize(p.velocity)
}
p.updateAnimation = true
}
func (p *penguin) SetSpeed(s float64) {
func (p *penguin) SetSpeed(s float32) {
p.speed = s
}
func (p *penguin) GetSpeed() float64 {
func (p *penguin) GetSpeed() float32 {
return p.speed
}
func (p *penguin) SetMoveAnimation() {
if p.velocity.Zero() {
if p.velocity.X == 0 && p.velocity.Y == 0 {
// Stay facing whatever direction we were facing
switch p.direction {
case DIR_LEFT:

View File

@ -3,117 +3,74 @@ package game
import (
"context"
"fmt"
"log"
"math/rand"
"os"
"strconv"
"sync"
"time"
"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/Project-Ely/project-ely/internal/game/window"
"git.wisellama.rocks/Project-Ely/project-ely/internal/vector"
"git.wisellama.rocks/Wisellama/gosimpleconf"
"github.com/faiface/beep"
"github.com/faiface/beep/effects"
"github.com/faiface/beep/flac"
"github.com/faiface/beep/speaker"
"github.com/veandco/go-sdl2/sdl"
rl "github.com/gen2brain/raylib-go/raylib"
)
type EntityCmdHandler interface {
Run()
CloseRequests()
CommandRequest() chan command.Command
DrawRequest() chan bool
DrawResponse() chan bool
UpdateRequest() chan bool
UpdateResponse() chan bool
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 {
var err error
ctx, cancel := context.WithCancel(ctx)
defer cancel()
go musicTest()
framerate64 := gosimpleconf.Int64(configMap["game.framerate"])
framerate := int32(framerate64)
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 = window.SdlInit()
if err != nil {
return err
}
defer window.SdlQuit()
gameWindow, err := window.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)
// Initialize the RayLib window
channels.RL.Do(func() {
rl.InitWindow(800, 600, configMap["game.title"])
rl.SetTargetFPS(framerate)
})
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)
}
channels.RL.Do(func() {
rl.CloseWindow()
})
}()
err = sprite.InitSpriteCache(renderer)
if err != nil {
err = fmt.Errorf("failed in InitSpriteCache: %w", err)
return err
}
// Initialize our sprites and animations
sprite.InitSpriteCache()
animation.DefineAnimations()
inputHandler := window.NewInputHandler(ctx)
// Done with main setup, now moving on to creating specific entities
entityList := make([]EntityCmdHandler, 0)
wg := sync.WaitGroup{}
// Setup Player 1
// Let them control a penguin to start with
player1 := player.NewPlayer(ctx, inputHandler)
penguinEntity := types.NewPenguin(renderer)
penguinCmdHandler := command.NewCommandHandler(ctx, &penguinEntity)
penguinEntity.SetSpeed(2.0)
entityList = append(entityList, penguinCmdHandler)
player1.SetEntityChan(penguinCmdHandler.CommandRequest())
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()
penguinCmdHandler.Run()
playerPenguinCmd.Run()
}()
for i := 0; i < 10; i++ {
entity := types.NewPenguin(renderer)
entity := types.NewPenguin()
entityCmd := command.NewCommandHandler(ctx, &entity)
randomPos := vector.Vec2F{X: rand.Float64() * 500, Y: rand.Float64() * -500}
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)
@ -125,37 +82,12 @@ func Run(ctx context.Context, configMap gosimpleconf.ConfigMap) error {
}()
}
// Start the inputHandler
wg.Add(1)
go func() {
defer wg.Done()
inputHandler.Run()
}()
// And now starting the main loop
running := true
for 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 to the inputHandler
inputHandler.KeyboardChan() <- *e
}
}
event = sdl.PollEvent()
channels.RL.Do(func() {
if rl.WindowShouldClose() {
cancel()
}
})
@ -170,87 +102,41 @@ func Run(ctx context.Context, configMap gosimpleconf.ConfigMap) error {
break
}
// Players
player1.Update()
// Update players
playerPenguin.Update()
// 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)
}
// Start drawing
channels.RL.Do(func() {
rl.BeginDrawing()
})
// Tell everything to Update and Draw
for _, e := range entityList {
e.UpdateRequest() <- true
e.DrawRequest() <- true
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()
}
// Draw
sdl.Do(func() {
renderer.Present()
sdl.Delay(framerateDelay)
// 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()
})
}
inputHandler.CloseChannels()
for _, e := range entityList {
e.CloseRequests()
}
sprite.CleanupSpriteCache()
wg.Wait()
return nil
}
func musicTest() {
f, err := os.Open("assets/audio/test.flac")
if err != nil {
log.Printf("%v", err)
}
s, format, err := flac.Decode(f)
if err != nil {
log.Printf("%v", err)
}
speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
done := make(chan struct{})
callbackFunc := func() {
close(done)
}
notify := beep.Callback(callbackFunc)
v := &effects.Volume{
Streamer: s,
Base: 2,
Volume: -5,
Silent: false,
}
v = beep.Loop(-1, s)
speaker.Play(beep.Seq(v, notify))
select {
case <-done:
break
}
}

View File

@ -4,33 +4,28 @@ import (
"context"
"time"
"git.wisellama.rocks/Project-Ely/project-ely/internal/channels"
"git.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/command"
"github.com/veandco/go-sdl2/sdl"
rl "github.com/gen2brain/raylib-go/raylib"
)
type InputHandler interface {
Keystate(keycode sdl.Keycode) bool
}
// 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),
// as well as the entity the player is currently controlling.
type player struct {
ctx context.Context
timeout time.Duration
inputHandler InputHandler
ctx context.Context
timeout time.Duration
entityChan chan command.Command
}
func NewPlayer(ctx context.Context, inputHandler InputHandler) *player {
func NewPlayer(ctx context.Context) *player {
defaultTimeout := time.Second
p := player{
ctx: ctx,
timeout: defaultTimeout,
inputHandler: inputHandler,
ctx: ctx,
timeout: defaultTimeout,
}
return &p
}
@ -39,30 +34,44 @@ func (p *player) SetEntityChan(e chan command.Command) {
p.entityChan = e
}
func (p *player) Update() error {
func (p *player) Update() {
var (
up bool
down bool
left bool
right bool
speed bool
)
channels.RL.Do(func() {
speed = 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 p.inputHandler.Keystate(sdl.K_LSHIFT) {
if speed {
p.entityChan <- command.NewCommand(command.SET_SPEED, 4)
} else {
p.entityChan <- command.NewCommand(command.SET_SPEED, 2)
}
// Move X
if p.inputHandler.Keystate(sdl.K_d) {
// Move horizontal
if right {
p.entityChan <- command.NewCommand(command.MOVE_X, 1.0)
} else if p.inputHandler.Keystate(sdl.K_a) {
} else if left {
p.entityChan <- command.NewCommand(command.MOVE_X, -1.0)
} else if !p.inputHandler.Keystate(sdl.K_d) && !p.inputHandler.Keystate(sdl.K_a) {
} else if !right && !left {
p.entityChan <- command.NewCommand(command.MOVE_X, 0.0)
}
// Move Y
if p.inputHandler.Keystate(sdl.K_w) {
// Move vertical
if up {
p.entityChan <- command.NewCommand(command.MOVE_Y, 1.0)
} else if p.inputHandler.Keystate(sdl.K_s) {
} else if down {
p.entityChan <- command.NewCommand(command.MOVE_Y, -1.0)
} else if !p.inputHandler.Keystate(sdl.K_w) && !p.inputHandler.Keystate(sdl.K_s) {
} else if !up && !down {
p.entityChan <- command.NewCommand(command.MOVE_Y, 0.0)
}
return nil
}

View File

@ -3,23 +3,23 @@ package sprite
import (
"fmt"
"github.com/veandco/go-sdl2/sdl"
rl "github.com/gen2brain/raylib-go/raylib"
)
// spriteAnimation specifies which subsections of a spritesheet define this animation.
// For example, walking to the right could be defined by 4 subsections of a sprite sheet.
type spriteAnimation struct {
spritesheet *spritesheet
dimensions sdl.Point
offset sdl.Point
dimensions rl.Vector2
offset rl.Vector2
length int
border int
}
func NewAnimation(
filename string,
dimensions sdl.Point,
offset sdl.Point,
dimensions rl.Vector2,
offset rl.Vector2,
length int,
border int,
) spriteAnimation {
@ -37,29 +37,37 @@ func NewAnimation(
func (a spriteAnimation) Draw(
frame int,
windowPosition sdl.Point,
angle float64,
center sdl.Point,
flip sdl.RendererFlip,
windowPosition rl.Vector2,
angle float32,
center rl.Vector2,
flip rl.Vector2,
color rl.Color,
) error {
width := a.dimensions.X
height := a.dimensions.Y
border := int32(a.border)
border := float32(a.border)
base := sdl.Point{
base := rl.Vector2{
X: (width+border)*a.offset.X + border,
Y: (height+border)*a.offset.Y + border,
}
// Assuming all frames are horizontal going left to right.
// Potentially with a border offset as well.
f := int32(frame % a.length)
section := sdl.Rect{
X: base.X + f*(width+border),
Y: base.Y,
W: width,
H: height,
f := float32(frame % a.length)
section := rl.Rectangle{
X: base.X + f*(width+border),
Y: base.Y,
Width: width,
Height: height,
}
if flip.X > 0 {
section.Width *= -1
}
if flip.Y > 0 {
section.Height *= -1
}
err := a.checkBounds(section)
@ -67,14 +75,14 @@ func (a spriteAnimation) Draw(
return err
}
placement := sdl.Rect{
X: windowPosition.X,
Y: windowPosition.Y,
W: width * 2, // TODO just testing with x2, eventually scale based on screen size or something
H: height * 2,
placement := rl.Rectangle{
X: windowPosition.X,
Y: windowPosition.Y,
Width: width * 2,
Height: height * 2,
}
err = a.spritesheet.Draw(section, placement, angle, center, flip)
err = a.spritesheet.Draw(section, placement, angle, center, color)
if err != nil {
return err
}
@ -82,9 +90,9 @@ func (a spriteAnimation) Draw(
return nil
}
func (a spriteAnimation) checkBounds(section sdl.Rect) error {
width := a.spritesheet.surface.W
height := a.spritesheet.surface.H
func (a spriteAnimation) checkBounds(section rl.Rectangle) error {
width := float32(a.spritesheet.texture.Width)
height := float32(a.spritesheet.texture.Height)
outOfBounds := false
if section.X < 0 {
@ -93,15 +101,15 @@ func (a spriteAnimation) checkBounds(section sdl.Rect) error {
if section.Y < 0 {
outOfBounds = true
}
if section.X+section.W > width {
if section.X+section.Width > width {
outOfBounds = true
}
if section.Y+section.H > height {
if section.Y+section.Height > height {
outOfBounds = true
}
if outOfBounds {
return fmt.Errorf("draw section was out of bounds - section: %v, image: %v", section, a.spritesheet.surface.Bounds())
return fmt.Errorf("draw section was out of bounds - section: %v, image: %v", section, a.spritesheet.Bounds())
}
return nil

View File

@ -1,81 +1,48 @@
package sprite
import (
"fmt"
"log"
"github.com/veandco/go-sdl2/img"
"github.com/veandco/go-sdl2/sdl"
"git.wisellama.rocks/Project-Ely/project-ely/internal/channels"
rl "github.com/gen2brain/raylib-go/raylib"
)
type spritesheet struct {
filename string
renderer *sdl.Renderer
surface *sdl.Surface // the original png file
texture *sdl.Texture // the SDL texture created from the image
texture rl.Texture2D
}
func NewSprite(renderer *sdl.Renderer, filename string) (*spritesheet, error) {
var err error
// Load the surface file
var surface *sdl.Surface
sdl.Do(func() {
surface, err = img.Load(filename)
func NewSprite(filename string) *spritesheet {
var texture rl.Texture2D
channels.RL.Do(func() {
texture = rl.LoadTexture(filename)
})
if err != nil {
err = fmt.Errorf("failed to load image: %w", err)
return nil, err
}
// Create the sprite sheet texture from the image
var texture *sdl.Texture
sdl.Do(func() {
texture, err = renderer.CreateTextureFromSurface(surface)
})
if err != nil {
err = fmt.Errorf("failed to create texture: %w", err)
return nil, err
}
s := spritesheet{
filename: filename,
renderer: renderer,
surface: surface,
texture: texture,
}
return &s, nil
return &s
}
func (s *spritesheet) Cleanup() {
sdl.Do(func() {
channels.RL.Do(func() {
// Clean up spritesheet
if s.texture != nil {
err := s.texture.Destroy()
if err != nil {
log.Printf("error destroying spritesheet %v: %v\n", s.filename, err)
}
}
// Clean up image
if s.surface != nil {
s.surface.Free()
}
rl.UnloadTexture(s.texture)
})
}
func (s *spritesheet) Draw(
section sdl.Rect,
placement sdl.Rect,
angle float64,
center sdl.Point,
flip sdl.RendererFlip,
section rl.Rectangle,
placement rl.Rectangle,
angle float32,
center rl.Vector2,
color rl.Color,
) error {
var err error
sdl.Do(func() {
err = s.renderer.CopyEx(s.texture, &section, &placement, angle, &center, flip)
channels.RL.Do(func() {
origin := rl.Vector2{}
rl.DrawTexturePro(s.texture, section, placement, origin, angle, color)
})
if err != nil {
return err
@ -84,9 +51,9 @@ func (s *spritesheet) Draw(
return nil
}
func (s *spritesheet) Bounds() sdl.Point {
return sdl.Point{
X: s.surface.W,
Y: s.surface.H,
func (s *spritesheet) Bounds() rl.Vector2 {
return rl.Vector2{
X: float32(s.texture.Width),
Y: float32(s.texture.Height),
}
}

View File

@ -2,43 +2,25 @@ package sprite
import (
"log"
"github.com/veandco/go-sdl2/sdl"
)
var (
spriteCache map[string]*spritesheet
defaultSprite *spritesheet
spriteCache map[string]*spritesheet
)
func InitSpriteCache(renderer *sdl.Renderer) error {
func InitSpriteCache() {
spriteCache = make(map[string]*spritesheet)
for _, filename := range SPRITE_FILE_LIST {
s, err := NewSprite(renderer, filename)
if err != nil {
log.Printf("error creating sprite %v, using DefaultSprite: %v", filename, err)
defaultSprite, err = createDefaultSprite(renderer)
if err != nil {
log.Printf("failed to create DefaultSprite: %v", err)
return err
}
spriteCache[filename] = defaultSprite
} else {
spriteCache[filename] = s
}
s := NewSprite(filename)
spriteCache[filename] = s
}
return nil
}
func GetSpritesheet(filename string) *spritesheet {
s, exists := spriteCache[filename]
if !exists {
log.Printf("no sprite found for %v, using DefaultSprite", filename)
return defaultSprite
log.Printf("no sprite found for %v", filename)
}
return s
@ -49,27 +31,3 @@ func CleanupSpriteCache() {
defer v.Cleanup()
}
}
// The DefaultSprite is just a 100x100 black square
func createDefaultSprite(renderer *sdl.Renderer) (*spritesheet, error) {
var err error
surface, err := sdl.CreateRGBSurface(0, 100, 100, 32, 0, 0, 0, 0)
if err != nil {
return nil, err
}
texture, err := renderer.CreateTextureFromSurface(surface)
if err != nil {
return nil, err
}
s := spritesheet{
filename: "DEFAULT_SPRITE",
renderer: renderer,
texture: texture,
surface: surface,
}
return &s, nil
}

View File

@ -1,75 +0,0 @@
package window
import (
"context"
"sync"
"github.com/veandco/go-sdl2/sdl"
)
type inputHandler struct {
ctx context.Context
keyboardChan chan sdl.KeyboardEvent
// The actual internal state
mxKeyboard sync.RWMutex
keystates map[sdl.Keycode]bool
// TODO joystick/gamepad
// mxJoystick sync.RWMutex
}
func NewInputHandler(ctx context.Context) *inputHandler {
keyboardChan := make(chan sdl.KeyboardEvent)
keystates := make(map[sdl.Keycode]bool)
i := inputHandler{
ctx: ctx,
keystates: keystates,
keyboardChan: keyboardChan,
}
return &i
}
func (i *inputHandler) CloseChannels() {
close(i.keyboardChan)
}
func (i *inputHandler) KeyboardChan() chan sdl.KeyboardEvent {
return i.keyboardChan
}
func (i *inputHandler) Run() {
running := true
for running {
select {
case event := <-i.keyboardChan:
i.UpdateKeyboard(event)
case <-i.ctx.Done():
running = false
}
}
// Finish up anything in the queues
for e := range i.keyboardChan {
i.UpdateKeyboard(e)
}
}
func (i *inputHandler) UpdateKeyboard(e sdl.KeyboardEvent) {
i.mxKeyboard.Lock()
defer i.mxKeyboard.Unlock()
// Key states (just set a boolean whether the key is actively being pressed)
if e.Type == sdl.KEYDOWN {
i.keystates[e.Keysym.Sym] = true
} else if e.Type == sdl.KEYUP {
i.keystates[e.Keysym.Sym] = false
}
}
func (i *inputHandler) Keystate(keycode sdl.Keycode) bool {
i.mxKeyboard.RLock()
defer i.mxKeyboard.RUnlock()
return i.keystates[keycode]
}

View File

@ -1,60 +0,0 @@
package window
import (
"fmt"
"github.com/veandco/go-sdl2/sdl"
)
/*
NOTE: We're using the SDL with goroutines example.
Every call to an SDL function must be passed through the sdl.Do() function so that it joins the queue of thread-sensitive calls.
You'll see this a lot below, but basically every SDL function must do something like this:
sdl.Do(func() {
err = sdl.SomeFunction()
})
This is for thread safety because SDL is not intended to be thread-safe.
However, the rest of the code can do whatever it wants with threads so long as the SDL calls all use this method.
*/
const (
SDL_INIT_FLAGS uint32 = sdl.INIT_TIMER | sdl.INIT_AUDIO | sdl.INIT_VIDEO | sdl.INIT_EVENTS | sdl.INIT_JOYSTICK | sdl.INIT_HAPTIC | sdl.INIT_GAMECONTROLLER // ignore sensor subsystem
SDL_WINDOW_FLAGS uint32 = sdl.WINDOW_SHOWN | sdl.WINDOW_RESIZABLE
SDL_FULLSCREEN_WINDOW_FLAGS uint32 = SDL_WINDOW_FLAGS | sdl.WINDOW_FULLSCREEN_DESKTOP
SDL_WINDOW_WIDTH int32 = 800
SDL_WINDOW_HEIGHT int32 = 600
)
func SdlInit() error {
var err error
// Initialize SDL
sdl.Do(func() {
err = sdl.Init(SDL_INIT_FLAGS)
})
if err != nil {
err = fmt.Errorf("failed initializing SDL: %w", err)
return err
}
// Set some base SDL settings
sdl.Do(func() {
// Disable the Linux compositor flicker.
// https://github.com/mosra/magnum/issues/184#issuecomment-425952900
sdl.SetHint(sdl.HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0")
sdl.DisableScreenSaver()
// Capture the mouse for movement
//sdl.SetRelativeMouseMode(true)
})
return nil
}
func SdlQuit() {
sdl.Do(func() {
sdl.Quit()
})
}

View File

@ -1,51 +0,0 @@
package window
import (
"log"
"github.com/veandco/go-sdl2/sdl"
)
type window struct {
SdlWindow *sdl.Window
KeyStates map[sdl.Keycode]bool
}
func NewWindow(title string) (*window, error) {
var (
err error
sdlWindow *sdl.Window
)
sdl.Do(func() {
sdlWindow, err = sdl.CreateWindow(
title,
sdl.WINDOWPOS_UNDEFINED,
sdl.WINDOWPOS_UNDEFINED,
SDL_WINDOW_WIDTH,
SDL_WINDOW_HEIGHT,
SDL_WINDOW_FLAGS)
})
if err != nil {
return nil, err
}
keyStates := make(map[sdl.Keycode]bool)
gw := &window{
SdlWindow: sdlWindow,
KeyStates: keyStates,
}
return gw, nil
}
func (g *window) Cleanup() {
if g.SdlWindow != nil {
sdl.Do(func() {
err := g.SdlWindow.Destroy()
if err != nil {
log.Printf("error destroying SdlWindow: %v\n", err)
}
})
}
}

View File

@ -1,38 +0,0 @@
package vector
import "math"
// This is a small-scale vector implementation for a Vec2F.
// If you need anything more complicated than normals and dot-products,
// then you should probably just switch to the glmath library instead.
type Vec2F struct {
X float64
Y float64
}
func (v Vec2F) Zero() bool {
return v.X == 0.0 && v.Y == 0.0
}
func (v Vec2F) LengthSquared() float64 {
return v.X*v.X + v.Y*v.Y
}
func (v Vec2F) Normalized() Vec2F {
output := Vec2F{X: v.X, Y: v.Y}
length := math.Hypot(v.X, v.Y)
if length == 0 {
return output
}
output.X = v.X / length
output.Y = v.Y / length
return output
}
func (v Vec2F) Dot(other Vec2F) float64 {
return v.X*other.X + v.Y*other.Y
}

View File

@ -4,10 +4,10 @@ import (
"log"
"os"
"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"
"github.com/veandco/go-sdl2/sdl"
)
const (
@ -16,7 +16,7 @@ const (
var defaultConfig gosimpleconf.ConfigMap = gosimpleconf.ConfigMap{
"game.title": "Project Ely",
"game.framerate": "60.0",
"game.framerate": "60",
"log.utcTime": "false",
"log.writeToFile": "false",
}
@ -34,9 +34,8 @@ func main() {
defer baseConfig.Cancel()
// Run the program
// Start everything with the SDL goroutine context
log.Printf("=== Starting %v ===", baseConfig.ConfigMap["game.title"])
sdl.Main(func() {
channels.RL.Main(func() {
err = game.Run(baseConfig.Ctx, baseConfig.ConfigMap)
})
if err != nil {

View File

@ -19,7 +19,6 @@ build(){
EXE=""
if [ "${OS}" == "windows" ]
then
# Hide the windows console popup for SDL
LDFLAGS_WIN="-H windowsgui"
EXE=".exe"
fi