Castlevania/Metroid ähnliche Minimap/Teleport

    Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

    • Castlevania/Metroid ähnliche Minimap/Teleport

      So, ich hab mal vor Monaten versprochen ein Tutorial ins netzt zu stellen, wie man eine Minimap macht. Gut, heute ist soweit. Folgende vorraussetzungen sind nötig, um das Teil in seinem Projekt einzubauen:

      • Gamemaker 6.1 (registriert)
      • GML Wissen - Fortgeschritten
      • Ausdauer, um alles durchzulesen


      Allgemeines


      Gut fangen wir mal an. Eine Minimap ist ein zweidimensionales Array, welches informationen beinhaltet, wie ein Raum aufgebaut ist. Damit die Karte weiß, wie ein Raum aussieht, muß jeder Raum Informationen beinhalten, wie dieser aussieht. Sprich seine Position innerhalb der Minimap, seine Wände, seine Außgänge. Jeder Raum muß ein Script beinhalten, das so aussieht:

      Dieses Script muß im Create Event eines Rooms aufgerufen werden. Man kann es direkt ins Room Create Event reingeben, jedoch zur Übersichtlicheren Gestalltung würde ich eine Funktion anlegen die ca so heißt: <SCR_CreateTeleportData_*Raumname*>.

      Dieser Raum, ist in diesem Fall einen Bildschirm Breit und 4 Bildschirme Hoch. Es gibt zwei Ausgänge, an der Linken Seite.

      C_ROOM_NONE ist eine Konstante. Sie hat den Wert -1. Eigentlich dient sie nur der übersichtlichkeit, die Runtime wird dadurch nicht beeinträchtigt, da ohnehin alle werte in Bytecode verwandelt werden sollten, bevor das Spiel gestartet wird (yeah, Technobabble).

      GML-Quellcode

      1. {
      2. global.Absolute_X = 4; //Raumposition innerhalb der Karte
      3. global.Absolute_Y = 1; //Raumposition innerhalb der Karte
      4. global.Trans_Top[0] = C_ROOM_NONE;
      5. global.Trans_Right[0] = C_ROOM_NONE;
      6. global.Trans_Right[1] = C_ROOM_NONE;
      7. global.Trans_Right[2] = C_ROOM_NONE;
      8. global.Trans_Right[3] = C_ROOM_NONE;
      9. global.Trans_Bottom[0] = C_ROOM_NONE;
      10. global.Trans_Left[0] = C_ROOM_NONE;
      11. global.Trans_Left[1] = ROOM_TestRealm_04;
      12. global.Trans_Left[2] = C_ROOM_NONE;
      13. global.Trans_Left[3] = ROOM_TestRealm_02;
      14. }
      Alles anzeigen


      Einige Weitere Konstanten die Benötigt werden:

      C_ROOM_SIZE_X 320
      C_ROOM_SIZE_Y 240
      C_ROOM_OFFSET 32

      Das bedeutet nichts anderes, als das ein Raum 320 Pixel Breit und 240 Hoch ist, oder ein Vielfaches davon. Das Offset ist die Zusatzbreite, die man dann links, rechts, oben und unten noch dazumacht. Das hat den Grund, damit die Spielfigur den Raum etwas verlassen kann (ca. bis zur Hälfte), bevor der Teleport durchgeführt wird. Mit hilfe einer Simplen ViewControll kann man verhindern, das die Kamera diesen bereich einsieht:

      GML-Quellcode

      1. {
      2. //if(instance_number(OBJ_Hero)<1)
      3. // exit;
      4. view_xview[0] = round(global.PlayerObject.x-(C_ROOM_SIZE_X/2));
      5. view_yview[0] = round(global.PlayerObject.y-(C_ROOM_SIZE_Y/2));
      6. if(view_xview[0]<C_ROOM_OFFSET)
      7. {
      8. view_xview[0] = C_ROOM_OFFSET;
      9. }
      10. else
      11. if(view_xview[0] > (room_width - (C_ROOM_SIZE_X + C_ROOM_OFFSET)) )
      12. {
      13. view_xview[0] = room_width - (C_ROOM_SIZE_X + C_ROOM_OFFSET);
      14. }
      15. if(view_yview[0]<C_ROOM_OFFSET)
      16. {
      17. view_yview[0] = C_ROOM_OFFSET;
      18. }
      19. else
      20. if(view_yview[0]> (room_height - (C_ROOM_SIZE_Y + C_ROOM_OFFSET)) )
      21. {
      22. view_yview[0] = room_height - (C_ROOM_SIZE_Y + C_ROOM_OFFSET);
      23. }
      24. }
      Alles anzeigen


      Der Teleport


      Gut, um die Teleports richtig durchzuführen brauchen wir einen Teleport Handler. Das ist ein unsichtbares, Persistentes Objekte, das überprüft ob der das Spielerobjekt einen Raum verlassen hat, und führt dann den Teleport durch.

      Folgende Funktionen werden dafür benötigt:

      Im Create Event:

      GML-Quellcode

      1. {
      2. global.JustTeleported=false; //Wurde ich teleportiert?
      3. global.CurrentTeleport = 0; //welcher Raum?
      4. global.Absolute_X = 0; //Aktuelle Kartenposition X
      5. global.Absolute_Y = 0; //Aktuelle Kartenposition Y
      6. global.Old_Absolute_X = 0; //Alte Kartenposition vor Teleport
      7. global.Old_Absolute_Y = 0;
      8. }


      Erklärung:
      Im Create Event werden Variablen gesetzt die für das Durchführen des Teleports wichtig sind.

      Im Stepevent:

      GML-Quellcode

      1. {
      2. var bs;
      3. if(
      4. (global.PlayerObject.x>=C_ROOM_OFFSET) &&
      5. (global.PlayerObject.x<=(room_width-C_ROOM_OFFSET)) &&
      6. (global.PlayerObject.y>=C_ROOM_OFFSET) &&
      7. (global.PlayerObject.y<=(room_height-C_ROOM_OFFSET))
      8. )
      9. return 0; //Inside the room, nothing else to do
      10. else
      11. if(global.PlayerObject.x>(room_width-C_ROOM_OFFSET))
      12. global.CurrentTeleport = global.Trans_Right[floor((global.PlayerObject.y-C_ROOM_OFFSET)/C_ROOM_SIZE_Y)];
      13. else
      14. if(global.PlayerObject.x<C_ROOM_OFFSET)
      15. global.CurrentTeleport = global.Trans_Left[floor((global.PlayerObject.y-C_ROOM_OFFSET)/C_ROOM_SIZE_Y)];
      16. else
      17. if(global.PlayerObject.y>(room_height-C_ROOM_OFFSET))
      18. global.CurrentTeleport = global.Trans_Bottom[floor((global.PlayerObject.x-C_ROOM_OFFSET)/C_ROOM_SIZE_X)];
      19. else
      20. if(global.PlayerObject.y<C_ROOM_OFFSET)
      21. global.CurrentTeleport = global.Trans_Top[floor((global.PlayerObject.x-C_ROOM_OFFSET)/C_ROOM_SIZE_X)];
      22. else
      23. global.CurrentTeleport = 0;
      24. JustTeleported=false;
      25. if(global.CurrentTeleport)
      26. {
      27. room_goto(global.CurrentTeleport);
      28. global.CurrentRoom = global.CurrentTeleport;
      29. global.JustTeleported=true;
      30. global.CurrentTeleport = 0;
      31. set_automatic_draw(false);
      32. global.Old_Absolute_X = global.Absolute_X;
      33. global.Old_Absolute_Y = global.Absolute_Y;
      34. }
      35. }
      Alles anzeigen

      Erklärung:
      Im Step Event wird überprüft ob das Spielerobjekt den Raum verläßt. Wenn dem so ist, wird festgestellt, wohin man Teleportieren muß. Das automatische Zeichnen wird deaktiviert, um ein häßliches Flackern zu verhindern, da die Spielfigur beim wiedereintritt in den neuen Raum an einer völlig falschen Position ist.

      Im Begin Step Event:

      GML-Quellcode

      1. {
      2. if(global.JustTeleported)
      3. {
      4. global.PlayerObject.x += (global.Old_Absolute_X - global.Absolute_X)*C_ROOM_SIZE_X;
      5. global.PlayerObject.y += (global.Old_Absolute_Y - global.Absolute_Y)*C_ROOM_SIZE_Y;
      6. global.JustTeleported=false;
      7. SCR_ViewControll();
      8. set_automatic_draw(true);
      9. }
      10. }
      Alles anzeigen

      Erklärung:
      Man muß ingesammt einen Step warte, damit das Room Create Event des neuen Raums aufgerufen wird, damit man die Aktuelle Position der Spielfigur weiß. Danach wird das Automatische Zeichnen wieder aktiviert.

      Wer kein Interesse an einer Minimap hat, der Teil bis hierher ist alleine Lauffähig und kann als Reine Teleport Engine benutzt werden.

      Gut, wems bis jetzt zu einfach war, jetzt kommt der Kompliziertere Teil :3.

      Die Minimap.


      Ein paar neue Konstanten die nötig sind:

      C_MM_MAX_X 64 -> Maximale Größe der Minimap
      C_MM_MAX_Y 64
      C_MM_POS_X 284 -> Position am Bildschirm
      C_MM_POS_Y 12

      Gut, mal der Logische Teil der Minimap. Wie schon erwähnt, eine Minimap ist ein 2 Dimensionales Array, wobei jeder eintrag in dem Array genau einen Bildschirm darstellt. Ein Raum, der 3 Bildschirme hoch ist, beinhaltet auch 3 Felder im Array.

      Ein Feld des Arrays beinhaltet eine vierstellige Zahl, welche wiefolgt aufgebaut ist

      0 -> Oben
      0 -> Rechts
      0 -> Unten
      0 -> Links

      wobei Jede Stelle folgende Zustände haben kann

      0 -> Nichts
      1 -> Wand
      2 -> Türe

      Ein Raum der genau einen Bildschirm groß ist und eine Türe oben hätte sieht so aus:

      2111

      Um nicht wahnsinnig bei den Zuweisungen zu werden habe ich ein Trinäres Zahlensystem (ähnlich Binär, nur mit Basis 3) entwickelt, um die Zuweisungen leichter zu machen. Das ganze geht ca so: Ich jabe jetzt einen Raum, sagen wir mal mit den werten 0021. Wenn man das als Trinäre Zahl hernimmt und auf Dezimal umrechnet kommt 7 Raus. In einem Array, welchesl alle möglichen Sprites beinhaltet, um die Minimap darzustellen ist auf der Position 7 ein Sprite welches oben und rechts keine Wand hat, unten ist eine Türe und links eine Wand.

      Für das Trinäre Zahlensystem benötigt man zwei Funktionen:

      SCR_B3toINT

      GML-Quellcode

      1. {
      2. var i;
      3. var m;
      4. var erg;
      5. var zahl;
      6. zahl = argument0;
      7. m = 10;
      8. rv = 0;
      9. for(i = 0; i < 4; i+=1)
      10. {
      11. erg = zahl mod m;
      12. zahl = (zahl - erg)/10;
      13. rv += erg*power(3,i);
      14. }
      15. return rv;
      16. }
      Alles anzeigen


      SCR_INTtoB3

      GML-Quellcode

      1. {
      2. var i;
      3. var m;
      4. var erg;
      5. var zahl;
      6. var r;
      7. zahl = argument0;
      8. m = 1;
      9. rv = 0;
      10. do
      11. {
      12. r = zahl mod 3;
      13. zahl = floor(zahl/3)
      14. rv += r*m;
      15. m = m*10;
      16. }
      17. until(zahl == 0)
      18. return rv;
      19. }
      Alles anzeigen


      Die Beiden Funktionen verwandeln einen Integer (eine ganzzahlige ... Zahl) in eine Trinäre Zahl und vize versa.

      Diese Funktion erzeugt alle nötigen Sprites für die Minimaps. Diese Art eine Minimap darzustellen verbraucht zwar mehr speicher, aber ist um einiges Schneller zur laufzeit, anstatt alle Sprites einzeln aufzuzeichnen.

      SCR_Minimap_CreateSprite

      GML-Quellcode

      1. {
      2. //argument 0 = B3 Code of Map Sprite
      3. var SRF_Sprite;
      4. var i;
      5. var zahl;
      6. var MM_Array;
      7. var rv;
      8. MM_Array[0,0] = SPR_Minimap_0001;
      9. MM_Array[0,1] = SPR_Minimap_0002;
      10. MM_Array[1,0] = SPR_Minimap_0010;
      11. MM_Array[1,1] = SPR_Minimap_0020;
      12. MM_Array[2,0] = SPR_Minimap_0100;
      13. MM_Array[2,1] = SPR_Minimap_0200;
      14. MM_Array[3,0] = SPR_Minimap_1000;
      15. MM_Array[3,1] = SPR_Minimap_2000;
      16. zahl = argument0;
      17. SRF_Sprite = surface_create(8,8);
      18. surface_set_target(SRF_Sprite);
      19. draw_clear(make_color_rgb(128,128,128));
      20. draw_sprite(SPR_Minimap_0000,-1,0,0);
      21. for(i = 0; i < 4; i+= 1)
      22. {
      23. if(zahl mod 10)
      24. {
      25. draw_sprite(MM_Array[i,((zahl mod 10) - 1)],-1,0,0);
      26. }
      27. //show_message(string(argument0) + ':' + string(zahl mod 10));
      28. zahl = (zahl - (zahl mod 10))/10;
      29. }
      30. surface_reset_target();
      31. rv = sprite_create_from_surface(SRF_Sprite,0,0,8,8,false,false,false,true,0,0);
      32. surface_free(SRF_Sprite);
      33. return rv;
      34. }
      Alles anzeigen


      Diese Funktion initialisiert die Minimap:

      SCR_Minimap_Create

      GML-Quellcode

      1. {
      2. var i,j;
      3. global.MiniMap_x = 0;
      4. global.MiniMap_y = 0;
      5. for(i = 0; i < C_MM_MAX_X; i+=1)
      6. for(j = 0; j < C_MM_MAX_Y; j+=1)
      7. {
      8. global.Minimap[i,j] = -1;
      9. }
      10. for(i = 0; i <81; i+= 1)
      11. {
      12. global.MinimapSpriteArray[i] = SCR_Minimap_CreateSprite(SCR_INTtoB3(i));
      13. }
      14. }
      Alles anzeigen


      Mit hilfe Dieser Funktion wird am Ende eines Steps überprüft ob man sich in einem Bereich des Map Arrays befindet, das noch nicht Definiert ist. Wenn dem so ist, wird ein Wert gesetzt, welcher aus den Daten der Teleporterfunktionen zusammengestellt wird.

      SCR_Minimap_StepEnd

      GML-Quellcode

      1. {
      2. global.MiniMap_x = floor(global.Absolute_X + ((OBJ_Player_WalkCircle.x-32)/320));
      3. global.MiniMap_y = floor(global.Absolute_Y + ((OBJ_Player_WalkCircle.y-32)/240));
      4. var up,down,left,right;
      5. if(global.Minimap[global.MiniMap_x,global.MiniMap_y]==-1) //undefined
      6. {
      7. if(
      8. !((OBJ_Player_WalkCircle.x>33)&&
      9. (OBJ_Player_WalkCircle.x<(room_width-33))&&
      10. (OBJ_Player_WalkCircle.y>33)&&
      11. (OBJ_Player_WalkCircle.y<(room_height-33)))
      12. )
      13. return 0;
      14. up = 0;
      15. down = 0;
      16. left = 0;
      17. right = 0;
      18. //Top of the Room
      19. if(floor((OBJ_Player_WalkCircle.y-32)/240)==0)
      20. {
      21. if(global.Trans_Top[floor((OBJ_Player_WalkCircle.x-32)/320)] == C_ROOM_NONE)
      22. {
      23. up = 1;
      24. }
      25. else
      26. {
      27. up = 2;
      28. }
      29. }
      30. //Bottom of the Room
      31. if((global.MiniMap_y-global.Absolute_Y)==(floor((room_height-64)/240)-1))
      32. {
      33. if(global.Trans_Bottom[floor((OBJ_Player_WalkCircle.x-32)/320)] == C_ROOM_NONE)
      34. {
      35. down = 1;
      36. }
      37. else
      38. {
      39. down = 2;
      40. }
      41. }
      42. //Left of the Room
      43. if(floor((OBJ_Player_WalkCircle.x-32)/320)==0)
      44. {
      45. if(global.Trans_Left[floor((OBJ_Player_WalkCircle.y-32)/240)] == C_ROOM_NONE)
      46. {
      47. left = 1;
      48. }
      49. else
      50. {
      51. left = 2;
      52. }
      53. }
      54. //Right of the Room
      55. if((global.MiniMap_x-global.Absolute_X)==(floor((room_width-64)/320)-1))
      56. {
      57. if(global.Trans_Right[floor((OBJ_Player_WalkCircle.y-32)/240)] == C_ROOM_NONE)
      58. {
      59. right = 1;
      60. }
      61. else
      62. {
      63. right = 2;
      64. }
      65. }
      66. global.Minimap[global.MiniMap_x,global.MiniMap_y] = SCR_B3toINT(up+10*right+100*down+1000*left)
      67. }
      68. }
      Alles anzeigen


      So, jetzt gehört das ganze noch gezeichnet:

      SCR_Minimap_Draw

      GML-Quellcode

      1. {
      2. var i,j;
      3. draw_set_blend_mode(bm_normal);
      4. for(i = -3; i< 4; i+=1)
      5. for(j = -1; j < 2; j+=1)
      6. {
      7. if( ((global.MiniMap_x+i)<0) ||
      8. ((global.MiniMap_y+j)<0) ||
      9. ((global.MiniMap_x+i)>C_MM_MAX_X) ||
      10. ((global.MiniMap_y+j)>C_MM_MAX_Y)
      11. )
      12. {
      13. draw_sprite_ext(global.MinimapSpriteArray[0],
      14. -1,
      15. view_xview[0]+C_MM_POS_X+i*8,
      16. view_yview[0]+C_MM_POS_Y+j*8,
      17. 1,1,0,make_color_rgb(64,64,64),0.5);
      18. //1,1,0,c_white,1);
      19. }
      20. else
      21. if(global.Minimap[global.MiniMap_x+i,global.MiniMap_y+j] == -1)
      22. {
      23. draw_sprite_ext(global.MinimapSpriteArray[0],
      24. -1,
      25. view_xview[0]+C_MM_POS_X+i*8,
      26. view_yview[0]+C_MM_POS_Y+j*8,
      27. 1,1,0,make_color_rgb(64,64,64),0.5);
      28. }
      29. else
      30. {
      31. if((i == 0) && (j == 0))
      32. draw_sprite_ext(global.MinimapSpriteArray[global.Minimap[global.MiniMap_x,global.MiniMap_y]],
      33. -1,
      34. view_xview[0]+C_MM_POS_X,
      35. view_yview[0]+C_MM_POS_Y,
      36. 1,1,0,make_color_rgb(128,128,255),1);
      37. else
      38. draw_sprite_ext(global.MinimapSpriteArray[global.Minimap[global.MiniMap_x+i,global.MiniMap_y+j]],
      39. -1,
      40. view_xview[0]+C_MM_POS_X+i*8,
      41. view_yview[0]+C_MM_POS_Y+j*8,
      42. 1,1,0,make_color_rgb(64,64,255),1);
      43. }
      44. }
      45. }
      Alles anzeigen


      Die Funktionen SCR_Minimap_Create gehört ins Create Event, StepEnd ins StepEnd Event und Draw ins Draw Event eines Minimap Objekts, welches persistent ist.

      Jo, das wars in etwa. Im anhang findet ihr das ganze noch mal als GML Datei.
      Dateien
      ...
    • Nö, meiner Meinung nach nicht. Wenn du Code im Creation Code eines Raums aufrufst, musst du immer in den Raum Editor um diesen zu überprüfen, das selbe gilt übrigens meiner Meinung nach für Code, welcher direkt in Objekten aufgerufen wird. Das ganze Projekt bleibt so für mich leichter zu warten. Des weiteren ist der Aktuelle Ansatz für Räume mit beliebiger Größe. Aber Funktionen haben eine Maximale Anzahl von Argumenten, welche sie übernehmen können, so kann ein Raum z.b. nur mehr 3x4 Raumeinheiten groß sein (argument0 und 1 gehen für Raumgröße drauf).
      ...
    • Shoba schrieb:

      Nö, meiner Meinung nach nicht. Wenn du Code im Creation Code eines Raums aufrufst, musst du immer in den Raum Editor um diesen zu überprüfen, das selbe gilt übrigens meiner Meinung nach für Code, welcher direkt in Objekten aufgerufen wird. Das ganze Projekt bleibt so für mich leichter zu warten. Des weiteren ist der Aktuelle Ansatz für Räume mit beliebiger Größe. Aber Funktionen haben eine Maximale Anzahl von Argumenten, welche sie übernehmen können, so kann ein Raum z.b. nur mehr 3x4 Raumeinheiten groß sein (argument0 und 1 gehen für Raumgröße drauf).


      Mein Reden. Lasst die Finger von Room Creation Code und Instance Creation Code - eigentlich auch von Time Lines und Pieces of Code.
      Die Sache ist einfach, wie Shoba schon sagte, dass bei einem größeren Projekt man unglaublich glücklich ist, alles an einem Ort zu haben und Sachen nicht suchen zu müssen. Selbst Sprite-Origins können zum Nerv werden.


      Ansonsten sieht das Tutorial sehr schick aus - mir gefällt die Unterteilung in Code und Erklärung des Löungsansatzes.