Bomber
Overview
The Quantum Bomber sample is provided with full source code and demonstrates how to build bomberman-like gameplay in Quantum.
Download
Version | Release Date | Download | |
---|---|---|---|
2.1.9 | Sep 23, 2024 | Quantum Bomber 2.1.9 Build 516 |
Before You Start
To run the sample in online multiplayer mode, first create a Quantum AppId in the PhotonEngine Dashboard and paste it into the AppId
field in PhotonServerSettings
asset.
Then load the Menu
scene in the Scenes menu and press Play
.
Technical Info
- Unity: 2021.3.13f1 or higher
- Platforms: PC (Windows)
Highlights
Technical
- Custom
Movement
component and system tailored to top-down grid based games - Semi-procedural generation of map and power-up spawning
- Character customization
- Time-based explosion spread
- Zero-event simulation approach
- Relatively puristic ECS simulation architecture
Gameplay
- Battle royale bomber
- Place bomb
- Power-ups in the form of modifiers for bomb amount, explosion reach and movement speed.
Controls
- WASD for Movement
- Space to place bombs
Cell
The Cell
is a DSL defined struct which holds a single value in the form of the CellType
flag. To access and modify these values more conveniently, the struct has been extended with properties and methods in the Cell.User.cs
script.
Each cell is aware of what is the type of object currently at its position, but it does not have any reference to the entities themselves. When a system iterates over an Entity, it checks whether there is anything of interest in the cell. For instance, if the cell IsBurning
, then it will trigger the appropriate response on the component of which it is in charge.
Since CellType
is a flag, all properties can be constructed as bitwise operations which makes them very efficient.
Grid
The Grid
is a regular CSharp class containing the GridSettings
struct, an array of Cell
structs and a pointer.
Frame.User
By having the Grid
defined as a regular class, and having an instance thereof in Frame.User
it is possible to both make it available through the regular Frame.XYZ
Api while encapsulating all the relevant methods in Frame.Grid.XYZ
.
The downside of the Grid being a regular class is that its data resides outside of the regular DSL generated memory. It is therefore necessary to manually handle the initialization, serialization, copying, clearing and dumping of the data. This is done by hooking into the following methods inside of Frame.User
:
InitUser()
: Called once when the frame is initially created.FreeUser()
: Called when the Frame is destroyed.CopyUser()
: Called when the previous frame state is copied into the next frame.SerializeUser()
: Called when the frame is serialized (e.g. as a buddy snapshot for late joiners).DumpFrameUser()
: Called when a desync happens.
Grid Settings
The information on how to generate and setup the grid is found in the GridSettings
struct. Using the information held in there, it is possible to setup the 1D-array at runtime and populate it with the locations of fixed and destructible blocks, as well as infer the positions of the SpawnPoints
.
Cell Array aka Grid
The grid cells are contained in a 1D-array of Cells
. The principle is the same as the one as for the Tilemap Asset in the Tilemap Pathfinder Tech Sample. 1D-arrays are supported by the DSL; however, the DSL requires the size of the array to be known at compile time, since the grid is generated at runtime based on the size requested by the players it has to be handled differently.
The array is created at runtime by hooking into the InitUser()
method in Frame.User
.
The 1D-array inside of the Grid
is made compatible with predict-rollback and late-join serialization by hooking into the CopyUser()
and SerializeUser()
methods inside of Frame.User
.
Performance
Despite a Cell
only containing a byte-flag, the amount of read and writes done by copying the frame can create significant performance bottleneck. To prevent this, several get-set methods for the grid and its cells have been defined in Grid.cs
. These methods user pointer math to return pointers to cells, thus allows to both read and write directly to the struct.
Broadphase Set and Clear
There are two systems which write memory to the grid:
SetBroadphaseSystem
: runs before all gameplay systems and sets the cell type based on the entities it currently contains.ClearBroadphaseSystem
: runs after all gameplay systems and clears the information in the cells for entities about to be destroyed.
Input
The input struct is made of 5 buttons.
C#
input {
Button MoveUp;
Button MoveDown;
Button MoveLeft;
Button MoveRight;
Button PlaceBomb;
}
This is the most condense form the input struct can take. Although the Button
type take ups 1-byte in the simulation, it is compressed to 1-bit over the wire.
To prepare the player input to be consumed by the gameplay systems, the InputSystem
constructs a Direction
using the movement buttons and sets the WantsToPlaceBomb
boolean in the AbilityPlaceBomb
based on the PlaceBomb
button value.
Bomber
The Bomber
component is a flag-component to identify and filter entities.
As such the associated BomberSystem
is relatively simple since its only job is to check whether a bomber type entity is standing on a burning cell during a given Update()
.
C#
public override void Update(Frame f, ref BomberFilter filter)
{
var gridPosition = filter.Transform->Position.RoundToInt(Axis.Both);
var isInvincible = false;
#if DEBUG
// Used for debugging purposes
isInvincible = f.RuntimeConfig.IsInvincible;
#endif
if (isInvincible == false && f.GetCellRef(gridPosition).IsBurning)
{
// Death animation is triggered from OnEntityDestroyed
f.Destroy(filter.Entity);
}
}
Bombs
Bombs are temporary entities which are created by a player action and results in an explosion when their Timer
expires.
There are two systems which interact with the Bomb
component:
AbilityPlaceBombSystem
: checks whether a player has pressed thePlaceBomb
button and whether the conditions are met to place one.BombSystem
: starts theTimer
on the bomb, handles chain reactions and triggers an explosion when a bomb is destroyed (either because the timer expired or another bomb's explosion touched it).
Explosion
Explosions last a certain amount of time. Therefore they need to exist as entities rather than just being a raycast. The ExplosionSystem
handles the explosion lifetime as well as the explosion spread from cell to cell.
An explosion entity is made of two components:
Explosion
: a component used as both a flag-component as well as to hold the information about the explosion config.Timer
: the timer is used to calculate the remaining time until the explosion spreads to the next adjacent cells.
Power-Ups
The power-up functionalities are provided through two main components
PowerUp
: defines a power-up by type and modifier amount; and,PowerUpManager
: holds the list of spawnable power-ups and the spawn probability.
Power-ups have a chance of spawning when a destroyable block is cleared from the grid. Upon the destruction of a block, the PowerUpManager
is informed of the newly cleared position. The PowerUpManagerSystem
iterates through the newly available locations and attemps to spawn a random power-up if the cell is empty and not currently on fire.
Movement
The movement is unrestricted inside the cells themselves and reads the grid to determine where the character is allowed to go.
Direction
Direction
is a byte-flag which contains the currently pressed movement buttons.
Movement Component
The Movement
component keeps track of the relevant movement values between frames.
FP CurrentSpeed
: the current speed at which the character is moving.FP MaxSpeed
: the maximum speed at which the character is allowed to move.Boolean IsMoving
: keeps track of whether the last movement input resulted in a move.Boolean LastMoveWasHorizontal
: keeps track of the last move's orientation.Direction LastNewInput
: keeps track of the latest input provided by the player in both vertical and horizontal directions.Direction CurrentInput
: the currently pressed movement directions.FPVector2 MoveDirection
: the last movement directionFP StartRotation
: the last look rotation prior to a direction change.FP TargetRotation
: the look rotation the character should have after a direction change.int RotationStartTick
: the tick at which a rotation towards a new direction has started.FP RotationDuration
: the maximum total duration a rotation should take.FP RotationTimeMultiplier
: the rotation speed multiplier required to achieve a full rotation within the allotted time.
Movement System
The movement system fulfills three core functionalities:
- Calculate the possible movement based on the currently pressed movement keys (
GetMovementResult()
) and return aMoveResult
struct containing the values. - Update the current movement speed (
UpdateCurrentSpeed()
) - Move the character (
UpdateMovement()
) - Rotate the character (
UpdateRotation()
)
The MovementSystem
implements two features to offer a smooth continuous diagonal movement across the grid:
CanMoveInDirection()
accounts for corner slide. This enables a character to smoothly move around a corner when a diagonal direction is being pressed.GetMoveVector()
contains a direction toggle to alternate which direction (vertical or horizontal) to check first. This allows the character to continuously move diagonally through the grid.
MoveResult
MoveResult
is a utility struct. It holds the result of processing the current Movement.CurrentInput
against the grid cells on which the character would be moving. Since these values are not used as part of the frame state, it is defined as a regular CSharp struct rather than in the DSL.
FPVector2 Direction
: the desired movement direction.FP MaxDistance
: the maximum possible distance the character can move in the desired direction.FPVector2 LookDirection
: the direction in which the character will be looking as a result of this movement.FP RotationTimeMultiplier
: the speed at which the character should switch their look direction based on the angle between its current look direction and desired look direction to complete the rotation in the pre-defined amount of time.
3rd Party Assets
The Bomber Sample includes several assets provided courtesy of their respective creators. The full packages can be acquired for your own projects at their respective site:
- Epic Toon FX by Archanor VFX
- HSV color picker for Unity UI by Judah4
IMPORTANT: To use them in a commercial project, it is required to purchase a license from the respective creators.
Back to top