Collisions

The game screen appears here if your browser supports the Canvas API.

Attribution

Essentials - Make Games with Python, pages 81 - 93.

Licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported.

Original Python code


import pygame, sys, random, math
import pygame.locals as GAME_GLOBALS
import pygame.event as GAME_EVENTS
import pygame.time as GAME_TIME

windowWidth = 1024
windowHeight = 768

pygame.init()
clock = pygame.time.Clock()
surface = pygame.display.set_mode((windowWidth, windowHeight))

pygame.display.set_caption('Collisions')

previousMousePosition = [0,0]
mousePosition = None
mouseDown = False

collidables = []
currentObject = None
expanding = True

drawAttractions = False

gravity = 1.0

def drawCollidables():

	for anObject in collidables:
		anObject["position"][0] += anObject["velocity"][0]
		anObject["position"][1] += anObject["velocity"][1]

		pygame.draw.circle(surface, (255,255,255), (int(anObject["position"][0]), int(anObject["position"][1])), int(anObject["radius"]), 0)

def drawCurrentObject():

	global expanding, currentObject

	currentObject["position"][0] = mousePosition[0]
	currentObject["position"][1] = mousePosition[1]

	if expanding is True and currentObject["radius"] < 30:
		currentObject["radius"] += 0.2

		if currentObject["radius"] >= 30:
			expanding = False
			currentObject["radius"] = 9.9

	elif expanding is False and currentObject["radius"] > 1:
		currentObject["radius"] -= 0.2

		if currentObject["radius"] <= 1:
			expanding = True
			currentObject["radius"] = 1.1

	currentObject["mass"] = currentObject["radius"]

	pygame.draw.circle(surface, (255,0,0), (int(currentObject["position"][0]), int(currentObject["position"][1])), int(currentObject["radius"]), 0)

def calculateMovement():

	for anObject in collidables:

		for theOtherObject in collidables:

			if anObject is not theOtherObject:

				direction = (theOtherObject["position"][0] - anObject["position"][0], theOtherObject["position"][1] - anObject["position"][1])
				magnitude = math.hypot(theOtherObject["position"][0] - anObject["position"][0], theOtherObject["position"][1] - anObject["position"][1])
				nDirection = (direction[0] / magnitude, direction[1] / magnitude)

				if magnitude < 5:
					magnitude = 5
				elif magnitude > 15:
					magnitude = 15

				strength = ((gravity * anObject["mass"] * theOtherObject["mass"]) / (magnitude * magnitude)) / theOtherObject["mass"]

				appliedForce = (nDirection[0] * strength, nDirection[1] * strength)

				theOtherObject["velocity"][0] -= appliedForce[0]
				theOtherObject["velocity"][1] -= appliedForce[1]

				if drawAttractions is True:
					pygame.draw.line(surface, (255,255,255), (anObject["position"][0],anObject["position"][1]), (theOtherObject["position"][0],theOtherObject["position"][1]), 1)

def handleCollisions():

	h = 0

	while h < len(collidables):

		i = 0

		anObject = collidables[h]

		while i < len(collidables):

			otherObject = collidables[i]

			if anObject != otherObject:

				distance = math.hypot(otherObject["position"][0] - anObject["position"][0], otherObject["position"][1] - anObject["position"][1])

				if distance < otherObject["radius"] + anObject["radius"]:

					# First we get the angle of the collision between the two objects
					collisionAngle = math.atan2(anObject["position"][1] - otherObject["position"][1], anObject["position"][0] - otherObject["position"][0])

					#Then we need to calculate the speed of each object
					anObjectSpeed = math.sqrt(anObject["velocity"][0] * anObject["velocity"][0] + anObject["velocity"][1] * anObject["velocity"][1])
					theOtherObjectSpeed = math.sqrt(otherObject["velocity"][0] * otherObject["velocity"][0] + otherObject["velocity"][1] * otherObject["velocity"][1])

					# Now, we work out the direction of the objects in radians
					anObjectDirection = math.atan2(anObject["velocity"][1], anObject["velocity"][0])
					theOtherObjectDirection = math.atan2(otherObject["velocity"][1], otherObject["velocity"][0])

					# Now we calculate the new X/Y values of each object for the collision
					anObjectsNewVelocityX = anObjectSpeed * math.cos(anObjectDirection - collisionAngle)
					anObjectsNewVelocityY = anObjectSpeed * math.sin(anObjectDirection - collisionAngle)

					otherObjectsNewVelocityX = theOtherObjectSpeed * math.cos(theOtherObjectDirection - collisionAngle)
					otherObjectsNewVelocityY = theOtherObjectSpeed * math.sin(theOtherObjectDirection - collisionAngle)

					# We adjust the velocity based on the mass of the objects
					anObjectsFinalVelocityX = ((anObject["mass"] - otherObject["mass"]) * anObjectsNewVelocityX + (otherObject["mass"] + otherObject["mass"]) * otherObjectsNewVelocityX)/(anObject["mass"] + otherObject["mass"])
					otherObjectsFinalVelocityX = ((anObject["mass"] + anObject["mass"]) * anObjectsNewVelocityX + (otherObject["mass"] - anObject["mass"]) * otherObjectsNewVelocityX)/(anObject["mass"] + otherObject["mass"])

					# Now we set those values
					anObject["velocity"][0] = anObjectsFinalVelocityX
					otherObject["velocity"][0] = otherObjectsFinalVelocityX


			i += 1

		h += 1

def handleMouseDown():
	global currentObject

	currentObject = {
		"radius" : 3,
		"mass" : 3,
		"velocity" : [0,0],
		"position" : [0,0]
	}

def quitGame():
	pygame.quit()
	sys.exit()

# 'main' loop
while True:

	surface.fill((0,0,0))
	mousePosition = pygame.mouse.get_pos()

	# Handle user and system events
	for event in GAME_EVENTS.get():

		if event.type == pygame.KEYDOWN:

			if event.key == pygame.K_ESCAPE:
				quitGame()

		if event.type == pygame.KEYUP:

			if event.key == pygame.K_r:
				collidables = []
			if event.key == pygame.K_a:
				if drawAttractions is True:
					drawAttractions = False
				elif drawAttractions is False:
					drawAttractions = True

		if event.type == pygame.MOUSEBUTTONDOWN:
			mouseDown = True
			handleMouseDown()

		if event.type == pygame.MOUSEBUTTONUP:
			mouseDown = False

		if event.type == GAME_GLOBALS.QUIT:
			quitGame()

	calculateMovement()
	handleCollisions()
	drawCollidables()

	if currentObject is not None:
		drawCurrentObject()

		# If our user has released the mouse, add the new anObject to the collidables list and let gravity do its thing
		if mouseDown is False:
			currentObject["velocity"][0] = (mousePosition[0] - previousMousePosition[0]) / 4
			currentObject["velocity"][1] = (mousePosition[1] - previousMousePosition[1]) / 4
			collidables.append(currentObject)
			currentObject = None

	# Store the previous mouse coordinates to create a vector when we release a new anObject
	previousMousePosition = mousePosition

	clock.tick(60)
	pygame.display.update()