workshop14

Top  Zurück  Weiter

Workshop 14: Das Verwenden der Maus, Maus-Ereignisse

Lite-C kann für jede nur erdenkliche Art von Spiel verwendet werden und auch der integrierte Maus-Code steht da in nichts zurück. Es gibt drei Maus-Modi zur Auswahl und jeder dieser Modi kann mithilfe einer einfachen Code-Zeile wie dieser gesetzt werden:

mouse_mode = 0; // wir können hier 0, 1, 2 oder 4 benutzen

Nun, Sie haben es wieder geahnt: mouse_mode ist eine vordefinierte Variable. Mehr darüber finden Sie im Referenzhandbuch.

Setzen Sie "mouse_mode = 0;" und der Mauszeiger wird unsichtbar, ganz so als existierte überhaupt gar keine Maus (eine gute Einstellung für Rennspiele, Shooters etc.). Wenn Sie einen sichtbaren Mauszeiger haben wollen, der Kameraposition oder Winkel kontrollieren kann (z. B. erste / dritte-Person-Action Games, Rollenspiele), verwenden Sie "mouse_mode = 1;". "mouse_mode = 2;" nehmen Sie für einen Mauszeiger, der sichtbar ist, die Kamera nicht beeinflussen kann aber zum Anklicken von Objekten etc. frei bewegbar ist (z. B. sämtliche Strategiespiele, inklusive Brettspiele). Und mouse_mode = 4; läßt die Maus automatisch der Windows-Maus folgen; dies haben wir in Workshop 04 benutzt. Machen Sie sich um das Ganze jetzt keine Gedanken, wir werden alle Maus-Modi demnächst in Aktion sehen.

Starten Sie Lite-C, öffnen und starten Sie script14.c. Sieht aus wie unser Level mit den beiden Zaubermännern aber diesmal schein keiner zuhause zu sein... Bewegen Sie die Maus hin und her und sie können die Pan- und Tilt-Winkel der Kamera verändern.

w14_02

Drücken Sie einmal auf die Taste [M] und werden Sie Zeuge dieses Wunders:

w14_03

Aus dem Nichts tauchte ein graues Fadenkreuz auf! Ich kann die Kamera bewegen und das Fadenkreuz bewegt sich mit!

Drücken Sie gleich noch einmal auf [M]. Nun sehen Sie einen Mauspfeil auf dem Bildschirm:

w14_04

Das ist ja ein Ding: jetzt lässt sich der Mauspfeil bewegen aber die Kamera bleibt unverändert!

Drücken Sie [M] so oft Sie wollen und probieren Sie die drei Maus-Modi aus. Während Sie damit beschäftigt sind, werfen wir schon mal einen Blick auf den Code in der Datei script14.c:

////////////////////////////////////////////////////////////////////
#include <acknex.h>
#include <default.c>
////////////////////////////////////////////////////////////////////

BMAP* cursor_pcx = "cursor.pcx";
BMAP* crosshair_pcx = "crosshair.pcx";

////////////////////////////////////////////////////////////////////

function change_mouse_mode()
{
	mouse_mode += 1;
	mouse_mode %= 3;
	if (1 == mouse_mode)
		mouse_map = crosshair_pcx;
	if (2 == mouse_mode)
		mouse_map = cursor_pcx;
}

function main()
{
	video_screen = 1;
	video_mode = 7;
	level_load ("work14.wmb");
	on_m = change_mouse_mode;
	
	while (1)
	{
		mouse_pos.x = mouse_cursor.x;
		mouse_pos.y = mouse_cursor.y;
		camera.pan -= mouse_force.x;
		camera.tilt += mouse_force.y;
		 
		wait (1);
	}
}     

Das sieht eher unkompliziert aus, mit einigen dieser Dinge haben wir uns schon früher befasst, stimmt's? Wir haben zwei Bitmaps definiert: für den Mauspfeil und für das Fadenkreuz. Die verwendeten Bilddateien finden Sie im Ordner workshop14. Betrachten wir uns jetzt den Code unserer Main-Funktion:

function main()
{
 video_screen = 1;
 video_mode = 7;
 level_load ("work14.wmb");
 on_m = change_mouse_mode;

 while (1)
 {
  mouse_pos.x = mouse_cursor.x;
  mouse_pos.y = mouse_cursor.y;
  camera.pan -= mouse_force.x;
  camera.tilt += mouse_force.y;

  wait (1);
 }
}

Die erste Codezeile setzt die vordefinierte Variable video_screen auf 1 und sorgt so dafür, daß das Programm im Vollbildmodus läuft. Ja, so einfach ist das! Diese Zeile läßt sich jedem anderen Game oder sonstigen Anwendung, die Sie erstellen, hinzufügen und schon wird von Anfang an im Vollbildmodus gestartet. Natürlich können Sie per Standard-Tastenkombination [Alt] + [Eingabe] in den Fenstermodus wechseln. Die vierte Codezeile bindet function change_mouse_mode an die Taste [M] unserer Tastatur und sorgt dafür, daß sie immer, wenn der Player auf [M] drückt, aufgerufen wird. Dann wiederholen wir ein paar Zeilen in einer while (1)-Schleife. Betrachten wir uns die ersten beiden Codezeilen etwas genauer:

mouse_pos.x = mouse_cursor.x;
mouse_pos.y = mouse_cursor.y;

Die vordefinierten Variablen mouse_pos.x und mouse_pos.y steuern die Position des Mauszeigers auf dem Bildschirm. Ist beispielsweise mouse_pos.x = 200 und mouse_pos.y = 300", erscheint der Mauszeiger genau dort auf dem Bildschirm:

w14_06

Erraten? Die vordefinierten Variablen namens pointer.x und pointer.y konvertieren die Mausbewegungen in Pixel-Bildschirmkoordinaten und das sind genau die Daten, die wir für mouse_pos.x und mouse_pos.y gebraucht haben! Ich frage mich, ob die das mit Absicht gemacht haben... Na, egal, Sie brauchen sich nicht die ganze Geschichte zu behalten, packen Sie die beiden Code-Zeilen in eine while-Schleife und schon können Sie die Maus in jedem Projekt anwenden. Oh, und vergessen Sie die "wait (1);"-Anweisung nicht - in allen Schleifen!

Werfen wir nun einen Blick auf die nächsten beiden Code-Zeilen:

camera.pan -= mouse_force.x;
camera.tilt += mouse_force.y;

Gut, wir verändern also die Kamera-Winkel "pan" und "tilt" indem wir mouse_force.x und mouse_force.y verwenden. Dabei handelt es sich um zwei vordefinierte Variablen, die für die horizontale (mouse_force.x) und vertikale (mouse_force.y) Bewegungsgeschwindigkeit der Maus zuständig sind. Immer, wenn Sie aufhören, die Maus zu bewegen, werden diese beiden Variablen auf Null gesetzt, sonst liegen sie zwischen -1 und 1. Ich habe hier keine Zeit / Frameraten-Korrektur benutzt aber ich bin sicher, das kann ich regeln (Tipp: multiplizieren Sie die rechte Seite des Ausdrucks mit sowas wie 5 * time_step).

Betrachten wir uns nun den Code der Funktion, der die vordefinierte Variable namens mouse_mode ändert:

function change_mouse_mode()
{
 mouse_mode += 1;
 mouse_mode %= 3;
 if (1 == mouse_mode)
   mouse_map = crosshair_pcx;
 if (2 == mouse_mode)
   mouse_map = cursor_pcx;
}

Es ist einigermaßen klar, dass diese Funktion durch Drücken der [M]-Taste ausgelöst wird. Bei Spielstart ist mouse_mode auf Null gesetzt (das ist der Default-Wert), also taucht der Zeiger auch nicht auf. Sobald wir auf [M] drücken, läuft die Funktion change_mouse_mode einmal durch und hört dann auf. Besprechen wir die ersten beiden Code-Zeilen:

mouse_mode += 1;
mouse_mode %= 3;

"mouse_mode += 1;" ist lediglich die kürzere Version von "mouse_mode = mouse_mode + 1;", erinnern Sie sich? Beide Aussagen machen dasselbe: sie vergrößern mouse_mode um 1. Der Ausgangswert von mouse_mode war Null, nun ist er also 1. Der Mauszeiger taucht als schwenkbares Fadenkreuz auf und gleichzeitig steuert die Maus auch die Kamera. Das ist der passende "mouse_mode" für Action- oder Rollenspiele (RPG).

Die zweite Code-Zeile "mouse_mode %= 3;" ist die Kurzform von "mouse_mode = mouse_mode % 3;", richtig? Der Modulo-Operator (%) ist der Rest einer Division aus zwei Zahlen, zum Beispiel: 14 % 3 = 2, denn 14 = 3 * 4 + 2 also ist bei dieser Operation der Rest 2.

Warum brauchen wir diesen Modulo-Operator? Hauptsächlich deshalb, weil wir so einen Bereich von Werten, die sich aus einer Rechenoperation ergeben, festlegen können. Wenn wir z. B."eine_zahl %= 3;" nehmen, können wir sicher sein, dass "eine_zahl" nur 0, 1 oder 2 sein kann. Nehmen wir aber "eine_andere_zahl % 7;", legen wir fest, dass "eine_andere_zahl" ausschließlich einen dieser Werte annehmen kann: 0, 1, 2, 3, 4, 5 oder 6.

Es mag ein wenig seltsam erscheinen, aber wir müssen den Modulo-Operatoren benutzen, um mouse_mod auf einen gültigen Wert zu setzen: 0, 1, 2. Was aber, wenn der Player fünf Mal auf [M] drückt? Wir können nicht zulassen, dass mouse_mode auf 5 gesetzt wird! Die zweite Codezeile sorgt dafür, dass mouse_mode bei fünfmaligem Drücken der [M]-Taste = 2 ist, denn 5 = 3 * 1 + 2, wobei 2 der Rest ist. Sie können also so oft Sie wollen auf [M] drücken, denn "mouse_mode" wird seinen Wert wie folgt verändern: 0, 1, 2, 0, 1, 2, 0, 1, 2,...

Schauen wir uns den Rest des Codes innerhalb von Funktion change_mouse_mode an:

if (1 == mouse_mode)
  mouse_map = crosshair_pcx;
if (2 == mouse_mode)
  mouse_map = cursor_pcx;

Diese Code-Zeilen ändern die Bitmap für den Mauszeiger (vordefiniert als mouse_map) in crosshair_pcx oder cursor_pcx. Das ist alles, was in dieser Skript-Datei an Code zu besprechen ist! Sie sehen, wir brauchen keinen Code, um den Mauszeiger verschwinden zu lassen. Wenn "mouse_mode" auf Null gesetzt ist, macht die Engine das ganz von alleine. Auch musste ich die Mauszeiger-Bitmaps nicht verändern, aber ich will Ihnen zeigen wie und wann Sie einen bestimmten Typ von Mauszeiger benutzen sollten.

Die Variablen namens mouse_force.x und mouse_force.y reagieren nur dann auf die Mausbewegung (haben also vom Skript verwertbare Werte ungleich Null), wenn mouse_mode auf 0 oder 1 gesetzt ist. Erwarten Sie also nicht, dass sich die Kamera mit der Maus bewegen lässt, wenn Sie den mouse_mode auf 2 gesetzt haben; das ist der Maus-Modus, der für Strategie- und Brettspiele benutzt werden sollte.

Genauso, wie ich es in diesem Workshop gemacht habe, können Sie alle drei Maus-Modi in einem einzigen Game verwenden. Wenn Sie etwa eine Cut-Szene haben, wollen Sie den Mauszeiger verschwinden lassen während die beiden Figuren miteinander reden ((mouse_mode = 0). Sobald diese Szene vorbei ist, wollen Sie, je nachdem, um welchen Spieletyp es sich handelt auf mouse_mode = 1 oder mouse_mode = 2 umschalten.

Sind Sie bereit, etwas Neues auszuprobieren? Kopieren Sie diese Zeilen und fügen Sie sie in die while-Schleife in der main Funktion ein:

if (mouse_left)
  
camera.ambient += 1;
if (mouse_right)
  
camera.ambient -= 1;

Der entstehende while-Loop sollte wie in folgender Abbildung aussehen (die neuen Codezeilen sind grün):

...
 
 while (1)
 {
	mouse_pos.x = pointer.x;
	mouse_pos.y = pointer.y;
	camera.pan -= mouse_force.x;
	camera.tilt += mouse_force.y;
	camera.tilt += mouse_force.y;
	if (mouse_left)
		camera.ambient += 1;
	if (mouse_right)
		camera.ambient -= 1;
	wait (1);
 }

}        
Starten Sie das Level erneut und Sie werden etwas Interessantes feststellen: durch Gedrückt-Halten der rechten oder linken Maustaste können Sie die Ambient-Beleuchtung im Level steuern. Der Trick ist wirklich einfach: verschiedene camera.ambient-Werte führen zu unterschiedlichen Helligkeitswerten in unserem Level. Was wir (bisher) nicht wissen, ist, dass es für die Maustasten vordefinierte Variablen, nämlich mouse_left, mouse_middle (hier nicht gebraucht) und mouse_right gibt, die durch Drücken der entsprechenden Maustaste auf 1 (oder "true") und bei nicht gedrückter Maustaste auf 0 (oder "falsch")gesetzt werden.

Kommt Ihnen diese Zeile bekannt vor?

if (key_w)
........

Ja! Wir haben sie in unserem vorhergehenden Workshop zum Ändern eines Kamera-Winkels benutzt! Die gleiche Methode lässt sich auch auf die drei Maustasten anwenden: ersetzen Sie if (key_irgendwas) durch if (mouse_irgendwas) und schon haben Sie die Dinge in ähnlicher Weise unter Maus-Kontrolle! Allerdings sollten Sie dabei nicht vergessen, dass mouse_irgendwas lediglich mouse_left, mouse_middle oder mouse_right sein kann.

Maus-Events

Vermutlich ist Ihnen bekannt, daß wir für Interaktionen in einer 3D-Welt die Maus verwenden? Öffnen und starten Sie einfach die Datei script14_2.c. Sie sehen wieder die gute alte Hütte mit den beiden Zauberern. Jetzt bewegen Sie den Mauszeiger über einen davon und sehen Sie, was passiert!

wizshot

Der Zauberer muß einen Zauberspruch gesprochen haben, denn er leuchtet auf und beleuchtet auch seine Umgebung!

Was ist hier geschehen? Wir haben eine sogenannte event-Funktion benutzt. Diese sagt der Entity wie sie reagieren soll, sobald sie mit der Maus berührt oder angeklickt wird. Werfen Sie einen Blick in script14_2.c:

////////////////////////////////////////////////////////////////////
#include <acknex.h>
#include <default.c>
////////////////////////////////////////////////////////////////////

BMAP* cursor_pcx = "cursor.pcx";
BMAP* crosshair_pcx = "crosshair.pcx";

////////////////////////////////////////////////////////////////////

function highlight_event()
{
  if (event_type == EVENT_TOUCH) // der Zauberer wurde mit der Maus berührt?
  {
    my.ambient = 100; // dann lass' ihn heller aussehen
    my.lightrange = 200; // und generiere Licht in einem Radius von 200 Quants!
  }
  else if (event_type == EVENT_RELEASE) // wurde die Maus von ihm wegbewegt?
  {
     my.ambient = 0; // dann stelle seinen Anfangs-Ambient-Wert wieder her (null)
     my.lightrange = 0; // und lass ihn aufhören seine Umgebung zu erleuchten
  }
}

action wizard_lights() // diese Aktion ist beiden Z. zugewiesen
{  
  // mache das Z-Modell für Mausberührung und -loslassen empfindlich
  my.emask = ENABLE_TOUCH | ENABLE_RELEASE;
  // starte d. Function highlight_event wenn die Z. berührt oder losgelassen werden
  my.event = highlight_event; 
}

function main()
{
  video_screen = 1; // starte im Vollbildmodus 
  video_mode = 7; // verwende eine Bildschirmaufl. v. 800 x 600 Pixeln
  level_load ("work14_2.wmb"); // lade das Level
  mouse_mode = 2; // zeige den Mauszeiger
  mouse_map = cursor_pcx; // setze die Mauszeiger-bitmap
  
  while (1)
  {
    mouse_pos.x = mouse_cursor.x; // ermögliche das Bewegen des Mauszeigers
    mouse_pos.y = mouse_cursor.y; // auf x- und y-Achse
    wait (1);
  }
}

Genauso wie die Taste [M] am Anfang dieses Workshops unsere change_mouse_mode-Funktion ausgelöst hat, so können verschiedene Dinge, die mit Entities geschehen spezielle Funktionen auslösen: die sogenannten Event-Funktionen.

action wizard_lights() // diese Aktion ist beiden Zauberern zugewiesen
{       my.emask = ENABLE_TOUCH | ENABLE_RELEASE;
       my.event = highlight_event;
}

Beachten Sie, dass auch hier die Methode object.property benutzt wird.

1) die Eigenschaft emask setzt den Event-Typus, der die Event-Funktion (in unserem Beispiel Berühren mit der Maus und Loslassen der Maus) auslösen wird. Dies bedeutet, daß, wenn der Player ihn berührt, die Event-Funktion des Zauberers starten wird und außerdem auch dann, wenn der Player den Mauszeiger vom (zuvor berührten) Zauberer wegbewegt. Im Handbuch zu Lite-C finden Sie die komplette Liste aller Event-Typen.

2) Die Eigenschaft event setzt den Namen der Funktion, die gestartet wird sobald einer der Events ausgelöst wird. In unserem Fall wird bei jeder Mausberührung oder eben Mauswegnahme die Funktion highlight_event gestartet.

Was geschieht nun wenn einer der Zauberer mit der Maus berührt wird?

function highlight_event( )
{
      if (event_type == EVENT_TOUCH)
       {
               my.ambient = 100;
               my.lightrange = 200;
       }
       else if (event_type == EVENT_RELEASE)
       {
              my.ambient = 0;
              my.lightrange = 0;
       }
}

Zunächst einmal müssen wir uns entscheiden was für ein Event-Typ unsere Funktion ausgelöst hat und dafür brauchen wir unsere vordefinierte Variable event_type. In Lite-C ist ein Mechanismus enthalten, welcher die Events auslöst und event_type gleichzeitig auf den passenden Wert setzt. Daher müssen wir event_type lediglich mit ein paar vordefinierten Werten vergleichen, um herauszufinden, welcher Event-Typ ausgelöst wurde.

Ist event_type auf EVENT_TOUCH gesetzt, wissen wir, daß unsere Entity mit der Maus berührt wurde. Da der vordefinierte "my"-Pointer auf den mausberührten Zauberer gesetzt wurde und auch in der Event-Funktion funktioniert, können wir diesen zum Beeinflussen der Entity-Eigenschaften verwenden. Nun setzen wir den ambient-Wert der Entity auf 100 Prozent, so daß diese hell aussieht und dann setzen wir die Eigenschaft lightrange der Entity auf 200 was bewirkt, dass alles im Umkreis von 200 Quant vom internen dynamischen Licht der Entity beleuchtet wird. Die "else"-Verzweigung startet wenn der Event nicht EVENT_TOUCH war. Es könnte demnach ein EVENT_RELEASE gewesen sein. Das überprüfen wir, indem wir hinter das "else" eine weitere "if" Abfrage platzieren. Wenn es wahr ist, setzen wir die (auch als "Parameter" bekannten) Eigenschaften ambient und lightrange für unseren Zauberer zurück.

Das waren fast schon zwei Workshops in einem - machen wir eine Pause.

Weiter: Der Joystick