Search
• Max Clark

# Creating the Solar System - Part 2

Part 2 of this series will look at adding a first person view mode to our solar system simulator. Please read part 1 before reading this.

There are two new variables to be added after the line 'view_mode = 0'. When the view mode is set to 0, the user will see a top down view and a view mode of 1 will display the first person view. We will set the field of view (fov) to 90 degrees. The variable 'offset' will be the angle at which the user is looking (or the camera angle). We will use the mouse to change the camera angle, just like in most first person games.

fov = 90 offset = 0

In order to render the planets/stars correctly, we must render the closer bodies on top of the farther bodies. Therefore we must store all of the distances between the bodies and the spaceship. We will store it in the dictionary 'bodies'. We need to set the the distance from body 0 to the spaceship as 0.0 since body 0 is the spaceship. We will use an array with the first value being the distance and the second being the body number. This can be done after we define the dictionary.

bodies["dist0"] = [0.0,0]

Now, we will set the key v to toggle the view mode. This can be done anywhere within the loop: 'for event in pygame.event.get():'.

if keys[pygame.K_v]: if view_mode == 0: view_mode = 1 else: view_mode = 0

After the 'event' loop, the array 'collect' will store the distances spoken about above.

collect = []

When in first person view, we want the key w to apply thrust in the direction the camera is facing, not "north". We will use the variable 'foffset' (meaning force offset) to change the angle of the force applied. When not in first person view, we do not want the camera angle to have an effect, hence we set it to zero. This will be a small adaption to the current code implemented in part 1.

if view_mode == 1: foffset = offset else: foffset = 0

if keys[pygame.K_w]: hf, vf = apply_force(15*pow(10,thrust),0 - foffset,hf,vf) if keys[pygame.K_a]: hf, vf = apply_force(15*pow(10,thrust),270 - foffset,hf,vf) if keys[pygame.K_s]: hf, vf = apply_force(15*pow(10,thrust),180 - foffset,hf,vf) if keys[pygame.K_d]: hf, vf = apply_force(15*pow(10,thrust),90 - foffset,hf,vf)

Directly before the line, 'if d < int(pow(bodies["mass{0}".format(b)]/pow(10,22),1/3.5)) + 2:', we will store the distances with the respective body.

bodies["dist{0}".format(b)] = [d,b] collect.append(bodies["dist{0}".format(b)])

Now we want to sort the array 'collect'. This happens directly before we comment, '#Move'.

collect.sort(reverse=True)

Finally, we can start the code to be run if the view mode is 1. Add the elif statement after the if statement, 'if view_mode == 0:'.

elif view_mode == 1:

Next, 'px' and 'py' are the coordinates of the spaceship.

px, py = bodies["pos0"], bodies["pos0"]

The camera angle will be changed if the user moves the mouse or presses the left/right arrow keys.

if keys[pygame.K_RIGHT]: offset -= 0.1 if keys[pygame.K_LEFT]: offset += 0.1 while offset <= -180: offset += 360 while offset > 180: offset -= 360

delta_x, delta_y = pygame.mouse.get_rel() pygame.mouse.set_visible(False) pygame.event.set_grab(True) offset -= delta_x/15

Now we will render the bodies in order; 'a' is the number of the current body in question, 'mx' and 'my' are the coordinates of body a. The correct angle between body a and the camera angle is calculated. Then, 'd' and 'r' are the distance and radius of body a. The variable, 'rad' is the radius of body a on screen and takes into account the distance of body a. Finally, we draw the circles with the x values being calculated using the angle, field of view and screen width. The y value will always be on the horizon since it is a 2 dimensional solar system.

for c in collect: a = c mx, my = bodies["pos{0}".format(a)], bodies["pos{0}".format(a)]

angle = degrees(atan2((my - py),(mx - px))) if -135 <= offset and offset < 45: if mx < px and my >= py: angle -= 270 - offset else: angle += 90 + offset else: if mx <= px and my < py: angle += 90 + offset else: angle -= 270 - offset d = pythag(py - bodies["pos{0}".format(a)],px - bodies["pos{0}".format(a)])

r = pow(bodies["mass{0}".format(a)]/pow(10,22),1/2) + 1 if d >= 11: rad = int(pythag(ySize,xSize)*degrees(atan(r/(d - 8)))/150) else: rad = int(pythag(ySize,xSize)*degrees(atan(r/(d*d*d*3/1331)))/150)

pygame.draw.circle(screen, bodies["color{0}".format(a)], (int(xSize/2 + angle/fov*xSize),int(ySize/2)),rad) pygame.draw.circle(screen, bodies["color{0}".format(a)], (int(xSize/2 + (angle + 360)/fov*xSize),int(ySize/2)),rad) pygame.draw.circle(screen, bodies["color{0}".format(a)], (int(xSize/2 + (angle - 360)/fov*xSize),int(ySize/2)),rad)

My current version of the project is on GitHub here. Here's the whole code:

import pygame import time import os from random import randint from math import sqrt, atan, atan2, sin as sine, cos as cosine, radians, degrees

def pythag(a,b): return sqrt(a*a + b*b)

def apply_force(magnitude,angle,hf,vf): hf += magnitude*sin(angle) vf += -magnitude*cos(angle) return hf, vf

def add_body(name,col,mass,pos,hv,vv): bodies["name{0}".format(bodies["num"])] = name bodies["mass{0}".format(bodies["num"])] = mass bodies["color{0}".format(bodies["num"])] = col bodies["pos{0}".format(bodies["num"])] = pos bodies["hv{0}".format(bodies["num"])] = hv bodies["vv{0}".format(bodies["num"])] = vv bodies["num"] += 1

def conv_z(pos,panx,pany,zoom): return (int((pos + panx - xSize/2)*zoom + xSize/2),int((pos + pany - ySize/2)*zoom + ySize/2))

dir_path = os.path.dirname(os.path.realpath(__file__))

pygame.init() infoObject = pygame.display.Info() xSize, ySize = infoObject.current_w, infoObject.current_h screen = pygame.display.set_mode((xSize, ySize), pygame.NOFRAME | pygame.FULLSCREEN) pygame.display.set_caption("Gravity")

#----------------------Main Loop----------------------#

zoom_d = 3*sqrt(xSize)/5 #zoom focus = 0 view_mode = 0

fov = 90

offset = 0

G = pow(10,-21)

bodies = {} bodies["num"] = 0

bodies["dist0"] = [0.0,0]

fps = 60 clock = pygame.time.Clock() frameCount = 0 done = False while not done: frameCount += 1 keys = pygame.key.get_pressed() for event in pygame.event.get(): if event.type == pygame.QUIT: done = True if keys[pygame.K_0]: focus = 0 if keys[pygame.K_1]: focus = 1 if keys[pygame.K_2]: focus = 2 if keys[pygame.K_3]: focus = 3 if keys[pygame.K_4]: focus = 4 if keys[pygame.K_5]: focus = 5 if keys[pygame.K_6]: focus = 6 if keys[pygame.K_7]: focus = 7 if keys[pygame.K_8]: focus = 8 if keys[pygame.K_9]: focus = 9 focus = min(bodies["num"] - 1,focus)

if keys[pygame.K_v]: if view_mode == 0: view_mode = 1 else: view_mode = 0

if keys[pygame.K_PAGEUP]: zoom_d -= 1 if keys[pygame.K_PAGEDOWN]: zoom_d += 1 zoom_d = max(zoom_d,1) zoom = xSize/(zoom_d*zoom_d) screen.fill([0,0,0])

phf, pvf = 0, 0 #player horizontal/vertical velocity

collect = []

for a in range(bodies["num"] - 1): for b in range(bodies["num"] - 1): if b != a: px1, py1 = bodies["pos{0}".format(b)] px2, py2 = bodies["pos{0}".format(a)] th = degrees(atan2((py2 - py1),(px2 - px1))) - 90 #theta, in degrees d = pythag(py1 - py2,px1 - px2) + 0.00000001 #true distance

f = G*bodies["mass{0}".format(a)]*bodies["mass{0}".format(b)]/(d*d) #gravitational force hf = 0 #horizontal force vf = 0 #vertical force

if a == 0: if view_mode == 1: foffset = offset else: foffset = 0 if keys[pygame.K_LSHIFT]: thrust = 2 elif keys[pygame.K_SPACE]: thrust = 0.3 else: thrust = 1 if b == 1: if keys[pygame.K_w]: hf, vf = apply_force(15*pow(10,thrust),0 - foffset,hf,vf) if keys[pygame.K_a]: hf, vf = apply_force(15*pow(10,thrust),270 - foffset,hf,vf) if keys[pygame.K_s]: hf, vf = apply_force(15*pow(10,thrust),180 - foffset,hf,vf) if keys[pygame.K_d]: hf, vf = apply_force(15*pow(10,thrust),90 - foffset,hf,vf)

bodies["dist{0}".format(b)] = [d,b] collect.append(bodies["dist{0}".format(b)])

if d < int(pow(bodies["mass{0}".format(b)]/pow(10,22),1/3.5)) + 2: print("You crashed into",bodies["name{0}".format(b)] + ".") done = True site = bodies["name{0}".format(b)] hf, vf = apply_force(f,th,hf,vf)

if a == 0: phf += hf pvf += vf

ha = hf/bodies["mass{0}".format(a)] #horizontal acceleration va = vf/bodies["mass{0}".format(a)]

bodies["hv{0}".format(a)] += ha*5/fps #horizontal velocity bodies["vv{0}".format(a)] += va*5/fps collect.sort(reverse=True) #Move for a in range(bodies["num"]): bodies["pos{0}".format(a)] += bodies["hv{0}".format(a)]*5/fps bodies["pos{0}".format(a)] += bodies["vv{0}".format(a)]*5/fps

#Draw if view_mode == 0: px, py = bodies["pos0"], bodies["pos0"] panx = xSize/2 - bodies["pos{0}".format(focus)] pany = ySize/2 - bodies["pos{0}".format(focus)] for a in range(1,bodies["num"]): rad = int(pow(bodies["mass{0}".format(a)]/pow(10,22),1/3.5)) + 2

x, y = conv_z((bodies["pos{0}".format(a)],bodies["pos{0}".format(a)]),panx,pany,zoom) pygame.draw.circle(screen, [255,255,255], (x,y), int(rad*zoom)) pygame.draw.circle(screen, [255,255,255], conv_z((px,py),panx,pany,zoom), 1) pygame.draw.line(screen, [255,0,0], conv_z((px + phf*pow(10,-1),py),panx,pany,zoom),conv_z((px,py),panx,pany,zoom),1) pygame.draw.line(screen, [255,0,0], conv_z((px,py + pvf*pow(10,-1)),panx,pany,zoom),conv_z((px,py),panx,pany,zoom),1)

elif view_mode == 1: px, py = bodies["pos0"], bodies["pos0"]

if keys[pygame.K_RIGHT]: offset -= 0.1 if keys[pygame.K_LEFT]: offset += 0.1 while offset <= -180: offset += 360 while offset > 180: offset -= 360

delta_x, delta_y = pygame.mouse.get_rel() pygame.mouse.set_visible(False) pygame.event.set_grab(True) offset -= delta_x/15 for c in collect: a = c r = pow(bodies["mass{0}".format(a)]/pow(10,22),1/2) + 1 mx, my = bodies["pos{0}".format(a)], bodies["pos{0}".format(a)] angle = degrees(atan2((my - py),(mx - px))) if -135 <= offset and offset < 45: if mx < px and my >= py: angle -= 270 - offset else: angle += 90 + offset else: if mx <= px and my < py: angle += 90 + offset else: angle -= 270 - offset d = pythag(py - bodies["pos{0}".format(a)],px - bodies["pos{0}".format(a)]) if d >= 11: rad = int(pythag(ySize,xSize)*degrees(atan(r/(d - 8)))/150) else: rad = int(pythag(ySize,xSize)*degrees(atan(r/(d*d*d*3/1331)))/150)

pygame.draw.circle(screen, bodies["color{0}".format(a)], (int(xSize/2 + angle/fov*xSize),int(ySize/2)),rad) pygame.draw.circle(screen, bodies["color{0}".format(a)], (int(xSize/2 + (angle + 360)/fov*xSize),int(ySize/2)),rad) pygame.draw.circle(screen, bodies["color{0}".format(a)], (int(xSize/2 + (angle - 360)/fov*xSize),int(ySize/2)),rad) pygame.display.flip() clock.tick(fps)

pygame.quit()

205 views