Pygame: Kollisionserkennung verhält sich merkwürdig

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
rttc
User
Beiträge: 4
Registriert: Samstag 15. Oktober 2011, 12:43

Hi! Ich bin blutiger Python-Anfänger und wollte mir die Sprache beibringen, indem ich ein kleines Spiel code. Jetzt bin ich allerdings schon auf die erste Hürde gestoßen, bei der ich nur noch ratlos auf den Bildschirm starre. Und zwar habe ich versucht, eine Kollisionserkennung zu implementieren. Ich erzeuge dabei ein Rechteck, dass die zukünftige Position der Spielfigur erhält und wenn dieses mit dem Rechteck einer Wand kollidiert, wird der Vektor, der im nächsten Schritt als Bewegung zum Positionsvektor der Spielfigur hinzuaddiert würde, auf (0, 0) gesetzt. Das Problem ist folgendes: Man kann die Spielfigur durch mehrfaches Drücken der Pfeiltasten immer noch durch eine Wand bewegen. Ich hoffe das ist einigermaßen verständlich, aber hier natürlich noch der relevante Code...

Main:

Code: Alles auswählen

import pygame
from hero import Hero
from wall import Wall
from vector2 import Vector2

pygame.init()

screen = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()
game_objects = []

hero = Hero([10, 10])
wall = Wall([100, 100])

game_objects.append(hero)
game_objects.append(wall)

def update(events):
	for event in events:
		if event.type == pygame.QUIT:
			done = True
		
		if event.type == pygame.KEYDOWN:
			if event.key == pygame.K_LEFT:
				hero.move.x = -hero.speed
			if event.key == pygame.K_RIGHT:
				hero.move.x = hero.speed
			if event.key == pygame.K_UP:
				hero.move.y = -hero.speed
			if event.key == pygame.K_DOWN:
				hero.move.y = hero.speed
		
		if event.type == pygame.KEYUP:
			if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
				hero.move.x = 0
			if event.key == pygame.K_UP or event.key == pygame.K_DOWN:
				hero.move.y = 0
	
	test_rect = pygame.Rect(hero.rect.left + hero.move.x, hero.rect.top + hero.move.y, hero.rect.width, hero.rect.height)
	
	for game_object in game_objects:
		if isinstance(game_object, Wall) and test_rect.colliderect(game_object.rect):
			hero.move = Vector2(0, 0) #Why the HELL is this not working properly?!
			
		game_object.update()

def draw(screen):
	screen.fill((255, 255, 255))
	for game_object in game_objects:
		game_object.draw(screen)
	pygame.display.update()
	clock.tick(60)

done = False
while done == False:
	
	events = pygame.event.get()
	
	for event in events:
		if event.type == pygame.QUIT:
			done = True
			
	update(events)
	draw(screen)
	
pygame.quit()
Game Object:

Code: Alles auswählen

import pygame

class GameObject(pygame.sprite.Sprite):
	def __init__(self, image_path, position):
		super(GameObject, self).__init__()
		self.visible = True
		self.image = pygame.image.load(image_path).convert()
		self.rect = self.image.get_rect()
		self.rect.topleft = position
	
	def update(self):
		pass
		
	def draw(self, screen):
		pass
Wall:

Code: Alles auswählen

import pygame
from gameobject import GameObject

class Wall(GameObject):
	def __init__(self, position):
		super(Wall, self).__init__("wall.png", position)
	
	def update(self):
		pass
		
	def draw(self, screen):
		if self.visible:
			screen.blit(self.image, self.rect)
Hero:

Code: Alles auswählen

import pygame
from gameobject import GameObject
from vector2 import Vector2

class Hero(GameObject):
	def __init__(self, position):
		super(Hero, self).__init__("hero.png", position)
		self.health = 100
		self.position = Vector2(position[0], position[1])
		self.move = Vector2(0, 0)
		self.speed = 4

	def update(self):
		self.position += self.move
		self.rect.topleft = [self.position.x, self.position.y]

	def draw(self, screen):
		if self.visible:
			screen.blit(self.image, self.rect)
Wenn jemand eine Ahnung hat, was da verkehrt läuft, würde er mir sehr weiterhelfen. Ich nehme gerne auch anderweitige Verbesserungsvorschläge entgegen.
BlackJack

@rttc: Lass Dir vor dem Test doch einfach mal die Werte anzeigen, die in der Testbedingung verwendet werden. Vielleicht sind die ja schon nicht mit den Werten bestückt, die Du erwartest. Dann kannst Du versuchen heraus zu finden warum das der Fall ist.

Weitere Bemerkungen zum Quelltext: Du rückst zu weit ein. Konventionell sind das vier Leerzeichen pro Ebene.

Man sollte keinen Code auf Modulebene haben der nicht dazu da ist Namen von Konstanten zu definieren. Also Importe, Funktions- und Klassendefinitionen, und Definitionen von Konstanten. Alles andere sollte in Funktionen oder Klassen verschwinden. Und die sollten nicht auf nicht-konstante Daten ausserhalb zugreifen, die nicht als Argumente die Funktion betreten, oder als Attribute auf dem eigenen Objekt bestehen. Das macht Programme unnötig unübersichtlich und schlecht zu warten und testen.

Das Binden von `done` an `True` in der `update()`-Funktion macht nicht das, was Du vielleicht denkst. `done` ist hier ein lokaler Name, der nichts mit dem `done` auf Modulebene zu tun hat. So etwas sollte man wie gesagt, sowieso gar nicht erst versuchen.

Bei den `KEYUP`-Tests kannst Du etwas Tipparbeit sparen in dem Du nicht mit ``or`` arbeitest, sondern testest ob `event.key` in einer Liste mit den Konstanten enthalten ist: ``if event.key in [pygame.K_LEFT, pygame.K_RIGHT]:``.

Zeilen sollten in der Länge 80 Zeichen nicht überschreiten. Danach wird es schwerer lesbar, vor allem wenn die Zeilen entweder rechts abgeschitten werden, oder umgebrochen werden.

`isisntance()` sollte man nur sehr sparsam verwenden. Das ist fast immer ein Zeichen für schlechte objektorientierte Programmierung. In diesem Fall sollte man den entsprechenden Objekten besser eine Testmethode spendieren, ob der Held durch das Objekt durchlaufen kann oder nicht. Dann musst Du an der Stelle diese Objekttypen nicht hart kodieren.

Bei den abgeleiteten `Sprite`-Klassen musst Du Methoden die nichts tun, nicht immer wieder überschreiben. Ein `update()` was nichts tut gibt es schon in der `Sprite`-Klasse selbst. Und die `draw()`-Methode könntest Du in `GameObject()` verschieben, denn die macht in den beiden abgeleiteten Klassen exakt das selbe.

Du könntest Gebrauch von `sprite.Group()` machen.
rttc
User
Beiträge: 4
Registriert: Samstag 15. Oktober 2011, 12:43

BlackJack hat geschrieben:@rttc: Lass Dir vor dem Test doch einfach mal die Werte anzeigen, die in der Testbedingung verwendet werden. Vielleicht sind die ja schon nicht mit den Werten bestückt, die Du erwartest. Dann kannst Du versuchen heraus zu finden warum das der Fall ist.
Gute Idee, werde ich machen.
BlackJack hat geschrieben: Weitere Bemerkungen zum Quelltext: Du rückst zu weit ein. Konventionell sind das vier Leerzeichen pro Ebene.
Hmm, in Textmate waren das auch nur vier Leerzeichen bzw. ein Tab. Weiss nicht, warum sich das bei Copy & Paste verändert hat.
BlackJack hat geschrieben: Man sollte keinen Code auf Modulebene haben der nicht dazu da ist Namen von Konstanten zu definieren. Also Importe, Funktions- und Klassendefinitionen, und Definitionen von Konstanten. Alles andere sollte in Funktionen oder Klassen verschwinden. Und die sollten nicht auf nicht-konstante Daten ausserhalb zugreifen, die nicht als Argumente die Funktion betreten, oder als Attribute auf dem eigenen Objekt bestehen. Das macht Programme unnötig unübersichtlich und schlecht zu warten und testen.
Das ist für mich gerade nicht so leicht zu verstehen. Meinst du, ich sollte für Variablen wie speed in der Hero-Klasse Getter und Setter schreiben, statt direkt darauf zuzugreifen?

Vielen Dank für die ganzen Anmerkungen. Ich werde auf jeden Fall versuchen, den Code damit zu verbessern.

Edit: Aaaah, meinst du, dass z.B. dieser Teil

Code: Alles auswählen

pygame.init()

screen = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()
game_objects = []

hero = Hero([10, 10])
wall = Wall([100, 100])

game_objects.append(hero)
game_objects.append(wall)
des Main-Moduls in eine Funktion gehört?
BlackJack

@rttc: Es werden wahrscheinlich Tabs sein. Wie breit die sind, ist nirgends einheitlich festgelegt, darum sollte man besser vier echte Leerzeichen verwenden. Jeder vernünftige Texteditor kann das so handhaben, dass man bei der Handhabung kaum einen Unterschied merkt.

Um Himmels willen keine trivialen Getter und Setter. `Hero.speed` ist ja nicht auf Modulebene, sondern ein Attribut auf einem `Hero`-Exemplar. Aber `hero` ist auf Modulebene und die `update()`-Funktion greift da einfach so drauf zu. Ebenso `screen`, `clock`, `game_objects`, `wall`, `done`, `events`, und `event`, und der Code nach der `draw()`-Funktion. Das sollte alles nicht auf Modulebene herumliegen, sondern mindestens in einer Funktion verschwinden.

Dann können einige Deiner Funktionen nicht mehr einfach so darauf zugreifen, das sollten sie ja aber auch nicht. Man müsste ihnen dann das was sie davon benötigen als Argument übergeben.
rttc
User
Beiträge: 4
Registriert: Samstag 15. Oktober 2011, 12:43

Okay, verstehe ich. Danke.
rttc
User
Beiträge: 4
Registriert: Samstag 15. Oktober 2011, 12:43

Ich habe den Fehler jetzt gefunden. Das Problem war, dass jedes Objekt in game_objects direkt nach der Kollisionserkennung geupdated wurde, bevor die Kollisionserkennung mit den restlichen Objekten durchgeführt werden konnte. Eigentlich ein dummer Fehler, aber manchmal sieht man wohl den Wald vor lauter Bäumen nicht.
Antworten