Movement

Constantin Bremer

Die Probleme

Das größte Problem wurde relativ schnell offensichtlich: Es gibt in three.js keine vernünftige Standardlibrary für diese Art der Bewegung. Also mussten wir unser eigenes Movementmodell entwerfen. Von vornherein war klar, dass die Pointerlock API unumgänglich war, da diese den Mauszeiger in der Bildmitte 'fängt' und somit unterbrechungsfreie Rotationen ermöglicht. Dadurch lässt sich die Mausbewegung allerdings nur noch relativ zum letzten Frame bestimmen.

function moveCallback(event) {
    mouseX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
    mouseY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
}//Beachte Browserunterschiede

Durch das Sperren des Mauszeigers wurden allerdings noch weitere Probleme aufgeworfen, wie das Bedienen des Pausemenüs, die Erkennung von Fokusverlust (Esc) oder das (Wieder-)einfangen des Zeigers. Als größtes Problem erwies sich allerdings das Erkennen und Lösen von Konflikten beim gleichzeitigen Anzeigen mehrere Pausebildschirme: Die Pointerlock API setzt vorraus, dass der User aktiv in den Pointerlock anfordernden Bereich klickt um Missbrauch zu verhindern. Wenn jetzt aber während dieses Fokusverlustbildschirms das Pausemenü durch drücken von P aufgerufen wird, dann wurde in früheren versionen das Spiel im Hintergrund fortgesetzt. Um dies zu verhindern existieren nun vier Zustandsfelder im Code.

var Pause; //Render Pause: Wenn true wird der Renderloop bis auf die Kamera suspended
var PauseScreen; //Pause Menu: Wenn true ist das Pause-Overlay aktiv

var fullscreenchange = function (event) {
   if (document.fullscreenElement === element || document.mozFullscreenElement === element || document.mozFullScreenElement === element) {
   ...//Fullscreen: Wenn true läuft das Spiel im Vollbildmodus
   }
...
}

var pointerlockchange = function (event) {
   if (document.pointerLockElement === element || document.mozPointerLockElement === element || document.webkitPointerLockElement === element) {
   ...//Pointerlock: Wenn true ist der Mauszeiger gefangen
   }
...
}

Da der Fullscreen und der Pointerlock direkt im Movement liegen, liegen auch die Methoden mit denen z.B. das Interface den Pointerlock aufheben bzw. anfordern kann im Movement.

Die Rotation des Schiffes durch setzen von Object3D.rotation erwies sich als kompliziert, da man von zweidimensionalen Mauskoordinaten nur durch langwierige (und Rechenzeit kostende) Berechnungen auf eine dreidimensionale Rotation schließen kann. Wir haben uns also an den First Person Controls orientiert und einen Punkt konstruiert, welcher immer im Abstand von 400 Einheiten hinter dem Schiff schwebt. Somit konnten wir mit Object3D.lookAt() die Ausrichtung vereinfachen.

lon += mouseX;//Absolute Mausbewegung speichern
lat -= mouseY;        

lat = Math.max(-85, Math.min(70, lat));//Setzen der maximalen Ausschläge
phi = THREE.Math.degToRad(90 - lat);//Umwandlung in Bogenmaß
theta = THREE.Math.degToRad(lon);

var position = ship.position;

targetPosition.x = position.x + 100 * Math.sin(phi) * Math.cos(theta);//Projizieren des Punktes
targetPosition.y = position.y + 100 * Math.cos(phi);
targetPosition.z = position.z + 100 * Math.sin(phi) * Math.sin(theta);
ship.lookAt(targetPosition);

Die Achsen

Wie bereits erwähnt haben wir uns gegen eine vertikale Bewegung entschieden, da dies zu Problemen mit der Target Camera geführt hat. Somit beschränken sich die Bewegungsmöglichkeiten mit der Tastatur auf Beschleunigen, Abbremsen und nach rechts und links Ausweichen. Da in großen Teilen des Weltraums keine nennenswerten Anziehungskräfte gelten und uns nach Newtons erstem Gesetz

"Ein Körper verharrt im Zustand der Ruhe oder der gleichförmig geradlinigen Translation,
sofern er nicht durch einwirkende Kräfte zur Änderung seines Zustands gezwungen wird"

die Trägheit der Masse bekannt ist, könnte man in einem einfachen realistischen Modell die Tasteneingaben aus Modellkoordinatensicht als Pro-Frame-Translationsvektor aus Weltkoordinatensicht addieren und hätte somit ein für den Spieler nahezu unkontrollierbares Schiff, welches sich nie genau in die Richtung bewegt in die man fliegen möchte. Um diesem Problem entgegenzuwirken haben wir das in der Weltraum Science Fiction gerne genutzte Prinzip der Inertia damper umgesetzt. Dabei werden alle Achsen in jedem Frame schrittweise wieder der Neutralstellung zugeführt.

Render(){
...
  if (zAxis > 0) {
    zAxis -= 0.5;
  } else if (zAxis < 0) {
    zAxis += 0.5;
  }
...
}

Dieses Konzept haben wir nun sowohl auf die 'strafe-bewegung 'als auch die Maus angewendet, um die Steuerung zu vereinfachen. Der "Schubregler" jedoch ist bewusst von der Dämpfung ausgenommen, da somit die digitalen Tastendrücke einfach als analoge Schubreglerpositionsänderung übernommen werden können. Das verhindert, dass der Pilot beim Spielen dauerhaft W gedrückt halten muss. Sollte der Spieler nun doch einmal volkommen zum Stillstand kommen wollen, haben wir eine "Notstop-Funktion" eingebaut, welche durch drücken von X ausgeführt wird.

function stop() {
    xAxis = 0.0;
    yAxis = -0.0;
    zAxis = 0.0;
    setSpeed(-yAxis);//Interface auf 0 Setzen
    mouseX = 0.0;
    mouseY = 0.0;
}

Die Optimierung

Im ursprünglichen Modell war es zwischenzeitlich nicht mehr möglich das Schiff unter einer bestimmten Mindestgeschwindigkeit zu fliegen, da ansonsten bei zu extremen Mausausschlägen ( unter -60° über 80°) die Kamera unvorhersehbar reagiert hat. Da nun aber im weiteren Verlauf das langsame Fliegen und auf der Stelle drehen für das Gameplay des Spiels unabdingbar schien und das weitere limitieren der Rotationswinkel außer Frage stand, haben wir eine Funktion geschrieben, welche in Grenzfällen versucht, eine optimale Kameraposition zu finden.

function cameraWatcher (){       //Wird jeden Frame aufgerufen
  if(camon == true){             //Nur falls nicht in Animation
    if(!isFirstPerson){          //Nur falls ThirdPerson da FPCam == Static
      if(lat> 57 && yAxis ==0){
        camera.setTarget('fTarget'); 
            //Bei Stillstand und zu niedrigem Blickwinkel, setze Kamera auf Ausweichposition
      }else if(lat>65 &&yAxis ==-1){
        camera.setTarget ('fTarget');
            //Bei langsamen fliegen, setze Kamera auf Ausweichposition
      }else {
        camera.setTarget('Target');
            //Bei normalen fliegen, setze Kamera auf Standardposition.
      }    
    }
  }
}

Der "Special-Move"

Als das Movement an sich fertig war, wollten wir unbedingt noch eine aufwändigere Ausweichfunktion einbauen. Diese ist dann in Form einer Aufwärtsrolle realisiert worden. Die größte Herausforderung hierbei lag darin, eine geeignete Methode zu finden, das Schiff für die Animation neu auszurichten, ohne dass die Mausbewegung nachher beeinflusst wird. Dies haben wir nun dadurch erreicht, dass wir die Animation durch simulierte Mausbewegungen Frame für Frame vorgegeben haben. Während der Animation ist der Mousemove Listener abgeschaltet, was jedoch nicht den Pointerlock aufhebt. Somit kann der Spieler direkt nach Abschluss der Animation wie gewohnt weiterfliegen.

function animation(){    //Wird ab Tastendruck jeden Frame aufgerufen bis zur Vollendung der Animation        
            camon = false;//Deaktiviert Mauswinkelkontrolle
            camera.setTarget('animation')//Setzt neue statische Kamera
            document.removeEventListener('mousemove', moveCallback, false);//Entfernt Maus-Listener

            if(aniframe<=35){
                yAxis=-6;
                ship.translateY(-5);
                aniframe++;
                lat -=5;
            }//Drehe senkrecht und steig nach oben

            if(aniframe <= 80)
                {
                    aniframe++;
                    lon +=15;
                }//Drehe um die eigene Achse

            setTimeout(function(){
                aniframe--;
                lat+=1;//Level wieder Aus
                document.addEventListener('mousemove', moveCallback, false);                
                                     }
                , 2300);            

            setTimeout(function(){
                doanimate=false;                
                camera.setTarget('Target');
                camon = true;//Stelle Normalzustand wieder her
                aniframe=0                
                    ;},2000);                    
            }
            player.updateSpaceshipAnimation();
}

results matching ""

    No results matching ""