All code must follow these standards to maintain consistency and quality across the Basebound codebase.
See architecture.md for detailed component architecture. All game behavior must inherit from Component.
- Classes:
PascalCasePlayerController,EconomySystem,RaidManager
- Methods:
PascalCaseOnPlayerSpawn(),CalculateReward(),PlaceBlock()
- Properties:
PascalCaseHealth,IsAlive,CurrentBalance
- Fields:
_camelCase(private)_localCache,_spawnPoints,_playerData
- Constants:
UPPER_SNAKE_CASEMAX_PLAYERS,TICK_RATE,MIN_RAID_DURATION
- Parameters:
camelCaseplayerCount,blockType,rewardAmount
// Use sealed unless inheritance is required
public sealed class MyGameSystem : Component
{
// Fields first (private)
private int _internalValue;
// Properties (public, exposed to inspector)
[Property] public string ConfigValue { get; set; } = "default";
[Property] public int MaxCount { get; set; } = 100;
// Dependencies
[RequireComponent] private OtherComponent Dependency { get; set; }
// Lifecycle methods
protected override void OnAwake() { }
protected override void OnStart() { }
protected override void OnUpdate() { }
protected override void OnFixedUpdate() { }
protected override void OnDestroy() { }
// Public methods
public void PublicMethod() { }
// Private methods
private void PrivateHelper() { }
}- Expose configuration to inspector with
[Property] - Use auto-properties where possible:
public int Value { get; set; } - Initialize properties with meaningful defaults
- Use
[RequireComponent]for auto-linking dependencies
// Keep methods focused and single-responsibility
public void ProcessPlayerAction(PlayerAction action)
{
if (!IsValidAction(action)) return;
ApplyAction(action);
BroadcastUpdate(action);
}
// Use descriptive names that explain intent
private bool IsValidAction(PlayerAction action) => action != null && action.IsEnabled;
// Document public methods
/// <summary>
/// Calculates the reward for a completed contract based on difficulty and skill.
/// </summary>
/// <param name="contract">The contract to evaluate</param>
/// <returns>Reward in currency</returns>
public int CalculateReward(Contract contract)
{
return contract.BaseDifficulty * contract.SkillMultiplier;
}// Bad: Only works for local player, ignores proxies
if (Input.Down("attack")) Fire();
// Good: Explicitly check if controlled by this client
if (!IsProxy && Input.Down("attack"))
{
Fire();
}
// Good: Broadcast to all clients
[Rpc.Broadcast]
public void OnPlayerJump() => PlayJumpAnimation();// Server-side validation before broadcasting
[Rpc.Broadcast]
public void PlaceBlock(Vector3 position, int blockType)
{
if (!IsProxy) // Only server validates
{
if (!IsValidPlacement(position, blockType)) return;
DeductCurrency(BLOCK_COST);
}
// All clients execute this to update their view
SpawnBlockVisual(position, blockType);
}Add XML comments to all public classes and methods:
/// <summary>
/// Manages player economy including currency and transactions.
/// </summary>
public sealed class EconomySystem : Component
{
/// <summary>
/// Gets the current balance for a player.
/// </summary>
/// <param name="playerId">The player's unique identifier</param>
/// <returns>Current balance in currency units</returns>
public int GetBalance(int playerId) { }
}- Keep comments concise and explain why, not what
- Good comment:
// Skip proxy objects to avoid duplicate processing - Avoid obvious comments:
// Increment counter(this is clear from code)
When adding new systems or significant changes:
- Update architecture.md if changing component structure
- Update networking.md if adding networked features
- Update CONTRIBUTING.md for development workflow changes
- Keep README.md overview current
// Validate inputs early
public void SetPlayerLimit(int limit)
{
if (limit < 1 || limit > MAX_PLAYERS)
{
Log.Error($"Invalid player limit: {limit}");
return;
}
_playerLimit = limit;
}
// Use guard clauses for early returns
protected override void OnFixedUpdate()
{
if (IsProxy) return;
if (!IsEnabled) return;
if (Players.Count == 0) return;
// Main logic here
}// Cache expensive lookups
private GameObject[] _cachedPlayers;
protected override void OnStart()
{
_cachedPlayers = GetAllPlayers(); // Cache once
}
// Avoid LINQ in hot paths (OnFixedUpdate, OnUpdate)
// Bad in OnFixedUpdate:
foreach (var player in Players.Where(p => p.IsAlive))
// Good in OnFixedUpdate:
foreach (var player in _cachedPlayers)
{
if (!player.IsAlive) continue;
}
// Use appropriate collections
// Dictionary for O(1) lookups: playerId -> PlayerData
// List for O(n) iteration: players to update each frameSee CONTRIBUTING.md for commit message guidelines.
- CONTRIBUTING.md - Full contribution guidelines
- architecture.md - Component pattern details
- networking.md - Multiplayer implementation