32 Commits

Author SHA1 Message Date
f324122874 updated to godot 4.5 2025-10-26 18:30:27 +01:00
f932ae1bcd refactoring 2025-05-01 23:34:31 +02:00
c41cdd57c4 fixed activate not set on copy 2025-05-01 23:06:46 +02:00
e653f23a42 dirt shall only move downwards 2025-05-01 23:01:42 +02:00
6acccefa0a diffuse in both directions 2025-05-01 22:51:39 +02:00
28ada065a6 fixed stuck pixels 2025-05-01 22:50:35 +02:00
ea34b8ecc0 moved tick to process from physics process
fixed fall speed
improved naming
2025-05-01 22:46:51 +02:00
4462573267 fixed element leak across chunks 2025-04-30 23:23:20 +02:00
0b97027e00 fixed stuck rain
refactoring
2025-04-30 23:13:53 +02:00
d9bde10c3c fixed rain 2025-04-27 15:31:20 +02:00
2e4fc22263 enabled overwrite of existing pixels 2025-04-27 15:21:56 +02:00
202e71d97e improved debug color 2025-04-27 15:19:49 +02:00
a119e92bf6 fixed brush drawing across chunk borders 2025-04-27 15:16:10 +02:00
da3afd22fc fixed drawpixel optimization
minor class refactoring
2025-04-27 15:05:40 +02:00
abf948a310 adjusted debug mode 2025-04-27 14:15:14 +02:00
2cd6a757dc fixed memory leak
fixed trying to draw out of bounds
2025-04-27 14:00:54 +02:00
4ce265e722 fixed unnecessary write to texture if no changes happened 2025-04-27 13:49:35 +02:00
572e02956c fixed benchmark placement 2025-04-27 13:18:18 +02:00
de99d54ad6 fixed brush Y dimension 2025-04-27 13:04:06 +02:00
2bcdeecb81 Merge branch 'performance-improvements' 2025-04-21 18:57:20 +02:00
7f31c8c3d5 WiP: chunk overdraw fix 2025-04-18 01:11:10 +02:00
882a12862e updated to 4.4 2025-04-17 20:26:56 +02:00
5045b4421e fixed inactivity timer 2025-04-17 20:21:24 +02:00
71db6513f2 improved handling of colors
improved debug colors
refactoring
2024-12-21 23:04:01 +01:00
4e6b1d642c fixed collection access/modification 2024-12-21 15:08:55 +01:00
8ebaa9e987 fixed elements not simulating
updated to godot 4.3
2024-12-21 15:05:54 +01:00
383e1343ef WiP: deactivated array elements 2024-11-03 14:56:07 +01:00
d1dac0b855 added simple activeElements list 2024-10-27 14:44:14 +01:00
34aee98a6a added benchmark function
added .txt for benchmark results
fixed generating level message
2024-10-27 14:43:21 +01:00
7b2845ee01 added benchmark function
added .txt for benchmark results
fixed generating level message
2024-10-27 14:08:19 +01:00
bb2f498ac5 re-enabled rain 2024-10-27 13:53:49 +01:00
7388e1a2d7 fixed inactive elements when spawning with brush 2024-08-25 19:03:40 +02:00
27 changed files with 423 additions and 129 deletions

View File

@@ -1,6 +1,9 @@
<Project Sdk="Godot.NET.Sdk/4.2.2">
<Project Sdk="Godot.NET.Sdk/4.5.1">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
</PropertyGroup>
<ItemGroup>
<Content Include="benchmark.txt" />
</ItemGroup>
</Project>

12
FOU.csproj.old.1 Normal file
View File

@@ -0,0 +1,12 @@
<Project Sdk="Godot.NET.Sdk/4.4.1">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
</PropertyGroup>
<ItemGroup>
<Content Include="benchmark.txt" />
</ItemGroup>
<ItemGroup>
<Folder Include="UI\" />
</ItemGroup>
</Project>

View File

@@ -2,7 +2,8 @@ using Godot;
public partial class FPSLabel : Label
{
bool ShowFPS = true;
[Export]
public bool ShowFPS = true;
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta) {

1
Scenes/FPSLabel.cs.uid Normal file
View File

@@ -0,0 +1 @@
uid://d3cbk8f7lckbr

17
Scenes/PerfDetails.cs Normal file
View File

@@ -0,0 +1,17 @@
using FOU.Scripts;
using Godot;
public partial class PerfDetails : Label
{
public override void _Process(double delta)
{
Text = "";
if (!Main.Instance.DebugMode) return;
int activeElements = 0;
foreach (Chunk c in Main.Instance.Level.GetChunks())
activeElements += c.ActiveElementsCount();
Text += $"Active Elements: {activeElements}";
}
}

View File

@@ -0,0 +1 @@
uid://52grhydeyy0t

View File

@@ -1,4 +1,3 @@
using System.Reflection;
using Godot;
public partial class SettingsController : VBoxContainer
@@ -18,7 +17,7 @@ public partial class SettingsController : VBoxContainer
slSize.Value = main.BrushSize;
slRain.ValueChanged += OnRainValueChanged;
slRain.Value = main.RainAmount;
slRain.Value = main.rainAmount;
}
private void OnSizeValueChanged(double value) {

View File

@@ -0,0 +1 @@
uid://dxmkbb5f1t368

View File

@@ -1,14 +1,14 @@
[gd_scene load_steps=4 format=3 uid="uid://cf34vk5r055dx"]
[gd_scene load_steps=5 format=3 uid="uid://cf34vk5r055dx"]
[ext_resource type="Script" path="res://Scripts/Main.cs" id="1_k1i8e"]
[ext_resource type="Script" path="res://Scenes/FPSLabel.cs" id="2_8cb7y"]
[ext_resource type="Script" path="res://Scenes/SettingsController.cs" id="3_a4w6m"]
[ext_resource type="Script" uid="uid://cmvvubxfvdca7" path="res://Scripts/Main.cs" id="1_k1i8e"]
[ext_resource type="Script" uid="uid://d3cbk8f7lckbr" path="res://Scenes/FPSLabel.cs" id="2_8cb7y"]
[ext_resource type="Script" uid="uid://dxmkbb5f1t368" path="res://Scenes/SettingsController.cs" id="3_a4w6m"]
[ext_resource type="Script" uid="uid://52grhydeyy0t" path="res://Scenes/PerfDetails.cs" id="3_u2p48"]
[node name="Main" type="Node2D"]
script = ExtResource("1_k1i8e")
BrushSize = 0
TextureResolution = 0.25
RainAmount = 5.0
DebugMode = true
BrushSize = 4
[node name="CanvasLayer" type="CanvasLayer" parent="."]
@@ -34,6 +34,15 @@ text = "FPS: 0
"
script = ExtResource("2_8cb7y")
[node name="PerfDetails" type="Label" parent="CanvasLayer/TopUI"]
layout_mode = 0
offset_top = 30.0
offset_right = 76.0
offset_bottom = 67.0
theme_override_font_sizes/font_size = 10
text = "Active Elements: 0"
script = ExtResource("3_u2p48")
[node name="Settings" type="VBoxContainer" parent="CanvasLayer/TopUI"]
layout_mode = 1
anchors_preset = 1
@@ -75,6 +84,13 @@ custom_minimum_size = Vector2(150, 0)
layout_mode = 2
size_flags_horizontal = 8
size_flags_vertical = 1
max_value = 5.0
max_value = 10.0
step = 0.01
value = 1.0
[node name="ToolbarMarginContainer2" parent="CanvasLayer/TopUI" instance=ExtResource("5_kry3j")]
layout_mode = 0
offset_left = 860.0
offset_top = 5.0
offset_right = 1060.0
offset_bottom = 69.0

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using FOU.Scripts.Elements;
using Godot;
@@ -15,7 +16,9 @@ public class Chunk {
private readonly Image image;
private readonly int sizeX;
private readonly int sizeY;
private int frame;
private readonly HashSet<Element> activeElements;
private readonly HashSet<Element> toAddElements = new();
private readonly HashSet<Element> toRemoveElements = new();
public Chunk(int x, int y, int index) {
Index = index;
@@ -29,46 +32,62 @@ public class Chunk {
}
}
image = Image.Create(sizeX, sizeY, false, Image.Format.Rgb8);
activeElements = new HashSet<Element>(sizeX * sizeY);
image = Image.CreateEmpty(sizeX, sizeY, false, Image.Format.Rgb8);
image.Fill(Colors.Black);
}
public void Update() {
for (int x = 0; x < sizeX; x++) {
for (int y = 0; y < sizeY; y++) {
if (Elements[x,y] != null)
Elements[x,y].Update(frame);
foreach (Element e in activeElements)
e.Update();
// apply changes:
if (toRemoveElements.Count > 0) {
foreach (Element removeElement in toRemoveElements) {
activeElements.Remove(removeElement);
removeElement.ResetColor();
}
toRemoveElements.Clear();
}
if (toAddElements.Count > 0) {
foreach (Element addElement in toAddElements) {
activeElements.Add(addElement);
addElement.SetDebugColor(addElement.Color * 1.5f);
}
toAddElements.Clear();
}
frame++;
// TODO: enable rain again
// MakeItRain(_main.RainAmount);
}
public void WritePixel<T>(int x, int y, int size) {
int halfsize = size/2;
public void SetElementActive(Element e, bool active) {
if (e.GetType() == typeof(Element)) return;
if (active) AddToChunk(e);
else RemoveFromChunk(e);
}
public void WritePixel<T>(int x, int y) {
if (x < 0 || x >= sizeX || y < 0 || y >= sizeY) {
GD.PrintErr($"Out of bounds for chunk: {x}:{y}");
return;
}
Type type = typeof(T);
object o = Activator.CreateInstance(type, x, y, this);
for (int i = -halfsize; i <= halfsize; i++) {
for (int j = -halfsize; j <= halfsize; j++) {
int X = Mathf.Clamp(x + i, 0, sizeX-1);
int Y = Mathf.Clamp(y + j, 0, sizeY-1);
object o = Activator.CreateInstance(type, X, Y, this);
if (Elements[X,Y].GetType() != type)
Elements[X,Y] = o as Element;
}
// check if not empty and overwrite not allowed:
if (!Elements[x,y].IsEmpty() && !Main.Instance.AllowOverwrite) {
return;
}
Elements[x, y].Chunk.RemoveFromChunk(Elements[x, y]);
Elements[x, y] = o as Element;
Elements[x, y].Active = true;
}
public Image DrawLevel() {
for (int x = 0; x < sizeX; x++) {
for (int y = 0; y < sizeY; y++) {
image.SetPixel(x,y, Elements[x,y].Color);
}
}
return image;
public int ActiveElementsCount() {
return activeElements.Count;
}
public void Swap(Element what, Vector2I pos) {
@@ -115,6 +134,15 @@ public class Chunk {
GD.PrintErr("Trying to swap null");
return;
}
if (what.Chunk != swapTo.Chunk) {
what.Chunk.RemoveFromChunk(what);
swapTo.Chunk.AddToChunk(what);
swapTo.Chunk.RemoveFromChunk(swapTo);
what.Chunk.AddToChunk(swapTo);
}
Element temp = new Element(what);
what.Position = swapTo.Position;
@@ -126,8 +154,20 @@ public class Chunk {
swapTo.Chunk.Elements[swapTo.Position.X, swapTo.Position.Y] = swapTo;
what.Active = true;
what.Moved();
swapTo.Active = true;
what.LastMove = frame;
swapTo.LastMove = frame;
swapTo.Moved();
}
public override string ToString() {
return $"Chunk {Index}";
}
private void RemoveFromChunk(Element e) {
toRemoveElements.Add(e);
}
private void AddToChunk(Element e) {
toAddElements.Add(e);
}
}

1
Scripts/Chunk.cs.uid Normal file
View File

@@ -0,0 +1 @@
uid://r17upkbkmfiy

View File

@@ -5,7 +5,7 @@ namespace FOU.Scripts.Elements;
public class Dirt : Solid {
public Dirt(int x, int y, ref Chunk chunk) : base(x, y, ref chunk) {
Color = AddColorVariance(Colors.Brown);
color = AddColorVariance(Colors.Brown);
Density = 10;
DiffuseSpeed = 50;
}

View File

@@ -0,0 +1 @@
uid://80esqy0cardd

View File

@@ -2,83 +2,83 @@
namespace FOU.Scripts.Elements;
public class Element {
public Color Color = Colors.Black;
public class Element{
public Vector2I Position;
public Chunk Chunk;
public Vector2I Position;
public int Density;
public int LastUpdate = -1;
public int LastMove = 0;
public int DiffuseSpeed = 10;
public const int MAX_DIFFUSE_SPEED = 100;
public const int STEPS_UNTIL_INACTIVE = 100;
protected int DiffuseSpeed = 10;
protected Color color = Colors.Black;
protected const int MAX_DIFFUSE_SPEED = 100;
protected const int STEPS_UNTIL_INACTIVE = 500;
protected const float MAX_COLOR_VARIANCE = 0.1f;
protected static readonly Vector2I VERTICAL_OPPOSITE = new Vector2I(-1, 1);
private const float MAX_COLOR_VARIANCE = 0.1f;
private bool active = true;
private bool active = false;
private bool markedForUpdate = true;
private int lastUpdate = -1;
private int lastMove = 0;
private Color originalColor;
public Element(Element e) {
Position = e.Position;
Chunk = e.Chunk;
Active = e.active;
lastMove = e.lastMove;
lastUpdate = e.lastUpdate;
Chunk.SetElementActive(this, Active);
}
public Element(int x, int y, Chunk chunk) {
Position.X = x;
Position.Y = y;
Chunk = chunk;
lastMove = Engine.GetFramesDrawn();
Chunk.SetElementActive(this, Active);
}
public bool Active {
get => active;
set {
if (active == value) return;
active = value;
Chunk.SetElementActive(this, value);
if (active)
Color = originalColor;
else if (Main.Instance.DebugVisualization)
Color = new Color(0.2f, 0.2f, 0.2f);
if (active) {
Moved();
} else {
ResetColor();
}
}
}
public Color Color => color;
/// <summary>
/// base update method, checks if anything is to do at all
/// </summary>
/// <param name="currentFrame"></param>
/// <returns>false if there is nothing to do</returns>
public virtual bool Update(int currentFrame) {
public virtual bool Update() {
if (!Active) return false;
if (LastUpdate == currentFrame) return false; // already updated this frame
LastUpdate = currentFrame;
int frame = Engine.GetFramesDrawn();
if (lastMove + STEPS_UNTIL_INACTIVE < frame)
Active = false;
if (lastUpdate == frame)
return false; // already updated this frame
lastUpdate = frame;
return true;
}
public override string ToString() {
return $"{GetType()} {Position}[{Chunk.Index}]";
return $"{GetType()} {Position}[{Chunk.Index}] {active}";
}
protected virtual void Tick() {
Vector2I randomDirection = RandomDirectionDown();
if (Chunk.IsEmpty(Position + Vector2I.Down))
Chunk.Swap(this, Position + Vector2I.Down);
else if (Chunk.IsEmpty(Position + randomDirection))
Chunk.Swap(this, Position + randomDirection);
else if (Chunk.IsEmpty(Position + randomDirection * VERTICAL_OPPOSITE))
Chunk.Swap(this, Position + randomDirection * VERTICAL_OPPOSITE);
if (GD.Randi() % MAX_DIFFUSE_SPEED > DiffuseSpeed) return; // ascend slower
if (Chunk.Get(Position + randomDirection)?.Density < Density)
Chunk.Swap(this, Position + Vector2I.Down);
}
protected virtual void Tick() { }
protected Vector2I RandomDirectionDown() {
int randomDirection = GD.Randi() % 2 != 0 ? 1 : -1;
@@ -87,6 +87,13 @@ public class Element {
+ (GD.Randi() % 2 != 0 ? Vector2I.Zero : Vector2I.Right * randomDirection);
}
/// <returns>-1, 0 or 1</returns>
protected Vector2I RandomDirection() {
int randomDirection = GD.Randi() % 2 != 0 ? 1 : -1;
return (GD.Randi() % 2 != 0 ? Vector2I.Zero : Vector2I.Right * randomDirection);
}
protected Color AddColorVariance(Color baseColor) {
Color c = baseColor;
c.R += (GD.Randf() - 1) * MAX_COLOR_VARIANCE;
@@ -97,4 +104,31 @@ public class Element {
return c;
}
public void ResetColor() {
color = originalColor;
MarkForUpdate();
}
public void SetDebugColor(Color color) {
if (!Main.Instance.DebugMode) return;
this.color = color;
}
public void Moved() {
lastMove = Engine.GetFramesDrawn();
MarkForUpdate();
}
public bool IsMarkedForUpdate() {
return markedForUpdate;
}
public void MarkForUpdate(bool mark = true) {
markedForUpdate = true;
}
public bool IsEmpty() {
return GetType() == typeof(Element);
}
}

View File

@@ -0,0 +1 @@
uid://b5lme1c4rx15n

View File

@@ -5,18 +5,19 @@ namespace FOU.Scripts.Elements;
public abstract class Liquid : Element {
public const int MAX_VERTICAL_SPREAD = 3;
protected Liquid(int x, int y, ref Chunk chunk) : base(x, y, chunk) { }
protected Liquid(int x, int y, ref Chunk chunk) : base(x, y, chunk) {
MarkForUpdate();
}
public override bool Update(int currentFrame) {
if (!base.Update(currentFrame)) return false;
if (LastMove + STEPS_UNTIL_INACTIVE < currentFrame) Active = false;
public override bool Update() {
if (!base.Update()) return false;
Tick();
return true; // not necessarily end, subclasses could do some more things
}
protected override void Tick() {
protected virtual void Tick() {
Vector2I randomDirection = RandomDirectionDown();
if (randomDirection.Y != 0)
randomDirection *= Vector2I.Left * (int)(GD.Randi() % MAX_VERTICAL_SPREAD);

View File

@@ -0,0 +1 @@
uid://bqx36wisy7127

View File

@@ -3,14 +3,40 @@
namespace FOU.Scripts.Elements;
public abstract class Solid : Element {
protected Solid(int x, int y, ref Chunk chunk) : base(x, y, chunk) { }
protected Solid(int x, int y, ref Chunk chunk) : base(x, y, chunk) {
MarkForUpdate();
}
public override bool Update(int currentFrame) {
if (!base.Update(currentFrame)) return false;
if (LastMove + STEPS_UNTIL_INACTIVE < currentFrame) Active = false;
public override bool Update() {
if (!base.Update()) return false;
Tick();
return true; // not necessarily end, subclasses could do some more things
}
protected virtual void Tick() {
Vector2I randomDirection = RandomDirectionDown();
// fall speed reduction:
if (GD.Randi() % MAX_DIFFUSE_SPEED > DiffuseSpeed) return; // descend slower
if (Chunk.IsEmpty(Position + Vector2I.Down))
Chunk.Swap(this, Position + Vector2I.Down);
else if (Chunk.IsEmpty(Position + Vector2I.Down + randomDirection))
Chunk.Swap(this, Position + Vector2I.Down + randomDirection);
else if (Chunk.IsEmpty(Position + Vector2I.Down + randomDirection * VERTICAL_OPPOSITE))
Chunk.Swap(this, Position + Vector2I.Down + randomDirection * VERTICAL_OPPOSITE);
// density check
if (Chunk.Get(Position + Vector2I.Down)?.Density < Density)
Chunk.Swap(this, Position + Vector2I.Down);
else if (Chunk.Get(Position + Vector2I.Down + randomDirection)?.Density < Density)
Chunk.Swap(this, Position + Vector2I.Down + randomDirection);
else if (Chunk.Get(Position + Vector2I.Down + randomDirection * VERTICAL_OPPOSITE)?.Density < Density)
Chunk.Swap(this, Position + Vector2I.Down + randomDirection * VERTICAL_OPPOSITE);
}
}

View File

@@ -0,0 +1 @@
uid://ur56t06r7n4l

View File

@@ -5,7 +5,7 @@ namespace FOU.Scripts.Elements;
public class Water : Liquid {
public Water(int x, int y, ref Chunk chunk) : base(x, y, ref chunk) {
Color = AddColorVariance(Colors.Blue);
color = AddColorVariance(Colors.Blue);
Density = 1;
}

View File

@@ -0,0 +1 @@
uid://k8id5egjc7x8

View File

@@ -1,4 +1,6 @@
using Godot;
using System;
using FOU.Scripts.Elements;
using Godot;
namespace FOU.Scripts;
@@ -9,73 +11,164 @@ public class Level {
private Chunk[,] chunks;
private Image image;
private int chunksX;
private int chunksY;
private int chunksPerX;
private int chunksPerY;
// per chunk:
private int chunkResX;
private int chunkResY;
private float rainAmount = 0;
public Level(Main main, int sizeX, int sizeY) {
GD.Print($"Generating level ({sizeX}:{sizeY}) with {chunksX*chunksY} chunks ({chunkResX} * {chunkResY})");
Resolution = new Vector2I(sizeX, sizeY);
chunksX = main.ChunksPerAxis;
chunksY = main.ChunksPerAxis;
chunksPerX = main.ChunksPerAxis;
chunksPerY = main.ChunksPerAxis;
chunkResX = sizeX / chunksX;
chunkResY = sizeY / chunksY;
chunkResX = sizeX / chunksPerX;
chunkResY = sizeY / chunksPerY;
image = Image.Create(sizeX, sizeY, false, Image.Format.Rgb8);
GD.Print($"Generating level ({sizeX}:{sizeY}) with {chunksPerX*chunksPerY} chunks ({chunkResX} * {chunkResY})");
chunks = new Chunk[chunksX, chunksY];
image = Image.CreateEmpty(sizeX, sizeY, false, Image.Format.Rgb8);
chunks = new Chunk[chunksPerX, chunksPerY];
int index = 0;
// create all chunks
for (int x = 0; x < chunksX; x++) {
for (int y = 0; y < chunksY; y++) {
for (int x = 0; x < chunksPerX; x++) {
for (int y = 0; y < chunksPerY; y++) {
chunks[x, y] = new Chunk(chunkResX, chunkResY, index++);
}
}
// assign neighbors
for (int x = 0; x < chunksX; x++) {
for (int y = 0; y < chunksY; y++) {
for (int x = 0; x < chunksPerX; x++) {
for (int y = 0; y < chunksPerY; y++) {
if (y > 0) chunks[x, y].NeighborN = chunks[x, y-1];
if (y < chunksY-1) chunks[x, y].NeighborS = chunks[x, y+1];
if (y < chunksPerY-1) chunks[x, y].NeighborS = chunks[x, y+1];
if (x > 0) chunks[x, y].NeighborE = chunks[x-1, y];
if (x < chunksX-1) chunks[x, y].NeighborW = chunks[x+1, y];
if (x < chunksPerX-1) chunks[x, y].NeighborW = chunks[x+1, y];
}
}
}
public void Update() {
MakeItRain();
foreach (Chunk c in chunks)
c.Update();
}
// TODO!
private void MakeItRain(float rainAmount) {
// int rainDrops = (int)Math.Round(rainAmount);
//
// for (int i = 0; i <= rainDrops; i++) {
// if (GD.Randf() < rainAmount)
// WritePixel<Water>((int)(GD.Randi() % SizeX), 0, 1);
// }
public void SetRainAmount(float amount) {
rainAmount = amount;
}
public void StartBenchmark() {
GD.Print("benchmark");
for (int x = 0; x < chunksPerX; x++) {
for (int y = 0; y < chunksPerY; y++) {
WritePixel<Dirt>((x * chunkResX) + chunkResX/2, (y * chunkResY) + chunkResY/2, 100);
}
}
}
public Chunk[,] GetChunks() {
return chunks;
}
private void MakeItRain() {
if (rainAmount < .1f) return;
int rainDrops = (int)Math.Round(rainAmount);
for (int i = 0; i <= rainDrops; i++) {
if (GD.Randf() < rainAmount) {
int d = (int)(GD.Randi() % (chunkResX * chunksPerX));
if (GetElement(d, 0).IsEmpty())
WritePixel<Water>(d, 0, 1);
}
}
}
public void WritePixel<T>(int x, int y, int size) {
chunks[x/chunkResX, y/chunkResY].WritePixel<T>(x % chunkResX, y % chunkResY, size);
int halfsize = size/2;
int startPtX = x % chunkResX;
int startPtY = y % chunkResY;
for (int i = -halfsize; i <= halfsize; i++) {
for (int j = -halfsize; j <= halfsize; j++) {
// calculate in-chunk coordinates
int iteratorX = i;
int iteratorY = j;
int ptX = startPtX;
int ptY = startPtY;
// calculate chunk to write to
int chunkX = x/chunkResX;
// left of chunk
if (startPtX + iteratorX < 0) {
chunkX--;
iteratorX = chunkResX + startPtX + iteratorX;
ptX = startPtX + iteratorX;
} else {
ptX = startPtX + iteratorX;
}
if (startPtX + iteratorX >= chunkResX) {
chunkX++;
ptX = startPtX + iteratorX - chunkResX;
}
int chunkY = y/chunkResY;
// above chunk
if (startPtY + iteratorY < 0) {
chunkY--;
ptY = chunkResY + (startPtY + iteratorY);
} else {
ptY = startPtY + iteratorY;
}
if (startPtY + iteratorY >= chunkResY) {
chunkY++;
ptY = startPtY + iteratorY - chunkResY;
}
// ignore everything outside
if (chunkX < 0) continue;
if (chunkX >= chunksPerX) continue;
if (chunkY < 0) continue;
if (chunkY >= chunksPerY) continue;
chunks[chunkX, chunkY].WritePixel<T>(ptX, ptY);
}
}
}
public Element GetElement(int x, int y) {
if (x < 0 || x > chunkResX * chunkResX) return null;
if (y < 0 || y > chunkResY * chunkResY) return null;
return chunks[x / chunkResX, y / chunkResY].Elements[x % chunkResX, y % chunkResY];
}
public Image DrawLevel() {
for (int cx = 0; cx < chunksX; cx++) {
for (int cy = 0; cy < chunksY; cy++) {
// chunk
for (int cx = 0; cx < chunksPerX; cx++) {
for (int cy = 0; cy < chunksPerY; cy++) {
// pixel in chunk
for (int x = 0; x < chunkResX; x++) {
for (int y = 0; y < chunkResY; y++) {
image.SetPixel(cx*chunkResX + x, cy*chunkResY + y, chunks[cx,cy].Elements[x, y].Color);
// TODO: multithreading here! use Chunk.DrawLevel() and stitch images together
if (chunks[cx, cy].Elements[x, y].IsMarkedForUpdate()) {
image.SetPixel(cx * chunkResX + x, cy * chunkResY + y, chunks[cx, cy].Elements[x, y].Color);
chunks[cx, cy].Elements[x, y].MarkForUpdate(false);
}
}
}
}

1
Scripts/Level.cs.uid Normal file
View File

@@ -0,0 +1 @@
uid://bkwbjagt8s01s

View File

@@ -1,32 +1,47 @@
using System.Reflection;
using FOU.Scripts;
using FOU.Scripts.Elements;
using Godot;
public partial class Main : Node2D {
[Export] public bool DebugVisualization = false;
[Export] public bool DebugMode = false;
[Export] public bool AllowOverwrite = true;
[Export] public int BrushSize = 5;
[Export] public float TextureResolution = 0.5f;
[Export] public int ChunksPerAxis = 2;
[Export] public float RainAmount = 0.01f;
[Export] public float rainAmount = 0;
public static Main Instance;
public Level Level;
private TextureRect mLevelDrawer;
private Level mLevel;
private bool enableRain;
public float RainAmount {
get => rainAmount;
set {
rainAmount = value;
Level?.SetRainAmount(rainAmount);
}
}
public override void _Ready() {
mLevel = new Level(this, (int)(GetViewportRect().Size.X * TextureResolution),
Level = new Level(this, (int)(GetViewportRect().Size.X * TextureResolution),
(int)(GetViewportRect().Size.Y * TextureResolution));
Level.SetRainAmount(rainAmount);
mLevelDrawer = GetNode<TextureRect>("CanvasLayer/LevelDrawer");
Instance = this;
}
public override void _PhysicsProcess(double delta) {
base._PhysicsProcess(delta);
}
public override void _Process(double delta) {
mLevel.Update();
mLevelDrawer.Texture = ImageTexture.CreateFromImage(mLevel.DrawLevel());
Level.Update();
mLevelDrawer.Texture?.Dispose();
mLevelDrawer.Texture = ImageTexture.CreateFromImage(Level.DrawLevel());
}
public override void _UnhandledInput(InputEvent @event) {
@@ -35,11 +50,15 @@ public partial class Main : Node2D {
if (eventMouseButton.IsPressed()) {
Vector2 mouse = GetViewport().GetMousePosition();
float mappedX = mouse.X / (GetViewportRect().Size.X / mLevel.Resolution.X);
float mappedY = mouse.Y / (GetViewportRect().Size.Y / mLevel.Resolution.Y);
float mappedX = mouse.X / (GetViewportRect().Size.X / Level.Resolution.X);
float mappedY = mouse.Y / (GetViewportRect().Size.Y / Level.Resolution.Y);
mLevel.WritePixel<Dirt>((int)mappedX, (int)mappedY, BrushSize);
Level.WritePixel<Dirt>((int)mappedX, (int)mappedY, BrushSize);
}
} else base._UnhandledInput(@event);
} else if (@event is InputEventKey keyEvent && keyEvent.Pressed) {
if (keyEvent.Keycode == Key.F9)
Level.StartBenchmark();
}
else base._UnhandledInput(@event);
}
}

1
Scripts/Main.cs.uid Normal file
View File

@@ -0,0 +1 @@
uid://cmvvubxfvdca7

22
benchmark.txt Normal file
View File

@@ -0,0 +1,22 @@
# Benchmark
Start from editor not rider!
## Settings
- Texture Resolution: 0.5
- CHunks per Axis: 2
- Rain Amount: 0
## Measurements:
### original (no optimization):
- idle: 42 - 44 fps
- benchmark: 24 fps minimum
### performance-improvements (71db6513f22094e7dd67bd32c7ab86b326f9ad9e)
- idle before: 89
- benchmark: 33
- idle after: 75

View File

@@ -12,7 +12,7 @@ config_version=5
config/name="FOU"
run/main_scene="res://Scenes/main.tscn"
config/features=PackedStringArray("4.2", "C#", "Forward Plus")
config/features=PackedStringArray("4.5", "C#", "Forward Plus")
config/icon="res://icon.svg"
[dotnet]