You're doing Settings
all wrong.
I know you don't believe me, but you are. I've been working on a (very) long-term project (3+ years) with a 'settings' type system, and my solution was very bulky, but creates a lot less work later.
I can't even tell where you plan on using Seed
in the Settings
class, what does it act as a seed for? Everything? You need to group things into categories so that you can create a separation of responsibilities (SRP), and keep things that are related together.
The first thing I did is create a BaseConfiguration
class that has the common settings systems in it:
[DataContract]
public class BaseConfiguration
{
[DataMember]
public NetworkConfiguration NetworkConfiguration { get; set; }
[DataMember]
public LoggingConfiguration LoggingConfiguration { get; set; }
[XmlIgnore]
[ScriptIgnore]
[IgnoreDataMember]
public static string ConfigurationFile { get; set; } = "Config.xml";
public void Save<T>()
{
Save<T>(this);
}
public static void Save<T>(BaseConfiguration config)
{
DataContractSerializer dcs = new DataContractSerializer(typeof(T));
using (FileStream fs = new FileStream(ConfigurationFile, FileMode.Create))
using (XmlWriter writer = XmlDictionaryWriter.CreateTextWriter(fs))
{
dcs.WriteObject(writer, config);
}
}
public static T Load<T>()
{
DataContractSerializer dcs = new DataContractSerializer(typeof(T));
using (FileStream fs = new FileStream(ConfigurationFile, FileMode.OpenOrCreate))
using (XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(fs, new XmlDictionaryReaderQuotas()))
{
return (T)dcs.ReadObject(reader);
}
}
}
Then, there are two classes that derive from this, a ClientConfiguration
and a ServerConfiguration
:
[DataContract]
public class ClientConfiguration : BaseConfiguration
{
[DataMember]
public GraphicsConfiguration GraphicsConfiguration { get; set; }
}
[DataContract]
public class ServerConfiguration : BaseConfiguration
{
[DataMember]
public WorldConfiguration WorldConfiguration { get; set; }
[DataMember]
public SqlConfiguration SqlConfiguration { get; set; }
}
Then you have the actual settings in each Configuration
type:
[DataContract]
public class WorldConfiguration
{
/// <summary>
/// The number of <see cref="Chunk"/>s wide that the <see cref="World"/> is.
/// </summary>
[DataMember]
public ushort WorldChunkWidth { get; set; }
/// <summary>
/// The number of <see cref="Chunk"/>s high that the <see cref="World"/> is.
/// </summary>
[DataMember]
public ushort WorldChunkHeight { get; set; }
/// <summary>
/// The total number of <see cref="Chunk"/>s in the <see cref="World"/>.
/// </summary>
[DataMember]
public uint WorldChunkSize { get; set; }
/// <summary>
/// The number of <see cref="Tiles"/> wide each <see cref="Chunk"/> is.
/// </summary>
[DataMember]
public ushort ChunkWidth { get; set; }
/// <summary>
/// The number of <see cref="Tiles"/> high each <see cref="Chunk"/> is.
/// </summary>
[DataMember]
public ushort ChunkHeight { get; set; }
/// <summary>
/// The number of pixels wide each of the <see cref="Tiles"/>"/> are.
/// </summary>
[DataMember]
public byte TileWidth { get; set; }
/// <summary>
/// The number of pixels high each of the <see cref="Tiles"/>"/> are.
/// </summary>
[DataMember]
public byte TileHeight { get; set; }
[DataMember]
public string ChunkFolder { get; set; }
[DataMember]
public string PlayerFolder { get; set; }
[DataMember]
public string PlayerTable { get; set; }
[XmlIgnore]
[ScriptIgnore]
[IgnoreDataMember]
public Rectangle TileBounds => new Rectangle(0, 0, WorldChunkWidth * TileBounds.Width, WorldChunkHeight * TileBounds.Height);
[XmlIgnore]
[ScriptIgnore]
[IgnoreDataMember]
public Rectangle PixelBounds => new Rectangle(0, 0, WorldChunkWidth * PixelBounds.Width, WorldChunkHeight * PixelBounds.Height);
public WorldConfiguration()
{
WorldChunkWidth = 256;
WorldChunkHeight = 256;
ChunkWidth = 256;
ChunkHeight = 256;
TileWidth = 32;
TileHeight = 32;
ChunkFolder = @"World\Chunks";
PlayerFolder = @"World\Players";
PlayerTable = @"dbo.Players";
}
}
[DataContract]
public class LoggingConfiguration
{
[DataMember]
public string Folder { get; set; }
[DataMember]
public int MaxFileSize { get; set; }
}
[DataContract]
public class GraphicsConfiguration
{
[DataMember]
public bool VSync { get; set; } = true;
[DataMember]
public bool IsFixedTimeStep { get; set; } = false;
[DataMember]
public bool FullScreen { get; set; } = false;
[DataMember]
public byte[] ResolutionBytes
{
get { return Resolution.GetBytes(); }
set
{
var sz = new Size();
sz.FromBytes(value);
Resolution = sz;
}
}
[XmlIgnore]
[ScriptIgnore]
[IgnoreDataMember]
public Size Resolution { get; set; }
}
Etc. I won't go into detail on all of them, but you get the picture. Do note that Resolution
is a custom Size
type I built myself, not the built-in .NET ones, you can cut that and ResolutionBytes
out if you want to try to use this and build your own alternative.
Now eventually we come to the point where we want to use the Configuration
types:
BaseConfiguration.ConfigurationFile = @"Config\Config.xml";
ClientConfiguration clientConfiguration = new ClientConfiguration();
clientConfiguration.LoggingConfiguration = new LoggingConfiguration();
clientConfiguration.LoggingConfiguration.Folder = "Log";
clientConfiguration.LoggingConfiguration.MaxFileSize = 16 * 1024 * 1024;
clientConfiguration.NetworkConfiguration = new NetworkConfiguration();
clientConfiguration.NetworkConfiguration.PeerString = "Ancients";
clientConfiguration.NetworkConfiguration.Port = 8093;
clientConfiguration.NetworkConfiguration.IP = "127.0.0.1";
clientConfiguration.GraphicsConfiguration = new GraphicsConfiguration();
clientConfiguration.GraphicsConfiguration.IsFixedTimeStep = false;
clientConfiguration.GraphicsConfiguration.VSync = true;
clientConfiguration.GraphicsConfiguration.FullScreen = false;
clientConfiguration.GraphicsConfiguration.Resolution = new Size(1024, 768);
BaseConfiguration.Save<ClientConfiguration>(clientConfiguration);
(This bit is ugly, but it's for testing purposes only.)
Then to load it:
var config = BaseConfiguration.Load<ClientConfiguration>();
Then finally you pass config
or config.LoggingConfiguration
, config.GraphicsConfiguration
, etc. around as you desire.
Finally, we come to Noise
:
Why is everything in this static
? What happens if I want two Noise
variations with different seeds? You cannot do that with your implementation.
Only mark things static that are universal states. That is, something that there is absolutely no reason to want to have different states for.
In your case, Noise
does not meet that criteria.
class Noise
{
private float _seed;
public Noise(float seed)
{
_seed = seed;
}
public int GetValue(int x, int z)
{
return computedValue; //to calculate computedValue, I need Seed value
}
}
Even better: make an interface so you can have different types of noise:
public interface INoise
{
int GetValue(int x, int z);
}
Then implement it:
public class PerlinNoise
{
private float _seed;
public PerlinNoise(float seed)
{
_seed = seed;
}
public int GetValue(int x, int z)
{
return calculatedValue;
}
}
Then any object/method that needs a Noise
can just take an INoise
, which means you can supply different noise-types if you need to. (I do this very frequently with logging.)
public static float Seed;
is not a property, that's a field. A property has aget
orset
. \$\endgroup\$public const float Seed = 1337;
instead ofstatic
, theconst
values are implicitly accessible by the types and cannot be modified later. \$\endgroup\$instance
property/class instead ofstatic
. Just as well,Noise
should have a constructor that takes aSeed
value. \$\endgroup\$