Autor Beitrag
knittel
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 71
Erhaltene Danke: 2

Win XP, Win7, openSUSE
Delphi 7
BeitragVerfasst: Di 09.07.13 16:35 
Hallo allerseits,

In meinem Spiel soll sich der Charakter durch eine Welt bewegen können. Seine Bewegungsrichtung ist komplett beliebig, d.h. er kann sich nicht nach Nord/Nord-Ost usw. bewegen sondern auch in einem z.B. 13° Winkel. In dem Spiel gibt es Wände mit denen der Charakter kollidieren kann. Und wenn der Spieler den Charakter auf eine Wand zu steuert, dann soll die beiden kollidieren und der Charakter soll sich nur noch in die Richtung bewegen die Parallel zum Verlauf der Wand ist (sowie man es aus allen Spielen kennt).

Zuerst hatte ich das nur für Wände gemacht die parallel zur X oder Y-Achse verliefen, aber nun mit dem Dazukommen von schrägen Wänden, hab ich so meine Probleme.

Das ist mein Kollisionscode, der erstmal nur prüft ob der Spieler mit der Wand kollidiert:

Variablen Erklärung:
nPos: Position des Spielers (vom Typ TMeleeCoord, besteht aus x, y (float) Koordinate);
nMov: Bewegungsvektor des Spielers
nSize: Radius der Kollisionsgröße des Spielers.

FLine.A: Startpunkt der Wand.
FLine.B: Endpunkt der Wand.
fv: Vektor von Start zu Endpunkt der Wand.
nv: Bewegungsvektor des Spielers
fvn: normalisiert(fv)
nvn: normalisiert(nv)

rN: Zahlen Teil von r (r = rN + rS * s)
rS: s Teil von r (r = rN + rS * s)
sN: Zahlen von s
hitlength: Verlängerungsmaß des Richtungsvektor um zu verhindern, dass wenn man schräg auf eine Wand zu geht, man dichter an die Wand gehen kann, als eigentlich von Kreisradius möglich wäre.

ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
function TMeColliderLine.IsColliding(const nPos, nMov: TMeleeCoord; nSize: single = 0): boolean;
var fv, nv, fvn, nvn: TMeleeCoord; q: boolean; rN, rS, sN, hitlength: single; 
begin
result := false;
// Zunächst eine einfach Box-Kollisionsabfrage um Rechenzeit zu sparen. 
if (Abs(nPos.X - FLine.A.X) > ABS(FLine.B.X - FLine.A.X) + ABS(nMov.X)) or
   (Abs(nPos.Y - FLine.A.Y) > ABS(FLine.B.Y - FLine.A.Y) + ABS(nMov.Y)) then exit;
fv := meCalculate(FLine.B, me_Substract, FLine.A);
nv := nMov;
// Bei Parallelen Vektoren besteht keine Kollisionsgefahr und außerdem wäre das Gleichungssysstem nicht lösbar.
q := meIsParallel(fv, nv);
if q then exit;
// Solve Linear Equations
if fv.X <> 0 then // Avoid Division through Zero
  begin
  // Get R of FLine:
  rN := (nPos.X - FLine.A.X) / fv.X; // Number Part of R
  rS := (nv.X) / fv.X; // S Part of R
  // Get S of nLine:
  sN := (FLine.A.Y + fv.Y * rN - nPos.Y) / (nv.Y - fv.Y * rS); // Get S
  rN := rN + rS * sN; // Set S into formula of R
  end
else
  begin
  // Get R of FLine:
  rN := (nPos.Y - FLine.A.Y) / fv.Y; // Number Part of R
  rS := (nv.Y) / fv.Y; // S Part of R
  // Get S of nLine:
  sN := (FLine.A.X + fv.X * rN - nPos.X) / (nv.X - fv.X * rS);
  rN := rN + rS * sN;
  end;
// Hitlength berechnen
hitlength := abs(nSize / meLength(nv) * tan(90 - arccos(meDot(meNormalize(fv), meNormalize(nv)))));
if (rN > 0 - nSize / meLength(fv)) and (rN < 1 + nSize / meLength(fv)) and
   (sN > 0 - hitlength) and (sN < 1 + hitlength) then begin
   result := true; 
   FHitPoint := meCalculate(FLine.A, me_Add, fv); // Punkt auf dem der Spieler auf die Wand trifft.
   end;
end;


Bei einer Kollision wird Folgendes noch ausgeführt:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
var qVec: TMeleeCoord; x: single;
begin
// This function assumes that nLine and FLine collide.
// qVec ist der Vektor der Orthogonal zum Richtungsvektor der Wand ist.
qVec := meNormalize(meCalculate(FLine.B, me_Substract, FLine.A));
x := qVec.X;
qVec.X := qVec.Y;
qVec.Y := -x;
// der Teil von nMov (Bewegungsrichtung) der orthogonal zur Wand ist soll entfernt werden.
qVec := meCalculate(qVec, me_Multiply, meLength(nMov) * meDot(qVec, meNormalize(nMov)));
nPos := meCalculate(npos, me_Add,
          meCalculate(meNormalize(qVec), me_Multiply, ));     
nMov := meCalculate(nMov, me_Substract, qVec);
end;


Der Berechnungsteil ob sich die Geraden schneiden funktioniert, nur irgendwie kommt man zu Nahe an die Wand heran und stuckt in ihr. Manchmal kann man sich auch irgendwie durch sie hindurch bewegen, wenn man lange genug wild rumprobiert.

Wenn ihr eine andere Idee habt wie man das implementieren könnte so, dass es performant und "korrekt genug" ist immer her damit :D (es soll sich halt flüssig anfühlen). Ihr könnt aber auch Veränderungen an meinem Code vorschlagen, die weiterhelfen würden. Danke auf jeden Fall schonmal!

_________________
"Wir können nicht fliehen!" "Wieso nicht?" "Sie haben mir die Schnürsenkel zusammengebunden!" "Die Schweine."
Mr_Emre_D
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 114
Erhaltene Danke: 14



BeitragVerfasst: Di 09.07.13 17:09 
Also sofern es sich bei deinen Objekten nicht um konvexe Polygone handelt, lässt sich das ganze über das SAT-Theorem lösen - Seperating Axis Theorem:
Sofern man bei der Projektion aller Punkte auf alle Geradennormale beider Polygone einen Zustand vorfindet, wo die projezierten maximalen "Geraden" einander nicht schneiden, so kollidieren beide Objekte auch nicht.
Evt schwer zu verdauen, aber hier wirds verständlicher erklärt.
Vorallem diese zwei Bilder 1 - 2

Moderiert von user profile iconNarses: Beiträge zusammengefasst

Damit stellste zunächst einmal fest, obs zu einer Kollision kommt (davor würd ich aber das ganze über Quadtrees optimieren und nen gröberen Kollisionscheck über sog. Bounding Boxes machen - jene Objekte/Polygone, die den Test nicht bestehen, werden näher untersucht (eben mit SAT).
Nachdem du per SAT festgestellt hast, dass sie kollidieren, müsstest du über Geradenschnitt alle Geraden miteinander schneiden um den Schnittpunkt zu bestimmen (hier gibts 2 Fälle: 1 Schnitt oder mehrere Schnittpunkte)
Wenn du dann den Punkt hast, kannste bel. damit anstellen. Meistens reichen aber auch beide Geraden (Sprich 2 Eckpunkte der beiden kollidierenden Objekte) auch aus (normalen Bestimmung usw)

Edit: Verdammt, hab mich selbst zitiert, anstelle meine Beitrag zu editieren =/

Für diesen Beitrag haben gedankt: knittel