Block System¶
The Block system in Evoker Engine provides a flexible framework for creating voxel-based blocks with properties, states, and behaviors similar to Minecraft.
Overview¶
The block system includes:
- Blocks: Base objects with properties like hardness, light level, and transparency
- Block States: Dynamic properties (facing direction, powered state, etc.)
- Block Registry: Central management for all block types
- Block Events: Callbacks for placement, breaking, interaction, and updates
- Drop System: Configurable item drops when blocks are broken
Block Class¶
public abstract class Block
{
public ResourceKey Id { get; set; } // namespace:key identifier
public string Name { get; set; } // Display name
public float Hardness { get; set; } // Mining time multiplier
public float Resistance { get; set; } // Explosion resistance
public int LightLevel { get; set; } // Light emitted (0-15)
public bool IsSolid { get; set; } // Blocks movement
public bool IsTransparent { get; set; } // Light passes through
public bool IsBreakable { get; set; } // Can be destroyed
public string ModelId { get; set; } // Texture/model ID
public Dictionary<string, object> Properties { get; set; }
public Dictionary<string, string> States { get; set; }
public virtual void OnPlaced(Vector3 position, object? context = null);
public virtual void OnBroken(Vector3 position, object? context = null);
public virtual bool OnInteract(Vector3 position, object? context = null);
public virtual void OnUpdate(Vector3 position, float deltaTime);
public virtual List<(ResourceKey itemId, int quantity)> GetDrops(object? context = null);
}
Creating Blocks¶
Simple Block¶
using EvokerEngine.Blocks;
using EvokerEngine.Core;
public class StoneBlock : Block
{
public StoneBlock()
{
Id = new ResourceKey("game", "stone");
Name = "Stone";
Hardness = 1.5f;
Resistance = 6.0f;
IsSolid = true;
IsTransparent = false;
ModelId = "block/stone";
}
}
Block with Light¶
public class TorchBlock : Block
{
public TorchBlock()
{
Id = new ResourceKey("game", "torch");
Name = "Torch";
Hardness = 0f; // Instantly breakable
Resistance = 0f;
IsSolid = false;
IsTransparent = true;
LightLevel = 14; // Very bright
ModelId = "block/torch";
}
}
Block with States¶
public class DoorBlock : Block
{
public DoorBlock()
{
Id = new ResourceKey("game", "door");
Name = "Wooden Door";
Hardness = 3.0f;
IsSolid = true;
// Initial states
States["open"] = "false";
States["facing"] = "north";
States["half"] = "lower";
}
public override bool OnInteract(Vector3 position, object? context = null)
{
// Toggle door state
bool isOpen = States["open"] == "true";
States["open"] = (!isOpen).ToString().ToLower();
Logger.Info($"Door {(isOpen ? "closed" : "opened")}");
return true;
}
}
Block with Custom Behavior¶
public class TNTBlock : Block
{
public TNTBlock()
{
Id = new ResourceKey("game", "tnt");
Name = "TNT";
Hardness = 0f;
Resistance = 0f; // Weak to explosions
}
public override void OnBroken(Vector3 position, object? context = null)
{
// Explode when broken
Explode(position, radius: 4f);
}
public override bool OnInteract(Vector3 position, object? context = null)
{
// Ignite with flint and steel
StartTimer(position, fuseDuration: 4f);
return true;
}
private void Explode(Vector3 position, float radius)
{
Logger.Info($"TNT exploded at {position}!");
// Explosion logic...
}
private void StartTimer(Vector3 position, float fuseDuration)
{
Logger.Info("TNT fuse lit!");
// Timer logic...
}
}
Block with Custom Drops¶
public class OreBlock : Block
{
public ResourceKey DropItemId { get; set; }
public int MinDrops { get; set; } = 1;
public int MaxDrops { get; set; } = 1;
public OreBlock(string name, ResourceKey dropItem)
{
Id = new ResourceKey("game", name.ToLower().Replace(" ", "_"));
Name = name;
Hardness = 3.0f;
DropItemId = dropItem;
}
public override List<(ResourceKey itemId, int quantity)> GetDrops(object? context = null)
{
var amount = Random.Shared.Next(MinDrops, MaxDrops + 1);
return new List<(ResourceKey, int)> { (DropItemId, amount) };
}
}
// Usage
var diamondOre = new OreBlock("Diamond Ore", new ResourceKey("game", "diamond"))
{
MinDrops = 1,
MaxDrops = 1,
Hardness = 3.0f
};
Block Registry¶
Central management for all blocks.
public class BlockRegistry
{
public static BlockRegistry Instance { get; }
public void Register(Block block);
public void RegisterAll(params Block[] blocks);
public Block? Get(ResourceKey id);
public Block? Get(string id);
public IEnumerable<Block> GetAll();
public bool IsRegistered(ResourceKey id);
}
Registering Blocks¶
using EvokerEngine.Blocks;
// Register single block
var stone = new StoneBlock();
BlockRegistry.Instance.Register(stone);
// Register multiple blocks
BlockRegistry.Instance.RegisterAll(
new StoneBlock(),
new DirtBlock(),
new GrassBlock(),
new TorchBlock()
);
// Get registered block
var block = BlockRegistry.Instance.Get("game:stone");
if (block != null)
{
Logger.Info($"Found block: {block.Name}");
}
Block Properties¶
Custom Properties¶
var furnace = new Block
{
Id = new ResourceKey("game", "furnace"),
Name = "Furnace"
};
// Add custom properties
furnace.Properties["cookTime"] = 10f;
furnace.Properties["fuelEfficiency"] = 1.5f;
furnace.Properties["maxHeat"] = 1000;
// Access properties
if (furnace.Properties.TryGetValue("cookTime", out var cookTime))
{
float time = (float)cookTime;
}
Block States¶
Dynamic properties that can change at runtime.
// Set initial states
var lever = new Block
{
Id = new ResourceKey("game", "lever"),
Name = "Lever"
};
lever.States["powered"] = "false";
lever.States["facing"] = "north";
// Change state
lever.States["powered"] = "true";
// Query state
bool isPowered = lever.States.TryGetValue("powered", out var state) &&
state == "true";
Complete Examples¶
Example 1: Farmland System¶
public class FarmlandBlock : Block
{
public FarmlandBlock()
{
Id = new ResourceKey("game", "farmland");
Name = "Farmland";
Hardness = 0.6f;
IsSolid = true;
States["moisture"] = "0"; // 0-7, 7 = fully wet
}
public override void OnUpdate(Vector3 position, float deltaTime)
{
// Dry out over time if no water nearby
int moisture = int.Parse(States["moisture"]);
if (moisture > 0 && !IsWaterNearby(position))
{
moisture--;
States["moisture"] = moisture.ToString();
if (moisture == 0)
{
// Turn back to dirt
ConvertToDirt(position);
}
}
}
public override void OnPlaced(Vector3 position, object? context = null)
{
// Check for water nearby
if (IsWaterNearby(position))
{
States["moisture"] = "7";
}
}
private bool IsWaterNearby(Vector3 position)
{
// Check 4-block radius for water
// Implementation...
return false;
}
private void ConvertToDirt(Vector3 position)
{
Logger.Info("Farmland dried out, converting to dirt");
// Replace block...
}
}
Example 2: Redstone-like System¶
public class RedstoneLampBlock : Block
{
public RedstoneLampBlock()
{
Id = new ResourceKey("game", "redstone_lamp");
Name = "Redstone Lamp";
States["lit"] = "false";
}
public void SetPowered(bool powered)
{
bool wasLit = States["lit"] == "true";
States["lit"] = powered.ToString().ToLower();
if (powered != wasLit)
{
LightLevel = powered ? 15 : 0;
Logger.Info($"Lamp {(powered ? "on" : "off")}");
}
}
}
public class LeverBlock : Block
{
public LeverBlock()
{
Id = new ResourceKey("game", "lever");
Name = "Lever";
States["powered"] = "false";
}
public override bool OnInteract(Vector3 position, object? context = null)
{
bool isPowered = States["powered"] == "true";
States["powered"] = (!isPowered).ToString().ToLower();
// Notify connected blocks
UpdateConnectedBlocks(position, !isPowered);
return true;
}
private void UpdateConnectedBlocks(Vector3 position, bool powered)
{
// Find and update connected redstone lamps
// Implementation...
}
}
Example 3: Growth System¶
public class CropBlock : Block
{
public int MaxGrowthStage { get; set; } = 7;
public float GrowthTime { get; set; } = 60f; // seconds
public CropBlock(string cropName)
{
Id = new ResourceKey("game", cropName);
Name = cropName;
IsSolid = false;
IsTransparent = true;
States["age"] = "0";
}
public override void OnUpdate(Vector3 position, float deltaTime)
{
int age = int.Parse(States["age"]);
if (age < MaxGrowthStage)
{
// Random growth chance
if (Random.Shared.NextDouble() < deltaTime / GrowthTime)
{
age++;
States["age"] = age.ToString();
if (age == MaxGrowthStage)
{
Logger.Info($"{Name} fully grown!");
}
}
}
}
public override List<(ResourceKey itemId, int quantity)> GetDrops(object? context = null)
{
int age = int.Parse(States["age"]);
if (age == MaxGrowthStage)
{
// Fully grown - drop crops
return new List<(ResourceKey, int)>
{
(new ResourceKey("game", $"{Id.Key}_item"), Random.Shared.Next(2, 5)),
(new ResourceKey("game", $"{Id.Key}_seeds"), Random.Shared.Next(1, 3))
};
}
else
{
// Not grown - just drop seeds
return new List<(ResourceKey, int)>
{
(new ResourceKey("game", $"{Id.Key}_seeds"), 1)
};
}
}
}
Best Practices¶
✅ Do's¶
- Use ResourceKey format (namespace:key) for block IDs
- Set appropriate hardness and resistance values
- Use block states for dynamic properties
- Implement GetDrops() for custom loot
- Call base methods when overriding
❌ Don'ts¶
- Don't use spaces or special characters in IDs
- Don't make all blocks indestructible (IsBreakable = false)
- Don't forget to register blocks before using them
- Don't perform heavy computation in OnUpdate()
See Also¶
- Inventory - Item system for drops
- Crafting - Block crafting recipes
- ResourceKey - ID system