Das zweite Spiel
Universität zu Köln
Institut für Historisch-Kulturwissenschaftliche Informationsverarbeitung
Prof. Dr. Manfred Thaller
AM 3 Übung: Softwaretechnologie II Teil 1: Simulation und 3D-Programmierung
WS 2011/2012
Dennis Kiewning
Auto-Aiming
- Waffen richten sich automatisch auf ihr Ziel aus
- Nicht alle Waffen unterstützen Auto-Aiming
- Wenn Waffexy Auto-Aiming unterstützt: Element BOOL m_bAutoAim = True/False
- erweitern der Methode CShip::Fire
Auto-Aiming
CShip::Fire
//Wenn Auto-Aim eingeschaltet ist und die Waffe es unterstützt, wird der Laserstrahl auf das Ziel ausgerichtet.
if(m_bAutoAim && m_pType->apWeaponType[iWeapon]->bAutoAim){
if(m_iTarget != -1) {
// Zielvektor berechnen
vAimAt = tbComputeAimingVector(m_vPosition,
m_pGame->m_aShip[m_iTarget].m_vPosition,
m_pGame->m_aShip[m_iTarget].m_vVelocity,
m_pType->apWeaponType[iWeapon]->fLaserSpeed);
// Je nach Sensorenschaden wird diese Position leicht geändert.
vAimAt += tbVector3Random() * tbFloatRandom(0.0f, 100.0f * (1.0f - m_fSensorsEfficiency));
// Den Winkel zwischen der z-Achse des Schiffs und dem Zielpunkt berechnen
fAngle = tbVector3Angle(tbVector3Normalize(vAimAt - m_vPosition), m_vZAxis);
// Wenn der Winkel zu groß ist, funktioniert Auto-Aim nicht.
if(fabsf(fAngle) <= TB_DEG_TO_RAD(m_pType->apWeaponType[iWeapon]->fMaxAutoAimAngle))
{
// Den Laserstrahl auf den neuen Zielpunkt ausrichten
pProjectile->LookAt(vAimAt);
}
}
}
Künstliche Intelligenz
- Verhalten der Schiffe definieren
1.
Es soll immer Kurs auf sein aktuelles Ziel nehmen (Keine Flucht)
2.
Es soll immer sofort feuern
3.
Es soll bei Treffern Ausweichmanöver durchführen
4.
Es soll trotz möglicher Kollisionen ausweichen
5.
Es soll angreifende Schiffe als neues Ziel markieren
Künstliche Intelligenz
1.
Kurs auf das Ziel nehmen
-
Mittelwert aus allen Projektilgeschwindigkeiten und Waffenpositionen bilden
-
Die Funktion tbComputeAimingVector bestimmt den Zielpunkt des Schiffs
CShip::Control
// Mittelwerte berechnen
vWeapons /= (float)(iNumWeapons);
fWeaponSpeed /= (float)(iNumWeapons);
// Punkt berechnen, auf den gezielt werden muss, um das gegnerische
// Schiff treffen zu können
vAimAt = tbComputeAimingVector(RelToAbsPos(vWeapons),
pTarget->m_vPosition,
pTarget->m_vVelocity,
fWeaponSpeed);
Künstliche Intelligenz
2.
Feuern
-
Ein Schiff feuert dann, wenn die Distanz zum Zielpunkt kleiner oder gleich 2500 Einheiten ist
-
Laserwaffen: Wenn der Winkel zum Ziel klein genug ist wird gefeuert
-
Raketen: Es muss eine Mindestentfernung von 300 Einheiten vorliegen
CShip::Control
// Ab 2500 Einheiten fängt das Schiff mit dem Feuern an
fDistance = tbVector3Length(vAimAt - m_vPosition);
if(fDistance <= 2500.0f &&
pTarget->m_fExplosionCountDown == 0.0f)
Künstliche Intelligenz
2.
Feuern
-
Unterscheiden ob es sich um Laser- oder Raketenwaffen handelt
// Jede Waffe durchgehen
for (int w = 0; w < m_pType->iNumWeapons; w++)
{
if(m_pType->apWeaponType[w]->bIsLaserWeapon)
{
// Unterstützt die Waffe Auto-Aiming und ist der Winkel klein genug?
if((m_pType->apWeaponType[w]->bAutoAim && m_bAutoAim && fabsf(fAngle) <=
TB_DEG_TO_RAD(m_pType- >apWeaponType[w]->fMaxAutoAimAngle)) ||
(!m_pType->apWeaponType[w]->bAutoAim && fabsf(fAngle) <= TB_DEG_TO_RAD(10.0f)))
{
Fire(w);
}
}
else
{
// Raketen werden abgefeuert, wenn das Ziel eine Mindestentfernung von 300 Einheiten hat
oder der Winkel sehr klein ist.
if(fDistance > 300.0f || fabsf(fAngle) <= TB_DEG_TO_RAD(10.0f)) Fire(w);
Künstliche Intelligenz
3.
Ausweichmanöver bei Treffern
-
Ausweichmanöver nur über einen bestimmten Zeitraum
-
Variable m_fEvasiveManeuvers dient als Countdown
// Wenn das Schiff Ausweichmanöver durchführt, lenkt es wild.
if(m_fEvasiveManeuvers > 0.0f)
{
m_vSteering.x += 2.0f * sinf(m_pGame->m_fTime * 0.25f + tbFloatRandom(-0.1f, 0.1f));
m_vSteering.y += 2.0f * cosf(m_pGame->m_fTime * 0.25f + tbFloatRandom(-0.1f, 0.1f));
m_vSteering.z += tbFloatRandom(-1.0f, 1.0f);
m_fThrottle = sinf(m_pGame->m_fTime);
// Count-Down
m_fEvasiveManeuvers -= fTime;
}
Künstliche Intelligenz
4.
Ausweichmanöver bei drohender Kollision
-
Entfernung anderer Schiffe berechnen
-
Verringert sich die die neue Entfernung droht eine Kollision
-
m_fEvasiveManeuvers-Variable wird ein Zufallswert zwischen 2 und 3 zugewiesen
if (fNextDistance < (m_pType->pModel->GetBoundingSphereRadius() +
pShip->m_pType->pModel->GetBoundingSphereRadius()) * 2.0f
&& fNextDistance < fDistance)
{
// Ausweichmanöver!
m_fEvasiveManeuvers = tbFloatRandom(2.0f, 3.0f);
}
5.
Wechseln des Ziels
-
Wird ein Schiff beschossen, soll es sein Ziel auf das Schiff des Angreifers wechseln
-
In der Klasse CProjektile wird dazu die Variable m_FiredBy auf einen Zufallswert gesetzt (0-20)
Partikel
-
Flammen und Explosionen werden mit Hilfe einzelner Sprites dargestellt
-
Klasse tbParticleSystem bietet dazu ein komplettes Partikelsystem
-
Jeder Partikel wird durch folgende Angaben beschrieben:
1.
2.
3.
4.
5.
6.
Lebenszeit
Start-Sprite und End-Sprite-Typ
Position und Bewegungsvektor sowie Reibungsfaktor
Start und Endgröße
Start und Endfarbe
Startrotation und Endrotation
-
Neue Instanz von von tbParticleSystem anlegen und mit der Init-Methode initialisieren
(der man dann die maximale Anzahl der Partikel übergibt)
-
Jeder neue Frame ruft die Move-Methode auf (die alle Partikel bewegt)
-
Zum rendern wird der Methode AddToSpriteEngine ein Zeiger auf die Klasse der Sprite-Engine
übergeben
-
Über die Methode AddParticle werden Partikel hinzugefügt
Optische Verfeinerungen
-
Die Klasse tbSkyBox
1.
2.
3.
Cube-Map laden die mit der SkyBox verwendet werden soll
Aufrufen der Init-Methode einer mit new erzeugten tbSkyBox–Instanz (Texturen als Parameter)
Aufruf der Render-Methode rendert die Skybox
-
Licht und Nebel
1.
2.
CGame::Render werden Richtungslicht der Sonne erstellt und eingesetzt
Zusätzlich werden Render-States für Nebel gesetzt: weit entfernte Schiffe erscheinen dunkler
if(m_pSkyBox != NULL)
{
// Sky-Box rendern
m_pSkyBox->Render(m_vCameraPos);
}
// Nebel einstellen (damit weit entfernte Objekte dunkler werden, bevor sie aus dem Sichtbereich geraten
D3D.SetRS(D3DRS_FOGENABLE, TRUE);
D3D.SetRS(D3DRS_FOGVERTEXMODE, D3DFOG_LINEAR);
D3D.SetRS(D3DRS_FOGCOLOR, tbColor(0.0f, 0.0f, 0.0f));
D3D.SetRSF(D3DRS_FOGSTART, 4000.0f);
D3D.SetRSF(D3DRS_FOGEND, 5000.0f);
Die Kamera
-
Galactica besitzt mehere Kameramodi
enum ECameraMode
{
CM_COCKPIT,
CM_CHASE,
CM_FREECHASE,
CM_FRONTCHASE,
(…)
};
-
// Sicht aus dem Cockpit
// Jagdkamera
// Freie Jagdkamera
// Jagdkamera von vorne
Methode CGame::SetupCamera setzt die Kamera
// Position, Blickpunkt, Nach-Oben-Vektor und Blickfeld je nach Kameramodus setzen
switch(m_CameraMode)
{
case CM_COCKPIT:
// Die Kamera befindet sich im Cockpit. Wo das liegt (relativ zum Schiff), ist in der Variablen vCockpitPos
der SShipType-Struktur gespeichert.
m_vCameraPos = m_pPlayer->RelToAbsPos(m_pPlayer->m_pType->vCockpitPos);
m_vCameraLookAt = m_vCameraPos + m_pPlayer->m_vZAxis;
m_vCameraUp = m_pPlayer->m_vYAxis;
m_fFOV = TB_DEG_TO_RAD(70.0f);
break;
Das Cockpit
Für das Cockpit kommt ein 3DModell zum Einsatz
Die Datei COCKPIT.TBM wird mit CGame::Load in die Variable tbModel*CGame::m_ CockpitModel geladen
tbResult CGame::RenderCockpit(float fTime)
{
// Wenn das Schiff des Spielers zerstört ist, wird kein Cockpit mehr gerendert.
if(!m_pPlayer->m_bExists) return TB_OK;
// Radar rendern
RenderRadar(fTime);
// Cockpitmodell rendern
tbDirect3D& D3D = tbDirect3D::Instance();
D3D.SetTransform(D3DTS_WORLD, tbMatrixTranslation(m_pPlayer->m_pType->vCockpitPos
+ tbVector3(0.0f, -10.0f, 5.0f) + m_pPlayer->m_vCockpitShaking) * m_pPlayer->m_mMatrix);
m_pCockpitModel->Render();
Die Anzeigen
-
Informationen über das eigene und das als Ziel erfasstes Schiff
-
Status des Auto-Aimings (ein/aus eigenes Schiff)
Schaden aller Systeme
Aufladung und Munitionsvorrat der Waffen (eigenes Schiff)
Schub (eigens Schiff)
Geschwindigkeit beider Schiffe und Entfernung zum Zielschiff
Schildenergie
Waffenenergie (eigenes Schiff)
-
Anzeigen werden durch Aufrufen der Methode tbFont::DrawText angezeigt
g_pGalactica->m_pFont2->Begin();
// Radarreichweite anzeigen
sprintf(acText, "Radar: %.0f", m_fRadarRange);
g_pGalactica->m_pFont2->DrawText(tbVector2(0.025f, 0.92f), acText, TB_FF_RELATIVE | TB_FF_RELATIVESCALING);
// Auto-Aim-Status anzeigen
sprintf(acText, m_pPlayer->m_bAutoAim ? "Auto-Aim ein" : "Auto-Aim aus");
g_pGalactica->m_pFont2->DrawText(tbVector2(0.025f, 0.95f), acText, TB_FF_RELATIVE | TB_FF_RELATIVESCALING);
Das Hud
Ziel:
-
1. Das gerade erfasste Schiff soll durch ein Fadenkreuz markiert werden.
2. Fadenkreuz für Waffen ohne Auto-Autoaiming
Problem:
-
Nur die dreidimensionale Position des Ziels ist bekannt. Zum rendern wird
allerdings die zweidimensionale Position (Bildschirmkoordinaten) benötitgt
Lösung:
-
Den Zielpunkt mit dem Produkt der Sicht- und Projektionsmatrix transformieren
Der Radar
-
Die Klasse tbDraw2D bietet Methoden zum zeichnen von Pixeln, Linien, Rechtecken und Kreisen
z.B
-
Funktion: Setpixel
Beschreibung: Setzt einen Pixel
Parameter: Koordinaten und Farbe des Pixels
-
Funktion: Drawline
Zeichnet eine Linie
Parameter: Start- und Endkoordinaten sowie Farbe der Linie
pDraw1->SetPixel(10,10 pDrawl->MakeRGB(255,0,0)),
pDraw1->SetPixel(30,50 pDrawl->MakeRGB(255,0,0)),
pDraw1->SetPixel(80,20 pDrawl->MakeRGB(255,0,0)),
// roter Pixel
// grüner Pixel
// blauer Pixel
pDraw1->DrawLine(0,0,100,100, pDrawl->MakeRGB(255,255,255)),
// Weiße Linie
Der Radar
Den Radar zeichnen:
1. Die tbDraw2D-Klasseninstanz die zum Zeichnen der Radartextur verwendet werden soll,
wird in tbDraw*CGame::m_pRadar gespeichert
2. In CGame::Init wird die Instanz erstellt, nachdem das Cockpitmodell geladen wurde
// Cockpitmodell laden
m_pCockpitModel = new tbModel;
if(m_pCockpitModel->Init("Data\\Cockpit.tbm", "Data\\")) ;
// Zeichenklasse für die Radartextur erstellen
m_pRadar = new tbDraw2D;
if(m_pRadar->Init((PDIRECT3DTEXTURE9)(m_pCockpitModel->GetEffects()[2].apTexture[0]), 0)) ;
Der Sound
-
Zum verwenden von Sound werden die tbDirectSound und tbSound-Klasse verwendet
-
Sound der Waffen abhängig von ihrer Gattung
Sound der Waffen wird zu einem Teil der SWeaponType-Struktur:
tbSound*SWeapon::pLauncherSound
-
Geladen werde alle Sounds in der Methode CGame::LoadWeaponTypes
// Abschusssound laden
pType->pLauncherSound = new tbSound;
if(pType->pLauncherSound->Init(pType->acLauncherSound,
DSBCAPS_STATIC | DSBCAPS_LOCDEFER |
DSBCAPS_CTRL3D | DSBCAPS_CTRLFREQUENCY |
DSBCAPS_MUTE3DATMAXDISTANCE,
DS3DALG_HRTF_FULL, 16))
Die Tribase-Benutzeroberfläche
Die Benutzeroberfläche wird durch die Klasse tbGUI repräsentiert
Ein Spiel benötigt immer nur eine Instanz dieser Klasse. Die Initialisierung erfolgt
durch die Iint-Methode
Bedienelemente:
1.
2.
3.
4.
5.
6.
7.
8.
Klasse: tbGUIFrame
Klasse: tbGUIText
Klasse: tbGUIImage
Klasse: tbGUIButton
Klasse: tbGUICheckBox
Klasse: tbGUIRadioBox
Klasse: tbGUIInput
Klasse: tbGUIList
Bedienelement: Rahmen (Fenster)
Bedienelement: Einfacher Text
Bedienelement: Bild (Textur)
Bedienelement: Knopf mit Beschriftung
Bedienelement: CheckBox mit Beschriftung
Bedienelement: RadioBox mit Beschriftung
Bedienelement: EingabeFeld
Bedienelement: Listenfeld
Bedienelemente
Der Rahmen:
-
Ein Rahmen wird durch die Methode tbGUi::CreateFrame erstellt
Parameter:
-
Int iID: ID des zu erstellenden Rahmens
Int iPage: Nummer der Seite, auf welcher der Rahmen erzeugt werden soll
tbVector2 vPosition: Position der linken oberen Ecke des Rahmens
tbVector2 vSize: Breite und Höhe des Rahmens
Bedienelemente
Texte:
-
Ein Text wird durch die Methode tbGUi::CreateText erstellt
Parameter:
-
Standard: Int iID, Int iPage, tbVector2 vPosition, tbVector2 vSize
char* pcText: darzustellender Text
tbColor Color: Farbe des Textes
tbVector2 vTextsize: Größe des Textes
Bedienelemente
Texte:
-
Ein Bild wird durch die Methode tbGUi::CreateImage erstellt
Parameter:
-
Standard: Int iID, Int iPage, tbVector2 vPosition
tbVector2 vSize : Größe des Bildes
tbColor Color: Farbe des Bildes
tbVector2 vTopLeftTex tbVector2 vBottomRightTex: Texturkoordinaten für die linke obere und
rechte Ecke untere Ecke des Bildes
Das Hauptmenü
In CMainMenu::Load wird eine Instanz der tbGUI-Klasse erstellt und initialisiert
Die Liste der Schiffstypen wird erstellt: Jeder Schiffstyp wird durchgegangen und erhält einen
neuen Eintrag
Als Eintragstext dient der Name des Schiffs und als Datenzeiger ein Zeiger auf dessen SShipType-Struktur
m_pGUI->CreateText(105, 0, tbVector2(250.0f, 110.0f), "Verfügbare Schiffstypen");
m_pGUI->CreateList(106, 0, tbVector2(250.0f, 140.0f), tbVector2(192.0f, 120.0f), 20.0f);
for(int i = 0; i < pGame->m_iNumShipTypes; i++)
{
((tbGUIList*)(m_pGUI->GetElement(106)))->AddEntry(pGame->m_aShipType[i].acName,
&pGame->m_aShipType[i]);
}
Render- und Kollisionsmodell
-
Eine exakte Kollisionserkennung beansprucht bei Modellen mit vielen Dreiecken sehr viel Rechenzeit
-
Für die Kollisionserkennung werden daher weniger detaillierte Modelle geladen
-
Die Kollisionsmodelle sind in den Dateien SHIP1C.TBM, SHIP2C.TBM usw. gespeichert
-
Galactica lädt sie so, dass nur die Daten die man für die Kollisionserkennung braucht berechnet werden
-
Dementsprechend haben diese dann keine Effekte oder Vertex- und IndexBuffer
-
Um das Spiel noch weiter zu optimieren: Render-Modelle mit verschiedenen Versionen unterschiedlichen
Detailgrades
-
Je nach Entfernung werden entsprechende Modelle geladen
Render- und Kollisionsmodell
Vielen Dank!

Klasse - Historisch-Kulturwissenschaftliche Informationsverarbeitung