voxel_engine 0.3.0-dev copy "voxel_engine: ^0.3.0-dev" to clipboard
voxel_engine: ^0.3.0-dev copied to clipboard

The pure-Dart half of the voxel kit: chunk grid, meshing, streaming, physics, world generation, block and item content, signals and a TCP net layer.

example/voxel_engine_example.dart

// The subjects of this package in one run, which is the reason they are one
// package: content declares the blocks, worldgen builds a world out of the same
// ids, core generates, meshes and casts a ray into it, and content mines what
// the ray hit into a bag.
//
//   dart run example/voxel_engine_example.dart
//
// Each subject also has an example of its own beside this one, signals and net
// included.
import 'dart:typed_data';

import 'package:vector_math/vector_math.dart';
import 'package:voxel_engine/content.dart';
import 'package:voxel_engine/core.dart';
import 'package:voxel_engine/worldgen.dart';

/// 1. The blocks. Air is block 0; the order numbers them for the engine.
final BlockRegistry<BlockType> blocks = BlockRegistry(const [
  BlockType('air', color: 0, solid: false, hardness: -1, drop: ''),
  BlockType('stone', color: 0x7F7F84, hardness: 1.5, tool: 'pickaxe', tier: 1, drop: 'cobblestone'),
  BlockType('cobblestone', color: 0x6E6E70, hardness: 2.0, tool: 'pickaxe'),
  BlockType('dirt', color: 0x8A5E3B, hardness: 0.5, tool: 'shovel'),
  BlockType('grass', color: 0x5C9E3A, hardness: 0.6, tool: 'shovel', drop: 'dirt'),
  BlockType('sand', color: 0xDCCB8A, hardness: 0.5, tool: 'shovel'),
  BlockType('log', color: 0x6B4F2A, hardness: 2.0, tool: 'axe'),
  BlockType('leaves', color: 0x3F8A2E, hardness: 0.2, opaque: false),
  BlockType('coal_ore', color: 0x3A3A3E, hardness: 3.0, tool: 'pickaxe', tier: 1, drop: 'coal'),
  BlockType.liquid('water', color: 0x3366CC),
]);

/// Everything you can hold: a block each, plus the items that are not blocks.
final ItemRegistry<ItemType> items = ItemRegistry([
  ...ItemRegistry.forBlocks(blocks),
  const ItemType('coal', color: 0x202020),
  const ItemType('wooden_pickaxe', color: 0xB08850, tool: 'pickaxe', tier: 1, stack: 1, durability: 60, damage: 2),
]);

/// 2. The world, written in the ids the registry above numbered.
const WorldGenSpec world = WorldGenSpec(
  biomes: [
    Biome(
      'forest',
      top: 'grass',
      under: 'dirt',
      climate: Climate.wet,
      trees: [TreeSpec.oak(log: 'log', leaves: 'leaves')],
      treeChance: 90,
    ),
    Biome(
      'plains',
      top: 'grass',
      under: 'dirt',
      trees: [TreeSpec.oak(log: 'log', leaves: 'leaves')],
      treeChance: 12,
    ),
  ],
  beach: Biome('beach', top: 'sand'),
  ores: [Ore('coal_ore', share: 0.11)],
);

/// The generated chunks, as core's rays and physics read a world: whole chunks
/// on demand, kept in a map. A game streams them on isolates instead — that is
/// `ChunkStreamer`, in `core_example.dart`.
class GeneratedWorld implements VoxelQuery {
  /// A world generated by [generator], whose blocks [table] describes.
  GeneratedWorld(this.generator, this.table);

  /// Fills a chunk from the spec.
  final ChunkGenerator generator;

  @override
  final VoxelBlockTable table;

  final Map<ChunkPos, Uint8List> _chunks = {};

  /// Chunk [pos], generated the first time it is asked for.
  Uint8List chunkAt(ChunkPos pos) => _chunks.putIfAbsent(pos, () => generator.generateIn(pos.x, pos.z, 0));

  @override
  int getBlockXYZ(int x, int y, int z) {
    if (y < 0 || y >= ChunkSize.sizeY) return 0;
    final c = ChunkStreamer.chunkOfXZ(x, z);
    return chunkAt(c)[ChunkSize.index(x - c.x * ChunkSize.sizeX, y, z - c.z * ChunkSize.sizeZ)];
  }
}

void main() {
  // The spec is compiled against the registry's numbering: one id table, two
  // subjects.
  final generator = world.compile(blocks.ids, 2024);
  final voxels = GeneratedWorld(generator, blocks.table);

  // 3. Meshing a chunk needs its eight neighbours, the way a worker isolate
  // gets them, so that a face on a border knows whether it is hidden.
  final mesh = blocks.table.mesher().build(0, 0, [for (final offset in ChunkStreamer.ring) voxels.chunkAt(offset)]);
  print('chunk (0, 0) meshed into ${mesh.faces} faces in ${mesh.ms.toStringAsFixed(1)} ms');

  // 4. A ray straight down from the sky finds the ground.
  final hit = VoxelRaycast.solid(voxels, Vector3(12.5, 120, 3.5), Vector3(0, -1, 0), 200)!;
  final top = voxels.getBlockXYZ(hit.block.x, hit.block.y, hit.block.z);
  print(
    'a ray down at (12, 3) lands on ${blocks.idOf(top)} at ${hit.block}, '
    'in the ${generator.biomeAt(12, 3).name}',
  );

  // 5. Breaking it is content again: the tool decides the time and whether the
  // block breaks at all, and the drop is not always the block.
  const rules = MiningRules();
  final pick = items['wooden_pickaxe'];
  final deep = voxels.getBlockXYZ(hit.block.x, hit.block.y - 6, hit.block.z);
  for (final id in [top, deep]) {
    print(
      '${blocks.idOf(id)}: ${rules.mineTime(blocks[id], null).toStringAsFixed(2)} s by hand, '
      '${rules.mineTime(blocks[id], pick).toStringAsFixed(2)} s with a wooden pickaxe '
      '(-1 meaning never), drops ${blocks.dropOf(id)}',
    );
  }

  // 6. The drops go in a bag that knows stack sizes and wears the tool down.
  final bag = Inventory(stackSize: (id) => items[id].stack, maxDurability: (id) => items[id].durability)
    ..add('wooden_pickaxe', 1)
    ..add(blocks.dropOf(top), 1)
    ..add(blocks.dropOf(deep), 1);
  bag.wear(bag.find('wooden_pickaxe'), 1);
  print(
    'the bag holds ${blocks.dropOf(top)} and ${blocks.dropOf(deep)}; '
    'the pickaxe has ${bag.durAt(bag.find('wooden_pickaxe'))} uses left',
  );
}
1
likes
160
points
326
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

The pure-Dart half of the voxel kit: chunk grid, meshing, streaming, physics, world generation, block and item content, signals and a TCP net layer.

Homepage
Repository (GitHub)
View/report issues

Topics

#voxel #game-engine #procedural-generation #physics #networking

License

MIT (license)

Dependencies

meta, vector_math

More

Packages that depend on voxel_engine