[bullet] Ego Shooter Controller

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
Jonathan
Establishment
Beiträge: 2374
Registriert: 04.08.2004, 20:06
Kontaktdaten:

[bullet] Ego Shooter Controller

Beitrag von Jonathan »

Ich möchte jetzt also meinen Charakter aus der Egoansicht durch meine Welt steuern. Bisher habe ich eine statische btCollisionWorld benutzt, aber es ist natürlich sinnlos, den Charakter auf gut Glück zu bewegen und auf Kollision zu testen, wenn Bullet mir da helfen kann.

Die Frage ist jetzt, wo genau fang ich an? Es gibt ja diesen btKinematicCharacterController aber die Doku sagt nicht wirklich viel über dessen Verwendung und an diversen Stellen im Internet liest man auch viel negatives darüber ("Es fühlt sich nicht an, wie ein Mensch, der läuft, sondern eher wie eine Kugel, die durch die Gegen hüpft). Und leider ist eine nervige Steuerung in Egoshootern etwas das in der ersten Sekunde auffällt und bis zum Rest des Spiels nervt.
Also wollte ich einfach mal Fragen, ob jemand damit bereits Erfahrung hat und mir Tipps geben kann. Gerne auch Dinge, die über Bullet hinausgehen und alles Betreffen, was dafür sorgt, dass sich eine Steuerung "gut" anfühlt (z.B. Kamera leicht mitwippen lassen oder ähnliches).
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Schrompf
Moderator
Beiträge: 4859
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [bullet] Ego Shooter Controller

Beitrag von Schrompf »

Ich habe noch nicht so viele Egoshooter geschrieben, aber das Zitat aus dem Internet klingt für mich nach fehlgeleiteter Erwartungshaltung. Der Character Controller ist prima ein Grundkörper, den Du linear durch die Landschaft schiebst und der bei Kontakt entweder seinen oder den Pfad des berührten Körpers ändert. Das "menschliche" Gefühl kommt wohl eher von den leicht hüpfenden Perspektiv-Veränderungen. Ich habe bei den Splitterwelten festgestellt, dass es anfangs einen enorm guten Eindruck gemacht hat, die Kamera einfach an den Kopf-Bone des animierten Charakters zu heften. Auf Dauer wird es allerdings ein bisschen unangenehm, weil man ja für ein angenehmes Spielererlebnis das Bewegungstempo des Charakters ziemlich übertreibt, und das Hin- und Hergewippe der Kamera dann ganz schön nerven kann.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Sternmull
Establishment
Beiträge: 264
Registriert: 27.04.2007, 00:30
Echter Name: Til
Wohnort: Dresden

Re: [bullet] Ego Shooter Controller

Beitrag von Sternmull »

Ha! Netter Zufall: Hab gestern mal wieder angefangen mich exakt mit diesem Problem zu befassen: FPS Character-Controller für Bullet.

Den eingebauten Controller kann man sich in einem der Beispiele angucken... und feststellen das er vollkommen wertlos ist. Er fühlt sich tatsächlich an als würde man sich als Regentonne durch die Welt schieben. Aber ehrlich gesgat finde ich die Beispiele von Bullet grundsätzlich sehr grottig.

Ich würde es auch als schlechtes Zeichen werten das schon seit vielen Jahren immer wieder nach einem brauchbaren Character-Controller gefragt wird, es bisher aber keiner geschafft hat was annehmbares zu implementieren. Es scheint wirklich ein recht knackiges Problem zu sein. Purzelnde Kisten und rollende Kugeln lassen sich halt einfacher simulieren, dass sind ja auch primitive Festkörper für die es wohlbekannte Simulationsmethoden gibt.

Um einen eigenen Controller zu basteln würde ich folgenden Ansatz verfolgen: Als Kollisions-Hülle würde ich einen Zylinder oder vielleicht auch eine Kapsel (swept sphere) verwenden. Allerdings müsste die sich aus zwei Teilen zusammensetzen: Einem soliden oberen Teil der den Raum von Kopf und Rumpf einnimmt und die Größe hat die der Körper im zusammengekauerten Zustand einnimmt. An diesem Teil müsste auch die Kamera befestigt sein. Der untere Teil müsste mit einer Feder-Dämpfer-Verbindung zum oberen Teil als "Fußfläche" dienen. Die Reibung der Bodenfläche sollte relativ hoch sein (Haftung der Schuhsolen auf dem Untergrund). Die Reibung an allen anderen Stellen sollte 0 sein (wiederstandsloses entlanggleiten an Wänden, eine echte Person würde den Kontakt mit der Wand ja grundsätzlich vermeiden und andere Hindernisse eher mit den Händen wegschieben als sich wirklich an ihnen zu reiben, deshalb erscheint mir 0 Reibung hier am intuitivsten).

Nun zu den einzelnen Features die man braucht:

Immer aufrecht stehend: Im Normalfall erwartet man das man in einem FPS nicht stolpern kann. Also sollte die längs-Achse des Controllers immer an der Schwerkraft ausgerichtet bleiben. Das geht in Bullet indem man irgendeinen Wert auf 0 setzt der an der Rotation mitwirkt (steht irgendwo in der Doku... hab vergessen was das genau war, sorry).

Plausible Interaktion mit anderen Objekten: Laufbewegungen müsste man als abstoßende Kraft zwischen dem Objekt auf dem der Controller steht und dem Controller selbst imlementieren. Schließlich erwartet man das dynamische Objekte unter dem Controller in die entgegengesetzte Richtung beschleunigt werden in die der Controller beschleunigt. Springen würde ich wahrscheinlich durch einen Impuls zwischen dem Controller und dem darunterliegenden Objekt implementieren. Allerdings müsste das wirklich das abstoßen vom Boden implementiert sein: Wenn man von etwas abspringt das nachgibt (z.B. leichtes Objekt das an Federn aufghängt ist), dann kann man ja nicht so stark abspringen sondern beschleunigt eher das Objekt auf dem man stheht statt sich selbst.

Treppensteigen: Der Character sollte einfach die "Beine einziehen" wenn er auf eine Stufe kommt. Damit meine ich das Kollisionen zwischen der Höhe der Schuhsolen und dieser Höhe + der maximal erlaubten Stufenhöhe ignoriert werden. Statt dessen wird die Vertikale Position des unteren Teils einfach um diesen Abstand nach oben verschoben, so das der Controller genau auf der Stufe steht. Durch die Feder-Dämpfer-Verbindung der beiden Controller-Teile würde der obere Teil sich dann wieder so weit nach oben schieben das der Kopf wieder den normalen Abstand zu den Füßen hat. Sollten sich Objekte überhalb des Kopfes befinden so werden sie entweder weg gedrückt oder blockieren diese Bewegung, so wie man es erwarten würde.

Hocken: Einfach die länge der Feder-Dämpfer-Verbindung auf den gewünschten Wert verkürzen.

Soweit mein bisher recht grober Ansatz. Ich hab noch nicht in Erfahrung gebracht wie man die abstoßenden Kräfte und vor allem den Sprung-Impuls korrekt implemenieren könnte. Ich bezweifel auch ehrlich gesagt das ich mich überhaupt dazu durchringen werde die Idee mal ernsthaft auszuprobieren. Aber vielleicht hast du ja mehr Ehrgeiz :)

Zur wackelnden Kamera: Das würde ich unabhängig von dem Controller machen. Beim oben genannten Ansatz dürfte sich die Kameraposition auch beim Treppensteigen etc. ähnlich zu Quake usw. verhalten. Wenn man will das die Kamera mit den Schritten des Spielers hin und her schwenkt, so sollte man wahrscheinlich Kamerabewegungen relativ zu dieser Position hinzufügen. Allerdings gibt es Leute denen durch so was schlecht wird.
Benutzeravatar
Jonathan
Establishment
Beiträge: 2374
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: [bullet] Ego Shooter Controller

Beitrag von Jonathan »

Ach, das darf doch nicht wahr sein. Ist die Welt wirklich so schlecht?
Ich meine, einen EgoController kann man doch quasi unmöglich ohne direkte Interaktion mit der Physikengine schreiben. Wie haben denn all die anderen das so elegant gelöst? (Wobei ich sagen muss, viele Unityspiele steuern sich auch grauenhaft - was wieder das Klischee von Gamemaker-Spielen erfüllt, aber egal).

Mein billigster Ansatz war ja, mir immer die Bodenhöhe zu holen und die Z-Position entsprechend anzupassen, und Bewegungen nur auszuführen, wenn es zu keiner Kollision kommt. Aber selbst das würde ein Weilchen dauern und wäre so mies, dass man es eh direkt ändern würde.

Der Ansatz klingt schon ziemlich gut. Ich glaube, Kapseln sind erstmal netter als Zylinder, weil die besser über Unebenheiten rutschen, aber mit dem Federansatz hätte sich das ja sowieso erledigt. Das mit der variablen Reibung klingt auch super, man will ja nicht Berge runterrutschen aber natürlich auch nicht an Wänden kleben bleiben. Die Rückwirkende Kraft auf andere Objekte würde ich glaube ich zunächst vernachlässigen: Ich brauche ja erstmal keine Trampoline oder Schaukeln oder Bretter die auf dem Wasser zu treiben anfangen, wenn ich auf ihnen in die andere Richtung laufe - soll ja kein Physikpuzzlespiel werden. Und sowas wie sich von Gegnern abstoßen kann man ja sonst noch irgendwie reinhacken, sollte man es irgendwann brauchen.

Treppen sind so ein Thema, ich glaube da wirds echt kniffelig. Man will die ja hoch und runter laufen, also weder jede Stufe einzeln runterfallen, noch bei hinaufsteigen hängen bleiben. Ich denke eine gefederte Kugel unter einem Zylinder könnte da ganz gut sein: Wie du schon gesagt hast, schiebt sich diese dann nach oben und drückt kurz darauf den Zylinder höher. Mit etwas Glück entsteht dann entlang der Treppe eine gleichförmige Bewegung, man will ja auch echt nicht jede Stufe als Kamerabewegung sehen. Für das Treppabwärtslaufen würde ich dann sogar die Feder auch im normalen laufen leicht eingedrückt haben, sobald man über die erste Stufe läuft, würde die Kugel runtergehen und ein Fallen verhindern.

Für das Hocken würde ich vielleicht einfach den Zylinder ändern - weil ich bin mir nicht sicher, ob die Feder unten wirklich so groß sein sollte, aber das kann man dann ja sehen.
Achja: Der Zylinder hätte dann 0 Reibung, und die Federnde Kugel eine Hohe, damit man nicht abrutscht? Aber wie sorgt man dann dafür, dass die Kugel Treppenstufen hoch und runterrutschen kann?

Insgesamt finde ich die Feder-Idee schon ziemlich gut. Scheint ein sehr adäquates Modell dafür zu sein, was unsere Beine so tagtäglich leisten müssen (Respekt, meine lieben Beine :D) aber trotzdem noch einfach genug um umsetzbar zu sein. Aber wenn man sich dann mal überlegt, das man für so etwas einfaches wie Rumlaufen ein viel komplexeres Modell mit zig Constraints, zusammengesetzen Objekten und verschiedenen Reibungen braucht, als für einen einstürzenden Kistenturm - nun das ist schon irgendwie albern.


Der Ehrgeiz müsste da sein. Ich will den Kollision und Physik Teil nicht selber schreiben, Bullet schien mir eine sehr brauchbare Physikengine zu sein und rumlaufen muss nun eben sein. Sollte mir jetzt nicht jemand sagen, Physikengine X leiste das gleiche wie Bullet, hätte aber bereits funktionierende Controller und alles, habe ich wohl kaum eine andere Wahl, als das ganze zu implementieren.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Jonathan
Establishment
Beiträge: 2374
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: [bullet] Ego Shooter Controller

Beitrag von Jonathan »

So, ich arbeite wieder daran. Es ist echt unglaublich, man findet keine vernünftige Lösung im Internet. Aber ich mache Fortschritte:

Code: Alles auswählen

// --------------------- Initialisierung: -------------------------------
	//the shape
	m_CollisionShape.reset(new btCapsuleShapeZ(0.5, 0.5));//height=1.7-2*r
	m_CollisionShapeOffset=vec3(0, 0, 1);

	//the collision object
	m_CollisionObject.reset(new btRigidBody(1, this, m_CollisionShape.get()));
	m_CollisionObject->setUserPointer(&m_CollisionObjectData);
	m_CollisionObject->setActivationState(DISABLE_DEACTIVATION);
	m_CollisionObject->setFriction(1);
	m_CollisionObject->setAngularFactor(0);



//------------------Motion State:-----------------------------------

void EgoController::getWorldTransform(btTransform& worldTrans) const
{
	//set the initial values
	auto Pos=m_Entity->GetPosition()+m_CollisionShapeOffset;
	worldTrans.setOrigin(asBtVec(Pos));
	worldTrans.setRotation(btQuaternion(0, 0, m_Entity->GetDirection()*PI/180.0f));
}

void EgoController::setWorldTransform(const btTransform& worldTrans)
{
	m_Entity->SetPosition(asGlmVec(worldTrans.getOrigin())-m_CollisionShapeOffset);
}



// ------------------------- Controll -------------------------------

void EgoController::Update(float Time)
{
	static int x, y;
	static int lx, ly;
	glfwGetMousePos(&x, &y);


	vec3 Movement;

	//------ Walk ------
	float MoveSpeed=6;

	if(glfwGetKey(GLFW_KEY_LSHIFT))
		MoveSpeed=12;

	if(glfwGetKey('W'))
	{
		float Dir=(m_Entity->GetDirection()+90)*PI/180.f;
		Movement+=vec3(cosf(Dir), sinf(Dir), 0)*MoveSpeed;
	}
	if(glfwGetKey('S'))
	{
		float Dir=(m_Entity->GetDirection()+270)*PI/180.f;
		Movement+=vec3(cosf(Dir), sinf(Dir), 0)*MoveSpeed;
	}
	if(glfwGetKey('A'))
	{
		float Dir=(m_Entity->GetDirection()+180)*PI/180.f;
		Movement+=vec3(cosf(Dir), sinf(Dir), 0)*MoveSpeed;
	}
	if(glfwGetKey('D'))
	{
		float Dir=(m_Entity->GetDirection()+0)*PI/180.f;
		Movement+=vec3(cosf(Dir), sinf(Dir), 0)*MoveSpeed;
	}

	if(glfwGetKey(GLFW_KEY_SPACE))
	{
		auto Velocity=m_CollisionObject->getLinearVelocity();
		if(abs(Velocity.z())<0.1)
			m_CollisionObject->applyCentralImpulse(btVector3(0, 0, 10));
	}
	
	if(true)//check if we are on the ground
	{
		auto Velocity=m_CollisionObject->getLinearVelocity();
		m_CollisionObject->setLinearVelocity(btVector3(0, 0, Velocity.z()));
	}

	//look:
	if(glfwGetMouseButton(GLFW_MOUSE_BUTTON_1))
	{
		const float MouseSensitivity=0.8f;
		m_Entity->SetDirection(m_Entity->GetDirection()+(lx-x)*MouseSensitivity);
		m_PitchAngle+=(ly-y)*MouseSensitivity;
		if(m_PitchAngle< -89)
			m_PitchAngle=-89;
		if(m_PitchAngle> 89)
			m_PitchAngle=89;
	}

	m_CollisionObject->applyCentralImpulse(asBtVec(Movement));
	
	glfwGetMousePos(&lx, &ly);
}

Hier mal, was ich tue:

Der Grundkörper des Spielers ist eine Kapsel. Da sie unten rund ist, gleitet sie leichter über Unebenheiten.
Mit setAngularFactor(0) verhindert man, dass sie umfällt. (Man war das bescheuert, als ich das erste mal das Spiel startete, und mein Charakter einfach umkippte...).
setActivationState(DISABLE_DEACTIVATION) ist wichtig, da Bullet sonst das Objekt beim ersten Stillstand deaktiviert. Danach kannst du Kräfte anwenden wie du willst, das Objekt wird sich nicht mehr bewegen.
Die Masse setze ich auf 1, damit sämtliche "applyImpulse" Aufrufe direkt die Nette-Geschwindigkeit angeben. Aber man muss darauf achten, dass man mit einem 1kg schweren Menschen keine 50kg schwere Objekte mehr schieben kann.

Die Steuerung ansich soll sehr direkt sein, also keine Beschleunigung oder dergleichen haben. Mein Ansatz ist der, am Anfang jeder Bewegung die Geschwindigkeit auf 0 zu setzen (außer die Z-Achse, das ist meine Hoch-Achse und Objekte sollen ja ganz normal runter fallen können). Ist dann eine Taste gedrückt, erzeuge ich den entsprechenden Impuls (und keine Kraft, es soll ja direkt wirken). Dieser würde natürlich in jedem Frame auf die aktuelle Geschwindigkeit addiert werden, aber davor wird sie ja immer auf 0 gesetzt. Effektiv laufe ich also genau dann, wenn die Taste gedrückt ist.

Das Springen ist sehr provisorisch. Eigentlich bräuchte ich eine komplette "Objekt steht auf Boden" Prüfung, aber die Abfrage der Z Geschwindigkeit funktioniert auch so halbwegs.


Weitere Dinge die ich unbedingt einbauen möchte: Die gesamte Steuerung wirkt nur, solange der Spieler am Boden ist. Das hieße. dass man sich in der Luft nicht mehr bewegen könnte. Dafür könnte man den Spieler aber auch wirklich physikalisch simuliert durch die Luft fliegen lassen.

Ich habe auch mit der Reibung experimentiert, und sie sehr hoch gesetzt, damit der Spieler bei einer normal beschleunigten Bewegung auf dem Boden schnell stehen bleibt. Aber das hat sich nie gut angefühlt.
Insgesamt ist die Steuerung so jetzt schon fast gut. Sie ist sehr direkt (eine leichte Dämpfung wäre hier und da sicherlich gut, mal sehen, wie ich das noch hinbekomme), aber man gleitet auch schön an Objekten wie Wänden entlang. Außerdem kommt man über Bodenunebenheiten und Treppenlaufen klappt sogar auch.

Das zweiteilige Modell mit der Feder klingt auch interessant, und man könnte sicherlich auch mit anderen Kollisionsformen experimentieren, um zu sehen, wie man damit über Bodenunebenheiten kommt. Aber zumindest habe ich jetzt etwas, mit dem ich so halbwegs vernünftig durch mein Level laufen kann.

Achja: Ducken, Leiter, Vorsprünge hochziehen - das wären alles so Dinge über die man sich auch noch Gedanken machen sollte.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Antworten