PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Konsole programmieren - OpenGL


Einfachkrank
2003-02-08, 21:02:49
Hi,

ich programmiere mit C++ und OpenGL. Und jetzt würde ich gerne noch in meiner Vollbildanwendung eine Konsole programmieren, wie z.B. bei CS. Wie mache ich das am einfachsten? Und vor allem wie kann ich diese dann richtig anwenden?

MFG Einfachkrank

Abe Ghiran
2003-02-09, 00:28:03
Schau doch mal in den Quelltext von Quake2 um dich inspirieren zu lassen. Vielleicht kannst du da direkt was übernehmen.

Ansonsten würde ich ein "KonsolenObjekt" programmieren. Wenn dann die Konsole aufgerufen wird (so wie bei Quake mit "~"), müsstest du alle Tastatur Events an das Objekt umleiten, das die dann in einem internen Puffer (string, char[] -> dann aber bzgl. überläufen aufpassen) speichert. Ist die gedrückte Taste dann mal "return", müsste der gespeicherte String interpretiert werden. Dafür solltest du dir vorher ein bestimmtes Schema überlegen, wie die Kommandos für dein Programm auszusehen haben.

Also ungefähr so (nicht hauen, wenn's syntaktisch nicht so 100% ist, kann kaum c++):


class Konsole {
char input[];

public:
Konsole();
handleKeyEvent(char c);

private:
parseInput();
}


Und damit der User nicht blind tippen muß, sollten natürlich die getippten Buchstaben noch auf dem Bildschirm ausgegeben werden. Soweit (Text mit Opengl) bin ich noch nicht gekommen, ich habe aber das "Game Programming with Opengl" hier, da sind code Beispiele drin. Wenn dir das hilft kann ich die gerne noch mal posten. Aber in Sachen OpenGl haben wir ja auch Zeckensack ;).

Zu möglichen Anwendungen: keine Ahnung, was macht denn dein Programm? Aber so spontan würden mir einfallen:
- set variable value Setzt den Wert value für die variable, zum Beispiel um einstellungen des Renderes zu ändern oder so
- screenshot dateiname Speichert einen screenshot in einer datei mit dem gegebenen Namen.

So, muß jetzt eben auf Eurosport Rally Schweden gucken :). Hoffe, das war schon mal eine kleine Hilfe.

Grüße, Jan

Edit: tYpFehLer

Einfachkrank
2003-02-09, 13:02:58
Hi,

also so lange ich noch am entwickeln bin, wollte ich die Konsole für States nutzen. Als Beispiel: Während dem Testen möchte ich schnell bestimmte Effekte einschalten, aber habe noch keine Menü für die Anwendung programmiert etc.
Den Quake2 Quellcode - wo find ich den?

MFG Einfachkrank

Demirug
2003-02-09, 13:37:03
Einfachkrank,

quake 2 sourcecode:

http://www.cfxweb.net/files/Detailed/1241.shtml

Aber Guten gewissens kann ich dir den nicht ans Herz legen. JC programmiert immer noch für Compiler und nicht für Menschen.

Abe Ghiran
2003-02-09, 16:48:09
Hallo,

ich versuche ja gerade OpenGl / c / Windows Programmierung / c++ zu lernen (hab bisher nur java programmiert). Und das erste was ich gemacht habe, um nachvollziehen zu können was da so manchmal vor sich geht war ein LogWindow zu programmieren.

Das macht ein extra Fenster auf, mit einem Button für screenshots, einer TextArea für debug meldungen und einem Textinput für eine Art Konsole (aber die macht noch nichts). Wenn dir das helfen würde, kann ich gerne davon mal den quelltext posten (ist aber noch etwas krautig :)).

Ich weiß, das ist noch nicht ganz das was du haben willst (zu einer Konsole im Hauptfenster will ich auch noch mal irgendwann) aber das wäre zumindest mal ein Anfang.

Noch mal zu den Kommandos: Dazu müsste natürlich dein Programm erst mal schon mal intern flexibel sein, so im Sinne von "hardcode nothing". Z.B. nicht irgendwo fix stehen haben

glShadeModel(GL_SMOOTH);

sondern lieber:

GLenum shademode;
...
glShadeModel(shademode);


Jetzt bräuchtest du halt noch einen Parser für den Input der Konsole. Als erstes würde ich den String an Hand von Leerzeichen in einzelne Tokens zerlegen (dazu gibt es doch bestimmt ne fertige funktion in irgendeiner standard lib). Dann wird mit dem ersten Token entschieden was das für ein Command ist und ein entsprechender Handler aufgerufen. Der interpretiert dann den Rest. Also etwa so (pseudocode):


processInput(String input){
// in einzelteile zerlegen
String[] t = tokenize(s);

// entscheiden was zu tun ist
if(t[0] equals "screenshot"){
// t[1] ist der dateiname
renderer.saveScreenshot(t[1]);

} else if(t[0] equals "set"){
handleStateChange(t);

} ...usw
}

handleStateChange(Token[] t){
// unterscheiden was geändert werden soll
if(t[1] equals "shading"){
if(t[2] equals "smooth")
renderer.setShading(GL_SMOOTH);
else if(t[2] equals "flat")
renderer.setShading(GL_FLAT);

} else if(t[1] equals "lightning"){
if(t[2] equals "true")
renderer.enableLighting();
else if(t[2] equals "false)
renderer.disableLightning();
}
}


Dann könnte z.B. man mit "set shading smooth" auf gouraud shading umstellen. So in der Art stelle ich mir das vor.

Grüße, Jan

firewars
2003-02-09, 17:49:43
Ist so gesehen immernoch etwas zu umständlich und indirekt.

Warum eine if-Abfrage, ob der 3. Parameter x ist, um dann y auszulösen? Warum nicht gleich x verwenden?

Zum Beispiel würde folgende Eingabe:

/set shading GL_SMOOTH

das auslösen:


glShadeModel(t[3]);


und somit eine zusätzliche if-Abfrage unterbinden.

Sicher müssten die Eingaben dann auch "existentem und richtigem" (OpenGL-)Code entsprechen.

Abe Ghiran
2003-02-09, 18:15:57
Hi!

Das stimmt natürlich, dieser Rattenschwanz von if / else ist noch sehr unschön. Aber das was du vorschlägst wird so einfach auch nicht funktionieren, denn z.B. GL_SMOOTH ist ja nur ein Variablenname für eine Konstante.
Ich bin mir nicht sicher aber wahrscheinlich wird es ein nummerischer Wert sein. Was der User in der Konsole eingibt ist aber ein String, das passt dann nicht.
Deine Idee ist aber schon mal gut, die Ersetzung durch if / else anders zu machen. Meiner Meinung nach bräuchte man da eine Hashtable, in die man dann Schlüssel / Werte Paare reinstopft. Z.b.

Schlüssel = "GL_SMOOTH" (string!)
Wert = GL_SMOOTH (als GLenum!)

Dann macht man eine Abfrage

if(hastable.contains(t[3]))
renderer.setShadeMode(hashtable.getValue(t[3]);
else
konsole.printText("Unknown value: "+t[3]);


Aber auch hier muß man mit if / else wieder checken, ob die Eingabe überhaupt Sinn macht (sprich als Schlüssel / Werte Paar in der Hastable drin ist).

Grüße, Jan

Einfachkrank
2003-02-09, 18:20:08
Hi,

erst mal danke für eure Hilfe, aber ich glaub ich werde das ganze erst mal durch leichtes Hardcoding umsetzen und mal sehen, was ich hinbekomme... Hab da so ne kleine Vorstellung wie ichs mach :)

MFG Einfachkrank

zeckensack
2003-02-09, 21:04:14
Ich habe da mal früher seeehr unsauberen Code in der Richtung produziert :D

Folgendes Konzept für Variablen:
Man deklariere eine Klasse, die sich selbst im Konstruktor bei der Konsole 'anmeldet', und ihr einen String mit ihrem Namen übergibt, sowie ein Flag das angibt, ob sie in der Konfigurationsdatei gespeichert werden will.
Die Konsole nimmt den String entgegen, und bei der ersten Eingabe wird der so entstandene Stringhaufen erstmal sortiert. Dann sucht die Konsole mit Binary Search innerhalb der Strings nach dem eingegebenen Token. Außerdem behält die Konsole einen Zeiger auf das Objekt, um die Zuweisung von Werten durchführen zu können.

Beispiel für eine solche Variable:
fvar i_mouse_sensitivity_x("i_mouse_sensitivity_x",1.0f,true);

Das ist eine Float-Variable (fvar), und sie heißt im Code genau wie später in der Konsole 'i_mouse_sensitivity_x'.

Konstruktor:fvar::fvar(const char*const name,float value,bool persist):
name(name),
persist(persist)
{
get_console()->register_fvar(this,name);
if (persist&&config_contains_value(name))
{
this->value=get_float_from_config(name);
}
else
{
this->value=value;
}
}

Destruktor:fvar::~fvar()
{
get_console()->deregister(this,name);
if (persist) set_config(name,value);
}


Das ganze funzt erstaunlich gut, dabei gibt's aber eine Einschränkungen, damit man es im ganzen Projekt einsetzen kann:
get_console() muß eine globale Funktion sein, die einen Zeiger auf die Konsole zurückliefert. Wenn das Konsolen-Objekt nicht existiert, muß es innerhalb dieser Funktion erzeugt werden.

Ansonsten gibt es mit Sicherheit elegantere Lösungen, aber wenigstens bleiben mit dieser Technik die Variablen schön überschaubar. Im Gegenzug ist das Konsolen-Objekt dann recht aufwendig, weil es fast alle Fäden in der Hand halten muß.

Ist also nur als Vorschlag und Idee zu sehen, und nicht als der Weisheit letzter Schluß.

Gnafoo
2003-02-10, 12:48:50
Oder schau dir doch mal SDL_Console an .. das
is ne relativ primitive Konsole für SDL Anwendungen
(www.libsdl.org) .. die Blitting-Befehle etc. kann man
denke ich relativ leicht auf andere APIs übertragen.

findet man auf: http://sdlconsole.tuxfamily.org/

Hab ich mir selber aber auch noch nicht genau angesehen.

PS: Open Source rockt :D

Einfachkrank
2003-02-10, 19:34:53
Hi,

passt zwar wieder mal nicht zum Thema, aber ich hab da en Problem, wenn ich jetzt meine Projekte in immer mehr Dateien aufgliedere. Mir passiert in letzter Zeit immer öfter, dass ich Probleme in der Ordnung zwischen Header Dateien habe. In verschiedenen Header Dateien habe ich Klassen deklariert und brauche für alle, bestimmte Standartheader wie stdio.h und windows.h. Wenn ich dann aber meine eigenen Headerdateien in die Mainheader-Datei includiere, dann prallen die Standartheader-Dateien aufeinander(also die stdio.h und windows.h)...

Wie kann ich das Problem beseitigen? Oder kann ich einen Prototyp einer Klasse deklarieren(jetzt allgemein gesehen, so dass ich die eigentliche Klasse in einer Quellcodedatei deklariere)?

MFG Einfachkrank

Abe Ghiran
2003-02-10, 20:39:05
Ist das nicht diese Art von mehrfach include, die man mit Hilfe von ifndef verhindern kann / sollte?
Also in die Headerdatei renderer.h am Anfang ein

#ifndef INCLUDE_RENDERER
#define INCLUDE_RENDERER

Damit sollte man das bei den eigenen Header dateien verhindern können, auch wenn jetzt irgendwo, z.b. im LogManager direkt der renderer eingebunden wird und dann noch mal wegen der konsole noch ein zweites mal indirekt, sollte das obrige Makro dafür sorgen, das renderer.h doch nur einmal im code (nach dem Präprozessorlauf) des LogManagers landet.
Bei standard header Dateien ist dieser Mechanismus meine ich auch meist schon drin. Ich include z.B. an allen Ecken und win.h und ähnliches:

renderer.h enthält dann:
#include <windows.h>

log.h enthält dann:
#include <windows.h>
#include <renderer.h>

Obwohl hier windows.h doppelt eingebunden werden müsste, habe ich da keine Fehlermeldung. Also steckt wohl auch in win.h dieser Mechanismus, müsste man mal nen Blick reinwerfen.

Edit: windows.h enthält ganz am Anfang ein

#ifndef _WINDOWS_H
#define _WINDOWS_H


Grüße, Jan

Einfachkrank
2003-02-10, 20:54:46
Boah, das versteh ich jetzt nicht so auf Anhieb! Hilft mir das dann auch, wenn ich in verschiedenen Dateien windows.h mehrmals eingebunden habe?

Xmas
2003-02-10, 21:38:43
Generell sollte in jeder deiner Header-Dateien sowas am Anfang:


#ifndef NameDerHeaderdatei_INCLUDED
#define NameDerHeaderdatei_INCLUDED

#pragma once


und dies am Ende stehen:


#endif


Damit wird der enthaltene Code bei jedem Compilerpass nur ein einziges Mal eingebunden. Bei allen Standard-Headern ist dies schon drin, deswegen verstehe ich nicht ganz wo dein Problem liegt.

Header solltest du grundsätzlich dort einbinden wo du sie auch benötigst, und auch in jeder Datei wo du sie benötigst. Sprich, wenn du in einem Modul Funktionen aus x.h und y.h benötigst, dann solltest du auch beide einbinden, selbst wenn y.h von x.h schon eingebunden wird.

zeckensack
2003-02-11, 06:16:21
Ich hab' mir angewhöhnt, Header nie in anderen Headern einzubinden, sondern immer nur in den Code-Moduelen.

Sprich, a.cpp braucht a.h
In a.h wird eine Klasse gebraucht, die in b.h deklariert ist.

Dann steht in a.cpp das hier ganz oben:#include "b.h"
#include "a.h"

Wenn's unübersichtlich wird, macht man sich eine Datei "all_headers", die alle im Projekt gebrauchten Headers in der richtigen Reihenfolge einbindet.

Die kann man dann in den Code-Modulen einbinden.

Im Techno-Forum gibt's den kompletten Quellcode zu meinem Mandelbrötchen, da benutze ich das, wenn ihr mal schauen wollt :)

Abe Ghiran
2003-02-11, 10:13:37
Originally posted by zeckensack
Ich hab' mir angewhöhnt, Header nie in anderen Headern einzubinden, sondern immer nur in den Code-Moduelen.


So weit so gut. Ich hatte z.B. immer sowas:

/* LogSystem.h */
#include <windows.h>

void setupLogWindow(HINSTANCE hThisInstance, int nFensterStil);

Du würdest jetzt das #include windows.h weglassen. Das habe ich mal ausprobiert, in der Erwartung, das er das dann nicht mehr geht, weil ihm HINSTANCE unbekannt ist.
Es lässt sich aber compilieren (sonst könntest du ja auch kaum so programmieren ;)) aber meine Frage ist jetzt: warum geht das?
:idea: Ich Blödian, klar, der Präporzessor kopiert ja tatsächlich den kompletten Text in die .c Datei und wenn dann in der vorher include windows.h drin steht, ist wieder alles in Butter. Ist das so richtig?
Irgendwie hänge ich immer noch so in dem java denken drin :bonk:.

Jan

zeckensack
2003-02-11, 12:38:20
Richtig. Du mußt nur auf die Reihenfolge achten. :)

Wenn du in einem .cpp einen Header einbindest, in dem Typen benutzt werden die anderswo definiert sind (also HINSTANCE in deinem Fall), dann mußt du zuerst diesen Header (->windows.h) einbinden.

Davon abgesehen funzt's. Der Compiler kriegt die Deklarationen immer in der richtigen Reihenfolge und hat keinen Grund zu motzen. Der eigentliche Grund warum ich das so mache, ist daß es dann unmöglich wird zirkuläre Abhängigkeiten zu kriegen.
Gut, die #define/#ifdef-Geschichte kann das auch, aber da steh' ich nicht so drauf ;)

Xmas
2003-02-11, 13:07:32
Ich mache es deswegen anders, weil IMHO die Abhängigkeit von anderen Headern verborgen sein sollte. Denn ob z.B. eine Klasse B nur durch Vererbung von Klasse A ihre Funktionalität erreicht, ist in manchen Fällen gar nicht so wichtig. Aber wenn ich A in meinem Code-Modul nicht nutze, wieso sollte ich es dann einbinden?

Die all_headers-Idee mag ich nicht weil dann so manche Zeile unnötig kompiliert wird.

Einfachkrank
2003-02-11, 16:01:50
Hi,

ach, sorry, es hat ein wenig klick gemacht, dass mit dem #ifndef - #define und #endif ist jetzt klar und funktioniert auch gut...

Nur noch das #pragma kenn ich noch nicht... Was machtn das jetzt noch?

MFG Einfachkrank

Xmas
2003-02-11, 16:19:10
Originally posted by Einfachkrank
Nur noch das #pragma kenn ich noch nicht... Was machtn das jetzt noch?
#pragma once ist AFAIK MS-spezifisch und verhindert, dass die Datei bei einem Compilerdurchlauf mehrmals geöffnet wird. Damit macht es prinzipiell dasselbe wie die #ifndef-Kombination, ist aber noch effizienter.