0

I started work on my own version of Super Mario Kart in python, just as a small project for myself. So far I've managed to transform an image to look like the player is moving over a pseudo-3D track. However, there is no perspective at all, which somewhat ruins the illusion of 3D. I know that I could pretty much fix this issue if I had a way of skewing the image so that the bottom of the surface is wider than the top, as this would help to simulate perspective.

I've been looking for quite a while online to see if there is a way to achieve this, as I really have no idea where to start. Although my searches have been inconclusive, and I have not found anything useful.

So how would I go about doing this to skew the image of the track?

Here is my code so far:

import os, sys, pygame
from pygame.locals import *
from math import *

pygame.init()

path, file_name = os.path.split(__file__)
path            = os.path.join(path, 'data')

WIDTH  = 800
HEIGHT = 600
SCREEN = pygame.display.set_mode((WIDTH,HEIGHT))
CLOCK  = pygame.time.Clock()
FPS    = 30

pygame.mouse.set_visible(False)
pygame.display.set_caption('Mario Kart')

class Driver(object):
    def __init__(self, image, start_pos):
        self.surface = pygame.image.load(os.path.join(path, image)).convert()
        self.surface.set_colorkey(MAGENTA)
        self.surface = pygame.transform.scale(self.surface, (64, 64))

        self.pos = list(start_pos)
        self.angle = 0
        self.velocity = 0

    def move(self):
        keys = pygame.key.get_pressed()

        if keys[K_a] or keys[K_LEFT]:
            self.angle -= 14 / (1 + e ** (-0.3 * self.velocity)) - 7
            #Sigmoid function to contain the angular velocity between 7 and -7

        if keys[K_d] or keys[K_RIGHT]:
            self.angle += 14 / (1 + e ** (-0.3 * self.velocity)) - 7

        if keys[K_w] or keys[K_UP]:
            self.velocity += 3

        elif keys[K_s] or keys[K_DOWN]:
            self.velocity -= 1

        self.velocity *= 0.85

        self.pos[0] += self.velocity * sin(radians(self.angle))
        self.pos[1] -= self.velocity * cos(radians(self.angle))

    def render(self):
        SCREEN.blit(self.surface, (WIDTH / 2 - 32, HEIGHT / 2 - 32))

class Track(object):
    def __init__(self, image, tilt = 1, zoom = 1):
        self.surface = pygame.image.load(os.path.join(path, image)).convert_alpha()
        self.angle = 0
        self.tilt = tilt
        self.zoom = zoom
        self.rect = self.surface.get_rect()
        self.rect.center = (WIDTH / 2, HEIGHT / 2)

        self.image = self.surface
        self.image_rect = self.image.get_rect()

    def render(self, angle, center = (0, 0)):
        self.angle = angle

        self.image = self.surface 
        self.image = pygame.transform.rotate(self.image, self.angle)
        self.image_rect = self.image.get_rect()

        self.image = pygame.transform.scale(self.image, (int(self.image_rect.width * self.zoom), int((self.image_rect.height * self.zoom) / self.tilt)))

        self.image_rect = self.image.get_rect(center = (self.rect.centerx - ((center[0] * self.zoom) * cos(radians(-self.angle)) - (center[1] * self.zoom) * sin(radians(-self.angle))),
                                                        self.rect.centery - ((center[0] * self.zoom) * sin(radians(-self.angle)) + (center[1] * self.zoom) * cos(radians(-self.angle))) / self.tilt))

        SCREEN.blit(self.image, self.image_rect)


def main():
    track = Track('MushroomCup1.png', tilt = 4, zoom = 7)
    toad = Driver('Toad Sprite.png', (408, 90))

    while True:
        SCREEN.fill((255, 255, 255))

        toad.move() 

        track.render(toad.angle, toad.pos)
        toad.render()

        events = pygame.event.get()
        for event in events:
            if event.type == QUIT or event.type == KEYDOWN and event.key == K_ESCAPE:
                pygame.quit()
                sys.exit()

        pygame.display.update()
        CLOCK.tick(FPS)

if __name__ == '__main__':
    main()
  • MushroomCup1.png

Mushroom Cup Track 1

  • Toad Sprite.png

Toad Kart Sprite

George Willcox
  • 677
  • 12
  • 30
  • There's no function in pygame that allows you to skew images as far as I know. You could use another library like pillow to skew the image and then convert it into a pygame surface with `Image.tobytes` and [`pygame.image.fromstring`](http://www.pygame.org/docs/ref/image.html#pygame.image.fromstring). I have no idea if that's efficient enough for a game. [Here's an interesting answer](https://stackoverflow.com/a/14178717/6220679). – skrx Feb 05 '18 at 15:29

1 Answers1

1

You'll have to try something like PyOpenGL or Pyglet if you want to get real 3D. PyGame is really only for doing things on a conventional 2D surface/perspective.

That said, I actually did write a Mario Kart style 3D driving game that ran on pure PyGame (here's a JS port). I set coordinates for the road, decomposed the road into individual triangles, and did lots of matrix math. This was mostly a humorous exercise to see if I could, not something that I should have done. And that was with only geometric primitives. For an image it gets more complicated.

If you really wanted to, you could probably do something like preprocess that image resource to simplify and decompose it into a bunch of large-ish square blocks, and do a similar method of decomposing those into triangles with lots of matrix math. It wouldn't be fast, but it would technically work and only require PyGame.

Basically what I'm saying is, there is no Pygame solution in spirit, and if you want 3D proper, you'll have to move to a framework that supports it (like Pyglet or PyOpenGL).

Blake O'Hare
  • 1,863
  • 12
  • 16