So I've been making this platformer game for a school project, but I've run into a major issue. Whenever my character moves, my platforms (which are made of 32x32 blocks) don't move at the same speed on the x axis and end up clipping into each other until the level and the platforms become tiny.
I first thought that the friction I put on my character might have been the problem as it caused bugs with collision, but after I made it much smoother the problem stayed the same. The weird thing is that all of my platforms will clip into each other except the last one, which doesn't really make sense to me.
Here's the code I'm using to move the platforms so that they give this moving camera effect. The +1s in the bottom half of each half are used to make sure the character can use the latent friction from his movement to slide all the way to the edge of the screen:
if coll:
self.joueur.vel.y*=-0.2
if self.joueur.rect.right>=width-350 and self.joueur.rect.right<width-340 and keys[pygame.K_d]:
self.joueur.pos.x-=abs(self.joueur.vel.x)
for plat in self.platform:
plat.rect.x-=abs(self.joueur.vel.x)
if self.joueur.rect.right>=width-340 and keys[pygame.K_d]:
self.joueur.pos.x-=abs(self.joueur.vel.x)+1
for plat in self.platform:
plat.rect.x-=abs(self.joueur.vel.x)
if self.joueur.rect.left<=width-450 and self.joueur.rect.left>width-460 and keys[pygame.K_a]:
self.joueur.pos.x+=abs(self.joueur.vel.x)
for plat in self.platform:
plat.rect.x+=abs(self.joueur.vel.x)
if self.joueur.rect.left<=width-460 and keys[pygame.K_a]:
self.joueur.pos.x+=abs(self.joueur.vel.x)+1
for plat in self.platform:
plat.rect.x+=abs(self.joueur.vel.x)+1
Here's the full code from my main.py file:
import random
import os
from settings import*
vec=pygame.math.Vector2
gamefile=os.path.dirname(__file__)
imagefile=os.path.join(gamefile,"sprites")
passed_time=0
class Joueur(pygame.sprite.Sprite):
def __init__(self,jeu):
pygame.sprite.Sprite.__init__(self)
self.jeu=jeu
self.bloup=False
self.image=pygame.image.load(os.path.join(imagefile,"32character_1.png"))
self.image.set_colorkey(BLACK)
self.rect=self.image.get_rect()
self.rect.center=(width/2,height/2)
self.pos=vec(width/2,700)
self.vel=vec(0,0)
self.acc=vec(0,0)
def saut(self):
self.rect.y+=1
coll=pygame.sprite.spritecollide(self,self.jeu.platform,False)
self.rect.y-=1
if coll:
self.vel.y=-18 #-18
def update(self):
self.acc=vec(0,0.8)
keys=pygame.key.get_pressed()
if keys[pygame.K_a]:
self.image=pygame.image.load(os.path.join(imagefile,"32character_1_flip.png"))
self.image.set_colorkey(BLACK)
self.acc.x=-acc_joueur
if keys[pygame.K_d]:
self.image=pygame.image.load(os.path.join(imagefile,"32character_1.png"))
self.image.set_colorkey(BLACK)
self.acc.x=acc_joueur
self.acc.x+=self.vel.x*frict_joueur
self.acc.y+=self.vel.y*frict_air
self.vel+=self.acc
self.pos+=self.vel+0.5*self.acc
self.rect.midbottom=self.pos
class Grass(pygame.sprite.Sprite):
def __init__(self,x,y,w,h):
pygame.sprite.Sprite.__init__(self)
self.image=self.image=pygame.image.load(os.path.join(imagefile,"grass_block.png"))
self.image.set_colorkey(BLACK)
self.rect=self.image.get_rect()
self.rect.x=x
self.rect.y=y
class Dirt(pygame.sprite.Sprite):
def __init__(self,x,y,w,h):
pygame.sprite.Sprite.__init__(self)
self.image=self.image=pygame.image.load(os.path.join(imagefile,"dirt_block.png"))
self.image.set_colorkey(BLACK)
self.rect=self.image.get_rect()
self.rect.x=x
self.rect.y=y
class Jeu:
def __init__(self):
pygame.init()
pygame.mixer.init()
self.cadre=pygame.display.set_mode((width,height))
pygame.display.set_caption("Hell yeah")
self.clock=pygame.time.Clock()
self.start_time=pygame.time.get_ticks()
self.lives=3
self.death=0
self.running=True
self.font_name=pygame.font.match_font(font_name)
def new(self):
self.allsprites=pygame.sprite.Group()
self.platform=pygame.sprite.Group()
self.joueur=Joueur(self)
self.allsprites.add(self.joueur)
for grass in grass_list:
p=Grass(*grass)
self.allsprites.add(p)
self.platform.add(p)
for dirt in dirt_list:
p=Dirt(*dirt)
self.allsprites.add(p)
self.platform.add(p)
self.run()
def run(self):
self.playing=True
while self.playing:
self.clock.tick(fps)
self.passed_time=pygame.time.get_ticks()-self.start_time
self.events()
self.update()
self.draw()
def update(self):
self.allsprites.update()
coll=pygame.sprite.spritecollide(self.joueur,self.platform,False)
keys=pygame.key.get_pressed()
if self.joueur.vel.y>0:
if coll:
self.joueur.pos.y=coll[0].rect.top+1
self.joueur.vel.y=0
self.joueur.rect.midbottom=self.joueur.pos
if self.joueur.vel.y<0:
if coll:
self.joueur.vel.y*=-0.2
if self.joueur.rect.right>=width-350 and self.joueur.rect.right<width-340 and keys[pygame.K_d]:
self.joueur.pos.x-=abs(self.joueur.vel.x)
for plat in self.platform:
plat.rect.x-=abs(self.joueur.vel.x)
if self.joueur.rect.right>=width-340 and keys[pygame.K_d]:
self.joueur.pos.x-=abs(self.joueur.vel.x)+1
for plat in self.platform:
plat.rect.x-=abs(self.joueur.vel.x)
if self.joueur.rect.left<=width-450 and self.joueur.rect.left>width-460 and keys[pygame.K_a]:
self.joueur.pos.x+=abs(self.joueur.vel.x)
for plat in self.platform:
plat.rect.x+=abs(self.joueur.vel.x)
if self.joueur.rect.left<=width-460 and keys[pygame.K_a]:
self.joueur.pos.x+=abs(self.joueur.vel.x)+1
for plat in self.platform:
plat.rect.x+=abs(self.joueur.vel.x)+1
if self.joueur.rect.bottom>height:
pygame.time.wait(500)
self.death+=1
self.lives=3-self.death
self.playing=False
def events(self):
for event in pygame.event.get():
if event.type==pygame.QUIT:
if self.playing:
self.playing=False
self.running=False
if event.type==pygame.KEYDOWN:
if event.key==pygame.K_SPACE:
self.joueur.saut()
def draw(self):
self.cadre.fill(SKY_BLUE)
self.allsprites.draw(self.cadre)
self.draw_text(str(round(self.passed_time/1000,1))+" s",22,WHITE,32,15)
if self.lives>1:
self.draw_text(str(self.lives)+" lives",20,WHITE,32,35)
if self.lives==1:
self.draw_text(str(self.lives)+" life",20,ORANGE,32,35)
if self.lives==0:
self.show_go_screen()
pygame.display.update()
def show_start_screen(self):
self.cadre.fill(DARK_BLUE)
self.draw_text("Woodland King's Adventure",50,WHITE,400,200)
self.draw_text("A=Move left, D=Move right, SPACE=Jump",24,LIGHT_YELLOW,400,300)
self.draw_text("Press enter to start!",32,LIGHT_GREEN,400,400)
pygame.display.update()
self.wait_for_start()
def show_go_screen(self):
if not self.running:
return
self.cadre.fill(GRAY)
self.draw_text("GAME OVER",50,DARK_RED,400,400)
self.draw_text("Press ESCAPE to quit",30,YELLOW,400,500)
self.draw_text("Pres R to play again",30,GREEN,400,600)
pygame.display.update()
self.wait_for_end()
def wait_for_start(self):
attente=True
while attente:
self.clock.tick(fps)
for event in pygame.event.get():
if event.type==pygame.QUIT:
attente=False
self.running=False
if event.type==pygame.KEYDOWN:
if event.key==pygame.K_RETURN:
attente=False
def wait_for_end(self):
attente=True
while attente:
self.clock.tick(fps)
for event in pygame.event.get():
if event.type==pygame.QUIT:
attente=False
self.running=False
if event.type==pygame.KEYDOWN:
if event.key==pygame.K_ESCAPE:
attente=False
self.running=False
if event.key==pygame.K_r:
self.death=0
self.lives=3
attente=False
self.show_start_screen()
def draw_text(self,text,size,color,x,y):
font=pygame.font.Font(self.font_name,size)
text_surface=font.render(text,True,color)
text_rect=text_surface.get_rect()
text_rect.midtop=(x,y)
self.cadre.blit(text_surface,text_rect)
j=Jeu()
j.show_start_screen()
while j.running:
j.new()
pygame.quit()
And here's the settings.py file so you can understand what the platforms look like:
height=800
fps=60
font_name="arial"
acc_joueur=0.88
frict_joueur=-0.19
frict_air=-0.02
#(pos_x,pos_y,largeur,hauteur)
dirt_list=[(300,731,32,32),(300,762,32,32),(300,793,32,32),(332,731,32,32),(332,762,32,32),
(332,793,32,32),(364,731,32,32),(364,762,32,32),(364,793,32,32),(396,731,32,32),
(396,762,32,32),(396,793,32,32),(428,731,32,32),(268,731,32,32),(268,762,32,32),
(268,793,32,32),(428,762,32,32),(428,793,32,32),#base platform
(652,607,32,32),(684,607,32,32),(716,607,32,32),(748,607,32,32),#lil platform 2
(940,545,32,32),(940,576,32,32),(940,607,32,32),(940,638,32,32),(940,669,32,32),
(940,700,32,32),(940,731,32,32),(940,762,32,32),(940,793,32,32),(972,545,32,32),
(972,576,32,32),(972,607,32,32),(972,638,32,32),(972,669,32,32),(972,700,32,32),
(972,731,32,32),(972,762,32,32),(972,793,32,32),(1004,545,32,32),(1004,576,32,32),
(1004,607,32,32),(1004,638,32,32),(1004,669,32,32),(1004,700,32,32),(1004,731,32,32),
(1004,762,32,32),(1004,793,32,32),(1036,545,32,32),(1036,576,32,32),(1036,607,32,32),
(1036,638,32,32),(1036,669,32,32),(1036,700,32,32),(1036,731,32,32),(1036,762,32,32),
(1036,793,32,32),(1068,545,32,32),(1068,576,32,32),(1068,607,32,32),(1068,638,32,32),
(1068,669,32,32),(1068,700,32,32),(1068,731,32,32),(1068,762,32,32),(1068,793,32,32),
(1100,545,32,32),(1100,576,32,32),(1100,607,32,32),(1100,638,32,32),(1100,669,32,32),
(1100,700,32,32),(1100,731,32,32),(1100,762,32,32),(1100,793,32,32),(1132,545,32,32),
(1132,576,32,32),(1132,607,32,32),(1132,638,32,32),(1132,669,32,32),(1132,700,32,32),
(1132,731,32,32),(1132,762,32,32),(1132,793,32,32),#big platform 1
(1388,762,32,32),(1388,793,32,32),(1420,762,32,32),(1420,793,32,32),(1452,762,32,32),
(1452,793,32,32),(1484,762,32,32),(1484,793,32,32),(1516,762,32,32),(1516,793,32,32),
(1548,762,32,32),(1548,793,32,32),(1580,762,32,32),(1580,793,32,32),(1612,762,32,32),
(1612,793,32,32),(1644,762,32,32),(1644,793,32,32),(1676,762,32,32),(1676,793,32,32),
(1708,762,32,32),(1708,793,32,32),(1740,762,32,32),(1740,793,32,32),(1772,762,32,32),
(1772,793,32,32),(1804,762,32,32),(1804,793,32,32),#big platform 2
]
grass_list=[(300,700,32,32),(332,700,32,32),(364,700,32,32),(396,700,32,32),(428,700,32,32),
(268,700,32,32), #base platform
(492,638,32,32),(524,638,32,32),#lil platform 1
(652,576,32,32),(684,576,32,32),(716,576,32,32),(748,576,32,32),#lil platform 2
(940,514,32,32),(972,514,32,32),(1004,514,32,32),(1036,514,32,32),(1068,514,32,32),
(1100,514,32,32),(1132,514,32,32),#big platform 1
(1388,731,32,32),(1420,731,32,32),(1452,731,32,32),(1484,731,32,32),(1516,731,32,32),
(1548,731,32,32),(1580,731,32,32),(1612,731,32,32),(1644,731,32,32),(1676,731,32,32),
(1708,731,32,32),(1740,731,32,32),(1772,731,32,32),(1804,731,32,32),#big platform 2
]
WHITE=(255,255,255)
BLACK=(0,0,0)
RED=(255,0,0)
GREEN=(0,255,0)
BLUE=(0,0,255)
ORANGE=(255,128,0)
PURPLE=(127,0,255)
YELLOW=(255,255,0)
GRAY=(128,128,128)
BROWN=(102,51,0)
PINK=(255,102,178)
DARK_RED=(153,0,0)
DARK_BLUE=(0,0,153)
NIGHT_BLUE=(0,0,50)
LIGHT_YELLOW=(255,255,204)
LIGHT_GREEN=(204,255,204)
BROWN=(153,76,0)
NATURE_GREEN=(0,153,0)
SKY_BLUE=(0,204,204)
So yeah the platforms are supposed to move all together. If you want to run this code and see the problem for yourself i'm using the latest version of pygame.
As requested, here is the simplified main.py:
import random
import os
from settings import*
vec=pygame.math.Vector2
gamefile=os.path.dirname(__file__)
imagefile=os.path.join(gamefile,"sprites")
class Joueur(pygame.sprite.Sprite):
def __init__(self,jeu):
pygame.sprite.Sprite.__init__(self)
self.jeu=jeu
self.image=pygame.image.load(os.path.join(imagefile,"32character_1.png"))
self.rect=self.image.get_rect()
self.rect.center=(width/2,height/2)
self.pos=vec(width/2,700)
self.vel=vec(0,0)
self.acc=vec(0,0)
def saut(self):
self.rect.y+=1
coll=pygame.sprite.spritecollide(self,self.jeu.platform,False)
self.rect.y-=1
if coll:
self.vel.y=-18
def update(self):
self.acc=vec(0,0.8)
keys=pygame.key.get_pressed()
if keys[pygame.K_a]:
self.acc.x=-acc_joueur
if keys[pygame.K_d]:
self.acc.x=acc_joueur
self.acc.x+=self.vel.x*frict_joueur
self.acc.y+=self.vel.y*frict_air
self.vel+=self.acc
self.pos+=self.vel+0.5*self.acc
self.rect.midbottom=self.pos
class Grass(pygame.sprite.Sprite):
def __init__(self,x,y,w,h):
pygame.sprite.Sprite.__init__(self)
self.image=self.image=pygame.image.load(os.path.join(imagefile,"grass_block.png"))
self.rect=self.image.get_rect()
self.rect.x=x
self.rect.y=y
class Jeu:
def __init__(self):
pygame.init()
pygame.mixer.init()
self.cadre=pygame.display.set_mode((width,height))
self.clock=pygame.time.Clock()
self.running=True
def new(self):
self.allsprites=pygame.sprite.Group()
self.platform=pygame.sprite.Group()
self.joueur=Joueur(self)
self.allsprites.add(self.joueur)
for grass in grass_list:
p=Grass(*grass)
self.allsprites.add(p)
self.platform.add(p)
self.run()
def run(self):
self.playing=True
while self.playing:
self.clock.tick(fps)
self.events()
self.update()
self.draw()
def update(self):
self.allsprites.update()
coll=pygame.sprite.spritecollide(self.joueur,self.platform,False)
keys=pygame.key.get_pressed()
if self.joueur.vel.y>0:
if coll:
self.joueur.pos.y=coll[0].rect.top+1
self.joueur.vel.y=0
self.joueur.rect.midbottom=self.joueur.pos
if self.joueur.vel.y<0:
if coll:
self.joueur.vel.y*=-0.2
if self.joueur.rect.right>=width-350 and self.joueur.rect.right<width-340 and keys[pygame.K_d]:
self.joueur.pos.x-=abs(self.joueur.vel.x)
for plat in self.platform:
plat.rect.x-=abs(self.joueur.vel.x)
if self.joueur.rect.right>=width-340 and keys[pygame.K_d]:
self.joueur.pos.x-=abs(self.joueur.vel.x)+1
for plat in self.platform:
plat.rect.x-=abs(self.joueur.vel.x)+1
if self.joueur.rect.left<=width-450 and self.joueur.rect.left>width-460 and keys[pygame.K_a]:
self.joueur.pos.x+=abs(self.joueur.vel.x)
for plat in self.platform:
plat.rect.x+=abs(self.joueur.vel.x)
if self.joueur.rect.left<=width-460 and keys[pygame.K_a]:
self.joueur.pos.x+=abs(self.joueur.vel.x)+1
for plat in self.platform:
plat.rect.x+=abs(self.joueur.vel.x)+1
if self.joueur.rect.bottom>height:
pygame.time.wait(500)
self.playing=False
def events(self):
for event in pygame.event.get():
if event.type==pygame.QUIT:
if self.playing:
self.playing=False
self.running=False
if event.type==pygame.KEYDOWN:
if event.key==pygame.K_SPACE:
self.joueur.saut()
def draw(self):
self.cadre.fill(SKY_BLUE)
self.allsprites.draw(self.cadre)
pygame.display.update()
j=Jeu()
while j.running:
j.new()
pygame.quit()
And the simplified settings.py:
height=800
fps=60
acc_joueur=0.88
frict_joueur=-0.19
frict_air=-0.02
#(pos_x,pos_y,largeur,hauteur)
grass_list=[(300,700,32,32),(332,700,32,32),(364,700,32,32),(396,700,32,32),(428,700,32,32),
(268,700,32,32), #base platform
(492,638,32,32),(524,638,32,32),#lil platform 1
(652,576,32,32),(684,576,32,32),(716,576,32,32),(748,576,32,32),#lil platform 2
(940,514,32,32),(972,514,32,32),(1004,514,32,32),(1036,514,32,32),(1068,514,32,32),
(1100,514,32,32),(1132,514,32,32),#big platform 1
(1388,731,32,32),(1420,731,32,32),(1452,731,32,32),(1484,731,32,32),(1516,731,32,32),
(1548,731,32,32),(1580,731,32,32),(1612,731,32,32),(1644,731,32,32),(1676,731,32,32),
(1708,731,32,32),(1740,731,32,32),(1772,731,32,32),(1804,731,32,32),#big platform 2
]
WHITE=(255,255,255)
BLACK=(0,0,0)
RED=(255,0,0)
GREEN=(0,255,0)
BLUE=(0,0,255)
SKY_BLUE=(0,204,204)