# Filename: A16c_2D_B2D_serverN.py # Written by: James D. Miller # 5:31 PM Sat May 18, 2019 import sys, os import pygame import datetime import math import time import commands, platform import inspect # PyGame Constants from pygame.locals import * from pygame.color import THECOLORS # PyGame gui from pgu import gui # Import the vector class from a local module (in this same directory) from vec2d_jdm import Vec2D # Networking from PodSixNet.Server import Server from PodSixNet.Channel import Channel import socket # Box2D from Box2D import * # Argument parsing... import argparse #===================================================================== # Classes #===================================================================== class ClientChannel(Channel): def __init__(self, *args, **kwargs): Channel.__init__(self, *args, **kwargs) # def Network(self, data): # #print "Client State Dictionary:", data # #print "Network, data['ID']", data['ID'] # pass def Network_CN(self, data): #global env # Store incoming data in the client objects. speaking_client_name = 'C' + str(data['ID']) # Check to make sure that this client is still in the client dictionary. if speaking_client_name in env.clients: # Mouse controls. env.clients[speaking_client_name].cursor_location_px = data['mXY'] # mouse x,y env.clients[speaking_client_name].buttonIsStillDown = data['mBd'] # mouse button down (true/false) env.clients[speaking_client_name].mouse_button = data['mB'] # mouse button number (1,2,3,0) # Jet controls. # Make the s key behave as a toggle. # If key is up, make it ready to accept the down ('D') event. if (data['s'] == 'U'): env.clients[speaking_client_name].key_s_onoff = 'ON' env.clients[speaking_client_name].key_s = data['s'] # If getting 'D' from network client and the key is enabled. elif (env.clients[speaking_client_name].key_s_onoff == 'ON'): env.clients[speaking_client_name].key_s = data['s'] env.clients[speaking_client_name].key_a = data['a'] env.clients[speaking_client_name].key_d = data['d'] env.clients[speaking_client_name].key_w = data['w'] # Control for stopping all objects (f for freeze). env.clients[speaking_client_name].key_f = data['f'] # Gun controls. # Make the k key behave as a toggle. # If key is up, make it ready to accept the down ('D') event. if (data['k'] == 'U'): env.clients[speaking_client_name].key_k_onoff = 'ON' env.clients[speaking_client_name].key_k = data['k'] # If getting 'D' from network client and the key is enabled. elif (env.clients[speaking_client_name].key_k_onoff == 'ON'): env.clients[speaking_client_name].key_k = data['k'] env.clients[speaking_client_name].key_j = data['j'] env.clients[speaking_client_name].key_l = data['l'] env.clients[speaking_client_name].key_i = data['i'] env.clients[speaking_client_name].key_space = data[' '] # Cursor controls. env.clients[speaking_client_name].key_lshift = data['ls'] env.clients[speaking_client_name].key_t = data['t'] # Keep track of client activity... env.clients[speaking_client_name].sendCount += 1 def Close(self): print "A network client game pad has been closed." class GameServer(Server): channelClass = ClientChannel def __init__(self, *args, **kwargs): Server.__init__(self, *args, **kwargs) self.client_count = 0 # This runs when each client connects. def Connected(self, channel, addr): #print 'new connection (channel, addr):', channel, addr self.client_count += 1 if (self.client_count <= 10): channel.Send({"action": "hello", "P_ID":self.client_count}) client_name = 'C' + str(self.client_count) # Make a client and put it in the clients list. env.clients[client_name] = Client(env.client_colors[client_name]) # Add the channel as an attribute of the client. Use this to Send to this client. env.clients[client_name].channel = channel else: channel.Send({"action": "hello", "P_ID":0}) print "self.client_count =", self.client_count class Client: def __init__(self, cursor_color): self.cursor_location_px = (0,0) # x_px, y_px self.mouse_button = 1 # 1, 2, or 3 self.buttonIsStillDown = False self.channel = 0 # Jet self.key_a = "U" self.key_s = "U" self.key_s_onoff = "ON" self.key_d = "U" self.key_w = "U" # Gun self.key_j = "U" self.key_k = "U" self.key_k_onoff = "ON" self.key_l = "U" self.key_i = "U" self.key_space = "U" # Freeze it self.key_f = "U" # Zoom self.key_b = "U" self.key_n = "U" self.key_m = "U" self.key_h = "U" self.key_lctrl = 'U' # Cursor selection modification #b2d self.key_lshift = "U" self.key_t = "U" self.selected_puck = None self.COM_selection = None self.selection_pointOnPuck_b2d_m = b2Vec2(0,0) #b2d self.cursor_color = cursor_color self.bullet_hit_count = 0 self.bullet_hit_limit = 50.0 self.previousSendCount = 0 self.sendCount = 0 self.active = False # Define the nature of the cursor strings, one for each mouse button. self.mouse_strings = {'string1':{'c_drag': 2.0, 'k_Npm': 60.0}, 'string2':{'c_drag': 0.1, 'k_Npm': 2.0}, 'string3':{'c_drag': 20.0, 'k_Npm': 1000.0}} # self.mouse_strings = {'string1':{'c_drag': 0.0, 'k_Npm': 60.0}, # 'string2':{'c_drag': 0.0, 'k_Npm': 2.0}, # 'string3':{'c_drag': 0.0, 'k_Npm': 1000.0}} # Special case for objects selected at nonCOM points. c_rot can control the drag (torque) # associated with rotation. c_pnt_drag is applied to a selected object at the local body point of the # cursor-selected object. self.mouse_strings_nonCOM = {'string1':{'c_drag': 0.0, 'c_pnt_drag': 2.0, 'c_rot': 0.0, 'k_Npm': 60.0}, 'string2':{'c_drag': 0.0, 'c_pnt_drag': 0.1, 'c_rot': 0.0, 'k_Npm': 2.0}, 'string3':{'c_drag': 0.0, 'c_pnt_drag': 20.0, 'c_rot': 0.0, 'k_Npm': 1000.0}} def calc_string_forces_on_pucks(self): # Calculated the string forces on the selected puck and add to the aggregate # that is stored in the puck object. # First deal with selecting and unselecting. # Only check for a selected puck if one isn't already selected. This keeps # the puck from unselecting if cursor is dragged off the puck! if (self.selected_puck == None): if self.buttonIsStillDown: # Depending on whether the shift key is down or not, do a COM based selection # or use box2d to select the object at a particular point on it. #b2d if (self.key_lshift == 'D'): self.COM_selection = False temp = air_table.checkForPuckAtThisPosition_b2d(self.cursor_location_px) self.selected_puck = temp['puck'] self.selection_pointOnPuck_b2d_m = temp['b2d_xy_m'] else: # COM selection self.COM_selection = True self.selected_puck = air_table.checkForPuckAtThisPosition(self.cursor_location_px) self.selection_pointOnPuck_b2d_m = b2Vec2(0,0) # If a puck is already selected, unselect it if the mouse button is up. else: if not self.buttonIsStillDown: # Unselect the puck and bomb out of here. #self.selected_puck.selected = False self.selected_puck = None self.COM_selection = None self.selection_2d_m = Vec2D(0,0) return None # Now calculate the forces on a selected puck. if (self.selected_puck != None): # Calculate the absolute World position of the selection point. Can't just add the local vector to # the center of mass vector. Would have to know the orientation (rotation) of the local coordinate system. # So use box2d do that transform for us. #b2d selection_b2d_m = self.selected_puck.b2d_body.GetWorldPoint( self.selection_pointOnPuck_b2d_m) # body.GetWorldVector(localVector) self.selection_2d_m = Vec2D( selection_b2d_m.x, selection_b2d_m.y) # Use dx difference to calculate the hooks law force being applied by the tether line. # If you release the mouse button after a drag it will fling the puck. # This tether force will diminish as the puck gets closer to the mouse point. stringName = "string" + str(self.mouse_button) # Limit the acceleration caused by the cursor string if the targeted object is very small (light). # Do this with a scaling factor based on the mass of the selected object. This avoids instability # in the physics engines that can be caused by large changes in position/velocity in a time step. if ((self.mouse_strings_nonCOM[stringName]['k_Npm'] / self.selected_puck.mass_kg) > 10000.0): cursor_scaling_factor = 3.0 * self.selected_puck.mass_kg else: cursor_scaling_factor = 1 # Calculation and aggregation of the cursor forces. if self.COM_selection: # Spring force dx_2d_m = env.ConvertScreenToWorld(Vec2D(self.cursor_location_px)) - self.selected_puck.pos_2d_m spring_force_2d_N = dx_2d_m * self.mouse_strings[stringName]['k_Npm'] * cursor_scaling_factor self.selected_puck.cursorString_spring_force_2d_N += spring_force_2d_N # Calculate the drag and then add to the pucks aggregate drag force. drag_force_2d_N = (self.selected_puck.vel_2d_mps * -1 * self.mouse_strings[stringName]['c_drag']) * cursor_scaling_factor self.selected_puck.cursorString_puckDrag_force_2d_N += drag_force_2d_N else: # NonCOM selection: # Spring dx_2d_m = env.ConvertScreenToWorld(Vec2D(self.cursor_location_px)) - self.selection_2d_m # Spring force spring_force_2d_N = dx_2d_m * self.mouse_strings_nonCOM[stringName]['k_Npm'] * cursor_scaling_factor # Append, this force and the location it is to be applied on the body, to the list on the puck. #b2d self.selected_puck.nonCOM_N.append({'force_2d_N': spring_force_2d_N,'local_b2d_m': self.selection_pointOnPuck_b2d_m}) # Calculate a drag force based on the velocity of the selected point. Apply this drag to the selected point on the body. v_selected_pnt_b2d_mps = self.selected_puck.b2d_body.GetLinearVelocityFromLocalPoint( self.selection_pointOnPuck_b2d_m) #print "vel of selected point:", v_selected_pnt_b2d_mps v_selected_pnt_2d_mps = Vec2D(v_selected_pnt_b2d_mps.x, v_selected_pnt_b2d_mps.y) point_drag_2d_N = v_selected_pnt_2d_mps * (-1) * self.mouse_strings_nonCOM[stringName]['c_pnt_drag'] * cursor_scaling_factor self.selected_puck.nonCOM_N.append({'force_2d_N':point_drag_2d_N, 'local_b2d_m':self.selection_pointOnPuck_b2d_m}) # Calculate a drag force based on COM velocity and then add to the pucks aggregate drag force. drag_force_2d_N = (self.selected_puck.vel_2d_mps * -1 * self.mouse_strings_nonCOM[stringName]['c_drag'])* cursor_scaling_factor self.selected_puck.cursorString_puckDrag_force_2d_N += drag_force_2d_N # Calculate the drag torque... torque_force_N = -1 * self.selected_puck.rotation_speed * self.mouse_strings_nonCOM[stringName]['c_rot'] * cursor_scaling_factor self.selected_puck.cursorString_torque_force_Nm += torque_force_N # Some torque to spin the objects. if (self.key_t == 'D'): if (self.selected_puck.b2d_body.angularVelocity < 200.0): if (self.key_lshift == 'D'): spin_direction = +1.0 else: spin_direction = -1.0 self.selected_puck.cursorString_torque_force_Nm = 10.0 * self.selected_puck.mass_kg * spin_direction def draw_cursor_string(self): if self.COM_selection: selection_location_2d_m = self.selected_puck.pos_2d_m else: selection_location_2d_m = self.selection_2d_m line_points = [env.ConvertWorldToScreen( selection_location_2d_m), self.cursor_location_px] if (self.selected_puck != None): pygame.draw.line(game_window.surface, self.cursor_color, line_points[0], line_points[1], 1) # Draw small circle at selection point. radius_px = 2 pygame.draw.circle(game_window.surface, THECOLORS["red"], line_points[0], radius_px, 0) def draw_fancy_server_cursor(self): self.draw_server_cursor( self.cursor_color, 0) self.draw_server_cursor( THECOLORS["black"], 1) def draw_server_cursor(self, color, edge_px): cursor_outline_vertices = [] cursor_outline_vertices.append( self.cursor_location_px ) cursor_outline_vertices.append( (self.cursor_location_px[0] + 10, self.cursor_location_px[1] + 10) ) cursor_outline_vertices.append( (self.cursor_location_px[0] + 0, self.cursor_location_px[1] + 15) ) pygame.draw.polygon(game_window.surface, color, cursor_outline_vertices, edge_px) class runningAvg: def __init__(self, n_target): self.n_target = n_target self.reset() def update(self, new_value): if self.n_in_avg < self.n_target: self.total += new_value self.n_in_avg += 1 else: # Add the new value and subtract the oldest. self.total += new_value - self.values[0] # Discard the oldest value. self.values.pop(0) self.values.append(new_value) self.result = self.total / float(self.n_in_avg) return self.result def reset(self): self.n_in_avg = 0 self.result = 0.0 self.values = [] self.total = 0.0 class Puck: def __init__(self, pos_2d_m, radius_m, density_kgpm2, puck_color=THECOLORS["grey"], coef_rest=0.85, CR_fixed=False, pinned=False, rect_fixture=False, aspect_ratio=1.0): self.radius_m = radius_m self.radius_px = int(round(env.px_from_m(self.radius_m * env.viewZoom))) self.density_kgpm2 = density_kgpm2 # mass per unit area self.mass_kg = self.density_kgpm2 * math.pi * self.radius_m ** 2 self.coef_rest = coef_rest self.CR_fixed = CR_fixed self.pos_2d_m = pos_2d_m self.vel_2d_mps = Vec2D(0.0,0.0) self.rotation_speed = 0.0 self.SprDamp_force_2d_N = Vec2D(0.0,0.0) self.jet_force_2d_N = Vec2D(0.0,0.0) self.cursorString_spring_force_2d_N = Vec2D(0.0,0.0) self.cursorString_puckDrag_force_2d_N = Vec2D(0.0,0.0) self.cursorString_torque_force_Nm = 0 # Non-center of mass (COM). #b2d # This is a list of dictionaries: each dictionary contains a force and a body location self.nonCOM_N = [] self.impulse_2d_Ns = Vec2D(0.0,0.0) #self.selected = False self.color = puck_color self.client_name = None self.jet = None self.gun = None self.hit = False self.hitflash_duration_timer_s = 0.0 # Make the hit flash persist for this number of seconds: self.hitflash_duration_timer_limit_s = 0.05 # Bullet data... self.bullet = False self.birth_time_s = env.time_s self.age_limit_s = 3.0 # Create a Box2d puck. self.aspect_ratio = aspect_ratio self.rect_fixture = rect_fixture if (not pinned): self.b2d_body = self.create_Box2d_Puck() # If you print an object instance... def __str__(self): return "puck: x is %s, y is %s" % (self.pos_2d_m.x, self.pos_2d_m.y) # Box2d def create_Box2d_Puck(self): # Create a dynamic body dynamic_body = b2_world.CreateDynamicBody(position=b2Vec2(self.pos_2d_m.tuple()), angle=0, linearVelocity=b2Vec2(self.vel_2d_mps.tuple())) # Surface friction coef_friction = air_table.coef_friction_puck if self.rect_fixture: # And add a box fixture onto it. dynamic_body.CreatePolygonFixture(box=(self.radius_m, self.radius_m * self.aspect_ratio), density=self.density_kgpm2, friction=coef_friction, restitution=self.coef_rest) # Set the mass attribute based on what box2d calculates. self.mass_kg = dynamic_body.mass else: # And add a circle fixture onto it. dynamic_body.CreateCircleFixture(radius=self.radius_m , density=self.density_kgpm2, friction=coef_friction, restitution=self.coef_rest) # Some fluid drag inside the Box2D engine. dynamic_body.linearDamping = 0.0 #0.6 dynamic_body.angularDamping = 0.0 #0.6 #print "box2d", dynamic_body.mass, dynamic_body.fixtures[0].restitution return dynamic_body # Box2d def get_Box2d_XandV(self): # Position box2d_pos_2d_m = self.b2d_body.GetWorldPoint(b2Vec2(0,0)) self.pos_2d_m = Vec2D( box2d_pos_2d_m.x, box2d_pos_2d_m.y) # Velocity box2d_vel_2d_m = self.b2d_body.linearVelocity self.vel_2d_mps = Vec2D( box2d_vel_2d_m.x, box2d_vel_2d_m.y) # Rotational speed. self.rotation_speed = self.b2d_body.angularVelocity #print self.rotation_speed def draw(self): # Convert x,y to pixel screen location and then draw. self.pos_2d_px = env.ConvertWorldToScreen( self.pos_2d_m) #print "draw position", self.pos_px[0], self.pos_px[1] # Update based on zoom factor self.radius_px = int(round(env.px_from_m( self.radius_m))) if (self.radius_px < 3): self.radius_px = 3 # Just after a hit, fill the whole circle with RED (i.e., thickness = 0). if self.hit: puck_circle_thickness = 0 puck_color = THECOLORS["red"] self.hitflash_duration_timer_s += dt_render_s if self.hitflash_duration_timer_s > self.hitflash_duration_timer_limit_s: self.hit = False else: puck_circle_thickness = 3 puck_color = self.color if self.rect_fixture: # Box2d fixture_shape = self.b2d_body.fixtures[0].shape vertices_screen_2d_px = [] for vertex_object_2d_m in fixture_shape.vertices: vertex_world_2d_m = self.b2d_body.transform * vertex_object_2d_m # Overload operation vertex_screen_2d_px = env.ConvertWorldToScreen( Vec2D(vertex_world_2d_m.x, vertex_world_2d_m.y)) # This returns a tuple vertices_screen_2d_px.append( vertex_screen_2d_px) # Append to the list. pygame.draw.polygon(game_window.surface, puck_color, vertices_screen_2d_px, puck_circle_thickness) else: # Draw main puck body. pygame.draw.circle(game_window.surface, puck_color, self.pos_2d_px, self.radius_px, puck_circle_thickness) # If it's not a bullet and not a rectangle, dray a line on the puck to indicate rotational orientation. if ((self.bullet == False) and (self.rect_fixture==False)): point_on_radius_b2d_m = self.b2d_body.GetWorldPoint( b2Vec2(0.0, self.radius_m)) point_on_radius_2d_m = Vec2D( point_on_radius_b2d_m.x, point_on_radius_b2d_m.y) point_on_radius_2d_px = env.ConvertWorldToScreen( point_on_radius_2d_m) point_at_center_b2d_m = self.b2d_body.GetWorldPoint( b2Vec2(0.0, 0.0)) point_at_center_2d_m = Vec2D( point_at_center_b2d_m.x, point_at_center_b2d_m.y) point_at_center_2d_px = env.ConvertWorldToScreen( point_at_center_2d_m) pygame.draw.line(game_window.surface, puck_color, point_on_radius_2d_px, point_at_center_2d_px, 2) # Draw life (poor health) indicator circle. if (((self.client_name != None) and env.clients[self.client_name].active) or (self.client_name == 'test')) and (not self.bullet): spent_fraction = float(env.clients[self.client_name].bullet_hit_count) / float(env.clients[self.client_name].bullet_hit_limit) life_radius = spent_fraction * self.radius_px if (life_radius > 2.0): life_radius_px = int(round(life_radius)) else: life_radius_px = 2 pygame.draw.circle(game_window.surface, THECOLORS["red"], self.pos_2d_px, life_radius_px, 1) class RotatingTube: def __init__(self, puck): # Associate the tube with the puck. self.puck = puck self.color = env.clients[self.puck.client_name].cursor_color # Degrees of rotation per second. #self.rotation_rate_dps = 360.0 # Scaling factors to manage the aspect ratio of the tube. self.sf_x = 0.15 self.sf_y = 0.50 # Notice the counter-clockwise drawing pattern. Four vertices for a rectangle. # Each vertex is represented by a vector. self.tube_vertices_2d_m = [Vec2D(-0.50 * self.sf_x, 0.00 * self.sf_y), Vec2D( 0.50 * self.sf_x, 0.00 * self.sf_y), Vec2D( 0.50 * self.sf_x, 1.00 * self.sf_y), Vec2D(-0.50 * self.sf_x, 1.00 * self.sf_y)] # Define a normal (1 meter) pointing vector to keep track of the direction of the jet. self.direction_2d_m = Vec2D(0.0, 1.0) def rotate_vertices(self, vertices_2d_m, angle_deg): # Put modified vectors in a new list. rotated_vertices_2d_m = [] for vertex_2d_m in vertices_2d_m: rotated_vertices_2d_m.append( vertex_2d_m.rotated( angle_deg)) return rotated_vertices_2d_m def rotate_everything(self, angle_deg): # Rotate the pointer. self.direction_2d_m = self.direction_2d_m.rotated( angle_deg) # Rotate the tube. self.tube_vertices_2d_m = self.rotate_vertices( self.tube_vertices_2d_m, angle_deg) def convert_from_world_to_screen(self, vertices_2d_m, base_point_2d_m): vertices_2d_px = [] for vertex_2d_m in vertices_2d_m: # Calculate absolute position of this vertex. vertices_2d_px.append( env.ConvertWorldToScreen( vertex_2d_m + base_point_2d_m)) return vertices_2d_px def draw_tube(self, line_thickness=3): # Draw the tube on the game-window surface. Establish the base_point as the center of the puck. pygame.draw.polygon(game_window.surface, self.color, self.convert_from_world_to_screen(self.tube_vertices_2d_m, self.puck.pos_2d_m), line_thickness) class Jet( RotatingTube): def __init__(self, puck): RotatingTube.__init__(self, puck) # Degrees of rotation per second. self.rotation_rate_dps = 360.0 self.color = THECOLORS["yellow"] # The jet flame (triangle) self.flame_vertices_2d_m =[Vec2D(-0.50 * self.sf_x, 1.02 * self.sf_y), Vec2D( 0.50 * self.sf_x, 1.02 * self.sf_y), Vec2D(-0.00 * self.sf_x, 1.80 * self.sf_y)] # Scaler magnitude of jet force. self.jet_force_N = 1.3 * self.puck.mass_kg * abs(air_table.gON_2d_mps2.y) # Point everything down for starters. self.rotate_everything( 180) def turn_jet_forces_onoff(self, client_name): if (env.clients[client_name].key_w == "D"): # Force on puck is in the opposite direction of the jet tube. self.puck.jet_force_2d_N = self.direction_2d_m * (-1) * self.jet_force_N else: self.puck.jet_force_2d_N = self.direction_2d_m * 0.0 def client_rotation_control(self, client_name): if (env.clients[client_name].key_a == "D"): self.rotate_everything( +1 * self.rotation_rate_dps * dt_render_s) if (env.clients[client_name].key_d == "D"): self.rotate_everything( -1 * self.rotation_rate_dps * dt_render_s) if (env.clients[client_name].key_s == "D"): # Rotate jet tube to be in the same direction as the motion of the puck. puck_velocity_angle = self.puck.vel_2d_mps.get_angle() current_jet_angle = self.direction_2d_m.get_angle() self.rotate_everything(puck_velocity_angle - current_jet_angle) #self.rotate_everything(180) # Reset this so it doesn't keep flipping. Just want it to flip the # direction once but not keep flipping. # This first line is enough to keep the local client from flipping again because # the local keyboard doesn't keep sending the "D" event if the key is held down. env.clients[client_name].key_s = "U" # This second one is also needed for the network clients because they keep # sending the "D" until they release the key. env.clients[client_name].key_s_onoff = "OFF" def rotate_everything(self, angle_deg): # Rotate the pointer. self.direction_2d_m = self.direction_2d_m.rotated( angle_deg) # Rotate the tube. self.tube_vertices_2d_m = self.rotate_vertices( self.tube_vertices_2d_m, angle_deg) # Rotate the flame. self.flame_vertices_2d_m = self.rotate_vertices( self.flame_vertices_2d_m, angle_deg) def draw(self): # Draw the jet tube. self.draw_tube() # Draw the red flame. if (env.clients[self.puck.client_name].key_w == "D"): pygame.draw.polygon(game_window.surface, THECOLORS["red"], self.convert_from_world_to_screen(self.flame_vertices_2d_m, self.puck.pos_2d_m), 0) class Gun( RotatingTube): def __init__(self, puck): RotatingTube.__init__(self, puck) # Degrees of rotation per second. self.rotation_rate_dps = 180.0 self.color = env.clients[self.puck.client_name].cursor_color # Run this method of the RotationTube class to set the initial angle of each new gun. self.rotate_everything( 45) self.bullet_speed_mps = 5.0 self.fire_time_s = env.time_s self.firing_delay_s = 0.1 self.bullet_count = 0 self.bullet_count_limit = 10 self.gun_recharge_wait_s = 2.5 self.gun_recharge_start_time_s = env.time_s self.gun_recharging = False self.testing_gun = False self.shield = False self.shield_hit = False self.shield_hit_duration_s = 0.0 # Make the hit remove the shield for this number of seconds: self.shield_hit_duration_limit_s = 0.05 self.shield_hit_count = 0 self.shield_hit_count_limit = 20 self.shield_recharging = False self.shield_recharge_wait_s = 4.0 self.shield_recharge_start_time_s = env.time_s def client_rotation_control(self, client_name): if (env.clients[client_name].key_j == "D"): self.rotate_everything( +self.rotation_rate_dps * dt_render_s) if (env.clients[client_name].key_l == "D"): self.rotate_everything( -self.rotation_rate_dps * dt_render_s) if (env.clients[client_name].key_k == "D"): # Rotate jet tube to be in the same direction as the motion of the puck. puck_velocity_angle = self.puck.vel_2d_mps.get_angle() current_gun_angle = self.direction_2d_m.get_angle() self.rotate_everything(puck_velocity_angle - current_gun_angle) # Reset this so it doesn't keep flipping. Just want it to flip the # direction once but not keep flipping. # This first line is enough to keep the local client from flipping again because # the local keyboard doesn't keep sending the "D" event if the key is held down. env.clients[client_name].key_k = "U" # This second one is also needed for the network clients because they keep # sending the "D" until they release the key. env.clients[client_name].key_k_onoff = "OFF" def control_firing(self, client_name): # Fire only if the shield is off. if ((env.clients[client_name].key_i == "D") and (not self.shield)) or self.testing_gun: # Fire the gun. if ((env.time_s - self.fire_time_s) > self.firing_delay_s) and (not self.gun_recharging): self.fire_gun() self.bullet_count += 1 # Timestamp the firing event. self.fire_time_s = env.time_s # Check to see if gun bullet count indicates the need to start recharging. if (self.bullet_count > self.bullet_count_limit): self.gun_recharge_start_time_s = env.time_s self.gun_recharging = True self.bullet_count = 0 # If recharged. if (self.gun_recharging and (env.time_s - self.gun_recharge_start_time_s) > self.gun_recharge_wait_s): self.gun_recharging = False def fire_gun(self): bullet_radius_m = 0.05 # Set the initial position of the bullet so that it clears (doesn't collide with) the host puck. initial_position_2d_m = (self.puck.pos_2d_m + (self.direction_2d_m * (1.1 * self.puck.radius_m + 1.1 * bullet_radius_m)) ) temp_bullet = Puck(initial_position_2d_m, bullet_radius_m, 0.3) # Relative velocity of the bullet: the bullet velocity as seen from the host puck. This is the # speed of the bullet relative to the motion of the host puck (host velocity BEFORE the firing of # the bullet). bullet_relative_vel_2d_mps = self.direction_2d_m * self.bullet_speed_mps # Absolute velocity of the bullet. temp_bullet.vel_2d_mps = self.puck.vel_2d_mps + bullet_relative_vel_2d_mps # Also set the velocity of the Box2d puck. temp_bullet.b2d_body.linearVelocity = b2Vec2( temp_bullet.vel_2d_mps.tuple()) temp_bullet.bullet = True temp_bullet.b2d_body.bullet = True temp_bullet.color = env.clients[self.puck.client_name].cursor_color temp_bullet.client_name = self.puck.client_name air_table.pucks.append( temp_bullet) # Calculate the recoil impulse from firing the gun (opposite the direction of the bullet). self.puck.impulse_2d_Ns = bullet_relative_vel_2d_mps * temp_bullet.mass_kg * (-1) def control_shield(self, client_name): if (env.clients[client_name].key_space == "D") and (not self.shield_recharging): self.shield = True else: self.shield = False # Check to see if the shield hit count indicates the need to start recharging. if (self.shield_hit_count > self.shield_hit_count_limit): self.shield_recharge_start_time_s = env.time_s self.shield = False self.shield_recharging = True self.shield_hit_count = 0 # If recharged. if (self.shield_recharging and (env.time_s - self.shield_recharge_start_time_s) > self.shield_recharge_wait_s): self.shield_recharging = False def draw(self): # Draw the gun tube. if (self.gun_recharging): line_thickness = 3 else: line_thickness = 0 # Draw the jet tube. self.draw_tube( line_thickness) # Draw the shield. if (self.shield): if self.shield_hit: # Don't draw the shield for a moment after the hit. This visualizes the shield hit. self.shield_hit_duration_s += dt_render_s if (self.shield_hit_duration_s > self.shield_hit_duration_limit_s): self.shield_hit = False else: pygame.draw.circle(game_window.surface, self.color, self.puck.pos_2d_px, self.puck.radius_px + 6, 4) class Spring: def __init__(self, p1, p2, length_m=3.0, strength_Npm=0.5, spring_color=THECOLORS["yellow"], width_m=0.025, drag_c=0.0): # Optionally this spring can have one end pinned to a vector point. Do this by passing in p2 as a vector. if (p2.__class__.__name__ == 'Vec2D'): # Create a point puck at the pinning location. # The location of this point puck will never change because # it is not in the pucks list that is processed by the # physics engine. p2 = Puck( p2, 1.0, 1.0, pinned=True) p2.vel_2d_mps = Vec2D(0.0,0.0) length_m = 0.0 self.p1 = p1 self.p2 = p2 self.p1p2_separation_2d_m = Vec2D(0,0) self.p1p2_separation_m = 0 self.p1p2_normalized_2d = Vec2D(0,0) self.length_m = length_m self.strength_Npm = strength_Npm self.damper_Ns2pm2 = 0.5 #5.0 #0.05 self.unstretched_width_m = width_m #0.05 self.drag_c = drag_c self.spring_vertices_2d_m = [] self.spring_vertices_2d_px = [] self.spring_color = spring_color self.draw_as_line = False def calc_spring_forces_on_pucks(self): self.p1p2_separation_2d_m = self.p1.pos_2d_m - self.p2.pos_2d_m self.p1p2_separation_m = self.p1p2_separation_2d_m.length() # The pinned case needs to be able to handle the zero length spring. The # separation distance will be zero when the pinned spring is at rest. # This will cause a divide by zero error if not handled here. if ((self.p1p2_separation_m == 0.0) and (self.length_m == 0.0)): spring_force_on_1_2d_N = Vec2D(0.0,0.0) else: self.p1p2_normalized_2d = self.p1p2_separation_2d_m / self.p1p2_separation_m # Spring force: acts along the separation vector and is proportional to the separation distance. spring_force_on_1_2d_N = self.p1p2_normalized_2d * (self.length_m - self.p1p2_separation_m) * self.strength_Npm # Damper force: acts along the separation vector and is proportional to the relative speed. v_relative_2d_mps = self.p1.vel_2d_mps - self.p2.vel_2d_mps v_relative_alongNormal_2d_mps = v_relative_2d_mps.projection_onto(self.p1p2_separation_2d_m) damper_force_on_1_N = v_relative_alongNormal_2d_mps * self.damper_Ns2pm2 # Net force by both spring and damper SprDamp_force_2d_N = spring_force_on_1_2d_N - damper_force_on_1_N # This force acts in opposite directions for each of the two pucks. Notice the "+=" here, this # is an aggregate across all the springs. This aggregate MUST be reset (zeroed) after the movements are # calculated. So by the time you've looped through all the springs, you get the NET force, one each ball, # applied of all individual springs. self.p1.SprDamp_force_2d_N += SprDamp_force_2d_N * (+1) self.p2.SprDamp_force_2d_N += SprDamp_force_2d_N * (-1) # Add in some drag forces if a non-zero drag coef is specified. These are based on the # velocity of the pucks (not relative speed as is the case above for damper forces). self.p1.SprDamp_force_2d_N += self.p1.vel_2d_mps * (-1) * self.drag_c self.p2.SprDamp_force_2d_N += self.p2.vel_2d_mps * (-1) * self.drag_c def width_to_draw_m(self): width_m = self.unstretched_width_m * (1 + 0.30 * (self.length_m - self.p1p2_separation_m)) if width_m < (0.05 * self.unstretched_width_m): self.draw_as_line = True width_m = 0.0 else: self.draw_as_line = False return width_m def draw(self): # Change the width to indicate the stretch or compression in the spring. Note, it's good to # do this outside of the main calc loop (using the rendering timer). No need to do all this each # time step. width_m = self.width_to_draw_m() # Calculate the four corners of the spring rectangle. p1p2_perpendicular_2d = self.p1p2_normalized_2d.rotate90() self.spring_vertices_2d_m = [] self.spring_vertices_2d_m.append(self.p1.pos_2d_m + (p1p2_perpendicular_2d * width_m)) self.spring_vertices_2d_m.append(self.p1.pos_2d_m - (p1p2_perpendicular_2d * width_m)) self.spring_vertices_2d_m.append(self.p2.pos_2d_m - (p1p2_perpendicular_2d * width_m)) self.spring_vertices_2d_m.append(self.p2.pos_2d_m + (p1p2_perpendicular_2d * width_m)) # Transform from world to screen. self.spring_vertices_2d_px = [] for vertice_2d_m in self.spring_vertices_2d_m: self.spring_vertices_2d_px.append( env.ConvertWorldToScreen( vertice_2d_m)) # Draw the spring if self.draw_as_line == True: pygame.draw.aaline(game_window.surface, self.spring_color, env.ConvertWorldToScreen(self.p1.pos_2d_m), env.ConvertWorldToScreen(self.p2.pos_2d_m)) else: pygame.draw.polygon(game_window.surface, self.spring_color, self.spring_vertices_2d_px) class fwQueryCallback( b2QueryCallback): # Checks for objects at particular locations (p) like under the cursor. #b2d def __init__(self, p): super(fwQueryCallback, self).__init__() self.point = p self.fixture = None def ReportFixture(self, fixture): body = fixture.body if body.type == b2_dynamicBody: inside=fixture.TestPoint(self.point) if inside: self.fixture=fixture # We found the object, so stop the query return False # Continue the query return True class AirTable: def __init__(self, walls_dic): self.gON_2d_mps2 = Vec2D(-0.0, -9.8) self.gOFF_2d_mps2 = Vec2D(-0.0, -0.0) self.g_2d_mps2 = self.gOFF_2d_mps2 self.g_ON = False self.b2_walls = [] self.pucks = [] self.puck_dictionary = {} self.controlled_pucks = [] self.springs = [] self.walls = walls_dic self.collision_count = 0 self.coef_friction_puck = 0.2 self.color_transfer = False self.stop_physics = False self.tangled = False # Only do this is you have to. Avoids calls to the collision checker. self.collision_checking_enabled = False self.FPS_display = True def draw(self): #{"L_m":0.0, "R_m":10.0, "B_m":0.0, "T_m":10.0} topLeft_2d_px = env.ConvertWorldToScreen( Vec2D( self.walls['L_m'], self.walls['T_m'])) topRight_2d_px = env.ConvertWorldToScreen( Vec2D( self.walls['R_m']-0.01, self.walls['T_m'])) botLeft_2d_px = env.ConvertWorldToScreen( Vec2D( self.walls['L_m'], self.walls['B_m']+0.01)) botRight_2d_px = env.ConvertWorldToScreen( Vec2D( self.walls['R_m']-0.01, self.walls['B_m']+0.01)) pygame.draw.line(game_window.surface, THECOLORS["orangered1"], topLeft_2d_px, topRight_2d_px, 1) pygame.draw.line(game_window.surface, THECOLORS["orangered1"], topRight_2d_px, botRight_2d_px, 1) pygame.draw.line(game_window.surface, THECOLORS["orangered1"], botRight_2d_px, botLeft_2d_px, 1) pygame.draw.line(game_window.surface, THECOLORS["orangered1"], botLeft_2d_px, topLeft_2d_px, 1) def checkForPuckAtThisPosition_b2d(self, x_px_or_tuple, y_px = None): # This is used for cursor selection at a particular point on the puck. #b2d # Return the selected puck and also the local point on the puck. selected_puck = None if y_px == None: self.x_px = x_px_or_tuple[0] self.y_px = x_px_or_tuple[1] else: self.x_px = x_px_or_tuple self.y_px = y_px # Convert to a world point. test_position_2d_m = env.ConvertScreenToWorld(Vec2D(self.x_px, self.y_px)) # Convert this to a box2d vector. p = test_position_b2d_m = b2Vec2( test_position_2d_m.tuple()) # Make a small box. aabb = b2AABB( lowerBound=p-(0.001, 0.001), upperBound=p+(0.001, 0.001)) # Query the world for overlapping shapes. query = fwQueryCallback( p) b2_world.QueryAABB( query, aabb) # If the query was successful and found a body at the cursor point. if query.fixture: selected_b2d_body = query.fixture.body selected_b2d_body.awake = True # Find the local point in the body's coordinate system. local_b2d_m = selected_b2d_body.GetLocalPoint( p) #local_b_b2d_m = selected_b2d_body.GetLocalVector( p) #print local_b2d_m # Use a dictionary to identify the puck based on the b2d body. # Bullets have not been added to the dictionary. if not selected_b2d_body.bullet: selected_puck = air_table.puck_dictionary[ selected_b2d_body] # Return a dictionary with the puck and local selection point on it. return {'puck': selected_puck, 'b2d_xy_m': local_b2d_m} else: return {'puck': None, 'b2d_xy_m': b2Vec2(0,0)} def checkForPuckAtThisPosition(self, x_px_or_tuple, y_px = None): if y_px == None: self.x_px = x_px_or_tuple[0] self.y_px = x_px_or_tuple[1] else: self.x_px = x_px_or_tuple self.y_px = y_px test_position_2d_m = env.ConvertScreenToWorld(Vec2D(self.x_px, self.y_px)) for puck in self.pucks: vector_difference_m = test_position_2d_m - puck.pos_2d_m # Use squared lengths for speed (avoid square root) mag_of_difference_m2 = vector_difference_m.length_squared() if mag_of_difference_m2 < puck.radius_m**2: #puck.selected = True return puck return None def update_TotalForceVectorOnPuck(self, puck, dt_s): # Net resulting force on the puck. puck_forces_2d_N = (self.g_2d_mps2 * puck.mass_kg) + (puck.SprDamp_force_2d_N + puck.jet_force_2d_N + puck.cursorString_spring_force_2d_N + puck.cursorString_puckDrag_force_2d_N + puck.impulse_2d_Ns/dt_s) # Apply this force to the puck's center of mass (COM) in the Box2d world force_point_b2d_m = puck.b2d_body.GetWorldPoint( b2Vec2(0,0)) force_vector_b2d_N = b2Vec2( puck_forces_2d_N.tuple()) puck.b2d_body.ApplyForce( force=force_vector_b2d_N, point=force_point_b2d_m, wake=True) # Apply any non-COM forces. #b2d for force_dict in puck.nonCOM_N: force_point_b2d_m = puck.b2d_body.GetWorldPoint( force_dict['local_b2d_m']) force_vector_b2d_N = b2Vec2( force_dict['force_2d_N'].tuple()) puck.b2d_body.ApplyForce( force=force_vector_b2d_N, point=force_point_b2d_m, wake=True) # Apply torques. #b2d puck.b2d_body.ApplyTorque( puck.cursorString_torque_force_Nm, wake=True) # Now reset the aggregate forces. puck.SprDamp_force_2d_N = Vec2D(0.0,0.0) puck.cursorString_spring_force_2d_N = Vec2D(0.0,0.0) puck.nonCOM_N = [] puck.cursorString_puckDrag_force_2d_N = Vec2D(0.0,0.0) puck.cursorString_torque_force_Nm = 0.0 puck.impulse_2d_Ns = Vec2D(0.0,0.0) def check_for_collisions(self): # Simplified for Box2d self.tangled = False for i, puck in enumerate(self.pucks): # Collisions with other pucks. for otherpuck in self.pucks[i+1:]: # Check if the two puck circles are overlapping. # Parallel to the normal puck_to_puck_2d_m = otherpuck.pos_2d_m - puck.pos_2d_m # Keep the following checks fast by avoiding square roots. # Separation between the pucks, squared (not a vector). p_to_p_m2 = puck_to_puck_2d_m.length_squared() # The sum of the radii of the two pucks, squared. r_plus_r_m2 = (puck.radius_m + otherpuck.radius_m)**2 # A check for the Jello-madness game. If it's tangled, balls # will be close and this will be set to True. if (p_to_p_m2 < 1.1 * r_plus_r_m2): self.tangled = True if platform.system() == 'Linux': # The RPi needs a little more margin for error to register a bullet collision. enlarging_factor = 1.2 else: enlarging_factor = 1.0 # Keep this collision check fast by avoiding square roots. if (p_to_p_m2 < enlarging_factor * r_plus_r_m2): self.collision_count += 1 #print "collision_count", self.collision_count # If it's a bullet coming from another client, add to the # hit count for non-bullet client. if (puck.client_name != None) and (otherpuck.client_name != None): if (puck.client_name != otherpuck.client_name): if (otherpuck.bullet and (not puck.bullet)): if not puck.gun.shield: env.clients[puck.client_name].bullet_hit_count += 1 puck.hit = True puck.hitflash_duration_timer_s = 0.0 else: puck.gun.shield_hit = True puck.gun.shield_hit_duration_s = 0.0 puck.gun.shield_hit_count += 1 #print puck.client_name, env.clients[puck.client_name].bullet_hit_count, puck.gun.shield_hit_count class Environment: def __init__(self, screenSize_px, length_x_m): self.screenSize_px = Vec2D(screenSize_px) self.viewOffset_2d_px = Vec2D(0,0) self.viewCenter_px = Vec2D(0,0) self.viewZoom = 1 self.viewZoom_rate = 0.01 self.px_to_m = length_x_m/float(self.screenSize_px.x) self.m_to_px = (float(self.screenSize_px.x)/length_x_m) self.client_colors = {'C1': THECOLORS["orangered1"],'C2': THECOLORS["tan"],'C3': THECOLORS["cyan"],'C4': THECOLORS["blue"], 'C5': THECOLORS["pink"], 'C6': THECOLORS["red"],'C7': THECOLORS["coral"],'C8': THECOLORS["green"], 'C9': THECOLORS["grey80"],'C10': THECOLORS["rosybrown3"],'test': THECOLORS["purple"]} # Add a local (non-network) client to the client dictionary. self.clients = {'local':Client(THECOLORS["green"])} self.clients['local'].active = True # General clock time for determining bullet age. self.time_s = 0 # Timer for the Jello Madness game. self.game_time_s = 0 self.FR_avg = runningAvg(300) self.loopsSinceLastQuietCheck = 0 self.constant_dt_s = None def checkForQuietClients(self): self.loopsSinceLastQuietCheck += 1 if self.loopsSinceLastQuietCheck > 20: self.loopsSinceLastQuietCheck = 0 for clientname in self.clients: if clientname != 'local': # Check for the no change case (client is quiet). countChange = self.clients[clientname].sendCount - self.clients[clientname].previousSendCount if countChange == 0: self.clients[clientname].active = False else: self.clients[clientname].active = True # Update the previous value for use in the next comparison. self.clients[clientname].previousSendCount = self.clients[clientname].sendCount def remove_healthless_clients(self): # Make a list of terminal clients. #print len(air_table.pucks), len(air_table.controlled_pucks) spent_client_names = [] for thisclient_name in self.clients: if self.clients[thisclient_name].bullet_hit_count > self.clients[thisclient_name].bullet_hit_limit: spent_client_names.append( thisclient_name) # Send the bad news if one of the network clients has died. if (thisclient_name not in ['local','test']): self.clients[thisclient_name].channel.Send({"action": "badhealth", "message":"not good"}) print "\"" + thisclient_name + "\"" + " has been popped. " # Reset the counter for the local client. That will keep this block from running repeatedly # when the local puck gets popped. Have to do this because the local client does not get # deleted below. That's so it can continue to receive keyboard and mouse input and reset the game # if needed. The local client always lives on even if its puck gets popped. if thisclient_name == 'local': self.clients[thisclient_name].bullet_hit_count = 0 pucks_list_copy = air_table.pucks[:] for puck in pucks_list_copy: if puck.client_name in spent_client_names: # Had to put this check in to prevent server crash on simultaneous death bullets between two clients. # Don't yet understand why this is necessary. # First remove the puck in Box2d. b2_world.DestroyBody(puck.b2d_body) if (puck in air_table.controlled_pucks): air_table.controlled_pucks.remove( puck) #print "\"" + puck.client_name + "\"" + " has been removed from the controlled puck list." air_table.pucks.remove( puck) for spent_client in spent_client_names: # Remove client from client dictionary if (spent_client != 'local'): del self.clients[ spent_client] del pucks_list_copy # Convert from meters to pixels def px_from_m(self, dx_m): return dx_m * self.m_to_px * self.viewZoom # Convert from pixels to meters # Note: still floating values here) def m_from_px(self, dx_px): return float(dx_px) * self.px_to_m / self.viewZoom def control_zoom_and_view(self): if self.clients['local'].key_h == "D": self.viewZoom += self.viewZoom_rate * self.viewZoom if self.clients['local'].key_n == "D": self.viewZoom -= self.viewZoom_rate * self.viewZoom def ConvertScreenToWorld(self, point_2d_px): #self.viewOffset_2d_px = self.viewCenter_px x_m = ( point_2d_px.x + self.viewOffset_2d_px.x) / (self.m_to_px * self.viewZoom) y_m = (self.screenSize_px.y - point_2d_px.y + self.viewOffset_2d_px.y) / (self.m_to_px * self.viewZoom) return Vec2D( x_m, y_m) def ConvertWorldToScreen(self, point_2d_m): """ Convert from world to screen coordinates (pixels). In the class instance, we store a zoom factor, an offset indicating where the view extents start at, and the screen size (in pixels). """ # self.viewOffset = self.viewCenter - self.screenSize_px/2 #self.viewOffset = self.viewCenter_px x_px = (point_2d_m.x * self.m_to_px * self.viewZoom) - self.viewOffset_2d_px.x y_px = (point_2d_m.y * self.m_to_px * self.viewZoom) - self.viewOffset_2d_px.y y_px = self.screenSize_px.y - y_px # Return a tuple of integers. return Vec2D(x_px, y_px, "int").tuple() # The next two functions give an alternate approach to using a modification key. This could also # be done by setting up a user key state for shift and controls keys, and use that to see if # the modifier key has been pressed. def ctrl_key_down(self): keys = pygame.key.get_pressed() if (keys[pygame.K_LCTRL] or keys[pygame.K_RCTRL]): return True def shift_key_down(self): keys = pygame.key.get_pressed() if (keys[pygame.K_LSHIFT] or keys[pygame.K_RSHIFT]): return True def get_local_user_input(self): local_user = self.clients['local'] # Get all the events since the last call to get(). for event in pygame.event.get(): if (event.type == pygame.QUIT): sys.exit() elif (event.type == pygame.KEYDOWN): #print "keydown event" if (event.key == K_ESCAPE): sys.exit() elif (event.key==K_1): return 1 elif (event.key==K_2): return 2 elif (event.key==K_3): return 3 elif (event.key==K_4): return 4 elif (event.key==K_5): return 5 elif (event.key==K_6): return 6 elif (event.key==K_7): return 7 elif (event.key==K_8): return 8 elif (event.key==K_9): return 9 elif (event.key==K_0): return 0 elif (event.key==K_c): # Toggle color option. air_table.color_transfer = not air_table.color_transfer #form['ColorTransfer'].value = air_table.color_transfer elif (event.key==K_f): # Stop all the pucks... for puck in air_table.pucks: puck.vel_2d_mps = Vec2D(0,0) # And for the Box2d puck. puck.b2d_body.linearVelocity = b2Vec2(0,0) elif (event.key==K_r): # Stop all the puck rotation... for puck in air_table.pucks: puck.b2d_body.angularVelocity = 0.0 elif (event.key==K_g): # Toggle the logical flag for g. air_table.g_ON = not air_table.g_ON print "g", air_table.g_ON if air_table.g_ON: air_table.g_2d_mps2 = air_table.gON_2d_mps2 # Box2d... for eachpuck in air_table.pucks: eachpuck.b2d_body.fixtures[0].restitution = eachpuck.coef_rest eachpuck.b2d_body.fixtures[0].friction = air_table.coef_friction_puck else: air_table.g_2d_mps2 = air_table.gOFF_2d_mps2 # Box2d... for eachpuck in air_table.pucks: if not eachpuck.CR_fixed: eachpuck.b2d_body.fixtures[0].restitution = 1.0 eachpuck.b2d_body.fixtures[0].friction = 0 elif (event.key==K_F1): # Toggle FPS display on/off air_table.FPS_display = not air_table.FPS_display # Jet keys elif (event.key==K_a): local_user.key_a = 'D' elif (event.key==K_s): local_user.key_s = 'D' elif (event.key==K_d): local_user.key_d = 'D' elif (event.key==K_w): local_user.key_w = 'D' # Gun keys elif (event.key==K_j): local_user.key_j = 'D' elif (event.key==K_k): local_user.key_k = 'D' elif (event.key==K_l): local_user.key_l = 'D' elif (event.key==K_i): local_user.key_i = 'D' elif (event.key==K_SPACE): local_user.key_space = 'D' # Zoom keys elif (event.key==K_b): local_user.key_b = 'D' elif (event.key==K_n): local_user.key_n = 'D' elif (event.key==K_m): local_user.m = 'D' elif (event.key==K_h): #print "h--> D" local_user.key_h = 'D' elif (event.key==K_LCTRL): #print "lctrl--> D" local_user.key_lctrl = 'D' # Control physics for Jello Madness elif ((event.key==K_p) and not self.shift_key_down()): air_table.stop_physics = not air_table.stop_physics if (not air_table.stop_physics): env.game_time_s = 0 print "game loop is active again" else: print "game loop is paused" elif ((event.key==K_p) and self.shift_key_down()): if (env.constant_dt_s == None): env.constant_dt_s = 1.0/env.FR_avg.result print "physics engine is stepping in equal intervals of 1 /", int( env.FR_avg.result) else: env.constant_dt_s = None print "physics engine steps are floating with the game loop" env.FR_avg.reset() # For modifying cursor selection. #b2d elif (event.key==K_LSHIFT): #print "lshift--> D" local_user.key_lshift = 'D' elif (event.key==K_t): local_user.key_t = 'D' else: return "nothing set up for this key" elif (event.type == pygame.KEYUP): # Jet keys if (event.key==K_a): local_user.key_a = 'U' elif (event.key==K_s): local_user.key_s = 'U' elif (event.key==K_d): local_user.key_d = 'U' elif (event.key==K_w): local_user.key_w = 'U' # Gun keys elif (event.key==K_j): local_user.key_j = 'U' elif (event.key==K_k): local_user.key_k = 'U' elif (event.key==K_l): local_user.key_l = 'U' elif (event.key==K_i): local_user.key_i = 'U' elif (event.key==K_SPACE): local_user.key_space = 'U' # Zoom keys elif (event.key==K_b): local_user.key_b = 'U' elif (event.key==K_n): local_user.key_n = 'U' elif (event.key==K_m): local_user.key_m = 'U' elif (event.key==K_h): local_user.key_h = 'U' elif (event.key==K_LCTRL): #print "lctrl--> U" local_user.key_lctrl = 'U' # Cursor selection modification #b2d elif (event.key==K_LSHIFT): local_user.key_lshift = 'U' elif (event.key==K_t): local_user.key_t = 'U' elif event.type == pygame.MOUSEBUTTONDOWN: local_user.buttonIsStillDown = True (button1, button2, button3) = pygame.mouse.get_pressed() if button1: local_user.mouse_button = 1 elif button2: local_user.mouse_button = 2 elif button3: local_user.mouse_button = 3 else: local_user.mouse_button = 0 elif event.type == pygame.MOUSEBUTTONUP: local_user.buttonIsStillDown = False local_user.mouse_button = 0 elif ((event.type == pygame.MOUSEMOTION) and (local_user.key_lctrl == 'D')): #print "in mousemotion block", event.pos, event.rel[0], event.rel[1] self.viewOffset_2d_px -= Vec2D(event.rel[0], -event.rel[1]) # In all cases, pass the event to the Gui. #app.event(event) if local_user.buttonIsStillDown: # This will select a puck when the puck runs into the cursor of the mouse with it's button still down. local_user.cursor_location_px = (mouseX, mouseY) = pygame.mouse.get_pos() class GameWindow: def __init__(self, screen_tuple_px, title): self.width_px = screen_tuple_px[0] self.height_px = screen_tuple_px[1] # The initial World position vector of the Upper Right corner of the screen. # Yes, that's right y_px = 0 for UR. self.UR_2d_m = env.ConvertScreenToWorld( Vec2D( self.width_px, 0)) # Create a reference to the display surface object. This is a pygame "surface". # Screen dimensions in pixels (tuple) self.surface = pygame.display.set_mode( screen_tuple_px) self.update_caption( title) self.surface.fill( THECOLORS["black"]) pygame.display.update() def update_caption(self, title): pygame.display.set_caption( title) self.caption = title def update(self): pygame.display.update() def clear(self): # Useful for shifting between the various demos. self.surface.fill( THECOLORS["black"]) pygame.display.update() #=========================================================== # Functions #=========================================================== def make_some_pucks(resetmode): game_window.update_caption("PyBox2D Air Table V.3: Demo #" + str(resetmode)) env.constant_dt_s = None env.FR_avg.reset() if resetmode == 1: # position ,radius,density air_table.pucks.append( Puck(Vec2D(2.5, 7.5), 0.25, 0.3, THECOLORS["orange"])) air_table.pucks.append( Puck(Vec2D(6.0, 2.5), 0.45, 0.3)) # maybe not. air_table.pucks.append( Puck(Vec2D(7.5, 2.5), 0.65, 0.3)) air_table.pucks.append( Puck(Vec2D(2.5, 5.5), 1.65, 0.3)) air_table.pucks.append( Puck(Vec2D(7.5, 7.5), 0.95, 0.3)) elif resetmode == 2: spacing_factor = 2.0 grid_size = 4,2 for j in range(grid_size[0]): for k in range(grid_size[1]): if ((j,k) == (1,1)): puck_color_value = THECOLORS["orange"] else: puck_color_value = THECOLORS["grey"] air_table.pucks.append( Puck(Vec2D(spacing_factor*(j+1), spacing_factor*(k+1)), 0.75, 0.3, puck_color=puck_color_value)) elif resetmode == 3: spacing_factor = 1.5 grid_size = 5,3 for j in range(grid_size[0]): for k in range(grid_size[1]): if ((j,k) == (2,2)): puck_color_value = THECOLORS["orange"] else: puck_color_value = THECOLORS["grey"] air_table.pucks.append( Puck(Vec2D(spacing_factor*(j+1), spacing_factor*(k+1)), 0.55, 0.3, puck_color=puck_color_value)) elif resetmode == 4: spacing_factor = 1.0 if platform.system() == 'Linux': grid_size = 5,4 else: grid_size = 7,7 for j in range(grid_size[0]): for k in range(grid_size[1]): if ((j,k) == (2,2)): puck_color_value = THECOLORS["orange"] else: puck_color_value = THECOLORS["grey"] air_table.pucks.append( Puck(Vec2D(spacing_factor*(j+1), spacing_factor*(k+1)), radius_m=0.25, density_kgpm2=1.0, puck_color=puck_color_value, CR_fixed=False, coef_rest=0.9) ) elif resetmode == 5: air_table.pucks.append( Puck(Vec2D(2.00, 3.00), 0.4, 0.3) ) air_table.pucks.append( Puck(Vec2D(3.50, 4.50), 0.4, 0.3) ) # No springs on this one. #air_table.pucks.append( Puck(Vec2D(3.50, 7.00), 0.95, 0.3) ) spring_strength_Npm2 = 20.0 #18.0 spring_length_m = 1.5 air_table.springs.append( Spring(air_table.pucks[0], air_table.pucks[1], spring_length_m, spring_strength_Npm2, width_m=0.2)) elif resetmode == 6: if platform.system() == 'Linux': density = 2.0 radius = 0.7 # Lower the CR for these pucks and fix them, using CR_fixed, so when gravity # toggles on/off they stay at these levels. coef_rest_puck = 0.50 spring_strength_Npm2 = 300.0 spring_length_m = 2.5 spring_width_m = 0.07 spring_drag = 0.0 spring_damper = 10.0 else: density = 1.5 radius = 0.7 coef_rest_puck = 0.70 spring_strength_Npm2 = 400.0 spring_length_m = 2.5 spring_width_m = 0.07 spring_drag = 0.0 spring_damper = 5.0 air_table.pucks.append( Puck(Vec2D(2.00, 3.00), radius, density, coef_rest=coef_rest_puck, CR_fixed=True) ) air_table.pucks.append( Puck(Vec2D(3.50, 4.50), radius, density, coef_rest=coef_rest_puck, CR_fixed=True) ) air_table.pucks.append( Puck(Vec2D(5.00, 3.00), radius, density, coef_rest=coef_rest_puck, CR_fixed=True) ) # No springs on this one. air_table.pucks.append( Puck(Vec2D(3.50, 7.00), 0.95, density, coef_rest=coef_rest_puck, CR_fixed=True) ) air_table.springs.append( Spring(air_table.pucks[0], air_table.pucks[1], spring_length_m, spring_strength_Npm2, width_m=spring_width_m, drag_c=spring_drag)) air_table.springs.append( Spring(air_table.pucks[1], air_table.pucks[2], spring_length_m, spring_strength_Npm2, width_m=spring_width_m, drag_c=spring_drag)) air_table.springs.append( Spring(air_table.pucks[2], air_table.pucks[0], spring_length_m, spring_strength_Npm2, width_m=spring_width_m, drag_c=spring_drag)) # Increase the shock-absorber strength for each spring. for spring in air_table.springs: spring.damper_Ns2pm2 = spring_damper elif resetmode == 7: # Optionally set fixed timestep. #env.constant_dt_s = float(1.0/60.0) # None air_table.collision_checking_enabled = True env.game_time_s = 0 offset_xy_m = Vec2D(2.5, 2.1) if platform.system() == 'Linux': spacing_factor = 1.0 grid_size = 3 density = 45.0 radius = 0.25 spring_strength_Npm2 = 800.0 #18.0 spring_length_m = 1.2 spring_damper_Ns2pm2 = 5.0 cr7 = 0.85 else: spacing_factor = 1.0 grid_size = 4 density = 5.0 radius = 0.23 spring_strength_Npm2 = 350.0 spring_length_m = 1.0 spring_damper_Ns2pm2 = 5.0 cr7 = 0.0 grid = grid_size, grid_size for j in range(grid[0]): for k in range(grid[1]): if ((j,k) == (2,2)): air_table.pucks.append( Puck(Vec2D(spacing_factor*(j+1), spacing_factor*(k+1)) + offset_xy_m, radius, density, THECOLORS["orange"], coef_rest=cr7, CR_fixed=True)) else: air_table.pucks.append( Puck(Vec2D(spacing_factor*(j+1), spacing_factor*(k+1)) + offset_xy_m, radius, density, coef_rest=cr7, CR_fixed=True)) for m in range(grid_size*(grid_size-1)): air_table.springs.append( Spring(air_table.pucks[m], air_table.pucks[m+grid_size], spring_length_m, spring_strength_Npm2, spring_color=THECOLORS["blue"])) for m in range(grid_size-1): for n in range(grid_size): o_index = m + (n * grid_size) #print "index:", m, n, o_index, o_index+1 air_table.springs.append( Spring(air_table.pucks[o_index], air_table.pucks[o_index+1], spring_length_m, spring_strength_Npm2, spring_color=THECOLORS["blue"])) # Diagonal springs are yellow for m in range(0, grid_size-1): for n in range(1, grid_size): o_index = m + (n * grid_size) air_table.springs.append( Spring(air_table.pucks[o_index], air_table.pucks[o_index-(grid_size-1)], spring_length_m * 1.41, spring_strength_Npm2, spring_color=THECOLORS["yellow"])) for m in range(0, grid_size-1): for n in range(0, grid_size-1): o_index = m + (n * grid_size) air_table.springs.append( Spring(air_table.pucks[o_index], air_table.pucks[o_index+(grid_size+1)], spring_length_m * 1.41, spring_strength_Npm2, spring_color=THECOLORS["yellow"])) # Increase the shock-absorber strength for each spring. for spring in air_table.springs: spring.damper_Ns2pm2 = spring_damper_Ns2pm2 elif resetmode == 8: air_table.collision_checking_enabled = True if platform.system() == 'Linux': # for Raspberry Pi density = 1.0 # ,radius,density air_table.pucks.append( Puck(Vec2D(5.0, 2.5), 0.30, density, rect_fixture=True)) air_table.pucks.append( Puck(Vec2D(5.0, 2.5), 0.30, density)) air_table.pucks.append( Puck(Vec2D(7.5, 2.5), 0.65, density, rect_fixture=True)) air_table.pucks.append( Puck(Vec2D(7.5, 5.5), 0.95, density, rect_fixture=True)) #air_table.pucks.append( Puck(Vec2D(2.5, 5.5), 1.65, density)) air_table.pucks.append( Puck(Vec2D(7.5, 7.5), 0.95, density)) # Make some pinned-spring pucks. for m in range(0, 3): pinPoint_2d = Vec2D(2.0 + float(m) * 1.65, 4.0) tempPuck = Puck(pinPoint_2d, 0.7, density*5.0, THECOLORS["orange"]) air_table.pucks.append( tempPuck) air_table.springs.append( Spring(tempPuck, pinPoint_2d, strength_Npm=300.0, width_m=0.02, drag_c = 1.5 + 10.0)) else: density = 0.7 # ,radius,density air_table.pucks.append( Puck(Vec2D(5.0, 2.5), 0.15, density, rect_fixture=True)) air_table.pucks.append( Puck(Vec2D(5.0, 2.5), 0.15, density, rect_fixture=False)) air_table.pucks.append( Puck(Vec2D(7.5, 2.5), 0.65, density, rect_fixture=True)) air_table.pucks.append( Puck(Vec2D(7.5, 5.5), 0.95, density, rect_fixture=True)) #air_table.pucks.append( Puck(Vec2D(2.5, 5.5), 1.65, density)) air_table.pucks.append( Puck(Vec2D(7.5, 7.5), 0.95, density)) # Make some pinned-spring pucks. for m in range(0, 6): pinPoint_2d = Vec2D(2.0 + float(m) * 0.65, 4.0) tempPuck = Puck(pinPoint_2d, 0.25, density, THECOLORS["orange"]) air_table.pucks.append( tempPuck) air_table.springs.append( Spring(tempPuck, pinPoint_2d, strength_Npm=300.0, width_m=0.02, drag_c=1.5)) # Make user/client controllable pucks # for all the clients. y_puck_position_m = 1.0 for client_name in env.clients: if env.clients[client_name].active: tempPuck = Puck(Vec2D(6.0, y_puck_position_m), 0.45, density) # Let the puck reference the jet and the jet reference the puck. tempPuck.client_name = client_name tempPuck.jet = Jet( tempPuck) tempPuck.gun = Gun( tempPuck) air_table.pucks.append( tempPuck) air_table.controlled_pucks.append( tempPuck) y_puck_position_m += 1.2 # Reset the hit counters. env.clients[client_name].bullet_hit_count = 0 # Keep gun on in a testing puck... if args.testPuck == 'on': tempPuck = Puck(Vec2D(6.0, y_puck_position_m), 0.45, density) # Let the puck reference the jet and the jet reference the puck. tempPuck.client_name = "test" env.clients[tempPuck.client_name] = Client(env.client_colors[tempPuck.client_name]) tempPuck.jet = Jet( tempPuck) tempPuck.gun = Gun( tempPuck) tempPuck.gun.testing_gun = True # The default position at instantiation is 45 degrees counter-clockwise from vertical. # The degree value specified here is relative to that +45. Negative values are clockwise. tempPuck.gun.rotate_everything( -110) air_table.pucks.append( tempPuck) air_table.controlled_pucks.append( tempPuck) elif resetmode == 9: # Make user/client controllable pucks # for all the clients. y_puck_position_m = 1.0 for client_name in env.clients: if env.clients[client_name].active: tempPuck = Puck(Vec2D(6.0, y_puck_position_m), 0.45, 0.3) # Let the puck reference the jet and the jet reference the puck. tempPuck.client_name = client_name tempPuck.jet = Jet( tempPuck) tempPuck.gun = Gun( tempPuck) air_table.pucks.append( tempPuck) air_table.controlled_pucks.append( tempPuck) y_puck_position_m += 1.2 elif resetmode == 0: # Make user/client controllable pucks # for all the clients. density = 0.7 width_m = 0.01 aspect_ratio = 9.0 x_position_m = 0.3 for j in range(0, 9): y_puck_position_m = (width_m * aspect_ratio / 1.0) + 0.1 air_table.pucks.append( Puck(Vec2D(x_position_m, y_puck_position_m), width_m, density, rect_fixture=True, aspect_ratio=aspect_ratio)) width_m *= 1.5 x_position_m *= 1.5 else: print "Nothing set up for this key." # Make a dictionary of the pucks so you can find a puck based on the b2d body. for puck in air_table.pucks: air_table.puck_dictionary[puck.b2d_body] = puck def display_number(numeric_value, font_object, mode='FPS'): if mode=='FPS': fps_value = "%.0f" % numeric_value if (env.constant_dt_s != None): # Small background rectangle for FPS text (left, top, width, height) pygame.draw.rect( game_window.surface, THECOLORS["white"], pygame.Rect(10, 10, 64, 20)) fps_string = fps_value + " (" + str( int( 1/env.constant_dt_s)) + ")" else: pygame.draw.rect( game_window.surface, THECOLORS["white"], pygame.Rect(10, 10, 35, 20)) fps_string = fps_value txt_surface = font_object.render( fps_string, True, THECOLORS["black"]) game_window.surface.blit( txt_surface, [18, 11]) elif mode=='counter': pygame.draw.rect( game_window.surface, THECOLORS["white"], pygame.Rect(10, 40, 40, 20)) cnt_string = "%.0f" % numeric_value txt_surface = font_object.render( cnt_string, True, THECOLORS["black"]) game_window.surface.blit( txt_surface, [18, 41]) elif mode=='gameTimer': time_string = "%.2f" % numeric_value txt_surface = font_object.render( time_string, True, THECOLORS["white"]) game_window.surface.blit( txt_surface, [600, 11]) #============================================================ # Main procedural script. #============================================================ def main(): # A few globals. global env, game_window, air_table, b2_world, args, dt_render_s # Parse parameters provided in the command line. # This description string (and parameter help) gets displayed if help is requested (-h added after the filename). parser = argparse.ArgumentParser(description='Please add optional client parameters after the file name. For example: \n' + 'A16c_2D_B2D_serverN.py off') # An optional positional argument. parser.add_argument('testPuck', type=str, nargs='?', default='on', help='Please indicate whether the practice puck should be on or off (default is on).') args = parser.parse_args() print "testPuck:", args.testPuck pygame.init() myclock = pygame.time.Clock() if platform.system() == 'Linux': window_dimensions_px = (800, 700) #window_width_px, window_height_px (600, 500) else: window_dimensions_px = (800, 700) #window_width_px, window_height_px (800, 700) # Create the first user/client and the methods for moving between the screen and the world. env = Environment(window_dimensions_px, 10.0) # 10m in along the x axis. game_window = GameWindow(window_dimensions_px, 'nothing yet...') # Define the Left, Right, Bottom, and Top boundaries of the game window. air_table = AirTable({"L_m":0.0, "R_m":game_window.UR_2d_m.x, "B_m":0.0, "T_m":game_window.UR_2d_m.y}) #===================================================================== # Box2d setup (start) #===================================================================== # Create the world b2_world = b2World(gravity=(-0.0, -0.0), doSleep=True) # List of wall bodies. # Floor wall_body = b2_world.CreateStaticBody(position=(0.0, -1.0), shapes=b2PolygonShape(box=(150, 1.0)) ) air_table.b2_walls.append( wall_body) # Ceiling wall_body = b2_world.CreateStaticBody(position=(0.0, game_window.UR_2d_m.y+1.0), shapes=b2PolygonShape(box=(150, 1.0)) ) air_table.b2_walls.append( wall_body) # Left wall. wall_body = b2_world.CreateStaticBody(position=(-1.0, 0.0), shapes=b2PolygonShape(box=(1.0, 150.0)) ) air_table.b2_walls.append( wall_body) # Right wall. wall_body = b2_world.CreateStaticBody(position=(game_window.UR_2d_m.x+1.0, 0.0), shapes=b2PolygonShape(box=(1.0, 150.0)) ) air_table.b2_walls.append( wall_body) #===================================================================== # Box2d setup (end) #===================================================================== # Add some pucks to the table. demo_mode = 8 make_some_pucks( demo_mode) # Setup network server. if platform.system() == 'Linux': local_ip = commands.getoutput("hostname -I") else: local_ip = socket.gethostbyname(socket.gethostname()) print "Server IP address:", local_ip game_server = GameServer(localaddr=(local_ip, 4330)) # Font object for rendering text onto display surface. fnt_FPS = pygame.font.SysFont("Arial", 14) fnt_gameTimer = pygame.font.SysFont("Arial", 60) dt_render_s = 0.0 dt_render_limit_s = 1.0/float(120) # 1/FR # An object containing the running average of the framerate of the physics calculations. if platform.system() == 'Linux': env.FR_avg.n_target = 50 #50 else: env.FR_avg.n_target = 2500 #Default is 300 while True: #print dt_physics_s, myclock.get_fps() if (env.constant_dt_s != None): gameLoop_FR_limit = int(1.0/env.constant_dt_s) else: gameLoop_FR_limit = 480 # default dt_gameLoop_s = float( myclock.tick( gameLoop_FR_limit) * 1e-3) if (env.constant_dt_s != None): dt_physics_s = env.constant_dt_s else: dt_physics_s = dt_gameLoop_s if air_table.FPS_display: env.FR_avg.update(1.0/dt_gameLoop_s) # Get input from local user. resetmode = env.get_local_user_input() # This check avoids problem when dragging the game window. if ((dt_gameLoop_s < 0.10) and (not air_table.stop_physics)): # Reset the game based on local user control. if resetmode in [0,1,2,3,4,5,6,7,8,9]: demo_mode = resetmode print resetmode # This should remove all references to the pucks and effectively kill them off. If there were other # variables referring to this list, this would not stop the pucks. # Delete all the objects on the table. Cleaning out these list reference to these objects effectively # deletes the objects. Notice the controlled list must be cleared also. # First some Box2d clean-up. for eachpuck in air_table.pucks: b2_world.DestroyBody(eachpuck.b2d_body) # Then all the lists. air_table.pucks = [] air_table.puck_dictionary = {} air_table.controlled_pucks = [] air_table.springs = [] # This avoids call to the collision checker. air_table.collision_checking_enabled = False # Now just black out the screen. game_window.clear() # Reinitialize the demo. make_some_pucks( resetmode) if (dt_render_s > dt_render_limit_s): # Get input from network clients. game_server.Pump() env.checkForQuietClients() for client_name in env.clients: # Calculate client related forces. env.clients[client_name].calc_string_forces_on_pucks() if (dt_render_s > dt_render_limit_s): # Control the zoom env.control_zoom_and_view() for controlled_puck in air_table.controlled_pucks: # Rotate based on keyboard of the controlling client. controlled_puck.jet.client_rotation_control( controlled_puck.client_name) controlled_puck.gun.client_rotation_control( controlled_puck.client_name) # Turn gun on/off controlled_puck.gun.control_firing( controlled_puck.client_name) # Turn shield on/off controlled_puck.gun.control_shield( controlled_puck.client_name) # Calculate jet forces on pucks... for controlled_puck in air_table.controlled_pucks: controlled_puck.jet.turn_jet_forces_onoff( controlled_puck.client_name) # Calculate the forces the springs apply on the pucks... for eachspring in air_table.springs: eachspring.calc_spring_forces_on_pucks() # Apply forces to the pucks and calculate movements. for eachpuck in air_table.pucks: air_table.update_TotalForceVectorOnPuck( eachpuck, dt_physics_s) # Run Box2d b2_world.Step( dt_physics_s, 10, 10) # Get new positions, translational velocities, and rotational speeds, from box2d for eachpuck in air_table.pucks: eachpuck.get_Box2d_XandV() # Check for puck-puck collisions. if air_table.collision_checking_enabled: air_table.check_for_collisions() if (dt_render_s > dt_render_limit_s): # Erase the blackboard. if not air_table.g_ON: game_window.surface.fill((0,0,0)) # Black else: #grayscale = 50 #game_window.surface.fill((grayscale,grayscale,grayscale)) # Gray game_window.surface.fill((0,82,110)) # Blue # Display FPS and game timer text. if air_table.FPS_display: display_number(env.FR_avg.result, fnt_FPS, mode='FPS') #display_number(1/dt_physics_s, fnt_FPS, mode='FPS') if (demo_mode == 7): display_number(env.game_time_s, fnt_gameTimer, mode='gameTimer') # Clean out old bullets. puck_list_copy = air_table.pucks[:] for thisPuck in puck_list_copy: if (thisPuck.bullet) and ((env.time_s - thisPuck.birth_time_s) > thisPuck.age_limit_s): # First remove the box2d bullet. b2_world.DestroyBody(thisPuck.b2d_body) #b2d air_table.pucks.remove(thisPuck) del puck_list_copy # Draw pucks, springs, mouse tethers, and jets. # Draw boundaries of table. air_table.draw() for eachpuck in air_table.pucks: eachpuck.draw() if (eachpuck.jet != None): if env.clients[eachpuck.client_name].active or (eachpuck.client_name == 'test'): eachpuck.jet.draw() eachpuck.gun.draw() for eachspring in air_table.springs: eachspring.draw() env.remove_healthless_clients() for client_name in env.clients: if (env.clients[client_name].selected_puck != None): env.clients[client_name].draw_cursor_string() # Draw cursors for network clients. if ((client_name != 'local') and env.clients[client_name].active): env.clients[client_name].draw_fancy_server_cursor() #print client_name, env.clients[client_name].bullet_hit_count pygame.display.flip() dt_render_s = 0 # Limit the rendering framerate to be below that of the physics calculations. dt_render_s += dt_gameLoop_s # Keep track of time for use in timestamping operations # (determine the age of old bullets to be deleted) env.time_s += dt_gameLoop_s # Jello madness game timer if air_table.tangled: env.game_time_s += dt_gameLoop_s #============================================================ # Run the main program. #============================================================ if __name__ == "__main__": main()