Hilfreiche Funktionen in MMM

Das Mitmach-Projekt aus dem Maniac-Mansion-Universum.
Antworten
a-v-o
Süßwasserpirat
Süßwasserpirat
Beiträge: 258
Registriert: 22.09.2002, 21:28
Kontaktdaten:

Hilfreiche Funktionen in MMM

Beitrag von a-v-o »

Lieber spät als nie... Hier sind einige Hinweise für das Erstellen von Interaction-Scripts in MMM:

1. Es wird immer nur die Interaction „Any click on ...“ verwendet... keine anderen!

2. Es gibt im Global Script definierte Funktionen, die in Interaction-Scripts verwendet werden können und die Scripts teilweise wesentlich verkürzen:

a) FaceDirection
Anstelle von FaceLocation kann FaceDirection verwendet werden, wenn der Character in eine bestimmte Richtung und nicht auf einen bestimmten Punkt schauen soll:

FaceDirection (CharId, DIR_UP);
FaceDirection (CharId, DIR_LEFT);
FaceDirection (CharId, DIR_DOWN);
FaceDirection (CharId, DIR_RIGHT);

Hierdurch wird das Script auch leichter lesbar.

b) EnterRoom
Anstelle von NewRoom kann der Playercharacter mit EnterRoom in einen neuen Raum an eine bestimmte Stelle geschickt werden:

EnterRoom (int room, int x, int y, int dir);
Für „dir“ werden die Konstanten von FaceDirection verwendet.

c) GiveInv und GiveInvEx
GiveInv (int invitem, int charid)
PlayerCharacter gibt invitem an charid.

GiveInvEx (int invitem, int charidfrom, int charidto)
charidfrom gibt invitem an charidto.

d) MovePlayer und abgeleitete Funktionen
Die folgenden Funktionen bewegen den PlayerCharacter; die weitere Ausführung des Scripts wird unterbrochen. Diese Bewegung kann der Spieler durch Klicken mit der Maus abbrechen. Die Funktionen geben zurück, ob das Ziel erreicht oder abgebrochen wurde.

MovePlayer (int x, int y)
Bewegung auf Walkable Area zu den angegebene Koordinaten.

MovePlayerEx (int x, int y, int direct)
Mit direct = 1 werden Walkable Areas ignoriert. Der PlayerCharacter bewegt sich in gerader Linie zu den Koordinaten.

GoToCharacter (int charid, int direction, int NPCfacesplayer, int blocking)
Bewegt den PlayerCharacter zu charid, wobei Walkable Areas beachtet werden. Bleibt in einem definierten Abstand auf der Seite von charid stehen, die in direction (1=oberhalb/hinter, 2=rechts, 3=darunter/vor, 4=links) angegeben ist. Der PlayerCharacter schaut charid an. charid schaut den PlayerCharacter nur an, wenn NPCfacesplayer=1 ist. Für blocking können folgende Werte verwendet werden:
0=nicht blockierend (MoveCharacter)
1=blockierend (MoveCharacterBlocking)
2=halb-blockierend (MovePlayer)

Entsprechend gibt es eine Funktion, um eine NPC zu einem anderen Character zu bewegen: NPCGoToCharacter.

Eine allgemeine Funktion, um einen Character zu einem Character zu bewegen ist GoToCharacterEx. Bei dieser Funktion lässt sich auch der Abstand angeben, in dem die Figuren voneinander stehen sollen.

Es gibt auch zwei Funktionen, die den PlayerCharacter zu dem Hotspot, Object oder Character bewegen, auf die der Spieler geklickt hat: Go und GoTo. Nähere Erklärungen im global script.

e) PlaceCharacter und PlacePC
mit diesen Funktionen wird ein Character bzw. der PlayerCharacter an eine Stelle im Raum so platziert, dass er in eine bestimmte Richtung schaut. Einsatz in erster Linie in (... before fadein).

f) init_object (int GI, int objects)
Ist GlobalInt GI ==1, dann wird das Objekt objects angezeigt, ansonsten versteckt. Einsatz in erster Linie in (... before fadein).

(Fortsetzung folgt...)
a-v-o
Süßwasserpirat
Süßwasserpirat
Beiträge: 258
Registriert: 22.09.2002, 21:28
Kontaktdaten:

Hilfreiche Funktionen in MMM

Beitrag von a-v-o »

3. Scripts für die Interaction „any click on ...“
a) Ein Hotpot oder Objekt, zu dem nur hingelaufen werden kann, für alle anderen Interactionen wird Unhandled aufgerufen:

Code: Alles auswählen

if (any_click_walk (x, y, dir) == 0) Unhandled ();
dir: siehe FaceDirection

b) Ein Hotpot oder Objekt, das darüber hinaus angeschaut werden kann:

Code: Alles auswählen

if (any_click_walk_look (x, y, dir, lookat) == 0) Unhandled ();
Der Text lookat wird beim Anschauen angezeigt.

c) Ein Objekt nehmen, das dann im Inventory erscheint:

Code: Alles auswählen

if (any_click_walk_look_pick (x, y, dir, lookat, objects, item, GI, sound) == 0) Unhandled ();
objects ist die Nummer des Objekts, item der Name im Inventory. GI ist das GlobalInt, das zum Objekt gehört und sound eine Tondatei, die vor/beim Entfernen des Objekts gespielt wird. Für nicht benötigte Parameter –1 angeben.

d) Ein InvItem mit dem Hotspot oder Objekt verwenden:

Code: Alles auswählen

if (any_click_walk_look (x, y, dir, lookat) == 0) 
{
 int result = any_click_use_inv (item, x, y, dir);
 if (result == 0) Unhandled ();
 if (result == 2)
 {
   // Script für das was passiert, wenn item verwendet wird
 }
}
4. Den Türen (oder ähnlichen Arten von Raumwechsel) widme ich einen eigenen Punkt.
Türen können entweder zu, offen oder verschlossen sein. Manche können einfach so geöffnet werden, für andere braucht man einen Schlüssel. Diese Begriffe lassen sich auch umdefinieren: Ein Gartentor, das eingerostet ist und sich daher nicht einfach so öffnen lässt ist verschlossen. Der Schlüssel ist vielleicht ein Ölkännchen.

Grundsätzlich werden Türen im Hintergrund als geschlossen gezeichnet, ein darüber gelegtes Objekt zeigt die geöffnete Türe. Das Häkchen vor "Object is initially visible" wird entfernt, an die Bezeichnung der Türe in object name wird „>v“ angehängt.

Im global script in der game_start Funktion wird der Anfangszustand der Türe in einem GlobalInt gespeichert, hier z.B. GlobalInt 6:
a) Türe ist zu: SetGlobalInt (6, 0);
b) Türe ist offen: SetGlobalInt (6, 1);
c) Türe ist verschlossen: SetGlobalInt (6, 2);

Nun sind 2 Einträge im global script in der Funktion „VariableExtensions“ erforderlich.

Beispielsweise für eine Tür, deren Zustand in GlobalInt 6 gespeichert wird und zu der in Raum 2 der Hotspot 3 und in Raum 5 der Hotspot 2 gehört:

Code: Alles auswählen

else if ((r == 2) && (h == 3)) OpenCloseExtension (6, location);
else if ((r == 5) && (h == 2)) OpenCloseExtension (6, location);
Angenommen in Raum 2 zeigt Objekt 4 die geöffnete Tür, dann wird in „...before fadein“ des Raumes folgendes eingetragen:

Code: Alles auswählen

SetObjectClickable (4, 0);
init_object (6, 4);
Entsprechendes wird in Raum 5 für das dortige Türobjekt eingetragen.

In der „any click on hotspot“-Interaktion von Hotspot 3 in Raum 2 wird je nach gewünschter Öffnungsart eines der folgenden Scripts verwendet:

a) Normale Türe
Diese Türe lässt sich einfach von Hand öffnen und es ertönen die Standardsounds (2 beim Öffnen und 3 beim Schließen).

Code: Alles auswählen

if (any_click_on_door (6, 4, x, y, dir, 5, nr_x, nr_y, nr_dir) == 0) Unhandled ();
b) Spezielle Tür
In diesem Fall sind 2 Zeilen erforderlich:

Code: Alles auswählen

SetDoorStrings („Dies ist eine Tür.“, „Die Tür ist verschlossen.“, „Damit geht die Tür nicht auf.“);
if (any_click_on_door_special (6, 4, x, y, dir, 5, nr_x, nr_y, nr_dir, opensound, closesound, key, closevalue) == 0) Unhandled ();
Mit der ersten Zeile werden die Texte für das Anschauen, den Versuch eine verschlossene Tür zu öffnen und das Benutzen eines falschen Inv-Items gesetzt.
Die zweite Zeile ähnelt der Zeile bei der normalen Türe, allerdings können bspw. die Sounds angegeben werden. Mit key wird das InvItem bezeichnet, das die Türe öffnet, wenn sie verschlossen ist. Ist key=-1, dann kann die verschlossene Türe auch von Hand geöffnet werden. Wird eine offene Türe geschlossen, dann bezeichnet closevalue die Art: 0=zu, 2=verschlossen.

Bsp.: Verschlossene Tür
Die Tür kann in Raum 2 mit dem Schlüssel 7 aufgeschlossen werden, einmal aufgeschlossen, bleibt sie unverschlossen und kann künftig von Hand geöffnet werden.
Raum 2: Key = 7; closevalue = 0;
Raum 5: normale Tür

Bsp.: Haustüre
Zum Öffnen der Haustüre benötigt man in Raum 2 einen Schlüssel 7, in Raum 5 kann sie von Hand geöffnet werden:
Raum 2: Key = 7; closevalue = 2
Raum 5: Key = -1; closevalue = 2

Bsp.: Tür ist nur einseitig öffenbar, bspw. weil auf der anderen Seite kein Türgriff ist.
Raum 2: Key = -1; closevalue = 2
Raum 5: normale Tür

Bsp.: Eine automatische Tür mit Zahlenschloss
Bei Anklicken des Zahlenschlosses Hotspot 1 in Raum 2 springt die Tür auf.
In game_start wird die Tür als verschlossen angegeben:

Code: Alles auswählen

SetGlobalInt (6, 2);
In der „any click on hotpot“-Interaction von Hotspot 1 (Zahlenschloss) wird bei erfolgreicher Eingabe der Zahlenkombination folgendes Script ausgeführt:

Code: Alles auswählen

if (GetGlobalInt (6) != 1)
{
 SetGlobalInt (6, 1);
 PlaySound (2);
 init_object (6, 4);
}
Die Tür steht nun offen. Soll die Tür nach einer gewissen Zeit oder einem anderen Ereignis wieder so verschlossen werden, dass erneut die Eingabe der Zahlenkombination erforderlich ist, dann ist folgendes Script auszuführen:

Code: Alles auswählen

if (GetGlobalInt (6) == 1)
{
 SetGlobalInt (6, 2);
 PlaySound (3);
 init_object (6, 4);
}
[ZENSIERT]
Adventure-Gott
Adventure-Gott
Beiträge: 4575
Registriert: 13.07.2004, 14:04
Wohnort: Da wo muss
Kontaktdaten:

Die Tücken von MovePlayer

Beitrag von [ZENSIERT] »

*push*

Die Funktion MovePlayer ist nicht dafür geeignet, den Spieler durch's Bild marschieren zu lassen. Dafür ist angebracht:

Code: Alles auswählen

MoveCharacterBlocking(GetPlayerCharacter(), x, y, 0); // Die 0 am Ende bedeutet, dass er Spieler auf den Walkable Areas bleibt
Oder für AGS 2.7 aufwärts:

Code: Alles auswählen

player.Walk(x,y,eBlock,eWalkableAreas);
Es heißt, Leute mit den originellsten Nicknames schreiben die besten Beiträge

Ausnahmen bestätigen die Regel
_________________
<Problem> Weil du denken kannst.

Zuletzt bearbeitet von [ZENSIERT] am 16.07.1759, 16:19, insgesamt 54743869-mal bearbeitet
Benutzeravatar
KhrisMUC
Adventure-Gott
Adventure-Gott
Beiträge: 4674
Registriert: 14.03.2005, 00:55
Wohnort: München

Beitrag von KhrisMUC »

*nochmal push*

Wer in seiner Episode anderen Charakteren Inventargegenstände mit dem "Gib X an Y"-Befehl übergeben will, kann dies nun tun, ohne sich selber mit dem global script rumschlagen zu müssen bzw. Benutze zu verwenden:

Der entsprechende Teil in der on_mouse_click() muss so aussehen (zu erkennen an "else if ((GSagsusedmode == 4)"):

Code: Alles auswählen

    else if ((GSagsusedmode == 4) && (GetLocationType (mouse.x, mouse.y) == 2) && isAction (A_GIVE_TO) && (GetCharacterAt (mouse.x, mouse.y) >= 0))
    {
      SetLabelColor (ACTION, 0, ActionLabelColorHighlighted);
      if (IsInteractionAvailable (mrx - GetViewportX (), mry - GetViewportY (), MODE_USEINV) == 0)
        player.Say("Ich werde das nicht ohne Grund hergeben.");
      else if (GoToCharacter (GSlocid, 0, 1, 2))
      {
        ItemGiven = character [GetPlayerCharacter ()].activeinv;
        RunCharacterInteraction (GSlocid, MODE_USEINV);
      }
      SetAction (A_DEFAULT);
    }
Bei Use inventory on character steht dann z.B.:

Code: Alles auswählen

  if (ItemGiven==7) {
    player.Say("Hier, nimm den Luckenöffner(TM).");
    LoseInventory(ItemGiven);
    AddInventoryToCharacter(DAVE, ItemGiven);
    cDave.Say("Danke.");
  }
  else {
    player.Say("Kannst du das gebrauchen?"); 
    cDave.Say("Nö."); 
  }
Man muss auch kein MovePlayer oder FaceDirection oder ähnliches verwenden, das geschieht automatisch durch GoToCharacter (GSlocid, 0, 1, 2).

Bei Charakteren, bei denen nichts in Use inventory on character steht, läuft die Hauptperson nicht hin und sagt nur "Ich werde das nicht ohne Grund hergeben."
Hier kann man natürlich eine eigene Funktion aufrufen, die unterschiedliche/situationsangepasste Sätze ausgibt.

(Ich hätte es auch so scripten können, dass man bei any click on character mit "else if (UsedAction(A_GIVE_TO))" arbeiten kann, vielleicht ein andermal ;))
Use gopher repellent on funny little man
Benutzeravatar
KhrisMUC
Adventure-Gott
Adventure-Gott
Beiträge: 4674
Registriert: 14.03.2005, 00:55
Wohnort: München

Beitrag von KhrisMUC »

Und noch ein anderes Thema (deswegen der Doppelpost):

Im Tutorial gibt es Beispielcode für eine Lampe:

Code: Alles auswählen

if (UsedAction (A_WALK_TO)) {
if (MovePlayer (130, 133)) {
FaceLocation (GetPlayerCharacter (), 130, 132);
}
}

else if (UsedAction (A_LOOK_AT)) {
if (MovePlayer (130, 133)) {
FaceLocation (GetPlayerCharacter (), 130, 132);
DisplaySpeech (GetPlayerCharacter (), "Oha, die stand sonst nicht da.");
}
}

else if (UsedAction (A_PICK_UP)) {
if (MovePlayer (130, 133)) {
FaceLocation (GetPlayerCharacter (), 130, 132);
ObjectOff(6);
AddInventory(8);
}
}

else Unhandled ();
Mal abgesehen von der grauenhaften Formatierung (hehe, sorry DasJan ;)) kann man sich eine Menge Schreibarbeit sparen, wenn man den Code abändert:

Code: Alles auswählen

if (MovePlayer (130, 133)) {
  FaceDirection (GetPlayerCharacter (), DIR_UP);    // EDIT: FaceDirection

  if (UsedAction (A_WALK_TO)) {}

  else if (UsedAction (A_LOOK_AT)) {
    DisplaySpeech (GetPlayerCharacter (), "Oha, die stand sonst nicht da.");
  }

  else if (UsedAction (A_PICK_UP)) {
    ObjectOff(6);
    AddInventory(8);
  }

  else Unhandled ();
}
Und man hat jetzt die Reaktionen auf die verschiedenen Verben direkt untereinander stehen.
Zwar läuft jetzt der Charakter grundsätzlich erst zum Gegenstand hin, aber das ist IMO auch realistischer, gerade bei Unhandled()-Antworten wie "Ich kann das nicht öffnen."

EDIT: Genau, bitte einen Stickie draus machen. Vielleicht kann man auch einen Link zu diesem Thread ans Ende des Tutorials setzen.

EDIT [ZENSIERT]: Das kamma nochmal abkürzen ;)

Code: Alles auswählen

if (MovePlayer (130, 133)) {
  FaceDirection (GetPlayerCharacter (), DIR_UP);

  if (UsedAction (A_LOOK_AT)) {
    DisplaySpeech (GetPlayerCharacter (), "Oha, die stand sonst nicht da.");
  }

  else if (UsedAction (A_PICK_UP)) {
    ObjectOff(6);
    AddInventory(8);
  }

  else Unhandled ();
}
EDIT: Das hatte ich auch erst so, aber dann wird bei A_WALK_TO auch Unhandled() aufgerufen. Hab aber gerade gesehen, dass dann nix passiert, somit passts.
Zuletzt geändert von KhrisMUC am 07.04.2006, 01:04, insgesamt 1-mal geändert.
Use gopher repellent on funny little man
Benutzeravatar
Rocco
Adventure-Treff
Adventure-Treff
Beiträge: 1019
Registriert: 25.11.2003, 16:20
Wohnort: Ronville
Kontaktdaten:

Beitrag von Rocco »

nachtrag zum give to posting von khrismuc.

Es geht um einen allgemeinen Bug, der sich bis jetzt hartnäckig in allen starterpacks gehalten hat, vorwiegend aber auch beim GIVE TO Befehl auftritt.
Und zwar wenn man eine Interaction mit einem anderen Character versucht und dabei ein Unhandled Event auslöst und man währendessen mit der Maus nicht mehr am Character ist, stürzt das Game ab mit einer Fehlermeldung - FaceCharacter kann nicht ausgeführt werden....

und zwar gibts in der Funktion unhandled_event nach dem grünen auskommentierten code so rund um zeile 2000 eine Zeile da steht:

Code: Alles auswählen

if (type == 2 || type == 6) FaceCharacter (GetPlayerCharacter (), GetCharacterAt (mouse.x, mouse.y));

diese ersetzen durch:

Code: Alles auswählen

if (type == 2 || type == 6)
    {
    if(GetLocationType(mouse.x,mouse.y)==2)
    FaceCharacter (GetPlayerCharacter (), GetCharacterAt (mouse.x, mouse.y));
    }

dann sollte der bug nicht mehr auftreten
Benutzeravatar
KhrisMUC
Adventure-Gott
Adventure-Gott
Beiträge: 4674
Registriert: 14.03.2005, 00:55
Wohnort: München

Beitrag von KhrisMUC »

Mir ist gerade ein "Fehler" im Starterpack aufgefallen, der wohl bis jetzt bei allen vorhanden ist:
Die Interaktionsskripte fangen ja normalerweise mit if (MovePlayer(x, y)) { ... an, der Vorteil hierbei ist, dass man durch erneuten Klick die momentane Aktion abbrechen kann abstatt darauf zu warten, dass man die Kontrolle zurückbekommt.
Soweit so gut, normalerweise klappt das ja auch einwandfrei.

Aber nehmen wir mal an, dass auf Grund gewisser Vorkommnisse im Spiel ein Gegenstand nicht mehr erreichbar ist, da eine walkable area abgeschaltet wurde.

Jetzt läuft der Spieler, bis er "anstößt" und führt dann trotzdem die Aktion aus.
Das liegt daran, dass MovePlayerEx auch 1 zurückliefert, wenn der Spieler nur stehengeblieben ist.

Am Ende sieht MovePlayerEx nämlich so aus:

Code: Alles auswählen

     if (GScancelable == 0) return 1;
    else return 0;
  }
  else return 0;
}
Das ändert man einfach in Folgendes ab:

Code: Alles auswählen

    if (GScancelable==0 && player.x==x && player.y==y) return 2;
    else if (GScancelable == 0) return 1;
    else return 0;
  }
  else return 0;
}
Dadurch muss man bestehende Skripte nicht abändern, kann aber jetzt mit if (MovePlayer(x, y)==2) { abfragen, ob der Spieler auch wirklich sein Ziel erreicht hat.
Use gopher repellent on funny little man
Benutzeravatar
Rocco
Adventure-Treff
Adventure-Treff
Beiträge: 1019
Registriert: 25.11.2003, 16:20
Wohnort: Ronville
Kontaktdaten:

Re: Hilfreiche Funktionen in MMM

Beitrag von Rocco »

Hier gibts ein Tutorial für die neuesten Starterpacks mit AGS Version 3++ aufwärts
http://www.maniac-mansion-mania.de/foru ... 9#msg36729
Antworten