Skip to content

Development Guide

stphung edited this page Sep 14, 2025 · 1 revision

Development Guide

This guide covers advanced development workflows, contribution guidelines, and coding standards for the Continuum project.

🤝 Contributing to Continuum

Contribution Philosophy

Continuum welcomes contributors of all skill levels. We believe that diverse perspectives and experiences strengthen the project and help create better games for everyone.

What We Value:

  • Code Quality: Clean, well-tested, and documented code
  • Innovation: Creative solutions and technical excellence
  • Collaboration: Constructive feedback and knowledge sharing
  • Learning: Growth mindset and willingness to improve

Types of Contributions

Code Contributions:

  • New enemy types and behaviors
  • Weapon systems and power-ups
  • Visual effects and audio improvements
  • Performance optimizations
  • Bug fixes and quality improvements

Non-Code Contributions:

  • Documentation improvements
  • Bug reports and issue identification
  • Game balance suggestions
  • User experience feedback
  • Community support and moderation

Contribution Workflow

1. Planning Your Contribution

Before Starting:

  1. Check existing issues and pull requests
  2. Open a discussion issue for major features
  3. Review the Architecture to understand system design
  4. Familiarize yourself with the codebase structure

Feature Planning:

  • Describe the problem your contribution solves
  • Outline the technical approach
  • Consider impact on existing systems
  • Plan for comprehensive testing

2. Development Process

Setup Development Environment:

# Fork and clone the repository
git clone https://github.com/yourusername/continuum.git
cd continuum

# Install dependencies
pip install scons pre-commit
pre-commit install
./plug.gd install

# Verify setup
scons test

Create Feature Branch:

# Use descriptive branch names
git checkout -b feature/enemy-boss-system
git checkout -b fix/audio-memory-leak
git checkout -b docs/architecture-update

3. Quality Standards

Before Submitting:

  • All tests pass (scons test)
  • Code follows project style guidelines
  • New features include comprehensive tests
  • Documentation updated for API changes
  • Performance impact assessed and optimized

📋 Coding Standards

GDScript Style Guidelines

Naming Conventions

# Variables and functions: snake_case
var player_health = 100
var current_weapon_level = 1

func calculate_damage(base_damage: int) -> int:
    return base_damage * weapon_multiplier

# Constants: SCREAMING_SNAKE_CASE
const MAX_WEAPON_LEVEL = 5
const DEFAULT_PLAYER_SPEED = 300.0

# Classes and scenes: PascalCase
class_name EnemyManager
# Files: PascalCase.gd or snake_case.gd (prefer PascalCase for classes)

Code Organization

# Order of elements in GDScript files:
# 1. class_name declaration
# 2. extends declaration
# 3. Constants
# 4. @export variables
# 5. Public variables
# 6. Private variables
# 7. @onready variables
# 8. Built-in methods (_ready, _process, etc.)
# 9. Public methods
# 10. Private methods
# 11. Signal handlers

class_name Player
extends CharacterBody2D

const MAX_SPEED = 400.0
const ACCELERATION = 1500.0

@export var weapon_damage: int = 10
@export var health: int = 100

var current_weapon_type: WeaponType
var _velocity_buffer: Vector2

@onready var sprite: Sprite2D = $Sprite2D
@onready var collision_shape: CollisionShape2D = $CollisionShape2D

func _ready():
    # Initialization code
    pass

func _physics_process(delta):
    # Physics updates
    pass

func take_damage(amount: int) -> void:
    # Public method implementation
    pass

func _update_weapon_visuals() -> void:
    # Private method implementation
    pass

func _on_area_entered(area: Area2D) -> void:
    # Signal handler implementation
    pass

Documentation Standards

## Player controller managing movement, combat, and health systems.
##
## The Player class handles all player-related functionality including:
## - WASD/Arrow key movement with boundary constraints
## - Dual weapon system (Vulcan and Laser)
## - Health management with invulnerability frames
## - Power-up collection and weapon progression
class_name Player
extends CharacterBody2D

## Fires the current weapon in the specified direction.
##
## Creates projectile instances based on the current weapon type and level.
## Automatically handles audio feedback and visual effects.
##
## @param direction: Normalized vector indicating fire direction
## @param weapon_type: Type of weapon to fire (Vulcan or Laser)
func fire_weapon(direction: Vector2, weapon_type: WeaponType) -> void:
    # Implementation
    pass

Architecture Guidelines

Singleton Pattern Usage

# Autoloaded singletons should follow this pattern:
extends Node

# Private state variables
var _active_sounds: Dictionary = {}
var _audio_players: Dictionary = {}

func _ready():
    # Initialize singleton state
    _setup_audio_players()

# Public interface methods
func play_sound(sound_name: String, volume: float = 0.0) -> void:
    # Implementation
    pass

# Private helper methods
func _setup_audio_players() -> void:
    # Implementation
    pass

Signal-Based Communication

# Define signals at the top of the class
signal health_changed(new_health: int)
signal weapon_upgraded(weapon_type: WeaponType, new_level: int)
signal player_died

# Connect signals in _ready()
func _ready():
    player.health_changed.connect(_on_player_health_changed)

# Use descriptive signal handler names
func _on_player_health_changed(new_health: int) -> void:
    health_bar.update_display(new_health)

Error Handling Patterns

func load_weapon_config(config_path: String) -> Dictionary:
    if not FileAccess.file_exists(config_path):
        push_error("Weapon config file not found: " + config_path)
        return {}

    var file = FileAccess.open(config_path, FileAccess.READ)
    if file == null:
        push_error("Failed to open weapon config: " + config_path)
        return {}

    var json_string = file.get_as_text()
    file.close()

    var json = JSON.new()
    var parse_result = json.parse(json_string)
    if parse_result != OK:
        push_error("Invalid JSON in weapon config: " + config_path)
        return {}

    return json.data

🧪 Testing Guidelines

Test Organization

Test Structure

test/
├── unit/                   # Component isolation tests
│   ├── test_player.gd     # Player class unit tests
│   ├── test_weapons.gd    # Weapon system tests
│   └── test_enemies.gd    # Enemy behavior tests
├── integration/           # System interaction tests
│   ├── test_combat.gd     # Combat system integration
│   └── test_progression.gd # Wave progression tests
├── scene/                 # End-to-end scene tests
│   └── test_gameplay.gd   # Full gameplay scenario tests
└── helpers/               # Testing utilities
    ├── test_helpers.gd    # Common test utilities
    └── mock_objects.gd    # Mock implementations

Test Writing Patterns

Unit Test Example:

extends GdUnitTestSuite

var player: Player

func before_test():
    player = preload("res://scenes/player/Player.tscn").instantiate()
    add_child(player)

func after_test():
    if is_instance_valid(player):
        player.queue_free()

func test_weapon_upgrade_increases_damage():
    # Given
    var initial_damage = player.get_weapon_damage()

    # When
    player.upgrade_weapon()

    # Then
    assert_that(player.get_weapon_damage()).is_greater_than(initial_damage)

func test_player_takes_damage_correctly():
    # Given
    var initial_health = player.health
    var damage_amount = 25

    # When
    player.take_damage(damage_amount)

    # Then
    assert_that(player.health).is_equal(initial_health - damage_amount)

Integration Test Example:

extends GdUnitTestSuite

var game_scene: Node
var player: Player
var enemy_manager: EnemyManager

func before_test():
    game_scene = preload("res://scenes/main/Game.tscn").instantiate()
    add_child(game_scene)
    player = game_scene.get_node("Player")
    enemy_manager = EnemyManager

func test_enemy_wave_completion_triggers_next_wave():
    # Given
    var initial_wave = enemy_manager.current_wave
    enemy_manager.spawn_wave()

    # When - Simulate destroying all enemies
    var enemies = get_tree().get_nodes_in_group("enemies")
    for enemy in enemies:
        enemy.take_damage(1000)  # Overkill damage

    await get_tree().process_frame  # Wait for cleanup

    # Then
    assert_that(enemy_manager.current_wave).is_equal(initial_wave + 1)

Test Quality Standards

What to Test

  • Public API behavior: Focus on observable outcomes
  • Business logic: Game rules and mechanics
  • Error conditions: Invalid inputs and edge cases
  • Integration points: System interactions

What NOT to Test

  • Implementation details: Private methods and internal state
  • Framework behavior: Godot engine functionality
  • Performance specifics: Frame rates or memory usage (use benchmarks instead)

Test Assertions Best Practices

# Good: Tests behavior, not implementation
assert_that(player.is_alive()).is_true()
assert_that(enemy.is_defeated()).is_true()

# Bad: Tests internal state
assert_that(player._health).is_greater_than(0)
assert_that(enemy._state).is_equal(Enemy.State.DESTROYED)

# Good: Descriptive test names
func test_laser_pierces_through_multiple_enemies():
    pass

func test_vulcan_weapon_spreads_damage_to_nearby_targets():
    pass

# Bad: Vague test names
func test_weapons():
    pass

func test_combat_system():
    pass

🚀 Performance Guidelines

Performance Targets

Frame Rate: Consistent 60 FPS during normal gameplay Memory Usage: Stable memory consumption without leaks Load Times: Scene transitions under 200ms Responsiveness: Input lag under 16ms (1 frame)

Optimization Techniques

Object Pooling

# Bullet pool manager example
class_name BulletPool
extends Node

var bullet_pool: Array[Bullet] = []
var pool_size: int = 100

func _ready():
    # Pre-allocate bullet instances
    for i in pool_size:
        var bullet = preload("res://scenes/projectiles/Bullet.tscn").instantiate()
        bullet.set_process(false)
        bullet.visible = false
        add_child(bullet)
        bullet_pool.append(bullet)

func get_bullet() -> Bullet:
    for bullet in bullet_pool:
        if not bullet.visible:  # Available for use
            bullet.set_process(true)
            bullet.visible = true
            return bullet

    # Pool exhausted - create temporary bullet
    push_warning("Bullet pool exhausted, creating temporary instance")
    return preload("res://scenes/projectiles/Bullet.tscn").instantiate()

func return_bullet(bullet: Bullet) -> void:
    bullet.set_process(false)
    bullet.visible = false
    bullet.position = Vector2.ZERO
    bullet.velocity = Vector2.ZERO

Memory Management

# Proper resource cleanup
func _exit_tree():
    # Clean up references to prevent memory leaks
    if audio_stream:
        audio_stream.data.clear()

    # Disconnect signals to prevent reference cycles
    if signal_connection.is_valid():
        signal_connection.disconnect()

# Use weak references for optional dependencies
@onready var optional_component: WeakRef = weakref(get_node_or_null("OptionalComponent"))

func use_optional_component():
    var component = optional_component.get_ref()
    if component:
        component.do_something()

Collision Optimization

# Use groups for efficient collision detection
func _ready():
    add_to_group("enemies")
    add_to_group("damageable")

# Efficient group-based queries
func check_for_player_collision():
    var players = get_tree().get_nodes_in_group("players")
    for player in players:
        if global_position.distance_to(player.global_position) < collision_radius:
            handle_player_collision(player)

Profiling and Debugging

Performance Monitoring

# Built-in performance monitoring
func _ready():
    # Enable performance monitoring in debug builds
    if OS.is_debug_build():
        Performance.set_monitor_enabled(Performance.TIME_FPS, true)
        Performance.set_monitor_enabled(Performance.MEMORY_STATIC, true)

func _process(_delta):
    # Log performance warnings
    var fps = Performance.get_monitor(Performance.TIME_FPS)
    if fps < 55.0:
        push_warning("Low FPS detected: " + str(fps))

Debug Information

# Debug-only information display
func _draw():
    if OS.is_debug_build():
        # Draw collision boundaries
        draw_circle(Vector2.ZERO, collision_radius, Color.RED, false, 2.0)

        # Display state information
        var font = ThemeDB.fallback_font
        draw_string(font, Vector2(0, -50), "State: " + str(current_state))

📦 Release Guidelines

Version Management

Semantic Versioning: Use MAJOR.MINOR.PATCH format

  • MAJOR: Breaking changes or major feature overhauls
  • MINOR: New features and significant improvements
  • PATCH: Bug fixes and minor improvements

Release Process

1. Pre-Release Checklist

  • All tests pass (scons test)
  • Performance benchmarks meet targets
  • Documentation updated for new features
  • Breaking changes documented
  • Release notes prepared

2. Build Process

# Clean previous builds
scons clean-build

# Run comprehensive validation
scons validate

# Build release versions for all platforms
scons build-release platform=all

# Package for distribution
scons package-release

3. Quality Assurance

  • Manual gameplay testing on target platforms
  • Performance profiling under load
  • Memory leak detection during extended play
  • Accessibility testing for various user needs

Community Guidelines

Code Review Process

Pull Request Requirements:

  • Descriptive title and detailed description
  • Link to related issues or discussions
  • Screenshots/videos for visual changes
  • Performance impact assessment

Review Criteria:

  • Code quality and adherence to standards
  • Test coverage and quality
  • Documentation completeness
  • Integration with existing systems

Communication Standards

Issue Reporting:

  • Use issue templates for bug reports and feature requests
  • Provide reproduction steps for bugs
  • Include system information and logs
  • Tag issues appropriately

Discussion Etiquette:

  • Be respectful and constructive
  • Focus on technical merits
  • Provide specific examples and suggestions
  • Welcome newcomers and different perspectives

This development guide ensures that Continuum maintains high quality standards while remaining accessible to contributors of all experience levels.