JirkaDellOro / FUDGE

Furtwangen University Didactic Game Editor
https://jirkadelloro.github.io/FUDGE
MIT License
33 stars 27 forks source link

Rigidbody Physics Type durch Kollision ändern #264

Closed luca1107 closed 3 years ago

luca1107 commented 3 years ago

@JirkaDellOro @Dev-MarkoF Hallo ihr zwei, ich habe eine kleines Problem mit der Rigidbody Komponente, vlt liegt es auch am Code und nicht an der Komponente selbst.

export class Einzelgeometrie extends ƒ.Node { static mesh: ƒ.Mesh = new ƒ.MeshCube("Cube"); static material: ƒ.Material = new ƒ.Material("Black", ƒ.ShaderUniColor, new ƒ.CoatColored(new ƒ.Color(0, 0, 1, 1))); rigidbody: ƒ.ComponentRigidbody = new ƒ.ComponentRigidbody(1, ƒ.PHYSICS_TYPE.KINEMATIC, ƒ.COLLIDER_TYPE.CUBE, ƒ.PHYSICS_GROUP.DEFAULT); // Hier erzeuge ich den Rigidbody, erstmal Kinematic, sodass er von der Physik unberührt bleibt, bis er mit einem Objekt kollidiert direction: boolean ;

constructor(_name: string, _pos: ƒ.Vector3, _scale: ƒ.Vector3, _dir: boolean ) {
  super(_name);

  this.addComponent(new ƒ.ComponentTransform());
  this.mtxLocal.translateX(_pos.x);
  this.mtxLocal.translateY(_pos.y);
  this.mtxLocal.translateZ(_pos.z);

  let cmpMesh: ƒ.ComponentMesh = new ƒ.ComponentMesh(Einzelgeometrie.mesh);
  cmpMesh.mtxPivot.scaleX(_scale.x);
  cmpMesh.mtxPivot.scaleY(_scale.y);
  cmpMesh.mtxPivot.scaleZ(_scale.z);

  this.direction = _dir;

  this.addComponent(cmpMesh);

  this.addComponent(new ƒ.ComponentMaterial(Einzelgeometrie.material));
  this.addComponent(this.rigidbody);
  **this.rigidbody.addEventListener(ƒ.EVENT_PHYSICS.COLLISION_ENTER, this.activatePhysics);**  _//Hier setze ich den Event Listener für die Kollision_ 
}

public setTransform(_pos: ƒ.Vector3, _rot: ƒ.Vector3): void {
  this.mtxLocal.translateX(_pos.x);
  this.mtxLocal.translateY(_pos.y);
  this.mtxLocal.translateZ(_pos.z);

  this.mtxLocal.rotateX(_rot.x);
  this.mtxLocal.rotateY(_rot.y);
  this.mtxLocal.rotateZ(_rot.z);

}

public move(): void {
  if (!this.direction)
  this.mtxLocal.translateX( - 1 / 4 * ƒ.Loop.timeFrameReal / 1000);
  else
  this.mtxLocal.translateX(  1 / 4 * ƒ.Loop.timeFrameReal / 1000);
}

**private activatePhysics(_event: ƒ.EventPhysics): void {
  console.log("Col");
  this.rigidbody.physicsType = ƒ.PHYSICS_TYPE.DYNAMIC;
}**  _// Hier die Funktion zum ändern des Physic Types des Rigidbodys des Objektes bei welchem eine Kollision stattfindet_

} }

Die Kollisionsabfrage funktioniert und es wird in der Konsole eine Kollision ausgegeben, Fehlermeldung gibt es auch keine, jedoch passiert nichts und der Rigidbody bleibt weiterhin Kinematic

LG Luca

JirkaDellOro commented 3 years ago

Poste bitte Links zu Quellcode auf Github und zur App auf Pages. Dann ist es für uns viel besser möglich das zu untersuchen

Dev-MarkoF commented 3 years ago

Code sieht eigentlich clean aus, wenn die Collision registriert wird, ist auch schonmal gut. Sehe intern auch kein Problem set physicsType macht vom Code her was es soll. Kurzer Test in dem ich per (einmaligem) Tastendruck einen Body von Kinematic auf Dynamic setze geht auch problemlos.

Ich muss Jirka da zustimmen, ich bräuchte den ganzen Source Code / Link zum Repository. Du könntest auch testen, ob es bei dir klappt, wenn du das nicht per Collision sondern per KeyPress versuchst, das würde mir mehr Anhaltspunkte geben, einfach per Keypress-Event die activatePhysics aufrufen (natürlich dann ohne EventPhysics Parameter), sonst nichts ändern.

Ich gehe davon aus, dass es maximal eine Timing Geschichte sein könnte, dass Oimo intern der Body gelocked ist, weil der sich in der Kollisionsberechnung befindet und deshalb nicht erlaubt deine Änderung zu übernehmen, ist aber unwahrscheinlich.

luca1107 commented 3 years ago

https://github.com/luca1107/Prima_Abgabe_360_Defender

Hier ist der Link zum aktuellen Repository.

Für Pages muss ich erst noch erstellen .

Alles klar Marko, ich versuche das heute mal zu testen und gebe nochmal bescheid :)

Liebe Grüße

luca1107 commented 3 years ago

Also ich habe es über Tastendruck versucht und jetzt funktioniert es 👯

Woran liegt es dann, dass es bei der Kollision nicht funktioniert, bzw. was muss ich ändern dass er die Rigidbody-Änderung wie bei der Tasteneingabe ändert ?

Liebe Grüße

Dev-MarkoF commented 3 years ago

Es liegt nicht an der Physik, so viel kann ich schonmal sagen.

Habe kurzfristig, meine Tutorial 3 - Physical Events Szene umgebaut, so dass der Collider bei Berührung anstatt seiner bisherigen Funktion von Kinematic auf Dynamic springt und das funktioniert einwandfrei, also quasi derselbe Versuchsaufbau wie bei dir.

Da es mit Tasteneingabe bei dir auch funktioniert ist, das also schon mal safe.

EDIT: Also ich habe mal ganz kurz deinen Code gesichtet und denke, dass das eher ein Strukturelles Problem ist. Wenn du activatePhysics aus dem Body selbst aufrufst, dann ist this.rigidbody natürlich der Body selbst und dann wird das korrekt gesetzt. Im Falle von einem Aufruf über das Event, wird vom Event der Körper mit dem Kollidiert übergeben, aber nicht er selbst. Daher istthis.rigidbody nicht der erwartete Wert, weil this bereits der Rigidbody ist.

Das Problem zu beheben mag der typescript compiler nicht, der zeigt dir einen Fehler an, aber das ist Javascript Logik.

Weil das Objekt, dass das Event aufruft ist der Rigidbody nicht die Node, daher ist this, der Körper nicht die Node. Per Tastendruck, wenn du triggerPhysicsaufrufst und die ruft dann activatePhysicsauf, dann ruft die Nodedie Funktion auf, daher ist thisdann die "Einzelgeometrie" und dann funktioniert das.

Folgendes ändere die Zeile this.rigidbody.physicsType = ƒ.PHYISCS_TYPE.DYNAMIC; in this.physicsType = ƒ.PHYISCS_TYPE.DYNAMIC; und es wird funktionieren, trotz Fehleranzeige.

Fehler kriegst weg mit einem Kommentar drüber: // @ts-ignore Der "Fehler" wird angezeigt, weil typescript davon ausgeht, dass innerhalb einer Klasse .thissich immer auf die Klasse, also die Node bezieht, weil Javascript aber eine "gewachsene" Sprache ist, die Klassen erst so halb verstanden hat, ist .this Kontextsensitiv und der Kontext ist das EventTarget, was der ComponentRigidbody ist, weil typescript das nicht einsieht, muss man dem durch den Decorator Kommentar mitteilen, dass er falsch liegt.

Das Problem insgesamt ist, dass hier eine ungewöhnliche Logik dahintersteckt, die nicht für gutes Software/Gamedesign steht. Dein Körper registriert, wenn er von einer Kugel getroffen wird. Die eigentlich übliche Logik ist, dass die Kugel den Körper trifft.

In Kürze ist das schwer zu erklären, aber in komplexen Spielen ist es immer sinnvoller, wenn die Kugel eine Collision Abfrage hätte, die bei Kollision FunktionX auf dem getroffenen KörperX auslöst, sofern vorhanden. Da dein Körper jetzt mit allem kollidiert und immer die Funktion activatePhysics jetzt immer auslöst, wenn er etwas berührt, musst du jetzt in der Einzelgeometrie eigentlich noch eine Abfrage ausführen, mittels derer der PhysicsType nur geändert wird, wenn der Name des kollidierenden RB's "Kugel" ist (_event.rigidbodyComponent.getContainer().name == "Kugel"). Somit muss der Körper quasi wissen, von was er getroffen wird. Das musst du aber noch einbauen, sonst wird der Type jedes Mal gesetzt, wenn der RB kollidiert, egal mit was und das frisst performance.

Zweitens solltest du die Kugeln auch wieder despawnen, das frisst mit der Zeit auch performance, da könnte man auch mit Objektpooling arbeiten.

Aber nette Spielidee.

luca1107 commented 3 years ago

Hey Marko, super viele Danke für die ausführliche Erklärung und die Logik die dahinter steckt. Sehr interessant und gut zu wissen für die Zukunft, dass das so intern gehandhabt wird.

Ich werde deine Antwort mal in mein Projekt einbauen und melde mich nochmal ob soweit alles geklappt hat. Aber ich denke wird haben hiermit das Problem gefunden.

Nochmal vielen Dank und schönen Abend

LG Luca

luca1107 commented 3 years ago

Hey , ich habe noch ein kleines Problem damit.

Und zwar kann ich nicht direkt auf this.rigidbody referenzieren und muss noch davor nach .getComponent(ƒ.ComponentRigidbody) suchen. Hierbei bekomme ich den Fehler "xy is not a function" .

Habe es dann so versucht

this.rigidbody.addEventListener(ƒ.EVENT_PHYSICS.COLLISION_ENTER, this.triggerPhysics);

private triggerPhysics (): void { this.activatePhysics(); }

private activatePhysics(): void {
  console.log("Col");
  this.rigidbody.physicsType = ƒ.PHYSICS_TYPE.DYNAMIC;
}

Auch hier der Fehler

luca1107 commented 3 years ago

Ich habe die Kollision jetzt über die Kugel ausgelöst, jetzt funktioniert es.

Mich würde jetzt interessieren, wie es möglich ist , ein Rigidbody mitsammt dem Node welches des RB enthält zu löschen. Ich frage nach dem Namen des kollidierten Objektes, jedoch weiß ich nicht wie ich es löschen soll, da ich ja in einer seperaten Objektklasse die Kollision abrufe und somit noch auf den Wurzelknoten Zugriff habe.

LG

luca1107 commented 3 years ago

Ich bin mit der Kollision noch nicht so zufrieden, irgendwas läuft da noch nicht richtig, was ich nicht verstehe. Er schmeißt Errors beim RB, aber nur manchmal.

Könntest du dir es mal anschauen ?

Dev-MarkoF commented 3 years ago

Habs mir angeschaut, da kommt wieder verschiedenes Zusammen.

a) Du brauchst kein _event.cmpRigidbody.getContainer().getComponent(ƒ.ComponentRigibody). .... das Event enthält den getroffenen Body, also deine "enemy".

b) Die Fehler kommen, weil du versuchst den _event.cmpRigidbody.setVelocity()mit this.velocity zu füttern, was sich wieder auf die beschriebene Problematik bezieht, dass .this nicht der vermutete Wert "Kugeln" ist, auf dem die Property velocityliegt, sondern der Event Auslöser -> also die ComponentRigidbody. Daher ist this.velocity undefinedund der wirft dir den Error, weil er undefined nicht verarbeiten kann. Das ist aber auch gut, weil du die Class Property nur im Constructor verwendest, wo du einmal die Velocity vom Rigidbody setzt, danach solltest du aber die Geschwindigkeit vom Körper bekommen, weil this.velocity nur einmalig der Startwert ist, du schreibst da später nicht die momentane Geschwindigkeit des Körpers rein. -> this.getVelocity()ist die Funktion die Geschwindigkeit vom Körper am Ende der Kollision zu bekommen. Also wäre die Zeile _event.cmpRigidbody.setVelocity(this.getVelocity());

c) Dieser Ansatz ist aber auch physikalisch erstmal Falsch, den das getroffene Ziel bekommt ja nicht automatisch die Geschwindigkeit des Projektils, von dem es getroffenen wurde, Trägheit etc. spielen da noch eine Rolle. In deinem Game ist das oft sogar eine negative Geschwindigkeit, weil die Kugeln ja auch abprallen, also fliegen die Enemies dann auf dich zu, wenn sie einfach die Kugelgeschwindigkeit übernehmen.

Ich gehe davon aus, dass du sie "wegballern" möchtest. Logischerweise wendest du also den Impuls des Projektils auf den getroffenen Körper übertragen.

          _event.cmpRigidbody.applyImpulseAtPoint(new ƒ.Vector3
            (_event.collisionNormal.x * _event.normalImpulse, _event.collisionNormal.y * _event.normalImpulse,
              _event.collisionNormal.z * _event.normalImpulse), _event.collisionPoint);

-> Der getroffene Körper, bekommt den Impuls trefferNormale * trefferIntensität, angewendet am getroffenen Punkt. D.h. wenn du die obere Ecke eines Feindes anschießt, dann wird auch von genau da der Impuls ausgelöst. Vorsicht Impulse sind recht kräftig stark.

Edit: Falls du willst, dass die wirklich einfach nur mit der Initalgeschwindigkeit der Kugel, also deiner Velocity Property "wegfliegen", ist es entsprechend _event.cmpRigidbody.setVelocity(this.getContainer().velocity); Weil .this eben der Body ist und getContainer(), die Node / "Kugeln" in deinem Fall.

Wie man Körper mit Node löscht? Einfach die ganze Node löschen, dann wird auch der Körper mit gelöscht. Das Löschen geschieht, wenn das nicht zwischenzeitlich verändert wurde, durch das Entfernen der Node aus der Hierachie, also deinem Szenenroot.

luca1107 commented 3 years ago

Hey Marco erstmal vielen Dank für deine Antwort.

Ich habe noch das Problem, dass wenn ich die Node entferne aus ihrem Elternknoten, dass der Rigidbody an der Stelle weiterhin verbleibt, obwohl die Node aus der Szenenhierachie entfernt wurde.

LG

luca1107 commented 3 years ago

Die Impulsweitergabe an der jeweiligen Position funktioniert sehr gut

Dev-MarkoF commented 3 years ago

@luca1107 Danke guter Hinweis, es wird tatsächlich beim Remove der Node nicht die ComponentRigidbody Remove Funktion aufgerufen, diese Funktioniert zwar intern korrekt, aber da kommt kein Event von der Node, dass dieses Auslöst, das ist ein allg. Fudge Problem. Wenn du bevor du die Node entfernst, den RB noch entfernst, dann hat sich dieses Problem derzeit gelöst, das kann sogar sein, dass es Absicht ist und meine Physik Implementierung falsch ist.

Lösung derzeit:

_event.cmpRigidbody.getContainer().removeComponent(
_event.cmpRigidbody.getContainer().getComponent(ƒ.ComponentRigidbody));

gameRoot.removeChild( [...]

Es kann trotzdem zu Fehlern kommen, wie "can't get Contact of Null", weil wenn du zu schnell schießt versucht die Physik Collision Events zu berechnen, mit einem RB der bereits weg ist. Ich schau, dass ich da noch einen besseren Check in der Zukunft einbaue, momentan einfach ignorieren. Oder du gehst in die Compiled FudgeCore.js suchst nach getContact() Zeile 10734 z,B. und setzt, die Zeilen objHit = list.getContact()[...] auf objHit = list?.getContact()[...] selbiges für objHit2und collisionManifold, dass ist Typescript Kurzform für Nullcheck.

Deine Kugeln fliegen derzeit noch etwas komisch, du gibst denen immer einen Initialspeed in dieselbe Richtung, this.velocity = new ƒ.Vector3(_pos.x * 2 , _pos.y * 2 , .3); das solltest du noch anpassen, die sollten Velocity in die Richtung bekommen in der Player derzeit schaut. Dazu übergibst du der Kugel am besten die komplette WorldMatrix und Transformst damit einen Forward Vector, diesen Multiplizierst du mit der Schussstärke die du willst. Allerdings bin ich mir nicht ganz sicher wie dein Szenenaufbau aussieht, wer da welche Rotation etc. hat, das musst du selbst rausfinden.

@JirkaDellOroNode.removeChild(Node), ruft nicht das Event EVENT.COMPONENT_REMOVE in den Components der entfernten Node auf. Ist das gewollt? Physik und Audio Components sind die einzigen, die diese Funktion von Node derzeit selbst implementieren.

JirkaDellOro commented 3 years ago

EVENT.COMPONENT_REMOVE wäre auch das falsche Event, da ja nicht eine Komponente vom Knoten, sondern der Knoten von seinem Parent entfernt wird. Hier sollte CHILD_REMOVE gefeuert werden. Siehe https://jirkadelloro.github.io/FUDGE/Documentation/Reference/Core/enums/fudgecore.event.html#child_remove

Allerdings sollte man etwas vorsichtig sein Komponenten zu entfernen, wenn man den Knoten weiterverwenden will. Die Events kommen auch beim "Umhängen". Wenn dabei die Komponenten erhalten bleiben sollen, muss man etwas mehr Gedanken investieren.

Dev-MarkoF commented 3 years ago

Muss früher anders gewesen sein, EVENT.COMPONENT_REMOVE ist ebenso in der Audio und Animator Component als das Event welches gefeuert werden soll, bei der Beseitigung der Komponente.

Die Komponente sollte als Folge der Löschung der Node auch mit beseitigt werden oder? Als Nutzer gehe ich zumindest davon aus. Sonst muss in Zukunft jede Physik Komponente halt noch auf das EVENT.CHILD_REMOVE hören.

JirkaDellOro commented 3 years ago

"Löschung der Node" gibt es nicht in Javascript/Typescript, daher kann man das auch nicht abfangen.

Ich würde auch keinen Automatismus einbauen, wie ich oben schon erwähnte. Das erwartet nämlich niemand, dass ein Knoten seine Komponenten verliert, wenn sich der Parent ändert. Hier sollte der Kreateur eher eine Subklasse bauen, welche die Physikkomponenten verwaltet für die spezifischen Anwendungsfälle.

Man könnte es dahingehend vereinfachen, dass die Physik noch eine Methode hat, welche einen Graphen mit der Physikwelt synchronisiert. Die ruft man dann auf, wenn man den Graphen strukturell verändert.

Dev-MarkoF commented 3 years ago

@JirkaDellOro So war es auch nicht gemeint, my bad ging da vom Falschen aus. Hab das aus Reflex wie Unity gedacht, nicht wie Fudge. Hab RemoveChild auf dem Root irgendwie aufgefasst wie ein GameObject.Destroy(go), in dem Fall müssten natürlich die Components mit zerstört werden.

Ich ging davon aus, dass wenn man von einer Node, dass Child removed die Components auch removed werden. Aber es stimmt, so funktioniert das hier im Zusammenhang wirklich nicht, da das Child vom Root entfernen dieselbe Vorgehensweise ist, wie das Child von irgendeiner Node entfernen und da sollten die Components natürlich erhalten bleiben.

Den Graphen und die Physik zu synchronisieren, wäre vermutlich auch wieder ein ungewollter Automatismus, der würde ja jetzt im gegebenen Fall für den Kreateur den Rigidbody entfernen, wenn er die Node "löscht", dass soll ja nicht geschehen. Durch umhängen wird die Physik ja bereits aktualisiert durch ComponentAdd/Remove Event. Daher stimmt das obige, @luca1107 du musst den Body manuell entfernen, das ist so gedacht.

PS: Hab OimoPhysics auch mal selbst compiled und ein paar Anregungen die bisher noch keiner bearbeitet hat übernommen. Erster Test für "Formgenaue" Trigger ist in langsamer Arbeit, ich sende bereits das OimoEvent und verwerfe die Kollisionsverarbeitung, (funktioniert noch nicht ganz korrekt), aber auf Fudge Seite hab ichs noch nicht angefasst.

JirkaDellOro commented 3 years ago

PS: Hab OimoPhysics auch mal selbst compiled und ein paar Anregungen die bisher noch keiner bearbeitet hat übernommen. Erster Test für "Formgenaue" Trigger ist in langsamer Arbeit, ich sende bereits das OimoEvent und verwerfe die Kollisionsverarbeitung, (funktioniert noch nicht ganz korrekt), aber auf Fudge Seite hab ichs noch nicht angefasst.

👍