diff --git a/Dockerfile b/Dockerfile index 57cb3c8..86648d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/docs/builds/linux-static-release.md b/docs/builds/linux-static-release.md deleted file mode 100644 index 6ec99ea..0000000 --- a/docs/builds/linux-static-release.md +++ /dev/null @@ -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 diff --git a/docs/builds/windows-static-release.md b/docs/builds/windows-static-release.md deleted file mode 100644 index c9a04b3..0000000 --- a/docs/builds/windows-static-release.md +++ /dev/null @@ -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 diff --git a/docs/setup/go-sdl-setup.md b/docs/setup/setup-unix.md similarity index 77% rename from docs/setup/go-sdl-setup.md rename to docs/setup/setup-unix.md index cd3cb95..e1357bb 100644 --- a/docs/setup/go-sdl-setup.md +++ b/docs/setup/setup-unix.md @@ -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 diff --git a/docs/setup/go-sdl-setup-windows.md b/docs/setup/setup-windows.md similarity index 94% rename from docs/setup/go-sdl-setup-windows.md rename to docs/setup/setup-windows.md index 9c4c57f..77ef2e0 100644 --- a/docs/setup/go-sdl-setup-windows.md +++ b/docs/setup/setup-windows.md @@ -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 diff --git a/go.mod b/go.mod index 5dfb60b..b21bcc4 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index fc4a9e1..5e41cef 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/channels/threadChannel.go b/internal/channels/threadChannel.go new file mode 100644 index 0000000..6bba90b --- /dev/null +++ b/internal/channels/threadChannel.go @@ -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) +} diff --git a/internal/game/camera.go b/internal/game/camera.go deleted file mode 100644 index d439b6c..0000000 --- a/internal/game/camera.go +++ /dev/null @@ -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 -} diff --git a/internal/game/entity/animation/entity_animation.go b/internal/game/entity/animation/entity_animation.go index 7361f02..b481900 100644 --- a/internal/game/entity/animation/entity_animation.go +++ b/internal/game/entity/animation/entity_animation.go @@ -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} } diff --git a/internal/game/entity/animation/penguin_animations.go b/internal/game/entity/animation/penguin_animations.go index 4c5cb54..bd77060 100644 --- a/internal/game/entity/animation/penguin_animations.go +++ b/internal/game/entity/animation/penguin_animations.go @@ -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) } diff --git a/internal/game/entity/command/commands.go b/internal/game/entity/command/commands.go index e984882..37341cc 100644 --- a/internal/game/entity/command/commands.go +++ b/internal/game/entity/command/commands.go @@ -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, diff --git a/internal/game/entity/command/handler.go b/internal/game/entity/command/handler.go index 1e80ddc..e8e25c9 100644 --- a/internal/game/entity/command/handler.go +++ b/internal/game/entity/command/handler.go @@ -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) diff --git a/internal/game/entity/types/common.go b/internal/game/entity/types/common.go index ba6e855..e931eba 100644 --- a/internal/game/entity/types/common.go +++ b/internal/game/entity/types/common.go @@ -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 diff --git a/internal/game/entity/types/penguin.go b/internal/game/entity/types/penguin.go index 9a21b5d..84a48f8 100644 --- a/internal/game/entity/types/penguin.go +++ b/internal/game/entity/types/penguin.go @@ -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: diff --git a/internal/game/game.go b/internal/game/game.go index 193f9c6..4f24064 100644 --- a/internal/game/game.go +++ b/internal/game/game.go @@ -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 - } - -} diff --git a/internal/game/player/player.go b/internal/game/player/player.go index 87b219e..c9745af 100644 --- a/internal/game/player/player.go +++ b/internal/game/player/player.go @@ -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 } diff --git a/internal/game/sprite/sprite_animation.go b/internal/game/sprite/sprite_animation.go index e38ea98..ce9c3ff 100644 --- a/internal/game/sprite/sprite_animation.go +++ b/internal/game/sprite/sprite_animation.go @@ -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 diff --git a/internal/game/sprite/spritesheet.go b/internal/game/sprite/spritesheet.go index a8b8e75..776dc70 100644 --- a/internal/game/sprite/spritesheet.go +++ b/internal/game/sprite/spritesheet.go @@ -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, §ion, &placement, angle, ¢er, 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), } } diff --git a/internal/game/sprite/spritesheet_cache.go b/internal/game/sprite/spritesheet_cache.go index c40555d..6ceeb95 100644 --- a/internal/game/sprite/spritesheet_cache.go +++ b/internal/game/sprite/spritesheet_cache.go @@ -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 -} diff --git a/internal/game/window/input.go b/internal/game/window/input.go deleted file mode 100644 index 42c8020..0000000 --- a/internal/game/window/input.go +++ /dev/null @@ -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] -} diff --git a/internal/game/window/sdl.go b/internal/game/window/sdl.go deleted file mode 100644 index 1d087d4..0000000 --- a/internal/game/window/sdl.go +++ /dev/null @@ -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() - }) -} diff --git a/internal/game/window/window.go b/internal/game/window/window.go deleted file mode 100644 index 6c113a4..0000000 --- a/internal/game/window/window.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/internal/vector/vector.go b/internal/vector/vector.go deleted file mode 100644 index 5404ce6..0000000 --- a/internal/vector/vector.go +++ /dev/null @@ -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 -} diff --git a/main.go b/main.go index ad105b0..496e105 100644 --- a/main.go +++ b/main.go @@ -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 { diff --git a/scripts/common-release-build.sh b/scripts/common-release-build.sh index e831f78..9b79d09 100644 --- a/scripts/common-release-build.sh +++ b/scripts/common-release-build.sh @@ -19,7 +19,6 @@ build(){ EXE="" if [ "${OS}" == "windows" ] then - # Hide the windows console popup for SDL LDFLAGS_WIN="-H windowsgui" EXE=".exe" fi