Add mcrf-init.sh: game project scaffolding without engine recompilation
New workflow for game developers: run mcrf-init to create a project directory with symlinks to a pre-built engine, then just write Python scripts and assets. Games package for distribution (Linux/Windows/WASM) without ever rebuilding the engine. mcrf-init.sh creates: - build/ with symlinked binary and libs, game content in assets/ + scripts/ - build-windows/ (if engine has a Windows build) - Makefile with run, wasm, dist-linux, dist-windows, dist-wasm targets - Starter game.py, .gitignore, pyrightconfig.json, VERSION file CMakeLists.txt: WASM preload paths (assets, scripts) are now configurable via MCRF_ASSETS_DIR / MCRF_SCRIPTS_DIR cache variables, so game project Makefiles can point WASM builds at their own content without modifying the engine. Also adds pyrightconfig.json for the engine repo itself (IDE support via stubs/). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
732897426a
commit
9176dca055
3 changed files with 437 additions and 2 deletions
422
mcrf-init.sh
Executable file
422
mcrf-init.sh
Executable file
|
|
@ -0,0 +1,422 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# mcrf-init.sh - Initialize a McRogueFace game project
|
||||
#
|
||||
# Creates a game project directory with symlinks to a pre-built engine,
|
||||
# so game developers only write Python + assets, never rebuild the engine.
|
||||
#
|
||||
# Usage:
|
||||
# mcrf-init # initialize current directory
|
||||
# mcrf-init my-game # create and initialize my-game/
|
||||
#
|
||||
# Setup (add to ~/.bashrc):
|
||||
# alias mcrf-init='/path/to/McRogueFace/mcrf-init.sh'
|
||||
# # or: export PATH="$PATH:/path/to/McRogueFace"
|
||||
#
|
||||
# After init:
|
||||
# make run # run the game
|
||||
# make dist # package for distribution
|
||||
|
||||
set -e
|
||||
|
||||
# --- Resolve engine root (where this script lives) ---
|
||||
ENGINE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# --- Determine game directory ---
|
||||
if [ -n "$1" ]; then
|
||||
GAME_DIR="$(pwd)/$1"
|
||||
mkdir -p "$GAME_DIR"
|
||||
else
|
||||
GAME_DIR="$(pwd)"
|
||||
fi
|
||||
|
||||
# --- Colors ---
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${GREEN}[mcrf]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[mcrf]${NC} $1"; }
|
||||
error() { echo -e "${RED}[mcrf]${NC} $1"; }
|
||||
|
||||
# --- Safety checks ---
|
||||
if [ "$GAME_DIR" = "$ENGINE_ROOT" ]; then
|
||||
error "Cannot init inside the engine directory itself."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$ENGINE_ROOT/build/mcrogueface" ]; then
|
||||
error "Engine not built. Run 'make' in $ENGINE_ROOT first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Engine version for packaging
|
||||
ENGINE_VERSION=$(grep 'MCRFPY_VERSION' "$ENGINE_ROOT/src/McRogueFaceVersion.h" \
|
||||
| sed 's/.*"\(.*\)"/\1/')
|
||||
|
||||
info "Initializing McRogueFace game project"
|
||||
info " Engine: $ENGINE_ROOT (v${ENGINE_VERSION})"
|
||||
info " Game: $GAME_DIR"
|
||||
echo
|
||||
|
||||
# --- Create game directories ---
|
||||
mkdir -p "$GAME_DIR/assets"
|
||||
mkdir -p "$GAME_DIR/scripts"
|
||||
|
||||
# --- Set up build/ (Linux) ---
|
||||
info "Setting up build/ (Linux)..."
|
||||
mkdir -p "$GAME_DIR/build"
|
||||
|
||||
# Binary
|
||||
ln -sfn "$ENGINE_ROOT/build/mcrogueface" "$GAME_DIR/build/mcrogueface"
|
||||
|
||||
# Shared libraries (entire lib/ tree: .so files + Python stdlib + extensions)
|
||||
ln -sfn "$ENGINE_ROOT/build/lib" "$GAME_DIR/build/lib"
|
||||
|
||||
# Game content: symlink to project's own directories
|
||||
ln -sfn ../assets "$GAME_DIR/build/assets"
|
||||
ln -sfn ../scripts "$GAME_DIR/build/scripts"
|
||||
|
||||
# --- Set up build-windows/ (if engine has it) ---
|
||||
if [ -f "$ENGINE_ROOT/build-windows/mcrogueface.exe" ]; then
|
||||
info "Setting up build-windows/..."
|
||||
mkdir -p "$GAME_DIR/build-windows"
|
||||
|
||||
# Executable
|
||||
ln -sfn "$ENGINE_ROOT/build-windows/mcrogueface.exe" "$GAME_DIR/build-windows/mcrogueface.exe"
|
||||
|
||||
# DLLs
|
||||
for dll in "$ENGINE_ROOT/build-windows/"*.dll; do
|
||||
[ -f "$dll" ] && ln -sfn "$dll" "$GAME_DIR/build-windows/$(basename "$dll")"
|
||||
done
|
||||
|
||||
# Python stdlib zip
|
||||
[ -f "$ENGINE_ROOT/build-windows/python314.zip" ] && \
|
||||
ln -sfn "$ENGINE_ROOT/build-windows/python314.zip" "$GAME_DIR/build-windows/python314.zip"
|
||||
|
||||
# Python lib directory
|
||||
[ -d "$ENGINE_ROOT/build-windows/lib" ] && \
|
||||
ln -sfn "$ENGINE_ROOT/build-windows/lib" "$GAME_DIR/build-windows/lib"
|
||||
|
||||
# Game content
|
||||
ln -sfn ../assets "$GAME_DIR/build-windows/assets"
|
||||
ln -sfn ../scripts "$GAME_DIR/build-windows/scripts"
|
||||
else
|
||||
warn "No Windows build found (skipping build-windows/)"
|
||||
fi
|
||||
|
||||
# --- WASM build info ---
|
||||
info "WASM support: use 'make wasm' to build (requires emsdk)"
|
||||
|
||||
# --- Starter game.py ---
|
||||
if [ ! -f "$GAME_DIR/scripts/game.py" ]; then
|
||||
info "Creating starter scripts/game.py..."
|
||||
cat > "$GAME_DIR/scripts/game.py" << 'PYTHON'
|
||||
"""McRogueFace Game - created by mcrf-init"""
|
||||
import mcrfpy
|
||||
|
||||
scene = mcrfpy.Scene("title")
|
||||
ui = scene.children
|
||||
|
||||
ui.append(mcrfpy.Caption(
|
||||
text="My McRogueFace Game",
|
||||
pos=(512, 300),
|
||||
font=mcrfpy.Font("assets/JetbrainsMono.ttf"),
|
||||
fill_color=(255, 255, 255),
|
||||
font_size=48
|
||||
))
|
||||
|
||||
ui.append(mcrfpy.Caption(
|
||||
text="Press ESC to quit",
|
||||
pos=(512, 400),
|
||||
font=mcrfpy.Font("assets/JetbrainsMono.ttf"),
|
||||
fill_color=(180, 180, 180),
|
||||
font_size=24
|
||||
))
|
||||
|
||||
def on_key(key, state):
|
||||
if key == mcrfpy.Key.ESCAPE and state == mcrfpy.InputState.PRESSED:
|
||||
mcrfpy.exit()
|
||||
|
||||
scene.on_key = on_key
|
||||
mcrfpy.current_scene = scene
|
||||
PYTHON
|
||||
fi
|
||||
|
||||
# --- Copy a default font if game assets/ is empty ---
|
||||
if [ -z "$(ls -A "$GAME_DIR/assets/" 2>/dev/null)" ]; then
|
||||
if [ -f "$ENGINE_ROOT/assets/JetbrainsMono.ttf" ]; then
|
||||
info "Copying default font to assets/..."
|
||||
cp "$ENGINE_ROOT/assets/JetbrainsMono.ttf" "$GAME_DIR/assets/"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- .gitignore ---
|
||||
if [ ! -f "$GAME_DIR/.gitignore" ]; then
|
||||
info "Creating .gitignore..."
|
||||
cat > "$GAME_DIR/.gitignore" << 'GITIGNORE'
|
||||
# McRogueFace game project
|
||||
build/
|
||||
build-windows/
|
||||
build-wasm/
|
||||
dist/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.png.bak
|
||||
GITIGNORE
|
||||
fi
|
||||
|
||||
# --- pyrightconfig.json (for Vim/LSP autocomplete) ---
|
||||
if [ ! -f "$GAME_DIR/pyrightconfig.json" ]; then
|
||||
info "Creating pyrightconfig.json (IDE autocomplete)..."
|
||||
cat > "$GAME_DIR/pyrightconfig.json" << PYRIGHT
|
||||
{
|
||||
"include": ["scripts"],
|
||||
"extraPaths": ["${ENGINE_ROOT}/stubs"],
|
||||
"pythonVersion": "3.14",
|
||||
"pythonPlatform": "Linux",
|
||||
"typeCheckingMode": "basic",
|
||||
"reportMissingModuleSource": false
|
||||
}
|
||||
PYRIGHT
|
||||
fi
|
||||
|
||||
# --- Makefile ---
|
||||
info "Creating Makefile..."
|
||||
cat > "$GAME_DIR/Makefile" << MAKEFILE
|
||||
# McRogueFace Game Project Makefile
|
||||
# Generated by mcrf-init
|
||||
#
|
||||
# Engine: ${ENGINE_ROOT}
|
||||
|
||||
ENGINE_ROOT := ${ENGINE_ROOT}
|
||||
GAME_NAME := $(basename "$GAME_DIR")
|
||||
GAME_DIR := \$(shell pwd)
|
||||
|
||||
# Game version - edit this or create a VERSION file
|
||||
VERSION := \$(shell cat VERSION 2>/dev/null || echo "0.1.0")
|
||||
|
||||
# Engine version (from the linked build)
|
||||
ENGINE_VERSION := \$(shell grep 'MCRFPY_VERSION' "\$(ENGINE_ROOT)/src/McRogueFaceVersion.h" \\
|
||||
| sed 's/.*"\\(.*\\)"/\\1/' 2>/dev/null || echo "unknown")
|
||||
|
||||
.PHONY: run run-windows wasm serve-wasm dist dist-linux dist-windows dist-wasm clean-dist clean-wasm info
|
||||
|
||||
# ============================================================
|
||||
# Run targets
|
||||
# ============================================================
|
||||
|
||||
run:
|
||||
@cd build && ./mcrogueface
|
||||
|
||||
run-headless:
|
||||
@cd build && ./mcrogueface --headless --exec ../scripts/game.py
|
||||
|
||||
run-windows:
|
||||
@echo "Launch build-windows/mcrogueface.exe on a Windows machine or via Wine"
|
||||
|
||||
serve-wasm:
|
||||
@if [ ! -f build-wasm/mcrogueface.html ]; then \\
|
||||
echo "No WASM build found. Run 'make wasm' first."; \\
|
||||
exit 1; \\
|
||||
fi
|
||||
@echo "Serving WASM build at http://localhost:8080"
|
||||
@cd build-wasm && python3 -m http.server 8080
|
||||
|
||||
# ============================================================
|
||||
# WASM build (requires emsdk)
|
||||
# ============================================================
|
||||
|
||||
wasm:
|
||||
@if ! command -v emcmake >/dev/null 2>&1; then \\
|
||||
echo "Error: emcmake not found. Activate emsdk first:"; \\
|
||||
echo " source ~/emsdk/emsdk_env.sh"; \\
|
||||
exit 1; \\
|
||||
fi
|
||||
@echo "Building WASM with game assets..."
|
||||
@emcmake cmake -S "\$(ENGINE_ROOT)" -B build-wasm \\
|
||||
-DMCRF_SDL2=ON \\
|
||||
-DMCRF_GAME_SHELL=ON \\
|
||||
-DMCRF_ASSETS_DIR="\$(GAME_DIR)/assets" \\
|
||||
-DMCRF_SCRIPTS_DIR="\$(GAME_DIR)/scripts"
|
||||
@emmake make -C build-wasm -j\$\$(nproc)
|
||||
@echo ""
|
||||
@echo "WASM build complete: build-wasm/"
|
||||
@echo " make serve-wasm - test in browser"
|
||||
@echo " make dist-wasm - package for distribution"
|
||||
|
||||
clean-wasm:
|
||||
@rm -rf build-wasm
|
||||
@echo "Cleaned build-wasm/"
|
||||
|
||||
# ============================================================
|
||||
# Distribution packaging
|
||||
# ============================================================
|
||||
|
||||
dist: dist-linux dist-windows dist-wasm
|
||||
@echo ""
|
||||
@echo "=== Packages ==="
|
||||
@ls -lh dist/ 2>/dev/null
|
||||
|
||||
dist-linux:
|
||||
@if [ ! -f build/mcrogueface ]; then \\
|
||||
echo "Error: build/mcrogueface not found. Run mcrf-init first."; \\
|
||||
exit 1; \\
|
||||
fi
|
||||
@echo "Packaging \$(GAME_NAME) for Linux..."
|
||||
@mkdir -p dist
|
||||
\$(eval PKG := dist/\$(GAME_NAME)-\$(VERSION)-Linux)
|
||||
@rm -rf \$(PKG)
|
||||
@mkdir -p \$(PKG)/lib
|
||||
@# Binary
|
||||
@cp "\$(ENGINE_ROOT)/build/mcrogueface" \$(PKG)/
|
||||
@# Shared libraries
|
||||
@for lib in libpython3.14.so.1.0 \\
|
||||
libsfml-graphics.so.2.6.1 libsfml-window.so.2.6.1 \\
|
||||
libsfml-system.so.2.6.1 libsfml-audio.so.2.6.1 \\
|
||||
libtcod.so; do \\
|
||||
[ -f "\$(ENGINE_ROOT)/__lib/\$\$lib" ] && \\
|
||||
cp "\$(ENGINE_ROOT)/__lib/\$\$lib" \$(PKG)/lib/; \\
|
||||
done
|
||||
@# Library symlinks
|
||||
@cd \$(PKG)/lib && \\
|
||||
ln -sf libpython3.14.so.1.0 libpython3.14.so && \\
|
||||
ln -sf libsfml-graphics.so.2.6.1 libsfml-graphics.so.2.6 && \\
|
||||
ln -sf libsfml-graphics.so.2.6.1 libsfml-graphics.so && \\
|
||||
ln -sf libsfml-window.so.2.6.1 libsfml-window.so.2.6 && \\
|
||||
ln -sf libsfml-window.so.2.6.1 libsfml-window.so && \\
|
||||
ln -sf libsfml-system.so.2.6.1 libsfml-system.so.2.6 && \\
|
||||
ln -sf libsfml-system.so.2.6.1 libsfml-system.so && \\
|
||||
ln -sf libsfml-audio.so.2.6.1 libsfml-audio.so.2.6 && \\
|
||||
ln -sf libsfml-audio.so.2.6.1 libsfml-audio.so && \\
|
||||
ln -sf libtcod.so libtcod.so.2
|
||||
@# Python extension modules
|
||||
@if [ -d "\$(ENGINE_ROOT)/__lib/Python/lib.linux-x86_64-3.14" ]; then \\
|
||||
mkdir -p \$(PKG)/lib/Python/lib.linux-x86_64-3.14; \\
|
||||
for so in "\$(ENGINE_ROOT)/__lib/Python/lib.linux-x86_64-3.14"/*.so; do \\
|
||||
case "\$\$(basename \$\$so)" in \\
|
||||
*test*|xxlimited*|_ctypes_test*|_xxtestfuzz*) continue ;; \\
|
||||
*) cp "\$\$so" \$(PKG)/lib/Python/lib.linux-x86_64-3.14/ ;; \\
|
||||
esac; \\
|
||||
done; \\
|
||||
fi
|
||||
@# Python stdlib
|
||||
@if [ -d "\$(ENGINE_ROOT)/__lib/Python/Lib" ]; then \\
|
||||
mkdir -p \$(PKG)/lib/Python; \\
|
||||
cp -r "\$(ENGINE_ROOT)/__lib/Python/Lib" \$(PKG)/lib/Python/; \\
|
||||
rm -rf \$(PKG)/lib/Python/Lib/test \$(PKG)/lib/Python/Lib/tests \\
|
||||
\$(PKG)/lib/Python/Lib/idlelib \$(PKG)/lib/Python/Lib/tkinter \\
|
||||
\$(PKG)/lib/Python/Lib/turtledemo \$(PKG)/lib/Python/Lib/pydoc_data \\
|
||||
\$(PKG)/lib/Python/Lib/lib2to3 \$(PKG)/lib/Python/Lib/ensurepip \\
|
||||
\$(PKG)/lib/Python/Lib/_pyrepl; \\
|
||||
find \$(PKG)/lib/Python/Lib -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true; \\
|
||||
find \$(PKG)/lib/Python/Lib -name "*.pyc" -delete 2>/dev/null || true; \\
|
||||
find \$(PKG)/lib/Python/Lib -name "test_*.py" -delete 2>/dev/null || true; \\
|
||||
fi
|
||||
@# Game content (real copies, not symlinks)
|
||||
@cp -r assets \$(PKG)/assets
|
||||
@cp -r scripts \$(PKG)/scripts
|
||||
@# Run script
|
||||
@printf '#!/bin/bash\\nDIR="\$\$(cd "\$\$(dirname "\$\$0")" && pwd)"\\nexport LD_LIBRARY_PATH="\$\$DIR/lib:\$\$LD_LIBRARY_PATH"\\nexec "\$\$DIR/mcrogueface" "\$\$@"\\n' > \$(PKG)/run.sh
|
||||
@chmod +x \$(PKG)/run.sh
|
||||
@# Archive
|
||||
@cd dist && tar -czf "\$(GAME_NAME)-\$(VERSION)-Linux.tar.gz" "\$(GAME_NAME)-\$(VERSION)-Linux"
|
||||
@rm -rf \$(PKG)
|
||||
@echo "Created: dist/\$(GAME_NAME)-\$(VERSION)-Linux.tar.gz"
|
||||
|
||||
dist-windows:
|
||||
@if [ ! -f "\$(ENGINE_ROOT)/build-windows/mcrogueface.exe" ]; then \\
|
||||
echo "No Windows build available. Skipping."; \\
|
||||
exit 0; \\
|
||||
fi
|
||||
@echo "Packaging \$(GAME_NAME) for Windows..."
|
||||
@mkdir -p dist
|
||||
\$(eval PKG := dist/\$(GAME_NAME)-\$(VERSION)-Windows)
|
||||
@rm -rf \$(PKG)
|
||||
@mkdir -p \$(PKG)
|
||||
@# Executable and DLLs
|
||||
@cp "\$(ENGINE_ROOT)/build-windows/mcrogueface.exe" \$(PKG)/
|
||||
@for dll in "\$(ENGINE_ROOT)/build-windows/"*.dll; do \\
|
||||
[ -f "\$\$dll" ] && cp "\$\$dll" \$(PKG)/; \\
|
||||
done
|
||||
@# Python stdlib zip
|
||||
@[ -f "\$(ENGINE_ROOT)/build-windows/python314.zip" ] && \\
|
||||
cp "\$(ENGINE_ROOT)/build-windows/python314.zip" \$(PKG)/ || true
|
||||
@# Python lib directory
|
||||
@if [ -d "\$(ENGINE_ROOT)/build-windows/lib" ]; then \\
|
||||
cp -r "\$(ENGINE_ROOT)/build-windows/lib" \$(PKG)/lib; \\
|
||||
fi
|
||||
@# Game content (real copies)
|
||||
@cp -r assets \$(PKG)/assets
|
||||
@cp -r scripts \$(PKG)/scripts
|
||||
@# Archive
|
||||
@cd dist && zip -qr "\$(GAME_NAME)-\$(VERSION)-Windows.zip" "\$(GAME_NAME)-\$(VERSION)-Windows"
|
||||
@rm -rf \$(PKG)
|
||||
@echo "Created: dist/\$(GAME_NAME)-\$(VERSION)-Windows.zip"
|
||||
|
||||
dist-wasm:
|
||||
@if ! command -v emcmake >/dev/null 2>&1; then \\
|
||||
echo "WASM: emsdk not activated. Skipping WASM package."; \\
|
||||
echo " To include WASM: source ~/emsdk/emsdk_env.sh && make dist-wasm"; \\
|
||||
exit 0; \\
|
||||
fi; \\
|
||||
if [ ! -f build-wasm/mcrogueface.html ]; then \\
|
||||
echo "Building WASM first..."; \\
|
||||
\$(MAKE) wasm || exit 1; \\
|
||||
fi; \\
|
||||
echo "Packaging \$(GAME_NAME) for Web..."; \\
|
||||
mkdir -p dist; \\
|
||||
PKG="dist/\$(GAME_NAME)-\$(VERSION)-Web"; \\
|
||||
rm -rf "\$\$PKG"; \\
|
||||
mkdir -p "\$\$PKG"; \\
|
||||
cp build-wasm/mcrogueface.html "\$\$PKG/index.html"; \\
|
||||
cp build-wasm/mcrogueface.js "\$\$PKG/"; \\
|
||||
cp build-wasm/mcrogueface.wasm "\$\$PKG/"; \\
|
||||
cp build-wasm/mcrogueface.data "\$\$PKG/"; \\
|
||||
cd dist && zip -qr "\$(GAME_NAME)-\$(VERSION)-Web.zip" "\$(GAME_NAME)-\$(VERSION)-Web"; \\
|
||||
rm -rf "\$\$PKG"; \\
|
||||
echo "Created: dist/\$(GAME_NAME)-\$(VERSION)-Web.zip"
|
||||
|
||||
clean-dist:
|
||||
@rm -rf dist
|
||||
@echo "Cleaned dist/"
|
||||
|
||||
# ============================================================
|
||||
# Info
|
||||
# ============================================================
|
||||
|
||||
info:
|
||||
@echo "Game: \$(GAME_NAME) v\$(VERSION)"
|
||||
@echo "Engine: McRogueFace v\$(ENGINE_VERSION)"
|
||||
@echo "Root: \$(ENGINE_ROOT)"
|
||||
@echo ""
|
||||
@echo "Targets:"
|
||||
@echo " make run Run the game"
|
||||
@echo " make run-headless Run headless (for testing)"
|
||||
@echo " make wasm Build for web (requires emsdk)"
|
||||
@echo " make serve-wasm Test WASM build in browser"
|
||||
@echo " make dist Package for all platforms"
|
||||
@echo " make dist-linux Package for Linux only"
|
||||
@echo " make dist-windows Package for Windows only"
|
||||
@echo " make dist-wasm Package for web only"
|
||||
@echo " make clean-dist Remove dist/"
|
||||
@echo " make clean-wasm Remove build-wasm/"
|
||||
MAKEFILE
|
||||
|
||||
# --- VERSION file ---
|
||||
if [ ! -f "$GAME_DIR/VERSION" ]; then
|
||||
echo "0.1.0" > "$GAME_DIR/VERSION"
|
||||
fi
|
||||
|
||||
# --- Done ---
|
||||
echo
|
||||
info "Project ready!"
|
||||
echo
|
||||
echo -e " ${BOLD}make run${NC} - play the game"
|
||||
echo -e " ${BOLD}make dist${NC} - package for distribution"
|
||||
echo -e " ${BOLD}make info${NC} - show project info"
|
||||
echo
|
||||
echo -e " Edit ${BOLD}scripts/game.py${NC} and ${BOLD}assets/${NC} to build your game."
|
||||
echo -e " Edit ${BOLD}VERSION${NC} to set your game version."
|
||||
Loading…
Add table
Add a link
Reference in a new issue