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 \ RUN apk add --no-cache \
gcc musl-dev make go \ gcc musl-dev make go \
alsa-lib libxrandr \ alsa-lib libxrandr
sdl2-dev sdl2_mixer-dev sdl2_image-dev sdl2_ttf-dev
WORKDIR /home 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. 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. 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 * Git
* Go (Golang) * Go (Golang)
* GCC * GCC
* SDL2 (with Image, TTF, and Mixer) * X11 or Wayland
That's all, you may even have most of them installed already. 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 ### 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: As the most common example, on Debian or Ubuntu, the following `apt` command should install all the dependencies:
```sh ```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]. 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: On Mac, the typical recommendation is to use [Homebrew][5]. The package names are pretty similar:
```sh ```sh
brew install git go gcc sdl2 sdl2_image sdl2_ttf sdl2_mixer brew install git go gcc
``` ```
### Redhat/Fedora/Rocky/CentOS ### 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) (these distros may use `dnf` or `yum` as the package manager executable, `dnf` is newer)
```sh ```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 ### pkgsrc
@ -48,10 +50,11 @@ As a reference for the BSD crowd, the dependencies in pkgsrc are:
* devel/git * devel/git
* lang/go119 * lang/go119
* lang/gcc12 * lang/gcc12
* devel/SDL2 * graphics/MesaLib
* graphics/SDL2_image * x11/libXinerama
* fonts/SDL2_ttf * x11/libXrandr
* audio/SDL2_mixer * x11/libXcursor
* x11/libXi
### Others ### Others
@ -72,3 +75,4 @@ or you can use `go build` directly.
[4]: https://unix.stackexchange.com/q/107689 [4]: https://unix.stackexchange.com/q/107689
[5]: https://brew.sh/ [5]: https://brew.sh/
[6]: https://golangci-lint.run/usage/install/ [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. 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. 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 ## Install Git

15
go.mod
View File

@ -5,18 +5,5 @@ go 1.18
require ( require (
git.wisellama.rocks/Wisellama/gopackagebase v0.0.4 git.wisellama.rocks/Wisellama/gopackagebase v0.0.4
git.wisellama.rocks/Wisellama/gosimpleconf v0.1.0 git.wisellama.rocks/Wisellama/gosimpleconf v0.1.0
github.com/faiface/beep v1.1.0 github.com/gen2brain/raylib-go/raylib v0.0.0-20221117130019-ce3c8e83dd6d
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
) )

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/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 h1:Z2FAzARct8ShV4NSueC/y+PyuSQVcyo4WnW7GoZ9L10=
git.wisellama.rocks/Wisellama/gosimpleconf v0.1.0/go.mod h1:Gg1vUTBRZD7qcXvdF8L50PsnL9coLt/XbWa5BwSDN/M= 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/gen2brain/raylib-go/raylib v0.0.0-20221117130019-ce3c8e83dd6d h1:X+URitXeDGTT5Ng5LhEG0DPdHq8DDL16dWnJMxgGj+8=
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= github.com/gen2brain/raylib-go/raylib v0.0.0-20221117130019-ce3c8e83dd6d/go.mod h1:+NbsqGlEQqGqrsgJFF5Yj2dkvn0ML2SQb8RqM2hJsPU=
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=

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 package animation
import ( import (
"github.com/veandco/go-sdl2/sdl" rl "github.com/gen2brain/raylib-go/raylib"
) )
type SpriteAnimation interface { 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) // 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. // This way you may cut down the actual number of pictures needed to define all the different animations.
type entityAnimation struct { type entityAnimation struct {
spriteAnimation SpriteAnimation spriteAnimation SpriteAnimation
speed int speed int
length int length int
angle float64 angle float32
center sdl.Point center rl.Vector2
flip sdl.RendererFlip flip rl.Vector2
} }
func NewEntityAnimation( func NewEntityAnimation(
spriteAnimation SpriteAnimation, spriteAnimation SpriteAnimation,
speed int, speed int,
length int, length int,
angle float64, angle float32,
center sdl.Point, center rl.Vector2,
flip sdl.RendererFlip, flip rl.Vector2,
) entityAnimation { ) entityAnimation {
return entityAnimation{ return entityAnimation{
@ -38,8 +52,8 @@ func NewEntityAnimation(
} }
} }
func (e entityAnimation) Draw(frame int, windowPosition sdl.Point) error { 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) return e.spriteAnimation.Draw(frame, windowPosition, e.angle, e.center, e.flip, color)
} }
func (e entityAnimation) GetSpeed() int { func (e entityAnimation) GetSpeed() int {
@ -50,9 +64,9 @@ func DefineAnimations() {
DefinePenguinAnimations() DefinePenguinAnimations()
} }
func getCenter(dimensions sdl.Point) sdl.Point { func getCenter(dimensions rl.Vector2) rl.Vector2 {
x := dimensions.X / 2 x := dimensions.X / 2.0
y := dimensions.Y / 2 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 ( import (
"git.wisellama.rocks/Project-Ely/project-ely/internal/game/sprite" "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 var PenguinAnimations map[int]entityAnimation
@ -27,54 +27,54 @@ func DefinePenguinAnimations() {
filename := sprite.DELILAHWALKING filename := sprite.DELILAHWALKING
var ( var (
dimensions sdl.Point dimensions rl.Vector2
offset sdl.Point offset rl.Vector2
center sdl.Point center rl.Vector2
length int length int
speed int speed int
border int border int
) )
dimensions = sdl.Point{X: 13, Y: 17} dimensions = rl.Vector2{X: 13, Y: 17}
PenguinAnimations = make(map[int]entityAnimation) PenguinAnimations = make(map[int]entityAnimation)
// Walking Right is in the spritesheet. // Walking Right is in the spritesheet.
speed = 5 speed = 5
offset = sdl.Point{X: 0, Y: 1} offset = rl.Vector2{X: 0, Y: 1}
length = 5 length = 5
border = 1 // optional border around each sprite border = 1 // optional border around each sprite
center = getCenter(dimensions) // center is for rotation, nil will default to w/2 h/2 center = getCenter(dimensions) // center is for rotation, nil will default to w/2 h/2
walkRight := sprite.NewAnimation(filename, dimensions, offset, length, border) 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. // 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. // Stationary Right/Left is just the first frame.
length = 1 length = 1
stationaryRight := sprite.NewAnimation(filename, dimensions, offset, length, border) stationaryRight := sprite.NewAnimation(filename, dimensions, offset, length, border)
PenguinAnimations[PENGUIN_STATIONARY_RIGHT] = NewEntityAnimation(stationaryRight, speed, length, 0, center, sdl.FLIP_NONE) PenguinAnimations[PENGUIN_STATIONARY_RIGHT] = NewEntityAnimation(stationaryRight, speed, length, 0, center, FLIP_NONE)
PenguinAnimations[PENGUIN_STATIONARY_LEFT] = NewEntityAnimation(stationaryRight, speed, length, 0, center, sdl.FLIP_HORIZONTAL) PenguinAnimations[PENGUIN_STATIONARY_LEFT] = NewEntityAnimation(stationaryRight, speed, length, 0, center, FLIP_HORIZONTAL)
// Walk Up // Walk Up
length = 4 length = 4
offset = sdl.Point{X: 0, Y: 3} offset = rl.Vector2{X: 0, Y: 3}
walkUp := sprite.NewAnimation(filename, dimensions, offset, length, border) 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 // Stationary Up
length = 1 length = 1
stationaryUp := sprite.NewAnimation(filename, dimensions, offset, length, border) 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 // Walk Down
length = 4 length = 4
offset = sdl.Point{X: 0, Y: 0} offset = rl.Vector2{X: 0, Y: 0}
walkDown := sprite.NewAnimation(filename, dimensions, offset, length, border) 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 // Stationary Down
length = 1 length = 1
stationaryDown := sprite.NewAnimation(filename, dimensions, offset, length, border) 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 { type Command struct {
key int key int
value float64 value float32
} }
func NewCommand(key int, value float64) Command { func NewCommand(key int, value float32) Command {
return Command{ return Command{
key: key, key: key,
value: value, value: value,

View File

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

View File

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

View File

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

View File

@ -3,117 +3,74 @@ package game
import ( import (
"context" "context"
"fmt" "fmt"
"log"
"math/rand" "math/rand"
"os"
"strconv"
"sync" "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/animation"
"git.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/command" "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/entity/types"
"git.wisellama.rocks/Project-Ely/project-ely/internal/game/player" "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/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" "git.wisellama.rocks/Wisellama/gosimpleconf"
"github.com/faiface/beep" rl "github.com/gen2brain/raylib-go/raylib"
"github.com/faiface/beep/effects"
"github.com/faiface/beep/flac"
"github.com/faiface/beep/speaker"
"github.com/veandco/go-sdl2/sdl"
) )
type EntityCmdHandler interface { type EntityCmdHandler interface {
Run() Run()
CloseRequests() CloseRequests()
CommandRequest() chan command.Command CommandRequest() chan command.Command
DrawRequest() chan bool DrawRequest() chan struct{}
DrawResponse() chan bool DrawResponse() chan struct{}
UpdateRequest() chan bool UpdateRequest() chan struct{}
UpdateResponse() chan bool UpdateResponse() chan struct{}
} }
// Run is the main function to start the game. // Run is the main function to start the game.
func Run(ctx context.Context, configMap gosimpleconf.ConfigMap) error { func Run(ctx context.Context, configMap gosimpleconf.ConfigMap) error {
var err error
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
go musicTest() framerate64 := gosimpleconf.Int64(configMap["game.framerate"])
framerate := int32(framerate64)
framerate, err := strconv.ParseFloat(configMap["game.framerate"], 64) // Initialize the RayLib window
if err != nil { channels.RL.Do(func() {
err = fmt.Errorf("error parsing framerate: %w", err) rl.InitWindow(800, 600, configMap["game.title"])
return err rl.SetTargetFPS(framerate)
}
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)
}) })
if err != nil {
err = fmt.Errorf("failed creating SDL renderer: %w", err)
return err
}
defer func() { defer func() {
sdl.Do(func() { channels.RL.Do(func() {
err = renderer.Destroy() rl.CloseWindow()
if err != nil {
log.Printf("error destroying renderer: %v\n", err)
}
}) })
}() }()
err = sprite.InitSpriteCache(renderer) // Initialize our sprites and animations
if err != nil { sprite.InitSpriteCache()
err = fmt.Errorf("failed in InitSpriteCache: %w", err)
return err
}
animation.DefineAnimations() animation.DefineAnimations()
inputHandler := window.NewInputHandler(ctx)
// Done with main setup, now moving on to creating specific entities
entityList := make([]EntityCmdHandler, 0) entityList := make([]EntityCmdHandler, 0)
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
// Setup Player 1 playerPenguinEntity := types.NewPenguin()
// Let them control a penguin to start with playerPenguinEntity.SetSpeed(2)
player1 := player.NewPlayer(ctx, inputHandler) playerPenguinEntity.SetPosition(rl.Vector2{X: 100, Y: -100})
penguinEntity := types.NewPenguin(renderer) playerPenguinCmd := command.NewCommandHandler(ctx, &playerPenguinEntity)
penguinCmdHandler := command.NewCommandHandler(ctx, &penguinEntity) playerPenguin := player.NewPlayer(ctx)
penguinEntity.SetSpeed(2.0) playerPenguin.SetEntityChan(playerPenguinCmd.CommandRequest())
entityList = append(entityList, penguinCmdHandler) entityList = append(entityList, playerPenguinCmd)
player1.SetEntityChan(penguinCmdHandler.CommandRequest())
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
penguinCmdHandler.Run() playerPenguinCmd.Run()
}() }()
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
entity := types.NewPenguin(renderer) entity := types.NewPenguin()
entityCmd := command.NewCommandHandler(ctx, &entity) 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.SetPosition(randomPos)
entity.SetAnimation(rand.Intn(animation.PENGUIN_NUM_ANIMS)) entity.SetAnimation(rand.Intn(animation.PENGUIN_NUM_ANIMS))
entityList = append(entityList, entityCmd) 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 // And now starting the main loop
running := true running := true
for running { for running {
// Poll for SDL events channels.RL.Do(func() {
var event sdl.Event if rl.WindowShouldClose() {
sdl.Do(func() { cancel()
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()
} }
}) })
@ -170,87 +102,41 @@ func Run(ctx context.Context, configMap gosimpleconf.ConfigMap) error {
break break
} }
// Players // Update players
player1.Update() playerPenguin.Update()
// Background // Start drawing
sdl.Do(func() { channels.RL.Do(func() {
err = renderer.SetDrawColor(0, 120, 0, 255) rl.BeginDrawing()
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)
}
}) })
// Tell everything to Update and Draw // Tell everything to Update and Draw
for _, e := range entityList { for _, e := range entityList {
e.UpdateRequest() <- true e.UpdateRequest() <- struct{}{}
e.DrawRequest() <- true e.DrawRequest() <- struct{}{}
} }
// Wait for each entity to finish their Draw and Update commands before proceeding // Wait for each entity to finish their Draw and Update commands before proceeding
for _, e := range entityList { for _, e := range entityList {
<-e.UpdateResponse() <-e.UpdateResponse()
<-e.DrawResponse() <-e.DrawResponse()
} }
// Draw // Finish drawing
sdl.Do(func() { channels.RL.Do(func() {
renderer.Present() rl.ClearBackground(rl.Black)
sdl.Delay(framerateDelay) 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 { for _, e := range entityList {
e.CloseRequests() e.CloseRequests()
} }
sprite.CleanupSpriteCache() sprite.CleanupSpriteCache()
wg.Wait() wg.Wait()
return nil 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" "context"
"time" "time"
"git.wisellama.rocks/Project-Ely/project-ely/internal/channels"
"git.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/command" "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. // A player represents a collection of stuff controlled by the user's input.
// It contains the camera used to view the world, // It contains the camera used to view the world,
// the viewport for the part of the screen to draw to (splitscreen support), // the viewport for the part of the screen to draw to (splitscreen support),
// as well as the entity the player is currently controlling. // as well as the entity the player is currently controlling.
type player struct { type player struct {
ctx context.Context ctx context.Context
timeout time.Duration timeout time.Duration
inputHandler InputHandler
entityChan chan command.Command entityChan chan command.Command
} }
func NewPlayer(ctx context.Context, inputHandler InputHandler) *player { func NewPlayer(ctx context.Context) *player {
defaultTimeout := time.Second defaultTimeout := time.Second
p := player{ p := player{
ctx: ctx, ctx: ctx,
timeout: defaultTimeout, timeout: defaultTimeout,
inputHandler: inputHandler,
} }
return &p return &p
} }
@ -39,30 +34,44 @@ func (p *player) SetEntityChan(e chan command.Command) {
p.entityChan = e 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 // Speed
if p.inputHandler.Keystate(sdl.K_LSHIFT) { if speed {
p.entityChan <- command.NewCommand(command.SET_SPEED, 4) p.entityChan <- command.NewCommand(command.SET_SPEED, 4)
} else { } else {
p.entityChan <- command.NewCommand(command.SET_SPEED, 2) p.entityChan <- command.NewCommand(command.SET_SPEED, 2)
} }
// Move X // Move horizontal
if p.inputHandler.Keystate(sdl.K_d) { if right {
p.entityChan <- command.NewCommand(command.MOVE_X, 1.0) 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) 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) p.entityChan <- command.NewCommand(command.MOVE_X, 0.0)
} }
// Move Y // Move vertical
if p.inputHandler.Keystate(sdl.K_w) { if up {
p.entityChan <- command.NewCommand(command.MOVE_Y, 1.0) 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) 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) p.entityChan <- command.NewCommand(command.MOVE_Y, 0.0)
} }
return nil
} }

View File

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

View File

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

View File

@ -2,43 +2,25 @@ package sprite
import ( import (
"log" "log"
"github.com/veandco/go-sdl2/sdl"
) )
var ( var (
spriteCache map[string]*spritesheet spriteCache map[string]*spritesheet
defaultSprite *spritesheet
) )
func InitSpriteCache(renderer *sdl.Renderer) error { func InitSpriteCache() {
spriteCache = make(map[string]*spritesheet) spriteCache = make(map[string]*spritesheet)
for _, filename := range SPRITE_FILE_LIST { for _, filename := range SPRITE_FILE_LIST {
s, err := NewSprite(renderer, filename) s := NewSprite(filename)
if err != nil { spriteCache[filename] = s
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
}
} }
return nil
} }
func GetSpritesheet(filename string) *spritesheet { func GetSpritesheet(filename string) *spritesheet {
s, exists := spriteCache[filename] s, exists := spriteCache[filename]
if !exists { if !exists {
log.Printf("no sprite found for %v, using DefaultSprite", filename) log.Printf("no sprite found for %v", filename)
return defaultSprite
} }
return s return s
@ -49,27 +31,3 @@ func CleanupSpriteCache() {
defer v.Cleanup() 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" "log"
"os" "os"
"git.wisellama.rocks/Project-Ely/project-ely/internal/channels"
"git.wisellama.rocks/Project-Ely/project-ely/internal/game" "git.wisellama.rocks/Project-Ely/project-ely/internal/game"
"git.wisellama.rocks/Wisellama/gopackagebase" "git.wisellama.rocks/Wisellama/gopackagebase"
"git.wisellama.rocks/Wisellama/gosimpleconf" "git.wisellama.rocks/Wisellama/gosimpleconf"
"github.com/veandco/go-sdl2/sdl"
) )
const ( const (
@ -16,7 +16,7 @@ const (
var defaultConfig gosimpleconf.ConfigMap = gosimpleconf.ConfigMap{ var defaultConfig gosimpleconf.ConfigMap = gosimpleconf.ConfigMap{
"game.title": "Project Ely", "game.title": "Project Ely",
"game.framerate": "60.0", "game.framerate": "60",
"log.utcTime": "false", "log.utcTime": "false",
"log.writeToFile": "false", "log.writeToFile": "false",
} }
@ -34,9 +34,8 @@ func main() {
defer baseConfig.Cancel() defer baseConfig.Cancel()
// Run the program // Run the program
// Start everything with the SDL goroutine context
log.Printf("=== Starting %v ===", baseConfig.ConfigMap["game.title"]) log.Printf("=== Starting %v ===", baseConfig.ConfigMap["game.title"])
sdl.Main(func() { channels.RL.Main(func() {
err = game.Run(baseConfig.Ctx, baseConfig.ConfigMap) err = game.Run(baseConfig.Ctx, baseConfig.ConfigMap)
}) })
if err != nil { if err != nil {

View File

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