From e579086ec5183bb577be5ca78b9c5cd4fe3af58e Mon Sep 17 00:00:00 2001 From: Quantum Date: Fri, 24 Aug 2018 14:44:11 -0400 Subject: [PATCH] Move non-UI logic from game to world. --- punyverse/camera.py | 15 ++++++ punyverse/entity.py | 54 ++++++++++++-------- punyverse/game.py | 119 +++++++++++-------------------------------- punyverse/world.json | 1 + punyverse/world.py | 68 +++++++++++++++++++------ 5 files changed, 131 insertions(+), 126 deletions(-) diff --git a/punyverse/camera.py b/punyverse/camera.py index 29abf9e..18a6d64 100644 --- a/punyverse/camera.py +++ b/punyverse/camera.py @@ -10,6 +10,10 @@ class Camera(object): self.yaw = yaw self.roll = roll + self.speed = 0 + self.roll_left = False + self.roll_right = False + def move(self, speed): dx, dy, dz = self.direction() self.x += dx * speed @@ -40,5 +44,16 @@ class Camera(object): dz = sin(radians(self.yaw - 90)) * m return dx, dy, dz + def update(self, dt, move): + if self.roll_left: + self.roll += 4 * dt * 10 + if self.roll: + self.roll -= 4 * dt * 10 + if move: + self.move(self.speed * 10 * dt) + + def reset_roll(self): + self.roll = 0 + def distance(self, x, y, z): return hypot(hypot(x - self.x, y - self.y), z - self.z) diff --git a/punyverse/entity.py b/punyverse/entity.py index 877a5b1..f85a68a 100644 --- a/punyverse/entity.py +++ b/punyverse/entity.py @@ -34,20 +34,14 @@ class Entity(object): def collides(self, x, y, z): return False - def draw(self, cam, options): + def draw(self, options): raise NotImplementedError() class Asteroid(Entity): - asteroid_ids = [] - - @classmethod - def load_asteroid(cls, file): - cls.asteroid_ids.append(model_list(load_model(file), 5, 5, 5, (0, 0, 0))) - - def __init__(self, location, direction): + def __init__(self, asteroid_id, location, direction): super(Asteroid, self).__init__('Asteroid', location, direction=direction) - self.asteroid_id = random.choice(self.asteroid_ids) + self.asteroid_id = asteroid_id def update(self): super(Asteroid, self).update() @@ -56,11 +50,26 @@ class Asteroid(Entity): # Increment all axis to 'spin' self.rotation = rx + 1, ry + 1, rz + 1 - def draw(self, cam, options): + def draw(self, options): with glMatrix(self.location, self.rotation), glRestore(GL_CURRENT_BIT): glCallList(self.asteroid_id) +class AsteroidManager(object): + def __init__(self): + self.asteroids = [] + + def __bool__(self): + return bool(self.asteroids) + __nonzero__ = __bool__ + + def load(self, file): + self.asteroids.append(model_list(load_model(file), 5, 5, 5, (0, 0, 0))) + + def new(self, location, direction): + return Asteroid(random.choice(self.asteroids), location, direction) + + class Belt(Entity): def __init__(self, name, world, info): self.world = world @@ -93,7 +102,7 @@ class Belt(Entity): pitch, yaw, roll = self.rotation self.rotation = pitch, self.world.tick * self.rotation_angle % 360, roll - def draw(self, cam, options): + def draw(self, options): with glMatrix(self.location, self.rotation), glRestore(GL_CURRENT_BIT): glCallList(self.belt_id) @@ -114,7 +123,8 @@ class Sky(Entity): self.sky_id = compile(sphere, info.get('radius', 1000000), division, division, texture, inside=True, lighting=False) - def draw(self, cam, options): + def draw(self, options): + cam = self.world.cam with glMatrix((-cam.x, -cam.y, -cam.z), self.rotation), glRestore(GL_CURRENT_BIT): glCallList(self.sky_id) @@ -219,18 +229,18 @@ class Body(Entity): glLineWidth(1) glCallList(self.get_orbit()) - def draw(self, cam, options): - self._draw(cam, options) + def draw(self, options): + self._draw(options) if options.orbit and self.orbit: - dist = cam.distance(*self.parent.location) + dist = self.world.cam.distance(*self.parent.location) if dist < self.parent.orbit_show: self._draw_orbits(dist) for satellite in self.satellites: - satellite.draw(cam, options) + satellite.draw(options) - def _draw(self, cam, options): + def _draw(self, options): raise NotImplementedError() def collides(self, x, y, z): @@ -297,7 +307,7 @@ class SphericalBody(Body): glDisable(GL_LIGHTING) glCallList(self.sphere_id) - def _draw_atmosphere(self, cam, glMatrixBuffer=GLfloat * 16): + def _draw_atmosphere(self, glMatrixBuffer=GLfloat * 16): with glMatrix(self.location), glRestore(GL_ENABLE_BIT | GL_CURRENT_BIT): matrix = glMatrixBuffer() glGetFloatv(GL_MODELVIEW_MATRIX, matrix) @@ -310,7 +320,7 @@ class SphericalBody(Body): glCallList(self.atmosphere_id) if self.corona_id: - x, y, z = cam.direction() + x, y, z = self.world.cam.direction() glTranslatef(-x, -y, -z) glEnable(GL_BLEND) glCallList(self.corona_id) @@ -325,11 +335,11 @@ class SphericalBody(Body): with glMatrix(self.location, self.ring_rotation), glRestore(GL_CURRENT_BIT): glCallList(self.ring_id) - def _draw(self, cam, options): + def _draw(self, options): self._draw_sphere() if options.atmosphere and (self.atmosphere_id or self.corona_id): - self._draw_atmosphere(cam) + self._draw_atmosphere() if options.cloud and self.cloudmap_id: self._draw_clouds() @@ -352,6 +362,6 @@ class ModelBody(Body): self.object_id = model_list(load_model(info['model']), info.get('sx', scale), info.get('sy', scale), info.get('sz', scale), (0, 0, 0)) - def _draw(self, cam, options): + def _draw(self, options): with glMatrix(self.location, self.rotation), glRestore(GL_CURRENT_BIT): glCallList(self.object_id) diff --git a/punyverse/game.py b/punyverse/game.py index 689d4b7..c1e4f76 100644 --- a/punyverse/game.py +++ b/punyverse/game.py @@ -8,8 +8,6 @@ from time import clock import six from punyverse import texture -from punyverse.camera import Camera -from punyverse.entity import Asteroid from punyverse.glgeom import * from punyverse.world import World @@ -28,7 +26,6 @@ from pyglet.window import key, mouse import pyglet -INITIAL_SPEED = 0 # The initial speed of the player MOUSE_SENSITIVITY = 0.3 # Mouse sensitivity, 0..1, none...hyperspeed MAX_DELTA = 5 @@ -44,8 +41,6 @@ def entity_distance(x0, y0, z0): class Applet(pyglet.window.Window): - asteroids = ['asteroids/01.obj', 'asteroids/02.obj', 'asteroids/03.obj'] - def __init__(self, *args, **kwargs): super(Applet, self).__init__(*args, **kwargs) texture.init() @@ -94,8 +89,6 @@ class Applet(pyglet.window.Window): self.fps = 0 self.world = World('world.json', self._load_callback) self._load_callback('Initializing game...', '', 0) - self.speed = INITIAL_SPEED - self.keys = set() self.info = True self.debug = False self.orbit = True @@ -105,7 +98,6 @@ class Applet(pyglet.window.Window): self.atmosphere = True self.cloud = True - self.tick = self.world.tick_length self.ticks = [ 1, 2, 5, 10, 20, 40, 60, # Second range 120, 300, 600, 1200, 1800, 2700, 3600, # Minute range @@ -116,9 +108,6 @@ class Applet(pyglet.window.Window): 63072000, 157680000, 315360000, # 2, 5, 10 years 630720000, 1576800000, 3153600000, # 20, 50, 100 years ] - self.__time_per_second_cache = None - self.__time_per_second_value = None - self.__time_accumulate = 0 def speed_incrementer(object, increment): def incrementer(): @@ -135,26 +124,26 @@ class Applet(pyglet.window.Window): return toggler def increment_tick(): - index = self.ticks.index(self.tick) + 1 + index = self.ticks.index(self.world.tick_length) + 1 if index < len(self.ticks): - self.tick = self.ticks[index] + self.world.tick_length = self.ticks[index] def decrement_tick(): - index = self.ticks.index(self.tick) - 1 + index = self.ticks.index(self.world.tick_length) - 1 if index >= 0: - self.tick = self.ticks[index] + self.world.tick_length = self.ticks[index] self.key_handler = { key.ESCAPE: pyglet.app.exit, - key.NUM_ADD: speed_incrementer(self, 1), - key.NUM_SUBTRACT: speed_incrementer(self, -1), - key.NUM_MULTIPLY: speed_incrementer(self, 10), - key.NUM_DIVIDE: speed_incrementer(self, -10), - key.PAGEUP: speed_incrementer(self, 100), - key.PAGEDOWN: speed_incrementer(self, -100), - key.HOME: speed_incrementer(self, 1000), - key.END: speed_incrementer(self, -1000), - key.R: lambda: setattr(self.cam, 'roll', 0), + key.NUM_ADD: speed_incrementer(self.world.cam, 1), + key.NUM_SUBTRACT: speed_incrementer(self.world.cam, -1), + key.NUM_MULTIPLY: speed_incrementer(self.world.cam, 10), + key.NUM_DIVIDE: speed_incrementer(self.world.cam, -10), + key.PAGEUP: speed_incrementer(self.world.cam, 100), + key.PAGEDOWN: speed_incrementer(self.world.cam, -100), + key.HOME: speed_incrementer(self.world.cam, 1000), + key.END: speed_incrementer(self.world.cam, -1000), + key.R: self.world.cam.reset_roll, key.I: attribute_toggler(self, 'info'), key.D: attribute_toggler(self, 'debug'), key.O: attribute_toggler(self, 'orbit'), @@ -164,19 +153,18 @@ class Applet(pyglet.window.Window): key.ENTER: attribute_toggler(self, 'running'), key.INSERT: increment_tick, key.DELETE: decrement_tick, - key.SPACE: self.launch_meteor, + key.SPACE: self.world.spawn_asteroid, key.E: lambda: self.set_exclusive_mouse(False), key.F: lambda: self.set_fullscreen(not self.fullscreen), } self.mouse_press_handler = { - mouse.LEFT: self.launch_meteor, + mouse.LEFT: self.world.spawn_asteroid, mouse.RIGHT: attribute_toggler(self, 'moving'), } self.label = pyglet.text.Label('', font_name='Consolas', font_size=12, x=10, y=self.height - 20, color=(255,) * 4, multiline=True, width=600) - self.cam = Camera() self.exclusive = False @@ -205,18 +193,6 @@ class Applet(pyglet.window.Window): glLightfv(GL_LIGHT1, GL_DIFFUSE, fv4(.5, .5, .5, 1)) glLightfv(GL_LIGHT1, GL_SPECULAR, fv4(1, 1, 1, 1)) - for id, file in enumerate(self.asteroids): - self._load_callback('Loading asteroids...', 'Loading %s...' % file, float(id) / len(self.asteroids)) - Asteroid.load_asteroid(file) - - c = self.cam - c.x, c.y, c.z = self.world.start - c.pitch, c.yaw, c.roll = self.world.direction - - self._load_callback('Updating entities...', '', 0) - for entity in self.world.tracker: - entity.update() - print('Loaded in %s seconds.' % (clock() - start)) self.loaded = True pyglet.clock.schedule(self.update) @@ -252,15 +228,6 @@ class Applet(pyglet.window.Window): super(Applet, self).set_exclusive_mouse(exclusive) self.exclusive = exclusive - def launch_meteor(self): - c = self.cam - dx, dy, dz = c.direction() - speed = abs(self.speed) * 1.1 + 5 - dx *= speed - dy *= speed - dz *= speed - self.world.tracker.append(Asteroid((c.x, c.y - 3, c.z + 5), (dx, dy, dz))) - def on_mouse_press(self, x, y, button, modifiers): self.modifiers = modifiers if not self.loaded: @@ -277,7 +244,7 @@ class Applet(pyglet.window.Window): return if self.exclusive: # Only handle camera movement if mouse is grabbed - self.cam.mouse_move(dx * MOUSE_SENSITIVITY, dy * MOUSE_SENSITIVITY) + self.world.cam.mouse_move(dx * MOUSE_SENSITIVITY, dy * MOUSE_SENSITIVITY) def on_key_press(self, symbol, modifiers): self.modifiers = modifiers @@ -288,15 +255,19 @@ class Applet(pyglet.window.Window): if self.exclusive: # Only handle keyboard input if mouse is grabbed if symbol in self.key_handler: self.key_handler[symbol]() - else: - self.keys.add(symbol) + elif symbol == key.A: + self.world.cam.roll_left = True + elif symbol == key.S: + self.world.cam.roll_right = True def on_key_release(self, symbol, modifiers): if not self.loaded: return - if symbol in self.keys: - self.keys.remove(symbol) + if symbol == key.A: + self.world.cam.roll_left = False + elif symbol == key.S: + self.world.cam.roll_right = False def on_resize(self, width, height): if not self.loaded: @@ -315,12 +286,10 @@ class Applet(pyglet.window.Window): if not self.loaded: return - self.speed += scroll_y * 50 + scroll_x * 500 + self.world.cam.speed += scroll_y * 50 + scroll_x * 500 def get_time_per_second(self): - if self.__time_per_second_cache == self.tick: - return self.__time_per_second_value - time = self.tick + .0 + time = self.world.tick_length unit = 'seconds' for size, name in ((60, 'minutes'), (60, 'hours'), (24, 'days'), (365, 'years')): if time < size: @@ -328,35 +297,10 @@ class Applet(pyglet.window.Window): time /= size unit = name result = '%s %s' % (round(time, 1), unit) - self.__time_per_second_cache = self.tick - self.__time_per_second_value = result return result def update(self, dt): - c = self.cam - - if self.exclusive: - if key.A in self.keys: - c.roll += 4 * dt * 10 - if key.S in self.keys: - c.roll -= 4 * dt * 10 - if self.moving: - c.move(self.speed * 10 * dt) - - if self.running: - delta = self.tick * dt - update = int(delta + self.__time_accumulate + 0.5) - if update: - self.__time_accumulate = 0 - self.world.tick += update - for entity in self.world.tracker: - entity.update() - collision = entity.collides(c.x, c.y, c.z) - if collision: - self.speed *= -1 - c.move(self.speed * 12 * dt) - else: - self.__time_accumulate += delta + self.world.update(dt, move=self.exclusive and self.moving, tick=self.running) def draw_loading(self, phase=None, message=None, progress=None): glClear(GL_COLOR_BUFFER_BIT) @@ -378,8 +322,7 @@ class Applet(pyglet.window.Window): glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glLoadIdentity() - c = self.cam - + c = self.world.cam x, y, z = c.x, c.y, c.z glRotatef(c.pitch, 1, 0, 0) glRotatef(c.yaw, 0, 1, 0) @@ -395,7 +338,7 @@ class Applet(pyglet.window.Window): world.x, world.y, world.z = x, y, z for entity in world.tracker: - entity.draw(c, self) + entity.draw(self) glColor4f(1, 1, 1, 1) glDisable(GL_TEXTURE_2D) @@ -407,11 +350,11 @@ class Applet(pyglet.window.Window): if self.info_precise: info = ('%d FPS @ (x=%.2f, y=%.2f, z=%.2f) @ %s, %s/s\n' 'Direction(pitch=%.2f, yaw=%.2f, roll=%.2f)\nTick: %d' % - (pyglet.clock.get_fps(), c.x, c.y, c.z, self.speed, self.get_time_per_second(), + (pyglet.clock.get_fps(), c.x, c.y, c.z, self.world.cam.speed, self.get_time_per_second(), c.pitch, c.yaw, c.roll, self.world.tick)) else: info = ('%d FPS @ (x=%.2f, y=%.2f, z=%.2f) @ %s, %s/s\n' % - (pyglet.clock.get_fps(), c.x, c.y, c.z, self.speed, self.get_time_per_second())) + (pyglet.clock.get_fps(), c.x, c.y, c.z, self.world.cam.speed, self.get_time_per_second())) self.label.text = info self.label.draw() with glRestore(GL_CURRENT_BIT | GL_LINE_BIT): diff --git a/punyverse/world.json b/punyverse/world.json index 9cd13f3..2b73f48 100644 --- a/punyverse/world.json +++ b/punyverse/world.json @@ -308,6 +308,7 @@ "yaw": 30, "roll": 180 }, + "asteroids": ["asteroids/01.obj", "asteroids/02.obj", "asteroids/03.obj"], "start": { "z": "AU - 400", "yaw": 180 diff --git a/punyverse/world.py b/punyverse/world.py index 347d634..ea1cc54 100644 --- a/punyverse/world.py +++ b/punyverse/world.py @@ -6,14 +6,15 @@ from collections import OrderedDict import six +from punyverse import texture +from punyverse.camera import Camera +from punyverse.entity import * + try: from punyverse._model import model_list, load_model except ImportError: from punyverse.model import model_list, load_model -from punyverse.entity import * -from punyverse import texture - def load_world(file, callback=lambda message, completion: None): return World(file, callback) @@ -22,18 +23,23 @@ def load_world(file, callback=lambda message, completion: None): class World(object): def __init__(self, file, callback): self.tracker = [] - self.start = (0, 0, 0) - self.direction = (0, 0, 0) self.x = None self.y = None self.z = None - self.tick_length = 1 + self.tick_length = 0 self.tick = 0 + self.asteroids = AsteroidManager() + self.cam = Camera() self.callback = callback self._parse(file) del self.callback # So it can't be used after loading finishes + self._time_accumulate = 0 + + for entity in self.tracker: + entity.update() + def evaluate(self, value): return eval(str(value), {'__builtins__': None}, self._context) @@ -53,8 +59,7 @@ class World(object): self._length = root.get('length', 4320) self._context = {'AU': self._au, 'TEXTURE': texture.max_texture, 'KM': 1.0 / self._length} - tick = root.get('tick', 4320) # How many second is a tick? - self.tick_length = tick + self.tick_length = root.get('tick', 4320) # How many second is a tick? # Need to know how many objects are being loaded self._objects = 0 @@ -68,14 +73,12 @@ class World(object): if 'start' in root: info = root['start'] - x = self.evaluate(info.get('x', 0)) - y = self.evaluate(info.get('y', 0)) - z = self.evaluate(info.get('z', 0)) - pitch = self.evaluate(info.get('pitch', 0)) - yaw = self.evaluate(info.get('yaw', 0)) - roll = self.evaluate(info.get('roll', 0)) - self.start = (x, y, z) - self.direction = (pitch, yaw, roll) + self.cam.x = self.evaluate(info.get('x', 0)) + self.cam.y = self.evaluate(info.get('y', 0)) + self.cam.z = self.evaluate(info.get('z', 0)) + self.cam.pitch = self.evaluate(info.get('pitch', 0)) + self.cam.yaw = self.evaluate(info.get('yaw', 0)) + self.cam.roll = self.evaluate(info.get('roll', 0)) for planet, info in six.iteritems(root['bodies']): self.callback('Loading objects (%d of %d)...' % (self._current_object, self._objects), @@ -94,6 +97,12 @@ class World(object): self.callback('Loading sky...', 'Loading sky.', 0) self.tracker.append(Sky(self, root['sky'])) + if 'asteroids' in root: + asteroids = root['asteroids'] + for i, file in enumerate(asteroids): + self.callback('Loading asteroids...', 'Loading %s...' % file, i / len(asteroids)) + self.asteroids.load(file) + def _body(self, name, info, parent=None): if 'texture' in info: body = SphericalBody(name, self, info, parent) @@ -112,3 +121,30 @@ class World(object): 'Loading %s, satellite of %s.' % (satellite, name), self._current_object / self._objects) self._body(satellite, info, body) self._current_object += 1 + + def spawn_asteroid(self): + if self.asteroids: + c = self.cam + dx, dy, dz = c.direction() + speed = abs(self.cam.speed) * 1.1 + 5 + self.tracker.append(self.asteroids.new((c.x, c.y - 3, c.z + 5), (dx * speed, dy * speed, dz * speed))) + + def update(self, dt, move, tick): + c = self.cam + c.update(dt, move) + + if tick: + delta = self.tick_length * dt + update = int(delta + self._time_accumulate + 0.5) + if update: + self._time_accumulate = 0 + self.tick += update + + for entity in self.tracker: + entity.update() + collision = entity.collides(c.x, c.y, c.z) + if collision: + c.speed *= -1 + c.move(c.speed * 12 * dt) + else: + self._time_accumulate += delta