Pseudo-3d-Spiel Straßengenerierung (C++,SFML)
-
Hallo Leute,
ich hätte einige Fragen zum Thema Pseudo-3D-Spiele. Vielen Dank für jede Hilfe im Voraus.Es geht darum eine Autobahn mit baulich getrennten Richtungsfahrbahnen und beliebig vielen Fahrstreifen zu generieren.
Eine Straße funktioniert noch, aber bei der linken Fahrbahn musste ich schon tricksen und eine neue Funktion einfügen. Gibt es da keine bessere Möglichkeit, zumal die Performance jetzt schon nicht mehr so gut ist? Die linke Fahrbahn ist exemplarisch ohne Markierung, jede weitere Markierung verschlechtert die Performance.Gibt es da andere Möglichkeiten multiple Straßen zu machen und vor allem auch mit noch mehr versch. Fahrbahnmarkierungen (z.b. 4 spurig)? Sollte man einfach die ConvexShapes texturieren? Hab das mal probiert und es sah fürchterlich aus (vllt. hab ich was falsch gemacht) und die Performance war vllt. ein Frame pro Sekunde.
Hab bei youtube Videos gesehen, wo das funktioniert hat und wo es sogar Ausfahrten und Einfahrten gab. Ist der ganze Ansatz falsch? (Hab den Source-Code irgendwo im Netz gefunden und daran herumexperimentiert) Bei manchen youtube Videos haben die eine Bildrate von bis zu 60 fps erreicht.
Außerdem sind mir bei den Übergängen so Unebenheiten aufgefallen, wo ich auch nicht weiterkomme...scheint irgendwas mit der sinus-Formel (Zeile 63) nicht richtig zu sein??, wenn ein Hügel kommt oder endet.
#include <SFML/Graphics.hpp> using namespace sf; int width = 1024; int height = 768; int roadW = 2500; int segL = 200; //segment length float camD = 0.84; //camera depth void drawQuad(RenderWindow &w, Color c, int x1,int y1,int w1,int x2,int y2,int w2) // right two lanes { ConvexShape shape(4); shape.setFillColor(c); shape.setPoint(0, Vector2f(x1-w1,y1)); shape.setPoint(1, Vector2f(x2-w2,y2)); shape.setPoint(2, Vector2f(x2+w2,y2)); shape.setPoint(3, Vector2f(x1+w1,y1)); w.draw(shape); } void drawQuad2(RenderWindow &w, Color c, int x1,int y1,int w1,int x2,int y2,int w2) // left two lanes { ConvexShape shape(4); shape.setFillColor(c); shape.setPoint(0, Vector2f(x1-w1*3.8,y1)); shape.setPoint(1, Vector2f(x2-w2*3.8,y2)); shape.setPoint(2, Vector2f(x2-w2*1.3,y2)); shape.setPoint(3, Vector2f(x1-w1*1.3,y1)); w.draw(shape); } struct Line { float x,y,z; //3d center of line float X,Y,W; //screen coord float curve,spriteX,clip,scale; Sprite sprite; Line() {spriteX=curve=x=y=z=0;} void project(int camX,int camY,int camZ) { scale = camD/(z-camZ); X = (1 + scale*(x - camX)) * width/2; Y = (1 - scale*(y - camY)) * height/2; W = scale * roadW * width/2; } }; int main() { RenderWindow app(VideoMode(width, height), "Outrun Racing!"); std::vector<Line> lines; for(int i=0;i<1600;i++) { Line line; line.z = i*segL; if (i>300 && i<700) line.curve=0.5; if (i>1100) line.curve=-0.7; if (i>750) line.y = sin(i/30.0)*1500; lines.push_back(line); } int N = lines.size(); float playerX = 0; int pos = 0; int H = 1500; while (app.isOpen()) { Event e; while (app.pollEvent(e)) { if (e.type == Event::Closed) app.close(); } int speed=0; if (Keyboard::isKeyPressed(Keyboard::Right)) playerX+=0.1; if (Keyboard::isKeyPressed(Keyboard::Left)) playerX-=0.1; if (Keyboard::isKeyPressed(Keyboard::Up)) speed=200; if (Keyboard::isKeyPressed(Keyboard::Down)) speed=-200; if (Keyboard::isKeyPressed(Keyboard::Tab)) speed*=3; if (Keyboard::isKeyPressed(Keyboard::W)) H+=100; if (Keyboard::isKeyPressed(Keyboard::S)) H-=100; pos+=speed; while (pos >= N*segL) pos-=N*segL; while (pos < 0) pos += N*segL; app.clear(Color(150,210,220)); int startPos = pos/segL; int camH = lines[startPos].y + H; int maxy = height; float x=0,dx=0; ///////draw road//////// for(int n = startPos; n<startPos+300; n++) { Line &l = lines[n%N]; l.project(playerX*roadW-x, camH, startPos*segL - (n>=N?N*segL:0)); x+=dx; dx+=l.curve; l.clip=maxy; if (l.Y>=maxy) continue; maxy = l.Y; Color grass = (n/3)%2?Color(16,200,16):Color(0,154,0); Color rumble = (n/3)%2?Color(255,255,255):Color(255,255,255); Color road = (n/3)%2?Color(107,107,107):Color(105,105,105); Color pasy = (n/3)%2?Color(255,255,255):Color(105,105,105); Line p = lines[(n-1)%N]; //previous line drawQuad(app, grass, 0, p.Y, width, 0, l.Y, width); drawQuad(app, rumble,p.X, p.Y, p.W*1.1, l.X, l.Y, l.W*1.1); drawQuad(app, road, p.X, p.Y, p.W, l.X, l.Y, l.W); drawQuad(app, pasy, p.X, p.Y, p.W/20, l.X, l.Y, l.W/20); drawQuad2(app, road, p.X, p.Y, p.W, l.X, l.Y, l.W); } app.display(); } return 0; }
-
Zeig mal das Youtubevideo, wo man sieht, wie es denn aussehen soll und wie du es bei dir dann auch hinbekommen willst.
-
Bei folgendem Youtubelink geht alles sehr schnell:
https://www.youtube.com/watch?v=W56uWnzkLl8Man beachte die vielen und unzähligen Fahrstreifen und es gibt sogar eine Art Teilung und Zusammenfügung der Autobahn. Würde gerne auch sowas und auch richtige Ausfahrten zu anderen Straßen machen wollen. Man müsste nur irgendwie eine (neue) Straße in einem best. Segment verbiegen und dann bei Kollision des Wagens mit einem unsichtbaren Auslöser eine neue Instanz/Strecke auslösen oder so?
Bei mir muss ich für jede Markierung eine eigene Funktion erstellen, wie man am unteren Code sehen kann. Die Performance ist besonders im hügeligen Terrain nicht so gut, möchte aber nicht noch mehr Sichtweite opfern. Möchte mir nicht ausmalen, wie schlecht das wird, wenn ich noch mehr Fahrstreifen und Sprites hinzufüge. Gibt es da andere, bessere Ansätze?
#include <SFML/Graphics.hpp> using namespace sf; int width = 1024; int height = 768; int roadW = 2500; int segL = 200; //segment length float camD = 0.84; //camera depth void drawQuad(RenderWindow &w, Color c, int x1,int y1,int w1,int x2,int y2,int w2) // right two lanes { ConvexShape shape(4); shape.setFillColor(c); shape.setPoint(0, Vector2f(x1-w1,y1)); shape.setPoint(1, Vector2f(x2-w2,y2)); shape.setPoint(2, Vector2f(x2+w2,y2)); shape.setPoint(3, Vector2f(x1+w1,y1)); w.draw(shape); } void drawQuad2(RenderWindow &w, Color c, int x1,int y1,int w1,int x2,int y2,int w2) // outer road marking of left lanes { ConvexShape shape(4); shape.setFillColor(c); shape.setPoint(0, Vector2f(x1-w1*3.9,y1)); shape.setPoint(1, Vector2f(x2-w2*3.9,y2)); shape.setPoint(2, Vector2f(x2-w2*1.2,y2)); shape.setPoint(3, Vector2f(x1-w1*1.2,y1)); w.draw(shape); } void drawQuad3(RenderWindow &w, Color c, int x1,int y1,int w1,int x2,int y2,int w2) // left two lanes { ConvexShape shape(4); shape.setFillColor(c); shape.setPoint(0, Vector2f(x1-w1*3.8,y1)); shape.setPoint(1, Vector2f(x2-w2*3.8,y2)); shape.setPoint(2, Vector2f(x2-w2*1.3,y2)); shape.setPoint(3, Vector2f(x1-w1*1.3,y1)); w.draw(shape); } void drawQuad4(RenderWindow &w, Color c, int x1,int y1,int w1,int x2,int y2,int w2) // inner road marking of left lanes { ConvexShape shape(4); shape.setFillColor(c); shape.setPoint(0, Vector2f(x1-w1*2.55,y1)); shape.setPoint(1, Vector2f(x2-w2*2.55,y2)); shape.setPoint(2, Vector2f(x2-w2*2.45,y2)); shape.setPoint(3, Vector2f(x1-w1*2.45,y1)); w.draw(shape); } struct Line { float x,y,z; //3d center of line float X,Y,W; //screen coord float curve,spriteX,clip,scale; Sprite sprite; Line() {spriteX=curve=x=y=z=0;} void project(int camX,int camY,int camZ) { scale = camD/(z-camZ); X = (1 + scale*(x - camX)) * width/2; Y = (1 - scale*(y - camY)) * height/2; W = scale * roadW * width/2; } }; int main() { RenderWindow app(VideoMode(width, height), "Outrun Racing!"); std::vector<Line> lines; for(int i=0;i<1600;i++) { Line line; line.z = i*segL; if (i>300 && i<700) line.curve=0.5; if (i>300 && i<700) line.curve=0.5; if (i>1100) line.curve=-0.7; if (i>750) line.y = sin(i/30.0)*1500; lines.push_back(line); } int N = lines.size(); float playerX = 0; int pos = 0; int H = 1500; while (app.isOpen()) { Event e; while (app.pollEvent(e)) { if (e.type == Event::Closed) app.close(); } int speed=0; if (Keyboard::isKeyPressed(Keyboard::Right)) playerX+=0.1; if (Keyboard::isKeyPressed(Keyboard::Left)) playerX-=0.1; if (Keyboard::isKeyPressed(Keyboard::Up)) speed=200; if (Keyboard::isKeyPressed(Keyboard::Down)) speed=-200; if (Keyboard::isKeyPressed(Keyboard::Tab)) speed*=3; if (Keyboard::isKeyPressed(Keyboard::W)) H+=100; if (Keyboard::isKeyPressed(Keyboard::S)) H-=100; pos+=speed; while (pos >= N*segL) pos-=N*segL; while (pos < 0) pos += N*segL; app.clear(Color(150,210,220)); int startPos = pos/segL; int camH = lines[startPos].y + H; int maxy = height; float x=0,dx=0; ///////draw road//////// for(int n = startPos; n<startPos+300; n++) { Line &l = lines[n%N]; l.project(playerX*roadW-x, camH, startPos*segL - (n>=N?N*segL:0)); x+=dx; dx+=l.curve; l.clip=maxy; if (l.Y>=maxy) continue; maxy = l.Y; Color grass = (n/3)%2?Color(16,200,16):Color(0,154,0); Color rumble = (n/3)%2?Color(255,255,255):Color(255,255,255); Color road = (n/3)%2?Color(107,107,107):Color(105,105,105); Color pasy = (n/3)%2?Color(255,255,255):Color(105,105,105); Line p = lines[(n-1)%N]; //previous line drawQuad(app, grass, 0, p.Y, width, 0, l.Y, width); drawQuad(app, rumble,p.X, p.Y, p.W*1.1, l.X, l.Y, l.W*1.1); drawQuad(app, road, p.X, p.Y, p.W, l.X, l.Y, l.W); drawQuad(app, pasy, p.X, p.Y, p.W/20, l.X, l.Y, l.W/20); drawQuad2(app, rumble, p.X, p.Y, p.W, l.X, l.Y, l.W); // outer road marking of left lanes drawQuad3(app, road, p.X, p.Y, p.W, l.X, l.Y, l.W); // left lanes drawQuad4(app, pasy, p.X, p.Y, p.W, l.X, l.Y, l.W); // inner road marking of left lanes } app.display(); } return 0; }
-
Ich würde erstmal auf die Verwendung von ConvexShape verzichten und stattdressen zwei Dreiecke zeichnen.
Außerdem würde ich erstmal ein Performancetest machen, wo du in einer Schleife sagen wir mal 10000 Dreicke/Quads/ConvexShapes,... zeichnest, und vergleichst, wie lange dieser Zeichenvorgang dauert. So bekommst du erstmal ein Gefühlt dafür, was SFML maximal überhaupt kann und ob dein Spiel damit dann theoretisch damit gehen könnte.
Außerem sieht die Straße da im Video schon recht 3D aus. Du selbst schreibst was von Pseude-3D. Was meinst du damit?
Unter 3D verstehe ich, wenn ich eine Liste von Dreiecken in Weltkoordinaten definiere, und diese zusammen mit einer Kamera- und Projektionsmatrix in 2D-Dreiecke umrechne und zeichne.
-
-
Mit psuedo 3d meine ich kein 3d mit echter landschaftlicher Tiefe, sondern wo alles nur gefaked ist.
Ich glaube, es gehen wirklich nur ConvexShapes, wenn man den "polygonalen Quad-Ansatz" wählt, weil man echte Recht- oder Dreiecke nicht verzerren kann, zumindest nicht in SFML. Man könnte natürlich auch ein Dreieck aus nem ConvexShape machen, aber dann bräuchte man doppelt so viele Dreiecke.
Habe das festgestellt, als ich die Performance von ConvexShapes und Dreiecken testen wollte. Wollte ja eigentlich den Convex Ansatz, aber der ist wohl zu rechenintensiv.
Eine Seite, die auch solche Spiele und die verschiedenen Ansätze untersucht, ist: https://demo.joocial.com/2022-joomla-developer/developer-news/14130-lou-s-pseudo-3d-page-extentofthejam-com.html
Ein schönes, flüssiges Beispiel (online spielbar) mit polygonalem Ansatz in javascript ist: http://codeincomplete.com/posts/javascript-racer-v1-straight/
Sieht auch schöner aus als die alten Pixel-Spiele. Warum läuft das flüssiger als mein Code-Beispiel?Die Youtube-Methode ist das mit der Herangehensweise, wo die Farbpalette zeilenweise verändert und die Grafik zeilenweise verzerrt wird. Ich habe Schwierigkeiten mir aus SFML-Sicht vorzustellen, wie man so eine mit Pixeln befüllte Linie in einem festen Bild verzerrt oder wo man einfach je Zeile die Farben verändert. Ob SFML sowas kann?
-
Ich weiß nicht ob es dir hilft, aber ich habe mal den Mode 7 in SDL2 umgesetzt. Ursprünglich kommt diese Idee aus diesem Artikel:
http://www.helixsoft.nl/articles/circle/sincos.htm
Vielleicht hilft es dir. Ich ziege dir hier mal meine Klasse:#ifndef CGRAPHIC_H #define CGRAPHIC_H #include <SDL2/SDL.h> typedef struct mode7params { int space_z; int horizon; int scalex,scaley; }; class cgraphic { public: cgraphic(int resx,int resy); virtual ~cgraphic(); void render(SDL_Surface* screen); void SetFullscreen(int i); void mode7(SDL_Surface *bmp,SDL_Surface *tile, float angle,float cx, float cy, mode7params *params); void putpixel(SDL_Surface *bmp ,int x,int y,int c); Uint32 getpixel(SDL_Surface *bmp ,int x,int y); SDL_Surface* screen; protected: private: SDL_Window *window; }; #endif // CGRAPHIC_H
#include "cgraphics.h" #include <stdio.h> #include <math.h> cgraphic::cgraphic(int resx,int resy) { SDL_Init(SDL_INIT_VIDEO); window = SDL_CreateWindow( "Mode7", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, resx, resy, SDL_WINDOW_OPENGL|SDL_WINDOW_FULLSCREEN ); if (window == NULL) { printf("Could not create window: %s\n", SDL_GetError()); delete this; } screen = SDL_GetWindowSurface( window ); } cgraphic::~cgraphic() { SDL_DestroyWindow(window); SDL_Quit(); } void cgraphic::render(SDL_Surface* screen) { SDL_UpdateWindowSurface(window); } void cgraphic::SetFullscreen(int i) { SDL_SetWindowFullscreen(window,i); } void cgraphic::mode7 (SDL_Surface *screen,SDL_Surface *tile, float angle,float cx, float cy, mode7params *params) { float screenx,screeny, distance,horizonscale; int maskx=tile->w-1, masky=tile->h-1; float spacex,spacey, linex,liney; for (screeny=140; screeny < screen->h;screeny++) { distance = (params->space_z * params->scaley) / (screeny + params->horizon); horizonscale=(distance / params->scalex); linex= (-SDL_sinf(angle) * horizonscale); liney= (SDL_cosf(angle) * horizonscale); spacex=cx+(distance * SDL_cosf(angle))- screen->w/2 * linex; spacey=cy+(distance * SDL_sinf(angle))- screen->h/2 * liney; for (screenx=0; screenx < screen->w;screenx++) { putpixel (screen, (int)screenx, (int)screeny, getpixel (tile, (int)spacex &maskx, (int)spacey &masky )); spacex += linex; spacey += liney; } } } void cgraphic::putpixel(SDL_Surface *bmp,int x, int y, int c) { int bpp = bmp->format->BytesPerPixel; /* Here p is the address to the pixel we want to set */ Uint8 *p = (Uint8 *)bmp->pixels + y * bmp->pitch + x * bpp; switch(bpp) { case 1: *p = c; break; case 2: *(Uint16 *)p = c; break; case 3: if(SDL_BYTEORDER == SDL_BIG_ENDIAN) { p[0] = (c >> 16) & 0xff; p[1] = (c >> 8) & 0xff; p[2] = c & 0xff; } else { p[0] = c & 0xff; p[1] = (c >> 8) & 0xff; p[2] = (c >> 16) & 0xff; } break; case 4: *(Uint32 *)p = c; break; } } Uint32 cgraphic::getpixel(SDL_Surface *bmp, int x, int y) { int bpp = bmp->format->BytesPerPixel; Uint8 *p = (Uint8 *)bmp->pixels + y * bmp->pitch + x * bpp; switch(bpp) { case 1: return *p; break; case 2: return *(Uint16 *)p; break; case 3: if(SDL_BYTEORDER == SDL_BIG_ENDIAN) return p[0] << 16 | p[1] << 8 | p[2]; else return p[0] | p[1] << 8 | p[2] << 16; break; case 4: return *(Uint32 *)p; break; default: return 0; } }
#include <iostream> #include "cgraphics.h" #include <SDL2/SDL.h> using namespace std; cgraphic *g; SDL_Event e; SDL_Surface *tile,*bg; float posx=100,posy=100; int quit; float angle=0; bool moveup,turnleft,turnright; mode7params params; void ControlCheck(); int main() { g=new cgraphic(800,600); tile=SDL_LoadBMP("map2.bmp"); bg=SDL_LoadBMP("bg.bmp"); params.horizon=-130; params.scalex=500; params.scaley=500; params.space_z=15; while (!quit) { if (moveup==true) { posx+=SDL_cosf(angle)*1.5; posy+=SDL_sinf(angle)*1.5; } if(turnleft==true) angle-=0.01; if(turnright==true) angle+=0.01; SDL_FillRect(g->screen,NULL,SDL_MapRGB(g->screen->format,0x00,0x00,0x00)); SDL_BlitSurface(bg,NULL,g->screen,NULL); g->mode7(g->screen,tile,angle,posx,posy,¶ms); g->render(g->screen); ControlCheck(); } delete g; return 0; } void ControlCheck() { while(SDL_PollEvent(&e)) { const Uint8*KeyStates=SDL_GetKeyboardState(NULL); turnleft=false; turnright=false; moveup=false; if (KeyStates[SDL_SCANCODE_ESCAPE]) { quit=true; } //-------------------------------------- if (KeyStates[SDL_SCANCODE_UP]) { moveup=true; } if (KeyStates[SDL_SCANCODE_DOWN]) { posx-=SDL_cosf(angle)*2; posy-=SDL_sinf(angle)*2; } if (KeyStates[SDL_SCANCODE_LEFT]) turnleft=true; if (KeyStates[SDL_SCANCODE_RIGHT]) turnright=true; if(e.type==SDL_QUIT) quit=true; } }
Viele Grüße
Guitarlo