3D Projection in pygame - python

I'm trying to create a simple 3d rendering of a cube. As in this video from the Coding Train: https://www.youtube.com/watch?v=p4Iz0XJY-Qk on minute 14. I got stuck at one point. Since I'm pretty new to all of this, I'm not exactly sure what's causing my issue. When I start the project, the cube rotates as I want it to, but moves away from the screen to the left and it looks like it's making a circle.
import pygame
import numpy as np
import os
import math
WHITE = (255,255,255)
width, height = 700, 700
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
points = []
angle = 0
points.append(np.array([[300], [250], [1]]))
points.append(np.array([[300], [350], [1]]))
points.append(np.array([[400], [250], [1]]))
points.append(np.array([[400], [350], [1]]))
projectionMatrix = np.array([[1, 0, 0],
[0, 1, 0]])
while True:
clock.tick(30)
screen.fill((0,0,0))
rotation = np.array([[math.cos(angle), -math.sin(angle)],
[math.sin(angle), math.cos(angle)]])
for event in pygame.event.get():
if event.type == pygame.QUIT:
os._exit(1)
for point in points:
projected2d = np.dot(projectionMatrix, point)
rotated = np.dot(rotation, projected2d)
pygame.draw.circle(screen, WHITE, (int(rotated[0][0]), int(rotated[1][0])), 5)
angle += 0.01
pygame.display.update()
I would really appreciate any help to why this is happening and how I could fix it so it's just rotating around.

There is no bug in this code. The points rotate around the top left (0, 0). Note, In 3D mode, p5.js uses a different coordinate system than pygame.
If you want to rotate the dots around the centre of the window, then define the points in range [-1, 1] (normalized device space:
points.append(np.matrix([ 0.5, 0.5, 1]))
points.append(np.matrix([ 0.5, -0.5, 1]))
points.append(np.matrix([-0.5, 0.5, 1]))
points.append(np.matrix([-0.5, -0.5, 1]))
Define a projection matrix from the range[-1, 1] to window space:
projectionMatrix = np.matrix([[height/2, 0, width/2],
[0, height/2, height/2]])
Specify a 3x3 rotation matrix:
rotation = np.array([[math.cos(angle), -math.sin(angle), 0],
[math.sin(angle), math.cos(angle), 0],
[0, 0, 1]])
First rotate the points, then project it to the window:
projected2d = projectionMatrix * rotation * point.reshape((3, 1))
Complete example:
import pygame
import numpy as np
import os
import math
WHITE = (255,255,255)
width, height = 400, 300
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
points = []
angle = 0
points.append(np.matrix([ 0.5, 0.5, 1]))
points.append(np.matrix([ 0.5, -0.5, 1]))
points.append(np.matrix([-0.5, 0.5, 1]))
points.append(np.matrix([-0.5, -0.5, 1]))
projectionMatrix = np.matrix([[height/2, 0, width/2],
[0, height/2, height/2]])
while True:
clock.tick(30)
screen.fill((0,0,0))
rotation = np.matrix([[math.cos(angle), -math.sin(angle), 0],
[math.sin(angle), math.cos(angle), 0],
[0, 0, 1]])
for event in pygame.event.get():
if event.type == pygame.QUIT:
os._exit(1)
for point in points:
projected2d = projectionMatrix * rotation * point.reshape((3, 1))
pygame.draw.circle(screen, WHITE, (int(projected2d[0][0]), int(projected2d[1][0])), 5)
angle += 0.01
pygame.display.update()

Related

How Can I Make a Thicker Bezier in Pygame?

I'm building a specialized node editor in Pygame. Each node will be connected with a bezier curve. This curve is built by clicking on a node first. A bezier is drawn between the mouse cursor and the node and once you click on a second node, the bezier line is fixed. My code can already draw the curve and follow the mouse cursor. My problem is that the curve is too thin. Does anyone know of a way to easily specify width in pygame.gfxdraw.bezier? Also, I have no idea what the argument "6" corresponds to; I only know the code won't function without it.
# This draws the bezier curve for the node editor
x, y = pygame.mouse.get_pos()
b_points = [(380,390),(410,385),(425,405), (425, y), (x, y)]
pygame.gfxdraw.bezier(screen, b_points, 6, blue)
Simple answer: You cant't, at least not with pygame.gfxdraw
or pygame.draw. You have to do it yourself.
Compute points along the curve and connect them with pygame.draw.lines.
See Finding a Point on a Bézier Curve: De Casteljau's Algorithm and create a function that draw a bezier curve piont, by point:
import pygame
def ptOnCurve(b, t):
q = b.copy()
for k in range(1, len(b)):
for i in range(len(b) - k):
q[i] = (1-t) * q[i][0] + t * q[i+1][0], (1-t) * q[i][1] + t * q[i+1][1]
return round(q[0][0]), round(q[0][1])
def bezier(surf, b, samples, color, thickness):
pts = [ptOnCurve(b, i/samples) for i in range(samples+1)]
pygame.draw.lines(surf, color, False, pts, thickness)
Minimal example:
import pygame, pygame.gfxdraw
def ptOnCurve(b, t):
q = b.copy()
for k in range(1, len(b)):
for i in range(len(b) - k):
q[i] = (1-t) * q[i][0] + t * q[i+1][0], (1-t) * q[i][1] + t * q[i+1][1]
return round(q[0][0]), round(q[0][1])
def bezier(surf, b, samples, color, thickness):
pts = [ptOnCurve(b, i/samples) for i in range(samples+1)]
pygame.draw.lines(surf, color, False, pts, thickness)
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
x, y = pygame.mouse.get_pos()
b_points = [(380,390), (410,385), (425,405), (425, y), (x, y)]
screen.fill(0)
bezier(screen, b_points, 20, (255, 255, 0), 6)
pygame.draw.lines(screen, (255, 255, 255), False, b_points, 1)
pygame.gfxdraw.bezier(screen, b_points, 6, (255, 0, 0))
pygame.display.flip()
clock.tick(60)
pygame.quit()

How to translate and rotate the coordinate axis about a point in pygame screen?

I'm trying to draw a polygon with DDA line algorithm using pygame.
def Round(a):
return int(a + 0.5)
def mainloop():
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
def Draw():
DrawPoly((100, 100), 6, 100)
pygame.display.flip()
def drawDDA(p1, p2, color=[0, 0, 0]):
x0, y0, x1, y1 = p1[0], p1[1], p2[0], p2[1]
steps = abs(x0-x1) if abs(x0-x1) > abs(y0-y1) else abs(y0-y1)
dx = (x1-x0)/steps
dy = (y1-y0)/steps
x, y = x0, y0
gfxdraw.pixel(screen,Round(x),Round(y),color)
for i in range(int(steps)):
x += dx
y += dy
gfxdraw.pixel(screen,Round(x), Round(y),color)
def DrawPoly(center, n, s, color=[0, 0, 0]):
cx, cy = center[0], center[1]
sideAngle = 360/n
bv1x = cx-s/2
bv1y = cy - (s/2)*(1/math.tan(math.radians(sideAngle/2)))
bv2x = cx+s/2
bv2y = bv1y
drawDDA((bv1x, bv1y), (bv2x, bv2y), color)
for i in range(n-1):
# i want to rotate the coordinate plane about an angle "sideAngle" with (cx,cy) as the center
# code here
drawDDA((bv1x, bv1y), (bv2x, bv2y), color)
size = [640, 720]
os.environ['SDL_VIDEO_CENTERED'] = '0'
pygame.init()
screen = pygame.display.set_mode(size)
screen.fill((255, 255, 255))
Draw()
mainloop()
The idea I tried to execute to draw a polygon is
I'm taking a point as center of the polygon
calculating the coordinates of two consecutive points
draw a line between those two points
rotate the whole plane about the center of the polygon by side angle which is (360 degrees/number of sides of the polygon)
now, draw a line with the same coordinates to get another side of the polygon
repeat 4 and 5 for n-2 times to get all the remaining side. (n is the number of sides)
I need a way to rotate the coordinate axis about the center of my polygon.
If you can transform Polar coordinates (d, a) to Cartesian coordinate (x, y) with math.sin and math.cos
x = d * cos(a)
y = d * sin(a)
Change the function DrawPoly:
def DrawPoly(center, n, s, color=[0, 0, 0]):
x0, y0 = center[0], center[1]
a = math.radians(360 / n)
d = s / 2 / math.sin(a / 2)
pts = []
for i in range(n+1):
sideAngle = math.radians(360 * i / n)
x = x0 + d * math.cos(sideAngle)
y = y0 + d * math.sin(sideAngle)
pts.append([x, y])
for i in range(n):
drawDDA(pts[i], pts[i+1], color)
Different polygons with the same side length:
def Draw():
DrawPoly((100, 100), 3, 100, (192, 0, 0))
DrawPoly((100, 100), 4, 100, (0, 192, 0))
DrawPoly((100, 100), 5, 100, (0, 0, 192))
DrawPoly((100, 100), 6, 100, (0, 0, 0))
pygame.display.flip()

How to rotate slices of a Rubik's Cube in python PyOpenGL?

I'm attempting to create a Rubik's Cube in Python, i have gotten as far as visually representing the cube. Struggling a bit with how to implement rotation.
I guess i'm asking for feedback as to how to go about doing this. I thought at first of, rotating each cubes set of vertices's, without much luck.
I basically want to select a slice from an array of cube objects (of varying size), perform a rotation and a translation on each object.
import pygame
import random
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
vertices = (
(1, -1, -1),
(1, 1, -1),
(-1, 1, -1),
(-1, -1, -1),
(1, -1, 1),
(1, 1, 1),
(-1, -1, 1),
(-1, 1, 1)
)
edges = (
(0,1),
(0,3),
(0,4),
(2,1),
(2,3),
(2,7),
(6,3),
(6,4),
(6,7),
(5,1),
(5,4),
(5,7)
)
surfaces = (
(0,1,2,3),
(3,2,7,6),
(6,7,5,4),
(4,5,1,0),
(1,5,7,2),
(4,0,3,6)
)
colors = (
(1,0,0), #Red
(0,1,0), #Green
(1,0.5,0), #Orange
(1,1,0), #Yellow
(1,1,1), #White
(0,0,1), #Blue
)
class Cube():
'''set the vertices edges and surfaces(colored) for a Cube'''
def __init__(self):
'''initiate the display to show the cube'''
pygame.init()
display = (800,600)
pygame.display.set_mode(display, DOUBLEBUF|OPENGL)
glEnable(GL_DEPTH_TEST)
gluPerspective(45, (display[0]/display[1]), 0.1, 50.0)
glTranslatef(1,1, -40)
def setVertices(self, xmove, ymove, zmove):
'''set predefined vertices'''
xValueChange = xmove
yValueChange = ymove
zValueChange = zmove
newVertices = []
for vert in vertices:
newVert = []
newX = vert[0] + xValueChange
newY = vert[1] + yValueChange
newZ = vert[2] + zValueChange
newVert.append(newX)
newVert.append(newY)
newVert.append(newZ)
newVertices.append(newVert)
return newVertices
def CreateCube(self, vertices):
'''create with OpenGL'''
glBegin(GL_QUADS)
x = 0
for surface in surfaces:
glColor3fv(colors[x])
x+=1
for vertex in surface:
glVertex3fv(vertices[vertex])
glEnd()
class EntireCube():
def __init__(self,typeOfCube):
self.typeOfCube = typeOfCube
self.NewCube = Cube()
def createEntireCube(self):
'''for each dimension x,y,z make a dictionary containing the vertices to be displayed'''
self.cubeDict = {}
count = 0
for x in range(self.typeOfCube):
for y in range(self.typeOfCube):
for z in range(self.typeOfCube):
self.cubeDict[count] = self.NewCube.setVertices(x*2.1,y*2.1,z*2.1)
count += 1
def mainloop(self):
'''key events, creates the matrix of cubes'''
rotateUpKey, rotateDownKey, rotateLeftKey, rotateRightKey = False, False, False, False
rotationalSensitivity = 2
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == KEYDOWN:
if event.key == K_UP:
rotateUpKey = True
if event.key == K_DOWN:
rotateDownKey = True
if event.key == K_LEFT:
rotateLeftKey = True
if event.key == K_RIGHT:
rotateRightKey = True
if event.type == KEYUP:
if event.key == K_UP:
rotateUpKey = False
if event.key == K_DOWN:
rotateDownKey = False
if event.key == K_LEFT:
rotateLeftKey = False
if event.key == K_RIGHT:
rotateRightKey = False
if rotateUpKey:
glRotatef(rotationalSensitivity,-rotationalSensitivity,0,0)
if rotateDownKey:
glRotatef(rotationalSensitivity,rotationalSensitivity,0,0)
if rotateLeftKey:
glRotatef(rotationalSensitivity,0,-rotationalSensitivity,0)
if rotateRightKey:
glRotatef(rotationalSensitivity,0,rotationalSensitivity,0)
#eventually implement keysbindings to call function to rotate a slice of the matrix created
# x = glGetDoublev(GL_MODELVIEW_MATRIX)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
for eachCube in self.cubeDict:
self.NewCube.CreateCube(self.cubeDict[eachCube])
# glPushMatrix()
# glRotatef(1,3,1,1)
# glPopMatrix()
pygame.display.flip()
pygame.time.wait(10)
def main():
NewEntireCube = EntireCube(3) #create a 3x3x3 cube
NewEntireCube.createEntireCube()
NewEntireCube.mainloop()
if __name__ == '__main__':
main()
pygame.quit()
quit()
I'm hoping someone who knows much more about this can give me some guidance as to how to proceed.
A rubik's cube can be organized by an 3 dimensional array of 3x3x3 cubes. It seems to be easy to rotate a slice of the cube, but note if on slice is rotated the positions of the cube change and have to be reorganized. Not only the position changes, also the orientation of the (rotated) single cubes changes.
First of all remove the PyGame and OpenGL initialization form the constructor of the class Cube. That is the wrong place for this. In the following will generate 27 objects of type Cube.
Each cube has to know where it is initially located (self.init_i) and where it is current located after some rotations (self.current_i). This information is encoded in a list with 3 elements, one for each axis. The values are indices of cube in the NxNxN rubik's cube in range [0, N[.
The orientation of a single cube is encoded in 3 dimensional Rotation matrix (self.rot). The rotation matrix has to be initialized by the identity matrix.
class Cube():
def __init__(self, id, N, scale):
self.N = N
self.scale = scale
self.init_i = [*id]
self.current_i = [*id]
self.rot = [[1 if i==j else 0 for i in range(3)] for j in range(3)]
Create a list of the 27 cubes
cr = range(3)
self.cubes = [Cube((x, y, z), 3, scale) for x in cr for y in cr for z in cr]
If a slice of the rubik's cube is rotated, then it has to be checked which of the single cubes is affected. This can be done by checking if the slice matches the entry of the rotation axis of the current position.
def isAffected(self, axis, slice, dir):
return self.current_i[axis] == slice
To rotate a cube, the position and the orientation has to be rotated by 90° around an axis. A 3 dimension rotation matrix consists of 3 direction vectors. A d dimensional vector can be rotated by swapping the coordinates of the vector and inverting the x coordinate of the result for a right rotation and inverting the y coordinate of the result for a left rotation:
rotate right: (x, y) -> (-y, x)
rotate left: (x, y) -> (y, -x)
Since all the vectors of the rotation matrix are in an axis aligned plane this algorithm can be used to change the orientation and the position of the cube. axis the rotation axis (x=0, y=1, z=2) and dir is the rotation direction (1 is right and -1 left)
To rotate the axis vector, 2 components of the vector have to be swapped and one of them inverted.
e.g rotate left around the Y-axis:
(x, y, z) -> (z, y, -x)
When the position is rotated, then the indices have to be swapped. Inverting an index means to map the index i to the index N-1-i:
e.g rotate left around the Y-axis:
(ix, iy, iz) -> (iz, iy, N-1-ix)
Rotation of a single cube:
i, j = (axis+1) % 3, (axis+2) % 3
for k in range(3):
self.rot[k][i], self.rot[k][j] = -self.rot[k][j]*dir, self.rot[k][i]*dir
self.current_i[i], self.current_i[j] = (
self.current_i[j] if dir < 0 else self.N - 1 - self.current_i[j],
self.current_i[i] if dir > 0 else self.N - 1 - self.current_i[i] )
When the cube has to be drawn, then the current position of the cube (self.current_i) and the orientation self.rot can be used to set up 4x4 transformation matrix:
def transformMat(self):
scaleA = [[s*self.scale for s in a] for a in self.rot]
scaleT = [(p-(self.N-1)/2)*2.1*self.scale for p in self.current_i]
return [
*scaleA[0], 0,
*scaleA[1], 0,
*scaleA[2], 0,
*scaleT, 1]
With glPushMatrix respectively glPushMatrix. By glMultMatrix a matrix can be multiplied to the current matrix.
The following function draws a single cube. The parameters angle, axis, slice, dir and it can even apply an animation to the cube, by setting animate=True and setting parameters angle, axis, slice, dir:
def draw(self, col, surf, vert, animate, angle, axis, slice, dir):
glPushMatrix()
if animate and self.isAffected(axis, slice, dir):
glRotatef( angle*dir, *[1 if i==axis else 0 for i in range(3)] )
glMultMatrixf( self.transformMat() )
glBegin(GL_QUADS)
for i in range(len(surf)):
glColor3fv(colors[i])
for j in surf[i]:
glVertex3fv(vertices[j])
glEnd()
glPopMatrix()
To draw the cubes, it is sufficient to call the method draw in a loop:
for cube in self.cubes:
cube.draw(colors, surfaces, vertices, animate, animate_ang, *action)
The implementation of the class Cube works for any NxNxN Rubik's cube.
See the example program for a 3x3x3 cube. The slices of the cube are rotated to the right by the keys 1 to 9 and to the left by the keys F1 to F9:
Of course the code uses the Legacy OpenGL in regard to your original code. But the method Cube.transformMat sets a general 4x4 model matrix for a single partial cube. Thus it is possible to port this code to modern OpenGL with ease.
import pygame
import random
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
vertices = (
( 1, -1, -1), ( 1, 1, -1), (-1, 1, -1), (-1, -1, -1),
( 1, -1, 1), ( 1, 1, 1), (-1, -1, 1), (-1, 1, 1)
)
edges = ((0,1),(0,3),(0,4),(2,1),(2,3),(2,7),(6,3),(6,4),(6,7),(5,1),(5,4),(5,7))
surfaces = ((0, 1, 2, 3), (3, 2, 7, 6), (6, 7, 5, 4), (4, 5, 1, 0), (1, 5, 7, 2), (4, 0, 3, 6))
colors = ((1, 0, 0), (0, 1, 0), (1, 0.5, 0), (1, 1, 0), (1, 1, 1), (0, 0, 1))
class Cube():
def __init__(self, id, N, scale):
self.N = N
self.scale = scale
self.init_i = [*id]
self.current_i = [*id]
self.rot = [[1 if i==j else 0 for i in range(3)] for j in range(3)]
def isAffected(self, axis, slice, dir):
return self.current_i[axis] == slice
def update(self, axis, slice, dir):
if not self.isAffected(axis, slice, dir):
return
i, j = (axis+1) % 3, (axis+2) % 3
for k in range(3):
self.rot[k][i], self.rot[k][j] = -self.rot[k][j]*dir, self.rot[k][i]*dir
self.current_i[i], self.current_i[j] = (
self.current_i[j] if dir < 0 else self.N - 1 - self.current_i[j],
self.current_i[i] if dir > 0 else self.N - 1 - self.current_i[i] )
def transformMat(self):
scaleA = [[s*self.scale for s in a] for a in self.rot]
scaleT = [(p-(self.N-1)/2)*2.1*self.scale for p in self.current_i]
return [*scaleA[0], 0, *scaleA[1], 0, *scaleA[2], 0, *scaleT, 1]
def draw(self, col, surf, vert, animate, angle, axis, slice, dir):
glPushMatrix()
if animate and self.isAffected(axis, slice, dir):
glRotatef( angle*dir, *[1 if i==axis else 0 for i in range(3)] )
glMultMatrixf( self.transformMat() )
glBegin(GL_QUADS)
for i in range(len(surf)):
glColor3fv(colors[i])
for j in surf[i]:
glVertex3fv(vertices[j])
glEnd()
glPopMatrix()
class EntireCube():
def __init__(self, N, scale):
self.N = N
cr = range(self.N)
self.cubes = [Cube((x, y, z), self.N, scale) for x in cr for y in cr for z in cr]
def mainloop(self):
rot_cube_map = { K_UP: (-1, 0), K_DOWN: (1, 0), K_LEFT: (0, -1), K_RIGHT: (0, 1)}
rot_slice_map = {
K_1: (0, 0, 1), K_2: (0, 1, 1), K_3: (0, 2, 1), K_4: (1, 0, 1), K_5: (1, 1, 1),
K_6: (1, 2, 1), K_7: (2, 0, 1), K_8: (2, 1, 1), K_9: (2, 2, 1),
K_F1: (0, 0, -1), K_F2: (0, 1, -1), K_F3: (0, 2, -1), K_F4: (1, 0, -1), K_F5: (1, 1, -1),
K_F6: (1, 2, -1), K_F7: (2, 0, -1), K_F8: (2, 1, -1), K_F9: (2, 2, -1),
}
ang_x, ang_y, rot_cube = 0, 0, (0, 0)
animate, animate_ang, animate_speed = False, 0, 5
action = (0, 0, 0)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == KEYDOWN:
if event.key in rot_cube_map:
rot_cube = rot_cube_map[event.key]
if not animate and event.key in rot_slice_map:
animate, action = True, rot_slice_map[event.key]
if event.type == KEYUP:
if event.key in rot_cube_map:
rot_cube = (0, 0)
ang_x += rot_cube[0]*2
ang_y += rot_cube[1]*2
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glTranslatef(0, 0, -40)
glRotatef(ang_y, 0, 1, 0)
glRotatef(ang_x, 1, 0, 0)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
if animate:
if animate_ang >= 90:
for cube in self.cubes:
cube.update(*action)
animate, animate_ang = False, 0
for cube in self.cubes:
cube.draw(colors, surfaces, vertices, animate, animate_ang, *action)
if animate:
animate_ang += animate_speed
pygame.display.flip()
pygame.time.wait(10)
def main():
pygame.init()
display = (800,600)
pygame.display.set_mode(display, DOUBLEBUF|OPENGL)
glEnable(GL_DEPTH_TEST)
glMatrixMode(GL_PROJECTION)
gluPerspective(45, (display[0]/display[1]), 0.1, 50.0)
NewEntireCube = EntireCube(3, 1.5)
NewEntireCube.mainloop()
if __name__ == '__main__':
main()
pygame.quit()
quit()

Pyqtgraph. Draw text label next to the rectangle

I am trying to draw a grid of rectangles with text labels on each rectangle. I am using this code:
import pyqtgraph as pg
win = pg.GraphicsWindow()
vb = win.addViewBox(col=0, row=0)
board = ['1234',
'abcd',
'efgh']
def draw_board(board):
for j, row in enumerate(board):
for i, cell in enumerate(row):
r = pg.QtGui.QGraphicsRectItem(i, -j, 0.9, 0.9)
r.setPen(pg.mkPen((0, 0, 0, 100)))
r.setBrush(pg.mkBrush((50, 50, 200)))
vb.addItem(r)
t = pg.TextItem(cell, (255, 255, 255), anchor=(i, -j))
vb.addItem(t)
pg.QtGui.QApplication.exec_()
draw_board(board)
For some reason labels seem to be drawn in a completely different coordinate system that even uses different scale. What is the easy way to put my labels in the middle of the respective rectangles?
Solved the problem with this code:
t_up = pg.TextItem(cell, (255, 255, 255), anchor=(0, 0))
t_up.setPos(i + 0.5, -j + 0.5)

How to render an isometric tile-based world in Python?

I'm having a bit of trouble rendering an isometric 2D tile-based world using Python and Pygame with the following code:
'''
Map Rendering Demo
rendermap.py
By James Walker (trading as Ilmiont Software).
Copyright (C)Ilmiont Software 2013. All rights reserved.
This is a simple program demonstrating rendering a 2D map in Python with Pygame from a list of map data.
Support for isometric or flat view is included.
'''
import pygame
from pygame.locals import *
pygame.init()
DISPLAYSURF = pygame.display.set_mode((640, 480), DOUBLEBUF) #set the display mode, window title and FPS clock
pygame.display.set_caption('Map Rendering Demo')
FPSCLOCK = pygame.time.Clock()
map_data = [
[1, 1, 1, 1, 1],
[1, 0, 0, 0, 1],
[1, 0, 0, 0, 1],
[1, 0, 0, 0, 1],
[1, 0, 0, 0, 1],
[1, 1, 1, 1, 1]
] #the data for the map expressed as [row[tile]].
wall = pygame.image.load('wall.png').convert() #load images
grass = pygame.image.load('grass.png').convert()
tileWidth = 64 #holds the tile width and height
tileHeight = 64
currentRow = 0 #holds the current map row we are working on (y)
currentTile = 0 #holds the current tile we are working on (x)
for row in map_data: #for every row of the map...
for tile in row:
tileImage = wall
cartx = currentTile * 64 #x is the index of the currentTile * the tile width
print(cartx)
carty = currentRow * 64 #y is the index of the currentRow * the tile height
print(carty)
x = cartx - carty
print(x)
y = (cartx + carty) / 2
print(y)
print('\n\n')
currentTile += 1 #increase the currentTile holder so we know that we are starting rendering a new tile in a moment
DISPLAYSURF.blit(tileImage, (x, y)) #display the actual tile
currentTile = 0 #reset the current working tile to 0 (we're starting a new row remember so we need to render the first tile of that row at index 0)
currentRow += 1 #increment the current working row so we know we're starting a new row (used for calculating the y coord for the tile)
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYUP:
if event.key == K_ESCAPE:
pygame.quit()
sys.exit()
pygame.display.flip()
FPSCLOCK.tick(30)
The tile size used is 64x64; the above code when run generates the following output:
The tiles all have transparent edges and only the 'wall' tile is featured in this example but obviously something is going wrong as the tiles are all too far apart.
I have tried reading some tutorials online but I can't seem to find one actually written in Python so please advise me as to where I am going wrong.
Thanks in advance,
Ilmiont
Try this:
print(carty)
x = (cartx - carty) / 2
print(x)
y = (cartx + carty)/4*3
And, after test, modify convert() to convert_alpha(), because optimisation kill alpha on convert()
I also modify y ( /4*3 ), to take in account your image. It's work for me.
You can get rid of the black triangles by setting the colorkey to black, I copied your example and was able to fix it by changing:
for row in map_data: #for every row of the map...
for tile in row:
tileImage = wall
to
for row in map:
for tile in row:
tileImage = wall
tileImage.set_colorkey((0,0,0))
Found from:
https://www.pygame.org/docs/ref/surface.html#pygame.Surface.set_colorkey
There is probably a better way to set the colorkey for all, but I tried this and it seems to work. I see the comment about about alpha, I'm sure that works better too.
In regards to positioning, You would just need to start rendering from the middle of the screen. I was able to do this with your code by simple changing:
x = cartx - carty
print(x)
y = (cartx + carty) / 2
to
x = 320 + ((cartx - carty) / 2)
y = ((cartx+carty) / 4 * 3)
I hope this helps any newcomers to this thread!

Categories