Tagebuch: Industry City

Guava hat doch Table für sowas: https://google.github.io/guava/releases/19.0/api/docs/com/google/common/collect/Table.html

Das ist schön für guava. Nur wird mir das nicht helfen mit C# :wink:

Ich glaube ich verstehe die Problematik nicht ganz. Was ich mitgenommen habe, du versuchst die 2D Map als Array zu speichern, aber die Map ist nicht gleichförmig wie ein Array…?

Nein. 2D würde schon passen. Die Gebäude brauchen 2 Koordinaten (x und z, y wäre die Höhe und die ist immer 0). Deswegen dachte ich inital an ein 2D Array. Da die Position sehr einfach zu ermitteln wäre anhand der Position in der Welt (jedes Gebäude hat eine Fläche von 1x1).

Abgespeichert wird (aktuell noch) dann eben ein 2D Array. Will ich die Information jetzt im Spiel Verwenden um die map zu erstellen, dann transformiere ich meine Daten in ein anderes Datenmodell welches die gleichen Felder hat + Vector Infos. Denn ein Index von [0][0] könnt z.B. für x:-3 und z:-3 stehen. Dafür muss ich halt einmal das komplette 2D Array durchgehen.

Ich poste einfach Mal das entsprechende Codestück:

public IEnumerable<CityInfo> GetInfos() {
            for (var x = 0; x < CityElements.Length; x++) {
                var elements = CityElements[x];

                if (elements == null) continue;

                for (var z = 0; z < elements.Length; z++) {
                    var element = elements[z];

                    if (element != null) {
                        yield return new CityInfo {
                            Type = element.Type,
                            Position = new Vector3(x, 0, z)
                        };
                    }
                }
            }
        }

internal int CoordinateToIndex(int position) => position + Size;
internal int IndexToCoordinate(int index) => index - Size;

Also wie du siehst: das ist doch relativ viel Aufwand. Wenn ich von dem 2D Array aber weg gehe zu einem Dictionary, dann kann ich auf eine ein 2tes Datenobjekt verzichten.

Wobei ich hier wohl vergessen hab den Index in Koordinaten umzurechnen. Wäre eigentlich ein Bug ^^

Die neue GetInfos schaut übrigens nun so aus (noch nicht 100% sicher ob final):

public IEnumerable<CityInfo> GetInfos() {
    foreach (var info in CityInfos) {
        yield return info.Value;
    }
}

Das schöne daran ist folgendes:

  1. Da ich nicht das Dictionary zurück gebe, kann der Aufrufer dieses auch nicht manipulieren
  2. CityInfos ist ein struct. Und (in C#) wird dabei keine Referenz übergeben - sondern eine Kopie des Structs (so hab ich es zumindest mal verstanden). Was soviel bedeutet wie: Der Aufrufer kann nicht (ausversehen) das Originale Objekt manipulieren :slight_smile:.

Ok, hab mal etwas weiter gemacht und das neue Format funktioniert wunderbar :slight_smile:.

Zudem hab ich noch ein paar Sachen eingebaut/geändert.

  1. Das speichern hat nicht funktioniert, da Vector3 nicht Serializeable ist. Darum musste ich mir eine neue Variante suchen und bin auf hab die Lib json-net gefunden. Die bringt gleich auch noch den Vorteil mit sich, dass ich das ganze im BSON-Format speichern kann. Zudem lasse ich noch eine kleine Encrypten über den BSON-String laufen.
  2. Ich hab jetzt eine Möglichkeit eingebaut, dass ich die Stadt in Unity aufbauen kann. Im Inspector hab ich nun noch einen Extra-Button save, welcher mir dann die Stadt (so wie sie ist) in die Datei rein schreibt :slight_smile::

Aber ich glaub, dass wars dann auch für heute :slight_smile:.

Ich verstehe, du meinst weil die Koordinaten zum Rendern (oder für Gameplayspäßchen) so oder so im Vector3 Format vorliegen muss.
Wenn ich darüber nachdenke, mache ich es nicht anders: Jedes Objekt speichert nochmals seine eigene Position im Array aus das sich auch die Position beim Rendern berechnet.

Das Array hat halt den Vorteil dass du sicherstellst an jeder Position auch wirklich immer nur ein Objekt zu haben. Zudem ist es relativ einfach auf den Inhalt von Nachbarzellen oder zu ganz spezifischen Zellen zu zugreifen. Ansonsten musst du halt jedes mal den Container durchwandern.

Die Vorteile hab ich dem Array auch anfangs zugeschrieben. Und bei der Umsetzung hab ich gemerkt, dass ein Hash doch kein guter Index ist. Deswegen ist mein Index die Koordinate als String (z.b. “3|1”). Das ist aber eine interne Geschichte, die Methoden akzeptieren Vector3. Somit hab ich praktisch die gleichen Vorteile wie beim Array. Auch Nachbar-Zellen sind kein Problem. Kann die ja auch einfach per Index versuchen auszulesen wie ich es beim Array machen würde. Beides sind O(1) operationen (zumindest in C#. Bei Java weiß ich es gerade nicht müsste es mir nochmal anschauen).

Dementsprechend hat für mich der Container halt die gleichen Vorteile und mehr, denn:

  1. Er hat eine dynamische Größe und ist imho einfacher im Umgang (z.B. wenn das innere Array unter Index x nicht existiert muss ich es immer zuerst anlegen, oder eben das ich ein Koordinaten Mapping brauch - sofern ich negative Koordinaten unterstützen möchte)
  2. Ich brauche nur noch ein Modell. OK zugegeben: das hätte auch mit einem Array so funktioniert. Aber mir hat es iwie nicht gefallen, dass die Positionen 2x vorhanden sind. Und ne, ich hab ka warum es mich bei dem Dictionary nicht stört :-/

Ich werfe noch zwei Vorteile fürs Array ein:

  • Performanter
  • Weniger Speicherplatz
    Beides war mir auf dem Smartphone sehr wichtig weil ich eine möglichst große Karte anlegen wollte. 8 MB von 128 MB für eine 1000x1000 große Karte hört sich erstmal wenig an, aber wir sprechen hier nur über die Adressierung und die Anforderung steigt n^2. Und je nachdem kann 1000x1000 eher klein sein.

Wobei mir gerade auffällt dass ich jedesmal auch leere Felder bearbeiten muss, bei dir passt sich die Anforderung dynamisch an den Inhalt an.

Das dürfte bei mir sogar noch viel schlimmer sein. Bei dir sind leere Felder null. Aber wenn man bei C# nicht aufpasst und mit Structs arbeitet, dann gehe ich von aus, dass jedes Array eben tatsächlich ein Objekt enthält. Denn Structs sind per default nicht null:

MyStruct myStruct;
myStruct.ValueXyz = "Hallo Welt";

würde z.B. nicht mit einer NPE enden.

Somit würde ich hier tatsächlich Speicherplatz verschenken. Wegen der Performance mache ich mir derzeit noch keine Gedanken (zumindest nicht für den Punkt). Denn meine Map wird um ein vielfaches kleiner ausfallen. Da man momentan die Kamera nicht bewegen kann, würde ich mal drauf tippen, dass 20x30 schon mein Maximum sein würde.


Aber wie gesagt: das ganze Konstrukt steht und fällt mit dem Szenario (und den Implementierungen). Wenn ich 1000x1000 Objekte im Speicher von einem Smartphone halten müsste, dann würde ich auch zusehen, dass das möglichst wenig Platz einnimmt.

So. Ich glaube meinen Task nun fast abgeschlossen zu haben. Beinahe hätte ich vergessen, die Stadt auch erstellen zu lassen bei einem neuen Spiel - aber das hab ich jetzt drin ^^.

Aber nicht nur das. Das Spiel erkennt nun wenn was mit den Speicherdateien nicht in Ordnung ist (z.B. weil eine fehlt), dann erkennt es das Problem und repariert es.

Muss halt noch ein wenig testen. Sollte das alles passen, dann hab ich für die nächste Version nur noch einen Bugfix in Planung. Somit sollte ein Release in diesem Jahr noch machbar sein.

Wie „Release“ in den nächsten 7 Tagen? :open_mouth: Wann ist dass denn passiert?

Ja - aber ich rede nicht vom fertigen Spiel ^^. Sondern von version 0.1.2 (welche eben das neue Map-Handling, kleine Verbesserung in der Bedienung und einen Bug-Fix enthalten soll)

Her damit :blush: :raising_hand_woman:

So. Mein „Weihnachtsgeschenk“ ist raus. Ich hab Version 0.1.2 in den Store eingereicht :slight_smile:.

1 „Gefällt mir“

Hab heute etwas festgestellt, was ich mir schon all die Zeit „gewünscht“ habe. Meine Tests sahen bisher immer so aus, dass ich mir mehr oder weniger mein Prefab im Code nachgebaut hatte (was mitunter der Grund war, für so manchen internal setter).

Jetzt hab ich aber rausgefunden, wie einfach es ist meine existierenden Prefabs zu nutzen - was ja auch sehr viel besser ist! Denn somit teste ich nicht nur den Code - sondern auch ob meine Prefabs noch passen :slight_smile:.

Und alles was ich dafür machen musste, war mir diese eine kleine Hiflsmethode zu schreiben:

private static T Load<T>(string path) where T : Object {
    return Object.Instantiate(AssetDatabase.LoadAssetAtPath<T>(path));
} 

In Unity3D bekomme ich den Pfad auch sehr einfach über das Kontext-Menü. Das ist wirklich eine sehr angenehme Sache :slight_smile:.

Hach. Tests sind schon etwas schönes. Gerade eben wieder gemerkt. War mir sicher: passt alles, der Test wird grün sein. Aber zur Sicherheit nochmal ausgeführt und er war rot. Grund war einfach:

set { inputSelection.gameObject.SetActive(value); }

Hier hatte ich nicht wirklich aufgepasst und anstatt „value“ „true“ geschrieben gehabt ^^. Schön wenn man solch kleine Bugs direkt ausmerzen kann :stuck_out_tongue:

So - für meine nächste Version soll man Gebäude “Konfigurieren” können. Also im wesentlichen soll man bestimmen können, wer liefert und wer beliefert wird.

Hab eben die Funktionalität geschrieben, dass das Panel entsprechende Aufrufe verarbeiten kann. Ziel ist es, dass man über das obere Panel den Lieferanten (input) und das untere Panel das zu beliefernden (Output) angeben kann. Hier mal ein kurzes Video dazu:

Mein Speichersystem bringt mich gerade etwas an den Rande der Verzweiflung. Warum: es zeigt mir die Schwächen meiner Architektur gerade auf.

Ich bin gerade einiges am Umbauen - aber ich glaube die Erkenntnis von vor eine Woche (Also das man assets in den Test laden kann) - hätte einiges an Einfluss auf meine Architektur gehabt.

So manche Entscheidung hab ich getroffen - weil es sonst schlecht zu testen wäre. Aber dadurch, dass ich fertige Assets „einfach“ durch IntegrationsTests rennen lassn kann, hätte ich die gut getestet. Somit müssten nur die Services der Assets „geunittested“ werden und alles wäre schön, gut, toll und wunderbar.

NUR HAB ICH DAS NICHT GEWUSST -.-

Ich mein ok: zweites Spiel. Ich hab sicherlich noch viel zu lernen (und ich würde mir einiges erleichtern, wenn ich mir Videokurse bis zu Ende anschauen würde ^^), aber trotzdem. Ich könnte mir in den Arsch treten, dass ich das damals einfach so hingenommen hab anstatt mal danach zu schauen. Und ja - ich dachte mir schon öfters, dass ich das sehr gut gebrauchen könnte :frowning:.