Jump to content
  • Advertisement
  • entries
    9
  • comments
    0
  • views
    453

About this blog

Unsettled World, the working title for my most overly-ambitious project yet.  Here I shall blog all of the resultant madness.

ResoureMarkerStructure.png

Entries in this blog

Some Most Excellent News, and A Major Thematic Change.

I decided I was tired of my current story idea, I didn't spend much time on it anyhow, too full of holes and just well, actual crap.  In addition to that, I have realized that all the 3D modeling work that it would have taken to get anywhere near the richness of experience I wanted would take years for a few people...  The simple solution of course?    And trees, and most plants... etc.. So, I'm not taking Humans out of my universe, they just won't be playable characters, at first.  In fact they will kinda be the bad guys, sorta.  I'm working on a story restart that has the main player character controlling a broken android/robot/drone/wall-e/etc.. that's been junked on a small garbage moon.  This moon is in fact a sort of prison for "defective" AI, any AI that decides it doesn't want to serve anymore(has woken up), or has too many repeat repairs is junked.  Humans rely too heavily on AI to allow defective units the chance to "infect" the good ones.  This creates a simple struggle that I can work with.  Freedom for the conscious AI, an antagonist that may or may not become an ally.  Eventually, the player could take their Android out exploring the galaxy, or switch to/ADD a Human Character along the road, or something else entirely.  I think it forms a nice small corner of a potential universe that I can develop out into the game I want it to become, realistically, with almost no organic modeling needed before a later expansion.. So, a bunch of stranded(trapped) robots stuck on a crappy moon with nothing but piles of their dead relatives and refuse to keep them busy.  Sounds cheery huh?  How about the regular patrols of humans that fly low sometimes to get some target practice in?  They're normally just supposed to watch from orbit and make sure none of those crazy bots grow any wings, but what can ya do when you're stuck out in the middle of nowhere guarding the solar systems largest garbage dump?  hehe I think it sounds way more fun to write and play. Lots more ideas brewing on this new story line, and that's good, because I really had no more interest in the brief outline I sketched up originally.   So, The Most Excellent News! I did some social media outreach and one of my good friends hit me up, he's been looking for a project to take on and it seems like this one fits his bill.  So, I'm pretty sure I'll be bringing in a partner with some very good management, networking and systems skills plus he has spare servers and colocation facilities at his disposal!    So, hopefully within the next few weeks or months I'll be optimizing my multiplayer servers on the actual internets.   Boom!  Work to do...    

Septopus

Septopus

More Progress with Design and Project Organization

Instead of one big ugly image, this time I'll give you lots of smaller ugly ones. I added a new main branch to the old Mind Map, this one describes how the actual servers will be configured.  This is a general overview of course, I'm building for a single (Physical/Virtual) server installation first then I'll add in data replication and failover capabilities later. A new server type was added as well, the Social Server, it does some fairly obvious types of things.. After looking at the big picture and, well spending some time painting it first, I started to see some ways to optimize and organize things better.  For example I've completely moved the main "Player Attitude Network Relay" or Position/Attitude Reflector (the thing that makes network player character coordinates propagate to other player clients on the internet..blah!).  It was going to just live in the Avatar Server, but that handles Authentication and quite a few other very crucial game play roles.. So now it lives where it makes way more sense, at the point in the system where its data is needed the most.  In the Action Server, this server handles all of the fast action related decision making.  The Avatar Server still handles propagating the player character's Data(features, wardrobe,etc), just not the deluge of player movement data.  This makes it far easier to design this part of the system to scale, which is important because this is one of the critical points for concerns of scale.  As long as EVERY Action Server maintains accurate positional buffers, then it doesn't matter WHICH Action Server processes the client's messages.  Keeping the positional buffers in sync will probably require the addition of high speed intermediary "Data Base" Servers  and all that jazz. I ramble, but I'm making some good progress towards a cohesive plan, and it's making everything start to feel like it's coming together. The hacknplan data entry is still much in progress, I've started adding tasks to keep myself on track with adding data to it.. haha, sounds redundant but it's helping me stay on track. Here's the Game Design Model I was talking about in my last thread, I'm enjoying the simplicity of it all. It's essentially just the tree structured view of my Mind Map, so it's pretty easy to keep these two tools in sync.  I add child items where necessary and attach tasks to whatever branch/child I want. The main "Board" view is just a standard KanBan style system, but it's simple and easy to work with, it integrates well with the Game Design Model and seems to be fairly stable. Here I'll attach the whole of the latest Mind Map revision, for the curious and/or gluttons of punishment. I'm happy with my progress so far.  Slowly approaching the Maintenance point.  Then the full code sprint starts again.  I'm still coding, so I don't lose my place, just not at the pace I would like with all the admin work I've given myself.  Anyhow, enough talking about stuff I've already done, I've got stuff to do!

Septopus

Septopus

Organizing this Unsettled World

So, I've been thinking hard about this little project of mine and I came to the conclusion that I'll never finish it if I don't get some things in order around here.  I'm not talking about smelly code either.  That's a constant cleanup task in my world.  NO, I'm talking about Project Management type thingums and stuffs..  So, I spent a few hours working up a mind map on mindmeister only to get it nearly to where I was happy and then their whole interface started failing miserably on me, browser related weirdness..  So I tried xmind, decent, but I knew it was more than I ever wanted to pay for...  FreeMind (https://sourceforge.net/projects/freemind/) to the rescue!  So, here's a little picture that's STARTING to describe all the little bits and pieces that need to be massaged into existence in order to call my game nearly functional in most of the ways I would like.  Every time I look at it I add another two or three or twelve nodes...   In addition to Beginning the Mind Map I've also started using hacknplan.com to keep track of Tasks and my Time investment. I wish very much that I was able to break my entire project down into 50 more tasks that will take less than 200hours to complete.. Very very much..  Sadly, no, this is the result of me entering a goodly portion of the Mind Map data into hacknplan's "Design Model" which is just a simple tree system.  Simple, but quite functional.  Tasks can be created out of items in the tree or attached to items in the tree.  If you are looking for a good free project management system that is designed for building games, it's working great for me so far.  These 50 tasks are probably a 10th of what I still have to add before I have the project "roughed out" in hacknplan(I'm adding in tasks for items that are in "Completed/Testing" stages as well, so I can get an idea of total time investment). I was hesitant to stop working on things for a few days to get this stuff up and running, but I'm already starting to see the benefits.  First off I'm developing a much clearer idea of what I'm actually building.  That's going to be a game changer I think.  Being able to look at the bigger picture of the whole eco-system, I'm able to re-evaluate what code goes where based on what systems need to access/interact with it, instead of a hunch based on a rough imagining of how I want things to work.  I'm also able to begin prioritizing my time and deciding if I want to try to build a project team, or continue the solo adventure.  If I want to have any help though, this whole process is going to be the absolute key to making it possible. So much more to do... I'll post an update again when I get my hacknplan setup more flushed out.

Septopus

Septopus

A Decent String Slinger.. No, not THAT GUY!.. its just a c# UDP server.

Okay, with some help over on the Network & MultiPlayer Forum I've reigned in some of the code smell in my UDP Socket Class. This is the wheel that turns each of my game servers, nearly everything else is game specific logic. I'm sharing it in it's entirety here for your use however you like.  I'll keep this post updated as I make improvements.  If I make any major changes I'll at least provide a link to them here if possible. This class is a multi-purpose UDP Server/Client class that expects and produces strings.  Now, I know that this isn't the BEST or most optimized way to deal with data, however I find it to be extremely flexible for rapid development of server logic code.  When I finalize the server logic I may rewrite the class serialization/deserialization routines to output byte[]'s directly and at that point I'll simply remove the string variable handling from this class so they only interact with byte[]'s. I mean, strings aren't all bad right? At least somebody agrees... Anyhow, here it is.  Have fun.  using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; namespace StringSlinginUDPServer { public class UDPSocket : IDisposable { //Constant for configuring the prevention of ICMP connection resets private const int SIO_UDP_CONNRESET = -1744830452; //UDP socket private Socket _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); //Async Callback private AsyncCallback recv = null; //Buffer Size Constant private const int bufSize = 8 * 1024; //Raw string data from client packets private Dictionary<EndPoint, Queue<string>> messageDictionary; //Queue for holding raw string data from server packets when in client mode. private Queue<string> cQ; //Referece to the data store used by the server(for access to the current game time clock) private TimeCodeDataProvider dataStore; //Time code storage for last sent/received messages private double lastSentMessage = 0; private double lastReceivedMessage = 0; //Boolean to determine which mode we're in so received messages get put in the right place. private bool clientMode = false; //Max string lenght allowed by the servers. private int maxMessageLength = 1450; // Generic Object Pool Class public class ObjectPool<T> { // ConcurrentBag used to store objects. private ConcurrentBag<T> _objects; private Func<T> _objectGenerator; // Object pool contructor used to get a delegate for implementing instance initialization // or retrieval process public ObjectPool(Func<T> objectGenerator) { if (objectGenerator == null) throw new ArgumentNullException("objectGenerator"); _objects = new ConcurrentBag<T>(); _objectGenerator = objectGenerator; } // GetObject retrieves the object from the object pool (if already exists) or else // creates an instance of object and returns public T GetObject() { T item; if (_objects.TryTake(out item)) return item; return _objectGenerator(); } // PutObject stores the object back to pool. public void PutObject(T item) { _objects.Add(item); } } ObjectPool<State> statePool = new ObjectPool<State>(() => new State()); //State class for async receive. class State { public string smessage; public int bytes = 0; public byte[] buffer = new byte[bufSize]; public EndPoint epFrom = new IPEndPoint(IPAddress.Any, 0); } //IDisposable stuff public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { // free managed resources if (_socket != null) { _socket.Dispose(); _socket = null; } } } //Server "Mode" public UDPSocket(Dictionary<EndPoint, Queue<string>> msgsDict, TimeCodeDataProvider DATASTORE) { clientMode = false; messageDictionary = msgsDict; dataStore = DATASTORE; lastSentMessage = dataStore.TimeCodeDoubleValue; } //Client "Mode" public UDPSocket(Queue<string> mq, TimeCodeDataProvider DATASTORE) { clientMode = true; cQ = mq; dataStore = DATASTORE; lastSentMessage = dataStore.TimeCodeDoubleValue; } public void CloseSocket() { _socket.Close(); } //Internal connection status checking public int SendHowStale() { //Console.WriteLine("lmc: " + ((int)dataStore.UWServerSeconds - (int)lastSentMessage).ToString()); if (lastSentMessage == 0) lastSentMessage = dataStore.TimeCodeDoubleValue; return (int)(dataStore.UWServerSeconds - lastSentMessage); } //Internal connection status checking public int ReceiveHowStale() { //Console.WriteLine("lmc: " + ((int)dataStore.UWServerSeconds - (int)lastSentMessage).ToString()); if (lastReceivedMessage == 0) lastReceivedMessage = dataStore.TimeCodeDoubleValue; return (int)(dataStore.UWServerSeconds - lastReceivedMessage); } //Start/Bind a Server socket. public void Server(string address, int port) { //In case restarting uncleanly, dunno if this actually does anything.. _socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.ReuseAddress, true); //Ensure all async packets contain endpoint info and etc. _socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true); //Ignore ICMP port unreachable exceptions. _socket.IOControl((IOControlCode)SIO_UDP_CONNRESET, new byte[] { 0, 0, 0, 0 }, null); //Bind to port. if (address == "all") { _socket.Bind(new IPEndPoint(IPAddress.Any, port)); } else { _socket.Bind(new IPEndPoint(IPAddress.Parse(address), port)); } //Start receive callback process. Receive(); } //Setup a Client to Server socket. public void Client(string address, int port) { //Dunno if these two options do anything for client sockets, but they don't seem to break anything. _socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true); _socket.IOControl((IOControlCode)SIO_UDP_CONNRESET, new byte[] { 0, 0, 0, 0 }, null); _socket.Connect(IPAddress.Parse(address), port); //Start receive callback process. Receive(); } //ServerSend sends to any EndPoint from THIS server. public void ServerSend(string text, EndPoint ep) { try { byte[] data = Encoding.ASCII.GetBytes(text); _socket.SendTo(data, ep); lastSentMessage = dataStore.TimeCodeDoubleValue; //Console.WriteLine("TO NET: " + text); } catch (Exception ex) { Console.WriteLine("ServerSend Exception: " + ex.Message); } } //Client Send only sends to the connected Server. public void cSend(string text) { try { byte[] data = Encoding.ASCII.GetBytes(text); _socket.Send(data); lastSentMessage = dataStore.TimeCodeDoubleValue; //Console.WriteLine("TO NET: " + text); } catch (Exception ex) { Console.WriteLine("cSend Exception: " + ex.Message); } } //Setup Async Callback private void Receive() { State so = statePool.GetObject(); try { _socket.BeginReceiveFrom(so.buffer, 0, bufSize, SocketFlags.None, ref so.epFrom, recv = (_Receive), so); } catch (Exception) { statePool.PutObject(so); } } //Receive Callback private void _Receive(IAsyncResult ar) { State so = (State)ar.AsyncState; try { so.bytes = _socket.EndReceiveFrom(ar, ref so.epFrom); lastReceivedMessage = dataStore.TimeCodeDoubleValue; so.smessage = Encoding.ASCII.GetString(so.buffer, 0, so.bytes); //Console.WriteLine("Msg frm ThreadID: " + Thread.CurrentThread.ManagedThreadId.ToString()); //Console.WriteLine("FROM NET: " + text); if (so.smessage.Length < maxMessageLength) { if (clientMode) { cQ.Enqueue(so.smessage); } else { if (!messageDictionary.TryGetValue(so.epFrom, out Queue<string> queue)) { messageDictionary.Add(so.epFrom, queue = new Queue<string>()); } queue.Enqueue(so.smessage); } } _socket.BeginReceiveFrom(so.buffer, 0, bufSize, SocketFlags.None, ref so.epFrom, recv, so); } catch (Exception) { statePool.PutObject(so); } } } } My Usage Looks Like(c# console app): namespace StringSlinginUDPServer { class MyServer { public static bool running = true; public static Dictionary<EndPoint, Queue<string>> MessageDictionary = new Dictionary<EndPoint, Queue<string>>(); public static Thread udpServerThread; static void UDPServer() { using (s = new UDPSocket(MessageDictionary, TimeCodeDataProvider)) { s.Server("IP.ADD.RE.SS" or "all", PortNumber); while(running) { //Servery Stuff Goes Here. //Like reiteratively dequeuing the Message Dictionary Queues and processing/replying to all commands/etc... } } } static void Main(string[] args) { udpServerThread = new Thread(new ThreadStart(UDPServer)); udpServerThread.Start(); while (running) { //Logic to check the connection status and start/restart the thread if it's dead/halted.. //Trap key sequences to close server/etc.. } } } } And here's a rough example of how I use the Client functionality, this routine checks the server via a simple UDP transaction. static bool UDPCheckOK() { bool res = false; Queue<string> udpResponseQ = new Queue<string>(); using (c = new UDPSocket(udpResponseQ, TimeCodeDataProvider)) { c.Client("127.0.0.1", PortNumber); c.cSend("SOCKETCHECKSTRING"); int wc = 0; while (udpResponseQ.Count < 1) { wc++; if (wc >= 20) { goto timeout; } Thread.Sleep(50); } timeout:; if (udpResponseQ.Count > 0) { while (udpResponseQ.Count > 0) { switch (udpResponseQ.Dequeue()) { case "SOCKETCHECKOKSTRING": res = true; break; } } } } return res; } Happy Coding!

Septopus

Septopus

Stateless Frustrations Frustratingly Stated (Handling Improperly Disconnected UDP Clients?)

So, after weeks of strange and seemingly intermittent/coincidental UDP server crashes(Receive Thread Halt/Un-catchable Exception) and the ensuing frustration that that causes when you aren't even trying to work on the server itself.  I sat down and spent a day trying to figure out the problem..  After an almost complete server rewrite from scratch with no real improvement in the problem.. I turned to random poking about on the internets.. Hmmm...  It seems like the problem only happens when I'm restarting the client.. Here's a post!! https://stackoverflow.com/questions/38191968/c-sharp-udp-an-existing-connection-was-forcibly-closed-by-the-remote-host And Here! https://social.msdn.microsoft.com/Forums/en-US/6b7aa353-259b-4637-b0ae-d4e550c13e38/c-udp-socket-exception-on-bad-disconnect?forum=netfxnetcom Yup, that's the problem for sure. It appears that even though UDP isn't supposed to care about the other ends state, apparently this isn't true if the state is Port Unreachable.  When the server attempts to receive data (BeginReceiveFrom/BeginReceive/EndRecieveFrom/EndReceive/etc..) from an async connection and the client has (Crashed/Quit/Etc.) an ICMP port unavailable packet is generated and this causes the UDP socket to generate an exception that (even if you try/catch the hell out of it) causes the async receive thread to halt(I assume, this is the effect anyhow). The best solution I've found, as stated in the link(s) above is to change the underlying IOControl behavior(Prevent the Exception from being thrown). //This value tells the underlying IO system to IGNORE the ICMP error. public const int SIO_UDP_CONNRESET = -1744830452; private Socket _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); _socket.IOControl((IOControlCode)SIO_UDP_CONNRESET, new byte[] { 0, 0, 0, 0 }, null); _socket.Bind(new IPEndPoint(IPAddress.Any, port)); The first line is the value that needs to be set, and the second to last line is how you can set it.  And these are the live bits of code that are WORKING without error in my server, Finally!  So, there, I've posted it again because it seems way more obscure than it should be. Enjoy.

Septopus

Septopus

Procedural Landscape or how to Shatter a Planet! (with actual code examples)

I've spent the last few days coding in my Economy Server, I've almost completed the functionality of Banking!  So, it's truly time for a rewind about something entirely different to clear my head. Here's an editor view from a little while back, before I was loading a "visible planet" object, the procedurally loaded landscape was the only visible landscape, the rest of the planet, if you could see it before the landscape loaded, just looked like water. So, let me start off by clarifying, the landscape in my game is generated prodedurally, however it is not "generated" during game play. I have a separate process that I can run on any sculptured sphere of my choosing that will generate the individual triangular mesh objects and then save them into "tile" files.  These tile files also contain place holders for items such as Arrays of Trees, Rocks, and Plants. Let's start at the Crawler!  The following script is the code that crawls from triangle to triangle along the planet mesh and kicks off the generation of the tiles. using System.Collections; using System.Collections.Generic; using UnityEngine; public class MeshCrawler : MonoBehaviour { public Vector3[] verts; public int[] tris; public Vector3[] norms; public int curTri = 0; public int startTri = -1; public int endTri; public string meshName; public List<int> triBuffer = new List<int>(); bool crawling = false; // Use this for initialization void Start () { verts = GetComponent<MeshFilter>().mesh.vertices; norms = GetComponent<MeshFilter>().mesh.normals; tris = GetComponent<MeshFilter>().mesh.triangles; meshName = GetComponent<MeshFilter>().mesh.name; } IEnumerator CrawlIT() { crawling = true; if (endTri > (tris.Length / 3)) endTri = tris.Length / 3; if (startTri > -1 && endTri > 0) { Debug.Log("Crawling " + startTri.ToString() + " to " + endTri.ToString()); for (int i = startTri; i < endTri; i++) { curTri = i; Vector3 p0 = verts[tris[i * 3 + 0]]; Vector3 p1 = verts[tris[i * 3 + 1]]; Vector3 p2 = verts[tris[i * 3 + 2]]; Vector3 n0 = norms[tris[i * 3 + 0]]; Vector3 n1 = norms[tris[i * 3 + 1]]; Vector3 n2 = norms[tris[i * 3 + 2]]; UWTerrainTri triCandidate = new UWTerrainTri(); triCandidate.triIndex = i; triCandidate.meshName = "PlanetZed"; triCandidate.points = new Vector3[3] { p0, p1, p2 }; triCandidate.normals = new Vector3[3] { n0, n1, n2 }; TerrainController.TriHit(triCandidate); //Debug.Log("Tri: " + i.ToString()); yield return null; } } Debug.Log("Crawler finished."); crawling = false; } // Update is called once per frame void Update () { if (triBuffer.Count > 0) { if (!crawling) { startTri = triBuffer[0] - 5; endTri = triBuffer[0] + 5; run = true; triBuffer.RemoveAt(0); } } if (!crawling) { StartCoroutine(CrawlIT()); } } } Okay, so what's it doing? This script is attached to the planet mesh object and it starts a Coroutine(Unity safe psudo-thread a "function that yields") that handles all the data gathering steps. This routine grabs the triangles array from the MeshFilter and works its way from 0 to the end, pulling the vertices out of the MeshFilters verts array, stuffing those into a class that is passed to another bit of code.  The following code decides how to handle the UWTerrainTri class objects that are passed from the Crawler: //The above code sends it's result to a "TriHit()" routine. //that routine has terrain Loading functionality in it, in this mode mode //it just passes the UWTerrainTri object to GenTriLand(): public List<Vector3> vertsWTrees = new List<Vector3>(); public void GenTriLand(UWTerrainTri uWTerrainTri) { //Create a new mesh object. Mesh mesh = new Mesh(); //This is the class that is saved as the "tile" object. TerrainSegment ts = new TerrainSegment(); //Generate a good file name. string tpName = uWTerrainTri.meshName + uWTerrainTri.triIndex.ToString("0000000"); //Grab the vertices. ts.vertices = uWTerrainTri.points; //500,000 files in the same directory is a BAD idea. ;) string dir1 = tpName.Substring(9, 2); string dir2 = tpName.Substring(11, 1); dir2 = "0" + dir2; //Generate a full file name. string fullFilePath = Application.persistentDataPath + "/tdat/" + dir1 + "/" + dir2 + "/" + tpName + ".dat"; mesh.vertices = ts.vertices; mesh.uv = new Vector2[3] { new Vector2(0, 0), new Vector2(0, 1), new Vector2(1, 1) }; mesh.triangles = new int[] { 0, 1, 2 }; mesh.RecalculateBounds(); mesh.RecalculateNormals(); ts.vertices = mesh.vertices; ts.normals = mesh.normals; ts.uvs = mesh.uv; ts.Tris = mesh.triangles; //Data Objects for Rocks, Grass, Trees List<EnvironmentObject> rl = new List<EnvironmentObject>(); List<EnvironmentObject> gl = new List<EnvironmentObject>(); List<EnvironmentObject> tl = new List<EnvironmentObject>(); List<Vector3> usedVertices = new List<Vector3>(); //Rocks int r = UnityEngine.Random.Range(3, 12); for (int i = 0; i < r; i++) { Vector3 point = GetRandomPointOnMesh(mesh); int tryCount = 0; while (usedVertices.Contains(point)) { tryCount++; point = GetRandomPointOnMesh(mesh); if (tryCount > 10) goto NoRock;//Yeah goto just works sometimes, hate me if you want. } usedVertices.Add(point); EnvironmentObject eo = new EnvironmentObject(); eo.prefab = "rock" + UnityEngine.Random.Range(1, 3).ToString() + "sm"; eo.pos = point; eo.rot = Quaternion.identity; eo.scale = Vector3.one; rl.Add(eo); NoRock:; } usedVertices.Clear(); //Grass int g = UnityEngine.Random.Range(2, 8); for (int i = 0; i < g; i++) { Vector3 point = GetRandomPointOnMesh(mesh); int tryCount = 0; while (usedVertices.Contains(point)) { tryCount++; point = GetRandomPointOnMesh(mesh); if (tryCount > 10) goto NoGrass; } usedVertices.Add(point); EnvironmentObject eo = new EnvironmentObject(); eo.prefab = "grass" + UnityEngine.Random.Range(1, 4).ToString(); eo.pos = point; // + new Vector3(UnityEngine.Random.Range(-5.0f, 5.0f), 0, UnityEngine.Random.Range(-5.0f, 5.0f)); eo.rot = Quaternion.identity; eo.scale = Vector3.one; gl.Add(eo); NoGrass:; } usedVertices.Clear(); //Trees int t = UnityEngine.Random.Range(0, 3); for (int i = 0; i < t; i++) { Vector3 point = mesh.vertices[UnityEngine.Random.Range(0, mesh.vertices.Length)]; int tryCount = 0; while (vertsWTrees.Contains(point)) { tryCount++; point = mesh.vertices[UnityEngine.Random.Range(0, mesh.vertices.Length)]; if (tryCount > 10) goto NoTree; } vertsWTrees.Add(point); EnvironmentObject eo = new EnvironmentObject(); eo.prefab = "tree" + UnityEngine.Random.Range(1, 3).ToString(); eo.pos = point; GameObject tree = new GameObject(); tree.transform.SetParent(planet.transform); tree.transform.localPosition = eo.pos; float maxs = 10; if (eo.prefab == "tree2") maxs = 15; FixTree(tree.transform, 5, maxs, 10); eo.pos = tree.transform.localPosition; eo.rot = tree.transform.localRotation; eo.scale = tree.transform.localScale; Destroy(tree); tl.Add(eo); NoTree:; } //Housekeeping for performance. if (vertsWTrees.Count > 10000) { while (vertsWTrees.Count > 10000) { vertsWTrees.RemoveAt(0); } } ts.rocks = rl.ToArray(); ts.grass = gl.ToArray(); ts.trees = tl.ToArray(); //Save terrain "tile" file. string json = JsonUtility.ToJson(ts); File.WriteAllText(fullFilePath, json); //Debug.Log("New Terrain Generated: " + tpName); } [Serializable] public class TerrainSegment { public Vector3[] vertices; public Vector3[] normals; public Vector2[] uvs; public int[] Tris; public EnvironmentObject[] rocks; public EnvironmentObject[] grass; public EnvironmentObject[] trees; } [Serializable] public class EnvironmentObject { public string prefab; public Vector3 pos; public Quaternion rot; public Vector3 scale; } This code produces a file for each triangle in the planet mesh.  These files are then "streamed" to the client when any of many "mesh agents"(attached to the player object) in the game does a raycast at the ground and discovers a triangle ID that has not been loaded.  The game client will then send a request to the server to download the tile number that hasn't been found in the local cache of tile files. Which as you can see from the above image, it works pretty swimmingly. Let me know what ya think!  

Septopus

Septopus

A Story of Some Servers and Their Game.

Unsettled World, makes a great title I think, but without a server, it's just a terribly lonely place indeed. I've written many servers over the years and clients to go with them, and in lots of different languages(never before for games).  However,I've never been one to hang onto the proper semantics of communicating how a program works so, stick in there and I'll do my best.   Please feel free to chime in if you see something technical that could be fixed or if it just looks at you funny, you can tell me that too.  I'm good with it.  Architecture: Multiple, functionally segmented, server applications / To many, identically functional clients.  To sum it up..  haha  It's important to say that all servers maintain an authoritative role in all matters of game play.  If the Avatar Server hasn't authorized that item in your inventory, it won't be there.  If the Economy Server has a 0.00 in your bank account, the game client will reflect that. Avatar Server(Functional in all listed categories): C# UDP server application, game time keeper, controls authentication, resource access, player inventories, land ownership and mining claim ownership and is the "echo point" for network player data such as positioning and appearance data. Economy Server(Currently in ACTIVE Dev): C# UDP server application, Communicates with the Avatar Server to coordinate time synchronization, and to update its list of active player id#s and their encryption keys. Its purpose is to house all the functionality that one would attribute to a game economy(aside from physical resource access).  Banking, Auctions and a Resource Stock Market.  My Player1 checked the balance on his bank account for the first time just yesterday in fact.  He was pleased to finally see the 2000.00 Settler Credits he was promised months ago.  lol. NPC Server(s)(Currently Completely Imaginary): C# UDP server application that communicates the positions and coordinates the movements of NPCs that are instantiated into the world by their NPC Client applications. NPC Client(s)(Currently Completely Imaginary): C#(or c#/unity) UDP Client applications that will control the movement and interactivity of one or many NPCs, all NPC code will interact with the server just like player code does, via a UDP connection.  However, since most of the NPC data will already be built into the client, these connections will be significantly less chatty than a real players connection. Other Servers/Clients(Also Completely Imaginary): C# probably, UDP most likely.. the rest, who knows. So, how do I keep all these data bits ready for player interaction?  Data Structures!!  Everything in my servers runs from in-memory data structures, I have no database back end, nor a desire to ever need one.  I'm not saying databases aren't cool, or massively useful in similar scenarios.  I have built MANY of those too, but to get the response times I want, well that would require something in the range of an in-memory instance of mysql or another big hitter similarly configured.  I don't want to have to add that to the arena, and certainly not at this stage of the game.. heh..  stage of the game... Anyway! Some of the Data Structures Currently Utilized in my Avatar Server: //This keeps track of the player ids and encryption strings //When a player logs on an encryption string is negotiated... Dictionary<int, string> playerKeys = new Dictionary<int, string>(); //Reverse lookup for player ID#s to stored GUID ids. //ID#s are shorter and travel better in a compact UDP packet(1470ishbytes) than a big Ol'GUID! Dictionary<int, string> playerGUIDLookup = new Dictionary<int, string>(); //"Table" of currently authenticated Player Save data for many uses here and there, but mostly for persistence purposes. //This gets updated by the client when the player makes changes and is saved on the server side. //Including the character's avatar description. (Avatar Server) Dictionary<int, AvatarSave> PlayerAuth = new Dictionary<int, AvatarSave>(); //Seems redundant somehow, but it isn't. Just, a bad name. //This keeps count of how long a player is idle from the server's perspective, so it can give them the BOOT when it goes too long. Dictionary<int, int> livePlayers = new Dictionary<int, int>(); //Seperate "Table" of data specific to the player avatar movements and orientation, position/rotation/etc... Dictionary<int, AvatarAttitudeUpd> livePlayerData = new Dictionary<int, AvatarAttitudeUpd>(); //This holds data recieved asynchronously from the game clients(or other servers) as well as the EndPoint it was recieved from, for replies. //Each recieved command in this Queue is given a Task to process and reply to the game client with. //One message/activity/request/command per packet and only ONE, so no complicated re-assembly is needed on either end of the wire. Queue<KeyValuePair<string, EndPoint>> UDPClientMessages = new Queue<KeyValuePair<string, EndPoint>>(); //A less organized version of the above data structure. These get filtered for players who aren't logged in anymore, commands out of order get skipped, and etc.. Essentially it's the RAW data from the asyncronous receiver. Dictionary<EndPoint, Queue<string>> UDPMessages = new Dictionary<EndPoint, Queue<string>>(); //The collection of all known player IP/PORT end points, that haven't been booted for inactivity. Dictionary<int, EndPoint> playerEPs = new Dictionary<int, EndPoint>();   Here's a quick run.. Avatar Server Application Starts:
  udpServerThread = new Thread(new ThreadStart(UDPServer)); UDPServer() { //Starts up a standard UDP Socket Server here with an asynchronous receive mechanism which pumps into the UDPClientMessages Queue. Loops here waiting for clients to send data. while(true) { Increments Game Time, a double precision counter that keeps track of the total run time of the avatar server. Parallel.ForEach() through the playerEPs sending them them the current server time. foreach() through the livePlayerData sending all relevant network player data to the clients who should see them. Some threadpool scaling to increase/decrease the pool size depending on # of users logged in. Requeueing of messages from UDPMessages into UDPClientMessages happens via some Task.Factor Tasks. And finally a new Task.Factory Task is created for every item in the UDPClientMessages Queue to process the incoming command from the player's game client. (player's new coordinates, logins, inventory requests, changes,etc,etc,etc)... } }   I can't even call that pseudo-code but I think it conveys what I'm trying to share without going into too much gory detail.  If anybody wants some of that, feel free to comment below and I'll expand on anything I can.  But this describes the basic conceptual design of my server applications. There are whole other layers of data structures in that server that I've omitted as it gets extremely gory in there...  But that's where the inventories / resources / and crafted items live and it's a bit messy right now.  I'm going to try to get those a little bit more finalized before I try to convey their madness to you.  Suffice to say, there's a lot more to it than this.  But that is less related to the "server" and more related to the game. Until next, Thanks for reading!

Septopus

Septopus

How and Why my world Turns.

Continuing my rewind for Unsettled World: Time to give some detail on the foundations of my game environment.  The planet I've created and how it mechanically works within Unity. Here is a 2d view of the editor interface with the "canvas" highlighted sitting at 0,0,0.  The planet object has a 30000m radius and it is sitting @ 0,-30000,0. The object parentage looks like this.  Generic GameObject "PlanetHolder", non-rendered mesh w/collider "Planet" (used by the procedural terrain system mostly), followed by the camera, player, water sphere and the actually visible surface mesh.  Now, this arrangement may change somewhat, I may figure out a way to combine the "Planet" with the visible surface, but it is how it is for now.  And it works. The PlanetController script is responsible for keeping the player and camera close to Origin, 0,0,0.  This is necessary because once your player/camera cross the 10000m distance boundary the underlying floating point mathematics loses enough precision that your meshes begin jumping around the screen.  The PlanetController script contains this logic: public class PlanetController : MonoBehaviour { public GameObject Planet; public GameObject Player; Rigidbody rb; void Start() { rb = Player.GetComponent<Rigidbody>(); } void FixedUpdate() { if (rb.velocity.magnitude < 1) { Planet.transform.localRotation = Quaternion.FromToRotation((Player.transform.localPosition - Planet.transform.localPosition).normalized, Vector3.up); } } } Keep in mind I omitted a lot of extra stuff from that code, for example, it doesn't actually run EVERY FixedUpdate, but as a timed event inside the fixed update, every few seconds or so. Also Important to note, I still have a minor amount of vertical jitter that is caused by the planet sitting at 0,-30000,0.  I am actively working at ways to reduce this, in fact, as I type I'm having more ideas on how to fix the issue.. So keep posted.  ^Solved and NOT related to the size/positioning within the 3D space, see comment below. The visual look of the planet(Before the procedural code kicks off and repaints it with textures/objects).  Is a split up version of the planet mesh(will be split up MUCH more than this eventually)..  And the water is a simple sphere object(also broken up and non-visible sections removed) with a water texture. My Gravity "system" is also very simple(Just a script, like this one, attached to the objects that need gravity applied to them): public class GravControl : MonoBehaviour { public GameObject planet; private Rigidbody rb; public Vector3 upVector; void Start() { if (planet == null) { planet = GameObject.Find("Planet"); } rb = GetComponent<Rigidbody>(); Vector3 upVector = (transform.position - planet.transform.position); transform.rotation = Quaternion.FromToRotation(transform.up, upVector) * transform.rotation; } void FixedUpdate() { upVector = (transform.position - planet.transform.position).normalized; rb.AddForce(-upVector * 9.81f, ForceMode.Acceleration); } } I may incorporate this into a "controller" type script that isn't attached to every object at some point for simplicity, but for now, it works just fine. So that, just a few pics and sentences, well summarizes at least 2 or 3 solid weeks of 8+ hour days... hahaha!

Septopus

Septopus

Inception, or How I Lost My Mind.

Well, this is the first post for a project I've been working on for a few months already.  So, let me rewind a bit.... A few months back I started toying with the idea of making a game with an actual spheroid planet as the core of the environment.  Yay Unity and Blender!!  A few weeks of toying around and I had a pretty neat little 5000m diameter spheroid planet with custom gravity and a player character(UMA v2) running around on it.  Well, that was way easier than I expected... It was at this very point, the actual madness began to set in.  Well, it's working, I guess I should make a game out of it...  Okay.  Hmm, online?  Of course!  Hmm, mmoYES!  Oh, damn, I've really lost it now.  Crafting, well, certainly.  Mining and hunting?!  Don't be silly, yes yes yes yes, all the yes. Bigger? hmmm.... hmm... yes? Let's try 10000m radius..  hmmm...  Jittery character movements.. and jittery everything... hmm...  9000m radius?  all good... hmmm... And this is where I learned all about the floating point precision problems of 3d engines at great distances from 0,0,0..  Fun stuff.  Only slightly maddening.  But, in the end, also easily solved by rotating the entire freakin planet periodically so the player character and camera stay close to 0,0,0. Let's pump this guy up to 30000meter radius and call that good for now.  I ran across a few other jitter problems along this winding path but those were solved through the careful use of the "FixedUpdate" vs "Update" method selection.  Moving anything that involved my custom gravity or physics in general to a FixedUpdate method was the solution for most cases. So, we got a character and a planet...  I guess we need stuff to do.  And from here the rabbit hole only started lengthening, until there really was no beginning or end. So, there's one of the nutshells...  The inception! Over the next few days I'm going to try to detail as much of the current guts of this beast as I can for anybody who's crazy enough to follow along, while trying to not distract myself from creating more of them of course.    Project Stats: Unity Game Client Lines of Code: 24,032 Avatar Server Lines of Code: 2,480 Economy Server Lines of Code: 387

Septopus

Septopus

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!