Compare commits
77 Commits
8881f442bb
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ce5c2ef02 | |||
| db2cad10d8 | |||
| 35ee2f9a5e | |||
|
|
1f1c394587 | ||
| 3b8da89ad5 | |||
| 538459f323 | |||
| 550b2ac220 | |||
| f6475f83f6 | |||
| 35151cecf1 | |||
| dde7b98ed2 | |||
| d1761a2464 | |||
| ffd88c02e1 | |||
| bd9b7c009a | |||
| e61ab94935 | |||
| 9150622e74 | |||
| 501cad6175 | |||
| 5275c5ca94 | |||
| 61951a047b | |||
| 5f6a3ae175 | |||
| 40c06ae249 | |||
| 1189ce0931 | |||
| ff04b6ee22 | |||
| ff0a4fefe1 | |||
| 666823c641 | |||
| 02f2bb2703 | |||
| 38e85c2a24 | |||
| e31278e389 | |||
| 024343db19 | |||
| ad7a2575da | |||
| 26991ce61a | |||
| 8ded8c81ee | |||
| eb99c6a18e | |||
| c1f3f9f708 | |||
| e2e49f89ce | |||
| 7ec75a3b26 | |||
| 33a25dc532 | |||
| 60279542e1 | |||
| 35bdd44649 | |||
| 77971497a4 | |||
| 9b83bec37d | |||
| 0791b80f15 | |||
| ca6111cd28 | |||
| 0cf76d595f | |||
| 821d9aa42c | |||
| 06f0f87970 | |||
| 86439abea8 | |||
| dd0c1a123c | |||
| 3e960a955c | |||
| ca233f4171 | |||
| ea8c85d7ad | |||
| e76297b3f3 | |||
| 14d5e8fd8f | |||
| 7b0db3abff | |||
| 83cc433c2f | |||
| 7182c45351 | |||
| e11e864b26 | |||
| d8435ef46c | |||
| 9b3a20cdf1 | |||
| 4e0b5fe3d2 | |||
| 4596bc203a | |||
| 78a61fe33c | |||
| d9084606c8 | |||
| 7951a42c88 | |||
| 2589f9f73f | |||
| a1f8536855 | |||
| c57a08f5da | |||
| 542cf6a4f1 | |||
| 1b95573720 | |||
| dac3d42b6b | |||
| e732f9b6da | |||
| c52d861e63 | |||
| 54c23a83e5 | |||
| 7882e70e03 | |||
| 20440fe07f | |||
| f949502ef4 | |||
| d7a0494d7e | |||
| fe69347625 |
15
.claude/settings.local.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"WebSearch",
|
||||
"Bash(find:*)",
|
||||
"Bash(godot:*)",
|
||||
"Bash(python:*)",
|
||||
"Bash(git mv:*)",
|
||||
"Bash(dir:*)",
|
||||
"Bash(yamllint:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
19
.gdformatrc
Normal file
@@ -0,0 +1,19 @@
|
||||
# GDFormat configuration file (YAML format)
|
||||
# This file configures the gdformat tool for consistent GDScript formatting
|
||||
|
||||
# Maximum line length (default is 100)
|
||||
# Godot's style guide recommends keeping lines under 100 characters
|
||||
line_length: 80
|
||||
|
||||
# Use spaces instead of tabs (null = use tabs)
|
||||
# Set to integer for space count, or null for tabs
|
||||
# Godot uses tabs by default
|
||||
use_spaces: null
|
||||
|
||||
# Safety checks (null = enabled by default)
|
||||
safety_checks: null
|
||||
|
||||
# Directories to exclude from formatting
|
||||
excluded_directories: !!set
|
||||
.git: null
|
||||
.godot: null
|
||||
649
.gitea/workflows/build.yml
Normal file
@@ -0,0 +1,649 @@
|
||||
name: Build Game
|
||||
|
||||
# Build pipeline for creating game executables across multiple platforms
|
||||
#
|
||||
# Features:
|
||||
# - Manual trigger with individual platform checkboxes
|
||||
# - Configurable version override (defaults to auto-generated)
|
||||
# - Configurable tool versions (Godot, Java, Android API, etc.)
|
||||
# - Flexible runner OS selection
|
||||
# - Tag-based automatic builds for releases
|
||||
# - Multi-platform builds (Windows, Linux, macOS, Android)
|
||||
# - Artifact storage for one week
|
||||
# - Comprehensive build configuration options
|
||||
|
||||
on:
|
||||
# Manual trigger with platform selection
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build_windows:
|
||||
description: 'Build for Windows'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
build_linux:
|
||||
description: 'Build for Linux'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
build_macos:
|
||||
description: 'Build for macOS'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
build_android:
|
||||
description: 'Build for Android'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
version:
|
||||
description: 'Version (leave empty for auto-generated)'
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
build_type:
|
||||
description: 'Build type'
|
||||
required: true
|
||||
default: 'release'
|
||||
type: debug
|
||||
options:
|
||||
- release
|
||||
- debug
|
||||
godot_version:
|
||||
description: 'Godot version (leave empty for default)'
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
runner_os:
|
||||
description: 'Runner OS (leave empty for default ubuntu-latest)'
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
java_version:
|
||||
description: 'Java version (leave empty for default)'
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
android_api_level:
|
||||
description: 'Android API level (leave empty for default)'
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
|
||||
# Automatic trigger on git tags (for releases)
|
||||
push:
|
||||
tags:
|
||||
- 'v*' # Version tags (v1.0.0, v2.1.0, etc.)
|
||||
- 'release-*' # Release tags
|
||||
|
||||
env:
|
||||
# Core Configuration
|
||||
GODOT_VERSION: "4.4.1"
|
||||
PROJECT_NAME: "Skelly"
|
||||
BUILD_DIR: "builds"
|
||||
DEFAULT_VERSION: "1.0.0-dev"
|
||||
|
||||
# GitHub Actions Versions
|
||||
ACTIONS_CHECKOUT_VERSION: "v4"
|
||||
ACTIONS_CACHE_VERSION: "v4"
|
||||
ACTIONS_UPLOAD_ARTIFACT_VERSION: "v3"
|
||||
ACTIONS_SETUP_JAVA_VERSION: "v4"
|
||||
|
||||
# Third-party Actions Versions
|
||||
CHICKENSOFT_SETUP_GODOT_VERSION: "v1"
|
||||
ANDROID_ACTIONS_SETUP_ANDROID_VERSION: "v3"
|
||||
|
||||
# Runner Configuration
|
||||
RUNNER_OS: "ubuntu-latest"
|
||||
|
||||
# Java Configuration
|
||||
JAVA_DISTRIBUTION: "temurin"
|
||||
JAVA_VERSION: "17"
|
||||
|
||||
# Android Configuration
|
||||
ANDROID_API_LEVEL: "33"
|
||||
ANDROID_BUILD_TOOLS_VERSION: "33.0.0"
|
||||
ANDROID_CMDLINE_TOOLS_VERSION: "latest"
|
||||
|
||||
jobs:
|
||||
# Preparation job - determines build configuration
|
||||
prepare:
|
||||
name: Prepare Build
|
||||
runs-on: ${{ env.RUNNER_OS }}
|
||||
outputs:
|
||||
platforms: ${{ steps.config.outputs.platforms }}
|
||||
build_type: ${{ steps.config.outputs.build_type }}
|
||||
version: ${{ steps.config.outputs.version }}
|
||||
artifact_name: ${{ steps.config.outputs.artifact_name }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@${{ env.ACTIONS_CHECKOUT_VERSION }}
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure build parameters
|
||||
id: config
|
||||
run: |
|
||||
# Override environment variables with user inputs if provided
|
||||
if [[ -n "${{ github.event.inputs.godot_version }}" ]]; then
|
||||
echo "GODOT_VERSION=${{ github.event.inputs.godot_version }}" >> $GITHUB_ENV
|
||||
echo "🔧 Using custom Godot version: ${{ github.event.inputs.godot_version }}"
|
||||
fi
|
||||
|
||||
if [[ -n "${{ github.event.inputs.runner_os }}" ]]; then
|
||||
echo "RUNNER_OS=${{ github.event.inputs.runner_os }}" >> $GITHUB_ENV
|
||||
echo "🔧 Using custom runner OS: ${{ github.event.inputs.runner_os }}"
|
||||
fi
|
||||
|
||||
if [[ -n "${{ github.event.inputs.java_version }}" ]]; then
|
||||
echo "JAVA_VERSION=${{ github.event.inputs.java_version }}" >> $GITHUB_ENV
|
||||
echo "🔧 Using custom Java version: ${{ github.event.inputs.java_version }}"
|
||||
fi
|
||||
|
||||
if [[ -n "${{ github.event.inputs.android_api_level }}" ]]; then
|
||||
echo "ANDROID_API_LEVEL=${{ github.event.inputs.android_api_level }}" >> $GITHUB_ENV
|
||||
echo "ANDROID_BUILD_TOOLS_VERSION=${{ github.event.inputs.android_api_level }}.0.0" >> $GITHUB_ENV
|
||||
echo "🔧 Using custom Android API level: ${{ github.event.inputs.android_api_level }}"
|
||||
fi
|
||||
|
||||
# Determine platforms to build
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
# Build platforms array from individual checkboxes
|
||||
platforms=""
|
||||
if [[ "${{ github.event.inputs.build_windows }}" == "true" ]]; then
|
||||
platforms="${platforms}windows,"
|
||||
fi
|
||||
if [[ "${{ github.event.inputs.build_linux }}" == "true" ]]; then
|
||||
platforms="${platforms}linux,"
|
||||
fi
|
||||
if [[ "${{ github.event.inputs.build_macos }}" == "true" ]]; then
|
||||
platforms="${platforms}macos,"
|
||||
fi
|
||||
if [[ "${{ github.event.inputs.build_android }}" == "true" ]]; then
|
||||
platforms="${platforms}android,"
|
||||
fi
|
||||
# Remove trailing comma
|
||||
platforms="${platforms%,}"
|
||||
|
||||
build_type="${{ github.event.inputs.build_type }}"
|
||||
user_version="${{ github.event.inputs.version }}"
|
||||
else
|
||||
# Tag-triggered build - build all platforms
|
||||
platforms="windows,linux,macos,android"
|
||||
build_type="release"
|
||||
user_version=""
|
||||
fi
|
||||
|
||||
# Determine version with improved logic
|
||||
if [[ -n "$user_version" ]]; then
|
||||
# User provided explicit version
|
||||
version="$user_version"
|
||||
echo "🏷️ Using user-specified version: $version"
|
||||
elif [[ "${{ github.ref_type }}" == "tag" ]]; then
|
||||
# Tag-triggered build - use tag name
|
||||
version="${{ github.ref_name }}"
|
||||
echo "🏷️ Using git tag version: $version"
|
||||
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
# Manual dispatch without version - use default + git info
|
||||
commit_short=$(git rev-parse --short HEAD)
|
||||
branch_name="${{ github.ref_name }}"
|
||||
timestamp=$(date +%Y%m%d-%H%M)
|
||||
version="${{ env.DEFAULT_VERSION }}-${branch_name}-${commit_short}-${timestamp}"
|
||||
echo "🏷️ Using auto-generated version: $version"
|
||||
else
|
||||
# Fallback for other triggers
|
||||
commit_short=$(git rev-parse --short HEAD)
|
||||
branch_name="${{ github.ref_name }}"
|
||||
timestamp=$(date +%Y%m%d-%H%M)
|
||||
version="${{ env.DEFAULT_VERSION }}-${branch_name}-${commit_short}-${timestamp}"
|
||||
echo "🏷️ Using fallback version: $version"
|
||||
fi
|
||||
|
||||
# Create artifact name
|
||||
artifact_name="${{ env.PROJECT_NAME }}-${version}-${build_type}"
|
||||
|
||||
echo "platforms=${platforms}" >> $GITHUB_OUTPUT
|
||||
echo "build_type=${build_type}" >> $GITHUB_OUTPUT
|
||||
echo "version=${version}" >> $GITHUB_OUTPUT
|
||||
echo "artifact_name=${artifact_name}" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "🔧 Build Configuration:"
|
||||
echo " Platforms: ${platforms}"
|
||||
echo " Build Type: ${build_type}"
|
||||
echo " Version: ${version}"
|
||||
echo " Artifact: ${artifact_name}"
|
||||
echo ""
|
||||
echo "🔧 Tool Versions:"
|
||||
echo " Godot: ${GODOT_VERSION}"
|
||||
echo " Runner OS: ${RUNNER_OS}"
|
||||
echo " Java: ${JAVA_VERSION}"
|
||||
echo " Android API: ${ANDROID_API_LEVEL}"
|
||||
echo " Android Build Tools: ${ANDROID_BUILD_TOOLS_VERSION}"
|
||||
|
||||
# Setup export templates (shared across all platform builds)
|
||||
setup-templates:
|
||||
name: Setup Export Templates
|
||||
runs-on: ${{ env.RUNNER_OS }}
|
||||
needs: prepare
|
||||
|
||||
steps:
|
||||
- name: Cache export templates
|
||||
id: cache-templates
|
||||
uses: actions/cache@${{ env.ACTIONS_CACHE_VERSION }}
|
||||
with:
|
||||
path: ~/.local/share/godot/export_templates
|
||||
key: godot-templates-${{ env.GODOT_VERSION }}
|
||||
restore-keys: |
|
||||
godot-templates-
|
||||
|
||||
- name: Setup Godot
|
||||
if: steps.cache-templates.outputs.cache-hit != 'true'
|
||||
uses: chickensoft-games/setup-godot@${{ env.CHICKENSOFT_SETUP_GODOT_VERSION }}
|
||||
with:
|
||||
version: ${{ env.GODOT_VERSION }}
|
||||
use-dotnet: false
|
||||
|
||||
- name: Install export templates
|
||||
if: steps.cache-templates.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
echo "📦 Installing Godot export templates..."
|
||||
mkdir -p ~/.local/share/godot/export_templates/${{ env.GODOT_VERSION }}.stable
|
||||
wget -q https://github.com/godotengine/godot/releases/download/${{ env.GODOT_VERSION }}-stable/Godot_v${{ env.GODOT_VERSION }}-stable_export_templates.tpz
|
||||
unzip -q Godot_v${{ env.GODOT_VERSION }}-stable_export_templates.tpz
|
||||
mv templates/* ~/.local/share/godot/export_templates/${{ env.GODOT_VERSION }}.stable/
|
||||
echo "✅ Export templates installed successfully"
|
||||
ls -la ~/.local/share/godot/export_templates/${{ env.GODOT_VERSION }}.stable/
|
||||
|
||||
- name: Verify templates cache
|
||||
run: |
|
||||
echo "🔍 Verifying export templates are available:"
|
||||
ls -la ~/.local/share/godot/export_templates/${{ env.GODOT_VERSION }}.stable/
|
||||
|
||||
# Windows build job
|
||||
build-windows:
|
||||
name: Build Windows
|
||||
runs-on: ${{ env.RUNNER_OS }}
|
||||
needs: [prepare, setup-templates]
|
||||
if: contains(needs.prepare.outputs.platforms, 'windows')
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@${{ env.ACTIONS_CHECKOUT_VERSION }}
|
||||
|
||||
- name: Setup Godot
|
||||
uses: chickensoft-games/setup-godot@${{ env.CHICKENSOFT_SETUP_GODOT_VERSION }}
|
||||
with:
|
||||
version: ${{ env.GODOT_VERSION }}
|
||||
use-dotnet: false
|
||||
|
||||
- name: Restore export templates cache
|
||||
uses: actions/cache@${{ env.ACTIONS_CACHE_VERSION }}
|
||||
with:
|
||||
path: ~/.local/share/godot/export_templates
|
||||
key: godot-templates-${{ env.GODOT_VERSION }}
|
||||
restore-keys: |
|
||||
godot-templates-
|
||||
|
||||
- name: Create build directory
|
||||
run: mkdir -p ${{ env.BUILD_DIR }}
|
||||
|
||||
- name: Import project assets
|
||||
run: |
|
||||
echo "📦 Importing project assets..."
|
||||
godot --headless --verbose --editor --quit || true
|
||||
sleep 2
|
||||
|
||||
- name: Build Windows executable
|
||||
run: |
|
||||
echo "🏗️ Building Windows executable..."
|
||||
godot --headless --verbose --export-${{ needs.prepare.outputs.build_type }} "Windows Desktop" \
|
||||
${{ env.BUILD_DIR }}/skelly-windows-${{ needs.prepare.outputs.version }}.exe
|
||||
|
||||
# Verify build output
|
||||
if [[ -f "${{ env.BUILD_DIR }}/skelly-windows-${{ needs.prepare.outputs.version }}.exe" ]]; then
|
||||
echo "✅ Windows build successful"
|
||||
ls -la ${{ env.BUILD_DIR }}/
|
||||
else
|
||||
echo "❌ Windows build failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Upload Windows build
|
||||
uses: actions/upload-artifact@${{ env.ACTIONS_UPLOAD_ARTIFACT_VERSION }}
|
||||
with:
|
||||
name: ${{ needs.prepare.outputs.artifact_name }}-windows
|
||||
path: ${{ env.BUILD_DIR }}/skelly-windows-${{ needs.prepare.outputs.version }}.exe
|
||||
retention-days: 7
|
||||
compression-level: 0
|
||||
|
||||
# Linux build job
|
||||
build-linux:
|
||||
name: Build Linux
|
||||
runs-on: ${{ env.RUNNER_OS }}
|
||||
needs: [prepare, setup-templates]
|
||||
if: contains(needs.prepare.outputs.platforms, 'linux')
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@${{ env.ACTIONS_CHECKOUT_VERSION }}
|
||||
|
||||
- name: Setup Godot
|
||||
uses: chickensoft-games/setup-godot@${{ env.CHICKENSOFT_SETUP_GODOT_VERSION }}
|
||||
with:
|
||||
version: ${{ env.GODOT_VERSION }}
|
||||
use-dotnet: false
|
||||
|
||||
- name: Restore export templates cache
|
||||
uses: actions/cache@${{ env.ACTIONS_CACHE_VERSION }}
|
||||
with:
|
||||
path: ~/.local/share/godot/export_templates
|
||||
key: godot-templates-${{ env.GODOT_VERSION }}
|
||||
restore-keys: |
|
||||
godot-templates-
|
||||
|
||||
- name: Create build directory
|
||||
run: mkdir -p ${{ env.BUILD_DIR }}
|
||||
|
||||
- name: Import project assets
|
||||
run: |
|
||||
echo "📦 Importing project assets..."
|
||||
godot --headless --verbose --editor --quit || true
|
||||
sleep 2
|
||||
|
||||
- name: Build Linux executable
|
||||
run: |
|
||||
echo "🏗️ Building Linux executable..."
|
||||
godot --headless --verbose --export-${{ needs.prepare.outputs.build_type }} "Linux" \
|
||||
${{ env.BUILD_DIR }}/skelly-linux-${{ needs.prepare.outputs.version }}.x86_64
|
||||
|
||||
# Make executable
|
||||
chmod +x ${{ env.BUILD_DIR }}/skelly-linux-${{ needs.prepare.outputs.version }}.x86_64
|
||||
|
||||
# Verify build output
|
||||
if [[ -f "${{ env.BUILD_DIR }}/skelly-linux-${{ needs.prepare.outputs.version }}.x86_64" ]]; then
|
||||
echo "✅ Linux build successful"
|
||||
ls -la ${{ env.BUILD_DIR }}/
|
||||
else
|
||||
echo "❌ Linux build failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Upload Linux build
|
||||
uses: actions/upload-artifact@${{ env.ACTIONS_UPLOAD_ARTIFACT_VERSION }}
|
||||
with:
|
||||
name: ${{ needs.prepare.outputs.artifact_name }}-linux
|
||||
path: ${{ env.BUILD_DIR }}/skelly-linux-${{ needs.prepare.outputs.version }}.x86_64
|
||||
retention-days: 7
|
||||
compression-level: 0
|
||||
|
||||
# macOS build job
|
||||
build-macos:
|
||||
name: Build macOS
|
||||
runs-on: ${{ env.RUNNER_OS }}
|
||||
needs: [prepare, setup-templates]
|
||||
if: contains(needs.prepare.outputs.platforms, 'macos')
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@${{ env.ACTIONS_CHECKOUT_VERSION }}
|
||||
|
||||
- name: Setup Godot
|
||||
uses: chickensoft-games/setup-godot@${{ env.CHICKENSOFT_SETUP_GODOT_VERSION }}
|
||||
with:
|
||||
version: ${{ env.GODOT_VERSION }}
|
||||
use-dotnet: false
|
||||
|
||||
- name: Restore export templates cache
|
||||
uses: actions/cache@${{ env.ACTIONS_CACHE_VERSION }}
|
||||
with:
|
||||
path: ~/.local/share/godot/export_templates
|
||||
key: godot-templates-${{ env.GODOT_VERSION }}
|
||||
restore-keys: |
|
||||
godot-templates-
|
||||
|
||||
- name: Create build directory
|
||||
run: mkdir -p ${{ env.BUILD_DIR }}
|
||||
|
||||
- name: Import project assets
|
||||
run: |
|
||||
echo "📦 Importing project assets..."
|
||||
godot --headless --verbose --editor --quit || true
|
||||
sleep 2
|
||||
|
||||
- name: Build macOS application
|
||||
run: |
|
||||
echo "🏗️ Building macOS application..."
|
||||
godot --headless --verbose --export-${{ needs.prepare.outputs.build_type }} "macOS" \
|
||||
${{ env.BUILD_DIR }}/skelly-macos-${{ needs.prepare.outputs.version }}.zip
|
||||
|
||||
# Verify build output
|
||||
if [[ -f "${{ env.BUILD_DIR }}/skelly-macos-${{ needs.prepare.outputs.version }}.zip" ]]; then
|
||||
echo "✅ macOS build successful"
|
||||
ls -la ${{ env.BUILD_DIR }}/
|
||||
else
|
||||
echo "❌ macOS build failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Upload macOS build
|
||||
uses: actions/upload-artifact@${{ env.ACTIONS_UPLOAD_ARTIFACT_VERSION }}
|
||||
with:
|
||||
name: ${{ needs.prepare.outputs.artifact_name }}-macos
|
||||
path: ${{ env.BUILD_DIR }}/skelly-macos-${{ needs.prepare.outputs.version }}.zip
|
||||
retention-days: 7
|
||||
compression-level: 0
|
||||
|
||||
# Android build job
|
||||
build-android:
|
||||
name: Build Android
|
||||
runs-on: ${{ env.RUNNER_OS }}
|
||||
needs: [prepare, setup-templates]
|
||||
if: contains(needs.prepare.outputs.platforms, 'android')
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@${{ env.ACTIONS_CHECKOUT_VERSION }}
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@${{ env.ACTIONS_SETUP_JAVA_VERSION }}
|
||||
with:
|
||||
distribution: ${{ env.JAVA_DISTRIBUTION }}
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@${{ env.ANDROID_ACTIONS_SETUP_ANDROID_VERSION }}
|
||||
with:
|
||||
api-level: ${{ env.ANDROID_API_LEVEL }}
|
||||
build-tools: ${{ env.ANDROID_BUILD_TOOLS_VERSION }}
|
||||
|
||||
- name: Install Android Build Tools
|
||||
run: |
|
||||
echo "🔧 Installing Android Build Tools..."
|
||||
|
||||
# Set Android environment variables
|
||||
export ANDROID_HOME=${ANDROID_SDK_ROOT}
|
||||
echo "ANDROID_HOME=${ANDROID_SDK_ROOT}" >> $GITHUB_ENV
|
||||
echo "ANDROID_SDK_ROOT=${ANDROID_SDK_ROOT}" >> $GITHUB_ENV
|
||||
|
||||
# Install build-tools using sdkmanager
|
||||
yes | ${ANDROID_SDK_ROOT}/cmdline-tools/${{ env.ANDROID_CMDLINE_TOOLS_VERSION }}/bin/sdkmanager --licenses || true
|
||||
${ANDROID_SDK_ROOT}/cmdline-tools/${{ env.ANDROID_CMDLINE_TOOLS_VERSION }}/bin/sdkmanager "build-tools;${{ env.ANDROID_BUILD_TOOLS_VERSION }}"
|
||||
${ANDROID_SDK_ROOT}/cmdline-tools/${{ env.ANDROID_CMDLINE_TOOLS_VERSION }}/bin/sdkmanager "platforms;android-${{ env.ANDROID_API_LEVEL }}"
|
||||
|
||||
- name: Verify Android SDK Configuration
|
||||
run: |
|
||||
echo "📱 Verifying Android SDK setup..."
|
||||
echo "📱 Using API Level: ${{ env.ANDROID_API_LEVEL }}"
|
||||
echo "📱 Using Build Tools: ${{ env.ANDROID_BUILD_TOOLS_VERSION }}"
|
||||
|
||||
# Verify SDK installation
|
||||
echo "📱 Android SDK Location: ${ANDROID_SDK_ROOT}"
|
||||
ls -la ${ANDROID_SDK_ROOT}/
|
||||
echo "📱 Build Tools:"
|
||||
ls -la ${ANDROID_SDK_ROOT}/build-tools/
|
||||
echo "📱 Platforms:"
|
||||
ls -la ${ANDROID_SDK_ROOT}/platforms/ || echo "No platforms directory"
|
||||
|
||||
# Verify apksigner exists
|
||||
if [ -f "${ANDROID_SDK_ROOT}/build-tools/${{ env.ANDROID_BUILD_TOOLS_VERSION }}/apksigner" ]; then
|
||||
echo "✅ apksigner found at ${ANDROID_SDK_ROOT}/build-tools/${{ env.ANDROID_BUILD_TOOLS_VERSION }}/apksigner"
|
||||
else
|
||||
echo "❌ apksigner not found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Setup Godot
|
||||
uses: chickensoft-games/setup-godot@${{ env.CHICKENSOFT_SETUP_GODOT_VERSION }}
|
||||
with:
|
||||
version: ${{ env.GODOT_VERSION }}
|
||||
use-dotnet: false
|
||||
|
||||
- name: Configure Godot for Android
|
||||
run: |
|
||||
echo "🎮 Configuring Godot for Android builds..."
|
||||
|
||||
# Create Godot config directory
|
||||
mkdir -p ~/.config/godot
|
||||
|
||||
# Configure Android SDK path in Godot settings
|
||||
cat > ~/.config/godot/editor_settings-4.4.tres << EOF
|
||||
[gd_resource type="EditorSettings" format=3]
|
||||
|
||||
[resource]
|
||||
export/android/android_sdk_path = "${ANDROID_SDK_ROOT}"
|
||||
export/android/debug_keystore = ""
|
||||
export/android/debug_keystore_user = "androiddebugkey"
|
||||
export/android/debug_keystore_pass = "android"
|
||||
export/android/force_system_user = false
|
||||
export/android/timestamping_authority_url = ""
|
||||
export/android/shutdown_adb_on_exit = true
|
||||
EOF
|
||||
|
||||
echo "✅ Godot Android configuration complete"
|
||||
|
||||
- name: Restore export templates cache
|
||||
uses: actions/cache@${{ env.ACTIONS_CACHE_VERSION }}
|
||||
with:
|
||||
path: ~/.local/share/godot/export_templates
|
||||
key: godot-templates-${{ env.GODOT_VERSION }}
|
||||
restore-keys: |
|
||||
godot-templates-
|
||||
|
||||
- name: Create build directory
|
||||
run: mkdir -p ${{ env.BUILD_DIR }}
|
||||
|
||||
- name: Import project assets
|
||||
run: |
|
||||
echo "📦 Importing project assets..."
|
||||
godot --headless --verbose --editor --quit || true
|
||||
sleep 2
|
||||
|
||||
- name: Build Android APK
|
||||
run: |
|
||||
echo "🏗️ Building Android APK..."
|
||||
|
||||
# Verify Android environment
|
||||
echo "📱 Android SDK: ${ANDROID_SDK_ROOT}"
|
||||
echo "📱 API Level: ${{ env.ANDROID_API_LEVEL }}"
|
||||
echo "📱 Build Tools Version: ${{ env.ANDROID_BUILD_TOOLS_VERSION }}"
|
||||
echo "📱 Available Build Tools: $(ls ${ANDROID_SDK_ROOT}/build-tools/)"
|
||||
|
||||
godot --headless --verbose --export-${{ needs.prepare.outputs.build_type }} "Android" \
|
||||
${{ env.BUILD_DIR }}/skelly-android-${{ needs.prepare.outputs.version }}.apk
|
||||
|
||||
# Verify build output
|
||||
if [[ -f "${{ env.BUILD_DIR }}/skelly-android-${{ needs.prepare.outputs.version }}.apk" ]]; then
|
||||
echo "✅ Android build successful"
|
||||
ls -la ${{ env.BUILD_DIR }}/
|
||||
|
||||
# Show APK info
|
||||
echo "📱 APK Information:"
|
||||
file ${{ env.BUILD_DIR }}/skelly-android-${{ needs.prepare.outputs.version }}.apk
|
||||
else
|
||||
echo "❌ Android build failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Upload Android build
|
||||
uses: actions/upload-artifact@${{ env.ACTIONS_UPLOAD_ARTIFACT_VERSION }}
|
||||
with:
|
||||
name: ${{ needs.prepare.outputs.artifact_name }}-android
|
||||
path: ${{ env.BUILD_DIR }}/skelly-android-${{ needs.prepare.outputs.version }}.apk
|
||||
retention-days: 7
|
||||
compression-level: 0
|
||||
|
||||
# Summary job - creates release summary
|
||||
summary:
|
||||
name: Build Summary
|
||||
runs-on: ${{ env.RUNNER_OS }}
|
||||
needs: [prepare, setup-templates, build-windows, build-linux, build-macos, build-android]
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Generate build summary
|
||||
run: |
|
||||
echo "🎮 Build Summary for ${{ needs.prepare.outputs.artifact_name }}"
|
||||
echo "=================================="
|
||||
echo ""
|
||||
echo "📋 Configuration:"
|
||||
echo " Version: ${{ needs.prepare.outputs.version }}"
|
||||
echo " Build Type: ${{ needs.prepare.outputs.build_type }}"
|
||||
echo " Platforms: ${{ needs.prepare.outputs.platforms }}"
|
||||
echo ""
|
||||
echo "📊 Build Results:"
|
||||
|
||||
platforms="${{ needs.prepare.outputs.platforms }}"
|
||||
|
||||
if [[ "$platforms" == *"windows"* ]]; then
|
||||
windows_status="${{ needs.build-windows.result }}"
|
||||
echo " 🪟 Windows: $windows_status"
|
||||
fi
|
||||
|
||||
if [[ "$platforms" == *"linux"* ]]; then
|
||||
linux_status="${{ needs.build-linux.result }}"
|
||||
echo " 🐧 Linux: $linux_status"
|
||||
fi
|
||||
|
||||
if [[ "$platforms" == *"macos"* ]]; then
|
||||
macos_status="${{ needs.build-macos.result }}"
|
||||
echo " 🍎 macOS: $macos_status"
|
||||
fi
|
||||
|
||||
if [[ "$platforms" == *"android"* ]]; then
|
||||
android_status="${{ needs.build-android.result }}"
|
||||
echo " 🤖 Android: $android_status"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📦 Artifacts are available for 7 days"
|
||||
echo "🔗 Download from: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
|
||||
- name: Check overall build status
|
||||
run: |
|
||||
# Check if any required builds failed
|
||||
platforms="${{ needs.prepare.outputs.platforms }}"
|
||||
failed_builds=()
|
||||
|
||||
if [[ "$platforms" == *"windows"* ]] && [[ "${{ needs.build-windows.result }}" != "success" ]]; then
|
||||
failed_builds+=("Windows")
|
||||
fi
|
||||
|
||||
if [[ "$platforms" == *"linux"* ]] && [[ "${{ needs.build-linux.result }}" != "success" ]]; then
|
||||
failed_builds+=("Linux")
|
||||
fi
|
||||
|
||||
if [[ "$platforms" == *"macos"* ]] && [[ "${{ needs.build-macos.result }}" != "success" ]]; then
|
||||
failed_builds+=("macOS")
|
||||
fi
|
||||
|
||||
if [[ "$platforms" == *"android"* ]] && [[ "${{ needs.build-android.result }}" != "success" ]]; then
|
||||
failed_builds+=("Android")
|
||||
fi
|
||||
|
||||
if [[ ${#failed_builds[@]} -gt 0 ]]; then
|
||||
echo "❌ Build failed for: ${failed_builds[*]}"
|
||||
exit 1
|
||||
else
|
||||
echo "✅ All builds completed successfully!"
|
||||
fi
|
||||
357
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,357 @@
|
||||
name: Continuous Integration
|
||||
|
||||
# CI pipeline for the Skelly Godot project
|
||||
#
|
||||
# Code quality checks (formatting, linting, testing) run as independent jobs
|
||||
# in parallel. Uses tools/run_development.py for consistency with local development.
|
||||
#
|
||||
# Features:
|
||||
# - Independent job execution (no dependencies between format/lint/test)
|
||||
# - Automatic code formatting with commit back to branch
|
||||
# - Error reporting and PR comments
|
||||
# - Manual execution with selective step skipping
|
||||
|
||||
on:
|
||||
# Trigger on push to any branch - only when relevant files change
|
||||
push:
|
||||
branches: ['*']
|
||||
paths:
|
||||
- '**/*.gd' # Any GDScript file
|
||||
- '.gdlintrc' # Linting configuration
|
||||
- '.gdformatrc' # Formatting configuration
|
||||
- 'tools/run_development.py' # Development workflow script
|
||||
- '.gitea/workflows/ci.yml' # This workflow file
|
||||
|
||||
# Trigger on pull requests - same file filters as push
|
||||
pull_request:
|
||||
branches: ['*']
|
||||
paths:
|
||||
- '**/*.gd'
|
||||
- '.gdlintrc'
|
||||
- '.gdformatrc'
|
||||
- 'tools/run_development.py'
|
||||
- '.gitea/workflows/ci.yml'
|
||||
|
||||
# Allow manual triggering with optional step skipping
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
skip_format:
|
||||
description: 'Skip code formatting'
|
||||
required: false
|
||||
default: 'false'
|
||||
type: boolean
|
||||
skip_lint:
|
||||
description: 'Skip code linting'
|
||||
required: false
|
||||
default: 'false'
|
||||
type: boolean
|
||||
skip_tests:
|
||||
description: 'Skip test execution'
|
||||
required: false
|
||||
default: 'false'
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
format:
|
||||
name: Code Formatting
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
if: ${{ always() && github.event.inputs.skip_format != 'true' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref || github.ref }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade "setuptools<81"
|
||||
pip install gdtoolkit==4
|
||||
|
||||
- name: Run code formatting
|
||||
id: format
|
||||
run: |
|
||||
echo "🎨 Running GDScript formatting..."
|
||||
python tools/run_development.py --format --silent --yaml > format_results.yaml
|
||||
|
||||
- name: Upload formatting results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: format-results
|
||||
path: |
|
||||
format_results.yaml
|
||||
retention-days: 7
|
||||
compression-level: 0
|
||||
|
||||
- name: Check for formatting changes
|
||||
id: check-changes
|
||||
run: |
|
||||
if git diff --quiet; then
|
||||
echo "📝 No formatting changes detected"
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "📝 Formatting changes detected"
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
echo "🔍 Changed files:"
|
||||
git diff --name-only
|
||||
echo ""
|
||||
echo "📊 Diff summary:"
|
||||
git diff --stat
|
||||
fi
|
||||
|
||||
# - name: Commit and push formatting changes
|
||||
# if: steps.check-changes.outputs.has_changes == 'true'
|
||||
# run: |
|
||||
# echo "💾 Committing formatting changes..."
|
||||
|
||||
# git config user.name "Gitea Actions"
|
||||
# git config user.email "actions@gitea.local"
|
||||
|
||||
# git add -A
|
||||
|
||||
# commit_message="🎨 Auto-format GDScript code
|
||||
|
||||
# Automated formatting applied by tools/run_development.py
|
||||
|
||||
# 🤖 Generated by Gitea Actions
|
||||
# Workflow: ${{ github.workflow }}
|
||||
# Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
|
||||
# git commit -m "$commit_message"
|
||||
|
||||
# target_branch="${{ github.event.pull_request.head.ref || github.ref_name }}"
|
||||
# echo "📤 Pushing changes to branch: $target_branch"
|
||||
# git push origin HEAD:"$target_branch"
|
||||
|
||||
# echo "✅ Formatting changes pushed successfully!"
|
||||
|
||||
lint:
|
||||
name: Code Quality Check
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.inputs.skip_lint != 'true' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref || github.ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade "setuptools<81"
|
||||
pip install gdtoolkit==4
|
||||
|
||||
- name: Run linting
|
||||
id: lint
|
||||
run: |
|
||||
echo "🔍 Running GDScript linting..."
|
||||
python tools/run_development.py --lint --silent --yaml > lint_results.yaml
|
||||
|
||||
- name: Upload linting results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lint-results
|
||||
path: |
|
||||
lint_results.yaml
|
||||
retention-days: 7
|
||||
compression-level: 0
|
||||
|
||||
test:
|
||||
name: Test Execution
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.inputs.skip_tests != 'true' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref || github.ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade "setuptools<81"
|
||||
pip install gdtoolkit==4
|
||||
|
||||
- name: Set up Godot
|
||||
uses: chickensoft-games/setup-godot@v1
|
||||
with:
|
||||
version: 4.3.0
|
||||
use-dotnet: false
|
||||
|
||||
- name: Run tests
|
||||
id: test
|
||||
continue-on-error: true
|
||||
run: |
|
||||
echo "🧪 Running GDScript tests..."
|
||||
python tools/run_development.py --test --yaml > test_results.yaml 2>&1
|
||||
TEST_EXIT_CODE=$?
|
||||
|
||||
# Display test results regardless of success/failure
|
||||
echo ""
|
||||
echo "📊 Test Results:"
|
||||
cat test_results.yaml
|
||||
|
||||
# Exit with the original test exit code
|
||||
exit $TEST_EXIT_CODE
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: test-results
|
||||
path: |
|
||||
test_results.yaml
|
||||
retention-days: 7
|
||||
compression-level: 0
|
||||
|
||||
- name: Check test results and display errors
|
||||
if: always()
|
||||
run: |
|
||||
echo ""
|
||||
echo "================================================"
|
||||
echo "📊 TEST RESULTS SUMMARY"
|
||||
echo "================================================"
|
||||
|
||||
# Parse YAML to check if tests failed
|
||||
if grep -q "success: false" test_results.yaml; then
|
||||
echo "❌ Status: FAILED"
|
||||
echo ""
|
||||
echo "💥 Failed Test Details:"
|
||||
echo "================================================"
|
||||
|
||||
# Extract and display failed test information
|
||||
grep -A 2 "failed_test_details:" test_results.yaml || echo "No detailed error information available"
|
||||
|
||||
# Show statistics
|
||||
echo ""
|
||||
echo "📈 Statistics:"
|
||||
grep "tests_passed:" test_results.yaml || true
|
||||
grep "tests_failed:" test_results.yaml || true
|
||||
grep "total_tests_run:" test_results.yaml || true
|
||||
|
||||
echo ""
|
||||
echo "================================================"
|
||||
echo "⚠️ Please review the test errors above and fix them before merging."
|
||||
echo "================================================"
|
||||
exit 1
|
||||
elif grep -q "success: true" test_results.yaml; then
|
||||
echo "✅ Status: PASSED"
|
||||
echo ""
|
||||
grep "total_tests_run:" test_results.yaml || true
|
||||
echo "================================================"
|
||||
exit 0
|
||||
else
|
||||
echo "⚠️ Status: UNKNOWN"
|
||||
echo "Could not determine test status from YAML output"
|
||||
echo "================================================"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
summary:
|
||||
name: CI Summary
|
||||
runs-on: ubuntu-latest
|
||||
needs: [format, lint, test]
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Set workflow status
|
||||
id: status
|
||||
run: |
|
||||
format_status="${{ needs.format.result }}"
|
||||
lint_status="${{ needs.lint.result }}"
|
||||
test_status="${{ needs.test.result }}"
|
||||
|
||||
echo "📊 Workflow Results:"
|
||||
echo "🎨 Format: $format_status"
|
||||
echo "🔍 Lint: $lint_status"
|
||||
echo "🧪 Test: $test_status"
|
||||
|
||||
if [[ "$format_status" == "success" && "$lint_status" == "success" && ("$test_status" == "success" || "$test_status" == "skipped") ]]; then
|
||||
echo "overall_status=success" >> $GITHUB_OUTPUT
|
||||
echo "✅ All CI checks passed!"
|
||||
else
|
||||
echo "overall_status=failure" >> $GITHUB_OUTPUT
|
||||
echo "❌ Some CI checks failed"
|
||||
fi
|
||||
|
||||
- name: Comment on PR (if applicable)
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const formatStatus = '${{ needs.format.result }}';
|
||||
const lintStatus = '${{ needs.lint.result }}';
|
||||
const testStatus = '${{ needs.test.result }}';
|
||||
const overallStatus = '${{ steps.status.outputs.overall_status }}';
|
||||
|
||||
const getStatusEmoji = (status) => {
|
||||
switch(status) {
|
||||
case 'success': return '✅';
|
||||
case 'failure': return '❌';
|
||||
case 'skipped': return '⏭️';
|
||||
default: return '⚠️';
|
||||
}
|
||||
};
|
||||
|
||||
const message = `## 🤖 CI Pipeline Results
|
||||
|
||||
| Step | Status | Result |
|
||||
|------|--------|--------|
|
||||
| 🎨 Formatting | ${getStatusEmoji(formatStatus)} | ${formatStatus} |
|
||||
| 🔍 Linting | ${getStatusEmoji(lintStatus)} | ${lintStatus} |
|
||||
| 🧪 Testing | ${getStatusEmoji(testStatus)} | ${testStatus} |
|
||||
|
||||
**Overall Status:** ${getStatusEmoji(overallStatus)} ${overallStatus.toUpperCase()}
|
||||
|
||||
${overallStatus === 'success'
|
||||
? '🎉 All checks passed! This PR is ready for review.'
|
||||
: '⚠️ Some checks failed. Please review the workflow logs and fix any issues.'}
|
||||
|
||||
[View workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})`;
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: message
|
||||
});
|
||||
|
||||
- name: Set final exit code
|
||||
run: |
|
||||
if [[ "${{ steps.status.outputs.overall_status }}" == "success" ]]; then
|
||||
echo "🎉 CI Pipeline completed successfully!"
|
||||
exit 0
|
||||
else
|
||||
echo "❌ CI Pipeline failed"
|
||||
exit 1
|
||||
fi
|
||||
15
.gitignore
vendored
@@ -1,3 +1,18 @@
|
||||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
/android/
|
||||
|
||||
# Generated files
|
||||
*.tmp
|
||||
*.import~
|
||||
test_results.txt
|
||||
|
||||
# Auto-generated code maps (regenerate with: python tools/generate_code_map.py)
|
||||
.llm/
|
||||
docs/generated/
|
||||
|
||||
# python
|
||||
|
||||
.venv/
|
||||
*.pyc
|
||||
.ruff/
|
||||
|
||||
32
.llm/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Machine-Readable Code Maps
|
||||
|
||||
Auto-generated JSON files for LLM development assistance.
|
||||
|
||||
**⚠️ Git-ignored** - Regenerate with: `python tools/generate_code_map.py`
|
||||
|
||||
## Files
|
||||
|
||||
- `code_map_api.json` - Function signatures, parameters, return types, docstrings
|
||||
- `code_map_architecture.json` - Autoloads, design patterns, system structure
|
||||
- `code_map_flows.json` - Signal chains, scene transitions, call graphs
|
||||
- `code_map_security.json` - Input validation, error handling patterns
|
||||
- `code_map_assets.json` - Asset dependencies, licensing information
|
||||
- `code_map_metadata.json` - Code quality metrics, test coverage, TODOs
|
||||
|
||||
## Diagrams
|
||||
|
||||
**Location**: `diagrams/` subdirectory (also copied to `docs/generated/diagrams/` for standalone viewing)
|
||||
|
||||
**Contents**:
|
||||
- `*.mmd` - Mermaid source files (architecture, signal flows, scene hierarchy, dependencies)
|
||||
- `*.png` - Rendered PNG diagrams
|
||||
|
||||
**Note**: Diagrams are automatically copied to `docs/generated/diagrams/` when generating documentation.
|
||||
|
||||
## Format
|
||||
|
||||
All JSON files are **minified** (no whitespace) for optimal LLM token efficiency.
|
||||
|
||||
## Usage
|
||||
|
||||
See root `CLAUDE.md` for integration with LLM development workflows.
|
||||
224
CLAUDE.md
Normal file
@@ -0,0 +1,224 @@
|
||||
# CLAUDE.md
|
||||
|
||||
Guidance for Claude Code (claude.ai/code) when working with this repository.
|
||||
|
||||
## Required Context Files
|
||||
|
||||
Before doing anything else, get the following files in context:
|
||||
- **docs/ARCHITECTURE.md** - System design, autoloads, architectural patterns
|
||||
- **docs/CODE_OF_CONDUCT.md** - Coding standards, naming conventions, best practices
|
||||
- **project.godot** - Input actions and autoload definitions
|
||||
|
||||
## Auto-Generated Machine-Readable Documentation
|
||||
|
||||
**⚠️ IMPORTANT**: Before starting development, generate fresh code maps for current codebase context:
|
||||
|
||||
```bash
|
||||
python tools/generate_code_map.py
|
||||
```
|
||||
|
||||
This generates machine-readable JSON maps optimized for LLM consumption in `.llm/` directory (git-ignored).
|
||||
|
||||
**When to use**:
|
||||
- At the start of every development session
|
||||
- After major refactoring or adding new files
|
||||
- When you need current codebase structure/API reference
|
||||
|
||||
## Core Development Rules
|
||||
|
||||
- **Use TDD methodology** for development
|
||||
- **Use static data types** in all GDScript code
|
||||
- **Keep documentation up to date** when making changes
|
||||
- **Always run tests**: `./tools/run_development.py --yaml --silent`
|
||||
|
||||
## Code Maps (LLM Development Workflow)
|
||||
|
||||
Machine-readable code intelligence for LLM-assisted development.
|
||||
|
||||
### Quick Reference: Which Map to Load?
|
||||
|
||||
| Task | Load This Map | Size | Contains |
|
||||
|------|---------------|------|----------|
|
||||
| Adding/modifying functions | `code_map_api.json` | ~47KB | Function signatures, parameters, return types |
|
||||
| System architecture work | `code_map_architecture.json` | ~32KB | Autoloads, design patterns, structure |
|
||||
| Signal/scene connections | `code_map_flows.json` | ~7KB | Signal chains, scene transitions |
|
||||
| Input validation/errors | `code_map_security.json` | ~12KB | Validation patterns, error handling |
|
||||
| Asset/resource management | `code_map_assets.json` | ~12KB | Dependencies, preloads, licensing |
|
||||
| Code metrics/statistics | `code_map_metadata.json` | ~300B | Stats, complexity metrics |
|
||||
|
||||
### Generated Files (Git-Ignored)
|
||||
|
||||
**Location**: `.llm/` directory (automatically excluded from git)
|
||||
|
||||
**Format**: All JSON files are minified (no whitespace) for optimal LLM token efficiency.
|
||||
|
||||
**Total size**: ~110KB if loading all maps (recommended: load only what you need)
|
||||
|
||||
### Generating Code Maps
|
||||
|
||||
```bash
|
||||
# Generate everything (default behavior - maps + diagrams + docs + metrics)
|
||||
python tools/generate_code_map.py
|
||||
|
||||
# Generate only specific outputs
|
||||
python tools/generate_code_map.py --diagrams # Diagrams only
|
||||
python tools/generate_code_map.py --docs # Documentation only
|
||||
python tools/generate_code_map.py --metrics # Metrics dashboard only
|
||||
|
||||
# Explicitly generate all (same as no arguments)
|
||||
python tools/generate_code_map.py --all
|
||||
|
||||
# Via development workflow tool
|
||||
python tools/run_development.py --codemap
|
||||
|
||||
# Custom output location (single JSON file)
|
||||
python tools/generate_code_map.py --output custom_map.json
|
||||
|
||||
# Skip PNG rendering (Mermaid source only)
|
||||
python tools/generate_code_map.py --diagrams --no-render
|
||||
```
|
||||
|
||||
**New Features**:
|
||||
- **Diagrams**: Auto-generated diagrams (architecture, signal flows, scene hierarchy, dependencies) rendered to PNG using matplotlib
|
||||
- **Documentation**: Human-readable markdown docs with embedded diagrams (AUTOLOADS_API.md, SIGNALS_CATALOG.md, FUNCTION_INDEX.md, etc.)
|
||||
- **Metrics**: Markdown dashboard with statistics charts and complexity analysis
|
||||
|
||||
### When to Regenerate
|
||||
|
||||
Regenerate code maps when:
|
||||
- **Starting an LLM-assisted development session** - Get fresh context
|
||||
- **After adding new files** - Include new code in maps
|
||||
- **After major refactoring** - Update structure information
|
||||
- **After changing autoloads** - Capture architectural changes
|
||||
- **When onboarding new developers** - Provide comprehensive context
|
||||
|
||||
### LLM Development Workflow
|
||||
|
||||
1. **Generate maps**: `python tools/generate_code_map.py`
|
||||
|
||||
2. **Load relevant maps**: Choose based on your task
|
||||
|
||||
**For API/Function development**:
|
||||
```
|
||||
Load: .llm/code_map_api.json (~47KB)
|
||||
Contains: All function signatures, parameters, return types, docstrings
|
||||
```
|
||||
|
||||
**For architecture/system design**:
|
||||
```
|
||||
Load: .llm/code_map_architecture.json (~32KB)
|
||||
Contains: Autoloads, design patterns, system structure
|
||||
```
|
||||
|
||||
**For signal/scene work**:
|
||||
```
|
||||
Load: .llm/code_map_flows.json (~7KB)
|
||||
Contains: Signal chains, scene transitions, connections
|
||||
```
|
||||
|
||||
**For security/validation**:
|
||||
```
|
||||
Load: .llm/code_map_security.json (~12KB)
|
||||
Contains: Input validation patterns, error handling
|
||||
```
|
||||
|
||||
**For assets/resources**:
|
||||
```
|
||||
Load: .llm/code_map_assets.json (~12KB)
|
||||
Contains: Asset dependencies, preloads, licensing
|
||||
```
|
||||
|
||||
**For metrics/stats**:
|
||||
```
|
||||
Load: .llm/code_map_metadata.json (~300B)
|
||||
Contains: Code statistics, complexity metrics
|
||||
```
|
||||
|
||||
3. **Use selectively**:
|
||||
- Load only maps relevant to current task
|
||||
- Reduces token usage and improves focus
|
||||
- Can always add more context later
|
||||
- Total size if loading all: ~110KB minified JSON
|
||||
|
||||
## Essential Coding Patterns
|
||||
|
||||
### Scene Management
|
||||
```gdscript
|
||||
# ✅ Correct - Use GameManager
|
||||
GameManager.start_game_with_mode("match3")
|
||||
|
||||
# ❌ Wrong - Never call directly
|
||||
get_tree().change_scene_to_file("res://scenes/game/Game.tscn")
|
||||
```
|
||||
|
||||
### Logging
|
||||
```gdscript
|
||||
# ✅ Correct - Use DebugManager with categories
|
||||
DebugManager.log_info("Scene loaded successfully", "GameManager")
|
||||
DebugManager.log_error("Failed to load resource", "AssetLoader")
|
||||
|
||||
# ❌ Wrong - Never use plain print/push_error
|
||||
print("Scene loaded") # No structure, no category
|
||||
push_error("Failed") # Missing context
|
||||
```
|
||||
|
||||
### Memory Management
|
||||
```gdscript
|
||||
# ✅ Correct - Use queue_free()
|
||||
for child in children_to_remove:
|
||||
child.queue_free()
|
||||
await get_tree().process_frame # Wait for cleanup
|
||||
|
||||
# ❌ Wrong - Never use free()
|
||||
child.free() # Immediate deletion causes crashes
|
||||
```
|
||||
|
||||
### Input Validation
|
||||
```gdscript
|
||||
# ✅ Correct - Validate all inputs
|
||||
func set_volume(value: float) -> bool:
|
||||
if value < 0.0 or value > 1.0:
|
||||
DebugManager.log_error("Invalid volume: " + str(value), "Settings")
|
||||
return false
|
||||
# Process validated input
|
||||
|
||||
# ❌ Wrong - No validation
|
||||
func set_volume(value: float):
|
||||
volume = value # Could be negative or > 1.0
|
||||
```
|
||||
|
||||
See [docs/CODE_OF_CONDUCT.md](docs/CODE_OF_CONDUCT.md) for complete coding standards.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
- **Development commands**: See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md)
|
||||
- **Testing protocols**: See [docs/TESTING.md](docs/TESTING.md)
|
||||
- **Architecture patterns**: See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
|
||||
- **Naming conventions**: See [docs/CODE_OF_CONDUCT.md](docs/CODE_OF_CONDUCT.md#naming-convention-quick-reference)
|
||||
- **Quality checklist**: See [docs/CODE_OF_CONDUCT.md](docs/CODE_OF_CONDUCT.md#code-quality-checklist)
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
skelly/
|
||||
├── src/autoloads/ # Global manager singletons
|
||||
├── scenes/game/gameplays/ # Gameplay mode implementations
|
||||
├── scenes/ui/ # Menu scenes and components
|
||||
├── assets/ # Audio, sprites, fonts (kebab-case)
|
||||
├── data/ # Godot resource files (.tres)
|
||||
├── docs/ # Documentation
|
||||
├── tests/ # Test scripts
|
||||
└── tools/ # Development tools
|
||||
```
|
||||
|
||||
See [docs/MAP.md](docs/MAP.md) for complete file organization.
|
||||
|
||||
## Key Files to Understand
|
||||
|
||||
- `src/autoloads/GameManager.gd` - Scene transitions with race condition protection
|
||||
- `src/autoloads/SaveManager.gd` - Save system with security features
|
||||
- `src/autoloads/SettingsManager.gd` - Settings with input validation
|
||||
- `src/autoloads/DebugManager.gd` - Unified logging and debug UI
|
||||
- `scenes/game/Game.gd` - Main game scene, modular gameplay system
|
||||
- `scenes/game/gameplays/Match3Gameplay.gd` - Match-3 implementation
|
||||
- `project.godot` - Input actions and autoload definitions
|
||||
97
DEVELOPMENT_TOOLS.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Development Tools
|
||||
|
||||
Development workflow tools for the Skelly Godot project.
|
||||
|
||||
Python script that handles code formatting, linting, and testing.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Run all development checks (recommended for pre-commit):
|
||||
```bash
|
||||
run_dev.bat
|
||||
```
|
||||
|
||||
Runs code formatting → linting → testing.
|
||||
|
||||
## Available Commands
|
||||
|
||||
### Main Unified Script
|
||||
- **`run_dev.bat`** - Main unified development script with all functionality
|
||||
|
||||
### Individual Tools (Legacy - redirect to unified script)
|
||||
- **`run_all.bat`** - Same as `run_dev.bat` (legacy compatibility)
|
||||
- **`run_lint.bat`** - Run only linting (redirects to `run_dev.bat --lint`)
|
||||
- **`run_format.bat`** - Run only formatting (redirects to `run_dev.bat --format`)
|
||||
- **`run_tests.bat`** - Run only tests (redirects to `run_dev.bat --test`)
|
||||
|
||||
## Usage Examples
|
||||
|
||||
```bash
|
||||
# Run all checks (default behavior)
|
||||
run_dev.bat
|
||||
|
||||
# Run only specific tools
|
||||
run_dev.bat --lint
|
||||
run_dev.bat --format
|
||||
run_dev.bat --test
|
||||
|
||||
# Run custom workflow steps
|
||||
run_dev.bat --steps format lint
|
||||
run_dev.bat --steps format test
|
||||
|
||||
# Show help
|
||||
run_dev.bat --help
|
||||
```
|
||||
|
||||
## What Each Tool Does
|
||||
|
||||
### 🔍 Linting (`gdlint`)
|
||||
- Checks GDScript code for style violations
|
||||
- Enforces naming conventions
|
||||
- Validates code structure and patterns
|
||||
- **Fails the workflow if errors are found**
|
||||
|
||||
### 🎨 Formatting (`gdformat`)
|
||||
- Automatically formats GDScript code
|
||||
- Ensures consistent indentation and spacing
|
||||
- Fixes basic style issues
|
||||
- **Fails the workflow if files cannot be formatted**
|
||||
|
||||
### 🧪 Testing (`godot`)
|
||||
- Runs all test files in `tests/` directory
|
||||
- Executes Godot scripts in headless mode
|
||||
- Reports test results and failures
|
||||
- **Continues workflow even if tests fail** (for review)
|
||||
|
||||
## Dependencies
|
||||
|
||||
The script automatically checks for and provides installation instructions for:
|
||||
- Python 3.x
|
||||
- pip
|
||||
- Godot Engine (for tests)
|
||||
- gdtoolkit (gdlint, gdformat)
|
||||
|
||||
## Output Features
|
||||
|
||||
- Colorized output
|
||||
- Emoji status indicators
|
||||
- Tool summaries
|
||||
- Execution time tracking
|
||||
- Warning suppression
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. **Before committing**: Run `run_dev.bat` to ensure code quality
|
||||
2. **Fix any linting errors** - the workflow will abort on errors
|
||||
3. **Review any test failures** - tests don't abort workflow but should be addressed
|
||||
4. **Commit your changes** once all checks pass
|
||||
|
||||
## Integration
|
||||
|
||||
Works with:
|
||||
- Git hooks (pre-commit)
|
||||
- CI/CD pipelines
|
||||
- IDE integrations
|
||||
- Manual development workflow
|
||||
|
||||
Legacy batch files remain functional.
|
||||
674
LICENSE
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) 2025 Vladimir @nett00n Budylnikov
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) 2025 Vladimir @nett00n Budylnikov
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
147
README.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# Skelly
|
||||
|
||||
Godot 4.4 mobile game project with multiple gameplay modes. Features modular gameplay system with match-3 puzzle (implemented) and planned clickomania mode.
|
||||
|
||||
**Tech Stack**: Godot 4.4, GDScript
|
||||
**Target Platform**: Mobile (Android/iOS)
|
||||
**Architecture**: See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for detailed system design
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Running the Project
|
||||
|
||||
1. **Open in Godot Editor**: Import `project.godot`
|
||||
2. **Run**: Press `F5` or click the "Play" button
|
||||
3. **Debug UI**: Press `F12` in-game to toggle debug panels
|
||||
|
||||
### Development Commands
|
||||
|
||||
```bash
|
||||
# Generate code maps for LLM development
|
||||
python tools/generate_code_map.py
|
||||
|
||||
# Run tests
|
||||
godot --headless --script tests/TestLogging.gd
|
||||
|
||||
# Development workflow helper
|
||||
python tools/run_development.py --codemap # Generate code maps
|
||||
python tools/run_development.py --test # Run tests
|
||||
```
|
||||
|
||||
See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for complete development workflows and commands.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
skelly/
|
||||
├── src/autoloads/ # Global manager singletons (GameManager, SaveManager, etc.)
|
||||
├── scenes/
|
||||
│ ├── game/gameplays/ # Gameplay mode implementations (Match3, Clickomania)
|
||||
│ ├── ui/components/ # Reusable UI components (ValueStepper, etc.)
|
||||
│ └── ui/ # Menu scenes (MainMenu, SettingsMenu)
|
||||
├── assets/ # Audio, sprites, fonts (kebab-case naming)
|
||||
├── data/ # Godot resource files (.tres)
|
||||
├── docs/ # Documentation (architecture, coding standards, testing)
|
||||
├── tests/ # Test scripts for system validation
|
||||
├── tools/ # Development tools (code map generator, etc.)
|
||||
└── localization/ # Translation files (English, Russian)
|
||||
```
|
||||
|
||||
See [docs/MAP.md](docs/MAP.md) for complete file organization details.
|
||||
|
||||
## Documentation
|
||||
|
||||
- **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)** - System design, autoloads, architectural patterns
|
||||
- **[docs/CODE_OF_CONDUCT.md](docs/CODE_OF_CONDUCT.md)** - Coding standards, naming conventions, best practices
|
||||
- **[docs/DEVELOPMENT.md](docs/DEVELOPMENT.md)** - Development workflows and commands
|
||||
- **[docs/TESTING.md](docs/TESTING.md)** - Testing procedures and protocols
|
||||
- **[docs/UI_COMPONENTS.md](docs/UI_COMPONENTS.md)** - UI component API reference
|
||||
- **[docs/MAP.md](docs/MAP.md)** - Complete project structure
|
||||
|
||||
## Features
|
||||
|
||||
- **Modular Gameplay System**: Easy to add new game modes
|
||||
- **Match-3 Puzzle**: Fully implemented with keyboard/gamepad support
|
||||
- **Settings Management**: Volume, language, difficulty with input validation
|
||||
- **Save System**: Secure save/load with tamper detection and auto-repair
|
||||
- **Debug System**: F12 toggle for debug panels on all major scenes
|
||||
- **Localization**: Multi-language support (English, Russian)
|
||||
- **Audio System**: Music and SFX with bus-based volume control
|
||||
|
||||
## Contributing
|
||||
|
||||
### Before Making Changes
|
||||
|
||||
1. Read [docs/CODE_OF_CONDUCT.md](docs/CODE_OF_CONDUCT.md) for coding standards
|
||||
2. Review [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for system design
|
||||
3. Check [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for workflows
|
||||
4. Follow the [quality checklist](docs/CODE_OF_CONDUCT.md#code-quality-checklist)
|
||||
|
||||
### Core Development Rules
|
||||
|
||||
- **Scene transitions**: Use `GameManager.start_game_with_mode()` - never call `get_tree().change_scene_to_file()` directly
|
||||
- **Logging**: Use `DebugManager.log_*()` functions with categories - never use plain `print()` or `push_error()`
|
||||
- **Memory management**: Always use `queue_free()` instead of `free()`
|
||||
- **Input validation**: Validate all user inputs with type/bounds/null checking
|
||||
- **TDD methodology**: Write tests for new functionality
|
||||
|
||||
See [docs/CODE_OF_CONDUCT.md](docs/CODE_OF_CONDUCT.md) for complete standards.
|
||||
|
||||
### Asset Management
|
||||
|
||||
- **Document every asset** in `assets/sources.yaml` before committing
|
||||
- Include source URL, license, attribution, modifications, and usage
|
||||
- Verify license compatibility (CC BY, CC0, Public Domain, etc.)
|
||||
- Commit asset files and sources.yaml together
|
||||
|
||||
See [docs/CODE_OF_CONDUCT.md](docs/CODE_OF_CONDUCT.md#asset-management) for detailed workflow.
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Manual testing
|
||||
# F5 in Godot Editor - Run project
|
||||
# F12 in-game - Toggle debug UI
|
||||
|
||||
# Automated testing
|
||||
godot --headless --script tests/TestLogging.gd
|
||||
godot --headless --script tests/test_checksum_issue.gd
|
||||
```
|
||||
|
||||
See [docs/TESTING.md](docs/TESTING.md) for complete testing procedures.
|
||||
|
||||
## Development Highlights
|
||||
|
||||
### Autoload System
|
||||
|
||||
Global singleton services providing core functionality:
|
||||
- **GameManager**: Scene transitions with race condition protection
|
||||
- **SaveManager**: Secure save/load with tamper detection
|
||||
- **SettingsManager**: Settings persistence with input validation
|
||||
- **DebugManager**: Unified logging and debug UI
|
||||
- **AudioManager**: Music and SFX playback
|
||||
- **LocalizationManager**: Language switching
|
||||
|
||||
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#autoload-system) for details.
|
||||
|
||||
### Code Quality Standards
|
||||
|
||||
- **Memory Safety**: `queue_free()` pattern for safe node cleanup
|
||||
- **Error Handling**: Comprehensive error handling with fallbacks
|
||||
- **Race Condition Protection**: State flags for async operations
|
||||
- **Instance-Based Architecture**: No global static state
|
||||
- **Input Validation**: Complete validation coverage
|
||||
|
||||
See [docs/CODE_OF_CONDUCT.md](docs/CODE_OF_CONDUCT.md#code-quality-checklist) for complete quality standards.
|
||||
|
||||
## Resources
|
||||
|
||||
- [Godot GDScript Style Guide](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_styleguide.html)
|
||||
- [Godot Best Practices](https://docs.godotengine.org/en/stable/tutorials/best_practices/index.html)
|
||||
- Project documentation in `docs/` directory
|
||||
|
||||
## License
|
||||
|
||||
GPLv3.0
|
||||
|
||||
See [LICENSE](LICENSE) file for more information
|
||||
24
assets/audio/music/817587__silverdubloons__tick06.wav.import
Normal file
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://briuh6uhf0tdt"
|
||||
path="res://.godot/imported/817587__silverdubloons__tick06.wav-6928d7a957ad4e7ff0ddda00a7348675.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/817587__silverdubloons__tick06.wav"
|
||||
dest_files=["res://.godot/imported/817587__silverdubloons__tick06.wav-6928d7a957ad4e7ff0ddda00a7348675.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://dcpehnwdueyyo"
|
||||
path="res://.godot/imported/Space Horror InGame Music (Exploration) _Clement Panchout.wav-b8a2c544037b0505487a02f0a4760b5c.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/audio/music/Space Horror InGame Music (Exploration) _Clement Panchout.wav"
|
||||
dest_files=["res://.godot/imported/Space Horror InGame Music (Exploration) _Clement Panchout.wav-b8a2c544037b0505487a02f0a4760b5c.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=2
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
assets/audio/sfx/817587__silverdubloons__tick06.wav
Normal file
24
assets/audio/sfx/817587__silverdubloons__tick06.wav.import
Normal file
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://bafsd057v7yvg"
|
||||
path="res://.godot/imported/817587__silverdubloons__tick06.wav-073a8f633d78aad3d77b2f7c8ae0c273.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/audio/sfx/817587__silverdubloons__tick06.wav"
|
||||
dest_files=["res://.godot/imported/817587__silverdubloons__tick06.wav-073a8f633d78aad3d77b2f7c8ae0c273.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
52
assets/sources.yaml
Normal file
@@ -0,0 +1,52 @@
|
||||
# Asset Attribution and Source Information
|
||||
# Every asset in this project must be documented here with complete metadata
|
||||
# Required fields: source, license, attribution, modifications, usage
|
||||
|
||||
audio:
|
||||
music:
|
||||
"Space Horror InGame Music (Exploration) _Clement Panchout.wav":
|
||||
source: "https://clement-panchout.itch.io/yet-another-free-music-pack"
|
||||
license: "" # TODO: Verify license from source
|
||||
attribution: "Space Horror InGame Music by Clement Panchout"
|
||||
modifications: "Converted to WAV format, loop configuration applied in AudioManager"
|
||||
usage: "Background music for all gameplay scenes and menus"
|
||||
|
||||
sfx:
|
||||
"817587__silverdubloons__tick06.wav":
|
||||
source: "https://freesound.org/people/SilverDubloons/sounds/817587/"
|
||||
license: "" # TODO: Verify license from freesound.org
|
||||
attribution: "Tick06 by SilverDubloons"
|
||||
modifications: "None"
|
||||
usage: "UI button click sound effects throughout the application"
|
||||
|
||||
sprites:
|
||||
characters:
|
||||
skeleton:
|
||||
"assets/sprites/characters/skeleton/*":
|
||||
source: "https://jesse-m.itch.io/skeleton-pack"
|
||||
license: "" # TODO: Verify license from itch.io page
|
||||
attribution: "Skeleton Pack by Jesse M"
|
||||
modifications: ""
|
||||
usage: "Placeholder for animation sprites"
|
||||
|
||||
skulls:
|
||||
"assets/sprites/skulls/*":
|
||||
source: "https://gitea.nett00n.org/nett00n/pixelart/src/branch/main/pixelorama/2025-skelly-assests"
|
||||
license: "CC"
|
||||
attribution: "Skelly icons by @nett00n"
|
||||
modifications: ""
|
||||
usage: ""
|
||||
|
||||
Referenced in original sources.yaml but file not found:
|
||||
textures:
|
||||
backgrounds:
|
||||
"BG.pg":
|
||||
source: "https://gitea.nett00n.org/nett00n/pixelart/src/branch/main/pixelorama/2025-skelly-assests"
|
||||
license: "CC"
|
||||
attribution: "Skelly icons by @nett00n"
|
||||
modifications: ""
|
||||
usage: ""
|
||||
|
||||
# TODO: Verify all license information by visiting source URLs
|
||||
# TODO: Check for any missing assets not documented here
|
||||
# TODO: Confirm all attribution text meets source requirements
|
||||
BIN
assets/sprites/characters/skeleton/Skeleton Attack.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cowabod6jrn47"
|
||||
path="res://.godot/imported/Skeleton Attack.png-31a9e5b2c10f873e155f012eeea3cf92.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/characters/skeleton/Skeleton Attack.png"
|
||||
dest_files=["res://.godot/imported/Skeleton Attack.png-31a9e5b2c10f873e155f012eeea3cf92.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
assets/sprites/characters/skeleton/Skeleton Dead.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
34
assets/sprites/characters/skeleton/Skeleton Dead.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://660wqxgwh8dr"
|
||||
path="res://.godot/imported/Skeleton Dead.png-e1687d67e643bab4b8c92b164a03a547.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/characters/skeleton/Skeleton Dead.png"
|
||||
dest_files=["res://.godot/imported/Skeleton Dead.png-e1687d67e643bab4b8c92b164a03a547.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
assets/sprites/characters/skeleton/Skeleton Hit.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
34
assets/sprites/characters/skeleton/Skeleton Hit.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://btfjyc4jfhiii"
|
||||
path="res://.godot/imported/Skeleton Hit.png-7311c991bcb76e5d6a77397d5558f61c.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/characters/skeleton/Skeleton Hit.png"
|
||||
dest_files=["res://.godot/imported/Skeleton Hit.png-7311c991bcb76e5d6a77397d5558f61c.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
assets/sprites/characters/skeleton/Skeleton Idle.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
34
assets/sprites/characters/skeleton/Skeleton Idle.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bcr4bokw87m5n"
|
||||
path="res://.godot/imported/Skeleton Idle.png-6287b112f02c0e4ac23abbe600ade526.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/characters/skeleton/Skeleton Idle.png"
|
||||
dest_files=["res://.godot/imported/Skeleton Idle.png-6287b112f02c0e4ac23abbe600ade526.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
assets/sprites/characters/skeleton/Skeleton React.png
Normal file
|
After Width: | Height: | Size: 673 B |
34
assets/sprites/characters/skeleton/Skeleton React.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cuo2o785qopo3"
|
||||
path="res://.godot/imported/Skeleton React.png-bae9c1ad14cb9e90c7d6e7d717e2699e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/characters/skeleton/Skeleton React.png"
|
||||
dest_files=["res://.godot/imported/Skeleton React.png-bae9c1ad14cb9e90c7d6e7d717e2699e.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
assets/sprites/characters/skeleton/Skeleton Walk.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
34
assets/sprites/characters/skeleton/Skeleton Walk.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b0dqy1at078ct"
|
||||
path="res://.godot/imported/Skeleton Walk.png-19ec7f5950a5cbfa9505c0dc4dd1b48f.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/characters/skeleton/Skeleton Walk.png"
|
||||
dest_files=["res://.godot/imported/Skeleton Walk.png-19ec7f5950a5cbfa9505c0dc4dd1b48f.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
assets/sprites/skulls/blue.png
Normal file
|
After Width: | Height: | Size: 205 B |
34
assets/sprites/skulls/blue.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dxq2ab2uo3fel"
|
||||
path="res://.godot/imported/blue.png-a5b81332a57b3efef9a75dd151a28d11.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/skulls/blue.png"
|
||||
dest_files=["res://.godot/imported/blue.png-a5b81332a57b3efef9a75dd151a28d11.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
assets/sprites/skulls/dark-blue.png
Normal file
|
After Width: | Height: | Size: 205 B |
34
assets/sprites/skulls/dark-blue.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bma78772dfdq3"
|
||||
path="res://.godot/imported/dark-blue.png-59d632e889a5b721da0ae18edaed44bb.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/skulls/dark-blue.png"
|
||||
dest_files=["res://.godot/imported/dark-blue.png-59d632e889a5b721da0ae18edaed44bb.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
assets/sprites/skulls/green.png
Normal file
|
After Width: | Height: | Size: 197 B |
34
assets/sprites/skulls/green.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bodkdsn8aqcs0"
|
||||
path="res://.godot/imported/green.png-ff6e1cc04288883fe02a8594d666e276.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/skulls/green.png"
|
||||
dest_files=["res://.godot/imported/green.png-ff6e1cc04288883fe02a8594d666e276.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
assets/sprites/skulls/grey.png
Normal file
|
After Width: | Height: | Size: 205 B |
34
assets/sprites/skulls/grey.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dnq7a0tfqs6xv"
|
||||
path="res://.godot/imported/grey.png-ec35f235cffbff0246c0b496073088b5.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/skulls/grey.png"
|
||||
dest_files=["res://.godot/imported/grey.png-ec35f235cffbff0246c0b496073088b5.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
assets/sprites/skulls/orange.png
Normal file
|
After Width: | Height: | Size: 193 B |
34
assets/sprites/skulls/orange.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://iq603aympcro"
|
||||
path="res://.godot/imported/orange.png-8b2bb01a523f1a7b73fd852bdfe7ef38.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/skulls/orange.png"
|
||||
dest_files=["res://.godot/imported/orange.png-8b2bb01a523f1a7b73fd852bdfe7ef38.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
assets/sprites/skulls/pink.png
Normal file
|
After Width: | Height: | Size: 195 B |
34
assets/sprites/skulls/pink.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ckslt30117ow5"
|
||||
path="res://.godot/imported/pink.png-8e1ef4f0f945c54fb98f36f4cbd18350.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/skulls/pink.png"
|
||||
dest_files=["res://.godot/imported/pink.png-8e1ef4f0f945c54fb98f36f4cbd18350.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
assets/sprites/skulls/purple.png
Normal file
|
After Width: | Height: | Size: 198 B |
34
assets/sprites/skulls/purple.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dhxqh8wegngyu"
|
||||
path="res://.godot/imported/purple.png-532ae50f2abc0def69a2a3e5012ac904.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/skulls/purple.png"
|
||||
dest_files=["res://.godot/imported/purple.png-532ae50f2abc0def69a2a3e5012ac904.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
assets/sprites/skulls/red.png
Normal file
|
After Width: | Height: | Size: 193 B |
34
assets/sprites/skulls/red.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d4mn1p6620x5s"
|
||||
path="res://.godot/imported/red.png-b1cc6fdfcc710fe28188480ae838b7ce.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/skulls/red.png"
|
||||
dest_files=["res://.godot/imported/red.png-b1cc6fdfcc710fe28188480ae838b7ce.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
assets/sprites/skulls/yellow.png
Normal file
|
After Width: | Height: | Size: 196 B |
34
assets/sprites/skulls/yellow.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://fw01lg2olk7f"
|
||||
path="res://.godot/imported/yellow.png-b1e27f543291797e145a5e83f2d9c671.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/sprites/skulls/yellow.png"
|
||||
dest_files=["res://.godot/imported/yellow.png-b1e27f543291797e145a5e83f2d9c671.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
assets/textures/backgrounds/BGx3.png
Normal file
|
After Width: | Height: | Size: 350 B |
34
assets/textures/backgrounds/BGx3.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bengv32u1jeym"
|
||||
path="res://.godot/imported/BGx3.png-7878045c31a8f7297b620b7e42c1a5bf.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/textures/backgrounds/BGx3.png"
|
||||
dest_files=["res://.godot/imported/BGx3.png-7878045c31a8f7297b620b7e42c1a5bf.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
15
data/default_bus_layout.tres
Normal file
@@ -0,0 +1,15 @@
|
||||
[gd_resource type="AudioBusLayout" format=3 uid="uid://c4bwiq14nq074"]
|
||||
|
||||
[resource]
|
||||
bus/1/name = &"SFX"
|
||||
bus/1/solo = false
|
||||
bus/1/mute = false
|
||||
bus/1/bypass_fx = false
|
||||
bus/1/volume_db = 0.0
|
||||
bus/1/send = &"Master"
|
||||
bus/2/name = &"Music"
|
||||
bus/2/solo = false
|
||||
bus/2/mute = false
|
||||
bus/2/bypass_fx = false
|
||||
bus/2/volume_db = 0.0
|
||||
bus/2/send = &"Master"
|
||||
424
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,424 @@
|
||||
# System Architecture
|
||||
|
||||
High-level architecture guide for the Skelly project, explaining system design, architectural patterns, and design decisions.
|
||||
|
||||
**Quick Links**:
|
||||
- **Coding Standards**: See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
|
||||
- **Testing Protocols**: See [TESTING.md](TESTING.md)
|
||||
- **Component APIs**: See [UI_COMPONENTS.md](UI_COMPONENTS.md)
|
||||
|
||||
## Overview
|
||||
|
||||
Skelly uses a service-oriented architecture with **autoload managers** providing global services, **scene-based components** implementing gameplay, and **signal-based communication** for loose coupling.
|
||||
|
||||
**Key Architectural Principles**:
|
||||
- **Instance-based design**: No global static state for testability
|
||||
- **Signal-driven communication**: Loose coupling between systems
|
||||
- **Service layer pattern**: Autoloads as singleton services
|
||||
- **State machines**: Explicit state management for complex flows
|
||||
- **Defensive programming**: Input validation, error recovery, fallback mechanisms
|
||||
|
||||
## Autoload System
|
||||
|
||||
Autoloads provide singleton services accessible globally. Each has a specific responsibility.
|
||||
|
||||
### GameManager
|
||||
**Responsibility**: Scene transitions and game state coordination
|
||||
|
||||
**Key Features**:
|
||||
- Centralized scene loading with error handling
|
||||
- Race condition protection via `is_changing_scene` flag
|
||||
- Scene path constants for maintainability
|
||||
- Gameplay mode validation with whitelist
|
||||
- Never use `get_tree().change_scene_to_file()` directly
|
||||
|
||||
**Usage**:
|
||||
```gdscript
|
||||
# ✅ Correct
|
||||
GameManager.start_game_with_mode("match3")
|
||||
|
||||
# ❌ Wrong
|
||||
get_tree().change_scene_to_file("res://scenes/game/Game.tscn")
|
||||
```
|
||||
|
||||
**Files**: `src/autoloads/GameManager.gd`
|
||||
|
||||
### SaveManager
|
||||
**Responsibility**: Save/load game state with security and data integrity
|
||||
|
||||
**Key Features**:
|
||||
- **Tamper Detection**: Deterministic checksums detect save file modification
|
||||
- **Race Condition Protection**: Save operation locking prevents concurrent conflicts
|
||||
- **Permissive Validation**: Auto-repair system fixes corrupted data
|
||||
- **Type Safety**: NaN/Infinity/bounds checking for numeric values
|
||||
- **Memory Protection**: File size limits prevent memory exhaustion
|
||||
- **Version Migration**: Backward-compatible save format upgrades
|
||||
- **Error Recovery**: Multi-layered backup and fallback systems
|
||||
|
||||
**Security Model**:
|
||||
```gdscript
|
||||
# Checksum validates data integrity
|
||||
save_data["_checksum"] = _calculate_checksum(save_data)
|
||||
|
||||
# Auto-repair fixes corrupted fields
|
||||
if not _validate_save_data(data):
|
||||
data = _repair_save_data(data)
|
||||
|
||||
# Race condition protection
|
||||
if _is_saving:
|
||||
return # Prevent concurrent saves
|
||||
```
|
||||
|
||||
**Testing**: See [TESTING.md](TESTING.md#save-system-testing-protocols) for comprehensive test suites validating checksums, migration, and integration.
|
||||
|
||||
**Files**: `src/autoloads/SaveManager.gd`
|
||||
|
||||
### SettingsManager
|
||||
**Responsibility**: User settings persistence and validation
|
||||
|
||||
**Key Features**:
|
||||
- Input validation with NaN/Infinity checks
|
||||
- Bounds checking for numeric values
|
||||
- Security hardening against invalid inputs
|
||||
- Default fallback values
|
||||
- Type coercion with validation
|
||||
|
||||
**Files**: `src/autoloads/SettingsManager.gd`
|
||||
|
||||
### DebugManager
|
||||
**Responsibility**: Unified logging and debug UI coordination
|
||||
|
||||
**Key Features**:
|
||||
- Structured logging with log levels (TRACE, DEBUG, INFO, WARN, ERROR, FATAL)
|
||||
- Category-based log organization
|
||||
- Global debug UI toggle (F12 key)
|
||||
- Log level filtering for development/production
|
||||
- Replaces all `print()` and `push_error()` calls
|
||||
|
||||
**Usage**: See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md#logging-standards) for logging best practices.
|
||||
|
||||
**Files**: `src/autoloads/DebugManager.gd`
|
||||
|
||||
### AudioManager
|
||||
**Responsibility**: Music and sound effect playback
|
||||
|
||||
**Key Features**:
|
||||
- Audio bus system (Music, SFX)
|
||||
- Volume control per bus
|
||||
- Loop configuration for music
|
||||
- UI click sounds
|
||||
|
||||
**Files**: `src/autoloads/AudioManager.gd`
|
||||
|
||||
### LocalizationManager
|
||||
**Responsibility**: Language switching and translation management
|
||||
|
||||
**Key Features**:
|
||||
- Multi-language support (English, Russian)
|
||||
- Runtime language switching
|
||||
- Translation file management
|
||||
|
||||
**Files**: `src/autoloads/LocalizationManager.gd`
|
||||
|
||||
## Scene Management Pattern
|
||||
|
||||
All scene transitions flow through **GameManager** to ensure consistent error handling and state management.
|
||||
|
||||
### Scene Loading Flow
|
||||
|
||||
```
|
||||
User Action
|
||||
↓
|
||||
GameManager.start_game_with_mode(mode)
|
||||
↓
|
||||
Validate mode (whitelist check)
|
||||
↓
|
||||
Check is_changing_scene flag
|
||||
↓
|
||||
Load PackedScene with validation
|
||||
↓
|
||||
change_scene_to_packed()
|
||||
↓
|
||||
Reset is_changing_scene flag
|
||||
```
|
||||
|
||||
### Race Condition Prevention
|
||||
|
||||
```gdscript
|
||||
var is_changing_scene: bool = false
|
||||
|
||||
func start_game_with_mode(gameplay_mode: String) -> void:
|
||||
# Prevent concurrent scene changes
|
||||
if is_changing_scene:
|
||||
DebugManager.log_warn("Scene change already in progress", "GameManager")
|
||||
return
|
||||
|
||||
is_changing_scene = true
|
||||
# ... scene loading logic ...
|
||||
is_changing_scene = false
|
||||
```
|
||||
|
||||
**Why This Matters**: Multiple rapid button clicks or input events could trigger concurrent scene loads, causing crashes or undefined state.
|
||||
|
||||
## Modular Gameplay System
|
||||
|
||||
Game modes are implemented as separate gameplay modules in `scenes/game/gameplays/`.
|
||||
|
||||
### Gameplay Architecture
|
||||
|
||||
```
|
||||
Game.tscn (Main scene)
|
||||
↓
|
||||
├─> Match3Gameplay.tscn (Match-3 mode)
|
||||
├─> ClickomaniaGameplay.tscn (Clickomania mode)
|
||||
└─> [Future gameplay modes]
|
||||
```
|
||||
|
||||
**Pattern**: Each gameplay mode:
|
||||
- Extends Control or Node2D
|
||||
- Emits `score_changed` signal
|
||||
- Implements `_ready()` for initialization
|
||||
- Handles input independently
|
||||
- Includes optional debug menu
|
||||
|
||||
**Files**: `scenes/game/gameplays/`
|
||||
|
||||
## Design Patterns Used
|
||||
|
||||
### Instance-Based Architecture
|
||||
|
||||
**Problem**: Static variables prevent testing and create hidden dependencies.
|
||||
|
||||
**Solution**: Instance-based architecture with explicit dependencies.
|
||||
|
||||
```gdscript
|
||||
# ❌ Bad: Static global state
|
||||
static var current_gem_pool = [0, 1, 2, 3, 4]
|
||||
|
||||
# ✅ Good: Instance-based
|
||||
var active_gem_types: Array = []
|
||||
|
||||
func set_active_gem_types(gem_indices: Array) -> void:
|
||||
active_gem_types = gem_indices.duplicate()
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- Each instance isolated for testing
|
||||
- No hidden global state
|
||||
- Explicit dependencies
|
||||
- Thread-safe by default
|
||||
|
||||
### Signal-Based Communication
|
||||
|
||||
**Pattern**: Use signals for loose coupling between systems.
|
||||
|
||||
```gdscript
|
||||
# Component emits signal
|
||||
signal score_changed(new_score: int)
|
||||
|
||||
# Parent connects to signal
|
||||
gameplay.score_changed.connect(_on_score_changed)
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- Loose coupling
|
||||
- Easy to test components in isolation
|
||||
- Clear event flow
|
||||
- Flexible subscription model
|
||||
|
||||
### Service Layer Pattern
|
||||
|
||||
Autoloads act as singleton services providing global functionality.
|
||||
|
||||
**Pattern**:
|
||||
- Autoloads expose public API methods
|
||||
- Components call autoload methods
|
||||
- Autoloads emit signals for state changes
|
||||
- Never nest autoload calls deeply
|
||||
|
||||
### State Machine Pattern
|
||||
|
||||
Complex workflows use explicit state machines.
|
||||
|
||||
**Example**: Match-3 tile swapping
|
||||
```
|
||||
WAITING → SELECTING → SWAPPING → PROCESSING → WAITING
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- Clear state transitions
|
||||
- Easy to debug
|
||||
- Prevents invalid state combinations
|
||||
- Self-documenting code
|
||||
|
||||
## Critical Architecture Decisions
|
||||
|
||||
### Memory Management: queue_free() Over free()
|
||||
|
||||
**Decision**: Always use `queue_free()` instead of `free()` for node cleanup.
|
||||
|
||||
**Rationale**:
|
||||
- `free()` causes immediate deletion (crashes if referenced)
|
||||
- `queue_free()` waits until safe deletion point
|
||||
- Prevents use-after-free bugs
|
||||
|
||||
**Implementation**:
|
||||
```gdscript
|
||||
# ✅ Correct
|
||||
for child in children_to_remove:
|
||||
child.queue_free()
|
||||
await get_tree().process_frame # Wait for cleanup
|
||||
|
||||
# ❌ Dangerous
|
||||
for child in children_to_remove:
|
||||
child.free() # Can crash
|
||||
```
|
||||
|
||||
**Impact**: Eliminated multiple potential crash points. See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md#memory-management-standards) for standards.
|
||||
|
||||
### Race Condition Prevention: State Flags
|
||||
|
||||
**Decision**: Use state flags to prevent concurrent operations.
|
||||
|
||||
**Rationale**:
|
||||
- Async operations can overlap without protection
|
||||
- Multiple rapid inputs can trigger race conditions
|
||||
- State flags provide simple, effective protection
|
||||
|
||||
**Implementation**: Used in GameManager (scene loading), SaveManager (save operations), and gameplay systems (tile swapping).
|
||||
|
||||
### Error Recovery: Fallback Mechanisms
|
||||
|
||||
**Decision**: Provide fallback behavior for all critical failures.
|
||||
|
||||
**Rationale**:
|
||||
- Games should degrade gracefully, not crash
|
||||
- User experience > strict validation
|
||||
- Log errors but continue operation
|
||||
|
||||
**Examples**:
|
||||
- Settings file corrupted? Load defaults
|
||||
- Scene load failed? Return to main menu
|
||||
- Audio file missing? Continue without sound
|
||||
|
||||
### Input Validation: Whitelist Approach
|
||||
|
||||
**Decision**: Validate all user inputs against known-good values.
|
||||
|
||||
**Rationale**:
|
||||
- Security hardening
|
||||
- Prevent invalid state
|
||||
- Clear error messages
|
||||
- Self-documenting code
|
||||
|
||||
**Implementation**: Used in SettingsManager (volume, language), GameManager (gameplay modes), Match3Gameplay (grid movements).
|
||||
|
||||
## System Interactions
|
||||
|
||||
### Typical Game Flow
|
||||
|
||||
```
|
||||
Main Menu
|
||||
↓
|
||||
[GameManager.start_game_with_mode("match3")]
|
||||
↓
|
||||
Game Scene Loads
|
||||
↓
|
||||
Match3Gameplay initializes
|
||||
↓
|
||||
├─> SettingsManager (load difficulty)
|
||||
├─> AudioManager (play background music)
|
||||
└─> DebugManager (setup debug UI)
|
||||
↓
|
||||
Gameplay Loop
|
||||
↓
|
||||
├─> Input handling
|
||||
├─> Score updates (emit score_changed signal)
|
||||
├─> SaveManager (autosave high score)
|
||||
└─> DebugManager (log important events)
|
||||
```
|
||||
|
||||
### Signal Flow Example: Score Change
|
||||
|
||||
```
|
||||
Match3Gameplay detects match
|
||||
↓
|
||||
[emit score_changed(new_score)]
|
||||
↓
|
||||
Game.gd receives signal
|
||||
↓
|
||||
Updates score display UI
|
||||
↓
|
||||
SaveManager.save_high_score(score)
|
||||
```
|
||||
|
||||
### Debug System Integration
|
||||
|
||||
```
|
||||
User presses F12
|
||||
↓
|
||||
DebugManager.toggle_debug_ui()
|
||||
↓
|
||||
[emit debug_ui_toggled(visible)]
|
||||
↓
|
||||
All debug menus receive signal
|
||||
↓
|
||||
Show/hide debug panels
|
||||
```
|
||||
|
||||
## Quality Improvements
|
||||
|
||||
The project implements high-quality code standards from the start:
|
||||
|
||||
**Key Quality Features**:
|
||||
- **Memory Safety**: Uses `queue_free()` pattern for safe node cleanup
|
||||
- **Error Handling**: Comprehensive error handling with fallbacks
|
||||
- **Race Condition Protection**: State flag protection for async operations
|
||||
- **Instance-Based Architecture**: No global static state for testability
|
||||
- **Code Reuse**: Base class architecture (DebugMenuBase) for common functionality
|
||||
- **Input Validation**: Complete validation coverage for all user inputs
|
||||
|
||||
These standards are enforced through the [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md#code-quality-checklist) quality checklist.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Extending the Architecture
|
||||
|
||||
When adding new features:
|
||||
|
||||
1. **New autoload?**
|
||||
- Only if truly global and used by many systems
|
||||
- Consider dependency injection instead
|
||||
- Keep autoloads focused on single responsibility
|
||||
|
||||
2. **New gameplay mode?**
|
||||
- Create in `scenes/game/gameplays/`
|
||||
- Extend appropriate base class
|
||||
- Emit `score_changed` signal
|
||||
- Add to GameManager mode whitelist
|
||||
|
||||
3. **New UI component?**
|
||||
- Create in `scenes/ui/components/`
|
||||
- Follow [UI_COMPONENTS.md](UI_COMPONENTS.md) patterns
|
||||
- Support keyboard/gamepad navigation
|
||||
- Emit signals for state changes
|
||||
|
||||
### Architecture Review Checklist
|
||||
|
||||
Before committing architectural changes:
|
||||
|
||||
- [ ] No new global static state introduced
|
||||
- [ ] All autoload access justified and documented
|
||||
- [ ] Signal-based communication used appropriately
|
||||
- [ ] Error handling with fallbacks implemented
|
||||
- [ ] Input validation in place
|
||||
- [ ] Memory management uses `queue_free()`
|
||||
- [ ] Race condition protection if async operations
|
||||
- [ ] Testing strategy defined
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- **[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)**: Coding standards and best practices
|
||||
- **[TESTING.md](TESTING.md)**: Testing protocols and procedures
|
||||
- **[UI_COMPONENTS.md](UI_COMPONENTS.md)**: Component API reference
|
||||
- **[CLAUDE.md](CLAUDE.md)**: LLM assistant quick start guide
|
||||
655
docs/CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,655 @@
|
||||
# Code of Conduct - Skelly Project
|
||||
|
||||
## Overview
|
||||
|
||||
Coding standards and development practices for the Skelly project. These guidelines help developers contribute effectively while maintaining code quality and project consistency.
|
||||
|
||||
## Core Principles
|
||||
|
||||
### 1. Code Clarity Over Cleverness
|
||||
- Write code that is easy to read
|
||||
- Use descriptive variable and function names
|
||||
- Prefer explicit code over "clever" solutions
|
||||
- Comment complex logic and business rules
|
||||
|
||||
### 2. Consistency First
|
||||
- Follow existing code patterns
|
||||
- Use same naming conventions throughout
|
||||
- Maintain consistent indentation and formatting
|
||||
- Follow Godot's GDScript style guide
|
||||
|
||||
### 3. Incremental Development
|
||||
- Make small, focused commits
|
||||
- Test changes before committing
|
||||
- Don't break existing functionality
|
||||
- Use debug system to verify changes
|
||||
|
||||
## GDScript Coding Standards
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
> 📋 **Quick Reference**: For complete naming convention details, see the **[Naming Convention Quick Reference](#naming-convention-quick-reference)** section below.
|
||||
|
||||
```gdscript
|
||||
# Variables and functions: snake_case
|
||||
var player_health: int = 100
|
||||
func calculate_damage() -> int:
|
||||
|
||||
# Constants: SCREAMING_SNAKE_CASE
|
||||
const MAX_HEALTH := 100
|
||||
const TILE_SPACING := 54
|
||||
|
||||
# Classes: PascalCase
|
||||
class_name PlayerController
|
||||
|
||||
# Scene files (.tscn) and Script files (.gd): PascalCase
|
||||
# MainMenu.tscn, MainMenu.gd
|
||||
# Match3Gameplay.tscn, Match3Gameplay.gd
|
||||
# TestAudioManager.gd (test files)
|
||||
|
||||
# Signals: past_tense
|
||||
signal health_changed
|
||||
signal game_started
|
||||
|
||||
# Private functions: prefix with underscore
|
||||
func _ready():
|
||||
func _initialize_grid():
|
||||
```
|
||||
|
||||
### File Organization
|
||||
```gdscript
|
||||
# 1. extends statement
|
||||
extends Node2D
|
||||
|
||||
# 2. class_name (if applicable)
|
||||
class_name Match3Controller
|
||||
|
||||
# 3. Constants
|
||||
const GRID_SIZE := Vector2i(8, 8)
|
||||
|
||||
# 4. Signals
|
||||
signal match_found(tiles: Array)
|
||||
|
||||
# 5. Variables
|
||||
var grid := []
|
||||
@onready var debug_ui = $DebugUI
|
||||
|
||||
# 6. Functions (lifecycle first, then custom)
|
||||
func _ready():
|
||||
func _process(delta):
|
||||
func custom_function():
|
||||
```
|
||||
|
||||
### Documentation Requirements
|
||||
```gdscript
|
||||
# Required for all public functions
|
||||
func set_gem_pool(gem_indices: Array) -> void:
|
||||
"""Set specific gem types as the active pool"""
|
||||
_update_all_tiles_gem_pool(gem_indices)
|
||||
print("Set gem pool to: ", gem_indices)
|
||||
|
||||
# Document complex algorithms
|
||||
func _get_match_line(start: Vector2i, dir: Vector2i) -> Array:
|
||||
"""Find all matching tiles in a line from start position in given direction"""
|
||||
var line = [grid[start.y][start.x]]
|
||||
var type = grid[start.y][start.x].tile_type
|
||||
# Implementation...
|
||||
```
|
||||
|
||||
## Project-Specific Guidelines
|
||||
|
||||
### Scene Management
|
||||
- All scene transitions go through `GameManager`
|
||||
- Never use `get_tree().change_scene_to_file()` directly
|
||||
- Define scene paths as constants in GameManager
|
||||
|
||||
```gdscript
|
||||
# ✅ Correct
|
||||
GameManager.start_match3_game()
|
||||
|
||||
# ❌ Wrong
|
||||
get_tree().change_scene_to_file("res://scenes/game/Game.tscn")
|
||||
```
|
||||
|
||||
### Autoload Usage
|
||||
- Use autoloads for global state only
|
||||
- Don't access autoloads from deeply nested components
|
||||
- Pass data through signals or direct references when possible
|
||||
|
||||
```gdscript
|
||||
# ✅ Correct - using signals
|
||||
signal settings_changed
|
||||
SettingsManager.volume_changed.connect(_on_volume_changed)
|
||||
|
||||
# ✅ Also correct - direct access for global state
|
||||
var current_language = SettingsManager.get_setting("language")
|
||||
|
||||
# ❌ Wrong - tight coupling
|
||||
func some_deep_function():
|
||||
SettingsManager.set_setting("volume", 0.5) # Too deep in call stack
|
||||
```
|
||||
|
||||
### Debug System Integration
|
||||
- Always connect to DebugManager signals for debug UI
|
||||
- Use structured logging instead of plain print statements
|
||||
- Remove temporary debug logs before committing (unless permanently useful)
|
||||
|
||||
```gdscript
|
||||
# ✅ Correct debug integration
|
||||
func _connect_to_global_debug() -> void:
|
||||
DebugManager.debug_ui_toggled.connect(_on_debug_ui_toggled)
|
||||
debug_ui.visible = DebugManager.is_debug_ui_visible()
|
||||
|
||||
# ✅ Good structured logging
|
||||
DebugManager.log_debug("Debug UI toggled to: " + str(visible), "Match3")
|
||||
DebugManager.log_info("Initialized " + str(tiles.size()) + " tiles", "TileSystem")
|
||||
|
||||
# ❌ Bad logging practices
|
||||
print("test") # Not descriptive, use structured logging
|
||||
print(some_variable) # No context, use proper log level
|
||||
```
|
||||
|
||||
### Logging Standards
|
||||
- Use `DebugManager.log_*()` functions instead of `print()` or `push_error()`
|
||||
- Choose log levels based on message importance and audience
|
||||
- Include categories to organize log output by system/component
|
||||
- Format messages with clear, descriptive text and relevant context
|
||||
|
||||
```gdscript
|
||||
# ✅ Correct logging usage
|
||||
DebugManager.log_info("Game scene loaded successfully", "SceneManager")
|
||||
DebugManager.log_warn("Audio file not found, using default", "AudioManager")
|
||||
DebugManager.log_error("Failed to save settings: " + error_message, "Settings")
|
||||
|
||||
# ❌ Wrong approaches
|
||||
print("loaded") # Use DebugManager.log_info() with category
|
||||
push_error("error") # Use DebugManager.log_error() with context
|
||||
if debug_mode: print("debug info") # Use DebugManager.log_debug()
|
||||
```
|
||||
|
||||
### Asset Management
|
||||
- **Document every asset** in `assets/sources.yaml`
|
||||
- Include source information, license details, and attribution
|
||||
- Document modifications made to original assets
|
||||
- Verify license compatibility before adding assets
|
||||
- Update sources.yaml in same commit as adding asset
|
||||
|
||||
```gdscript
|
||||
# ✅ Correct asset addition workflow
|
||||
# 1. Add asset file to appropriate assets/ subdirectory
|
||||
# 2. Update assets/sources.yaml with complete metadata
|
||||
# 3. Commit both files together with descriptive message
|
||||
|
||||
# Example sources.yaml entry:
|
||||
# audio:
|
||||
# sfx:
|
||||
# "button_click.wav":
|
||||
# source: "https://freesound.org/people/user/sounds/12345/"
|
||||
# license: "CC BY 3.0"
|
||||
# attribution: "Button Click by SoundArtist"
|
||||
# modifications: "Normalized volume, trimmed silence"
|
||||
# usage: "UI button interactions"
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
- Check if resources loaded successfully
|
||||
- Use `DebugManager.log_error()` for critical failures
|
||||
- Provide fallback behavior when possible
|
||||
- Include meaningful context in error messages
|
||||
|
||||
```gdscript
|
||||
# Good error handling with structured logging
|
||||
func load_scene(path: String) -> void:
|
||||
var packed_scene := load(path)
|
||||
if not packed_scene or not packed_scene is PackedScene:
|
||||
DebugManager.log_error("Failed to load scene at: %s" % path, "SceneLoader")
|
||||
return
|
||||
DebugManager.log_info("Successfully loaded scene: %s" % path, "SceneLoader")
|
||||
get_tree().change_scene_to_packed(packed_scene)
|
||||
```
|
||||
|
||||
### Memory Management Standards
|
||||
|
||||
**Critical Rules**:
|
||||
1. **Always use `queue_free()`** instead of `free()` for node cleanup
|
||||
2. **Wait for frame completion** after queueing nodes for removal
|
||||
3. **Clear references before cleanup** to prevent access to freed memory
|
||||
4. **Connect signals properly** for dynamically created nodes
|
||||
|
||||
```gdscript
|
||||
# ✅ Correct memory management
|
||||
for child in children_to_remove:
|
||||
child.queue_free()
|
||||
await get_tree().process_frame # Wait for cleanup
|
||||
|
||||
# ❌ Dangerous pattern (causes crashes)
|
||||
for child in children_to_remove:
|
||||
child.free() # Immediate deletion can crash
|
||||
```
|
||||
|
||||
**Why This Matters**: Using `free()` causes immediate deletion which can crash if the node is still referenced elsewhere. `queue_free()` waits until it's safe to delete.
|
||||
|
||||
**See Also**: [ARCHITECTURE.md](ARCHITECTURE.md#memory-management-queue_free-over-free) for architectural rationale.
|
||||
|
||||
### Input Validation Standards
|
||||
|
||||
All user inputs must be validated before processing.
|
||||
|
||||
**Validation Requirements**:
|
||||
1. **Type checking**: Verify input types before processing
|
||||
2. **Bounds checking**: Validate numeric ranges and array indices
|
||||
3. **Null checking**: Handle null and empty inputs gracefully
|
||||
4. **Whitelist validation**: Validate against known good values
|
||||
|
||||
```gdscript
|
||||
# ✅ Proper input validation
|
||||
func set_volume(value: float, setting_key: String) -> bool:
|
||||
# Whitelist validation
|
||||
if not setting_key in ["master_volume", "music_volume", "sfx_volume"]:
|
||||
DebugManager.log_error("Invalid volume setting key: " + str(setting_key), "Settings")
|
||||
return false
|
||||
|
||||
# Bounds checking
|
||||
var clamped_value = clamp(value, 0.0, 1.0)
|
||||
if clamped_value != value:
|
||||
DebugManager.log_warn("Volume value clamped from %f to %f" % [value, clamped_value], "Settings")
|
||||
|
||||
SettingsManager.set_setting(setting_key, clamped_value)
|
||||
return true
|
||||
|
||||
# ✅ Grid movement validation
|
||||
func _move_cursor(direction: Vector2i) -> void:
|
||||
# Bounds checking
|
||||
if abs(direction.x) > 1 or abs(direction.y) > 1:
|
||||
DebugManager.log_error("Invalid cursor direction: " + str(direction), "Match3")
|
||||
return
|
||||
|
||||
# Null/empty checking
|
||||
if not grid or grid.is_empty():
|
||||
DebugManager.log_error("Grid not initialized", "Match3")
|
||||
return
|
||||
|
||||
# Process validated input
|
||||
var new_pos = cursor_pos + direction
|
||||
# ... continue with validated data
|
||||
```
|
||||
|
||||
**See Also**: [ARCHITECTURE.md](ARCHITECTURE.md#input-validation-whitelist-approach) for architectural decision rationale.
|
||||
|
||||
## Code Quality Checklist
|
||||
|
||||
Before committing code, verify:
|
||||
|
||||
- [ ] All user inputs validated (type, bounds, null checks)
|
||||
- [ ] Error handling with fallback mechanisms implemented
|
||||
- [ ] Memory cleanup uses `queue_free()` not `free()`
|
||||
- [ ] No global static state introduced
|
||||
- [ ] Proper logging with categories and appropriate levels
|
||||
- [ ] Race condition protection for async operations
|
||||
- [ ] Input actions defined in project input map
|
||||
- [ ] Resources loaded with null/type checking
|
||||
- [ ] Documentation updated for new public APIs
|
||||
- [ ] Test coverage for new functionality
|
||||
|
||||
## Git Workflow
|
||||
|
||||
### Commit Guidelines
|
||||
- Use clear, descriptive commit messages
|
||||
- Start with a verb in imperative mood
|
||||
- Keep first line under 50 characters
|
||||
- Add body if needed for complex changes
|
||||
|
||||
```bash
|
||||
# Good commit messages
|
||||
Add gem pool management to match-3 system
|
||||
Fix debug UI visibility toggle issue
|
||||
Update documentation for new debug system
|
||||
|
||||
# Bad commit messages
|
||||
fix bug
|
||||
update
|
||||
wip
|
||||
```
|
||||
|
||||
### Branch Management
|
||||
- Create feature branches for new functionality
|
||||
- Use descriptive branch names: `feature/debug-ui`, `fix/tile-positioning`
|
||||
- Keep branches focused on single features
|
||||
- Delete branches after merging
|
||||
|
||||
### Before Committing
|
||||
1. Test your changes in the Godot editor
|
||||
2. Check that existing functionality still works
|
||||
3. Use the debug system to verify your implementation
|
||||
4. Run the project and navigate through affected areas
|
||||
5. Remove temporary debug code
|
||||
|
||||
## Code Review Guidelines
|
||||
|
||||
### For Reviewers
|
||||
- Focus on code clarity and maintainability
|
||||
- Check for adherence to project patterns
|
||||
- Verify that autoloads are used appropriately
|
||||
- Ensure debug integration is properly implemented
|
||||
- Test the changes yourself
|
||||
|
||||
### For Contributors
|
||||
- Explain complex logic in commit messages or comments
|
||||
- Provide context for why changes were made
|
||||
- Test edge cases and error conditions
|
||||
- Ask questions if project patterns are unclear
|
||||
|
||||
## Testing Approach
|
||||
|
||||
### Manual Testing Requirements
|
||||
- Test in Godot editor with F5 run
|
||||
- Verify debug UI works with F12 toggle
|
||||
- Check scene transitions work
|
||||
- Test on different screen sizes (mobile target)
|
||||
- Verify audio and settings integration
|
||||
|
||||
### Debug System Testing
|
||||
- Ensure debug panels appear/disappear correctly
|
||||
- Test all debug buttons and controls
|
||||
- Verify debug state persists across scene changes
|
||||
- Check debug code doesn't affect release builds
|
||||
|
||||
## Naming Convention Quick Reference
|
||||
|
||||
> 🎯 **Single Source of Truth**: This section contains all naming conventions for the Skelly project. All other documentation files reference this section to avoid duplication and ensure consistency.
|
||||
|
||||
### 1. GDScript Code Elements
|
||||
|
||||
```gdscript
|
||||
# Variables and functions: snake_case
|
||||
var player_health: int = 100
|
||||
func calculate_damage() -> int:
|
||||
|
||||
# Constants: SCREAMING_SNAKE_CASE
|
||||
const MAX_HEALTH := 100
|
||||
const TILE_SPACING := 54
|
||||
|
||||
# Classes: PascalCase
|
||||
class_name PlayerController
|
||||
|
||||
# Signals: past_tense_with_underscores
|
||||
signal health_changed
|
||||
signal game_started
|
||||
signal match_found
|
||||
|
||||
# Private functions: prefix with underscore
|
||||
func _ready():
|
||||
func _initialize_grid():
|
||||
```
|
||||
|
||||
### 2. File Naming Standards
|
||||
|
||||
#### Script and Scene Files
|
||||
```gdscript
|
||||
# ✅ Correct: All .gd and .tscn files use PascalCase
|
||||
MainMenu.tscn / MainMenu.gd
|
||||
Match3Gameplay.tscn / Match3Gameplay.gd
|
||||
ClickomaniaGameplay.tscn / ClickomaniaGameplay.gd
|
||||
ValueStepper.tscn / ValueStepper.gd
|
||||
|
||||
# Test files: PascalCase with "Test" prefix
|
||||
TestAudioManager.gd
|
||||
TestGameManager.gd
|
||||
TestMatch3Gameplay.gd
|
||||
|
||||
# ❌ Wrong: Old snake_case style (being migrated)
|
||||
main_menu.tscn / main_menu.gd
|
||||
TestAudioManager.gd
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- Scene files (.tscn) must match their script file name exactly
|
||||
- All new files must use PascalCase
|
||||
- Test files use "Test" prefix + PascalCase
|
||||
- Autoload scripts follow PascalCase (GameManager.gd, AudioManager.gd)
|
||||
|
||||
### 3. Directory Naming Conventions
|
||||
|
||||
#### Source Code Directories
|
||||
```
|
||||
# Source directories: snake_case
|
||||
src/autoloads/
|
||||
scenes/game/gameplays/
|
||||
scenes/ui/components/
|
||||
tests/helpers/
|
||||
|
||||
# Root directories: lowercase
|
||||
docs/
|
||||
tests/
|
||||
tools/
|
||||
data/
|
||||
```
|
||||
|
||||
#### Asset Directories
|
||||
```
|
||||
# Asset directories: kebab-case
|
||||
assets/audio-files/
|
||||
assets/ui-sprites/
|
||||
assets/game-textures/
|
||||
assets/fonts/
|
||||
localization/
|
||||
```
|
||||
|
||||
### 4. Resource and Configuration Files
|
||||
|
||||
```bash
|
||||
# Configuration files: lowercase with dots
|
||||
project.godot
|
||||
gdlintrc
|
||||
.gdformatrc
|
||||
.editorconfig
|
||||
export_presets.cfg
|
||||
|
||||
# Godot resource files (.tres): PascalCase
|
||||
data/DefaultBusLayout.tres
|
||||
data/PlayerSaveData.tres
|
||||
scenes/ui/DefaultTheme.tres
|
||||
|
||||
# Asset metadata: kebab-case
|
||||
assets/asset-sources.yaml
|
||||
assets/audio-files/audio-sources.yaml
|
||||
assets/ui-sprites/sprite-sources.yaml
|
||||
|
||||
# Development files: kebab-case
|
||||
requirements.txt
|
||||
development-tools.md
|
||||
```
|
||||
|
||||
### 5. Asset File Naming
|
||||
|
||||
```bash
|
||||
# Audio files: kebab-case in kebab-case directories
|
||||
assets/audio-files/background-music.ogg
|
||||
assets/audio-files/ui-sounds/button-click.wav
|
||||
assets/audio-files/game-sounds/match-sound.wav
|
||||
|
||||
# Visual assets: kebab-case
|
||||
assets/ui-sprites/main-menu-background.png
|
||||
assets/game-textures/gem-blue.png
|
||||
assets/fonts/main-ui-font.ttf
|
||||
|
||||
# Import settings: match the original file
|
||||
background-music.ogg.import
|
||||
button-click.wav.import
|
||||
```
|
||||
|
||||
**Asset Rules:**
|
||||
- All asset files use kebab-case
|
||||
- Organized in kebab-case directories
|
||||
- Import files automatically match asset names
|
||||
- Document all assets in `asset-sources.yaml`
|
||||
|
||||
### 6. Git Workflow Conventions
|
||||
|
||||
#### Branch Naming
|
||||
```bash
|
||||
# Feature branches: feature/description-with-hyphens
|
||||
feature/new-gameplay-mode
|
||||
feature/settings-ui-improvement
|
||||
feature/audio-system-upgrade
|
||||
|
||||
# Bug fixes: fix/description-with-hyphens
|
||||
fix/tile-positioning-bug
|
||||
fix/save-data-corruption
|
||||
fix/debug-menu-visibility
|
||||
|
||||
# Refactoring: refactor/component-name
|
||||
refactor/match3-input-system
|
||||
refactor/autoload-structure
|
||||
|
||||
# Documentation: docs/section-name
|
||||
docs/code-of-conduct-update
|
||||
docs/api-documentation
|
||||
```
|
||||
|
||||
#### Commit Message Format
|
||||
```bash
|
||||
# Format: <type>: <description>
|
||||
# Examples:
|
||||
feat: add dark mode toggle to settings menu
|
||||
fix: resolve tile swap animation timing issue
|
||||
docs: update naming conventions in code of conduct
|
||||
refactor: migrate print statements to DebugManager
|
||||
test: add comprehensive match3 validation tests
|
||||
```
|
||||
|
||||
### 7. Quick Reference Summary
|
||||
|
||||
| File Type | Convention | Example |
|
||||
|-----------|------------|---------|
|
||||
| **GDScript Files** | PascalCase | `MainMenu.gd`, `AudioManager.gd` |
|
||||
| **Scene Files** | PascalCase | `MainMenu.tscn`, `Match3Gameplay.tscn` |
|
||||
| **Test Files** | Test + PascalCase | `TestAudioManager.gd` |
|
||||
| **Variables/Functions** | snake_case | `player_health`, `calculate_damage()` |
|
||||
| **Constants** | SCREAMING_SNAKE_CASE | `MAX_HEALTH`, `TILE_SPACING` |
|
||||
| **Classes** | PascalCase | `class_name PlayerController` |
|
||||
| **Signals** | past_tense | `health_changed`, `game_started` |
|
||||
| **Directories** | snake_case (src) / kebab-case (assets) | `src/autoloads/`, `assets/audio-files/` |
|
||||
| **Assets** | kebab-case | `background-music.ogg`, `gem-blue.png` |
|
||||
| **Config Files** | lowercase.extension | `project.godot`, `.gdformatrc` |
|
||||
| **Branches** | type/kebab-case | `feature/new-gameplay`, `fix/tile-bug` |
|
||||
|
||||
> ✅ **Status**: All major file naming inconsistencies have been resolved. The project now follows consistent PascalCase naming for all .gd and .tscn files.
|
||||
|
||||
### 8. File Renaming Migration Guide
|
||||
|
||||
When renaming files to follow conventions:
|
||||
|
||||
**Step-by-step procedure:**
|
||||
1. **Use Git rename**: `git mv old_file.gd NewFile.gd` (preserves history)
|
||||
2. **Update .tscn references**: Modify script path in scene files
|
||||
3. **Update code references**: Search and replace all `preload()` and `load()` statements
|
||||
4. **Update project.godot**: If file is referenced in autoloads or project settings
|
||||
5. **Update documentation**: Search all .md files for old references
|
||||
6. **Update test files**: Modify any test files that reference the renamed file
|
||||
7. **Run validation**: Execute `gdlint`, `gdformat`, and project tests
|
||||
8. **Verify in editor**: Load scenes in Godot editor to confirm everything works
|
||||
|
||||
**Tools for validation:**
|
||||
- `python tools/run_development.py --test` - Run all tests
|
||||
- `python tools/run_development.py --lint` - Check code quality
|
||||
- `python tools/run_development.py --format` - Ensure consistent formatting
|
||||
|
||||
## Common Mistakes to Avoid
|
||||
|
||||
### Architecture Violations
|
||||
```gdscript
|
||||
# Don't bypass GameManager
|
||||
get_tree().change_scene_to_file("some_scene.tscn")
|
||||
|
||||
# Don't hardcode paths
|
||||
var tile = load("res://scenes/game/gameplays/Tile.tscn")
|
||||
|
||||
# Don't ignore null checks
|
||||
var node = get_node("SomeNode")
|
||||
node.do_something() # Could crash if node doesn't exist
|
||||
|
||||
# Don't create global state in random scripts
|
||||
# Use autoloads instead
|
||||
```
|
||||
|
||||
### Asset Management Violations
|
||||
```gdscript
|
||||
# Don't add assets without documentation
|
||||
# Adding audio/new_music.mp3 without updating sources.yaml
|
||||
|
||||
# Don't use assets without verifying licenses
|
||||
# Using copyrighted music without permission
|
||||
|
||||
# Don't modify assets without documenting changes
|
||||
# Editing sprites without noting modifications in sources.yaml
|
||||
|
||||
# Don't commit assets and documentation separately
|
||||
git add assets/sprites/new_sprite.png
|
||||
git commit -m "add sprite" # Missing sources.yaml update
|
||||
|
||||
# Correct approach
|
||||
git add assets/sprites/new_sprite.png assets/sources.yaml
|
||||
git commit -m "add new sprite with attribution"
|
||||
```
|
||||
|
||||
### Performance Issues
|
||||
```gdscript
|
||||
# Don't search nodes repeatedly
|
||||
func _process(delta):
|
||||
var ui = get_node("UI") # Expensive every frame
|
||||
|
||||
# Cache node references
|
||||
@onready var ui = $UI
|
||||
func _process(delta):
|
||||
ui.update_display() # Much better
|
||||
```
|
||||
|
||||
### Debug System Misuse
|
||||
```gdscript
|
||||
# Don't create separate debug systems
|
||||
var my_debug_enabled = false
|
||||
print("debug: " + some_info) # Don't use plain print()
|
||||
|
||||
# Use the global debug and logging systems
|
||||
if DebugManager.is_debug_enabled():
|
||||
show_debug_info()
|
||||
DebugManager.log_debug("Debug information: " + some_info, "MyComponent")
|
||||
```
|
||||
|
||||
## Learning Resources
|
||||
|
||||
### Godot-Specific
|
||||
- [GDScript Style Guide](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_styleguide.html)
|
||||
- [Godot Best Practices](https://docs.godotengine.org/en/stable/tutorials/best_practices/index.html)
|
||||
- Project documentation in `docs/map.md`
|
||||
|
||||
### General Programming
|
||||
- Clean Code principles
|
||||
- SOLID principles (adapted for game development)
|
||||
- Git best practices
|
||||
|
||||
## Getting Help
|
||||
|
||||
### When Stuck
|
||||
1. Check existing code for similar patterns
|
||||
2. Review project documentation (`docs/`)
|
||||
3. Use the debug system to understand current state
|
||||
4. Ask specific questions about project architecture
|
||||
5. Test your understanding with small experiments
|
||||
|
||||
### Code Review Process
|
||||
- Submit pull requests early and often
|
||||
- Ask for feedback on approach before implementing large features
|
||||
- Be open to suggestions and alternative approaches
|
||||
- Learn from review feedback for future contributions
|
||||
|
||||
## Summary
|
||||
|
||||
This code of conduct emphasizes:
|
||||
- **Clarity**: Write code that others can understand
|
||||
- **Consistency**: Follow established patterns
|
||||
- **Integration**: Use the project's systems properly
|
||||
- **Learning**: Continuously improve your skills
|
||||
|
||||
Remember: It's better to ask questions and write clear, simple code than to guess and create complex, hard-to-maintain solutions. The goal is to contribute effectively to a codebase that will grow and evolve over time.
|
||||
383
docs/DEVELOPMENT.md
Normal file
@@ -0,0 +1,383 @@
|
||||
# Development Guide
|
||||
|
||||
Development workflows, commands, and procedures for the Skelly project.
|
||||
|
||||
**Quick Links**:
|
||||
- **Architecture**: [ARCHITECTURE.md](ARCHITECTURE.md) - Understand system design before developing
|
||||
- **Coding Standards**: [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) - Follow coding conventions
|
||||
- **Testing**: [TESTING.md](TESTING.md) - Testing procedures and protocols
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Running the Project
|
||||
|
||||
```bash
|
||||
# Open project in Godot Editor
|
||||
# Import project.godot
|
||||
|
||||
# Run project
|
||||
# Press F5 in Godot Editor or use "Play" button
|
||||
|
||||
# Toggle debug UI in-game
|
||||
# Press F12 key or use debug button
|
||||
```
|
||||
|
||||
### Code Maps Generation
|
||||
|
||||
Generate machine-readable code intelligence and visual documentation for development:
|
||||
|
||||
```bash
|
||||
# Generate everything (default behavior - maps + diagrams + docs + metrics)
|
||||
python tools/generate_code_map.py
|
||||
|
||||
# Generate specific outputs
|
||||
python tools/generate_code_map.py --diagrams # Visual diagrams only
|
||||
python tools/generate_code_map.py --docs # Markdown documentation only
|
||||
python tools/generate_code_map.py --metrics # Metrics dashboard only
|
||||
|
||||
# Generate all explicitly (same as no arguments)
|
||||
python tools/generate_code_map.py --all
|
||||
|
||||
# Via development workflow tool
|
||||
python tools/run_development.py --codemap
|
||||
|
||||
# Custom output (single JSON file)
|
||||
python tools/generate_code_map.py --output custom_map.json
|
||||
|
||||
# Skip PNG rendering (Mermaid source only)
|
||||
python tools/generate_code_map.py --diagrams --no-render
|
||||
```
|
||||
|
||||
**Generated Files** (Git-ignored):
|
||||
|
||||
**JSON Maps** (`.llm/` directory):
|
||||
- `code_map_api.json` - Function signatures, parameters, return types
|
||||
- `code_map_architecture.json` - Autoloads, design patterns, structure
|
||||
- `code_map_flows.json` - Signal chains, scene transitions
|
||||
- `code_map_security.json` - Input validation, error handling
|
||||
- `code_map_assets.json` - Asset dependencies, licensing
|
||||
- `code_map_metadata.json` - Statistics, quality metrics
|
||||
|
||||
**Diagrams** (`.llm/diagrams/` directory):
|
||||
- `architecture.png` - Autoload system dependencies
|
||||
- `signal_flows.png` - Signal connections between components
|
||||
- `scene_hierarchy.png` - Scene tree structure
|
||||
- `dependency_graph.png` - Module dependencies
|
||||
- `*.mmd` - Mermaid source files
|
||||
|
||||
**Documentation** (`docs/generated/` directory):
|
||||
- `AUTOLOADS_API.md` - Complete autoload API reference with diagrams
|
||||
- `SIGNALS_CATALOG.md` - Signal catalog with flow diagrams
|
||||
- `FUNCTION_INDEX.md` - Searchable function reference
|
||||
- `SCENE_REFERENCE.md` - Scene hierarchy with diagrams
|
||||
- `TODO_LIST.md` - Extracted TODO/FIXME comments
|
||||
- `METRICS.md` - Project metrics dashboard with charts
|
||||
|
||||
**When to Regenerate**:
|
||||
- Before LLM-assisted development sessions (fresh context)
|
||||
- After adding new files or major refactoring (update structure)
|
||||
- After changing autoloads or core architecture (capture changes)
|
||||
- When onboarding new developers (comprehensive docs)
|
||||
- To track project metrics and code complexity
|
||||
|
||||
### Testing Commands
|
||||
|
||||
```bash
|
||||
# Run specific test
|
||||
godot --headless --script tests/TestLogging.gd
|
||||
|
||||
# Run all save system tests
|
||||
godot --headless --script tests/test_checksum_issue.gd
|
||||
godot --headless --script tests/TestMigrationCompatibility.gd
|
||||
godot --headless --script tests/test_save_system_integration.gd
|
||||
|
||||
# Development workflow tool
|
||||
python tools/run_development.py --test
|
||||
```
|
||||
|
||||
See [TESTING.md](TESTING.md) for complete testing procedures.
|
||||
|
||||
## Development Workflows
|
||||
|
||||
### Adding a New Feature
|
||||
|
||||
1. **Generate code maps** for LLM context:
|
||||
```bash
|
||||
python tools/generate_code_map.py
|
||||
```
|
||||
|
||||
2. **Review architecture** to understand system design:
|
||||
- Read [ARCHITECTURE.md](ARCHITECTURE.md) for relevant system
|
||||
- Understand autoload responsibilities
|
||||
- Review existing patterns
|
||||
|
||||
3. **Follow coding standards**:
|
||||
- Use [naming conventions](CODE_OF_CONDUCT.md#naming-convention-quick-reference)
|
||||
- Follow [core patterns](CODE_OF_CONDUCT.md#project-specific-guidelines)
|
||||
- Implement [input validation](CODE_OF_CONDUCT.md#input-validation-standards)
|
||||
- Use [proper memory management](CODE_OF_CONDUCT.md#memory-management-standards)
|
||||
|
||||
4. **Write tests** for new functionality:
|
||||
- Create test file in `tests/` directory
|
||||
- Follow [test structure guidelines](TESTING.md#adding-new-tests)
|
||||
- Run tests to verify functionality
|
||||
|
||||
5. **Check quality checklist** before committing:
|
||||
- Review [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md#code-quality-checklist)
|
||||
- Verify all checkboxes are satisfied
|
||||
- Run tests one more time
|
||||
|
||||
### Debugging Issues
|
||||
|
||||
1. **Toggle debug UI** with F12 in-game:
|
||||
- View system state in debug panels
|
||||
- Check current values and configurations
|
||||
- Use debug controls for testing
|
||||
|
||||
2. **Check logs** using structured logging:
|
||||
```gdscript
|
||||
DebugManager.log_debug("Detailed debug info", "MyComponent")
|
||||
DebugManager.log_info("Important event occurred", "MyComponent")
|
||||
DebugManager.log_warn("Unexpected situation", "MyComponent")
|
||||
DebugManager.log_error("Something failed", "MyComponent")
|
||||
```
|
||||
|
||||
3. **Verify state** in debug panels:
|
||||
- Match-3: Check gem pool, grid state, match detection
|
||||
- Settings: Verify volume, language, difficulty values
|
||||
- Save: Check save data integrity
|
||||
|
||||
4. **Run relevant test scripts**:
|
||||
```bash
|
||||
# Test specific system
|
||||
godot --headless --script tests/TestMatch3Gameplay.gd
|
||||
godot --headless --script tests/TestSettingsManager.gd
|
||||
```
|
||||
|
||||
5. **Use Godot debugger**:
|
||||
- Set breakpoints in code
|
||||
- Inspect variables during execution
|
||||
- Step through code execution
|
||||
- Use remote inspector for live debugging
|
||||
|
||||
### Modifying Core Systems
|
||||
|
||||
1. **Understand current design**:
|
||||
- Read [ARCHITECTURE.md](ARCHITECTURE.md) for system architecture
|
||||
- Review autoload responsibilities
|
||||
- Understand signal flows and dependencies
|
||||
|
||||
2. **Run existing tests first**:
|
||||
```bash
|
||||
# Save system testing
|
||||
godot --headless --script tests/test_checksum_issue.gd
|
||||
godot --headless --script tests/TestMigrationCompatibility.gd
|
||||
|
||||
# Settings testing
|
||||
godot --headless --script tests/TestSettingsManager.gd
|
||||
|
||||
# Game manager testing
|
||||
godot --headless --script tests/TestGameManager.gd
|
||||
```
|
||||
|
||||
3. **Make changes following standards**:
|
||||
- Follow [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) patterns
|
||||
- Maintain backward compatibility where possible
|
||||
- Add input validation and error handling
|
||||
- Use proper memory management
|
||||
|
||||
4. **Run tests again** to verify no regressions:
|
||||
```bash
|
||||
# Run all relevant test suites
|
||||
python tools/run_development.py --test
|
||||
```
|
||||
|
||||
5. **Update documentation** if architecture changes:
|
||||
- Update [ARCHITECTURE.md](ARCHITECTURE.md) if design patterns change
|
||||
- Update [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) if new patterns emerge
|
||||
- Update [TESTING.md](TESTING.md) if new test protocols needed
|
||||
|
||||
## Testing Changes
|
||||
|
||||
### Manual Testing
|
||||
|
||||
```bash
|
||||
# Run project with F5 in Godot Editor
|
||||
# Test the following:
|
||||
|
||||
# Basic functionality
|
||||
# - F12 toggle for debug UI
|
||||
# - Scene transitions work correctly
|
||||
# - Settings persist across restarts
|
||||
# - Audio plays correctly
|
||||
|
||||
# Gameplay testing
|
||||
# - Match-3: Gem swapping, match detection, scoring
|
||||
# - Input: Keyboard (WASD/Arrows + Enter) and gamepad (D-pad + A)
|
||||
# - Visual feedback: Selection highlights, animations
|
||||
|
||||
# Mobile compatibility (if UI changes made)
|
||||
# - Touch input works
|
||||
# - UI scales correctly on different resolutions
|
||||
# - Performance is acceptable
|
||||
```
|
||||
|
||||
### Automated Testing
|
||||
|
||||
```bash
|
||||
# Logging system
|
||||
godot --headless --script tests/TestLogging.gd
|
||||
|
||||
# Save system (after SaveManager changes)
|
||||
godot --headless --script tests/test_checksum_issue.gd
|
||||
godot --headless --script tests/TestMigrationCompatibility.gd
|
||||
godot --headless --script tests/test_save_system_integration.gd
|
||||
|
||||
# Settings system
|
||||
godot --headless --script tests/TestSettingsManager.gd
|
||||
|
||||
# Game manager
|
||||
godot --headless --script tests/TestGameManager.gd
|
||||
|
||||
# Gameplay
|
||||
godot --headless --script tests/TestMatch3Gameplay.gd
|
||||
```
|
||||
|
||||
See [TESTING.md](TESTING.md) for complete testing protocols.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### When Stuck
|
||||
|
||||
1. **Check system design**: Read [ARCHITECTURE.md](ARCHITECTURE.md) for understanding
|
||||
2. **Review coding patterns**: Check [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for examples
|
||||
3. **Use debug system**: Press F12 to inspect current state
|
||||
4. **Search existing code**: Look for similar patterns in the codebase
|
||||
5. **Test understanding**: Create small experiments to verify assumptions
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Scene Loading Fails
|
||||
**Problem**: Scene transitions not working or crashes during scene change
|
||||
|
||||
**Solution**:
|
||||
- Check if using `GameManager.start_game_with_mode()` (correct)
|
||||
- Never use `get_tree().change_scene_to_file()` directly
|
||||
- Verify scene paths are correct constants in GameManager
|
||||
- Check for race conditions (multiple rapid scene changes)
|
||||
|
||||
**Example**:
|
||||
```gdscript
|
||||
# ✅ Correct
|
||||
GameManager.start_game_with_mode("match3")
|
||||
|
||||
# ❌ Wrong
|
||||
get_tree().change_scene_to_file("res://scenes/game/Game.tscn")
|
||||
```
|
||||
|
||||
#### Logs Not Appearing
|
||||
**Problem**: Debug messages not showing in console
|
||||
|
||||
**Solution**:
|
||||
- Use `DebugManager.log_*()` functions instead of `print()`
|
||||
- Include category for log organization
|
||||
- Check log level filtering (TRACE/DEBUG require debug mode)
|
||||
- Verify DebugManager is configured as autoload
|
||||
|
||||
**Example**:
|
||||
```gdscript
|
||||
# ✅ Correct
|
||||
DebugManager.log_info("Scene loaded", "GameManager")
|
||||
DebugManager.log_error("Failed to load resource", "AssetLoader")
|
||||
|
||||
# ❌ Wrong
|
||||
print("Scene loaded") # Use structured logging
|
||||
push_error("Failed") # Missing context and category
|
||||
```
|
||||
|
||||
#### Memory Crashes
|
||||
**Problem**: Crashes related to freed nodes or memory access
|
||||
|
||||
**Solution**:
|
||||
- Always use `queue_free()` instead of `free()`
|
||||
- Wait for frame completion after queueing nodes
|
||||
- Clear references before cleanup
|
||||
- Check for access to freed objects
|
||||
|
||||
**Example**:
|
||||
```gdscript
|
||||
# ✅ Correct
|
||||
for child in children_to_remove:
|
||||
child.queue_free()
|
||||
await get_tree().process_frame # Wait for cleanup
|
||||
|
||||
# ❌ Wrong
|
||||
for child in children_to_remove:
|
||||
child.free() # Immediate deletion causes crashes
|
||||
```
|
||||
|
||||
#### Tests Failing
|
||||
**Problem**: Test scripts fail or hang
|
||||
|
||||
**Solution**:
|
||||
- Run tests in sequence (avoid parallel execution)
|
||||
- Check autoload configuration in project.godot
|
||||
- Remove existing save files before testing SaveManager
|
||||
- Verify test file permissions
|
||||
- Check test timeout (should complete within seconds)
|
||||
|
||||
#### Input Not Working
|
||||
**Problem**: Keyboard/gamepad input not responding
|
||||
|
||||
**Solution**:
|
||||
- Verify input actions defined in project.godot
|
||||
- Check input map configuration
|
||||
- Ensure scene has input focus
|
||||
- Test with both keyboard and gamepad
|
||||
- Check for input event consumption by UI elements
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Before Committing
|
||||
|
||||
1. **Run all relevant tests**:
|
||||
```bash
|
||||
python tools/run_development.py --test
|
||||
```
|
||||
|
||||
2. **Check quality checklist**: [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md#code-quality-checklist)
|
||||
|
||||
3. **Verify no regressions**: Test existing functionality
|
||||
|
||||
4. **Update documentation**: If patterns or architecture changed
|
||||
|
||||
5. **Review changes**: Use `git diff` to review all modifications
|
||||
|
||||
### Code Review
|
||||
|
||||
- Focus on code clarity and maintainability
|
||||
- Check adherence to project patterns
|
||||
- Verify input validation and error handling
|
||||
- Ensure memory management is safe
|
||||
- Test the changes yourself before approving
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
- Profile critical code paths
|
||||
- Use object pooling for frequently created nodes
|
||||
- Optimize expensive calculations
|
||||
- Cache node references with `@onready`
|
||||
- Avoid searching nodes repeatedly in `_process()`
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Godot GDScript Style Guide](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_styleguide.html)
|
||||
- [Godot Best Practices](https://docs.godotengine.org/en/stable/tutorials/best_practices/index.html)
|
||||
- [Godot Debugger](https://docs.godotengine.org/en/stable/tutorials/scripting/debug/overview_of_debugging_tools.html)
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [ARCHITECTURE.md](ARCHITECTURE.md) - System design and patterns
|
||||
- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) - Coding standards
|
||||
- [TESTING.md](TESTING.md) - Testing procedures
|
||||
- [UI_COMPONENTS.md](UI_COMPONENTS.md) - Component API reference
|
||||
404
docs/MAP.md
Normal file
@@ -0,0 +1,404 @@
|
||||
# Skelly - Project Structure Map
|
||||
|
||||
## Overview
|
||||
Skelly is a Godot 4.4 game project featuring multiple gameplay modes. The project supports match-3 puzzle gameplay with planned clickomania gameplay through a modular gameplay architecture. It follows a modular structure with clear separation between scenes, autoloads, assets, and data.
|
||||
|
||||
> 📋 **Naming Conventions**: All file and directory naming follows the standards defined in [Naming Convention Quick Reference](CODE_OF_CONDUCT.md#naming-convention-quick-reference).
|
||||
|
||||
## Project Root Structure
|
||||
|
||||
```
|
||||
skelly/
|
||||
├── .claude/ # Claude Code configuration
|
||||
├── .godot/ # Godot engine generated files (ignored)
|
||||
├── assets/ # Game assets (sprites, audio, textures)
|
||||
├── data/ # Game data and configuration files
|
||||
├── docs/ # Documentation
|
||||
├── localization/ # Internationalization files
|
||||
├── scenes/ # Godot scenes (.tscn) and scripts (.gd)
|
||||
├── src/ # Source code organization
|
||||
├── tests/ # Test scripts and validation utilities
|
||||
├── project.godot # Main Godot project configuration
|
||||
└── icon.svg # Project icon
|
||||
```
|
||||
|
||||
## Core Architecture
|
||||
|
||||
### Autoloads (Global Singletons)
|
||||
Located in `src/autoloads/`, these scripts are automatically loaded when the game starts:
|
||||
|
||||
1. **SaveManager** (`src/autoloads/SaveManager.gd`)
|
||||
- Persistent game data management with validation
|
||||
- High score tracking and current score management
|
||||
- Game statistics (games played, total score)
|
||||
- Grid state persistence for match-3 gameplay continuity
|
||||
- Progress reset functionality with complete data cleanup
|
||||
- Security features: backup system, checksum validation, file size limits
|
||||
- Robust error handling with backup restoration capabilities
|
||||
- Uses: `user://savegame.save` for persistent storage
|
||||
|
||||
2. **SettingsManager** (`src/autoloads/SettingsManager.gd`)
|
||||
- Manages game settings and user preferences
|
||||
- Configuration file I/O
|
||||
- input validation
|
||||
- JSON parsing
|
||||
- Provides language selection functionality
|
||||
- Dependencies: `localization/languages.json`
|
||||
|
||||
3. **AudioManager** (`src/autoloads/AudioManager.gd`)
|
||||
- Controls music and sound effects
|
||||
- Manages audio bus configuration
|
||||
- Uses: `data/default_bus_layout.tres`
|
||||
|
||||
4. **GameManager** (`src/autoloads/GameManager.gd`)
|
||||
- Game state management and gameplay mode coordination with race condition protection
|
||||
- Scene transitions with concurrent change prevention and validation
|
||||
- Gameplay mode selection and launching with input validation (match3, clickomania)
|
||||
- Error handling for scene loading failures and fallback mechanisms
|
||||
- Navigation flow control with state protection
|
||||
- References: main.tscn, game.tscn and individual gameplay scenes
|
||||
|
||||
5. **LocalizationManager** (`src/autoloads/LocalizationManager.gd`)
|
||||
- Language switching functionality
|
||||
- Works with Godot's built-in internationalization system
|
||||
- Uses translation files in `localization/`
|
||||
|
||||
6. **DebugManager** (`src/autoloads/DebugManager.gd`)
|
||||
- Global debug state management and centralized logging system
|
||||
- Debug UI visibility control
|
||||
- F12 toggle functionality
|
||||
- Signal-based debug system
|
||||
- Structured logging with configurable log levels (TRACE, DEBUG, INFO, WARN, ERROR, FATAL)
|
||||
- Timestamp-based log formatting with category support
|
||||
- Runtime log level filtering
|
||||
|
||||
|
||||
## Scene Hierarchy & Flow
|
||||
|
||||
### Main Scenes
|
||||
```
|
||||
main.tscn (Entry Point)
|
||||
├── SplashScreen.tscn
|
||||
├── MainMenu.tscn
|
||||
└── SettingsMenu.tscn
|
||||
|
||||
game.tscn (Gameplay Container)
|
||||
├── GameplayContainer (Dynamic content)
|
||||
├── UI/ScoreDisplay
|
||||
├── BackButton
|
||||
└── DebugToggle
|
||||
```
|
||||
|
||||
### Game Flow
|
||||
1. **Main Scene** (`scenes/main/Main.tscn` + `Main.gd`)
|
||||
- Application entry point
|
||||
- Manages splash screen
|
||||
- Transitions to main menu
|
||||
- Dynamic menu loading system
|
||||
|
||||
2. **Splash Screen** (`scenes/main/SplashScreen.tscn` + `SplashScreen.gd`)
|
||||
- Initial splash screen
|
||||
- Input detection for any key/button
|
||||
- Signals to main scene for transition
|
||||
|
||||
3. **Main Menu** (`scenes/ui/MainMenu.tscn` + `MainMenu.gd`)
|
||||
- Primary navigation hub
|
||||
- Start game, settings, quit options
|
||||
- Connected to GameManager for scene transitions
|
||||
|
||||
4. **Settings Menu** (`scenes/ui/SettingsMenu.tscn` + `SettingsMenu.gd`)
|
||||
- User preferences interface
|
||||
- Language selection
|
||||
- Audio volume controls
|
||||
- Connected to SettingsManager and AudioManager
|
||||
|
||||
5. **Game Scene** (`scenes/game/Game.tscn` + `Game.gd`)
|
||||
- Main gameplay container with modular gameplay system
|
||||
- Dynamic loading of gameplay modes into GameplayContainer
|
||||
- Global score management and display
|
||||
- Back button for navigation to main menu
|
||||
- Gameplay mode switching support (Space+Enter debug key)
|
||||
- Bridge between UI and individual gameplay implementations
|
||||
|
||||
### UI Components
|
||||
```
|
||||
scenes/ui/
|
||||
├── components/
|
||||
│ └── ValueStepper.tscn + ValueStepper.gd # Reusable arrow-based value selector
|
||||
├── DebugToggle.tscn + DebugToggle.gd # Available on all major scenes
|
||||
├── DebugMenuBase.gd # Unified base class for debug menus
|
||||
├── DebugMenu.tscn + DebugMenu.gd # Global debug controls (extends DebugMenuBase)
|
||||
├── Match3DebugMenu.gd # Match-3 specific debug controls (extends DebugMenuBase)
|
||||
├── MainMenu.tscn + MainMenu.gd # With gamepad/keyboard navigation
|
||||
└── SettingsMenu.tscn + SettingsMenu.gd # With comprehensive input validation
|
||||
```
|
||||
|
||||
**Quality Improvements:**
|
||||
- **ValueStepper Component**: Reusable arrow-based selector for discrete values (language, resolution, difficulty)
|
||||
- **DebugMenuBase.gd**: Eliminates 90% code duplication between debug menu classes
|
||||
- **Input Validation**: User inputs are validated and sanitized before processing
|
||||
- **Error Recovery**: Error handling with fallback mechanisms throughout UI
|
||||
- **Navigation Support**: Gamepad/keyboard navigation across menus
|
||||
|
||||
## Modular Gameplay System
|
||||
|
||||
The game now uses a modular gameplay architecture where different game modes can be dynamically loaded into the main game scene.
|
||||
|
||||
### Gameplay Architecture
|
||||
- **Main Game Scene** (`scenes/game/Game.gd`) - Container and coordinator
|
||||
- **Gameplay Directory** (`scenes/game/gameplays/`) - Individual gameplay implementations
|
||||
- **Dynamic Loading** - Gameplay scenes loaded at runtime based on mode selection
|
||||
- **Signal-based Communication** - Gameplays communicate with main scene via signals
|
||||
|
||||
### Current Gameplay Modes
|
||||
|
||||
#### Match-3 Mode (`scenes/game/gameplays/Match3Gameplay.tscn`)
|
||||
1. **Match3 Controller** (`scenes/game/gameplays/Match3Gameplay.gd`)
|
||||
- Grid management (8x8 default) with memory-safe node cleanup
|
||||
- Match detection algorithms with bounds checking and validation
|
||||
- Tile dropping and refilling with signal connections
|
||||
- Gem pool management (3-8 gem types) with instance-based architecture
|
||||
- Debug UI integration with validation
|
||||
- Score reporting via `score_changed` signal
|
||||
- **Memory Safety**: Uses `queue_free()` with frame waiting to prevent crashes
|
||||
- **Gem Movement System**: Keyboard and gamepad input for tile selection and swapping
|
||||
- State machine: WAITING → SELECTING → SWAPPING → PROCESSING
|
||||
- Adjacent tile validation (horizontal/vertical neighbors only)
|
||||
- Match validation (swaps must create matches or revert)
|
||||
- Smooth tile position animations with Tween
|
||||
- Cursor-based navigation with visual highlighting and bounds checking
|
||||
|
||||
2. **Tile System** (`scenes/game/gameplays/Tile.gd` + `Tile.tscn`)
|
||||
- Tile behavior with instance-based architecture (no global state)
|
||||
- Gem type management with validation and bounds checking
|
||||
- Visual representation with scaling and color
|
||||
- Group membership for coordination
|
||||
- **Visual Feedback System**: Multi-state display for game interaction
|
||||
- Selection visual feedback (scale and color modulation)
|
||||
- State management (normal, highlighted, selected)
|
||||
- Signal-based communication with gameplay controller
|
||||
- Smooth animations with Tween system
|
||||
- **Memory Safety**: Resource management and cleanup
|
||||
|
||||
#### Clickomania Mode (`scenes/game/gameplays/ClickomaniaGameplay.tscn`)
|
||||
- Planned implementation for clickomania-style gameplay
|
||||
- Will integrate with same scoring and UI systems as match-3
|
||||
|
||||
### Debug System
|
||||
- Global debug state via DebugManager with initialization
|
||||
- Debug toggle available on all major scenes (MainMenu, SettingsMenu, SplashScreen, Game)
|
||||
- Match-3 specific debug UI panel with gem count controls and difficulty presets
|
||||
- Gem count controls (+/- buttons) with difficulty presets (Easy: 3, Normal: 5, Hard: 8)
|
||||
- Board reroll functionality for testing
|
||||
- F12 toggle support across all scenes
|
||||
- Fewer debug prints in production code
|
||||
|
||||
## Asset Organization
|
||||
|
||||
### Audio (`assets/audio/`)
|
||||
```
|
||||
audio/
|
||||
├── music/ # Background music files
|
||||
└── sfx/ # Sound effects
|
||||
```
|
||||
|
||||
### Visual Assets (`assets/`)
|
||||
```
|
||||
assets/
|
||||
├── audio/
|
||||
│ ├── music/ # Background music files
|
||||
│ └── sfx/ # Sound effects
|
||||
├── sprites/
|
||||
│ ├── characters/skeleton/ # Character artwork
|
||||
│ ├── gems/ # Match-3 gem sprites
|
||||
│ └── ui/ # User interface elements
|
||||
├── textures/
|
||||
│ └── backgrounds/ # Background images
|
||||
├── fonts/ # Custom fonts
|
||||
└── sources.yaml # Asset metadata and attribution
|
||||
```
|
||||
|
||||
### Asset Management (`assets/sources.yaml`)
|
||||
**REQUIRED**: Every asset added to the project must be documented in `assets/sources.yaml` with:
|
||||
- **Source information** - Where the asset came from (URL, artist, store, etc.)
|
||||
- **License details** - Usage rights, attribution requirements, commercial permissions
|
||||
- **Attribution text** - Exact text to use for credits if required
|
||||
- **Modification notes** - Any changes made to the original asset
|
||||
- **Usage context** - Where and how the asset is used in the project
|
||||
|
||||
**Example format:**
|
||||
```yaml
|
||||
audio:
|
||||
music:
|
||||
"Space Horror InGame Music (Exploration) _Clement Panchout.wav":
|
||||
source: "https://freesound.org/people/ClementPanchout/"
|
||||
license: "CC BY 4.0"
|
||||
attribution: "Space Horror InGame Music by Clement Panchout"
|
||||
modifications: "Converted to WAV, loop points adjusted"
|
||||
usage: "Background music for all gameplay scenes"
|
||||
|
||||
sprites:
|
||||
gems:
|
||||
"gem_blue.png":
|
||||
source: "Created in-house"
|
||||
license: "Project proprietary"
|
||||
attribution: "Skelly development team"
|
||||
modifications: "None"
|
||||
usage: "Match-3 blue gem sprite"
|
||||
```
|
||||
|
||||
## Data & Configuration
|
||||
|
||||
### Game Data (`data/`)
|
||||
- `default_bus_layout.tres` - Audio bus configuration for Godot
|
||||
|
||||
### Documentation (`docs/`)
|
||||
- `MAP.md` - Project architecture and structure overview
|
||||
- `CLAUDE.md` - Claude Code development guidelines
|
||||
- `CODE_OF_CONDUCT.md` - Coding standards and best practices
|
||||
- `TESTING.md` - Testing guidelines and conventions
|
||||
|
||||
### Localization (`localization/`)
|
||||
- `languages.json` - Available language definitions
|
||||
- `MainStrings.en.translation` - English translations
|
||||
- `MainStrings.ru.translation` - Russian translations
|
||||
|
||||
### Testing & Validation (`tests/`)
|
||||
- `TestLogging.gd` - DebugManager logging system validation
|
||||
- **`test_checksum_issue.gd`** - SaveManager checksum validation and deterministic hashing
|
||||
- **`TestMigrationCompatibility.gd`** - SaveManager version migration and backward compatibility
|
||||
- **`test_save_system_integration.gd`** - Complete save/load workflow integration testing
|
||||
- **`test_checksum_fix_verification.gd`** - JSON serialization checksum fix verification
|
||||
- `README.md` - Brief directory overview (see docs/TESTING.md for full guidelines)
|
||||
- Comprehensive test scripts for save system security and data integrity validation
|
||||
- Temporary test utilities for development and debugging
|
||||
|
||||
### Project Configuration
|
||||
- `project.godot` - Main Godot project settings
|
||||
- Autoload definitions
|
||||
- Input map configurations
|
||||
- Rendering settings
|
||||
- Audio bus references
|
||||
|
||||
## Key Dependencies & Connections
|
||||
|
||||
### Signal Connections
|
||||
```
|
||||
SplashScreen --[confirm_pressed]--> Main
|
||||
MainMenu --[open_settings]--> Main
|
||||
SettingsMenu --[back_to_main_menu]--> Main
|
||||
DebugManager --[debug_toggled]--> All scenes with DebugToggle
|
||||
GameplayModes --[score_changed]--> Game Scene
|
||||
Game Scene --[score updates]--> UI/ScoreDisplay
|
||||
```
|
||||
|
||||
### Scene References
|
||||
```
|
||||
GameManager --> main.tscn, game.tscn
|
||||
GameManager --> scenes/game/gameplays/*.tscn (via GAMEPLAY_SCENES constant)
|
||||
Main --> MainMenu.tscn, SettingsMenu.tscn
|
||||
Game --> GameplayContainer (dynamic loading of gameplay scenes)
|
||||
Game --> scenes/game/gameplays/Match3Gameplay.tscn, ClickomaniaGameplay.tscn
|
||||
```
|
||||
|
||||
### Asset Dependencies
|
||||
```
|
||||
AudioManager --> assets/audio/music/
|
||||
SettingsManager --> localization/languages.json
|
||||
AudioManager --> data/default_bus_layout.tres
|
||||
Asset Management --> assets/sources.yaml (required for all assets)
|
||||
```
|
||||
|
||||
## Logging System
|
||||
|
||||
The project uses a centralized logging system implemented in DebugManager for consistent, structured logging throughout the application.
|
||||
|
||||
### Log Levels
|
||||
```
|
||||
TRACE (0) - Detailed execution tracing (debug mode only)
|
||||
DEBUG (1) - Development debugging information (debug mode only)
|
||||
INFO (2) - General application information (always visible)
|
||||
WARN (3) - Warning messages that don't break functionality
|
||||
ERROR (4) - Error conditions that may affect functionality
|
||||
FATAL (5) - Critical errors that may cause application failure
|
||||
```
|
||||
|
||||
### Usage Examples
|
||||
```gdscript
|
||||
# Basic logging with automatic categorization
|
||||
DebugManager.log_info("Game started successfully")
|
||||
DebugManager.log_warn("Settings file not found, using defaults")
|
||||
DebugManager.log_error("Failed to load audio resource")
|
||||
|
||||
# Logging with custom categories for better organization
|
||||
DebugManager.log_debug("Grid regenerated with 64 tiles", "Match3")
|
||||
DebugManager.log_info("Language changed to: en", "Settings")
|
||||
DebugManager.log_error("Invalid scene path provided", "GameManager")
|
||||
|
||||
# Standard categories in use:
|
||||
# - GameManager: Scene transitions, gameplay mode management
|
||||
# - Match3: Match-3 gameplay, grid operations, tile interactions
|
||||
# - Settings: Settings management, language changes
|
||||
# - Game: Main game scene, mode switching
|
||||
# - MainMenu: Main menu interactions
|
||||
# - SplashScreen: Splash screen
|
||||
# - Clickomania: Clickomania gameplay mode
|
||||
# - DebugMenu: Debug menu operations
|
||||
```
|
||||
|
||||
### Log Format
|
||||
```
|
||||
[timestamp] LEVEL [category]: message
|
||||
Example: [2025-09-24T10:48:08] INFO [GameManager]: Loading main scene
|
||||
```
|
||||
|
||||
### Runtime Configuration
|
||||
```gdscript
|
||||
# Set minimum log level (filters out lower priority messages)
|
||||
DebugManager.set_log_level(DebugManager.LogLevel.WARN)
|
||||
|
||||
# Get current log level
|
||||
var current_level = DebugManager.get_log_level()
|
||||
|
||||
# Check if a specific level would be logged
|
||||
if DebugManager._should_log(DebugManager.LogLevel.DEBUG):
|
||||
# Expensive debug calculation here
|
||||
```
|
||||
|
||||
### Integration with Godot Systems
|
||||
- **WARN/ERROR/FATAL** levels automatically call `push_warning()` and `push_error()`
|
||||
- **TRACE/DEBUG** levels only display when debug mode is enabled
|
||||
- **INFO** and higher levels always display regardless of debug mode
|
||||
- All levels respect the configured minimum log level threshold
|
||||
|
||||
## Development Notes
|
||||
|
||||
### Current Implementation Status
|
||||
- Modular gameplay system with dynamic loading architecture
|
||||
- Match-3 system with 8x8 grid and configurable gem pools
|
||||
- **Interactive Match-3 Gameplay**: Keyboard and gamepad gem movement system
|
||||
- Keyboard: Arrow key navigation with Enter to select/confirm (WASD also supported)
|
||||
- Gamepad: D-pad navigation with A button to select/confirm
|
||||
- Visual feedback: Tile highlighting, selection indicators, smooth animations
|
||||
- Game logic: Adjacent-only swaps, match validation, automatic revert on invalid moves
|
||||
- State machine: WAITING → SELECTING → SWAPPING → PROCESSING states
|
||||
- **Comprehensive Logging System**: All print()/push_error() statements migrated to DebugManager
|
||||
- Structured logging with categories: GameManager, Match3, Settings, Game, MainMenu, etc.
|
||||
- Multiple log levels: TRACE, DEBUG, INFO, WARN, ERROR, FATAL
|
||||
- Debug mode integration with level filtering
|
||||
- Global scoring system integrated across gameplay modes
|
||||
- Debug UI system with F12 toggle functionality across all major scenes
|
||||
- Scene transition system via GameManager with gameplay mode support
|
||||
- Internationalization support for English/Russian
|
||||
|
||||
### Architecture Patterns
|
||||
1. **Autoload Pattern** - Global managers as singletons
|
||||
2. **Signal-Based Communication** - Loose coupling between components
|
||||
3. **Modular Gameplay Architecture** - Dynamic loading of gameplay modes
|
||||
4. **Scene Composition** - Modular scene loading and management
|
||||
5. **Data-Driven Configuration** - JSON for settings and translations
|
||||
6. **Component Architecture** - Reusable UI and game components
|
||||
7. **Centralized Scoring System** - Global score management across gameplay modes
|
||||
8. **Structured Logging System** - Centralized logging with level-based filtering and formatted output
|
||||
|
||||
This structure provides a clean separation of concerns, making the codebase maintainable and extensible for future features.
|
||||
277
docs/TESTING.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# Tests Directory
|
||||
|
||||
Test scripts and utilities for validating Skelly project systems.
|
||||
|
||||
**Related Documentation**:
|
||||
- **Coding Standards**: [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) - Follow coding standards when writing tests
|
||||
- **Architecture**: [ARCHITECTURE.md](ARCHITECTURE.md) - Understand system design before testing
|
||||
|
||||
## Overview
|
||||
|
||||
The `tests/` directory contains:
|
||||
- System validation scripts
|
||||
- Component testing utilities
|
||||
- Integration tests
|
||||
- Performance benchmarks
|
||||
- Debugging tools
|
||||
|
||||
> 📋 **File Naming**: All test files follow the [naming conventions](CODE_OF_CONDUCT.md#naming-convention-quick-reference) with PascalCase and "Test" prefix (e.g., `TestAudioManager.gd`).
|
||||
|
||||
## Current Test Files
|
||||
|
||||
### `TestLogging.gd`
|
||||
Test script for DebugManager logging system.
|
||||
|
||||
**Features:**
|
||||
- Tests all log levels (TRACE, DEBUG, INFO, WARN, ERROR, FATAL)
|
||||
- Validates log level filtering
|
||||
- Tests category-based logging
|
||||
- Verifies debug mode integration
|
||||
- Demonstrates logging usage patterns
|
||||
|
||||
**Usage:**
|
||||
```gdscript
|
||||
# Option 1: Add as temporary autoload
|
||||
# In project.godot, add: tests/TestLogging.gd
|
||||
|
||||
# Option 2: Instantiate in a scene
|
||||
var test_script = preload("res://tests/TestLogging.gd").new()
|
||||
add_child(test_script)
|
||||
|
||||
# Option 3: Run directly from editor
|
||||
# Open the script and run the scene containing it
|
||||
```
|
||||
|
||||
**Expected Output:**
|
||||
Formatted log messages showing:
|
||||
- Timestamp formatting
|
||||
- Log level filtering
|
||||
- Category organization
|
||||
- Debug mode dependency for TRACE/DEBUG levels
|
||||
|
||||
## Adding New Tests
|
||||
|
||||
Follow these conventions for new test files:
|
||||
|
||||
### File Naming
|
||||
- Use descriptive names starting with `test_`
|
||||
- Example: `TestAudioManager.gd`, `test_scene_transitions.gd`
|
||||
|
||||
### File Structure
|
||||
```gdscript
|
||||
extends Node
|
||||
|
||||
# Brief description of what this test validates
|
||||
|
||||
func _ready():
|
||||
# Wait for system initialization if needed
|
||||
await get_tree().process_frame
|
||||
run_tests()
|
||||
|
||||
func run_tests():
|
||||
print("=== Starting [System Name] Tests ===")
|
||||
|
||||
# Individual test functions
|
||||
test_basic_functionality()
|
||||
test_edge_cases()
|
||||
test_error_conditions()
|
||||
|
||||
print("=== [System Name] Tests Complete ===")
|
||||
|
||||
func test_basic_functionality():
|
||||
print("\\n--- Test: Basic Functionality ---")
|
||||
# Test implementation
|
||||
|
||||
func test_edge_cases():
|
||||
print("\\n--- Test: Edge Cases ---")
|
||||
# Edge case testing
|
||||
|
||||
func test_error_conditions():
|
||||
print("\\n--- Test: Error Conditions ---")
|
||||
# Error condition testing
|
||||
```
|
||||
|
||||
### Testing Guidelines
|
||||
|
||||
1. **Independence**: Each test is self-contained
|
||||
2. **Cleanup**: Restore original state after testing
|
||||
3. **Clear Output**: Use descriptive print statements
|
||||
4. **Error Handling**: Test success and failure conditions
|
||||
5. **Documentation**: Comment complex test scenarios
|
||||
|
||||
### Integration with Main Project
|
||||
|
||||
- **Temporary Usage**: Add test files temporarily during development
|
||||
- **Not in Production**: Exclude from release builds
|
||||
- **Autoload Testing**: Add to autoloads temporarily for automatic execution
|
||||
- **Manual Testing**: Run individually for specific components
|
||||
|
||||
## Test Categories
|
||||
|
||||
### System Tests
|
||||
Test core autoload managers and global systems:
|
||||
- `TestLogging.gd` - DebugManager logging system
|
||||
- `test_checksum_issue.gd` - SaveManager checksum validation and deterministic hashing
|
||||
- `TestMigrationCompatibility.gd` - SaveManager version migration and backward compatibility
|
||||
- `test_save_system_integration.gd` - Complete save/load workflow integration testing
|
||||
- `test_checksum_fix_verification.gd` - Verification of JSON serialization checksum fixes
|
||||
- `TestSettingsManager.gd` - SettingsManager security validation, input validation, and error handling
|
||||
- `TestGameManager.gd` - GameManager scene transitions, race condition protection, and input validation
|
||||
- `TestAudioManager.gd` - AudioManager functionality, resource loading, and volume management
|
||||
|
||||
### Component Tests
|
||||
Test individual game components:
|
||||
- `TestMatch3Gameplay.gd` - Match-3 gameplay mechanics, grid management, and match detection
|
||||
- `TestTile.gd` - Tile component behavior, visual feedback, and memory safety
|
||||
- `TestValueStepper.gd` - ValueStepper UI component functionality and settings integration
|
||||
|
||||
### Integration Tests
|
||||
Test system interactions and workflows:
|
||||
- Future: `test_game_flow.gd` - Complete game session flow
|
||||
- Future: `test_debug_system.gd` - Debug UI integration
|
||||
- Future: `test_localization.gd` - Language switching and translations
|
||||
|
||||
## Save System Testing Protocols
|
||||
|
||||
SaveManager implements security features requiring testing for modifications.
|
||||
|
||||
### Critical Test Suites
|
||||
|
||||
#### **`test_checksum_issue.gd`** - Checksum Validation
|
||||
**Tests**: Checksum generation, JSON serialization consistency, save/load cycles
|
||||
**Usage**: Run after checksum algorithm changes
|
||||
|
||||
#### **`TestMigrationCompatibility.gd`** - Version Migration
|
||||
**Tests**: Backward compatibility, missing field addition, data structure normalization
|
||||
**Usage**: Test save format upgrades
|
||||
|
||||
#### **`test_save_system_integration.gd`** - End-to-End Integration
|
||||
**Tests**: Save/load workflow, grid state serialization, race condition prevention
|
||||
**Usage**: Run after SaveManager modifications
|
||||
|
||||
#### **`test_checksum_fix_verification.gd`** - JSON Serialization Fix
|
||||
**Tests**: Checksum consistency, int/float conversion, type safety validation
|
||||
**Usage**: Test JSON type conversion fixes
|
||||
|
||||
### Save System Security Testing
|
||||
|
||||
#### **Required Tests Before SaveManager Changes**
|
||||
1. Run 4 save system test suites
|
||||
2. Test tamper detection by modifying save files
|
||||
3. Validate error recovery by corrupting files
|
||||
4. Check race condition protection
|
||||
5. Verify permissive validation
|
||||
|
||||
#### **Performance Benchmarks**
|
||||
- Checksum calculation: < 1ms
|
||||
- Memory usage: File size limits prevent exhaustion
|
||||
- Error recovery: Never crash regardless of corruption
|
||||
- Data preservation: User scores survive migration
|
||||
|
||||
#### **Test Sequence After Modifications**
|
||||
1. `test_checksum_issue.gd` - Verify checksum consistency
|
||||
2. `TestMigrationCompatibility.gd` - Check version upgrades
|
||||
3. `test_save_system_integration.gd` - Validate workflow
|
||||
4. Manual testing with corrupted files
|
||||
5. Performance validation
|
||||
|
||||
**Failure Response**: Test failure indicates corruption risk. Do not commit until all tests pass.
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Manual Test Execution
|
||||
|
||||
#### **Direct Script Execution (Recommended)**
|
||||
```bash
|
||||
# Run specific test
|
||||
godot --headless --script tests/test_checksum_issue.gd
|
||||
|
||||
# Run all save system tests
|
||||
godot --headless --script tests/test_checksum_issue.gd
|
||||
godot --headless --script tests/TestMigrationCompatibility.gd
|
||||
godot --headless --script tests/test_save_system_integration.gd
|
||||
```
|
||||
|
||||
#### **Other Methods**
|
||||
- **Temporary Autoload**: Add to project.godot autoloads temporarily, run with F5
|
||||
- **Scene-based**: Create temporary scene, add test script as child, run with F6
|
||||
- **Editor**: Open test file, attach to scene, run with F6
|
||||
|
||||
### Automated Test Execution
|
||||
|
||||
Use provided scripts `run_tests.bat` (Windows) or `run_tests.sh` (Linux/Mac) to run all tests sequentially.
|
||||
|
||||
For CI/CD integration:
|
||||
```yaml
|
||||
- name: Run Test Suite
|
||||
run: |
|
||||
godot --headless --script tests/test_checksum_issue.gd
|
||||
godot --headless --script tests/TestMigrationCompatibility.gd
|
||||
# Add other tests as needed
|
||||
```
|
||||
|
||||
### Expected Test Output
|
||||
|
||||
#### **Successful Test Run:**
|
||||
```
|
||||
=== Testing Checksum Issue Fix ===
|
||||
Testing checksum consistency across save/load cycles...
|
||||
✅ SUCCESS: Checksums are deterministic
|
||||
✅ SUCCESS: JSON serialization doesn't break checksums
|
||||
✅ SUCCESS: Save/load cycle maintains checksum integrity
|
||||
=== Test Complete ===
|
||||
```
|
||||
|
||||
#### **Failed Test Run:**
|
||||
```
|
||||
=== Testing Checksum Issue Fix ===
|
||||
Testing checksum consistency across save/load cycles...
|
||||
❌ FAILURE: Checksum mismatch detected
|
||||
Expected: 1234567890
|
||||
Got: 9876543210
|
||||
=== Test Failed ===
|
||||
```
|
||||
|
||||
### Test Execution Best Practices
|
||||
|
||||
**Before**: Remove existing save files, verify autoloads configured, run one test at a time
|
||||
**During**: Monitor console output, note timing (tests complete within seconds)
|
||||
**After**: Clean up temporary files, document issues
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
**Common Issues:**
|
||||
- Permission errors: Run with elevated permissions if needed
|
||||
- Missing dependencies: Ensure autoloads configured
|
||||
- Timeout issues: Add timeout for hung tests
|
||||
- Path issues: Use absolute paths if relative paths fail
|
||||
|
||||
### Performance Benchmarks
|
||||
|
||||
Expected execution times: Individual tests < 5 seconds, total suite < 35 seconds.
|
||||
|
||||
If tests take longer, investigate file I/O issues, memory leaks, infinite loops, or external dependencies.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Document expected behavior
|
||||
2. Test boundary conditions and edge cases
|
||||
3. Measure performance for critical components
|
||||
4. Include visual validation for UI components
|
||||
5. Cleanup after tests
|
||||
|
||||
## Contributing
|
||||
|
||||
When adding test files:
|
||||
1. Follow [naming conventions](CODE_OF_CONDUCT.md#naming-convention-quick-reference)
|
||||
2. Follow [coding standards](CODE_OF_CONDUCT.md) for test code quality
|
||||
3. Understand [system architecture](ARCHITECTURE.md) before writing integration tests
|
||||
4. Update this file with test descriptions
|
||||
5. Ensure tests are self-contained and documented
|
||||
6. Test success and failure scenarios
|
||||
|
||||
This testing approach maintains code quality and provides validation tools for system changes.
|
||||
|
||||
**See Also**:
|
||||
- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md#code-quality-checklist) - Quality checklist before committing
|
||||
- [ARCHITECTURE.md](ARCHITECTURE.md) - System design and architectural patterns
|
||||
281
docs/UI_COMPONENTS.md
Normal file
@@ -0,0 +1,281 @@
|
||||
# UI Components
|
||||
|
||||
This document describes the custom UI components available in the Skelly project.
|
||||
|
||||
## ValueStepper Component
|
||||
|
||||
### Overview
|
||||
|
||||
**ValueStepper** is a reusable UI control for stepping through discrete values with left/right arrow navigation. It provides an intuitive interface for selecting from predefined options and is particularly well-suited for game settings menus.
|
||||
|
||||
**Location**: `scenes/ui/components/ValueStepper.tscn` and `scenes/ui/components/ValueStepper.gd`
|
||||
|
||||
### Why ValueStepper Exists
|
||||
|
||||
Godot's built-in controls have limitations for discrete option selection:
|
||||
- **OptionButton**: Dropdown popups don't work well with gamepad navigation
|
||||
- **SpinBox**: Designed for numeric values, not text options
|
||||
- **HSlider**: Better for continuous values, not discrete choices
|
||||
|
||||
ValueStepper fills this gap by providing:
|
||||
- ✅ **Gamepad-friendly** discrete option selection
|
||||
- ✅ **No popup complications** - values displayed inline
|
||||
- ✅ **Dual input support** - mouse clicks + keyboard/gamepad
|
||||
- ✅ **Clean horizontal layout** with current value always visible
|
||||
- ✅ **Perfect for game settings** like language, difficulty, resolution
|
||||
|
||||
### Features
|
||||
|
||||
- **Multiple Data Sources**: Built-in support for language, resolution, difficulty
|
||||
- **Custom Values**: Easy setup with custom arrays of values
|
||||
- **Navigation Integration**: Built-in highlighting and input handling
|
||||
- **Signal-Based**: Clean event communication with parent scenes
|
||||
- **Visual Feedback**: Automatic highlighting and animations
|
||||
- **Audio Support**: Integrated click sounds
|
||||
- **Flexible Display**: Separate display names and internal values
|
||||
|
||||
### Visual Structure
|
||||
|
||||
```
|
||||
[<] [Current Value] [>]
|
||||
```
|
||||
|
||||
- **Left Arrow Button** (`<`): Navigate to previous value
|
||||
- **Value Display**: Shows current selection (e.g., "English", "Hard", "1920×1080")
|
||||
- **Right Arrow Button** (`>`): Navigate to next value
|
||||
|
||||
## API Reference
|
||||
|
||||
### Signals
|
||||
|
||||
```gdscript
|
||||
signal value_changed(new_value: String, new_index: int)
|
||||
```
|
||||
Emitted when the value changes, providing both the new value string and its index.
|
||||
|
||||
### Properties
|
||||
|
||||
```gdscript
|
||||
@export var data_source: String = "language"
|
||||
@export var custom_format_function: String = ""
|
||||
```
|
||||
|
||||
- **data_source**: Determines the data type ("language", "resolution", "difficulty", or "custom")
|
||||
- **custom_format_function**: Reserved for future custom formatting (currently unused)
|
||||
|
||||
### Key Methods
|
||||
|
||||
#### Setup and Configuration
|
||||
```gdscript
|
||||
func setup_custom_values(custom_values: Array[String], custom_display_names: Array[String] = [])
|
||||
```
|
||||
Configure the stepper with custom values and optional display names.
|
||||
|
||||
#### Value Management
|
||||
```gdscript
|
||||
func get_current_value() -> String
|
||||
func set_current_value(value: String)
|
||||
func change_value(direction: int)
|
||||
```
|
||||
Get, set, or modify the current value programmatically.
|
||||
|
||||
#### Navigation Integration
|
||||
```gdscript
|
||||
func set_highlighted(highlighted: bool)
|
||||
func handle_input_action(action: String) -> bool
|
||||
func get_control_name() -> String
|
||||
```
|
||||
Integration methods for navigation systems and visual feedback.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Usage in Scene
|
||||
|
||||
1. **Add to Scene**: Instance `ValueStepper.tscn` in your scene
|
||||
2. **Set Data Source**: Configure the `data_source` property
|
||||
3. **Connect Signal**: Connect the `value_changed` signal
|
||||
|
||||
```gdscript
|
||||
# In your scene script
|
||||
@onready var language_stepper: ValueStepper = $LanguageStepper
|
||||
|
||||
func _ready():
|
||||
language_stepper.value_changed.connect(_on_language_changed)
|
||||
|
||||
func _on_language_changed(new_value: String, new_index: int):
|
||||
print("Language changed to: ", new_value)
|
||||
```
|
||||
|
||||
### Built-in Data Sources
|
||||
|
||||
#### Language Selection
|
||||
```gdscript
|
||||
# Set data_source = "language" in editor or code
|
||||
language_stepper.data_source = "language"
|
||||
```
|
||||
Automatically loads available languages from SettingsManager.
|
||||
|
||||
#### Resolution Selection
|
||||
```gdscript
|
||||
# Set data_source = "resolution"
|
||||
resolution_stepper.data_source = "resolution"
|
||||
```
|
||||
Provides common resolution options with display names.
|
||||
|
||||
#### Difficulty Selection
|
||||
```gdscript
|
||||
# Set data_source = "difficulty"
|
||||
difficulty_stepper.data_source = "difficulty"
|
||||
```
|
||||
Provides difficulty levels: Easy, Normal, Hard, Nightmare.
|
||||
|
||||
### Custom Values
|
||||
|
||||
```gdscript
|
||||
# Setup custom theme selector
|
||||
var theme_values = ["light", "dark", "blue", "green"]
|
||||
var theme_names = ["Light Theme", "Dark Theme", "Blue Theme", "Green Theme"]
|
||||
theme_stepper.setup_custom_values(theme_values, theme_names)
|
||||
theme_stepper.data_source = "theme" # For better logging
|
||||
```
|
||||
|
||||
### Navigation System Integration
|
||||
|
||||
```gdscript
|
||||
# In a navigation-enabled menu
|
||||
var navigable_controls: Array[Control] = []
|
||||
|
||||
func _setup_navigation():
|
||||
navigable_controls.append(volume_slider)
|
||||
navigable_controls.append(language_stepper) # Add stepper to navigation
|
||||
navigable_controls.append(back_button)
|
||||
|
||||
func _update_visual_selection():
|
||||
for i in range(navigable_controls.size()):
|
||||
var control = navigable_controls[i]
|
||||
if control is ValueStepper:
|
||||
control.set_highlighted(i == current_index)
|
||||
else:
|
||||
# Handle other control highlighting
|
||||
pass
|
||||
|
||||
func _handle_input(action: String):
|
||||
var current_control = navigable_controls[current_index]
|
||||
if current_control is ValueStepper:
|
||||
if current_control.handle_input_action(action):
|
||||
AudioManager.play_ui_click()
|
||||
return true
|
||||
return false
|
||||
```
|
||||
|
||||
## Integration Patterns
|
||||
|
||||
### Settings Menu Pattern
|
||||
See `scenes/ui/SettingsMenu.gd` for a complete example of integrating ValueStepper into a settings menu with full navigation support.
|
||||
|
||||
### Multiple Steppers Navigation
|
||||
See `examples/ValueStepperExample.gd` for an example showing multiple steppers with keyboard/gamepad navigation.
|
||||
|
||||
## Extending ValueStepper
|
||||
|
||||
### Adding New Data Sources
|
||||
|
||||
1. **Add to `_load_data()` method**:
|
||||
```gdscript
|
||||
func _load_data():
|
||||
match data_source:
|
||||
"language":
|
||||
_load_language_data()
|
||||
"your_custom_type":
|
||||
_load_your_custom_data()
|
||||
# ... other cases
|
||||
```
|
||||
|
||||
2. **Implement your loader**:
|
||||
```gdscript
|
||||
func _load_your_custom_data():
|
||||
values = ["value1", "value2", "value3"]
|
||||
display_names = ["Display 1", "Display 2", "Display 3"]
|
||||
current_index = 0
|
||||
```
|
||||
|
||||
3. **Add value application logic**:
|
||||
```gdscript
|
||||
func _apply_value_change(new_value: String, index: int):
|
||||
match data_source:
|
||||
"your_custom_type":
|
||||
# Apply your custom logic here
|
||||
YourManager.set_custom_setting(new_value)
|
||||
```
|
||||
|
||||
### Custom Formatting
|
||||
|
||||
Override `_update_display()` for custom display formatting:
|
||||
|
||||
```gdscript
|
||||
func _update_display():
|
||||
if data_source == "your_custom_type":
|
||||
# Custom formatting logic
|
||||
value_display.text = "Custom: " + display_names[current_index]
|
||||
else:
|
||||
super._update_display() # Call parent implementation
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### When to Use ValueStepper
|
||||
- ✅ **Discrete options**: Language, difficulty, resolution, theme
|
||||
- ✅ **Settings menus**: Any option with predefined choices
|
||||
- ✅ **Game configuration**: Graphics quality, control schemes
|
||||
- ✅ **Limited options**: 2-10 options work best
|
||||
|
||||
### When NOT to Use ValueStepper
|
||||
- ❌ **Continuous values**: Use sliders for volume, brightness
|
||||
- ❌ **Large lists**: Use ItemList or OptionButton for 20+ items
|
||||
- ❌ **Text input**: Use LineEdit for user-entered text
|
||||
- ❌ **Numeric input**: Use SpinBox for number entry
|
||||
|
||||
### Performance Considerations
|
||||
- ValueStepper is lightweight and suitable for multiple instances
|
||||
- Data loading happens once in `_ready()`
|
||||
- Visual updates are minimal (just text changes)
|
||||
|
||||
### Accessibility
|
||||
- Visual highlighting provides clear focus indication
|
||||
- Audio feedback confirms user actions
|
||||
- Keyboard and gamepad support for non-mouse users
|
||||
- Consistent navigation patterns
|
||||
|
||||
## Common Issues and Solutions
|
||||
|
||||
### Stepper Not Responding to Input
|
||||
- Ensure `handle_input_action()` is called from parent's `_input()`
|
||||
- Check that the stepper has proper focus/highlighting
|
||||
- Verify input actions are defined in project input map
|
||||
|
||||
### Values Not Saving
|
||||
- Override `_apply_value_change()` to handle persistence
|
||||
- Connect to `value_changed` signal for custom save logic
|
||||
- Ensure SettingsManager or your data manager is configured
|
||||
|
||||
### Display Names Not Showing
|
||||
- Check that `display_names` array is properly populated
|
||||
- Ensure `display_names.size()` matches `values.size()`
|
||||
- Verify `_update_display()` is called after data loading
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
scenes/ui/components/
|
||||
├── ValueStepper.gd # Main component script
|
||||
└── ValueStepper.tscn # Component scene
|
||||
|
||||
examples/
|
||||
├── ValueStepperExample.gd # Usage example script
|
||||
└── ValueStepperExample.tscn # Example scene
|
||||
|
||||
docs/
|
||||
└── UI_COMPONENTS.md # This documentation
|
||||
```
|
||||
|
||||
This component provides a solid foundation for any game's settings system and can be easily extended for project-specific needs.
|
||||
106
examples/ValueStepperExample.gd
Normal file
@@ -0,0 +1,106 @@
|
||||
# Example of how to use the ValueStepper component in any scene
|
||||
extends Control
|
||||
|
||||
# Example of setting up custom navigation
|
||||
var navigable_steppers: Array[ValueStepper] = []
|
||||
var current_stepper_index: int = 0
|
||||
|
||||
@onready
|
||||
var language_stepper: ValueStepper = $VBoxContainer/Examples/LanguageContainer/LanguageStepper
|
||||
@onready
|
||||
var difficulty_stepper: ValueStepper = $VBoxContainer/Examples/DifficultyContainer/DifficultyStepper
|
||||
@onready
|
||||
var resolution_stepper: ValueStepper = $VBoxContainer/Examples/ResolutionContainer/ResolutionStepper
|
||||
@onready
|
||||
var custom_stepper: ValueStepper = $VBoxContainer/Examples/CustomContainer/CustomStepper
|
||||
|
||||
|
||||
func _ready():
|
||||
DebugManager.log_info("ValueStepper example ready", "Example")
|
||||
|
||||
# Setup navigation array
|
||||
navigable_steppers = [
|
||||
language_stepper, difficulty_stepper, resolution_stepper, custom_stepper
|
||||
]
|
||||
|
||||
# Connect to value change events
|
||||
for stepper in navigable_steppers:
|
||||
if not stepper.value_changed.is_connected(_on_stepper_value_changed):
|
||||
stepper.value_changed.connect(_on_stepper_value_changed)
|
||||
|
||||
# Setup custom stepper with custom values
|
||||
var themes = ["Light", "Dark", "Blue", "Green", "Purple"]
|
||||
var theme_values = ["light", "dark", "blue", "green", "purple"]
|
||||
custom_stepper.setup_custom_values(theme_values, themes)
|
||||
custom_stepper.data_source = "theme" # For better logging
|
||||
|
||||
# Highlight first stepper
|
||||
_update_stepper_highlighting()
|
||||
|
||||
|
||||
func _input(event: InputEvent):
|
||||
# Example navigation handling
|
||||
if event.is_action_pressed("move_up"):
|
||||
_navigate_steppers(-1)
|
||||
get_viewport().set_input_as_handled()
|
||||
elif event.is_action_pressed("move_down"):
|
||||
_navigate_steppers(1)
|
||||
get_viewport().set_input_as_handled()
|
||||
elif event.is_action_pressed("move_left"):
|
||||
_handle_stepper_input("move_left")
|
||||
get_viewport().set_input_as_handled()
|
||||
elif event.is_action_pressed("move_right"):
|
||||
_handle_stepper_input("move_right")
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _navigate_steppers(direction: int):
|
||||
current_stepper_index = (
|
||||
(current_stepper_index + direction) % navigable_steppers.size()
|
||||
)
|
||||
if current_stepper_index < 0:
|
||||
current_stepper_index = navigable_steppers.size() - 1
|
||||
_update_stepper_highlighting()
|
||||
DebugManager.log_info(
|
||||
"Stepper navigation: index " + str(current_stepper_index), "Example"
|
||||
)
|
||||
|
||||
|
||||
func _handle_stepper_input(action: String):
|
||||
if (
|
||||
current_stepper_index >= 0
|
||||
and current_stepper_index < navigable_steppers.size()
|
||||
):
|
||||
var stepper = navigable_steppers[current_stepper_index]
|
||||
if stepper.handle_input_action(action):
|
||||
AudioManager.play_ui_click()
|
||||
|
||||
|
||||
func _update_stepper_highlighting():
|
||||
for i in range(navigable_steppers.size()):
|
||||
navigable_steppers[i].set_highlighted(i == current_stepper_index)
|
||||
|
||||
|
||||
func _on_stepper_value_changed(new_value: String, new_index: int):
|
||||
DebugManager.log_info(
|
||||
(
|
||||
"Stepper value changed to: "
|
||||
+ new_value
|
||||
+ " (index: "
|
||||
+ str(new_index)
|
||||
+ ")"
|
||||
),
|
||||
"Example"
|
||||
)
|
||||
# Handle value change in your scene
|
||||
# For example: apply settings, save preferences, update UI, etc.
|
||||
|
||||
|
||||
# Example of programmatically setting values
|
||||
func _on_reset_to_defaults_pressed():
|
||||
AudioManager.play_ui_click()
|
||||
language_stepper.set_current_value("en")
|
||||
difficulty_stepper.set_current_value("normal")
|
||||
resolution_stepper.set_current_value("1920x1080")
|
||||
custom_stepper.set_current_value("dark")
|
||||
DebugManager.log_info("Reset all steppers to defaults", "Example")
|
||||
1
examples/ValueStepperExample.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bu6f2vbdku4gg
|
||||
113
examples/ValueStepperExample.tscn
Normal file
@@ -0,0 +1,113 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://cw03putw85q1o"]
|
||||
|
||||
[ext_resource type="Script" path="res://examples/ValueStepperExample.gd" id="1_example"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/ui/components/ValueStepper.tscn" id="2_value_stepper"]
|
||||
|
||||
[node name="ValueStepperExample" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_example")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -200.0
|
||||
offset_top = -150.0
|
||||
offset_right = 200.0
|
||||
offset_bottom = 150.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Title" type="Label" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "ValueStepper Component Examples"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Examples" type="VBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="LanguageContainer" type="HBoxContainer" parent="VBoxContainer/Examples"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="LanguageLabel" type="Label" parent="VBoxContainer/Examples/LanguageContainer"]
|
||||
custom_minimum_size = Vector2(120, 0)
|
||||
layout_mode = 2
|
||||
text = "Language:"
|
||||
|
||||
[node name="LanguageStepper" parent="VBoxContainer/Examples/LanguageContainer" instance=ExtResource("2_value_stepper")]
|
||||
layout_mode = 2
|
||||
data_source = "language"
|
||||
|
||||
[node name="DifficultyContainer" type="HBoxContainer" parent="VBoxContainer/Examples"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="DifficultyLabel" type="Label" parent="VBoxContainer/Examples/DifficultyContainer"]
|
||||
custom_minimum_size = Vector2(120, 0)
|
||||
layout_mode = 2
|
||||
text = "Difficulty:"
|
||||
|
||||
[node name="DifficultyStepper" parent="VBoxContainer/Examples/DifficultyContainer" instance=ExtResource("2_value_stepper")]
|
||||
layout_mode = 2
|
||||
data_source = "difficulty"
|
||||
|
||||
[node name="ResolutionContainer" type="HBoxContainer" parent="VBoxContainer/Examples"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ResolutionLabel" type="Label" parent="VBoxContainer/Examples/ResolutionContainer"]
|
||||
custom_minimum_size = Vector2(120, 0)
|
||||
layout_mode = 2
|
||||
text = "Resolution:"
|
||||
|
||||
[node name="ResolutionStepper" parent="VBoxContainer/Examples/ResolutionContainer" instance=ExtResource("2_value_stepper")]
|
||||
layout_mode = 2
|
||||
data_source = "resolution"
|
||||
|
||||
[node name="CustomContainer" type="HBoxContainer" parent="VBoxContainer/Examples"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="CustomLabel" type="Label" parent="VBoxContainer/Examples/CustomContainer"]
|
||||
custom_minimum_size = Vector2(120, 0)
|
||||
layout_mode = 2
|
||||
text = "Theme:"
|
||||
|
||||
[node name="CustomStepper" parent="VBoxContainer/Examples/CustomContainer" instance=ExtResource("2_value_stepper")]
|
||||
layout_mode = 2
|
||||
data_source = "custom"
|
||||
|
||||
[node name="HSeparator2" type="HSeparator" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Instructions" type="Label" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Navigation:
|
||||
• Up/Down arrows - Navigate between steppers
|
||||
• Left/Right arrows - Change values
|
||||
• Mouse clicks work on arrow buttons
|
||||
|
||||
Features:
|
||||
• Multiple data sources (language, difficulty, resolution)
|
||||
• Custom values support (theme example)
|
||||
• Gamepad/keyboard navigation
|
||||
• Visual highlighting
|
||||
• Signal-based value changes"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="HSeparator3" type="HSeparator" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ResetButton" type="Button" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Reset to Defaults"
|
||||
|
||||
[connection signal="pressed" from="VBoxContainer/ResetButton" to="." method="_on_reset_to_defaults_pressed"]
|
||||
394
export_presets.cfg
Normal file
@@ -0,0 +1,394 @@
|
||||
[preset.0]
|
||||
|
||||
name="Windows Desktop"
|
||||
platform="Windows Desktop"
|
||||
runnable=true
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
export_files=PackedStringArray()
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="builds/skelly-windows.exe"
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
|
||||
[preset.0.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
debug/export_console_wrapper=1
|
||||
binary_format/embed_pck=true
|
||||
texture_format/bptc=true
|
||||
texture_format/s3tc=true
|
||||
texture_format/etc=false
|
||||
texture_format/etc2=false
|
||||
binary_format/architecture="x86_64"
|
||||
codesign/enable=false
|
||||
codesign/identity=""
|
||||
codesign/password=""
|
||||
codesign/timestamp=true
|
||||
codesign/timestamp_server_url=""
|
||||
codesign/digest_algorithm=1
|
||||
codesign/description=""
|
||||
codesign/custom_options=PackedStringArray()
|
||||
application/modify_resources=true
|
||||
application/icon=""
|
||||
application/console_wrapper_icon=""
|
||||
application/icon_interpolation=4
|
||||
application/file_version=""
|
||||
application/product_version=""
|
||||
application/company_name=""
|
||||
application/product_name="Skelly"
|
||||
application/file_description=""
|
||||
application/copyright=""
|
||||
application/trademarks=""
|
||||
application/export_angle=0
|
||||
ssh_remote_deploy/enabled=false
|
||||
ssh_remote_deploy/host="user@host_ip"
|
||||
ssh_remote_deploy/port="22"
|
||||
ssh_remote_deploy/extra_args_ssh=""
|
||||
ssh_remote_deploy/extra_args_scp=""
|
||||
ssh_remote_deploy/run_script=""
|
||||
ssh_remote_deploy/cleanup_script=""
|
||||
|
||||
[preset.1]
|
||||
|
||||
name="Linux"
|
||||
platform="Linux/X11"
|
||||
runnable=true
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
export_files=PackedStringArray()
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="builds/skelly-linux.x86_64"
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
|
||||
[preset.1.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
debug/export_console_wrapper=1
|
||||
binary_format/embed_pck=true
|
||||
texture_format/bptc=true
|
||||
texture_format/s3tc=true
|
||||
texture_format/etc=false
|
||||
texture_format/etc2=false
|
||||
binary_format/architecture="x86_64"
|
||||
ssh_remote_deploy/enabled=false
|
||||
ssh_remote_deploy/host="user@host_ip"
|
||||
ssh_remote_deploy/port="22"
|
||||
ssh_remote_deploy/extra_args_ssh=""
|
||||
ssh_remote_deploy/extra_args_scp=""
|
||||
ssh_remote_deploy/run_script=""
|
||||
ssh_remote_deploy/cleanup_script=""
|
||||
|
||||
[preset.2]
|
||||
|
||||
name="macOS"
|
||||
platform="macOS"
|
||||
runnable=true
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
export_files=PackedStringArray()
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="builds/skelly-macos.zip"
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
|
||||
[preset.2.options]
|
||||
|
||||
binary_format/architecture="universal"
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
debug/export_console_wrapper=1
|
||||
application/icon=""
|
||||
application/icon_interpolation=4
|
||||
application/bundle_identifier="com.skelly.game"
|
||||
application/signature=""
|
||||
application/app_category="Games"
|
||||
application/short_version="1.0"
|
||||
application/version="1.0"
|
||||
application/copyright=""
|
||||
application/copyright_localized={}
|
||||
application/min_macos_version="10.12"
|
||||
display/high_res=false
|
||||
xcode/platform_build="14C18"
|
||||
xcode/sdk_version="13.1"
|
||||
xcode/sdk_name="macosx13.1"
|
||||
xcode/sdk_build="22C55"
|
||||
xcode/xcode_version="1420"
|
||||
xcode/xcode_build="14C18"
|
||||
codesign/codesign=1
|
||||
codesign/installer_identity=""
|
||||
codesign/apple_team_id=""
|
||||
codesign/identity=""
|
||||
codesign/entitlements/custom_file=""
|
||||
codesign/entitlements/allow_jit_code_execution=false
|
||||
codesign/entitlements/allow_unsigned_executable_memory=false
|
||||
codesign/entitlements/allow_dyld_environment_variables=false
|
||||
codesign/entitlements/disable_library_validation=false
|
||||
codesign/entitlements/audio_input=false
|
||||
codesign/entitlements/camera=false
|
||||
codesign/entitlements/location=false
|
||||
codesign/entitlements/address_book=false
|
||||
codesign/entitlements/calendars=false
|
||||
codesign/entitlements/photos_library=false
|
||||
codesign/entitlements/apple_events=false
|
||||
codesign/entitlements/debugging=false
|
||||
codesign/entitlements/app_sandbox/enabled=false
|
||||
codesign/entitlements/app_sandbox/network_server=false
|
||||
codesign/entitlements/app_sandbox/network_client=false
|
||||
codesign/entitlements/app_sandbox/device_usb=false
|
||||
codesign/entitlements/app_sandbox/device_bluetooth=false
|
||||
codesign/entitlements/app_sandbox/files_downloads=0
|
||||
codesign/entitlements/app_sandbox/files_pictures=0
|
||||
codesign/entitlements/app_sandbox/files_music=0
|
||||
codesign/entitlements/app_sandbox/files_movies=0
|
||||
codesign/entitlements/app_sandbox/helper_executables=[]
|
||||
notarization/notarization=0
|
||||
privacy/microphone_usage_description=""
|
||||
privacy/microphone_usage_description_localized={}
|
||||
privacy/camera_usage_description=""
|
||||
privacy/camera_usage_description_localized={}
|
||||
privacy/location_usage_description=""
|
||||
privacy/location_usage_description_localized={}
|
||||
privacy/address_book_usage_description=""
|
||||
privacy/address_book_usage_description_localized={}
|
||||
privacy/calendar_usage_description=""
|
||||
privacy/calendar_usage_description_localized={}
|
||||
privacy/photos_library_usage_description=""
|
||||
privacy/photos_library_usage_description_localized={}
|
||||
privacy/desktop_folder_usage_description=""
|
||||
privacy/desktop_folder_usage_description_localized={}
|
||||
privacy/documents_folder_usage_description=""
|
||||
privacy/documents_folder_usage_description_localized={}
|
||||
privacy/downloads_folder_usage_description=""
|
||||
privacy/downloads_folder_usage_description_localized={}
|
||||
privacy/network_volumes_usage_description=""
|
||||
privacy/network_volumes_usage_description_localized={}
|
||||
privacy/removable_volumes_usage_description=""
|
||||
privacy/removable_volumes_usage_description_localized={}
|
||||
ssh_remote_deploy/enabled=false
|
||||
ssh_remote_deploy/host="user@host_ip"
|
||||
ssh_remote_deploy/port="22"
|
||||
ssh_remote_deploy/extra_args_ssh=""
|
||||
ssh_remote_deploy/extra_args_scp=""
|
||||
ssh_remote_deploy/run_script=""
|
||||
ssh_remote_deploy/cleanup_script=""
|
||||
|
||||
[preset.3]
|
||||
|
||||
name="Android"
|
||||
platform="Android"
|
||||
runnable=true
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
export_files=PackedStringArray()
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="builds/skelly-android.apk"
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
|
||||
[preset.3.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
gradle_build/use_gradle_build=false
|
||||
gradle_build/export_format=0
|
||||
gradle_build/min_sdk=""
|
||||
gradle_build/target_sdk=""
|
||||
architectures/armeabi-v7a=false
|
||||
architectures/arm64-v8a=true
|
||||
architectures/x86=false
|
||||
architectures/x86_64=false
|
||||
version/code=1
|
||||
version/name="1.0"
|
||||
package/unique_name="com.skelly.game"
|
||||
package/name="Skelly"
|
||||
package/signed=true
|
||||
package/app_category=2
|
||||
package/retain_data_on_uninstall=false
|
||||
package/exclude_from_recents=false
|
||||
launcher_icons/main_192x192=""
|
||||
launcher_icons/adaptive_foreground_432x432=""
|
||||
launcher_icons/adaptive_background_432x432=""
|
||||
graphics/32_bits_framebuffer=true
|
||||
graphics/opengl_debug=false
|
||||
xr_features/xr_mode=0
|
||||
xr_features/hand_tracking=0
|
||||
xr_features/hand_tracking_frequency=0
|
||||
xr_features/passthrough=0
|
||||
screen/immersive_mode=true
|
||||
screen/orientation=0
|
||||
screen/support_small=true
|
||||
screen/support_normal=true
|
||||
screen/support_large=true
|
||||
screen/support_xlarge=true
|
||||
user_data_backup/allow=false
|
||||
command_line/extra_args=""
|
||||
apk_expansion/enable=false
|
||||
apk_expansion/SALT=""
|
||||
apk_expansion/public_key=""
|
||||
permissions/custom_permissions=PackedStringArray()
|
||||
permissions/access_checkin_properties=false
|
||||
permissions/access_coarse_location=false
|
||||
permissions/access_fine_location=false
|
||||
permissions/access_location_extra_commands=false
|
||||
permissions/access_mock_location=false
|
||||
permissions/access_network_state=false
|
||||
permissions/access_surface_flinger=false
|
||||
permissions/access_wifi_state=false
|
||||
permissions/account_manager=false
|
||||
permissions/add_voicemail=false
|
||||
permissions/authenticate_accounts=false
|
||||
permissions/battery_stats=false
|
||||
permissions/bind_accessibility_service=false
|
||||
permissions/bind_appwidget=false
|
||||
permissions/bind_device_admin=false
|
||||
permissions/bind_input_method=false
|
||||
permissions/bind_nfc_service=false
|
||||
permissions/bind_notification_listener_service=false
|
||||
permissions/bind_print_service=false
|
||||
permissions/bind_remoteviews=false
|
||||
permissions/bind_text_service=false
|
||||
permissions/bind_vpn_service=false
|
||||
permissions/bind_wallpaper=false
|
||||
permissions/bluetooth=false
|
||||
permissions/bluetooth_admin=false
|
||||
permissions/bluetooth_privileged=false
|
||||
permissions/brick=false
|
||||
permissions/broadcast_package_removed=false
|
||||
permissions/broadcast_sms=false
|
||||
permissions/broadcast_sticky=false
|
||||
permissions/broadcast_wap_push=false
|
||||
permissions/call_phone=false
|
||||
permissions/call_privileged=false
|
||||
permissions/camera=false
|
||||
permissions/capture_audio_output=false
|
||||
permissions/capture_secure_video_output=false
|
||||
permissions/capture_video_output=false
|
||||
permissions/change_component_enabled_state=false
|
||||
permissions/change_configuration=false
|
||||
permissions/change_network_state=false
|
||||
permissions/change_wifi_multicast_state=false
|
||||
permissions/change_wifi_state=false
|
||||
permissions/clear_app_cache=false
|
||||
permissions/clear_app_user_data=false
|
||||
permissions/control_location_updates=false
|
||||
permissions/delete_cache_files=false
|
||||
permissions/delete_packages=false
|
||||
permissions/device_power=false
|
||||
permissions/diagnostic=false
|
||||
permissions/disable_keyguard=false
|
||||
permissions/dump=false
|
||||
permissions/expand_status_bar=false
|
||||
permissions/factory_test=false
|
||||
permissions/flashlight=false
|
||||
permissions/force_back=false
|
||||
permissions/get_accounts=false
|
||||
permissions/get_package_size=false
|
||||
permissions/get_tasks=false
|
||||
permissions/get_top_activity_info=false
|
||||
permissions/global_search=false
|
||||
permissions/hardware_test=false
|
||||
permissions/inject_events=false
|
||||
permissions/install_location_provider=false
|
||||
permissions/install_packages=false
|
||||
permissions/install_shortcut=false
|
||||
permissions/internal_system_window=false
|
||||
permissions/internet=false
|
||||
permissions/kill_background_processes=false
|
||||
permissions/location_hardware=false
|
||||
permissions/manage_accounts=false
|
||||
permissions/manage_app_tokens=false
|
||||
permissions/manage_documents=false
|
||||
permissions/manage_external_storage=false
|
||||
permissions/master_clear=false
|
||||
permissions/media_content_control=false
|
||||
permissions/modify_audio_settings=false
|
||||
permissions/modify_phone_state=false
|
||||
permissions/mount_format_filesystems=false
|
||||
permissions/mount_unmount_filesystems=false
|
||||
permissions/nfc=false
|
||||
permissions/persistent_activity=false
|
||||
permissions/process_outgoing_calls=false
|
||||
permissions/read_calendar=false
|
||||
permissions/read_call_log=false
|
||||
permissions/read_contacts=false
|
||||
permissions/read_external_storage=false
|
||||
permissions/read_frame_buffer=false
|
||||
permissions/read_history_bookmarks=false
|
||||
permissions/read_input_state=false
|
||||
permissions/read_logs=false
|
||||
permissions/read_phone_state=false
|
||||
permissions/read_profile=false
|
||||
permissions/read_sms=false
|
||||
permissions/read_social_stream=false
|
||||
permissions/read_sync_settings=false
|
||||
permissions/read_sync_stats=false
|
||||
permissions/read_user_dictionary=false
|
||||
permissions/reboot=false
|
||||
permissions/receive_boot_completed=false
|
||||
permissions/receive_mms=false
|
||||
permissions/receive_sms=false
|
||||
permissions/receive_wap_push=false
|
||||
permissions/record_audio=false
|
||||
permissions/reorder_tasks=false
|
||||
permissions/restart_packages=false
|
||||
permissions/send_respond_via_message=false
|
||||
permissions/send_sms=false
|
||||
permissions/set_activity_watcher=false
|
||||
permissions/set_alarm=false
|
||||
permissions/set_always_finish=false
|
||||
permissions/set_animation_scale=false
|
||||
permissions/set_debug_app=false
|
||||
permissions/set_orientation=false
|
||||
permissions/set_pointer_speed=false
|
||||
permissions/set_preferred_applications=false
|
||||
permissions/set_process_limit=false
|
||||
permissions/set_time=false
|
||||
permissions/set_time_zone=false
|
||||
permissions/set_wallpaper=false
|
||||
permissions/set_wallpaper_hints=false
|
||||
permissions/signal_persistent_processes=false
|
||||
permissions/status_bar=false
|
||||
permissions/subscribed_feeds_read=false
|
||||
permissions/subscribed_feeds_write=false
|
||||
permissions/system_alert_window=false
|
||||
permissions/transmit_ir=false
|
||||
permissions/uninstall_shortcut=false
|
||||
permissions/update_device_stats=false
|
||||
permissions/use_credentials=false
|
||||
permissions/use_sip=false
|
||||
permissions/vibrate=false
|
||||
permissions/wake_lock=false
|
||||
permissions/write_apn_settings=false
|
||||
permissions/write_calendar=false
|
||||
permissions/write_call_log=false
|
||||
permissions/write_contacts=false
|
||||
permissions/write_external_storage=false
|
||||
permissions/write_gservices=false
|
||||
permissions/write_history_bookmarks=false
|
||||
permissions/write_profile=false
|
||||
permissions/write_secure_settings=false
|
||||
permissions/write_settings=false
|
||||
permissions/write_sms=false
|
||||
permissions/write_social_stream=false
|
||||
permissions/write_sync_settings=false
|
||||
permissions/write_user_dictionary=false
|
||||
46
gdlintrc
Normal file
@@ -0,0 +1,46 @@
|
||||
class-definitions-order:
|
||||
- tools
|
||||
- classnames
|
||||
- extends
|
||||
- signals
|
||||
- enums
|
||||
- consts
|
||||
- exports
|
||||
- pubvars
|
||||
- prvvars
|
||||
- onreadypubvars
|
||||
- onreadyprvvars
|
||||
- others
|
||||
class-load-variable-name: (([A-Z][a-z0-9]*)+|_?[a-z][a-z0-9]*(_[a-z0-9]+)*)
|
||||
class-name: ([A-Z][a-z0-9]*)+
|
||||
class-variable-name: _?[a-z][a-z0-9]*(_[a-z0-9]+)*
|
||||
comparison-with-itself: null
|
||||
constant-name: '[A-Z][A-Z0-9]*(_[A-Z0-9]+)*'
|
||||
disable: []
|
||||
duplicated-load: null
|
||||
enum-element-name: '[A-Z][A-Z0-9]*(_[A-Z0-9]+)*'
|
||||
enum-name: ([A-Z][a-z0-9]*)+
|
||||
excluded_directories: !!set
|
||||
.git: null
|
||||
expression-not-assigned: null
|
||||
function-argument-name: _?[a-z][a-z0-9]*(_[a-z0-9]+)*
|
||||
function-arguments-number: 10
|
||||
function-name: (_on_([A-Z][a-z0-9]*)+(_[a-z0-9]+)*|_?[a-z][a-z0-9]*(_[a-z0-9]+)*)
|
||||
function-preload-variable-name: ([A-Z][a-z0-9]*)+
|
||||
function-variable-name: '[a-z][a-z0-9]*(_[a-z0-9]+)*'
|
||||
load-constant-name: (([A-Z][a-z0-9]*)+|[A-Z][A-Z0-9]*(_[A-Z0-9]+)*)
|
||||
loop-variable-name: _?[a-z][a-z0-9]*(_[a-z0-9]+)*
|
||||
max-file-lines: 1500
|
||||
max-line-length: 120
|
||||
max-public-methods: 20
|
||||
max-returns: 6
|
||||
mixed-tabs-and-spaces: null
|
||||
no-elif-return: null
|
||||
no-else-return: null
|
||||
private-method-call: null
|
||||
signal-name: '[a-z][a-z0-9]*(_[a-z0-9]+)*'
|
||||
sub-class-name: _?([A-Z][a-z0-9]*)+
|
||||
tab-characters: 1
|
||||
trailing-whitespace: null
|
||||
unnecessary-pass: null
|
||||
unused-argument: null
|
||||
@@ -6,3 +6,13 @@ music_volume;"Music Volume";"Громкость музыки"
|
||||
sfx_volume;"SFX Volume";"Громкость эффектов"
|
||||
language;"Language";"Язык"
|
||||
back;"Back";"Назад"
|
||||
reset_progress;"Reset All Progress";"Сбросить весь прогресс"
|
||||
confirm_reset_title;"Confirm Progress Reset";"Подтвердите сброс прогресса"
|
||||
confirm_reset_message;"Are you sure you want to delete ALL your progress?\n\nThis will permanently remove:\n• All scores and high scores\n• Game statistics\n• Saved game states\n\nThis action cannot be undone!";"Вы уверены, что хотите удалить ВЕСЬ свой прогресс?\n\nЭто навсегда удалит:\n• Все очки и рекорды\n• Игровую статистику\n• Сохранённые игровые состояния\n\nЭто действие нельзя отменить!"
|
||||
reset_confirm;"Yes, Reset Everything";"Да, сбросить всё"
|
||||
cancel;"Cancel";"Отмена"
|
||||
reset_success_title;"Progress Reset";"Прогресс сброшен"
|
||||
reset_success_message;"All progress has been successfully deleted.\nYour game has been reset to the beginning.";"Весь прогресс был успешно удалён.\nВаша игра была сброшена к началу."
|
||||
reset_error_title;"Reset Failed";"Ошибка сброса"
|
||||
reset_error_message;"There was an error resetting your progress.\nPlease try again.";"Произошла ошибка при сбросе вашего прогресса.\nПопробуйте ещё раз."
|
||||
ok;"OK";"ОК"
|
||||
|
||||
|
187
project.godot
@@ -11,16 +11,32 @@ config_version=5
|
||||
[application]
|
||||
|
||||
config/name="Skelly"
|
||||
run/main_scene="uid://ci2gk11211n0d"
|
||||
run/main_scene="res://scenes/main/Main.tscn"
|
||||
config/features=PackedStringArray("4.4", "Mobile")
|
||||
config/icon="res://icon.svg"
|
||||
boot_splash/handheld/orientation=0
|
||||
boot_splash/stretch/aspect="keep"
|
||||
|
||||
[audio]
|
||||
|
||||
buses/default_bus_layout="res://data/default_bus_layout.tres"
|
||||
|
||||
[autoload]
|
||||
|
||||
SettingsManager="*res://scripts/SettingsManager.gd"
|
||||
AudioManager="*res://scripts/AudioManager.gd"
|
||||
GameManager="*res://scripts/GameManager.gd"
|
||||
LocalizationManager="*res://scripts/LocalizationManager.gd"
|
||||
SettingsManager="*res://src/autoloads/SettingsManager.gd"
|
||||
AudioManager="*res://src/autoloads/AudioManager.gd"
|
||||
GameManager="*res://src/autoloads/GameManager.gd"
|
||||
LocalizationManager="*res://src/autoloads/LocalizationManager.gd"
|
||||
DebugManager="*res://src/autoloads/DebugManager.gd"
|
||||
SaveManager="*res://src/autoloads/SaveManager.gd"
|
||||
UIConstants="*res://src/autoloads/UIConstants.gd"
|
||||
|
||||
[display]
|
||||
|
||||
window/size/viewport_width=1024
|
||||
window/size/viewport_height=768
|
||||
window/stretch/mode="canvas_items"
|
||||
window/handheld/orientation=4
|
||||
|
||||
[input]
|
||||
|
||||
@@ -30,9 +46,10 @@ ui_pause={
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":6,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
any_key={
|
||||
action_confirm={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194309,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
|
||||
, Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(165, 16),"global_position":Vector2(174, 64),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null)
|
||||
]
|
||||
@@ -43,6 +60,160 @@ ui_menu_toggle={
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
action_south={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194309,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":74,"key_label":0,"unicode":106,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
action_east={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194308,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":75,"key_label":0,"unicode":107,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
action_west={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":102,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":72,"key_label":0,"unicode":104,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":2,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
action_north={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":73,"key_label":0,"unicode":105,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":76,"key_label":0,"unicode":108,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":3,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_up={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194320,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":75,"key_label":0,"unicode":107,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":11,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_down={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194322,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":74,"key_label":0,"unicode":106,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_left={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194319,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":72,"key_label":0,"unicode":104,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_right={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194321,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":76,"key_label":0,"unicode":108,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
shoulder_left={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":81,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":90,"key_label":0,"unicode":122,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":4,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
shoulder_right={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":82,"key_label":0,"unicode":114,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":5,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
trigger_left={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194323,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":91,"key_label":0,"unicode":91,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":6,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
trigger_right={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194324,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":93,"key_label":0,"unicode":93,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":7,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
pause_menu={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":80,"key_label":0,"unicode":112,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":6,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
options_menu={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":79,"key_label":0,"unicode":111,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194332,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":4,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
special_1={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":8,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
special_2={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194326,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":9,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
restart_level={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":82,"key_label":0,"unicode":114,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
next_level={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":78,"key_label":0,"unicode":110,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
toggle_music={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":77,"key_label":0,"unicode":109,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
toggle_sound={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194326,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
fullscreen={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194342,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
quit_game={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194335,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
ui_back={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":6,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[internationalization]
|
||||
|
||||
@@ -51,4 +222,6 @@ locale/translations=PackedStringArray("res://localization/MainStrings.en.transla
|
||||
[rendering]
|
||||
|
||||
textures/canvas_textures/default_texture_filter=0
|
||||
renderer/rendering_method="mobile"
|
||||
renderer/rendering_method="gl_compatibility"
|
||||
renderer/rendering_method.mobile="gl_compatibility"
|
||||
textures/vram_compression/import_etc2_astc=true
|
||||
|
||||
10
requirements.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
aiofiles>=23
|
||||
gdtoolkit>=4
|
||||
PyYAML>=6.0
|
||||
ruff>=0.1.0
|
||||
setuptools<81
|
||||
yamllint>=1
|
||||
|
||||
# Diagram generation and rendering
|
||||
matplotlib>=3.8.0 # Chart and graph generation
|
||||
pillow>=10.0.0 # Image processing and manipulation
|
||||
232
run_dev.bat
Normal file
@@ -0,0 +1,232 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM =============================================================================
|
||||
REM Skelly Development Tools Runner
|
||||
REM =============================================================================
|
||||
REM
|
||||
REM This script runs development tools for the Skelly Godot project.
|
||||
REM By default, it runs all checks: linting, formatting, and testing.
|
||||
REM
|
||||
REM Usage:
|
||||
REM run_dev.bat - Run all checks (lint + format + test)
|
||||
REM run_dev.bat --lint - Run only linting
|
||||
REM run_dev.bat --format - Run only formatting
|
||||
REM run_dev.bat --test - Run only tests
|
||||
REM run_dev.bat --help - Show this help message
|
||||
REM run_dev.bat --steps lint test - Run specific steps in order
|
||||
REM
|
||||
REM =============================================================================
|
||||
|
||||
REM Initialize variables
|
||||
set "ARG_LINT_ONLY="
|
||||
set "ARG_FORMAT_ONLY="
|
||||
set "ARG_TEST_ONLY="
|
||||
set "ARG_HELP="
|
||||
set "ARG_STEPS="
|
||||
set "CUSTOM_STEPS="
|
||||
|
||||
REM Parse command line arguments
|
||||
:parse_args
|
||||
if "%~1"=="" goto :args_parsed
|
||||
if /i "%~1"=="--lint" (
|
||||
set "ARG_LINT_ONLY=1"
|
||||
shift
|
||||
goto :parse_args
|
||||
)
|
||||
if /i "%~1"=="--format" (
|
||||
set "ARG_FORMAT_ONLY=1"
|
||||
shift
|
||||
goto :parse_args
|
||||
)
|
||||
if /i "%~1"=="--test" (
|
||||
set "ARG_TEST_ONLY=1"
|
||||
shift
|
||||
goto :parse_args
|
||||
)
|
||||
if /i "%~1"=="--help" (
|
||||
set "ARG_HELP=1"
|
||||
shift
|
||||
goto :parse_args
|
||||
)
|
||||
if /i "%~1"=="--steps" (
|
||||
set "ARG_STEPS=1"
|
||||
shift
|
||||
REM Collect remaining arguments as custom steps
|
||||
:collect_steps
|
||||
if "%~1"=="" goto :args_parsed
|
||||
if "!CUSTOM_STEPS!"=="" (
|
||||
set "CUSTOM_STEPS=%~1"
|
||||
) else (
|
||||
set "CUSTOM_STEPS=!CUSTOM_STEPS! %~1"
|
||||
)
|
||||
shift
|
||||
goto :collect_steps
|
||||
)
|
||||
REM Unknown argument
|
||||
echo ❌ Unknown argument: %~1
|
||||
echo Use --help for usage information
|
||||
exit /b 1
|
||||
|
||||
:args_parsed
|
||||
|
||||
REM Show help if requested
|
||||
if defined ARG_HELP (
|
||||
echo.
|
||||
echo 🔧 Skelly Development Tools Runner
|
||||
echo.
|
||||
echo Usage:
|
||||
echo run_dev.bat - Run all checks ^(lint + format + test^)
|
||||
echo run_dev.bat --lint - Run only linting
|
||||
echo run_dev.bat --format - Run only formatting
|
||||
echo run_dev.bat --test - Run only tests
|
||||
echo run_dev.bat --help - Show this help message
|
||||
echo run_dev.bat --steps lint test - Run specific steps in order
|
||||
echo.
|
||||
echo Available steps for --steps:
|
||||
echo lint - Run GDScript linting ^(gdlint^)
|
||||
echo format - Run GDScript formatting ^(gdformat^)
|
||||
echo test - Run Godot tests
|
||||
echo.
|
||||
echo Examples:
|
||||
echo run_dev.bat ^(runs lint, format, test^)
|
||||
echo run_dev.bat --lint ^(runs only linting^)
|
||||
echo run_dev.bat --steps format lint ^(runs format then lint^)
|
||||
echo.
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
echo ================================
|
||||
echo 🚀 Development Tools Runner
|
||||
echo ================================
|
||||
echo.
|
||||
|
||||
REM Check if Python is available
|
||||
python --version >nul 2>&1
|
||||
if !errorlevel! neq 0 (
|
||||
echo ❌ ERROR: Python is not installed or not in PATH
|
||||
echo.
|
||||
echo Installation instructions:
|
||||
echo 1. Install Python: winget install Python.Python.3.13
|
||||
echo 2. Restart your command prompt
|
||||
echo 3. Run this script again
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Check if pip is available
|
||||
pip --version >nul 2>&1
|
||||
if !errorlevel! neq 0 (
|
||||
echo ❌ ERROR: pip is not installed or not in PATH
|
||||
echo Please ensure Python was installed correctly with pip
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Check if Godot is available (only if test step will be run)
|
||||
set "NEED_GODOT="
|
||||
if defined ARG_TEST_ONLY set "NEED_GODOT=1"
|
||||
if defined ARG_STEPS (
|
||||
echo !CUSTOM_STEPS! | findstr /i "test" >nul && set "NEED_GODOT=1"
|
||||
)
|
||||
if not defined ARG_LINT_ONLY if not defined ARG_FORMAT_ONLY if not defined ARG_STEPS set "NEED_GODOT=1"
|
||||
|
||||
if defined NEED_GODOT (
|
||||
godot --version >nul 2>&1
|
||||
if !errorlevel! neq 0 (
|
||||
echo ❌ ERROR: Godot is not installed or not in PATH
|
||||
echo.
|
||||
echo Installation instructions:
|
||||
echo 1. Download Godot from https://godotengine.org/download
|
||||
echo 2. Add Godot executable to your PATH environment variable
|
||||
echo 3. Or place godot.exe in this project directory
|
||||
echo 4. Restart your command prompt
|
||||
echo 5. Run this script again
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
REM Check if gdlint and gdformat are available (only if needed)
|
||||
set "NEED_GDTOOLS="
|
||||
if defined ARG_LINT_ONLY set "NEED_GDTOOLS=1"
|
||||
if defined ARG_FORMAT_ONLY set "NEED_GDTOOLS=1"
|
||||
if defined ARG_STEPS (
|
||||
echo !CUSTOM_STEPS! | findstr /i /c:"lint" >nul && set "NEED_GDTOOLS=1"
|
||||
echo !CUSTOM_STEPS! | findstr /i /c:"format" >nul && set "NEED_GDTOOLS=1"
|
||||
)
|
||||
if not defined ARG_TEST_ONLY if not defined ARG_STEPS set "NEED_GDTOOLS=1"
|
||||
|
||||
if defined NEED_GDTOOLS (
|
||||
gdlint --version >nul 2>&1
|
||||
if !errorlevel! neq 0 (
|
||||
echo ❌ ERROR: gdlint is not installed or not in PATH
|
||||
echo.
|
||||
echo Installation instructions:
|
||||
echo 1. pip install --upgrade "setuptools<81"
|
||||
echo 2. pip install gdtoolkit==4
|
||||
echo 3. Restart your command prompt
|
||||
echo 4. Run this script again
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
gdformat --version >nul 2>&1
|
||||
if !errorlevel! neq 0 (
|
||||
echo ❌ ERROR: gdformat is not installed or not in PATH
|
||||
echo.
|
||||
echo Installation instructions:
|
||||
echo 1. pip install --upgrade "setuptools<81"
|
||||
echo 2. pip install gdtoolkit==4
|
||||
echo 3. Restart your command prompt
|
||||
echo 4. Run this script again
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
echo ✅ All dependencies are available. Running development workflow...
|
||||
echo.
|
||||
|
||||
REM Build Python command based on arguments
|
||||
set "PYTHON_CMD=python tools\run_development.py"
|
||||
|
||||
if defined ARG_LINT_ONLY (
|
||||
set "PYTHON_CMD=!PYTHON_CMD! --lint"
|
||||
echo 🔍 Running linting only...
|
||||
) else if defined ARG_FORMAT_ONLY (
|
||||
set "PYTHON_CMD=!PYTHON_CMD! --format"
|
||||
echo 🎨 Running formatting only...
|
||||
) else if defined ARG_TEST_ONLY (
|
||||
set "PYTHON_CMD=!PYTHON_CMD! --test"
|
||||
echo 🧪 Running tests only...
|
||||
) else if defined ARG_STEPS (
|
||||
set "PYTHON_CMD=!PYTHON_CMD! --steps !CUSTOM_STEPS!"
|
||||
echo 🔄 Running custom workflow: !CUSTOM_STEPS!...
|
||||
) else (
|
||||
echo 🚀 Running complete development workflow: format + lint + test...
|
||||
)
|
||||
|
||||
echo.
|
||||
|
||||
REM Run the Python development workflow script
|
||||
!PYTHON_CMD!
|
||||
|
||||
REM Capture exit code and display result
|
||||
set WORKFLOW_RESULT=!errorlevel!
|
||||
|
||||
echo.
|
||||
if !WORKFLOW_RESULT! equ 0 (
|
||||
echo 🎉 Development workflow completed successfully!
|
||||
) else (
|
||||
echo ⚠️ Development workflow completed with issues.
|
||||
echo Please review the output above and fix any problems.
|
||||
)
|
||||
|
||||
echo.
|
||||
pause
|
||||
exit /b !WORKFLOW_RESULT!
|
||||
240
run_dev.sh
Normal file
@@ -0,0 +1,240 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
# =============================================================================
|
||||
# Skelly Development Tools Runner
|
||||
# =============================================================================
|
||||
#
|
||||
# This script runs development tools for the Skelly Godot project.
|
||||
# By default, it runs all checks: linting, formatting, and testing.
|
||||
#
|
||||
# Usage:
|
||||
# ./run_dev.sh - Run all checks (lint + format + test)
|
||||
# ./run_dev.sh --lint - Run only linting
|
||||
# ./run_dev.sh --format - Run only formatting
|
||||
# ./run_dev.sh --test - Run only tests
|
||||
# ./run_dev.sh --help - Show this help message
|
||||
# ./run_dev.sh --steps lint test - Run specific steps in order
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
# Initialize variables
|
||||
ARG_LINT_ONLY=""
|
||||
ARG_FORMAT_ONLY=""
|
||||
ARG_TEST_ONLY=""
|
||||
ARG_HELP=""
|
||||
ARG_STEPS=""
|
||||
CUSTOM_STEPS=""
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--lint)
|
||||
ARG_LINT_ONLY=1
|
||||
shift
|
||||
;;
|
||||
--format)
|
||||
ARG_FORMAT_ONLY=1
|
||||
shift
|
||||
;;
|
||||
--test)
|
||||
ARG_TEST_ONLY=1
|
||||
shift
|
||||
;;
|
||||
--help)
|
||||
ARG_HELP=1
|
||||
shift
|
||||
;;
|
||||
--steps)
|
||||
ARG_STEPS=1
|
||||
shift
|
||||
# Collect remaining arguments as custom steps
|
||||
while [[ $# -gt 0 ]]; do
|
||||
if [[ -z "$CUSTOM_STEPS" ]]; then
|
||||
CUSTOM_STEPS="$1"
|
||||
else
|
||||
CUSTOM_STEPS="$CUSTOM_STEPS $1"
|
||||
fi
|
||||
shift
|
||||
done
|
||||
;;
|
||||
*)
|
||||
echo "❌ Unknown argument: $1"
|
||||
echo "Use --help for usage information"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Show help if requested
|
||||
if [[ -n "$ARG_HELP" ]]; then
|
||||
echo
|
||||
echo "🔧 Skelly Development Tools Runner"
|
||||
echo
|
||||
echo "Usage:"
|
||||
echo " ./run_dev.sh - Run all checks (lint + format + test)"
|
||||
echo " ./run_dev.sh --lint - Run only linting"
|
||||
echo " ./run_dev.sh --format - Run only formatting"
|
||||
echo " ./run_dev.sh --test - Run only tests"
|
||||
echo " ./run_dev.sh --help - Show this help message"
|
||||
echo " ./run_dev.sh --steps lint test - Run specific steps in order"
|
||||
echo
|
||||
echo "Available steps for --steps:"
|
||||
echo " lint - Run GDScript linting (gdlint)"
|
||||
echo " format - Run GDScript formatting (gdformat)"
|
||||
echo " test - Run Godot tests"
|
||||
echo
|
||||
echo "Examples:"
|
||||
echo " ./run_dev.sh (runs lint, format, test)"
|
||||
echo " ./run_dev.sh --lint (runs only linting)"
|
||||
echo " ./run_dev.sh --steps format lint (runs format then lint)"
|
||||
echo
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "================================"
|
||||
echo "🚀 Development Tools Runner"
|
||||
echo "================================"
|
||||
echo
|
||||
|
||||
# Check if Python is available
|
||||
if ! command -v python3 &> /dev/null && ! command -v python &> /dev/null; then
|
||||
echo "❌ ERROR: Python is not installed or not in PATH"
|
||||
echo
|
||||
echo "Installation instructions:"
|
||||
echo "1. Ubuntu/Debian: sudo apt update && sudo apt install python3 python3-pip"
|
||||
echo "2. macOS: brew install python"
|
||||
echo "3. Or download from: https://python.org/downloads"
|
||||
echo "4. Restart your terminal"
|
||||
echo "5. Run this script again"
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Use python3 if available, otherwise python
|
||||
PYTHON_CMD="python3"
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
PYTHON_CMD="python"
|
||||
fi
|
||||
|
||||
# Check if pip is available
|
||||
if ! command -v pip3 &> /dev/null && ! command -v pip &> /dev/null; then
|
||||
echo "❌ ERROR: pip is not installed or not in PATH"
|
||||
echo "Please ensure Python was installed correctly with pip"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Use pip3 if available, otherwise pip
|
||||
PIP_CMD="pip3"
|
||||
if ! command -v pip3 &> /dev/null; then
|
||||
PIP_CMD="pip"
|
||||
fi
|
||||
|
||||
# Check if Godot is available (only if test step will be run)
|
||||
NEED_GODOT=""
|
||||
if [[ -n "$ARG_TEST_ONLY" ]]; then
|
||||
NEED_GODOT=1
|
||||
fi
|
||||
if [[ -n "$ARG_STEPS" ]] && [[ "$CUSTOM_STEPS" == *"test"* ]]; then
|
||||
NEED_GODOT=1
|
||||
fi
|
||||
if [[ -z "$ARG_LINT_ONLY" && -z "$ARG_FORMAT_ONLY" && -z "$ARG_STEPS" ]]; then
|
||||
NEED_GODOT=1
|
||||
fi
|
||||
|
||||
if [[ -n "$NEED_GODOT" ]]; then
|
||||
if ! command -v godot &> /dev/null; then
|
||||
echo "❌ ERROR: Godot is not installed or not in PATH"
|
||||
echo
|
||||
echo "Installation instructions:"
|
||||
echo "1. Download Godot from https://godotengine.org/download"
|
||||
echo "2. Add Godot executable to your PATH environment variable"
|
||||
echo "3. Or place godot executable in this project directory"
|
||||
echo "4. Restart your terminal"
|
||||
echo "5. Run this script again"
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if gdlint and gdformat are available (only if needed)
|
||||
NEED_GDTOOLS=""
|
||||
if [[ -n "$ARG_LINT_ONLY" ]]; then
|
||||
NEED_GDTOOLS=1
|
||||
fi
|
||||
if [[ -n "$ARG_FORMAT_ONLY" ]]; then
|
||||
NEED_GDTOOLS=1
|
||||
fi
|
||||
if [[ -n "$ARG_STEPS" ]] && ([[ "$CUSTOM_STEPS" == *"lint"* ]] || [[ "$CUSTOM_STEPS" == *"format"* ]]); then
|
||||
NEED_GDTOOLS=1
|
||||
fi
|
||||
if [[ -z "$ARG_TEST_ONLY" && -z "$ARG_STEPS" ]]; then
|
||||
NEED_GDTOOLS=1
|
||||
fi
|
||||
|
||||
if [[ -n "$NEED_GDTOOLS" ]]; then
|
||||
if ! command -v gdlint &> /dev/null; then
|
||||
echo "❌ ERROR: gdlint is not installed or not in PATH"
|
||||
echo
|
||||
echo "Installation instructions:"
|
||||
echo "1. $PIP_CMD install --upgrade \"setuptools<81\""
|
||||
echo "2. $PIP_CMD install gdtoolkit==4"
|
||||
echo "3. Restart your terminal"
|
||||
echo "4. Run this script again"
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v gdformat &> /dev/null; then
|
||||
echo "❌ ERROR: gdformat is not installed or not in PATH"
|
||||
echo
|
||||
echo "Installation instructions:"
|
||||
echo "1. $PIP_CMD install --upgrade \"setuptools<81\""
|
||||
echo "2. $PIP_CMD install gdtoolkit==4"
|
||||
echo "3. Restart your terminal"
|
||||
echo "4. Run this script again"
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "✅ All dependencies are available. Running development workflow..."
|
||||
echo
|
||||
|
||||
# Build Python command based on arguments
|
||||
PYTHON_FULL_CMD="$PYTHON_CMD tools/run_development.py"
|
||||
|
||||
if [[ -n "$ARG_LINT_ONLY" ]]; then
|
||||
PYTHON_FULL_CMD="$PYTHON_FULL_CMD --lint"
|
||||
echo "🔍 Running linting only..."
|
||||
elif [[ -n "$ARG_FORMAT_ONLY" ]]; then
|
||||
PYTHON_FULL_CMD="$PYTHON_FULL_CMD --format"
|
||||
echo "🎨 Running formatting only..."
|
||||
elif [[ -n "$ARG_TEST_ONLY" ]]; then
|
||||
PYTHON_FULL_CMD="$PYTHON_FULL_CMD --test"
|
||||
echo "🧪 Running tests only..."
|
||||
elif [[ -n "$ARG_STEPS" ]]; then
|
||||
PYTHON_FULL_CMD="$PYTHON_FULL_CMD --steps $CUSTOM_STEPS"
|
||||
echo "🔄 Running custom workflow: $CUSTOM_STEPS..."
|
||||
else
|
||||
echo "🚀 Running complete development workflow: format + lint + test..."
|
||||
fi
|
||||
|
||||
echo
|
||||
|
||||
# Run the Python development workflow script
|
||||
$PYTHON_FULL_CMD
|
||||
|
||||
# Capture exit code and display result
|
||||
WORKFLOW_RESULT=$?
|
||||
|
||||
echo
|
||||
if [[ $WORKFLOW_RESULT -eq 0 ]]; then
|
||||
echo "🎉 Development workflow completed successfully!"
|
||||
else
|
||||
echo "⚠️ Development workflow completed with issues."
|
||||
echo "Please review the output above and fix any problems."
|
||||
fi
|
||||
|
||||
echo
|
||||
exit $WORKFLOW_RESULT
|
||||
152
scenes/game/Game.gd
Normal file
@@ -0,0 +1,152 @@
|
||||
extends Control
|
||||
|
||||
const GAMEPLAY_SCENES = {
|
||||
"match3": "res://scenes/game/gameplays/Match3Gameplay.tscn",
|
||||
"clickomania": "res://scenes/game/gameplays/ClickomaniaGameplay.tscn"
|
||||
}
|
||||
|
||||
var current_gameplay_mode: String
|
||||
var global_score: int = 0:
|
||||
set = set_global_score
|
||||
|
||||
@onready var back_button: Button = $BackButtonContainer/BackButton
|
||||
@onready var gameplay_container: Control = $GameplayContainer
|
||||
@onready var score_display: Label = $UI/ScoreDisplay
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not back_button.pressed.is_connected(_on_back_button_pressed):
|
||||
back_button.pressed.connect(_on_back_button_pressed)
|
||||
|
||||
# GameManager will set the gameplay mode, don't set default here
|
||||
DebugManager.log_debug(
|
||||
"Game _ready() completed, waiting for GameManager to set gameplay mode",
|
||||
"Game"
|
||||
)
|
||||
|
||||
|
||||
func set_gameplay_mode(mode: String) -> void:
|
||||
DebugManager.log_info(
|
||||
"set_gameplay_mode called with mode: %s" % mode, "Game"
|
||||
)
|
||||
current_gameplay_mode = mode
|
||||
await load_gameplay(mode)
|
||||
DebugManager.log_info(
|
||||
"set_gameplay_mode completed for mode: %s" % mode, "Game"
|
||||
)
|
||||
|
||||
|
||||
func load_gameplay(mode: String) -> void:
|
||||
DebugManager.log_debug("Loading gameplay mode: %s" % mode, "Game")
|
||||
|
||||
# Clear existing gameplay and wait for removal
|
||||
var existing_children = gameplay_container.get_children()
|
||||
if existing_children.size() > 0:
|
||||
DebugManager.log_debug(
|
||||
"Removing %d existing children" % existing_children.size(), "Game"
|
||||
)
|
||||
for child in existing_children:
|
||||
DebugManager.log_debug(
|
||||
"Removing existing child: %s" % child.name, "Game"
|
||||
)
|
||||
child.queue_free()
|
||||
|
||||
# Wait for children to be properly removed from scene tree
|
||||
await get_tree().process_frame
|
||||
DebugManager.log_debug(
|
||||
(
|
||||
"Children removal complete, container count: %d"
|
||||
% gameplay_container.get_child_count()
|
||||
),
|
||||
"Game"
|
||||
)
|
||||
|
||||
# Load new gameplay
|
||||
if GAMEPLAY_SCENES.has(mode):
|
||||
DebugManager.log_debug(
|
||||
"Found scene path: %s" % GAMEPLAY_SCENES[mode], "Game"
|
||||
)
|
||||
var gameplay_scene = load(GAMEPLAY_SCENES[mode])
|
||||
var gameplay_instance = gameplay_scene.instantiate()
|
||||
DebugManager.log_debug(
|
||||
"Instantiated gameplay: %s" % gameplay_instance.name, "Game"
|
||||
)
|
||||
gameplay_container.add_child(gameplay_instance)
|
||||
DebugManager.log_debug(
|
||||
(
|
||||
"Added gameplay to container, child count now: %d"
|
||||
% gameplay_container.get_child_count()
|
||||
),
|
||||
"Game"
|
||||
)
|
||||
|
||||
# Connect gameplay signals to shared systems
|
||||
if gameplay_instance.has_signal("score_changed"):
|
||||
gameplay_instance.score_changed.connect(_on_score_changed)
|
||||
DebugManager.log_debug("Connected score_changed signal", "Game")
|
||||
else:
|
||||
DebugManager.log_error(
|
||||
"Gameplay mode '%s' not found in GAMEPLAY_SCENES" % mode, "Game"
|
||||
)
|
||||
|
||||
|
||||
func set_global_score(value: int) -> void:
|
||||
global_score = value
|
||||
if score_display:
|
||||
score_display.text = "Score: " + str(global_score)
|
||||
|
||||
|
||||
func _on_score_changed(points: int) -> void:
|
||||
self.global_score += points
|
||||
SaveManager.update_current_score(self.global_score)
|
||||
|
||||
|
||||
func get_global_score() -> int:
|
||||
return global_score
|
||||
|
||||
|
||||
func _get_current_gameplay_instance() -> Node:
|
||||
if gameplay_container.get_child_count() > 0:
|
||||
return gameplay_container.get_child(0)
|
||||
return null
|
||||
|
||||
|
||||
func _on_back_button_pressed() -> void:
|
||||
DebugManager.log_debug("Back button pressed in game scene", "Game")
|
||||
AudioManager.play_ui_click()
|
||||
|
||||
# Save current grid state if we have an active match3 gameplay
|
||||
var gameplay_instance = _get_current_gameplay_instance()
|
||||
if gameplay_instance and gameplay_instance.has_method("save_current_state"):
|
||||
DebugManager.log_info("Saving grid state before exit", "Game")
|
||||
# Make sure the gameplay instance is still valid and not being destroyed
|
||||
if (
|
||||
is_instance_valid(gameplay_instance)
|
||||
and gameplay_instance.is_inside_tree()
|
||||
):
|
||||
gameplay_instance.save_current_state()
|
||||
else:
|
||||
DebugManager.log_warn(
|
||||
"Gameplay instance invalid, skipping grid save on exit", "Game"
|
||||
)
|
||||
|
||||
# Save the current score immediately before exiting
|
||||
SaveManager.finish_game(global_score)
|
||||
GameManager.exit_to_main_menu()
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("ui_back"):
|
||||
# Handle gamepad/keyboard back action - same as back button
|
||||
_on_back_button_pressed()
|
||||
elif (
|
||||
event.is_action_pressed("action_south")
|
||||
and Input.is_action_pressed("action_north")
|
||||
):
|
||||
# Debug: Switch to clickomania when primary+secondary actions pressed together
|
||||
if current_gameplay_mode == "match3":
|
||||
set_gameplay_mode("clickomania")
|
||||
DebugManager.log_debug("Switched to clickomania gameplay", "Game")
|
||||
else:
|
||||
set_gameplay_mode("match3")
|
||||
DebugManager.log_debug("Switched to match3 gameplay", "Game")
|
||||
77
scenes/game/Game.tscn
Normal file
@@ -0,0 +1,77 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://dmctbuggudsx2"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bs4veuda3h358" path="res://scenes/game/game.gd" id="1_uwrxv"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/ui/DebugToggle.tscn" id="3_debug"]
|
||||
[ext_resource type="Texture2D" uid="uid://bengv32u1jeym" path="res://assets/textures/backgrounds/BGx3.png" id="GlobalBackground"]
|
||||
|
||||
[node name="Game" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_uwrxv")
|
||||
|
||||
[node name="Background" type="TextureRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = ExtResource("GlobalBackground")
|
||||
expand_mode = 1
|
||||
stretch_mode = 1
|
||||
|
||||
[node name="UI" type="Control" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="ScoreDisplay" type="Label" parent="UI"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 2
|
||||
anchor_top = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 10.0
|
||||
offset_top = -31.0
|
||||
offset_right = 110.0
|
||||
offset_bottom = -10.0
|
||||
grow_vertical = 0
|
||||
text = "Score: 0"
|
||||
|
||||
[node name="GameplayContainer" type="Control" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="BackButtonContainer" type="Control" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 1
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
offset_left = 10.0
|
||||
offset_top = 10.0
|
||||
offset_right = 55.0
|
||||
offset_bottom = 41.0
|
||||
|
||||
[node name="BackButton" type="Button" parent="BackButtonContainer"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
text = "back"
|
||||
|
||||
[node name="DebugToggle" parent="." instance=ExtResource("3_debug")]
|
||||
layout_mode = 1
|
||||
|
||||
[connection signal="pressed" from="BackButtonContainer/BackButton" to="." method="_on_back_button_pressed"]
|
||||
1
scenes/game/game.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bs4veuda3h358
|
||||
11
scenes/game/gameplays/ClickomaniaGameplay.gd
Normal file
@@ -0,0 +1,11 @@
|
||||
extends Node2D
|
||||
|
||||
signal score_changed(points: int)
|
||||
|
||||
|
||||
func _ready():
|
||||
DebugManager.log_info("Clickomania gameplay loaded", "Clickomania")
|
||||
# Example: Add some score after a few seconds to test the system
|
||||
await get_tree().create_timer(2.0).timeout
|
||||
score_changed.emit(100)
|
||||
DebugManager.log_info("Clickomania awarded 100 points", "Clickomania")
|
||||
1
scenes/game/gameplays/ClickomaniaGameplay.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bkheckv0upd82
|
||||
21
scenes/game/gameplays/ClickomaniaGameplay.tscn
Normal file
@@ -0,0 +1,21 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://cl7g8v0eh3mam"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/game/gameplays/ClickomaniaGameplay.gd" id="1_script"]
|
||||
|
||||
[node name="Clickomania" type="Node2D"]
|
||||
script = ExtResource("1_script")
|
||||
|
||||
[node name="Label" type="Label" parent="."]
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -100.0
|
||||
offset_top = -12.0
|
||||
offset_right = 100.0
|
||||
offset_bottom = 12.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
text = "Clickomania Gameplay (Demo)"
|
||||
horizontal_alignment = 1
|
||||
1
scenes/game/gameplays/GameplayInputHandler.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cgkrsgxk0stw4
|
||||
1
scenes/game/gameplays/GridGameplay.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://t8awjmas4wcg
|
||||
46
scenes/game/gameplays/Match3DebugMenu.gd
Normal file
@@ -0,0 +1,46 @@
|
||||
extends DebugMenuBase
|
||||
|
||||
|
||||
func _ready():
|
||||
# Set specific configuration for Match3DebugMenu
|
||||
log_category = "Match3"
|
||||
target_script_path = "res://scenes/game/gameplays/Match3Gameplay.gd"
|
||||
|
||||
# Call parent's _ready
|
||||
super()
|
||||
|
||||
DebugManager.log_debug("Match3DebugMenu _ready() completed", log_category)
|
||||
|
||||
# Initialize with current debug state if enabled
|
||||
var current_debug_state = DebugManager.is_debug_enabled()
|
||||
if current_debug_state:
|
||||
_on_debug_toggled(true)
|
||||
|
||||
|
||||
func _find_target_scene():
|
||||
# Debug menu is now: Match3 -> UILayer -> Match3DebugMenu
|
||||
# So we need to go up two levels: get_parent() = UILayer, get_parent().get_parent() = Match3
|
||||
var ui_layer = get_parent()
|
||||
if ui_layer and ui_layer is CanvasLayer:
|
||||
var potential_match3 = ui_layer.get_parent()
|
||||
if potential_match3 and potential_match3.get_script():
|
||||
var script_path = potential_match3.get_script().resource_path
|
||||
if script_path == target_script_path:
|
||||
match3_scene = potential_match3
|
||||
DebugManager.log_debug(
|
||||
(
|
||||
"Found match3 scene: "
|
||||
+ match3_scene.name
|
||||
+ " at path: "
|
||||
+ str(match3_scene.get_path())
|
||||
),
|
||||
log_category
|
||||
)
|
||||
_update_ui_from_scene()
|
||||
_stop_search_timer()
|
||||
return
|
||||
|
||||
# If we couldn't find it, clear the reference and continue searching
|
||||
match3_scene = null
|
||||
DebugManager.log_error("Could not find match3_gameplay scene", log_category)
|
||||
_start_search_timer()
|
||||
1
scenes/game/gameplays/Match3DebugMenu.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ccfms5oiub35j
|
||||
83
scenes/game/gameplays/Match3DebugMenu.tscn
Normal file
@@ -0,0 +1,83 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://b76oiwlifikl3"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/game/gameplays/Match3DebugMenu.gd" id="1_debug_menu"]
|
||||
|
||||
[node name="DebugMenu" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 1
|
||||
anchor_left = 1.0
|
||||
anchor_right = 1.0
|
||||
offset_left = -201.0
|
||||
offset_top = 59.0
|
||||
offset_right = -11.0
|
||||
offset_bottom = 169.0
|
||||
grow_horizontal = 0
|
||||
script = ExtResource("1_debug_menu")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 10.0
|
||||
offset_top = 10.0
|
||||
offset_right = -10.0
|
||||
offset_bottom = -10.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Match-3 Debug Menu"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="RegenerateButton" type="Button" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Generate New Grid"
|
||||
|
||||
[node name="GemTypesContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GemTypesLabel" type="Label" parent="VBoxContainer/GemTypesContainer"]
|
||||
layout_mode = 2
|
||||
text = "Gem Types: 5"
|
||||
|
||||
[node name="GemTypesSpinBox" type="SpinBox" parent="VBoxContainer/GemTypesContainer"]
|
||||
layout_mode = 2
|
||||
min_value = 3.0
|
||||
max_value = 8.0
|
||||
value = 5.0
|
||||
|
||||
[node name="GridSizeContainer" type="VBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GridSizeLabel" type="Label" parent="VBoxContainer/GridSizeContainer"]
|
||||
layout_mode = 2
|
||||
text = "Grid Size"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="GridWidthContainer" type="HBoxContainer" parent="VBoxContainer/GridSizeContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GridWidthLabel" type="Label" parent="VBoxContainer/GridSizeContainer/GridWidthContainer"]
|
||||
layout_mode = 2
|
||||
text = "Width: 8"
|
||||
|
||||
[node name="GridWidthSpinBox" type="SpinBox" parent="VBoxContainer/GridSizeContainer/GridWidthContainer"]
|
||||
layout_mode = 2
|
||||
min_value = 4.0
|
||||
max_value = 12.0
|
||||
value = 8.0
|
||||
|
||||
[node name="GridHeightContainer" type="HBoxContainer" parent="VBoxContainer/GridSizeContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GridHeightLabel" type="Label" parent="VBoxContainer/GridSizeContainer/GridHeightContainer"]
|
||||
layout_mode = 2
|
||||
text = "Height: 8"
|
||||
|
||||
[node name="GridHeightSpinBox" type="SpinBox" parent="VBoxContainer/GridSizeContainer/GridHeightContainer"]
|
||||
layout_mode = 2
|
||||
min_value = 4.0
|
||||
max_value = 12.0
|
||||
value = 8.0
|
||||
1399
scenes/game/gameplays/Match3Gameplay.gd
Normal file
1
scenes/game/gameplays/Match3Gameplay.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dbbi8ooysxp7f
|
||||
13
scenes/game/gameplays/Match3Gameplay.tscn
Normal file
@@ -0,0 +1,13 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://b4kv7g7kllwgb"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://o8crf6688lan" path="res://scenes/game/gameplays/Match3Gameplay.gd" id="1_mvfdp"]
|
||||
[ext_resource type="PackedScene" uid="uid://b76oiwlifikl3" path="res://scenes/game/gameplays/Match3DebugMenu.tscn" id="2_debug_menu"]
|
||||
|
||||
[node name="Match3" type="Node2D"]
|
||||
script = ExtResource("1_mvfdp")
|
||||
|
||||
[node name="GridContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="UILayer" type="CanvasLayer" parent="."]
|
||||
|
||||
[node name="Match3DebugMenu" parent="UILayer" instance=ExtResource("2_debug_menu")]
|
||||
87
scenes/game/gameplays/Match3InputHandler.gd
Normal file
@@ -0,0 +1,87 @@
|
||||
class_name Match3InputHandler
|
||||
extends RefCounted
|
||||
|
||||
## Mouse input handler for Match3 gameplay
|
||||
##
|
||||
## Static methods for handling mouse interactions in Match3 games.
|
||||
## Converts between world coordinates and grid positions, performs hit detection on tiles.
|
||||
##
|
||||
## Usage:
|
||||
## var tile = Match3InputHandler.find_tile_at_position(grid, grid_size, mouse_pos)
|
||||
## var grid_pos = Match3InputHandler.get_grid_position_from_world(node, world_pos, offset, size)
|
||||
|
||||
|
||||
static func find_tile_at_position(
|
||||
grid: Array, grid_size: Vector2i, world_pos: Vector2
|
||||
) -> Node2D:
|
||||
## Find the tile that contains the world position.
|
||||
##
|
||||
## Iterates through all tiles and checks if the world position falls within
|
||||
## any tile's sprite boundaries.
|
||||
##
|
||||
## Args:
|
||||
## grid: 2D array of tile nodes arranged in [y][x] format
|
||||
## grid_size: Dimensions of the grid (width x height)
|
||||
## world_pos: World coordinates to test
|
||||
##
|
||||
## Returns:
|
||||
## The first tile node that contains the position, or null if no tile found
|
||||
for y in range(grid_size.y):
|
||||
for x in range(grid_size.x):
|
||||
if y < grid.size() and x < grid[y].size():
|
||||
var tile = grid[y][x]
|
||||
if tile and tile.has_node("Sprite2D"):
|
||||
var sprite = tile.get_node("Sprite2D")
|
||||
if sprite and sprite.texture:
|
||||
var sprite_bounds = get_sprite_world_bounds(
|
||||
tile, sprite
|
||||
)
|
||||
if is_point_inside_rect(world_pos, sprite_bounds):
|
||||
return tile
|
||||
return null
|
||||
|
||||
|
||||
static func get_sprite_world_bounds(tile: Node2D, sprite: Sprite2D) -> Rect2:
|
||||
## Calculate the world space bounding rectangle of a sprite.
|
||||
##
|
||||
## Args:
|
||||
## tile: The tile node containing the sprite
|
||||
## sprite: The Sprite2D node to calculate bounds for
|
||||
##
|
||||
## Returns:
|
||||
## Rect2 representing the sprite's bounds in world coordinates
|
||||
var texture_size = sprite.texture.get_size()
|
||||
var actual_size = texture_size * sprite.scale
|
||||
var half_size = actual_size * 0.5
|
||||
var top_left = tile.position - half_size
|
||||
return Rect2(top_left, actual_size)
|
||||
|
||||
|
||||
static func is_point_inside_rect(point: Vector2, rect: Rect2) -> bool:
|
||||
# Check if a point is inside a rectangle
|
||||
return (
|
||||
point.x >= rect.position.x
|
||||
and point.x <= rect.position.x + rect.size.x
|
||||
and point.y >= rect.position.y
|
||||
and point.y <= rect.position.y + rect.size.y
|
||||
)
|
||||
|
||||
|
||||
static func get_grid_position_from_world(
|
||||
node: Node2D, world_pos: Vector2, grid_offset: Vector2, tile_size: float
|
||||
) -> Vector2i:
|
||||
## Convert world coordinates to grid array indices.
|
||||
##
|
||||
## Args:
|
||||
## node: Reference node for coordinate space conversion
|
||||
## world_pos: Position in world coordinates to convert
|
||||
## grid_offset: Offset of the grid's origin from the node's position
|
||||
## tile_size: Size of each tile in world units
|
||||
##
|
||||
## Returns:
|
||||
## Vector2i containing the grid coordinates (x, y) for array indexing
|
||||
var local_pos = node.to_local(world_pos)
|
||||
var relative_pos = local_pos - grid_offset
|
||||
var grid_x = int(relative_pos.x / tile_size)
|
||||
var grid_y = int(relative_pos.y / tile_size)
|
||||
return Vector2i(grid_x, grid_y)
|
||||
1
scenes/game/gameplays/Match3InputHandler.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ogm8w7l6bhif
|
||||
151
scenes/game/gameplays/Match3SaveManager.gd
Normal file
@@ -0,0 +1,151 @@
|
||||
class_name Match3SaveManager
|
||||
extends RefCounted
|
||||
|
||||
## Save/Load manager for Match3 gameplay state
|
||||
##
|
||||
## Handles serialization and deserialization of Match3 game state.
|
||||
## Converts game objects to data structures for storage and restoration.
|
||||
##
|
||||
## Usage:
|
||||
## # Save current state
|
||||
## var grid_data = Match3SaveManager.serialize_grid_state(game_grid, grid_size)
|
||||
##
|
||||
## # Restore previous state
|
||||
## var success = Match3SaveManager.deserialize_grid_state(grid_data, game_grid, grid_size)
|
||||
|
||||
|
||||
static func serialize_grid_state(grid: Array, grid_size: Vector2i) -> Array:
|
||||
## Convert the current game grid to a serializable 2D array of tile types.
|
||||
##
|
||||
## Extracts the tile_type property from each tile node and creates a 2D array
|
||||
## that can be saved to disk. Invalid or missing tiles are represented as -1.
|
||||
##
|
||||
## Args:
|
||||
## grid: The current game grid (2D array of tile nodes)
|
||||
## grid_size: Dimensions of the grid to serialize
|
||||
##
|
||||
## Returns:
|
||||
## Array: 2D array where each element is either a tile type (int) or -1 for empty
|
||||
var serialized_grid = []
|
||||
var valid_tiles = 0
|
||||
var null_tiles = 0
|
||||
|
||||
for y in range(grid_size.y):
|
||||
var row = []
|
||||
for x in range(grid_size.x):
|
||||
if y < grid.size() and x < grid[y].size() and grid[y][x]:
|
||||
row.append(grid[y][x].tile_type)
|
||||
valid_tiles += 1
|
||||
else:
|
||||
row.append(-1) # Invalid/empty tile
|
||||
null_tiles += 1
|
||||
serialized_grid.append(row)
|
||||
|
||||
DebugManager.log_info(
|
||||
(
|
||||
"Serialized grid state: %dx%d grid, %d valid tiles, %d null tiles"
|
||||
% [grid_size.x, grid_size.y, valid_tiles, null_tiles]
|
||||
),
|
||||
"Match3"
|
||||
)
|
||||
return serialized_grid
|
||||
|
||||
|
||||
static func get_active_gem_types_from_grid(
|
||||
grid: Array, tile_types: int
|
||||
) -> Array:
|
||||
# Get active gem types from the first available tile
|
||||
if grid.size() > 0 and grid[0].size() > 0 and grid[0][0]:
|
||||
return grid[0][0].active_gem_types.duplicate()
|
||||
|
||||
# Fallback to default
|
||||
var default_types = []
|
||||
for i in range(tile_types):
|
||||
default_types.append(i)
|
||||
return default_types
|
||||
|
||||
|
||||
static func save_game_state(grid: Array, grid_size: Vector2i, tile_types: int):
|
||||
# Save complete game state
|
||||
var grid_layout = serialize_grid_state(grid, grid_size)
|
||||
var active_gems = get_active_gem_types_from_grid(grid, tile_types)
|
||||
|
||||
DebugManager.log_info(
|
||||
(
|
||||
"Saving match3 state: size(%d,%d), %d tile types, %d active gems"
|
||||
% [grid_size.x, grid_size.y, tile_types, active_gems.size()]
|
||||
),
|
||||
"Match3"
|
||||
)
|
||||
|
||||
SaveManager.save_grid_state(grid_size, tile_types, active_gems, grid_layout)
|
||||
|
||||
|
||||
static func restore_grid_from_layout(
|
||||
match3_node: Node2D,
|
||||
grid_layout: Array,
|
||||
active_gems: Array[int],
|
||||
grid_size: Vector2i,
|
||||
tile_scene: PackedScene,
|
||||
grid_offset: Vector2,
|
||||
tile_size: float,
|
||||
tile_types: int
|
||||
) -> Array[Array]:
|
||||
# Clear ALL existing tile children
|
||||
var all_tile_children = []
|
||||
for child in match3_node.get_children():
|
||||
if child.has_method("get_script") and child.get_script():
|
||||
var script_path = child.get_script().resource_path
|
||||
if script_path == "res://scenes/game/gameplays/Tile.gd":
|
||||
all_tile_children.append(child)
|
||||
|
||||
# Remove all found tile children
|
||||
for child in all_tile_children:
|
||||
child.queue_free()
|
||||
|
||||
# Wait for nodes to be freed
|
||||
await match3_node.get_tree().process_frame
|
||||
|
||||
# Create new grid
|
||||
var new_grid: Array[Array] = []
|
||||
for y in range(grid_size.y):
|
||||
new_grid.append(Array([]))
|
||||
for x in range(grid_size.x):
|
||||
var tile = tile_scene.instantiate()
|
||||
var tile_position = grid_offset + Vector2(x, y) * tile_size
|
||||
tile.position = tile_position
|
||||
tile.grid_position = Vector2i(x, y)
|
||||
|
||||
match3_node.add_child(tile)
|
||||
|
||||
# Configure Area2D
|
||||
tile.monitoring = true
|
||||
tile.monitorable = true
|
||||
tile.input_pickable = true
|
||||
|
||||
tile.set_tile_size(tile_size)
|
||||
tile.set_active_gem_types(active_gems)
|
||||
|
||||
# Set the saved tile type
|
||||
var saved_tile_type = grid_layout[y][x]
|
||||
if saved_tile_type >= 0 and saved_tile_type < tile_types:
|
||||
tile.tile_type = saved_tile_type
|
||||
else:
|
||||
tile.tile_type = randi() % tile_types
|
||||
|
||||
# Connect tile signals
|
||||
if (
|
||||
tile.has_signal("tile_selected")
|
||||
and match3_node.has_method("_on_tile_selected")
|
||||
):
|
||||
tile.tile_selected.connect(match3_node._on_tile_selected)
|
||||
if (
|
||||
tile.has_signal("tile_hovered")
|
||||
and match3_node.has_method("_on_tile_hovered")
|
||||
):
|
||||
tile.tile_hovered.connect(match3_node._on_tile_hovered)
|
||||
tile.tile_unhovered.connect(match3_node._on_tile_unhovered)
|
||||
|
||||
new_grid[y].append(tile)
|
||||
|
||||
return new_grid
|
||||
1
scenes/game/gameplays/Match3SaveManager.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://duheejfr6de6x
|
||||
116
scenes/game/gameplays/Match3Validator.gd
Normal file
@@ -0,0 +1,116 @@
|
||||
class_name Match3Validator
|
||||
extends RefCounted
|
||||
|
||||
## Validation utilities for Match3 gameplay
|
||||
##
|
||||
## Static methods for validating Match3 game state and data integrity.
|
||||
## Prevents crashes by checking bounds, data structures, and game logic constraints.
|
||||
##
|
||||
## Usage:
|
||||
## if Match3Validator.is_valid_grid_position(pos, grid_size):
|
||||
## # Safe to access grid[pos.y][pos.x]
|
||||
##
|
||||
## if Match3Validator.validate_grid_integrity(grid, grid_size):
|
||||
## # Grid structure is valid for game operations
|
||||
|
||||
|
||||
static func is_valid_grid_position(pos: Vector2i, grid_size: Vector2i) -> bool:
|
||||
## Check if the position is within the grid boundaries.
|
||||
##
|
||||
## Performs bounds checking to prevent index out of bounds errors.
|
||||
##
|
||||
## Args:
|
||||
## pos: Grid position to validate (x, y coordinates)
|
||||
## grid_size: Dimensions of the grid (width, height)
|
||||
##
|
||||
## Returns:
|
||||
## bool: True if position is valid, False if out of bounds
|
||||
return (
|
||||
pos.x >= 0
|
||||
and pos.y >= 0
|
||||
and pos.x < grid_size.x
|
||||
and pos.y < grid_size.y
|
||||
)
|
||||
|
||||
|
||||
static func validate_grid_integrity(grid: Array, grid_size: Vector2i) -> bool:
|
||||
## Verify that the grid array structure matches expected dimensions.
|
||||
##
|
||||
## Validates the grid's 2D array structure for safe game operations.
|
||||
## Checks array types, dimensions, and structural consistency.
|
||||
##
|
||||
## Args:
|
||||
## grid: The 2D array representing the game grid
|
||||
## grid_size: Expected dimensions (width x height)
|
||||
##
|
||||
## Returns:
|
||||
## bool: True if grid structure is valid, False if corrupted or malformed
|
||||
if not grid is Array:
|
||||
DebugManager.log_error("Grid is not an array", "Match3")
|
||||
return false
|
||||
|
||||
if grid.size() != grid_size.y:
|
||||
DebugManager.log_error(
|
||||
"Grid height mismatch: %d vs %d" % [grid.size(), grid_size.y],
|
||||
"Match3"
|
||||
)
|
||||
return false
|
||||
|
||||
for y in range(grid.size()):
|
||||
if not grid[y] is Array:
|
||||
DebugManager.log_error("Grid row %d is not an array" % y, "Match3")
|
||||
return false
|
||||
|
||||
if grid[y].size() != grid_size.x:
|
||||
DebugManager.log_error(
|
||||
(
|
||||
"Grid row %d width mismatch: %d vs %d"
|
||||
% [y, grid[y].size(), grid_size.x]
|
||||
),
|
||||
"Match3"
|
||||
)
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
|
||||
static func safe_grid_access(
|
||||
grid: Array, pos: Vector2i, grid_size: Vector2i
|
||||
) -> Node2D:
|
||||
# Safe grid access with comprehensive bounds checking
|
||||
if not is_valid_grid_position(pos, grid_size):
|
||||
return null
|
||||
|
||||
if pos.y >= grid.size() or pos.x >= grid[pos.y].size():
|
||||
DebugManager.log_warn(
|
||||
"Grid bounds exceeded: (%d,%d)" % [pos.x, pos.y], "Match3"
|
||||
)
|
||||
return null
|
||||
|
||||
var tile = grid[pos.y][pos.x]
|
||||
if not tile or not is_instance_valid(tile):
|
||||
return null
|
||||
|
||||
return tile
|
||||
|
||||
|
||||
static func safe_tile_access(tile: Node2D, property: String):
|
||||
# Safe property access on tiles
|
||||
if not tile or not is_instance_valid(tile):
|
||||
return null
|
||||
|
||||
if not property in tile:
|
||||
DebugManager.log_warn("Tile missing property: %s" % property, "Match3")
|
||||
return null
|
||||
|
||||
return tile.get(property)
|
||||
|
||||
|
||||
static func are_tiles_adjacent(tile1: Node2D, tile2: Node2D) -> bool:
|
||||
if not tile1 or not tile2:
|
||||
return false
|
||||
|
||||
var pos1 = tile1.grid_position
|
||||
var pos2 = tile2.grid_position
|
||||
var diff = abs(pos1.x - pos2.x) + abs(pos1.y - pos2.y)
|
||||
return diff == 1
|
||||
1
scenes/game/gameplays/Match3Validator.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dy3aym6riijct
|
||||
1
scenes/game/gameplays/SelectableItem.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bib676n6sm05v
|
||||
308
scenes/game/gameplays/Tile.gd
Normal file
@@ -0,0 +1,308 @@
|
||||
extends Node2D
|
||||
|
||||
signal tile_selected(tile: Node2D)
|
||||
|
||||
# Target size for each tile to fit in the 54x54 grid cells
|
||||
const TILE_SIZE = 48 # Slightly smaller than 54 to leave some padding
|
||||
|
||||
@export var tile_type: int = 0:
|
||||
set = _set_tile_type
|
||||
|
||||
var grid_position: Vector2i
|
||||
var is_selected: bool = false:
|
||||
set = _set_selected
|
||||
var is_highlighted: bool = false:
|
||||
set = _set_highlighted
|
||||
var original_scale: Vector2 = Vector2.ONE # Store the original scale for the board
|
||||
|
||||
# All available gem textures
|
||||
var all_gem_textures: Array[Texture2D] = [
|
||||
preload("res://assets/sprites/skulls/red.png"),
|
||||
preload("res://assets/sprites/skulls/blue.png"),
|
||||
preload("res://assets/sprites/skulls/green.png"),
|
||||
preload("res://assets/sprites/skulls/pink.png"),
|
||||
preload("res://assets/sprites/skulls/purple.png"),
|
||||
preload("res://assets/sprites/skulls/dark-blue.png"),
|
||||
preload("res://assets/sprites/skulls/grey.png"),
|
||||
preload("res://assets/sprites/skulls/orange.png"),
|
||||
preload("res://assets/sprites/skulls/yellow.png"),
|
||||
]
|
||||
|
||||
# Currently active gem types (indices into all_gem_textures)
|
||||
var active_gem_types: Array[int] = [] # Will be set from TileManager
|
||||
|
||||
@onready var sprite: Sprite2D = $Sprite2D
|
||||
|
||||
|
||||
func _set_tile_type(value: int) -> void:
|
||||
tile_type = value
|
||||
# Fixed: Add sprite null check to prevent crashes during initialization
|
||||
if not sprite:
|
||||
return
|
||||
if value >= 0 and value < active_gem_types.size():
|
||||
var texture_index = active_gem_types[value]
|
||||
sprite.texture = all_gem_textures[texture_index]
|
||||
_scale_sprite_to_fit()
|
||||
else:
|
||||
DebugManager.log_error(
|
||||
(
|
||||
"Invalid tile type: "
|
||||
+ str(value)
|
||||
+ ". Available types: 0-"
|
||||
+ str(active_gem_types.size() - 1)
|
||||
),
|
||||
"Match3"
|
||||
)
|
||||
|
||||
|
||||
func _scale_sprite_to_fit() -> void:
|
||||
# Fixed: Add additional null checks
|
||||
if sprite and sprite.texture:
|
||||
var texture_size = sprite.texture.get_size()
|
||||
var max_dimension = max(texture_size.x, texture_size.y)
|
||||
var scale_factor = TILE_SIZE / max_dimension
|
||||
original_scale = Vector2(scale_factor, scale_factor)
|
||||
sprite.scale = original_scale
|
||||
DebugManager.log_debug(
|
||||
(
|
||||
"Set original scale to %s for tile (%d,%d)"
|
||||
% [original_scale, grid_position.x, grid_position.y]
|
||||
),
|
||||
"Match3"
|
||||
)
|
||||
|
||||
|
||||
func set_active_gem_types(gem_indices: Array[int]) -> void:
|
||||
if not gem_indices or gem_indices.is_empty():
|
||||
DebugManager.log_error("Empty gem indices array provided", "Tile")
|
||||
return
|
||||
|
||||
active_gem_types = gem_indices.duplicate()
|
||||
|
||||
# Validate all gem indices are within bounds
|
||||
for gem_index in active_gem_types:
|
||||
if gem_index < 0 or gem_index >= all_gem_textures.size():
|
||||
DebugManager.log_error(
|
||||
(
|
||||
"Invalid gem index: %d (valid range: 0-%d)"
|
||||
% [gem_index, all_gem_textures.size() - 1]
|
||||
),
|
||||
"Tile"
|
||||
)
|
||||
# Use default fallback
|
||||
active_gem_types = [0, 1, 2, 3, 4]
|
||||
break
|
||||
|
||||
# Re-validate current tile type
|
||||
if tile_type >= active_gem_types.size():
|
||||
# Generate a new random tile type within valid range
|
||||
tile_type = randi() % active_gem_types.size()
|
||||
|
||||
_set_tile_type(tile_type)
|
||||
|
||||
|
||||
func get_active_gem_count() -> int:
|
||||
return active_gem_types.size()
|
||||
|
||||
|
||||
func add_gem_type(gem_index: int) -> bool:
|
||||
if gem_index < 0 or gem_index >= all_gem_textures.size():
|
||||
DebugManager.log_error("Invalid gem index: %d" % gem_index, "Tile")
|
||||
return false
|
||||
|
||||
if not active_gem_types.has(gem_index):
|
||||
active_gem_types.append(gem_index)
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
|
||||
func remove_gem_type(gem_index: int) -> bool:
|
||||
var type_index = active_gem_types.find(gem_index)
|
||||
if type_index == -1:
|
||||
return false
|
||||
|
||||
if active_gem_types.size() <= 2: # Keep at least 2 gem types
|
||||
DebugManager.log_warn(
|
||||
"Cannot remove gem type - minimum 2 types required", "Tile"
|
||||
)
|
||||
return false
|
||||
|
||||
active_gem_types.erase(gem_index)
|
||||
|
||||
# Update tile if it was using the removed type
|
||||
if tile_type >= active_gem_types.size():
|
||||
tile_type = 0
|
||||
_set_tile_type(tile_type)
|
||||
|
||||
return true
|
||||
|
||||
|
||||
func _set_selected(value: bool) -> void:
|
||||
var old_value = is_selected
|
||||
is_selected = value
|
||||
DebugManager.log_debug(
|
||||
(
|
||||
"Tile (%d,%d) selection changed: %s -> %s"
|
||||
% [grid_position.x, grid_position.y, old_value, value]
|
||||
),
|
||||
"Match3"
|
||||
)
|
||||
_update_visual_feedback()
|
||||
|
||||
|
||||
func _set_highlighted(value: bool) -> void:
|
||||
var old_value = is_highlighted
|
||||
is_highlighted = value
|
||||
DebugManager.log_debug(
|
||||
(
|
||||
"Tile (%d,%d) highlight changed: %s -> %s"
|
||||
% [grid_position.x, grid_position.y, old_value, value]
|
||||
),
|
||||
"Match3"
|
||||
)
|
||||
_update_visual_feedback()
|
||||
|
||||
|
||||
func _update_visual_feedback() -> void:
|
||||
if not sprite:
|
||||
return
|
||||
|
||||
# Determine target values based on priority: selected > highlighted > normal
|
||||
var target_modulate: Color
|
||||
var target_scale: Vector2
|
||||
var scale_multiplier: float
|
||||
|
||||
if is_selected:
|
||||
# Selected: bright and larger than original board size
|
||||
target_modulate = Color(1.2, 1.2, 1.2, 1.0)
|
||||
scale_multiplier = UIConstants.TILE_SELECTED_SCALE
|
||||
DebugManager.log_debug(
|
||||
(
|
||||
"SELECTING tile (%d,%d): target scale %.2fx, current scale %s"
|
||||
% [
|
||||
grid_position.x,
|
||||
grid_position.y,
|
||||
scale_multiplier,
|
||||
sprite.scale
|
||||
]
|
||||
),
|
||||
"Match3"
|
||||
)
|
||||
elif is_highlighted:
|
||||
# Highlighted: subtle glow and larger than original board size
|
||||
target_modulate = Color(1.1, 1.1, 1.1, 1.0)
|
||||
scale_multiplier = UIConstants.TILE_HIGHLIGHTED_SCALE
|
||||
(
|
||||
DebugManager
|
||||
. log_debug(
|
||||
(
|
||||
"HIGHLIGHTING tile (%d,%d): target scale %.2fx, current scale %s"
|
||||
% [
|
||||
grid_position.x,
|
||||
grid_position.y,
|
||||
scale_multiplier,
|
||||
sprite.scale
|
||||
]
|
||||
),
|
||||
"Match3"
|
||||
)
|
||||
)
|
||||
else:
|
||||
# Normal state: white and original board size
|
||||
target_modulate = Color.WHITE
|
||||
scale_multiplier = UIConstants.TILE_NORMAL_SCALE
|
||||
DebugManager.log_debug(
|
||||
(
|
||||
"NORMALIZING tile (%d,%d): target scale %.2fx, current scale %s"
|
||||
% [
|
||||
grid_position.x,
|
||||
grid_position.y,
|
||||
scale_multiplier,
|
||||
sprite.scale
|
||||
]
|
||||
),
|
||||
"Match3"
|
||||
)
|
||||
|
||||
# Calculate target scale relative to original board scale
|
||||
target_scale = original_scale * scale_multiplier
|
||||
|
||||
# Apply modulate immediately
|
||||
sprite.modulate = target_modulate
|
||||
|
||||
# Only animate scale if it's actually changing
|
||||
if sprite.scale != target_scale:
|
||||
DebugManager.log_debug(
|
||||
(
|
||||
"Animating scale from %s to %s for tile (%d,%d)"
|
||||
% [sprite.scale, target_scale, grid_position.x, grid_position.y]
|
||||
),
|
||||
"Match3"
|
||||
)
|
||||
|
||||
var tween = create_tween()
|
||||
tween.tween_property(sprite, "scale", target_scale, 0.15)
|
||||
|
||||
# Add completion callback for debugging
|
||||
tween.tween_callback(_on_scale_animation_completed.bind(target_scale))
|
||||
else:
|
||||
DebugManager.log_debug(
|
||||
(
|
||||
"No scale change needed for tile (%d,%d)"
|
||||
% [grid_position.x, grid_position.y]
|
||||
),
|
||||
"Match3"
|
||||
)
|
||||
|
||||
|
||||
func _on_scale_animation_completed(expected_scale: Vector2) -> void:
|
||||
DebugManager.log_debug(
|
||||
(
|
||||
"Scale animation completed for tile (%d,%d): expected %s, actual %s"
|
||||
% [grid_position.x, grid_position.y, expected_scale, sprite.scale]
|
||||
),
|
||||
"Match3"
|
||||
)
|
||||
|
||||
|
||||
func force_reset_visual_state() -> void:
|
||||
# Force reset all visual states - debug function
|
||||
is_selected = false
|
||||
is_highlighted = false
|
||||
if sprite:
|
||||
sprite.modulate = Color.WHITE
|
||||
sprite.scale = original_scale # Reset to original board scale, not 1.0
|
||||
DebugManager.log_debug(
|
||||
(
|
||||
"Forced visual reset on tile (%d,%d) to original scale %s"
|
||||
% [grid_position.x, grid_position.y, original_scale]
|
||||
),
|
||||
"Match3"
|
||||
)
|
||||
|
||||
|
||||
# Handle input for tile selection
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
|
||||
# Check if the mouse click is within the tile's bounds
|
||||
var local_position = to_local(get_global_mouse_position())
|
||||
var sprite_rect = Rect2(
|
||||
-TILE_SIZE / 2.0, -TILE_SIZE / 2.0, TILE_SIZE, TILE_SIZE
|
||||
)
|
||||
|
||||
if sprite_rect.has_point(local_position):
|
||||
tile_selected.emit(self)
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
add_to_group("tiles") # Add to group for gem pool management
|
||||
|
||||
# Initialize with default gem pool if not already set
|
||||
if active_gem_types.is_empty():
|
||||
active_gem_types = [0, 1, 2, 3, 4] # Default to first 5 gems
|
||||
|
||||
_set_tile_type(tile_type)
|
||||
8
scenes/game/gameplays/Tile.tscn
Normal file
@@ -0,0 +1,8 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bnk1gqom3oi6q"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/game/gameplays/Tile.gd" id="1_tile_script"]
|
||||
|
||||
[node name="Tile" type="Node2D"]
|
||||
script = ExtResource("1_tile_script")
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
1
scenes/game/gameplays/match3_gameplay.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://o8crf6688lan
|
||||
1
scenes/game/gameplays/match3_input_handler.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bgygx6iofwqwc
|
||||
1
scenes/game/gameplays/match3_save_manager.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://balbki1cnwdn1
|
||||
1
scenes/game/gameplays/match3_validator.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cjav8g5js6umr
|
||||
1
scenes/game/gameplays/tile.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ctdlvwin7q2cc
|
||||
@@ -1,16 +0,0 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://ci2gk11211n0d"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cwlop1ettlqhg" path="res://scripts/Main.gd" id="1_0wfyh"]
|
||||
[ext_resource type="PackedScene" uid="uid://gbe1jarrwqsi" path="res://ui/PressAnyKeyScreen.tscn" id="1_o5qli"]
|
||||
|
||||
[node name="main" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_0wfyh")
|
||||
|
||||
[node name="PressAnyKeyScreen" parent="." instance=ExtResource("1_o5qli")]
|
||||
layout_mode = 1
|
||||
102
scenes/main/Main.gd
Normal file
@@ -0,0 +1,102 @@
|
||||
extends Control
|
||||
|
||||
const MAIN_MENU_SCENE = preload("res://scenes/ui/MainMenu.tscn")
|
||||
const SETTINGS_MENU_SCENE = preload("res://scenes/ui/SettingsMenu.tscn")
|
||||
|
||||
var current_menu: Control = null
|
||||
|
||||
@onready var splash_screen: Node = $SplashScreen
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
DebugManager.log_debug("Main scene ready", "Main")
|
||||
# Use alternative connection method with input handling
|
||||
_setup_splash_screen_connection()
|
||||
|
||||
|
||||
func _setup_splash_screen_connection() -> void:
|
||||
# Wait for all nodes to be ready
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
|
||||
# Try to find SplashScreen node
|
||||
splash_screen = get_node_or_null("SplashScreen")
|
||||
if not splash_screen:
|
||||
DebugManager.log_warn(
|
||||
"SplashScreen node not found, trying alternative methods", "Main"
|
||||
)
|
||||
# Try to find by class or group
|
||||
var splash_nodes = get_tree().get_nodes_in_group("localizable")
|
||||
for node in splash_nodes:
|
||||
if node.scene_file_path.ends_with("SplashScreen.tscn"):
|
||||
splash_screen = node
|
||||
break
|
||||
|
||||
if splash_screen:
|
||||
DebugManager.log_debug(
|
||||
"SplashScreen node found: %s" % splash_screen.name, "Main"
|
||||
)
|
||||
# Try connecting to the signal if it exists
|
||||
if splash_screen.has_signal("confirm_pressed"):
|
||||
splash_screen.confirm_pressed.connect(_on_confirm_pressed)
|
||||
DebugManager.log_debug(
|
||||
"Connected to confirm_pressed signal", "Main"
|
||||
)
|
||||
else:
|
||||
# Fallback: use input handling directly on the main scene
|
||||
DebugManager.log_warn("Using fallback input handling", "Main")
|
||||
_use_fallback_input_handling()
|
||||
else:
|
||||
DebugManager.log_error("Could not find SplashScreen node", "Main")
|
||||
_use_fallback_input_handling()
|
||||
|
||||
|
||||
func _use_fallback_input_handling() -> void:
|
||||
# Fallback: handle input directly in the main scene
|
||||
set_process_unhandled_input(true)
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if splash_screen and splash_screen.is_inside_tree():
|
||||
# Forward input to splash screen or handle directly
|
||||
if event.is_action_pressed("action_confirm"):
|
||||
_on_confirm_pressed()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _on_confirm_pressed() -> void:
|
||||
DebugManager.log_debug("Transitioning to main menu", "Main")
|
||||
splash_screen.queue_free()
|
||||
show_main_menu()
|
||||
|
||||
|
||||
func show_main_menu() -> void:
|
||||
clear_current_menu()
|
||||
var main_menu = MAIN_MENU_SCENE.instantiate()
|
||||
main_menu.open_settings.connect(_on_open_settings)
|
||||
add_child(main_menu)
|
||||
current_menu = main_menu
|
||||
|
||||
|
||||
func show_settings_menu() -> void:
|
||||
clear_current_menu()
|
||||
var settings_menu = SETTINGS_MENU_SCENE.instantiate()
|
||||
settings_menu.back_to_main_menu.connect(_on_back_to_main_menu)
|
||||
add_child(settings_menu)
|
||||
current_menu = settings_menu
|
||||
|
||||
|
||||
func clear_current_menu() -> void:
|
||||
if current_menu:
|
||||
current_menu.queue_free()
|
||||
current_menu = null
|
||||
|
||||
|
||||
func _on_open_settings() -> void:
|
||||
DebugManager.log_debug("Opening settings menu", "Main")
|
||||
show_settings_menu()
|
||||
|
||||
|
||||
func _on_back_to_main_menu() -> void:
|
||||
DebugManager.log_debug("Back to main menu", "Main")
|
||||
show_main_menu()
|
||||
1
scenes/main/Main.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://rvuchiy0guv3
|
||||
31
scenes/main/Main.tscn
Normal file
@@ -0,0 +1,31 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://podhr4b5aq2v"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://rvuchiy0guv3" path="res://scenes/main/Main.gd" id="1_0wfyh"]
|
||||
[ext_resource type="PackedScene" uid="uid://gbe1jarrwqsi" path="res://scenes/main/SplashScreen.tscn" id="1_o5qli"]
|
||||
[ext_resource type="PackedScene" uid="uid://df2b4wn8j6cxl" path="res://scenes/ui/DebugToggle.tscn" id="4_v7g8d"]
|
||||
[ext_resource type="Texture2D" uid="uid://bengv32u1jeym" path="res://assets/textures/backgrounds/BGx3.png" id="GlobalBackground"]
|
||||
|
||||
[node name="main" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_0wfyh")
|
||||
|
||||
[node name="Background" type="TextureRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = ExtResource("GlobalBackground")
|
||||
stretch_mode = 1
|
||||
|
||||
[node name="SplashScreen" parent="." instance=ExtResource("1_o5qli")]
|
||||
layout_mode = 1
|
||||
|
||||
[node name="DebugToggle" parent="." instance=ExtResource("4_v7g8d")]
|
||||
layout_mode = 1
|
||||
27
scenes/main/SplashScreen.gd
Normal file
@@ -0,0 +1,27 @@
|
||||
extends Control
|
||||
|
||||
signal confirm_pressed
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
DebugManager.log_debug("SplashScreen ready", "SplashScreen")
|
||||
update_text()
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if (
|
||||
event.is_action_pressed("action_confirm")
|
||||
or event is InputEventScreenTouch
|
||||
or (
|
||||
event is InputEventMouseButton
|
||||
and event.button_index == MOUSE_BUTTON_LEFT
|
||||
and event.pressed
|
||||
)
|
||||
):
|
||||
DebugManager.log_debug("Action pressed: " + str(event), "SplashScreen")
|
||||
confirm_pressed.emit()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func update_text() -> void:
|
||||
$SplashContainer/ContinueLabel.text = tr("press_ok_continue")
|
||||