User Tools

Site Tools


2.5d_example

2.5d example

How to simulate 3d programming using 2d graphics functions.

The Program

The program uses Pygame Framework with the following changes to Game.py. You can just cut and paste the “Game.py” section (below) as a replacement.

One: Something 2d

For this example we need some image, such as “naruto.jpg”. It doesn't matter what this image contains, but you can find some image online. The image we found was a 425×600 jpeg. We added it like this, in the init section:

        # 1. Load image
        self.oimage = pygame.image.load('naruto.jpg')
        self.rimage = self.oimage
        self.image = self.oimage
        self.scale = 1.0
        self.imgw = self.image.get_width()
        self.imgh = self.image.get_height()
        self.cpx = Screen.WIDTH // 2
        self.cpy = Screen.HEIGHT // 2
        self.imgx = self.cpx - self.imgw // 2
        self.imgy = self.cpy - self.imgh // 2

        self.scalex = 1
        self.scaley = 1
        self.angle = 0

It's a Projection!

In addition to loading the image into “oimage”, which means “original image”, we have a wide variety of information we will need to do the “3d” projection. It's called a projection because we calculate what it should look like and then “project” it onto the 2d surface.

Projection Information

We need the original image, and the image we will display. We will keep track of the position and information about the image and then modify it into image. We need to keep track of the x and y location and the z locastion. But we are calling the z location “scale” here, because it isn't really a z location we are using. Thats why it is called 2.5d. It will “look like” a z axis but it isn't.

Simulating Z?

We also need to keep track of scale in the x and y axis as well. This will allow us to “sorta” rotate the image around the x or y axis. Because scaling an image in that direction kinda-maybe-sorta looks like rotation into the z dimension around the x or y axis. This isn't perfect; if it was a little better then the top of the image would get smaller if it moved away and the bottom would get bigger as it rotated towards you; we didn't account for this effect here. We just wanted to try some cheap effects to simulate 3d that you could use in a game.

Rotation is also broken

We also keep track of rotation around the z axis but it's somewhat broken. It's at this point you will realize the limitations of simple 2.5d programming. Fixing this would require a lot of work.

Classes are better

Because of the large amount of data we need to keep about an image to make it kinda maybe sorta 3d it's better to make this into some kind of class. Maybe a 3d sprite class that we can somehow have a list of extra transform functions to make it look like it's moving in 3d space. For many games this will be a fine solution. A lot of early 2.5d games worked very well like this.

Centerpoint and other Kludges

There are a lot of kludges here. One is using centerpoint to keep track of the center in order to make scaling look like z direction movement. Using kludges like this is the best way to make 2.5d games. You can be inventive and find some way to fix rotation this way too!

Transforms

The key to making this work is the transform functions. We are keeping track of at least six things; X, Y and Z position, and X, Y and Z rotation. Again, Z here is a form of scale or rotation but the idea is the same.

The transforms we used were:

  • def transform_r(self, factor):
    • A rotational transformation made to look like rotation around z.
  • def transform_size(self, factor):
    • A scale transform to simulate movement along z.
  • def transform_sx(self, factor):
  • def transform_sy(self, factor):
    • Scale along x and y separately to make it look like rotation around x or y.
  • def transform_xy(self, dx, dy):
    • Movement along X and Y is easy because it's 2d.

Not really proper projection

These are all really bad kludges because we aren't really projecting things, so you will need to tweak a number of constants to make the game feel more natural. But it can certainly work in a pinch! And to add special effects!

Keyboard Controls

I went with frame by frame detection to keep things 'live' which I felt was a good choice for this demo. The key choices could probably be improved, 1, 2, 3, 4 etc, is not the best choice of keys. You might try:

  • OK: wsad and UP, DOWN for movement
  • CHANGED: Use YU, HJ, NM for axial rotation (not 12, 34, 56).

Maybe not the most important change, but could be nice.

Game.py

You can cut and paste this into PyGame Framework.

import pygame
import time
from Screen import *

class Game:
    def __init__(self, window):
        self.window = window
        self.screen = window.screen
        self.font = window.font

        # Set up game variables (see notes)
        self.running = True

        # 1. Load image
        self.oimage = pygame.image.load('naruto.jpg')
        self.rimage = self.oimage
        self.image = self.oimage
        self.scale = 1.0
        self.imgw = self.image.get_width()
        self.imgh = self.image.get_height()
        self.cpx = Screen.WIDTH // 2
        self.cpy = Screen.HEIGHT // 2
        self.imgx = self.cpx - self.imgw // 2
        self.imgy = self.cpy - self.imgh // 2

        self.scalex = 1
        self.scaley = 1
        self.angle = 0

    def transform_r(self, factor):
        self.angle += factor
        self.image = pygame.transform.rotate(self.image, factor)

    def transform_size(self, factor):
        w, h = self.oimage.get_size()
        self.scale = self.scale + factor
        self.image = pygame.transform.scale(self.oimage, (w * self.scale* self.scalex, h * self.scale*self.scaley))

        self.imgw = self.image.get_width()
        self.imgh = self.image.get_height()
        self.imgx = self.cpx - self.imgw // 2
        self.imgy = self.cpy - self.imgh // 2

    def transform_sx(self, factor):
        w, h = self.oimage.get_size()
        self.scalex = self.scalex + factor
        self.transform_size(0)

    def transform_sy(self, factor):
        w, h = self.oimage.get_size()
        self.scaley = self.scaley + factor
        self.transform_size(0)

    def transform_xy(self, dx, dy):
        self.cpx += dx
        self.cpy += dy
        self.imgx = self.cpx - self.imgw // 2
        self.imgy = self.cpy - self.imgh // 2

    def start(self):
        # Clock for controlling the frame rate
        clock = pygame.time.Clock()

        # Variables to keep track of time, if needed
        frame_counter = 0
        time_start = time.time() * 1000

        # Main Loop
        while self.running == True:
            # Manage time for timed events like animations
            time_clock = (time.time() * 1000) - time_start
            frame_counter += 1
            if frame_counter > Screen.FPS:
                frame_counter = 1

            # All event handling
            self.checkEvents()

            # Frame generation
            self.screen.fill((0, 0, 0))  # Clear the screen.
            self.drawGame()
            pygame.display.update()  # update the display. Can also use flip()

            # Control the frame rate
            clock.tick(Screen.FPS)

    def drawGame(self):
        self.window.screen.blit(self.image, (self.imgx, self.imgy) )

    def checkEvents(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.running = False
                return

            if event.type == pygame.KEYDOWN:
                # key down event, process keys.

                if event.key == pygame.K_q:
                    self.quit_game()
                if event.key == pygame.K_ESCAPE:
                    self.quit_game()

        keys = pygame.key.get_pressed()
        if keys[pygame.K_UP]:
            self.transform_size(-0.001)
        if keys[pygame.K_DOWN]:
            self.transform_size(0.001)

        if keys[pygame.K_1]:
            self.transform_sx(-0.003)
        if keys[pygame.K_2]:
            self.transform_sx(0.003)
        if keys[pygame.K_3]:
            self.transform_sy(-0.003)
        if keys[pygame.K_4]:
            self.transform_sy(0.003)
        if keys[pygame.K_5]:
            self.transform_r(-1)
        if keys[pygame.K_6]:
            self.transform_r(1)



        if keys[pygame.K_w]:
            self.transform_xy(0,-1)

        if keys[pygame.K_s]:
            self.transform_xy(0,1)

        if keys[pygame.K_a]:
            self.transform_xy(-1,0)

        if keys[pygame.K_d]:
            self.transform_xy(1,0)




    def quit_game(self):
        print("Exiting game...")
        pygame.quit()
        quit()
        exit()
2.5d_example.txt · Last modified: 2024/09/20 00:35 by appledog

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki