Compare commits
10 Commits
project-el
...
main
Author | SHA1 | Date |
---|---|---|
Sean Hickey | 662517d1de | |
Sean Hickey | 2e0ac84163 | |
Sean Hickey | 06bd0cfd12 | |
Sean Hickey | 460316aaf6 | |
Sean Hickey | cee1d19f36 | |
Sean Hickey | ce5ff1a552 | |
Sean Hickey | f8d47107ce | |
Sean Hickey | d86124b735 | |
Sean Hickey | 4882cdedf5 | |
Cahley | e20133d5d0 |
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Package",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
1
Makefile
1
Makefile
|
@ -29,6 +29,7 @@ release_static: prebuild
|
|||
CGO_LDFLAGS="-Wl,-rpath -L${HOME}/pkg/lib -L/usr/lib -L/usr/lib/x86_64-linux-gnu" \
|
||||
${GO} ${GO_BUILD_RELEASE_STATIC}
|
||||
|
||||
lint: linter
|
||||
linter:
|
||||
golangci-lint run
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
This is some game project that we are working on for fun.
|
||||
|
||||
This project is written in Go using SDL2.
|
||||
This project is written in Go using raylib.
|
||||
|
||||
## Compiling
|
||||
|
||||
See the [Setup Docs][1] for installing all the necessary dependencies. It's fairly bare-bones, only requiring SDL.
|
||||
See the [Setup Docs][1] for installing all the necessary dependencies. It's fairly bare-bones, only requiring raylib (which bundles everything, only depending on base graphics libs like X11).
|
||||
|
||||
Then you can use the Makefile:
|
||||
```sh
|
||||
|
@ -23,10 +23,6 @@ A release build can be build with these extra flags:
|
|||
go build -x -v -ldflags "-s -w"
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
You may need the SDL2 libraries in order to run the resulting binary. Copy the SDL2 *.dll or *.so files into the same directory as the output executable and run the program.
|
||||
|
||||
## Docs
|
||||
|
||||
For initial environment setup, see the [Setup Docs][1].
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 4.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,9 +1,7 @@
|
|||
# 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.
|
||||
|
||||
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
|
||||
If you don't have Git yet, you can download it here.
|
||||
https://git-scm.com/
|
||||
|
@ -21,7 +19,7 @@ You can use the Git Bash terminal to do this too. Open up `Git Bash`. This is a
|
|||
```sh
|
||||
mkdir projects
|
||||
cd projects
|
||||
git clone ssh://git@gitea.wisellama.rocks:222/Project-Ely/project-ely.git
|
||||
git clone ssh://git@git.wisellama.rocks:222/Project-Ely/project-ely.git
|
||||
```
|
||||
|
||||
## Install Go
|
||||
|
@ -41,19 +39,6 @@ There is no installer for this, so we just need to put this in a folder that we'
|
|||
|
||||
Later on, we will need to set up the environment variables so that other software knows where we installed GCC.
|
||||
|
||||
## Install SDL2
|
||||
Download the "devel mingw" versions of the following SDL2 releases (again, ideally the latest versions, just providing links for examples).
|
||||
* SDL2 https://github.com/libsdl-org/SDL/releases/download/release-2.24.0/SDL2-devel-2.24.0-mingw.zip
|
||||
* SDL2_Image https://github.com/libsdl-org/SDL_image/releases/download/release-2.6.2/SDL2_image-devel-2.6.2-mingw.zip
|
||||
* SDL2_TTF https://github.com/libsdl-org/SDL_ttf/releases/download/release-2.20.1/SDL2_ttf-devel-2.20.1-mingw.zip
|
||||
* SDL2_Mixer https://github.com/libsdl-org/SDL_mixer/releases/download/release-2.6.2/SDL2_mixer-devel-2.6.2-mingw.zip
|
||||
|
||||
Extract the zip files (you can use 7zip or the built-in Windows zip tool) to get a directory for each. They should look something like `SDL2_ttf-2.20.1`.
|
||||
|
||||
Then put these all in some folder you will remember, something like `C:\Users\Username\SDL2`.
|
||||
|
||||
You should end up with 4 folders inside the `SDL2` folder you created, one for each of the things we downloaded.
|
||||
|
||||
## Set environment variables
|
||||
Now that we have all the software downloaded, we need to set some environment variables so that everything will know where we installed the software.
|
||||
|
||||
|
@ -71,14 +56,9 @@ Then we need to enable CGO (allowing the Go compiler to use a C compiler too):
|
|||
CGO_ENABLED = 1
|
||||
```
|
||||
|
||||
Then we need to set our "include" path. This is all of the directories that contain C header files to be included in our code. We need this to include the default GCC include directory as well as each of the SDL2 library include directories that we downloaded. This might be easier to copy-paste yourself if the versions are different. Also, change the `Username` to match your actual username (use search-replace to make this easier).
|
||||
Then we need to set our "include" path. This is all of the directories that contain C header files to be included in our code. We need this to include the default GCC include directory. (Depending on how you installed GCC, this may already be set). Below is an example, but it would probably be easier to copy-paste your path to get the version information correct.
|
||||
```
|
||||
C_INCLUDE_PATH = C:\Users\Username\GCC\x86_64-12.1.0-release-win32-seh-rt_v10-rev3\mingw64\include;C:\Users\Username\SDL2\SDL2_image-2.6.2\x86_64-w64-mingw32\include;C:\Users\Username\SDL2\SDL2_mixer-2.6.2\x86_64-w64-mingw32\include;C:\Users\Username\SDL2\SDL2_ttf-2.20.1\x86_64-w64-mingw32\include;C:\Users\Username\SDL2\SDL2-2.24.0\x86_64-w64-mingw32\include;C:\Users\Username\SDL2\SDL2-2.24.0\x86_64-w64-mingw32\include\SDL2;
|
||||
```
|
||||
|
||||
Then we need to set our "library" path. This is all of the directories that contain compiled library files (on Windows these are `*.dll` files) that can be imported into our code. This only needs to add all of the SDL2 directories.
|
||||
```
|
||||
LIBRARY_PATH = C:\Users\Username\SDL2\SDL2-2.24.0\x86_64-w64-mingw32\lib;C:\Users\Username\SDL2\SDL2_ttf-2.20.1\x86_64-w64-mingw32\lib;C:\Users\Username\SDL2\SDL2_mixer-2.6.2\x86_64-w64-mingw32\lib;C:\Users\Username\SDL2\SDL2_image-2.6.2\x86_64-w64-mingw32\lib;
|
||||
C_INCLUDE_PATH = C:\Users\Username\GCC\x86_64-12.1.0-release-win32-seh-rt_v10-rev3\mingw64\include;
|
||||
```
|
||||
|
||||
And depending on how you installed GCC, you may need to also add its `bin` directory to your path so that you will be able to call the `gcc` compiler. There are probably other entries already in your `PATH` variable, so just add this as an additional entry:
|
||||
|
@ -86,11 +66,6 @@ And depending on how you installed GCC, you may need to also add its `bin` direc
|
|||
PATH = C:\Users\Username\GCC\x86_64-12.1.0-release-win32-seh-rt_v10-rev3\mingw64\bin
|
||||
```
|
||||
|
||||
## Copy the SDL2 libraries into your code directory
|
||||
Your code may compile successfully, but it might not run because of the missing SDL2 libraries. Copy each of the `*.dll` library files into your code directory so that your resulting binary can load them correctly.
|
||||
|
||||
The DLL files should be in each of the `bin` directories of the SDL2 libraries that you downloaded.
|
||||
|
||||
## Build the software
|
||||
Now we can just build the software and hope it works!
|
||||
```
|
|
@ -0,0 +1,37 @@
|
|||
# Setting up SDL on Windows
|
||||
|
||||
Note: this project no longer uses SDL, but I wanted to keep the documentation around on how to set up and configure SDL on Windows because it was particularly weird.
|
||||
|
||||
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 SDL2
|
||||
Download the "devel mingw" versions of the following SDL2 releases (again, ideally the latest versions, just providing links for examples).
|
||||
* SDL2 https://github.com/libsdl-org/SDL/releases/download/release-2.24.0/SDL2-devel-2.24.0-mingw.zip
|
||||
* SDL2_Image https://github.com/libsdl-org/SDL_image/releases/download/release-2.6.2/SDL2_image-devel-2.6.2-mingw.zip
|
||||
* SDL2_TTF https://github.com/libsdl-org/SDL_ttf/releases/download/release-2.20.1/SDL2_ttf-devel-2.20.1-mingw.zip
|
||||
* SDL2_Mixer https://github.com/libsdl-org/SDL_mixer/releases/download/release-2.6.2/SDL2_mixer-devel-2.6.2-mingw.zip
|
||||
|
||||
Extract the zip files (you can use 7zip or the built-in Windows zip tool) to get a directory for each. They should look something like `SDL2_ttf-2.20.1`.
|
||||
|
||||
Then put these all in some folder you will remember, something like `C:\Users\Username\SDL2`.
|
||||
|
||||
You should end up with 4 folders inside the `SDL2` folder you created, one for each of the things we downloaded.
|
||||
|
||||
## Set environment variables
|
||||
|
||||
Now that we've downloaded SDL, we need to set some environment variables so that everything will know where we installed it.
|
||||
|
||||
We need to set our "include" path. This is all of the directories that contain C header files to be included in our code. We need this to include the default GCC include directory as well as each of the SDL2 library include directories that we downloaded. This might be easier to copy-paste yourself if the versions are different. Also, change the `Username` to match your actual username (use search-replace to make this easier).
|
||||
```
|
||||
C_INCLUDE_PATH = C:\Users\Username\GCC\x86_64-12.1.0-release-win32-seh-rt_v10-rev3\mingw64\include;C:\Users\Username\SDL2\SDL2_image-2.6.2\x86_64-w64-mingw32\include;C:\Users\Username\SDL2\SDL2_mixer-2.6.2\x86_64-w64-mingw32\include;C:\Users\Username\SDL2\SDL2_ttf-2.20.1\x86_64-w64-mingw32\include;C:\Users\Username\SDL2\SDL2-2.24.0\x86_64-w64-mingw32\include;C:\Users\Username\SDL2\SDL2-2.24.0\x86_64-w64-mingw32\include\SDL2;
|
||||
```
|
||||
|
||||
Then we need to set our "library" path. This is all of the directories that contain compiled library files (on Windows these are `*.dll` files) that can be imported into our code. This only needs to add all of the SDL2 directories.
|
||||
```
|
||||
LIBRARY_PATH = C:\Users\Username\SDL2\SDL2-2.24.0\x86_64-w64-mingw32\lib;C:\Users\Username\SDL2\SDL2_ttf-2.20.1\x86_64-w64-mingw32\lib;C:\Users\Username\SDL2\SDL2_mixer-2.6.2\x86_64-w64-mingw32\lib;C:\Users\Username\SDL2\SDL2_image-2.6.2\x86_64-w64-mingw32\lib;
|
||||
```
|
||||
|
||||
## Copy the SDL2 libraries into your code directory
|
||||
Your code may compile successfully, but it might not run because of the missing SDL2 libraries. Copy each of the `*.dll` library files into your code directory so that your resulting binary can load them correctly.
|
||||
|
||||
The DLL files should be in each of the `bin` directories of the SDL2 libraries that you downloaded.
|
|
@ -1,5 +0,0 @@
|
|||
game.title = "Project Ely"
|
||||
game.framerate = 60.0
|
||||
|
||||
log.file = "output.log"
|
||||
log.writeToFile = false
|
9
go.mod
9
go.mod
|
@ -1,8 +1,9 @@
|
|||
module gitea.wisellama.rocks/Project-Ely/project-ely
|
||||
module git.wisellama.rocks/Project-Ely/project-ely
|
||||
|
||||
go 1.18
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
gitea.wisellama.rocks/Wisellama/gosimpleconf v0.0.4
|
||||
github.com/veandco/go-sdl2 v0.4.25
|
||||
git.wisellama.rocks/Wisellama/gopackagebase v0.0.4
|
||||
git.wisellama.rocks/Wisellama/gosimpleconf v0.1.0
|
||||
github.com/gen2brain/raylib-go/raylib v0.0.0-20230413192425-0fdd3be3077b
|
||||
)
|
||||
|
|
10
go.sum
10
go.sum
|
@ -1,4 +1,6 @@
|
|||
gitea.wisellama.rocks/Wisellama/gosimpleconf v0.0.4 h1:xFjG/dGPUoh1L7/PG9xQRr0GQZwlh1EGI9RnG8Ja8R8=
|
||||
gitea.wisellama.rocks/Wisellama/gosimpleconf v0.0.4/go.mod h1:kY9gQL8laVTe+tW0ue5bYb6QThw78d7mx6AHwQ5CIzc=
|
||||
github.com/veandco/go-sdl2 v0.4.25 h1:J5ac3KKOccp/0xGJA1PaNYKPUcZm19IxhDGs8lJofPI=
|
||||
github.com/veandco/go-sdl2 v0.4.25/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
|
||||
git.wisellama.rocks/Wisellama/gopackagebase v0.0.4 h1:EUj/GqcSLJVm4aedSFaleudXlJNwJqRuSrTE0LhcA1M=
|
||||
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/gen2brain/raylib-go/raylib v0.0.0-20230413192425-0fdd3be3077b h1:a6MhRr2wZWGfWVu+hrnz+qkW/lGZzFPggpFJA8Zzj0I=
|
||||
github.com/gen2brain/raylib-go/raylib v0.0.0-20230413192425-0fdd3be3077b/go.mod h1:AwtGA3aTtYdezNxEVbfchaLw/z+CuRDh2Mlxy0FbBro=
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package animation
|
||||
|
||||
// AnimationMap maps each animation ID to the actual animation
|
||||
var AnimationMap map[int]entityAnimation
|
||||
|
||||
// Enum containing IDs of all animations that exist to make them easy to reference.
|
||||
const (
|
||||
PenguinWalkRight int = iota
|
||||
PenguinWalkLeft
|
||||
PenguinWalkUp
|
||||
PenguinWalkDown
|
||||
PenguinStationaryRight
|
||||
PenguinStationaryLeft
|
||||
PenguinStationaryUp
|
||||
PenguinStationaryDown
|
||||
)
|
||||
|
||||
func DefineAnimations() {
|
||||
AnimationMap = make(map[int]entityAnimation)
|
||||
DefinePenguinAnimations()
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package animation
|
||||
|
||||
import rl "github.com/gen2brain/raylib-go/raylib"
|
||||
|
||||
type animationTracker struct {
|
||||
currentAnimation entityAnimation
|
||||
animationStep int
|
||||
}
|
||||
|
||||
func NewAnimationTracker() *animationTracker {
|
||||
return &animationTracker{}
|
||||
}
|
||||
|
||||
func (a *animationTracker) Draw(windowPosition rl.Vector2, color rl.Color) error {
|
||||
step := a.animationStep / a.currentAnimation.GetSpeed()
|
||||
|
||||
err := a.currentAnimation.Draw(step, windowPosition, color)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.animationStep = 1 + a.animationStep
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *animationTracker) SetAnimation(id int) {
|
||||
newAnim := AnimationMap[id]
|
||||
|
||||
if newAnim != a.currentAnimation {
|
||||
a.animationStep = 0
|
||||
}
|
||||
|
||||
a.currentAnimation = newAnim
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package animation
|
||||
|
||||
import (
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
type SpriteAnimation interface {
|
||||
Draw(
|
||||
frame int,
|
||||
windowPosition rl.Vector2,
|
||||
angle float32,
|
||||
center rl.Vector2,
|
||||
flip rl.Vector2,
|
||||
color rl.Color,
|
||||
) error
|
||||
}
|
||||
|
||||
var (
|
||||
FlipNone = rl.Vector2{X: 0, Y: 0}
|
||||
FlipHorizontal = rl.Vector2{X: 1, Y: 0}
|
||||
FlipVertical = rl.Vector2{X: 0, Y: 1}
|
||||
FlipBoth = 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 float32
|
||||
center rl.Vector2
|
||||
flip rl.Vector2
|
||||
}
|
||||
|
||||
func NewEntityAnimation(
|
||||
spriteAnimation SpriteAnimation,
|
||||
speed int,
|
||||
length int,
|
||||
angle float32,
|
||||
center rl.Vector2,
|
||||
flip rl.Vector2,
|
||||
) entityAnimation {
|
||||
|
||||
return entityAnimation{
|
||||
spriteAnimation: spriteAnimation,
|
||||
speed: speed,
|
||||
length: length,
|
||||
angle: angle,
|
||||
center: center,
|
||||
flip: 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 {
|
||||
return e.speed
|
||||
}
|
||||
|
||||
func getCenter(dimensions rl.Vector2) rl.Vector2 {
|
||||
x := dimensions.X / 2.0
|
||||
y := dimensions.Y / 2.0
|
||||
|
||||
return rl.Vector2{X: x, Y: y}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package animation
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/sprite"
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
const (
|
||||
PenguinDefault = PenguinStationaryRight
|
||||
)
|
||||
|
||||
var penguinAnimations []int
|
||||
|
||||
func DefinePenguinAnimations() {
|
||||
filename := sprite.DelilahWalking
|
||||
|
||||
penguinAnimations = make([]int, 0)
|
||||
penguinAnimations = append(penguinAnimations, PenguinWalkRight)
|
||||
penguinAnimations = append(penguinAnimations, PenguinWalkLeft)
|
||||
penguinAnimations = append(penguinAnimations, PenguinWalkUp)
|
||||
penguinAnimations = append(penguinAnimations, PenguinWalkDown)
|
||||
penguinAnimations = append(penguinAnimations, PenguinStationaryRight)
|
||||
penguinAnimations = append(penguinAnimations, PenguinStationaryLeft)
|
||||
penguinAnimations = append(penguinAnimations, PenguinStationaryUp)
|
||||
penguinAnimations = append(penguinAnimations, PenguinStationaryDown)
|
||||
|
||||
var (
|
||||
dimensions rl.Vector2
|
||||
offset rl.Vector2
|
||||
center rl.Vector2
|
||||
length int
|
||||
speed int
|
||||
border int
|
||||
)
|
||||
|
||||
dimensions = rl.Vector2{X: 13, Y: 17}
|
||||
|
||||
// Walking Right is in the spritesheet.
|
||||
speed = 5
|
||||
offset = rl.Vector2{X: 0, Y: 1}
|
||||
length = 5
|
||||
border = 1 // optional border around each sprite
|
||||
center = getCenter(dimensions) // center is for rotation, nil will default to w/2 h/2
|
||||
walkRight := sprite.NewAnimation(filename, dimensions, offset, length, border)
|
||||
AnimationMap[PenguinWalkRight] = NewEntityAnimation(walkRight, speed, length, 0, center, FlipNone)
|
||||
|
||||
// Walking Left is just that flipped.
|
||||
AnimationMap[PenguinWalkLeft] = NewEntityAnimation(walkRight, speed, length, 0, center, FlipHorizontal)
|
||||
|
||||
// Stationary Right/Left is just the first frame.
|
||||
length = 1
|
||||
stationaryRight := sprite.NewAnimation(filename, dimensions, offset, length, border)
|
||||
AnimationMap[PenguinStationaryRight] = NewEntityAnimation(stationaryRight, speed, length, 0, center, FlipNone)
|
||||
AnimationMap[PenguinStationaryLeft] = NewEntityAnimation(stationaryRight, speed, length, 0, center, FlipHorizontal)
|
||||
|
||||
// Walk Up
|
||||
length = 4
|
||||
offset = rl.Vector2{X: 0, Y: 3}
|
||||
walkUp := sprite.NewAnimation(filename, dimensions, offset, length, border)
|
||||
AnimationMap[PenguinWalkUp] = NewEntityAnimation(walkUp, speed, length, 0, center, FlipNone)
|
||||
|
||||
// Stationary Up
|
||||
length = 1
|
||||
stationaryUp := sprite.NewAnimation(filename, dimensions, offset, length, border)
|
||||
AnimationMap[PenguinStationaryUp] = NewEntityAnimation(stationaryUp, speed, length, 0, center, FlipNone)
|
||||
|
||||
// Walk Down
|
||||
length = 4
|
||||
offset = rl.Vector2{X: 0, Y: 0}
|
||||
walkDown := sprite.NewAnimation(filename, dimensions, offset, length, border)
|
||||
AnimationMap[PenguinWalkDown] = NewEntityAnimation(walkDown, speed, length, 0, center, FlipNone)
|
||||
|
||||
// Stationary Down
|
||||
length = 1
|
||||
stationaryDown := sprite.NewAnimation(filename, dimensions, offset, length, border)
|
||||
AnimationMap[PenguinStationaryDown] = NewEntityAnimation(stationaryDown, speed, length, 0, center, FlipNone)
|
||||
}
|
||||
|
||||
func RandomPenguinAnimation() int {
|
||||
n := len(penguinAnimations)
|
||||
i := rand.Intn(n)
|
||||
return penguinAnimations[i]
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"gitea.wisellama.rocks/Wisellama/gosimpleconf"
|
||||
)
|
||||
|
||||
var defaultConfig gosimpleconf.ConfigMap = gosimpleconf.ConfigMap{
|
||||
"game.title": "Project Ely",
|
||||
"game.framerate": "60.0",
|
||||
"log.writeToFile": "false",
|
||||
}
|
||||
|
||||
func Configure(filename string) (gosimpleconf.ConfigMap, error) {
|
||||
var err error
|
||||
|
||||
_, err = os.Stat(filename)
|
||||
if os.IsNotExist(err) {
|
||||
// Config file does not exist, return a default configMap
|
||||
return defaultConfig, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
configMap, err := gosimpleconf.Load(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
flagMap := gosimpleconf.SetupFlagOverrides(configMap)
|
||||
configMap = gosimpleconf.ParseFlags(configMap, flagMap)
|
||||
|
||||
return configMap, nil
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type logWriter struct {
|
||||
writeToFile bool
|
||||
logFile *os.File
|
||||
}
|
||||
|
||||
func (w *logWriter) Write(bytes []byte) (int, error) {
|
||||
t := time.Now().UTC().Format(time.RFC3339)
|
||||
return fmt.Fprintf(w.logFile, "%v %v", t, string(bytes))
|
||||
}
|
||||
|
||||
func (w *logWriter) Cleanup() {
|
||||
defer w.logFile.Close()
|
||||
}
|
||||
|
||||
func SetupLogging(writeToFile bool, logFilename string) (*logWriter, error) {
|
||||
var err error
|
||||
log.SetFlags(0)
|
||||
|
||||
logFile := os.Stdout
|
||||
if writeToFile {
|
||||
logFile, err = os.OpenFile(logFilename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
writer := &logWriter{
|
||||
writeToFile: writeToFile,
|
||||
logFile: logFile,
|
||||
}
|
||||
log.SetOutput(writer)
|
||||
|
||||
return writer, nil
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
crypto_rand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
math_rand "math/rand"
|
||||
)
|
||||
|
||||
// InitRNG will grab some cryptographically secure bytes to use as the
|
||||
// seed for the non-crypto random number generator. Suggested on
|
||||
// StackOverflow to avoid time-based seeds:
|
||||
// https://stackoverflow.com/a/54491783
|
||||
func InitRNG() error {
|
||||
var b [8]byte
|
||||
_, err := crypto_rand.Read(b[:])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error seeding random number generator: %w", err)
|
||||
return err
|
||||
}
|
||||
math_rand.Seed(int64(binary.LittleEndian.Uint64(b[:])))
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
type EntityAnimation interface {
|
||||
Draw(step int, windowPosition rl.Vector2, color rl.Color) error
|
||||
GetSpeed() int
|
||||
}
|
||||
|
||||
type AnimationTracker interface {
|
||||
Draw(windowPosition rl.Vector2, color rl.Color) error
|
||||
SetAnimation(id int)
|
||||
}
|
||||
|
||||
const (
|
||||
// These line up with the VEC_DIRECTIONS slice
|
||||
|
||||
DirLeft int = iota
|
||||
DirRight
|
||||
DirUp
|
||||
DirDown
|
||||
)
|
||||
|
||||
// The following are axis direction vectors based on world coordinates.
|
||||
// UP/DOWN is intentionally UP = positive (which is different from window coordinates)
|
||||
var (
|
||||
VecLeft = rl.Vector2{X: -1, Y: 0}
|
||||
VecRight = rl.Vector2{X: 1, Y: 0}
|
||||
VecUp = rl.Vector2{X: 0, Y: 1}
|
||||
VecDown = rl.Vector2{X: 0, Y: -1}
|
||||
|
||||
VecDirections = []rl.Vector2{
|
||||
// Prefer left/right animations by checking them first in the list
|
||||
VecLeft,
|
||||
VecRight,
|
||||
VecUp,
|
||||
VecDown,
|
||||
}
|
||||
)
|
||||
|
||||
func determineClosestDirection(velocity rl.Vector2) int {
|
||||
closest := DirRight
|
||||
max := float32(0)
|
||||
buffer := float32(0.5) // This buffer lets us stick to the left/right animations for diagonal movement
|
||||
|
||||
for i, dir := range VecDirections {
|
||||
dot := rl.Vector2DotProduct(velocity, dir)
|
||||
if dot > max+buffer {
|
||||
max = dot
|
||||
closest = i
|
||||
}
|
||||
}
|
||||
|
||||
return closest
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math"
|
||||
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/animation"
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/physics"
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
type penguin struct {
|
||||
animation AnimationTracker
|
||||
object physics.Object
|
||||
direction int
|
||||
staticAnimation bool
|
||||
color rl.Color
|
||||
}
|
||||
|
||||
func NewPenguin() *penguin {
|
||||
object := physics.NewObject()
|
||||
anim := animation.NewAnimationTracker()
|
||||
anim.SetAnimation(animation.PenguinDefault)
|
||||
|
||||
return &penguin{
|
||||
animation: anim,
|
||||
object: object,
|
||||
staticAnimation: false,
|
||||
color: rl.White,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *penguin) Draw() error {
|
||||
|
||||
position := p.object.GetPosition()
|
||||
|
||||
// TODO?
|
||||
//windowPosition := worldPosToWindowPos()
|
||||
windowPosition := rl.Vector2{
|
||||
X: float32(math.Round(float64(position.X))),
|
||||
Y: float32(-1 * math.Round(float64(position.Y))),
|
||||
}
|
||||
|
||||
return p.animation.Draw(windowPosition, p.color)
|
||||
}
|
||||
|
||||
func (p *penguin) Update() error {
|
||||
p.SetMoveAnimation()
|
||||
|
||||
return p.object.Update()
|
||||
}
|
||||
|
||||
func (p *penguin) GetObject() physics.Object {
|
||||
return p.object
|
||||
}
|
||||
|
||||
func (p *penguin) GetAnimationTracker() AnimationTracker {
|
||||
return p.animation
|
||||
}
|
||||
|
||||
// ToggleStaticAnimation will set whether or not this entity should ever update its animation loop or not.
|
||||
// True means this will never update, it is static.
|
||||
// False means this will update the animation loop based on physical properties of this entity.
|
||||
func (p *penguin) ToggleStaticAnimation() {
|
||||
p.staticAnimation = !p.staticAnimation
|
||||
}
|
||||
|
||||
func (p *penguin) SetMoveAnimation() {
|
||||
if p.staticAnimation {
|
||||
return
|
||||
}
|
||||
|
||||
velocity := p.object.GetVelocity()
|
||||
if velocity.X == 0 && velocity.Y == 0 {
|
||||
// Stay facing whatever direction we were facing
|
||||
direction := p.direction
|
||||
switch direction {
|
||||
case DirLeft:
|
||||
p.animation.SetAnimation(animation.PenguinStationaryLeft)
|
||||
case DirRight:
|
||||
p.animation.SetAnimation(animation.PenguinStationaryRight)
|
||||
case DirUp:
|
||||
p.animation.SetAnimation(animation.PenguinStationaryUp)
|
||||
case DirDown:
|
||||
p.animation.SetAnimation(animation.PenguinStationaryDown)
|
||||
default:
|
||||
log.Printf("unknown direction: %v", direction)
|
||||
p.animation.SetAnimation(animation.PenguinDefault)
|
||||
}
|
||||
|
||||
} else {
|
||||
// Figure out which way we are facing now that we're moving
|
||||
direction := determineClosestDirection(velocity)
|
||||
p.direction = direction
|
||||
|
||||
switch direction {
|
||||
case DirLeft:
|
||||
p.animation.SetAnimation(animation.PenguinWalkLeft)
|
||||
case DirRight:
|
||||
p.animation.SetAnimation(animation.PenguinWalkRight)
|
||||
case DirUp:
|
||||
p.animation.SetAnimation(animation.PenguinWalkUp)
|
||||
case DirDown:
|
||||
p.animation.SetAnimation(animation.PenguinWalkDown)
|
||||
default:
|
||||
log.Printf("unknown direction: %v", direction)
|
||||
p.animation.SetAnimation(animation.PenguinDefault)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *penguin) SetColor(color rl.Color) {
|
||||
p.color = color
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/animation"
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/channels"
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/entity"
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/player"
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/sprite"
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/version"
|
||||
"git.wisellama.rocks/Wisellama/gosimpleconf"
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
// Drawable can be rendered with OpenGL. See the 'entity' and 'animation' packages.
|
||||
type Drawable interface {
|
||||
Draw() error
|
||||
}
|
||||
|
||||
// Object can have physical interactions. See the 'entity' and 'physics' packages.
|
||||
type Object interface {
|
||||
Update() error
|
||||
}
|
||||
|
||||
// Run is the main function to start the game.
|
||||
func Run(ctx context.Context, configMap gosimpleconf.ConfigMap) error {
|
||||
var err error
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
framerate64 := gosimpleconf.Int64(configMap["game.framerate"])
|
||||
framerate := int32(framerate64)
|
||||
|
||||
windowTitle := fmt.Sprintf("%s - %s (%s)",
|
||||
configMap["game.title"],
|
||||
version.Version,
|
||||
version.CommitHash,
|
||||
)
|
||||
|
||||
// Initialize the RayLib window
|
||||
channels.RL.Do(func() {
|
||||
monitor := rl.GetCurrentMonitor()
|
||||
windowWidth := int32(rl.GetMonitorWidth(monitor))
|
||||
windowHeight := int32(rl.GetMonitorHeight(monitor))
|
||||
rl.InitWindow(windowWidth, windowHeight, windowTitle)
|
||||
rl.ToggleFullscreen()
|
||||
rl.SetTargetFPS(framerate)
|
||||
})
|
||||
defer func() {
|
||||
channels.RL.Do(func() {
|
||||
rl.CloseWindow()
|
||||
})
|
||||
}()
|
||||
|
||||
// Initialize our sprites and animations
|
||||
sprite.InitSpriteCache()
|
||||
animation.DefineAnimations()
|
||||
|
||||
drawables := make([]Drawable, 0)
|
||||
objects := make([]Object, 0)
|
||||
|
||||
center := rl.Vector2{X: float32(rl.GetScreenWidth()) / 2, Y: float32(rl.GetScreenHeight()) / 2 * -1}
|
||||
player1 := player.NewPlayer(ctx)
|
||||
player1Penguin := entity.NewPenguin()
|
||||
player1Penguin.SetColor(rl.Gold)
|
||||
player1Penguin.GetObject().SetPosition(center)
|
||||
player1.SetControlledObject(player1Penguin.GetObject())
|
||||
drawables = append(drawables, player1Penguin)
|
||||
objects = append(objects, player1Penguin)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
p := entity.NewPenguin()
|
||||
p.GetObject().SetPosition(rl.Vector2{
|
||||
X: rand.Float32() * float32(rl.GetScreenWidth()),
|
||||
Y: rand.Float32() * float32(rl.GetScreenHeight()*-1),
|
||||
})
|
||||
p.GetAnimationTracker().SetAnimation(animation.RandomPenguinAnimation())
|
||||
p.ToggleStaticAnimation()
|
||||
|
||||
drawables = append(drawables, p)
|
||||
objects = append(objects, p)
|
||||
}
|
||||
|
||||
// And now starting the main loop
|
||||
running := true
|
||||
for running {
|
||||
channels.RL.Do(func() {
|
||||
if rl.WindowShouldClose() {
|
||||
cancel()
|
||||
}
|
||||
})
|
||||
|
||||
// Allow us to exit early if the context is done
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
running = false
|
||||
default:
|
||||
// Keep running
|
||||
}
|
||||
if !running {
|
||||
break
|
||||
}
|
||||
|
||||
player1.Update()
|
||||
|
||||
// Update physics
|
||||
// TODO probably move out to something else to handle collisions.
|
||||
for _, o := range objects {
|
||||
err = o.Update()
|
||||
if err != nil {
|
||||
log.Printf("error updating physics: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
channels.RL.Do(func() {
|
||||
rl.BeginDrawing()
|
||||
})
|
||||
|
||||
// Draw stuff
|
||||
for _, d := range drawables {
|
||||
err = d.Draw()
|
||||
if err != nil {
|
||||
log.Printf("error drawing: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
channels.RL.Do(func() {
|
||||
rl.ClearBackground(rl.Black)
|
||||
rl.DrawText("Some Text!", 190, 200, 20, rl.Blue)
|
||||
rl.DrawText(fmt.Sprintf("%v FPS", rl.GetFPS()), 190, 250, 20, rl.Blue)
|
||||
})
|
||||
|
||||
channels.RL.Do(func() {
|
||||
rl.EndDrawing()
|
||||
})
|
||||
}
|
||||
|
||||
sprite.CleanupSpriteCache()
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,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
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
package animation
|
||||
|
||||
import (
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
type SpriteAnimation interface {
|
||||
Draw(frame int, worldPosition *sdl.Point, angle float64, center *sdl.Point, flip sdl.RendererFlip) error
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func NewEntityAnimation(
|
||||
spriteAnimation SpriteAnimation,
|
||||
speed int,
|
||||
length int,
|
||||
angle float64,
|
||||
center *sdl.Point,
|
||||
flip sdl.RendererFlip,
|
||||
) *entityAnimation {
|
||||
|
||||
e := entityAnimation{
|
||||
spriteAnimation: spriteAnimation,
|
||||
speed: speed,
|
||||
length: length,
|
||||
angle: angle,
|
||||
center: center,
|
||||
flip: flip,
|
||||
}
|
||||
|
||||
return &e
|
||||
}
|
||||
|
||||
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) GetSpeed() int {
|
||||
return e.speed
|
||||
}
|
||||
|
||||
func DefineAnimations() {
|
||||
DefinePenguinAnimations()
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
package animation
|
||||
|
||||
import (
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/sprite"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
var PenguinAnimations map[int]*entityAnimation
|
||||
|
||||
const (
|
||||
PENGUIN_WALK_RIGHT int = iota
|
||||
PENGUIN_WALK_LEFT
|
||||
PENGUIN_WALK_UP
|
||||
PENGUIN_WALK_DOWN
|
||||
PENGUIN_STATIONARY_RIGHT
|
||||
PENGUIN_STATIONARY_LEFT
|
||||
PENGUIN_STATIONARY_UP
|
||||
PENGUIN_STATIONARY_DOWN
|
||||
)
|
||||
|
||||
const (
|
||||
PENGUIN_NUM_ANIMS = 8
|
||||
PENGUIN_DEFAULT = PENGUIN_STATIONARY_RIGHT
|
||||
)
|
||||
|
||||
func DefinePenguinAnimations() {
|
||||
filename := sprite.PERCYWALKING
|
||||
|
||||
var (
|
||||
dimensions sdl.Point
|
||||
offset sdl.Point
|
||||
center *sdl.Point
|
||||
length int
|
||||
speed int
|
||||
border int
|
||||
)
|
||||
|
||||
dimensions = sdl.Point{X: 13, Y: 17}
|
||||
PenguinAnimations = make(map[int]*entityAnimation)
|
||||
|
||||
// Walking Right is in the spritesheet.
|
||||
speed = 5
|
||||
offset = sdl.Point{X: 0, Y: 1}
|
||||
length = 5
|
||||
border = 1 // optional border around each sprite
|
||||
center = nil // 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)
|
||||
|
||||
// Walking Left is just that flipped.
|
||||
PenguinAnimations[PENGUIN_WALK_LEFT] = NewEntityAnimation(walkRight, speed, length, 0, center, sdl.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)
|
||||
|
||||
// Walk Up
|
||||
length = 4
|
||||
offset = sdl.Point{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)
|
||||
|
||||
// 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)
|
||||
|
||||
// Walk Down
|
||||
length = 4
|
||||
offset = sdl.Point{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)
|
||||
|
||||
// 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)
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package command
|
||||
|
||||
// List of keys supported by all entities
|
||||
const (
|
||||
MOVE_X int = iota
|
||||
MOVE_Y
|
||||
SET_SPEED
|
||||
SET_POSITION
|
||||
SET_ANIMATION
|
||||
)
|
||||
|
||||
type Command struct {
|
||||
key int
|
||||
value float64
|
||||
}
|
||||
|
||||
func NewCommand(key int, value float64) Command {
|
||||
return Command{
|
||||
key: key,
|
||||
value: value,
|
||||
}
|
||||
}
|
|
@ -1,170 +0,0 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/channels"
|
||||
)
|
||||
|
||||
type Entity interface {
|
||||
Draw() error
|
||||
Update() error
|
||||
MoveX(x float64)
|
||||
MoveY(y float64)
|
||||
SetSpeed(s float64)
|
||||
}
|
||||
|
||||
type CommandHandler struct {
|
||||
ctx context.Context
|
||||
timeout time.Duration
|
||||
entity Entity
|
||||
commandChan chan Command
|
||||
drawRequestChan chan bool
|
||||
drawResponseChan chan bool
|
||||
updateRequestChan chan bool
|
||||
updateResponseChan chan bool
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
defaultTimeout := time.Second
|
||||
return &CommandHandler{
|
||||
ctx: ctx,
|
||||
timeout: defaultTimeout,
|
||||
entity: entity,
|
||||
commandChan: commandChan,
|
||||
drawRequestChan: drawRequestChan,
|
||||
drawResponseChan: drawResponseChan,
|
||||
updateRequestChan: updateRequestChan,
|
||||
updateResponseChan: updateResponseChan,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandHandler) closeResponses() {
|
||||
close(c.drawResponseChan)
|
||||
close(c.updateResponseChan)
|
||||
}
|
||||
|
||||
func (c *CommandHandler) CloseRequests() {
|
||||
close(c.drawRequestChan)
|
||||
close(c.updateRequestChan)
|
||||
close(c.commandChan)
|
||||
}
|
||||
|
||||
func (c *CommandHandler) CommandRequest() chan Command {
|
||||
return c.commandChan
|
||||
}
|
||||
|
||||
func (c *CommandHandler) DrawRequest() chan bool {
|
||||
return c.drawRequestChan
|
||||
}
|
||||
|
||||
func (c *CommandHandler) DrawResponse() chan bool {
|
||||
return c.drawResponseChan
|
||||
}
|
||||
|
||||
func (c *CommandHandler) UpdateRequest() chan bool {
|
||||
return c.updateRequestChan
|
||||
}
|
||||
|
||||
func (c *CommandHandler) UpdateResponse() chan bool {
|
||||
return c.updateResponseChan
|
||||
}
|
||||
|
||||
func (c *CommandHandler) Run() {
|
||||
defer c.closeResponses()
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
running := true
|
||||
for running {
|
||||
select {
|
||||
case <-c.drawRequestChan:
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
c.DrawWithTimeout()
|
||||
c.drawResponseChan <- true
|
||||
}()
|
||||
case <-c.updateRequestChan:
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
c.UpdateWithTimeout()
|
||||
c.updateResponseChan <- true
|
||||
}()
|
||||
case cmd := <-c.commandChan:
|
||||
wg.Add(1)
|
||||
go func(cmd Command) {
|
||||
defer wg.Done()
|
||||
c.HandleWithTimeout(cmd)
|
||||
}(cmd)
|
||||
case <-c.ctx.Done():
|
||||
// Graceful shutdown
|
||||
running = false
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Finish up anything in the queues
|
||||
for cmd := range c.commandChan {
|
||||
c.HandleWithTimeout(cmd)
|
||||
}
|
||||
for range c.drawRequestChan {
|
||||
c.UpdateWithTimeout()
|
||||
}
|
||||
for range c.drawRequestChan {
|
||||
c.DrawWithTimeout()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandHandler) HandleWithTimeout(cmd Command) {
|
||||
err := channels.RunWithTimeout(c.timeout, func() error {
|
||||
return c.Handle(cmd)
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("handle with timeout error: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandHandler) Handle(cmd Command) error {
|
||||
switch cmd.key {
|
||||
case MOVE_X:
|
||||
c.entity.MoveX(cmd.value)
|
||||
case MOVE_Y:
|
||||
c.entity.MoveY(cmd.value)
|
||||
case SET_SPEED:
|
||||
c.entity.SetSpeed(cmd.value)
|
||||
default:
|
||||
log.Printf("unknown entity command: %v", cmd.key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CommandHandler) DrawWithTimeout() {
|
||||
err := channels.RunWithTimeout(c.timeout, func() error {
|
||||
return c.entity.Draw()
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("draw with timeout error: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandHandler) UpdateWithTimeout() {
|
||||
err := channels.RunWithTimeout(c.timeout, func() error {
|
||||
return c.entity.Update()
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("update with timeout error: %v\n", err)
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/vector"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
type EntityAnimation interface {
|
||||
Draw(step int, windowPosition *sdl.Point) error
|
||||
GetSpeed() int
|
||||
}
|
||||
|
||||
const (
|
||||
// These line up with the VEC_DIRECTIONS slice
|
||||
DIR_LEFT int = iota
|
||||
DIR_RIGHT
|
||||
DIR_UP
|
||||
DIR_DOWN
|
||||
)
|
||||
|
||||
// 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_DIRECTIONS = []*vector.Vec2F{
|
||||
// Prefer left/right animations by checking them first in the list
|
||||
VEC_LEFT,
|
||||
VEC_RIGHT,
|
||||
VEC_UP,
|
||||
VEC_DOWN,
|
||||
}
|
||||
)
|
||||
|
||||
func determineClosestDirection(velocity *vector.Vec2F) int {
|
||||
closest := DIR_RIGHT
|
||||
max := 0.0
|
||||
buffer := 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)
|
||||
if dot > max+buffer {
|
||||
max = dot
|
||||
closest = i
|
||||
}
|
||||
}
|
||||
|
||||
return closest
|
||||
}
|
|
@ -1,166 +0,0 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/animation"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/vector"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
type penguin struct {
|
||||
mx sync.RWMutex
|
||||
|
||||
// Animation parameters
|
||||
currentAnimation EntityAnimation // pointer to current animation loop
|
||||
animationStep int // index of animation loop
|
||||
direction int // direction facing for sprite animations
|
||||
updateAnimation bool // if false, don't change the animation
|
||||
|
||||
// Physical parameters
|
||||
worldPosition *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
|
||||
}
|
||||
|
||||
func NewPenguin(renderer *sdl.Renderer) *penguin {
|
||||
position := vector.Vec2F{}
|
||||
velocity := vector.Vec2F{}
|
||||
speed := 1.0
|
||||
|
||||
p := penguin{
|
||||
currentAnimation: animation.PenguinAnimations[animation.PENGUIN_DEFAULT],
|
||||
animationStep: 0,
|
||||
direction: DIR_RIGHT,
|
||||
|
||||
worldPosition: &position,
|
||||
speed: speed,
|
||||
velocity: &velocity,
|
||||
}
|
||||
|
||||
return &p
|
||||
}
|
||||
|
||||
func (p *penguin) Draw() error {
|
||||
step := p.animationStep / p.currentAnimation.GetSpeed()
|
||||
|
||||
// TODO
|
||||
//windowPosition := worldPosToWindowPos()
|
||||
windowPosition := &sdl.Point{
|
||||
X: int32(math.Round(p.worldPosition.X)),
|
||||
Y: int32(math.Round(-1 * p.worldPosition.Y)),
|
||||
}
|
||||
|
||||
err := p.currentAnimation.Draw(step, windowPosition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.animationStep = 1 + p.animationStep
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *penguin) SetPosition(vec *vector.Vec2F) {
|
||||
p.worldPosition = vec
|
||||
}
|
||||
|
||||
func (p *penguin) SetDirection(dir int) {
|
||||
p.direction = dir
|
||||
}
|
||||
|
||||
func (p *penguin) SetAnimation(id int) {
|
||||
a, exists := animation.PenguinAnimations[id]
|
||||
|
||||
if !exists {
|
||||
log.Printf("animation does not exist: %v", id)
|
||||
a = animation.PenguinAnimations[animation.PENGUIN_DEFAULT]
|
||||
}
|
||||
|
||||
if a != p.currentAnimation {
|
||||
p.animationStep = 0
|
||||
}
|
||||
|
||||
p.currentAnimation = a
|
||||
}
|
||||
|
||||
func (p *penguin) MoveX(x float64) {
|
||||
p.mx.Lock()
|
||||
defer p.mx.Unlock()
|
||||
|
||||
p.velocity.X = x
|
||||
p.velocity.Normalize()
|
||||
p.updateAnimation = true
|
||||
}
|
||||
|
||||
func (p *penguin) MoveY(y float64) {
|
||||
p.mx.Lock()
|
||||
defer p.mx.Unlock()
|
||||
|
||||
// (0,0) is the top left, so negative y moves up
|
||||
p.velocity.Y = y
|
||||
p.velocity.Normalize()
|
||||
p.updateAnimation = true
|
||||
}
|
||||
|
||||
func (p *penguin) SetSpeed(s float64) {
|
||||
p.speed = s
|
||||
}
|
||||
|
||||
func (p *penguin) GetSpeed() float64 {
|
||||
return p.speed
|
||||
}
|
||||
|
||||
func (p *penguin) SetMoveAnimation() {
|
||||
if p.velocity.Zero() {
|
||||
// Stay facing whatever direction we were facing
|
||||
switch p.direction {
|
||||
case DIR_LEFT:
|
||||
p.SetAnimation(animation.PENGUIN_STATIONARY_LEFT)
|
||||
case DIR_RIGHT:
|
||||
p.SetAnimation(animation.PENGUIN_STATIONARY_RIGHT)
|
||||
case DIR_UP:
|
||||
p.SetAnimation(animation.PENGUIN_STATIONARY_UP)
|
||||
case DIR_DOWN:
|
||||
p.SetAnimation(animation.PENGUIN_STATIONARY_DOWN)
|
||||
default:
|
||||
log.Printf("unknown direction: %v", p.direction)
|
||||
p.SetAnimation(animation.PENGUIN_DEFAULT)
|
||||
}
|
||||
|
||||
} else {
|
||||
// Figure out which way we are facing now that we're moving
|
||||
p.direction = determineClosestDirection(p.velocity)
|
||||
|
||||
switch p.direction {
|
||||
case DIR_LEFT:
|
||||
p.SetAnimation(animation.PENGUIN_WALK_LEFT)
|
||||
case DIR_RIGHT:
|
||||
p.SetAnimation(animation.PENGUIN_WALK_RIGHT)
|
||||
case DIR_UP:
|
||||
p.SetAnimation(animation.PENGUIN_WALK_UP)
|
||||
case DIR_DOWN:
|
||||
p.SetAnimation(animation.PENGUIN_WALK_DOWN)
|
||||
default:
|
||||
log.Printf("unknown direction: %v", p.direction)
|
||||
p.SetAnimation(animation.PENGUIN_DEFAULT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *penguin) Update() error {
|
||||
if p.updateAnimation {
|
||||
p.SetMoveAnimation()
|
||||
p.updateAnimation = false
|
||||
}
|
||||
|
||||
x := p.velocity.X * p.speed
|
||||
y := p.velocity.Y * p.speed
|
||||
|
||||
p.worldPosition.X += x
|
||||
p.worldPosition.Y += y
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,210 +0,0 @@
|
|||
package game
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/animation"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/command"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/types"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/player"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/sprite"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/window"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/vector"
|
||||
"gitea.wisellama.rocks/Wisellama/gosimpleconf"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
type EntityCmdHandler interface {
|
||||
Run()
|
||||
CloseRequests()
|
||||
CommandRequest() chan command.Command
|
||||
DrawRequest() chan bool
|
||||
DrawResponse() chan bool
|
||||
UpdateRequest() chan bool
|
||||
UpdateResponse() chan bool
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
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)
|
||||
})
|
||||
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)
|
||||
}
|
||||
})
|
||||
}()
|
||||
|
||||
err = sprite.InitSpriteCache(renderer)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed in InitSpriteCache: %w", err)
|
||||
return err
|
||||
}
|
||||
defer sprite.CleanupSpriteCache()
|
||||
|
||||
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())
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
penguinCmdHandler.Run()
|
||||
}()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
entity := types.NewPenguin(renderer)
|
||||
entityCmd := command.NewCommandHandler(ctx, entity)
|
||||
randomPos := vector.Vec2F{X: rand.Float64() * 500, Y: rand.Float64() * -500}
|
||||
entity.SetPosition(&randomPos)
|
||||
entity.SetAnimation(rand.Intn(animation.PENGUIN_NUM_ANIMS))
|
||||
entityList = append(entityList, entityCmd)
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
entityCmd.Run()
|
||||
}()
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
})
|
||||
|
||||
// Allow us to exit early if the context is done
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
running = false
|
||||
default:
|
||||
// Keep running
|
||||
}
|
||||
if !running {
|
||||
break
|
||||
}
|
||||
|
||||
// Players
|
||||
player1.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)
|
||||
}
|
||||
})
|
||||
|
||||
// Tell everything to Update and Draw
|
||||
for _, e := range entityList {
|
||||
e.UpdateRequest() <- true
|
||||
e.DrawRequest() <- true
|
||||
}
|
||||
|
||||
// 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)
|
||||
})
|
||||
}
|
||||
|
||||
inputHandler.CloseChannels()
|
||||
|
||||
for _, e := range entityList {
|
||||
e.CloseRequests()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
package player
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/entity/command"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
entityChan chan command.Command
|
||||
}
|
||||
|
||||
func NewPlayer(ctx context.Context, inputHandler InputHandler) *player {
|
||||
defaultTimeout := time.Second
|
||||
|
||||
p := player{
|
||||
ctx: ctx,
|
||||
timeout: defaultTimeout,
|
||||
inputHandler: inputHandler,
|
||||
}
|
||||
return &p
|
||||
}
|
||||
|
||||
func (p *player) SetEntityChan(e chan command.Command) {
|
||||
p.entityChan = e
|
||||
}
|
||||
|
||||
func (p *player) Update() error {
|
||||
// Speed
|
||||
if p.inputHandler.Keystate(sdl.K_LSHIFT) {
|
||||
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) {
|
||||
p.entityChan <- command.NewCommand(command.MOVE_X, 1.0)
|
||||
} else if p.inputHandler.Keystate(sdl.K_a) {
|
||||
p.entityChan <- command.NewCommand(command.MOVE_X, -1.0)
|
||||
} else if !p.inputHandler.Keystate(sdl.K_d) && !p.inputHandler.Keystate(sdl.K_a) {
|
||||
p.entityChan <- command.NewCommand(command.MOVE_X, 0.0)
|
||||
}
|
||||
|
||||
// Move Y
|
||||
if p.inputHandler.Keystate(sdl.K_w) {
|
||||
p.entityChan <- command.NewCommand(command.MOVE_Y, 1.0)
|
||||
} else if p.inputHandler.Keystate(sdl.K_s) {
|
||||
p.entityChan <- command.NewCommand(command.MOVE_Y, -1.0)
|
||||
} else if !p.inputHandler.Keystate(sdl.K_w) && !p.inputHandler.Keystate(sdl.K_s) {
|
||||
p.entityChan <- command.NewCommand(command.MOVE_Y, 0.0)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
package sprite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/veandco/go-sdl2/img"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
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
|
||||
}
|
||||
|
||||
func (s *spritesheet) Cleanup() {
|
||||
// Clean up image
|
||||
defer func() {
|
||||
if s.surface != nil {
|
||||
sdl.Do(func() {
|
||||
s.surface.Free()
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
// Clean up spritesheet
|
||||
defer func() {
|
||||
if s.texture != nil {
|
||||
sdl.Do(func() {
|
||||
err := s.texture.Destroy()
|
||||
if err != nil {
|
||||
log.Printf("error destroying spritesheet %v: %v\n", s.filename, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *spritesheet) Draw(
|
||||
section *sdl.Rect,
|
||||
placement *sdl.Rect,
|
||||
angle float64,
|
||||
center *sdl.Point,
|
||||
flip sdl.RendererFlip,
|
||||
) error {
|
||||
|
||||
var err error
|
||||
sdl.Do(func() {
|
||||
err = s.renderer.CopyEx(s.texture, section, placement, angle, center, flip)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *spritesheet) Bounds() *sdl.Point {
|
||||
p := sdl.Point{
|
||||
X: s.surface.W,
|
||||
Y: s.surface.H,
|
||||
}
|
||||
return &p
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
package sprite
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
const (
|
||||
PENGUIN = "assets/images/penguin.png"
|
||||
PERCYWALKING = "assets/images/percywalking.png"
|
||||
PLATFORMER_FOREST_CHARACTERS = "assets/images/a-platformer-in-the-forest/characters.png"
|
||||
)
|
||||
|
||||
var fileList []string = []string{
|
||||
PENGUIN,
|
||||
PERCYWALKING,
|
||||
PLATFORMER_FOREST_CHARACTERS,
|
||||
}
|
||||
|
||||
var (
|
||||
spriteCache map[string]*spritesheet
|
||||
defaultSprite *spritesheet
|
||||
)
|
||||
|
||||
func InitSpriteCache(renderer *sdl.Renderer) error {
|
||||
var err error
|
||||
|
||||
defaultSprite, err = createDefaultSprite(renderer)
|
||||
if err != nil {
|
||||
log.Printf("failed to create DefaultSprite: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
spriteCache = make(map[string]*spritesheet)
|
||||
|
||||
for _, filename := range fileList {
|
||||
s, err := NewSprite(renderer, filename)
|
||||
if err != nil {
|
||||
log.Printf("error creating sprite %v, using DefaultSprite: %v", filename, err)
|
||||
spriteCache[filename] = defaultSprite
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func CleanupSpriteCache() {
|
||||
for _, v := range spriteCache {
|
||||
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{
|
||||
renderer: renderer,
|
||||
texture: texture,
|
||||
surface: surface,
|
||||
}
|
||||
|
||||
return &s, nil
|
||||
}
|
|
@ -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]
|
||||
}
|
|
@ -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()
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package physics
|
||||
|
||||
import rl "github.com/gen2brain/raylib-go/raylib"
|
||||
|
||||
// Object defines the interface for physical objects.
|
||||
//
|
||||
// This is defined here in the object.go file because after
|
||||
// passing an 'object' to an 'entity' (e.g. 'penguin'),
|
||||
// then to a 'player', as well as the main function,
|
||||
// we needed almost all of the functions for the whole
|
||||
// interface defined in each place.
|
||||
type Object interface {
|
||||
Update() error
|
||||
|
||||
GetPosition() rl.Vector2
|
||||
GetVelocity() rl.Vector2
|
||||
GetAcceleration() rl.Vector2
|
||||
GetBaseAcceleration() rl.Vector2
|
||||
|
||||
SetPosition(pos rl.Vector2)
|
||||
SetVelocity(vel rl.Vector2)
|
||||
SetAcceleration(acc rl.Vector2)
|
||||
SetBaseAcceleration(base rl.Vector2)
|
||||
}
|
||||
|
||||
type object struct {
|
||||
position rl.Vector2
|
||||
velocity rl.Vector2
|
||||
acceleration rl.Vector2
|
||||
baseAcceleration rl.Vector2 // some default speed for this object
|
||||
}
|
||||
|
||||
func NewObject() *object {
|
||||
pos := rl.Vector2{X: 0.0, Y: 0.0}
|
||||
vel := rl.Vector2{X: 0.0, Y: 0.0}
|
||||
acc := rl.Vector2{X: 0.0, Y: 0.0}
|
||||
base := rl.Vector2{X: 1.0, Y: 1.0}
|
||||
|
||||
return &object{
|
||||
position: pos,
|
||||
velocity: vel,
|
||||
acceleration: acc,
|
||||
baseAcceleration: base,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *object) Update() error {
|
||||
p := o.position
|
||||
v := o.velocity
|
||||
a := o.acceleration
|
||||
|
||||
v = rl.Vector2Add(v, a)
|
||||
o.velocity = v
|
||||
|
||||
p = rl.Vector2Add(p, v)
|
||||
o.position = p
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *object) SetPosition(pos rl.Vector2) {
|
||||
o.position = pos
|
||||
}
|
||||
|
||||
func (o *object) SetVelocity(vel rl.Vector2) {
|
||||
o.velocity = vel
|
||||
}
|
||||
func (o *object) SetAcceleration(acc rl.Vector2) {
|
||||
o.acceleration = acc
|
||||
}
|
||||
|
||||
func (o *object) SetBaseAcceleration(base rl.Vector2) {
|
||||
o.baseAcceleration = base
|
||||
}
|
||||
|
||||
func (o *object) GetPosition() rl.Vector2 {
|
||||
return o.position
|
||||
}
|
||||
func (o *object) GetVelocity() rl.Vector2 {
|
||||
return o.velocity
|
||||
}
|
||||
func (o *object) GetAcceleration() rl.Vector2 {
|
||||
return o.acceleration
|
||||
}
|
||||
func (o *object) GetBaseAcceleration() rl.Vector2 {
|
||||
return o.baseAcceleration
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package player
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/channels"
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
type PhysicalObject interface {
|
||||
SetVelocity(vel rl.Vector2)
|
||||
}
|
||||
|
||||
// A player represents a collection of stuff controlled by the user's input.
|
||||
// It contains the camera used to view the world,
|
||||
// the viewport for the part of the screen to draw to (splitscreen support),
|
||||
// as well as the entity the player is currently controlling.
|
||||
type player struct {
|
||||
controlledObject PhysicalObject
|
||||
ctx context.Context
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func NewPlayer(ctx context.Context) *player {
|
||||
defaultTimeout := time.Second
|
||||
|
||||
p := player{
|
||||
ctx: ctx,
|
||||
timeout: defaultTimeout,
|
||||
}
|
||||
return &p
|
||||
}
|
||||
|
||||
func (p *player) SetControlledObject(c PhysicalObject) {
|
||||
p.controlledObject = c
|
||||
}
|
||||
|
||||
func (p *player) Update() {
|
||||
var (
|
||||
up bool
|
||||
down bool
|
||||
left bool
|
||||
right bool
|
||||
fast bool
|
||||
)
|
||||
channels.RL.Do(func() {
|
||||
fast = rl.IsKeyDown(rl.KeyLeftShift)
|
||||
left = rl.IsKeyDown(rl.KeyA) || rl.IsKeyDown(rl.KeyLeft)
|
||||
right = rl.IsKeyDown(rl.KeyD) || rl.IsKeyDown(rl.KeyRight)
|
||||
up = rl.IsKeyDown(rl.KeyW) || rl.IsKeyDown(rl.KeyUp)
|
||||
down = rl.IsKeyDown(rl.KeyS) || rl.IsKeyDown(rl.KeyDown)
|
||||
})
|
||||
|
||||
baseVel := rl.Vector2{X: 1.0, Y: 1.0}
|
||||
velocity := rl.Vector2{X: 0.0, Y: 0.0}
|
||||
|
||||
// Move horizontal
|
||||
if right {
|
||||
velocity.X = baseVel.X
|
||||
} else if left {
|
||||
velocity.X = baseVel.X * -1
|
||||
}
|
||||
|
||||
// Move vertical
|
||||
if up {
|
||||
velocity.Y = baseVel.Y
|
||||
} else if down {
|
||||
velocity.Y = baseVel.Y * -1
|
||||
}
|
||||
|
||||
// If we're moving diagonal, normalize the vector to avoid weird diagonal super-speed
|
||||
if velocity.X != 0 || velocity.Y != 0 {
|
||||
velocity = rl.Vector2Normalize(velocity)
|
||||
}
|
||||
|
||||
// Faster speed
|
||||
if fast {
|
||||
velocity.X *= 2
|
||||
velocity.Y *= 2
|
||||
}
|
||||
|
||||
p.controlledObject.SetVelocity(velocity)
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package sprite
|
||||
|
||||
const (
|
||||
Penguin = "assets/images/penguin.png"
|
||||
PercyWalking = "assets/images/percywalking.png"
|
||||
PlatformerForestCharacters = "assets/images/a-platformer-in-the-forest/characters.png"
|
||||
DelilahWalking = "assets/images/Delilah_walking.png"
|
||||
KingWalking = "assets/images/King_walking.png"
|
||||
)
|
||||
|
||||
var SpriteFileList []string = []string{
|
||||
Penguin,
|
||||
PercyWalking,
|
||||
PlatformerForestCharacters,
|
||||
DelilahWalking,
|
||||
KingWalking,
|
||||
}
|
|
@ -3,80 +3,86 @@ 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 {
|
||||
) spriteAnimation {
|
||||
|
||||
spritesheet := GetSpritesheet(filename)
|
||||
|
||||
a := spriteAnimation{
|
||||
return spriteAnimation{
|
||||
spritesheet: spritesheet,
|
||||
dimensions: dimensions,
|
||||
offset: offset,
|
||||
length: length,
|
||||
border: border,
|
||||
}
|
||||
|
||||
return &a
|
||||
}
|
||||
|
||||
func (a *spriteAnimation) Draw(
|
||||
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,
|
||||
}
|
||||
|
||||
err := a.checkBounds(§ion)
|
||||
if flip.X > 0 {
|
||||
section.Width *= -1
|
||||
}
|
||||
if flip.Y > 0 {
|
||||
section.Height *= -1
|
||||
}
|
||||
|
||||
err := a.checkBounds(section)
|
||||
if err != nil {
|
||||
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(§ion, &placement, angle, center, flip)
|
||||
err = a.spritesheet.Draw(section, placement, angle, center, color)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -84,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 {
|
||||
|
@ -95,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
|
|
@ -0,0 +1,55 @@
|
|||
package sprite
|
||||
|
||||
import (
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/channels"
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
type spritesheet struct {
|
||||
filename string
|
||||
texture rl.Texture2D
|
||||
}
|
||||
|
||||
func NewSprite(filename string) *spritesheet {
|
||||
var texture rl.Texture2D
|
||||
channels.RL.Do(func() {
|
||||
texture = rl.LoadTexture(filename)
|
||||
})
|
||||
|
||||
s := spritesheet{
|
||||
filename: filename,
|
||||
texture: texture,
|
||||
}
|
||||
|
||||
return &s
|
||||
}
|
||||
|
||||
func (s *spritesheet) Cleanup() {
|
||||
channels.RL.Do(func() {
|
||||
// Clean up spritesheet
|
||||
rl.UnloadTexture(s.texture)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *spritesheet) Draw(
|
||||
section rl.Rectangle,
|
||||
placement rl.Rectangle,
|
||||
angle float32,
|
||||
center rl.Vector2,
|
||||
color rl.Color,
|
||||
) error {
|
||||
|
||||
channels.RL.Do(func() {
|
||||
origin := rl.Vector2{}
|
||||
rl.DrawTexturePro(s.texture, section, placement, origin, angle, color)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *spritesheet) Bounds() rl.Vector2 {
|
||||
return rl.Vector2{
|
||||
X: float32(s.texture.Width),
|
||||
Y: float32(s.texture.Height),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package sprite
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
var (
|
||||
spriteCache map[string]*spritesheet
|
||||
)
|
||||
|
||||
func InitSpriteCache() {
|
||||
spriteCache = make(map[string]*spritesheet)
|
||||
|
||||
for _, filename := range SpriteFileList {
|
||||
s := NewSprite(filename)
|
||||
spriteCache[filename] = s
|
||||
}
|
||||
}
|
||||
|
||||
func GetSpritesheet(filename string) *spritesheet {
|
||||
s, exists := spriteCache[filename]
|
||||
if !exists {
|
||||
log.Printf("no sprite found for %v", filename)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func CleanupSpriteCache() {
|
||||
for _, v := range spriteCache {
|
||||
defer v.Cleanup()
|
||||
}
|
||||
}
|
|
@ -1,34 +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) Normalize() {
|
||||
length := math.Hypot(v.X, v.Y)
|
||||
if length == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
v.X = v.X / length
|
||||
v.Y = v.Y / length
|
||||
}
|
||||
|
||||
func (v *Vec2F) Dot(other *Vec2F) float64 {
|
||||
return v.X*other.X + v.Y*other.Y
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package version
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Thanks to the following blog post for how to use the linker flags
|
||||
// to pass in build-time variables.
|
||||
//
|
||||
// https://belief-driven-design.com/build-time-variables-in-go-51439b26ef9/
|
||||
|
||||
var (
|
||||
Version = "dev"
|
||||
CommitHash = "n/a"
|
||||
BuildTimestamp = "n/a"
|
||||
)
|
||||
|
||||
func BuildVersion() string {
|
||||
return fmt.Sprintf("%s %s %s", Version, CommitHash, BuildTimestamp)
|
||||
}
|
93
main.go
93
main.go
|
@ -1,44 +1,43 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/config"
|
||||
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game"
|
||||
"gitea.wisellama.rocks/Wisellama/gosimpleconf"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal"
|
||||
"git.wisellama.rocks/Project-Ely/project-ely/internal/channels"
|
||||
"git.wisellama.rocks/Wisellama/gopackagebase"
|
||||
"git.wisellama.rocks/Wisellama/gosimpleconf"
|
||||
)
|
||||
|
||||
const (
|
||||
configFile = "project-ely.conf"
|
||||
)
|
||||
|
||||
var defaultConfig gosimpleconf.ConfigMap = gosimpleconf.ConfigMap{
|
||||
"game.title": "Project Ely",
|
||||
"game.framerate": "60",
|
||||
"log.utcTime": "false",
|
||||
"log.writeToFile": "false",
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Setup some initial context for gracefully killing
|
||||
// the program with system interrupt signals like ctrl+c
|
||||
// https://pace.dev/blog/2020/02/17/repond-to-ctrl-c-interrupt-signals-gracefully-with-context-in-golang-by-mat-ryer.html
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, os.Interrupt)
|
||||
defer func() {
|
||||
signal.Stop(signalChan)
|
||||
cancel()
|
||||
}()
|
||||
go func() {
|
||||
select {
|
||||
case <-signalChan:
|
||||
// Graceful exit on ctrl+c
|
||||
log.Printf("Attempting to exit gracefully...\n")
|
||||
cancel()
|
||||
case <-ctx.Done():
|
||||
}
|
||||
<-signalChan // Hard exit on second ctrl+c
|
||||
log.Printf("Hard kill\n")
|
||||
os.Exit(2)
|
||||
}()
|
||||
var err error
|
||||
|
||||
baseConfig, err := gopackagebase.Initialize(configFile, defaultConfig)
|
||||
if err != nil {
|
||||
log.Fatalf("error initializing: %v", err)
|
||||
}
|
||||
if baseConfig == nil {
|
||||
log.Fatalf("baseConfig was nil")
|
||||
}
|
||||
defer baseConfig.Cancel()
|
||||
|
||||
// Run the program
|
||||
err := run(ctx)
|
||||
log.Printf("=== Starting %v ===", baseConfig.ConfigMap["game.title"])
|
||||
channels.RL.Main(func() {
|
||||
err = internal.Run(baseConfig.Ctx, baseConfig.ConfigMap)
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("%v\n", err)
|
||||
os.Exit(1)
|
||||
|
@ -46,37 +45,3 @@ func main() {
|
|||
|
||||
log.Printf("Exited gracefully!\n")
|
||||
}
|
||||
|
||||
func run(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
// Read configuration
|
||||
configMap, err := config.Configure("game.conf")
|
||||
if err != nil {
|
||||
log.Fatalf("error during configure: %v\n", err)
|
||||
}
|
||||
|
||||
// Setup logging
|
||||
writeToFile := gosimpleconf.Bool(configMap["log.writeToFile"])
|
||||
logFilename := configMap["log.file"]
|
||||
logWriter, err := config.SetupLogging(writeToFile, logFilename)
|
||||
if err != nil {
|
||||
log.Fatalf("error setting up logging: %v\n", err)
|
||||
}
|
||||
defer logWriter.Cleanup()
|
||||
|
||||
// Setup Random Number Generator seed
|
||||
config.InitRNG()
|
||||
|
||||
// Start everything with the SDL goroutine context
|
||||
log.Printf("=== Starting %v ===", configMap["game.title"])
|
||||
sdl.Main(func() {
|
||||
err = game.Run(ctx, configMap)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
d=$(cd $(dirname $0) && pwd -P)
|
||||
|
||||
. "${d}/common-release-build.sh"
|
||||
|
||||
build $(uname) $(uname -m)
|
|
@ -1,5 +1,7 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
. common-build-windows.sh
|
||||
d=$(cd $(dirname $0) && pwd -P)
|
||||
|
||||
. "${d}/common-release-build.sh"
|
||||
|
||||
build windows 386
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
. common-release-build.sh
|
||||
|
||||
build windows amd64
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
d=$(cd $(dirname $0) && pwd -P)
|
||||
|
||||
. "${d}/common-release-build.sh"
|
||||
|
||||
build windows amd64
|
|
@ -5,26 +5,38 @@ build(){
|
|||
OS=$1
|
||||
ARCH=$2
|
||||
|
||||
if [ -z $NAME ]
|
||||
then
|
||||
NAME=output
|
||||
fi
|
||||
# Lowercase
|
||||
OS=$(echo "$OS" | awk '{ print tolower($0) }')
|
||||
ARCH=$(echo "$ARCH" | awk '{ print tolower($0) }')
|
||||
|
||||
if [ -z "${CC}" ]
|
||||
then
|
||||
export CC=gcc
|
||||
fi
|
||||
|
||||
LDFLAGS_WIN=""
|
||||
# Add .exe to Windows output
|
||||
EXE=""
|
||||
if [ "${OS}" == "windows" ]
|
||||
if [ "${OS}" = "windows" ]
|
||||
then
|
||||
# Hide the windows console popup for SDL
|
||||
LDFLAGS_WIN="-H windowsgui"
|
||||
EXE=".exe"
|
||||
fi
|
||||
|
||||
OUTPUT="${NAME}-${OS}-${ARCH}${EXE}"
|
||||
if [ "${ARCH}" = "x86_64" ]
|
||||
then
|
||||
ARCH="amd64"
|
||||
fi
|
||||
|
||||
PACKAGE="$(grep module go.mod | awk '{print $2}')"
|
||||
TAG="$(git describe --tags --abbrev=0)"
|
||||
VERSION="$(echo ${TAG} | awk -F '-' '{print $NF}')"
|
||||
COMMIT_HASH="$(git rev-parse --short HEAD)"
|
||||
BUILD_TIMESTAMP="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||
|
||||
OUTPUT="${TAG}-${OS}-${ARCH}${EXE}"
|
||||
|
||||
LDFLAGS="${LDFLAGS} -X '${PACKAGE}/internal/version.Version=${VERSION}'"
|
||||
LDFLAGS="${LDFLAGS} -X '${PACKAGE}/internal/version.CommitHash=${COMMIT_HASH}'"
|
||||
LDFLAGS="${LDFLAGS} -X '${PACKAGE}/internal/version.BuildTimestamp=${BUILD_TIMESTAMP}'"
|
||||
|
||||
export CGO_ENABLED=1
|
||||
export CC="${CC}"
|
||||
|
@ -32,7 +44,6 @@ build(){
|
|||
export GOARCH=$ARCH
|
||||
go build -x -v \
|
||||
-trimpath \
|
||||
-tags static \
|
||||
-ldflags="-s -w ${LDFLAGS_WIN}" \
|
||||
-ldflags="-s -w ${LDFLAGS}" \
|
||||
-o "${OUTPUT}"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue