-
Notifications
You must be signed in to change notification settings - Fork 0
Development Guide
This guide covers advanced development workflows, contribution guidelines, and coding standards for the Continuum project.
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
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
Before Starting:
- Check existing issues and pull requests
- Open a discussion issue for major features
- Review the Architecture to understand system design
- 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
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 testCreate 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-updateBefore 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
# 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)# 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## 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# 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# 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)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.datatest/
├── 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
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)- Public API behavior: Focus on observable outcomes
- Business logic: Game rules and mechanics
- Error conditions: Invalid inputs and edge cases
- Integration points: System interactions
- Implementation details: Private methods and internal state
- Framework behavior: Godot engine functionality
- Performance specifics: Frame rates or memory usage (use benchmarks instead)
# 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():
passFrame 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)
# 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# 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()# 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)# 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-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))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
- All tests pass (
scons test) - Performance benchmarks meet targets
- Documentation updated for new features
- Breaking changes documented
- Release notes prepared
# 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- Manual gameplay testing on target platforms
- Performance profiling under load
- Memory leak detection during extended play
- Accessibility testing for various user needs
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
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.