Only chunks that are close to players are sent to them, as well as actions occuring in those chunks. - Add Chunk and ChunkLayer classes / nodes In the future, chunks could make use of multiple layers such as terrain, building, interior, liquids, pipes and wires, ... - Add PlayerVisibilityTracker Used on the server to keep track of which chunks a player can see, firing events when chunks go in or out of range. - Add RPC extension methods which accept an IEnumerable<int> for players to send to - Add GetPlayersTracking extension method - Use GlobalPosition instead of Position where needed - Update WorldSave to save Chunks (Incremented WorldSave.LATEST_VERSION) - Add methods to BlockPos relating to chunk position - Improve upon Facing with BlockPos.Add/Subtract - Add generic GetChildren extension method - Add GetOrCreateChild extension methodmain
parent
4aeb380aa8
commit
7c4ae2ce45
20 changed files with 396 additions and 161 deletions
@ -0,0 +1,72 @@ |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using Godot; |
||||
|
||||
public class Chunk : Node2D |
||||
{ |
||||
public const int LENGTH = 32; |
||||
public const int BIT_SHIFT = 5; |
||||
public const int BIT_MASK = ~(~0 << BIT_SHIFT); |
||||
|
||||
public (int, int) ChunkPosition { get; } |
||||
|
||||
public Chunk(int x, int y) |
||||
{ |
||||
ChunkPosition = (x, y); |
||||
Position = new Vector2(x << (BIT_SHIFT + Block.BIT_SHIFT), y << (BIT_SHIFT + Block.BIT_SHIFT)); |
||||
} |
||||
|
||||
public ChunkLayer<T> GetLayerOrNull<T>() |
||||
=> GetNodeOrNull<ChunkLayer<T>>($"{typeof(T).Name}Layer"); |
||||
public ChunkLayer<T> GetOrCreateLayer<T>() |
||||
{ |
||||
var layer = GetLayerOrNull<T>(); |
||||
if (layer == null) AddChild(layer = new ChunkLayer<T> { Name = $"{typeof(T).Name}Layer" }); |
||||
return layer; |
||||
} |
||||
|
||||
// TODO: How should we handle chunk extends? Blocks can go "outside" of the current extends, since they're centered. |
||||
// public override void _Draw() |
||||
// => DrawRect(new Rect2(Vector2.Zero, Vector2.One * (LENGTH * Block.LENGTH)), Colors.Blue, false); |
||||
|
||||
} |
||||
|
||||
public class ChunkLayer<T> : Node2D |
||||
{ |
||||
private static readonly IEqualityComparer<T> COMPARER = EqualityComparer<T>.Default; |
||||
|
||||
// TODO: Use one-dimensional array? |
||||
private readonly T[,] _data = new T[Chunk.LENGTH, Chunk.LENGTH]; |
||||
private int _numNonDefault = 0; |
||||
|
||||
public T this[BlockPos pos] { |
||||
get => this[pos.X, pos.Y]; |
||||
set => this[pos.X, pos.Y] = value; |
||||
} |
||||
public T this[int x, int y] { |
||||
get { EnsureWithin(x, y); return _data[x, y]; } |
||||
set { |
||||
EnsureWithin(x, y); |
||||
var previous = _data[x, y]; |
||||
if (COMPARER.Equals(value, previous)) return; |
||||
|
||||
if (!COMPARER.Equals(previous, default)) { |
||||
if (previous is Node node) RemoveChild(node); |
||||
_numNonDefault--; |
||||
} |
||||
if (!COMPARER.Equals(value, default)) { |
||||
if (value is Node node) AddChild(node); |
||||
_numNonDefault++; |
||||
} |
||||
_data[x, y] = value; |
||||
} |
||||
} |
||||
|
||||
public bool IsDefault => _numNonDefault == 0; |
||||
|
||||
private static void EnsureWithin(int x, int y) |
||||
{ |
||||
if ((x < 0) || (x >= Chunk.LENGTH) || (y < 0) || (y >= Chunk.LENGTH)) throw new ArgumentException( |
||||
$"x and y ({x},{y}) must be within chunk boundaries - (0,0) inclusive to ({Chunk.LENGTH},{Chunk.LENGTH}) exclusive"); |
||||
} |
||||
} |
@ -0,0 +1,52 @@ |
||||
using System; |
||||
using System.Collections.Generic; |
||||
|
||||
public class PlayerVisibilityTracker |
||||
{ |
||||
public const int TRACK_RANGE = 4; |
||||
public const int UNTRACK_RANGE = 5; |
||||
|
||||
private static readonly List<(int, int)> _removedChunks |
||||
= new List<(int, int)>((UNTRACK_RANGE * 2 + 1) * (UNTRACK_RANGE * 2 + 1)); |
||||
|
||||
private readonly HashSet<(int X, int Y)> _trackingChunks = new HashSet<(int, int)>(); |
||||
private (int, int)? _previousChunkPos; |
||||
|
||||
public event Action<(int, int)> ChunkTracked; |
||||
public event Action<(int, int)> ChunkUntracked; |
||||
|
||||
public bool IsChunkTracked((int, int) chunkPos) |
||||
=> _trackingChunks.Contains(chunkPos); |
||||
|
||||
public void Process(Player player) |
||||
{ |
||||
var chunkPos = BlockPos.FromVector(player.GlobalPosition).ToChunkPos(); |
||||
if (chunkPos == _previousChunkPos) return; |
||||
|
||||
bool IsWithin((int X, int Y) pos, int range) |
||||
=> (pos.X >= chunkPos.X - UNTRACK_RANGE) && (pos.X <= chunkPos.X + UNTRACK_RANGE) && |
||||
(pos.Y >= chunkPos.Y - UNTRACK_RANGE) && (pos.Y <= chunkPos.Y + UNTRACK_RANGE); |
||||
|
||||
foreach (var pos in _trackingChunks) |
||||
if (!IsWithin(pos, UNTRACK_RANGE)) |
||||
_removedChunks.Add(pos); |
||||
foreach (var pos in _removedChunks) { |
||||
_trackingChunks.Remove(pos); |
||||
ChunkUntracked?.Invoke(pos); |
||||
} |
||||
_removedChunks.Clear(); |
||||
|
||||
for (var x = chunkPos.X - TRACK_RANGE; x <= chunkPos.X + TRACK_RANGE; x++) |
||||
for (var y = chunkPos.Y - TRACK_RANGE; y <= chunkPos.Y + TRACK_RANGE; y++) |
||||
if (_trackingChunks.Add((x, y))) |
||||
ChunkTracked?.Invoke((x, y)); |
||||
|
||||
_previousChunkPos = chunkPos; |
||||
} |
||||
|
||||
public void Reset() |
||||
{ |
||||
_trackingChunks.Clear(); |
||||
_previousChunkPos = null; |
||||
} |
||||
} |
Loading…
Reference in new issue