Diverse Funktionen für Sechseckraster

Übersicht BlitzBasic Codearchiv

Neue Antwort erstellen

 

n-Halbleiter

Betreff: Diverse Funktionen für Sechseckraster

BeitragSo, Aug 30, 2009 22:23
Antworten mit Zitat
Benutzer-Profile anzeigen
Hallo und guten Abend!

Ich hatte es ja schon in meinem Worklog angekündigt hatte, möchte ich ein paar Funktionen vorstellen, die den Leuten helfen sollen, die sich mit Sechseckrastern befassen und diese umsetzen möchten. Ich hoffe, ich kann hier helfen. Wink

Zuallererst: Wie unterscheidet sich ein Sechseckraster von einem Viereckraster?

Der auffälligste Unterschied: Es hat sechs anstatt vier Ecken, dadurch eine andere Form, was darin resultiert, dass wir 6 Nachbarn haben, und nicht 8. Erschwerend kommt noch hinzu, dass die Nachbarn anders angeordnet sind als bei einem quadratischen Raster. Diesen Umstand verdeutlicht diese Grafik:

user posted image

Auch ist es eine Umstellung, dass man anders Kacheln muss. Bei einem quadratischen Raster geht man einfach stur von oben links nach unten rechts und malt bei CX*Tilebreite bzw. CY*Tilehöhe. Bei einem Sechseckraster wird jede Kachel, die einen ungeraden U-Wert hat (U und V ist hier X bzw. Y im Array), um eine halbe Kachelhöhe nach oben gesetzt wird. Auch gibt es ein paar Unterschiede bei dem horizontalen Abstand. Zu dem Problem mt dem Versetzen habe ich auch eine Grafik angefertigt:

user posted image

Und als sei das noch nicht genug, gibt es hier direkt einen Code, mit dem das Kacheln übernommen wird. Code: [AUSKLAPPEN]
Function TileGrid(ShowText=0)
   Local CX,CY,AX,AY
   Local XStep=(HexGrid_Length_Middle/2)+(HexGrid_Length_UpLo/2),YStep=HexGrid_Height,YStepH=YStep/2
   Color 255,255,255
   For CX=0 To HexGrid_Info_Width-1
      For CY=0 To HexGrid_Info_Height-1
         ;Aktuelle Positionen zwischenspeichern, das Konstrukt YStep-(...) sorgt für den Verschub nach oben
         AX=SchubX+(CX+1)*XStep:AY=SchubY+(CY+1)*YStep-((CX Mod 2)*(YStepH))
         DrawImage GridImage,AX,AY
         If ShowText=1 Then Text AX,AY,CX+","+CY,1,1
      Next
   Next
End Function


Der Part mit dem "ShowText" kann an sich auch weggelassen werden, allerdings werde ich ihn in dem Beispiel, das noch kommt, gebrauchen. Eine kurze Erläuterung: CX und CY sind die Zählvariablen, HexGrid_Info_Width bzw. HexGrid_Info_Height sind Konstanten (für Spiele oder Anwendungen wären Globale besser gewesen, aber dies ist ja nur zum Testen), die die Breite und Höhe angeben. AX uns AY sind die aktuelle Position des Tiles GridImage ist das Kachelbild. Dann gibt es noch XStep YStep und YStepH. Diese sind die horizontalen/vertikalen/halben vertikalen Abstände der Kacheln untereinander. Ich denke, was die Berechnungen machen, dürfte klar sein. Die letzten Variablen SchubX und SchubY speichern die Einrückung in Pixeln. Das habe ich zu Testzwecken eingebaut.

Dann ist das nächste Problem das mit den Nachbarn. Hierzu muss man sich noch einmal die "Bauweise" des Gitters vor Augen führen: Jede Kachel hat einen Nachbarn oben (U,V-1) und einen unten (U,V+1). Jetzt kommt der vertikale Abstand jedes zweiten Tiles in die Quere: Ist U gerade ("U Mod 2 = 0"), dann sind das andere, als wenn U ungerade ist. Dazu habe ich natürlich auch eine Funktion geschrieben, die das berücksichtigt. Die Nachbarn sind bei geradem U bei
Arrow U-1,V-1
Arrow U-1,V
Arrow U+1,V-1
Arrow U+1,V
und bei ungeradem U
Arrow U-1,V
Arrow U-1,V+1
Arrow U+1;V
Arrow U+1,V+1

Der Code ist folgender: Code: [AUSKLAPPEN]
;Formt die Eingabe in eine gültige Form um, z.B. wenn die Eingabe außerhalb der Map sein würde.
;Hier wird die Karte Torusförmig behandelt.
Function TransformToValid(Inp,XY=0)
   Local margin
   If XY=0
      margin=HexGrid_Info_Width
   Else
      margin=HexGrid_Info_Height
   End If
   Return (Inp+margin)Mod(margin)
End Function

Dim Neighbour(5,1);Die Nachbarn des Feldes, gegen den Uhrzeigersinn, angefangen oben links
; Elemente: (Nachbar, X 0/Y 1)
Function GetNeighbours(U,V)
   Local UIsStraight=(U Mod 2)
   Neighbour(1,0)=TransformToValid(U):Neighbour(1,1)=TransformToValid(V-1,1)
   Neighbour(4,0)=TransformToValid(U):Neighbour(4,1)=TransformToValid(V+1,1)
   If UIsStraight
      Neighbour(0,0)=TransformToValid(U-1):Neighbour(0,1)=TransformToValid(V-1,1)
      Neighbour(5,0)=TransformToValid(U-1):Neighbour(5,1)=TransformToValid(V,1)
      Neighbour(2,0)=TransformToValid(U+1):Neighbour(2,1)=TransformToValid(V-1,1)
      Neighbour(3,0)=TransformToValid(U+1):Neighbour(3,1)=TransformToValid(V,1)
   Else
      Neighbour(0,0)=TransformToValid(U-1):Neighbour(0,1)=TransformToValid(V,1)
      Neighbour(5,0)=TransformToValid(U-1):Neighbour(5,1)=TransformToValid(V+1,1)
      Neighbour(2,0)=TransformToValid(U+1):Neighbour(2,1)=TransformToValid(V,1)
      Neighbour(3,0)=TransformToValid(U+1):Neighbour(3,1)=TransformToValid(V+1,1)
   End If
End Function


Die Funktion "TransformToValid" wird oben schon beschrieben. An sich lassen sich jetzt die meisten Problembereiche abdecken. Das dritte Problem ist die Sache mit dem Selektieren, per Maus zum Beispiel. Man kann nun mittels komplizierter mathematischer Berechnungen (oder mit dreimaligem "CheckQuad3D", wenn man die D3D benutzt) bestimmen, in welchem Feld sich der Cursor befindet. Da jedoch die "Mathe-Methode" recht aufwendig ist, können wir einen Kompromiss in Form einer Annäherung eingehen: Wir prüfen für jedes Feld, ob sich die angegebenen Koordinaten in einem gewissen Abstand befinden. Wenn jetzt dieser Abstand gut gewählt ist, dann ist das Ergebnis ziemlich genau dem exakten, und schneller. Hierzu habe ich auch eine Funktion vorbereitet: Code: [AUSKLAPPEN]
;Eine einfache Funktion, die zurückgibt, ob sich zwei Objekte in einem gewissen Abstand zueinander befinden.
Function InRange(X1#,Y1#,X2#,Y2#,Range)
   Return (((X1-X2)*(X1-X2)+(Y1-Y2)*(Y1-Y2))*((X1-X2)*(X1-X2)+(Y1-Y2)*(Y1-Y2)))<=(Range*Range)
End Function

Global SelectedX=5,SelectedY=8
Function SelectedTileAt(X,Y)
   Local CX,CY,AX,AY
   Local XStep=(HexGrid_Length_Middle/2)+(HexGrid_Length_UpLo/2),YStep=HexGrid_Height,YStepH=YStep/2
   For CX=0 To HexGrid_Info_Width-1
      For CY=0 To HexGrid_Info_Height-1
         AX=SchubX+(CX+1)*XStep:AY=SchubY+(CY+1)*YStep-((CX Mod 2)*(YStepH))
         If InRange(X,Y,AX,AY,HexGrid_DiagLength) Then SelectedX=CX:SelectedY=CY:Return
      Next
   Next
End Function


Achja, was die ganze Zeit an Variablen genutzt, aber nicht dokumentiert wurde: Code: [AUSKLAPPEN]
Const HexGrid_Height=55
Const HexGrid_Length_Middle=70
Const HexGrid_Length_UpLo=30
Const HexGrid_DiagLength=HexGrid_Length_Middle*10


Diese ganzen Variablen werden dazu benötigt, um die erforderlichen Zwischenwerte zu errechnen. Die Variable HexGrid_DiagLength ist der minimal benötigte Abstand, um ein Tile auszuwählen, HexGrid_Length_Middle und HexGrid_Length_UpLo sind die Längen in der Mitte (zwischen den beiden äußersten Punkten) bzw. die der Strecke oben und unten. HexGrid_Height ist die Höhe einer Kachel.

So, um das alles nochmal ein wenig in Richtung Praxis zu bringen und direkt ein Beispiel zu liefern, gibt es noch diesen Code: [AUSKLAPPEN]
Graphics 800,600,32,2
AutoMidHandle True
SetBuffer BackBuffer()

Const HexGrid_Height=55
Const HexGrid_Length_Middle=70
Const HexGrid_Length_UpLo=30
Const HexGrid_DiagLength=HexGrid_Length_Middle*10

Dim HexPoints(5,1);Punkte gegen den Uhrzeigersinn, angefangen oben links
HexPoints(0,0)=-(HexGrid_Length_UpLo/2):HexPoints(0,1)=-(HexGrid_Height/2);OL
HexPoints(1,0)=-(HexGrid_Length_Middle/2):HexPoints(1,1)=0;L
HexPoints(2,0)=-(HexGrid_Length_UpLo/2):HexPoints(2,1)=(HexGrid_Height/2);UL
HexPoints(3,0)=(HexGrid_Length_UpLo/2):HexPoints(3,1)=(HexGrid_Height/2);UR
HexPoints(4,0)=(HexGrid_Length_Middle/2):HexPoints(4,1)=0;R
HexPoints(5,0)=(HexGrid_Length_UpLo/2):HexPoints(5,1)=-(HexGrid_Height/2);OR

Global HexGrid_Info_Width=15,HexGrid_Info_Height=10
Global SchubX=(-(HexGrid_Length_Middle/2)+(HexGrid_Length_UpLo/2)),SchubY=0

Global GridImage=CreateGridImage()

;Hier wird das Rasterbild erstellt
Function CreateGridImage()
   Local C,Img,Width=HexGrid_Length_Middle/2,Height=HexGrid_Height/2
   Img=CreateImage(HexGrid_Length_Middle,HexGrid_Height)
   SetBuffer ImageBuffer(Img)
   Color 255,255,255
   ;Diese kryptische Konstruktion zieht Linien von Punkt 0 zu 1, 1 zu 2, ... 5 zu 0
   For C=0 To 5
      Line Width+HexPoints(C,0),Height+HexPoints(C,1),Width+HexPoints(((C+1) Mod 6),0),Height+HexPoints(((C+1) Mod 6),1)
   Next
   Return Img
End Function

Dim Neighbour(5,1);Die Nachbarn des Feldes, gegen den Uhrzeigersinn, angefangen oben links
; Elemente: (Nachbar, X 0/Y 1)
Function GetNeighbours(U,V)
   Local UIsStraight=(U Mod 2)
   Neighbour(1,0)=TransformToValid(U):Neighbour(1,1)=TransformToValid(V-1,1)
   Neighbour(4,0)=TransformToValid(U):Neighbour(4,1)=TransformToValid(V+1,1)
   If UIsStraight
      Neighbour(0,0)=TransformToValid(U-1):Neighbour(0,1)=TransformToValid(V-1,1)
      Neighbour(5,0)=TransformToValid(U-1):Neighbour(5,1)=TransformToValid(V,1)
      Neighbour(2,0)=TransformToValid(U+1):Neighbour(2,1)=TransformToValid(V-1,1)
      Neighbour(3,0)=TransformToValid(U+1):Neighbour(3,1)=TransformToValid(V,1)
   Else
      Neighbour(0,0)=TransformToValid(U-1):Neighbour(0,1)=TransformToValid(V,1)
      Neighbour(5,0)=TransformToValid(U-1):Neighbour(5,1)=TransformToValid(V+1,1)
      Neighbour(2,0)=TransformToValid(U+1):Neighbour(2,1)=TransformToValid(V,1)
      Neighbour(3,0)=TransformToValid(U+1):Neighbour(3,1)=TransformToValid(V+1,1)
   End If
End Function

;Ob ein Feld Nachbar ist
Function IsNeighbour(U1,V1,U2,V2)
   Local C
   GetNeighbours(U1,V1)
   For C=0 To 5
      If Neighbour(C,0)=U2
         If Neighbour(C,1)=V2
            Return 1
         End If
      End If
   Next
End Function

;Kacheln
Function TileGrid(ShowText=0)
   Local CX,CY,AX,AY
   Local XStep=(HexGrid_Length_Middle/2)+(HexGrid_Length_UpLo/2),YStep=HexGrid_Height,YStepH=YStep/2
   Color 255,255,255
   For CX=0 To HexGrid_Info_Width-1
      For CY=0 To HexGrid_Info_Height-1
         ;Aktuelle Positionen zwischenspeichern, das Konstrukt YStep-(...) sorgt für den Verschub nach oben
         AX=SchubX+(CX+1)*XStep:AY=SchubY+(CY+1)*YStep-((CX Mod 2)*(YStepH))
         DrawImage GridImage,AX,AY
         If ShowText=1 Then Text AX,AY,CX+","+CY,1,1
      Next
   Next
End Function

Global SelectedX=5,SelectedY=8
Function SelectedTileAt(X,Y)
   Local CX,CY,AX,AY
   Local XStep=(HexGrid_Length_Middle/2)+(HexGrid_Length_UpLo/2),YStep=HexGrid_Height,YStepH=YStep/2
   For CX=0 To HexGrid_Info_Width-1
      For CY=0 To HexGrid_Info_Height-1
         AX=SchubX+(CX+1)*XStep:AY=SchubY+(CY+1)*YStep-((CX Mod 2)*(YStepH))
         If InRange(X,Y,AX,AY,HexGrid_DiagLength) Then SelectedX=CX:SelectedY=CY:Return
      Next
   Next
End Function

;Eine einfache Funktion, die zurückgibt, ob sich zwei Objekte in einem gewissen Abstand zueinander befinden.
Function InRange(X1#,Y1#,X2#,Y2#,Range)
   Return (((X1-X2)*(X1-X2)+(Y1-Y2)*(Y1-Y2))*((X1-X2)*(X1-X2)+(Y1-Y2)*(Y1-Y2)))<=(Range*Range)
End Function

;Formt die Eingabe in eine gültige Form um, z.B. wenn die Eingabe außerhalb der Map sein würde.
;Hier wird die Karte Torusförmig behandelt.
Function TransformToValid(Inp,XY=0)
   Local margin
   If XY=0
      margin=HexGrid_Info_Width
   Else
      margin=HexGrid_Info_Height
   End If
   Return (Inp+margin)Mod(margin)
End Function

;Diese Funktion markiert ein Tile farbig
Function ColorTile(U,V,R=255,G=0,B=0)
   Local AX,AY
   Local XStep=(HexGrid_Length_Middle/2)+(HexGrid_Length_UpLo/2),YStep=HexGrid_Height,YStepH=YStep/2
   AX=SchubX+(U+1)*XStep:AY=SchubY+(V+1)*YStep-((U Mod 2)*(YStepH))
   Color R,G,B
   Oval(AX-5,AY-5,10,10,0)
End Function

;Testschleife inkl. Variablen
SetBuffer BackBuffer()
Local Timer=CreateTimer(60),C,ShowGridText=1,ShowNeighbours=1,ShowText=1
While Not KeyHit(1)
   Cls
   TileGrid(ShowGridText)
   
   SelectedTileAt(MouseX(),MouseY())
   SelectedX=TransformToValid(SelectedX)
   SelectedY=TransformToValid(SelectedY,1)
   ColorTile(SelectedX,SelectedY)
   
   GetNeighbours(SelectedX,SelectedY)
   If ShowNeighbours
      For C=0 To 5
         ColorTile(Neighbour(C,0),Neighbour(C,1),0,255,0)
      Next
   End If
   If KeyHit(2) Then ShowGridText=ShowGridText=False
   If KeyHit(3) Then ShowNeighbours=ShowNeighbours=False
   If KeyHit(4) Then ShowText=ShowText=False
   If ShowText
      Color 255,255,255
      Text 0,550,MouseX()+","+MouseY()+"->"+SelectedX+","+SelectedY
      If IsNeighbour(SelectedX,SelectedY,5,6)
         Text 0,560,"5,6 ist Nachbar von "+SelectedX+","+SelectedY
      Else
         Text 0,560,"5,6 ist nicht Nachbar von "+SelectedX+","+SelectedY
      End If
   End If
   Flip 0
   WaitTimer Timer
Wend
End


Für sämtliche Funktionen ist dazugeschrieben, für was sie zuständig sind, außerdem habe ich mich bemüht, Ausdrucksstarke Namen der Variablen und Funktionen zu wählen. Mit den Tasten 1,2 und 3 lässt sich ein wenig mit den Modi herumspielen (beschriftetes Raster, farbig markierte Nachbarn, zusätzlich angezeigter Text).

Das wär's. Falls sich noch Fragen oder Anregungen ergeben, ich kann euch nicht verbieten, sie mitzuteilen. Wink
mfg, Calvin
Maschine: Intel Core2 Duo E6750, 4GB DDR2-Ram, ATI Radeon HD4850, Win 7 x64 und Ubuntu 12.04 64-Bit
Ploing!
Blog

"Die Seele einer jeden Ordnung ist ein großer Papierkorb." - Kurt Tucholsky (09.01.1890 - 21.12.1935)

Neue Antwort erstellen


Übersicht BlitzBasic Codearchiv

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group