-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added some sort of example. Asteroids. Still needs work on world coll…
…ision API...
- Loading branch information
1 parent
afe42c4
commit 3e88ba1
Showing
7 changed files
with
732 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
import abc | ||
import math | ||
from planar import Vec2, Affine | ||
from pyglet.gl import GL_LINE_LOOP | ||
from pyglet.graphics import draw as gl_draw | ||
|
||
from gengine.collision import Shape, Polygon, Circle | ||
|
||
|
||
class Component: | ||
__metaclass__ = abc.ABCMeta | ||
|
||
_world = None | ||
|
||
def __init__( | ||
self, | ||
position: Vec2, | ||
shape: Shape, | ||
timestamp: int): | ||
self._position = position | ||
self._shape = shape | ||
self._timestamp = timestamp | ||
|
||
@property | ||
def shape(self): | ||
return self._shape | ||
|
||
@property | ||
def position(self): | ||
return self._position | ||
|
||
@property | ||
def timestamp(self): | ||
return self._timestamp | ||
|
||
|
||
class PolygonComponent(Component): | ||
|
||
def draw(self, timestamp): | ||
dt = timestamp - self.timestamp | ||
shape = self.shape | ||
transform = self._get_transform(dt) | ||
n_points = len(shape) | ||
flatten_array = [] | ||
for vertice in shape: | ||
flatten_array.extend(vertice * transform) | ||
gl_draw(n_points, GL_LINE_LOOP, ('v2f', flatten_array)) | ||
|
||
def _get_transform(self, dt): | ||
position = Affine.translation(self._position + self._velocity * dt) | ||
return position | ||
|
||
|
||
class Asteroid(PolygonComponent): | ||
|
||
ROTATION_SPEED = 60 # deg/s | ||
|
||
def __init__( | ||
self, | ||
position: Vec2, | ||
velocity: Vec2, | ||
timestamp: int): | ||
shape = Polygon([ | ||
Vec2(2, 4), Vec2(4, 2), | ||
Vec2(2, 1), Vec2(5, -1), | ||
Vec2(2, -4), Vec2(-2, -4), | ||
Vec2(-4, -2), Vec2(-4, 2), | ||
Vec2(-2, 4), Vec2(0, 3), | ||
]) | ||
self.radius = max((ob.length for ob in shape)) | ||
super().__init__(position, shape, timestamp) | ||
self._velocity = velocity | ||
|
||
def _get_transform(self, dt): | ||
# Set rotation in analogue to direction of velocity | ||
position = Affine.translation(self._position + self._velocity * dt) | ||
return position | ||
|
||
def _update_time(self, timestamp): | ||
dt = timestamp - self._timestamp | ||
self._position = self._position + self._velocity * dt | ||
self._timestamp = timestamp | ||
|
||
|
||
class Bullet(PolygonComponent): | ||
|
||
BULLET_SPEED = 120 | ||
BULLET_TTL = 0.8 | ||
|
||
def __init__( | ||
self, | ||
position: Vec2, | ||
direction: Vec2, | ||
timestamp: int): | ||
r = self.radius = 0.15 | ||
self.ttl = self.BULLET_TTL | ||
shape = Polygon([ | ||
Vec2(r, r), Vec2(-r, r), | ||
Vec2(-r, -r), Vec2(r, -r), | ||
]) | ||
super().__init__(position, shape, timestamp) | ||
self._velocity = direction * self.BULLET_SPEED | ||
|
||
def draw(self, timestamp): | ||
assert self.timestamp + self.ttl > timestamp, \ | ||
"Why do we draw an expired bullet" | ||
super().draw(timestamp) | ||
|
||
def _get_transform(self, dt): | ||
# Set rotation in analogue to direction of velocity | ||
rotation = Affine.rotation(self._velocity.angle) | ||
position = Affine.translation(self._position + self._velocity * dt) | ||
return position * rotation | ||
|
||
def _update_time(self, timestamp): | ||
dt = timestamp - self._timestamp | ||
self._position = self._position + self._velocity * dt | ||
self._timestamp = timestamp | ||
|
||
|
||
class SpaceShip(PolygonComponent): | ||
|
||
ROTATION_SPEED = 12 # deg / s | ||
BUMP_AMPLITUDE = 10 | ||
DECELERATION = 0.5 | ||
|
||
def __init__( | ||
self, | ||
position: Vec2, | ||
velocity: Vec2, | ||
angle: int, | ||
timestamp: int, | ||
): | ||
shape = Polygon([ | ||
Vec2(4, 0), Vec2(-2, -2), | ||
Vec2(-1, 0), Vec2(-2, 2), | ||
]) | ||
super().__init__(position, shape, timestamp) | ||
self._velocity = velocity | ||
self._angle = angle | ||
|
||
def _get_transform(self, dt): | ||
# Set rotation in analogue to direction of velocity | ||
rotation = Affine.rotation(self._angle) | ||
position = Affine.translation(self._position_in(dt)) | ||
return position * rotation | ||
|
||
def _position_in(self, dt): | ||
# integral v/(1+b t) dt = (v log(b t+1))/b+constant | ||
# where v - velocity, b - DECELERATION | ||
return self._position + ( | ||
self._velocity * ( | ||
math.log(self.DECELERATION * dt + 1) / self.DECELERATION)) | ||
|
||
def _velocity_in(self, dt): | ||
# v/(1+b t) | ||
# where v - velocity, b - DECELERATION | ||
return self._velocity / (1 + self.DECELERATION * dt) | ||
|
||
def _update_time(self, timestamp): | ||
dt = timestamp - self._timestamp | ||
self._position = self._position_in(dt) | ||
self._velocity = self._velocity_in(dt) | ||
self._timestamp = timestamp | ||
|
||
def bump(self, timestamp): | ||
self._update_time(timestamp) | ||
direction = Vec2.polar(self._angle) | ||
self._velocity += direction * self.BUMP_AMPLITUDE | ||
# FIXME: Reschedule world | ||
|
||
def rotate(self, timestamp, direction): | ||
if direction not in [1, -1]: | ||
raise ValueError("`direction` should be 1 or -1") | ||
|
||
self._update_time(timestamp) | ||
self._angle += direction * self.ROTATION_SPEED | ||
# FIXME: Reschedule world | ||
|
||
def fire_bullet(self, timestamp): | ||
dt = timestamp - self._timestamp | ||
position = self._position_in(dt) | ||
# Bullet is created at pin of the ship. Which is 4 units further than | ||
# center | ||
direction = Vec2.polar(self._angle) | ||
position += direction * 4 | ||
|
||
bullet = Bullet( | ||
position, direction, timestamp) | ||
|
||
self._world._add_bullet(timestamp, bullet) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[main] | ||
x = 1 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import argparse | ||
import pathlib | ||
import time | ||
from .world import World | ||
|
||
|
||
def get_parser(): | ||
parser = argparse.ArgumentParser( | ||
description='Asteroids game') | ||
# parser.add_argument( | ||
# '--host', help='Host to connect to', default="localhost") | ||
# parser.add_argument( | ||
# '--port', help='Port to connect to', default=24000, type=int) | ||
parser.add_argument( | ||
'--config', help='Path to config.ini', default="config.ini", | ||
type=pathlib.Path) | ||
return parser | ||
|
||
|
||
def main(): | ||
parser = get_parser() | ||
args = parser.parse_args() | ||
|
||
world = World() | ||
|
||
try: | ||
# We import here, so, that pyglet does not start before we parsed | ||
# arguments. This is useful if we ask for command line help to not | ||
# load GUI window. | ||
import pyglet | ||
from .window import GameWindow | ||
GameWindow(world, 600, 600) | ||
|
||
pyglet.app.run() | ||
except KeyboardInterrupt: | ||
print("Received SIG_INT. Quiting...") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import math | ||
import pyglet | ||
from pyglet.window import key | ||
|
||
|
||
from pyglet.gl import ( | ||
glBegin, | ||
glVertex2f, | ||
glEnd, | ||
glDisable, | ||
glViewport, | ||
glMatrixMode, | ||
glLoadIdentity, | ||
glOrtho, | ||
GL_TRIANGLE_FAN, | ||
GL_DEPTH_TEST, | ||
GL_PROJECTION, | ||
GL_MODELVIEW, | ||
GL_LINES, | ||
glScalef | ||
) | ||
|
||
COLLISION_RADIUS = 3 | ||
|
||
|
||
class GameWindow(pyglet.window.Window): | ||
|
||
SPACE_TTL = 0.08 | ||
LEFT_TTL = 1 / 30 | ||
RIGHT_TTL = 1 / 30 | ||
UP_TTL = 1 / 10 | ||
|
||
def __init__(self, world, *args, **kw): | ||
super().__init__(*args, **kw) | ||
self._world = world | ||
self.fps_display = pyglet.clock.ClockDisplay() | ||
self.keys = key.KeyStateHandler() | ||
self.push_handlers(self.keys) | ||
pyglet.clock.schedule_interval(self._tick, 1 / 60) | ||
# Key trottle times | ||
self._last_calls = {} | ||
|
||
def _tick(self, dt): | ||
self._world.advance_by(dt) | ||
self.check_keys() | ||
|
||
def check_keys(self): | ||
timestamp = self._world.time() | ||
if self.keys[key.SPACE]: | ||
last_called = self._last_calls.get(key.SPACE, 0) | ||
if timestamp - last_called >= self.SPACE_TTL: | ||
self._last_calls[key.SPACE] = timestamp | ||
self.trigger_space(timestamp) | ||
if self.keys[key.LEFT]: | ||
last_called = self._last_calls.get(key.LEFT, 0) | ||
if timestamp - last_called >= self.LEFT_TTL: | ||
self._last_calls[key.LEFT] = timestamp | ||
self.trigger_left(timestamp) | ||
if self.keys[key.RIGHT]: | ||
last_called = self._last_calls.get(key.RIGHT, 0) | ||
if timestamp - last_called >= self.RIGHT_TTL: | ||
self._last_calls[key.RIGHT] = timestamp | ||
self.trigger_right(timestamp) | ||
if self.keys[key.UP]: | ||
last_called = self._last_calls.get(key.UP, 0) | ||
if timestamp - last_called >= self.UP_TTL: | ||
self._last_calls[key.UP] = timestamp | ||
self.trigger_up(timestamp) | ||
|
||
def trigger_space(self, timestamp): | ||
self._world.player.fire_bullet(timestamp) | ||
|
||
def trigger_left(self, timestamp): | ||
self._world.player.rotate(timestamp, 1) | ||
|
||
def trigger_right(self, timestamp): | ||
self._world.player.rotate(timestamp, -1) | ||
|
||
def trigger_up(self, timestamp): | ||
self._world.player.bump(timestamp) | ||
|
||
def on_draw(self): | ||
self.clear() | ||
self.set_2d() | ||
self._draw_static() | ||
self._draw_objects() | ||
glMatrixMode(GL_MODELVIEW) | ||
glLoadIdentity() | ||
self.fps_display.draw() | ||
|
||
def _draw_static(self): | ||
# No static for now =) | ||
pass | ||
|
||
def _draw_objects(self): | ||
timestamp = self._world.time() | ||
for component in self._world._objects: | ||
component.draw(timestamp) | ||
|
||
def set_2d(self): | ||
screen_width, screen_height = self.get_size() | ||
width, height = self._world.width, self._world.height | ||
|
||
glDisable(GL_DEPTH_TEST) | ||
glViewport(0, 0, screen_width, screen_height) | ||
glMatrixMode(GL_PROJECTION) | ||
glLoadIdentity() | ||
glOrtho( | ||
-screen_width/2, screen_width/2, | ||
-screen_height/2, screen_height/2, | ||
-1, 1) | ||
glMatrixMode(GL_MODELVIEW) | ||
glLoadIdentity() | ||
# We add 10% to bounds so Asteroids port between bounds smoothly | ||
glScalef(screen_width / width * 1.1, | ||
screen_height / height * 1.1, 1.0) | ||
|
||
|
||
def circle(x, y, radius): | ||
""" | ||
We want a pixel perfect circle. To get one, | ||
we have to approximate it densely with triangles. | ||
Each triangle thinner than a pixel is enough | ||
to do it. Sin and cosine are calculated once | ||
and then used repeatedly to rotate the vector. | ||
I dropped 10 iterations intentionally for fun. | ||
""" | ||
iterations = int(2*radius*math.pi) | ||
s = math.sin(2*math.pi / iterations) | ||
c = math.cos(2*math.pi / iterations) | ||
|
||
dx, dy = radius, 0 | ||
|
||
glBegin(GL_TRIANGLE_FAN) | ||
glVertex2f(x, y) | ||
for i in range(iterations+1): | ||
glVertex2f(x+dx, y+dy) | ||
dx, dy = (dx*c - dy*s), (dy*c + dx*s) | ||
glEnd() |
Oops, something went wrong.