Jump to content
Unity Insider Forum

Leveleditor mit Sprites und deren Priorität


Antragon

Recommended Posts

Hallöchen Leute,

Ich arbeite zur Zeit an dem rasterbasiertem LevelEditor für Lucy Lamplight:

 

Ein Aufhänger ist leider immer wieder die Priorität der Sprites beim Überlappen. Ich versuche mal die Bauteile anhand des angehängten Bilds zu erklären:

- jeder Sprite besteht aus einem Box-Collider und einem Überhang
- der Collider richtet sich nach dem Raster (es gibt zum Beispiel (BxH): 1x1, 2x1, 1x2 und 2x2 große Collider, aber nicht 1.3x0.5 o.ä.)
- der Überhang ragt in die benachbarten Raster-Teile herein und kann eine Breite von 0-1 haben (aber nie größer als 1)

Betrachtet man im angehängten Bild den roten Sprite, hat dieser einen 2x2-Collider und ein wenig Überhang auf jeder Seite. Wie ihr auch seht, wird der rote Sprite von grünen, zweifeldergroßen Sprites überlappt. Die Regeln für die Überlappung sollen sein:
1.) Ist die untere Kante von Sprite A auf der gleichen Horizontalen Ebene wie die obere Kante von Sprite B, so soll Sprite A eine höhere Priorität haben
2.) (Falls 1. nicht gilt) Ist die rechte Kante von Sprite A auf der gleichen Vertikalen Ebene wie die linke Kante von Sprite B, so soll Sprite A eine höhere Priorität haben


Es gibt auch Ausnahmefälle, bei denen die zweite Regel für Sprite A umgekehrt wird. Im unteren Bild wäre der blaue Sprite eine solche Ausnahme.

 

Fehlgeschlagene Versuche

Die Grundidee ist recht einfach:

Priorität=position.y - position.x

(Bei der Position gehe ich immer von der linken unteren Ecke des Sprites aus.)
Dass das ganze nicht klappt sieht man schon am Beispiel-Bild: Ausgehend davon, dass der rote Sprites beispielsweise eine Priorität von 0 hat, würde der grüne Sprite links unten eine Priorität von 1 haben (also darüber liegen), was nach obiger Regel falsch wäre. Dass das ganze für den blauen Sprite nicht funktioniert, liegt hoffentlich auf der Hand.

Eine bessere Idee wäre folgende:

Normale Sprites (rot, grün):

Priorität=position.y*rastergröße.x - position.x + 1

Blaue Sprites:

Priorität=position.y*rastergröße.x

Dadurch hätten automatisch alle Sprites mit höher y-Position eine höhere Priorität als alle anderen. Dumm ist nur: Der rote Sprite hat eine niedrigere Position als der grüne Sprite rechts oben; da hier aber Regel 1 nicht greift, muss Regel 2 angewendet werden und er soll den grünen Sprite stattdesse überdecken.

Es gab auch viele Versuche Sprites zu splitten, wobei jeder Teil-Sprite eine eigene Priorität bekommen hat. Ein 2x2-Sprite wurde beispielsweise in 4 Einzelsprites zerlegt. Ohne zu weit ausschweifen zu wollen: Nichts hat so richtig funktioniert.

 

Der Lösungsansatz

Daher möchte ich eine andere Herangehensweise versuchen: In der Theorie könnte man erst alle Sprites platzieren und ihnen dann händisch Prioritäten zuweisen, bis alles so passt, wie man es haben will. Ein ähnliches Verfahren möchte ich auch programmieren: Sobald ein Sprite platziert wird, werden die Sprites in der Umgebung gecheckt und dann allen beteiligten Sprites Prioritäten vergeben, bis alles passt.

Die benachbarten Sprites zu erkennen, ist kein großes Problem. Problematisch wird es, zu identifizieren, welche Prioritäten ich ihnen zuweisen muss.
Ich denke, es wäre am sinnvollsten, wenn man den Sprites beim Platzieren schonmal Grund-Prioritäten wie oben zuweist:

Priorität=position.y - position.x

Anschließend werden in Abhängigkeit von der Umgebung die Prioritäten erhöht oder gesenkt. Hierfür muss ein logischer Algorithmus her. Bevor ich mich jetzt aber dumm und dusselig probiere, wollte ich hier im Forum fragen, ob irgendjemand eine Idee oder einen Ansatz für sowas hat?
Oder denke ich viel zu kompliziert und es gibt eine viel viel trivialere Lösung für das Ganze?

 

Ich hoffe, ich konnte das Problem einigermaßen vermitteln und würde mich echt freuen, falls irgendwer hier eine rettende Idee parat hat!

Beispiel.png

Link zu diesem Kommentar
Auf anderen Seiten teilen

Du hast irgendwie den Thread verlinkt, so ist das Bild Mini und man kann nicht viel erkennen (ah ok über den Thread kommt man auch ans Bild).

Mal eine "dumme" Frage:
Warum bekommt ein neu platziertes Sprite nicht immer zuerst die oberste Priorität (ist also nie verdeckt) und dann hast du in deiner Editor-UI die Funktion "Ebene rauf" "Ebene runter", "Ebene ganz nach unten", "Ebene ganz nach oben" und "Setze Ebene auf X"? Zudem könnte man noch eine Funktion implementieren, wobei man mehrere Sprites gruppieren kann (die dann alle eine Gruppenpriorität bekommen). Der Spriteshader von Unity hat hier ja bereits eine Entsprechung.

Eventuell braucht man dabei noch eine Art Ebenenschablone, d.h. man kann eine Maskierung im Editor einschalten und sieht alle vorhandenen Ebenen und die zugewiesenen Sprites anhand einer farblichen Maske. Eventuell auch eine Auflistung aller vorhandenen Ebenen + Sprites in einer Treeview.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Bei mir wird das Bild unten am Post ganz groß dargestellt? Der verlinkte Thread ist an sich irrelevant, entschuldige bitte die Verwirrung.

Hinsichtlich deines Vorschlags habe ich das Problem, dass es immer nur eine "wahre" Darstellung der Sprites gibt. Unten habe ich nochmal ein Bild mit den tatsächlich für das Spiel benutzten Sprites hinzugefügt: Jede Kiste ist ein eigener Sprite und die linke Kiste muss immer eine höhere Priorität haben als die rechte, unabhängig von der Platzierungsreihenfolge. Wenn man als Levelbauer dann aber noch anfangen muss >100 Sprites pro Level händisch zu sortieren,  geht der Spaß schnell flöten.

Beispiel.png

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ok, nun wird das Problem zumindest deutlich. Es soll ein "3D-Effekt" über ein Sprite erzeugt werden.
Wie wäre es, wenn du den Sprites ein zusätzliches Attribut verpasst, über welches entschieden wird, wie es zu platzieren ist. Bei den Kistensprites würde dieses bedeuten, "Priorität links" und "Priorität oben". Aber die Platzierung hängt ja eigentlich vom Inhalt des Sprites ab, in dem Fall ist die Kiste nach unten rechts gedreht (bzw. die Kamera nach oben links verschoben). Ich gehe mal davon aus, ihr haltet diesen "3D-Effekt" über die ganze Szene aufrecht. Zusätzlich bestimmst du bei der Platzierung laufend die umgebenen Sprites (und den Typ). Sind 2 Sprites mit "3D-Effekt" nebeneinander, dann muss der 3D-Anteil des rechtes Sprites überdeckt werden. Ähnliches Verhalten für Übereinander, hier muss der 3D-Anteil des unteren Sprites überdeckt werden.
Ich denke du hattest es schon so ähnlich vor. Wenn es nun noch Sprites ohne "3D-Effekt" gibt, dann bekommen sie ein anderes Attribut verpasst.

Link zu diesem Kommentar
Auf anderen Seiten teilen

@Zer0Cool: Genau das ist im Endeffekt mein Vorhaben. Aktuell lass ich die platzierten Sprites erkennen, welche Sachen drumherum liegen. Auf dieser Basis könnte dann der Wert sortingOrder im SpriteRenderer festgelegt werden. Nur hapert es genau an dieser Stelle. Hier mal der (sehr abgespeckte) Code:

Vector2 gridSize=new Vector2(80, 80);
Dictionary<Vector2,GameObject> gridTiles=new Dictionary<Vector2, GameObject>();

void Awake() {
	for(int y=0; y<=gridSize-1; y++) {
		for(int x=0; x<=gridSize-1; x++) {
			gridTiles.Add(new Vector2(x, y), null);
		}
	}
}

public void CreateTile(Vector2 gridPosition, Texture2D tex) {
	//Eine Einheit im Raster sind 80 pixel; Jeder Sprite hat weitere 80 Pixel als Überhang
	//Beispiel: eine 2x2-Kiste hat einen 160 Pixel breiten und 160 Pixel hohen Collider und 80 Pixel zusätzlich auf jeder Seite; die Textur ist also 320x320 Pixel groß
	Vector2 spriteSize=new Vector2(tex.width, tex.height);
	Vector2 spriteDimension=spriteSize/80-2;

	//Erstelle GO und schiebe an korrekte Position
	GameObject tile=new GameObject(tex.name);
	tile.transform.localPosition=gridPosition;

	//Die Funktion "GetAdjacentTiles" schaut im Dictionary gridTiles nach allen angrenzenden Objekten und gibt zurück:
	//- welches Objekt grenzt an (Key-Wert)
	//- von welcher Seite grenzt es an (Value-Wert, kann "L"[eft], "R"[ight], "U"[p], "D"[own] sein
	Dictionary<GameObject,string> adjacentTiles=GetAdjacentTiles(tile);

	int minPriority=-999;
	int maxPriority=999;
	foreach(KeyValuePair<GameObject,string> adjacentTile in adjacentTiles) {
		//Information über Render-Priorität/Sorting Order des benachbarten Sprites erhalten (ist immer im ersten Child);
		int priority=adjacentTile.Key.transform.GetChild(0).GetComponent<SpriteRenderer>().sortingOrder;

		if(adjacentTile.Value=="L" || adjacentTile=="D")
			minPriority=Mathf.Max(priority, minPriority);
		else if(adjacentTile.Value=="R" || adjacentTile.Value=="U")
			maxPriority=Mathf.Min(priority, maxPriority);
	}



	SpriteRenderer spriteR;

	//Erstelle für jedes Raster-Element, welches das Sprite belegt, ein eigenes GO und fülle gridTiles mit dieser Info
	for(int x=0; x<=spriteDimension.x-1; x++) {
		for(int y=0; y<=spriteDimension.y-1; y++) {
			GameObject box=new GameObject("Box", typeof(SpriteRenderer));
			box.transform.parent=tile.transform;
			box.transform.localPosition=new Vector2(x, y);

			gridTiles[new Vector2(gridPosition.x+x, gridPosition.y+y)]=box;

			if(x==0 && y==0) {
				//Erstelle den Sprite für das Child-Objekt links unten
				spriteR=box.GetComponent<SpriteRenderer>();
				spriteR.sprite=Sprite.Create(tex,
				                             new Rect(0, 0, spriteSize.x, spriteSize.y),
				                             new Vector2(80/spriteSize.x, 80/spriteSize.y), //Pivot
				                             80);
				
				/* Hier ist das Problem: Es gilt einen Wert für Sorting Order zu finden, welcher:
				   - größer als minPriority und
				   - kleiner als maxPriority ist.
				   
				spriteR.sortingOrder=????? */
			}
		}
	}
}

 

Die Sorting Order muss halt so festgelegt werden, dass Sprites auch in komplexeren Strukturen wie zum Beispiel im folgenden Bild korrekt dargestellt werden - egal in welcher Reihenfolge Sprites platziert, gelöscht und/oder neu gesetzt werden.

5a01eab595c6d_Beispiel1.png.c347628c5306b51e7332ea4ec3093b86.png

 

@Sascha: So einen ähnlichen Versuch gab es ja bereits; da habe ich beispielsweise 2x2-Kisten in vier einzelne Sprites aufgeteilt. Bei so einem Sprite ist die linke obere Ecke immer hinten und die rechte untere Ecke immer vorn. Allerdings wird beispielsweise die Ecke rechts oben von Objekten darüber verdeckt, während sie aber Objekte rechts daneben selbst verdeckt.

 

Mal ne andere Frage in die Runde: Gibt es denn eine Möglichkeit, Unity direkt zu sagen, dass ein Sprite immer hinter oder vor einem anderen Sprite gerendert werden soll, ohne auf die sortingOrder/sortingLayer/z-Achse zurückzugreifen?

Link zu diesem Kommentar
Auf anderen Seiten teilen

Bei Sprite-Renderer kannst du ja über "Sorting Layer" und über "Order In Layer" sortieren. Wobei für deinen Ansatz "Sorting Layer" wohl unbrauchbar ist, da es einfach zu wenige Layer gibt und man die auch für andere Sachen braucht.

Ich bin aber eben noch über eine andere Möglichkeit gestoßen, ich glaube die Einstellungsmöglichkeit ist auch neu in Unity 2017 (siehe ganz unten):
https://docs.unity3d.com/Manual/Sprites.html

Hier kannst du generell bestimmen wie transparente Objekte sortiert werden sollen in Abhängigkeit der Perspektive und der Position zur Kamera. Wäre denke ich auch eine interessante Alternative für deinen Editor. 

PS:
Hab mir deinen Quellcode nicht angeschaut, ist mir zu viel ;) Ich denke aber das Prinzip sollte klar sein, wo hast du denn genau noch ein Problem?'
Versuche die Methodik einfach zu halten und komplexere Settings erst einmal auszublenden. Sobald einfache Anordnungen funktionieren, würde ich die Spezialkonstellationen als getrennte Fälle einzeln ausformulieren.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Wir sind ja bereits an dem Punkt mit den Spezialkonstellationen. Nur würde ich diese gerne vermeiden, denn je mehr Diversität es an Sprites im Editor gibt, desto komplizierter wird es. Ich hatte daher gehofft, eine allgemeine Lösung für alle möglichen Konstellationen zu finden (soll heißen, eine Formel, womit ich die sortingOrder immer passend festlegen kann). Am liebsten wäre mir eine Möglichkeit, die Render-Reihenfolge zwischen zwei genau Sprites festlegen zu können. Ähnlich wie Physics.IgnoreCollision(col1, col2) wäre eine Unity-interne Funktion "SpriteRenderer.PrioritizeSprite(sprite1, sprite2)" toll, womit festgelegt würde, dass sprite1 vor sprite2 liegt. Aber das ist wohl nur Wunschdenken. :D

 

Dass man die Transparency Axis festlegen kann, wusste ich noch nicht. Das wird vermutlich noch nicht das Hauptproblem lösen, aber sicherlich einiges vereinfachen- danke also dafür!

Link zu diesem Kommentar
Auf anderen Seiten teilen

Eine Funktion PrioritizeSprite(sprite1, sprite2) würde bedeuten, daß der Sprite Renderer jedes Frame diese Liste durchgehen müsste und daher wurde sowas nicht implementiert. Die "Order in Layer" wird im Endeffekt ja dem Renderer übergeben und er muss dann nur noch die Sprites in der entsprechenden Reihenfolge rendern. Du kannst dir aber eine solche Funktion selbst schreiben (mit einer entsprechenden zugehörigen Klasse), die Ausgabe einer solchen Klasse wäre dann die "Order in Layer" für alle Sprites:
Beispiel:
- Klasse "OrderinLayerBuilder"
- Methode PrioritizeSprite(sprite1, sprite2)
Diese Methode baut intern eine dynamische Liste aller Sprites auf und weist jeweils die aktuelle OrderinLayer zu.
- Methode AssignAllOrderinLayers()
Diese Methode geht die aktuelle Spriteliste durch und weist den Sprites die entsprechenden OrderinLayers zu.

Beispiel Methode "PrioritizeSprite":
PrioritizeSprite(sprite1, sprite2):
Liste:
sprite2 0
sprite1 1

PrioritizeSprite(sprite3, sprite4):
Verschiebungen 0
sprite2 0
sprite1 1
sprite4 2
sprite3 3

PrioritizeSprite(sprite1, sprite4):
Verschiebungen 1
sprite2 0
sprite4 1
sprite1 2

sprite3 3

PrioritizeSprite(sprite2, sprite4):
Verschiebungen 1
sprite4 0
sprite2 1

sprite1 2
sprite3 3

PrioritizeSprite(sprite2, sprite3):
Verschiebungen 2
sprite4 0
sprite3 1
sprite2 2
sprite1 3

Ich denke man muss alle Regeln bei jedem erneuten Einfügen wiederholt ausführen bis es keine Verschiebungen mehr gibt. Ich denke es gibt immer eine eindeutige Lösung. Bei der Anzahl der notwendigen Durchläufe bin ich mir unsicher.
________________________________________________________________________________________________________________________________________________________________

Nach reichlicher Überlegung, gibt es keine eindeutige Lösung für dieses Problem, es kann ein Zyklus entstehen:
PrioritizeSprite(sprite5, sprite1)
PrioritizeSprite(sprite2, sprite5):

Bedeutet indirekt PrioritizeSprite(sprite2, sprite1) und steht im Widerspruch zu  PrioritizeSprite(sprite1, sprite2)

Zusammenfassend gibt es gar keine Lösung für dieses Problem. Entweder ist Sprite 1 vor Sprite 2 oder umgekehrt, beides gleichzeitig ist unmöglich. Sobald ein 3. Sprite ins Spiel kommt,  kann diese Forderung entstehen.

BbPjFBB.png

Link zu diesem Kommentar
Auf anderen Seiten teilen

Archiviert

Dieses Thema ist jetzt archiviert und für weitere Antworten gesperrt.

×
×
  • Neu erstellen...