Overlays

Sebastian Stock

Auf der Karte sollen die für den Spieler relevanten positionsbezogenen Informationen, also Tresore, Geschenke, andere Spieler, das Spielfeld mit seiner Unterteilung in einzelne Gebiete und natürlich auch der Benutzer selbst, angezeigt werden . Dies geschieht mittels sogenannter Overlays, welche von der Klasse com.google.android.maps.Overlay aus der GoogleMaps API abgeleitet und zu der MapView hinzugefügt werden. Für jede Art von Gegenstand wurde dabei ein eigenes Overlay implementiert. So gibt es beispielsweise ein SafeOverlay zur Darstellung der Tresore und ein PlayerOverlay zur Anzeige der anderen Spieler. Die MapView verfügt über eine Liste ihre Overlays. Diese erhält man mit MapView.getOverlays() und zu ihr können einfach die zusätzlichen Overlays hinzugefügt werden. Diese werden dabei als Ebenen übereinander angeordnet und können sich daher auch gegenseitig überdecken.

Anzeigen

Overlays haben eine draw-Methode, die bei jedem Zeichnen der MapView aufgerufen wird. Das ist beispielsweise beim Zoomen oder Bewegen der Karte der Fall. Die notwendigen Schritte zum Zeichnen sind für die meisten Overlays ähnlich: Gegeben ist eine Liste der zu zeichnenden Gegenstände, welche als Attribut auch ihre GPS-Koordinaten enthalten. Diese GPS-Koordinaten müssen jeweils in Bildschirm-Pixel umgerechnet werden, was mit Hilfe der Klasse com.google.android.maps.Projection aus der GoogleMaps API geschieht. So erhält man die Positionen auf dem Bildschirm, an denen die Gegenstände eingezeichnet werden müssen. Die draw-Methode verfügt über ein Canvas, auf der direkt einfache 2D-Objekte oder Bitmaps gezeichnet werden können. Für die eigene Position, die Tresore, die Geschenke und die anderen Spieler werden Icons im png-Format benutzt. Diese können allerdings nicht direkt eingezeichnet werden, sondern müssen an die aktuelle Zoomstufe angepasst werden. Andernfalls sähe man bei starkem Herauszoomen auf dem Spielfeld nur noch eine Ansammlung von Icons, aber nicht mehr die zugrunde liegende Karte oder beim Hineinzoomen wären die Icons zu klein. Daher werden sie im Konstruktor einmalig als Bitmap geladen und in der draw-Methode passend zur aktuellen Zoomstufe skaliert.

Der folgende Programmcode zeigt exemplarisch die draw-Methode des PositionOverlay. Dort wird zunächst die Größe des Bildes in Abhängigkeit von der Zoomstufe berechnet, wobei ein Minimal- und Maximalwert von 8 bzw. 128 Pixeln nicht überschritten werden sollte. Mit der draw-Methode des Canvas wird das Bild in ein Rechteck um den zur GPS-Koordinate korrespondierenden Bildschirmpunkt gezeichnet, wodurch gleichzeitig auch die Skalierung vorgenommen wird. Der Rückgabewert false bewirkt, dass das Overlay nur dann neu gezeichnet wird, wenn dieses durch erneutes Zeichnen der MapView auch wirklich notwendig ist. Mit true würde das Overlay ständig neu gezeichnet werden, was durch die große Anzahl dargestellter Objekte zu Geschwindigkeitseinbußen führen könnte.

public boolean draw(Canvas canvas, MapView mapView, boolean shadow, long when) {
	super.draw(canvas, mapView, shadow, when);
 
	int zoom = mapView.getZoomLevel();
	float size = (float) (Math.exp(0.015 * zoom * zoom - 0.185 * zoom + 1.64));
	if(size < 4.0) {
		size = 4.0f;
	} else if(size > 64.0) {
		size = 64.0f;
	}
 
        // Transform position to pixels
        Point screenPointPosition = new Point();
        GeoPoint currentPosition = positionModel.getPosition().asAndroidFormat();
        Projection projection = mapView.getProjection();
	projection.toPixels(currentPosition, screenPointPosition);
        // Rectangle to scale bitmap 
	RectF rect = new RectF(screenPointPosition.x - size, screenPointPosition.y - size, 
                               screenPointPosition.x + size, screenPointPosition.y + size);
	canvas.drawBitmap(bmpPlayer, null, rect, null); 	
	return false;
}

Sechs Overlays wurden implementiert:

  • PositionOverlay : Zeichnet die Position des Spielers als grauen Punkt ein.
  • PlayerOverlay : Zeigt alle anderen sichtbaren Spieler als Polizisten bzw. Diebe an.
  • SafeOverlay: Zeigt die für den Benutzer sichtbaren Tresore an.
  • GiftOverlay : Zeichnet die Geschenke ein.
  • FogOverlay : Dieses Overlay zeichnet die Spielfeldbegrenzung als rote Linie ein und verdunkelt nicht sichtbare Gebiete des Spielfeldes, die der Benutzer erst noch aufdecken muss.
  • FakepositionTapOverlay : Zeigt nichts auf der Karte an, sondern dient zu Demonstrationszwecken lediglich dazu, dem Benutzer bei deaktiviertem GPS-Sensor durch langes Drücken auf die Karte einen Wechsel zu der so ausgewählten Geo-Position zu ermöglichen.

In den folgenden Bildern der Kartenansicht lassen sich die Overlays und der Einfluss der Zoomstufen auf die Größe der gezeichneten Bilder gut erkennen:

Abb. 1: Overlays bei geringer Zoomstufe

Abb. 2: Overlays bei mittlerer Zoomstufe

Abb. 3: Overlays bei hoher Zoomstufe

Wenn sich etwas an den zugrunde liegenden Daten in den Modellen etwas ändert, also beispielsweise neue Tresore hinzugekommen sind oder der Benutzer ein bisher verdecktes Gebiet betreten hat und dieses nun aufgedeckt wird, müssen auch die entsprechenden Overlays neu gezeichnet werden. Wie im Kapitel Datenhaltung beschrieben, wird das Observer-Observable-Pattern verwendet. Die GameActivity, welche auch die MapView enthält, implementiert dazu das Interface Observer und registriert sich an den einzelnen Modellen. Liegen in diesen Änderungen vor, wird die auf Basis des Observer-Interface implementierte update-Methode aufgerufen. Dort wird zunächst geprüft, wo die Änderungen vorlagen und anschließend die Daten der relevanten Overlays aktualisiert und die MapView mit deren postInvalidate()-Methode zum erneuten Zeichnen veranlasst.

Tap Events

Wenn der Benutzer auf die angezeigten Objekte klickt werden noch weitere Informationen zu diesen angezeigt. Dies kann in Overlays durch Überschreiben der Methode boolean onTap(GeoPoint p, MapView mapView) erreicht werden. Die Overlays sind als Ebenen über der MapView angeordnet. Bei jedem auf der MapView ausgelösten Tap Event werden, beginnend mit dem obersten Overlay, deren onTap-Methoden aufgerufen, bis dieses Event von einem der Overlays behandelt wird, was durch den Rückgabewert true signalisiert wird. Die Overlays müssen dafür in der onTap-Methode zunächst ermitteln ob dieses Tap Event für sie relevant ist und behandelt werden kann. Dafür ist der übergebene GeoPoint nützlich. Der GeoPoint repräsentiert die bereits in Geo-Koordinaten umgerechnete Position derjenigen Stelle auf dem Bildschirm, an der der Benutzer den Tap Event ausgelöst hat. onTap wurde im PositionOverlay, PlayerOverlay, SafeOverlay und GiftOverlay überschrieben werden. In allen vier Fällen wird die Distanz des übergebenen GeoPoints zu den Geo-Koordinaten der den Overlays zugrunde liegenden Objekte berechnet. Ist diese Distanz hinreichend gering, werden je nach Overlay nähere Informationen zu dem betreffenden Objekt angezeigt und der Tap Event somit behandelt. Andernfalls wird false zurückgegeben und der Tap Event an die nächste Ebene weitergereicht. Beim PositionOverlay muss lediglich mit der Geo-Position des Benutzers verglichen und gegebenenfalls dessen Längen- und Breitengrad ausgeben werden. Beim PlayerOverlay, SafeOVerlay und GiftOverlay muss hingegen jeweils eine ganze Liste von Objekten durchlaufen und gegebenenfalls dasjenige mit dem geringsten Abstand verwendet werden. Hier ist zu beachten, dass nur diejenigen Objekte berücksichtigt werden, die auch für den Benutzer sichtbar sind. Ansonsten könnte man wahllos auf die Karte klicken um zu schauen, ob sich dort ein Gegenstand befindet. Für Tresore wird dessen Wert und die direkte Distanz des Benutzers zu diesem ausgegeben, für Geschenke hingegen nur die Distanz, da nicht verraten werden soll welcher Gegenstand sich in diesem befindet. Beim Anklicken eines anderen Spielers wird schließlich dessen Name und Punktanzahl angezeigt. Die Anzeige geschieht mit einem Toast. Dies ist auf Android ein kleines Textfeld, das für eine kurze Zeit eingeblendet wird und automatisch wieder verschwindet. Auf den folgenden beiden Bildern wird auf der Karte jeweils nach dem Anklicken eines Tresors bzw. eines Spielers ein Toast angezeigt:

Abb. 4: Nach dem Anklicken eines Tresors angezeigter Toast, mit dem Wert des Tresors und der Distanz des Benutzers zu diesem.

Abb. 5: Nach dem Anklicken eines Spielers angezeigter Toast, mit dem Namen des Spielers und dessen Punktanzahl

Ein Sonderfall ist das FakepositionTapOverlay, welches nicht dazu dient Gegenstände auf der Karte anzuzeigen. Stattdessen kann der Spieler, wenn der GPS-Sensor deaktiviert ist, mit diesem seine Position im Spiel durch einen langen Druck auf der Karte zu einer gewünschten Position bewegen. Diese Funktion wurde allerdings nur für Demonstrationszwecke vorgesehen und sollte vor der Auslieferung des Spiels an Kunden deaktiviert werden. Während normale Tap Events bereits eigenständig von der MapView erkannt werden und für die Behandlung lediglich die onTap-Methode in den Overlays überschrieben werden muss, gestaltet sich das Erkennen eines langen Drückens etwas schwieriger. Es muss zunächst die Methode boolean onTouchEvent(android.view.MotionEvent event, MapView mapView) überschrieben werden, welche bei jeder Berührung der MapView aufgerufen wird. Eine Berührung des Bildschirms löst eine Reihe von MotionEvents aus. So sind das Herunterdrücken, Bewegen und Hochnehmen des Fingers einzelne oder im Falle von Bewegungen sogar mehrere MotionEvents. Ein Objekt der Klasse MotionEvent enthält unter anderem Informationen zur Art der Aktion, wie z.B. MotionEvent.ACTION_DOWN oder MotionEvent.ACTION_MOVE, und an welcher Position am Bildschirm der MotionEvent ausgelöst wurde.

Gesten sind bestimmte Sequenzen von MotionEvents. Hierunter fällt auch das lange Drücken, welches erkannt werden soll. Einfache Gesten können mit einem GestureDetector erkannt werden. Um auf die erkannten Gesten zu reagieren, muss diesen im Konstruktor ein Listener übergeben werden, der das Interface GestureDetector.OnGestureListener implementiert. Da nur ein langes Drücken erkannt werden soll genügt es in diesem Falle die Klasse GestureDetector.SimpleOnGestureListener zu erweitern und die Methode onLongPress(MotionEvent e) zu überschreiben. In dieser Methode wird der Benutzer nun gefragt, ob er sich zu der gewählten Geo-Position teleportieren möchte, wodurch ein unbeabsichtigter Positionswechsel verhindert wird. Schließlich wird die neue Position im PositionModel gesetzt und an den Server gesendet.


Page last modified on March 21, 2011, at 06:16 PM