1

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)
  • 1
    Reading @Hoog's answer, and testing the shortened-code, it does not seem to produce the error described. When I run, the "grass" platforms all move together equally, maintaining their relative positions and separation. Am I not understanding the problem description ? – Kingsley Sep 12 '19 at 23:07
  • That's weird... when I run the shortened code the platforms still start clipping into each other because of non uniform movement. Would you have an explanation as to why it works in your case? – Kostivaleureux Sep 14 '19 at 16:00

1 Answers1

0

Looks like you might have missed a +1 in the following:

        self.joueur.pos.x-=abs(self.joueur.vel.x)+1 # here joueur gets +1
        for plat in self.platform:
            plat.rect.x-=abs(self.joueur.vel.x) # But the platforms do not :(

That's the only one I can see right now. Let me know if that fixes it!

Hoog
  • 2,280
  • 1
  • 14
  • 20
  • Unfortunately not :(. This problem arose way before I had inplemented this code to avoid players getting outside of the screen. – Kostivaleureux Sep 12 '19 at 18:54
  • Darn, I can't see what would cause the issue. Could you try creating an MCVE? https://stackoverflow.com/help/minimal-reproducible-example Reduce your code to say 3 platforms, the player only moves right (no control) and generally remove everything you can while still being able to 1. run the script and 2. still see the platforms squish together. Building a minimal example both helps us answer your question AND has the added benefit of making you find the answer yourself some times! Let me know if you get your main script down to ~1/4 the number of lines and I'll give it another look :D – Hoog Sep 12 '19 at 19:06
  • 1
    I'll do this right now i'll try to shrink it down as much as possible – Kostivaleureux Sep 12 '19 at 19:12
  • Don't forget to save a copy to go back to before you go removing everything! – Hoog Sep 12 '19 at 19:13
  • 1
    I shortened it as much as I could while keeping the original movement system, which is pretty big but I hope the new edited version (at the bottom of the question) will be simpler to approach :) – Kostivaleureux Sep 12 '19 at 20:02
  • That is looking much more manageable. I still can't see where the issue is but I have upvoted for visibility and have another suggestion for you. In your `Jeu` class's `update` function you change the `pos.x` and `rect.x` variable in your objects multiple times DIRECTLY. It might be helpful to keep absolute positions for all your objects (player will move, stationary objects will keep a constant number) and then have a `shift_distance` variable that will dictate where the centre of your screen should be. Then, when each object is drawn you draw them at `absolute_position + shift_distance` – Hoog Sep 12 '19 at 21:29
  • By keeping these separate you can make sure your positions for your objects work well without the camera moving THEN simply add the same `shift_distance` number to all of your object positions before drawing them on screen. That was kind of a mouthful, let me know if I did not make it clear. – Hoog Sep 12 '19 at 21:31
  • I got the principle of what you were saying but i'm not entirely sure of how I would implement that into my code, could you maybe give me an example so that I can understand your solution better? – Kostivaleureux Sep 14 '19 at 15:58
  • At this point I would suggest making a new question on the game development part of stack exchange: https://gamedev.stackexchange.com/ the people there would be better suited at helping you with managing absolute and camera positions. Here is a sample question that might help out: https://gamedev.stackexchange.com/questions/165380/how-to-get-camera-to-follow-car – Hoog Sep 15 '19 at 11:01