• Willkommen im Linux Club - dem deutschsprachigen Supportforum für GNU/Linux. Registriere dich kostenlos, um alle Inhalte zu sehen und Fragen zu stellen.

[Gelöst] [C++, Outrun] Warum geht das?

abgdf

Guru
Hallo,

die Frage ist vielleicht eher was für ein C++-Programmierforum, aber vielleicht kann ja jemand hier auch was damit anfangen. Also: Hier hat jemand sehr eindrucksvoll gezeigt, wie man sehr kurz (das klassische Pseudo-3D-Spiel) "Outrun" bauen kann. Benutzt wird SFML (sowas ähnliches wie SDL); es sollte die neueste Version sein.
In der Videobeschreibung ist auch der Code mit den Grafiken ("images/1.png", usw.). Ich hab' den Code noch etwas aufgeräumt, hier:
Code:
#include <SFML/Graphics.hpp>
using namespace sf;

int WIDTH = 1024;
int HEIGHT = 768;
int ROADW = 2000;
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) {
    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);
}

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;
    }

    void drawSprite(RenderWindow &app) {
        Sprite s = sprite;
        int w = s.getTextureRect().width;
        int h = s.getTextureRect().height;

        float destX = X + scale * spriteX * WIDTH / 2;
        float destY = Y + 4;
        float destW  = w * W / 266;
        float destH  = h * W / 266;

        destX += destW * spriteX; //offsetX
        destY -= destH;    //offsetY

        float clipH = destY + destH - clip;
        if (clipH < 0) {
            clipH = 0;
        }
        if (clipH >= destH) {
            return;
        }
        s.setTextureRect(IntRect(0, 0, w, h - h * clipH / destH));
        s.setScale(destW / w, destH / h);
        s.setPosition(destX, destY);
        app.draw(s);
        /*
        printf("w: %d\n", w);
        printf("h: %d\n", h);
        printf("destX: %f\n", destX);
        printf("destY: %f\n", destY);
        printf("destW: %f\n", destW);
        printf("destH: %f\n", destH);
        printf("spriteX: %f\n", spriteX);
        printf("\n");
        */
    }
};


int main() {
    RenderWindow app(VideoMode(WIDTH, HEIGHT), "Outrun Racing!");
    app.setFramerateLimit(60);

    Texture t[50];
    Sprite object[50];
    for(int i=1;i<=7;i++) {
        t[i].loadFromFile("images/"+std::to_string(i)+".png");
        t[i].setSmooth(true);
        object[i].setTexture(t[i]);
    }
    Texture bg;
    bg.loadFromFile("images/bg.png");
    bg.setRepeated(true);
    Sprite sBackground(bg);
    sBackground.setTextureRect(IntRect(0,0,5000,411));
    sBackground.setPosition(-2000,0);

    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;
        }
        if (i < 300 && i % 20 == 0) {
            line.spriteX = -2.5;
            line.sprite = object[5];
        }
        if (i % 17 == 0) {
            line.spriteX = 2.0;
            line.sprite = object[6];
        }
        if (i > 300 && i % 20 == 0) {
            line.spriteX = -0.7;
            line.sprite = object[4];
        }
        if (i > 800 && i % 20 == 0) {
            line.spriteX = -1.2;
            line.sprite = object[1];
        }
        if (i == 400) {
            line.spriteX = -1.2;
            line.sprite = object[7];
        }
        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(105,205,4));
        app.draw(sBackground);
        int startPos = pos / SEGL;
        int camH = lines[startPos].y + H;
        if (speed > 0) {
            sBackground.move(-lines[startPos].curve * 2, 0);
        }
        if (speed < 0) {
            sBackground.move(lines[startPos].curve * 2, 0);
        }

        int maxy = HEIGHT;
        float x  = 0;
        float dx = 0;

        ///////draw road////////
        for(int i = startPos; i < startPos + 300; i++) {
            Line &l = lines[i % N];
            if (i >= N) {
                l.project(playerX * ROADW - x, camH, (startPos - N) * SEGL);
            } else {
                l.project(playerX * ROADW - x, camH, startPos * SEGL);
            }
            x += dx;
            dx += l.curve;
            l.clip = maxy;
            if (l.Y >= maxy) {
                continue;
            }
            maxy = l.Y;

            Color grass  = (i/3) % 2 ? Color(16,200,16)   : Color(0,154,0);
            Color rumble = (i/3) % 2 ? Color(255,255,255) : Color(0,0,0);
            Color road   = (i/3) % 2 ? Color(107,107,107) : Color(105,105,105);

            Line p = lines[(i-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.2, l.X, l.Y, l.W * 1.2);
            drawQuad(app, road,   p.X, p.Y, p.W,       l.X, l.Y, l.W);
        }

        ////////draw objects////////
        for (int i = startPos + 300; i > startPos; i--) {
            lines[i % N].drawSprite(app);
        }
        app.display();
    }

    return 0;
}
Ich kann das mit folgender Zeile kompilieren (wobei SFML sich in "/usr/local/lib" und "/usr/local/include/" befindet):
Code:
g++ main.cpp -o outrun -std=c++11 -I/usr/local/include/SFML -L/usr/local/lib -lsfml-audio -lsfml-graphics -lsfml-window -lsfml-system
Ich möchte das gern nach Python übersetzen. Es gibt Python-Bindings "PySFML"; und es geht auch schon alles bis auf die Palmen (und die anderen Sprites). Auf die es mir aber gerade ankam.

Es werden im Code 1600 "Lines"-Objekte erzeugt. Weiterhin gibt es ein Array mit 7 Sprites, für jedes Bild ein Sprite. Bestimmten "Lines" werden die Sprites aus dem Array (wiederholt) zugewiesen.
Dann geht eine Schleife über einen Teil der "Lines" und ruft jeweils die Methode "drawSprite()" auf. Im Ergebnis werden so die Sprites auf dem Bildschirm dargestellt.
Mein Problem ist jetzt: Nur einem Teil der "Lines" werden Sprites zugewiesen. In den anderen heißt es nur:
Code:
Sprite sprite;
Die Klasse hat also ein Attribut "sprite" vom Typ "Sprite". Das aber nicht näher defniert wird. Mit den Python-Bindings geht das nicht. Wenn man dort "Sprite" instantiiert, wird verlangt, daß man die zugehörige "Texture" mitliefert (gut, was man noch umgehen könnte, wenn man eine "Default-Texture" verwenden würde - aber das Zeichnen an der richtigen Stelle klappt trotzdem nicht).
Weiterhin heißt es in der Methode "drawSprite()":
Code:
Sprite s = sprite;
Was soll das? Es wird also ein Objekt "s" vom Typ "Sprite" erzeugt, dem "sprite" zugewiesen wird. Wozu?
Das Merkwürdige ist: Das Zeichnen der Palmen, usw. funktioniert in der C++-Version ohne diese Zeile nicht, wenn man also das ursprüngliche "sprite" statt "s" verwendet. Das verstehe ich nicht. Versteht das jemand? Würde mich freuen.
 
abgdf schrieb:
Es wird also ein Objekt "s" vom Typ "Sprite" erzeugt, dem "sprite" zugewiesen wird. Wozu?
Hier wird die class Sprite neu allokiert und dann s zugewiesen. &sprite ist der Prototyp.

abgdf schrieb:
Das Merkwürdige ist: Das Zeichnen der Palmen, usw. funktioniert in der C++-Version ohne diese Zeile nicht,...
Das funktioniert sicher aber so schnell kannst du nicht schaun, weil immer dasselbe Objekt .. siehe mein Satz vorher.

abgdf schrieb:
... wird verlangt, daß man die zugehörige "Texture" mitliefert
Class Sprite ist nicht Funktion Sprite();
Hier entlang:
https://www.sfml-dev.org/documentation/2.5.1/Sprite_8hpp_source.php

Gruß
Gräfin Klara
 

gehrke

Administrator
Teammitglied
Gräfin Klara schrieb:
>> Es wird also ein Objekt "s" vom Typ "Sprite" erzeugt, dem "sprite" zugewiesen wird. Wozu?
@Gräfin Klara: Ich bitte Dich nochmals, die Quoting-Funktionalität dieses Forums zu benutzen.
TNX
 
gehrke schrieb:
@Gräfin Klara: Ich bitte Dich nochmals, die Quoting-Funktionalität dieses Forums zu benutzen.
TNX
Klick,klick,klick und schon ist es weg.
Ich bin fürchterlich unordentlich.
Das werde ich ändern .. versprochen!

Gruß
Gräfin Klara
 
OP
A

abgdf

Guru
Gräfin Klara schrieb:
abgdf schrieb:
... wird verlangt, daß man die zugehörige "Texture" mitliefert
Class Sprite ist nicht Funktion Sprite();
Öhm, bin nicht sicher, ob ich Dich richtig verstehe. Es ging da jetzt um Python. Wenn man da ein Objekt instantiiert, wird dabei das "__init__()" der Klasse aufgerufen. Wenn diese Funktion noch ein Argument verlangt, muß man das mitgeben. Beispiel:
Code:
class MyObject:
    def __init__(self, argument):
        pass
MyObject()
Wird 'nen Fehler geben, weil man "argument" mitgeben muß, also z.B. "MyObject(5)".
----
Gräfin Klara schrieb:
abgdf schrieb:
Es wird also ein Objekt "s" vom Typ "Sprite" erzeugt, dem "sprite" zugewiesen wird. Wozu?
Hier wird die class Sprite neu allokiert und dann s zugewiesen. &sprite ist der Prototyp.
abgdf schrieb:
Das Merkwürdige ist: Das Zeichnen der Palmen, usw. funktioniert in der C++-Version ohne diese Zeile nicht,...
Das funktioniert sicher aber so schnell kannst du nicht schaun, weil immer dasselbe Objekt .. siehe mein Satz vorher.
Aha, die Funktion braucht also nochmal ein eigenes Objekt vom Typ "Sprite", mit eigenem Speicherbereich. Sie kann nicht das klassenweite Objekt dieses Typs verwenden. Das ist ja eigenartig.
Wenn ich das auch so in der Python-Übersetzung umsetze (für die Kopie des Objekts ging nicht "copy.deepcopy()"), dann geht es! Die Geschwindigkeit ist auch ganz in Ordnung. Danke!
Code:
#!/usr/bin/python
# coding: utf-8

from sfml import sf
import math

WIDTH = 1024.
HEIGHT = 768.
ROADW = 2000.
SEGL = 200
CAMD = 0.84

DEFAULTTEXTURE = sf.Texture.from_file("images/0.png")

class Line:

    def __init__(self):
        self.x = 0 # 3d center of line
        self.y = 0
        self.z = 0
        self.X = 0 # screen coord
        self.Y = 0
        self.W = 0
        self.scale = 0
        self.curve = 0
        self.spriteX = 0
        self.clip = 0
        self.sprite = sf.Sprite(DEFAULTTEXTURE)

    def setZ(self, a):
        self.z = a

    def setCurve(self, a):
        self.curve = a

    def setSprite(self, x, s):
        self.spriteX = x
        self.sprite  = s

    # from world to screen coordinates
    def project(self, camX, camY, camZ):
        # To avoid division by zero:
        if self.z == camZ:
            self.scale = 1
        else:
            self.scale = CAMD / (self.z - camZ)
        self.X = (1 + self.scale * (self.x - camX)) * WIDTH / 2.
        self.Y = (1 - self.scale * (self.y - camY)) * HEIGHT / 2.
        self.W = self.scale * ROADW * WIDTH / 2.

    def drawSprite(self, screen):
        s = sf.Sprite(self.sprite.texture)
        w = s.texture_rectangle.width
        h = s.texture_rectangle.height

        destX = self.X + self.scale * self.spriteX * WIDTH / 2.
        destY = self.Y + 4.
        destW  = w * self.W / 266.
        destH  = h * self.W / 266.

        destX += destW * self.spriteX
        destY -= destH

        clipH = destY + destH - self.clip
        if clipH < 0:
            clipH = 0
        if clipH >= destH:
            return
        s.texture_rectangle = sf.Rect((0, 0), (w, h - h * clipH / destH))
        s.scale((destW / w, destH / h))
        s.position = (destX, destY)
        screen.draw(s)

        """
        print "w: " + str(w)
        print "h: " + str(h)
        print "destX: " + str(destX)
        print "destY: " + str(destY)
        print "destW: " + str(destW)
        print "destH: " + str(destH)
        print "spriteX: " + str(self.spriteX)
        print
        """


class Main:

    def __init__(self):
        self.screen = sf.RenderWindow(sf.VideoMode(WIDTH, HEIGHT), unicode("Outrun Racing!"))
        self.screen.framerate_limit = 60
        self.sprites = []
        for i in range(1, 8, 1):
            texture = sf.Texture.from_file("images/" + str(i) + ".png")
            texture.smooth = True
            sprite = sf.Sprite(texture)
            self.sprites.append(sprite)

        self.bg = sf.Texture.from_file("images/bg.png")
        self.bg.smooth = True
        self.bg.repeated = True
        self.sBackground = sf.Sprite(self.bg)
        self.sBackground.texture_rectangle = sf.Rect((0,0), (5000,411))
        self.sBackground.position = (-2000, 0)
        self.lines = self.buildLines()
        self.nroflines = len(self.lines)
        self.playerX = 0
        self.pos = 0
        self.H = 1500
 
        while self.screen.is_open:

            self.speed = 0
            self.processEvents()

            self.pos += self.speed
            while self.pos >= self.nroflines * SEGL:
                self.pos -= self.nroflines * SEGL
            while self.pos < 0:
                self.pos += self.nroflines * SEGL
   
            self.screen.clear(sf.Color(105,205,4))
            self.screen.draw(self.sBackground)
            self.startPos = self.pos / SEGL
            self.camH = self.lines[self.startPos].y + self.H 
            if self.speed > 0:
                self.sBackground.move((-self.lines[self.startPos].curve * 2, 0))
            if self.speed < 0:
                self.sBackground.move((self.lines[self.startPos].curve * 2, 0))

            self.maxy = HEIGHT
            self.x = 0
            self.dx = 0
    
            # draw road
            for i in range(self.startPos, self.startPos + 300, 1):
                l = self.lines[i % self.nroflines]
                if i >= self.nroflines:
                    l.project(self.playerX * ROADW - self.x, self.camH, (self.startPos - self.nroflines) * SEGL)
                else:
                    l.project(self.playerX * ROADW - self.x, self.camH, self.startPos * SEGL)

                self.x += self.dx
                self.dx += l.curve
                l.clip = self.maxy
                if l.Y >= self.maxy:
                    continue

                self.maxy = l.Y
   
                if (i / 3) % 2:
                    self.grass = sf.Color(16, 200, 16)
                    self.rumble = sf.Color(255, 255, 255)
                    self.road = sf.Color(107, 107, 107)
                else:
                    self.grass = sf.Color(0, 154, 0)
                    self.rumble = sf.Color(0, 0, 0)
                    self.road = sf.Color(105, 105, 105)
  
                p = self.lines[(i - 1) % self.nroflines] # previous line

                self.drawQuad(self.grass,  0,   p.Y, WIDTH,     0,   l.Y, WIDTH)
                self.drawQuad(self.rumble, p.X, p.Y, p.W * 1.2, l.X, l.Y, l.W * 1.2)
                self.drawQuad(self.road,   p.X, p.Y, p.W,       l.X, l.Y, l.W)

            # draw sprites:
            for i in range(self.startPos + 300, self.startPos, -1):
                self.lines[i % self.nroflines].drawSprite(self.screen)

            self.screen.display()

    def processEvents(self):
        for event in self.screen.events:
            if event.type == sf.Event.CLOSED:
                self.screen.close()
        if sf.Keyboard.is_key_pressed(sf.Keyboard.ESCAPE):
            self.screen.close()
        if sf.Keyboard.is_key_pressed(sf.Keyboard.LEFT):
            self.playerX -= 0.1
        if sf.Keyboard.is_key_pressed(sf.Keyboard.RIGHT):
            self.playerX += 0.1
        if sf.Keyboard.is_key_pressed(sf.Keyboard.UP):
            self.speed = 200 # Not +=, but =.
        if sf.Keyboard.is_key_pressed(sf.Keyboard.DOWN):
            self.speed = -200
        if sf.Keyboard.is_key_pressed(sf.Keyboard.Q):
            self.H += 100
        if sf.Keyboard.is_key_pressed(sf.Keyboard.A):
            self.H -= 100


            """
            if event.type == sf.Event.KEY_PRESSED:
                if event['code'] == sf.Keyboard.ESCAPE:
                    self.screen.close()
                elif event['code'] == sf.Keyboard.LEFT:
                    self.playerX -= 0.1
                elif event['code'] == sf.Keyboard.RIGHT:
                    self.playerX += 0.1
                elif event['code'] == sf.Keyboard.UP:
                    self.speed += 200
                elif event['code'] == sf.Keyboard.DOWN:
                    self.speed -= 200
                elif event['code'] == sf.Keyboard.Q:
                    self.H += 100
                elif event['code'] == sf.Keyboard.A:
                    self.H -= 100

        """
    def drawQuad(self, color, x1, y1, w1, x2, y2, w2):
        shape = sf.ConvexShape(4)
        shape.fill_color = color
        shape.set_point(0, (x1 - w1, y1)),
        shape.set_point(1, (x2 - w2, y2)),
        shape.set_point(2, (x2 + w2, y2)),
        shape.set_point(3, (x1 + w1, y1))
        self.screen.draw(shape)

    def buildLines(self):
        lines = []
        for i in range(1600):
            line = Line()
            line.setZ(i * SEGL)
            if i > 300 and i < 700:
                line.setCurve(0.5)
            if i > 1100:
                line.setCurve(-0.7)
            if i > 750:
                line.y = math.sin(i / 30.0) * 1500.

            if i < 300 and i % 20 == 0:
                line.setSprite(-2.5, self.sprites[4])

            if i % 17 == 0:
                line.setSprite(2.0, self.sprites[5])

            if i > 300 and i % 20 == 0:
                line.setSprite(-0.7, self.sprites[3])

            if i > 800 and i % 20 == 0:
                line.setSprite(-1.2, self.sprites[0])

            if i == 400:
                line.setSprite(-1.2, self.sprites[6])

            lines.append(line)
        return lines

Main()
Ach so, wie gesagt braucht man noch eine "Default-Texture". Dafür habe ich mir mit gimp ein Bild "0.png" mit 1x1-Pixel erstellt, wobei dieser Pixel transparent ist (die Bilddatei hat 169 Bytes).

Schön, daß es jetzt geht. Jetzt noch von SFML nach Pygame. :)
 
OP
A

abgdf

Guru
:-? Tja, Pygame scheint dafür nicht gemacht zu sein. Es bleibt einem wohl nichts anderes übrig, als jeweils "scale()" auf dem "Image", bzw. der "Surface" auszuführen, und das ist ziemlich langsam.
Da hat SFML klar die Nase vorn. Deshalb hat der Programmierer aus dem Video das wohl auch verwendet (und nicht SDL).
Hier also Pygame (mit abfallenden FPS):
Code:
#!/usr/bin/python

import pygame
import math
import os

WIDTH  = 800
HEIGHT = 600
ROADW  = 2000
SEGL   = 200 
CAMD   = 0.84

class Line:

    def __init__(self):
        self.x = 0 # 3d center of line
        self.y = 0
        self.z = 0
        self.X = 0 # screen coord
        self.Y = 0
        self.W = 0
        self.scale = 0
        self.curve = 0
        self.spriteX = 0
        self.clip = 0
        self.image = pygame.image.load("images/0.png")
        self.image.convert_alpha()

    def setZ(self, a):
        self.z = a

    def setCurve(self, a):
        self.curve = a

    def setImage(self, spritex, image):
        self.spriteX = spritex
        self.image = image
        self.image.convert_alpha()

    # from world to screen coordinates
    def project(self, camX, camY, camZ):
        # To avoid division by zero:
        if self.z == camZ:
            self.scale = 1
        else:
            self.scale = CAMD / (self.z - camZ)
        self.X = (1 + self.scale * (self.x - camX)) * WIDTH / 2.
        self.Y = (1 - self.scale * (self.y - camY)) * HEIGHT / 2.
        self.W = self.scale * ROADW * WIDTH / 2.

    def drawSprite(self, screen):
        image = self.image.copy()
        # image.convert_alpha()
        rect = image.get_rect()
        w = rect.width
        h = rect.height

        destX = self.X + self.scale * self.spriteX * WIDTH / 2.
        destY = self.Y + 4.
        destW  = w * self.W / 266.
        destH  = h * self.W / 266.

        destX += destW * self.spriteX
        destY -= destH

        clipH = destY + destH - self.clip
        if clipH < 0:
            clipH = 0
        if clipH >= destH:
            return
        scale_x = int(destW)
        scale_y = int(destH - clipH)
        surface = pygame.Surface((scale_x, scale_y))
        surface = surface.convert_alpha()
        pygame.transform.scale(image, (scale_x, scale_y), surface)
        screen.blit(surface, (destX, destY))


class Main:

    def __init__(self):
        pygame.init()
        self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
        pygame.display.set_caption("Racing!")
        os.environ['SDL_VIDEO_WINDOW_POS'] = "100,50"
        pygame.mouse.set_visible(1)
        pygame.key.set_repeat(1, 30)
        self.clock = pygame.time.Clock()
        self.running = True

        self.images = []
        for i in range(8):
            image = pygame.image.load(os.path.join("images", str(i) + ".png"))
            self.images.append(image)

        """
        self.bg = sf.Texture.from_file("images/bg.png")
        self.bg.smooth = True
        self.bg.repeated = True
        self.sBackground = sf.Sprite(self.bg)
        self.sBackground.texture_rectangle = sf.Rect((0,0), (5000,411))
        self.sBackground.position = (-2000, 0)
        """
        self.lines = self.buildLines()
        self.nroflines = len(self.lines)
        self.playerX = 0
        self.pos = 0
        self.H = 1500
 
        while self.running:

            # self.clock.tick(60)
            self.clock.tick()
            print "FPS: " + str(self.clock.get_fps())
            self.speed = 0
            self.processEvents()

            self.pos += self.speed
            while self.pos >= self.nroflines * SEGL:
                self.pos -= self.nroflines * SEGL
            while self.pos < 0:
                self.pos += self.nroflines * SEGL
   
            # self.screen.fill((105,205,4))
            self.screen.fill((0, 0, 0))
            # self.screen.draw(self.sBackground)
            self.startPos = self.pos / SEGL
            self.camH = self.lines[self.startPos].y + self.H 
            """
            if self.speed > 0:
                self.sBackground.move((-self.lines[self.startPos].curve * 2, 0))
            if self.speed < 0:
                self.sBackground.move((self.lines[self.startPos].curve * 2, 0))
            """

            self.maxy = HEIGHT
            self.x = 0
            self.dx = 0
    
            # draw road
            for i in range(self.startPos, self.startPos + 300, 1):
                l = self.lines[i % self.nroflines]
                if i >= self.nroflines:
                    l.project(self.playerX * ROADW - self.x, self.camH, (self.startPos - self.nroflines) * SEGL)
                else:
                    l.project(self.playerX * ROADW - self.x, self.camH, self.startPos * SEGL)

                self.x += self.dx
                self.dx += l.curve
                l.clip = self.maxy
                if l.Y >= self.maxy:
                    continue

                self.maxy = l.Y
   
                if (i / 3) % 2:
                    self.grass  = (16, 200, 16)
                    self.rumble = (255, 255, 255)
                    self.road   = (107, 107, 107)
                else:
                    self.grass  = (0, 154, 0)
                    self.rumble = (0, 0, 0)
                    self.road   = (105, 105, 105)
  
                p = self.lines[(i - 1) % self.nroflines] # previous line

                self.drawQuad(self.grass,  0,   p.Y, WIDTH,     0,   l.Y, WIDTH)
                self.drawQuad(self.rumble, p.X, p.Y, p.W * 1.2, l.X, l.Y, l.W * 1.2)
                self.drawQuad(self.road,   p.X, p.Y, p.W,       l.X, l.Y, l.W)

            # draw sprites:
            for i in range(self.startPos + 300, self.startPos, -1):
                self.lines[i % self.nroflines].drawSprite(self.screen)

            pygame.display.flip()

    def processEvents(self):
        pygame.event.pump()
        keys = pygame.key.get_pressed()
        if keys[pygame.K_ESCAPE]:
            self.running = False
            return
        if keys[pygame.K_LEFT]:
            self.playerX -= 0.1
        if keys[pygame.K_RIGHT]:
            self.playerX += 0.1
        if keys[pygame.K_UP]:
            self.speed = 200
        if keys[pygame.K_DOWN]:
            self.speed = -200
        if keys[pygame.K_q]:
            self.H += 100
        if keys[pygame.K_a]:
            self.H -= 100


    def drawQuad(self, color, x1, y1, w1, x2, y2, w2):
        pygame.draw.polygon(self.screen,
                            color,
                            ((x1 - w1, y1),
                             (x2 - w2, y2),
                             (x2 + w2, y2),
                             (x1 + w1, y1))) 

    def buildLines(self):
        lines = []
        for i in range(1600):
            line = Line()
            line.setZ(i * SEGL)
            if i > 300 and i < 700:
                line.setCurve(0.5)
            if i > 1100:
                line.setCurve(-0.7)
            if i > 750:
                line.y = math.sin(i / 30.0) * 1500.

            if i < 300 and i % 20 == 0:
                line.setImage(-2.5, self.images[5])

            if i % 17 == 0:
                line.setImage(2.0, self.images[6])

            if i > 300 and i % 20 == 0:
                line.setImage(-0.7, self.images[4])

            if i > 800 and i % 20 == 0:
                line.setImage(-1.2, self.images[1])

            if i == 400:
                line.setImage(-1.2, self.images[7])

            lines.append(line)
        return lines

Main()
Aber das Zeichnen der Straße selbst läuft ziemlich schnell. Immerhin.
 
OP
A

abgdf

Guru
Und nochmal eine verbesserte Version mit PySFML (kein "0.png", keine überflüssigen Sprite-Objekte):
Code:
#!/usr/bin/python
# coding: utf-8

from sfml import sf
import math

WIDTH = 1024
HEIGHT = 768
ROADW = 2000
SEGL = 200
CAMD = 0.84

class Line:

    def __init__(self):
        self.x = 0 # 3d center of line
        self.y = 0
        self.z = 0
        self.X = 0 # screen coord
        self.Y = 0
        self.W = 0
        self.scale = 0
        self.curve = 0
        self.spriteX = 0
        self.clip = 0
        self.sprite = None

    def setZ(self, a):
        self.z = a

    def setCurve(self, a):
        self.curve = a

    def setSprite(self, x, s):
        self.spriteX = x
        self.sprite  = s

    # from world to screen coordinates
    def project(self, camX, camY, camZ):
        # To avoid division by zero:
        if self.z == camZ:
            self.scale = 1
        else:
            self.scale = CAMD / (self.z - camZ)
        self.X = (1 + self.scale * (self.x - camX)) * WIDTH / 2.
        self.Y = (1 - self.scale * (self.y - camY)) * HEIGHT / 2.
        self.W = self.scale * ROADW * WIDTH / 2.

    def drawSprite(self, screen):
        if not self.sprite:
            return
        s = sf.Sprite(self.sprite.texture)
        w = s.texture_rectangle.width
        h = s.texture_rectangle.height

        destX = self.X + self.scale * self.spriteX * WIDTH / 2.
        destY = self.Y + 4.
        destW  = w * self.W / 266.
        destH  = h * self.W / 266.

        destX += destW * self.spriteX
        destY -= destH

        clipH = destY + destH - self.clip
        if clipH < 0:
            clipH = 0
        if clipH >= destH:
            return
        s.texture_rectangle = sf.Rect((0, 0), (w, h - h * clipH / destH))
        s.scale((destW / w, destH / h))
        s.position = (destX, destY)
        screen.draw(s)


class Main:

    def __init__(self):
        self.screen = sf.RenderWindow(sf.VideoMode(WIDTH, HEIGHT), unicode("Outrun Racing!"))
        self.screen.framerate_limit = 60
        self.sprites = []
        for i in range(1, 8, 1):
            texture = sf.Texture.from_file("images/" + str(i) + ".png")
            texture.smooth = True
            sprite = sf.Sprite(texture)
            self.sprites.append(sprite)

        self.bg = sf.Texture.from_file("images/bg.png")
        self.bg.smooth = True
        self.bg.repeated = True
        self.sBackground = sf.Sprite(self.bg)
        self.sBackground.texture_rectangle = sf.Rect((0,0), (5000, 411))
        self.sBackground.position = (-2000, 0)
        self.lines = self.buildLines()
        self.nroflines = len(self.lines)
        self.playerX = 0
        self.pos = 0
        self.flying = 1500
 
        while self.screen.is_open:

            self.speed = 0
            self.processEvents()

            self.pos += self.speed
            while self.pos >= self.nroflines * SEGL:
                self.pos -= self.nroflines * SEGL
            while self.pos < 0:
                self.pos += self.nroflines * SEGL
   
            self.screen.clear(sf.Color(105, 205, 4))
            self.screen.draw(self.sBackground)
            self.startPos = self.pos / SEGL
            if self.flying < 400:
                self.flying = 400
            # Don't fly too high, my little friend:
            if self.flying > 20000:
                self.flying = 20000
            self.camH = self.lines[self.startPos].y + self.flying 
            if self.speed > 0:
                self.sBackground.move((-self.lines[self.startPos].curve * 2, 0))
            if self.speed < 0:
                self.sBackground.move((self.lines[self.startPos].curve * 2, 0))

            self.maxy = HEIGHT
            self.x = 0
            self.dx = 0
    
            # draw road
            for i in range(self.startPos, self.startPos + 300, 1):
                l = self.lines[i % self.nroflines]
                if i >= self.nroflines:
                    l.project(self.playerX * ROADW - self.x, self.camH, (self.startPos - self.nroflines) * SEGL)
                else:
                    l.project(self.playerX * ROADW - self.x, self.camH, self.startPos * SEGL)

                self.x += self.dx
                self.dx += l.curve
                l.clip = self.maxy
                if l.Y >= self.maxy:
                    continue

                self.maxy = l.Y
   
                if (i / 3) % 2:
                    self.grass = sf.Color(16, 200, 16)
                    self.rumble = sf.Color(255, 255, 255)
                    self.road = sf.Color(107, 107, 107)
                else:
                    self.grass = sf.Color(0, 154, 0)
                    self.rumble = sf.Color(0, 0, 0)
                    self.road = sf.Color(105, 105, 105)
  
                p = self.lines[(i - 1) % self.nroflines] # previous line

                self.drawQuad(self.grass,  0,   p.Y, WIDTH,     0,   l.Y, WIDTH)
                self.drawQuad(self.rumble, p.X, p.Y, p.W * 1.2, l.X, l.Y, l.W * 1.2)
                self.drawQuad(self.road,   p.X, p.Y, p.W,       l.X, l.Y, l.W)

            # draw sprites:
            for i in range(self.startPos + 300, self.startPos, -1):
                self.lines[i % self.nroflines].drawSprite(self.screen)

            self.screen.display()

    def processEvents(self):
        for event in self.screen.events:
            if event.type == sf.Event.CLOSED:
                self.screen.close()

        if sf.Keyboard.is_key_pressed(sf.Keyboard.ESCAPE):
            self.screen.close()
        if sf.Keyboard.is_key_pressed(sf.Keyboard.LEFT):
            self.playerX -= 0.1
        if sf.Keyboard.is_key_pressed(sf.Keyboard.RIGHT):
            self.playerX += 0.1
        if sf.Keyboard.is_key_pressed(sf.Keyboard.UP):
            self.speed = 200 # Not +=, but =.
        if sf.Keyboard.is_key_pressed(sf.Keyboard.DOWN):
            self.speed = -200
        if sf.Keyboard.is_key_pressed(sf.Keyboard.TAB):
            self.speed *= 3
        if sf.Keyboard.is_key_pressed(sf.Keyboard.Q):
            self.flying += 100
        if sf.Keyboard.is_key_pressed(sf.Keyboard.W):
            self.flying += 100
        if sf.Keyboard.is_key_pressed(sf.Keyboard.A):
            self.flying -= 100
        if sf.Keyboard.is_key_pressed(sf.Keyboard.D):
            self.flying -= 100

    def drawQuad(self, color, x1, y1, w1, x2, y2, w2):
        shape = sf.ConvexShape(4)
        shape.fill_color = color
        shape.set_point(0, sf.Vector2(x1 - w1, y1)),
        shape.set_point(1, sf.Vector2(x2 - w2, y2)),
        shape.set_point(2, sf.Vector2(x2 + w2, y2)),
        shape.set_point(3, sf.Vector2(x1 + w1, y1))
        self.screen.draw(shape)

    def buildLines(self):
        lines = []
        for i in range(1600):
            line = Line()
            line.setZ(i * SEGL)
            if i > 300 and i < 700:
                line.setCurve(0.5)
            if i > 1100:
                line.setCurve(-0.7)
            if i > 750:
                line.y = math.sin(i / 30.0) * 1500.

            if i < 300 and i % 20 == 0:
                line.setSprite(-2.5, self.sprites[4])

            if i % 17 == 0:
                line.setSprite(2.0, self.sprites[5])

            if i > 300 and i % 20 == 0:
                line.setSprite(-0.7, self.sprites[3])

            if i > 800 and i % 20 == 0:
                line.setSprite(-1.2, self.sprites[0])

            if i == 400:
                line.setSprite(-1.2, self.sprites[6])

            lines.append(line)
        return lines

Main()
Fliegen mit W / D, Tab für "Afterburner". Macht richtig Laune. :)

Das ist so viel schneller als Pygame, ich würde sagen, was Spieleprogrammierung mit Python angeht, dürfte SFML "the way to go" sein. Hätte ich nicht gedacht.
 
OP
A

abgdf

Guru
Gräfin Klara schrieb:
Sieht gut aus! (trotz python)
Ein screenshot wäre noch interessant ...
Ok, danke! Sieht halt aus wie in dem Video ;). Eigener Screenshot:

e2f3yowpdhomm9osu.jpg
 
OP
A

abgdf

Guru
Gräfin Klara schrieb:
Ein schönes Projekt das auch Spaß macht
;)
------------

Hab' mich mal in die pygame Mailingliste eingetragen (ein vernünftiges pygame-Forum scheint es nicht zu geben). Gerade erhalte ich eine Nachricht von einem neuen Release (1.9.5). Als ich versuche, das zu installieren, merke ich, daß pygame immer noch SDL 1 verwendet. :schockiert:
pygame 2 will be released with SDL2 being the default backend when some remaining issues are ironed out. The 1.9.x releases will continue with SDL1 until then.
Vielleicht könnte das der Grund sein, warum das Programm in pygame so langsam ist. Es gibt auch ein Projekt "pygame_sdl2", aber das scheint weniger "offiziell" und eher experimentell zu sein (und kompilierte bei mir nicht).
Bei sfml hatte ich ja die allerneuesten Versionen installiert. Kann sein, daß eines Tages ein "pygame 2" das Problem beheben würde. In einigen Jahren ...

Auch ganz interessant: Aus dem sfml-FAQ:
What is SFML?
SFML is a simple to use and portable API written in C++. You can think of it as an object oriented SDL.
Ach so! Na ja, grundsätzlich finde ich für so ein systemnahes API reines C vielleicht doch geeigneter, da normalerweise meist schneller. Aber sie scheinen mit SFML in Bezug auf Geschwindigkeit einen ganz guten Job gemacht zu haben.
 
OP
A

abgdf

Guru
Waynes interessiert: Konnte die neueste SDL2-Version installieren sowie pygame_sdl2. Da pygame_sdl2 nach Möglichkeit dieselbe Syntax verwendet wie pygame, mußte ich an meinem Code kaum was ändern. Ergebnis: Leider die gleichen Performance-Probleme wie das normale pygame (mit SDL 1.2). Schade ...
 
abgdf schrieb:
Ach so! Na ja, grundsätzlich finde ich für so ein systemnahes API reines C vielleicht doch geeigneter, da normalerweise meist schneller. Aber sie scheinen mit SFML in Bezug auf Geschwindigkeit einen ganz guten Job gemacht zu haben.
Auf dieser Ebene ist C++ ok. Natürlich auch in C realisierbar, freilich wäre es schneller aber viel mehr Arbeit und obendrein als API anwenderunfreundlich.
Schreib doch in C++ anstatt python, das ist keine Hexerei. Damit erreichst du die Performance die du willst

Gruß
Gräfin Klara
 
OP
A

abgdf

Guru
Gräfin Klara schrieb:
Schreib doch in C++ anstatt python, das ist keine Hexerei. Damit erreichst du die Performance die du willst.
Das stimmt sicher. Das Original von dem Videomacher "FamTrinli" (Eingangsposting) war ja schon in C++.
Mir kam das Video in der Tat vor wie Hexerei, als ich es zum ersten Mal gesehen hab'. Ich kann nicht so gut C/C++, deshalb der Versuch, es nach Python zu übersetzen, damit ich es begreifen kann.
In PySFML ist es ja auch schnell genug. Nun habe ich meine Übersetzung, verstehe auch so ungefähr, was er macht, aber es ist halt einfach nur ganz krass Mathe. :)

Ich hab' schon mehrmals versucht, C zu lernen (von da wollte ich dann zu C++), bin aber immer noch nicht durchgedrungen. In Python kann man halt locker sowas definieren:
Code:
a = ((3, "Hallo"), 4, {"test" : 5})
Das ist eine Liste (Tuple), in der das erste Element wieder eine Liste (Tuple) ist, die eine Zahl und einen String enthält, die dann als zweites Element eine Zahl hat, und als drittes Element einen Hash (Dictionary).
Sowas ist in C unheimlich umständlich, und man muß viel mit Zeigern arbeiten, die mich oft zur Verzweiflung bringen. Dadurch brauche ich sehr lange, um in C etwas zustandezubringen.
Es ist einfach so, daß manche Dinge (wie Verarbeitung von Zeichenketten) in C sehr unbequem sind, das war ja gerade der Grund, warum Larry Wall einst Perl geschrieben hat.
In Perl, oder noch besser in Python, komme ich als Programmersteller sehr viel schneller voran, deshalb sind das meine bevorzugten Sprachen.
Na ja, und schließlich wollte ich auch mal wissen, wie man 2D-Spiele schreibt, da bin ich zu Pygame gelangt. Ich war überrascht, daß das doch relativ schwierig ist, vor allem mit dem MainLoop, der die ganze Zeit durchläuft. Aber so ist es wohl.
Pygame war auch gut zum Lernen, denn das Wissen von dort kann man übertragen, SFML macht im wesentlichen etwas Ähnliches.
Resultate meiner Bemühungen bisher waren:

- Meine Notizen zu Pygame,
- Ein Klon von "Vier gewinnt" in Pygame im ZX Spectrum-Look.
- Und das Ganze doch noch mal in C, und zwar C auf dem ZX Spectrum, das heißt der Code muß unter Linux mit "z88dk" kompiliert werden und läuft dann mit einem ZX Spectrum-Emulator wie Fuse oder auf einem echten ZX Spectrum (der Compiler, der leider recht schwer zu installieren ist, erzeugt eine ".tap"-Datei, die eine ZX Spectrum Kassette repräsentiert). Natürlich könnte ich auch einfach die ".tap"-Datei irgendwo hinstellen, aber File-Hosting ist immer so eine Sache.

ZX Spectrum deshalb, weil das mein erster Computer war, sein System einfach zu verstehen und gut zu überblicken ist, und ich ihn schon immer in etwas anderem als BASIC programmieren wollte. :)
Jedenfalls ein guter Startpunkt, wenn man wissen will, wie Spiele gemacht werden. Schließlich hat es so angefangen ("Pong" kann man auch nochmal versuchen).
 
Oben