modified: content/2025-07-13-building-and-modding-multimower-with-dry-engine.rst modified: content/pages/cover-letters/2025-10-28-chime-russell-ballestrini.rst modified: content/pages/cover-letters/2025-10-28-gitlab-russell-ballestrini.rst modified: content/pages/cover-letters/2025-10-28-magicschool-russell-ballestrini.rst modified: content/pages/cover-letters/2025-11-03-canonical-russell-ballestrini.rst modified: content/pages/cover-letters/2025-11-03-datadog-curriculum-developer-russell-ballestrini.rst modified: content/pages/cover-letters/2025-11-03-lumin-digital-sre-russell-ballestrini.rst modified: content/pages/cover-letters/2025-11-03-synthropic-detection-engineer-russell-ballestrini.rst modified: content/pages/cover-letters/2025-11-03-truetax-elixir-phoenix-russell-ballestrini.rst modified: content/pages/cover-letters/2025-11-04-automattic-senior-systems-engineer-russell-ballestrini.rst modified: content/pages/cover-letters/2025-11-04-band-full-stack-engineer-russell-ballestrini.rst modified: content/pages/cover-letters/2025-11-06-river-senior-software-engineer-russell-ballestrini.rst modified: content/pages/cover-letters/2025-11-13-glass-health-founding-software-engineer-backend-russell-ballestrini.rst modified: content/pages/cover-letters/2025-11-13-schoolhouse-world-senior-software-engineer-russell-ballestrini.rst modified: content/pages/cover-letters/2025-11-15-zapier-senior-software-engineer-enterprise-zone-russell-ballestrini.rst modified: content/pages/cover-letters/2025-11-29-runpod-principal-software-engineer-storage-systems-russell-ballestrini.rst modified: content/pages/cover-letters/2025-11-30-coinbase-ai-growth-lead-staff-software-engineer-russell-ballestrini.rst modified: content/pages/cover-letters/2025-11-30-virta-health-senior-site-reliability-engineer-russell-ballestrini.rst modified: content/pages/cover-letters/2025-11-30-webflow-staff-software-engineer-applied-ai-russell-ballestrini.rst modified: content/pages/cover-letters/2025-12-13-energyhub-senior-software-engineer-russell-ballestrini.rst modified: content/pages/cover-letters/2026-01-05-hatchet-founding-engineer-russell-ballestrini.rst modified: content/pages/cover-letters/2026-01-05-radar-site-reliability-engineer-russell-ballestrini.rst modified: content/pages/cover-letters/2026-01-05-redpanda-software-engineer-russell-ballestrini.rst modified: content/pages/cover-letters/2026-01-05-temporal-technologies-senior-software-engineer-russell-ballestrini.rst modified: content/pages/cover-letters/2026-01-05-trunk-tech-lead-russell-ballestrini.rst modified: content/pages/cover-letters/2026-01-10-kobold-metals-senior-infrastructure-engineer-russell-ballestrini.rst modified: content/pages/cover-letters/2026-01-17-careforce-ai-senior-fullstack-engineer-russell-ballestrini.rst modified: content/pages/cover-letters/2026-01-17-lumos-devops-engineer-russell-ballestrini.rst modified: content/pages/cover-letters/2026-01-17-octane-staff-platform-engineer-russell-ballestrini.rst modified: content/pages/cover-letters/2026-01-17-prompt-health-senior-devops-engineer-russell-ballestrini.rst modified: content/pages/cover-letters/2026-01-25-phantom-senior-backend-engineer-money-movement-russell-ballestrini.rst modified: content/pages/cover-letters/2026-01-25-solflare-backend-developer-russell-ballestrini.rst new file: content/pages/cover-letters/2026-01-27-bluebird-kids-health-staff-software-engineer-russell-ballestrini.rst new file: content/pages/cover-letters/2026-01-27-hoodie-analytics-ai-automation-integration-engineer-russell-ballestrini.rst new file: content/pages/cover-letters/2026-01-27-hypothesisbase-python-software-engineer-russell-ballestrini.rst new file: content/pages/cover-letters/2026-01-27-illumen-group-developer-senior-russell-ballestrini.rst new file: content/pages/cover-letters/2026-01-27-known-senior-software-engineer-russell-ballestrini.rst new file: content/pages/cover-letters/2026-01-27-opendp-harvard-senior-data-privacy-software-developer-russell-ballestrini.rst new file: content/pages/cover-letters/2026-01-27-skycatchfire-senior-python-backend-developer-russell-ballestrini.rst modified: pelicanconf.py
1193 lines
41 KiB
ReStructuredText
1193 lines
41 KiB
ReStructuredText
Building and Modding MultiMower with Dry Engine
|
|
###############################################
|
|
|
|
:date: 2025-07-13 10:00
|
|
:author: Russell Ballestrini
|
|
:tags: Programming, Game Development, Code
|
|
:slug: building-and-modding-multimower-with-dry-engine
|
|
:status: published
|
|
:summary: Learn how to download, compile, and mod MultiMower - a multiplayer lawn mower game built with the Dry Engine. We'll explore the codebase structure.
|
|
|
|
Resources and Community
|
|
-----------------------
|
|
|
|
- **MultiMower Repository**: https://gitlab.com/luckeyproductions/games/MultiMower
|
|
- **Dry Engine**: https://gitlab.com/luckeyproductions/dry
|
|
- **Dry Engine Documentation**: Available in the Dry repository
|
|
|
|
Quick Start Summary
|
|
-------------------
|
|
|
|
For experienced developers, here's the TL;DR version:
|
|
|
|
.. code-block:: bash
|
|
|
|
# Install dependencies on Fedora
|
|
sudo dnf install git make cmake gcc gcc-c++ qt5-qtbase-devel \
|
|
libX11-devel libXrandr-devel alsa-lib-devel mesa-libEGL-devel \
|
|
wayland-devel wayland-protocols-devel
|
|
|
|
# Build Dry engine
|
|
cd ~/git
|
|
git clone https://gitlab.com/luckeyproductions/dry.git
|
|
cd dry && mkdir build && cd build
|
|
cmake .. -DDRY_64BIT=1 -DCMAKE_BUILD_TYPE=Release -DVIDEO_WAYLAND=OFF
|
|
make -j$(nproc)
|
|
|
|
# Build MultiMower (CRITICAL: set DRY_HOME first!)
|
|
export DRY_HOME=$HOME/git/dry/build
|
|
cd ~/git
|
|
git clone https://gitlab.com/luckeyproductions/games/MultiMower.git
|
|
cd MultiMower && mkdir build && cd build
|
|
qmake ../MultiMower.pro && make -j$(nproc)
|
|
|
|
# Run the game
|
|
cp -r ../Resources .
|
|
./multimower
|
|
|
|
What You'll Learn
|
|
-----------------
|
|
|
|
By building and modding MultiMower, you'll gain experience with:
|
|
|
|
- **C++ Game Development**: Modern C++11 with game-specific patterns
|
|
- **Physics Programming**: Bullet Physics integration for vehicles and projectiles
|
|
- **3D Graphics**: OpenGL with custom shaders and post-processing
|
|
- **Game Architecture**: Component systems, scene graphs, and input handling
|
|
- **Asset Pipeline**: Working with 3D models, textures, and audio
|
|
- **Cross-Platform Development**: CMake, qmake, make, and Linux development tools
|
|
|
|
This tutorial is perfect for intermediate programmers who want to understand game engine architecture and learn practical modding techniques.
|
|
|
|
What is MultiMower?
|
|
--------------------
|
|
|
|
MultiMower is a physics-based 3D game where players control armed robotic lawn mowers. Built with the Dry Engine, it's currently in early development but provides an excellent foundation for learning game modding and physics programming.
|
|
|
|
**Current Features:**
|
|
- Single mower type with basic weaponry (machine gun and missiles)
|
|
- Multiple stationary computer-controlled mowers that randomly fire missiles in random directions
|
|
- 3D arena with grass terrain and collision boundaries
|
|
- Real-time physics simulation for movement and projectiles
|
|
- Local player control with keyboard and mouse
|
|
|
|
**Technical Foundation:**
|
|
- OpenGL 3D rendering with custom shaders
|
|
- Bullet Physics integration for realistic movement
|
|
- Component-based entity system
|
|
- Sound system with SDL2 audio
|
|
- Cross-platform C++ codebase
|
|
|
|
**Development Status:**
|
|
MultiMower is an artistic technical demonstration in its current state. The original version provides a foundation with basic movement and projectile mechanics that we can enhance through modding. Our tutorial expands it into a full-featured tank combat experience.
|
|
|
|
Prerequisites
|
|
-------------
|
|
|
|
Before we begin, ensure you have the following installed on your Linux system:
|
|
|
|
For Ubuntu/Debian:
|
|
|
|
.. code-block:: bash
|
|
|
|
sudo apt-get update
|
|
sudo apt-get install -y git make cmake build-essential qtbase5-dev qt5-qmake \
|
|
libx11-dev libxrandr-dev libasound2-dev libegl1-mesa-dev \
|
|
libwayland-dev wayland-protocols
|
|
|
|
For Fedora:
|
|
|
|
.. code-block:: bash
|
|
|
|
sudo dnf install git make cmake gcc gcc-c++ qt5-qtbase-devel \
|
|
libX11-devel libXrandr-devel alsa-lib-devel mesa-libEGL-devel \
|
|
wayland-devel wayland-protocols-devel
|
|
|
|
Downloading the Source Code
|
|
---------------------------
|
|
|
|
First, let's clone the MultiMower repository and checkout the specific commit used in this tutorial:
|
|
|
|
.. code-block:: bash
|
|
|
|
cd ~/git
|
|
git clone git@gitlab.com:luckeyproductions/games/MultiMower.git
|
|
cd MultiMower
|
|
git checkout 60b5dcd6597fe6c7a480505f822a70e0a1469cda
|
|
|
|
Setting Up the Dry Engine
|
|
-------------------------
|
|
|
|
MultiMower uses the Dry Engine. Here's how to build it from source on Fedora:
|
|
|
|
Building Dry from Source on Fedora
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
1. Clone the Dry repository and checkout the specific commit used in this tutorial:
|
|
|
|
.. code-block:: bash
|
|
|
|
cd ~/git
|
|
git clone git@gitlab.com:luckeyproductions/dry.git
|
|
cd dry
|
|
git checkout bc78ed2c3b2c52f21ccb639ace09c016bce8536d
|
|
|
|
2. Install dependencies (the repository includes a helper script):
|
|
|
|
.. code-block:: bash
|
|
|
|
# This script automatically detects your distro and installs required packages
|
|
./script/installreq.sh
|
|
|
|
3. Build Dry using the provided build script:
|
|
|
|
.. code-block:: bash
|
|
|
|
# For a minimal build (recommended for game development)
|
|
./quick.sh
|
|
|
|
# Or for a full build with samples and tools (recommended):
|
|
mkdir build && cd build
|
|
cmake .. -DDRY_64BIT=1 -DCMAKE_BUILD_TYPE=Release -DVIDEO_WAYLAND=OFF
|
|
make -j$(nproc)
|
|
|
|
**Note**: We disable Wayland (``-DVIDEO_WAYLAND=OFF``) to avoid linking issues. X11 support works perfectly for gaming.
|
|
|
|
4. Set the DRY_HOME environment variable:
|
|
|
|
.. code-block:: bash
|
|
|
|
# Set DRY_HOME to your Dry build directory (adjust path for your username)
|
|
export DRY_HOME=$HOME/git/dry/build
|
|
|
|
# Make it permanent (add to ~/.bashrc)
|
|
echo "export DRY_HOME=$HOME/git/dry/build" >> ~/.bashrc
|
|
source ~/.bashrc
|
|
|
|
Building Dry with Debug Symbols
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
If you need debug symbols for development:
|
|
|
|
.. code-block:: bash
|
|
|
|
cd ~/git/dry
|
|
mkdir build-debug && cd build-debug
|
|
cmake .. -DDRY_64BIT=1 -DCMAKE_BUILD_TYPE=Debug -DVIDEO_WAYLAND=OFF
|
|
make -j$(nproc)
|
|
|
|
# Use this debug build instead
|
|
export DRY_HOME=$HOME/git/dry/build-debug
|
|
|
|
Compiling MultiMower
|
|
--------------------
|
|
|
|
Now we're ready to build the game in its own directory (much more sensible than building inside the engine):
|
|
|
|
.. code-block:: bash
|
|
|
|
# Navigate to your MultiMower directory and create a build folder
|
|
cd ~/git/MultiMower
|
|
mkdir build && cd build
|
|
|
|
# CRITICAL: Set DRY_HOME before running qmake
|
|
export DRY_HOME=$HOME/git/dry/build
|
|
|
|
# Generate the Makefile (qmake will find Dry via DRY_HOME)
|
|
qmake ../MultiMower.pro
|
|
|
|
# Compile the game with parallel jobs for faster builds
|
|
make -j$(nproc)
|
|
|
|
# Copy the game resources to the build directory
|
|
cp -r ../Resources .
|
|
|
|
Build Notes:
|
|
~~~~~~~~~~~~
|
|
|
|
- **Build Location**: The executable is created in ``~/git/MultiMower/build/`` (not in the Dry engine directory!)
|
|
- **CRITICAL**: You MUST set ``DRY_HOME=$HOME/git/dry/build`` before running qmake, or compilation will fail with "Dry/Dry.h: No such file or directory"
|
|
- **User-Specific Paths**: Replace ``$HOME`` with your actual home directory (e.g., ``/home/yourusername``, ``/Users/yourusername``, etc.)
|
|
- **Warnings Expected**: You'll see many compiler warnings about unused parameters and deprecated copy constructors from the Dry engine - these are normal and don't affect functionality
|
|
- **Build Time**: Compilation takes about 30-60 seconds on modern hardware
|
|
- **File Size**: The final executable is approximately 15MB
|
|
- **Dependencies**: Links against libDry.a from your ``$DRY_HOME``, plus pthread, dl, and OpenGL
|
|
|
|
If everything went well, you should now have a ``multimower`` executable in ``~/git/MultiMower/build/``.
|
|
|
|
Running the Game
|
|
----------------
|
|
|
|
To run MultiMower, navigate to the build directory:
|
|
|
|
.. code-block:: bash
|
|
|
|
# The game builds in its own build directory
|
|
cd ~/git/MultiMower/build
|
|
|
|
# Run the game
|
|
./multimower
|
|
|
|
**Important**: The game must be run from the directory containing the ``Resources`` folder, or it won't find its assets.
|
|
|
|
Game Controls:
|
|
~~~~~~~~~~~~~~
|
|
|
|
**Enhanced Player Controls:**
|
|
- **Movement**: WASD keys (W=forward, S=backward, A=turn left, D=turn right)
|
|
- **Aim**: Mouse to look around and aim
|
|
- **Machine Gun**: Left mouse button (fires bullets)
|
|
- **Missiles**: Right mouse button (fires homing missiles)
|
|
|
|
**Note**: The original demo provides basic movement that we enhance with realistic tank-style acceleration and responsive controls. The mouse aiming works perfectly out of the box.
|
|
|
|
Expected Output:
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
When you first run the game, you should see:
|
|
|
|
- A 3D area with tanks surrounding
|
|
- Robot mowers that can be controlled by players
|
|
- Physics-based movement and projectiles
|
|
- Visual effects like explosions & sparks
|
|
- **Working audio**: Launch sounds, explosion effects, and other game audio
|
|
|
|
**Success Indicators:**
|
|
- Log shows: ``Set audio mode 44100 Hz stereo interpolated``
|
|
- No "Failed to initialise SDL subsystem" errors
|
|
- Sound effects play when firing weapons with mouse clicks
|
|
|
|
Understanding the Codebase
|
|
--------------------------
|
|
|
|
Before modding, let's explore the project structure:
|
|
|
|
.. code-block:: text
|
|
|
|
MultiMower/
|
|
├── src/ # C++ source files
|
|
│ ├── mastercontrol.cpp # Main game loop
|
|
│ ├── mower.cpp # Mower implementation
|
|
│ ├── inputmaster.cpp # Input handling
|
|
│ └── ...
|
|
├── Resources/ # Game assets
|
|
│ ├── Models/ # 3D models
|
|
│ ├── Textures/ # Texture files
|
|
│ ├── Shaders/ # GLSL shaders
|
|
│ └── ...
|
|
└── blends/ # Blender source files
|
|
|
|
Key classes to understand:
|
|
|
|
- **MasterControl**: Manages the game state and scene
|
|
- **Mower**: The player-controlled mower entity
|
|
- **InputMaster**: Handles keyboard and joystick input
|
|
- **SpawnMaster**: Manages entity spawning
|
|
|
|
Version Compatibility
|
|
---------------------
|
|
|
|
This tutorial was tested and written for specific commits to ensure reproducibility:
|
|
|
|
- **Dry Engine**: commit ``bc78ed2c3b2c52f21ccb639ace09c016bce8536d``
|
|
- **MultiMower**: commit ``60b5dcd6597fe6c7a480505f822a70e0a1469cda``
|
|
|
|
If you want to follow this tutorial exactly, check out these specific commits:
|
|
|
|
.. code-block:: bash
|
|
|
|
# Check out the exact Dry version used in this tutorial
|
|
cd ~/git/dry
|
|
git checkout bc78ed2c3b2c52f21ccb639ace09c016bce8536d
|
|
|
|
# Check out the exact MultiMower version used in this tutorial
|
|
cd ~/git/MultiMower
|
|
git checkout 60b5dcd6597fe6c7a480505f822a70e0a1469cda
|
|
|
|
Game Development Tutorials
|
|
--------------------------
|
|
|
|
Here are three comprehensive mods that transform MultiMower into a proper tank combat game with realistic movement, damage systems, and spectacular explosions.
|
|
|
|
Part 1: Tank Movement Controls with Realistic Acceleration
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
**Goal**: Enhance the movement system with realistic tank-style acceleration and responsive controls.
|
|
|
|
**Solution**: Implement proper tank-style movement with realistic acceleration curves.
|
|
|
|
**Step 1**: Add acceleration variables to ``src/mower.h``:
|
|
|
|
.. code-block:: cpp
|
|
|
|
// Add these private members after existing variables:
|
|
private:
|
|
// Acceleration system
|
|
float currentSpeed_;
|
|
float maxSpeed_;
|
|
float acceleration_;
|
|
|
|
**Step 2**: Initialize acceleration in ``src/mower.cpp`` constructor:
|
|
|
|
.. code-block:: cpp
|
|
|
|
Mower::Mower(Context* context): Controllable(context),
|
|
// existing initializers...
|
|
lastFiredRight_{ false },
|
|
currentSpeed_{ 0.0f },
|
|
maxSpeed_{ 60.0f }, // 60 units/sec top speed
|
|
acceleration_{ 7.5f } // 0-60 in 8 seconds
|
|
{
|
|
}
|
|
|
|
**Step 3**: Update the method signature in ``src/mower.h``:
|
|
|
|
.. code-block:: cpp
|
|
|
|
private:
|
|
void HandleInput(float timeStep); // Add timeStep parameter
|
|
|
|
**Step 4**: Replace the ``HandleInput()`` method in ``src/mower.cpp``:
|
|
|
|
.. code-block:: cpp
|
|
|
|
void Mower::HandleInput(float timeStep)
|
|
{
|
|
RigidBody* body = node_->GetComponent<RigidBody>();
|
|
if (!body) return;
|
|
|
|
// Get inputs
|
|
float forwardInput = move_.z_; // W/S keys (-1 to 1)
|
|
float rotationInput = move_.x_; // A/D keys
|
|
|
|
// Calculate target speed based on input
|
|
float targetSpeed = forwardInput * maxSpeed_; // Can be negative for reverse
|
|
|
|
// Gradual acceleration/deceleration (0-60 in 8 seconds)
|
|
if (Abs(targetSpeed - currentSpeed_) > 0.1f)
|
|
{
|
|
if (targetSpeed > currentSpeed_)
|
|
{
|
|
currentSpeed_ += acceleration_ * timeStep;
|
|
if (currentSpeed_ > targetSpeed) currentSpeed_ = targetSpeed;
|
|
}
|
|
else
|
|
{
|
|
currentSpeed_ -= acceleration_ * timeStep;
|
|
if (currentSpeed_ < targetSpeed) currentSpeed_ = targetSpeed;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
currentSpeed_ = targetSpeed; // Close enough, snap to target
|
|
}
|
|
|
|
// Get current facing direction and set velocity
|
|
Vector3 forward = node_->GetWorldDirection();
|
|
Vector3 desiredVelocity = forward * currentSpeed_;
|
|
body->SetLinearVelocity(desiredVelocity);
|
|
|
|
// Handle rotation
|
|
if (Abs(rotationInput) > 0.01f)
|
|
{
|
|
Vector3 torque = Vector3::UP * rotationInput * 15.0f;
|
|
body->ApplyTorque(torque);
|
|
}
|
|
|
|
// Angular damping for smoother rotation
|
|
Vector3 angularVel = body->GetAngularVelocity();
|
|
body->ApplyTorque(-angularVel * 8.0f);
|
|
|
|
// Fire bullet (existing code)
|
|
if (INPUT->GetMouseButtonDown(MOUSEB_LEFT) && sinceBullet >= bulletInterval)
|
|
{
|
|
bullets_.Push(Projectile{ gun_ });
|
|
sinceBullet = 0.f;
|
|
}
|
|
|
|
// Fire missile (existing code)
|
|
if (INPUT->GetMouseButtonPress(MOUSEB_RIGHT) && sinceMissile >= missileInterval)
|
|
{
|
|
FireMissile();
|
|
}
|
|
}
|
|
|
|
**Note**: This is the initial version from Part 1. The health system checks (``isDestroyed_``) will be added in Part 2.
|
|
|
|
**Step 5**: Update the call site in ``Mower::Update()``:
|
|
|
|
.. code-block:: cpp
|
|
|
|
if (GetPlayer())
|
|
{
|
|
HandleInput(timeStep); // Pass timeStep parameter
|
|
}
|
|
|
|
**Result**: Tanks now accelerate realistically from 0-60 in 8 seconds, reach high speeds, and feel like real heavy vehicles!
|
|
|
|
Part 2: Health and Damage System with Random Damage
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
**Goal**: Add hit points, collision detection, and random weapon damage for balanced combat.
|
|
|
|
**Step 1**: Add health variables to ``src/mower.h``:
|
|
|
|
.. code-block:: cpp
|
|
|
|
// Add these private members:
|
|
private:
|
|
// Health system
|
|
float health_;
|
|
float maxHealth_;
|
|
bool isDestroyed_;
|
|
|
|
**Step 2**: Initialize health in ``src/mower.cpp`` constructor:
|
|
|
|
.. code-block:: cpp
|
|
|
|
Mower::Mower(Context* context): Controllable(context),
|
|
// existing initializers...
|
|
acceleration_{ 7.5f }, // 0-60 in 8 seconds
|
|
health_{ 100.0f },
|
|
maxHealth_{ 100.0f },
|
|
isDestroyed_{ false }
|
|
{
|
|
}
|
|
|
|
**Step 3**: Add damage methods to ``src/mower.h``:
|
|
|
|
.. code-block:: cpp
|
|
|
|
private:
|
|
void TakeDamage(float damage);
|
|
void DestroyMower();
|
|
float GetHealthPercentage() const;
|
|
|
|
**Step 4**: Implement damage methods in ``src/mower.cpp``:
|
|
|
|
.. code-block:: cpp
|
|
|
|
void Mower::TakeDamage(float damage)
|
|
{
|
|
if (isDestroyed_) return;
|
|
|
|
health_ -= damage;
|
|
DRY_LOGINFOF("Mower took %.1f damage, health now %.1f", damage, health_);
|
|
|
|
if (health_ <= 0.0f)
|
|
{
|
|
DestroyMower();
|
|
}
|
|
}
|
|
|
|
void Mower::DestroyMower()
|
|
{
|
|
if (isDestroyed_) return;
|
|
|
|
isDestroyed_ = true;
|
|
health_ = 0.0f;
|
|
currentSpeed_ = 0.0f; // Stop acceleration
|
|
|
|
DRY_LOGINFO("Mower destroyed!");
|
|
|
|
// Disable physics so it stops moving
|
|
RigidBody* body = node_->GetComponent<RigidBody>();
|
|
if (body)
|
|
{
|
|
body->SetEnabled(false);
|
|
}
|
|
|
|
// Hide the mower model
|
|
AnimatedModel* model = node_->GetComponent<AnimatedModel>();
|
|
if (model)
|
|
{
|
|
model->SetEnabled(false);
|
|
}
|
|
}
|
|
|
|
float Mower::GetHealthPercentage() const
|
|
{
|
|
return health_ / maxHealth_;
|
|
}
|
|
|
|
**Step 5**: Add collision detection in ``Mower::Update()`` method (in the bullet loop):
|
|
|
|
.. code-block:: cpp
|
|
|
|
// Replace the bullet update loop with collision detection
|
|
for (unsigned b{ 0 }; b < bullets_.Size(); )
|
|
{
|
|
Projectile& bullet{ bullets_.At(b)};
|
|
|
|
bullet.age_ += timeStep;
|
|
Vector3 bulletPos = bullet.path_.Solve(bullet.age_);
|
|
|
|
// Check for hits on other mowers
|
|
bool bulletHit = false;
|
|
|
|
// Check all mowers in the scene
|
|
PODVector<Node*> mowerNodes;
|
|
GetScene()->GetChildrenWithComponent<Mower>(mowerNodes, true);
|
|
|
|
for (Node* mowerNode : mowerNodes)
|
|
{
|
|
Mower* otherMower = mowerNode->GetComponent<Mower>();
|
|
|
|
if (otherMower && otherMower != this && !otherMower->isDestroyed_)
|
|
{
|
|
float distance = (bulletPos - otherMower->node_->GetWorldPosition()).Length();
|
|
if (distance < 5.0f) // Hit radius
|
|
{
|
|
float damage = 1.0f + Random(4); // Random 1-4 damage
|
|
DRY_LOGINFOF("Bullet hit! Distance: %f, Damage: %.0f", distance, damage);
|
|
otherMower->TakeDamage(damage);
|
|
bullets_.EraseSwap(b);
|
|
bulletHit = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bulletHit && (bullet.age_ > 0.17f || bulletPos.y_ < .075f))
|
|
{
|
|
for (int s{ 0 }; s < 23; ++s)
|
|
Sparkle(bulletPos, 5.f);
|
|
bullets_.EraseSwap(b);
|
|
}
|
|
else if (!bulletHit)
|
|
{
|
|
++b;
|
|
}
|
|
}
|
|
|
|
**Step 6**: Add missile collision detection in the missile update loop:
|
|
|
|
.. code-block:: cpp
|
|
|
|
// In the missile update loop, add collision detection
|
|
if (missile.age_ < 4.f/3.f)
|
|
{
|
|
missile.age_ += timeStep;
|
|
Sparkle(missile.path_.Solve(missile.age_), .1f);
|
|
|
|
Vector3 missilePos = missile.path_.Solve(missile.age_);
|
|
|
|
// Check for missile hits on other mowers
|
|
bool missileHit = false;
|
|
PODVector<Node*> mowerNodes;
|
|
GetScene()->GetChildrenWithComponent<Mower>(mowerNodes, true);
|
|
|
|
for (Node* mowerNode : mowerNodes)
|
|
{
|
|
Mower* otherMower = mowerNode->GetComponent<Mower>();
|
|
|
|
if (otherMower && otherMower != this && !otherMower->isDestroyed_)
|
|
{
|
|
float distance = (missilePos - otherMower->node_->GetWorldPosition()).Length();
|
|
if (distance < 8.0f) // Larger explosion radius
|
|
{
|
|
float damage = 10.0f + Random(16); // Random 10-25 damage
|
|
DRY_LOGINFOF("Missile hit! Distance: %f, Damage: %.0f", distance, damage);
|
|
otherMower->TakeDamage(damage);
|
|
missileHit = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (missileHit || missile.age_ > 4.f/3.f || missilePos.y_ < .25f)
|
|
{
|
|
// Create explosion effects and remove missile
|
|
// ... existing explosion code ...
|
|
}
|
|
}
|
|
|
|
**Step 7**: Add health bar visualization in ``RenderDebug()`` method:
|
|
|
|
.. code-block:: cpp
|
|
|
|
// Add to RenderDebug() method in mower.cpp
|
|
// Render health bar above mower
|
|
if (!isDestroyed_)
|
|
{
|
|
Vector3 healthBarPos = node_->GetWorldPosition() + Vector3::UP * 3.0f;
|
|
float healthPercent = GetHealthPercentage();
|
|
Color healthColor = Color::GREEN.Lerp(Color::RED, 1.0f - healthPercent);
|
|
|
|
// Health bar background (dark)
|
|
debug->AddLine(healthBarPos - Vector3::RIGHT,
|
|
healthBarPos + Vector3::RIGHT,
|
|
Color::BLACK);
|
|
|
|
// Health bar (colored based on health)
|
|
debug->AddLine(healthBarPos - Vector3::RIGHT,
|
|
healthBarPos - Vector3::RIGHT + Vector3::RIGHT * 2.0f * healthPercent,
|
|
healthColor);
|
|
}
|
|
|
|
**Weapon Damage Values**:
|
|
- **Bullets**: Random 1-4 damage per hit (takes 25-100 bullets to destroy)
|
|
- **Missiles**: Random 10-25 damage per hit (takes 4-10 missiles to destroy)
|
|
- **Total mower health**: 100 HP
|
|
|
|
**Step 8**: Update ``HandleInput()`` to prevent firing when dead (modify the method from Part 1):
|
|
|
|
.. code-block:: cpp
|
|
|
|
void Mower::HandleInput(float timeStep)
|
|
{
|
|
// Don't handle input if destroyed
|
|
if (isDestroyed_) return;
|
|
|
|
// ... existing movement code from Part 1 ...
|
|
|
|
// Fire bullet (only if not destroyed)
|
|
if (!isDestroyed_ && INPUT->GetMouseButtonDown(MOUSEB_LEFT) && sinceBullet >= bulletInterval)
|
|
{
|
|
bullets_.Push(Projectile{ gun_ });
|
|
sinceBullet = 0.f;
|
|
}
|
|
|
|
// Fire missile (only if not destroyed)
|
|
if (!isDestroyed_ && INPUT->GetMouseButtonPress(MOUSEB_RIGHT) && sinceMissile >= missileInterval)
|
|
{
|
|
FireMissile();
|
|
}
|
|
}
|
|
|
|
**Step 9**: Update AI firing in ``Update()`` method:
|
|
|
|
.. code-block:: cpp
|
|
|
|
else
|
|
{
|
|
// AI mowers only fire if not destroyed
|
|
if (!isDestroyed_ && Random(420) == 0)
|
|
FireMissile();
|
|
}
|
|
|
|
**Result**: Balanced combat with visible health bars and satisfying random damage!
|
|
|
|
Part 3: Death and Victory Effects
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
**Goal**: Create a complete end-game experience with massive explosions, death camera effects, electrical sparking, and victory celebrations.
|
|
|
|
**Step 1**: Add new variables to ``src/mower.h``:
|
|
|
|
.. code-block:: cpp
|
|
|
|
// Add these private members:
|
|
private:
|
|
// Death camera
|
|
float deathCameraDistance_;
|
|
|
|
// Continuous sparking effect for destroyed mowers
|
|
float sparkTimer_;
|
|
|
|
// Victory flag to prevent multiple celebrations
|
|
bool victoryTriggered_;
|
|
|
|
**Step 2**: Initialize new variables in ``src/mower.cpp`` constructor:
|
|
|
|
.. code-block:: cpp
|
|
|
|
Mower::Mower(Context* context): Controllable(context),
|
|
// existing initializers...
|
|
isDestroyed_{ false },
|
|
deathCameraDistance_{ 10.0f }, // Initial camera distance on death
|
|
sparkTimer_{ 0.0f }, // Timer for continuous sparking
|
|
victoryTriggered_{ false } // Flag to prevent multiple victory celebrations
|
|
{
|
|
}
|
|
|
|
**Step 3**: Replace the ``DestroyMower()`` method in ``src/mower.cpp``:
|
|
|
|
.. code-block:: cpp
|
|
|
|
void Mower::DestroyMower()
|
|
{
|
|
if (isDestroyed_) return;
|
|
|
|
isDestroyed_ = true;
|
|
health_ = 0.0f;
|
|
currentSpeed_ = 0.0f; // Stop acceleration
|
|
|
|
DRY_LOGINFO("Mower destroyed! MASSIVE EXPLOSION!");
|
|
|
|
Vector3 explosionPos = node_->GetWorldPosition();
|
|
|
|
// HUGE explosion effect - like multiple rockets going off!
|
|
for (int i = 0; i < 200; ++i) // 200 sparkles instead of 30
|
|
{
|
|
Sparkle(explosionPos + Vector3{RandomOffCenter(5.0f), Random(3.0f), RandomOffCenter(5.0f)},
|
|
30.0f + Random(20.0f)); // Varied speed and position
|
|
}
|
|
|
|
// Create multiple trail effects radiating outward (like rocket exhaust)
|
|
for (int i = 0; i < 50; ++i)
|
|
{
|
|
Vector3 trailStart = explosionPos + Vector3{RandomOffCenter(2.0f), Random(1.0f), RandomOffCenter(2.0f)};
|
|
Trail(trailStart);
|
|
}
|
|
|
|
// Multiple explosion sounds for massive effect
|
|
PlaySample(RES(Sound, "Samples/Explode.wav"), 1.5f); // Main explosion
|
|
|
|
// Add delayed secondary explosions (ammo cook-off effect)
|
|
for (int i = 0; i < 8; ++i)
|
|
{
|
|
// Create delayed sparkle bursts
|
|
for (int j = 0; j < 25; ++j)
|
|
{
|
|
Vector3 delayedPos = explosionPos + Vector3{RandomOffCenter(8.0f), Random(4.0f), RandomOffCenter(8.0f)};
|
|
Sparkle(delayedPos, 25.0f);
|
|
}
|
|
}
|
|
|
|
// Create blast lights for dramatic effect
|
|
for (int i = 0; i < 5; ++i)
|
|
{
|
|
Node* lightNode = GetScene()->CreateChild("BlastLight");
|
|
lightNode->CreateComponent<BlastLight>();
|
|
Vector3 lightPos = explosionPos + Vector3{RandomOffCenter(3.0f), Random(2.0f) + 1.0f, RandomOffCenter(3.0f)};
|
|
lightNode->SetPosition(lightPos);
|
|
}
|
|
|
|
// Disable physics so it stops moving
|
|
RigidBody* body = node_->GetComponent<RigidBody>();
|
|
if (body)
|
|
{
|
|
body->SetEnabled(false);
|
|
}
|
|
|
|
// Transform into a grayscale wreck instead of hiding
|
|
AnimatedModel* model = node_->GetComponent<AnimatedModel>();
|
|
if (model)
|
|
{
|
|
// Create grayscale material to show battle damage
|
|
SharedPtr<Material> grayscaleMaterial = model->GetMaterial(0)->Clone();
|
|
|
|
// Convert to grayscale - desaturate while keeping original brightness
|
|
grayscaleMaterial->SetShaderParameter("MatDiffColor", Color(0.4f, 0.4f, 0.4f)); // Grayscale
|
|
grayscaleMaterial->SetShaderParameter("MatSpecColor", Color(0.1f, 0.1f, 0.1f)); // Dull reflection
|
|
grayscaleMaterial->SetShaderParameter("MatEmissiveColor", Color(0.0f, 0.0f, 0.0f)); // No glow
|
|
|
|
model->SetMaterial(grayscaleMaterial);
|
|
|
|
DRY_LOGINFO("Applied grayscale material to destroyed mower");
|
|
}
|
|
}
|
|
|
|
**Explosion Features**:
|
|
- **200 sparkles** scattered over a large area (vs 30 in original)
|
|
- **50 rocket exhaust trails** radiating outward
|
|
- **8 secondary explosion bursts** simulating ammo cook-off
|
|
- **5 dramatic blast lights** for cinematic effect
|
|
- **Louder explosion sound** (1.5x volume)
|
|
- **Varied particle speeds** and positions for realism
|
|
|
|
**Step 4**: Add continuous sparking in ``Update()`` method:
|
|
|
|
.. code-block:: cpp
|
|
|
|
// Add to Update() method after existing timer updates
|
|
// Update spark timer for destroyed mowers
|
|
sparkTimer_ += timeStep;
|
|
|
|
// Continuous sparking effect for destroyed mowers
|
|
if (isDestroyed_ && sparkTimer_ >= 0.1f) // Spark every 0.1 seconds
|
|
{
|
|
sparkTimer_ = 0.0f; // Reset timer
|
|
|
|
// Create multiple sparks around the destroyed mower
|
|
Vector3 mowerPos = node_->GetWorldPosition();
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
Vector3 sparkPos = mowerPos + Vector3{RandomOffCenter(2.0f), Random(1.0f), RandomOffCenter(2.0f)};
|
|
Sparkle(sparkPos, 3.0f); // Electrical sparking effect
|
|
}
|
|
|
|
// Occasional larger sizzle effect
|
|
if (Random(5) == 0) // 20% chance
|
|
{
|
|
for (int i = 0; i < 8; ++i)
|
|
{
|
|
Vector3 sizzlePos = mowerPos + Vector3{RandomOffCenter(1.5f), Random(0.8f), RandomOffCenter(1.5f)};
|
|
Sparkle(sizzlePos, 5.0f); // Bigger electrical discharge
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
**Step 4**: Add dynamic camera zoom-out in ``PostUpdate()`` method:
|
|
|
|
.. code-block:: cpp
|
|
|
|
void Mower::PostUpdate(float timeStep)
|
|
{
|
|
if (!GetPlayer())
|
|
return;
|
|
|
|
Jib* jib{ GetPlayer()->GetJib() };
|
|
|
|
// Handle death camera zoom (both for death and victory)
|
|
if ((isDestroyed_ || victoryTriggered_) && jib && jib->GetCamera())
|
|
{
|
|
// Dynamic zoom speed - fast initially, then slow to a halt
|
|
float maxDistance = 50.0f; // Maximum zoom distance
|
|
float speedFactor = 1.0f - (deathCameraDistance_ / maxDistance); // 1.0 to 0.0
|
|
speedFactor = Max(0.1f, speedFactor); // Don't go below 0.1
|
|
|
|
// Fast zoom initially (60 units/sec), slowing down as we get further
|
|
deathCameraDistance_ += 60.0f * timeStep * speedFactor;
|
|
deathCameraDistance_ = Min(deathCameraDistance_, maxDistance); // Cap at max distance
|
|
|
|
// Set camera to orbit the mower (destroyed or victorious)
|
|
Node* cameraNode = jib->GetCamera()->GetNode();
|
|
Vector3 offset = Vector3::BACK * deathCameraDistance_ + Vector3::UP * (deathCameraDistance_ * 0.4f);
|
|
cameraNode->SetPosition(node_->GetWorldPosition() + offset);
|
|
cameraNode->LookAt(node_->GetWorldPosition(), Vector3::UP);
|
|
|
|
return; // Skip normal camera update when dead or victorious
|
|
}
|
|
|
|
// ... existing PostUpdate code continues here ...
|
|
}
|
|
|
|
**Visual Effects**:
|
|
- **Grayscale Material**: Mower turns gray (0.4 RGB) showing battle damage
|
|
- **Continuous Sparking**: 3 electrical sparks every 0.1 seconds around the wreck
|
|
- **Sizzle Bursts**: 20% chance of 8 larger electrical discharges
|
|
- **Dynamic Camera Movement**: Fast zoom initially (60 units/sec), slowing to a halt at 50 units distance
|
|
- **No Weapon Firing**: Both player and AI mowers stop firing when destroyed
|
|
- **Persistent Effect**: Sparking continues forever, showing damaged electronics
|
|
- **No Self-Damage**: Neither bullets nor missiles can damage the mower that fired them
|
|
|
|
**Result**: When you die, all firing stops, the camera dramatically pulls back, and your mower becomes a gray wreck with continuous electrical sparking effects - like damaged electronics shorting out!
|
|
|
|
**Step 5**: Add victory condition system. First add the method declaration to ``src/mower.h``:
|
|
|
|
.. code-block:: cpp
|
|
|
|
void CheckWinCondition();
|
|
|
|
**Step 6**: Add victory check in ``src/mower.cpp`` Update method:
|
|
|
|
.. code-block:: cpp
|
|
|
|
void Mower::Update(float timeStep)
|
|
{
|
|
// Existing update code...
|
|
|
|
// Check win condition - if player exists, check if all AI tanks are destroyed
|
|
if (GetPlayer())
|
|
{
|
|
CheckWinCondition();
|
|
}
|
|
|
|
// Rest of existing update code...
|
|
}
|
|
|
|
**Step 7**: Implement the victory detection system:
|
|
|
|
.. code-block:: cpp
|
|
|
|
void Mower::CheckWinCondition()
|
|
{
|
|
// Only check win condition if this is the player mower
|
|
if (!GetPlayer() || isDestroyed_) return;
|
|
|
|
// Get all mowers in the scene
|
|
PODVector<Node*> mowerNodes;
|
|
GetScene()->GetChildrenWithComponent<Mower>(mowerNodes, true);
|
|
|
|
int aliveTanks = 0;
|
|
int totalAITanks = 0;
|
|
|
|
for (Node* mowerNode : mowerNodes)
|
|
{
|
|
Mower* mower = mowerNode->GetComponent<Mower>();
|
|
if (mower && !mower->GetPlayer()) // AI mower
|
|
{
|
|
totalAITanks++;
|
|
if (!mower->isDestroyed_)
|
|
{
|
|
aliveTanks++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if all AI tanks are destroyed
|
|
if (totalAITanks > 0 && aliveTanks == 0 && !victoryTriggered_)
|
|
{
|
|
victoryTriggered_ = true; // Set flag to prevent multiple celebrations
|
|
|
|
// Player wins! Display victory message
|
|
DRY_LOGINFO("=== VICTORY! ALL ENEMY TANKS DESTROYED! ===");
|
|
|
|
// Create a big celebration explosion at player position
|
|
Vector3 playerPos = node_->GetWorldPosition();
|
|
|
|
// Victory celebration - exciting but controlled (only fires once!)
|
|
for (int i = 0; i < 50; ++i)
|
|
{
|
|
Vector3 fireworkPos = playerPos + Vector3{RandomOffCenter(6.0f), Random(4.0f) + 2.0f, RandomOffCenter(6.0f)};
|
|
Sparkle(fireworkPos, 25.0f + Random(15.0f));
|
|
}
|
|
|
|
// Victory trail burst
|
|
for (int i = 0; i < 20; ++i)
|
|
{
|
|
Vector3 trailStart = playerPos + Vector3{RandomOffCenter(4.0f), Random(2.0f) + 1.0f, RandomOffCenter(4.0f)};
|
|
Trail(trailStart);
|
|
}
|
|
|
|
// Multiple celebration lights
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
Node* lightNode = GetScene()->CreateChild("VictoryLight");
|
|
lightNode->CreateComponent<BlastLight>();
|
|
Vector3 lightPos = playerPos + Vector3{RandomOffCenter(3.0f), Random(2.0f) + 3.0f, RandomOffCenter(3.0f)};
|
|
lightNode->SetPosition(lightPos);
|
|
}
|
|
|
|
// Victory sound
|
|
PlaySample(RES(Sound, "Samples/Explode.wav"), 2.0f);
|
|
|
|
// Log stats
|
|
DRY_LOGINFOF("Victory achieved! Defeated %d enemy tanks!", totalAITanks);
|
|
}
|
|
}
|
|
|
|
**Victory Effects**:
|
|
- **Victory Sparkles**: 50 sparkles in a large area around the player (exciting celebration)
|
|
- **Victory Trails**: 20 colored trail effects radiating outward from player position
|
|
- **Celebration Lights**: 3 blast lights scattered around the player
|
|
- **Audio Feedback**: Victory explosion sound at double volume
|
|
- **Console Message**: Clear victory announcement with enemy count
|
|
- **Single Trigger**: Victory only fires once when condition is first met (prevents multiple celebrations per frame)
|
|
|
|
**How It Works**:
|
|
|
|
1. **Continuous Checking**: Every frame, the player mower scans all tanks in the scene
|
|
2. **AI Detection**: Counts tanks that don't have a player component (AI-controlled)
|
|
3. **Status Verification**: Checks if each AI tank is destroyed using ``isDestroyed_`` flag
|
|
4. **Victory Trigger**: When ``aliveTanks == 0`` and ``totalAITanks > 0``, victory fires
|
|
5. **Celebration**: Immediate massive fireworks display centered on player position
|
|
6. **Performance**: Efficient - only player mower checks, stops when victory achieved
|
|
|
|
**Testing Victory**:
|
|
|
|
.. code-block:: bash
|
|
|
|
# Run the game and destroy all enemy tanks
|
|
./multimower
|
|
|
|
# Victory triggers automatically when last enemy dies
|
|
# Watch for log message: "=== VICTORY! ALL ENEMY TANKS DESTROYED! ==="
|
|
# Enjoy the massive fireworks celebration!
|
|
|
|
**Result**: When you destroy the last enemy tank, a massive and exciting victory celebration appears around your mower with 50 sparkles, 20 trails, 3 lights, and victory sound - making you feel like a true tank commander with a spectacular fireworks display!
|
|
|
|
Complete Game Development Results
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
After implementing all three parts, you'll have:
|
|
|
|
1. **Realistic Tank Movement**: 0-60 acceleration in 8 seconds, high top speeds, smooth control
|
|
2. **Balanced Combat**: Random damage (1-4 bullets, 10-25 missiles), health bars, collision detection
|
|
3. **Complete End-Game Experience**: Massive explosions, death camera effects, electrical sparking, and victory celebrations
|
|
|
|
**Total Changes**:
|
|
- **Files Modified**: ``src/mower.h``, ``src/mower.cpp``, ``src/inputmaster.cpp``, ``src/mastercontrol.cpp``
|
|
- **Lines Added**: +392 lines of code, -16 lines removed
|
|
- **Net Addition**: +376 lines of code
|
|
- **New Features**: Acceleration system, health system, collision detection, massive explosions, death effects, victory celebrations
|
|
- **Gameplay Impact**: Transforms MultiMower into a complete cinematic tank combat experience
|
|
|
|
**Testing Your Mods**:
|
|
|
|
.. code-block:: bash
|
|
|
|
# Compile with your changes
|
|
make clean && make -j$(nproc)
|
|
|
|
# Run the enhanced game
|
|
./multimower
|
|
|
|
# Test the features:
|
|
# - W/S for realistic acceleration/deceleration
|
|
# - A/D for tank turning
|
|
# - Left click for machine gun (1-4 damage)
|
|
# - Right click for missiles (10-25 damage)
|
|
# - Destroy enemy tanks for massive explosions!
|
|
# - Destroy all enemy tanks for victory fireworks!
|
|
|
|
Project Statistics
|
|
------------------
|
|
|
|
Here are the actual numbers behind this modding tutorial, showing the scope and impact of our changes:
|
|
|
|
Engine and Game Size
|
|
~~~~~~~~~~~~~~~~~~~~
|
|
|
|
**Dry Engine (commit bc78ed2c)**:
|
|
- **Source Files**: 4,330 files (.cpp, .h, .c)
|
|
- **Lines of Code**: 199,687 total lines
|
|
- **Repository Size**: 1.1 GB (includes samples, tools, third-party libraries)
|
|
- **Static Library**: 44 MB (libDry.a)
|
|
- **Build Time**: ~5-8 minutes on modern hardware
|
|
|
|
**MultiMower Game (commit 60b5dcd)**:
|
|
- **Source Files**: 18 files (.cpp, .h)
|
|
- **Lines of Code**: 2,346 total lines
|
|
- **Repository Size**: 31 MB (includes assets, models, textures)
|
|
- **Game Resources**: 5.3 MB (3D models, textures, sounds)
|
|
- **Final Executable**: 15 MB (statically linked with Dry)
|
|
- **Build Time**: ~30-60 seconds
|
|
|
|
Our Game Development Impact
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
**Code Changes Made**:
|
|
- **Files Modified**: 4 source files
|
|
- **Lines Added**: +392 lines of code
|
|
- **Lines Removed**: -16 lines of code
|
|
- **Net Addition**: +376 lines of code
|
|
|
|
**Breakdown by File**:
|
|
- ``src/mower.cpp``: +359 lines, -13 lines (main implementation)
|
|
- ``src/mower.h``: +21 lines, -1 line (new variables and methods)
|
|
- ``src/inputmaster.cpp``: +9 lines, -1 line (debug logging)
|
|
- ``src/mastercontrol.cpp``: +3 lines, -1 line (debug logging)
|
|
|
|
**Feature Implementation by Parts**:
|
|
- **Part 1 - Tank Movement**: ~80 lines (acceleration system, realistic physics)
|
|
- **Part 2 - Health & Damage**: ~120 lines (collision detection, health bars, damage system)
|
|
- **Part 3 - Death & Victory**: ~160 lines (explosions, camera effects, sparking, victory system)
|
|
- **Supporting Code**: ~16 lines (debugging, initialization, cleanup)
|
|
|
|
Scale Perspective
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
**Code Efficiency**:
|
|
- Our 376 lines represent **0.16%** of the total MultiMower codebase
|
|
- Yet they transform the game from basic technical demonstration to complete tank combat with victory conditions
|
|
- Shows the power of focused, strategic modifications
|
|
|
|
**Engine vs Game**:
|
|
- Dry Engine: **85x larger** than MultiMower (199K vs 2.3K lines)
|
|
- MultiMower: **35x smaller** repository than Dry (31MB vs 1.1GB)
|
|
- Game executable includes the entire engine statically linked
|
|
|
|
**Development Effort**:
|
|
- **Engine Development**: Years of work, complex graphics/physics systems
|
|
- **Game Development**: Weeks of work, focused gameplay mechanics
|
|
- **Our Game Development**: Hours of work, targeted feature additions
|
|
|
|
**Build Performance**:
|
|
- **Dry Engine**: Takes 5-8 minutes to compile from scratch
|
|
- **MultiMower**: Takes 30-60 seconds with incremental changes
|
|
- **Our Patches**: Compile in 5-10 seconds after initial build
|
|
|
|
|
|
Troubleshooting on Fedora
|
|
-------------------------
|
|
|
|
Common Build Issues
|
|
~~~~~~~~~~~~~~~~~~~
|
|
|
|
1. **"Dry/Dry.h: No such file or directory"**: This happens when DRY_HOME isn't set before running qmake:
|
|
|
|
.. code-block:: bash
|
|
|
|
# Check if DRY_HOME is set correctly
|
|
echo $DRY_HOME
|
|
# Should print: /home/yourusername/git/dry/build (with your actual username)
|
|
|
|
# If empty or wrong, fix it:
|
|
export DRY_HOME=$HOME/git/dry/build
|
|
|
|
# Remove old Makefile and regenerate
|
|
rm -f Makefile
|
|
qmake ../MultiMower.pro
|
|
make -j$(nproc)
|
|
|
|
2. **Build directory confusion**: Always build in MultiMower's own build directory, not in the Dry engine:
|
|
|
|
.. code-block:: bash
|
|
|
|
# Correct approach - build in game directory
|
|
cd ~/git/MultiMower/build
|
|
|
|
# Check your executable is where you expect
|
|
ls -la multimower
|
|
|
|
3. **Resources not found at runtime**: The game needs the Resources folder:
|
|
|
|
.. code-block:: bash
|
|
|
|
# Copy resources to the build directory
|
|
cd ~/git/MultiMower/build
|
|
cp -r ../Resources .
|
|
|
|
4. **Library not found errors**: Ensure all dependencies are installed:
|
|
|
|
.. code-block:: bash
|
|
|
|
# Re-run the dependency installer
|
|
cd ~/git/dry
|
|
./script/installreq.sh
|
|
|
|
5. **Qt6 vs Qt5 conflicts**: The build uses Qt6 by default on newer Fedora:
|
|
|
|
.. code-block:: bash
|
|
|
|
# If you need to force Qt5 (though Qt6 works fine)
|
|
/usr/lib64/qt5/bin/qmake MultiMower.pro
|
|
|
|
6. **Compiler warnings**: Expect many warnings - they're from the Dry engine and don't affect functionality:
|
|
|
|
- Unused parameter warnings
|
|
- Deprecated copy constructor warnings
|
|
- Type-punned pointer warnings
|
|
- These are normal and the game will work fine
|
|
|
|
7. **Wayland linking or audio issues**: If you see ``undefined reference to 'wl_proxy_*'`` errors or "No available audio device" errors:
|
|
|
|
.. code-block:: bash
|
|
|
|
# Both issues are typically resolved by rebuilding Dry with proper dependencies
|
|
# Make sure audio packages are installed first:
|
|
sudo dnf install alsa-lib-devel
|
|
|
|
# Then rebuild Dry without Wayland (X11 is preferred for gaming anyway)
|
|
cd ~/git/dry
|
|
rm -rf build && mkdir build && cd build
|
|
cmake .. -DDRY_64BIT=1 -DCMAKE_BUILD_TYPE=Release -DVIDEO_WAYLAND=OFF
|
|
make -j$(nproc)
|
|
|
|
Real-World Build Experience
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
When I tested this process on Fedora, here's what actually happened:
|
|
|
|
- **Final executable**: 15MB located in ``~/git/MultiMower/build/multimower``
|
|
- **Warnings**: About 50+ compiler warnings (all safe to ignore)
|
|
- **Memory usage**: Game links against a 45MB libDry.a static library
|
|
- **Success rate**: 100% when following the exact steps above
|
|
- **Common issues**: Wayland linking errors (fixed by disabling Wayland), audio initialization failures (fixed by rebuilding Dry with audio packages installed)
|
|
- **Audio confirmation**: Working audio with Launch.wav and Explode.wav sound effects
|
|
- **Enhanced controls**: WASD movement with realistic tank acceleration
|
|
|
|
Conclusion
|
|
----------
|
|
|
|
MultiMower provides an excellent platform for learning game development and modding. The clean codebase and use of the Dry Engine make it accessible for beginners while offering enough depth for advanced modifications. Whether you're adding new mower types, creating custom game modes, or just tweaking the physics, there's plenty of room for creativity.
|
|
|
|
The build process is straightforward on Fedora when you follow these tested steps, and the Dry engine's helper scripts make dependency management painless.
|
|
|
|
Happy mowing & modding!
|
|
|
|
.. contents::
|