EInfache, aber effektive Kollisionsabfrage für Tilebased Jump and Run gesucht

Marius Heil

Erfahrenes Mitglied
Hallo Leute,

ich programmiere gerade an einem Jump and Run in Flash, die Kollisionsabfrage bereitet mir allerdings derzeit Probleme.
Derzeit wird berechnet ob sich einer von den 4 Eckpunkten der Figur in ein anderes Teil bewegt, das Tile wird dann angefragt ob es aus dieser Richtung begehbar ist, wenn nein weiß ich, dass eine
Kollision stattgefunden hat und muss den Character in die richtige Richtung an die Wand setzen. Bisher hat es nicht gescheit geklappt.
Hat jemand eine 100%ig funktionierende Kollisionsabfrage für ein tile-based Spiel? Ohne Dinge wie hitTest und so natürlich, das braucht es ja nicht. Ob
eine Kollision stattfindet kann man ja durch ne einfache Division durch die Teilchengröße bestimmen wenn man das Ergebnis in nem Integer speichert.
Tutorials hab ich nichts dolles gefunden, tonypa ist nicht das was ich gesucht habe, es gibt noch eins für C, das finde ich aber auch seltsam.
 
Hi,

ich kann mal den betreffenden Teil posten, das ganze ist AS3 und auf mehrere Klassen aufgeteilt.

In der Character.as werden jedes Frame die Physikberechnung und danach die Kollisionsberechnung aufgerufen
PHP:
public function ckeckCollision():void{
			//Recalculate Material under new Position
			calculateMaterial();
			//Check if x moved into a Material which is blocked in this direction (same for Y-direction)
			movementX = xAreColliding();
			movementY = yAreColliding();
			var xCollision:Boolean;
			var yCollision:Boolean;
			
			//Collision has to be checked, cause the character has moved into another Tile
			if(movementX != 0 || movementY != 0){
				trace("movement", movementX, movementY);
				
				//Nach Rechts
				if(movementX > 0){
					//Checkpoints nach Links versetzen und prüfen
					xCollision = checkAt(-1, 0);
				//Nach Links
				} else if(movementX < 0) {
					//Checkpoints nach Rechts versetzen
					xCollision = checkAt(1, 0);
					trace("xColl", xCollision);
				}
				//Nach Unten
				if(movementY > 0){
					//Checkpoints nach Oben versetzen
					yCollision = checkAt(0, -1);
					trace("yColl", yCollision);
				//Nach Oben
				} else if(movementY < 0) {
					//Checkpoints nach Unten versetzen
					yCollision = checkAt(0, 1);
				}
				
				//Case 1: sonderfall
				if(xCollision && yCollision){
					trace("critical")
					handleCollision(movementX, movementY);
				}
				//Case 2: only yCollision handled
				else if(xCollision && !yCollision){
					handleCollision(0, movementY);
				}
				//Case 3: only xCollision handled
				else if(!xCollision && yCollision){
					handleCollision(movementX, 0);
				}
				//Case 4: beide müssen
				else if(!xCollision && !yCollision){
					handleCollision(movementX, movementY);
				}
			}
		}
		/*
		 * Position checkpoints and character according to Parameters based on Collision detection
		 * @param x/yReposition (-1, 0, 1)
		 */
		public function handleCollision(xReposition:int, yReposition:int):void {
			trace(xReposition, yReposition);
			//Reposition x
			if(xReposition < 0){
				trace("left collision");
				x = world.roundCrd(x, 0);
				xAcceleration=0;
			} else if(xReposition > 0){
				trace("right collision");
				x = world.roundCrd(x, 1)-width-1;
				xAcceleration=0;
			}
			//Same for y
			if(yReposition < 0){
				trace("ceiling collision");
				y = world.roundCrd(y, 0);
				yAcceleration=0;
			} else if(yReposition > 0){
				trace("bottom collision");
				y = world.roundCrd(y, 1)-height-1;
				yAcceleration=0;
			}
			//Finally move the Checkpoints at Character Position
			moveCheckpoints(x, y);
		}

Außerdem gibt es noch ein paar Methoden die die Checkpunkte verwalten:
PHP:
/*
		 * Factory Method to create the Checkpoints
		 * @param x/y Character position
		 * @param width/height Character dimensions
		 */
		public function newCheckpoints(x:int, y:int, width:int, height:int):void {
			checkpoints[0] = new Checkpoint(x, y);					//Oben links
			checkpoints[1] = new Checkpoint(x + width, y);			//Oben rechts
			checkpoints[2] = new Checkpoint(x, y + height, true);			//Unten links + checkBottom
			checkpoints[3] = new Checkpoint(x + width, y + height, true);	//Unten rechts + checkBottom
		}
		/*
		 * sdf
		 */
		public function checkAt(offsetX:int, offsetY:int):Boolean{
			//Move all Checkpoints
			if (checkpoints[0].checkAt(offsetX, offsetY)) return true;
			if (checkpoints[1].checkAt(offsetX, offsetY)) return true;
			if (checkpoints[2].checkAt(offsetX, offsetY)) return true;
			if (checkpoints[3].checkAt(offsetX, offsetY)) return true;
			
			return false;
		}
		/*
		 * Moves all Checkpoints to a specified position
		 * @param x/y top-left Character x/y-Coordinate
		 * @param asTileCoord are x and y full Coordinates or divided by TileWidth/Height
		 * @param temporary Only temporary Movement for checking
		 */
		public function moveCheckpoints(x:int, y:int):void{
			//Move all Checkpoints
			checkpoints[0].moveTo(x, y);					//Oben links
			checkpoints[1].moveTo(x + width, y);			//Oben rechts
			checkpoints[2].moveTo(x, y + height);			//Unten links
			checkpoints[3].moveTo(x + width, y + height);	//Unten rechts
		}
/*
		 * Checks for every checkpoint for x Collision
		 * @return Direction of Movement (-1,0,1)
		 */
		public function xAreColliding():int{
			for (var i:int=0; i<4; i++) {
				if (checkpoints[i].xIsColliding()) return checkpoints[i].xIsColliding();
			}
			return 0;
		}
		/*
		 * Checks for every checkpoint for y Collision
		 * @return Direction of Movement (-1,0,1)
		 */
		public function yAreColliding():int{
			for (var i:int=0; i<4; i++) {
				if (checkpoints[i].yIsColliding()) return checkpoints[i].yIsColliding();
			}
			return 0;
		}

Die Checkpoint.as sieht so aus:
PHP:
package de.mariusheil.jump.world {

	/**
	 * @author Marius Heil
	 */
	public class Checkpoint {
		public var checkBottom:Boolean;
		public var x:Number;
		public var y:Number;
		public var xBackup:Number;
		public var yBackup:Number;
		public var X:int;
		public var Y:int;
		public var bottomY:int;
		public var tempX:int;
		public var tempY:int;
		public var xMovement:int;
		public var yMovement:int;
		public var currentMaterial:Material;
		public var onBottom:Boolean;
		
		/*
		 * Creates a Checkpoint at specific Coordinates
		 * @param x/y The initial Coordinates
		 * @param checkBottom if set to true this Checkpoint will look for bottom 1 pixel below
		 */
		public function Checkpoint(x:int, y:int, checkBottom:Boolean = false){
			//x+y Coordinates
			this.x = x;
			this.y = y;
			//Use this Checkpoint to check for Bottom Material
			this.checkBottom = checkBottom;
			//X and Y Tile in Worldmap + bottomY (1px under Point)
			this.X = x / World.tileWidth;
			this.Y = y / World.tileHeight;
			if(this.checkBottom) this.bottomY = (y+1) / World.tileHeight;
			//Movement in Tiles (-1; 0; 1)
			this.xMovement = 0;
			this.yMovement = 0;
			getMaterial();
		}
		/*
		 * Moves the Checkpoint to a specific Coordinate
		 * @param x/y new Position
		 * @param temporary Set to true if you just want to calculate how the Movement would be
		 */
		public function moveTo(x:int, y:int):void{
			//Calc Position in Tile-grid
			tempX = x / World.tileWidth;
			tempY = y / World.tileHeight;
			bottomY = (y+1) / World.tileHeight;
			//Set new Position
			this.x = x;
			this.y = y;
			//Calculate Movement in tile-grid
			xMovement = tempX - X;
			yMovement = tempY - Y;
			//Save new tile-grid Position
			X = tempX;
			Y = tempY;
			//Get Material from new Position to check for constraints and Bottom
			getMaterial();
		}
		public function checkAt(offsetX:int, offsetY:int):Boolean{
			var gridX:int = (x / World.tileWidth) + offsetX;
			var gridY:int = (y / World.tileHeight) + offsetY;
			
			if(xMovement < 0){
				if(World.tileMap[gridX][gridY].material.blockedRight) return true;
			} else if(xMovement > 0){
				if(World.tileMap[gridX][gridY].material.blockedLeft) return true;
			}
			if(yMovement < 0){
				if(World.tileMap[gridX][gridY].material.blockedBottom) return true;
			} else if(yMovement > 0){
				if(World.tileMap[gridX][gridY].material.blockedTop) return true;
			}			
			
			return false;
		}
		
		/* Fetches Material at Checkpoint Position */
		public function getMaterial():void {
			//Save Reference to tileMaterial
			currentMaterial = World.tileMap[X][Y].material;
			//Boden prüfen
			if(checkBottom && World.tileMap[X][bottomY].material.isBottom) onBottom = true;
				else onBottom = false;
		}
		/* Checks if y-Movement is Colliding based on Movement and Material Constraints */
		public function yIsColliding():int{
			if (yMovement < 0 && currentMaterial.blockedBottom) return -1;
			else if (yMovement > 0 && currentMaterial.blockedTop) return 1;
			else return 0;
		}
		/* Checks if x-Movement is Colliding based on Movement and Material Constraints */
		public function xIsColliding():int{
			if (xMovement < 0 && currentMaterial.blockedRight) return -1;
			else if (xMovement > 0 && currentMaterial.blockedLeft) return 1;
			else return 0;
		}
	}
}

Ich glaube, dass der Fehler beim Kollisionshandling und nicht bei der Abfrage liegt.
Bei der Kollisionsabfrage mache ich zB sowas:
var gridX:int = (x / World.tileWidth) + offsetX;
x / tileWidht gibt mir da es in einem Integer gespeichert wird den Index vom tileArray an den ich dann nur noch nach dem Material fragen muss.
offsetX ist in dem Fall zB -1, 0 oder 1, damit ich schauen kann ob der Character ein Feld weiter immer noch kollidieren würde.

Das Tutorial schau ich grad mal durch, weiß allerdings nciht ob es mir weiterhilft.

----
Habs mir angeschaut, das hilft mir allerdings nicht groß, in meinem Fall liegt das Problem wohl eher beim Handling als bei der Abfrage selbst.
 
Zuletzt bearbeitet:
Marius - was genau verstehst du unter einem Checkpoint, und was machen die Variabel tempX, tempY, X, Y?
 
Ein Checkpoint ist ne Instanz der Klasse Checkpoint, man weist einem Checkpoint einen x und y Wert zu und hat dann die Möglichkeit diese überprüfen zu lassen ob er in ein anderes Tile gewechselt ist. AUßerdem kann man sagen dass er auf Bodenkollision überprüfen soll, das geschieht einen Pixel unter dem wirklichen Checkpoint. Im wesentlichen ist es ein Punkt den man verschieben kann und den ich für Kollisionsabfragen benutze.
Wenn X oder Y großgeschrieben sind, handelt es sich um die Koordinaten im Tileraster. Wenn zB x = 65 und die Teilchengröße 30 ist, dann ist X = 3.
die temp-Variablen sind eigentlich unnötig, die werden nur für die Berechnung gebraucht, sind noch Überbleibsel aus vorherigen versuchen, die werd ich rauswerfen.
 
Zurück