Compare commits

...

13 Commits

32 changed files with 157 additions and 219 deletions

View File

@@ -8,5 +8,5 @@ rustflags = [
# "-C", "link-args=-sALLOW_MEMORY_GROWTH=1", # "-C", "link-args=-sALLOW_MEMORY_GROWTH=1",
"-C", "link-args=-sUSE_SDL=2 -sUSE_SDL_IMAGE=2 -sUSE_SDL_MIXER=2 -sUSE_OGG=1 -sUSE_SDL_GFX=2 -sUSE_SDL_TTF=2 -sSDL2_IMAGE_FORMATS=['png']", "-C", "link-args=-sUSE_SDL=2 -sUSE_SDL_IMAGE=2 -sUSE_SDL_MIXER=2 -sUSE_OGG=1 -sUSE_SDL_GFX=2 -sUSE_SDL_TTF=2 -sSDL2_IMAGE_FORMATS=['png']",
# USE_OGG, USE_VORBIS for OGG/VORBIS usage # USE_OGG, USE_VORBIS for OGG/VORBIS usage
"-C", "link-args=--preload-file assets/", "-C", "link-args=--preload-file assets/game/",
] ]

View File

@@ -9,7 +9,78 @@ env:
RUST_TOOLCHAIN: 1.86.0 RUST_TOOLCHAIN: 1.86.0
jobs: jobs:
build:
name: Build (${{ matrix.target }})
env:
VCPKG_SYSTEM_LIBRARIES: "OFF"
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
artifact_name: pacman
- os: macos-13
target: x86_64-apple-darwin
artifact_name: pacman
- os: macos-latest
target: aarch64-apple-darwin
artifact_name: pacman
- os: windows-latest
target: x86_64-pc-windows-gnu
artifact_name: pacman.exe
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Rust Toolchain
uses: dtolnay/rust-toolchain@master
with:
target: ${{ matrix.target }}
toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
- name: Cache vcpkg
uses: actions/cache@v4
with:
path: target/vcpkg
key: ${{ runner.os }}-vcpkg-${{ hashFiles('Cargo.toml', 'Cargo.lock') }}
restore-keys: |
${{ runner.os }}-vcpkg-
- name: Vcpkg Linux Dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y build-essential gettext libltdl-dev
- name: Vcpkg
run: |
cargo install cargo-vcpkg
cargo vcpkg -v build
- name: Build
run: cargo build --release
- name: Acquire Package Version
shell: bash
run: |
PACKAGE_VERSION=$(cargo metadata --format-version 1 --no-deps | jq '.packages[0].version' -r)
echo "PACKAGE_VERSION=${PACKAGE_VERSION}" >> $GITHUB_ENV
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: "pacman-${{ env.PACKAGE_VERSION }}-${{ matrix.target }}"
path: ./target/release/${{ matrix.artifact_name }}
retention-days: 7
if-no-files-found: error
wasm: wasm:
name: Build (wasm32-unknown-emscripten)
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
pages: write pages: write
@@ -23,6 +94,7 @@ jobs:
uses: mymindstorm/setup-emsdk@v14 uses: mymindstorm/setup-emsdk@v14
with: with:
version: 3.1.43 version: 3.1.43
actions-cache-folder: "emsdk-cache"
- name: Setup Rust (WASM32 Emscripten) - name: Setup Rust (WASM32 Emscripten)
uses: dtolnay/rust-toolchain@master uses: dtolnay/rust-toolchain@master
@@ -39,8 +111,21 @@ jobs:
version: 8 version: 8
run_install: true run_install: true
- name: Build - name: Build with Emscripten
run: ./build.sh -er # release mode, skip emsdk run: |
cargo build --target=wasm32-unknown-emscripten --release
- name: Assemble
run: |
echo "Generating CSS"
pnpx postcss-cli ./assets/site/styles.scss -o ./assets/site/build.css
echo "Copying WASM files"
mkdir -p dist
cp assets/site/{build.css,favicon.ico,index.html} dist
output_folder="target/wasm32-unknown-emscripten/release"
cp $output_folder/pacman.{wasm,js,data} dist
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-pages-artifact@v3 uses: actions/upload-pages-artifact@v3
@@ -50,137 +135,3 @@ jobs:
- name: Deploy - name: Deploy
uses: actions/deploy-pages@v4 uses: actions/deploy-pages@v4
linux:
runs-on: ubuntu-latest
env:
TARGET: x86_64-unknown-linux-gnu
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Rust Toolchain (Linux)
uses: dtolnay/rust-toolchain@master
with:
target: ${{ env.TARGET }}
toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
- name: Vcpkg
run: |
cargo install cargo-vcpkg
cargo vcpkg -v build
- name: Build
run: cargo build --release
- name: Install Cargo Binstall
uses: cargo-bins/cargo-binstall@main
- name: Acquire Package Version
run: |
cargo binstall toml-cli -y
PACKAGE_VERSION=$(toml get ./Cargo.toml package.version --raw)
echo "PACKAGE_VERSION=${PACKAGE_VERSION}" >> $GITHUB_ENV
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: "pacman-${{ env.PACKAGE_VERSION }}-${{ env.TARGET }}"
path: ./target/release/pacman
retention-days: 7
if-no-files-found: error
macos:
runs-on: macos-13
env:
TARGET: x86_64-apple-darwin
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Rust Toolchain (MacOS)
uses: dtolnay/rust-toolchain@master
with:
target: ${{ env.TARGET }}
toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
- name: Vcpkg
run: |
cargo install cargo-vcpkg
cargo vcpkg build
- name: Build
run: cargo build --release
- name: Install Cargo Binstall
uses: cargo-bins/cargo-binstall@main
- name: Acquire Package Version
run: |
cargo binstall toml-cli -y
PACKAGE_VERSION=$(toml get ./Cargo.toml package.version --raw)
echo "PACKAGE_VERSION=${PACKAGE_VERSION}" >> $GITHUB_ENV
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: "pacman-${{ env.PACKAGE_VERSION }}-${{ env.TARGET }}"
path: ./target/release/pacman
retention-days: 7
if-no-files-found: error
windows:
env:
TARGET: x86_64-pc-windows-gnu
SDL2: 2.30.2
SDL2_TTF: 2.22.0
SDL2_MIXER: 2.8.0
SDL2_IMAGE: 2.8.2
# SDL2_GFX: 1.0.4
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Rust (Windows)
uses: dtolnay/rust-toolchain@master
with:
target: ${{ env.TARGET }}
toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
- name: Vcpkg
run: |
cargo install cargo-vcpkg
cargo vcpkg build
- name: Build
run: cargo build --release
- name: Install Cargo Binstall
uses: cargo-bins/cargo-binstall@main
- name: Acquire Package Version
run: |
cargo binstall toml-cli -y
PACKAGE_VERSION=$(toml get ./Cargo.toml package.version --raw)
echo "PACKAGE_VERSION=${PACKAGE_VERSION}" >> $env:GITHUB_ENV
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: "pacman-${{ env.PACKAGE_VERSION }}-${{ env.TARGET }}"
path: ./target/release/pacman.exe
retention-days: 7
if-no-files-found: error

4
.gitignore vendored
View File

@@ -3,5 +3,5 @@
.idea .idea
*.dll *.dll
rust-sdl2-emscripten/ rust-sdl2-emscripten/
assets/build.css assets/site/build.css
emsdk/ emsdk/

View File

@@ -1,7 +0,0 @@
# Building Pac-Man
## GitHub Actions Workflow
1. Build workflow produces executables & WASM files for all platforms
2. Uploaded as artifacts
3. Deployment workflow downloads artifacts and uploads to GitHub Pages

View File

@@ -41,7 +41,8 @@ rev = "2024.05.24" # release 2024.05.24 # to check for a new one, check https://
[package.metadata.vcpkg.target] [package.metadata.vcpkg.target]
x86_64-pc-windows-msvc = { triplet = "x64-windows-static-md" } x86_64-pc-windows-msvc = { triplet = "x64-windows-static-md" }
stable-x86_64-unknown-linux-gnu = { triplet = "x86_64-unknown-linux-gnu" } x86_64-unknown-linux-gnu = { triplet = "x64-linux" }
# x86_64-apple-darwin = { triplet = "x64-osx-release" }
[target.'cfg(target_os = "emscripten")'.dependencies] [target.'cfg(target_os = "emscripten")'.dependencies]
libc = "0.2.16" libc = "0.2.16"

View File

@@ -1,35 +0,0 @@
# Implementation
A document detailing the implementation the project from rendering, to game logic, to build systems.
## Rendering
1. Map
- May require procedural text generation later on (cacheable?)
2. Pacman
3. Ghosts
- Requires colors
4. Items
5. Interface
- Requires fonts
## Grid System
1. How does the grid system work?
The grid is 28 x 36 (although, the map texture is 28 x 37), and each cell is 24x24 (pixels).
Many of the walls in the map texture only occupy a portion of the cell, so some items are able to render across multiple cells.
24x24 assets include pellets, the energizer, and the map itself ()
2. What constraints must be enforced on Ghosts and PacMan?
3. How do movement transitions work?
All entities store a precise position, and a direction. This position is only used for animation, rendering, and collision purposes. Otherwise, a separate 'cell position' (which is 24 times less precise, owing to the fact that it is based on the entity's position within the grid).
When an entity is transitioning between cells, movement directions are acknowledged, but won't take effect until the next cell has been entered completely.
4. Between transitions, how does collision detection work?
It appears the original implementation used cell-level detection.
I worry this may be prone to division errors. Make sure to use rounding (50% >=).

View File

Before

Width:  |  Height:  |  Size: 174 B

After

Width:  |  Height:  |  Size: 174 B

View File

Before

Width:  |  Height:  |  Size: 158 B

After

Width:  |  Height:  |  Size: 158 B

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 528 B

After

Width:  |  Height:  |  Size: 528 B

View File

Before

Width:  |  Height:  |  Size: 394 B

After

Width:  |  Height:  |  Size: 394 B

View File

Before

Width:  |  Height:  |  Size: 228 B

After

Width:  |  Height:  |  Size: 228 B

View File

Before

Width:  |  Height:  |  Size: 370 B

After

Width:  |  Height:  |  Size: 370 B

View File

Before

Width:  |  Height:  |  Size: 90 B

After

Width:  |  Height:  |  Size: 90 B

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

View File

View File

View File

23
assets/site/build.css Normal file
View File

@@ -0,0 +1,23 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@font-face {
font-family: "Liberation Mono";
src:
url("LiberationMono.woff2") format("woff2"),
url("LiberationMono.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
}
canvas {
@apply w-full h-[65vh] min-h-[200px] block mx-auto bg-black;
}
.code {
@apply px-1 rounded font-mono bg-zinc-900 border border-zinc-700 lowercase;
}
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0eWxlcy5zY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBIiwiZmlsZSI6ImJ1aWxkLmNzcyIsInNvdXJjZXNDb250ZW50IjpbIkB0YWlsd2luZCBiYXNlO1xuQHRhaWx3aW5kIGNvbXBvbmVudHM7XG5AdGFpbHdpbmQgdXRpbGl0aWVzO1xuXG5AZm9udC1mYWNlIHtcbiAgICBmb250LWZhbWlseTogXCJMaWJlcmF0aW9uIE1vbm9cIjtcbiAgICBzcmM6XG4gICAgICAgIHVybChcIkxpYmVyYXRpb25Nb25vLndvZmYyXCIpIGZvcm1hdChcIndvZmYyXCIpLFxuICAgICAgICB1cmwoXCJMaWJlcmF0aW9uTW9uby53b2ZmXCIpIGZvcm1hdChcIndvZmZcIik7XG4gICAgZm9udC13ZWlnaHQ6IG5vcm1hbDtcbiAgICBmb250LXN0eWxlOiBub3JtYWw7XG4gICAgZm9udC1kaXNwbGF5OiBzd2FwO1xufVxuXG5jYW52YXMge1xuICAgIEBhcHBseSB3LWZ1bGwgaC1bNjV2aF0gbWluLWgtWzIwMHB4XSBibG9jayBteC1hdXRvIGJnLWJsYWNrO1xufVxuXG4uY29kZSB7XG4gICAgQGFwcGx5IHB4LTEgcm91bmRlZCBmb250LW1vbm8gYmctemluYy05MDAgYm9yZGVyIGJvcmRlci16aW5jLTcwMCBsb3dlcmNhc2U7XG59XG4iXX0= */

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -52,20 +52,18 @@ else
fi fi
echo "Generating CSS" echo "Generating CSS"
pnpx postcss-cli ./assets/styles.scss -o ./assets/build.css pnpx postcss-cli ./assets/site/styles.scss -o ./assets/site/build.css
echo "Copying WASM files" echo "Copying WASM files"
mkdir -p dist mkdir -p dist
output_folder="target/wasm32-unknown-emscripten/$build_type" output_folder="target/wasm32-unknown-emscripten/$build_type"
cp assets/index.html dist
# cp assets/*.woff* dist cp assets/site/{build.css,favicon.ico,index.html} dist
cp assets/build.css dist cp $output_folder/pacman.{wasm,js} dist
cp assets/favicon.ico dist if [ -f $output_folder/deps/pacman.data ]; then
cp $output_folder/pacman.wasm dist cp $output_folder/deps/pacman.data dist
cp $output_folder/pacman.js dist fi
# only if .data file exists
cp $output_folder/deps/pacman.data dist
if [ -f $output_folder/pacman.wasm.map ]; then if [ -f $output_folder/pacman.wasm.map ]; then
cp $output_folder/pacman.wasm.map dist cp $output_folder/pacman.wasm.map dist
fi fi

View File

@@ -56,40 +56,47 @@ async function setupEmscripten() {
async function buildWeb(release: boolean) { async function buildWeb(release: boolean) {
console.log("Building WASM with Emscripten..."); console.log("Building WASM with Emscripten...");
const rustcFlags = [
"-C",
"link-arg=--preload-file",
"-C",
"link-arg=assets",
].join(" ");
if (release) { if (release) {
await $`cargo build --target=wasm32-unknown-emscripten --release`; await $`env RUSTFLAGS=${rustcFlags} cargo build --target=wasm32-unknown-emscripten --release`;
} else { } else {
await $`cargo build --target=wasm32-unknown-emscripten`; await $`env RUSTFLAGS=${rustcFlags} cargo build --target=wasm32-unknown-emscripten`;
} }
console.log("Generating CSS..."); console.log("Generating CSS...");
await $`pnpx postcss-cli ./assets/styles.scss -o ./assets/build.css`; await $`pnpx postcss-cli ./assets/site/styles.scss -o ./assets/site/build.css`;
console.log("Copying WASM files..."); console.log("Copying WASM files...");
const buildType = release ? "release" : "debug"; const buildType = release ? "release" : "debug";
const outputFolder = `target/wasm32-unknown-emscripten/${buildType}`; const outputFolder = `target/wasm32-unknown-emscripten/${buildType}`;
await $`mkdir -p dist`; await $`mkdir -p dist`;
await $`cp assets/index.html dist`; await $`cp assets/site/index.html dist`;
await $`cp assets/*.woff* dist`; await $`cp assets/site/*.woff* dist`;
await $`cp assets/build.css dist`; await $`cp assets/site/build.css dist`;
await $`cp assets/favicon.ico dist`; await $`cp assets/site/favicon.ico dist`;
await $`cp ${outputFolder}/spiritus.wasm dist`; await $`cp ${outputFolder}/pacman.wasm dist`;
await $`cp ${outputFolder}/spiritus.js dist`; await $`cp ${outputFolder}/pacman.js dist`;
// Check if .data file exists before copying // Check if .data file exists before copying
try { try {
await fs.access(`${outputFolder}/deps/spiritus.data`); await fs.access(`${outputFolder}/pacman.data`);
await $`cp ${outputFolder}/deps/spiritus.data dist`; await $`cp ${outputFolder}/pacman.data dist`;
} catch (e) { } catch (e) {
console.log("No spiritus.data file found, skipping copy."); console.log("No pacman.data file found, skipping copy.");
} }
// Check if .map file exists before copying // Check if .map file exists before copying
try { try {
await fs.access(`${outputFolder}/spiritus.wasm.map`); await fs.access(`${outputFolder}/pacman.wasm.map`);
await $`cp ${outputFolder}/spiritus.wasm.map dist`; await $`cp ${outputFolder}/pacman.wasm.map dist`;
} catch (e) { } catch (e) {
console.log("No spiritus.wasm.map file found, skipping copy."); console.log("No pacman.wasm.map file found, skipping copy.");
} }
console.log("WASM files copied."); console.log("WASM files copied.");

View File

@@ -56,17 +56,17 @@ mod imp {
macro_rules! asset_bytes_enum { macro_rules! asset_bytes_enum {
( $asset:expr ) => { ( $asset:expr ) => {
match $asset { match $asset {
Asset::Wav1 => Cow::Borrowed(include_bytes!("../assets/wav/1.ogg")), Asset::Wav1 => Cow::Borrowed(include_bytes!("../assets/game/wav/1.ogg")),
Asset::Wav2 => Cow::Borrowed(include_bytes!("../assets/wav/2.ogg")), Asset::Wav2 => Cow::Borrowed(include_bytes!("../assets/game/wav/2.ogg")),
Asset::Wav3 => Cow::Borrowed(include_bytes!("../assets/wav/3.ogg")), Asset::Wav3 => Cow::Borrowed(include_bytes!("../assets/game/wav/3.ogg")),
Asset::Wav4 => Cow::Borrowed(include_bytes!("../assets/wav/4.ogg")), Asset::Wav4 => Cow::Borrowed(include_bytes!("../assets/game/wav/4.ogg")),
Asset::Pacman => Cow::Borrowed(include_bytes!("../assets/32/pacman.png")), Asset::Pacman => Cow::Borrowed(include_bytes!("../assets/game/32/pacman.png")),
Asset::Pellet => Cow::Borrowed(include_bytes!("../assets/24/pellet.png")), Asset::Pellet => Cow::Borrowed(include_bytes!("../assets/game/24/pellet.png")),
Asset::Energizer => Cow::Borrowed(include_bytes!("../assets/24/energizer.png")), Asset::Energizer => Cow::Borrowed(include_bytes!("../assets/game/24/energizer.png")),
Asset::Map => Cow::Borrowed(include_bytes!("../assets/map.png")), Asset::Map => Cow::Borrowed(include_bytes!("../assets/game/map.png")),
Asset::FontKonami => Cow::Borrowed(include_bytes!("../assets/font/konami.ttf")), Asset::FontKonami => Cow::Borrowed(include_bytes!("../assets/game/font/konami.ttf")),
Asset::GhostBody => Cow::Borrowed(include_bytes!("../assets/32/ghost_body.png")), Asset::GhostBody => Cow::Borrowed(include_bytes!("../assets/game/32/ghost_body.png")),
Asset::GhostEyes => Cow::Borrowed(include_bytes!("../assets/32/ghost_eyes.png")), Asset::GhostEyes => Cow::Borrowed(include_bytes!("../assets/game/32/ghost_eyes.png")),
} }
}; };
} }
@@ -81,7 +81,7 @@ mod imp {
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
pub fn get_asset_bytes(asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> { pub fn get_asset_bytes(asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> {
let path = Path::new("assets").join(asset.path()); let path = Path::new("assets/game").join(asset.path());
if !path.exists() { if !path.exists() {
return Err(AssetError::NotFound(asset.path().to_string())); return Err(AssetError::NotFound(asset.path().to_string()));
} }