Python och pygame problem med hackande rörelse samt rect collision

Permalänk
Medlem

Python och pygame problem med hackande rörelse samt rect collision

Hej!

Sitter och jobbar med ett lite hobbyprojekt, vilket just nu har blivit en pong klon. Jag använder mig av python3 och pygame 1.9. Jag är en nybörjare med dessa så det kommer upp lite frågor hela tiden. Det jag tycker är väldigt lustigt just nu (eller lustigast) är två fenomen som jag inte vet hur jag skall tackla.

Det ena är att bollen jag ritar upp hackar lite när den rör sig över spelplanen. Inte alltid men då och då, samt att det ser ut som om jag ritar den två ggr. Jag tycker att jag inte uppdaterar skärmen på flera ställen eller ritar ut den flera gånger. Kan ju vara att jag är hemma blind nu när jag stirrat på koden ett antal timmar och dagar. Det jag provat att göra är att, förutom läsa läsa, är att ändra clock.tick men det verkar följa med hur jag än gör.

Det andra är att kollisionen mellan bollen och paddle. När jag träffar hörnan på paddle så stutsar bollen inne i paddle. Den liksom överskjuter och blir fast genom hela. Är det jag eller funkar inte?

collidelist

Tycker det är en smidig funktion att använda och hade hoppats slippa skriva en egen kollisionshantering.

Jag har alltid funderat på att göra ett "Mario liknande spel" och nu är första gången jag känner att det skulle vara möjligt dock känns de här problemen något som kan sätta grus i maskineriet för mig.

Bifogar koden, hoppas att den går att läsa. Suttit med PEP 8 och tar mig sakta fram i den. Såg precis att det saknas lite docstrings

#!/usr/bin/env python3 """ Pong Clone made by Me with pygame""" import pygame import random from random import randint class Field: """Holds the size and position of the field""" #Maybe it should be a function instead def __init__(self, gameDisplay, fieldXPos, fieldYPos, fieldWidth, fieldHeight, fieldBorderSize): self.fieldXPos = fieldXPos self.fieldYPos = fieldYPos self.fieldWidth = fieldWidth self.fieldHeight = fieldHeight self.fieldBorderSize = fieldBorderSize self.gameDisplay = gameDisplay def draw(self): """Draws the field to the gameDisplay""" pygame.draw.rect(self.gameDisplay, (0, 0, 255), [self.fieldXPos, self.fieldYPos, self.fieldWidth, self.fieldHeight], self.fieldBorderSize) def score(self, player1Score, player2Score): """Handels the score of the players""" self.player1Score = player1Score self.player2Score = player2Score def addScore(self, ballPosition): """Add a score to the current standing in the match""" if ballPosition >= 775: self.player1Score += 1 elif ballPosition <= 15: self.player2Score += 1 def drawScore(self): """Draws the score to the screen to the field""" font=pygame.font.Font(None, 100) scoretext=font.render(str(self.player1Score) + ' : ' + str(self.player2Score), 1, (255, 255, 255)) self.gameDisplay.blit(scoretext, (350, 20)) class Paddle(object): """Holds size and position of the paddle also moves and draws it to the display""" def __init__(self, gameDisplay, paddleXPos, paddleYPos, paddleWidth, paddleHeight): self.paddleXPos = paddleXPos self.paddleYPos = paddleYPos self.gameDisplay = gameDisplay self.paddleWidth = paddleWidth self.paddleHeight = paddleHeight self.direction = 0 def draw(self): """Draws the paddle to the display""" self.move(self.direction) pygame.draw.rect(self.gameDisplay, (255, 0, 0), [self.paddleXPos, self.paddleYPos, self.paddleWidth, self.paddleHeight]) def move(self, direction): """Moves the paddle according to direction""" self.direction = direction self.paddleYPos += self.direction if self.paddleYPos <= 100: self.paddleYPos = 100 self.direction = 0 if self.paddleYPos >= 490: self.paddleYPos = 490 self.direction = 0 @property def rect(self): return pygame.Rect(self.paddleXPos, self.paddleYPos, self.paddleWidth, self.paddleHeight) class Ball(object): """Holds size and position""" def __init__(self, gameDisplay, ballXPos, ballYPos, xDirection, yDirection): self.ballXPos = self.ballStartXPos = ballXPos self.ballYPos = self.ballStartYPos = ballYPos self.gameDisplay = gameDisplay self.xDirection = xDirection self.yDirection = yDirection self.xSpeed = 5 self.ySpeed = 5 def draw(self): """Draws the ball to the screen""" pygame.draw.rect(self.gameDisplay, (255, 0, 0), [self.ballXPos, self.ballYPos, 10, 10]) def move(self, obstacles): """Direction and speed of the ball""" self.ballXPos = self.ballXPos + (self.xSpeed * self.xDirection) self.ballYPos = self.ballYPos + (self.ySpeed * self.yDirection) if self.ballXPos >= 780: print('xPos= ' + str(self.ballXPos)) self.xDirection = -self.xDirection self.reset() elif self.ballXPos <= 10: print('xPos= ' + str(self.ballXPos)) self.xDirection = -self.xDirection self.reset() if self.ballYPos >= 580: self.yDirection = -self.yDirection elif self.ballYPos <= 100: self.yDirection = -self.yDirection if self.rect.collidelist(obstacles) != -1: self.xDirection = -self.xDirection def reset(self): """Resets the ball to start position""" self.ballXPos = self.ballStartXPos self.ballYPos = self.ballStartYPos @property def rect(self): return pygame.Rect(self.ballXPos, self.ballYPos, 10, 10) def windowSetup(): """Setting all initial variables and functions for start up""" pygame.display.set_caption('Pong Clone') windowWidth = 800 windowHeight = 600 def texts(gameDisplay, score): font=pygame.font.Font(None,30) scoretext=font.render("Score:"+str(score), 1,(255,255,255)) gameDisplay.blit(scoretext, (500, 457)) def main(): """Pong Clone that uses pygame""" pygame.init() windowSetup() clock = pygame.time.Clock() gameExit = False gameDisplay = pygame.display.set_mode((800, 600)) paddleWidth = 10 paddleHeight = 100 xPos = 400 yPos = 300 court = Field(gameDisplay, 8,98, 782, 492, 2) court.score(0, 0) player1 = Paddle(gameDisplay, 50, 250, paddleWidth, paddleHeight) player2 = Paddle(gameDisplay, 750, 250, paddleWidth, paddleHeight) ball = Ball(gameDisplay, 400, 295, 1, 1) players = (player1, player2) player1.draw() player2.draw() ball.draw() print(randint(0, 10)) while not gameExit: #Main game loop for event in pygame.event.get(): #Event handler if event.type == pygame.QUIT: gameExit = True if event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: gameExit = True elif event.key == pygame.K_LEFT: xPos -= 5 elif event.key == pygame.K_RIGHT: xPos += 5 elif event.key == pygame.K_UP: yPos -= 5 player1.move(-5) player2.move(-5) elif event.key == pygame.K_DOWN: yPos += 5 player1.move(5) player2.move(5) if event.type == pygame.KEYUP: if event.key == pygame.K_UP: yPos -= 5 player1.move(0) player2.move(0) elif event.key == pygame.K_DOWN: yPos += 5 player1.move(0) player2.move(0) gameDisplay.fill((0, 0, 0)) court.draw() court.drawScore() pygame.draw.rect(gameDisplay, (255, 0, 0), [xPos, yPos, 10, 10], 2) for player in players: player.draw() ball.move(players) ball.draw() court.addScore(ball.rect[0]) pygame.display.update() clock.tick(30) pygame.quit() quit() if __name__ == "__main__": main()

Min kod till spelet
Visa signatur

Laptop: HP Elitebook 640 G9
Server: HP Microserver N54L, 8 GB ram, 8 TB hd.

Permalänk
Medlem

Ser inget speciellt problem av att kolla i koden iaf, skulle nog behöva prova det och experimentera lite.

Du lade inte till sakerna en och en förresten? Så har jag brukat göra när jag gjort liknande spel, dvs skapat bollen först som kan visas på skärmen, ha logik så den rör sig automatiskt och ändrar riktning på väggar. När det är klart så får man liksom svart-på-vitt om man tänkt rätt när det gäller uppdateringen av skärmen, framerate och så vidare. Då när den biten är klar kan man gå vidare till att skapa alla andra klasser och sånt.

Nu ser jag däremot att allting redan är klart när du stöter på detta problem, du märkte inte detta innan när du provade? Föreslår att du provar bara att skapa bollen och se om det fungerar då.

Angående att bollen fastnar i paddeln, det beror med största sannolikhet att den ändrar x-riktning konstant utan att få en chans att ta sig ur. Det skulle du kunna prova att lösa genom att skjuta ut bollen med en likvärdig fart i x-led åt motsatt håll, eller kolla den "framtida" positionen på bollen istället för den aktuella positionen. Detta brukar vara problemet i såna här fall.

Permalänk
Medlem

@zeem: Har mycket begränsad erfarenhet av pygame, men är rätt väl bevandrad i Python i allmänhet, så ska försöka ge dig några tips.

1. Du har rätt många klasser med egna positioner. Du kan använda pygame.Rect istället, då denna klass har storlek och position, det skulle dra ned komplexiteten en hel del. Desutom kan du då använda denna instans av Rect när du ritar ut objekten.
2. Om du gör ovanstående kan du använda pygame.Rect.move(x, y) eller pygame.Rect.move_ip(x, y) för att förflytta dina objekt.
3. collidelist fungerar fint, men den är endast collision detection, medan det du måste sköta själv (och inte gör korrekt) är collision response. Problemet är att när du upptäcker en kollision på paddlen så inverterar du farten i x-led. Men om kollisionen kommer på ovansidan av paddlen, så skedde kollisionen egentligen i y-led, så bollen kan fritt färdas in i paddlen, och kommer då att fortsätta "kollidera" inuti paddlen tills den kommer ut på andra sidan. Du måste ta reda på vilken ytan som bollen kolliderar med, och invertera rätt hastighetskomponent.

Det finns ett till problem med din kollisionshantering. När du upptäcker en kollision så inverterar då hastigheten i kollisionens normalriktning (eller, ja, alltid i x-led men det är oftast normalriktningen), vilket är korrekt, men du bör även förflytta objekten så att de inte längre kolliderar. När du upptäcker en kollision överlappar ju nämligen objekten redan. På grund av att du just nu förflyttar bollen först och kollar kollision sedan, samt att du har fullständigt elastiska stötar (ingen förlorad fart vid kollision), så fungerar det, men gör du en mer komplicerad simulering kommer det att haverera. Ett annat, och troligtvis vettigare, sätt att jobba med kollisioner är att förutse om objekten kommer att kollidera i nästa simuleringssteg, men det är lite mer komplicerat om man ska få det att se snyggt ut. Notera att det egentligen inte räcker att bara kolla om två objekt redan HAR kolliderat, för om något av objekten har såpass hög fart att det i ett simuleringssteg förflyttar sig förbi det andra objektet, så kommer det hipp som haver att passera rakt igenom.
4. Jag rekommenderar att du inte använder pygame.init(), då denna funktion initialiserar ALLA pygame-moduler, vilket kan leda till mycket hög cpu-belastning (i mitt fall, 100% på en kärna). De enda moduler du just nu verkar använda är pygame.font och pygame.display, så det räcker med att starta dessa med:

pygame.display.init() pygame.font.init()

Detta kan eventuellt avhjälpa att den ritar hackigt, men jag har inte gått in tillräckligt djupt i koden för att se om det kan finnas andra problem.

Hoppas något av detta hjälper dig.

Permalänk
Medlem
Skrivet av MrDoggo:

Ser inget speciellt problem av att kolla i koden iaf, skulle nog behöva prova det och experimentera lite.

Du lade inte till sakerna en och en förresten? Så har jag brukat göra när jag gjort liknande spel, dvs skapat bollen först som kan visas på skärmen, ha logik så den rör sig automatiskt och ändrar riktning på väggar. När det är klart så får man liksom svart-på-vitt om man tänkt rätt när det gäller uppdateringen av skärmen, framerate och så vidare. Då när den biten är klar kan man gå vidare till att skapa alla andra klasser och sånt.

Nu ser jag däremot att allting redan är klart när du stöter på detta problem, du märkte inte detta innan när du provade? Föreslår att du provar bara att skapa bollen och se om det fungerar då.

Angående att bollen fastnar i paddeln, det beror med största sannolikhet att den ändrar x-riktning konstant utan att få en chans att ta sig ur. Det skulle du kunna prova att lösa genom att skjuta ut bollen med en likvärdig fart i x-led åt motsatt håll, eller kolla den "framtida" positionen på bollen istället för den aktuella positionen. Detta brukar vara problemet i såna här fall.

Hej och tack för ditt svar!

Jo jag började med att skapa en boll som studsade runt, dock tänkte jag inte så mycket på det vid det tillfället. Blev överlycklig att jag lyckats få något som studsade runt på skärmen. Därefter började jag lägga till paddlarna och de var en ordentlig nöt att knäcka för mig och ju längre projektet på gick så började jag märka problemet.

Att bollen fastnar så kanske jag lägger till en variabel som ändrar sig efter första studsen och går ett liten stund innan den ändrar tillbaka sig. Borde lösa problemet men får klura lite på det där med "framtida positionen" lite.

Visa signatur

Laptop: HP Elitebook 640 G9
Server: HP Microserver N54L, 8 GB ram, 8 TB hd.

Permalänk
Medlem
Skrivet av SimpLar:

@zeem: Har mycket begränsad erfarenhet av pygame, men är rätt väl bevandrad i Python i allmänhet, så ska försöka ge dig några tips.

1. Du har rätt många klasser med egna positioner. Du kan använda pygame.Rect istället, då denna klass har storlek och position, det skulle dra ned komplexiteten en hel del. Desutom kan du då använda denna instans av Rect när du ritar ut objekten.
2. Om du gör ovanstående kan du använda pygame.Rect.move(x, y) eller pygame.Rect.move_ip(x, y) för att förflytta dina objekt.
3. collidelist fungerar fint, men den är endast collision detection, medan det du måste sköta själv (och inte gör korrekt) är collision response. Problemet är att när du upptäcker en kollision på paddlen så inverterar du farten i x-led. Men om kollisionen kommer på ovansidan av paddlen, så skedde kollisionen egentligen i y-led, så bollen kan fritt färdas in i paddlen, och kommer då att fortsätta "kollidera" inuti paddlen tills den kommer ut på andra sidan. Du måste ta reda på vilken ytan som bollen kolliderar med, och invertera rätt hastighetskomponent.

Det finns ett till problem med din kollisionshantering. När du upptäcker en kollision så inverterar då hastigheten i kollisionens normalriktning (eller, ja, alltid i x-led men det är oftast normalriktningen), vilket är korrekt, men du bör även förflytta objekten så att de inte längre kolliderar. När du upptäcker en kollision överlappar ju nämligen objekten redan. På grund av att du just nu förflyttar bollen först och kollar kollision sedan, samt att du har fullständigt elastiska stötar (ingen förlorad fart vid kollision), så fungerar det, men gör du en mer komplicerad simulering kommer det att haverera. Ett annat, och troligtvis vettigare, sätt att jobba med kollisioner är att förutse om objekten kommer att kollidera i nästa simuleringssteg, men det är lite mer komplicerat om man ska få det att se snyggt ut. Notera att det egentligen inte räcker att bara kolla om två objekt redan HAR kolliderat, för om något av objekten har såpass hög fart att det i ett simuleringssteg förflyttar sig förbi det andra objektet, så kommer det hipp som haver att passera rakt igenom.
4. Jag rekommenderar att du inte använder pygame.init(), då denna funktion initialiserar ALLA pygame-moduler, vilket kan leda till mycket hög cpu-belastning (i mitt fall, 100% på en kärna). De enda moduler du just nu verkar använda är pygame.font och pygame.display, så det räcker med att starta dessa med:

pygame.display.init() pygame.font.init()

Detta kan eventuellt avhjälpa att den ritar hackigt, men jag har inte gått in tillräckligt djupt i koden för att se om det kan finnas andra problem.

Hoppas något av detta hjälper dig.

Hej och tack för dit svar!

1,2: Bara så jag förstår det hela rätt och om jag gjort det så funkar nog det mycket enklare. Istället för att skapa min klasser som jag gjort så byter jag ut dem mot:

player1 = pygame.Rect... player2 = pygame.Rect... ball = pygame.Rect...

Skall jag skippa att använda klasser helt och bara använda funktioner? Som jag tänkte när jag skapade klasserna var att det var ett snyggt sätt att samla funktionerna i. Tanken att använda superklass har funnits men inte kunskapen hur jag skulle få ihop det. En annan tanke var att det kanske blev enklare att läsa koden när jag gjorde som jag gjorde. Är sjukt nojig för att inte få till en läsbar kod vilket jag fått för mig är mycket viktig. Det som jag insåg under tiden är att jag inte läser på hur pygame fungerar. Tog en funktion om skrev för glatta livet. Vill inte att det skall låta som om jag sätter mig i försvarsställning snarare en förklaring på hur jag tänkte. För går det att göra enklare och snyggare så varför inte. Att använda move funktionen verkar främmande men jag kan för lite om den men borde generera lite mindre kod.

Det som blockerar min tankegång är att kanske main() funktionen blir stor kanske inte gör något dock. Inser under tiden jag skriver detta att jag kanske skall börja om från början (kill your darlings) för att inte fastna i en gammal tankegång.

3: Tack för förklaringen! Även här får jag tänka igenom vad jag håller på med och hur det kan fungera istället. Att förutse vart jag träffar blir det:
nuvarande position -> nästa position -> val: fortsätta i given riktning eller ändra -> ändra position -> rita ut på skärmen?

4: Provade att bara initiera font och display men jag får ingen skillnad i processor användning vad jag kan se. Upptäckte att jag inte använt aktivitetshanteraren på väldigt länge. Sitter på två olika datorer just nu i7 båda två och samma fenomen med bollen. Har inte provat på min RPi där spelet skall hamna till sist.
Tack för förklaringen med pygame.init() hade jag missat. Kan ju ställa till det när jag lyfter över spelet till RPi om den sätter igång massor av grejer som inte behövs. Det som är ett mysterium då är varför jag inte får någon spik i CPU användning om nu du fick det.

Visa signatur

Laptop: HP Elitebook 640 G9
Server: HP Microserver N54L, 8 GB ram, 8 TB hd.

Permalänk
Medlem

@zeem:

1,2: Nej, klasser kan du fortfarande använda om du gillar det, pygame är ju objektorienterat så klasser är passande. Menade att du kunde ersätta alla interna koordinater med en instans av pygame.Rect, eftersom det ändå är vad du vill få ut i slutändan.

move-funktionerna är mycket enkla. Låt r vara en instans av rect. pygame.Rect.move(x, y) returnerar en ny instans av pygame.Rect förflyttad x steg i x-led och y steg i y-led. pygame.Rect.move_ip(x, y) förändrar instansen in place.

# Dessa rader är ekvivalenta r = r.move(2, 3) r.move_ip(2, 3)

3. Det är knepigt det där. Poängen med att göra någon form av prediction är att kolla "kommer x kollidera med y någon gång mellan nu och nästa steg i simuleringen". Att bara kolla "kommer x kollidera med y i nästa tidssteg" är snarlikt att kolla i samma tidssteg. Men jag skulle personligen börja så enkelt som möjligt, och endast öka på komplexiteten om det inte fungerar. Det enklaste är att i varje simuleringssteg kolla om bollen kolliderat med någonting, precis som du gör just nu, och om den har gjort det så förflyttare du den så att den precis inte kolliderar, och inverterar farten i kollisionsytans normalriktning. Dvs, i x-led om den träffar framifrån paddlen, och i y-led om den träffar taket eller ovansidan av paddlen.

4. Mycket möjligt att det är platformsberoende, och jag tror det är en bugg. Om det påverkar dig kommer du ha en tråd som kör på 100% (Python är enkeltrådat). Jag kör på Arch Linux, och vet att andra på samma platform har det här problemet.