diff --git a/arcade/__init__.py b/arcade/__init__.py
new file mode 100644
index 0000000..c8b31f5
--- /dev/null
+++ b/arcade/__init__.py
@@ -0,0 +1,46 @@
+"""
+The Arcade Library
+
+A Python simple, easy to use module for creating 2D games.
+"""
+
+# Error out if we import Arcade with an incompatible version of Python.
+import sys
+
+if sys.version_info[0] < 3 or (sys.version_info[0] == 3 and sys.version_info[1] < 6):
+ sys.exit("The Arcade Library requires Python 3.6 or higher.")
+
+try:
+ import pyglet_ffmpeg2
+except Exception as e:
+ print("Unable to load the ffmpeg library. ", e)
+
+import pyglet
+
+pyglet.options['shadow_window'] = False
+
+from arcade import color
+from arcade import csscolor
+from arcade import key
+from arcade.application import *
+from arcade.arcade_types import *
+from arcade.utils import *
+from arcade.draw_commands import *
+from arcade.buffered_draw_commands import *
+from arcade.geometry import *
+from arcade.physics_engines import *
+from arcade.emitter import *
+from arcade.emitter_simple import *
+from arcade.particle import *
+from arcade.sound import *
+from arcade.sprite import *
+from arcade.sprite_list import *
+from arcade.version import *
+from arcade.window_commands import *
+from arcade.joysticks import *
+from arcade.read_tiled_map import *
+from arcade.isometric import *
+from arcade.text import draw_text
+from arcade.text import create_text
+from arcade.text import render_text
+from arcade import tilemap
diff --git a/arcade/application.py b/arcade/application.py
new file mode 100644
index 0000000..e2f031a
--- /dev/null
+++ b/arcade/application.py
@@ -0,0 +1,446 @@
+"""
+The main window class that all object-oriented applications should
+derive from.
+"""
+from numbers import Number
+from typing import Tuple
+
+import pyglet.gl as gl
+import pyglet
+
+from arcade.window_commands import (get_viewport, set_viewport, set_window)
+
+MOUSE_BUTTON_LEFT = 1
+MOUSE_BUTTON_MIDDLE = 2
+MOUSE_BUTTON_RIGHT = 4
+
+
+class NoOpenGLException(Exception):
+ """
+ Exception when we can't get an OpenGL 3.3+ context
+ """
+ pass
+
+
+class Window(pyglet.window.Window):
+ """
+ The Window class forms the basis of most advanced games that use Arcade.
+ It represents a window on the screen, and manages events.
+ """
+
+ def __init__(self, width: Number = 800, height: Number = 600,
+ title: str = 'Arcade Window', fullscreen: bool = False,
+ resizable: bool = False, update_rate=1/60,
+ antialiasing: bool = True):
+ """
+ Construct a new window
+
+ :param float width: Window width
+ :param float height: Window height
+ :param str title: Title (appears in title bar)
+ :param bool fullscreen: Should this be full screen?
+ :param bool resizable: Can the user resize the window?
+ :param float update_rate: How frequently to update the window.
+ :param bool antialiasing: Should OpenGL's anti-aliasing be enabled?
+ """
+ if antialiasing:
+ config = pyglet.gl.Config(major_version=3,
+ minor_version=3,
+ double_buffer=True,
+ sample_buffers=1,
+ samples=4)
+ else:
+ config = pyglet.gl.Config(major_version=3,
+ minor_version=3,
+ double_buffer=True)
+
+ try:
+ super().__init__(width=width, height=height, caption=title,
+ resizable=resizable, config=config)
+ except pyglet.window.NoSuchConfigException:
+ raise NoOpenGLException("Unable to create an OpenGL 3.3+ context. "
+ "Check to make sure your system supports OpenGL 3.3 or higher.")
+
+ if antialiasing:
+ try:
+ gl.glEnable(gl.GL_MULTISAMPLE_ARB)
+ except pyglet.gl.GLException:
+ print("Warning: Anti-aliasing not supported on this computer.")
+
+ if update_rate:
+ from pyglet import compat_platform
+ if compat_platform == 'darwin' or compat_platform == 'linux':
+ # Set vsync to false, or we'll be limited to a 1/30 sec update rate possibly
+ self.context.set_vsync(False)
+ self.set_update_rate(update_rate)
+
+ super().set_fullscreen(fullscreen)
+ self.invalid = False
+ set_window(self)
+ set_viewport(0, self.width, 0, self.height)
+ self.current_view = None
+
+ def update(self, delta_time: float):
+ """
+ Move everything. For better consistency in naming, use ``on_update`` instead.
+
+ :param float delta_time: Time interval since the last time the function was called in seconds.
+
+ """
+ try:
+ self.current_view.update(delta_time)
+ except AttributeError:
+ pass
+
+ def on_update(self, delta_time: float):
+ """
+ Move everything. Perform collision checks. Do all the game logic here.
+
+ :param float delta_time: Time interval since the last time the function was called.
+
+ """
+ try:
+ self.current_view.on_update(delta_time)
+ except AttributeError:
+ pass
+
+ def set_update_rate(self, rate: float):
+ """
+ Set how often the screen should be updated.
+ For example, self.set_update_rate(1 / 60) will set the update rate to 60 fps
+
+ :param float rate: Update frequency in seconds
+ """
+ pyglet.clock.unschedule(self.update)
+ pyglet.clock.schedule_interval(self.update, rate)
+ pyglet.clock.unschedule(self.on_update)
+ pyglet.clock.schedule_interval(self.on_update, rate)
+
+ def on_mouse_motion(self, x: float, y: float, dx: float, dy: float):
+ """
+ Override this function to add mouse functionality.
+
+ :param float x: x position of mouse
+ :param float y: y position of mouse
+ :param float dx: Change in x since the last time this method was called
+ :param float dy: Change in y since the last time this method was called
+ """
+ pass
+
+ def on_mouse_press(self, x: float, y: float, button: int, modifiers: int):
+ """
+ Override this function to add mouse button functionality.
+
+ :param float x: x position of the mouse
+ :param float y: y position of the mouse
+ :param int button: What button was hit. One of:
+ arcade.MOUSE_BUTTON_LEFT, arcade.MOUSE_BUTTON_RIGHT,
+ arcade.MOUSE_BUTTON_MIDDLE
+ :param int modifiers: Shift/click, ctrl/click, etc.
+ """
+ pass
+
+ def on_mouse_drag(self, x: float, y: float, dx: float, dy: float, buttons: int, modifiers: int):
+ """
+ Override this function to add mouse button functionality.
+
+ :param float x: x position of mouse
+ :param float y: y position of mouse
+ :param float dx: Change in x since the last time this method was called
+ :param float dy: Change in y since the last time this method was called
+ :param int buttons: Which button is pressed
+ :param int modifiers: Ctrl, shift, etc.
+ """
+ self.on_mouse_motion(x, y, dx, dy)
+
+ def on_mouse_release(self, x: float, y: float, button: int,
+ modifiers: int):
+ """
+ Override this function to add mouse button functionality.
+
+ :param float x:
+ :param float y:
+ :param int button:
+ :param int modifiers:
+ """
+
+ pass
+
+ def on_mouse_scroll(self, x: int, y: int, scroll_x: int, scroll_y: int):
+ """
+ User moves the scroll wheel.
+
+ :param int x:
+ :param int y:
+ :param int scroll_x:
+ :param int scroll_y:
+ """
+ pass
+
+ def set_mouse_visible(self, visible: bool = True):
+ """
+ If true, user can see the mouse cursor while it is over the window. Set false,
+ the mouse is not visible. Default is true.
+
+ :param bool visible:
+ """
+ super().set_mouse_visible(visible)
+
+ def on_key_press(self, symbol: int, modifiers: int):
+ """
+ Override this function to add key press functionality.
+
+ :param int symbol: Key that was hit
+ :param int modifiers: If it was shift/ctrl/alt
+ """
+ pass
+
+ def on_key_release(self, symbol: int, modifiers: int):
+ """
+ Override this function to add key release functionality.
+
+ :param int symbol: Key that was hit
+ :param int modifiers: If it was shift/ctrl/alt
+ """
+ pass
+
+ def on_draw(self):
+ """
+ Override this function to add your custom drawing code.
+ """
+ pass
+
+ def on_resize(self, width: float, height: float):
+ """
+ Override this function to add custom code to be called any time the window
+ is resized.
+
+ :param float width: New width
+ :param float height: New height
+ """
+ original_viewport = self.get_viewport()
+
+ # unscaled_viewport = self.get_viewport_size()
+ # scaling = unscaled_viewport[0] / width
+
+ self.set_viewport(original_viewport[0],
+ original_viewport[0] + width,
+ original_viewport[2],
+ original_viewport[2] + height)
+
+ def set_min_size(self, width: float, height: float):
+ """ Wrap the Pyglet window call to set minimum size
+
+ :param float width: width in pixels.
+ :param float height: height in pixels.
+ """
+
+ if self._resizable:
+ super().set_minimum_size(width, height)
+ else:
+ raise ValueError('Cannot set min size on non-resizable window')
+
+ def set_max_size(self, width: float, height: float):
+ """ Wrap the Pyglet window call to set maximum size
+
+ :param float width: width in pixels.
+ :param float height: height in pixels.
+ :Raises ValueError:
+
+ """
+
+ if self._resizable:
+ super().set_maximum_size(width, height)
+ else:
+ raise ValueError('Cannot set max size on non-resizable window')
+
+ def set_size(self, width: float, height: float):
+ """
+ Ignore the resizable flag and set the size
+
+ :param float width:
+ :param float height:
+ """
+
+ super().set_size(width, height)
+
+ def get_size(self) -> Tuple[int, int]:
+ """
+ Get the size of the window.
+
+ :returns: (width, height)
+ """
+
+ return super().get_size()
+
+ def get_location(self) -> Tuple[int, int]:
+ """
+ Return the X/Y coordinates of the window
+
+ :returns: x, y of window location
+ """
+
+ return super().get_location()
+
+ def set_visible(self, visible=True):
+ """
+ Set if the window is visible or not. Normally, a program's window is visible.
+
+ :param bool visible:
+ """
+ super().set_visible(visible)
+
+ def set_viewport(self, left: Number, right: Number, bottom: Number, top: Number):
+ """
+ Set the viewport. (What coordinates we can see.
+ Used to scale and/or scroll the screen.)
+
+ :param Number left:
+ :param Number right:
+ :param Number bottom:
+ :param Number top:
+ """
+ set_viewport(left, right, bottom, top)
+
+ def get_viewport(self) -> (float, float, float, float):
+ """ Get the viewport. (What coordinates we can see.) """
+ return get_viewport()
+
+ def test(self, frames: int = 10):
+ """
+ Used by unit test cases. Runs the event loop a few times and stops.
+
+ :param int frames:
+ """
+ for i in range(frames):
+ self.switch_to()
+ self.dispatch_events()
+ self.dispatch_event('on_draw')
+ self.flip()
+ self.update(1/60)
+
+ def show_view(self, new_view: 'View'):
+ if not isinstance(new_view, View):
+ raise ValueError("Must pass an arcade.View object to "
+ "Window.show_view()")
+
+ # Store the Window that is showing the "new_view" View.
+ if new_view.window is None:
+ new_view.window = self
+ elif new_view.window != self:
+ raise RuntimeError("You are attempting to pass the same view "
+ "object between multiple windows. A single "
+ "view object can only be used in one window.")
+
+ # remove previously shown view's handlers
+ if self.current_view is not None:
+ self.remove_handlers(self.current_view)
+
+ # push new view's handlers
+ self.current_view = new_view
+ self.push_handlers(self.current_view)
+ self.current_view.on_show()
+
+ # Note: After the View has been pushed onto pyglet's stack of event handlers (via push_handlers()), pyglet
+ # will still call the Window's event handlers. (See pyglet's EventDispatcher.dispatch_event() implementation
+ # for details)
+
+ def _create(self):
+ super()._create()
+
+ def _recreate(self, changes):
+ super()._recreate(changes)
+
+ def flip(self):
+ super().flip()
+
+ def switch_to(self):
+ super().switch_to()
+
+ def set_caption(self, caption):
+ super().set_caption(caption)
+
+ def set_minimum_size(self, width, height):
+ super().set_minimum_size(width, height)
+
+ def set_maximum_size(self, width, height):
+ super().set_maxiumum_size(width, height)
+
+ def set_location(self, x, y):
+ super().set_location(x, y)
+
+ def activate(self):
+ super().activate()
+
+ def minimize(self):
+ super().minimize()
+
+ def maximize(self):
+ super().maximize()
+
+ def set_vsync(self, vsync):
+ super().set_vsync(vsync)
+
+ def set_mouse_platform_visible(self, platform_visible=None):
+ super().set_mouse_platform_visible(platform_visible)
+
+ def set_exclusive_mouse(self, exclusive=True):
+ super().set_exclusive_mouse(exclusive)
+
+ def set_exclusive_keyboard(self, exclusive=True):
+ super().set_exclusive_keyboard(exclusive)
+
+ def get_system_mouse_cursor(self, name):
+ super().get_system_mouse_cursor(name)
+
+ def dispatch_events(self):
+ super().dispatch_events()
+
+
+def open_window(width: Number, height: Number, window_title: str, resizable: bool = False,
+ antialiasing: bool = True) -> Window:
+ """
+ This function opens a window. For ease-of-use we assume there will only be one window, and the
+ programmer does not need to keep a handle to the window. This isn't the best architecture, because
+ the window handle is stored in a global, but it makes things easier for programmers if they don't
+ have to track a window pointer.
+
+ :param Number width: Width of the window.
+ :param Number height: Height of the window.
+ :param str window_title: Title of the window.
+ :param bool resizable: Whether the window can be user-resizable.
+ :param bool antialiasing: Smooth the graphics?
+
+ :returns: Handle to window
+ :rtype arcade.Window:
+ """
+
+ global _window
+ _window = Window(width, height, window_title, resizable, update_rate=None,
+ antialiasing=antialiasing)
+ return _window
+
+
+class View:
+ """
+ TODO:Thoughts:
+ - is there a need for a close()/on_close() method?
+ """
+ def __init__(self):
+ self.window = None
+
+ def update(self, delta_time: float):
+ """To be overridden"""
+ pass
+
+ def on_update(self, delta_time: float):
+ """To be overridden"""
+ pass
+
+ def on_draw(self):
+ """Called when this view should draw"""
+ pass
+
+ def on_show(self):
+ """Called when this view is shown"""
+ pass
diff --git a/arcade/arcade_types.py b/arcade/arcade_types.py
new file mode 100644
index 0000000..d15f6e9
--- /dev/null
+++ b/arcade/arcade_types.py
@@ -0,0 +1,13 @@
+"""
+Module specifying data custom types used for type hinting.
+"""
+from typing import Tuple
+from typing import List
+from typing import Union
+
+RGB = Union[Tuple[int, int, int], List[int]]
+RGBA = Union[Tuple[int, int, int, int], List[int]]
+Color = Union[RGB, RGBA]
+Point = Union[Tuple[float, float], List[float]]
+Vector = Point
+PointList = Union[Tuple[Point, ...], List[Point]]
diff --git a/arcade/buffered_draw_commands.py b/arcade/buffered_draw_commands.py
new file mode 100644
index 0000000..959c5a5
--- /dev/null
+++ b/arcade/buffered_draw_commands.py
@@ -0,0 +1,793 @@
+"""
+Drawing commands that use vertex buffer objects (VBOs).
+
+This module contains commands for basic graphics drawing commands,
+but uses Vertex Buffer Objects. This keeps the vertices loaded on
+the graphics card for much faster render times.
+"""
+
+import math
+import itertools
+from collections import defaultdict
+import pyglet.gl as gl
+import numpy as np
+
+from typing import Iterable
+from typing import List
+from typing import TypeVar
+from typing import Generic
+
+from arcade.arcade_types import Color
+from arcade.draw_commands import rotate_point
+from arcade.arcade_types import PointList
+from arcade.draw_commands import get_four_byte_color
+from arcade.draw_commands import get_projection
+from arcade.draw_commands import _get_points_for_thick_line
+from arcade import shader
+
+
+class VertexBuffer:
+ """
+ This class represents a `vertex buffer object`_ for internal library use. Clients
+ of the library probably don't need to use this.
+
+ Attributes:
+ :vbo_id: ID of the vertex buffer as assigned by OpenGL
+ :size:
+ :width:
+ :height:
+ :color:
+
+
+ .. _vertex buffer object:
+ https://en.wikipedia.org/wiki/Vertex_Buffer_Object
+
+ """
+ def __init__(self, vbo_vertex_id: gl.GLuint, size: float, draw_mode: int, vbo_color_id: gl.GLuint = None):
+ self.vbo_vertex_id = vbo_vertex_id
+ self.vbo_color_id = vbo_color_id
+ self.size = size
+ self.draw_mode = draw_mode
+ self.color = None
+ self.line_width = 0
+
+
+class Shape:
+ def __init__(self):
+ self.vao = None
+ self.vbo = None
+ self.program = None
+ self.mode = None
+ self.line_width = 1
+
+ def draw(self):
+ # program['Projection'].write(get_projection().tobytes())
+
+ with self.vao:
+ assert(self.line_width == 1)
+ gl.glLineWidth(self.line_width)
+
+ gl.glEnable(gl.GL_BLEND)
+ gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
+ gl.glEnable(gl.GL_LINE_SMOOTH)
+ gl.glHint(gl.GL_LINE_SMOOTH_HINT, gl.GL_NICEST)
+ gl.glHint(gl.GL_POLYGON_SMOOTH_HINT, gl.GL_NICEST)
+ gl.glEnable(gl.GL_PRIMITIVE_RESTART)
+ gl.glPrimitiveRestartIndex(2 ** 32 - 1)
+
+ self.vao.render(mode=self.mode)
+
+
+def create_line(start_x: float, start_y: float, end_x: float, end_y: float,
+ color: Color, line_width: float = 1) -> Shape:
+ """
+ Create a line to be rendered later. This works faster than draw_line because
+ the vertexes are only loaded to the graphics card once, rather than each frame.
+
+ :param float start_x:
+ :param float start_y:
+ :param float end_x:
+ :param float end_y:
+ :param Color color:
+ :param float line_width:
+
+ :Returns Shape:
+
+ """
+
+ points = _get_points_for_thick_line(start_x, start_y, end_x, end_y, line_width)
+ color_list = [color, color, color, color]
+ triangle_point_list = points[1], points[0], points[2], points[3]
+ shape = create_triangles_filled_with_colors(triangle_point_list, color_list)
+ return shape
+
+
+def create_line_generic_with_colors(point_list: PointList,
+ color_list: Iterable[Color],
+ shape_mode: int,
+ line_width: float = 1) -> Shape:
+ """
+ This function is used by ``create_line_strip`` and ``create_line_loop``,
+ just changing the OpenGL type for the line drawing.
+
+ :param PointList point_list:
+ :param Iterable[Color] color_list:
+ :param float shape_mode:
+ :param float line_width:
+
+ :Returns Shape:
+ """
+ program = shader.program(
+ vertex_shader='''
+ #version 330
+ uniform mat4 Projection;
+ in vec2 in_vert;
+ in vec4 in_color;
+ out vec4 v_color;
+ void main() {
+ gl_Position = Projection * vec4(in_vert, 0.0, 1.0);
+ v_color = in_color;
+ }
+ ''',
+ fragment_shader='''
+ #version 330
+ in vec4 v_color;
+ out vec4 f_color;
+ void main() {
+ f_color = v_color;
+ }
+ ''',
+ )
+
+ buffer_type = np.dtype([('vertex', '2f4'), ('color', '4B')])
+ data = np.zeros(len(point_list), dtype=buffer_type)
+ data['vertex'] = point_list
+ data['color'] = [get_four_byte_color(color) for color in color_list]
+
+ vbo = shader.buffer(data.tobytes())
+ vao_content = [
+ shader.BufferDescription(
+ vbo,
+ '2f 4B',
+ ('in_vert', 'in_color'),
+ normalized=['in_color']
+ )
+ ]
+
+ vao = shader.vertex_array(program, vao_content)
+ with vao:
+ program['Projection'] = get_projection().flatten()
+
+ shape = Shape()
+ shape.vao = vao
+ shape.vbo = vbo
+ shape.program = program
+ shape.mode = shape_mode
+ shape.line_width = line_width
+
+ return shape
+
+
+def create_line_generic(point_list: PointList,
+ color: Color,
+ shape_mode: int, line_width: float = 1) -> Shape:
+ """
+ This function is used by ``create_line_strip`` and ``create_line_loop``,
+ just changing the OpenGL type for the line drawing.
+ """
+ colors = [get_four_byte_color(color)] * len(point_list)
+ shape = create_line_generic_with_colors(
+ point_list,
+ colors,
+ shape_mode,
+ line_width)
+
+ return shape
+
+
+def create_line_strip(point_list: PointList,
+ color: Color, line_width: float = 1):
+ """
+ Create a multi-point line to be rendered later. This works faster than draw_line because
+ the vertexes are only loaded to the graphics card once, rather than each frame.
+
+ :param PointList point_list:
+ :param Color color:
+ :param PointList line_width:
+
+ :Returns Shape:
+
+ """
+ if line_width == 1:
+ return create_line_generic(point_list, color, gl.GL_LINE_STRIP, line_width)
+ else:
+ triangle_point_list = []
+ new_color_list = []
+ for i in range(1, len(point_list)):
+ start_x = point_list[i - 1][0]
+ start_y = point_list[i - 1][1]
+ end_x = point_list[i][0]
+ end_y = point_list[i][1]
+ color1 = color
+ color2 = color
+ points = _get_points_for_thick_line(start_x, start_y, end_x, end_y, line_width)
+ new_color_list += color1, color2, color1, color2
+ triangle_point_list += points[1], points[0], points[2], points[3]
+
+ shape = create_triangles_filled_with_colors(triangle_point_list, new_color_list)
+ return shape
+
+
+def create_line_loop(point_list: PointList,
+ color: Color, line_width: float = 1):
+ """
+ Create a multi-point line loop to be rendered later. This works faster than draw_line because
+ the vertexes are only loaded to the graphics card once, rather than each frame.
+
+ :param PointList point_list:
+ :param Color color:
+ :param float line_width:
+
+ :Returns Shape:
+
+ """
+ point_list = list(point_list) + [point_list[0]]
+ return create_line_generic(point_list, color, gl.GL_LINE_STRIP, line_width)
+
+
+def create_lines(point_list: PointList,
+ color: Color, line_width: float = 1):
+ """
+ Create a multi-point line loop to be rendered later. This works faster than draw_line because
+ the vertexes are only loaded to the graphics card once, rather than each frame.
+
+ :param PointList point_list:
+ :param Color color:
+ :param float line_width:
+
+ :Returns Shape:
+
+ """
+ return create_line_generic(point_list, color, gl.GL_LINES, line_width)
+
+
+def create_lines_with_colors(point_list: PointList,
+ color_list: List[Color],
+ line_width: float = 1):
+
+ if line_width == 1:
+ return create_line_generic_with_colors(point_list, color_list, gl.GL_LINES, line_width)
+ else:
+
+ triangle_point_list = []
+ new_color_list = []
+ for i in range(1, len(point_list), 2):
+ start_x = point_list[i-1][0]
+ start_y = point_list[i-1][1]
+ end_x = point_list[i][0]
+ end_y = point_list[i][1]
+ color1 = color_list[i-1]
+ color2 = color_list[i]
+ points = _get_points_for_thick_line(start_x, start_y, end_x, end_y, line_width)
+ new_color_list += color1, color1, color2, color2
+ triangle_point_list += points[1], points[0], points[2], points[3]
+
+ shape = create_triangles_filled_with_colors(triangle_point_list, new_color_list)
+ return shape
+
+
+def create_polygon(point_list: PointList,
+ color: Color):
+ """
+ Draw a convex polygon. This will NOT draw a concave polygon.
+ Because of this, you might not want to use this function.
+
+ :param PointList point_list:
+ :param color:
+
+ :Returns Shape:
+
+ """
+ # We assume points were given in order, either clockwise or counter clockwise.
+ # Polygon is assumed to be monotone.
+ # To fill the polygon, we start by one vertex, and we chain triangle strips
+ # alternating with vertices to the left and vertices to the right of the
+ # initial vertex.
+ half = len(point_list) // 2
+ interleaved = itertools.chain.from_iterable(
+ itertools.zip_longest(point_list[:half], reversed(point_list[half:]))
+ )
+ point_list = [p for p in interleaved if p is not None]
+ return create_line_generic(point_list, color, gl.GL_TRIANGLE_STRIP, 1)
+
+
+def create_rectangle_filled(center_x: float, center_y: float, width: float,
+ height: float, color: Color,
+ tilt_angle: float = 0) -> Shape:
+ """
+ Create a filled rectangle.
+
+ :param float center_x:
+ :param float center_y:
+ :param float width:
+ :param float height:
+ :param Color color:
+ :param float tilt_angle:
+
+ :Returns Shape:
+
+ """
+ return create_rectangle(center_x, center_y, width, height,
+ color, tilt_angle=tilt_angle)
+
+
+def create_rectangle_outline(center_x: float, center_y: float, width: float,
+ height: float, color: Color,
+ border_width: float = 1, tilt_angle: float = 0) -> Shape:
+ """
+ Create a rectangle outline.
+
+ Args:
+ center_x:
+ center_y:
+ width:
+ height:
+ color:
+ border_width:
+ tilt_angle:
+
+ Returns:
+
+ """
+ return create_rectangle(center_x, center_y, width, height,
+ color, border_width, tilt_angle, filled=False)
+
+
+def get_rectangle_points(center_x: float, center_y: float, width: float,
+ height: float, tilt_angle: float = 0) -> PointList:
+ """
+ Utility function that will return all four coordinate points of a
+ rectangle given the x, y center, width, height, and rotation.
+
+ Args:
+ center_x:
+ center_y:
+ width:
+ height:
+ tilt_angle:
+
+ Returns:
+
+ """
+ x1 = -width / 2 + center_x
+ y1 = -height / 2 + center_y
+
+ x2 = -width / 2 + center_x
+ y2 = height / 2 + center_y
+
+ x3 = width / 2 + center_x
+ y3 = height / 2 + center_y
+
+ x4 = width / 2 + center_x
+ y4 = -height / 2 + center_y
+
+ if tilt_angle:
+ x1, y1 = rotate_point(x1, y1, center_x, center_y, tilt_angle)
+ x2, y2 = rotate_point(x2, y2, center_x, center_y, tilt_angle)
+ x3, y3 = rotate_point(x3, y3, center_x, center_y, tilt_angle)
+ x4, y4 = rotate_point(x4, y4, center_x, center_y, tilt_angle)
+
+ data = [(x1, y1),
+ (x2, y2),
+ (x3, y3),
+ (x4, y4)]
+
+ return data
+
+
+def create_rectangle(center_x: float, center_y: float, width: float,
+ height: float, color: Color,
+ border_width: float = 1, tilt_angle: float = 0,
+ filled=True) -> Shape:
+ """
+ This function creates a rectangle using a vertex buffer object.
+ Creating the rectangle, and then later drawing it with ``render_rectangle``
+ is faster than calling ``draw_rectangle``.
+
+ Args:
+ center_x:
+ center_y:
+ width:
+ height:
+ color:
+ border_width:
+ tilt_angle:
+ filled:
+
+ Returns:
+
+ """
+ data = get_rectangle_points(center_x, center_y, width, height, tilt_angle)
+
+ if filled:
+ shape_mode = gl.GL_TRIANGLE_STRIP
+ data[-2:] = reversed(data[-2:])
+ else:
+
+ i_lb = center_x - width / 2 + border_width / 2, center_y - height / 2 + border_width / 2
+ i_rb = center_x + width / 2 - border_width / 2, center_y - height / 2 + border_width / 2
+ i_rt = center_x + width / 2 - border_width / 2, center_y + height / 2 - border_width / 2
+ i_lt = center_x - width / 2 + border_width / 2, center_y + height / 2 - border_width / 2
+
+ o_lb = center_x - width / 2 - border_width / 2, center_y - height / 2 - border_width / 2
+ o_rb = center_x + width / 2 + border_width / 2, center_y - height / 2 - border_width / 2
+ o_rt = center_x + width / 2 + border_width / 2, center_y + height / 2 + border_width / 2
+ o_lt = center_x - width / 2 - border_width / 2, center_y + height / 2 + border_width / 2
+
+ data = o_lt, i_lt, o_rt, i_rt, o_rb, i_rb, o_lb, i_lb, o_lt, i_lt
+
+ if tilt_angle != 0:
+ point_list_2 = []
+ for point in data:
+ new_point = rotate_point(point[0], point[1], center_x, center_y, tilt_angle)
+ point_list_2.append(new_point)
+ data = point_list_2
+
+ border_width = 1
+ shape_mode = gl.GL_TRIANGLE_STRIP
+
+ # _generic_draw_line_strip(point_list, color, gl.GL_TRIANGLE_STRIP)
+
+ # shape_mode = gl.GL_LINE_STRIP
+ # data.append(data[0])
+
+ shape = create_line_generic(data, color, shape_mode, border_width)
+ return shape
+
+
+def create_rectangle_filled_with_colors(point_list, color_list) -> Shape:
+ """
+ This function creates one rectangle/quad using a vertex buffer object.
+ Creating the rectangles, and then later drawing it with ``render``
+ is faster than calling ``draw_rectangle``.
+ """
+
+ shape_mode = gl.GL_TRIANGLE_STRIP
+ new_point_list = [point_list[0], point_list[1], point_list[3], point_list[2]]
+ new_color_list = [color_list[0], color_list[1], color_list[3], color_list[2]]
+ return create_line_generic_with_colors(new_point_list, new_color_list, shape_mode)
+
+
+def create_rectangles_filled_with_colors(point_list, color_list) -> Shape:
+ """
+ This function creates multiple rectangle/quads using a vertex buffer object.
+ Creating the rectangles, and then later drawing it with ``render``
+ is faster than calling ``draw_rectangle``.
+ """
+
+ shape_mode = gl.GL_TRIANGLES
+ new_point_list = []
+ new_color_list = []
+ for i in range(0, len(point_list), 4):
+ new_point_list += [point_list[0 + i], point_list[1 + i], point_list[3 + i]]
+ new_point_list += [point_list[1 + i], point_list[3 + i], point_list[2 + i]]
+
+ new_color_list += [color_list[0 + i], color_list[1 + i], color_list[3 + i]]
+ new_color_list += [color_list[1 + i], color_list[3 + i], color_list[2 + i]]
+
+ return create_line_generic_with_colors(new_point_list, new_color_list, shape_mode)
+
+
+def create_triangles_filled_with_colors(point_list, color_list) -> Shape:
+ """
+ This function creates multiple rectangle/quads using a vertex buffer object.
+ Creating the rectangles, and then later drawing it with ``render``
+ is faster than calling ``draw_rectangle``.
+ """
+
+ shape_mode = gl.GL_TRIANGLE_STRIP
+ return create_line_generic_with_colors(point_list, color_list, shape_mode)
+
+
+def create_ellipse_filled(center_x: float, center_y: float,
+ width: float, height: float, color: Color,
+ tilt_angle: float = 0, num_segments: int = 128) -> Shape:
+ """
+ Create a filled ellipse. Or circle if you use the same width and height.
+ """
+
+ border_width = 1
+ return create_ellipse(center_x, center_y, width, height, color,
+ border_width, tilt_angle, num_segments, filled=True)
+
+
+def create_ellipse_outline(center_x: float, center_y: float,
+ width: float, height: float, color: Color,
+ border_width: float = 1,
+ tilt_angle: float = 0, num_segments: int = 128) -> Shape:
+ """
+ Create an outline of an ellipse.
+ """
+
+ return create_ellipse(center_x, center_y, width, height, color,
+ border_width, tilt_angle, num_segments, filled=False)
+
+
+def create_ellipse(center_x: float, center_y: float,
+ width: float, height: float, color: Color,
+ border_width: float = 1,
+ tilt_angle: float = 0, num_segments: int = 32,
+ filled=True) -> Shape:
+
+ """
+ This creates an ellipse vertex buffer object (VBO). It can later be
+ drawn with ``render_ellipse_filled``. This method of drawing an ellipse
+ is much faster than calling ``draw_ellipse_filled`` each frame.
+
+ Note: This can't be unit tested on Appveyor because its support for OpenGL is
+ poor.
+ """
+ # Create an array with the vertex point_list
+ point_list = []
+
+ for segment in range(num_segments):
+ theta = 2.0 * 3.1415926 * segment / num_segments
+
+ x = width * math.cos(theta) + center_x
+ y = height * math.sin(theta) + center_y
+
+ if tilt_angle:
+ x, y = rotate_point(x, y, center_x, center_y, tilt_angle)
+
+ point_list.append((x, y))
+
+ if filled:
+ half = len(point_list) // 2
+ interleaved = itertools.chain.from_iterable(
+ itertools.zip_longest(point_list[:half], reversed(point_list[half:]))
+ )
+ point_list = [p for p in interleaved if p is not None]
+ shape_mode = gl.GL_TRIANGLE_STRIP
+ else:
+ point_list.append(point_list[0])
+ shape_mode = gl.GL_LINE_STRIP
+
+ return create_line_generic(point_list, color, shape_mode, border_width)
+
+
+def create_ellipse_filled_with_colors(center_x: float, center_y: float,
+ width: float, height: float,
+ outside_color: Color, inside_color: Color,
+ tilt_angle: float = 0, num_segments: int = 32) -> Shape:
+ """
+ Draw an ellipse, and specify inside/outside color. Used for doing gradients.
+
+ :param float center_x:
+ :param float center_y:
+ :param float width:
+ :param float height:
+ :param Color outside_color:
+ :param float inside_color:
+ :param float tilt_angle:
+ :param int num_segments:
+
+ :Returns Shape:
+
+ """
+
+ # Create an array with the vertex data
+ # Create an array with the vertex point_list
+ point_list = [(center_x, center_y)]
+
+ for segment in range(num_segments):
+ theta = 2.0 * 3.1415926 * segment / num_segments
+
+ x = width * math.cos(theta) + center_x
+ y = height * math.sin(theta) + center_y
+
+ if tilt_angle:
+ x, y = rotate_point(x, y, center_x, center_y, tilt_angle)
+
+ point_list.append((x, y))
+ point_list.append(point_list[1])
+
+ color_list = [inside_color] + [outside_color] * (num_segments + 1)
+ return create_line_generic_with_colors(point_list, color_list, gl.GL_TRIANGLE_FAN)
+
+
+T = TypeVar('T', bound=Shape)
+
+
+class ShapeElementList(Generic[T]):
+ """
+ A program can put multiple drawing primitives in a ShapeElementList, and then
+ move and draw them as one. Do this when you want to create a more complex object
+ out of simpler primitives. This also speeds rendering as all objects are drawn
+ in one operation.
+ """
+ def __init__(self):
+ """
+ Initialize the sprite list
+ """
+ # List of sprites in the sprite list
+ self.shape_list = []
+ self.change_x = 0
+ self.change_y = 0
+ self._center_x = 0
+ self._center_y = 0
+ self._angle = 0
+ self.program = shader.program(
+ vertex_shader='''
+ #version 330
+ uniform mat4 Projection;
+ uniform vec2 Position;
+ uniform float Angle;
+
+ in vec2 in_vert;
+ in vec4 in_color;
+
+ out vec4 v_color;
+ void main() {
+ float angle = radians(Angle);
+ mat2 rotate = mat2(
+ cos(angle), sin(angle),
+ -sin(angle), cos(angle)
+ );
+ gl_Position = Projection * vec4(Position + (rotate * in_vert), 0.0, 1.0);
+ v_color = in_color;
+ }
+ ''',
+ fragment_shader='''
+ #version 330
+ in vec4 v_color;
+ out vec4 f_color;
+ void main() {
+ f_color = v_color;
+ }
+ ''',
+ )
+ # Could do much better using just one vbo and glDrawElementsBaseVertex
+ self.batches = defaultdict(_Batch)
+ self.dirties = set()
+
+ def append(self, item: T):
+ """
+ Add a new shape to the list.
+ """
+ self.shape_list.append(item)
+ group = (item.mode, item.line_width)
+ self.batches[group].items.append(item)
+ self.dirties.add(group)
+
+ def remove(self, item: T):
+ """
+ Remove a specific shape from the list.
+ """
+ self.shape_list.remove(item)
+ group = (item.mode, item.line_width)
+ self.batches[group].items.remove(item)
+ self.dirties.add(group)
+
+ def _refresh_shape(self, group):
+ # Create a buffer large enough to hold all the shapes buffers
+ batch = self.batches[group]
+ total_vbo_bytes = sum(s.vbo.size for s in batch.items)
+ vbo = shader.Buffer.create_with_size(total_vbo_bytes)
+ offset = 0
+ gl.glBindBuffer(gl.GL_COPY_WRITE_BUFFER, vbo.buffer_id)
+ # Copy all the shapes buffer in our own vbo
+ for shape in batch.items:
+ gl.glBindBuffer(gl.GL_COPY_READ_BUFFER, shape.vbo.buffer_id)
+ gl.glCopyBufferSubData(
+ gl.GL_COPY_READ_BUFFER,
+ gl.GL_COPY_WRITE_BUFFER,
+ gl.GLintptr(0),
+ gl.GLintptr(offset),
+ shape.vbo.size)
+ offset += shape.vbo.size
+
+ # Create an index buffer object. It should count starting from 0. We need to
+ # use a reset_idx to indicate that a new shape will start.
+ reset_idx = 2 ** 32 - 1
+ indices = []
+ counter = itertools.count()
+ for shape in batch.items:
+ indices.extend(itertools.islice(counter, shape.vao.num_vertices))
+ indices.append(reset_idx)
+ del indices[-1]
+ indices = np.array(indices)
+ ibo = shader.Buffer(indices.astype('i4').tobytes())
+
+ vao_content = [
+ shader.BufferDescription(
+ vbo,
+ '2f 4B',
+ ('in_vert', 'in_color'),
+ normalized=['in_color']
+ )
+ ]
+ vao = shader.vertex_array(self.program, vao_content, ibo)
+ with self.program:
+ self.program['Projection'] = get_projection().flatten()
+ self.program['Position'] = [self.center_x, self.center_y]
+ self.program['Angle'] = self.angle
+
+ batch.shape.vao = vao
+ batch.shape.vbo = vbo
+ batch.shape.ibo = ibo
+ batch.shape.program = self.program
+ mode, line_width = group
+ batch.shape.mode = mode
+ batch.shape.line_width = line_width
+
+ def move(self, change_x: float, change_y: float):
+ """
+ Move all the shapes ion the list
+ :param change_x: Amount to move on the x axis
+ :param change_y: Amount to move on the y axis
+ """
+ self.center_x += change_x
+ self.center_y += change_y
+
+ def __len__(self) -> int:
+ """ Return the length of the sprite list. """
+ return len(self.shape_list)
+
+ def __iter__(self) -> Iterable[T]:
+ """ Return an iterable object of sprites. """
+ return iter(self.shape_list)
+
+ def __getitem__(self, i):
+ return self.shape_list[i]
+
+ def draw(self):
+ """
+ Draw everything in the list.
+ """
+ for group in self.dirties:
+ self._refresh_shape(group)
+ self.dirties.clear()
+ for batch in self.batches.values():
+ batch.shape.draw()
+
+ def _get_center_x(self) -> float:
+ """Get the center x coordinate of the ShapeElementList."""
+ return self._center_x
+
+ def _set_center_x(self, value: float):
+ """Set the center x coordinate of the ShapeElementList."""
+ self._center_x = value
+ with self.program:
+ self.program['Position'] = [self._center_x, self._center_y]
+
+ center_x = property(_get_center_x, _set_center_x)
+
+ def _get_center_y(self) -> float:
+ """Get the center y coordinate of the ShapeElementList."""
+ return self._center_y
+
+ def _set_center_y(self, value: float):
+ """Set the center y coordinate of the ShapeElementList."""
+ self._center_y = value
+ with self.program:
+ self.program['Position'] = [self._center_x, self._center_y]
+
+ center_y = property(_get_center_y, _set_center_y)
+
+ def _get_angle(self) -> float:
+ """Get the angle of the ShapeElementList in degrees."""
+ return self._angle
+
+ def _set_angle(self, value: float):
+ """Set the angle of the ShapeElementList in degrees."""
+ self._angle = value
+ with self.program:
+ self.program['Angle'] = self._angle
+
+ angle = property(_get_angle, _set_angle)
+
+
+class _Batch(Generic[T]):
+ def __init__(self):
+ self.shape = Shape()
+ self.items = []
diff --git a/arcade/color/__init__.py b/arcade/color/__init__.py
new file mode 100644
index 0000000..0538903
--- /dev/null
+++ b/arcade/color/__init__.py
@@ -0,0 +1,1006 @@
+"""
+This module pre-defines several colors.
+"""
+AERO_BLUE = (201, 255, 229)
+AFRICAN_VIOLET = (178, 132, 190)
+AIR_FORCE_BLUE = (93, 138, 168)
+AIR_SUPERIORITY_BLUE = (114, 160, 193)
+ALABAMA_CRIMSON = (175, 0, 42)
+ALICE_BLUE = (240, 248, 255)
+ALIZARIN_CRIMSON = (227, 38, 54)
+ALLOY_ORANGE = (196, 98, 16)
+ALMOND = (239, 222, 205)
+AMARANTH = (229, 43, 80)
+AMARANTH_PINK = (241, 156, 187)
+AMARANTH_PURPLE = (171, 39, 79)
+AMAZON = (59, 122, 87)
+AMBER = (255, 191, 0)
+SAE = (255, 126, 0)
+AMERICAN_ROSE = (255, 3, 62)
+AMETHYST = (153, 102, 204)
+ANDROID_GREEN = (164, 198, 57)
+ANTI_FLASH_WHITE = (242, 243, 244)
+ANTIQUE_BRASS = (205, 149, 117)
+ANTIQUE_BRONZE = (102, 93, 30)
+ANTIQUE_FUCHSIA = (145, 92, 131)
+ANTIQUE_RUBY = (132, 27, 45)
+ANTIQUE_WHITE = (250, 235, 215)
+AO = (0, 128, 0)
+APPLE_GREEN = (141, 182, 0)
+APRICOT = (251, 206, 177)
+AQUA = (0, 255, 255)
+AQUAMARINE = (127, 255, 212)
+ARMY_GREEN = (75, 83, 32)
+ARSENIC = (59, 68, 75)
+ARTICHOKE = (143, 151, 121)
+ARYLIDE_YELLOW = (233, 214, 107)
+ASH_GREY = (178, 190, 181)
+ASPARAGUS = (135, 169, 107)
+ATOMIC_TANGERINE = (255, 153, 102)
+AUBURN = (165, 42, 42)
+AUREOLIN = (253, 238, 0)
+AUROMETALSAURUS = (110, 127, 128)
+AVOCADO = (86, 130, 3)
+AZURE = (0, 127, 255)
+AZURE_MIST = (240, 255, 255)
+BABY_BLUE = (137, 207, 240)
+BABY_BLUE_EYES = (161, 202, 241)
+BABY_PINK = (244, 194, 194)
+BABY_POWDER = (254, 254, 250)
+BAKER_MILLER_PINK = (255, 145, 175)
+BALL_BLUE = (33, 171, 205)
+BANANA_MANIA = (250, 231, 181)
+BANANA_YELLOW = (255, 225, 53)
+BANGLADESH_GREEN = (0, 106, 78)
+BARBIE_PINK = (224, 33, 138)
+BARN_RED = (124, 10, 2)
+BATTLESHIP_GREY = (132, 132, 130)
+BAZAAR = (152, 119, 123)
+BEAU_BLUE = (188, 212, 230)
+BRIGHT_LILAC = (216, 145, 239)
+BEAVER = (159, 129, 112)
+BEIGE = (245, 245, 220)
+BISQUE = (255, 228, 196)
+BISTRE = (61, 43, 31)
+BISTRE_BROWN = (150, 113, 23)
+BITTER_LEMON = (202, 224, 13)
+BITTER_LIME = (100, 140, 17)
+BITTERSWEET = (254, 111, 94)
+BITTERSWEET_SHIMMER = (191, 79, 81)
+BLACK = (0, 0, 0)
+BLACK_BEAN = (61, 12, 2)
+BLACK_LEATHER_JACKET = (37, 53, 41)
+BLACK_OLIVE = (59, 60, 54)
+BLANCHED_ALMOND = (255, 235, 205)
+BLAST_OFF_BRONZE = (165, 113, 100)
+BLEU_DE_FRANCE = (49, 140, 231)
+BLIZZARD_BLUE = (172, 229, 238)
+BLOND = (250, 240, 190)
+BLUE = (0, 0, 255)
+BLUE_BELL = (162, 162, 208)
+BLUE_GRAY = (102, 153, 204)
+BLUE_GREEN = (13, 152, 186)
+BLUE_SAPPHIRE = (18, 97, 128)
+BLUE_VIOLET = (138, 43, 226)
+BLUE_YONDER = (80, 114, 167)
+BLUEBERRY = (79, 134, 247)
+BLUEBONNET = (28, 28, 240)
+BLUSH = (222, 93, 131)
+BOLE = (121, 68, 59)
+BONDI_BLUE = (0, 149, 182)
+BONE = (227, 218, 201)
+BOSTON_UNIVERSITY_RED = (204, 0, 0)
+BOTTLE_GREEN = (0, 106, 78)
+BOYSENBERRY = (135, 50, 96)
+BRANDEIS_BLUE = (0, 112, 255)
+BRASS = (181, 166, 66)
+BRICK_RED = (203, 65, 84)
+BRIGHT_CERULEAN = (29, 172, 214)
+BRIGHT_GREEN = (102, 255, 0)
+BRIGHT_LAVENDER = (191, 148, 228)
+BRIGHT_MAROON = (195, 33, 72)
+BRIGHT_NAVY_BLUE = (25, 116, 210)
+BRIGHT_PINK = (255, 0, 127)
+BRIGHT_TURQUOISE = (8, 232, 222)
+BRIGHT_UBE = (209, 159, 232)
+BRILLIANT_LAVENDER = (244, 187, 255)
+BRILLIANT_ROSE = (255, 85, 163)
+BRINK_PINK = (251, 96, 127)
+BRITISH_RACING_GREEN = (0, 66, 37)
+BRONZE = (205, 127, 50)
+BRONZE_YELLOW = (115, 112, 0)
+BROWN = (165, 42, 42)
+BROWN_NOSE = (107, 68, 35)
+BRUNSWICK_GREEN = (27, 77, 62)
+BUBBLE_GUM = (255, 193, 204)
+BUBBLES = (231, 254, 255)
+BUD_GREEN = (123, 182, 97)
+BUFF = (240, 220, 130)
+BULGARIAN_ROSE = (72, 6, 7)
+BURGUNDY = (128, 0, 32)
+BURLYWOOD = (222, 184, 135)
+BURNT_ORANGE = (204, 85, 0)
+BURNT_SIENNA = (233, 116, 81)
+BURNT_UMBER = (138, 51, 36)
+BYZANTINE = (189, 51, 164)
+BYZANTIUM = (112, 41, 99)
+CADET = (83, 104, 114)
+CADET_BLUE = (95, 158, 160)
+CADET_GREY = (145, 163, 176)
+CADMIUM_GREEN = (0, 107, 60)
+CADMIUM_ORANGE = (237, 135, 45)
+CADMIUM_RED = (227, 0, 34)
+CADMIUM_YELLOW = (255, 246, 0)
+CAL_POLY_GREEN = (30, 77, 43)
+CAMBRIDGE_BLUE = (163, 193, 173)
+CAMEL = (193, 154, 107)
+CAMEO_PINK = (239, 187, 204)
+CAMOUFLAGE_GREEN = (120, 134, 107)
+CANARY_YELLOW = (255, 239, 0)
+CANDY_APPLE_RED = (255, 8, 0)
+CANDY_PINK = (228, 113, 122)
+CAPRI = (0, 191, 255)
+CAPUT_MORTUUM = (89, 39, 32)
+CARDINAL = (196, 30, 58)
+CARIBBEAN_GREEN = (0, 204, 153)
+CARMINE = (150, 0, 24)
+CARMINE_PINK = (235, 76, 66)
+CARMINE_RED = (255, 0, 56)
+CARNATION_PINK = (255, 166, 201)
+CARNELIAN = (179, 27, 27)
+CAROLINA_BLUE = (153, 186, 221)
+CARROT_ORANGE = (237, 145, 33)
+CASTLETON_GREEN = (0, 86, 63)
+CATALINA_BLUE = (6, 42, 120)
+CATAWBA = (112, 54, 66)
+CEDAR_CHEST = (201, 90, 73)
+CEIL = (146, 161, 207)
+CELADON = (172, 225, 175)
+CELADON_BLUE = (0, 123, 167)
+CELADON_GREEN = (47, 132, 124)
+CELESTE = (178, 255, 255)
+CELESTIAL_BLUE = (73, 151, 208)
+CERISE = (222, 49, 99)
+CERISE_PINK = (236, 59, 131)
+CERULEAN = (0, 123, 167)
+CERULEAN_BLUE = (42, 82, 190)
+CERULEAN_FROST = (109, 155, 195)
+CG_BLUE = (0, 122, 165)
+CG_RED = (224, 60, 49)
+CHAMOISEE = (160, 120, 90)
+CHAMPAGNE = (247, 231, 206)
+CHARCOAL = (54, 69, 79)
+CHARLESTON_GREEN = (35, 43, 43)
+CHARM_PINK = (230, 143, 172)
+CHARTREUSE = (127, 255, 0)
+CHERRY = (222, 49, 99)
+CHERRY_BLOSSOM_PINK = (255, 183, 197)
+CHESTNUT = (149, 69, 53)
+CHINA_PINK = (222, 111, 161)
+CHINA_ROSE = (168, 81, 110)
+CHINESE_RED = (170, 56, 30)
+CHINESE_VIOLET = (133, 96, 136)
+CHOCOLATE = (210, 105, 30)
+CHROME_YELLOW = (255, 167, 0)
+CINEREOUS = (152, 129, 123)
+CINNABAR = (227, 66, 52)
+CINNAMON = (210, 105, 30)
+CITRINE = (228, 208, 10)
+CITRON = (159, 169, 31)
+CLARET = (127, 23, 52)
+CLASSIC_ROSE = (251, 204, 231)
+COAL = (124, 185, 232)
+COBALT = (0, 71, 171)
+COCOA_BROWN = (210, 105, 30)
+COCONUT = (150, 90, 62)
+COFFEE = (111, 78, 55)
+COLUMBIA_BLUE = (155, 221, 255)
+CONGO_PINK = (248, 131, 121)
+COOL_BLACK = (0, 46, 99)
+COOL_GREY = (140, 146, 172)
+COPPER = (184, 115, 51)
+COPPER_PENNY = (173, 111, 105)
+COPPER_RED = (203, 109, 81)
+COPPER_ROSE = (153, 102, 102)
+COQUELICOT = (255, 56, 0)
+CORAL = (255, 127, 80)
+CORAL_PINK = (248, 131, 121)
+CORAL_RED = (255, 64, 64)
+CORDOVAN = (137, 63, 69)
+CORN = (251, 236, 93)
+CORNELL_RED = (179, 27, 27)
+CORNFLOWER_BLUE = (100, 149, 237)
+CORNSILK = (255, 248, 220)
+COSMIC_LATTE = (255, 248, 231)
+COTTON_CANDY = (255, 188, 217)
+CREAM = (255, 253, 208)
+CRIMSON = (220, 20, 60)
+CRIMSON_GLORY = (190, 0, 50)
+CYAN = (0, 255, 255)
+CYBER_GRAPE = (88, 66, 124)
+CYBER_YELLOW = (255, 211, 0)
+DAFFODIL = (255, 255, 49)
+DANDELION = (240, 225, 48)
+DARK_BLUE = (0, 0, 139)
+DARK_BLUE_GRAY = (102, 102, 153)
+DARK_BROWN = (101, 67, 33)
+DARK_BYZANTIUM = (93, 57, 84)
+DARK_CANDY_APPLE_RED = (164, 0, 0)
+DARK_CERULEAN = (8, 69, 126)
+DARK_CHESTNUT = (152, 105, 96)
+DARK_CORAL = (205, 91, 69)
+DARK_CYAN = (0, 139, 139)
+DARK_ELECTRIC_BLUE = (83, 104, 120)
+DARK_GOLDENROD = (184, 134, 11)
+DARK_GRAY = (169, 169, 169)
+DARK_GREEN = (1, 50, 32)
+DARK_IMPERIAL_BLUE = (0, 65, 106)
+DARK_JUNGLE_GREEN = (26, 36, 33)
+DARK_KHAKI = (189, 183, 107)
+DARK_LAVA = (72, 60, 50)
+DARK_LAVENDER = (115, 79, 150)
+DARK_LIVER = (83, 75, 79)
+DARK_MAGENTA = (139, 0, 139)
+DARK_MIDNIGHT_BLUE = (0, 51, 102)
+DARK_MOSS_GREEN = (74, 93, 35)
+DARK_OLIVE_GREEN = (85, 107, 47)
+DARK_ORANGE = (255, 140, 0)
+DARK_ORCHID = (153, 50, 204)
+DARK_PASTEL_BLUE = (119, 158, 203)
+DARK_PASTEL_GREEN = (3, 192, 60)
+DARK_PASTEL_PURPLE = (150, 111, 214)
+DARK_PASTEL_RED = (194, 59, 34)
+DARK_PINK = (231, 84, 128)
+DARK_POWDER_BLUE = (0, 51, 153)
+DARK_PUCE = (79, 58, 60)
+DARK_RASPBERRY = (135, 38, 87)
+DARK_RED = (139, 0, 0)
+DARK_SALMON = (233, 150, 122)
+DARK_SCARLET = (86, 3, 25)
+DARK_SEA_GREEN = (143, 188, 143)
+DARK_SIENNA = (60, 20, 20)
+DARK_SKY_BLUE = (140, 190, 214)
+DARK_SLATE_BLUE = (72, 61, 139)
+DARK_SLATE_GRAY = (47, 79, 79)
+DARK_SPRING_GREEN = (23, 114, 69)
+DARK_TAN = (145, 129, 81)
+DARK_TANGERINE = (255, 168, 18)
+DARK_TAUPE = (72, 60, 50)
+DARK_TERRA_COTTA = (204, 78, 92)
+DARK_TURQUOISE = (0, 206, 209)
+DARK_VANILLA = (209, 190, 168)
+DARK_VIOLET = (148, 0, 211)
+DARK_YELLOW = (155, 135, 12)
+DARTMOUTH_GREEN = (0, 112, 60)
+DAVY_GREY = (85, 85, 85)
+DEBIAN_RED = (215, 10, 83)
+DEEP_CARMINE = (169, 32, 62)
+DEEP_CARMINE_PINK = (239, 48, 56)
+DEEP_CARROT_ORANGE = (233, 105, 44)
+DEEP_CERISE = (218, 50, 135)
+DEEP_CHAMPAGNE = (250, 214, 165)
+DEEP_CHESTNUT = (185, 78, 72)
+DEEP_COFFEE = (112, 66, 65)
+DEEP_FUCHSIA = (193, 84, 193)
+DEEP_JUNGLE_GREEN = (0, 75, 73)
+DEEP_LEMON = (245, 199, 26)
+DEEP_LILAC = (153, 85, 187)
+DEEP_MAGENTA = (204, 0, 204)
+DEEP_MAUVE = (212, 115, 212)
+DEEP_MOSS_GREEN = (53, 94, 59)
+DEEP_PEACH = (255, 203, 164)
+DEEP_PINK = (255, 20, 147)
+DEEP_PUCE = (169, 92, 104)
+DEEP_RUBY = (132, 63, 91)
+DEEP_SAFFRON = (255, 153, 51)
+DEEP_SKY_BLUE = (0, 191, 255)
+DEEP_SPACE_SPARKLE = (74, 100, 108)
+DEEP_TAUPE = (126, 94, 96)
+DEEP_TUSCAN_RED = (102, 66, 77)
+DEER = (186, 135, 89)
+DENIM = (21, 96, 189)
+DESERT = (193, 154, 107)
+DESERT_SAND = (237, 201, 175)
+DESIRE = (234, 60, 83)
+DIAMOND = (185, 242, 255)
+DIM_GRAY = (105, 105, 105)
+DIRT = (155, 118, 83)
+DODGER_BLUE = (30, 144, 255)
+DOGWOOD_ROSE = (215, 24, 104)
+DOLLAR_BILL = (133, 187, 101)
+DONKEY_BROWN = (102, 76, 40)
+DRAB = (150, 113, 23)
+DUKE_BLUE = (0, 0, 156)
+DUST_STORM = (229, 204, 201)
+DUTCH_WHITE = (239, 223, 187)
+EARTH_YELLOW = (225, 169, 95)
+EBONY = (85, 93, 80)
+ECRU = (194, 178, 128)
+EERIE_BLACK = (27, 27, 27)
+EGGPLANT = (97, 64, 81)
+EGGSHELL = (240, 234, 214)
+EGYPTIAN_BLUE = (16, 52, 166)
+ELECTRIC_BLUE = (125, 249, 255)
+ELECTRIC_CRIMSON = (255, 0, 63)
+ELECTRIC_CYAN = (0, 255, 255)
+ELECTRIC_GREEN = (0, 255, 0)
+ELECTRIC_INDIGO = (111, 0, 255)
+ELECTRIC_LAVENDER = (244, 187, 255)
+ELECTRIC_LIME = (204, 255, 0)
+ELECTRIC_PURPLE = (191, 0, 255)
+ELECTRIC_ULTRAMARINE = (63, 0, 255)
+ELECTRIC_VIOLET = (143, 0, 255)
+ELECTRIC_YELLOW = (255, 255, 0)
+EMERALD = (80, 200, 120)
+EMINENCE = (108, 48, 130)
+ENGLISH_GREEN = (27, 77, 62)
+ENGLISH_LAVENDER = (180, 131, 149)
+ENGLISH_RED = (171, 75, 82)
+ENGLISH_VIOLET = (86, 60, 92)
+ETON_BLUE = (150, 200, 162)
+EUCALYPTUS = (68, 215, 168)
+FALLOW = (193, 154, 107)
+FALU_RED = (128, 24, 24)
+FANDANGO = (181, 51, 137)
+FANDANGO_PINK = (222, 82, 133)
+FASHION_FUCHSIA = (244, 0, 161)
+FAWN = (229, 170, 112)
+FELDGRAU = (77, 93, 83)
+FELDSPAR = (253, 213, 177)
+FERN_GREEN = (79, 121, 66)
+FERRARI_RED = (255, 40, 0)
+FIELD_DRAB = (108, 84, 30)
+FIREBRICK = (178, 34, 34)
+FIRE_ENGINE_RED = (206, 32, 41)
+FLAME = (226, 88, 34)
+FLAMINGO_PINK = (252, 142, 172)
+FLATTERY = (107, 68, 35)
+FLAVESCENT = (247, 233, 142)
+FLAX = (238, 220, 130)
+FLIRT = (162, 0, 109)
+FLORAL_WHITE = (255, 250, 240)
+FLUORESCENT_ORANGE = (255, 191, 0)
+FLUORESCENT_PINK = (255, 20, 147)
+FLUORESCENT_YELLOW = (204, 255, 0)
+FOLLY = (255, 0, 79)
+FOREST_GREEN = (34, 139, 34)
+FRENCH_BEIGE = (166, 123, 91)
+FRENCH_BISTRE = (133, 109, 77)
+FRENCH_BLUE = (0, 114, 187)
+FRENCH_FUCHSIA = (253, 63, 146)
+FRENCH_LILAC = (134, 96, 142)
+FRENCH_LIME = (158, 253, 56)
+FRENCH_MAUVE = (212, 115, 212)
+FRENCH_PINK = (253, 108, 158)
+FRENCH_PUCE = (78, 22, 9)
+FRENCH_RASPBERRY = (199, 44, 72)
+FRENCH_ROSE = (246, 74, 138)
+FRENCH_SKY_BLUE = (119, 181, 254)
+FRENCH_WINE = (172, 30, 68)
+FRESH_AIR = (166, 231, 255)
+FUCHSIA = (255, 0, 255)
+FUCHSIA_PINK = (255, 119, 255)
+FUCHSIA_PURPLE = (204, 57, 123)
+FUCHSIA_ROSE = (199, 67, 117)
+FULVOUS = (228, 132, 0)
+FUZZY_WUZZY = (204, 102, 102)
+GAINSBORO = (220, 220, 220)
+GAMBOGE = (228, 155, 15)
+GENERIC_VIRIDIAN = (0, 127, 102)
+GHOST_WHITE = (248, 248, 255)
+GIANTS_ORANGE = (254, 90, 29)
+GINGER = (176, 101, 0)
+GLAUCOUS = (96, 130, 182)
+GLITTER = (230, 232, 250)
+GO_GREEN = (0, 171, 102)
+GOLD = (255, 215, 0)
+GOLD_FUSION = (133, 117, 78)
+GOLDEN_BROWN = (153, 101, 21)
+GOLDEN_POPPY = (252, 194, 0)
+GOLDEN_YELLOW = (255, 223, 0)
+GOLDENROD = (218, 165, 32)
+GRANNY_SMITH_APPLE = (168, 228, 160)
+GRAPE = (111, 45, 168)
+GRAY = (128, 128, 128)
+GRAY_ASPARAGUS = (70, 89, 69)
+GRAY_BLUE = (140, 146, 172)
+GREEN = (0, 255, 0)
+GREEN_YELLOW = (173, 255, 47)
+GRULLO = (169, 154, 134)
+GUPPIE_GREEN = (0, 255, 127)
+HAN_BLUE = (68, 108, 207)
+HAN_PURPLE = (82, 24, 250)
+HANSA_YELLOW = (233, 214, 107)
+HARLEQUIN = (63, 255, 0)
+HARVARD_CRIMSON = (201, 0, 22)
+HARVEST_GOLD = (218, 145, 0)
+HEART_GOLD = (128, 128, 0)
+HELIOTROPE = (223, 115, 255)
+HELIOTROPE_GRAY = (170, 152, 169)
+HOLLYWOOD_CERISE = (244, 0, 161)
+HONEYDEW = (240, 255, 240)
+HONOLULU_BLUE = (0, 109, 176)
+HOOKER_GREEN = (73, 121, 107)
+HOT_MAGENTA = (255, 29, 206)
+HOT_PINK = (255, 105, 180)
+HUNTER_GREEN = (53, 94, 59)
+ICEBERG = (113, 166, 210)
+ICTERINE = (252, 247, 94)
+ILLUMINATING_EMERALD = (49, 145, 119)
+IMPERIAL = (96, 47, 107)
+IMPERIAL_BLUE = (0, 35, 149)
+IMPERIAL_PURPLE = (102, 2, 60)
+IMPERIAL_RED = (237, 41, 57)
+INCHWORM = (178, 236, 93)
+INDEPENDENCE = (76, 81, 109)
+INDIA_GREEN = (19, 136, 8)
+INDIAN_RED = (205, 92, 92)
+INDIAN_YELLOW = (227, 168, 87)
+INDIGO = (75, 0, 130)
+INTERNATIONAL_KLEIN_BLUE = (0, 47, 167)
+INTERNATIONAL_ORANGE = (255, 79, 0)
+IRIS = (90, 79, 207)
+IRRESISTIBLE = (179, 68, 108)
+ISABELLINE = (244, 240, 236)
+ISLAMIC_GREEN = (0, 144, 0)
+ITALIAN_SKY_BLUE = (178, 255, 255)
+IVORY = (255, 255, 240)
+JADE = (0, 168, 107)
+JAPANESE_CARMINE = (157, 41, 51)
+JAPANESE_INDIGO = (38, 67, 72)
+JAPANESE_VIOLET = (91, 50, 86)
+JASMINE = (248, 222, 126)
+JASPER = (215, 59, 62)
+JAZZBERRY_JAM = (165, 11, 94)
+JELLY_BEAN = (218, 97, 78)
+JET = (52, 52, 52)
+JONQUIL = (244, 202, 22)
+JORDY_BLUE = (138, 185, 241)
+JUNE_BUD = (189, 218, 87)
+JUNGLE_GREEN = (41, 171, 135)
+KELLY_GREEN = (76, 187, 23)
+KENYAN_COPPER = (124, 28, 5)
+KEPPEL = (58, 176, 158)
+KHAKI = (195, 176, 145)
+KOBE = (136, 45, 23)
+KOBI = (231, 159, 196)
+KOMBU_GREEN = (53, 66, 48)
+KU_CRIMSON = (232, 0, 13)
+LA_SALLE_GREEN = (8, 120, 48)
+LANGUID_LAVENDER = (214, 202, 221)
+LAPIS_LAZULI = (38, 97, 156)
+LASER_LEMON = (255, 255, 102)
+LAUREL_GREEN = (169, 186, 157)
+LAVA = (207, 16, 32)
+LAVENDER = (230, 230, 250)
+LAVENDER_BLUE = (204, 204, 255)
+LAVENDER_BLUSH = (255, 240, 245)
+LAVENDER_GRAY = (196, 195, 208)
+LAVENDER_INDIGO = (148, 87, 235)
+LAVENDER_MAGENTA = (238, 130, 238)
+LAVENDER_MIST = (230, 230, 250)
+LAVENDER_PINK = (251, 174, 210)
+LAVENDER_PURPLE = (150, 123, 182)
+LAVENDER_ROSE = (251, 160, 227)
+LAWN_GREEN = (124, 252, 0)
+LEMON = (255, 247, 0)
+LEMON_CHIFFON = (255, 250, 205)
+LEMON_CURRY = (204, 160, 29)
+LEMON_GLACIER = (253, 255, 0)
+LEMON_LIME = (227, 255, 0)
+LEMON_MERINGUE = (246, 234, 190)
+LEMON_YELLOW = (255, 244, 79)
+LIBERTY = (84, 90, 167)
+LICORICE = (26, 17, 16)
+LIGHT_APRICOT = (253, 213, 177)
+LIGHT_BLUE = (173, 216, 230)
+LIGHT_BROWN = (181, 101, 29)
+LIGHT_CARMINE_PINK = (230, 103, 113)
+LIGHT_CORAL = (240, 128, 128)
+LIGHT_CORNFLOWER_BLUE = (147, 204, 234)
+LIGHT_CRIMSON = (245, 105, 145)
+LIGHT_CYAN = (224, 255, 255)
+LIGHT_DEEP_PINK = (255, 92, 205)
+LIGHT_FUCHSIA_PINK = (249, 132, 239)
+LIGHT_GOLDENROD_YELLOW = (250, 250, 210)
+LIGHT_GRAY = (211, 211, 211)
+LIGHT_GREEN = (144, 238, 144)
+LIGHT_HOT_PINK = (255, 179, 222)
+LIGHT_KHAKI = (240, 230, 140)
+LIGHT_MEDIUM_ORCHID = (211, 155, 203)
+LIGHT_MOSS_GREEN = (173, 223, 173)
+LIGHT_ORCHID = (230, 168, 215)
+LIGHT_PASTEL_PURPLE = (177, 156, 217)
+LIGHT_PINK = (255, 182, 193)
+LIGHT_RED_OCHRE = (233, 116, 81)
+LIGHT_SALMON = (255, 160, 122)
+LIGHT_SALMON_PINK = (255, 153, 153)
+LIGHT_SEA_GREEN = (32, 178, 170)
+LIGHT_SKY_BLUE = (135, 206, 250)
+LIGHT_SLATE_GRAY = (119, 136, 153)
+LIGHT_STEEL_BLUE = (176, 196, 222)
+LIGHT_TAUPE = (179, 139, 109)
+LIGHT_THULIAN_PINK = (230, 143, 172)
+LIGHT_YELLOW = (255, 255, 224)
+LILAC = (200, 162, 200)
+LIME = (191, 255, 0)
+LIME_GREEN = (50, 205, 50)
+LIMERICK = (157, 194, 9)
+LINCOLN_GREEN = (25, 89, 5)
+LINEN = (250, 240, 230)
+LION = (193, 154, 107)
+LISERAN_PURPLE = (222, 111, 161)
+LITTLE_BOY_BLUE = (108, 160, 220)
+LIVER = (103, 76, 71)
+LIVER_CHESTNUT = (152, 116, 86)
+LIVID = (102, 153, 204)
+LUMBER = (255, 228, 205)
+LUST = (230, 32, 32)
+MAGENTA = (255, 0, 255)
+MAGENTA_HAZE = (159, 69, 118)
+MAGIC_MINT = (170, 240, 209)
+MAGNOLIA = (248, 244, 255)
+MAHOGANY = (192, 64, 0)
+MAIZE = (251, 236, 93)
+MAJORELLE_BLUE = (96, 80, 220)
+MALACHITE = (11, 218, 81)
+MANATEE = (151, 154, 170)
+MANGO_TANGO = (255, 130, 67)
+MANTIS = (116, 195, 101)
+MARDI_GRAS = (136, 0, 133)
+MAROON = (128, 0, 0)
+MAUVE = (224, 176, 255)
+MAUVE_TAUPE = (145, 95, 109)
+MAUVELOUS = (239, 152, 170)
+MAYA_BLUE = (115, 194, 251)
+MEAT_BROWN = (229, 183, 59)
+MEDIUM_AQUAMARINE = (102, 221, 170)
+MEDIUM_BLUE = (0, 0, 205)
+MEDIUM_CANDY_APPLE_RED = (226, 6, 44)
+MEDIUM_CARMINE = (175, 64, 53)
+MEDIUM_CHAMPAGNE = (243, 229, 171)
+MEDIUM_ELECTRIC_BLUE = (3, 80, 150)
+MEDIUM_JUNGLE_GREEN = (28, 53, 45)
+MEDIUM_LAVENDER_MAGENTA = (221, 160, 221)
+MEDIUM_ORCHID = (186, 85, 211)
+MEDIUM_PERSIAN_BLUE = (0, 103, 165)
+MEDIUM_PURPLE = (147, 112, 219)
+MEDIUM_RED_VIOLET = (187, 51, 133)
+MEDIUM_RUBY = (170, 64, 105)
+MEDIUM_SEA_GREEN = (60, 179, 113)
+MEDIUM_SLATE_BLUE = (123, 104, 238)
+MEDIUM_SPRING_BUD = (201, 220, 135)
+MEDIUM_SPRING_GREEN = (0, 250, 154)
+MEDIUM_SKY_BLUE = (128, 218, 235)
+MEDIUM_TAUPE = (103, 76, 71)
+MEDIUM_TURQUOISE = (72, 209, 204)
+MEDIUM_TUSCAN_RED = (121, 68, 59)
+MEDIUM_VERMILION = (217, 96, 59)
+MEDIUM_VIOLET_RED = (199, 21, 133)
+MELLOW_APRICOT = (248, 184, 120)
+MELLOW_YELLOW = (248, 222, 126)
+MELON = (253, 188, 180)
+METALLIC_SEAWEED = (10, 126, 140)
+METALLIC_SUNBURST = (156, 124, 56)
+MEXICAN_PINK = (228, 0, 124)
+MIDNIGHT_BLUE = (25, 25, 112)
+MIDNIGHT_GREEN = (0, 73, 83)
+MIKADO_YELLOW = (255, 196, 12)
+MINDARO = (227, 249, 136)
+MINT = (62, 180, 137)
+MINT_CREAM = (245, 255, 250)
+MINT_GREEN = (152, 255, 152)
+MISTY_ROSE = (255, 228, 225)
+MOCCASIN = (250, 235, 215)
+MODE_BEIGE = (150, 113, 23)
+MOONSTONE_BLUE = (115, 169, 194)
+MORDANT_RED_19 = (174, 12, 0)
+MOSS_GREEN = (138, 154, 91)
+MOUNTAIN_MEADOW = (48, 186, 143)
+MOUNTBATTEN_PINK = (153, 122, 141)
+MSU_GREEN = (24, 69, 59)
+MUGHAL_GREEN = (48, 96, 48)
+MULBERRY = (197, 75, 140)
+MUSTARD = (255, 219, 88)
+MYRTLE_GREEN = (49, 120, 115)
+NADESHIKO_PINK = (246, 173, 198)
+NAPIER_GREEN = (42, 128, 0)
+NAPLES_YELLOW = (250, 218, 94)
+NAVAJO_WHITE = (255, 222, 173)
+NAVY_BLUE = (0, 0, 128)
+NAVY_PURPLE = (148, 87, 235)
+NEON_CARROT = (255, 163, 67)
+NEON_FUCHSIA = (254, 65, 100)
+NEON_GREEN = (57, 255, 20)
+NEW_CAR = (33, 79, 198)
+NEW_YORK_PINK = (215, 131, 127)
+NON_PHOTO_BLUE = (164, 221, 237)
+NYANZA = (233, 255, 219)
+OCEAN_BOAT_BLUE = (0, 119, 190)
+OCHRE = (204, 119, 34)
+OFFICE_GREEN = (0, 128, 0)
+OLD_BURGUNDY = (67, 48, 46)
+OLD_GOLD = (207, 181, 59)
+OLD_HELIOTROPE = (86, 60, 92)
+OLD_LACE = (253, 245, 230)
+OLD_LAVENDER = (121, 104, 120)
+OLD_MAUVE = (103, 49, 71)
+OLD_MOSS_GREEN = (134, 126, 54)
+OLD_ROSE = (192, 128, 129)
+OLD_SILVER = (132, 132, 130)
+OLIVE = (128, 128, 0)
+OLIVE_DRAB = (107, 142, 35)
+OLIVINE = (154, 185, 115)
+ONYX = (53, 56, 57)
+OPERA_MAUVE = (183, 132, 167)
+ORANGE = (255, 165, 0)
+ORANGE_PEEL = (255, 159, 0)
+ORANGE_RED = (255, 69, 0)
+ORCHID = (218, 112, 214)
+ORCHID_PINK = (242, 141, 205)
+ORIOLES_ORANGE = (251, 79, 20)
+OTTER_BROWN = (101, 67, 33)
+OUTER_SPACE = (65, 74, 76)
+OUTRAGEOUS_ORANGE = (255, 110, 74)
+OXFORD_BLUE = (0, 33, 71)
+OU_CRIMSON_RED = (153, 0, 0)
+PAKISTAN_GREEN = (0, 102, 0)
+PALATINATE_BLUE = (39, 59, 226)
+PALATINATE_PURPLE = (104, 40, 96)
+PALE_AQUA = (188, 212, 230)
+PALE_BLUE = (175, 238, 238)
+PALE_BROWN = (152, 118, 84)
+PALE_CARMINE = (175, 64, 53)
+PALE_CERULEAN = (155, 196, 226)
+PALE_CHESTNUT = (221, 173, 175)
+PALE_COPPER = (218, 138, 103)
+PALE_CORNFLOWER_BLUE = (171, 205, 239)
+PALE_GOLD = (230, 190, 138)
+PALE_GOLDENROD = (238, 232, 170)
+PALE_GREEN = (152, 251, 152)
+PALE_LAVENDER = (220, 208, 255)
+PALE_MAGENTA = (249, 132, 229)
+PALE_PINK = (250, 218, 221)
+PALE_PLUM = (221, 160, 221)
+PALE_RED_VIOLET = (219, 112, 147)
+PALE_ROBIN_EGG_BLUE = (150, 222, 209)
+PALE_SILVER = (201, 192, 187)
+PALE_SPRING_BUD = (236, 235, 189)
+PALE_TAUPE = (188, 152, 126)
+PALE_TURQUOISE = (175, 238, 238)
+PALE_VIOLET_RED = (219, 112, 147)
+PANSY_PURPLE = (120, 24, 74)
+PAOLO_VERONESE_GREEN = (0, 155, 125)
+PAPAYA_WHIP = (255, 239, 213)
+PARADISE_PINK = (230, 62, 98)
+PARIS_GREEN = (80, 200, 120)
+PASTEL_BLUE = (174, 198, 207)
+PASTEL_BROWN = (131, 105, 83)
+PASTEL_GRAY = (207, 207, 196)
+PASTEL_GREEN = (119, 221, 119)
+PASTEL_MAGENTA = (244, 154, 194)
+PASTEL_ORANGE = (255, 179, 71)
+PASTEL_PINK = (222, 165, 164)
+PASTEL_PURPLE = (179, 158, 181)
+PASTEL_RED = (255, 105, 97)
+PASTEL_VIOLET = (203, 153, 201)
+PASTEL_YELLOW = (253, 253, 150)
+PATRIARCH = (128, 0, 128)
+PAYNE_GREY = (83, 104, 120)
+PEACH = (255, 229, 180)
+PEACH_ORANGE = (255, 204, 153)
+PEACH_PUFF = (255, 218, 185)
+PEACH_YELLOW = (250, 223, 173)
+PEAR = (209, 226, 49)
+PEARL = (234, 224, 200)
+PEARL_AQUA = (136, 216, 192)
+PEARLY_PURPLE = (183, 104, 162)
+PERIDOT = (230, 226, 0)
+PERIWINKLE = (204, 204, 255)
+PERSIAN_BLUE = (28, 57, 187)
+PERSIAN_GREEN = (0, 166, 147)
+PERSIAN_INDIGO = (50, 18, 122)
+PERSIAN_ORANGE = (217, 144, 88)
+PERSIAN_PINK = (247, 127, 190)
+PERSIAN_PLUM = (112, 28, 28)
+PERSIAN_RED = (204, 51, 51)
+PERSIAN_ROSE = (254, 40, 162)
+PERSIMMON = (236, 88, 0)
+PERU = (205, 133, 63)
+PHLOX = (223, 0, 255)
+PHTHALO_BLUE = (0, 15, 137)
+PHTHALO_GREEN = (18, 53, 36)
+PICTON_BLUE = (69, 177, 232)
+PICTORIAL_CARMINE = (195, 11, 78)
+PIGGY_PINK = (253, 221, 230)
+PINE_GREEN = (1, 121, 111)
+PINK = (255, 192, 203)
+PINK_LACE = (255, 221, 244)
+PINK_LAVENDER = (216, 178, 209)
+PINK_PEARL = (231, 172, 207)
+PINK_SHERBET = (247, 143, 167)
+PISTACHIO = (147, 197, 114)
+PLATINUM = (229, 228, 226)
+PLUM = (221, 160, 221)
+POMP_AND_POWER = (134, 96, 142)
+POPSTAR = (190, 79, 98)
+PORTLAND_ORANGE = (255, 90, 54)
+POWDER_BLUE = (176, 224, 230)
+PRINCETON_ORANGE = (255, 143, 0)
+PRUNE = (112, 28, 28)
+PRUSSIAN_BLUE = (0, 49, 83)
+PSYCHEDELIC_PURPLE = (223, 0, 255)
+PUCE = (204, 136, 153)
+PUCE_RED = (114, 47, 55)
+PULLMAN_BROWN = (100, 65, 23)
+PUMPKIN = (255, 117, 24)
+PURPLE = (128, 0, 128)
+PURPLE_HEART = (105, 53, 156)
+PURPLE_MOUNTAIN_MAJESTY = (150, 120, 182)
+PURPLE_NAVY = (78, 81, 128)
+PURPLE_PIZZAZZ = (254, 78, 218)
+PURPLE_TAUPE = (80, 64, 77)
+PURPUREUS = (154, 78, 174)
+QUARTZ = (81, 72, 79)
+QUEEN_BLUE = (67, 107, 149)
+QUEEN_PINK = (232, 204, 215)
+QUINACRIDONE_MAGENTA = (142, 58, 89)
+RACKLEY = (93, 138, 168)
+RADICAL_RED = (255, 53, 94)
+RAJAH = (251, 171, 96)
+RASPBERRY = (227, 11, 93)
+RASPBERRY_GLACE = (145, 95, 109)
+RASPBERRY_PINK = (226, 80, 152)
+RASPBERRY_ROSE = (179, 68, 108)
+RAW_UMBER = (130, 102, 68)
+RAZZLE_DAZZLE_ROSE = (255, 51, 204)
+RAZZMATAZZ = (227, 37, 107)
+RAZZMIC_BERRY = (141, 78, 133)
+RED = (255, 0, 0)
+RED_BROWN = (165, 42, 42)
+RED_DEVIL = (134, 1, 17)
+RED_ORANGE = (255, 83, 73)
+RED_PURPLE = (228, 0, 120)
+RED_VIOLET = (199, 21, 133)
+REDWOOD = (164, 90, 82)
+REGALIA = (82, 45, 128)
+RESOLUTION_BLUE = (0, 35, 135)
+RHYTHM = (119, 118, 150)
+RICH_BLACK = (0, 64, 64)
+RICH_BRILLIANT_LAVENDER = (241, 167, 254)
+RICH_CARMINE = (215, 0, 64)
+RICH_ELECTRIC_BLUE = (8, 146, 208)
+RICH_LAVENDER = (167, 107, 207)
+RICH_LILAC = (182, 102, 210)
+RICH_MAROON = (176, 48, 96)
+RIFLE_GREEN = (68, 76, 56)
+ROAST_COFFEE = (112, 66, 65)
+ROBIN_EGG_BLUE = (0, 204, 204)
+ROCKET_METALLIC = (138, 127, 128)
+ROMAN_SILVER = (131, 137, 150)
+ROSE = (255, 0, 127)
+ROSE_BONBON = (249, 66, 158)
+ROSE_EBONY = (103, 72, 70)
+ROSE_GOLD = (183, 110, 121)
+ROSE_MADDER = (227, 38, 54)
+ROSE_PINK = (255, 102, 204)
+ROSE_QUARTZ = (170, 152, 169)
+ROSE_RED = (194, 30, 86)
+ROSE_TAUPE = (144, 93, 93)
+ROSE_VALE = (171, 78, 82)
+ROSEWOOD = (101, 0, 11)
+ROSSO_CORSA = (212, 0, 0)
+ROSY_BROWN = (188, 143, 143)
+ROYAL_AZURE = (0, 56, 168)
+ROYAL_BLUE = (65, 105, 225)
+ROYAL_FUCHSIA = (202, 44, 146)
+ROYAL_PURPLE = (120, 81, 169)
+ROYAL_YELLOW = (250, 218, 94)
+RUBER = (206, 70, 118)
+RUBINE_RED = (209, 0, 86)
+RUBY = (224, 17, 95)
+RUBY_RED = (155, 17, 30)
+RUDDY = (255, 0, 40)
+RUDDY_BROWN = (187, 101, 40)
+RUDDY_PINK = (225, 142, 150)
+RUFOUS = (168, 28, 7)
+RUSSET = (128, 70, 27)
+RUSSIAN_GREEN = (103, 146, 103)
+RUSSIAN_VIOLET = (50, 23, 77)
+RUST = (183, 65, 14)
+RUSTY_RED = (218, 44, 67)
+SACRAMENTO_STATE_GREEN = (0, 86, 63)
+SADDLE_BROWN = (139, 69, 19)
+SAFETY_ORANGE = (255, 103, 0)
+SAFETY_YELLOW = (238, 210, 2)
+SAFFRON = (244, 196, 48)
+SAGE = (188, 184, 138)
+ST_PATRICK_BLUE = (35, 41, 122)
+SALMON = (250, 128, 114)
+SALMON_PINK = (255, 145, 164)
+SAND = (194, 178, 128)
+SAND_DUNE = (150, 113, 23)
+SANDSTORM = (236, 213, 64)
+SANDY_BROWN = (244, 164, 96)
+SANDY_TAUPE = (150, 113, 23)
+SANGRIA = (146, 0, 10)
+SAP_GREEN = (80, 125, 42)
+SAPPHIRE = (15, 82, 186)
+SAPPHIRE_BLUE = (0, 103, 165)
+SATIN_SHEEN_GOLD = (203, 161, 53)
+SCARLET = (255, 36, 0)
+SCHAUSS_PINK = (255, 145, 175)
+SCHOOL_BUS_YELLOW = (255, 216, 0)
+SCREAMIN_GREEN = (118, 255, 122)
+SEA_BLUE = (0, 105, 148)
+SEA_GREEN = (46, 255, 139)
+SEAL_BROWN = (50, 20, 20)
+SEASHELL = (255, 245, 238)
+SELECTIVE_YELLOW = (255, 186, 0)
+SEPIA = (112, 66, 20)
+SHADOW = (138, 121, 93)
+SHADOW_BLUE = (119, 139, 165)
+SHAMPOO = (255, 207, 241)
+SHAMROCK_GREEN = (0, 158, 96)
+SHEEN_GREEN = (143, 212, 0)
+SHIMMERING_BLUSH = (217, 134, 149)
+SHOCKING_PINK = (252, 15, 192)
+SIENNA = (136, 45, 23)
+SILVER = (192, 192, 192)
+SILVER_CHALICE = (172, 172, 172)
+SILVER_LAKE_BLUE = (93, 137, 186)
+SILVER_PINK = (196, 174, 173)
+SILVER_SAND = (191, 193, 194)
+SINOPIA = (203, 65, 11)
+SKOBELOFF = (0, 116, 116)
+SKY_BLUE = (135, 206, 235)
+SKY_MAGENTA = (207, 113, 175)
+SLATE_BLUE = (106, 90, 205)
+SLATE_GRAY = (112, 128, 144)
+SMALT = (0, 51, 153)
+SMITTEN = (200, 65, 134)
+SMOKE = (115, 130, 118)
+SMOKEY_TOPAZ = (147, 61, 65)
+SMOKY_BLACK = (16, 12, 8)
+SNOW = (255, 250, 250)
+SOAP = (206, 200, 239)
+SONIC_SILVER = (117, 117, 117)
+SPACE_CADET = (29, 41, 81)
+SPANISH_BISTRE = (128, 117, 90)
+SPANISH_CARMINE = (209, 0, 71)
+SPANISH_CRIMSON = (229, 26, 76)
+SPANISH_BLUE = (0, 112, 184)
+SPANISH_GRAY = (152, 152, 152)
+SPANISH_GREEN = (0, 145, 80)
+SPANISH_ORANGE = (232, 97, 0)
+SPANISH_PINK = (247, 191, 190)
+SPANISH_RED = (230, 0, 38)
+SPANISH_SKY_BLUE = (0, 170, 228)
+SPANISH_VIOLET = (76, 40, 130)
+SPANISH_VIRIDIAN = (0, 127, 92)
+SPIRO_DISCO_BALL = (15, 192, 252)
+SPRING_BUD = (167, 252, 0)
+SPRING_GREEN = (0, 255, 127)
+STAR_COMMAND_BLUE = (0, 123, 184)
+STEEL_BLUE = (70, 130, 180)
+STEEL_PINK = (204, 51, 102)
+STIL_DE_GRAIN_YELLOW = (250, 218, 94)
+STIZZA = (153, 0, 0)
+STORMCLOUD = (79, 102, 106)
+STRAW = (228, 217, 111)
+STRAWBERRY = (252, 90, 141)
+SUNGLOW = (255, 204, 51)
+SUNRAY = (227, 171, 87)
+SUNSET = (250, 214, 165)
+SUNSET_ORANGE = (253, 94, 83)
+SUPER_PINK = (207, 107, 169)
+TAN = (210, 180, 140)
+TANGELO = (249, 77, 0)
+TANGERINE = (242, 133, 0)
+TANGERINE_YELLOW = (255, 204, 0)
+TANGO_PINK = (228, 113, 122)
+TAUPE = (72, 60, 50)
+TAUPE_GRAY = (139, 133, 137)
+TEA_GREEN = (208, 240, 192)
+TEA_ROSE = (244, 194, 194)
+TEAL = (0, 128, 128)
+TEAL_BLUE = (54, 117, 136)
+TEAL_DEER = (153, 230, 179)
+TEAL_GREEN = (0, 130, 127)
+TELEMAGENTA = (207, 52, 118)
+TERRA_COTTA = (226, 114, 91)
+THISTLE = (216, 191, 216)
+THULIAN_PINK = (222, 111, 161)
+TICKLE_ME_PINK = (252, 137, 172)
+TIFFANY_BLUE = (10, 186, 181)
+TIGERS_EYE = (224, 141, 60)
+TIMBERWOLF = (219, 215, 210)
+TITANIUM_YELLOW = (238, 230, 0)
+TOMATO = (255, 99, 71)
+TOOLBOX = (116, 108, 192)
+TOPAZ = (255, 200, 124)
+TRACTOR_RED = (253, 14, 53)
+TROLLEY_GREY = (128, 128, 128)
+TROPICAL_RAIN_FOREST = (0, 117, 94)
+TRUE_BLUE = (0, 115, 207)
+TUFTS_BLUE = (65, 125, 193)
+TULIP = (255, 135, 141)
+TUMBLEWEED = (222, 170, 136)
+TURKISH_ROSE = (181, 114, 129)
+TURQUOISE = (64, 224, 208)
+TURQUOISE_BLUE = (0, 255, 239)
+TURQUOISE_GREEN = (160, 214, 180)
+TUSCAN = (250, 214, 165)
+TUSCAN_BROWN = (111, 78, 55)
+TUSCAN_RED = (124, 72, 72)
+TUSCAN_TAN = (166, 123, 91)
+TUSCANY = (192, 153, 153)
+TWILIGHT_LAVENDER = (138, 73, 107)
+TYRIAN_PURPLE = (102, 2, 60)
+UA_BLUE = (0, 51, 170)
+UA_RED = (217, 0, 76)
+UBE = (136, 120, 195)
+UCLA_BLUE = (83, 104, 149)
+UCLA_GOLD = (255, 179, 0)
+UFO_GREEN = (60, 208, 112)
+ULTRAMARINE = (18, 10, 143)
+ULTRAMARINE_BLUE = (65, 102, 245)
+ULTRA_PINK = (255, 111, 255)
+UMBER = (99, 81, 71)
+UNBLEACHED_SILK = (255, 221, 202)
+UNITED_NATIONS_BLUE = (91, 146, 229)
+UNIVERSITY_OF_CALIFORNIA_GOLD = (183, 135, 39)
+UNMELLOW_YELLOW = (255, 255, 102)
+UP_FOREST_GREEN = (1, 68, 33)
+UP_MAROON = (123, 17, 19)
+UPSDELL_RED = (174, 32, 41)
+UROBILIN = (225, 173, 33)
+USAFA_BLUE = (0, 79, 152)
+USC_CARDINAL = (153, 0, 0)
+USC_GOLD = (255, 204, 0)
+UNIVERSITY_OF_TENNESSEE_ORANGE = (247, 127, 0)
+UTAH_CRIMSON = (211, 0, 63)
+VANILLA = (243, 229, 171)
+VANILLA_ICE = (243, 143, 169)
+VEGAS_GOLD = (197, 179, 88)
+VENETIAN_RED = (200, 8, 21)
+VERDIGRIS = (67, 179, 174)
+VERMILION = (227, 66, 52)
+VERONICA = (160, 32, 240)
+VIOLET = (143, 0, 255)
+VIOLET_BLUE = (50, 74, 178)
+VIOLET_RED = (247, 83, 148)
+VIRIDIAN = (64, 130, 109)
+VIRIDIAN_GREEN = (0, 150, 152)
+VIVID_AUBURN = (146, 39, 36)
+VIVID_BURGUNDY = (159, 29, 53)
+VIVID_CERISE = (218, 29, 129)
+VIVID_ORCHID = (204, 0, 255)
+VIVID_SKY_BLUE = (0, 204, 255)
+VIVID_TANGERINE = (255, 160, 137)
+VIVID_VIOLET = (159, 0, 255)
+WARM_BLACK = (0, 66, 66)
+WATERSPOUT = (164, 244, 249)
+WENGE = (100, 84, 82)
+WHEAT = (245, 222, 179)
+WHITE = (255, 255, 255)
+WHITE_SMOKE = (245, 245, 245)
+WILD_BLUE_YONDER = (162, 173, 208)
+WILD_ORCHID = (212, 112, 162)
+WILD_STRAWBERRY = (255, 67, 164)
+WILD_WATERMELON = (252, 108, 133)
+WILLPOWER_ORANGE = (253, 88, 0)
+WINDSOR_TAN = (167, 85, 2)
+WINE = (114, 47, 55)
+WINE_DREGS = (103, 49, 71)
+WISTERIA = (201, 160, 220)
+WOOD_BROWN = (193, 154, 107)
+XANADU = (115, 134, 120)
+YALE_BLUE = (15, 77, 146)
+YANKEES_BLUE = (28, 40, 65)
+YELLOW = (255, 255, 0)
+YELLOW_GREEN = (154, 205, 50)
+YELLOW_ORANGE = (255, 174, 66)
+YELLOW_ROSE = (255, 240, 0)
+ZAFFRE = (0, 20, 168)
+ZINNWALDITE_BROWN = (44, 22, 8)
diff --git a/arcade/csscolor/__init__.py b/arcade/csscolor/__init__.py
new file mode 100644
index 0000000..753b5ec
--- /dev/null
+++ b/arcade/csscolor/__init__.py
@@ -0,0 +1,152 @@
+"""
+This module pre-defines colors as defined by the W3C CSS standard:
+https://www.w3.org/TR/2018/PR-css-color-3-20180315/
+"""
+
+ALICE_BLUE = (240, 248, 255)
+ANTIQUE_WHITE = (250, 235, 215)
+AQUA = (0, 255, 255)
+AQUAMARINE = (127, 255, 212)
+AZURE = (240, 255, 255)
+BEIGE = (245, 245, 220)
+BISQUE = (255, 228, 196)
+BLACK = (0, 0, 0)
+BLANCHED_ALMOND = (255, 235, 205)
+BLUE = (0, 0, 255)
+BLUE_VIOLET = (138, 43, 226)
+BROWN = (165, 42, 42)
+BURLYWOOD = (222, 184, 135)
+CADET_BLUE = (95, 158, 160)
+CHARTREUSE = (127, 255, 0)
+CHOCOLATE = (210, 105, 30)
+CORAL = (255, 127, 80)
+CORNFLOWER_BLUE = (100, 149, 237)
+CORNSILK = (255, 248, 220)
+CRIMSON = (220, 20, 60)
+CYAN = (0, 255, 255)
+DARK_BLUE = (0, 0, 139)
+DARK_CYAN = (0, 139, 139)
+DARK_GOLDENROD = (184, 134, 11)
+DARK_GRAY = (169, 169, 169)
+DARK_GREEN = (0, 100, 0)
+DARK_GREY = (169, 169, 169)
+DARK_KHAKI = (189, 183, 107)
+DARK_MAGENTA = (139, 0, 139)
+DARK_OLIVE_GREEN = (85, 107, 47)
+DARK_ORANGE = (255, 140, 0)
+DARK_ORCHID = (153, 50, 204)
+DARK_RED = (139, 0, 0)
+DARK_SALMON = (233, 150, 122)
+DARK_SEA_GREEN = (143, 188, 143)
+DARK_SLATE_BLUE = (72, 61, 139)
+DARK_SLATE_GRAY = (47, 79, 79)
+DARK_SLATE_GREY = (47, 79, 79)
+DARK_TURQUOISE = (0, 206, 209)
+DARK_VIOLET = (148, 0, 211)
+DEEP_PINK = (255, 20, 147)
+DEEP_SKY_BLUE = (0, 191, 255)
+DIM_GRAY = (105, 105, 105)
+DIM_GREY = (105, 105, 105)
+DODGER_BLUE = (30, 144, 255)
+FIREBRICK = (178, 34, 34)
+FLORAL_WHITE = (255, 250, 240)
+FOREST_GREEN = (34, 139, 34)
+FUCHSIA = (255, 0, 255)
+GAINSBORO = (220, 220, 220)
+GHOST_WHITE = (248, 248, 255)
+GOLD = (255, 215, 0)
+GOLDENROD = (218, 165, 32)
+GRAY = (128, 128, 128)
+GREEN = (0, 128, 0)
+GREENYELLOW = (173, 255, 47)
+GREY = (128, 128, 128)
+HONEYDEW = (240, 255, 240)
+HOTPINK = (255, 105, 180)
+INDIANRED = (205, 92, 92)
+INDIGO = (75, 0, 130)
+IVORY = (255, 255, 240)
+KHAKI = (240, 230, 140)
+LAVENDER = (230, 230, 250)
+LAVENDER_BLUSH = (255, 240, 245)
+LAWNGREEN = (124, 252, 0)
+LEMON_CHIFFON = (255, 250, 205)
+LIGHT_BLUE = (173, 216, 230)
+LIGHT_CORAL = (240, 128, 128)
+LIGHT_CYAN = (224, 255, 255)
+LIGHT_GOLDENROD_YELLOW = (250, 250, 210)
+LIGHT_GRAY = (211, 211, 211)
+LIGHT_GREEN = (144, 238, 144)
+LIGHT_GREY = (211, 211, 211)
+LIGHT_PINK = (255, 182, 193)
+LIGHT_SALMON = (255, 160, 122)
+LIGHT_SEA_GREEN = (32, 178, 170)
+LIGHT_SKY_BLUE = (135, 206, 250)
+LIGHT_SLATE_GRAY = (119, 136, 153)
+LIGHT_SLATE_GREY = (119, 136, 153)
+LIGHT_STEEL_BLUE = (176, 196, 222)
+LIGHT_YELLOW = (255, 255, 224)
+LIME = (0, 255, 0)
+LIME_GREEN = (50, 205, 50)
+LINEN = (250, 240, 230)
+MAGENTA = (255, 0, 255)
+MAROON = (128, 0, 0)
+MEDIUM_AQUAMARINE = (102, 205, 170)
+MEDIUM_BLUE = (0, 0, 205)
+MEDIUM_ORCHID = (186, 85, 211)
+MEDIUM_PURPLE = (147, 112, 219)
+MEDIUM_SEA_GREEN = (60, 179, 113)
+MEDIUM_SLATE_BLUE = (123, 104, 238)
+MEDIUM_SPRING_GREEN = (0, 250, 154)
+MEDIUM_TURQUOISE = (72, 209, 204)
+MEDIUM_VIOLET_RED = (199, 21, 133)
+MIDNIGHT_BLUE = (25, 25, 112)
+MINT_CREAM = (245, 255, 250)
+MISTY_ROSE = (255, 228, 225)
+MOCCASIN = (255, 228, 181)
+NAVAJO_WHITE = (255, 222, 173)
+NAVY = (0, 0, 128)
+OLD_LACE = (253, 245, 230)
+OLIVE = (128, 128, 0)
+OLIVE_DRAB = (107, 142, 35)
+ORANGE = (255, 165, 0)
+ORANGE_RED = (255, 69, 0)
+ORCHID = (218, 112, 214)
+PALE_GOLDENROD = (238, 232, 170)
+PALE_GREEN = (152, 251, 152)
+PALE_TURQUOISE = (175, 238, 238)
+PALE_VIOLET_RED = (219, 112, 147)
+PAPAYA_WHIP = (255, 239, 213)
+PEACH_PUFF = (255, 218, 185)
+PERU = (205, 133, 63)
+PINK = (255, 192, 203)
+PLUM = (221, 160, 221)
+POWDER_BLUE = (176, 224, 230)
+PURPLE = (128, 0, 128)
+RED = (255, 0, 0)
+ROSY_BROWN = (188, 143, 143)
+ROYAL_BLUE = (65, 105, 225)
+SADDLE_BROWN = (139, 69, 19)
+SALMON = (250, 128, 114)
+SANDY_BROWN = (244, 164, 96)
+SEA_GREEN = (46, 139, 87)
+SEASHELL = (255, 245, 238)
+SIENNA = (160, 82, 45)
+SILVER = (192, 192, 192)
+SKY_BLUE = (135, 206, 235)
+SLATE_BLUE = (106, 90, 205)
+SLATE_GRAY = (112, 128, 144)
+SLATE_GREY = (112, 128, 144)
+SNOW = (255, 250, 250)
+SPRING_GREEN = (0, 255, 127)
+STEEL_BLUE = (70, 130, 180)
+TAN = (210, 180, 140)
+TEAL = (0, 128, 128)
+THISTLE = (216, 191, 216)
+TOMATO = (255, 99, 71)
+TURQUOISE = (64, 224, 208)
+VIOLET = (238, 130, 238)
+WHEAT = (245, 222, 179)
+WHITE = (255, 255, 255)
+WHITE_SMOKE = (245, 245, 245)
+YELLOW = (255, 255, 0)
+YELLOW_GREEN = (154, 205)
diff --git a/arcade/draw_commands.py b/arcade/draw_commands.py
new file mode 100644
index 0000000..5f55a16
--- /dev/null
+++ b/arcade/draw_commands.py
@@ -0,0 +1,1342 @@
+"""
+This module contains commands for basic graphics drawing commands.
+(Drawing primitives.)
+
+Many of these commands are slow, because they load everything to the
+graphics card each time a shape is drawn. For faster drawing, see the
+Buffered Draw Commands.
+"""
+# pylint: disable=too-many-arguments, too-many-locals, too-few-public-methods
+
+import PIL.Image
+import PIL.ImageOps
+import PIL.ImageDraw
+import numpy as np
+
+import pyglet.gl as gl
+
+from typing import List
+
+from arcade.window_commands import get_projection
+from arcade.window_commands import get_window
+from arcade.arcade_types import Color
+from arcade.arcade_types import PointList
+from arcade import shader
+from arcade.earclip import earclip
+from arcade.utils import *
+
+line_vertex_shader = '''
+ #version 330
+ uniform mat4 Projection;
+ in vec2 in_vert;
+ in vec4 in_color;
+ out vec4 v_color;
+ void main() {
+ gl_Position = Projection * vec4(in_vert, 0.0, 1.0);
+ v_color = in_color;
+ }
+'''
+
+line_fragment_shader = '''
+ #version 330
+ in vec4 v_color;
+ out vec4 f_color;
+ void main() {
+ f_color = v_color;
+ }
+'''
+
+
+def get_four_byte_color(color: Color) -> Color:
+ """
+ Given a RGB list, it will return RGBA.
+ Given a RGBA list, it will return the same RGBA.
+
+ :param Color color: Three or four byte tuple
+
+ :returns: return: Four byte RGBA tuple
+ """
+
+ if len(color) == 4:
+ return color
+ elif len(color) == 3:
+ return color[0], color[1], color[2], 255
+ else:
+ raise ValueError("This isn't a 3 or 4 byte color")
+
+
+def get_four_float_color(color: Color) -> (float, float, float, float):
+ """
+ Given a 3 or 4 RGB/RGBA color where each color goes 0-255, this
+ returns a RGBA list where each item is a scaled float from 0 to 1.
+
+ :param Color color: Three or four byte tuple
+ :return: Four floats as a RGBA tuple
+ """
+ if len(color) == 4:
+ return color[0] / 255, color[1] / 255, color[2] / 255, color[3] / 255
+ elif len(color) == 3:
+ return color[0] / 255, color[1] / 255, color[2] / 255, 1.0
+ else:
+ raise ValueError("This isn't a 3 or 4 byte color")
+
+
+def make_transparent_color(color: Color, transparency: float):
+ """
+ Given a RGB color, along with an alpha, returns a RGBA color tuple.
+
+ :param Color color: Three or four byte RGBA color
+ :param float transparency: Transparency
+ """
+ return color[0], color[1], color[2], transparency
+
+
+def rotate_point(x: float, y: float, cx: float, cy: float,
+ angle: float) -> (float, float):
+ """
+ Rotate a point around a center.
+
+ :param x: x value of the point you want to rotate
+ :param y: y value of the point you want to rotate
+ :param cx: x value of the center point you want to rotate around
+ :param cy: y value of the center point you want to rotate around
+ :param angle: Angle, in degrees, to rotate
+ :return: Return rotated (x, y) pair
+ :rtype: (float, float)
+ """
+ temp_x = x - cx
+ temp_y = y - cy
+
+ # now apply rotation
+ rotated_x = temp_x * math.cos(math.radians(angle)) - temp_y * math.sin(math.radians(angle))
+ rotated_y = temp_x * math.sin(math.radians(angle)) + temp_y * math.cos(math.radians(angle))
+
+ # translate back
+ rounding_precision = 2
+ x = round(rotated_x + cx, rounding_precision)
+ y = round(rotated_y + cy, rounding_precision)
+
+ return x, y
+
+
+class Texture:
+ """
+ Class that represents a texture.
+ Usually created by the ``load_texture`` or ``load_textures`` commands.
+
+ Attributes:
+ :name:
+ :image:
+ :scale:
+ :width: Width of the texture image in pixels
+ :height: Height of the texture image in pixels
+
+ """
+
+ def __init__(self, name, image=None):
+ self.name = name
+ self.texture = None
+ self.image = image
+ self.scale = 1
+ if image:
+ self.width = image.width
+ self.height = image.height
+ else:
+ self.width = 0
+ self.height = 0
+
+ self._sprite = None
+ self._sprite = None
+
+ def draw(self, center_x: float, center_y: float, width: float,
+ height: float, angle: float = 0,
+ alpha: int = 255, transparent: bool = True,
+ repeat_count_x=1, repeat_count_y=1):
+ """
+
+ Args:
+ center_x:
+ center_y:
+ width:
+ height:
+ angle:
+ alpha: Currently unused.
+ transparent: Currently unused.
+ repeat_count_x: Currently unused.
+ repeat_count_y: Currently unused.
+
+ Returns:
+
+ """
+
+ from arcade.sprite import Sprite
+ from arcade.sprite_list import SpriteList
+
+ if self._sprite is None:
+ self._sprite = Sprite()
+ self._sprite._texture = self
+ self._sprite.textures = [self]
+
+ self._sprite_list = SpriteList()
+ self._sprite_list.append(self._sprite)
+
+ self._sprite.center_x = center_x
+ self._sprite.center_y = center_y
+ self._sprite.width = width
+ self._sprite.height = height
+ self._sprite.angle = angle
+ self._sprite.alpha = alpha
+
+ self._sprite_list.draw()
+
+
+def load_textures(file_name: str,
+ image_location_list: PointList,
+ mirrored: bool = False,
+ flipped: bool = False,
+ scale: float = 1) -> List['Texture']:
+ """
+ Load a set of textures off of a single image file.
+
+ Note, if the code is to load only part of the image, the given x, y
+ coordinates will start with the origin (0, 0) in the upper left of the
+ image. When drawing, Arcade uses (0, 0)
+ in the lower left corner when drawing. Be careful about this reversal.
+
+ For a longer explanation of why computers sometimes start in the upper
+ left, see:
+ http://programarcadegames.com/index.php?chapter=introduction_to_graphics&lang=en#section_5
+
+ :param str file_name: Name of the file.
+ :param PointList image_location_list: List of image locations. Each location should be
+ a list of four floats. ``[x, y, width, height]``.
+ :param bool mirrored: If set to true, the image is mirrored left to right.
+ :param bool flipped: If set to true, the image is flipped upside down.
+ :param float scale: Scale factor to apply on each new texture.
+
+
+ :Returns: List of textures loaded.
+ :Raises: ValueError
+ """
+ # See if we already loaded this texture file, and we can just use a cached version.
+ cache_file_name = "{}".format(file_name)
+ if cache_file_name in load_texture.texture_cache:
+ texture = load_texture.texture_cache[cache_file_name]
+ source_image = texture.image
+ else:
+ source_image = PIL.Image.open(file_name)
+ result = Texture(cache_file_name, source_image)
+ load_texture.texture_cache[cache_file_name] = result
+
+ source_image_width, source_image_height = source_image.size
+ texture_info_list = []
+ for image_location in image_location_list:
+ x, y, width, height = image_location
+
+ if width <= 0:
+ raise ValueError("Texture has a width of {}, must be > 0."
+ .format(width))
+ if x > source_image_width:
+ raise ValueError("Can't load texture starting at an x of {} "
+ "when the image is only {} across."
+ .format(x, source_image_width))
+ if y > source_image_height:
+ raise ValueError("Can't load texture starting at an y of {} "
+ "when the image is only {} high."
+ .format(y, source_image_height))
+ if x + width > source_image_width:
+ raise ValueError("Can't load texture ending at an x of {} "
+ "when the image is only {} wide."
+ .format(x + width, source_image_width))
+ if y + height > source_image_height:
+ raise ValueError("Can't load texture ending at an y of {} "
+ "when the image is only {} high."
+ .format(y + height, source_image_height))
+
+ # See if we already loaded this texture, and we can just use a cached version.
+ cache_name = "{}{}{}{}{}{}{}{}".format(file_name, x, y, width, height, scale, flipped, mirrored)
+ if cache_name in load_texture.texture_cache:
+ result = load_texture.texture_cache[cache_name]
+ else:
+ image = source_image.crop((x, y, x + width, y + height))
+ # image = _trim_image(image)
+
+ if mirrored:
+ image = PIL.ImageOps.mirror(image)
+
+ if flipped:
+ image = PIL.ImageOps.flip(image)
+ result = Texture(cache_name, image)
+ load_texture.texture_cache[cache_name] = result
+ result.scale = scale
+ texture_info_list.append(result)
+
+ return texture_info_list
+
+
+def load_texture(file_name: str, x: float = 0, y: float = 0,
+ width: float = 0, height: float = 0,
+ mirrored: bool = False,
+ flipped: bool = False,
+ scale: float = 1) -> Texture:
+ """
+ Load image from disk and create a texture.
+
+ Note, if the code is to load only part of the image, the given x, y
+ coordinates will start with the origin (0, 0) in the upper left of the
+ image. When drawing, Arcade uses (0, 0)
+ in the lower left corner when drawing. Be careful about this reversal.
+
+ For a longer explanation of why computers sometimes start in the upper
+ left, see:
+ http://programarcadegames.com/index.php?chapter=introduction_to_graphics&lang=en#section_5
+
+ :param str file_name: Name of the file to that holds the texture.
+ :param float x: X position of the crop area of the texture.
+ :param float y: Y position of the crop area of the texture.
+ :param float width: Width of the crop area of the texture.
+ :param float height: Height of the crop area of the texture.
+ :param bool mirrored: True if we mirror the image across the y axis
+ :param bool flipped: True if we flip the image across the x axis
+ :param float scale: Scale factor to apply on the new texture.
+
+ :Returns: The new texture.
+ :raises: None
+
+ """
+
+ # See if we already loaded this texture, and we can just use a cached version.
+ cache_name = "{}{}{}{}{}{}{}{}".format(file_name, x, y, width, height, scale, flipped, mirrored)
+ if cache_name in load_texture.texture_cache:
+ return load_texture.texture_cache[cache_name]
+
+ # See if we already loaded this texture file, and we can just use a cached version.
+ cache_file_name = "{}".format(file_name)
+ if cache_file_name in load_texture.texture_cache:
+ texture = load_texture.texture_cache[cache_file_name]
+ source_image = texture.image
+ else:
+ source_image = PIL.Image.open(file_name)
+ result = Texture(cache_file_name, source_image)
+ load_texture.texture_cache[cache_file_name] = result
+
+ source_image_width, source_image_height = source_image.size
+
+ if x != 0 or y != 0 or width != 0 or height != 0:
+ if x > source_image_width:
+ raise ValueError("Can't load texture starting at an x of {} "
+ "when the image is only {} across."
+ .format(x, source_image_width))
+ if y > source_image_height:
+ raise ValueError("Can't load texture starting at an y of {} "
+ "when the image is only {} high."
+ .format(y, source_image_height))
+ if x + width > source_image_width:
+ raise ValueError("Can't load texture ending at an x of {} "
+ "when the image is only {} wide."
+ .format(x + width, source_image_width))
+ if y + height > source_image_height:
+ raise ValueError("Can't load texture ending at an y of {} "
+ "when the image is only {} high."
+ .format(y + height, source_image_height))
+
+ image = source_image.crop((x, y, x + width, y + height))
+ else:
+ image = source_image
+
+ # image = _trim_image(image)
+ if mirrored:
+ image = PIL.ImageOps.mirror(image)
+
+ if flipped:
+ image = PIL.ImageOps.flip(image)
+
+ result = Texture(cache_name, image)
+ load_texture.texture_cache[cache_name] = result
+ result.scale = scale
+ return result
+
+
+def make_circle_texture(diameter: int, color: Color) -> Texture:
+ """
+ Return a Texture of a circle with given diameter and color
+
+ :param int diameter: Diameter of the circle and dimensions of the square Texture returned
+ :param Color color: Color of the circle
+ :Returns: A Texture object
+ :Raises: None
+ """
+ bg_color = (0, 0, 0, 0) # fully transparent
+ img = PIL.Image.new("RGBA", (diameter, diameter), bg_color)
+ draw = PIL.ImageDraw.Draw(img)
+ draw.ellipse((0, 0, diameter - 1, diameter - 1), fill=color)
+ name = "{}:{}:{}".format("circle_texture", diameter, color) # name must be unique for caching
+ return Texture(name, img)
+
+
+def make_soft_circle_texture(diameter: int, color: Color, center_alpha: int = 255, outer_alpha: int = 0) -> Texture:
+ """
+ Return a Texture of a circle with given diameter, color, and alpha values at its center and edges
+
+ Args:
+ :diameter (int): Diameter of the circle and dimensions of the square Texture returned
+ :color (Color): Color of the circle
+ :center_alpha (int): alpha value of circle at its center
+ :outer_alpha (int): alpha value of circle at its edge
+ Returns:
+ A Texture object
+ Raises:
+ None
+ """
+ # TODO: create a rectangle and circle (and triangle? and arbitrary poly where client passes
+ # in list of points?) particle?
+ bg_color = (0, 0, 0, 0) # fully transparent
+ img = PIL.Image.new("RGBA", (diameter, diameter), bg_color)
+ draw = PIL.ImageDraw.Draw(img)
+ max_radius = int(diameter // 2)
+ center = max_radius # for readability
+ for radius in range(max_radius, 0, -1):
+ alpha = int(lerp(center_alpha, outer_alpha, radius / max_radius))
+ clr = (color[0], color[1], color[2], alpha)
+ draw.ellipse((center - radius, center - radius, center + radius - 1, center + radius - 1), fill=clr)
+ name = "{}:{}:{}:{}:{}".format("soft_circle_texture", diameter, color, center_alpha,
+ outer_alpha) # name must be unique for caching
+ return Texture(name, img)
+
+
+def make_soft_square_texture(size: int, color: Color, center_alpha: int = 255, outer_alpha: int = 0) -> Texture:
+ """
+ Return a Texture of a circle with given diameter and color, fading out at the edges.
+
+ Args:
+ :diameter (int): Diameter of the circle and dimensions of the square Texture returned
+ :color (Color): Color of the circle
+ Returns:
+ The new texture.
+ Raises:
+ None
+ """
+ bg_color = (0, 0, 0, 0) # fully transparent
+ img = PIL.Image.new("RGBA", (size, size), bg_color)
+ draw = PIL.ImageDraw.Draw(img)
+ half_size = int(size // 2)
+ for cur_size in range(0, half_size):
+ alpha = int(lerp(outer_alpha, center_alpha, cur_size / half_size))
+ clr = (color[0], color[1], color[2], alpha)
+ # draw.ellipse((center-radius, center-radius, center+radius, center+radius), fill=clr)
+ draw.rectangle((cur_size, cur_size, size - cur_size, size - cur_size), clr, None)
+ name = "{}:{}:{}:{}".format("gradientsquare", size, color, center_alpha,
+ outer_alpha) # name must be unique for caching
+ return Texture(name, img)
+
+
+def _lerp_color(start_color: Color, end_color: Color, u: float) -> Color:
+ return (
+ int(lerp(start_color[0], end_color[0], u)),
+ int(lerp(start_color[1], end_color[1], u)),
+ int(lerp(start_color[2], end_color[2], u))
+ )
+
+
+load_texture.texture_cache = dict()
+
+
+# --- END TEXTURE FUNCTIONS # # #
+
+
+def trim_image(image: PIL.Image) -> PIL.Image:
+ """
+ Returns an image with extra whitespace cropped out.
+ """
+ bbox = image.getbbox()
+ return image.crop(bbox)
+
+
+# --- BEGIN ARC FUNCTIONS # # #
+
+
+def draw_arc_filled(center_x: float, center_y: float,
+ width: float, height: float,
+ color: Color,
+ start_angle: float, end_angle: float,
+ tilt_angle: float = 0,
+ num_segments: int = 128):
+ """
+ Draw a filled in arc. Useful for drawing pie-wedges, or Pac-Man.
+
+ :param float center_x: x position that is the center of the arc.
+ :param float center_y: y position that is the center of the arc.
+ :param float width: width of the arc.
+ :param float height: height of the arc.
+ :param Color color: color, specified in a list of 3 or 4 bytes in RGB or
+ RGBA format.
+ :param float start_angle: start angle of the arc in degrees.
+ :param float end_angle: end angle of the arc in degrees.
+ :param float tilt_angle: angle the arc is tilted.
+ :param float num_segments: Number of line segments used to draw arc.
+ """
+ unrotated_point_list = [[0, 0]]
+
+ start_segment = int(start_angle / 360 * num_segments)
+ end_segment = int(end_angle / 360 * num_segments)
+
+ for segment in range(start_segment, end_segment + 1):
+ theta = 2.0 * 3.1415926 * segment / num_segments
+
+ x = width * math.cos(theta)
+ y = height * math.sin(theta)
+
+ unrotated_point_list.append((x, y))
+
+ if tilt_angle == 0:
+ uncentered_point_list = unrotated_point_list
+ else:
+ uncentered_point_list = []
+ for point in unrotated_point_list:
+ uncentered_point_list.append(rotate_point(point[0], point[1], 0, 0, tilt_angle))
+
+ point_list = []
+ for point in uncentered_point_list:
+ point_list.append((point[0] + center_x, point[1] + center_y))
+
+ _generic_draw_line_strip(point_list, color, gl.GL_TRIANGLE_FAN)
+
+
+def draw_arc_outline(center_x: float, center_y: float, width: float,
+ height: float, color: Color,
+ start_angle: float, end_angle: float,
+ border_width: float = 1, tilt_angle: float = 0,
+ num_segments: int = 128):
+ """
+ Draw the outside edge of an arc. Useful for drawing curved lines.
+
+ :param float center_x: x position that is the center of the arc.
+ :param float center_y: y position that is the center of the arc.
+ :param float width: width of the arc.
+ :param float height: height of the arc.
+ :param Color color: color, specified in a list of 3 or 4 bytes in RGB or
+ RGBA format.
+ :param float start_angle: start angle of the arc in degrees.
+ :param float end_angle: end angle of the arc in degrees.
+ :param float border_width: width of line in pixels.
+ :param float tilt_angle: angle the arc is tilted.
+ :param int num_segments: float of triangle segments that make up this
+ circle. Higher is better quality, but slower render time.
+ """
+ unrotated_point_list = []
+
+ start_segment = int(start_angle / 360 * num_segments)
+ end_segment = int(end_angle / 360 * num_segments)
+
+ inside_width = width - border_width / 2
+ outside_width = width + border_width / 2
+ inside_height = height - border_width / 2
+ outside_height = height + border_width / 2
+
+ for segment in range(start_segment, end_segment + 1):
+ theta = 2.0 * math.pi * segment / num_segments
+
+ x1 = inside_width * math.cos(theta)
+ y1 = inside_height * math.sin(theta)
+
+ x2 = outside_width * math.cos(theta)
+ y2 = outside_height * math.sin(theta)
+
+ unrotated_point_list.append((x1, y1))
+ unrotated_point_list.append((x2, y2))
+
+ if tilt_angle == 0:
+ uncentered_point_list = unrotated_point_list
+ else:
+ uncentered_point_list = []
+ for point in unrotated_point_list:
+ uncentered_point_list.append(rotate_point(point[0], point[1], 0, 0, tilt_angle))
+
+ point_list = []
+ for point in uncentered_point_list:
+ point_list.append((point[0] + center_x, point[1] + center_y))
+
+ _generic_draw_line_strip(point_list, color, gl.GL_TRIANGLE_STRIP)
+
+
+# --- END ARC FUNCTIONS # # #
+
+
+# --- BEGIN PARABOLA FUNCTIONS # # #
+
+def draw_parabola_filled(start_x: float, start_y: float, end_x: float,
+ height: float, color: Color,
+ tilt_angle: float = 0):
+ """
+ Draws a filled in parabola.
+
+ :param float start_x: The starting x position of the parabola
+ :param float start_y: The starting y position of the parabola
+ :param float end_x: The ending x position of the parabola
+ :param float height: The height of the parabola
+ :param Color color: The color of the parabola
+ :param float tilt_angle: The angle of the tilt of the parabola
+
+ """
+ center_x = (start_x + end_x) / 2
+ center_y = start_y + height
+ start_angle = 0
+ end_angle = 180
+ width = (start_x - end_x)
+ draw_arc_filled(center_x, center_y, width, height, color,
+ start_angle, end_angle, tilt_angle)
+
+
+def draw_parabola_outline(start_x: float, start_y: float, end_x: float,
+ height: float, color: Color,
+ border_width: float = 1, tilt_angle: float = 0):
+ """
+ Draws the outline of a parabola.
+
+ :param float start_x: The starting x position of the parabola
+ :param float start_y: The starting y position of the parabola
+ :param float end_x: The ending x position of the parabola
+ :param float height: The height of the parabola
+ :param Color color: The color of the parabola
+ :param float border_width: The width of the parabola
+ :param float tilt_angle: The angle of the tilt of the parabola
+ """
+ center_x = (start_x + end_x) / 2
+ center_y = start_y + height
+ start_angle = 0
+ end_angle = 180
+ width = (start_x - end_x)
+ draw_arc_outline(center_x, center_y, width, height, color,
+ start_angle, end_angle, border_width, tilt_angle)
+
+
+# --- END PARABOLA FUNCTIONS # # #
+
+
+# --- BEGIN CIRCLE FUNCTIONS # # #
+
+def draw_circle_filled(center_x: float, center_y: float, radius: float,
+ color: Color,
+ num_segments: int = 128):
+ """
+ Draw a filled-in circle.
+
+ :param float center_x: x position that is the center of the circle.
+ :param float center_y: y position that is the center of the circle.
+ :param float radius: width of the circle.
+ :param Color color: color, specified in a list of 3 or 4 bytes in RGB or
+ RGBA format.
+ :param int num_segments: float of triangle segments that make up this
+ circle. Higher is better quality, but slower render time.
+ """
+ width = radius * 2
+ height = radius * 2
+ draw_ellipse_filled(center_x, center_y, width, height, color, num_segments=num_segments)
+
+
+def draw_circle_outline(center_x: float, center_y: float, radius: float,
+ color: Color, border_width: float = 1,
+ num_segments: int = 128):
+ """
+ Draw the outline of a circle.
+
+ :param float center_x: x position that is the center of the circle.
+ :param float center_y: y position that is the center of the circle.
+ :param float radius: width of the circle.
+ :param Color color: color, specified in a list of 3 or 4 bytes in RGB or
+ RGBA format.
+ :param float border_width: Width of the circle outline in pixels.
+ :param int num_segments: Int of triangle segments that make up this
+ circle. Higher is better quality, but slower render time.
+ """
+ width = radius * 2
+ height = radius * 2
+ draw_ellipse_outline(center_x, center_y, width, height,
+ color, border_width, num_segments=num_segments)
+
+
+# --- END CIRCLE FUNCTIONS # # #
+
+
+# --- BEGIN ELLIPSE FUNCTIONS # # #
+
+def draw_ellipse_filled(center_x: float, center_y: float,
+ width: float, height: float, color: Color,
+ tilt_angle: float = 0, num_segments: int = 128):
+ """
+ Draw a filled in ellipse.
+
+ :param float center_x: x position that is the center of the circle.
+ :param float center_y: y position that is the center of the circle.
+ :param float height: height of the ellipse.
+ :param float width: width of the ellipse.
+ :param Color color: color, specified in a list of 3 or 4 bytes in RGB or
+ RGBA format.
+ :param float tilt_angle: Angle in degrees to tilt the ellipse.
+ :param int num_segments: float of triangle segments that make up this
+ circle. Higher is better quality, but slower render time.
+ """
+
+ unrotated_point_list = []
+
+ for segment in range(num_segments):
+ theta = 2.0 * 3.1415926 * segment / num_segments
+
+ x = (width / 2) * math.cos(theta)
+ y = (height / 2) * math.sin(theta)
+
+ unrotated_point_list.append((x, y))
+
+ if tilt_angle == 0:
+ uncentered_point_list = unrotated_point_list
+ else:
+ uncentered_point_list = []
+ for point in unrotated_point_list:
+ uncentered_point_list.append(rotate_point(point[0], point[1], 0, 0, tilt_angle))
+
+ point_list = []
+ for point in uncentered_point_list:
+ point_list.append((point[0] + center_x, point[1] + center_y))
+
+ _generic_draw_line_strip(point_list, color, gl.GL_TRIANGLE_FAN)
+
+
+def draw_ellipse_outline(center_x: float, center_y: float, width: float,
+ height: float, color: Color,
+ border_width: float = 1, tilt_angle: float = 0,
+ num_segments=128):
+ """
+ Draw the outline of an ellipse.
+
+ :param float center_x: x position that is the center of the circle.
+ :param float center_y: y position that is the center of the circle.
+ :param float height: height of the ellipse.
+ :param float width: width of the ellipse.
+ :param Color color: color, specified in a list of 3 or 4 bytes in RGB or
+ RGBA format.
+ :param float border_width: Width of the circle outline in pixels.
+ :param float tilt_angle: Angle in degrees to tilt the ellipse.
+ :param float num_segments: Number of line segments used to make the ellipse
+ """
+
+ if border_width == 1:
+ unrotated_point_list = []
+
+ for segment in range(num_segments):
+ theta = 2.0 * 3.1415926 * segment / num_segments
+
+ x = (width / 2) * math.cos(theta)
+ y = (height / 2) * math.sin(theta)
+
+ unrotated_point_list.append((x, y))
+
+ if tilt_angle == 0:
+ uncentered_point_list = unrotated_point_list
+ else:
+ uncentered_point_list = []
+ for point in unrotated_point_list:
+ uncentered_point_list.append(rotate_point(point[0], point[1], 0, 0, tilt_angle))
+
+ point_list = []
+ for point in uncentered_point_list:
+ point_list.append((point[0] + center_x, point[1] + center_y))
+
+ _generic_draw_line_strip(point_list, color, gl.GL_LINE_LOOP)
+ else:
+
+ unrotated_point_list = []
+
+ start_segment = 0
+ end_segment = num_segments
+
+ inside_width = (width / 2) - border_width / 2
+ outside_width = (width / 2) + border_width / 2
+ inside_height = (height / 2) - border_width / 2
+ outside_height = (height / 2) + border_width / 2
+
+ for segment in range(start_segment, end_segment + 1):
+ theta = 2.0 * math.pi * segment / num_segments
+
+ x1 = inside_width * math.cos(theta)
+ y1 = inside_height * math.sin(theta)
+
+ x2 = outside_width * math.cos(theta)
+ y2 = outside_height * math.sin(theta)
+
+ unrotated_point_list.append((x1, y1))
+ unrotated_point_list.append((x2, y2))
+
+ if tilt_angle == 0:
+ uncentered_point_list = unrotated_point_list
+ else:
+ uncentered_point_list = []
+ for point in unrotated_point_list:
+ uncentered_point_list.append(rotate_point(point[0], point[1], 0, 0, tilt_angle))
+
+ point_list = []
+ for point in uncentered_point_list:
+ point_list.append((point[0] + center_x, point[1] + center_y))
+
+ _generic_draw_line_strip(point_list, color, gl.GL_TRIANGLE_STRIP)
+
+
+# --- END ELLIPSE FUNCTIONS # # #
+
+
+# --- BEGIN LINE FUNCTIONS # # #
+
+def _generic_draw_line_strip(point_list: PointList,
+ color: Color,
+ mode: int = gl.GL_LINE_STRIP):
+ """
+ Draw a line strip. A line strip is a set of continuously connected
+ line segments.
+
+ :param point_list: List of points making up the line. Each point is
+ in a list. So it is a list of lists.
+ :param Color color: color, specified in a list of 3 or 4 bytes in RGB or
+ RGBA format.
+ """
+ program = shader.program(
+ vertex_shader=line_vertex_shader,
+ fragment_shader=line_fragment_shader,
+ )
+ buffer_type = np.dtype([('vertex', '2f4'), ('color', '4B')])
+ data = np.zeros(len(point_list), dtype=buffer_type)
+
+ data['vertex'] = point_list
+
+ color = get_four_byte_color(color)
+ data['color'] = color
+
+ vbo = shader.buffer(data.tobytes())
+ vbo_desc = shader.BufferDescription(
+ vbo,
+ '2f 4B',
+ ('in_vert', 'in_color'),
+ normalized=['in_color']
+ )
+
+ vao_content = [vbo_desc]
+
+ vao = shader.vertex_array(program, vao_content)
+ with vao:
+ program['Projection'] = get_projection().flatten()
+
+ vao.render(mode=mode)
+
+
+def draw_line_strip(point_list: PointList,
+ color: Color, line_width: float = 1):
+ """
+ Draw a multi-point line.
+
+ :param PointList point_list: List of x, y points that make up this strip
+ :param Color color: Color of line strip
+ :param float line_width: Width of the line
+ """
+ if line_width == 1:
+ _generic_draw_line_strip(point_list, color, gl.GL_LINE_STRIP)
+ else:
+ triangle_point_list = []
+ # This needs a lot of improvement
+ last_point = None
+ for point in point_list:
+ if last_point is not None:
+ points = _get_points_for_thick_line(last_point[0], last_point[1], point[0], point[1], line_width)
+ reordered_points = points[1], points[0], points[2], points[3]
+ triangle_point_list.extend(reordered_points)
+ last_point = point
+ _generic_draw_line_strip(triangle_point_list, color, gl.GL_TRIANGLE_STRIP)
+
+
+def _get_points_for_thick_line(start_x: float, start_y:
+ float, end_x: float, end_y: float,
+ line_width: float):
+ vector_x = start_x - end_x
+ vector_y = start_y - end_y
+ perpendicular_x = vector_y
+ perpendicular_y = -vector_x
+ length = math.sqrt(vector_x * vector_x + vector_y * vector_y)
+ normal_x = perpendicular_x / length
+ normal_y = perpendicular_y / length
+ r1_x = start_x + normal_x * line_width / 2
+ r1_y = start_y + normal_y * line_width / 2
+ r2_x = start_x - normal_x * line_width / 2
+ r2_y = start_y - normal_y * line_width / 2
+ r3_x = end_x + normal_x * line_width / 2
+ r3_y = end_y + normal_y * line_width / 2
+ r4_x = end_x - normal_x * line_width / 2
+ r4_y = end_y - normal_y * line_width / 2
+ points = (r1_x, r1_y), (r2_x, r2_y), (r4_x, r4_y), (r3_x, r3_y)
+ return points
+
+
+def draw_line(start_x: float, start_y: float, end_x: float, end_y: float,
+ color: Color, line_width: float = 1):
+ """
+ Draw a line.
+
+ :param float start_x: x position of line starting point.
+ :param float start_y: y position of line starting point.
+ :param float end_x: x position of line ending point.
+ :param float end_y: y position of line ending point.
+ :param Color color: color, specified in a list of 3 or 4 bytes in RGB or
+ RGBA format.
+ :param float line_width: Width of the line in pixels.
+ """
+
+ # points = (start_x, start_y), (end_x, end_y)
+ points = _get_points_for_thick_line(start_x, start_y, end_x, end_y, line_width)
+ triangle_point_list = points[1], points[0], points[2], points[3]
+ _generic_draw_line_strip(triangle_point_list, color, gl.GL_TRIANGLE_STRIP)
+
+
+def draw_lines(point_list: PointList,
+ color: Color,
+ line_width: float = 1):
+ """
+ Draw a set of lines.
+
+ Draw a line between each pair of points specified.
+
+ :param PointList point_list: List of points making up the lines. Each point is
+ in a list. So it is a list of lists.
+ :param Color color: color, specified in a list of 3 or 4 bytes in RGB or
+ RGBA format.
+ :param float line_width: Width of the line in pixels.
+ """
+
+ triangle_point_list = []
+ last_point = None
+ for point in point_list:
+ if last_point is not None:
+ points = _get_points_for_thick_line(last_point[0], last_point[1], point[0], point[1], line_width)
+ reordered_points = points[1], points[0], points[2], points[0], points[2], points[3]
+ triangle_point_list.extend(reordered_points)
+ _generic_draw_line_strip(triangle_point_list, color, gl.GL_TRIANGLES)
+ last_point = None
+ else:
+ last_point = point
+
+
+# --- BEGIN POINT FUNCTIONS # # #
+
+
+def draw_point(x: float, y: float, color: Color, size: float):
+ """
+ Draw a point.
+
+ :param float x: x position of point.
+ :param float y: y position of point.
+ :param Color color: color, specified in a list of 3 or 4 bytes in RGB or
+ RGBA format.
+ :param float size: Size of the point in pixels.
+ """
+ draw_rectangle_filled(x, y, size / 2, size / 2, color)
+
+
+def _get_points_for_points(point_list, size):
+ new_point_list = []
+ hs = size / 2
+ for point in point_list:
+ x = point[0]
+ y = point[1]
+ new_point_list.append((x - hs, y - hs))
+ new_point_list.append((x + hs, y - hs))
+ new_point_list.append((x + hs, y + hs))
+
+ new_point_list.append((x + hs, y + hs))
+ new_point_list.append((x - hs, y - hs))
+ new_point_list.append((x - hs, y + hs))
+
+ return new_point_list
+
+
+def draw_points(point_list: PointList,
+ color: Color, size: float = 1):
+ """
+ Draw a set of points.
+
+ :param PointList point_list: List of points Each point is
+ in a list. So it is a list of lists.
+ :param Color color: color, specified in a list of 3 or 4 bytes in RGB or
+ RGBA format.
+ :param float size: Size of the point in pixels.
+ """
+ new_point_list = _get_points_for_points(point_list, size)
+ _generic_draw_line_strip(new_point_list, color, gl.GL_TRIANGLES)
+
+
+# --- END POINT FUNCTIONS # # #
+
+# --- BEGIN POLYGON FUNCTIONS # # #
+
+
+def draw_polygon_filled(point_list: PointList,
+ color: Color):
+ """
+ Draw a polygon that is filled in.
+
+ Args:
+ :point_list: List of points making up the lines. Each point is
+ in a list. So it is a list of lists.
+ :color: color, specified in a list of 3 or 4 bytes in RGB or
+ RGBA format.
+ Returns:
+ None
+ Raises:
+ None
+ """
+
+ triangle_points = earclip(point_list)
+ flattened_list = [i for g in triangle_points for i in g]
+ _generic_draw_line_strip(flattened_list, color, gl.GL_TRIANGLES)
+
+
+def draw_polygon_outline(point_list: PointList,
+ color: Color, line_width: float = 1):
+ """
+ Draw a polygon outline. Also known as a "line loop."
+
+ :param PointList point_list: List of points making up the lines. Each point is
+ in a list. So it is a list of lists.
+ :param Color color: color, specified in a list of 3 or 4 bytes in RGB or
+ RGBA format.
+ :param int line_width: Width of the line in pixels.
+ """
+ new_point_list = [point for point in point_list]
+ new_point_list.append(point_list[0])
+
+ triangle_point_list = []
+ # This needs a lot of improvement
+ last_point = None
+ for point in new_point_list:
+ if last_point is not None:
+ points = _get_points_for_thick_line(last_point[0], last_point[1], point[0], point[1], line_width)
+ reordered_points = points[1], points[0], points[2], points[3]
+ triangle_point_list.extend(reordered_points)
+ last_point = point
+
+ points = _get_points_for_thick_line(new_point_list[0][0], new_point_list[0][1], new_point_list[1][0],
+ new_point_list[1][1], line_width)
+ triangle_point_list.append(points[1])
+ _generic_draw_line_strip(triangle_point_list, color, gl.GL_TRIANGLE_STRIP)
+
+
+def draw_triangle_filled(x1: float, y1: float,
+ x2: float, y2: float,
+ x3: float, y3: float, color: Color):
+ """
+ Draw a filled in triangle.
+
+ :param float x1: x value of first coordinate.
+ :param float y1: y value of first coordinate.
+ :param float x2: x value of second coordinate.
+ :param float y2: y value of second coordinate.
+ :param float x3: x value of third coordinate.
+ :param float y3: y value of third coordinate.
+ :param Color color: Color of triangle.
+ """
+
+ first_point = (x1, y1)
+ second_point = (x2, y2)
+ third_point = (x3, y3)
+ point_list = (first_point, second_point, third_point)
+ _generic_draw_line_strip(point_list, color, gl.GL_TRIANGLES)
+
+
+def draw_triangle_outline(x1: float, y1: float,
+ x2: float, y2: float,
+ x3: float, y3: float,
+ color: Color,
+ border_width: float = 1):
+ """
+ Draw a the outline of a triangle.
+
+ :param float x1: x value of first coordinate.
+ :param float y1: y value of first coordinate.
+ :param float x2: x value of second coordinate.
+ :param float y2: y value of second coordinate.
+ :param float x3: x value of third coordinate.
+ :param float y3: y value of third coordinate.
+ :param Color color: Color of triangle.
+ :param float border_width: Width of the border in pixels. Defaults to 1.
+ """
+ first_point = [x1, y1]
+ second_point = [x2, y2]
+ third_point = [x3, y3]
+ point_list = (first_point, second_point, third_point)
+ draw_polygon_outline(point_list, color, border_width)
+
+
+# --- END POLYGON FUNCTIONS # # #
+
+
+# --- BEGIN RECTANGLE FUNCTIONS # # #
+
+
+def draw_lrtb_rectangle_outline(left: float, right: float, top: float,
+ bottom: float, color: Color,
+ border_width: float = 1):
+ """
+ Draw a rectangle by specifying left, right, top, and bottom edges.
+
+ :param float left: The x coordinate of the left edge of the rectangle.
+ :param float right: The x coordinate of the right edge of the rectangle.
+ :param float top: The y coordinate of the top of the rectangle.
+ :param float bottom: The y coordinate of the rectangle bottom.
+ :param Color color: The color of the rectangle.
+ :param float border_width: The width of the border in pixels. Defaults to one.
+ :Raises AttributeError: Raised if left > right or top < bottom.
+
+ """
+
+ if left > right:
+ raise AttributeError("Left coordinate must be less than or equal to "
+ "the right coordinate")
+
+ if bottom > top:
+ raise AttributeError("Bottom coordinate must be less than or equal to "
+ "the top coordinate")
+
+ center_x = (left + right) / 2
+ center_y = (top + bottom) / 2
+ width = right - left
+ height = top - bottom
+ draw_rectangle_outline(center_x, center_y, width, height, color,
+ border_width)
+
+
+def draw_xywh_rectangle_outline(bottom_left_x: float, bottom_left_y: float,
+ width: float, height: float,
+ color: Color,
+ border_width: float = 1):
+ """
+ Draw a rectangle extending from bottom left to top right
+
+ :param float bottom_left_x: The x coordinate of the left edge of the rectangle.
+ :param float bottom_left_y: The y coordinate of the bottom of the rectangle.
+ :param float width: The width of the rectangle.
+ :param float height: The height of the rectangle.
+ :param Color color: The color of the rectangle.
+ :param float border_width: The width of the border in pixels. Defaults to one.
+ """
+ center_x = bottom_left_x + (width / 2)
+ center_y = bottom_left_y + (height / 2)
+ draw_rectangle_outline(center_x, center_y, width, height, color,
+ border_width)
+
+
+def draw_rectangle_outline(center_x: float, center_y: float, width: float,
+ height: float, color: Color,
+ border_width: float = 1, tilt_angle: float = 0):
+ """
+ Draw a rectangle outline.
+
+ :param float center_x: x coordinate of top left rectangle point.
+ :param float center_y: y coordinate of top left rectangle point.
+ :param float width: width of the rectangle.
+ :param float height: height of the rectangle.
+ :param Color color: color, specified in a list of 3 or 4 bytes in RGB or
+ RGBA format.
+ :param float border_width: width of the lines, in pixels.
+ :param float tilt_angle: rotation of the rectangle. Defaults to zero.
+ """
+
+ i_lb = center_x - width / 2 + border_width / 2, center_y - height / 2 + border_width / 2
+ i_rb = center_x + width / 2 - border_width / 2, center_y - height / 2 + border_width / 2
+ i_rt = center_x + width / 2 - border_width / 2, center_y + height / 2 - border_width / 2
+ i_lt = center_x - width / 2 + border_width / 2, center_y + height / 2 - border_width / 2
+
+ o_lb = center_x - width / 2 - border_width / 2, center_y - height / 2 - border_width / 2
+ o_rb = center_x + width / 2 + border_width / 2, center_y - height / 2 - border_width / 2
+ o_rt = center_x + width / 2 + border_width / 2, center_y + height / 2 + border_width / 2
+ o_lt = center_x - width / 2 - border_width / 2, center_y + height / 2 + border_width / 2
+
+ point_list = o_lt, i_lt, o_rt, i_rt, o_rb, i_rb, o_lb, i_lb, o_lt, i_lt
+
+ if tilt_angle != 0:
+ point_list_2 = []
+ for point in point_list:
+ new_point = rotate_point(point[0], point[1], center_x, center_y, tilt_angle)
+ point_list_2.append(new_point)
+ point_list = point_list_2
+
+ _generic_draw_line_strip(point_list, color, gl.GL_TRIANGLE_STRIP)
+
+
+def draw_lrtb_rectangle_filled(left: float, right: float, top: float,
+ bottom: float, color: Color):
+ """
+ Draw a rectangle by specifying left, right, top, and bottom edges.
+
+ :param float left: The x coordinate of the left edge of the rectangle.
+ :param float right: The x coordinate of the right edge of the rectangle.
+ :param float top: The y coordinate of the top of the rectangle.
+ :param float bottom: The y coordinate of the rectangle bottom.
+ :param Color color: The color of the rectangle.
+ :Raises AttributeError: Raised if left > right or top < bottom.
+
+ """
+ if left > right:
+ raise AttributeError("Left coordinate {} must be less than or equal "
+ "to the right coordinate {}".format(left, right))
+
+ if bottom > top:
+ raise AttributeError("Bottom coordinate {} must be less than or equal "
+ "to the top coordinate {}".format(bottom, top))
+
+ center_x = (left + right) / 2
+ center_y = (top + bottom) / 2
+ width = right - left + 1
+ height = top - bottom + 1
+ draw_rectangle_filled(center_x, center_y, width, height, color)
+
+
+def draw_xywh_rectangle_filled(bottom_left_x: float, bottom_left_y: float,
+ width: float, height: float,
+ color: Color):
+ """
+ Draw a filled rectangle extending from bottom left to top right
+
+ :param float bottom_left_x: The x coordinate of the left edge of the rectangle.
+ :param float bottom_left_y: The y coordinate of the bottom of the rectangle.
+ :param float width: The width of the rectangle.
+ :param float height: The height of the rectangle.
+ :param Color color: The color of the rectangle.
+ """
+
+ center_x = bottom_left_x + (width / 2)
+ center_y = bottom_left_y + (height / 2)
+ draw_rectangle_filled(center_x, center_y, width, height, color)
+
+
+def draw_rectangle_filled(center_x: float, center_y: float, width: float,
+ height: float, color: Color,
+ tilt_angle: float = 0):
+ """
+ Draw a filled-in rectangle.
+
+ :param float center_x: x coordinate of rectangle center.
+ :param float center_y: y coordinate of rectangle center.
+ :param float width: width of the rectangle.
+ :param float height: height of the rectangle.
+ :param Color color: color, specified in a list of 3 or 4 bytes in RGB or
+ RGBA format.
+ :param float tilt_angle: rotation of the rectangle. Defaults to zero.
+ """
+ p1 = -width // 2 + center_x, -height // 2 + center_y
+ p2 = width // 2 + center_x, -height // 2 + center_y
+ p3 = width // 2 + center_x, height // 2 + center_y
+ p4 = -width // 2 + center_x, height // 2 + center_y
+
+ if tilt_angle != 0:
+ p1 = rotate_point(p1[0], p1[1], center_x, center_y, tilt_angle)
+ p2 = rotate_point(p2[0], p2[1], center_x, center_y, tilt_angle)
+ p3 = rotate_point(p3[0], p3[1], center_x, center_y, tilt_angle)
+ p4 = rotate_point(p4[0], p4[1], center_x, center_y, tilt_angle)
+
+ _generic_draw_line_strip((p1, p2, p4, p3), color, gl.GL_TRIANGLE_STRIP)
+
+
+def draw_texture_rectangle(center_x: float, center_y: float, width: float,
+ height: float, texture: Texture, angle: float = 0,
+ alpha: int = 255,
+ repeat_count_x: int = 1, repeat_count_y: int = 1):
+ """
+ Draw a textured rectangle on-screen.
+
+ :param float center_x: x coordinate of rectangle center.
+ :param float center_y: y coordinate of rectangle center.
+ :param float width: width of the rectangle.
+ :param float height: height of the rectangle.
+ :param int texture: identifier of texture returned from load_texture() call
+ :param float angle: rotation of the rectangle. Defaults to zero.
+ :param float alpha: Transparency of image. 0 is fully transparent, 255 (default) is visible
+ :param int repeat_count_x: Unused for now
+ :param int repeat_count_y: Unused for now
+ """
+
+ texture.draw(center_x, center_y, width,
+ height, angle, alpha,
+ repeat_count_x, repeat_count_y)
+
+
+def draw_xywh_rectangle_textured(bottom_left_x: float, bottom_left_y: float,
+ width: float, height: float,
+ texture: Texture, angle: float = 0,
+ alpha: int = 255,
+ repeat_count_x: int = 1, repeat_count_y: int = 1):
+ """
+ Draw a texture extending from bottom left to top right.
+
+ :param float bottom_left_x: The x coordinate of the left edge of the rectangle.
+ :param float bottom_left_y: The y coordinate of the bottom of the rectangle.
+ :param float width: The width of the rectangle.
+ :param float height: The height of the rectangle.
+ :param int texture: identifier of texture returned from load_texture() call
+ :param float angle: rotation of the rectangle. Defaults to zero.
+ :param int alpha: Transparency of image. 0 is fully transparent, 255 (default) is visible
+ :param int repeat_count_x: Unused for now
+ :param int repeat_count_y: Unused for now
+ """
+
+ center_x = bottom_left_x + (width / 2)
+ center_y = bottom_left_y + (height / 2)
+ draw_texture_rectangle(center_x, center_y,
+ width, height,
+ texture,
+ angle=angle, alpha=alpha)
+
+
+def get_pixel(x: int, y: int):
+ """
+ Given an x, y, will return RGB color value of that point.
+
+ :param int x: x location
+ :param int y: y location
+ :returns: Color
+ """
+ a = (gl.GLubyte * 3)(0)
+ gl.glReadPixels(x, y, 1, 1, gl.GL_RGB, gl.GL_UNSIGNED_BYTE, a)
+ red = a[0]
+ green = a[1]
+ blue = a[2]
+ return red, green, blue
+
+
+def get_image(x: int = 0, y: int = 0, width: int = None, height: int = None):
+ """
+ Get an image from the screen.
+
+ :param int x: Start (left) x location
+ :param int y: Start (top) y location
+ :param int width: Width of image. Leave blank for grabbing the 'rest' of the image
+ :param int height: Height of image. Leave blank for grabbing the 'rest' of the image
+
+ You can save the image like:
+
+ .. highlight:: python
+ .. code-block:: python
+
+ image = get_image()
+ image.save('screenshot.png', 'PNG')
+ """
+
+ # Get the dimensions
+ window = get_window()
+ if width is None:
+ width = window.width - x
+ if height is None:
+ height = window.height - y
+
+ # Create an image buffer
+ image_buffer = (gl.GLubyte * (4 * width * height))(0)
+
+ gl.glReadPixels(x, y, width, height, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, image_buffer)
+ image = PIL.Image.frombytes("RGBA", (width, height), image_buffer)
+ image = PIL.ImageOps.flip(image)
+
+ # image.save('glutout.png', 'PNG')
+ return image
diff --git a/arcade/earclip.py b/arcade/earclip.py
new file mode 100644
index 0000000..4a15a1d
--- /dev/null
+++ b/arcade/earclip.py
@@ -0,0 +1,113 @@
+"""
+
+from: https://github.com/linuxlewis/tripy/blob/master/tripy.py
+"""
+
+from collections import namedtuple
+
+Point = namedtuple('Point', ['x', 'y'])
+
+
+def earclip(polygon):
+ """
+ Simple earclipping algorithm for a given polygon p.
+ polygon is expected to be an array of 2-tuples of the cartesian points of the polygon
+ For a polygon with n points it will return n-2 triangles.
+ The triangles are returned as an array of 3-tuples where each item in the tuple is a 2-tuple of the cartesian point.
+
+ Implementation Reference:
+ - https://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf
+ """
+ ear_vertex = []
+ triangles = []
+
+ polygon = [Point(*point) for point in polygon]
+
+ if _is_clockwise(polygon):
+ polygon.reverse()
+
+ point_count = len(polygon)
+ for i in range(point_count):
+ prev_index = i - 1
+ prev_point = polygon[prev_index]
+ point = polygon[i]
+ next_index = (i + 1) % point_count
+ next_point = polygon[next_index]
+
+ if _is_ear(prev_point, point, next_point, polygon):
+ ear_vertex.append(point)
+
+ while ear_vertex and point_count >= 3:
+ ear = ear_vertex.pop(0)
+ i = polygon.index(ear)
+ prev_index = i - 1
+ prev_point = polygon[prev_index]
+ next_index = (i + 1) % point_count
+ next_point = polygon[next_index]
+
+ polygon.remove(ear)
+ point_count -= 1
+ triangles.append(((prev_point.x, prev_point.y), (ear.x, ear.y), (next_point.x, next_point.y)))
+ if point_count > 3:
+ prev_prev_point = polygon[prev_index - 1]
+ next_next_index = (i + 1) % point_count
+ next_next_point = polygon[next_next_index]
+
+ groups = [
+ (prev_prev_point, prev_point, next_point, polygon),
+ (prev_point, next_point, next_next_point, polygon)
+ ]
+ for group in groups:
+ p = group[1]
+ if _is_ear(*group):
+ if p not in ear_vertex:
+ ear_vertex.append(p)
+ elif p in ear_vertex:
+ ear_vertex.remove(p)
+ return triangles
+
+
+def _is_clockwise(polygon):
+ s = 0
+ polygon_count = len(polygon)
+ for i in range(polygon_count):
+ point = polygon[i]
+ point2 = polygon[(i + 1) % polygon_count]
+ s += (point2.x - point.x) * (point2.y + point.y)
+ return s > 0
+
+
+def _is_convex(prev, point, next_point):
+ return _triangle_sum(prev.x, prev.y, point.x, point.y, next_point.x, next_point.y) < 0
+
+
+def _is_ear(p1, p2, p3, polygon):
+ ear = _contains_no_points(p1, p2, p3, polygon) and \
+ _is_convex(p1, p2, p3) and \
+ _triangle_area(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y) > 0
+ return ear
+
+
+def _contains_no_points(p1, p2, p3, polygon):
+ for pn in polygon:
+ if pn in (p1, p2, p3):
+ continue
+ elif _is_point_inside(pn, p1, p2, p3):
+ return False
+ return True
+
+
+def _is_point_inside(p, a, b, c):
+ area = _triangle_area(a.x, a.y, b.x, b.y, c.x, c.y)
+ area1 = _triangle_area(p.x, p.y, b.x, b.y, c.x, c.y)
+ area2 = _triangle_area(p.x, p.y, a.x, a.y, c.x, c.y)
+ area3 = _triangle_area(p.x, p.y, a.x, a.y, b.x, b.y)
+ return area == sum([area1, area2, area3])
+
+
+def _triangle_area(x1, y1, x2, y2, x3, y3):
+ return abs((x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2.0)
+
+
+def _triangle_sum(x1, y1, x2, y2, x3, y3):
+ return x1 * (y3 - y2) + x2 * (y1 - y3) + x3 * (y2 - y1)
diff --git a/arcade/emitter.py b/arcade/emitter.py
new file mode 100644
index 0000000..a47b740
--- /dev/null
+++ b/arcade/emitter.py
@@ -0,0 +1,179 @@
+"""
+Emitter - Invisible object that determines when Particles are emitted, actually emits them, and manages them
+over their lifetime
+"""
+
+import arcade
+from arcade.particle import Particle
+from typing import Callable, cast
+from arcade.utils import _Vec2
+from arcade.arcade_types import Point, Vector
+
+
+##########
+class EmitController:
+ """Base class for how a client configure the rate at which an Emitter emits Particles
+
+ Subclasses allow the client to control the rate and duration of emitting"""
+ def how_many(self, delta_time: float, current_particle_count: int) -> int:
+ raise NotImplemented("EmitterRate.how_many must be implemented")
+
+ def is_complete(self) -> bool:
+ raise NotImplemented("EmitterRate.is_complete must be implemented")
+
+
+class EmitBurst(EmitController):
+ """Used to configure an Emitter to emit particles in one burst"""
+ def __init__(self, count: int):
+ self._is_complete = False
+ self._count = count
+
+ def how_many(self, delta_time: float, current_particle_count: int) -> int:
+ if not self._is_complete:
+ self._is_complete = True
+ return self._count
+ return 0
+
+ def is_complete(self) -> bool:
+ return True
+
+
+class EmitMaintainCount(EmitController):
+ """Used to configure an Emitter so it emits particles so that the given count is always maintained"""
+ def __init__(self, particle_count: int):
+ self._target_count = particle_count
+
+ def how_many(self, delta_time: float, current_particle_count: int) -> int:
+ return self._target_count - current_particle_count
+
+ def is_complete(self) -> bool:
+ return False
+
+
+class EmitInterval(EmitController):
+ """Base class used to configure an Emitter to have a constant rate of emitting. Will emit indefinitely."""
+ def __init__(self, emit_interval: float):
+ self._emit_interval = emit_interval
+ self._carryover_time = 0.0
+
+ def how_many(self, delta_time: float, current_particle_count: int) -> int:
+ self._carryover_time += delta_time
+ emit_count = 0
+ while self._carryover_time >= self._emit_interval:
+ self._carryover_time -= self._emit_interval
+ emit_count += 1
+ return emit_count
+
+ def is_complete(self) -> bool:
+ return False
+
+
+class EmitterIntervalWithCount(EmitInterval):
+ """Configure an Emitter to emit particles with given interval, ending after emitting given number of particles"""
+ def __init__(self, emit_interval: float, particle_count: int):
+ super().__init__(emit_interval)
+ self._count_remaining = particle_count
+
+ def how_many(self, delta_time: float, current_particle_count: int) -> int:
+ proposed_count = super().how_many(delta_time, current_particle_count)
+ actual_count = min(proposed_count, self._count_remaining)
+ self._count_remaining -= actual_count
+ return actual_count
+
+ def is_complete(self) -> bool:
+ return self._count_remaining <= 0
+
+
+class EmitterIntervalWithTime(EmitInterval):
+ """Configure an Emitter to emit particles with given interval, ending after given number of seconds"""
+ def __init__(self, emit_interval: float, lifetime: float):
+ super().__init__(emit_interval)
+ self._lifetime = lifetime
+
+ def how_many(self, delta_time: float, current_particle_count: int) -> int:
+ if self._lifetime <= 0.0:
+ return 0
+ self._lifetime -= delta_time
+ return super().how_many(delta_time, current_particle_count)
+
+ def is_complete(self) -> bool:
+ return self._lifetime <= 0
+
+
+# Emitter
+class Emitter:
+ """Emits and manages Particles over their lifetime. The foundational class in a particle system."""
+ def __init__(
+ self,
+ center_xy: Point,
+ emit_controller: EmitController,
+ particle_factory: Callable[["Emitter"], Particle],
+ change_xy: Vector = (0.0, 0.0),
+ emit_done_cb: Callable[["Emitter"], None] = None,
+ reap_cb: Callable[[], None] = None
+ ):
+ # Note Self-reference with type annotations:
+ # https://www.python.org/dev/peps/pep-0484/#the-problem-of-forward-declarations
+ self.change_x = change_xy[0]
+ self.change_y = change_xy[1]
+
+ self.center_x = center_xy[0]
+ self.center_y = center_xy[1]
+ self.angle = 0.0
+ self.change_angle = 0.0
+ self.rate_factory = emit_controller
+ self.particle_factory = particle_factory
+ self._emit_done_cb = emit_done_cb
+ self._reap_cb = reap_cb
+ self._particles = arcade.SpriteList(use_spatial_hash=False)
+
+ def _emit(self):
+ """Emit one particle, its initial position and velocity are relative to the position and angle of the emitter"""
+ p = self.particle_factory(self)
+ p.center_x += self.center_x
+ p.center_y += self.center_y
+
+ # given the velocity, rotate it by emitter's current angle
+ vel = _Vec2(p.change_x, p.change_y).rotated(self.angle)
+
+ p.change_x = vel.x
+ p.change_y = vel.y
+ self._particles.append(p)
+
+ def get_count(self):
+ return len(self._particles)
+
+ def get_pos(self) -> Point:
+ """Get position of emitter"""
+ # TODO: should this be a property so a method call isn't needed?
+ return self.center_x, self.center_y
+
+ def update(self):
+ # update emitter
+ self.center_x += self.change_x
+ self.center_y += self.change_y
+ self.angle += self.change_angle
+
+ # update particles
+ emit_count = self.rate_factory.how_many(1 / 60, len(self._particles))
+ for _ in range(emit_count):
+ self._emit()
+ self._particles.update()
+ particles_to_reap = [p for p in self._particles if cast(Particle, p).can_reap()]
+ for dead_particle in particles_to_reap:
+ dead_particle.kill()
+
+ def draw(self):
+ self._particles.draw()
+
+ def can_reap(self):
+ """Determine if Emitter can be deleted"""
+ is_emit_complete = self.rate_factory.is_complete()
+ can_reap = is_emit_complete and len(self._particles) <= 0
+ if is_emit_complete and self._emit_done_cb:
+ self._emit_done_cb(self)
+ self._emit_done_cb = None
+ if can_reap and self._reap_cb:
+ self._reap_cb()
+ self._reap_cb = None
+ return can_reap
diff --git a/arcade/emitter_simple.py b/arcade/emitter_simple.py
new file mode 100644
index 0000000..7efbf1d
--- /dev/null
+++ b/arcade/emitter_simple.py
@@ -0,0 +1,62 @@
+"""
+Convenience functions that provide a much simpler interface to Emitters and Particles.
+
+These trade away some flexibility in favor of simplicity to allow beginners to start using particle systems.
+"""
+
+import arcade
+import random
+from typing import List
+from arcade.arcade_types import Point
+from arcade.particle import FilenameOrTexture
+
+
+def make_burst_emitter(
+ center_xy: Point,
+ filenames_and_textures: List[FilenameOrTexture],
+ particle_count: int,
+ particle_speed: float,
+ particle_lifetime_min: float,
+ particle_lifetime_max: float,
+ particle_scale: float = 1.0,
+ fade_particles: bool = True):
+ """Returns an emitter that emits all of its particles at once"""
+ particle_factory = arcade.LifetimeParticle
+ if fade_particles:
+ particle_factory = arcade.FadeParticle
+ return arcade.Emitter(
+ center_xy=center_xy,
+ emit_controller=arcade.EmitBurst(particle_count),
+ particle_factory=lambda emitter: particle_factory(
+ filename_or_texture=random.choice(filenames_and_textures),
+ change_xy=arcade.rand_in_circle((0.0, 0.0), particle_speed),
+ lifetime=random.uniform(particle_lifetime_min, particle_lifetime_max),
+ scale=particle_scale
+ )
+ )
+
+
+def make_interval_emitter(
+ center_xy: Point,
+ filenames_and_textures: List[FilenameOrTexture],
+ emit_interval: float,
+ emit_duration: float,
+ particle_speed: float,
+ particle_lifetime_min: float,
+ particle_lifetime_max: float,
+ particle_scale: float = 1.0,
+ fade_particles: bool = True):
+ """Returns an emitter that emits its particles at a constant rate for a given amount of time"""
+ particle_factory = arcade.LifetimeParticle
+ if fade_particles:
+ particle_factory = arcade.FadeParticle
+ return arcade.Emitter(
+ center_xy=center_xy,
+ emit_controller=arcade.EmitterIntervalWithTime(emit_interval, emit_duration),
+ particle_factory=lambda emitter: particle_factory(
+ filename_or_texture=random.choice(filenames_and_textures),
+ change_xy=arcade.rand_on_circle((0.0, 0.0), particle_speed),
+ lifetime=random.uniform(particle_lifetime_min, particle_lifetime_max),
+ scale=particle_scale
+ )
+ )
diff --git a/arcade/examples/__init__.py b/arcade/examples/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/arcade/examples/array_backed_grid.py b/arcade/examples/array_backed_grid.py
new file mode 100644
index 0000000..01b7cdb
--- /dev/null
+++ b/arcade/examples/array_backed_grid.py
@@ -0,0 +1,113 @@
+"""
+Array Backed Grid
+
+Show how to use a two-dimensional list/array to back the display of a
+grid on-screen.
+
+Note: Regular drawing commands are slow. Particularly when drawing a lot of
+items, like the rectangles in this example.
+
+For faster drawing, create the shapes and then draw them as a batch.
+See array_backed_grid_buffered.py
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.array_backed_grid
+"""
+import arcade
+
+# Set how many rows and columns we will have
+ROW_COUNT = 15
+COLUMN_COUNT = 15
+
+# This sets the WIDTH and HEIGHT of each grid location
+WIDTH = 30
+HEIGHT = 30
+
+# This sets the margin between each cell
+# and on the edges of the screen.
+MARGIN = 5
+
+# Do the math to figure out our screen dimensions
+SCREEN_WIDTH = (WIDTH + MARGIN) * COLUMN_COUNT + MARGIN
+SCREEN_HEIGHT = (HEIGHT + MARGIN) * ROW_COUNT + MARGIN
+SCREEN_TITLE = "Array Backed Grid Example"
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ """
+ Set up the application.
+ """
+
+ super().__init__(width, height, title)
+
+ # Create a 2 dimensional array. A two dimensional
+ # array is simply a list of lists.
+ self.grid = []
+ for row in range(ROW_COUNT):
+ # Add an empty array that will hold each cell
+ # in this row
+ self.grid.append([])
+ for column in range(COLUMN_COUNT):
+ self.grid[row].append(0) # Append a cell
+
+ arcade.set_background_color(arcade.color.BLACK)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw the grid
+ for row in range(ROW_COUNT):
+ for column in range(COLUMN_COUNT):
+ # Figure out what color to draw the box
+ if self.grid[row][column] == 1:
+ color = arcade.color.GREEN
+ else:
+ color = arcade.color.WHITE
+
+ # Do the math to figure out where the box is
+ x = (MARGIN + WIDTH) * column + MARGIN + WIDTH // 2
+ y = (MARGIN + HEIGHT) * row + MARGIN + HEIGHT // 2
+
+ # Draw the box
+ arcade.draw_rectangle_filled(x, y, WIDTH, HEIGHT, color)
+
+ def on_mouse_press(self, x, y, button, modifiers):
+ """
+ Called when the user presses a mouse button.
+ """
+
+ # Change the x/y screen coordinates to grid coordinates
+ column = x // (WIDTH + MARGIN)
+ row = y // (HEIGHT + MARGIN)
+
+ print(f"Click coordinates: ({x}, {y}). Grid coordinates: ({row}, {column})")
+
+ # Make sure we are on-grid. It is possible to click in the upper right
+ # corner in the margin and go to a grid location that doesn't exist
+ if row < ROW_COUNT and column < COLUMN_COUNT:
+
+ # Flip the location between 1 and 0.
+ if self.grid[row][column] == 0:
+ self.grid[row][column] = 1
+ else:
+ self.grid[row][column] = 0
+
+
+def main():
+
+ MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/array_backed_grid_buffered.py b/arcade/examples/array_backed_grid_buffered.py
new file mode 100644
index 0000000..a7a864a
--- /dev/null
+++ b/arcade/examples/array_backed_grid_buffered.py
@@ -0,0 +1,111 @@
+"""
+Array Backed Grid
+
+Show how to use a two-dimensional list/array to back the display of a
+grid on-screen.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.array_backed_grid_buffered
+"""
+import arcade
+
+# Set how many rows and columns we will have
+ROW_COUNT = 15
+COLUMN_COUNT = 15
+
+# This sets the WIDTH and HEIGHT of each grid location
+WIDTH = 30
+HEIGHT = 30
+
+# This sets the margin between each cell
+# and on the edges of the screen.
+MARGIN = 5
+
+# Do the math to figure out our screen dimensions
+SCREEN_WIDTH = (WIDTH + MARGIN) * COLUMN_COUNT + MARGIN
+SCREEN_HEIGHT = (HEIGHT + MARGIN) * ROW_COUNT + MARGIN
+SCREEN_TITLE = "Array Backed Grid Buffered Example"
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ """
+ Set up the application.
+ """
+ super().__init__(width, height, title)
+
+ self.shape_list = None
+
+ # Create a 2 dimensional array. A two dimensional
+ # array is simply a list of lists.
+ self.grid = []
+ for row in range(ROW_COUNT):
+ # Add an empty array that will hold each cell
+ # in this row
+ self.grid.append([])
+ for column in range(COLUMN_COUNT):
+ self.grid[row].append(0) # Append a cell
+
+ arcade.set_background_color(arcade.color.BLACK)
+ self.recreate_grid()
+
+ def recreate_grid(self):
+ self.shape_list = arcade.ShapeElementList()
+ for row in range(ROW_COUNT):
+ for column in range(COLUMN_COUNT):
+ if self.grid[row][column] == 0:
+ color = arcade.color.WHITE
+ else:
+ color = arcade.color.GREEN
+
+ x = (MARGIN + WIDTH) * column + MARGIN + WIDTH // 2
+ y = (MARGIN + HEIGHT) * row + MARGIN + HEIGHT // 2
+
+ current_rect = arcade.create_rectangle_filled(x, y, WIDTH, HEIGHT, color)
+ self.shape_list.append(current_rect)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ self.shape_list.draw()
+
+ def on_mouse_press(self, x, y, button, modifiers):
+ """
+ Called when the user presses a mouse button.
+ """
+
+ # Change the x/y screen coordinates to grid coordinates
+ column = x // (WIDTH + MARGIN)
+ row = y // (HEIGHT + MARGIN)
+
+ print(f"Click coordinates: ({x}, {y}). Grid coordinates: ({row}, {column})")
+
+ # Make sure we are on-grid. It is possible to click in the upper right
+ # corner in the margin and go to a grid location that doesn't exist
+ if row < ROW_COUNT and column < COLUMN_COUNT:
+
+ # Flip the location between 1 and 0.
+ if self.grid[row][column] == 0:
+ self.grid[row][column] = 1
+ else:
+ self.grid[row][column] = 0
+
+ self.recreate_grid()
+
+
+def main():
+ MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/asteroid_smasher.py b/arcade/examples/asteroid_smasher.py
new file mode 100644
index 0000000..25c1c11
--- /dev/null
+++ b/arcade/examples/asteroid_smasher.py
@@ -0,0 +1,392 @@
+"""
+Asteroid Smasher
+
+Shoot space rocks in this demo program created with
+Python and the Arcade library.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.asteroid_smasher
+"""
+import random
+import math
+import arcade
+import os
+
+STARTING_ASTEROID_COUNT = 3
+SCALE = 0.5
+OFFSCREEN_SPACE = 300
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Asteroid Smasher"
+LEFT_LIMIT = -OFFSCREEN_SPACE
+RIGHT_LIMIT = SCREEN_WIDTH + OFFSCREEN_SPACE
+BOTTOM_LIMIT = -OFFSCREEN_SPACE
+TOP_LIMIT = SCREEN_HEIGHT + OFFSCREEN_SPACE
+
+
+class TurningSprite(arcade.Sprite):
+ """ Sprite that sets its angle to the direction it is traveling in. """
+ def update(self):
+ super().update()
+ self.angle = math.degrees(math.atan2(self.change_y, self.change_x))
+
+
+class ShipSprite(arcade.Sprite):
+ """
+ Sprite that represents our space ship.
+
+ Derives from arcade.Sprite.
+ """
+ def __init__(self, filename, scale):
+ """ Set up the space ship. """
+
+ # Call the parent Sprite constructor
+ super().__init__(filename, scale)
+
+ # Info on where we are going.
+ # Angle comes in automatically from the parent class.
+ self.thrust = 0
+ self.speed = 0
+ self.max_speed = 4
+ self.drag = 0.05
+ self.respawning = 0
+
+ # Mark that we are respawning.
+ self.respawn()
+
+ def respawn(self):
+ """
+ Called when we die and need to make a new ship.
+ 'respawning' is an invulnerability timer.
+ """
+ # If we are in the middle of respawning, this is non-zero.
+ self.respawning = 1
+ self.center_x = SCREEN_WIDTH / 2
+ self.center_y = SCREEN_HEIGHT / 2
+ self.angle = 0
+
+ def update(self):
+ """
+ Update our position and other particulars.
+ """
+ if self.respawning:
+ self.respawning += 1
+ self.alpha = self.respawning
+ if self.respawning > 250:
+ self.respawning = 0
+ self.alpha = 255
+ if self.speed > 0:
+ self.speed -= self.drag
+ if self.speed < 0:
+ self.speed = 0
+
+ if self.speed < 0:
+ self.speed += self.drag
+ if self.speed > 0:
+ self.speed = 0
+
+ self.speed += self.thrust
+ if self.speed > self.max_speed:
+ self.speed = self.max_speed
+ if self.speed < -self.max_speed:
+ self.speed = -self.max_speed
+
+ self.change_x = -math.sin(math.radians(self.angle)) * self.speed
+ self.change_y = math.cos(math.radians(self.angle)) * self.speed
+
+ self.center_x += self.change_x
+ self.center_y += self.change_y
+
+ """ Call the parent class. """
+ super().update()
+
+
+class AsteroidSprite(arcade.Sprite):
+ """ Sprite that represents an asteroid. """
+
+ def __init__(self, image_file_name, scale):
+ super().__init__(image_file_name, scale=scale)
+ self.size = 0
+
+ def update(self):
+ """ Move the asteroid around. """
+ super().update()
+ if self.center_x < LEFT_LIMIT:
+ self.center_x = RIGHT_LIMIT
+ if self.center_x > RIGHT_LIMIT:
+ self.center_x = LEFT_LIMIT
+ if self.center_y > TOP_LIMIT:
+ self.center_y = BOTTOM_LIMIT
+ if self.center_y < BOTTOM_LIMIT:
+ self.center_y = TOP_LIMIT
+
+
+class BulletSprite(TurningSprite):
+ """
+ Class that represents a bullet.
+
+ Derives from arcade.TurningSprite which is just a Sprite
+ that aligns to its direction.
+ """
+
+ def update(self):
+ super().update()
+ if self.center_x < -100 or self.center_x > 1500 or \
+ self.center_y > 1100 or self.center_y < -100:
+ self.kill()
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self):
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ self.frame_count = 0
+
+ self.game_over = False
+
+ # Sprite lists
+ self.all_sprites_list = None
+ self.asteroid_list = None
+ self.bullet_list = None
+ self.ship_life_list = None
+
+ # Set up the player
+ self.score = 0
+ self.player_sprite = None
+ self.lives = 3
+
+ # Sounds
+ self.laser_sound = arcade.load_sound("sounds/laser1.wav")
+
+ def start_new_game(self):
+ """ Set up the game and initialize the variables. """
+
+ self.frame_count = 0
+ self.game_over = False
+
+ # Sprite lists
+ self.all_sprites_list = arcade.SpriteList()
+ self.asteroid_list = arcade.SpriteList()
+ self.bullet_list = arcade.SpriteList()
+ self.ship_life_list = arcade.SpriteList()
+
+ # Set up the player
+ self.score = 0
+ self.player_sprite = ShipSprite("images/playerShip1_orange.png", SCALE)
+ self.all_sprites_list.append(self.player_sprite)
+ self.lives = 3
+
+ # Set up the little icons that represent the player lives.
+ cur_pos = 10
+ for i in range(self.lives):
+ life = arcade.Sprite("images/playerLife1_orange.png", SCALE)
+ life.center_x = cur_pos + life.width
+ life.center_y = life.height
+ cur_pos += life.width
+ self.all_sprites_list.append(life)
+ self.ship_life_list.append(life)
+
+ # Make the asteroids
+ image_list = ("images/meteorGrey_big1.png",
+ "images/meteorGrey_big2.png",
+ "images/meteorGrey_big3.png",
+ "images/meteorGrey_big4.png")
+ for i in range(STARTING_ASTEROID_COUNT):
+ image_no = random.randrange(4)
+ enemy_sprite = AsteroidSprite(image_list[image_no], SCALE)
+ enemy_sprite.guid = "Asteroid"
+
+ enemy_sprite.center_y = random.randrange(BOTTOM_LIMIT, TOP_LIMIT)
+ enemy_sprite.center_x = random.randrange(LEFT_LIMIT, RIGHT_LIMIT)
+
+ enemy_sprite.change_x = random.random() * 2 - 1
+ enemy_sprite.change_y = random.random() * 2 - 1
+
+ enemy_sprite.change_angle = (random.random() - 0.5) * 2
+ enemy_sprite.size = 4
+ self.all_sprites_list.append(enemy_sprite)
+ self.asteroid_list.append(enemy_sprite)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.all_sprites_list.draw()
+
+ # Put the text on the screen.
+ output = f"Score: {self.score}"
+ arcade.draw_text(output, 10, 70, arcade.color.WHITE, 13)
+
+ output = f"Asteroid Count: {len(self.asteroid_list)}"
+ arcade.draw_text(output, 10, 50, arcade.color.WHITE, 13)
+
+ def on_key_press(self, symbol, modifiers):
+ """ Called whenever a key is pressed. """
+ # Shoot if the player hit the space bar and we aren't respawning.
+ if not self.player_sprite.respawning and symbol == arcade.key.SPACE:
+ bullet_sprite = BulletSprite("images/laserBlue01.png", SCALE)
+ bullet_sprite.guid = "Bullet"
+
+ bullet_speed = 13
+ bullet_sprite.change_y = \
+ math.cos(math.radians(self.player_sprite.angle)) * bullet_speed
+ bullet_sprite.change_x = \
+ -math.sin(math.radians(self.player_sprite.angle)) \
+ * bullet_speed
+
+ bullet_sprite.center_x = self.player_sprite.center_x
+ bullet_sprite.center_y = self.player_sprite.center_y
+ bullet_sprite.update()
+
+ self.all_sprites_list.append(bullet_sprite)
+ self.bullet_list.append(bullet_sprite)
+
+ arcade.play_sound(self.laser_sound)
+
+ if symbol == arcade.key.LEFT:
+ self.player_sprite.change_angle = 3
+ elif symbol == arcade.key.RIGHT:
+ self.player_sprite.change_angle = -3
+ elif symbol == arcade.key.UP:
+ self.player_sprite.thrust = 0.15
+ elif symbol == arcade.key.DOWN:
+ self.player_sprite.thrust = -.2
+
+ def on_key_release(self, symbol, modifiers):
+ """ Called whenever a key is released. """
+ if symbol == arcade.key.LEFT:
+ self.player_sprite.change_angle = 0
+ elif symbol == arcade.key.RIGHT:
+ self.player_sprite.change_angle = 0
+ elif symbol == arcade.key.UP:
+ self.player_sprite.thrust = 0
+ elif symbol == arcade.key.DOWN:
+ self.player_sprite.thrust = 0
+
+ def split_asteroid(self, asteroid: AsteroidSprite):
+ """ Split an asteroid into chunks. """
+ x = asteroid.center_x
+ y = asteroid.center_y
+ self.score += 1
+
+ if asteroid.size == 4:
+ for i in range(3):
+ image_no = random.randrange(2)
+ image_list = ["images/meteorGrey_med1.png",
+ "images/meteorGrey_med2.png"]
+
+ enemy_sprite = AsteroidSprite(image_list[image_no],
+ SCALE * 1.5)
+
+ enemy_sprite.center_y = y
+ enemy_sprite.center_x = x
+
+ enemy_sprite.change_x = random.random() * 2.5 - 1.25
+ enemy_sprite.change_y = random.random() * 2.5 - 1.25
+
+ enemy_sprite.change_angle = (random.random() - 0.5) * 2
+ enemy_sprite.size = 3
+
+ self.all_sprites_list.append(enemy_sprite)
+ self.asteroid_list.append(enemy_sprite)
+ elif asteroid.size == 3:
+ for i in range(3):
+ image_no = random.randrange(2)
+ image_list = ["images/meteorGrey_small1.png",
+ "images/meteorGrey_small2.png"]
+
+ enemy_sprite = AsteroidSprite(image_list[image_no],
+ SCALE * 1.5)
+
+ enemy_sprite.center_y = y
+ enemy_sprite.center_x = x
+
+ enemy_sprite.change_x = random.random() * 3 - 1.5
+ enemy_sprite.change_y = random.random() * 3 - 1.5
+
+ enemy_sprite.change_angle = (random.random() - 0.5) * 2
+ enemy_sprite.size = 2
+
+ self.all_sprites_list.append(enemy_sprite)
+ self.asteroid_list.append(enemy_sprite)
+ elif asteroid.size == 2:
+ for i in range(3):
+ image_no = random.randrange(2)
+ image_list = ["images/meteorGrey_tiny1.png",
+ "images/meteorGrey_tiny2.png"]
+
+ enemy_sprite = AsteroidSprite(image_list[image_no],
+ SCALE * 1.5)
+
+ enemy_sprite.center_y = y
+ enemy_sprite.center_x = x
+
+ enemy_sprite.change_x = random.random() * 3.5 - 1.75
+ enemy_sprite.change_y = random.random() * 3.5 - 1.75
+
+ enemy_sprite.change_angle = (random.random() - 0.5) * 2
+ enemy_sprite.size = 1
+
+ self.all_sprites_list.append(enemy_sprite)
+ self.asteroid_list.append(enemy_sprite)
+
+ def update(self, x):
+ """ Move everything """
+
+ self.frame_count += 1
+
+ if not self.game_over:
+ self.all_sprites_list.update()
+
+ for bullet in self.bullet_list:
+ asteroids_plain = arcade.check_for_collision_with_list(bullet, self.asteroid_list)
+ asteroids_spatial = arcade.check_for_collision_with_list(bullet, self.asteroid_list)
+ if len(asteroids_plain) != len(asteroids_spatial):
+ print("ERROR")
+
+ asteroids = asteroids_spatial
+
+ for asteroid in asteroids:
+ self.split_asteroid(asteroid)
+ asteroid.kill()
+ bullet.kill()
+
+ if not self.player_sprite.respawning:
+ asteroids = arcade.check_for_collision_with_list(self.player_sprite, self.asteroid_list)
+ if len(asteroids) > 0:
+ if self.lives > 0:
+ self.lives -= 1
+ self.player_sprite.respawn()
+ self.split_asteroid(asteroids[0])
+ asteroids[0].kill()
+ self.ship_life_list.pop().kill()
+ print("Crash")
+ else:
+ self.game_over = True
+ print("Game over")
+
+
+def main():
+ window = MyGame()
+ window.start_new_game()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/bouncing_ball.py b/arcade/examples/bouncing_ball.py
new file mode 100644
index 0000000..cbacea1
--- /dev/null
+++ b/arcade/examples/bouncing_ball.py
@@ -0,0 +1,97 @@
+"""
+Bounce a ball on the screen, using gravity.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.bouncing_ball
+"""
+
+import arcade
+
+# --- Set up the constants
+
+# Size of the screen
+SCREEN_WIDTH = 600
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Bouncing Ball Example"
+
+# Size of the circle.
+CIRCLE_RADIUS = 20
+
+# How strong the gravity is.
+GRAVITY_CONSTANT = 0.3
+
+# Percent of velocity maintained on a bounce.
+BOUNCINESS = 0.9
+
+
+def draw(delta_time):
+ """
+ Use this function to draw everything to the screen.
+ """
+
+ # Start the render. This must happen before any drawing
+ # commands. We do NOT need an stop render command.
+ arcade.start_render()
+
+ # Draw our rectangle
+ arcade.draw_circle_filled(draw.x, draw.y, CIRCLE_RADIUS,
+ arcade.color.BLACK)
+
+ # Modify rectangles position based on the delta
+ # vector. (Delta means change. You can also think
+ # of this as our speed and direction.)
+ draw.x += draw.delta_x
+ draw.y += draw.delta_y
+
+ draw.delta_y -= GRAVITY_CONSTANT
+
+ # Figure out if we hit the left or right edge and need to reverse.
+ if draw.x < CIRCLE_RADIUS and draw.delta_x < 0:
+ draw.delta_x *= -BOUNCINESS
+ elif draw.x > SCREEN_WIDTH - CIRCLE_RADIUS and draw.delta_x > 0:
+ draw.delta_x *= -BOUNCINESS
+
+ # See if we hit the bottom
+ if draw.y < CIRCLE_RADIUS and draw.delta_y < 0:
+ # If we bounce with a decent velocity, do a normal bounce.
+ # Otherwise we won't have enough time resolution to accurate represent
+ # the bounce and it will bounce forever. So we'll divide the bounciness
+ # by half to let it settle out.
+ if draw.delta_y * -1 > GRAVITY_CONSTANT * 15:
+ draw.delta_y *= -BOUNCINESS
+ else:
+ draw.delta_y *= -BOUNCINESS / 2
+
+
+# Below are function-specific variables. Before we use them
+# in our function, we need to give them initial values. Then
+# the values will persist between function calls.
+#
+# In other languages, we'd declare the variables as 'static' inside the
+# function to get that same functionality.
+#
+# Later on, we'll use 'classes' to track position and velocity for multiple
+# objects.
+draw.x = CIRCLE_RADIUS
+draw.y = SCREEN_HEIGHT - CIRCLE_RADIUS
+draw.delta_x = 2
+draw.delta_y = 0
+
+
+def main():
+ # Open up our window
+ arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ arcade.set_background_color(arcade.color.WHITE)
+
+ # Tell the computer to call the draw command at the specified interval.
+ arcade.schedule(draw, 1 / 80)
+
+ # Run the program
+ arcade.run()
+
+ # When done running the program, close the window.
+ arcade.close_window()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/bouncing_balls.py b/arcade/examples/bouncing_balls.py
new file mode 100644
index 0000000..93b4f17
--- /dev/null
+++ b/arcade/examples/bouncing_balls.py
@@ -0,0 +1,113 @@
+"""
+Bounce balls on the screen.
+Spawn a new ball for each mouse-click.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.bouncing_balls
+"""
+
+import arcade
+import random
+
+# --- Set up the constants
+
+# Size of the screen
+SCREEN_WIDTH = 600
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Bouncing Balls Example"
+
+
+class Ball:
+ """
+ Class to keep track of a ball's location and vector.
+ """
+ def __init__(self):
+ self.x = 0
+ self.y = 0
+ self.change_x = 0
+ self.change_y = 0
+ self.size = 0
+ self.color = None
+
+
+def make_ball():
+ """
+ Function to make a new, random ball.
+ """
+ ball = Ball()
+
+ # Size of the ball
+ ball.size = random.randrange(10, 30)
+
+ # Starting position of the ball.
+ # Take into account the ball size so we don't spawn on the edge.
+ ball.x = random.randrange(ball.size, SCREEN_WIDTH - ball.size)
+ ball.y = random.randrange(ball.size, SCREEN_HEIGHT - ball.size)
+
+ # Speed and direction of rectangle
+ ball.change_x = random.randrange(-2, 3)
+ ball.change_y = random.randrange(-2, 3)
+
+ # Color
+ ball.color = (random.randrange(256), random.randrange(256), random.randrange(256))
+
+ return ball
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self):
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ self.ball_list = []
+ ball = make_ball()
+ self.ball_list.append(ball)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ for ball in self.ball_list:
+ arcade.draw_circle_filled(ball.x, ball.y, ball.size, ball.color)
+
+ # Put the text on the screen.
+ output = "Balls: {}".format(len(self.ball_list))
+ arcade.draw_text(output, 10, 20, arcade.color.WHITE, 14)
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+ for ball in self.ball_list:
+ ball.x += ball.change_x
+ ball.y += ball.change_y
+
+ if ball.x < ball.size:
+ ball.change_x *= -1
+
+ if ball.y < ball.size:
+ ball.change_y *= -1
+
+ if ball.x > SCREEN_WIDTH - ball.size:
+ ball.change_x *= -1
+
+ if ball.y > SCREEN_HEIGHT - ball.size:
+ ball.change_y *= -1
+
+ def on_mouse_press(self, x, y, button, modifiers):
+ """
+ Called whenever the mouse button is clicked.
+ """
+ ball = make_ball()
+ self.ball_list.append(ball)
+
+
+def main():
+ MyGame()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/bouncing_rectangle.py b/arcade/examples/bouncing_rectangle.py
new file mode 100644
index 0000000..e98003b
--- /dev/null
+++ b/arcade/examples/bouncing_rectangle.py
@@ -0,0 +1,95 @@
+"""
+This simple animation example shows how to bounce a rectangle
+on the screen.
+
+It assumes a programmer knows how to create functions already.
+
+It does not assume a programmer knows how to create classes. If you do know
+how to create classes, see the starting template for a better example:
+
+http://arcade.academy/examples/starting_template.html
+
+Or look through the examples showing how to use Sprites.
+
+A video walk-through of this example is available at:
+https://vimeo.com/168063840
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.bouncing_rectangle
+
+"""
+
+import arcade
+
+# --- Set up the constants
+
+# Size of the screen
+SCREEN_WIDTH = 600
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Bouncing Rectangle Example"
+
+# Size of the rectangle
+RECT_WIDTH = 50
+RECT_HEIGHT = 50
+
+
+def on_draw(delta_time):
+ """
+ Use this function to draw everything to the screen.
+ """
+
+ # Start the render. This must happen before any drawing
+ # commands. We do NOT need a stop render command.
+ arcade.start_render()
+
+ # Draw a rectangle.
+ # For a full list of colors see:
+ # http://arcade.academy/arcade.color.html
+ arcade.draw_rectangle_filled(on_draw.center_x, on_draw.center_y,
+ RECT_WIDTH, RECT_HEIGHT,
+ arcade.color.ALIZARIN_CRIMSON)
+
+ # Modify rectangles position based on the delta
+ # vector. (Delta means change. You can also think
+ # of this as our speed and direction.)
+ on_draw.center_x += on_draw.delta_x * delta_time
+ on_draw.center_y += on_draw.delta_y * delta_time
+
+ # Figure out if we hit the edge and need to reverse.
+ if on_draw.center_x < RECT_WIDTH // 2 \
+ or on_draw.center_x > SCREEN_WIDTH - RECT_WIDTH // 2:
+ on_draw.delta_x *= -1
+ if on_draw.center_y < RECT_HEIGHT // 2 \
+ or on_draw.center_y > SCREEN_HEIGHT - RECT_HEIGHT // 2:
+ on_draw.delta_y *= -1
+
+
+# Below are function-specific variables. Before we use them
+# in our function, we need to give them initial values. Then
+# the values will persist between function calls.
+#
+# In other languages, we'd declare the variables as 'static' inside the
+# function to get that same functionality.
+#
+# Later on, we'll use 'classes' to track position and velocity for multiple
+# objects.
+on_draw.center_x = 100 # Initial x position
+on_draw.center_y = 50 # Initial y position
+on_draw.delta_x = 115 # Initial change in x
+on_draw.delta_y = 130 # Initial change in y
+
+
+def main():
+ # Open up our window
+ arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ arcade.set_background_color(arcade.color.WHITE)
+
+ # Tell the computer to call the draw command at the specified interval.
+ arcade.schedule(on_draw, 1 / 80)
+
+ # Run the program
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/decorator_drawing_example.py b/arcade/examples/decorator_drawing_example.py
new file mode 100644
index 0000000..cc398a8
--- /dev/null
+++ b/arcade/examples/decorator_drawing_example.py
@@ -0,0 +1,124 @@
+"""
+Example "Arcade" library code.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.decorator_drawing_example
+"""
+
+# Library imports
+import arcade
+import random
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Drawing With Decorators Example"
+
+window = arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+bird_list = []
+
+
+def setup():
+ create_birds()
+ arcade.schedule(update, 1 / 60)
+ arcade.run()
+
+
+def create_birds():
+ for bird_count in range(10):
+ x = random.randrange(SCREEN_WIDTH)
+ y = random.randrange(SCREEN_HEIGHT/2, SCREEN_HEIGHT)
+ bird_list.append([x, y])
+
+
+def update(delta_time):
+ """
+ This is run every 1/60 of a second or so. Do not draw anything
+ in this function.
+ """
+ change_y = 0.3
+
+ for bird in bird_list:
+ bird[0] += change_y
+ if bird[0] > SCREEN_WIDTH + 20:
+ bird[0] = -20
+
+
+@window.event
+def on_draw():
+ """
+ This is called every time we need to update our screen. About 60
+ times per second.
+
+ Just draw things in this function, don't update where they are.
+ """
+ # Call our drawing functions.
+ draw_background()
+ draw_birds()
+ draw_trees()
+
+
+def draw_background():
+ """
+ This function draws the background. Specifically, the sky and ground.
+ """
+ # Draw the sky in the top two-thirds
+ arcade.draw_rectangle_filled(SCREEN_WIDTH / 2, SCREEN_HEIGHT * 2 / 3,
+ SCREEN_WIDTH - 1, SCREEN_HEIGHT * 2 / 3,
+ arcade.color.SKY_BLUE)
+
+ # Draw the ground in the bottom third
+ arcade.draw_rectangle_filled(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 6,
+ SCREEN_WIDTH - 1, SCREEN_HEIGHT / 3,
+ arcade.color.DARK_SPRING_GREEN)
+
+
+def draw_birds():
+ for bird in bird_list:
+ # Draw the bird.
+ draw_bird(bird[0], bird[1])
+
+
+def draw_bird(x, y):
+ """
+ Draw a bird using a couple arcs.
+ """
+ arcade.draw_arc_outline(x, y, 20, 20, arcade.color.BLACK, 0, 90)
+ arcade.draw_arc_outline(x + 40, y, 20, 20, arcade.color.BLACK, 90, 180)
+
+
+def draw_trees():
+
+ # Draw the top row of trees
+ for x in range(45, SCREEN_WIDTH, 90):
+ draw_pine_tree(x, SCREEN_HEIGHT / 3)
+
+ # Draw the bottom row of trees
+ for x in range(65, SCREEN_WIDTH, 90):
+ draw_pine_tree(x, (SCREEN_HEIGHT / 3) - 120)
+
+
+def draw_pine_tree(center_x, center_y):
+ """
+ This function draws a pine tree at the specified location.
+
+ Args:
+ :center_x: x position of the tree center.
+ :center_y: y position of the tree trunk center.
+ """
+ # Draw the trunk center_x
+ arcade.draw_rectangle_filled(center_x, center_y, 20, 40, arcade.color.DARK_BROWN)
+
+ tree_bottom_y = center_y + 20
+
+ # Draw the triangle on top of the trunk
+ point_list = ((center_x - 40, tree_bottom_y),
+ (center_x, tree_bottom_y + 100),
+ (center_x + 40, tree_bottom_y))
+
+ arcade.draw_polygon_filled(point_list, arcade.color.DARK_GREEN)
+
+
+if __name__ == "__main__":
+ setup()
+
diff --git a/arcade/examples/drawing_primitives.py b/arcade/examples/drawing_primitives.py
new file mode 100644
index 0000000..3a12935
--- /dev/null
+++ b/arcade/examples/drawing_primitives.py
@@ -0,0 +1,168 @@
+"""
+Example "Arcade" library code.
+
+This example shows the drawing primitives and how they are used.
+It does not assume the programmer knows how to define functions or classes
+yet.
+
+API documentation for the draw commands can be found here:
+http://arcade.academy/quick_index.html#id1
+
+A video explaining this example can be found here:
+https://vimeo.com/167158158
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.drawing_primitives
+"""
+
+# Import the Arcade library. If this fails, then try following the instructions
+# for how to install arcade:
+# http://arcade.academy/installation.html
+import arcade
+import os
+
+# Set the working directory (where we expect to find files) to the same
+# directory this .py file is in. You can leave this out of your own
+# code, but it is needed to easily run the examples using "python -m"
+# as mentioned at the top of this program.
+file_path = os.path.dirname(os.path.abspath(__file__))
+os.chdir(file_path)
+
+# Open the window. Set the window title and dimensions (width and height)
+arcade.open_window(600, 600, "Drawing Primitives Example")
+
+# Set the background color to white
+# For a list of named colors see
+# http://arcade.academy/arcade.color.html
+# Colors can also be specified in (red, green, blue) format and
+# (red, green, blue, alpha) format.
+arcade.set_background_color(arcade.color.WHITE)
+
+# Start the render process. This must be done before any drawing commands.
+arcade.start_render()
+
+# Draw a grid
+# Draw vertical lines every 120 pixels
+for x in range(0, 601, 120):
+ arcade.draw_line(x, 0, x, 600, arcade.color.BLACK, 2)
+
+# Draw horizontal lines every 200 pixels
+for y in range(0, 601, 200):
+ arcade.draw_line(0, y, 800, y, arcade.color.BLACK, 2)
+
+# Draw a point
+arcade.draw_text("draw_point", 3, 405, arcade.color.BLACK, 12)
+arcade.draw_point(60, 495, arcade.color.RED, 10)
+
+# Draw a set of points
+arcade.draw_text("draw_points", 123, 405, arcade.color.BLACK, 12)
+point_list = ((165, 495),
+ (165, 480),
+ (165, 465),
+ (195, 495),
+ (195, 480),
+ (195, 465))
+arcade.draw_points(point_list, arcade.color.ZAFFRE, 10)
+
+# Draw a line
+arcade.draw_text("draw_line", 243, 405, arcade.color.BLACK, 12)
+arcade.draw_line(270, 495, 300, 450, arcade.color.WOOD_BROWN, 3)
+
+# Draw a set of lines
+arcade.draw_text("draw_lines", 363, 405, arcade.color.BLACK, 12)
+point_list = ((390, 450),
+ (450, 450),
+ (390, 480),
+ (450, 480),
+ (390, 510),
+ (450, 510)
+ )
+arcade.draw_lines(point_list, arcade.color.BLUE, 3)
+
+# Draw a line strip
+arcade.draw_text("draw_line_strip", 483, 405, arcade.color.BLACK, 12)
+point_list = ((510, 450),
+ (570, 450),
+ (510, 480),
+ (570, 480),
+ (510, 510),
+ (570, 510)
+ )
+arcade.draw_line_strip(point_list, arcade.color.TROPICAL_RAIN_FOREST, 3)
+
+# Draw a polygon
+arcade.draw_text("draw_polygon_outline", 3, 207, arcade.color.BLACK, 9)
+point_list = ((30, 240),
+ (45, 240),
+ (60, 255),
+ (60, 285),
+ (45, 300),
+ (30, 300))
+arcade.draw_polygon_outline(point_list, arcade.color.SPANISH_VIOLET, 3)
+
+# Draw a filled in polygon
+arcade.draw_text("draw_polygon_filled", 123, 207, arcade.color.BLACK, 9)
+point_list = ((150, 240),
+ (165, 240),
+ (180, 255),
+ (180, 285),
+ (165, 300),
+ (150, 300))
+arcade.draw_polygon_filled(point_list, arcade.color.SPANISH_VIOLET)
+
+# Draw an outline of a circle
+arcade.draw_text("draw_circle_outline", 243, 207, arcade.color.BLACK, 10)
+arcade.draw_circle_outline(300, 285, 18, arcade.color.WISTERIA, 3)
+
+# Draw a filled in circle
+arcade.draw_text("draw_circle_filled", 363, 207, arcade.color.BLACK, 10)
+arcade.draw_circle_filled(420, 285, 18, arcade.color.GREEN)
+
+# Draw an ellipse outline, and another one rotated
+arcade.draw_text("draw_ellipse_outline", 483, 207, arcade.color.BLACK, 10)
+arcade.draw_ellipse_outline(540, 273, 15, 36, arcade.color.AMBER, 3)
+arcade.draw_ellipse_outline(540, 336, 15, 36,
+ arcade.color.BLACK_BEAN, 3, 45)
+
+# Draw a filled ellipse, and another one rotated
+arcade.draw_text("draw_ellipse_filled", 3, 3, arcade.color.BLACK, 10)
+arcade.draw_ellipse_filled(60, 81, 15, 36, arcade.color.AMBER)
+arcade.draw_ellipse_filled(60, 144, 15, 36,
+ arcade.color.BLACK_BEAN, 45)
+
+# Draw an arc, and another one rotated
+arcade.draw_text("draw_arc/filled_arc", 123, 3, arcade.color.BLACK, 10)
+arcade.draw_arc_outline(150, 81, 15, 36,
+ arcade.color.BRIGHT_MAROON, 90, 360)
+arcade.draw_arc_filled(150, 144, 15, 36,
+ arcade.color.BOTTLE_GREEN, 90, 360, 45)
+
+# Draw an rectangle outline
+arcade.draw_text("draw_rect", 243, 3, arcade.color.BLACK, 10)
+arcade.draw_rectangle_outline(295, 100, 45, 65,
+ arcade.color.BRITISH_RACING_GREEN)
+arcade.draw_rectangle_outline(295, 160, 20, 45,
+ arcade.color.BRITISH_RACING_GREEN, 3, 45)
+
+# Draw a filled in rectangle
+arcade.draw_text("draw_filled_rect", 363, 3, arcade.color.BLACK, 10)
+arcade.draw_rectangle_filled(420, 100, 45, 65, arcade.color.BLUSH)
+arcade.draw_rectangle_filled(420, 160, 20, 40, arcade.color.BLUSH, 45)
+
+# Load and draw an image to the screen
+# Image from kenney.nl asset pack #1
+arcade.draw_text("draw_bitmap", 483, 3, arcade.color.BLACK, 12)
+texture = arcade.load_texture("images/playerShip1_orange.png")
+scale = .6
+arcade.draw_texture_rectangle(540, 120, scale * texture.width,
+ scale * texture.height, texture, 0)
+arcade.draw_texture_rectangle(540, 60, scale * texture.width,
+ scale * texture.height, texture, 45)
+
+# Finish the render.
+# Nothing will be drawn without this.
+# Must happen after all draw commands
+arcade.finish_render()
+
+# Keep the window up until someone closes it.
+arcade.run()
diff --git a/arcade/examples/drawing_text.py b/arcade/examples/drawing_text.py
new file mode 100644
index 0000000..5fb4223
--- /dev/null
+++ b/arcade/examples/drawing_text.py
@@ -0,0 +1,107 @@
+"""
+Example showing how to draw text to the screen.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.drawing_text
+"""
+import arcade
+
+SCREEN_WIDTH = 500
+SCREEN_HEIGHT = 500
+SCREEN_TITLE = "Drawing Text Example"
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title)
+
+ arcade.set_background_color(arcade.color.WHITE)
+ self.text_angle = 0
+ self.time_elapsed = 0.0
+
+ def update(self, delta_time):
+ self.text_angle += 1
+ self.time_elapsed += delta_time
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command should happen before we start drawing. It will clear
+ # the screen to the background color, and erase what we drew last frame.
+ arcade.start_render()
+
+ # start_x and start_y make the start point for the text. We draw a dot to make it easy too see
+ # the text in relation to its start x and y.
+ start_x = 50
+ start_y = 450
+ arcade.draw_point(start_x, start_y, arcade.color.BLUE, 5)
+ arcade.draw_text("Simple line of text in 12 point", start_x, start_y, arcade.color.BLACK, 12)
+
+ start_x = 50
+ start_y = 150
+ arcade.draw_point(start_x, start_y, arcade.color.BLUE, 5)
+ arcade.draw_text("Garamond Text", start_x, start_y, arcade.color.BLACK, 15, font_name='GARA')
+
+ start_x = 50
+ start_y = 400
+ arcade.draw_point(start_x, start_y, arcade.color.BLUE, 5)
+ arcade.draw_text("Text anchored 'top' and 'left'.",
+ start_x, start_y, arcade.color.BLACK, 12, anchor_x="left", anchor_y="top")
+
+ start_y = 350
+ arcade.draw_point(start_x, start_y, arcade.color.BLUE, 5)
+ arcade.draw_text("14 point multi\nline\ntext",
+ start_x, start_y, arcade.color.BLACK, 14, anchor_y="top")
+
+ start_y = 450
+ start_x = 300
+ width = 200
+ height = 20
+ arcade.draw_point(start_x, start_y, arcade.color.BLUE, 5)
+ arcade.draw_lrtb_rectangle_outline(start_x, start_x + width,
+ start_y + height, start_y,
+ arcade.color.BLUE, 1)
+ arcade.draw_text("Centered Text.",
+ start_x, start_y, arcade.color.BLACK, 14, width=200, align="center")
+
+ start_y = 250
+ start_x = 300
+ arcade.draw_point(start_x, start_y, arcade.color.BLUE, 5)
+ arcade.draw_text("Text centered on\na point",
+ start_x, start_y, arcade.color.BLACK, 14, width=200, align="center",
+ anchor_x="center", anchor_y="center")
+
+ start_y = 150
+ start_x = 300
+ arcade.draw_point(start_x, start_y, arcade.color.BLUE, 5)
+ arcade.draw_text("Text rotated on\na point", start_x, start_y,
+ arcade.color.BLACK, 14, width=200, align="center", anchor_x="center",
+ anchor_y="center", rotation=self.text_angle)
+
+ start_y = 150
+ start_x = 20
+ arcade.draw_point(start_x, start_y, arcade.color.BLUE, 5)
+ arcade.draw_text("Sideways text", start_x, start_y,
+ arcade.color.BLACK, 14, width=200, align="center",
+ anchor_x="center", anchor_y="center", rotation=90.0)
+
+ start_y = 20
+ start_x = 50
+ arcade.draw_point(start_x, start_y, arcade.color.BLUE, 5)
+ arcade.draw_text(f"Time elapsed: {self.time_elapsed:7.1f}",
+ start_x, start_y, arcade.color.BLACK, 14)
+
+
+def main():
+ MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/drawing_with_functions.py b/arcade/examples/drawing_with_functions.py
new file mode 100644
index 0000000..a437101
--- /dev/null
+++ b/arcade/examples/drawing_with_functions.py
@@ -0,0 +1,93 @@
+"""
+Example "Arcade" library code.
+
+This example shows how to use functions to draw a scene.
+It does not assume that the programmer knows how to use classes yet.
+
+A video walk-through of this code is available at:
+https://vimeo.com/167296062
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.drawing_with_functions
+"""
+
+# Library imports
+import arcade
+
+# Constants - variables that do not change
+SCREEN_WIDTH = 600
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Drawing With Functions Example"
+
+
+def draw_background():
+ """
+ This function draws the background. Specifically, the sky and ground.
+ """
+ # Draw the sky in the top two-thirds
+ arcade.draw_lrtb_rectangle_filled(0,
+ SCREEN_WIDTH,
+ SCREEN_HEIGHT,
+ SCREEN_HEIGHT * (1 / 3),
+ arcade.color.SKY_BLUE)
+
+ # Draw the ground in the bottom third
+ arcade.draw_lrtb_rectangle_filled(0,
+ SCREEN_WIDTH,
+ SCREEN_HEIGHT / 3,
+ 0,
+ arcade.color.DARK_SPRING_GREEN)
+
+
+def draw_bird(x, y):
+ """
+ Draw a bird using a couple arcs.
+ """
+ arcade.draw_arc_outline(x, y, 20, 20, arcade.color.BLACK, 0, 90)
+ arcade.draw_arc_outline(x + 40, y, 20, 20, arcade.color.BLACK, 90, 180)
+
+
+def draw_pine_tree(x, y):
+ """
+ This function draws a pine tree at the specified location.
+ """
+ # Draw the triangle on top of the trunk
+ arcade.draw_triangle_filled(x + 40, y,
+ x, y - 100,
+ x + 80, y - 100,
+ arcade.color.DARK_GREEN)
+
+ # Draw the trunk
+ arcade.draw_lrtb_rectangle_filled(x + 30, x + 50, y - 100, y - 140,
+ arcade.color.DARK_BROWN)
+
+
+def main():
+ """
+ This is the main program.
+ """
+
+ # Open the window
+ arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Start the render process. This must be done before any drawing commands.
+ arcade.start_render()
+
+ # Call our drawing functions.
+ draw_background()
+ draw_pine_tree(50, 250)
+ draw_pine_tree(350, 320)
+ draw_bird(70, 500)
+ draw_bird(470, 550)
+
+ # Finish the render.
+ # Nothing will be drawn without this.
+ # Must happen after all draw commands
+ arcade.finish_render()
+
+ # Keep the window up until someone closes it.
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/drawing_with_loops.py b/arcade/examples/drawing_with_loops.py
new file mode 100644
index 0000000..1f25635
--- /dev/null
+++ b/arcade/examples/drawing_with_loops.py
@@ -0,0 +1,109 @@
+"""
+Example "Arcade" library code.
+
+This example shows how to use functions and loops to draw a scene.
+It does not assume that the programmer knows how to use classes yet.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.drawing_with_loops
+"""
+
+# Library imports
+import arcade
+import random
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Drawing With Loops Example"
+
+
+def draw_background():
+ """
+ This function draws the background. Specifically, the sky and ground.
+ """
+ # Draw the sky in the top two-thirds
+ arcade.draw_rectangle_filled(SCREEN_WIDTH / 2, SCREEN_HEIGHT * 2 / 3,
+ SCREEN_WIDTH - 1, SCREEN_HEIGHT * 2 / 3,
+ arcade.color.SKY_BLUE)
+
+ # Draw the ground in the bottom third
+ arcade.draw_rectangle_filled(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 6,
+ SCREEN_WIDTH - 1, SCREEN_HEIGHT / 3,
+ arcade.color.DARK_SPRING_GREEN)
+
+
+def draw_bird(x, y):
+ """
+ Draw a bird using a couple arcs.
+ """
+ arcade.draw_arc_outline(x, y, 20, 20, arcade.color.BLACK, 0, 90)
+ arcade.draw_arc_outline(x + 40, y, 20, 20, arcade.color.BLACK, 90, 180)
+
+
+def draw_pine_tree(center_x, center_y):
+ """
+ This function draws a pine tree at the specified location.
+
+ Args:
+ :center_x: x position of the tree center.
+ :center_y: y position of the tree trunk center.
+ """
+ # Draw the trunkcenter_x
+ arcade.draw_rectangle_filled(center_x, center_y, 20, 40,
+ arcade.color.DARK_BROWN)
+
+ tree_bottom_y = center_y + 20
+
+ # Draw the triangle on top of the trunk
+ point_list = ((center_x - 40, tree_bottom_y),
+ (center_x, tree_bottom_y + 100),
+ (center_x + 40, tree_bottom_y))
+
+ arcade.draw_polygon_filled(point_list, arcade.color.DARK_GREEN)
+
+
+def main():
+ """
+ This is the main program.
+ """
+
+ # Open the window
+ arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Start the render process. This must be done before any drawing commands.
+ arcade.start_render()
+
+ # Call our drawing functions.
+ draw_background()
+
+ # Loop to draw ten birds in random locations.
+ for bird_count in range(10):
+ # Any random x from 0 to the width of the screen
+ x = random.randrange(0, SCREEN_WIDTH)
+
+ # Any random y from in the top 2/3 of the screen.
+ # No birds on the ground.
+ y = random.randrange(SCREEN_HEIGHT / 3, SCREEN_HEIGHT - 20)
+
+ # Draw the bird.
+ draw_bird(x, y)
+
+ # Draw the top row of trees
+ for x in range(45, SCREEN_WIDTH, 90):
+ draw_pine_tree(x, SCREEN_HEIGHT / 3)
+
+ # Draw the bottom row of trees
+ for x in range(65, SCREEN_WIDTH, 90):
+ draw_pine_tree(x, (SCREEN_HEIGHT / 3) - 120)
+
+ # Finish the render.
+ # Nothing will be drawn without this.
+ # Must happen after all draw commands
+ arcade.finish_render()
+
+ # Keep the window up until someone closes it.
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/dual_stick_shooter.py b/arcade/examples/dual_stick_shooter.py
new file mode 100644
index 0000000..3cadcf7
--- /dev/null
+++ b/arcade/examples/dual_stick_shooter.py
@@ -0,0 +1,310 @@
+"""
+Dual-stick Shooter Example
+
+A dual-analog stick joystick is the preferred method of input. If a joystick is
+not present, the game will fail back to use keyboard controls (WASD to move, arrows to shoot)
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.dual_stick_shooter
+"""
+import arcade
+import random
+import math
+import os
+from typing import cast
+import pprint
+
+SCREEN_WIDTH = 1024
+SCREEN_HEIGHT = 768
+SCREEN_TITLE = "Dual-stick Shooter Example"
+MOVEMENT_SPEED = 4
+BULLET_SPEED = 10
+BULLET_COOLDOWN_TICKS = 10
+ENEMY_SPAWN_INTERVAL = 1
+ENEMY_SPEED = 1
+JOY_DEADZONE = 0.2
+
+
+def dump_obj(obj):
+ for key in sorted(vars(obj)):
+ val = getattr(obj, key)
+ print("{:30} = {} ({})".format(key, val, type(val).__name__))
+
+
+def dump_joystick(joy):
+ print("========== {}".format(joy))
+ print("x {}".format(joy.x))
+ print("y {}".format(joy.y))
+ print("z {}".format(joy.z))
+ print("rx {}".format(joy.rx))
+ print("ry {}".format(joy.ry))
+ print("rz {}".format(joy.rz))
+ print("hat_x {}".format(joy.hat_x))
+ print("hat_y {}".format(joy.hat_y))
+ print("buttons {}".format(joy.buttons))
+ print("========== Extra joy")
+ dump_obj(joy)
+ print("========== Extra joy.device")
+ dump_obj(joy.device)
+ print("========== pprint joy")
+ pprint.pprint(joy)
+ print("========== pprint joy.device")
+ pprint.pprint(joy.device)
+
+
+def dump_joystick_state(ticks, joy):
+ # print("{:5.2f} {:5.2f} {:>20} {:5}_".format(1.234567, -8.2757272903, "hello", str(True)))
+ fmt_str = "{:6d} "
+ num_fmts = ["{:5.2f}"] * 6
+ fmt_str += " ".join(num_fmts)
+ fmt_str += " {:2d} {:2d} {}"
+ buttons = " ".join(["{:5}".format(str(b)) for b in joy.buttons])
+ print(fmt_str.format(ticks,
+ joy.x,
+ joy.y,
+ joy.z,
+ joy.rx,
+ joy.ry,
+ joy.rz,
+ joy.hat_x,
+ joy.hat_y,
+ buttons))
+
+
+def get_joy_position(x, y):
+ """Given position of joystick axes, return (x, y, angle_in_degrees).
+ If movement is not outside of deadzone, return (None, None, None)"""
+ if x > JOY_DEADZONE or x < -JOY_DEADZONE or y > JOY_DEADZONE or y < -JOY_DEADZONE:
+ y = -y
+ rad = math.atan2(y, x)
+ angle = math.degrees(rad)
+ return x, y, angle
+ return None, None, None
+
+
+class Player(arcade.sprite.Sprite):
+ def __init__(self, filename):
+ super().__init__(filename=filename, scale=0.4, center_x=SCREEN_WIDTH/2, center_y=SCREEN_HEIGHT/2)
+ self.shoot_up_pressed = False
+ self.shoot_down_pressed = False
+ self.shoot_left_pressed = False
+ self.shoot_right_pressed = False
+
+
+class Enemy(arcade.sprite.Sprite):
+ def __init__(self, x, y):
+ super().__init__(filename='images/bumper.png', scale=0.5, center_x=x, center_y=y)
+
+ def follow_sprite(self, player_sprite):
+ """
+ This function will move the current sprite towards whatever
+ other sprite is specified as a parameter.
+
+ We use the 'min' function here to get the sprite to line up with
+ the target sprite, and not jump around if the sprite is not off
+ an exact multiple of ENEMY_SPEED.
+ """
+
+ if self.center_y < player_sprite.center_y:
+ self.center_y += min(ENEMY_SPEED, player_sprite.center_y - self.center_y)
+ elif self.center_y > player_sprite.center_y:
+ self.center_y -= min(ENEMY_SPEED, self.center_y - player_sprite.center_y)
+
+ if self.center_x < player_sprite.center_x:
+ self.center_x += min(ENEMY_SPEED, player_sprite.center_x - self.center_x)
+ elif self.center_x > player_sprite.center_x:
+ self.center_x -= min(ENEMY_SPEED, self.center_x - player_sprite.center_x)
+
+
+class MyGame(arcade.Window):
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ arcade.set_background_color(arcade.color.DARK_MIDNIGHT_BLUE)
+ self.game_over = False
+ self.score = 0
+ self.tick = 0
+ self.bullet_cooldown = 0
+ self.player = Player("images/playerShip2_orange.png")
+ self.bullet_list = arcade.SpriteList()
+ self.enemy_list = arcade.SpriteList()
+ self.joy = None
+ joys = arcade.get_joysticks()
+ for joy in joys:
+ dump_joystick(joy)
+ if joys:
+ self.joy = joys[0]
+ self.joy.open()
+ print("Using joystick controls: {}".format(self.joy.device))
+ arcade.window_commands.schedule(self.debug_joy_state, 0.1)
+ if not self.joy:
+ print("No joystick present, using keyboard controls")
+ arcade.window_commands.schedule(self.spawn_enemy, ENEMY_SPAWN_INTERVAL)
+
+ def debug_joy_state(self, delta_time):
+ dump_joystick_state(self.tick, self.joy)
+
+ def spawn_enemy(self, elapsed):
+ if self.game_over:
+ return
+ x = random.randint(0, SCREEN_WIDTH)
+ y = random.randint(0, SCREEN_HEIGHT)
+ self.enemy_list.append(Enemy(x, y))
+
+ def update(self, delta_time):
+ self.tick += 1
+ if self.game_over:
+ return
+
+ self.bullet_cooldown += 1
+
+ for enemy in self.enemy_list:
+ cast(Enemy, enemy).follow_sprite(self.player)
+
+ if self.joy:
+ # Joystick input - movement
+ move_x, move_y, move_angle = get_joy_position(self.joy.x, self.joy.y)
+ if move_angle:
+ self.player.change_x = move_x * MOVEMENT_SPEED
+ self.player.change_y = move_y * MOVEMENT_SPEED
+ # An angle of "0" means "right", but the player's image is drawn in the "up" direction.
+ # So an offset is needed.
+ self.player.angle = move_angle - 90
+ else:
+ self.player.change_x = 0
+ self.player.change_y = 0
+ # Joystick input - shooting
+ shoot_x, shoot_y, shoot_angle = get_joy_position(self.joy.z, self.joy.rz)
+ if shoot_angle:
+ self.spawn_bullet(shoot_angle)
+ else:
+ # Keyboard input - shooting
+ if self.player.shoot_right_pressed and self.player.shoot_up_pressed:
+ self.spawn_bullet(0+45)
+ elif self.player.shoot_up_pressed and self.player.shoot_left_pressed:
+ self.spawn_bullet(90+45)
+ elif self.player.shoot_left_pressed and self.player.shoot_down_pressed:
+ self.spawn_bullet(180+45)
+ elif self.player.shoot_down_pressed and self.player.shoot_right_pressed:
+ self.spawn_bullet(270+45)
+ elif self.player.shoot_right_pressed:
+ self.spawn_bullet(0)
+ elif self.player.shoot_up_pressed:
+ self.spawn_bullet(90)
+ elif self.player.shoot_left_pressed:
+ self.spawn_bullet(180)
+ elif self.player.shoot_down_pressed:
+ self.spawn_bullet(270)
+
+ self.enemy_list.update()
+ self.player.update()
+ self.bullet_list.update()
+ ship_death_hit_list = arcade.check_for_collision_with_list(self.player, self.enemy_list)
+ if len(ship_death_hit_list) > 0:
+ self.game_over = True
+ for bullet in self.bullet_list:
+ bullet_killed = False
+ enemy_shot_list = arcade.check_for_collision_with_list(bullet, self.enemy_list)
+ # Loop through each colliding sprite, remove it, and add to the score.
+ for enemy in enemy_shot_list:
+ enemy.kill()
+ bullet.kill()
+ bullet_killed = True
+ self.score += 1
+ if bullet_killed:
+ continue
+
+ def on_key_press(self, key, modifiers):
+ if key == arcade.key.W:
+ self.player.change_y = MOVEMENT_SPEED
+ self.player.angle = 0
+ elif key == arcade.key.A:
+ self.player.change_x = -MOVEMENT_SPEED
+ self.player.angle = 90
+ elif key == arcade.key.S:
+ self.player.change_y = -MOVEMENT_SPEED
+ self.player.angle = 180
+ elif key == arcade.key.D:
+ self.player.change_x = MOVEMENT_SPEED
+ self.player.angle = 270
+ elif key == arcade.key.RIGHT:
+ self.player.shoot_right_pressed = True
+ elif key == arcade.key.UP:
+ self.player.shoot_up_pressed = True
+ elif key == arcade.key.LEFT:
+ self.player.shoot_left_pressed = True
+ elif key == arcade.key.DOWN:
+ self.player.shoot_down_pressed = True
+
+ def on_key_release(self, key, modifiers):
+ if key == arcade.key.W:
+ self.player.change_y = 0
+ elif key == arcade.key.A:
+ self.player.change_x = 0
+ elif key == arcade.key.S:
+ self.player.change_y = 0
+ elif key == arcade.key.D:
+ self.player.change_x = 0
+ elif key == arcade.key.RIGHT:
+ self.player.shoot_right_pressed = False
+ elif key == arcade.key.UP:
+ self.player.shoot_up_pressed = False
+ elif key == arcade.key.LEFT:
+ self.player.shoot_left_pressed = False
+ elif key == arcade.key.DOWN:
+ self.player.shoot_down_pressed = False
+
+ def spawn_bullet(self, angle_in_deg):
+ # only allow bullet to spawn on an interval
+ if self.bullet_cooldown < BULLET_COOLDOWN_TICKS:
+ return
+ self.bullet_cooldown = 0
+
+ bullet = arcade.Sprite("images/laserBlue01.png", 0.75)
+
+ # Position the bullet at the player's current location
+ start_x = self.player.center_x
+ start_y = self.player.center_y
+ bullet.center_x = start_x
+ bullet.center_y = start_y
+
+ # angle the bullet visually
+ bullet.angle = angle_in_deg
+ angle_in_rad = math.radians(angle_in_deg)
+
+ # set bullet's movement direction
+ bullet.change_x = math.cos(angle_in_rad) * BULLET_SPEED
+ bullet.change_y = math.sin(angle_in_rad) * BULLET_SPEED
+
+ # Add the bullet to the appropriate lists
+ self.bullet_list.append(bullet)
+
+ def on_draw(self):
+ # clear screen and start render process
+ arcade.start_render()
+
+ # draw game items
+ self.bullet_list.draw()
+ self.enemy_list.draw()
+ self.player.draw()
+
+ # Put the score on the screen.
+ output = f"Score: {self.score}"
+ arcade.draw_text(output, 10, 20, arcade.color.WHITE, 14)
+
+ # Game over message
+ if self.game_over:
+ arcade.draw_text("Game Over", SCREEN_WIDTH/2, SCREEN_HEIGHT/2, arcade.color.WHITE, 100, width=SCREEN_WIDTH,
+ align="center", anchor_x="center", anchor_y="center")
+
+
+if __name__ == "__main__":
+ game = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ arcade.run()
diff --git a/arcade/examples/dungeon.tmx b/arcade/examples/dungeon.tmx
new file mode 100644
index 0000000..3334e84
--- /dev/null
+++ b/arcade/examples/dungeon.tmx
@@ -0,0 +1,777 @@
+
+
diff --git a/arcade/examples/frametime_plotter.py b/arcade/examples/frametime_plotter.py
new file mode 100644
index 0000000..a07cd2a
--- /dev/null
+++ b/arcade/examples/frametime_plotter.py
@@ -0,0 +1,58 @@
+"""
+Helper class to track length of time taken for each frame and draw a graph when application exits.
+Also able to add events at arbitrary times across the graph.
+"""
+import time
+import matplotlib.pyplot as plt
+import statistics
+
+
+class FrametimePlotter:
+ EVENT_POINT_Y = -0.05
+ EVENT_MSG_Y = -0.045
+
+ def __init__(self):
+ self.times = []
+ self.events = []
+ self.start = time.perf_counter()
+
+ def add_event(self, event_msg):
+ self.events.append((len(self.times), event_msg))
+
+ def end_frame(self, time_delta):
+ self.times.append(time_delta)
+
+ def _show_stats(self):
+ end = time.perf_counter()
+ print("Min : {:.5f}".format(min(self.times)))
+ print("Max : {:.5f}".format(max(self.times)))
+ print("Avg : {:.5f}".format(statistics.mean(self.times)))
+ print("Median: {:.5f}".format(statistics.median(self.times)))
+ try:
+ print("Mode : {:.5f}".format(statistics.mode(self.times)))
+ except statistics.StatisticsError as e:
+ print("Mode : {}".format(e))
+ print("StdDev: {:.5f}".format(statistics.stdev(self.times)))
+ frame_count = len(self.times)
+ elapsed_time = end - self.start
+ print("Frame count: {}".format(frame_count))
+ print("Elapsed time: {:.5f}".format(elapsed_time))
+ print("FPS: {:.5f}".format(frame_count / elapsed_time))
+
+ def show(self):
+ if len(self.times) <= 1:
+ return
+ self._show_stats()
+ frame_idxs = range(0, len(self.times))
+ event_idxs = [e[0] for e in self.events]
+ event_point_y = [self.EVENT_POINT_Y] * len(self.events)
+ plt.figure("Frame durations", figsize=(8, 6))
+ plt.plot(frame_idxs, self.times, event_idxs, event_point_y, "k|")
+ plt.xlabel("frames")
+ plt.ylabel("frame duration")
+ plt.ylim(self.EVENT_POINT_Y - 0.005, 0.5)
+ plt.tight_layout()
+ for frame_idx, msg in self.events:
+ plt.text(frame_idx, self.EVENT_MSG_Y, msg, horizontalalignment="center", verticalalignment="bottom",
+ size="smaller", rotation="vertical")
+ plt.show()
diff --git a/arcade/examples/full_screen_example.py b/arcade/examples/full_screen_example.py
new file mode 100644
index 0000000..213a064
--- /dev/null
+++ b/arcade/examples/full_screen_example.py
@@ -0,0 +1,108 @@
+"""
+Use sprites to scroll around a large screen.
+
+Simple program to show basic sprite usage.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.full_screen_example
+"""
+
+import arcade
+import os
+
+SPRITE_SCALING = 0.5
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Full Screen Example"
+
+# How many pixels to keep as a minimum margin between the character
+# and the edge of the screen.
+VIEWPORT_MARGIN = 40
+
+MOVEMENT_SPEED = 5
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self):
+ """
+ Initializer
+ """
+ # Open a window in full screen mode. Remove fullscreen=True if
+ # you don't want to start this way.
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, fullscreen=True)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # This will get the size of the window, and set the viewport to match.
+ # So if the window is 1000x1000, then so will our viewport. If
+ # you want something different, then use those coordinates instead.
+ width, height = self.get_size()
+ self.set_viewport(0, width, 0, height)
+ arcade.set_background_color(arcade.color.AMAZON)
+ self.example_image = arcade.load_texture("images/boxCrate_double.png")
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ arcade.start_render()
+
+ # Get viewport dimensions
+ left, screen_width, bottom, screen_height = self.get_viewport()
+
+ text_size = 18
+ # Draw text on the screen so the user has an idea of what is happening
+ arcade.draw_text("Press F to toggle between full screen and windowed mode, unstretched.",
+ screen_width // 2, screen_height // 2 - 20,
+ arcade.color.WHITE, text_size, anchor_x="center")
+ arcade.draw_text("Press S to toggle between full screen and windowed mode, stretched.",
+ screen_width // 2, screen_height // 2 + 20,
+ arcade.color.WHITE, text_size, anchor_x="center")
+
+ # Draw some boxes on the bottom so we can see how they change
+ for x in range(64, 800, 128):
+ y = 64
+ width = 128
+ height = 128
+ arcade.draw_texture_rectangle(x, y, width, height, self.example_image)
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+ if key == arcade.key.F:
+ # User hits f. Flip between full and not full screen.
+ self.set_fullscreen(not self.fullscreen)
+
+ # Get the window coordinates. Match viewport to window coordinates
+ # so there is a one-to-one mapping.
+ width, height = self.get_size()
+ self.set_viewport(0, width, 0, height)
+
+ if key == arcade.key.S:
+ # User hits s. Flip between full and not full screen.
+ self.set_fullscreen(not self.fullscreen)
+
+ # Instead of a one-to-one mapping, stretch/squash window to match the
+ # constants. This does NOT respect aspect ratio. You'd need to
+ # do a bit of math for that.
+ self.set_viewport(0, SCREEN_WIDTH, 0, SCREEN_HEIGHT)
+
+
+def main():
+ """ Main method """
+ MyGame()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/gradients.py b/arcade/examples/gradients.py
new file mode 100644
index 0000000..91cb494
--- /dev/null
+++ b/arcade/examples/gradients.py
@@ -0,0 +1,92 @@
+"""
+Drawing Gradients
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.gradients
+"""
+import arcade
+
+# Do the math to figure out our screen dimensions
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Gradients Example"
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ """
+ Set up the application.
+ """
+
+ super().__init__(width, height, title)
+
+ arcade.set_background_color(arcade.color.BLACK)
+
+ self.shapes = arcade.ShapeElementList()
+
+ # This is a large rectangle that fills the whole
+ # background. The gradient goes between the two colors
+ # top to bottom.
+ color1 = (215, 214, 165)
+ color2 = (219, 166, 123)
+ points = (0, 0), (SCREEN_WIDTH, 0), (SCREEN_WIDTH, SCREEN_HEIGHT), (0, SCREEN_HEIGHT)
+ colors = (color1, color1, color2, color2)
+ rect = arcade.create_rectangle_filled_with_colors(points, colors)
+ self.shapes.append(rect)
+
+ # Another rectangle, but in this case the color doesn't change. Just the
+ # transparency. This time it goes from left to right.
+ color1 = (165, 92, 85, 255)
+ color2 = (165, 92, 85, 0)
+ points = (100, 100), (SCREEN_WIDTH - 100, 100), (SCREEN_WIDTH - 100, 300), (100, 300)
+ colors = (color2, color1, color1, color2)
+ rect = arcade.create_rectangle_filled_with_colors(points, colors)
+ self.shapes.append(rect)
+
+ # Two lines
+ color1 = (7, 67, 88)
+ color2 = (69, 137, 133)
+ points = (100, 400), (SCREEN_WIDTH - 100, 400), (SCREEN_WIDTH - 100, 500), (100, 500)
+ colors = (color2, color1, color2, color1)
+ shape = arcade.create_lines_with_colors(points, colors, line_width=5)
+ self.shapes.append(shape)
+
+ # Triangle
+ color1 = (215, 214, 165)
+ color2 = (219, 166, 123)
+ color3 = (165, 92, 85)
+ points = (SCREEN_WIDTH // 2, 500), (SCREEN_WIDTH // 2 - 100, 400), (SCREEN_WIDTH // 2 + 100, 400)
+ colors = (color1, color2, color3)
+ shape = arcade.create_triangles_filled_with_colors(points, colors)
+ self.shapes.append(shape)
+
+ # Ellipse, gradient between center and outside
+ color1 = (69, 137, 133, 127)
+ color2 = (7, 67, 88, 127)
+ shape = arcade.create_ellipse_filled_with_colors(SCREEN_WIDTH // 2, 350, 50, 50,
+ inside_color=color1, outside_color=color2)
+ self.shapes.append(shape)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+ self.shapes.draw()
+ # arcade.draw_rectangle_filled(500, 500, 50, 50, (255, 0, 0, 127))
+
+
+def main():
+
+ MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/gui_text_button.py b/arcade/examples/gui_text_button.py
new file mode 100644
index 0000000..733696b
--- /dev/null
+++ b/arcade/examples/gui_text_button.py
@@ -0,0 +1,238 @@
+"""
+Buttons with text on them
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.gui_text_button
+"""
+import arcade
+import random
+import os
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "GUI Text Buton Example"
+
+
+class TextButton:
+ """ Text-based button """
+
+ def __init__(self,
+ center_x, center_y,
+ width, height,
+ text,
+ font_size=18,
+ font_face="Arial",
+ face_color=arcade.color.LIGHT_GRAY,
+ highlight_color=arcade.color.WHITE,
+ shadow_color=arcade.color.GRAY,
+ button_height=2):
+ self.center_x = center_x
+ self.center_y = center_y
+ self.width = width
+ self.height = height
+ self.text = text
+ self.font_size = font_size
+ self.font_face = font_face
+ self.pressed = False
+ self.face_color = face_color
+ self.highlight_color = highlight_color
+ self.shadow_color = shadow_color
+ self.button_height = button_height
+
+ def draw(self):
+ """ Draw the button """
+ arcade.draw_rectangle_filled(self.center_x, self.center_y, self.width,
+ self.height, self.face_color)
+
+ if not self.pressed:
+ color = self.shadow_color
+ else:
+ color = self.highlight_color
+
+ # Bottom horizontal
+ arcade.draw_line(self.center_x - self.width / 2, self.center_y - self.height / 2,
+ self.center_x + self.width / 2, self.center_y - self.height / 2,
+ color, self.button_height)
+
+ # Right vertical
+ arcade.draw_line(self.center_x + self.width / 2, self.center_y - self.height / 2,
+ self.center_x + self.width / 2, self.center_y + self.height / 2,
+ color, self.button_height)
+
+ if not self.pressed:
+ color = self.highlight_color
+ else:
+ color = self.shadow_color
+
+ # Top horizontal
+ arcade.draw_line(self.center_x - self.width / 2, self.center_y + self.height / 2,
+ self.center_x + self.width / 2, self.center_y + self.height / 2,
+ color, self.button_height)
+
+ # Left vertical
+ arcade.draw_line(self.center_x - self.width / 2, self.center_y - self.height / 2,
+ self.center_x - self.width / 2, self.center_y + self.height / 2,
+ color, self.button_height)
+
+ x = self.center_x
+ y = self.center_y
+ if not self.pressed:
+ x -= self.button_height
+ y += self.button_height
+
+ arcade.draw_text(self.text, x, y,
+ arcade.color.BLACK, font_size=self.font_size,
+ width=self.width, align="center",
+ anchor_x="center", anchor_y="center")
+
+ def on_press(self):
+ self.pressed = True
+
+ def on_release(self):
+ self.pressed = False
+
+
+def check_mouse_press_for_buttons(x, y, button_list):
+ """ Given an x, y, see if we need to register any button clicks. """
+ for button in button_list:
+ if x > button.center_x + button.width / 2:
+ continue
+ if x < button.center_x - button.width / 2:
+ continue
+ if y > button.center_y + button.height / 2:
+ continue
+ if y < button.center_y - button.height / 2:
+ continue
+ button.on_press()
+
+
+def check_mouse_release_for_buttons(x, y, button_list):
+ """ If a mouse button has been released, see if we need to process
+ any release events. """
+ for button in button_list:
+ if button.pressed:
+ button.on_release()
+
+
+class StartTextButton(TextButton):
+ def __init__(self, center_x, center_y, action_function):
+ super().__init__(center_x, center_y, 100, 40, "Start", 18, "Arial")
+ self.action_function = action_function
+
+ def on_release(self):
+ super().on_release()
+ self.action_function()
+
+
+class StopTextButton(TextButton):
+ def __init__(self, center_x, center_y, action_function):
+ super().__init__(center_x, center_y, 100, 40, "Stop", 18, "Arial")
+ self.action_function = action_function
+
+ def on_release(self):
+ super().on_release()
+ self.action_function()
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+
+ NOTE: Go ahead and delete the methods you don't need.
+ If you do need a method, delete the 'pass' and replace it
+ with your own code. Don't leave 'pass' in this program.
+ """
+
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ self.pause = False
+ self.coin_list = None
+ self.button_list = None
+
+ def setup(self):
+ # Create your sprites and sprite lists here
+ self.coin_list = arcade.SpriteList()
+ for i in range(10):
+ coin = arcade.Sprite("images/coin_01.png", 0.25)
+ coin.center_x = random.randrange(SCREEN_WIDTH)
+ coin.center_y = random.randrange(SCREEN_HEIGHT)
+ coin.change_y = -1
+ self.coin_list.append(coin)
+
+ # Create our on-screen GUI buttons
+ self.button_list = []
+
+ play_button = StartTextButton(60, 570, self.resume_program)
+ self.button_list.append(play_button)
+
+ quit_button = StopTextButton(60, 515, self.pause_program)
+ self.button_list.append(quit_button)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ arcade.start_render()
+
+ # Draw the coins
+ self.coin_list.draw()
+
+ # Draw the buttons
+ for button in self.button_list:
+ button.draw()
+
+ def update(self, delta_time):
+ """
+ All the logic to move, and the game logic goes here.
+ Normally, you'll call update() on the sprite lists that
+ need it.
+ """
+
+ if self.pause:
+ return
+
+ self.coin_list.update()
+
+ for coin in self.coin_list:
+ if coin.top < 0:
+ coin.bottom = SCREEN_HEIGHT
+
+ def on_mouse_press(self, x, y, button, key_modifiers):
+ """
+ Called when the user presses a mouse button.
+ """
+ check_mouse_press_for_buttons(x, y, self.button_list)
+
+ def on_mouse_release(self, x, y, button, key_modifiers):
+ """
+ Called when a user releases a mouse button.
+ """
+ check_mouse_release_for_buttons(x, y, self.button_list)
+
+ def pause_program(self):
+ self.pause = True
+
+ def resume_program(self):
+ self.pause = False
+
+
+def main():
+ """ Main method """
+ game = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ game.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/happy_face.py b/arcade/examples/happy_face.py
new file mode 100644
index 0000000..006ccd6
--- /dev/null
+++ b/arcade/examples/happy_face.py
@@ -0,0 +1,48 @@
+"""
+Drawing an example happy face
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.happy_face
+"""
+
+import arcade
+
+# Set constants for the screen size
+SCREEN_WIDTH = 600
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Happy Face Example"
+
+# Open the window. Set the window title and dimensions
+arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+# Set the background color
+arcade.set_background_color(arcade.color.WHITE)
+
+# Clear screen and start render process
+arcade.start_render()
+
+# --- Drawing Commands Will Go Here ---
+
+# Draw the face
+x = 300; y = 300; radius = 200
+arcade.draw_circle_filled(x, y, radius, arcade.color.YELLOW)
+
+# Draw the right eye
+x = 370; y = 350; radius = 20
+arcade.draw_circle_filled(x, y, radius, arcade.color.BLACK)
+
+# Draw the left eye
+x = 230; y = 350; radius = 20
+arcade.draw_circle_filled(x, y, radius, arcade.color.BLACK)
+
+# Draw the smile
+x = 300; y = 280; width = 120; height = 100
+start_angle = 190; end_angle = 350
+arcade.draw_arc_outline(x, y, width, height, arcade.color.BLACK,
+ start_angle, end_angle, 10)
+
+# Finish drawing and display the result
+arcade.finish_render()
+
+# Keep the window open until the user hits the 'close' button
+arcade.run()
diff --git a/arcade/examples/images/background.jpg b/arcade/examples/images/background.jpg
new file mode 100644
index 0000000..124bc7b
Binary files /dev/null and b/arcade/examples/images/background.jpg differ
diff --git a/arcade/examples/images/background_2.jpg b/arcade/examples/images/background_2.jpg
new file mode 100644
index 0000000..8964823
Binary files /dev/null and b/arcade/examples/images/background_2.jpg differ
diff --git a/arcade/examples/images/boxCrate_double.png b/arcade/examples/images/boxCrate_double.png
new file mode 100644
index 0000000..86ed133
Binary files /dev/null and b/arcade/examples/images/boxCrate_double.png differ
diff --git a/arcade/examples/images/bumper.png b/arcade/examples/images/bumper.png
new file mode 100644
index 0000000..35f8da4
Binary files /dev/null and b/arcade/examples/images/bumper.png differ
diff --git a/arcade/examples/images/bumper.svg b/arcade/examples/images/bumper.svg
new file mode 100644
index 0000000..c58f6a9
--- /dev/null
+++ b/arcade/examples/images/bumper.svg
@@ -0,0 +1,100 @@
+
+
+
+
diff --git a/arcade/examples/images/character.png b/arcade/examples/images/character.png
new file mode 100644
index 0000000..9ad34ca
Binary files /dev/null and b/arcade/examples/images/character.png differ
diff --git a/arcade/examples/images/character_sheet.png b/arcade/examples/images/character_sheet.png
new file mode 100644
index 0000000..e7513a6
Binary files /dev/null and b/arcade/examples/images/character_sheet.png differ
diff --git a/arcade/examples/images/character_sprites/character0.png b/arcade/examples/images/character_sprites/character0.png
new file mode 100644
index 0000000..025affd
Binary files /dev/null and b/arcade/examples/images/character_sprites/character0.png differ
diff --git a/arcade/examples/images/character_sprites/character1.png b/arcade/examples/images/character_sprites/character1.png
new file mode 100644
index 0000000..f83fd23
Binary files /dev/null and b/arcade/examples/images/character_sprites/character1.png differ
diff --git a/arcade/examples/images/character_sprites/character10.png b/arcade/examples/images/character_sprites/character10.png
new file mode 100644
index 0000000..325e130
Binary files /dev/null and b/arcade/examples/images/character_sprites/character10.png differ
diff --git a/arcade/examples/images/character_sprites/character13.png b/arcade/examples/images/character_sprites/character13.png
new file mode 100644
index 0000000..ad4566f
Binary files /dev/null and b/arcade/examples/images/character_sprites/character13.png differ
diff --git a/arcade/examples/images/character_sprites/character14.png b/arcade/examples/images/character_sprites/character14.png
new file mode 100644
index 0000000..3e32929
Binary files /dev/null and b/arcade/examples/images/character_sprites/character14.png differ
diff --git a/arcade/examples/images/character_sprites/character15.png b/arcade/examples/images/character_sprites/character15.png
new file mode 100644
index 0000000..bfa3307
Binary files /dev/null and b/arcade/examples/images/character_sprites/character15.png differ
diff --git a/arcade/examples/images/character_sprites/character16.png b/arcade/examples/images/character_sprites/character16.png
new file mode 100644
index 0000000..66eb6e1
Binary files /dev/null and b/arcade/examples/images/character_sprites/character16.png differ
diff --git a/arcade/examples/images/character_sprites/character17.png b/arcade/examples/images/character_sprites/character17.png
new file mode 100644
index 0000000..b9b6964
Binary files /dev/null and b/arcade/examples/images/character_sprites/character17.png differ
diff --git a/arcade/examples/images/character_sprites/character18.png b/arcade/examples/images/character_sprites/character18.png
new file mode 100644
index 0000000..4d606c0
Binary files /dev/null and b/arcade/examples/images/character_sprites/character18.png differ
diff --git a/arcade/examples/images/character_sprites/character2.png b/arcade/examples/images/character_sprites/character2.png
new file mode 100644
index 0000000..298c31e
Binary files /dev/null and b/arcade/examples/images/character_sprites/character2.png differ
diff --git a/arcade/examples/images/character_sprites/character3.png b/arcade/examples/images/character_sprites/character3.png
new file mode 100644
index 0000000..33e0805
Binary files /dev/null and b/arcade/examples/images/character_sprites/character3.png differ
diff --git a/arcade/examples/images/character_sprites/character4.png b/arcade/examples/images/character_sprites/character4.png
new file mode 100644
index 0000000..a1cd787
Binary files /dev/null and b/arcade/examples/images/character_sprites/character4.png differ
diff --git a/arcade/examples/images/character_sprites/character5.png b/arcade/examples/images/character_sprites/character5.png
new file mode 100644
index 0000000..446ca40
Binary files /dev/null and b/arcade/examples/images/character_sprites/character5.png differ
diff --git a/arcade/examples/images/character_sprites/character6.png b/arcade/examples/images/character_sprites/character6.png
new file mode 100644
index 0000000..fc76361
Binary files /dev/null and b/arcade/examples/images/character_sprites/character6.png differ
diff --git a/arcade/examples/images/character_sprites/character7.png b/arcade/examples/images/character_sprites/character7.png
new file mode 100644
index 0000000..dd226dc
Binary files /dev/null and b/arcade/examples/images/character_sprites/character7.png differ
diff --git a/arcade/examples/images/character_sprites/characterw0.png b/arcade/examples/images/character_sprites/characterw0.png
new file mode 100644
index 0000000..a73eceb
Binary files /dev/null and b/arcade/examples/images/character_sprites/characterw0.png differ
diff --git a/arcade/examples/images/character_sprites/characterw1.png b/arcade/examples/images/character_sprites/characterw1.png
new file mode 100644
index 0000000..a8b5b49
Binary files /dev/null and b/arcade/examples/images/character_sprites/characterw1.png differ
diff --git a/arcade/examples/images/character_sprites/characterw2.png b/arcade/examples/images/character_sprites/characterw2.png
new file mode 100644
index 0000000..c8525e4
Binary files /dev/null and b/arcade/examples/images/character_sprites/characterw2.png differ
diff --git a/arcade/examples/images/character_sprites/characterw3.png b/arcade/examples/images/character_sprites/characterw3.png
new file mode 100644
index 0000000..484cb7f
Binary files /dev/null and b/arcade/examples/images/character_sprites/characterw3.png differ
diff --git a/arcade/examples/images/coin_01.png b/arcade/examples/images/coin_01.png
new file mode 100644
index 0000000..9d4cbe1
Binary files /dev/null and b/arcade/examples/images/coin_01.png differ
diff --git a/arcade/examples/images/explosion/explosion0000.png b/arcade/examples/images/explosion/explosion0000.png
new file mode 100644
index 0000000..518433b
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0000.png differ
diff --git a/arcade/examples/images/explosion/explosion0001.png b/arcade/examples/images/explosion/explosion0001.png
new file mode 100644
index 0000000..f87d239
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0001.png differ
diff --git a/arcade/examples/images/explosion/explosion0002.png b/arcade/examples/images/explosion/explosion0002.png
new file mode 100644
index 0000000..fa6f198
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0002.png differ
diff --git a/arcade/examples/images/explosion/explosion0003.png b/arcade/examples/images/explosion/explosion0003.png
new file mode 100644
index 0000000..7b9aee0
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0003.png differ
diff --git a/arcade/examples/images/explosion/explosion0004.png b/arcade/examples/images/explosion/explosion0004.png
new file mode 100644
index 0000000..bf05f40
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0004.png differ
diff --git a/arcade/examples/images/explosion/explosion0005.png b/arcade/examples/images/explosion/explosion0005.png
new file mode 100644
index 0000000..843b907
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0005.png differ
diff --git a/arcade/examples/images/explosion/explosion0006.png b/arcade/examples/images/explosion/explosion0006.png
new file mode 100644
index 0000000..d1e8822
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0006.png differ
diff --git a/arcade/examples/images/explosion/explosion0007.png b/arcade/examples/images/explosion/explosion0007.png
new file mode 100644
index 0000000..4aa8ea8
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0007.png differ
diff --git a/arcade/examples/images/explosion/explosion0008.png b/arcade/examples/images/explosion/explosion0008.png
new file mode 100644
index 0000000..a5a06db
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0008.png differ
diff --git a/arcade/examples/images/explosion/explosion0009.png b/arcade/examples/images/explosion/explosion0009.png
new file mode 100644
index 0000000..a067285
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0009.png differ
diff --git a/arcade/examples/images/explosion/explosion0010.png b/arcade/examples/images/explosion/explosion0010.png
new file mode 100644
index 0000000..3420a55
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0010.png differ
diff --git a/arcade/examples/images/explosion/explosion0011.png b/arcade/examples/images/explosion/explosion0011.png
new file mode 100644
index 0000000..cee8857
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0011.png differ
diff --git a/arcade/examples/images/explosion/explosion0012.png b/arcade/examples/images/explosion/explosion0012.png
new file mode 100644
index 0000000..9b9c5fe
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0012.png differ
diff --git a/arcade/examples/images/explosion/explosion0013.png b/arcade/examples/images/explosion/explosion0013.png
new file mode 100644
index 0000000..f4bd5fe
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0013.png differ
diff --git a/arcade/examples/images/explosion/explosion0014.png b/arcade/examples/images/explosion/explosion0014.png
new file mode 100644
index 0000000..b9bcd6b
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0014.png differ
diff --git a/arcade/examples/images/explosion/explosion0015.png b/arcade/examples/images/explosion/explosion0015.png
new file mode 100644
index 0000000..80e1f77
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0015.png differ
diff --git a/arcade/examples/images/explosion/explosion0016.png b/arcade/examples/images/explosion/explosion0016.png
new file mode 100644
index 0000000..4311cd3
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0016.png differ
diff --git a/arcade/examples/images/explosion/explosion0017.png b/arcade/examples/images/explosion/explosion0017.png
new file mode 100644
index 0000000..ce8e54b
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0017.png differ
diff --git a/arcade/examples/images/explosion/explosion0018.png b/arcade/examples/images/explosion/explosion0018.png
new file mode 100644
index 0000000..5092c3b
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0018.png differ
diff --git a/arcade/examples/images/explosion/explosion0019.png b/arcade/examples/images/explosion/explosion0019.png
new file mode 100644
index 0000000..04a8226
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0019.png differ
diff --git a/arcade/examples/images/explosion/explosion0020.png b/arcade/examples/images/explosion/explosion0020.png
new file mode 100644
index 0000000..c50a579
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0020.png differ
diff --git a/arcade/examples/images/explosion/explosion0021.png b/arcade/examples/images/explosion/explosion0021.png
new file mode 100644
index 0000000..2c11dd0
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0021.png differ
diff --git a/arcade/examples/images/explosion/explosion0022.png b/arcade/examples/images/explosion/explosion0022.png
new file mode 100644
index 0000000..658f64c
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0022.png differ
diff --git a/arcade/examples/images/explosion/explosion0023.png b/arcade/examples/images/explosion/explosion0023.png
new file mode 100644
index 0000000..c8aef24
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0023.png differ
diff --git a/arcade/examples/images/explosion/explosion0024.png b/arcade/examples/images/explosion/explosion0024.png
new file mode 100644
index 0000000..69b9a30
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0024.png differ
diff --git a/arcade/examples/images/explosion/explosion0025.png b/arcade/examples/images/explosion/explosion0025.png
new file mode 100644
index 0000000..6781e03
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0025.png differ
diff --git a/arcade/examples/images/explosion/explosion0026.png b/arcade/examples/images/explosion/explosion0026.png
new file mode 100644
index 0000000..c9db7ba
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0026.png differ
diff --git a/arcade/examples/images/explosion/explosion0027.png b/arcade/examples/images/explosion/explosion0027.png
new file mode 100644
index 0000000..b64e138
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0027.png differ
diff --git a/arcade/examples/images/explosion/explosion0028.png b/arcade/examples/images/explosion/explosion0028.png
new file mode 100644
index 0000000..576992e
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0028.png differ
diff --git a/arcade/examples/images/explosion/explosion0029.png b/arcade/examples/images/explosion/explosion0029.png
new file mode 100644
index 0000000..11971bc
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0029.png differ
diff --git a/arcade/examples/images/explosion/explosion0030.png b/arcade/examples/images/explosion/explosion0030.png
new file mode 100644
index 0000000..1dfc522
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0030.png differ
diff --git a/arcade/examples/images/explosion/explosion0031.png b/arcade/examples/images/explosion/explosion0031.png
new file mode 100644
index 0000000..efe5b3a
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0031.png differ
diff --git a/arcade/examples/images/explosion/explosion0032.png b/arcade/examples/images/explosion/explosion0032.png
new file mode 100644
index 0000000..ab8ee50
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0032.png differ
diff --git a/arcade/examples/images/explosion/explosion0033.png b/arcade/examples/images/explosion/explosion0033.png
new file mode 100644
index 0000000..b3a3bae
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0033.png differ
diff --git a/arcade/examples/images/explosion/explosion0034.png b/arcade/examples/images/explosion/explosion0034.png
new file mode 100644
index 0000000..04f2b04
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0034.png differ
diff --git a/arcade/examples/images/explosion/explosion0035.png b/arcade/examples/images/explosion/explosion0035.png
new file mode 100644
index 0000000..a071379
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0035.png differ
diff --git a/arcade/examples/images/explosion/explosion0036.png b/arcade/examples/images/explosion/explosion0036.png
new file mode 100644
index 0000000..7f105f1
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0036.png differ
diff --git a/arcade/examples/images/explosion/explosion0037.png b/arcade/examples/images/explosion/explosion0037.png
new file mode 100644
index 0000000..2805027
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0037.png differ
diff --git a/arcade/examples/images/explosion/explosion0038.png b/arcade/examples/images/explosion/explosion0038.png
new file mode 100644
index 0000000..579964b
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0038.png differ
diff --git a/arcade/examples/images/explosion/explosion0039.png b/arcade/examples/images/explosion/explosion0039.png
new file mode 100644
index 0000000..4260d41
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0039.png differ
diff --git a/arcade/examples/images/explosion/explosion0040.png b/arcade/examples/images/explosion/explosion0040.png
new file mode 100644
index 0000000..047a0f8
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0040.png differ
diff --git a/arcade/examples/images/explosion/explosion0041.png b/arcade/examples/images/explosion/explosion0041.png
new file mode 100644
index 0000000..f331454
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0041.png differ
diff --git a/arcade/examples/images/explosion/explosion0042.png b/arcade/examples/images/explosion/explosion0042.png
new file mode 100644
index 0000000..e9b6f39
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0042.png differ
diff --git a/arcade/examples/images/explosion/explosion0043.png b/arcade/examples/images/explosion/explosion0043.png
new file mode 100644
index 0000000..090a7e2
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0043.png differ
diff --git a/arcade/examples/images/explosion/explosion0044.png b/arcade/examples/images/explosion/explosion0044.png
new file mode 100644
index 0000000..6ec58e6
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0044.png differ
diff --git a/arcade/examples/images/explosion/explosion0045.png b/arcade/examples/images/explosion/explosion0045.png
new file mode 100644
index 0000000..eceb4ad
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0045.png differ
diff --git a/arcade/examples/images/explosion/explosion0046.png b/arcade/examples/images/explosion/explosion0046.png
new file mode 100644
index 0000000..e660a00
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0046.png differ
diff --git a/arcade/examples/images/explosion/explosion0047.png b/arcade/examples/images/explosion/explosion0047.png
new file mode 100644
index 0000000..64aeaf9
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0047.png differ
diff --git a/arcade/examples/images/explosion/explosion0048.png b/arcade/examples/images/explosion/explosion0048.png
new file mode 100644
index 0000000..4f45067
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0048.png differ
diff --git a/arcade/examples/images/explosion/explosion0049.png b/arcade/examples/images/explosion/explosion0049.png
new file mode 100644
index 0000000..e4b7e61
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0049.png differ
diff --git a/arcade/examples/images/explosion/explosion0050.png b/arcade/examples/images/explosion/explosion0050.png
new file mode 100644
index 0000000..7de8b6b
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0050.png differ
diff --git a/arcade/examples/images/explosion/explosion0051.png b/arcade/examples/images/explosion/explosion0051.png
new file mode 100644
index 0000000..42713a7
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0051.png differ
diff --git a/arcade/examples/images/explosion/explosion0052.png b/arcade/examples/images/explosion/explosion0052.png
new file mode 100644
index 0000000..dd5f717
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0052.png differ
diff --git a/arcade/examples/images/explosion/explosion0053.png b/arcade/examples/images/explosion/explosion0053.png
new file mode 100644
index 0000000..5148270
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0053.png differ
diff --git a/arcade/examples/images/explosion/explosion0054.png b/arcade/examples/images/explosion/explosion0054.png
new file mode 100644
index 0000000..e157f36
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0054.png differ
diff --git a/arcade/examples/images/explosion/explosion0055.png b/arcade/examples/images/explosion/explosion0055.png
new file mode 100644
index 0000000..8a5bdd0
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0055.png differ
diff --git a/arcade/examples/images/explosion/explosion0056.png b/arcade/examples/images/explosion/explosion0056.png
new file mode 100644
index 0000000..235e547
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0056.png differ
diff --git a/arcade/examples/images/explosion/explosion0057.png b/arcade/examples/images/explosion/explosion0057.png
new file mode 100644
index 0000000..2cfb6cf
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0057.png differ
diff --git a/arcade/examples/images/explosion/explosion0058.png b/arcade/examples/images/explosion/explosion0058.png
new file mode 100644
index 0000000..11d5f08
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0058.png differ
diff --git a/arcade/examples/images/explosion/explosion0059.png b/arcade/examples/images/explosion/explosion0059.png
new file mode 100644
index 0000000..1824d83
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0059.png differ
diff --git a/arcade/examples/images/explosion/explosion0060.png b/arcade/examples/images/explosion/explosion0060.png
new file mode 100644
index 0000000..8fed34e
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0060.png differ
diff --git a/arcade/examples/images/explosion/explosion0061.png b/arcade/examples/images/explosion/explosion0061.png
new file mode 100644
index 0000000..c701f47
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0061.png differ
diff --git a/arcade/examples/images/explosion/explosion0062.png b/arcade/examples/images/explosion/explosion0062.png
new file mode 100644
index 0000000..5ea9987
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0062.png differ
diff --git a/arcade/examples/images/explosion/explosion0063.png b/arcade/examples/images/explosion/explosion0063.png
new file mode 100644
index 0000000..02da9b5
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0063.png differ
diff --git a/arcade/examples/images/explosion/explosion0064.png b/arcade/examples/images/explosion/explosion0064.png
new file mode 100644
index 0000000..60d03de
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0064.png differ
diff --git a/arcade/examples/images/explosion/explosion0065.png b/arcade/examples/images/explosion/explosion0065.png
new file mode 100644
index 0000000..2a98ead
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0065.png differ
diff --git a/arcade/examples/images/explosion/explosion0066.png b/arcade/examples/images/explosion/explosion0066.png
new file mode 100644
index 0000000..2e59682
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0066.png differ
diff --git a/arcade/examples/images/explosion/explosion0067.png b/arcade/examples/images/explosion/explosion0067.png
new file mode 100644
index 0000000..e2aecf1
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0067.png differ
diff --git a/arcade/examples/images/explosion/explosion0068.png b/arcade/examples/images/explosion/explosion0068.png
new file mode 100644
index 0000000..58ec48e
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0068.png differ
diff --git a/arcade/examples/images/explosion/explosion0069.png b/arcade/examples/images/explosion/explosion0069.png
new file mode 100644
index 0000000..fd8c4f3
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0069.png differ
diff --git a/arcade/examples/images/explosion/explosion0070.png b/arcade/examples/images/explosion/explosion0070.png
new file mode 100644
index 0000000..098bb69
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0070.png differ
diff --git a/arcade/examples/images/explosion/explosion0071.png b/arcade/examples/images/explosion/explosion0071.png
new file mode 100644
index 0000000..ccf4f7e
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0071.png differ
diff --git a/arcade/examples/images/explosion/explosion0072.png b/arcade/examples/images/explosion/explosion0072.png
new file mode 100644
index 0000000..63136b0
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0072.png differ
diff --git a/arcade/examples/images/explosion/explosion0073.png b/arcade/examples/images/explosion/explosion0073.png
new file mode 100644
index 0000000..739cd8c
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0073.png differ
diff --git a/arcade/examples/images/explosion/explosion0074.png b/arcade/examples/images/explosion/explosion0074.png
new file mode 100644
index 0000000..2fb107b
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0074.png differ
diff --git a/arcade/examples/images/explosion/explosion0075.png b/arcade/examples/images/explosion/explosion0075.png
new file mode 100644
index 0000000..7fa238c
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0075.png differ
diff --git a/arcade/examples/images/explosion/explosion0076.png b/arcade/examples/images/explosion/explosion0076.png
new file mode 100644
index 0000000..7c73a10
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0076.png differ
diff --git a/arcade/examples/images/explosion/explosion0077.png b/arcade/examples/images/explosion/explosion0077.png
new file mode 100644
index 0000000..422d89d
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0077.png differ
diff --git a/arcade/examples/images/explosion/explosion0078.png b/arcade/examples/images/explosion/explosion0078.png
new file mode 100644
index 0000000..5af8a93
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0078.png differ
diff --git a/arcade/examples/images/explosion/explosion0079.png b/arcade/examples/images/explosion/explosion0079.png
new file mode 100644
index 0000000..642ab0a
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0079.png differ
diff --git a/arcade/examples/images/explosion/explosion0080.png b/arcade/examples/images/explosion/explosion0080.png
new file mode 100644
index 0000000..ceb785f
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0080.png differ
diff --git a/arcade/examples/images/explosion/explosion0081.png b/arcade/examples/images/explosion/explosion0081.png
new file mode 100644
index 0000000..37a12a3
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0081.png differ
diff --git a/arcade/examples/images/explosion/explosion0082.png b/arcade/examples/images/explosion/explosion0082.png
new file mode 100644
index 0000000..fc7f345
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0082.png differ
diff --git a/arcade/examples/images/explosion/explosion0083.png b/arcade/examples/images/explosion/explosion0083.png
new file mode 100644
index 0000000..f5579dd
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0083.png differ
diff --git a/arcade/examples/images/explosion/explosion0084.png b/arcade/examples/images/explosion/explosion0084.png
new file mode 100644
index 0000000..d0434a0
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0084.png differ
diff --git a/arcade/examples/images/explosion/explosion0085.png b/arcade/examples/images/explosion/explosion0085.png
new file mode 100644
index 0000000..d037749
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0085.png differ
diff --git a/arcade/examples/images/explosion/explosion0086.png b/arcade/examples/images/explosion/explosion0086.png
new file mode 100644
index 0000000..e56a03f
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0086.png differ
diff --git a/arcade/examples/images/explosion/explosion0087.png b/arcade/examples/images/explosion/explosion0087.png
new file mode 100644
index 0000000..7884725
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0087.png differ
diff --git a/arcade/examples/images/explosion/explosion0088.png b/arcade/examples/images/explosion/explosion0088.png
new file mode 100644
index 0000000..2c6f117
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0088.png differ
diff --git a/arcade/examples/images/explosion/explosion0089.png b/arcade/examples/images/explosion/explosion0089.png
new file mode 100644
index 0000000..99a896f
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0089.png differ
diff --git a/arcade/examples/images/explosion/explosion0090.png b/arcade/examples/images/explosion/explosion0090.png
new file mode 100644
index 0000000..05b9678
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0090.png differ
diff --git a/arcade/examples/images/explosion/explosion0091.png b/arcade/examples/images/explosion/explosion0091.png
new file mode 100644
index 0000000..6582c54
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0091.png differ
diff --git a/arcade/examples/images/explosion/explosion0092.png b/arcade/examples/images/explosion/explosion0092.png
new file mode 100644
index 0000000..4e79e8f
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0092.png differ
diff --git a/arcade/examples/images/explosion/explosion0093.png b/arcade/examples/images/explosion/explosion0093.png
new file mode 100644
index 0000000..e0a907f
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0093.png differ
diff --git a/arcade/examples/images/explosion/explosion0094.png b/arcade/examples/images/explosion/explosion0094.png
new file mode 100644
index 0000000..2792a25
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0094.png differ
diff --git a/arcade/examples/images/explosion/explosion0095.png b/arcade/examples/images/explosion/explosion0095.png
new file mode 100644
index 0000000..5cdb3c8
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0095.png differ
diff --git a/arcade/examples/images/explosion/explosion0096.png b/arcade/examples/images/explosion/explosion0096.png
new file mode 100644
index 0000000..71cf280
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0096.png differ
diff --git a/arcade/examples/images/explosion/explosion0097.png b/arcade/examples/images/explosion/explosion0097.png
new file mode 100644
index 0000000..4d798e1
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0097.png differ
diff --git a/arcade/examples/images/explosion/explosion0098.png b/arcade/examples/images/explosion/explosion0098.png
new file mode 100644
index 0000000..c61b40e
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0098.png differ
diff --git a/arcade/examples/images/explosion/explosion0099.png b/arcade/examples/images/explosion/explosion0099.png
new file mode 100644
index 0000000..7e056a7
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0099.png differ
diff --git a/arcade/examples/images/explosion/explosion0100.png b/arcade/examples/images/explosion/explosion0100.png
new file mode 100644
index 0000000..fddc5da
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0100.png differ
diff --git a/arcade/examples/images/explosion/explosion0101.png b/arcade/examples/images/explosion/explosion0101.png
new file mode 100644
index 0000000..c7ed48f
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0101.png differ
diff --git a/arcade/examples/images/explosion/explosion0102.png b/arcade/examples/images/explosion/explosion0102.png
new file mode 100644
index 0000000..b41dfee
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0102.png differ
diff --git a/arcade/examples/images/explosion/explosion0103.png b/arcade/examples/images/explosion/explosion0103.png
new file mode 100644
index 0000000..9819c4e
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0103.png differ
diff --git a/arcade/examples/images/explosion/explosion0104.png b/arcade/examples/images/explosion/explosion0104.png
new file mode 100644
index 0000000..832e226
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0104.png differ
diff --git a/arcade/examples/images/explosion/explosion0105.png b/arcade/examples/images/explosion/explosion0105.png
new file mode 100644
index 0000000..72fb8aa
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0105.png differ
diff --git a/arcade/examples/images/explosion/explosion0106.png b/arcade/examples/images/explosion/explosion0106.png
new file mode 100644
index 0000000..eb00bd1
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0106.png differ
diff --git a/arcade/examples/images/explosion/explosion0107.png b/arcade/examples/images/explosion/explosion0107.png
new file mode 100644
index 0000000..65b9b4c
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0107.png differ
diff --git a/arcade/examples/images/explosion/explosion0108.png b/arcade/examples/images/explosion/explosion0108.png
new file mode 100644
index 0000000..f39258f
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0108.png differ
diff --git a/arcade/examples/images/explosion/explosion0109.png b/arcade/examples/images/explosion/explosion0109.png
new file mode 100644
index 0000000..eac41e9
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0109.png differ
diff --git a/arcade/examples/images/explosion/explosion0110.png b/arcade/examples/images/explosion/explosion0110.png
new file mode 100644
index 0000000..64bd912
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0110.png differ
diff --git a/arcade/examples/images/explosion/explosion0111.png b/arcade/examples/images/explosion/explosion0111.png
new file mode 100644
index 0000000..7f95119
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0111.png differ
diff --git a/arcade/examples/images/explosion/explosion0112.png b/arcade/examples/images/explosion/explosion0112.png
new file mode 100644
index 0000000..b66654c
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0112.png differ
diff --git a/arcade/examples/images/explosion/explosion0113.png b/arcade/examples/images/explosion/explosion0113.png
new file mode 100644
index 0000000..8ac77e8
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0113.png differ
diff --git a/arcade/examples/images/explosion/explosion0114.png b/arcade/examples/images/explosion/explosion0114.png
new file mode 100644
index 0000000..5e3168a
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0114.png differ
diff --git a/arcade/examples/images/explosion/explosion0115.png b/arcade/examples/images/explosion/explosion0115.png
new file mode 100644
index 0000000..9ff8a42
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0115.png differ
diff --git a/arcade/examples/images/explosion/explosion0116.png b/arcade/examples/images/explosion/explosion0116.png
new file mode 100644
index 0000000..c72008b
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0116.png differ
diff --git a/arcade/examples/images/explosion/explosion0117.png b/arcade/examples/images/explosion/explosion0117.png
new file mode 100644
index 0000000..a7f2689
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0117.png differ
diff --git a/arcade/examples/images/explosion/explosion0118.png b/arcade/examples/images/explosion/explosion0118.png
new file mode 100644
index 0000000..0c109a1
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0118.png differ
diff --git a/arcade/examples/images/explosion/explosion0119.png b/arcade/examples/images/explosion/explosion0119.png
new file mode 100644
index 0000000..0c7a7e4
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0119.png differ
diff --git a/arcade/examples/images/explosion/explosion0120.png b/arcade/examples/images/explosion/explosion0120.png
new file mode 100644
index 0000000..1eff255
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0120.png differ
diff --git a/arcade/examples/images/explosion/explosion0121.png b/arcade/examples/images/explosion/explosion0121.png
new file mode 100644
index 0000000..34bf696
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0121.png differ
diff --git a/arcade/examples/images/explosion/explosion0122.png b/arcade/examples/images/explosion/explosion0122.png
new file mode 100644
index 0000000..c80f718
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0122.png differ
diff --git a/arcade/examples/images/explosion/explosion0123.png b/arcade/examples/images/explosion/explosion0123.png
new file mode 100644
index 0000000..26a3bd4
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0123.png differ
diff --git a/arcade/examples/images/explosion/explosion0124.png b/arcade/examples/images/explosion/explosion0124.png
new file mode 100644
index 0000000..4ab4bd5
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0124.png differ
diff --git a/arcade/examples/images/explosion/explosion0125.png b/arcade/examples/images/explosion/explosion0125.png
new file mode 100644
index 0000000..4dd25d3
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0125.png differ
diff --git a/arcade/examples/images/explosion/explosion0126.png b/arcade/examples/images/explosion/explosion0126.png
new file mode 100644
index 0000000..fc4b7f0
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0126.png differ
diff --git a/arcade/examples/images/explosion/explosion0127.png b/arcade/examples/images/explosion/explosion0127.png
new file mode 100644
index 0000000..8f5eeab
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0127.png differ
diff --git a/arcade/examples/images/explosion/explosion0128.png b/arcade/examples/images/explosion/explosion0128.png
new file mode 100644
index 0000000..d76cdf4
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0128.png differ
diff --git a/arcade/examples/images/explosion/explosion0129.png b/arcade/examples/images/explosion/explosion0129.png
new file mode 100644
index 0000000..560518f
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0129.png differ
diff --git a/arcade/examples/images/explosion/explosion0130.png b/arcade/examples/images/explosion/explosion0130.png
new file mode 100644
index 0000000..fd309b0
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0130.png differ
diff --git a/arcade/examples/images/explosion/explosion0131.png b/arcade/examples/images/explosion/explosion0131.png
new file mode 100644
index 0000000..eca5dd1
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0131.png differ
diff --git a/arcade/examples/images/explosion/explosion0132.png b/arcade/examples/images/explosion/explosion0132.png
new file mode 100644
index 0000000..dea56a9
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0132.png differ
diff --git a/arcade/examples/images/explosion/explosion0133.png b/arcade/examples/images/explosion/explosion0133.png
new file mode 100644
index 0000000..3898df5
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0133.png differ
diff --git a/arcade/examples/images/explosion/explosion0134.png b/arcade/examples/images/explosion/explosion0134.png
new file mode 100644
index 0000000..73ab8af
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0134.png differ
diff --git a/arcade/examples/images/explosion/explosion0135.png b/arcade/examples/images/explosion/explosion0135.png
new file mode 100644
index 0000000..436488b
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0135.png differ
diff --git a/arcade/examples/images/explosion/explosion0136.png b/arcade/examples/images/explosion/explosion0136.png
new file mode 100644
index 0000000..0c5c480
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0136.png differ
diff --git a/arcade/examples/images/explosion/explosion0137.png b/arcade/examples/images/explosion/explosion0137.png
new file mode 100644
index 0000000..0456b07
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0137.png differ
diff --git a/arcade/examples/images/explosion/explosion0138.png b/arcade/examples/images/explosion/explosion0138.png
new file mode 100644
index 0000000..7e08173
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0138.png differ
diff --git a/arcade/examples/images/explosion/explosion0139.png b/arcade/examples/images/explosion/explosion0139.png
new file mode 100644
index 0000000..a873c8d
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0139.png differ
diff --git a/arcade/examples/images/explosion/explosion0140.png b/arcade/examples/images/explosion/explosion0140.png
new file mode 100644
index 0000000..0d97b8d
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0140.png differ
diff --git a/arcade/examples/images/explosion/explosion0141.png b/arcade/examples/images/explosion/explosion0141.png
new file mode 100644
index 0000000..adaff0c
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0141.png differ
diff --git a/arcade/examples/images/explosion/explosion0142.png b/arcade/examples/images/explosion/explosion0142.png
new file mode 100644
index 0000000..242a7b3
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0142.png differ
diff --git a/arcade/examples/images/explosion/explosion0143.png b/arcade/examples/images/explosion/explosion0143.png
new file mode 100644
index 0000000..7116401
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0143.png differ
diff --git a/arcade/examples/images/explosion/explosion0144.png b/arcade/examples/images/explosion/explosion0144.png
new file mode 100644
index 0000000..279b26a
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0144.png differ
diff --git a/arcade/examples/images/explosion/explosion0145.png b/arcade/examples/images/explosion/explosion0145.png
new file mode 100644
index 0000000..7c84ba7
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0145.png differ
diff --git a/arcade/examples/images/explosion/explosion0146.png b/arcade/examples/images/explosion/explosion0146.png
new file mode 100644
index 0000000..7fb43c8
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0146.png differ
diff --git a/arcade/examples/images/explosion/explosion0147.png b/arcade/examples/images/explosion/explosion0147.png
new file mode 100644
index 0000000..7f65f62
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0147.png differ
diff --git a/arcade/examples/images/explosion/explosion0148.png b/arcade/examples/images/explosion/explosion0148.png
new file mode 100644
index 0000000..144ef28
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0148.png differ
diff --git a/arcade/examples/images/explosion/explosion0149.png b/arcade/examples/images/explosion/explosion0149.png
new file mode 100644
index 0000000..f8c54d2
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0149.png differ
diff --git a/arcade/examples/images/explosion/explosion0150.png b/arcade/examples/images/explosion/explosion0150.png
new file mode 100644
index 0000000..67c636c
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0150.png differ
diff --git a/arcade/examples/images/explosion/explosion0151.png b/arcade/examples/images/explosion/explosion0151.png
new file mode 100644
index 0000000..0c19a83
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0151.png differ
diff --git a/arcade/examples/images/explosion/explosion0152.png b/arcade/examples/images/explosion/explosion0152.png
new file mode 100644
index 0000000..3daac28
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0152.png differ
diff --git a/arcade/examples/images/explosion/explosion0153.png b/arcade/examples/images/explosion/explosion0153.png
new file mode 100644
index 0000000..1500cb5
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0153.png differ
diff --git a/arcade/examples/images/explosion/explosion0154.png b/arcade/examples/images/explosion/explosion0154.png
new file mode 100644
index 0000000..6105d62
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0154.png differ
diff --git a/arcade/examples/images/explosion/explosion0155.png b/arcade/examples/images/explosion/explosion0155.png
new file mode 100644
index 0000000..de6756e
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0155.png differ
diff --git a/arcade/examples/images/explosion/explosion0156.png b/arcade/examples/images/explosion/explosion0156.png
new file mode 100644
index 0000000..63737fa
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0156.png differ
diff --git a/arcade/examples/images/explosion/explosion0157.png b/arcade/examples/images/explosion/explosion0157.png
new file mode 100644
index 0000000..63e4bfc
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0157.png differ
diff --git a/arcade/examples/images/explosion/explosion0158.png b/arcade/examples/images/explosion/explosion0158.png
new file mode 100644
index 0000000..6aa8768
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0158.png differ
diff --git a/arcade/examples/images/explosion/explosion0159.png b/arcade/examples/images/explosion/explosion0159.png
new file mode 100644
index 0000000..856db8a
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0159.png differ
diff --git a/arcade/examples/images/explosion/explosion0160.png b/arcade/examples/images/explosion/explosion0160.png
new file mode 100644
index 0000000..31e5496
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0160.png differ
diff --git a/arcade/examples/images/explosion/explosion0161.png b/arcade/examples/images/explosion/explosion0161.png
new file mode 100644
index 0000000..923482e
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0161.png differ
diff --git a/arcade/examples/images/explosion/explosion0162.png b/arcade/examples/images/explosion/explosion0162.png
new file mode 100644
index 0000000..9009878
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0162.png differ
diff --git a/arcade/examples/images/explosion/explosion0163.png b/arcade/examples/images/explosion/explosion0163.png
new file mode 100644
index 0000000..594cc24
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0163.png differ
diff --git a/arcade/examples/images/explosion/explosion0164.png b/arcade/examples/images/explosion/explosion0164.png
new file mode 100644
index 0000000..6bd8697
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0164.png differ
diff --git a/arcade/examples/images/explosion/explosion0165.png b/arcade/examples/images/explosion/explosion0165.png
new file mode 100644
index 0000000..5d7c860
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0165.png differ
diff --git a/arcade/examples/images/explosion/explosion0166.png b/arcade/examples/images/explosion/explosion0166.png
new file mode 100644
index 0000000..9abe834
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0166.png differ
diff --git a/arcade/examples/images/explosion/explosion0167.png b/arcade/examples/images/explosion/explosion0167.png
new file mode 100644
index 0000000..77c2d84
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0167.png differ
diff --git a/arcade/examples/images/explosion/explosion0168.png b/arcade/examples/images/explosion/explosion0168.png
new file mode 100644
index 0000000..30a6d8e
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0168.png differ
diff --git a/arcade/examples/images/explosion/explosion0169.png b/arcade/examples/images/explosion/explosion0169.png
new file mode 100644
index 0000000..4923e8e
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0169.png differ
diff --git a/arcade/examples/images/explosion/explosion0170.png b/arcade/examples/images/explosion/explosion0170.png
new file mode 100644
index 0000000..1973ebb
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0170.png differ
diff --git a/arcade/examples/images/explosion/explosion0171.png b/arcade/examples/images/explosion/explosion0171.png
new file mode 100644
index 0000000..a2dc0e7
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0171.png differ
diff --git a/arcade/examples/images/explosion/explosion0172.png b/arcade/examples/images/explosion/explosion0172.png
new file mode 100644
index 0000000..830f608
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0172.png differ
diff --git a/arcade/examples/images/explosion/explosion0173.png b/arcade/examples/images/explosion/explosion0173.png
new file mode 100644
index 0000000..6193dd5
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0173.png differ
diff --git a/arcade/examples/images/explosion/explosion0174.png b/arcade/examples/images/explosion/explosion0174.png
new file mode 100644
index 0000000..de762e1
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0174.png differ
diff --git a/arcade/examples/images/explosion/explosion0175.png b/arcade/examples/images/explosion/explosion0175.png
new file mode 100644
index 0000000..f6ca0b8
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0175.png differ
diff --git a/arcade/examples/images/explosion/explosion0176.png b/arcade/examples/images/explosion/explosion0176.png
new file mode 100644
index 0000000..bd72434
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0176.png differ
diff --git a/arcade/examples/images/explosion/explosion0177.png b/arcade/examples/images/explosion/explosion0177.png
new file mode 100644
index 0000000..443a938
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0177.png differ
diff --git a/arcade/examples/images/explosion/explosion0178.png b/arcade/examples/images/explosion/explosion0178.png
new file mode 100644
index 0000000..c6f21eb
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0178.png differ
diff --git a/arcade/examples/images/explosion/explosion0179.png b/arcade/examples/images/explosion/explosion0179.png
new file mode 100644
index 0000000..8ae00b2
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0179.png differ
diff --git a/arcade/examples/images/explosion/explosion0180.png b/arcade/examples/images/explosion/explosion0180.png
new file mode 100644
index 0000000..0bffd56
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0180.png differ
diff --git a/arcade/examples/images/explosion/explosion0181.png b/arcade/examples/images/explosion/explosion0181.png
new file mode 100644
index 0000000..59ed3ab
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0181.png differ
diff --git a/arcade/examples/images/explosion/explosion0182.png b/arcade/examples/images/explosion/explosion0182.png
new file mode 100644
index 0000000..e8b5e0e
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0182.png differ
diff --git a/arcade/examples/images/explosion/explosion0183.png b/arcade/examples/images/explosion/explosion0183.png
new file mode 100644
index 0000000..aef28a0
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0183.png differ
diff --git a/arcade/examples/images/explosion/explosion0184.png b/arcade/examples/images/explosion/explosion0184.png
new file mode 100644
index 0000000..1285d69
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0184.png differ
diff --git a/arcade/examples/images/explosion/explosion0185.png b/arcade/examples/images/explosion/explosion0185.png
new file mode 100644
index 0000000..e8c8fba
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0185.png differ
diff --git a/arcade/examples/images/explosion/explosion0186.png b/arcade/examples/images/explosion/explosion0186.png
new file mode 100644
index 0000000..e49af2b
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0186.png differ
diff --git a/arcade/examples/images/explosion/explosion0187.png b/arcade/examples/images/explosion/explosion0187.png
new file mode 100644
index 0000000..75facf8
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0187.png differ
diff --git a/arcade/examples/images/explosion/explosion0188.png b/arcade/examples/images/explosion/explosion0188.png
new file mode 100644
index 0000000..addb758
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0188.png differ
diff --git a/arcade/examples/images/explosion/explosion0189.png b/arcade/examples/images/explosion/explosion0189.png
new file mode 100644
index 0000000..89aeb80
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0189.png differ
diff --git a/arcade/examples/images/explosion/explosion0190.png b/arcade/examples/images/explosion/explosion0190.png
new file mode 100644
index 0000000..5789c79
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0190.png differ
diff --git a/arcade/examples/images/explosion/explosion0191.png b/arcade/examples/images/explosion/explosion0191.png
new file mode 100644
index 0000000..252c331
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0191.png differ
diff --git a/arcade/examples/images/explosion/explosion0192.png b/arcade/examples/images/explosion/explosion0192.png
new file mode 100644
index 0000000..f7be5d9
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0192.png differ
diff --git a/arcade/examples/images/explosion/explosion0193.png b/arcade/examples/images/explosion/explosion0193.png
new file mode 100644
index 0000000..5a5ce77
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0193.png differ
diff --git a/arcade/examples/images/explosion/explosion0194.png b/arcade/examples/images/explosion/explosion0194.png
new file mode 100644
index 0000000..5425b95
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0194.png differ
diff --git a/arcade/examples/images/explosion/explosion0195.png b/arcade/examples/images/explosion/explosion0195.png
new file mode 100644
index 0000000..b263775
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0195.png differ
diff --git a/arcade/examples/images/explosion/explosion0196.png b/arcade/examples/images/explosion/explosion0196.png
new file mode 100644
index 0000000..dc063b1
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0196.png differ
diff --git a/arcade/examples/images/explosion/explosion0197.png b/arcade/examples/images/explosion/explosion0197.png
new file mode 100644
index 0000000..de7ce28
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0197.png differ
diff --git a/arcade/examples/images/explosion/explosion0198.png b/arcade/examples/images/explosion/explosion0198.png
new file mode 100644
index 0000000..0d97451
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0198.png differ
diff --git a/arcade/examples/images/explosion/explosion0199.png b/arcade/examples/images/explosion/explosion0199.png
new file mode 100644
index 0000000..2e0d560
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0199.png differ
diff --git a/arcade/examples/images/explosion/explosion0200.png b/arcade/examples/images/explosion/explosion0200.png
new file mode 100644
index 0000000..94681f0
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0200.png differ
diff --git a/arcade/examples/images/explosion/explosion0201.png b/arcade/examples/images/explosion/explosion0201.png
new file mode 100644
index 0000000..063867b
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0201.png differ
diff --git a/arcade/examples/images/explosion/explosion0202.png b/arcade/examples/images/explosion/explosion0202.png
new file mode 100644
index 0000000..75f8026
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0202.png differ
diff --git a/arcade/examples/images/explosion/explosion0203.png b/arcade/examples/images/explosion/explosion0203.png
new file mode 100644
index 0000000..dd46b4a
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0203.png differ
diff --git a/arcade/examples/images/explosion/explosion0204.png b/arcade/examples/images/explosion/explosion0204.png
new file mode 100644
index 0000000..3395570
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0204.png differ
diff --git a/arcade/examples/images/explosion/explosion0205.png b/arcade/examples/images/explosion/explosion0205.png
new file mode 100644
index 0000000..cf1078b
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0205.png differ
diff --git a/arcade/examples/images/explosion/explosion0206.png b/arcade/examples/images/explosion/explosion0206.png
new file mode 100644
index 0000000..c493802
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0206.png differ
diff --git a/arcade/examples/images/explosion/explosion0207.png b/arcade/examples/images/explosion/explosion0207.png
new file mode 100644
index 0000000..fc0a2c2
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0207.png differ
diff --git a/arcade/examples/images/explosion/explosion0208.png b/arcade/examples/images/explosion/explosion0208.png
new file mode 100644
index 0000000..9a80484
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0208.png differ
diff --git a/arcade/examples/images/explosion/explosion0209.png b/arcade/examples/images/explosion/explosion0209.png
new file mode 100644
index 0000000..f6f86a1
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0209.png differ
diff --git a/arcade/examples/images/explosion/explosion0210.png b/arcade/examples/images/explosion/explosion0210.png
new file mode 100644
index 0000000..3ddea78
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0210.png differ
diff --git a/arcade/examples/images/explosion/explosion0211.png b/arcade/examples/images/explosion/explosion0211.png
new file mode 100644
index 0000000..346a318
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0211.png differ
diff --git a/arcade/examples/images/explosion/explosion0212.png b/arcade/examples/images/explosion/explosion0212.png
new file mode 100644
index 0000000..1b4255b
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0212.png differ
diff --git a/arcade/examples/images/explosion/explosion0213.png b/arcade/examples/images/explosion/explosion0213.png
new file mode 100644
index 0000000..c4ac164
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0213.png differ
diff --git a/arcade/examples/images/explosion/explosion0214.png b/arcade/examples/images/explosion/explosion0214.png
new file mode 100644
index 0000000..c43a38d
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0214.png differ
diff --git a/arcade/examples/images/explosion/explosion0215.png b/arcade/examples/images/explosion/explosion0215.png
new file mode 100644
index 0000000..3bcacb6
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0215.png differ
diff --git a/arcade/examples/images/explosion/explosion0216.png b/arcade/examples/images/explosion/explosion0216.png
new file mode 100644
index 0000000..f7130df
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0216.png differ
diff --git a/arcade/examples/images/explosion/explosion0217.png b/arcade/examples/images/explosion/explosion0217.png
new file mode 100644
index 0000000..9d07ff8
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0217.png differ
diff --git a/arcade/examples/images/explosion/explosion0218.png b/arcade/examples/images/explosion/explosion0218.png
new file mode 100644
index 0000000..2d67503
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0218.png differ
diff --git a/arcade/examples/images/explosion/explosion0219.png b/arcade/examples/images/explosion/explosion0219.png
new file mode 100644
index 0000000..5cb3d42
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0219.png differ
diff --git a/arcade/examples/images/explosion/explosion0220.png b/arcade/examples/images/explosion/explosion0220.png
new file mode 100644
index 0000000..51fe56b
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0220.png differ
diff --git a/arcade/examples/images/explosion/explosion0221.png b/arcade/examples/images/explosion/explosion0221.png
new file mode 100644
index 0000000..8434422
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0221.png differ
diff --git a/arcade/examples/images/explosion/explosion0222.png b/arcade/examples/images/explosion/explosion0222.png
new file mode 100644
index 0000000..26c701c
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0222.png differ
diff --git a/arcade/examples/images/explosion/explosion0223.png b/arcade/examples/images/explosion/explosion0223.png
new file mode 100644
index 0000000..ca6f3be
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0223.png differ
diff --git a/arcade/examples/images/explosion/explosion0224.png b/arcade/examples/images/explosion/explosion0224.png
new file mode 100644
index 0000000..8d051a1
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0224.png differ
diff --git a/arcade/examples/images/explosion/explosion0225.png b/arcade/examples/images/explosion/explosion0225.png
new file mode 100644
index 0000000..3e63c11
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0225.png differ
diff --git a/arcade/examples/images/explosion/explosion0226.png b/arcade/examples/images/explosion/explosion0226.png
new file mode 100644
index 0000000..32288cc
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0226.png differ
diff --git a/arcade/examples/images/explosion/explosion0227.png b/arcade/examples/images/explosion/explosion0227.png
new file mode 100644
index 0000000..c6eaa5a
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0227.png differ
diff --git a/arcade/examples/images/explosion/explosion0228.png b/arcade/examples/images/explosion/explosion0228.png
new file mode 100644
index 0000000..6f55a17
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0228.png differ
diff --git a/arcade/examples/images/explosion/explosion0229.png b/arcade/examples/images/explosion/explosion0229.png
new file mode 100644
index 0000000..5e9ee5a
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0229.png differ
diff --git a/arcade/examples/images/explosion/explosion0230.png b/arcade/examples/images/explosion/explosion0230.png
new file mode 100644
index 0000000..bb8f251
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0230.png differ
diff --git a/arcade/examples/images/explosion/explosion0231.png b/arcade/examples/images/explosion/explosion0231.png
new file mode 100644
index 0000000..5d76593
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0231.png differ
diff --git a/arcade/examples/images/explosion/explosion0232.png b/arcade/examples/images/explosion/explosion0232.png
new file mode 100644
index 0000000..1a20d34
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0232.png differ
diff --git a/arcade/examples/images/explosion/explosion0233.png b/arcade/examples/images/explosion/explosion0233.png
new file mode 100644
index 0000000..cfc2ffe
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0233.png differ
diff --git a/arcade/examples/images/explosion/explosion0234.png b/arcade/examples/images/explosion/explosion0234.png
new file mode 100644
index 0000000..88682cd
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0234.png differ
diff --git a/arcade/examples/images/explosion/explosion0235.png b/arcade/examples/images/explosion/explosion0235.png
new file mode 100644
index 0000000..39b784f
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0235.png differ
diff --git a/arcade/examples/images/explosion/explosion0236.png b/arcade/examples/images/explosion/explosion0236.png
new file mode 100644
index 0000000..0ea2b15
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0236.png differ
diff --git a/arcade/examples/images/explosion/explosion0237.png b/arcade/examples/images/explosion/explosion0237.png
new file mode 100644
index 0000000..82a6483
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0237.png differ
diff --git a/arcade/examples/images/explosion/explosion0238.png b/arcade/examples/images/explosion/explosion0238.png
new file mode 100644
index 0000000..335d3ad
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0238.png differ
diff --git a/arcade/examples/images/explosion/explosion0239.png b/arcade/examples/images/explosion/explosion0239.png
new file mode 100644
index 0000000..756d0f1
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0239.png differ
diff --git a/arcade/examples/images/explosion/explosion0240.png b/arcade/examples/images/explosion/explosion0240.png
new file mode 100644
index 0000000..f56eee0
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0240.png differ
diff --git a/arcade/examples/images/explosion/explosion0241.png b/arcade/examples/images/explosion/explosion0241.png
new file mode 100644
index 0000000..a30d334
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0241.png differ
diff --git a/arcade/examples/images/explosion/explosion0242.png b/arcade/examples/images/explosion/explosion0242.png
new file mode 100644
index 0000000..470e6dd
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0242.png differ
diff --git a/arcade/examples/images/explosion/explosion0243.png b/arcade/examples/images/explosion/explosion0243.png
new file mode 100644
index 0000000..946eabc
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0243.png differ
diff --git a/arcade/examples/images/explosion/explosion0244.png b/arcade/examples/images/explosion/explosion0244.png
new file mode 100644
index 0000000..0cac945
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0244.png differ
diff --git a/arcade/examples/images/explosion/explosion0245.png b/arcade/examples/images/explosion/explosion0245.png
new file mode 100644
index 0000000..f580c80
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0245.png differ
diff --git a/arcade/examples/images/explosion/explosion0246.png b/arcade/examples/images/explosion/explosion0246.png
new file mode 100644
index 0000000..630ce3e
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0246.png differ
diff --git a/arcade/examples/images/explosion/explosion0247.png b/arcade/examples/images/explosion/explosion0247.png
new file mode 100644
index 0000000..2012ae0
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0247.png differ
diff --git a/arcade/examples/images/explosion/explosion0248.png b/arcade/examples/images/explosion/explosion0248.png
new file mode 100644
index 0000000..645be40
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0248.png differ
diff --git a/arcade/examples/images/explosion/explosion0249.png b/arcade/examples/images/explosion/explosion0249.png
new file mode 100644
index 0000000..472e8c5
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0249.png differ
diff --git a/arcade/examples/images/explosion/explosion0250.png b/arcade/examples/images/explosion/explosion0250.png
new file mode 100644
index 0000000..66c87a5
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0250.png differ
diff --git a/arcade/examples/images/explosion/explosion0251.png b/arcade/examples/images/explosion/explosion0251.png
new file mode 100644
index 0000000..1543039
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0251.png differ
diff --git a/arcade/examples/images/explosion/explosion0252.png b/arcade/examples/images/explosion/explosion0252.png
new file mode 100644
index 0000000..7e0e379
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0252.png differ
diff --git a/arcade/examples/images/explosion/explosion0253.png b/arcade/examples/images/explosion/explosion0253.png
new file mode 100644
index 0000000..ade0918
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0253.png differ
diff --git a/arcade/examples/images/explosion/explosion0254.png b/arcade/examples/images/explosion/explosion0254.png
new file mode 100644
index 0000000..92f29a9
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0254.png differ
diff --git a/arcade/examples/images/explosion/explosion0255.png b/arcade/examples/images/explosion/explosion0255.png
new file mode 100644
index 0000000..197c202
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0255.png differ
diff --git a/arcade/examples/images/explosion/explosion0256.png b/arcade/examples/images/explosion/explosion0256.png
new file mode 100644
index 0000000..85d05c0
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0256.png differ
diff --git a/arcade/examples/images/explosion/explosion0257.png b/arcade/examples/images/explosion/explosion0257.png
new file mode 100644
index 0000000..6161492
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0257.png differ
diff --git a/arcade/examples/images/explosion/explosion0258.png b/arcade/examples/images/explosion/explosion0258.png
new file mode 100644
index 0000000..5c2fbac
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0258.png differ
diff --git a/arcade/examples/images/explosion/explosion0259.png b/arcade/examples/images/explosion/explosion0259.png
new file mode 100644
index 0000000..a25ceaf
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0259.png differ
diff --git a/arcade/examples/images/explosion/explosion0260.png b/arcade/examples/images/explosion/explosion0260.png
new file mode 100644
index 0000000..448b296
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0260.png differ
diff --git a/arcade/examples/images/explosion/explosion0261.png b/arcade/examples/images/explosion/explosion0261.png
new file mode 100644
index 0000000..da7c8b0
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0261.png differ
diff --git a/arcade/examples/images/explosion/explosion0262.png b/arcade/examples/images/explosion/explosion0262.png
new file mode 100644
index 0000000..00f4c53
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0262.png differ
diff --git a/arcade/examples/images/explosion/explosion0263.png b/arcade/examples/images/explosion/explosion0263.png
new file mode 100644
index 0000000..4304071
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0263.png differ
diff --git a/arcade/examples/images/explosion/explosion0264.png b/arcade/examples/images/explosion/explosion0264.png
new file mode 100644
index 0000000..502610d
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0264.png differ
diff --git a/arcade/examples/images/explosion/explosion0265.png b/arcade/examples/images/explosion/explosion0265.png
new file mode 100644
index 0000000..4de88ac
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0265.png differ
diff --git a/arcade/examples/images/explosion/explosion0266.png b/arcade/examples/images/explosion/explosion0266.png
new file mode 100644
index 0000000..c73b9db
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0266.png differ
diff --git a/arcade/examples/images/explosion/explosion0267.png b/arcade/examples/images/explosion/explosion0267.png
new file mode 100644
index 0000000..6933595
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0267.png differ
diff --git a/arcade/examples/images/explosion/explosion0268.png b/arcade/examples/images/explosion/explosion0268.png
new file mode 100644
index 0000000..2afaa2d
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0268.png differ
diff --git a/arcade/examples/images/explosion/explosion0269.png b/arcade/examples/images/explosion/explosion0269.png
new file mode 100644
index 0000000..6acafdf
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0269.png differ
diff --git a/arcade/examples/images/explosion/explosion0270.png b/arcade/examples/images/explosion/explosion0270.png
new file mode 100644
index 0000000..5b8e021
Binary files /dev/null and b/arcade/examples/images/explosion/explosion0270.png differ
diff --git a/arcade/examples/images/gold_1.png b/arcade/examples/images/gold_1.png
new file mode 100644
index 0000000..0012dda
Binary files /dev/null and b/arcade/examples/images/gold_1.png differ
diff --git a/arcade/examples/images/gold_2.png b/arcade/examples/images/gold_2.png
new file mode 100644
index 0000000..59bc795
Binary files /dev/null and b/arcade/examples/images/gold_2.png differ
diff --git a/arcade/examples/images/gold_3.png b/arcade/examples/images/gold_3.png
new file mode 100644
index 0000000..9df8e56
Binary files /dev/null and b/arcade/examples/images/gold_3.png differ
diff --git a/arcade/examples/images/gold_4.png b/arcade/examples/images/gold_4.png
new file mode 100644
index 0000000..1d40782
Binary files /dev/null and b/arcade/examples/images/gold_4.png differ
diff --git a/arcade/examples/images/grassCenter.png b/arcade/examples/images/grassCenter.png
new file mode 100644
index 0000000..d595bdc
Binary files /dev/null and b/arcade/examples/images/grassCenter.png differ
diff --git a/arcade/examples/images/grassCorner_left.png b/arcade/examples/images/grassCorner_left.png
new file mode 100644
index 0000000..385aa96
Binary files /dev/null and b/arcade/examples/images/grassCorner_left.png differ
diff --git a/arcade/examples/images/grassCorner_right.png b/arcade/examples/images/grassCorner_right.png
new file mode 100644
index 0000000..407a7d0
Binary files /dev/null and b/arcade/examples/images/grassCorner_right.png differ
diff --git a/arcade/examples/images/grassHill_left.png b/arcade/examples/images/grassHill_left.png
new file mode 100644
index 0000000..d9aab2e
Binary files /dev/null and b/arcade/examples/images/grassHill_left.png differ
diff --git a/arcade/examples/images/grassHill_right.png b/arcade/examples/images/grassHill_right.png
new file mode 100644
index 0000000..2d769d7
Binary files /dev/null and b/arcade/examples/images/grassHill_right.png differ
diff --git a/arcade/examples/images/grassLeft.png b/arcade/examples/images/grassLeft.png
new file mode 100644
index 0000000..f7cdc03
Binary files /dev/null and b/arcade/examples/images/grassLeft.png differ
diff --git a/arcade/examples/images/grassMid.png b/arcade/examples/images/grassMid.png
new file mode 100644
index 0000000..acd03cd
Binary files /dev/null and b/arcade/examples/images/grassMid.png differ
diff --git a/arcade/examples/images/grassRight.png b/arcade/examples/images/grassRight.png
new file mode 100644
index 0000000..2e15ec4
Binary files /dev/null and b/arcade/examples/images/grassRight.png differ
diff --git a/arcade/examples/images/instructions_0.png b/arcade/examples/images/instructions_0.png
new file mode 100644
index 0000000..79e4717
Binary files /dev/null and b/arcade/examples/images/instructions_0.png differ
diff --git a/arcade/examples/images/instructions_1.png b/arcade/examples/images/instructions_1.png
new file mode 100644
index 0000000..3ceedb8
Binary files /dev/null and b/arcade/examples/images/instructions_1.png differ
diff --git a/arcade/examples/images/laserBlue01.png b/arcade/examples/images/laserBlue01.png
new file mode 100644
index 0000000..93e9b61
Binary files /dev/null and b/arcade/examples/images/laserBlue01.png differ
diff --git a/arcade/examples/images/meteorGrey_big1.png b/arcade/examples/images/meteorGrey_big1.png
new file mode 100644
index 0000000..74371c3
Binary files /dev/null and b/arcade/examples/images/meteorGrey_big1.png differ
diff --git a/arcade/examples/images/meteorGrey_big2.png b/arcade/examples/images/meteorGrey_big2.png
new file mode 100644
index 0000000..bcc8dc7
Binary files /dev/null and b/arcade/examples/images/meteorGrey_big2.png differ
diff --git a/arcade/examples/images/meteorGrey_big3.png b/arcade/examples/images/meteorGrey_big3.png
new file mode 100644
index 0000000..0bb8674
Binary files /dev/null and b/arcade/examples/images/meteorGrey_big3.png differ
diff --git a/arcade/examples/images/meteorGrey_big4.png b/arcade/examples/images/meteorGrey_big4.png
new file mode 100644
index 0000000..6b7e708
Binary files /dev/null and b/arcade/examples/images/meteorGrey_big4.png differ
diff --git a/arcade/examples/images/meteorGrey_med1.png b/arcade/examples/images/meteorGrey_med1.png
new file mode 100644
index 0000000..9104746
Binary files /dev/null and b/arcade/examples/images/meteorGrey_med1.png differ
diff --git a/arcade/examples/images/meteorGrey_med2.png b/arcade/examples/images/meteorGrey_med2.png
new file mode 100644
index 0000000..e63e2f6
Binary files /dev/null and b/arcade/examples/images/meteorGrey_med2.png differ
diff --git a/arcade/examples/images/meteorGrey_small1.png b/arcade/examples/images/meteorGrey_small1.png
new file mode 100644
index 0000000..a559e9b
Binary files /dev/null and b/arcade/examples/images/meteorGrey_small1.png differ
diff --git a/arcade/examples/images/meteorGrey_small2.png b/arcade/examples/images/meteorGrey_small2.png
new file mode 100644
index 0000000..9f815b5
Binary files /dev/null and b/arcade/examples/images/meteorGrey_small2.png differ
diff --git a/arcade/examples/images/meteorGrey_tiny1.png b/arcade/examples/images/meteorGrey_tiny1.png
new file mode 100644
index 0000000..0b75fa8
Binary files /dev/null and b/arcade/examples/images/meteorGrey_tiny1.png differ
diff --git a/arcade/examples/images/meteorGrey_tiny2.png b/arcade/examples/images/meteorGrey_tiny2.png
new file mode 100644
index 0000000..75f944d
Binary files /dev/null and b/arcade/examples/images/meteorGrey_tiny2.png differ
diff --git a/arcade/examples/images/playerLife1_orange.png b/arcade/examples/images/playerLife1_orange.png
new file mode 100644
index 0000000..8c02aa2
Binary files /dev/null and b/arcade/examples/images/playerLife1_orange.png differ
diff --git a/arcade/examples/images/playerShip1_green.png b/arcade/examples/images/playerShip1_green.png
new file mode 100644
index 0000000..2eb6f9c
Binary files /dev/null and b/arcade/examples/images/playerShip1_green.png differ
diff --git a/arcade/examples/images/playerShip1_orange.png b/arcade/examples/images/playerShip1_orange.png
new file mode 100644
index 0000000..3902283
Binary files /dev/null and b/arcade/examples/images/playerShip1_orange.png differ
diff --git a/arcade/examples/images/playerShip2_orange.png b/arcade/examples/images/playerShip2_orange.png
new file mode 100644
index 0000000..82ddc80
Binary files /dev/null and b/arcade/examples/images/playerShip2_orange.png differ
diff --git a/arcade/examples/images/playerShip3_orange.png b/arcade/examples/images/playerShip3_orange.png
new file mode 100644
index 0000000..0b6b7ec
Binary files /dev/null and b/arcade/examples/images/playerShip3_orange.png differ
diff --git a/arcade/examples/images/pool_cue_ball.png b/arcade/examples/images/pool_cue_ball.png
new file mode 100644
index 0000000..ec90308
Binary files /dev/null and b/arcade/examples/images/pool_cue_ball.png differ
diff --git a/arcade/examples/images/pool_cue_ball.svg b/arcade/examples/images/pool_cue_ball.svg
new file mode 100644
index 0000000..e6d1d41
--- /dev/null
+++ b/arcade/examples/images/pool_cue_ball.svg
@@ -0,0 +1,91 @@
+
+
+
+
diff --git a/arcade/examples/images/python_logo.png b/arcade/examples/images/python_logo.png
new file mode 100644
index 0000000..71b0815
Binary files /dev/null and b/arcade/examples/images/python_logo.png differ
diff --git a/arcade/examples/images/python_logo.svg b/arcade/examples/images/python_logo.svg
new file mode 100644
index 0000000..40d0e4e
--- /dev/null
+++ b/arcade/examples/images/python_logo.svg
@@ -0,0 +1,145 @@
+
+
+
+
diff --git a/arcade/examples/images/stoneHalf.png b/arcade/examples/images/stoneHalf.png
new file mode 100644
index 0000000..972d932
Binary files /dev/null and b/arcade/examples/images/stoneHalf.png differ
diff --git a/arcade/examples/images/tiles_spritesheet.png b/arcade/examples/images/tiles_spritesheet.png
new file mode 100644
index 0000000..57ee83b
Binary files /dev/null and b/arcade/examples/images/tiles_spritesheet.png differ
diff --git a/arcade/examples/images/tiles_spritesheet.xml b/arcade/examples/images/tiles_spritesheet.xml
new file mode 100644
index 0000000..0a406d7
--- /dev/null
+++ b/arcade/examples/images/tiles_spritesheet.xml
@@ -0,0 +1,174 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/arcade/examples/images/wormGreen.png b/arcade/examples/images/wormGreen.png
new file mode 100644
index 0000000..5e56ee7
Binary files /dev/null and b/arcade/examples/images/wormGreen.png differ
diff --git a/arcade/examples/isometric_dungeon/dirtTiles_S.png b/arcade/examples/isometric_dungeon/dirtTiles_S.png
new file mode 100644
index 0000000..37e860e
Binary files /dev/null and b/arcade/examples/isometric_dungeon/dirtTiles_S.png differ
diff --git a/arcade/examples/isometric_dungeon/dirt_S.png b/arcade/examples/isometric_dungeon/dirt_S.png
new file mode 100644
index 0000000..70e3b3d
Binary files /dev/null and b/arcade/examples/isometric_dungeon/dirt_S.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneLeft_N.png b/arcade/examples/isometric_dungeon/stoneLeft_N.png
new file mode 100644
index 0000000..cd978df
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneLeft_N.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneMissingTiles_E.png b/arcade/examples/isometric_dungeon/stoneMissingTiles_E.png
new file mode 100644
index 0000000..ba0af75
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneMissingTiles_E.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneMissingTiles_N.png b/arcade/examples/isometric_dungeon/stoneMissingTiles_N.png
new file mode 100644
index 0000000..b955c9c
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneMissingTiles_N.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneMissingTiles_S.png b/arcade/examples/isometric_dungeon/stoneMissingTiles_S.png
new file mode 100644
index 0000000..de97698
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneMissingTiles_S.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneMissingTiles_W.png b/arcade/examples/isometric_dungeon/stoneMissingTiles_W.png
new file mode 100644
index 0000000..cd39d6f
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneMissingTiles_W.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneSideUneven_N.png b/arcade/examples/isometric_dungeon/stoneSideUneven_N.png
new file mode 100644
index 0000000..4d96645
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneSideUneven_N.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneSide_E.png b/arcade/examples/isometric_dungeon/stoneSide_E.png
new file mode 100644
index 0000000..f7c6fb9
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneSide_E.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneTile_N.png b/arcade/examples/isometric_dungeon/stoneTile_N.png
new file mode 100644
index 0000000..a712017
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneTile_N.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneTile_S.png b/arcade/examples/isometric_dungeon/stoneTile_S.png
new file mode 100644
index 0000000..9d88a73
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneTile_S.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneTile_W.png b/arcade/examples/isometric_dungeon/stoneTile_W.png
new file mode 100644
index 0000000..eb9c8aa
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneTile_W.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneUneven_E.png b/arcade/examples/isometric_dungeon/stoneUneven_E.png
new file mode 100644
index 0000000..bbcbe2b
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneUneven_E.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneUneven_N.png b/arcade/examples/isometric_dungeon/stoneUneven_N.png
new file mode 100644
index 0000000..2864fa4
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneUneven_N.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneUneven_S.png b/arcade/examples/isometric_dungeon/stoneUneven_S.png
new file mode 100644
index 0000000..9df6c00
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneUneven_S.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneUneven_W.png b/arcade/examples/isometric_dungeon/stoneUneven_W.png
new file mode 100644
index 0000000..4d1abff
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneUneven_W.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneWallAged_E.png b/arcade/examples/isometric_dungeon/stoneWallAged_E.png
new file mode 100644
index 0000000..9798648
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneWallAged_E.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneWallAged_S.png b/arcade/examples/isometric_dungeon/stoneWallAged_S.png
new file mode 100644
index 0000000..e9ceaea
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneWallAged_S.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneWallArchway_S.png b/arcade/examples/isometric_dungeon/stoneWallArchway_S.png
new file mode 100644
index 0000000..c65f54b
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneWallArchway_S.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneWallColumn_E.png b/arcade/examples/isometric_dungeon/stoneWallColumn_E.png
new file mode 100644
index 0000000..b9e9bba
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneWallColumn_E.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneWallCorner_E.png b/arcade/examples/isometric_dungeon/stoneWallCorner_E.png
new file mode 100644
index 0000000..6dfc010
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneWallCorner_E.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneWallCorner_N.png b/arcade/examples/isometric_dungeon/stoneWallCorner_N.png
new file mode 100644
index 0000000..36de47b
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneWallCorner_N.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneWallCorner_S.png b/arcade/examples/isometric_dungeon/stoneWallCorner_S.png
new file mode 100644
index 0000000..6a0710e
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneWallCorner_S.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneWallCorner_W.png b/arcade/examples/isometric_dungeon/stoneWallCorner_W.png
new file mode 100644
index 0000000..b12257a
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneWallCorner_W.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneWallGateClosed_E.png b/arcade/examples/isometric_dungeon/stoneWallGateClosed_E.png
new file mode 100644
index 0000000..84862c6
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneWallGateClosed_E.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneWallGateClosed_S.png b/arcade/examples/isometric_dungeon/stoneWallGateClosed_S.png
new file mode 100644
index 0000000..6fca1c9
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneWallGateClosed_S.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneWallGateOpen_E.png b/arcade/examples/isometric_dungeon/stoneWallGateOpen_E.png
new file mode 100644
index 0000000..f9a7609
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneWallGateOpen_E.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneWall_N.png b/arcade/examples/isometric_dungeon/stoneWall_N.png
new file mode 100644
index 0000000..e4ec986
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneWall_N.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneWall_S.png b/arcade/examples/isometric_dungeon/stoneWall_S.png
new file mode 100644
index 0000000..28a107e
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneWall_S.png differ
diff --git a/arcade/examples/isometric_dungeon/stoneWall_W.png b/arcade/examples/isometric_dungeon/stoneWall_W.png
new file mode 100644
index 0000000..017418d
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stoneWall_W.png differ
diff --git a/arcade/examples/isometric_dungeon/stone_E.png b/arcade/examples/isometric_dungeon/stone_E.png
new file mode 100644
index 0000000..bb0ff9e
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stone_E.png differ
diff --git a/arcade/examples/isometric_dungeon/stone_N.png b/arcade/examples/isometric_dungeon/stone_N.png
new file mode 100644
index 0000000..8d712b5
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stone_N.png differ
diff --git a/arcade/examples/isometric_dungeon/stone_S.png b/arcade/examples/isometric_dungeon/stone_S.png
new file mode 100644
index 0000000..3737fcb
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stone_S.png differ
diff --git a/arcade/examples/isometric_dungeon/stone_W.png b/arcade/examples/isometric_dungeon/stone_W.png
new file mode 100644
index 0000000..4141821
Binary files /dev/null and b/arcade/examples/isometric_dungeon/stone_W.png differ
diff --git a/arcade/examples/isometric_dungeon/tableChairsBroken_E.png b/arcade/examples/isometric_dungeon/tableChairsBroken_E.png
new file mode 100644
index 0000000..b47f966
Binary files /dev/null and b/arcade/examples/isometric_dungeon/tableChairsBroken_E.png differ
diff --git a/arcade/examples/isometric_dungeon/tableChairsBroken_S.png b/arcade/examples/isometric_dungeon/tableChairsBroken_S.png
new file mode 100644
index 0000000..4f16195
Binary files /dev/null and b/arcade/examples/isometric_dungeon/tableChairsBroken_S.png differ
diff --git a/arcade/examples/isometric_dungeon/tableShortChairs_W.png b/arcade/examples/isometric_dungeon/tableShortChairs_W.png
new file mode 100644
index 0000000..bb15d25
Binary files /dev/null and b/arcade/examples/isometric_dungeon/tableShortChairs_W.png differ
diff --git a/arcade/examples/isometric_dungeon/woodenCrates_W.png b/arcade/examples/isometric_dungeon/woodenCrates_W.png
new file mode 100644
index 0000000..fd9f232
Binary files /dev/null and b/arcade/examples/isometric_dungeon/woodenCrates_W.png differ
diff --git a/arcade/examples/isometric_dungeon/woodenSupportBeams_S.png b/arcade/examples/isometric_dungeon/woodenSupportBeams_S.png
new file mode 100644
index 0000000..827ba6b
Binary files /dev/null and b/arcade/examples/isometric_dungeon/woodenSupportBeams_S.png differ
diff --git a/arcade/examples/isometric_dungeon/woodenSupportsBeam_S.png b/arcade/examples/isometric_dungeon/woodenSupportsBeam_S.png
new file mode 100644
index 0000000..2710eb6
Binary files /dev/null and b/arcade/examples/isometric_dungeon/woodenSupportsBeam_S.png differ
diff --git a/arcade/examples/isometric_example.py b/arcade/examples/isometric_example.py
new file mode 100644
index 0000000..ae046a1
--- /dev/null
+++ b/arcade/examples/isometric_example.py
@@ -0,0 +1,193 @@
+"""
+Example of displaying an isometric map.
+
+Isometric map created with Tiled Map Editor: https://www.mapeditor.org/
+Tiles by Kenney: http://kenney.nl/assets/isometric-dungeon-tiles
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.isometric_example
+"""
+
+import arcade
+import os
+
+SPRITE_SCALING = 0.5
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Isometric Example"
+
+# How many pixels to keep as a minimum margin between the character
+# and the edge of the screen.
+VIEWPORT_MARGIN = 200
+
+MOVEMENT_SPEED = 5
+
+
+def read_sprite_list(grid, sprite_list):
+ for row in grid:
+ for grid_location in row:
+ if grid_location.tile is not None:
+ tile_sprite = arcade.Sprite(grid_location.tile.source, SPRITE_SCALING)
+ tile_sprite.center_x = grid_location.center_x * SPRITE_SCALING
+ tile_sprite.center_y = grid_location.center_y * SPRITE_SCALING
+ # print(f"{grid_location.tile.source} -- ({tile_sprite.center_x:4}, {tile_sprite.center_y:4})")
+ sprite_list.append(tile_sprite)
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self, width, height, title):
+ """
+ Initializer
+ """
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Sprite lists
+ self.all_sprites_list = None
+
+ # Set up the player
+ self.player_sprite = None
+ self.wall_list = None
+ self.floor_list = None
+ self.objects_list = None
+ self.player_list = None
+ self.view_bottom = 0
+ self.view_left = 0
+ self.my_map = None
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.wall_list = arcade.SpriteList()
+ self.floor_list = arcade.SpriteList()
+ self.objects_list = arcade.SpriteList()
+
+ self.my_map = arcade.read_tiled_map('dungeon.tmx', SPRITE_SCALING)
+
+ # Set up the player
+ self.player_sprite = arcade.Sprite("images/character.png", 0.4)
+ px, py = arcade.isometric_grid_to_screen(self.my_map.width // 2,
+ self.my_map.height // 2,
+ self.my_map.width,
+ self.my_map.height,
+ self.my_map.tilewidth,
+ self.my_map.tileheight)
+
+ self.player_sprite.center_x = px * SPRITE_SCALING
+ self.player_sprite.center_y = py * SPRITE_SCALING
+ self.player_list.append(self.player_sprite)
+
+ read_sprite_list(self.my_map.layers["Floor"], self.floor_list)
+ read_sprite_list(self.my_map.layers["Walls"], self.wall_list)
+ read_sprite_list(self.my_map.layers["Furniture"], self.wall_list)
+
+ # Set the background color
+ if self.my_map.backgroundcolor is None:
+ arcade.set_background_color(arcade.color.BLACK)
+ else:
+ arcade.set_background_color(self.my_map.backgroundcolor)
+
+ # Set the viewport boundaries
+ # These numbers set where we have 'scrolled' to.
+ self.view_left = 0
+ self.view_bottom = 0
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.floor_list.draw()
+ self.player_list.draw()
+ self.wall_list.draw()
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ if key == arcade.key.UP:
+ self.player_sprite.change_y = MOVEMENT_SPEED
+ elif key == arcade.key.DOWN:
+ self.player_sprite.change_y = -MOVEMENT_SPEED
+ elif key == arcade.key.LEFT:
+ self.player_sprite.change_x = -MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT:
+ self.player_sprite.change_x = MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.UP or key == arcade.key.DOWN:
+ self.player_sprite.change_y = 0
+ elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.player_sprite.update()
+
+ # --- Manage Scrolling ---
+
+ # Track if we need to change the viewport
+
+ changed = False
+
+ # Scroll left
+ left_bndry = self.view_left + VIEWPORT_MARGIN
+ if self.player_sprite.left < left_bndry:
+ self.view_left -= left_bndry - self.player_sprite.left
+ changed = True
+
+ # Scroll right
+ right_bndry = self.view_left + SCREEN_WIDTH - VIEWPORT_MARGIN
+ if self.player_sprite.right > right_bndry:
+ self.view_left += self.player_sprite.right - right_bndry
+ changed = True
+
+ # Scroll up
+ top_bndry = self.view_bottom + SCREEN_HEIGHT - VIEWPORT_MARGIN
+ if self.player_sprite.top > top_bndry:
+ self.view_bottom += self.player_sprite.top - top_bndry
+ changed = True
+
+ # Scroll down
+ bottom_bndry = self.view_bottom + VIEWPORT_MARGIN
+ if self.player_sprite.bottom < bottom_bndry:
+ self.view_bottom -= bottom_bndry - self.player_sprite.bottom
+ changed = True
+
+ if changed:
+ self.view_left = int(self.view_left)
+ self.view_bottom = int(self.view_bottom)
+ arcade.set_viewport(self.view_left,
+ SCREEN_WIDTH + self.view_left,
+ self.view_bottom,
+ SCREEN_HEIGHT + self.view_bottom)
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/joystick.py b/arcade/examples/joystick.py
new file mode 100644
index 0000000..16bd196
--- /dev/null
+++ b/arcade/examples/joystick.py
@@ -0,0 +1,148 @@
+"""
+This simple animation example shows how to move an item with the joystick.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.joystick
+"""
+
+import arcade
+
+# Set up the constants
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Joystick Control Example"
+
+RECT_WIDTH = 50
+RECT_HEIGHT = 50
+
+MOVEMENT_MULTIPLIER = 5
+DEAD_ZONE = 0.05
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title)
+ self.player = None
+ self.left_down = False
+
+ joysticks = arcade.get_joysticks()
+ if joysticks:
+ self.joystick = joysticks[0]
+ self.joystick.open()
+ self.joystick.on_joybutton_press = self.on_joybutton_press
+ self.joystick.on_joybutton_release = self.on_joybutton_release
+ self.joystick.on_joyhat_motion = self.on_joyhat_motion
+ else:
+ print("There are no Joysticks")
+ self.joystick = None
+
+ def on_joybutton_press(self, joystick, button):
+ print("Button {} down".format(button))
+
+ def on_joybutton_release(self, joystick, button):
+ print("Button {} up".format(button))
+
+ def on_joyhat_motion(self, joystick, hat_x, hat_y):
+ print("Hat ({}, {})".format(hat_x, hat_y))
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+ width = RECT_WIDTH
+ height = RECT_HEIGHT
+ x = SCREEN_WIDTH // 2
+ y = SCREEN_HEIGHT // 2
+ angle = 0
+ color = arcade.color.WHITE
+ self.player = Rectangle(x, y, width, height, angle, color)
+ self.left_down = False
+
+ def update(self, dt):
+ # Grab the position of the joystick
+ # This will be between -1.0 and +1.0
+
+ if self.joystick:
+ self.player.delta_x = self.joystick.x * MOVEMENT_MULTIPLIER
+ # Set a "dead zone" to prevent drive from a centered joystick
+ if abs(self.player.delta_x) < DEAD_ZONE:
+ self.player.delta_x = 0
+
+ self.player.delta_y = -self.joystick.y * MOVEMENT_MULTIPLIER
+ # Set a "dead zone" to prevent drive from a centered joystick
+ if abs(self.player.delta_y) < DEAD_ZONE:
+ self.player.delta_y = 0
+
+ """ Move everything """
+ self.player.move()
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+ arcade.start_render()
+
+ self.player.draw()
+
+
+class Rectangle:
+
+ """ Class to represent a rectangle on the screen """
+
+ def __init__(self, x, y, width, height, angle, color):
+ """ Initialize our rectangle variables """
+
+ # Position
+ self.x = x
+ self.y = y
+
+ # Vector
+ self.delta_x = 0
+ self.delta_y = 0
+
+ # Size and rotation
+ self.width = width
+ self.height = height
+ self.angle = angle
+
+ # Color
+ self.color = color
+
+ def draw(self):
+ """ Draw our rectangle """
+ arcade.draw_rectangle_filled(self.x, self.y, self.width, self.height,
+ self.color, self.angle)
+
+ def move(self):
+
+ """ Move our rectangle """
+
+ # Move left/right
+ self.x += self.delta_x
+
+ # See if we've gone beyond the border. If so, reset our position
+ # back to the border.
+ if self.x < RECT_WIDTH // 2:
+ self.x = RECT_WIDTH // 2
+ if self.x > SCREEN_WIDTH - (RECT_WIDTH // 2):
+ self.x = SCREEN_WIDTH - (RECT_WIDTH // 2)
+
+ # Move up/down
+ self.y += self.delta_y
+
+ # Check top and bottom boundaries
+ if self.y < RECT_HEIGHT // 2:
+ self.y = RECT_HEIGHT // 2
+ if self.y > SCREEN_HEIGHT - (RECT_HEIGHT // 2):
+ self.y = SCREEN_HEIGHT - (RECT_HEIGHT // 2)
+
+
+def main():
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/level_1.tmx b/arcade/examples/level_1.tmx
new file mode 100644
index 0000000..2019fef
--- /dev/null
+++ b/arcade/examples/level_1.tmx
@@ -0,0 +1,9 @@
+
+
diff --git a/arcade/examples/level_2.tmx b/arcade/examples/level_2.tmx
new file mode 100644
index 0000000..b974f9e
--- /dev/null
+++ b/arcade/examples/level_2.tmx
@@ -0,0 +1,9 @@
+
+
diff --git a/arcade/examples/lines_buffered.py b/arcade/examples/lines_buffered.py
new file mode 100644
index 0000000..b2a4300
--- /dev/null
+++ b/arcade/examples/lines_buffered.py
@@ -0,0 +1,78 @@
+"""
+Using a Vertex Buffer Object With Lines
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.lines_buffered
+"""
+import arcade
+import random
+
+# Do the math to figure out our screen dimensions
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Vertex Buffer Object With Lines Example"
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ """
+ Set up the application.
+ """
+ super().__init__(width, height, title)
+
+ self.shape_list = arcade.ShapeElementList()
+ point_list = ((0, 50),
+ (10, 10),
+ (50, 0),
+ (10, -10),
+ (0, -50),
+ (-10, -10),
+ (-50, 0),
+ (-10, 10),
+ (0, 50))
+ colors = [
+ getattr(arcade.color, color)
+ for color in dir(arcade.color)
+ if not color.startswith("__")
+ ]
+ for i in range(200):
+ x = SCREEN_WIDTH // 2 - random.randrange(SCREEN_WIDTH)
+ y = SCREEN_HEIGHT // 2 - random.randrange(SCREEN_HEIGHT)
+ color = random.choice(colors)
+ points = [(px + x, py + y) for px, py in point_list]
+
+ my_line_strip = arcade.create_line_strip(points, color, 5)
+ self.shape_list.append(my_line_strip)
+
+ self.shape_list.center_x = SCREEN_WIDTH // 2
+ self.shape_list.center_y = SCREEN_HEIGHT // 2
+ self.shape_list.angle = 0
+
+ arcade.set_background_color(arcade.color.BLACK)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ self.shape_list.draw()
+
+ def update(self, delta_time):
+ self.shape_list.angle += 1
+ self.shape_list.center_x += 0.1
+ self.shape_list.center_y += 0.1
+
+
+def main():
+ MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/map.csv b/arcade/examples/map.csv
new file mode 100644
index 0000000..51a4f0b
--- /dev/null
+++ b/arcade/examples/map.csv
@@ -0,0 +1,7 @@
+-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,1,2,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,1,2,3,-1,-1,-1,-1,-1,-1,-1,1,2,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0
+0,-1,-1,-1,1,2,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,-1,-1,-1,-1,-1,-1,0
+0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,-1,-1,-1,-1,-1,-1,-1,1,2,3,-1,-1,-1,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,-1,-1,-1,-1,-1,0
+0,-1,-1,-1,-1,-1,-1,-1,-1,0,-1,-1,-1,-1,-1,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,-1,-1,0,-1,-1,-1,-1,-1,0,0,0,0,0,-1,-1,-1,-1,0
+1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3
diff --git a/arcade/examples/map.tmx b/arcade/examples/map.tmx
new file mode 100644
index 0000000..71ed931
--- /dev/null
+++ b/arcade/examples/map.tmx
@@ -0,0 +1,43 @@
+
+
diff --git a/arcade/examples/map_with_custom_hitboxes.tmx b/arcade/examples/map_with_custom_hitboxes.tmx
new file mode 100644
index 0000000..1d6e85c
--- /dev/null
+++ b/arcade/examples/map_with_custom_hitboxes.tmx
@@ -0,0 +1,79 @@
+
+
diff --git a/arcade/examples/map_with_external_tileset.tmx b/arcade/examples/map_with_external_tileset.tmx
new file mode 100644
index 0000000..ed05a9f
--- /dev/null
+++ b/arcade/examples/map_with_external_tileset.tmx
@@ -0,0 +1,9 @@
+
+
diff --git a/arcade/examples/map_with_ramps.csv b/arcade/examples/map_with_ramps.csv
new file mode 100644
index 0000000..ac603fe
--- /dev/null
+++ b/arcade/examples/map_with_ramps.csv
@@ -0,0 +1,7 @@
+-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,8,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+-1,-1,-1,-1,2,-1,-1,0,-1,-1,-1,-1,-1,-1,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,6,-1,7,8,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+-1,-1,-1,2,9,-1,-1,0,-1,-1,-1,-1,2,4,4,1,-1,-1,0,-1,-1,-1,-1,-1,-1,-1,6,-1,-1,-1,7,7,8,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+-1,-1,2,9,7,-1,-1,0,-1,-1,-1,2,9,7,7,8,1,-1,0,-1,-1,-1,-1,-1,6,-1,-1,-1,-1,-1,7,7,7,8,1,-1,-1,-1,-1,-1,0,-1,-1,-1,-1,-1,-1,-1,-1,-1
+-1,2,9,7,7,-1,-1,0,-1,-1,2,9,7,7,7,7,8,4,4,4,1,-1,0,-1,-1,-1,-1,-1,-1,-1,7,7,7,7,8,1,-1,0,0,-1,0,-1,-1,-1,-1,-1,-1,-1,-1,-1
+4,4,4,4,4,4,4,4,4,4,9,7,7,7,7,7,7,7,7,7,8,4,4,4,4,4,4,4,4,4,7,7,7,7,7,8,4,4,4,4,4,4,4,4,4,4,4,4,4,4
diff --git a/arcade/examples/map_with_ramps_2.csv b/arcade/examples/map_with_ramps_2.csv
new file mode 100644
index 0000000..a4d239d
--- /dev/null
+++ b/arcade/examples/map_with_ramps_2.csv
@@ -0,0 +1,7 @@
+-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,5,-1,-1,4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,5,3,7,7,2,4,-1,-1,-1,-1,-1,5,7,4,-1
+-1,-1,-1,-1,-1,-1,-1,-1,9,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,5,7,4,-1,-1,-1,-1,-1,-1,5,7,-1,-1,5,3,1,1,1,1,2,4,-1,-1,-1,6,3,1,2,8
+-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,5,4,-1,-1,-1,-1,-1,5,7,4,5,3,1,2,4,-1,-1,-1,-1,5,3,1,-1,5,3,1,1,1,1,1,1,2,4,-1,-1,-1,0,0,0,-1
+-1,-1,-1,-1,5,7,4,-1,-1,5,3,2,4,-1,-1,5,7,3,1,2,3,1,1,1,2,4,-1,-1,5,3,1,1,7,3,1,1,1,1,1,1,1,1,2,4,-1,-1,0,0,0,-1
+6,7,7,7,3,1,2,7,7,3,1,1,2,7,7,3,1,1,1,1,1,1,1,1,1,2,7,7,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,7,7,7,7,7,8
diff --git a/arcade/examples/maze_depth_first.py b/arcade/examples/maze_depth_first.py
new file mode 100644
index 0000000..c6c024c
--- /dev/null
+++ b/arcade/examples/maze_depth_first.py
@@ -0,0 +1,312 @@
+"""
+Create a maze using a depth-first search maze generation algorithm.
+For more information on this algorithm see:
+http://www.algosome.com/articles/maze-generation-depth-first.html
+...or search up some other examples.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.maze_depth_first
+"""
+import random
+import arcade
+import timeit
+import os
+
+NATIVE_SPRITE_SIZE = 128
+SPRITE_SCALING = 0.25
+SPRITE_SIZE = NATIVE_SPRITE_SIZE * SPRITE_SCALING
+
+SCREEN_WIDTH = 1000
+SCREEN_HEIGHT = 700
+SCREEN_TITLE = "Maze Depth First Example"
+
+MOVEMENT_SPEED = 8
+
+TILE_EMPTY = 0
+TILE_CRATE = 1
+
+# Maze must have an ODD number of rows and columns.
+# Walls go on EVEN rows/columns.
+# Openings go on ODD rows/columns
+MAZE_HEIGHT = 51
+MAZE_WIDTH = 51
+
+MERGE_SPRITES = True
+
+# How many pixels to keep as a minimum margin between the character
+# and the edge of the screen.
+VIEWPORT_MARGIN = 200
+
+
+def _create_grid_with_cells(width, height):
+ """ Create a grid with empty cells on odd row/column combinations. """
+ grid = []
+ for row in range(height):
+ grid.append([])
+ for column in range(width):
+ if column % 2 == 1 and row % 2 == 1:
+ grid[row].append(TILE_EMPTY)
+ elif column == 0 or row == 0 or column == width - 1 or row == height - 1:
+ grid[row].append(TILE_CRATE)
+ else:
+ grid[row].append(TILE_CRATE)
+ return grid
+
+
+def make_maze_depth_first(maze_width, maze_height):
+ maze = _create_grid_with_cells(maze_width, maze_height)
+
+ w = (len(maze[0]) - 1) // 2
+ h = (len(maze) - 1) // 2
+ vis = [[0] * w + [1] for _ in range(h)] + [[1] * (w + 1)]
+
+ def walk(x, y):
+ vis[y][x] = 1
+
+ d = [(x - 1, y), (x, y + 1), (x + 1, y), (x, y - 1)]
+ random.shuffle(d)
+ for (xx, yy) in d:
+ if vis[yy][xx]:
+ continue
+ if xx == x:
+ maze[max(y, yy) * 2][x * 2 + 1] = TILE_EMPTY
+ if yy == y:
+ maze[y * 2 + 1][max(x, xx) * 2] = TILE_EMPTY
+
+ walk(xx, yy)
+
+ walk(random.randrange(w), random.randrange(h))
+
+ return maze
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self, width, height, title):
+ """
+ Initializer
+ """
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Sprite lists
+ self.player_list = None
+ self.wall_list = None
+
+ # Player info
+ self.score = 0
+ self.player_sprite = None
+
+ # Physics engine
+ self.physics_engine = None
+
+ # Used to scroll
+ self.view_bottom = 0
+ self.view_left = 0
+
+ # Time to process
+ self.processing_time = 0
+ self.draw_time = 0
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.wall_list = arcade.SpriteList()
+
+ self.score = 0
+
+ # Create the maze
+ maze = make_maze_depth_first(MAZE_WIDTH, MAZE_HEIGHT)
+
+ # Create sprites based on 2D grid
+ if not MERGE_SPRITES:
+ # This is the simple-to-understand method. Each grid location
+ # is a sprite.
+ for row in range(MAZE_HEIGHT):
+ for column in range(MAZE_WIDTH):
+ if maze[row][column] == 1:
+ wall = arcade.Sprite("images/grassCenter.png", SPRITE_SCALING)
+ wall.center_x = column * SPRITE_SIZE + SPRITE_SIZE / 2
+ wall.center_y = row * SPRITE_SIZE + SPRITE_SIZE / 2
+ self.wall_list.append(wall)
+ else:
+ # This uses new Arcade 1.3.1 features, that allow me to create a
+ # larger sprite with a repeating texture. So if there are multiple
+ # cells in a row with a wall, we merge them into one sprite, with a
+ # repeating texture for each cell. This reduces our sprite count.
+ for row in range(MAZE_HEIGHT):
+ column = 0
+ while column < len(maze):
+ while column < len(maze) and maze[row][column] == 0:
+ column += 1
+ start_column = column
+ while column < len(maze) and maze[row][column] == 1:
+ column += 1
+ end_column = column - 1
+
+ column_count = end_column - start_column + 1
+ column_mid = (start_column + end_column) / 2
+
+ wall = arcade.Sprite("images/grassCenter.png", SPRITE_SCALING,
+ repeat_count_x=column_count)
+ wall.center_x = column_mid * SPRITE_SIZE + SPRITE_SIZE / 2
+ wall.center_y = row * SPRITE_SIZE + SPRITE_SIZE / 2
+ wall.width = SPRITE_SIZE * column_count
+ self.wall_list.append(wall)
+
+ # Set up the player
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING)
+ self.player_list.append(self.player_sprite)
+
+ # Randomly place the player. If we are in a wall, repeat until we aren't.
+ placed = False
+ while not placed:
+
+ # Randomly position
+ self.player_sprite.center_x = random.randrange(MAZE_WIDTH * SPRITE_SIZE)
+ self.player_sprite.center_y = random.randrange(MAZE_HEIGHT * SPRITE_SIZE)
+
+ # Are we in a wall?
+ walls_hit = arcade.check_for_collision_with_list(self.player_sprite, self.wall_list)
+ if len(walls_hit) == 0:
+ # Not in a wall! Success!
+ placed = True
+
+ self.physics_engine = arcade.PhysicsEngineSimple(self.player_sprite, self.wall_list)
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ # Set the viewport boundaries
+ # These numbers set where we have 'scrolled' to.
+ self.view_left = 0
+ self.view_bottom = 0
+ print(f"Total wall blocks: {len(self.wall_list)}")
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Start timing how long this takes
+ draw_start_time = timeit.default_timer()
+
+ # Draw all the sprites.
+ self.wall_list.draw()
+ self.player_list.draw()
+
+ # Draw info on the screen
+ sprite_count = len(self.wall_list)
+
+ output = f"Sprite Count: {sprite_count}"
+ arcade.draw_text(output,
+ self.view_left + 20,
+ SCREEN_HEIGHT - 20 + self.view_bottom,
+ arcade.color.WHITE, 16)
+
+ output = f"Drawing time: {self.draw_time:.3f}"
+ arcade.draw_text(output,
+ self.view_left + 20,
+ SCREEN_HEIGHT - 40 + self.view_bottom,
+ arcade.color.WHITE, 16)
+
+ output = f"Processing time: {self.processing_time:.3f}"
+ arcade.draw_text(output,
+ self.view_left + 20,
+ SCREEN_HEIGHT - 60 + self.view_bottom,
+ arcade.color.WHITE, 16)
+
+ self.draw_time = timeit.default_timer() - draw_start_time
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ if key == arcade.key.UP:
+ self.player_sprite.change_y = MOVEMENT_SPEED
+ elif key == arcade.key.DOWN:
+ self.player_sprite.change_y = -MOVEMENT_SPEED
+ elif key == arcade.key.LEFT:
+ self.player_sprite.change_x = -MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT:
+ self.player_sprite.change_x = MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.UP or key == arcade.key.DOWN:
+ self.player_sprite.change_y = 0
+ elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ start_time = timeit.default_timer()
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.physics_engine.update()
+
+ # --- Manage Scrolling ---
+
+ # Track if we need to change the viewport
+
+ changed = False
+
+ # Scroll left
+ left_bndry = self.view_left + VIEWPORT_MARGIN
+ if self.player_sprite.left < left_bndry:
+ self.view_left -= left_bndry - self.player_sprite.left
+ changed = True
+
+ # Scroll right
+ right_bndry = self.view_left + SCREEN_WIDTH - VIEWPORT_MARGIN
+ if self.player_sprite.right > right_bndry:
+ self.view_left += self.player_sprite.right - right_bndry
+ changed = True
+
+ # Scroll up
+ top_bndry = self.view_bottom + SCREEN_HEIGHT - VIEWPORT_MARGIN
+ if self.player_sprite.top > top_bndry:
+ self.view_bottom += self.player_sprite.top - top_bndry
+ changed = True
+
+ # Scroll down
+ bottom_bndry = self.view_bottom + VIEWPORT_MARGIN
+ if self.player_sprite.bottom < bottom_bndry:
+ self.view_bottom -= bottom_bndry - self.player_sprite.bottom
+ changed = True
+
+ if changed:
+ arcade.set_viewport(self.view_left,
+ SCREEN_WIDTH + self.view_left,
+ self.view_bottom,
+ SCREEN_HEIGHT + self.view_bottom)
+
+ # Save the time it took to do this.
+ self.processing_time = timeit.default_timer() - start_time
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/maze_recursive.py b/arcade/examples/maze_recursive.py
new file mode 100644
index 0000000..97f0df7
--- /dev/null
+++ b/arcade/examples/maze_recursive.py
@@ -0,0 +1,366 @@
+"""
+Create a maze using a recursive division method.
+
+For more information on the algorithm, see "Recursive Division Method"
+at https://en.wikipedia.org/wiki/Maze_generation_algorithm
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.maze_recursive
+"""
+import random
+import arcade
+import timeit
+import os
+
+NATIVE_SPRITE_SIZE = 128
+SPRITE_SCALING = 0.25
+SPRITE_SIZE = NATIVE_SPRITE_SIZE * SPRITE_SCALING
+
+SCREEN_WIDTH = 1000
+SCREEN_HEIGHT = 700
+SCREEN_TITLE = "Maze Recursive Example"
+
+MOVEMENT_SPEED = 8
+
+TILE_EMPTY = 0
+TILE_CRATE = 1
+
+# Maze must have an ODD number of rows and columns.
+# Walls go on EVEN rows/columns.
+# Openings go on ODD rows/columns
+MAZE_HEIGHT = 51
+MAZE_WIDTH = 51
+
+
+# How many pixels to keep as a minimum margin between the character
+# and the edge of the screen.
+VIEWPORT_MARGIN = 200
+
+MERGE_SPRITES = True
+
+
+def create_empty_grid(width, height, default_value=TILE_EMPTY):
+ """ Create an empty grid. """
+ grid = []
+ for row in range(height):
+ grid.append([])
+ for column in range(width):
+ grid[row].append(default_value)
+ return grid
+
+
+def create_outside_walls(maze):
+ """ Create outside border walls."""
+
+ # Create left and right walls
+ for row in range(len(maze)):
+ maze[row][0] = TILE_CRATE
+ maze[row][len(maze[row])-1] = TILE_CRATE
+
+ # Create top and bottom walls
+ for column in range(1, len(maze[0]) - 1):
+ maze[0][column] = TILE_CRATE
+ maze[len(maze) - 1][column] = TILE_CRATE
+
+
+def make_maze_recursive_call(maze, top, bottom, left, right):
+ """
+ Recursive function to divide up the maze in four sections
+ and create three gaps.
+ Walls can only go on even numbered rows/columns.
+ Gaps can only go on odd numbered rows/columns.
+ Maze must have an ODD number of rows and columns.
+ """
+
+ # Figure out where to divide horizontally
+ start_range = bottom + 2
+ end_range = top - 1
+ y = random.randrange(start_range, end_range, 2)
+
+ # Do the division
+ for column in range(left + 1, right):
+ maze[y][column] = TILE_CRATE
+
+ # Figure out where to divide vertically
+ start_range = left + 2
+ end_range = right - 1
+ x = random.randrange(start_range, end_range, 2)
+
+ # Do the division
+ for row in range(bottom + 1, top):
+ maze[row][x] = TILE_CRATE
+
+ # Now we'll make a gap on 3 of the 4 walls.
+ # Figure out which wall does NOT get a gap.
+ wall = random.randrange(4)
+ if wall != 0:
+ gap = random.randrange(left + 1, x, 2)
+ maze[y][gap] = TILE_EMPTY
+
+ if wall != 1:
+ gap = random.randrange(x + 1, right, 2)
+ maze[y][gap] = TILE_EMPTY
+
+ if wall != 2:
+ gap = random.randrange(bottom + 1, y, 2)
+ maze[gap][x] = TILE_EMPTY
+
+ if wall != 3:
+ gap = random.randrange(y + 1, top, 2)
+ maze[gap][x] = TILE_EMPTY
+
+ # If there's enough space, to a recursive call.
+ if top > y + 3 and x > left + 3:
+ make_maze_recursive_call(maze, top, y, left, x)
+
+ if top > y + 3 and x + 3 < right:
+ make_maze_recursive_call(maze, top, y, x, right)
+
+ if bottom + 3 < y and x + 3 < right:
+ make_maze_recursive_call(maze, y, bottom, x, right)
+
+ if bottom + 3 < y and x > left + 3:
+ make_maze_recursive_call(maze, y, bottom, left, x)
+
+
+def make_maze_recursion(maze_width, maze_height):
+ """ Make the maze by recursively splitting it into four rooms. """
+ maze = create_empty_grid(maze_width, maze_height)
+ # Fill in the outside walls
+ create_outside_walls(maze)
+
+ # Start the recursive process
+ make_maze_recursive_call(maze, maze_height - 1, 0, 0, maze_width - 1)
+ return maze
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self, width, height, title):
+ """
+ Initializer
+ """
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Sprite lists
+ self.player_list = None
+ self.wall_list = None
+
+ # Player info
+ self.score = 0
+ self.player_sprite = None
+
+ # Physics engine
+ self.physics_engine = None
+
+ # Used to scroll
+ self.view_bottom = 0
+ self.view_left = 0
+
+ # Time to process
+ self.processing_time = 0
+ self.draw_time = 0
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.wall_list = arcade.SpriteList()
+
+ # Set up the player
+ self.score = 0
+
+ maze = make_maze_recursion(MAZE_WIDTH, MAZE_HEIGHT)
+
+ # Create sprites based on 2D grid
+ if not MERGE_SPRITES:
+ # This is the simple-to-understand method. Each grid location
+ # is a sprite.
+ for row in range(MAZE_HEIGHT):
+ for column in range(MAZE_WIDTH):
+ if maze[row][column] == 1:
+ wall = arcade.Sprite("images/grassCenter.png", SPRITE_SCALING)
+ wall.center_x = column * SPRITE_SIZE + SPRITE_SIZE / 2
+ wall.center_y = row * SPRITE_SIZE + SPRITE_SIZE / 2
+ self.wall_list.append(wall)
+ else:
+ # This uses new Arcade 1.3.1 features, that allow me to create a
+ # larger sprite with a repeating texture. So if there are multiple
+ # cells in a row with a wall, we merge them into one sprite, with a
+ # repeating texture for each cell. This reduces our sprite count.
+ for row in range(MAZE_HEIGHT):
+ column = 0
+ while column < len(maze):
+ while column < len(maze) and maze[row][column] == 0:
+ column += 1
+ start_column = column
+ while column < len(maze) and maze[row][column] == 1:
+ column += 1
+ end_column = column - 1
+
+ column_count = end_column - start_column + 1
+ column_mid = (start_column + end_column) / 2
+
+ wall = arcade.Sprite("images/grassCenter.png", SPRITE_SCALING,
+ repeat_count_x=column_count)
+ wall.center_x = column_mid * SPRITE_SIZE + SPRITE_SIZE / 2
+ wall.center_y = row * SPRITE_SIZE + SPRITE_SIZE / 2
+ wall.width = SPRITE_SIZE * column_count
+ self.wall_list.append(wall)
+
+ # Set up the player
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING)
+ self.player_list.append(self.player_sprite)
+
+ # Randomly place the player. If we are in a wall, repeat until we aren't.
+ placed = False
+ while not placed:
+
+ # Randomly position
+ self.player_sprite.center_x = random.randrange(MAZE_WIDTH * SPRITE_SIZE)
+ self.player_sprite.center_y = random.randrange(MAZE_HEIGHT * SPRITE_SIZE)
+
+ # Are we in a wall?
+ walls_hit = arcade.check_for_collision_with_list(self.player_sprite, self.wall_list)
+ if len(walls_hit) == 0:
+ # Not in a wall! Success!
+ placed = True
+
+ self.physics_engine = arcade.PhysicsEngineSimple(self.player_sprite, self.wall_list)
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ # Set the viewport boundaries
+ # These numbers set where we have 'scrolled' to.
+ self.view_left = 0
+ self.view_bottom = 0
+ print(f"Total wall blocks: {len(self.wall_list)}")
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Start timing how long this takes
+ draw_start_time = timeit.default_timer()
+
+ # Draw all the sprites.
+ self.wall_list.draw()
+ self.player_list.draw()
+
+ # Draw info on the screen
+ sprite_count = len(self.wall_list)
+
+ output = f"Sprite Count: {sprite_count}"
+ arcade.draw_text(output,
+ self.view_left + 20,
+ SCREEN_HEIGHT - 20 + self.view_bottom,
+ arcade.color.WHITE, 16)
+
+ output = f"Drawing time: {self.draw_time:.3f}"
+ arcade.draw_text(output,
+ self.view_left + 20,
+ SCREEN_HEIGHT - 40 + self.view_bottom,
+ arcade.color.WHITE, 16)
+
+ output = f"Processing time: {self.processing_time:.3f}"
+ arcade.draw_text(output,
+ self.view_left + 20,
+ SCREEN_HEIGHT - 60 + self.view_bottom,
+ arcade.color.WHITE, 16)
+
+ self.draw_time = timeit.default_timer() - draw_start_time
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ if key == arcade.key.UP:
+ self.player_sprite.change_y = MOVEMENT_SPEED
+ elif key == arcade.key.DOWN:
+ self.player_sprite.change_y = -MOVEMENT_SPEED
+ elif key == arcade.key.LEFT:
+ self.player_sprite.change_x = -MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT:
+ self.player_sprite.change_x = MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.UP or key == arcade.key.DOWN:
+ self.player_sprite.change_y = 0
+ elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ start_time = timeit.default_timer()
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.physics_engine.update()
+
+ # --- Manage Scrolling ---
+
+ # Track if we need to change the viewport
+
+ changed = False
+
+ # Scroll left
+ left_bndry = self.view_left + VIEWPORT_MARGIN
+ if self.player_sprite.left < left_bndry:
+ self.view_left -= left_bndry - self.player_sprite.left
+ changed = True
+
+ # Scroll right
+ right_bndry = self.view_left + SCREEN_WIDTH - VIEWPORT_MARGIN
+ if self.player_sprite.right > right_bndry:
+ self.view_left += self.player_sprite.right - right_bndry
+ changed = True
+
+ # Scroll up
+ top_bndry = self.view_bottom + SCREEN_HEIGHT - VIEWPORT_MARGIN
+ if self.player_sprite.top > top_bndry:
+ self.view_bottom += self.player_sprite.top - top_bndry
+ changed = True
+
+ # Scroll down
+ bottom_bndry = self.view_bottom + VIEWPORT_MARGIN
+ if self.player_sprite.bottom < bottom_bndry:
+ self.view_bottom -= bottom_bndry - self.player_sprite.bottom
+ changed = True
+
+ if changed:
+ arcade.set_viewport(self.view_left,
+ SCREEN_WIDTH + self.view_left,
+ self.view_bottom,
+ SCREEN_HEIGHT + self.view_bottom)
+
+ # Save the time it took to do this.
+ self.processing_time = timeit.default_timer() - start_time
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/mountains_midpoint_displacement.py b/arcade/examples/mountains_midpoint_displacement.py
new file mode 100644
index 0000000..58cb251
--- /dev/null
+++ b/arcade/examples/mountains_midpoint_displacement.py
@@ -0,0 +1,201 @@
+"""
+Mountains Midpoint Displacement
+
+Create a random mountain range.
+Original idea and some code from:
+https://bitesofcode.wordpress.com/2016/12/23/landscape-generation-using-midpoint-displacement/
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.mountains_midpoint_displacement
+"""
+
+# Library imports
+import arcade
+import random
+import bisect
+
+SCREEN_WIDTH = 1200
+SCREEN_HEIGHT = 700
+SCREEN_TITLE = "Mountains Midpoint Displacement Example"
+
+
+# Iterative midpoint vertical displacement
+def midpoint_displacement(start, end, roughness, vertical_displacement=None,
+ num_of_iterations=16):
+ """
+ Given a straight line segment specified by a starting point and an endpoint
+ in the form of [starting_point_x, starting_point_y] and [endpoint_x, endpoint_y],
+ a roughness value > 0, an initial vertical displacement and a number of
+ iterations > 0 applies the midpoint algorithm to the specified segment and
+ returns the obtained list of points in the form
+ points = [[x_0, y_0],[x_1, y_1],...,[x_n, y_n]]
+ """
+ # Final number of points = (2^iterations)+1
+ if vertical_displacement is None:
+ # if no initial displacement is specified set displacement to:
+ # (y_start+y_end)/2
+ vertical_displacement = (start[1]+end[1])/2
+ # Data structure that stores the points is a list of lists where
+ # each sublist represents a point and holds its x and y coordinates:
+ # points=[[x_0, y_0],[x_1, y_1],...,[x_n, y_n]]
+ # | | |
+ # point 0 point 1 point n
+ # The points list is always kept sorted from smallest to biggest x-value
+ points = [start, end]
+ iteration = 1
+ while iteration <= num_of_iterations:
+ # Since the list of points will be dynamically updated with the new computed
+ # points after each midpoint displacement it is necessary to create a copy
+ # of the state at the beginning of the iteration so we can iterate over
+ # the original sequence.
+ # Tuple type is used for security reasons since they are immutable in Python.
+ points_tup = tuple(points)
+ for i in range(len(points_tup)-1):
+ # Calculate x and y midpoint coordinates:
+ # [(x_i+x_(i+1))/2, (y_i+y_(i+1))/2]
+ midpoint = list(map(lambda x: (points_tup[i][x]+points_tup[i+1][x])/2,
+ [0, 1]))
+ # Displace midpoint y-coordinate
+ midpoint[1] += random.choice([-vertical_displacement,
+ vertical_displacement])
+ # Insert the displaced midpoint in the current list of points
+ bisect.insort(points, midpoint)
+ # bisect allows to insert an element in a list so that its order
+ # is preserved.
+ # By default the maintained order is from smallest to biggest list first
+ # element which is what we want.
+ # Reduce displacement range
+ vertical_displacement *= 2 ** (-roughness)
+ # update number of iterations
+ iteration += 1
+ return points
+
+
+def fix_points(points):
+ last_y = None
+ last_x = None
+ new_list = []
+ for point in points:
+ x = int(point[0])
+ y = int(point[1])
+
+ if last_y is None or y != last_y:
+ if last_y is None:
+ last_x = x
+ last_y = y
+
+ x1 = last_x
+ x2 = x
+ y1 = last_y
+ y2 = y
+
+ new_list.append((x1, 0))
+ new_list.append((x1, y1))
+ new_list.append((x2, y2))
+ new_list.append((x2, 0))
+
+ last_x = x
+ last_y = y
+
+ x1 = last_x
+ x2 = SCREEN_WIDTH
+ y1 = last_y
+ y2 = last_y
+
+ new_list.append((x1, 0))
+ new_list.append((x1, y1))
+ new_list.append((x2, y2))
+ new_list.append((x2, 0))
+
+ return new_list
+
+
+def create_mountain_range(start, end, roughness, vertical_displacement, num_of_iterations, color_start):
+
+ shape_list = arcade.ShapeElementList()
+
+ layer_1 = midpoint_displacement(start, end, roughness, vertical_displacement, num_of_iterations)
+ layer_1 = fix_points(layer_1)
+
+ color_list = [color_start] * len(layer_1)
+ lines = arcade.create_rectangles_filled_with_colors(layer_1, color_list)
+ shape_list.append(lines)
+
+ return shape_list
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title)
+
+ self.mountains = None
+
+ arcade.set_background_color(arcade.color.WHITE)
+
+ def setup(self):
+ """
+ This, and any function with the arcade.decorator.init decorator,
+ is run automatically on start-up.
+ """
+ self.mountains = []
+
+ background = arcade.ShapeElementList()
+
+ color1 = (195, 157, 224)
+ color2 = (240, 203, 163)
+ points = (0, 0), (SCREEN_WIDTH, 0), (SCREEN_WIDTH, SCREEN_HEIGHT), (0, SCREEN_HEIGHT)
+ colors = (color1, color1, color2, color2)
+ rect = arcade.create_rectangle_filled_with_colors(points, colors)
+
+ background.append(rect)
+ self.mountains.append(background)
+
+ layer_4 = create_mountain_range([0, 350], [SCREEN_WIDTH, 320], 1.1, 250, 8, (158, 98, 204))
+ self.mountains.append(layer_4)
+
+ layer_3 = create_mountain_range([0, 270], [SCREEN_WIDTH, 190], 1.1, 120, 9, (130, 79, 138))
+ self.mountains.append(layer_3)
+
+ layer_2 = create_mountain_range([0, 180], [SCREEN_WIDTH, 80], 1.2, 30, 12, (68, 28, 99))
+ self.mountains.append(layer_2)
+
+ layer_1 = create_mountain_range([250, 0], [SCREEN_WIDTH, 200], 1.4, 20, 12, (49, 7, 82))
+ self.mountains.append(layer_1)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ arcade.start_render()
+ """
+ This is called every time we need to update our screen. About 60
+ times per second.
+
+ Just draw things in this function, don't update where they are.
+ """
+ # Call our drawing functions.
+
+ for mountain_range in self.mountains:
+ mountain_range.draw()
+
+ def on_mouse_press(self, x, y, button, key_modifiers):
+ """
+ Called when the user presses a mouse button.
+ """
+ pass
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/mountains_random_walk.py b/arcade/examples/mountains_random_walk.py
new file mode 100644
index 0000000..0234e0a
--- /dev/null
+++ b/arcade/examples/mountains_random_walk.py
@@ -0,0 +1,140 @@
+"""
+Mountains Random Walk
+
+Idea and algorithm from:
+https://hackernoon.com/a-procedural-landscape-experiment-4efe1826906f
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.mountains_random_walk
+"""
+
+# Library imports
+import arcade
+import random
+
+SCREEN_WIDTH = 1200
+SCREEN_HEIGHT = 700
+SCREEN_TITLE = "Mountains Random Walk Example"
+
+
+def create_mountain_range(height_min, height_max, color_start, color_end):
+
+ shape_list = arcade.ShapeElementList()
+
+ step_max = 1.5
+ step_change = 0.5
+
+ height = random.random() * height_max
+ slope = (random.random() * step_max * 2) - step_max
+
+ line_point_list = []
+ line_color_list = []
+
+ for x in range(SCREEN_WIDTH):
+ height += slope
+ slope += (random.random() * step_change * 2) - step_change
+
+ if slope > step_max:
+ slope = step_max
+ elif slope < -step_max:
+ slope = -step_max
+
+ if height > height_max:
+ height = height_max
+ slope *= -1
+ elif height < height_min:
+ height = height_min
+ slope *= -1
+
+ line_point_list.extend(((x, height), (x, 0)))
+ line_color_list.extend((color_start, color_end))
+
+ lines = arcade.create_lines_with_colors(line_point_list, line_color_list)
+ shape_list.append(lines)
+
+ return shape_list
+
+
+def create_line_strip():
+ shape_list = arcade.ShapeElementList()
+
+ colors = (
+ arcade.color.RED,
+ arcade.color.BLACK,
+ arcade.color.GREEN,
+ arcade.color.BLACK,
+ arcade.color.BLUE,
+ arcade.color.BLACK
+ )
+ line_strip = arcade.create_lines_with_colors(
+ ([10, 10], [500, 10],
+ [10, 250], [500, 250],
+ [10, 500], [500, 500]),
+ colors,
+ line_width=4)
+
+ shape_list.append(line_strip)
+
+ return shape_list
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title)
+
+ self.mountains = None
+
+ arcade.set_background_color(arcade.color.WHITE)
+
+ def setup(self):
+ """
+ This, and any function with the arcade.decorator.init decorator,
+ is run automatically on start-up.
+ """
+
+ self.mountains = []
+
+ background = arcade.ShapeElementList()
+
+ points = (0, 0), (SCREEN_WIDTH, 0), (SCREEN_WIDTH, SCREEN_HEIGHT), (0, SCREEN_HEIGHT)
+ colors = (arcade.color.SKY_BLUE, arcade.color.SKY_BLUE, arcade.color.BLUE, arcade.color.BLUE)
+ rect = arcade.create_rectangles_filled_with_colors(points, colors)
+
+ background.append(rect)
+ self.mountains.append(background)
+
+ for i in range(1, 4):
+ color_start = (i * 10, i * 30, i * 10)
+ color_end = (i * 20, i * 40, i * 20)
+ min_y = 0 + 70 * (3 - i)
+ max_y = 120 + 70 * (3 - i)
+ mountain_range = create_mountain_range(min_y, max_y, color_start, color_end)
+ self.mountains.append(mountain_range)
+
+ def on_draw(self):
+ """
+ This is called every time we need to update our screen. About 60
+ times per second.
+
+ Just draw things in this function, don't update where they are.
+ """
+ # Call our drawing functions.
+
+ arcade.start_render()
+ for mountain_range in self.mountains:
+ mountain_range.draw()
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/move_joystick.py b/arcade/examples/move_joystick.py
new file mode 100644
index 0000000..a5b6573
--- /dev/null
+++ b/arcade/examples/move_joystick.py
@@ -0,0 +1,112 @@
+"""
+This simple animation example shows how to move an item with the joystick
+and game-pad.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.move_joystick
+"""
+
+import arcade
+
+SCREEN_WIDTH = 640
+SCREEN_HEIGHT = 480
+SCREEN_TITLE = "Move Joystick Example"
+MOVEMENT_SPEED = 5
+DEAD_ZONE = 0.02
+
+
+class Ball:
+ def __init__(self, position_x, position_y, change_x, change_y, radius, color):
+
+ # Take the parameters of the init function above, and create instance variables out of them.
+ self.position_x = position_x
+ self.position_y = position_y
+ self.change_x = change_x
+ self.change_y = change_y
+ self.radius = radius
+ self.color = color
+
+ def draw(self):
+ """ Draw the balls with the instance variables we have. """
+ arcade.draw_circle_filled(self.position_x, self.position_y, self.radius, self.color)
+
+ def update(self):
+ # Move the ball
+ self.position_y += self.change_y
+ self.position_x += self.change_x
+
+ # See if the ball hit the edge of the screen. If so, change direction
+ if self.position_x < self.radius:
+ self.position_x = self.radius
+
+ if self.position_x > SCREEN_WIDTH - self.radius:
+ self.position_x = SCREEN_WIDTH - self.radius
+
+ if self.position_y < self.radius:
+ self.position_y = self.radius
+
+ if self.position_y > SCREEN_HEIGHT - self.radius:
+ self.position_y = SCREEN_HEIGHT - self.radius
+
+
+class MyGame(arcade.Window):
+
+ def __init__(self, width, height, title):
+
+ # Call the parent class's init function
+ super().__init__(width, height, title)
+
+ # Make the mouse disappear when it is over the window.
+ # So we just see our object, not the pointer.
+ self.set_mouse_visible(False)
+
+ arcade.set_background_color(arcade.color.ASH_GREY)
+
+ # Create our ball
+ self.ball = Ball(50, 50, 0, 0, 15, arcade.color.AUBURN)
+
+ # Get a list of all the game controllers that are plugged in
+ joysticks = arcade.get_joysticks()
+
+ # If we have a game controller plugged in, grab it and
+ # make an instance variable out of it.
+ if joysticks:
+ self.joystick = joysticks[0]
+ self.joystick.open()
+ else:
+ print("There are no joysticks.")
+ self.joystick = None
+
+ def on_draw(self):
+
+ """ Called whenever we need to draw the window. """
+ arcade.start_render()
+ self.ball.draw()
+
+ def update(self, delta_time):
+
+ # Update the position according to the game controller
+ if self.joystick:
+
+ # Set a "dead zone" to prevent drive from a centered joystick
+ if abs(self.joystick.x) < DEAD_ZONE:
+ self.ball.change_x = 0
+ else:
+ self.ball.change_x = self.joystick.x * MOVEMENT_SPEED
+
+ # Set a "dead zone" to prevent drive from a centered joystick
+ if abs(self.joystick.y) < DEAD_ZONE:
+ self.ball.change_y = 0
+ else:
+ self.ball.change_y = -self.joystick.y * MOVEMENT_SPEED
+
+ self.ball.update()
+
+
+def main():
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/move_keyboard.py b/arcade/examples/move_keyboard.py
new file mode 100644
index 0000000..6d8d23a
--- /dev/null
+++ b/arcade/examples/move_keyboard.py
@@ -0,0 +1,99 @@
+"""
+This simple animation example shows how to move an item with the keyboard.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.move_keyboard
+"""
+
+import arcade
+
+SCREEN_WIDTH = 640
+SCREEN_HEIGHT = 480
+SCREEN_TITLE = "Move Keyboard Example"
+MOVEMENT_SPEED = 3
+
+
+class Ball:
+ def __init__(self, position_x, position_y, change_x, change_y, radius, color):
+
+ # Take the parameters of the init function above, and create instance variables out of them.
+ self.position_x = position_x
+ self.position_y = position_y
+ self.change_x = change_x
+ self.change_y = change_y
+ self.radius = radius
+ self.color = color
+
+ def draw(self):
+ """ Draw the balls with the instance variables we have. """
+ arcade.draw_circle_filled(self.position_x, self.position_y, self.radius, self.color)
+
+ def update(self):
+ # Move the ball
+ self.position_y += self.change_y
+ self.position_x += self.change_x
+
+ # See if the ball hit the edge of the screen. If so, change direction
+ if self.position_x < self.radius:
+ self.position_x = self.radius
+
+ if self.position_x > SCREEN_WIDTH - self.radius:
+ self.position_x = SCREEN_WIDTH - self.radius
+
+ if self.position_y < self.radius:
+ self.position_y = self.radius
+
+ if self.position_y > SCREEN_HEIGHT - self.radius:
+ self.position_y = SCREEN_HEIGHT - self.radius
+
+
+class MyGame(arcade.Window):
+
+ def __init__(self, width, height, title):
+
+ # Call the parent class's init function
+ super().__init__(width, height, title)
+
+ # Make the mouse disappear when it is over the window.
+ # So we just see our object, not the pointer.
+ self.set_mouse_visible(False)
+
+ arcade.set_background_color(arcade.color.ASH_GREY)
+
+ # Create our ball
+ self.ball = Ball(50, 50, 0, 0, 15, arcade.color.AUBURN)
+
+ def on_draw(self):
+ """ Called whenever we need to draw the window. """
+ arcade.start_render()
+ self.ball.draw()
+
+ def update(self, delta_time):
+ self.ball.update()
+
+ def on_key_press(self, key, modifiers):
+ """ Called whenever the user presses a key. """
+ if key == arcade.key.LEFT:
+ self.ball.change_x = -MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT:
+ self.ball.change_x = MOVEMENT_SPEED
+ elif key == arcade.key.UP:
+ self.ball.change_y = MOVEMENT_SPEED
+ elif key == arcade.key.DOWN:
+ self.ball.change_y = -MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """ Called whenever a user releases a key. """
+ if key == arcade.key.LEFT or key == arcade.key.RIGHT:
+ self.ball.change_x = 0
+ elif key == arcade.key.UP or key == arcade.key.DOWN:
+ self.ball.change_y = 0
+
+
+def main():
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/move_mouse.py b/arcade/examples/move_mouse.py
new file mode 100644
index 0000000..1d0f999
--- /dev/null
+++ b/arcade/examples/move_mouse.py
@@ -0,0 +1,78 @@
+"""
+This simple animation example shows how to move an item with the mouse, and
+handle mouse clicks.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.move_mouse
+"""
+
+import arcade
+
+SCREEN_WIDTH = 640
+SCREEN_HEIGHT = 480
+SCREEN_TITLE = "Move Mouse Example"
+
+
+class Ball:
+ def __init__(self, position_x, position_y, radius, color):
+
+ # Take the parameters of the init function above, and create instance variables out of them.
+ self.position_x = position_x
+ self.position_y = position_y
+ self.radius = radius
+ self.color = color
+
+ def draw(self):
+ """ Draw the balls with the instance variables we have. """
+ arcade.draw_circle_filled(self.position_x, self.position_y, self.radius, self.color)
+
+
+class MyGame(arcade.Window):
+
+ def __init__(self, width, height, title):
+
+ # Call the parent class's init function
+ super().__init__(width, height, title)
+
+ # Make the mouse disappear when it is over the window.
+ # So we just see our object, not the pointer.
+ self.set_mouse_visible(False)
+
+ arcade.set_background_color(arcade.color.ASH_GREY)
+
+ # Create our ball
+ self.ball = Ball(50, 50, 15, arcade.color.AUBURN)
+
+ def on_draw(self):
+ """ Called whenever we need to draw the window. """
+ arcade.start_render()
+ self.ball.draw()
+
+ def on_mouse_motion(self, x, y, dx, dy):
+ """ Called to update our objects. Happens approximately 60 times per second."""
+ self.ball.position_x = x
+ self.ball.position_y = y
+
+ def on_mouse_press(self, x, y, button, modifiers):
+ """
+ Called when the user presses a mouse button.
+ """
+ print(f"You clicked button number: {button}")
+ if button == arcade.MOUSE_BUTTON_LEFT:
+ self.ball.color = arcade.color.BLACK
+
+ def on_mouse_release(self, x, y, button, modifiers):
+ """
+ Called when a user releases a mouse button.
+ """
+ if button == arcade.MOUSE_BUTTON_LEFT:
+ self.ball.color = arcade.color.AUBURN
+
+
+def main():
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/my_tileset.tsx b/arcade/examples/my_tileset.tsx
new file mode 100644
index 0000000..5fe6845
--- /dev/null
+++ b/arcade/examples/my_tileset.tsx
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/arcade/examples/nested_loops_bottom_left_triangle.py b/arcade/examples/nested_loops_bottom_left_triangle.py
new file mode 100644
index 0000000..a2e4f4c
--- /dev/null
+++ b/arcade/examples/nested_loops_bottom_left_triangle.py
@@ -0,0 +1,42 @@
+"""
+Example "Arcade" library code.
+
+Showing how to do nested loops.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.nested_loops_bottom_left_triangle
+"""
+
+# Library imports
+import arcade
+
+COLUMN_SPACING = 20
+ROW_SPACING = 20
+LEFT_MARGIN = 110
+BOTTOM_MARGIN = 110
+
+# Open the window and set the background
+arcade.open_window(400, 400, "Complex Loops - Bottom Left Triangle")
+
+arcade.set_background_color(arcade.color.WHITE)
+
+# Start the render process. This must be done before any drawing commands.
+arcade.start_render()
+
+# Loop for each row
+for row in range(10):
+ # Loop for each column
+ # Change the number of columns depending on the row we are in
+ for column in range(10 - row):
+ # Calculate our location
+ x = column * COLUMN_SPACING + LEFT_MARGIN
+ y = row * ROW_SPACING + BOTTOM_MARGIN
+
+ # Draw the item
+ arcade.draw_circle_filled(x, y, 7, arcade.color.AO)
+
+# Finish the render.
+arcade.finish_render()
+
+# Keep the window up until someone closes it.
+arcade.run()
diff --git a/arcade/examples/nested_loops_bottom_right_triangle.py b/arcade/examples/nested_loops_bottom_right_triangle.py
new file mode 100644
index 0000000..7abaf3f
--- /dev/null
+++ b/arcade/examples/nested_loops_bottom_right_triangle.py
@@ -0,0 +1,42 @@
+"""
+Example "Arcade" library code.
+
+Showing how to do nested loops.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.nested_loops_bottom_right_triangle
+"""
+
+# Library imports
+import arcade
+
+COLUMN_SPACING = 20
+ROW_SPACING = 20
+LEFT_MARGIN = 110
+BOTTOM_MARGIN = 110
+
+# Open the window and set the background
+arcade.open_window(400, 400, "Complex Loops - Bottom Right Triangle")
+
+arcade.set_background_color(arcade.color.WHITE)
+
+# Start the render process. This must be done before any drawing commands.
+arcade.start_render()
+
+# Loop for each row
+for row in range(10):
+ # Loop for each column
+ # Change the number of columns depending on the row we are in
+ for column in range(row, 10):
+ # Calculate our location
+ x = column * COLUMN_SPACING + LEFT_MARGIN
+ y = row * ROW_SPACING + BOTTOM_MARGIN
+
+ # Draw the item
+ arcade.draw_circle_filled(x, y, 7, arcade.color.AO)
+
+# Finish the render.
+arcade.finish_render()
+
+# Keep the window up until someone closes it.
+arcade.run()
diff --git a/arcade/examples/nested_loops_box.py b/arcade/examples/nested_loops_box.py
new file mode 100644
index 0000000..84db8fa
--- /dev/null
+++ b/arcade/examples/nested_loops_box.py
@@ -0,0 +1,41 @@
+"""
+Example "Arcade" library code.
+
+Showing how to do nested loops.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.nested_loops_box
+"""
+
+# Library imports
+import arcade
+
+COLUMN_SPACING = 20
+ROW_SPACING = 20
+LEFT_MARGIN = 110
+BOTTOM_MARGIN = 110
+
+# Open the window and set the background
+arcade.open_window(400, 400, "Complex Loops - Box")
+
+arcade.set_background_color(arcade.color.WHITE)
+
+# Start the render process. This must be done before any drawing commands.
+arcade.start_render()
+
+# Loop for each row
+for row in range(10):
+ # Loop for each column
+ for column in range(10):
+ # Calculate our location
+ x = column * COLUMN_SPACING + LEFT_MARGIN
+ y = row * ROW_SPACING + BOTTOM_MARGIN
+
+ # Draw the item
+ arcade.draw_circle_filled(x, y, 7, arcade.color.AO)
+
+# Finish the render.
+arcade.finish_render()
+
+# Keep the window up until someone closes it.
+arcade.run()
diff --git a/arcade/examples/nested_loops_top_left_triangle.py b/arcade/examples/nested_loops_top_left_triangle.py
new file mode 100644
index 0000000..5e3f7c2
--- /dev/null
+++ b/arcade/examples/nested_loops_top_left_triangle.py
@@ -0,0 +1,42 @@
+"""
+Example "Arcade" library code.
+
+Showing how to do nested loops.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.nested_loops_top_left_triangle
+"""
+
+# Library imports
+import arcade
+
+COLUMN_SPACING = 20
+ROW_SPACING = 20
+LEFT_MARGIN = 110
+BOTTOM_MARGIN = 110
+
+# Open the window and set the background
+arcade.open_window(400, 400, "Complex Loops - Top Left Triangle")
+
+arcade.set_background_color(arcade.color.WHITE)
+
+# Start the render process. This must be done before any drawing commands.
+arcade.start_render()
+
+# Loop for each row
+for row in range(10):
+ # Loop for each column
+ # Change the number of columns depending on the row we are in
+ for column in range(row):
+ # Calculate our location
+ x = column * COLUMN_SPACING + LEFT_MARGIN
+ y = row * ROW_SPACING + BOTTOM_MARGIN
+
+ # Draw the item
+ arcade.draw_circle_filled(x, y, 7, arcade.color.AO)
+
+# Finish the render.
+arcade.finish_render()
+
+# Keep the window up until someone closes it.
+arcade.run()
diff --git a/arcade/examples/nested_loops_top_right_triangle.py b/arcade/examples/nested_loops_top_right_triangle.py
new file mode 100644
index 0000000..ad5701c
--- /dev/null
+++ b/arcade/examples/nested_loops_top_right_triangle.py
@@ -0,0 +1,42 @@
+"""
+Example "Arcade" library code.
+
+Showing how to do nested loops.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.nested_loops_bottom_right_triangle
+"""
+
+# Library imports
+import arcade
+
+COLUMN_SPACING = 20
+ROW_SPACING = 20
+LEFT_MARGIN = 110
+BOTTOM_MARGIN = 110
+
+# Open the window and set the background
+arcade.open_window(400, 400, "Complex Loops - Top Right Triangle")
+
+arcade.set_background_color(arcade.color.WHITE)
+
+# Start the render process. This must be done before any drawing commands.
+arcade.start_render()
+
+# Loop for each row
+for row in range(10):
+ # Loop for each column
+ # Change the number of columns depending on the row we are in
+ for column in range(9 - row, 10):
+ # Calculate our location
+ x = column * COLUMN_SPACING + LEFT_MARGIN
+ y = row * ROW_SPACING + BOTTOM_MARGIN
+
+ # Draw the item
+ arcade.draw_circle_filled(x, y, 7, arcade.color.AO)
+
+# Finish the render.
+arcade.finish_render()
+
+# Keep the window up until someone closes it.
+arcade.run()
diff --git a/arcade/examples/particle_fireworks.py b/arcade/examples/particle_fireworks.py
new file mode 100644
index 0000000..c39f585
--- /dev/null
+++ b/arcade/examples/particle_fireworks.py
@@ -0,0 +1,364 @@
+"""
+Particle Fireworks
+
+Use a fireworks display to demonstrate "real-world" uses of Emitters and Particles
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_list_particle_fireworks
+"""
+import arcade
+from arcade import Point, Vector
+from arcade.utils import _Vec2 # bring in "private" class
+from arcade.examples.frametime_plotter import FrametimePlotter
+import os
+import random
+import pyglet
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Particle based fireworks"
+LAUNCH_INTERVAL_MIN = 1.5
+LAUNCH_INTERVAL_MAX = 2.5
+TEXTURE = "images/pool_cue_ball.png"
+RAINBOW_COLORS = (
+ arcade.color.ELECTRIC_CRIMSON,
+ arcade.color.FLUORESCENT_ORANGE,
+ arcade.color.ELECTRIC_YELLOW,
+ arcade.color.ELECTRIC_GREEN,
+ arcade.color.ELECTRIC_CYAN,
+ arcade.color.MEDIUM_ELECTRIC_BLUE,
+ arcade.color.ELECTRIC_INDIGO,
+ arcade.color.ELECTRIC_PURPLE,
+)
+SPARK_TEXTURES = [arcade.make_circle_texture(15, clr) for clr in RAINBOW_COLORS]
+SPARK_PAIRS = [
+ [SPARK_TEXTURES[0], SPARK_TEXTURES[3]],
+ [SPARK_TEXTURES[1], SPARK_TEXTURES[5]],
+ [SPARK_TEXTURES[7], SPARK_TEXTURES[2]],
+]
+ROCKET_SMOKE_TEXTURE = arcade.make_soft_circle_texture(15, arcade.color.GRAY)
+PUFF_TEXTURE = arcade.make_soft_circle_texture(80, (40, 40, 40))
+FLASH_TEXTURE = arcade.make_soft_circle_texture(70, (128, 128, 90))
+CLOUD_TEXTURES = [
+ arcade.make_soft_circle_texture(50, arcade.color.WHITE),
+ arcade.make_soft_circle_texture(50, arcade.color.LIGHT_GRAY),
+ arcade.make_soft_circle_texture(50, arcade.color.LIGHT_BLUE),
+]
+STAR_TEXTURES = [
+ arcade.make_soft_circle_texture(6, arcade.color.WHITE),
+ arcade.make_soft_circle_texture(6, arcade.color.PASTEL_YELLOW),
+]
+SPINNER_HEIGHT = 75
+
+
+def make_spinner():
+ spinner = arcade.Emitter(
+ center_xy=(SCREEN_WIDTH / 2, SPINNER_HEIGHT - 5),
+ emit_controller=arcade.EmitterIntervalWithTime(0.025, 2.0),
+ particle_factory=lambda emitter: arcade.FadeParticle(
+ filename_or_texture=random.choice(STAR_TEXTURES),
+ change_xy=(0, 6.0),
+ lifetime=0.2
+ )
+ )
+ spinner.change_angle = 16.28
+ return spinner
+
+
+def make_rocket(emit_done_cb):
+ """Emitter that displays the smoke trail as the firework shell climbs into the sky"""
+ rocket = RocketEmitter(
+ center_xy=(random.uniform(100, SCREEN_WIDTH - 100), 25),
+ emit_controller=arcade.EmitterIntervalWithTime(0.04, 2.0),
+ particle_factory=lambda emitter: arcade.FadeParticle(
+ filename_or_texture=ROCKET_SMOKE_TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), 0.08),
+ scale=0.5,
+ lifetime=random.uniform(1.0, 1.5),
+ start_alpha=100,
+ end_alpha=0,
+ mutation_callback=rocket_smoke_mutator
+ ),
+ emit_done_cb=emit_done_cb
+ )
+ rocket.change_x = random.uniform(-1.0, 1.0)
+ rocket.change_y = random.uniform(5.0, 7.25)
+ return rocket
+
+
+def make_flash(prev_emitter):
+ """Return emitter that displays the brief flash when a firework shell explodes"""
+ return arcade.Emitter(
+ center_xy=prev_emitter.get_pos(),
+ emit_controller=arcade.EmitBurst(3),
+ particle_factory=lambda emitter: arcade.FadeParticle(
+ filename_or_texture=FLASH_TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), 3.5),
+ lifetime=0.15
+ )
+ )
+
+
+def make_puff(prev_emitter):
+ """Return emitter that generates the subtle smoke cloud left after a firework shell explodes"""
+ return arcade.Emitter(
+ center_xy=prev_emitter.get_pos(),
+ emit_controller=arcade.EmitBurst(4),
+ particle_factory=lambda emitter: arcade.FadeParticle(
+ filename_or_texture=PUFF_TEXTURE,
+ change_xy=(_Vec2(arcade.rand_in_circle((0.0, 0.0), 0.4)) + _Vec2(0.3, 0.0)).as_tuple(),
+ lifetime=4.0
+ )
+ )
+
+
+class AnimatedAlphaParticle(arcade.LifetimeParticle):
+ """A custom particle that animates between three different alpha levels"""
+
+ def __init__(
+ self,
+ filename_or_texture: arcade.FilenameOrTexture,
+ change_xy: Vector,
+ start_alpha: int = 0,
+ duration1: float = 1.0,
+ mid_alpha: int = 255,
+ duration2: float = 1.0,
+ end_alpha: int = 0,
+ center_xy: Point = (0.0, 0.0),
+ angle: float = 0,
+ change_angle: float = 0,
+ scale: float = 1.0,
+ mutation_callback=None,
+ ):
+ super().__init__(filename_or_texture, change_xy, duration1 + duration2, center_xy, angle, change_angle, scale,
+ start_alpha, mutation_callback)
+ self.start_alpha = start_alpha
+ self.in_duration = duration1
+ self.mid_alpha = mid_alpha
+ self.out_duration = duration2
+ self.end_alpha = end_alpha
+
+ def update(self):
+ super().update()
+ if self.lifetime_elapsed <= self.in_duration:
+ u = self.lifetime_elapsed / self.in_duration
+ self.alpha = arcade.lerp(self.start_alpha, self.mid_alpha, u)
+ else:
+ u = (self.lifetime_elapsed - self.in_duration) / self.out_duration
+ self.alpha = arcade.lerp(self.mid_alpha, self.end_alpha, u)
+
+
+class RocketEmitter(arcade.Emitter):
+ """Custom emitter class to add gravity to the emitter to represent gravity on the firework shell"""
+
+ def update(self):
+ super().update()
+ # gravity
+ self.change_y += -0.05
+
+
+class FireworksApp(arcade.Window):
+ def __init__(self):
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ arcade.set_background_color(arcade.color.BLACK)
+ self.emitters = []
+ self.frametime_plotter = FrametimePlotter()
+
+ self.launch_firework(0)
+ arcade.schedule(self.launch_spinner, 4.0)
+
+ stars = arcade.Emitter(
+ center_xy=(0.0, 0.0),
+ emit_controller=arcade.EmitMaintainCount(20),
+ particle_factory=lambda emitter: AnimatedAlphaParticle(
+ filename_or_texture=random.choice(STAR_TEXTURES),
+ change_xy=(0.0, 0.0),
+ start_alpha=0,
+ duration1=random.uniform(2.0, 6.0),
+ mid_alpha=128,
+ duration2=random.uniform(2.0, 6.0),
+ end_alpha=0,
+ center_xy=arcade.rand_in_rect((0.0, 0.0), SCREEN_WIDTH, SCREEN_HEIGHT)
+ )
+ )
+ self.emitters.append(stars)
+
+ self.cloud = arcade.Emitter(
+ center_xy=(50, 500),
+ change_xy=(0.15, 0),
+ emit_controller=arcade.EmitMaintainCount(60),
+ particle_factory=lambda emitter: AnimatedAlphaParticle(
+ filename_or_texture=random.choice(CLOUD_TEXTURES),
+ change_xy=(_Vec2(arcade.rand_in_circle((0.0, 0.0), 0.04)) + _Vec2(0.1, 0)).as_tuple(),
+ start_alpha=0,
+ duration1=random.uniform(5.0, 10.0),
+ mid_alpha=255,
+ duration2=random.uniform(5.0, 10.0),
+ end_alpha=0,
+ center_xy=arcade.rand_in_circle((0.0, 0.0), 50)
+ )
+ )
+ self.emitters.append(self.cloud)
+
+ def launch_firework(self, delta_time):
+ self.frametime_plotter.add_event("launch")
+ launchers = (
+ self.launch_random_firework,
+ self.launch_ringed_firework,
+ self.launch_sparkle_firework,
+ )
+ random.choice(launchers)(delta_time)
+ pyglet.clock.schedule_once(self.launch_firework, random.uniform(LAUNCH_INTERVAL_MIN, LAUNCH_INTERVAL_MAX))
+
+ def launch_random_firework(self, delta_time):
+ """Simple firework that explodes in a random color"""
+ rocket = make_rocket(self.explode_firework)
+ self.emitters.append(rocket)
+
+ def launch_ringed_firework(self, delta_time):
+ """"Firework that has a basic explosion and a ring of sparks of a different color"""
+ rocket = make_rocket(self.explode_ringed_firework)
+ self.emitters.append(rocket)
+
+ def launch_sparkle_firework(self, delta_time):
+ """Firework which has sparks that sparkle"""
+ rocket = make_rocket(self.explode_sparkle_firework)
+ self.emitters.append(rocket)
+
+ def launch_spinner(self, delta_time):
+ """Start the spinner that throws sparks"""
+ spinner1 = make_spinner()
+ spinner2 = make_spinner()
+ spinner2.angle = 180
+ self.emitters.append(spinner1)
+ self.emitters.append(spinner2)
+
+ def explode_firework(self, prev_emitter):
+ """Actions that happen when a firework shell explodes, resulting in a typical firework"""
+ self.emitters.append(make_puff(prev_emitter))
+ self.emitters.append(make_flash(prev_emitter))
+
+ spark_texture = random.choice(SPARK_TEXTURES)
+ sparks = arcade.Emitter(
+ center_xy=prev_emitter.get_pos(),
+ emit_controller=arcade.EmitBurst(random.randint(30, 40)),
+ particle_factory=lambda emitter: arcade.FadeParticle(
+ filename_or_texture=spark_texture,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), 9.0),
+ lifetime=random.uniform(0.5, 1.2),
+ mutation_callback=firework_spark_mutator
+ )
+ )
+ self.emitters.append(sparks)
+
+ def explode_ringed_firework(self, prev_emitter):
+ """Actions that happen when a firework shell explodes, resulting in a ringed firework"""
+ self.emitters.append(make_puff(prev_emitter))
+ self.emitters.append(make_flash(prev_emitter))
+
+ spark_texture, ring_texture = random.choice(SPARK_PAIRS)
+ sparks = arcade.Emitter(
+ center_xy=prev_emitter.get_pos(),
+ emit_controller=arcade.EmitBurst(25),
+ particle_factory=lambda emitter: arcade.FadeParticle(
+ filename_or_texture=spark_texture,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), 8.0),
+ lifetime=random.uniform(0.55, 0.8),
+ mutation_callback=firework_spark_mutator
+ )
+ )
+ self.emitters.append(sparks)
+
+ ring = arcade.Emitter(
+ center_xy=prev_emitter.get_pos(),
+ emit_controller=arcade.EmitBurst(20),
+ particle_factory=lambda emitter: arcade.FadeParticle(
+ filename_or_texture=ring_texture,
+ change_xy=arcade.rand_on_circle((0.0, 0.0), 5.0) + arcade.rand_in_circle((0.0, 0.0), 0.25),
+ lifetime=random.uniform(1.0, 1.6),
+ mutation_callback=firework_spark_mutator
+ )
+ )
+ self.emitters.append(ring)
+
+ def explode_sparkle_firework(self, prev_emitter):
+ """Actions that happen when a firework shell explodes, resulting in a sparkling firework"""
+ self.emitters.append(make_puff(prev_emitter))
+ self.emitters.append(make_flash(prev_emitter))
+
+ spark_texture = random.choice(SPARK_TEXTURES)
+ sparks = arcade.Emitter(
+ center_xy=prev_emitter.get_pos(),
+ emit_controller=arcade.EmitBurst(random.randint(30, 40)),
+ particle_factory=lambda emitter: AnimatedAlphaParticle(
+ filename_or_texture=spark_texture,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), 9.0),
+ start_alpha=255,
+ duration1=random.uniform(0.6, 1.0),
+ mid_alpha=0,
+ duration2=random.uniform(0.1, 0.2),
+ end_alpha=255,
+ mutation_callback=firework_spark_mutator
+ )
+ )
+ self.emitters.append(sparks)
+
+ def update(self, delta_time):
+ # prevent list from being mutated (often by callbacks) while iterating over it
+ emitters_to_update = self.emitters.copy()
+ # update cloud
+ if self.cloud.center_x > SCREEN_WIDTH:
+ self.cloud.center_x = 0
+ # update
+ for e in emitters_to_update:
+ e.update()
+ # remove emitters that can be reaped
+ to_del = [e for e in emitters_to_update if e.can_reap()]
+ for e in to_del:
+ self.emitters.remove(e)
+ self.frametime_plotter.end_frame(delta_time)
+
+ def on_draw(self):
+ arcade.start_render()
+ for e in self.emitters:
+ e.draw()
+ arcade.draw_lrtb_rectangle_filled(0, SCREEN_WIDTH, 25, 0, arcade.color.DARK_GREEN)
+ mid = SCREEN_WIDTH / 2
+ arcade.draw_lrtb_rectangle_filled(mid - 2, mid + 2, SPINNER_HEIGHT, 10, arcade.color.DARK_BROWN)
+
+ def on_key_press(self, key, modifiers):
+ if key == arcade.key.ESCAPE:
+ arcade.close_window()
+
+
+def firework_spark_mutator(particle: arcade.FadeParticle):
+ """mutation_callback shared by all fireworks sparks"""
+ # gravity
+ particle.change_y += -0.03
+ # drag
+ particle.change_x *= 0.92
+ particle.change_y *= 0.92
+
+
+def rocket_smoke_mutator(particle: arcade.LifetimeParticle):
+ particle.scale = arcade.lerp(0.5, 3.0, particle.lifetime_elapsed / particle.lifetime_original)
+ # A Sprite's scale doesn't affect generated textures
+ # (ex: make_soft_circle_texture) or scale being animated over time.
+ # The fix below is copied from Sprite.update_animation().
+ # Bug may have been recorded here: https://github.com/pvcraven/arcade/issues/331
+ particle.width = particle._texture.width * particle.scale
+ particle.height = particle._texture.height * particle.scale
+
+
+if __name__ == "__main__":
+ app = FireworksApp()
+ arcade.run()
+ app.frametime_plotter.show()
diff --git a/arcade/examples/particle_stress.py b/arcade/examples/particle_stress.py
new file mode 100644
index 0000000..2e63b9e
--- /dev/null
+++ b/arcade/examples/particle_stress.py
@@ -0,0 +1,62 @@
+"""
+Particle system stress test
+
+Run a particle system that spawns, updates, draws and reaps many particles every frame for performance testing.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_list_particle_fireworks
+"""
+import os
+import arcade
+from arcade.examples.frametime_plotter import FrametimePlotter
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Particle stress test"
+TEXTURE = "images/pool_cue_ball.png"
+
+
+def make_emitter():
+ return arcade.Emitter(
+ center_xy=(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2),
+ emit_controller=arcade.EmitterIntervalWithTime(0.0004, 15.0),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), 5.0),
+ lifetime=1.0,
+ scale=0.5,
+ alpha=128
+ )
+ )
+
+
+class MyGame(arcade.Window):
+ def __init__(self):
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ self.emitter = make_emitter()
+ arcade.set_background_color(arcade.color.BLACK)
+ self.frametime_plotter = FrametimePlotter()
+
+ def update(self, delta_time):
+ self.emitter.update()
+ if self.emitter.can_reap():
+ arcade.close_window()
+ self.frametime_plotter.end_frame(delta_time)
+
+ def on_draw(self):
+ arcade.start_render()
+ self.emitter.draw()
+
+
+if __name__ == "__main__":
+ app = MyGame()
+ arcade.run()
+ app.frametime_plotter.show()
diff --git a/arcade/examples/particle_systems.py b/arcade/examples/particle_systems.py
new file mode 100644
index 0000000..fa880be
--- /dev/null
+++ b/arcade/examples/particle_systems.py
@@ -0,0 +1,775 @@
+"""
+Particle Systems
+
+Demonstrate how to use the Emitter and Particle classes to create particle systems.
+
+Demonstrate the different effects possible with Emitter's and Particle's by showing
+a number of different emitters in sequence, with each example often varying just one
+setting from the previous example.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_list_particle_systems
+"""
+import arcade
+from arcade.examples.frametime_plotter import FrametimePlotter
+import pyglet
+import os
+import random
+import math
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Particle System Examples"
+QUIET_BETWEEN_SPAWNS = 0.25 # time between spawning another particle system
+EMITTER_TIMEOUT = 10 * 60
+CENTER_POS = (SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2)
+BURST_PARTICLE_COUNT = 500
+TEXTURE = "images/pool_cue_ball.png"
+TEXTURE2 = "images/playerShip3_orange.png"
+TEXTURE3 = "images/bumper.png"
+TEXTURE4 = "images/wormGreen.png"
+TEXTURE5 = "images/meteorGrey_med1.png"
+TEXTURE6 = "images/character.png"
+TEXTURE7 = "images/boxCrate_double.png"
+DEFAULT_SCALE = 0.3
+DEFAULT_ALPHA = 32
+DEFAULT_PARTICLE_LIFETIME = 3.0
+PARTICLE_SPEED_FAST = 1.0
+PARTICLE_SPEED_SLOW = 0.3
+DEFAULT_EMIT_INTERVAL = 0.003
+DEFAULT_EMIT_DURATION = 1.5
+
+
+# Utils
+def sine_wave(t, min_x, max_x, wavelength):
+ spread = max_x - min_x
+ mid = (max_x + min_x) / 2
+ return (spread / 2) * math.sin(2 * math.pi * t / wavelength) + mid
+
+
+# Example emitters
+def emitter_0():
+ """Burst, emit from center, particle with lifetime"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_0.__doc__, e
+
+
+def emitter_1():
+ """Burst, emit from center, particle lifetime 1.0 seconds"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
+ lifetime=1.0,
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_1.__doc__, e
+
+
+def emitter_2():
+ """Burst, emit from center, particle lifetime random in range"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
+ lifetime=random.uniform(DEFAULT_PARTICLE_LIFETIME - 1.0, DEFAULT_PARTICLE_LIFETIME),
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_2.__doc__, e
+
+
+def emitter_3():
+ """Burst, emit in circle"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_SLOW),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ center_xy=arcade.rand_in_circle((0.0, 0.0), 100),
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_3.__doc__, e
+
+
+def emitter_4():
+ """Burst, emit on circle"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_SLOW),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ center_xy=arcade.rand_on_circle((0.0, 0.0), 100),
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_4.__doc__, e
+
+
+def emitter_5():
+ """Burst, emit in rectangle"""
+ width, height = 200, 100
+ centering_offset = (-width / 2, -height / 2)
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_SLOW),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ center_xy=arcade.rand_in_rect(centering_offset, width, height),
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_5.__doc__, e
+
+
+def emitter_6():
+ """Burst, emit on line"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_SLOW),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ center_xy=arcade.rand_on_line((0.0, 0.0), (SCREEN_WIDTH, SCREEN_HEIGHT)),
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_6.__doc__, e
+
+
+def emitter_7():
+ """Burst, emit from center, velocity fixed speed around 360 degrees"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT // 4),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_on_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_7.__doc__, e
+
+
+def emitter_8():
+ """Burst, emit from center, velocity in rectangle"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_rect((-2.0, -2.0), 4.0, 4.0),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_8.__doc__, e
+
+
+def emitter_9():
+ """Burst, emit from center, velocity in fixed angle and random speed"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT // 4),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_vec_magnitude(45, 1.0, 4.0),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_9.__doc__, e
+
+
+def emitter_10():
+ """Burst, emit from center, velocity from angle with spread"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT // 4),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_vec_spread_deg(90, 45, 2.0),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_10.__doc__, e
+
+
+def emitter_11():
+ """Burst, emit from center, velocity along a line"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT // 4),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_on_line((-2, 1), (2, 1)),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_11.__doc__, e
+
+
+def emitter_12():
+ """Infinite emitting w/ eternal particle"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitInterval(0.02),
+ particle_factory=lambda emitter: arcade.EternalParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_12.__doc__, e
+
+
+def emitter_13():
+ """Interval, emit particle every 0.01 seconds for one second"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_13.__doc__, e
+
+
+def emitter_14():
+ """Interval, emit from center, particle lifetime 1.0 seconds"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
+ lifetime=1.0,
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_14.__doc__, e
+
+
+def emitter_15():
+ """Interval, emit from center, particle lifetime random in range"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
+ lifetime=random.uniform(DEFAULT_PARTICLE_LIFETIME - 1.0, DEFAULT_PARTICLE_LIFETIME),
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_15.__doc__, e
+
+
+def emitter_16():
+ """Interval, emit in circle"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_SLOW),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ center_xy=arcade.rand_in_circle((0.0, 0.0), 100),
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_16.__doc__, e
+
+
+def emitter_17():
+ """Interval, emit on circle"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_SLOW),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ center_xy=arcade.rand_on_circle((0.0, 0.0), 100),
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_17.__doc__, e
+
+
+def emitter_18():
+ """Interval, emit in rectangle"""
+ width, height = 200, 100
+ centering_offset = (-width / 2, -height / 2)
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_SLOW),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ center_xy=arcade.rand_in_rect(centering_offset, width, height),
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_18.__doc__, e
+
+
+def emitter_19():
+ """Interval, emit on line"""
+ e = arcade.Emitter(
+ center_xy=(0.0, 0.0),
+ emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_SLOW),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ center_xy=arcade.rand_on_line((0.0, 0.0), (SCREEN_WIDTH, SCREEN_HEIGHT)),
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_19.__doc__, e
+
+
+def emitter_20():
+ """Interval, emit from center, velocity fixed speed around 360 degrees"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_on_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_20.__doc__, e
+
+
+def emitter_21():
+ """Interval, emit from center, velocity in rectangle"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_rect((-2.0, -2.0), 4.0, 4.0),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_21.__doc__, e
+
+
+def emitter_22():
+ """Interval, emit from center, velocity in fixed angle and speed"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitterIntervalWithTime(0.2, DEFAULT_EMIT_DURATION),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=(1.0, 1.0),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ scale=DEFAULT_SCALE,
+ alpha=128
+ )
+ )
+ return emitter_22.__doc__, e
+
+
+def emitter_23():
+ """Interval, emit from center, velocity in fixed angle and random speed"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL * 8, DEFAULT_EMIT_DURATION),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_vec_magnitude(45, 1.0, 4.0),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_23.__doc__, e
+
+
+def emitter_24():
+ """Interval, emit from center, velocity from angle with spread"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_vec_spread_deg(90, 45, 2.0),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_24.__doc__, e
+
+
+def emitter_25():
+ """Interval, emit from center, velocity along a line"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_on_line((-2, 1), (2, 1)),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ scale=DEFAULT_SCALE,
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_25.__doc__, e
+
+
+def emitter_26():
+ """Interval, emit particles every 0.4 seconds and stop after emitting 5"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitterIntervalWithCount(0.4, 5),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ scale=0.6,
+ alpha=128
+ )
+ )
+ return emitter_26.__doc__, e
+
+
+def emitter_27():
+ """Maintain a steady count of particles"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitMaintainCount(3),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_on_circle((0.0, 0.0), 2.0),
+ lifetime=random.uniform(1.0, 3.0),
+ )
+ )
+ return emitter_27.__doc__, e
+
+
+def emitter_28():
+ """random particle textures"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL * 5, DEFAULT_EMIT_DURATION),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=random.choice((TEXTURE, TEXTURE2, TEXTURE3)),
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ scale=DEFAULT_SCALE
+ )
+ )
+ return emitter_28.__doc__, e
+
+
+def emitter_29():
+ """random particle scale"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL * 5, DEFAULT_EMIT_DURATION),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ scale=random.uniform(0.1, 0.8),
+ alpha=DEFAULT_ALPHA
+ )
+ )
+ return emitter_29.__doc__, e
+
+
+def emitter_30():
+ """random particle alpha"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL * 5, DEFAULT_EMIT_DURATION),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ scale=DEFAULT_SCALE,
+ alpha=random.uniform(32, 128)
+ )
+ )
+ return emitter_30.__doc__, e
+
+
+def emitter_31():
+ """Constant particle angle"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL * 5, DEFAULT_EMIT_DURATION),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE2,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ angle=45,
+ scale=DEFAULT_SCALE
+ )
+ )
+ return emitter_31.__doc__, e
+
+
+def emitter_32():
+ """animate particle angle"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL * 5, DEFAULT_EMIT_DURATION),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE2,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ change_angle=2,
+ scale=DEFAULT_SCALE
+ )
+ )
+ return emitter_32.__doc__, e
+
+
+def emitter_33():
+ """Particles that fade over time"""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
+ particle_factory=lambda emitter: arcade.FadeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ scale=DEFAULT_SCALE
+ )
+ )
+ return emitter_33.__doc__, e
+
+
+def emitter_34():
+ """Dynamically generated textures, burst emitting, fading particles"""
+ textures = [arcade.make_soft_circle_texture(48, p) for p in (arcade.color.GREEN, arcade.color.BLUE_GREEN)]
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT),
+ particle_factory=lambda emitter: arcade.FadeParticle(
+ filename_or_texture=random.choice(textures),
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
+ lifetime=DEFAULT_PARTICLE_LIFETIME,
+ scale=DEFAULT_SCALE
+ )
+ )
+ return emitter_34.__doc__, e
+
+
+def emitter_35():
+ """Use most features"""
+ soft_circle = arcade.make_soft_circle_texture(80, (255, 64, 64))
+ textures = (TEXTURE, TEXTURE2, TEXTURE3, TEXTURE4, TEXTURE5, TEXTURE6, TEXTURE7, soft_circle)
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitterIntervalWithTime(0.01, 1.0),
+ particle_factory=lambda emitter: arcade.FadeParticle(
+ filename_or_texture=random.choice(textures),
+ change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST * 2),
+ lifetime=random.uniform(1.0, 3.5),
+ angle=random.uniform(0, 360),
+ change_angle=random.uniform(-3, 3),
+ scale=random.uniform(0.1, 0.8)
+ )
+ )
+ return emitter_35.__doc__, e
+
+
+def emitter_36():
+ """Moving emitter. Particles spawn relative to emitter."""
+
+ class MovingEmitter(arcade.Emitter):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.elapsed = 0.0
+
+ def update(self):
+ super().update()
+ self.elapsed += 1 / 60
+ self.center_x = sine_wave(self.elapsed, 0, SCREEN_WIDTH, SCREEN_WIDTH / 100)
+ self.center_y = sine_wave(self.elapsed, 0, SCREEN_HEIGHT, SCREEN_HEIGHT / 100)
+
+ e = MovingEmitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitInterval(0.005),
+ particle_factory=lambda emitter: arcade.FadeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=arcade.rand_in_circle((0.0, 0.0), 0.1),
+ lifetime=random.uniform(1.5, 5.5),
+ scale=random.uniform(0.05, 0.2)
+ )
+ )
+ return emitter_36.__doc__, e
+
+
+def emitter_37():
+ """Rotating emitter. Particles initial velocity is relative to emitter's angle."""
+ e = arcade.Emitter(
+ center_xy=CENTER_POS,
+ emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
+ particle_factory=lambda emitter: arcade.LifetimeParticle(
+ filename_or_texture=TEXTURE,
+ change_xy=(0.0, 2.0),
+ lifetime=2.0,
+ scale=DEFAULT_SCALE
+ )
+ )
+ e.change_angle = 10.0
+ return emitter_37.__doc__, e
+
+
+def emitter_38():
+ """Use simple emitter interface to create a burst emitter"""
+ e = arcade.make_burst_emitter(
+ center_xy=CENTER_POS,
+ filenames_and_textures=(TEXTURE, TEXTURE3, TEXTURE4),
+ particle_count=50,
+ particle_speed=2.5,
+ particle_lifetime_min=1.0,
+ particle_lifetime_max=2.5,
+ particle_scale=0.3,
+ fade_particles=True
+ )
+ return emitter_38.__doc__, e
+
+
+def emitter_39():
+ """Use simple emitter interface to create an interval emitter"""
+ e = arcade.make_interval_emitter(
+ center_xy=CENTER_POS,
+ filenames_and_textures=(TEXTURE, TEXTURE3, TEXTURE4),
+ emit_interval=0.01,
+ emit_duration=2.0,
+ particle_speed=1.5,
+ particle_lifetime_min=1.0,
+ particle_lifetime_max=3.0,
+ particle_scale=0.2,
+ fade_particles=True
+ )
+ return emitter_39.__doc__, e
+
+
+class MyGame(arcade.Window):
+ def __init__(self):
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ arcade.set_background_color(arcade.color.BLACK)
+
+ # collect particle factory functions
+ self.factories = [v for k, v in globals().items() if k.startswith("emitter_")]
+
+ self.emitter_factory_id = -1
+ self.label = None
+ self.emitter = None
+ self.obj = arcade.Sprite("images/bumper.png", 0.2, center_x=0, center_y=15)
+ self.obj.change_x = 3
+ self.frametime_plotter = FrametimePlotter()
+ pyglet.clock.schedule_once(self.next_emitter, QUIET_BETWEEN_SPAWNS)
+
+ def next_emitter(self, time_delta):
+ self.emitter_factory_id = (self.emitter_factory_id + 1) % len(self.factories)
+ print("Changing emitter to {}".format(self.emitter_factory_id))
+ self.emitter_timeout = 0
+ self.label, self.emitter = self.factories[self.emitter_factory_id]()
+ self.frametime_plotter.add_event("spawn {}".format(self.emitter_factory_id))
+
+ def update(self, delta_time):
+ if self.emitter:
+ self.emitter_timeout += 1
+ self.emitter.update()
+ if self.emitter.can_reap() or self.emitter_timeout > EMITTER_TIMEOUT:
+ self.frametime_plotter.add_event("reap")
+ pyglet.clock.schedule_once(self.next_emitter, QUIET_BETWEEN_SPAWNS)
+ self.emitter = None
+ self.obj.update()
+ if self.obj.center_x > SCREEN_WIDTH:
+ self.obj.center_x = 0
+ self.frametime_plotter.end_frame(delta_time)
+
+ def on_draw(self):
+ arcade.start_render()
+ self.obj.draw()
+ if self.label:
+ arcade.draw_text("#{} {}".format(self.emitter_factory_id, self.label),
+ SCREEN_WIDTH / 2, SCREEN_HEIGHT - 20,
+ arcade.color.PALE_GOLD, 20, width=SCREEN_WIDTH, align="center",
+ anchor_x="center", anchor_y="center")
+ if self.emitter:
+ self.emitter.draw()
+ arcade.draw_text("Particles: " + str(len(self.emitter._particles)), 10, 30, arcade.color.PALE_GOLD, 12)
+
+ def on_key_press(self, key, modifiers):
+ if key == arcade.key.ESCAPE:
+ arcade.close_window()
+
+
+if __name__ == "__main__":
+ game = MyGame()
+ arcade.run()
+ game.frametime_plotter.show()
diff --git a/arcade/examples/perf_test/stress_test_charts.py b/arcade/examples/perf_test/stress_test_charts.py
new file mode 100644
index 0000000..2ea1c25
--- /dev/null
+++ b/arcade/examples/perf_test/stress_test_charts.py
@@ -0,0 +1,143 @@
+import csv
+import matplotlib.pyplot as plt
+
+SPRITE_COUNT = 1
+PROCESSING_TIME = 3
+DRAWING_TIME = 4
+
+
+def read_results(filename):
+ results = []
+ with open(filename) as csv_file:
+ csv_reader = csv.reader(csv_file, delimiter=',')
+ line_count = 0
+ for row in csv_reader:
+ results.append([float(cell) for cell in row])
+ return results
+
+
+def chart_stress_test_draw_moving_pygame():
+ results = read_results("stress_test_draw_moving_pygame.csv")
+
+ sprite_count = [row[SPRITE_COUNT] for row in results]
+ processing_time = [row[PROCESSING_TIME] for row in results]
+ drawing_time = [row[DRAWING_TIME] for row in results]
+
+ # Plot our results
+ plt.title("Moving and Drawing Sprites In Pygame")
+ plt.plot(sprite_count, processing_time, label="Processing Time")
+ plt.plot(sprite_count, drawing_time, label="Drawing Time")
+
+ plt.legend(loc='upper left', shadow=True, fontsize='x-large')
+
+ plt.ylabel('Time')
+ plt.xlabel('Sprite Count')
+
+ plt.savefig("chart_stress_test_draw_moving_pygame.svg")
+ # plt.show()
+ plt.clf()
+
+
+def chart_stress_test_draw_moving_arcade():
+ results = read_results("stress_test_draw_moving.csv")
+
+ sprite_count = [row[SPRITE_COUNT] for row in results]
+ processing_time = [row[PROCESSING_TIME] for row in results]
+ drawing_time = [row[DRAWING_TIME] for row in results]
+
+ # Plot our results
+ plt.title("Moving and Drawing Sprites In Arcade")
+ plt.plot(sprite_count, processing_time, label="Processing Time")
+ plt.plot(sprite_count, drawing_time, label="Drawing Time")
+
+ plt.legend(loc='upper left', shadow=True, fontsize='x-large')
+
+ plt.ylabel('Time')
+ plt.xlabel('Sprite Count')
+
+ # plt.show()
+ plt.savefig("chart_stress_test_draw_moving_arcade.svg")
+ plt.clf()
+
+
+def chart_stress_test_draw_moving_draw_comparison():
+ r1 = read_results("stress_test_draw_moving.csv")
+ r2 = read_results("stress_test_draw_moving_pygame.csv")
+
+ sprite_count = [row[SPRITE_COUNT] for row in r1]
+ d1 = [row[DRAWING_TIME] for row in r1]
+ d2 = [row[DRAWING_TIME] for row in r2]
+
+ # Plot our results
+ plt.title("Drawing Sprites - Arcade vs. Pygame")
+ plt.plot(sprite_count, d1, label="Drawing Time Arcade")
+ plt.plot(sprite_count, d2, label="Drawing Time Pygame")
+
+ plt.legend(loc='upper left', shadow=True, fontsize='x-large')
+
+ plt.ylabel('Time')
+ plt.xlabel('Sprite Count')
+
+ # plt.show()
+ plt.savefig("chart_stress_test_draw_moving_draw_comparison.svg")
+ plt.clf()
+
+
+def chart_stress_test_draw_moving_process_comparison():
+ r1 = read_results("stress_test_draw_moving_arcade.csv")
+ r2 = read_results("stress_test_draw_moving_pygame.csv")
+
+ sprite_count = [row[SPRITE_COUNT] for row in r1]
+ d1 = [row[PROCESSING_TIME] for row in r1]
+ d2 = [row[PROCESSING_TIME] for row in r2]
+
+ # Plot our results
+ plt.title("Moving Sprites - Arcade vs. Pygame")
+ plt.plot(sprite_count, d1, label="Processing Time Arcade")
+ plt.plot(sprite_count, d2, label="Processing Time Pygame")
+
+ plt.legend(loc='upper left', shadow=True, fontsize='x-large')
+
+ plt.ylabel('Time')
+ plt.xlabel('Sprite Count')
+
+ # plt.show()
+ plt.savefig("chart_stress_test_draw_moving_process_comparison.svg")
+ plt.clf()
+
+
+def chart_stress_test_collision_comparison():
+ r1 = read_results("stress_test_collision_arcade.csv")
+ r2 = read_results("stress_test_collision_arcade_spatial.csv")
+ r3 = read_results("stress_test_collision_pygame.csv")
+
+ sprite_count = [row[SPRITE_COUNT] for row in r1]
+ d1 = [row[PROCESSING_TIME] for row in r1]
+ d2 = [row[PROCESSING_TIME] for row in r2]
+ d3 = [row[PROCESSING_TIME] for row in r3]
+
+ # Plot our results
+ plt.title("Colliding Sprites - Arcade vs Pygame")
+ plt.plot(sprite_count, d1, label="Processing Time Arcade Normal")
+ plt.plot(sprite_count, d2, label="Processing Time Arcade Spatial")
+ plt.plot(sprite_count, d3, label="Processing Time Pygame")
+
+ plt.legend(loc='upper left', shadow=True, fontsize='x-large')
+
+ plt.ylabel('Time')
+ plt.xlabel('Sprite Count')
+
+ # plt.show()
+ plt.savefig("chart_stress_test_collision_comparison.svg")
+ plt.clf()
+
+
+def main():
+ chart_stress_test_draw_moving_pygame()
+ chart_stress_test_draw_moving_arcade()
+ chart_stress_test_draw_moving_draw_comparison()
+ chart_stress_test_draw_moving_process_comparison()
+ chart_stress_test_collision_comparison()
+
+
+main()
diff --git a/arcade/examples/perf_test/stress_test_collision_arcade.py b/arcade/examples/perf_test/stress_test_collision_arcade.py
new file mode 100644
index 0000000..f91a760
--- /dev/null
+++ b/arcade/examples/perf_test/stress_test_collision_arcade.py
@@ -0,0 +1,216 @@
+"""
+Moving Sprite Stress Test
+
+Simple program to test how fast we can draw sprites that are moving
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.stress_test_draw_moving
+"""
+
+import arcade
+import random
+import os
+import timeit
+import time
+import collections
+import pyglet
+
+# --- Constants ---
+SPRITE_SCALING_COIN = 0.09
+SPRITE_SCALING_PLAYER = 0.5
+SPRITE_NATIVE_SIZE = 128
+SPRITE_SIZE = int(SPRITE_NATIVE_SIZE * SPRITE_SCALING_COIN)
+COIN_COUNT_INCREMENT = 500
+
+STOP_COUNT = 12000
+RESULTS_FILE = "stress_test_collision_arcade.csv"
+
+SCREEN_WIDTH = 1800
+SCREEN_HEIGHT = 1000
+SCREEN_TITLE = "Moving Sprite Stress Test"
+
+
+class FPSCounter:
+ def __init__(self):
+ self.time = time.perf_counter()
+ self.frame_times = collections.deque(maxlen=60)
+
+ def tick(self):
+ t1 = time.perf_counter()
+ dt = t1 - self.time
+ self.time = t1
+ self.frame_times.append(dt)
+
+ def get_fps(self):
+ total_time = sum(self.frame_times)
+ if total_time == 0:
+ return 0
+ else:
+ return len(self.frame_times) / sum(self.frame_times)
+
+
+class MyGame(arcade.Window):
+ """ Our custom Window Class"""
+
+ def __init__(self):
+ """ Initializer """
+ # Call the parent class initializer
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Variables that will hold sprite lists
+ self.coin_list = None
+ self.player_list = None
+ self.player = None
+
+ self.processing_time = 0
+ self.draw_time = 0
+ self.program_start_time = timeit.default_timer()
+ self.sprite_count_list = []
+ self.fps_list = []
+ self.processing_time_list = []
+ self.drawing_time_list = []
+ self.last_fps_reading = 0
+ self.fps = FPSCounter()
+
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ # Open file to save timings
+ self.results_file = open(RESULTS_FILE, "w")
+
+ def add_coins(self):
+
+ # Create the coins
+ for i in range(COIN_COUNT_INCREMENT):
+ # Create the coin instance
+ # Coin image from kenney.nl
+ coin = arcade.Sprite("../images/coin_01.png", SPRITE_SCALING_COIN)
+
+ # Position the coin
+ coin.center_x = random.randrange(SPRITE_SIZE, SCREEN_WIDTH - SPRITE_SIZE)
+ coin.center_y = random.randrange(SPRITE_SIZE, SCREEN_HEIGHT - SPRITE_SIZE)
+
+ # Add the coin to the lists
+ self.coin_list.append(coin)
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.coin_list = arcade.SpriteList(use_spatial_hash=False)
+ self.player_list = arcade.SpriteList()
+ self.player = arcade.Sprite("../images/character.png", SPRITE_SCALING_PLAYER)
+ self.player.center_x = random.randrange(SCREEN_WIDTH)
+ self.player.center_y = random.randrange(SCREEN_HEIGHT)
+ self.player.change_x = 3
+ self.player.change_y = 5
+ self.player_list.append(self.player)
+
+ def on_draw(self):
+ """ Draw everything """
+
+ # Start timing how long this takes
+ draw_start_time = timeit.default_timer()
+
+ arcade.start_render()
+ self.coin_list.draw()
+ self.player_list.draw()
+
+ # Display info on sprites
+ output = f"Sprite count: {len(self.coin_list):,}"
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 20, arcade.color.BLACK, 16)
+
+ # Display timings
+ output = f"Processing time: {self.processing_time:.3f}"
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 40, arcade.color.BLACK, 16)
+
+ output = f"Drawing time: {self.draw_time:.3f}"
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 60, arcade.color.BLACK, 16)
+
+ fps = self.fps.get_fps()
+ output = f"FPS: {fps:3.0f}"
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 80, arcade.color.BLACK, 16)
+
+ self.draw_time = timeit.default_timer() - draw_start_time
+ self.fps.tick()
+
+ def update(self, delta_time):
+ # Start update timer
+
+ start_time = timeit.default_timer()
+
+ self.player_list.update()
+ if self.player.center_x < 0 and self.player.change_x < 0:
+ self.player.change_x *= -1
+ if self.player.center_y < 0 and self.player.change_y < 0:
+ self.player.change_y *= -1
+
+ if self.player.center_x > SCREEN_WIDTH and self.player.change_x > 0:
+ self.player.change_x *= -1
+ if self.player.center_y > SCREEN_HEIGHT and self.player.change_y > 0:
+ self.player.change_y *= -1
+
+ coin_hit_list = arcade.check_for_collision_with_list(self.player, self.coin_list)
+ for coin in coin_hit_list:
+ coin.center_x = random.randrange(SCREEN_WIDTH)
+ coin.center_y = random.randrange(SCREEN_HEIGHT)
+
+ # Save the time it took to do this.
+ self.processing_time = timeit.default_timer() - start_time
+
+ # Total time program has been running
+ total_program_time = int(timeit.default_timer() - self.program_start_time)
+
+ # Print out stats, or add more sprites
+ if total_program_time > self.last_fps_reading:
+ self.last_fps_reading = total_program_time
+
+ # It takes the program a while to "warm up", so the first
+ # few seconds our readings will be off. So wait some time
+ # before taking readings
+ if total_program_time > 5:
+
+ # We want the program to run for a while before taking
+ # timing measurements. We don't want the time it takes
+ # to add new sprites to be part of that measurement. So
+ # make sure we have a clear second of nothing but
+ # running the sprites, and not adding the sprites.
+ if total_program_time % 2 == 1:
+
+ output = f"{total_program_time}, {len(self.coin_list)}, {self.fps.get_fps():.1f}, {self.processing_time:.4f}, {self.draw_time:.4f}\n"
+ print(output, end="")
+ self.results_file.write(output)
+
+ if len(self.coin_list) >= STOP_COUNT:
+ pyglet.app.exit()
+ return
+
+ # Take timings
+ print(
+ f"{total_program_time}, {len(self.coin_list)}, {self.fps.get_fps():.1f}, {self.processing_time:.4f}, {self.draw_time:.4f}")
+ self.sprite_count_list.append(len(self.coin_list))
+ self.fps_list.append(round(self.fps.get_fps(), 1))
+ self.processing_time_list.append(self.processing_time)
+ self.drawing_time_list.append(self.draw_time)
+
+ # Now add the coins
+ self.add_coins()
+
+
+def main():
+ """ Main method """
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/perf_test/stress_test_collision_pygame.py b/arcade/examples/perf_test/stress_test_collision_pygame.py
new file mode 100644
index 0000000..caaa5e7
--- /dev/null
+++ b/arcade/examples/perf_test/stress_test_collision_pygame.py
@@ -0,0 +1,296 @@
+"""
+Sample Python/Pygame Programs
+Simpson College Computer Science
+http://programarcadegames.com/
+http://simpson.edu/computer-science/
+"""
+
+import pygame
+import random
+import os
+import time
+import timeit
+import collections
+
+# Define some colors
+BLACK = (0, 0, 0)
+WHITE = (255, 255, 255)
+RED = (255, 0, 0)
+
+# --- Constants ---
+SPRITE_SCALING_COIN = 0.09
+SPRITE_SCALING_PLAYER = 0.5
+SPRITE_NATIVE_SIZE = 128
+SPRITE_SIZE = int(SPRITE_NATIVE_SIZE * SPRITE_SCALING_COIN)
+COIN_COUNT_INCREMENT = 500
+
+STOP_COUNT = 12000
+RESULTS_FILE = "stress_test_collision_pygame.csv"
+
+SCREEN_WIDTH = 1800
+SCREEN_HEIGHT = 1000
+SCREEN_TITLE = "Moving Sprite Stress Test"
+
+
+class FPSCounter:
+ def __init__(self):
+ self.time = time.perf_counter()
+ self.frame_times = collections.deque(maxlen=60)
+
+ def tick(self):
+ t1 = time.perf_counter()
+ dt = t1 - self.time
+ self.time = t1
+ self.frame_times.append(dt)
+
+ def get_fps(self):
+ total_time = sum(self.frame_times)
+ if total_time == 0:
+ return 0
+ else:
+ return len(self.frame_times) / sum(self.frame_times)
+
+
+class Coin(pygame.sprite.Sprite):
+ """
+ This class represents the ball
+ It derives from the "Sprite" class in Pygame
+ """
+
+ def __init__(self):
+ """ Constructor. Pass in the color of the block,
+ and its x and y position. """
+ # Call the parent class (Sprite) constructor
+ super().__init__()
+
+ # Create an image of the block, and fill it with a color.
+ # This could also be an image loaded from the disk.
+ image = pygame.image.load("../images/coin_01.png")
+ rect = image.get_rect()
+ image = pygame.transform.scale(image,
+ (int(rect.width * SPRITE_SCALING_COIN), int(rect.height * SPRITE_SCALING_COIN)))
+ self.image = image.convert()
+ self.image.set_colorkey(BLACK)
+
+ # Fetch the rectangle object that has the dimensions of the image
+ # image.
+ # Update the position of this object by setting the values
+ # of rect.x and rect.y
+ self.rect = self.image.get_rect()
+
+
+class Player(pygame.sprite.Sprite):
+ """
+ This class represents the ball
+ It derives from the "Sprite" class in Pygame
+ """
+
+ def __init__(self):
+ """ Constructor. Pass in the color of the block,
+ and its x and y position. """
+ # Call the parent class (Sprite) constructor
+ super().__init__()
+
+ # Create an image of the block, and fill it with a color.
+ # This could also be an image loaded from the disk.
+ image = pygame.image.load("../images/character.png")
+ rect = image.get_rect()
+ image = pygame.transform.scale(image, (
+ int(rect.width * SPRITE_SCALING_PLAYER), int(rect.height * SPRITE_SCALING_PLAYER)))
+ self.image = image.convert()
+ self.image.set_colorkey(WHITE)
+
+ # Fetch the rectangle object that has the dimensions of the image
+ # image.
+ # Update the position of this object by setting the values
+ # of rect.x and rect.y
+ self.rect = self.image.get_rect()
+
+ def update(self):
+ """ Called each frame. """
+ self.rect.x += self.change_x
+ self.rect.y += self.change_y
+
+
+class MyGame:
+ """ Our custom Window Class"""
+
+ def __init__(self):
+ """ Initializer """
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Variables that will hold sprite lists
+ self.coin_list = None
+
+ self.processing_time = 0
+ self.draw_time = 0
+ self.program_start_time = timeit.default_timer()
+ self.sprite_count_list = []
+ self.fps_list = []
+ self.processing_time_list = []
+ self.drawing_time_list = []
+ self.last_fps_reading = 0
+ self.fps = FPSCounter()
+
+ # Initialize Pygame
+ pygame.init()
+
+ # Set the height and width of the screen
+ self.screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT])
+
+ # This is a list of every sprite. All blocks and the player block as well.
+ self.coin_list = pygame.sprite.Group()
+ self.player_list = pygame.sprite.Group()
+
+ # Create the player instance
+ self.player = Player()
+
+ self.player.rect.x = random.randrange(SPRITE_SIZE, SCREEN_WIDTH - SPRITE_SIZE)
+ self.player.rect.y = random.randrange(SPRITE_SIZE, SCREEN_HEIGHT - SPRITE_SIZE)
+ self.player.change_x = 3
+ self.player.change_y = 5
+
+ self.player_list.add(self.player)
+
+ self.font = pygame.font.SysFont('Calibri', 25, True, False)
+
+ # Open file to save timings
+ self.results_file = open(RESULTS_FILE, "w")
+
+ def add_coins(self):
+
+ # Create the coins
+ for i in range(COIN_COUNT_INCREMENT):
+ # Create the coin instance
+ # Coin image from kenney.nl
+ coin = Coin()
+
+ # Position the coin
+ coin.rect.x = random.randrange(SPRITE_SIZE, SCREEN_WIDTH - SPRITE_SIZE)
+ coin.rect.y = random.randrange(SPRITE_SIZE, SCREEN_HEIGHT - SPRITE_SIZE)
+
+ # Add the coin to the lists
+ self.coin_list.add(coin)
+
+ def on_draw(self):
+ """ Draw everything """
+
+ # Start timing how long this takes
+ draw_start_time = timeit.default_timer()
+
+ # Clear the screen
+ self.screen.fill((59, 122, 87))
+
+ # Draw all the spites
+ self.coin_list.draw(self.screen)
+ self.player_list.draw(self.screen)
+
+ # Display timings
+ output = f"Processing time: {self.processing_time:.3f}"
+ text = self.font.render(output, True, BLACK)
+ self.screen.blit(text, [20, SCREEN_HEIGHT - 40])
+
+ output = f"Drawing time: {self.draw_time:.3f}"
+ text = self.font.render(output, True, BLACK)
+ self.screen.blit(text, [20, SCREEN_HEIGHT - 60])
+
+ fps = self.fps.get_fps()
+ output = f"FPS: {fps:3.0f}"
+ text = self.font.render(output, True, BLACK)
+ self.screen.blit(text, [20, SCREEN_HEIGHT - 80])
+
+ pygame.display.flip()
+
+ self.draw_time = timeit.default_timer() - draw_start_time
+ self.fps.tick()
+
+ def update(self, delta_time):
+ # Start update timer
+ self.player_list.update()
+
+ if self.player.rect.x < 0 and self.player.change_x < 0:
+ self.player.change_x *= -1
+ if self.player.rect.y < 0 and self.player.change_y < 0:
+ self.player.change_y *= -1
+
+ if self.player.rect.x > SCREEN_WIDTH and self.player.change_x > 0:
+ self.player.change_x *= -1
+ if self.player.rect.y > SCREEN_HEIGHT and self.player.change_y > 0:
+ self.player.change_y *= -1
+
+ start_time = timeit.default_timer()
+
+ coin_hit_list = pygame.sprite.spritecollide(self.player, self.coin_list, False)
+ for coin in coin_hit_list:
+ coin.rect.x = random.randrange(SCREEN_WIDTH)
+ coin.rect.y = random.randrange(SCREEN_HEIGHT)
+
+ # Save the time it took to do this.
+ self.processing_time = timeit.default_timer() - start_time
+
+ # Total time program has been running
+ total_program_time = int(timeit.default_timer() - self.program_start_time)
+
+ # Print out stats, or add more sprites
+ if total_program_time > self.last_fps_reading:
+ self.last_fps_reading = total_program_time
+
+ # It takes the program a while to "warm up", so the first
+ # few seconds our readings will be off. So wait some time
+ # before taking readings
+ if total_program_time > 5:
+
+ # We want the program to run for a while before taking
+ # timing measurements. We don't want the time it takes
+ # to add new sprites to be part of that measurement. So
+ # make sure we have a clear second of nothing but
+ # running the sprites, and not adding the sprites.
+ if total_program_time % 2 == 1:
+
+ # Take timings
+ output = f"{total_program_time}, {len(self.coin_list)}, {self.fps.get_fps():.1f}, {self.processing_time:.4f}, {self.draw_time:.4f}\n"
+ print(output, end="")
+ self.results_file.write(output)
+
+ if len(self.coin_list) >= STOP_COUNT:
+ pygame.event.post(pygame.event.Event(pygame.QUIT, {}))
+ return
+
+ self.sprite_count_list.append(len(self.coin_list))
+ self.fps_list.append(round(self.fps.get_fps(), 1))
+ self.processing_time_list.append(self.processing_time)
+ self.drawing_time_list.append(self.draw_time)
+
+ # Now add the coins
+ self.add_coins()
+
+
+def main():
+ """ Main method """
+ window = MyGame()
+
+ # Loop until the user clicks the close button.
+ done = False
+
+ # Used to manage how fast the screen updates
+ clock = pygame.time.Clock()
+
+ # -------- Main Program Loop -----------
+ while not done:
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT:
+ done = True
+ window.update(0)
+ window.on_draw()
+ clock.tick(60)
+
+ pygame.quit()
+
+
+main()
diff --git a/arcade/examples/perf_test/stress_test_draw_moving_arcade.py b/arcade/examples/perf_test/stress_test_draw_moving_arcade.py
new file mode 100644
index 0000000..e295ff6
--- /dev/null
+++ b/arcade/examples/perf_test/stress_test_draw_moving_arcade.py
@@ -0,0 +1,211 @@
+"""
+Moving Sprite Stress Test
+
+Simple program to test how fast we can draw sprites that are moving
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.stress_test_draw_moving
+"""
+
+import random
+import arcade
+import os
+import timeit
+import time
+import collections
+import pyglet
+
+# --- Constants ---
+SPRITE_SCALING_COIN = 0.09
+SPRITE_NATIVE_SIZE = 128
+SPRITE_SIZE = int(SPRITE_NATIVE_SIZE * SPRITE_SCALING_COIN)
+COIN_COUNT_INCREMENT = 100
+
+STOP_COUNT = 6000
+RESULTS_FILE = "stress_test_draw_moving_arcade.csv"
+
+SCREEN_WIDTH = 1800
+SCREEN_HEIGHT = 1000
+SCREEN_TITLE = "Moving Sprite Stress Test"
+
+
+class FPSCounter:
+ def __init__(self):
+ self.time = time.perf_counter()
+ self.frame_times = collections.deque(maxlen=60)
+
+ def tick(self):
+ t1 = time.perf_counter()
+ dt = t1 - self.time
+ self.time = t1
+ self.frame_times.append(dt)
+
+ def get_fps(self):
+ total_time = sum(self.frame_times)
+ if total_time == 0:
+ return 0
+ else:
+ return len(self.frame_times) / sum(self.frame_times)
+
+
+class Coin(arcade.Sprite):
+
+ def update(self):
+ """
+ Update the sprite.
+ """
+ self.position = (self.position[0] + self.change_x, self.position[1] + self.change_y)
+
+
+class MyGame(arcade.Window):
+ """ Our custom Window Class"""
+
+ def __init__(self):
+ """ Initializer """
+ # Call the parent class initializer
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Variables that will hold sprite lists
+ self.coin_list = None
+
+ self.processing_time = 0
+ self.draw_time = 0
+ self.program_start_time = timeit.default_timer()
+ self.sprite_count_list = []
+ self.fps_list = []
+ self.processing_time_list = []
+ self.drawing_time_list = []
+ self.last_fps_reading = 0
+ self.fps = FPSCounter()
+
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ # Open file to save timings
+ self.results_file = open(RESULTS_FILE, "w")
+
+ def add_coins(self):
+
+ # Create the coins
+ for i in range(COIN_COUNT_INCREMENT):
+ # Create the coin instance
+ # Coin image from kenney.nl
+ coin = Coin("../images/coin_01.png", SPRITE_SCALING_COIN)
+
+ # Position the coin
+ coin.center_x = random.randrange(SPRITE_SIZE, SCREEN_WIDTH - SPRITE_SIZE)
+ coin.center_y = random.randrange(SPRITE_SIZE, SCREEN_HEIGHT - SPRITE_SIZE)
+
+ coin.change_x = random.randrange(-3, 4)
+ coin.change_y = random.randrange(-3, 4)
+
+ # Add the coin to the lists
+ self.coin_list.append(coin)
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.coin_list = arcade.SpriteList(use_spatial_hash=False)
+
+ def on_draw(self):
+ """ Draw everything """
+
+ # Start timing how long this takes
+ draw_start_time = timeit.default_timer()
+
+ arcade.start_render()
+ self.coin_list.draw()
+
+ # Display info on sprites
+ output = f"Sprite count: {len(self.coin_list):,}"
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 20, arcade.color.BLACK, 16)
+
+ # Display timings
+ output = f"Processing time: {self.processing_time:.3f}"
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 40, arcade.color.BLACK, 16)
+
+ output = f"Drawing time: {self.draw_time:.3f}"
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 60, arcade.color.BLACK, 16)
+
+ fps = self.fps.get_fps()
+ output = f"FPS: {fps:3.0f}"
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 80, arcade.color.BLACK, 16)
+
+ self.draw_time = timeit.default_timer() - draw_start_time
+ self.fps.tick()
+
+ def update(self, delta_time):
+ # Start update timer
+ start_time = timeit.default_timer()
+
+ self.coin_list.update()
+
+ for sprite in self.coin_list:
+
+ if sprite.position[0] < 0:
+ sprite.change_x *= -1
+ elif sprite.position[0] > SCREEN_WIDTH:
+ sprite.change_x *= -1
+ if sprite.position[1] < 0:
+ sprite.change_y *= -1
+ elif sprite.position[1] > SCREEN_HEIGHT:
+ sprite.change_y *= -1
+
+ # Save the time it took to do this.
+ self.processing_time = timeit.default_timer() - start_time
+
+ # Total time program has been running
+ total_program_time = int(timeit.default_timer() - self.program_start_time)
+
+ # Print out stats, or add more sprites
+ if total_program_time > self.last_fps_reading:
+ self.last_fps_reading = total_program_time
+
+ # It takes the program a while to "warm up", so the first
+ # few seconds our readings will be off. So wait some time
+ # before taking readings
+ if total_program_time > 5:
+
+ # We want the program to run for a while before taking
+ # timing measurements. We don't want the time it takes
+ # to add new sprites to be part of that measurement. So
+ # make sure we have a clear second of nothing but
+ # running the sprites, and not adding the sprites.
+ if total_program_time % 2 == 1:
+
+ # Take timings
+ output = f"{total_program_time}, {len(self.coin_list)}, {self.fps.get_fps():.1f}, {self.processing_time:.4f}, {self.draw_time:.4f}\n"
+
+ self.results_file.write(output)
+ print(output, end="")
+ if len(self.coin_list) >= STOP_COUNT:
+ pyglet.app.exit()
+ return
+
+ self.sprite_count_list.append(len(self.coin_list))
+ self.fps_list.append(round(self.fps.get_fps(), 1))
+ self.processing_time_list.append(self.processing_time)
+ self.drawing_time_list.append(self.draw_time)
+
+ # Now add the coins
+ self.add_coins()
+
+
+def main():
+ """ Main method """
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/perf_test/stress_test_draw_moving_pygame.py b/arcade/examples/perf_test/stress_test_draw_moving_pygame.py
new file mode 100644
index 0000000..7503a1f
--- /dev/null
+++ b/arcade/examples/perf_test/stress_test_draw_moving_pygame.py
@@ -0,0 +1,284 @@
+"""
+Moving Sprite Stress Test
+
+Simple program to test how fast we can draw sprites that are moving
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.stress_test_draw_moving_pygame
+"""
+
+import pygame
+import random
+import os
+import timeit
+import time
+import collections
+
+import matplotlib.pyplot as plt
+
+# Define some colors
+BLACK = (0, 0, 0)
+WHITE = (255, 255, 255)
+RED = (255, 0, 0)
+
+# --- Constants ---
+SPRITE_SCALING_COIN = 0.09
+SPRITE_NATIVE_SIZE = 128
+SPRITE_SIZE = int(SPRITE_NATIVE_SIZE * SPRITE_SCALING_COIN)
+COIN_COUNT_INCREMENT = 100
+
+STOP_COUNT = 6000
+RESULTS_FILE = "stress_test_draw_moving_pygame.csv"
+
+SCREEN_WIDTH = 1800
+SCREEN_HEIGHT = 1000
+SCREEN_TITLE = "Moving Sprite Stress Test"
+
+
+class FPSCounter:
+ def __init__(self):
+ self.time = time.perf_counter()
+ self.frame_times = collections.deque(maxlen=60)
+
+ def tick(self):
+ t1 = time.perf_counter()
+ dt = t1 - self.time
+ self.time = t1
+ self.frame_times.append(dt)
+
+ def get_fps(self):
+ total_time = sum(self.frame_times)
+ if total_time == 0:
+ return 0
+ else:
+ return len(self.frame_times) / sum(self.frame_times)
+
+
+class Coin(pygame.sprite.Sprite):
+ """
+ This class represents the ball
+ It derives from the "Sprite" class in Pygame
+ """
+
+ def __init__(self):
+ """ Constructor. Pass in the color of the block,
+ and its x and y position. """
+ # Call the parent class (Sprite) constructor
+ super().__init__()
+
+ # Create an image of the block, and fill it with a color.
+ # This could also be an image loaded from the disk.
+ image = pygame.image.load("../images/coin_01.png")
+ rect = image.get_rect()
+ image = pygame.transform.scale(
+ image,
+ (int(rect.width * SPRITE_SCALING_COIN), int(rect.height * SPRITE_SCALING_COIN)))
+ self.image = image.convert()
+ self.image.set_colorkey(BLACK)
+
+ # Fetch the rectangle object that has the dimensions of the image
+ # image.
+ # Update the position of this object by setting the values
+ # of rect.x and rect.y
+ self.rect = self.image.get_rect()
+
+ # Instance variables for our current speed and direction
+ self.change_x = 0
+ self.change_y = 0
+
+ def update(self):
+ """ Called each frame. """
+ self.rect.x += self.change_x
+ self.rect.y += self.change_y
+
+
+class MyGame:
+ """ Our custom Window Class"""
+
+ def __init__(self):
+ """ Initializer """
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Variables that will hold sprite lists
+ self.coin_list = None
+
+ self.processing_time = 0
+ self.draw_time = 0
+ self.program_start_time = timeit.default_timer()
+ self.sprite_count_list = []
+ self.fps_list = []
+ self.processing_time_list = []
+ self.drawing_time_list = []
+ self.last_fps_reading = 0
+ self.fps = FPSCounter()
+
+ # Initialize Pygame
+ pygame.init()
+
+ # Set the height and width of the screen
+ self.screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT])
+
+ # This is a list of every sprite. All blocks and the player block as well.
+ self.coin_list = pygame.sprite.Group()
+
+ self.font = pygame.font.SysFont('Calibri', 25, True, False)
+
+ # Open file to save timings
+ self.results_file = open(RESULTS_FILE, "w")
+
+ def add_coins(self):
+
+ # Create the coins
+ for i in range(COIN_COUNT_INCREMENT):
+ # Create the coin instance
+ # Coin image from kenney.nl
+ coin = Coin()
+
+ # Position the coin
+ coin.rect.x = random.randrange(SPRITE_SIZE, SCREEN_WIDTH - SPRITE_SIZE)
+ coin.rect.y = random.randrange(SPRITE_SIZE, SCREEN_HEIGHT - SPRITE_SIZE)
+
+ coin.change_x = random.randrange(-3, 4)
+ coin.change_y = random.randrange(-3, 4)
+
+ # Add the coin to the lists
+ self.coin_list.add(coin)
+
+ def on_draw(self):
+ """ Draw everything """
+
+ # Start timing how long this takes
+ draw_start_time = timeit.default_timer()
+
+ # Clear the screen
+ self.screen.fill((59, 122, 87))
+
+ # Draw all the spites
+ self.coin_list.draw(self.screen)
+
+ # Display timings
+ output = f"Processing time: {self.processing_time:.3f}"
+ text = self.font.render(output, True, BLACK)
+ self.screen.blit(text, [20, SCREEN_HEIGHT - 40])
+
+ output = f"Drawing time: {self.draw_time:.3f}"
+ text = self.font.render(output, True, BLACK)
+ self.screen.blit(text, [20, SCREEN_HEIGHT - 60])
+
+ fps = self.fps.get_fps()
+ output = f"FPS: {fps:3.0f}"
+ text = self.font.render(output, True, BLACK)
+ self.screen.blit(text, [20, SCREEN_HEIGHT - 80])
+
+ pygame.display.flip()
+
+ self.draw_time = timeit.default_timer() - draw_start_time
+ self.fps.tick()
+
+ def update(self, delta_time):
+ # Start update timer
+ start_time = timeit.default_timer()
+
+ self.coin_list.update()
+
+ for sprite in self.coin_list:
+
+ if sprite.rect.x < 0:
+ sprite.change_x *= -1
+ elif sprite.rect.x > SCREEN_WIDTH:
+ sprite.change_x *= -1
+ if sprite.rect.y < 0:
+ sprite.change_y *= -1
+ elif sprite.rect.y > SCREEN_HEIGHT:
+ sprite.change_y *= -1
+
+ # Save the time it took to do this.
+ self.processing_time = timeit.default_timer() - start_time
+
+ # Total time program has been running
+ total_program_time = int(timeit.default_timer() - self.program_start_time)
+
+ # Print out stats, or add more sprites
+ if total_program_time > self.last_fps_reading:
+ self.last_fps_reading = total_program_time
+
+ # It takes the program a while to "warm up", so the first
+ # few seconds our readings will be off. So wait some time
+ # before taking readings
+ if total_program_time > 5:
+
+ # We want the program to run for a while before taking
+ # timing measurements. We don't want the time it takes
+ # to add new sprites to be part of that measurement. So
+ # make sure we have a clear second of nothing but
+ # running the sprites, and not adding the sprites.
+ if total_program_time % 2 == 1:
+
+ # Take timings
+ output = f"{total_program_time}, {len(self.coin_list)}, {self.fps.get_fps():.1f}, {self.processing_time:.4f}, {self.draw_time:.4f}\n"
+ print(output, end="")
+ self.results_file.write(output)
+
+ if len(self.coin_list) >= STOP_COUNT:
+ pygame.event.post(pygame.event.Event(pygame.QUIT, {}))
+ return
+
+ self.sprite_count_list.append(len(self.coin_list))
+ self.fps_list.append(round(self.fps.get_fps(), 1))
+ self.processing_time_list.append(self.processing_time)
+ self.drawing_time_list.append(self.draw_time)
+
+ # Now add the coins
+ self.add_coins()
+
+
+def main():
+ """ Main method """
+ window = MyGame()
+
+ # Loop until the user clicks the close button.
+ done = False
+
+ # Used to manage how fast the screen updates
+ clock = pygame.time.Clock()
+
+ # -------- Main Program Loop -----------
+ while not done:
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT:
+ done = True
+ window.update(0)
+ window.on_draw()
+ clock.tick(60)
+
+ pygame.quit()
+
+ # Plot our results
+ plt.plot(window.sprite_count_list, window.processing_time_list, label="Processing Time")
+ plt.plot(window.sprite_count_list, window.drawing_time_list, label="Drawing Time")
+
+ plt.legend(loc='upper left', shadow=True, fontsize='x-large')
+
+ plt.ylabel('Time')
+ plt.xlabel('Sprite Count')
+
+ plt.show()
+
+ # Plot our results
+ plt.plot(window.sprite_count_list, window.fps_list)
+
+ plt.ylabel('FPS')
+ plt.xlabel('Sprite Count')
+
+ plt.show()
+
+
+main()
diff --git a/arcade/examples/perlin_noise_1.py b/arcade/examples/perlin_noise_1.py
new file mode 100644
index 0000000..57b0cbf
--- /dev/null
+++ b/arcade/examples/perlin_noise_1.py
@@ -0,0 +1,162 @@
+"""
+Perlin Noise 1
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.perlin_noise_1
+
+TODO: This code doesn't work properly, and isn't currently listed in the examples.
+"""
+import arcade
+import numpy as np
+from PIL import Image
+
+# Set how many rows and columns we will have
+ROW_COUNT = 30
+COLUMN_COUNT = 30
+
+# This sets the WIDTH and HEIGHT of each grid location
+WIDTH = 10
+HEIGHT = 10
+
+# This sets the margin between each cell
+# and on the edges of the screen.
+MARGIN = 2
+
+# Do the math to figure out our screen dimensions
+SCREEN_WIDTH = (WIDTH + MARGIN) * COLUMN_COUNT + MARGIN
+SCREEN_HEIGHT = (HEIGHT + MARGIN) * ROW_COUNT + MARGIN
+SCREEN_TITLE = "Perlin Noise 1 Example"
+
+
+# Perlin noise generator from:
+# https://stackoverflow.com/questions/42147776/producing-2d-perlin-noise-with-numpy
+
+def perlin(x, y, seed=0):
+ # permutation table
+ np.random.seed(seed)
+ p = np.arange(256, dtype=int)
+ np.random.shuffle(p)
+ p = np.stack([p, p]).flatten()
+ # coordinates of the top-left
+ xi = x.astype(int)
+ yi = y.astype(int)
+ # internal coordinates
+ xf = x - xi
+ yf = y - yi
+ # fade factors
+ u = fade(xf)
+ v = fade(yf)
+ # noise components
+ n00 = gradient(p[p[xi] + yi], xf, yf)
+ n01 = gradient(p[p[xi] + yi + 1], xf, yf - 1)
+ n11 = gradient(p[p[xi + 1] + yi + 1], xf - 1, yf - 1)
+ n10 = gradient(p[p[xi + 1] + yi], xf - 1, yf)
+ # combine noises
+ x1 = lerp(n00, n10, u)
+ x2 = lerp(n01, n11, u) # FIX1: I was using n10 instead of n01
+ return lerp(x1, x2, v) # FIX2: I also had to reverse x1 and x2 here
+
+
+def lerp(a, b, x):
+ """linear interpolation"""
+ return a + x * (b - a)
+
+
+def fade(t):
+ """6t^5 - 15t^4 + 10t^3"""
+ return 6 * t ** 5 - 15 * t ** 4 + 10 * t ** 3
+
+
+def gradient(h, x, y):
+ """grad converts h to the right gradient vector and return the dot product with (x,y)"""
+ vectors = np.array([[0, 1], [0, -1], [1, 0], [-1, 0]])
+ g = vectors[h % 4]
+ return g[:, :, 0] * x + g[:, :, 1] * y
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ """
+ Set up the application.
+ """
+ super().__init__(width, height, title)
+
+ self.shape_list = None
+
+ arcade.set_background_color(arcade.color.BLACK)
+
+ self.grid = None
+ self.recreate_grid()
+
+ def recreate_grid(self):
+ lin = np.linspace(0, 5, ROW_COUNT, endpoint=False)
+ y, x = np.meshgrid(lin, lin)
+ self.grid = (perlin(x, y, seed=0))
+
+ self.grid *= 255
+ self.grid += 128
+
+ # for row in range(ROW_COUNT):
+ # for column in range(COLUMN_COUNT):
+ # print(f"{self.grid[row][column]:5.2f} ", end="")
+ # print()
+
+ self.shape_list = arcade.ShapeElementList()
+ for row in range(ROW_COUNT):
+ for column in range(COLUMN_COUNT):
+ color = self.grid[row][column], 0, 0
+
+ x = (MARGIN + WIDTH) * column + MARGIN + WIDTH // 2
+ y = (MARGIN + HEIGHT) * row + MARGIN + HEIGHT // 2
+
+ current_rect = arcade.create_rectangle_filled(x, y, WIDTH, HEIGHT, color)
+ self.shape_list.append(current_rect)
+
+ im = Image.fromarray(np.uint8(self.grid), "L")
+ im.save("test.png")
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ self.shape_list.draw()
+
+ def on_mouse_press(self, x, y, button, modifiers):
+ """
+ Called when the user presses a mouse button.
+ """
+
+ # Change the x/y screen coordinates to grid coordinates
+ column = x // (WIDTH + MARGIN)
+ row = y // (HEIGHT + MARGIN)
+
+ print(f"Click coordinates: ({x}, {y}). Grid coordinates: ({row}, {column})")
+
+ # Make sure we are on-grid. It is possible to click in the upper right
+ # corner in the margin and go to a grid location that doesn't exist
+ if row < ROW_COUNT and column < COLUMN_COUNT:
+
+ # Flip the location between 1 and 0.
+ if self.grid[row][column] == 0:
+ self.grid[row][column] = 1
+ else:
+ self.grid[row][column] = 0
+
+ self.recreate_grid()
+
+
+def main():
+ MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/perlin_noise_2.py b/arcade/examples/perlin_noise_2.py
new file mode 100644
index 0000000..7f9d6aa
--- /dev/null
+++ b/arcade/examples/perlin_noise_2.py
@@ -0,0 +1,154 @@
+"""
+Perlin Noise 2
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.perlin_noise_2
+
+TODO: This code doesn't work properly, and isn't currently listed in the examples.
+"""
+import arcade
+import numpy as np
+from PIL import Image
+
+# Set how many rows and columns we will have
+ROW_COUNT = 30
+COLUMN_COUNT = 30
+
+# This sets the WIDTH and HEIGHT of each grid location
+WIDTH = 10
+HEIGHT = 10
+
+# This sets the margin between each cell
+# and on the edges of the screen.
+MARGIN = 2
+
+# Do the math to figure out our screen dimensions
+SCREEN_WIDTH = (WIDTH + MARGIN) * COLUMN_COUNT + MARGIN
+SCREEN_HEIGHT = (HEIGHT + MARGIN) * ROW_COUNT + MARGIN
+SCREEN_TITLE = "Perlin Noise 2 Example"
+
+
+# Perlin noise generator from:
+# https://stackoverflow.com/questions/42147776/producing-2d-perlin-noise-with-numpy
+
+def perlin(x, y, seed=0):
+ # permutation table
+ np.random.seed(seed)
+ p = np.arange(256, dtype=int)
+ np.random.shuffle(p)
+ p = np.stack([p, p]).flatten()
+ # coordinates of the top-left
+ xi = x.astype(int)
+ yi = y.astype(int)
+ # internal coordinates
+ xf = x - xi
+ yf = y - yi
+ # fade factors
+ u = fade(xf)
+ v = fade(yf)
+ # noise components
+ n00 = gradient(p[p[xi] + yi], xf, yf)
+ n01 = gradient(p[p[xi] + yi + 1], xf, yf - 1)
+ n11 = gradient(p[p[xi + 1] + yi + 1], xf - 1, yf - 1)
+ n10 = gradient(p[p[xi + 1] + yi], xf - 1, yf)
+ # combine noises
+ x1 = lerp(n00, n10, u)
+ x2 = lerp(n01, n11, u) # FIX1: I was using n10 instead of n01
+ return lerp(x1, x2, v) # FIX2: I also had to reverse x1 and x2 here
+
+
+def lerp(a, b, x):
+ """linear interpolation"""
+ return a + x * (b - a)
+
+
+def fade(t):
+ """6t^5 - 15t^4 + 10t^3"""
+ return 6 * t ** 5 - 15 * t ** 4 + 10 * t ** 3
+
+
+def gradient(h, x, y):
+ """grad converts h to the right gradient vector and return the dot product with (x,y)"""
+ vectors = np.array([[0, 1], [0, -1], [1, 0], [-1, 0]])
+ g = vectors[h % 4]
+ return g[:, :, 0] * x + g[:, :, 1] * y
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ """
+ Set up the application.
+ """
+ super().__init__(width, height, title)
+ self.background_list = None
+ arcade.set_background_color(arcade.color.BLACK)
+
+ self.grid = None
+ self.recreate_grid()
+
+ def recreate_grid(self):
+ lin = np.linspace(0, 5, ROW_COUNT, endpoint=False)
+ y, x = np.meshgrid(lin, lin)
+ self.grid = (perlin(x, y, seed=0))
+ self.grid *= 255
+ self.grid += 128
+
+ # for row in range(ROW_COUNT):
+ # for column in range(COLUMN_COUNT):
+ # print(f"{self.grid[row][column]:7.1f} ", end="")
+ # print()
+
+ im = Image.fromarray(np.uint8(self.grid), "L")
+ background_sprite = arcade.Sprite()
+ background_sprite.center_x = SCREEN_WIDTH / 2
+ background_sprite.center_y = SCREEN_HEIGHT / 2
+ background_sprite.append_texture(arcade.Texture("dynamic noise image", im))
+ background_sprite.set_texture(0)
+
+ self.background_list = arcade.SpriteList()
+ self.background_list.append(background_sprite)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+ self.background_list.draw()
+
+ def on_mouse_press(self, x, y, button, modifiers):
+ """
+ Called when the user presses a mouse button.
+ """
+
+ # Change the x/y screen coordinates to grid coordinates
+ column = x // (WIDTH + MARGIN)
+ row = y // (HEIGHT + MARGIN)
+
+ print(f"Click coordinates: ({x}, {y}). Grid coordinates: ({row}, {column})")
+
+ # Make sure we are on-grid. It is possible to click in the upper right
+ # corner in the margin and go to a grid location that doesn't exist
+ if row < ROW_COUNT and column < COLUMN_COUNT:
+
+ # Flip the location between 1 and 0.
+ if self.grid[row][column] == 0:
+ self.grid[row][column] = 1
+ else:
+ self.grid[row][column] = 0
+
+ self.recreate_grid()
+
+
+def main():
+ MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/platform_tutorial/01_open_window.py b/arcade/examples/platform_tutorial/01_open_window.py
new file mode 100644
index 0000000..70359a7
--- /dev/null
+++ b/arcade/examples/platform_tutorial/01_open_window.py
@@ -0,0 +1,43 @@
+"""
+Platformer Game
+"""
+import arcade
+
+# Constants
+SCREEN_WIDTH = 1000
+SCREEN_HEIGHT = 650
+SCREEN_TITLE = "Platformer"
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self):
+
+ # Call the parent class and set up the window
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ arcade.set_background_color(arcade.csscolor.CORNFLOWER_BLUE)
+
+ def setup(self):
+ """ Set up the game here. Call this function to restart the game. """
+ pass
+
+ def on_draw(self):
+ """ Render the screen. """
+
+ arcade.start_render()
+ # Code to draw the screen goes here
+
+
+def main():
+ """ Main method """
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/platform_tutorial/02_draw_sprites.py b/arcade/examples/platform_tutorial/02_draw_sprites.py
new file mode 100644
index 0000000..05965be
--- /dev/null
+++ b/arcade/examples/platform_tutorial/02_draw_sprites.py
@@ -0,0 +1,91 @@
+"""
+Platformer Game
+"""
+import arcade
+
+# Constants
+SCREEN_WIDTH = 1000
+SCREEN_HEIGHT = 650
+SCREEN_TITLE = "Platformer"
+
+# Constants used to scale our sprites from their original size
+CHARACTER_SCALING = 1
+TILE_SCALING = 0.5
+COIN_SCALING = 0.5
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self):
+
+ # Call the parent class and set up the window
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # These are 'lists' that keep track of our sprites. Each sprite should
+ # go into a list.
+ self.coin_list = None
+ self.wall_list = None
+ self.player_list = None
+
+ # Separate variable that holds the player sprite
+ self.player_sprite = None
+
+ arcade.set_background_color(arcade.csscolor.CORNFLOWER_BLUE)
+
+ def setup(self):
+ """ Set up the game here. Call this function to restart the game. """
+ # Create the Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.wall_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Set up the player, specifically placing it at these coordinates.
+ self.player_sprite = arcade.Sprite("images/player_1/player_stand.png", CHARACTER_SCALING)
+ self.player_sprite.center_x = 64
+ self.player_sprite.center_y = 120
+ self.player_list.append(self.player_sprite)
+
+ # Create the ground
+ # This shows using a loop to place multiple sprites horizontally
+ for x in range(0, 1250, 64):
+ wall = arcade.Sprite("images/tiles/grassMid.png", TILE_SCALING)
+ wall.center_x = x
+ wall.center_y = 32
+ self.wall_list.append(wall)
+
+ # Put some crates on the ground
+ # This shows using a coordinate list to place sprites
+ coordinate_list = [[512, 96],
+ [256, 96],
+ [768, 96]]
+
+ for coordinate in coordinate_list:
+ # Add a crate on the ground
+ wall = arcade.Sprite("images/tiles/boxCrate_double.png", TILE_SCALING)
+ wall.position = coordinate
+ self.wall_list.append(wall)
+
+ def on_draw(self):
+ """ Render the screen. """
+
+ # Clear the screen to the background color
+ arcade.start_render()
+
+ # Draw our sprites
+ self.wall_list.draw()
+ self.coin_list.draw()
+ self.player_list.draw()
+
+
+def main():
+ """ Main method """
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/platform_tutorial/03_user_control.py b/arcade/examples/platform_tutorial/03_user_control.py
new file mode 100644
index 0000000..cdd9d4b
--- /dev/null
+++ b/arcade/examples/platform_tutorial/03_user_control.py
@@ -0,0 +1,131 @@
+"""
+Platformer Game
+"""
+import arcade
+
+# Constants
+SCREEN_WIDTH = 1000
+SCREEN_HEIGHT = 650
+SCREEN_TITLE = "Platformer"
+
+# Constants used to scale our sprites from their original size
+CHARACTER_SCALING = 1
+TILE_SCALING = 0.5
+COIN_SCALING = 0.5
+
+# Movement speed of player, in pixels per frame
+PLAYER_MOVEMENT_SPEED = 5
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self):
+
+ # Call the parent class and set up the window
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # These are 'lists' that keep track of our sprites. Each sprite should
+ # go into a list.
+ self.coin_list = None
+ self.wall_list = None
+ self.player_list = None
+
+ # Separate variable that holds the player sprite
+ self.player_sprite = None
+
+ # Our physics engine
+ self.physics_engine = None
+
+ arcade.set_background_color(arcade.csscolor.CORNFLOWER_BLUE)
+
+ def setup(self):
+ """ Set up the game here. Call this function to restart the game. """
+ # Create the Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.wall_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Set up the player, specifically placing it at these coordinates.
+ self.player_sprite = arcade.Sprite("images/player_1/player_stand.png", CHARACTER_SCALING)
+ self.player_sprite.center_x = 64
+ self.player_sprite.center_y = 120
+ self.player_list.append(self.player_sprite)
+
+ # Create the ground
+ # This shows using a loop to place multiple sprites horizontally
+ for x in range(0, 1250, 64):
+ wall = arcade.Sprite("images/tiles/grassMid.png", TILE_SCALING)
+ wall.center_x = x
+ wall.center_y = 32
+ self.wall_list.append(wall)
+
+ # Put some crates on the ground
+ # This shows using a coordinate list to place sprites
+ coordinate_list = [[512, 96],
+ [256, 96],
+ [768, 96]]
+
+ for coordinate in coordinate_list:
+ # Add a crate on the ground
+ wall = arcade.Sprite("images/tiles/boxCrate_double.png", TILE_SCALING)
+ wall.position = coordinate
+ self.wall_list.append(wall)
+
+ # Create the 'physics engine'
+ self.physics_engine = arcade.PhysicsEngineSimple(self.player_sprite, self.wall_list)
+
+ def on_draw(self):
+ """ Render the screen. """
+
+ # Clear the screen to the background color
+ arcade.start_render()
+
+ # Draw our sprites
+ self.wall_list.draw()
+ self.coin_list.draw()
+ self.player_list.draw()
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ if key == arcade.key.UP or key == arcade.key.W:
+ self.player_sprite.change_y = PLAYER_MOVEMENT_SPEED
+ elif key == arcade.key.DOWN or key == arcade.key.S:
+ self.player_sprite.change_y = -PLAYER_MOVEMENT_SPEED
+ elif key == arcade.key.LEFT or key == arcade.key.A:
+ self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT or key == arcade.key.D:
+ self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.UP or key == arcade.key.W:
+ self.player_sprite.change_y = 0
+ elif key == arcade.key.DOWN or key == arcade.key.S:
+ self.player_sprite.change_y = 0
+ elif key == arcade.key.LEFT or key == arcade.key.A:
+ self.player_sprite.change_x = 0
+ elif key == arcade.key.RIGHT or key == arcade.key.D:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.physics_engine.update()
+
+
+def main():
+ """ Main method """
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/platform_tutorial/04_add_gravity.py b/arcade/examples/platform_tutorial/04_add_gravity.py
new file mode 100644
index 0000000..0a00b9f
--- /dev/null
+++ b/arcade/examples/platform_tutorial/04_add_gravity.py
@@ -0,0 +1,130 @@
+"""
+Platformer Game
+"""
+import arcade
+
+# Constants
+SCREEN_WIDTH = 1000
+SCREEN_HEIGHT = 650
+SCREEN_TITLE = "Platformer"
+
+# Constants used to scale our sprites from their original size
+CHARACTER_SCALING = 1
+TILE_SCALING = 0.5
+COIN_SCALING = 0.5
+
+# Movement speed of player, in pixels per frame
+PLAYER_MOVEMENT_SPEED = 5
+GRAVITY = 1
+PLAYER_JUMP_SPEED = 15
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self):
+
+ # Call the parent class and set up the window
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # These are 'lists' that keep track of our sprites. Each sprite should
+ # go into a list.
+ self.coin_list = None
+ self.wall_list = None
+ self.player_list = None
+
+ # Separate variable that holds the player sprite
+ self.player_sprite = None
+
+ # Our physics engine
+ self.physics_engine = None
+
+ arcade.set_background_color(arcade.csscolor.CORNFLOWER_BLUE)
+
+ def setup(self):
+ """ Set up the game here. Call this function to restart the game. """
+ # Create the Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.wall_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Set up the player, specifically placing it at these coordinates.
+ self.player_sprite = arcade.Sprite("images/player_1/player_stand.png", CHARACTER_SCALING)
+ self.player_sprite.center_x = 64
+ self.player_sprite.center_y = 96
+ self.player_list.append(self.player_sprite)
+
+ # Create the ground
+ # This shows using a loop to place multiple sprites horizontally
+ for x in range(0, 1250, 64):
+ wall = arcade.Sprite("images/tiles/grassMid.png", TILE_SCALING)
+ wall.center_x = x
+ wall.center_y = 32
+ self.wall_list.append(wall)
+
+ # Put some crates on the ground
+ # This shows using a coordinate list to place sprites
+ coordinate_list = [[512, 96],
+ [256, 96],
+ [768, 96]]
+
+ for coordinate in coordinate_list:
+ # Add a crate on the ground
+ wall = arcade.Sprite("images/tiles/boxCrate_double.png", TILE_SCALING)
+ wall.position = coordinate
+ self.wall_list.append(wall)
+
+ # Create the 'physics engine'
+ self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
+ self.wall_list,
+ GRAVITY)
+
+ def on_draw(self):
+ """ Render the screen. """
+
+ # Clear the screen to the background color
+ arcade.start_render()
+
+ # Draw our sprites
+ self.wall_list.draw()
+ self.coin_list.draw()
+ self.player_list.draw()
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ if key == arcade.key.UP or key == arcade.key.W:
+ if self.physics_engine.can_jump():
+ self.player_sprite.change_y = PLAYER_JUMP_SPEED
+ elif key == arcade.key.LEFT or key == arcade.key.A:
+ self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT or key == arcade.key.D:
+ self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.LEFT or key == arcade.key.A:
+ self.player_sprite.change_x = 0
+ elif key == arcade.key.RIGHT or key == arcade.key.D:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.physics_engine.update()
+
+
+def main():
+ """ Main method """
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/platform_tutorial/05_scrolling.py b/arcade/examples/platform_tutorial/05_scrolling.py
new file mode 100644
index 0000000..3da5e85
--- /dev/null
+++ b/arcade/examples/platform_tutorial/05_scrolling.py
@@ -0,0 +1,188 @@
+"""
+Platformer Game
+"""
+import arcade
+
+# Constants
+SCREEN_WIDTH = 1000
+SCREEN_HEIGHT = 650
+SCREEN_TITLE = "Platformer"
+
+# Constants used to scale our sprites from their original size
+CHARACTER_SCALING = 1
+TILE_SCALING = 0.5
+COIN_SCALING = 0.5
+
+# Movement speed of player, in pixels per frame
+PLAYER_MOVEMENT_SPEED = 5
+GRAVITY = 1
+PLAYER_JUMP_SPEED = 15
+
+# How many pixels to keep as a minimum margin between the character
+# and the edge of the screen.
+LEFT_VIEWPORT_MARGIN = 150
+RIGHT_VIEWPORT_MARGIN = 150
+BOTTOM_VIEWPORT_MARGIN = 50
+TOP_VIEWPORT_MARGIN = 100
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self):
+
+ # Call the parent class and set up the window
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # These are 'lists' that keep track of our sprites. Each sprite should
+ # go into a list.
+ self.coin_list = None
+ self.wall_list = None
+ self.player_list = None
+
+ # Separate variable that holds the player sprite
+ self.player_sprite = None
+
+ # Our physics engine
+ self.physics_engine = None
+
+ # Used to keep track of our scrolling
+ self.view_bottom = 0
+ self.view_left = 0
+
+ arcade.set_background_color(arcade.csscolor.CORNFLOWER_BLUE)
+
+ def setup(self):
+ """ Set up the game here. Call this function to restart the game. """
+
+ # Used to keep track of our scrolling
+ self.view_bottom = 0
+ self.view_left = 0
+
+ # Create the Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.wall_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Set up the player, specifically placing it at these coordinates.
+ self.player_sprite = arcade.Sprite("images/player_1/player_stand.png", CHARACTER_SCALING)
+ self.player_sprite.center_x = 64
+ self.player_sprite.center_y = 96
+ self.player_list.append(self.player_sprite)
+
+ # Create the ground
+ # This shows using a loop to place multiple sprites horizontally
+ for x in range(0, 1250, 64):
+ wall = arcade.Sprite("images/tiles/grassMid.png", TILE_SCALING)
+ wall.center_x = x
+ wall.center_y = 32
+ self.wall_list.append(wall)
+
+ # Put some crates on the ground
+ # This shows using a coordinate list to place sprites
+ coordinate_list = [[512, 96],
+ [256, 96],
+ [768, 96]]
+
+ for coordinate in coordinate_list:
+ # Add a crate on the ground
+ wall = arcade.Sprite("images/tiles/boxCrate_double.png", TILE_SCALING)
+ wall.position = coordinate
+ self.wall_list.append(wall)
+
+ # Create the 'physics engine'
+ self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
+ self.wall_list,
+ GRAVITY)
+
+ def on_draw(self):
+ """ Render the screen. """
+
+ # Clear the screen to the background color
+ arcade.start_render()
+
+ # Draw our sprites
+ self.wall_list.draw()
+ self.coin_list.draw()
+ self.player_list.draw()
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ if key == arcade.key.UP or key == arcade.key.W:
+ if self.physics_engine.can_jump():
+ self.player_sprite.change_y = PLAYER_JUMP_SPEED
+ elif key == arcade.key.LEFT or key == arcade.key.A:
+ self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT or key == arcade.key.D:
+ self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.LEFT or key == arcade.key.A:
+ self.player_sprite.change_x = 0
+ elif key == arcade.key.RIGHT or key == arcade.key.D:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.physics_engine.update()
+
+ # --- Manage Scrolling ---
+
+ # Track if we need to change the viewport
+
+ changed = False
+
+ # Scroll left
+ left_boundary = self.view_left + LEFT_VIEWPORT_MARGIN
+ if self.player_sprite.left < left_boundary:
+ self.view_left -= left_boundary - self.player_sprite.left
+ changed = True
+
+ # Scroll right
+ right_boundary = self.view_left + SCREEN_WIDTH - RIGHT_VIEWPORT_MARGIN
+ if self.player_sprite.right > right_boundary:
+ self.view_left += self.player_sprite.right - right_boundary
+ changed = True
+
+ # Scroll up
+ top_boundary = self.view_bottom + SCREEN_HEIGHT - TOP_VIEWPORT_MARGIN
+ if self.player_sprite.top > top_boundary:
+ self.view_bottom += self.player_sprite.top - top_boundary
+ changed = True
+
+ # Scroll down
+ bottom_boundary = self.view_bottom + BOTTOM_VIEWPORT_MARGIN
+ if self.player_sprite.bottom < bottom_boundary:
+ self.view_bottom -= bottom_boundary - self.player_sprite.bottom
+ changed = True
+
+ if changed:
+ # Only scroll to integers. Otherwise we end up with pixels that
+ # don't line up on the screen
+ self.view_bottom = int(self.view_bottom)
+ self.view_left = int(self.view_left)
+
+ # Do the scrolling
+ arcade.set_viewport(self.view_left,
+ SCREEN_WIDTH + self.view_left,
+ self.view_bottom,
+ SCREEN_HEIGHT + self.view_bottom)
+
+
+def main():
+ """ Main method """
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/platform_tutorial/06_coins_and_sound.py b/arcade/examples/platform_tutorial/06_coins_and_sound.py
new file mode 100644
index 0000000..7421065
--- /dev/null
+++ b/arcade/examples/platform_tutorial/06_coins_and_sound.py
@@ -0,0 +1,212 @@
+"""
+Platformer Game
+"""
+import arcade
+
+# Constants
+SCREEN_WIDTH = 1000
+SCREEN_HEIGHT = 650
+SCREEN_TITLE = "Platformer"
+
+# Constants used to scale our sprites from their original size
+CHARACTER_SCALING = 1
+TILE_SCALING = 0.5
+COIN_SCALING = 0.5
+
+# Movement speed of player, in pixels per frame
+PLAYER_MOVEMENT_SPEED = 5
+GRAVITY = 1
+PLAYER_JUMP_SPEED = 15
+
+# How many pixels to keep as a minimum margin between the character
+# and the edge of the screen.
+LEFT_VIEWPORT_MARGIN = 150
+RIGHT_VIEWPORT_MARGIN = 150
+BOTTOM_VIEWPORT_MARGIN = 50
+TOP_VIEWPORT_MARGIN = 100
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self):
+
+ # Call the parent class and set up the window
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # These are 'lists' that keep track of our sprites. Each sprite should
+ # go into a list.
+ self.coin_list = None
+ self.wall_list = None
+ self.player_list = None
+
+ # Separate variable that holds the player sprite
+ self.player_sprite = None
+
+ # Our physics engine
+ self.physics_engine = None
+
+ # Used to keep track of our scrolling
+ self.view_bottom = 0
+ self.view_left = 0
+
+ # Load sounds
+ self.collect_coin_sound = arcade.load_sound("sounds/coin1.wav")
+ self.jump_sound = arcade.load_sound("sounds/jump1.wav")
+
+ arcade.set_background_color(arcade.csscolor.CORNFLOWER_BLUE)
+
+ def setup(self):
+ """ Set up the game here. Call this function to restart the game. """
+
+ # Used to keep track of our scrolling
+ self.view_bottom = 0
+ self.view_left = 0
+
+ # Create the Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.wall_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Set up the player, specifically placing it at these coordinates.
+ self.player_sprite = arcade.Sprite("images/player_1/player_stand.png", CHARACTER_SCALING)
+ self.player_sprite.center_x = 64
+ self.player_sprite.center_y = 96
+ self.player_list.append(self.player_sprite)
+
+ # Create the ground
+ # This shows using a loop to place multiple sprites horizontally
+ for x in range(0, 1250, 64):
+ wall = arcade.Sprite("images/tiles/grassMid.png", TILE_SCALING)
+ wall.center_x = x
+ wall.center_y = 32
+ self.wall_list.append(wall)
+
+ # Put some crates on the ground
+ # This shows using a coordinate list to place sprites
+ coordinate_list = [[512, 96],
+ [256, 96],
+ [768, 96]]
+
+ for coordinate in coordinate_list:
+ # Add a crate on the ground
+ wall = arcade.Sprite("images/tiles/boxCrate_double.png", TILE_SCALING)
+ wall.position = coordinate
+ self.wall_list.append(wall)
+
+ # Use a loop to place some coins for our character to pick up
+ for x in range(128, 1250, 256):
+ coin = arcade.Sprite("images/items/coinGold.png", COIN_SCALING)
+ coin.center_x = x
+ coin.center_y = 96
+ self.coin_list.append(coin)
+
+ # Create the 'physics engine'
+ self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
+ self.wall_list,
+ GRAVITY)
+
+ def on_draw(self):
+ """ Render the screen. """
+
+ # Clear the screen to the background color
+ arcade.start_render()
+
+ # Draw our sprites
+ self.wall_list.draw()
+ self.coin_list.draw()
+ self.player_list.draw()
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ if key == arcade.key.UP or key == arcade.key.W:
+ if self.physics_engine.can_jump():
+ self.player_sprite.change_y = PLAYER_JUMP_SPEED
+ arcade.play_sound(self.jump_sound)
+ elif key == arcade.key.LEFT or key == arcade.key.A:
+ self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT or key == arcade.key.D:
+ self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.LEFT or key == arcade.key.A:
+ self.player_sprite.change_x = 0
+ elif key == arcade.key.RIGHT or key == arcade.key.D:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.physics_engine.update()
+
+ # See if we hit any coins
+ coin_hit_list = arcade.check_for_collision_with_list(self.player_sprite,
+ self.coin_list)
+
+ # Loop through each coin we hit (if any) and remove it
+ for coin in coin_hit_list:
+ # Remove the coin
+ coin.remove_from_sprite_lists()
+ # Play a sound
+ arcade.play_sound(self.collect_coin_sound)
+ # Add one to the score
+
+ # --- Manage Scrolling ---
+
+ # Track if we need to change the viewport
+
+ changed = False
+
+ # Scroll left
+ left_boundary = self.view_left + LEFT_VIEWPORT_MARGIN
+ if self.player_sprite.left < left_boundary:
+ self.view_left -= left_boundary - self.player_sprite.left
+ changed = True
+
+ # Scroll right
+ right_boundary = self.view_left + SCREEN_WIDTH - RIGHT_VIEWPORT_MARGIN
+ if self.player_sprite.right > right_boundary:
+ self.view_left += self.player_sprite.right - right_boundary
+ changed = True
+
+ # Scroll up
+ top_boundary = self.view_bottom + SCREEN_HEIGHT - TOP_VIEWPORT_MARGIN
+ if self.player_sprite.top > top_boundary:
+ self.view_bottom += self.player_sprite.top - top_boundary
+ changed = True
+
+ # Scroll down
+ bottom_boundary = self.view_bottom + BOTTOM_VIEWPORT_MARGIN
+ if self.player_sprite.bottom < bottom_boundary:
+ self.view_bottom -= bottom_boundary - self.player_sprite.bottom
+ changed = True
+
+ if changed:
+ # Only scroll to integers. Otherwise we end up with pixels that
+ # don't line up on the screen
+ self.view_bottom = int(self.view_bottom)
+ self.view_left = int(self.view_left)
+
+ # Do the scrolling
+ arcade.set_viewport(self.view_left,
+ SCREEN_WIDTH + self.view_left,
+ self.view_bottom,
+ SCREEN_HEIGHT + self.view_bottom)
+
+
+def main():
+ """ Main method """
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/platform_tutorial/07_score.py b/arcade/examples/platform_tutorial/07_score.py
new file mode 100644
index 0000000..b36f49f
--- /dev/null
+++ b/arcade/examples/platform_tutorial/07_score.py
@@ -0,0 +1,224 @@
+"""
+Platformer Game
+"""
+import arcade
+
+# Constants
+SCREEN_WIDTH = 1000
+SCREEN_HEIGHT = 650
+SCREEN_TITLE = "Platformer"
+
+# Constants used to scale our sprites from their original size
+CHARACTER_SCALING = 1
+TILE_SCALING = 0.5
+COIN_SCALING = 0.5
+
+# Movement speed of player, in pixels per frame
+PLAYER_MOVEMENT_SPEED = 5
+GRAVITY = 1
+PLAYER_JUMP_SPEED = 15
+
+# How many pixels to keep as a minimum margin between the character
+# and the edge of the screen.
+LEFT_VIEWPORT_MARGIN = 150
+RIGHT_VIEWPORT_MARGIN = 150
+BOTTOM_VIEWPORT_MARGIN = 50
+TOP_VIEWPORT_MARGIN = 100
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self):
+
+ # Call the parent class and set up the window
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # These are 'lists' that keep track of our sprites. Each sprite should
+ # go into a list.
+ self.coin_list = None
+ self.wall_list = None
+ self.player_list = None
+
+ # Separate variable that holds the player sprite
+ self.player_sprite = None
+
+ # Our physics engine
+ self.physics_engine = None
+
+ # Used to keep track of our scrolling
+ self.view_bottom = 0
+ self.view_left = 0
+
+ # Keep track of the score
+ self.score = 0
+
+ # Load sounds
+ self.collect_coin_sound = arcade.load_sound("sounds/coin1.wav")
+ self.jump_sound = arcade.load_sound("sounds/jump1.wav")
+
+ arcade.set_background_color(arcade.csscolor.CORNFLOWER_BLUE)
+
+ def setup(self):
+ """ Set up the game here. Call this function to restart the game. """
+
+ # Used to keep track of our scrolling
+ self.view_bottom = 0
+ self.view_left = 0
+
+ # Keep track of the score
+ self.score = 0
+
+ # Create the Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.wall_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Set up the player, specifically placing it at these coordinates.
+ self.player_sprite = arcade.Sprite("images/player_1/player_stand.png", CHARACTER_SCALING)
+ self.player_sprite.center_x = 64
+ self.player_sprite.center_y = 96
+ self.player_list.append(self.player_sprite)
+
+ # Create the ground
+ # This shows using a loop to place multiple sprites horizontally
+ for x in range(0, 1250, 64):
+ wall = arcade.Sprite("images/tiles/grassMid.png", TILE_SCALING)
+ wall.center_x = x
+ wall.center_y = 32
+ self.wall_list.append(wall)
+
+ # Put some crates on the ground
+ # This shows using a coordinate list to place sprites
+ coordinate_list = [[512, 96],
+ [256, 96],
+ [768, 96]]
+
+ for coordinate in coordinate_list:
+ # Add a crate on the ground
+ wall = arcade.Sprite("images/tiles/boxCrate_double.png", TILE_SCALING)
+ wall.position = coordinate
+ self.wall_list.append(wall)
+
+ # Use a loop to place some coins for our character to pick up
+ for x in range(128, 1250, 256):
+ coin = arcade.Sprite("images/items/coinGold.png", COIN_SCALING)
+ coin.center_x = x
+ coin.center_y = 96
+ self.coin_list.append(coin)
+
+ # Create the 'physics engine'
+ self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
+ self.wall_list,
+ GRAVITY)
+
+ def on_draw(self):
+ """ Render the screen. """
+
+ # Clear the screen to the background color
+ arcade.start_render()
+
+ # Draw our sprites
+ self.wall_list.draw()
+ self.coin_list.draw()
+ self.player_list.draw()
+
+ # Draw our score on the screen, scrolling it with the viewport
+ score_text = f"Score: {self.score}"
+ arcade.draw_text(score_text, 10 + self.view_left, 10 + self.view_bottom,
+ arcade.csscolor.WHITE, 18)
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ if key == arcade.key.UP or key == arcade.key.W:
+ if self.physics_engine.can_jump():
+ self.player_sprite.change_y = PLAYER_JUMP_SPEED
+ arcade.play_sound(self.jump_sound)
+ elif key == arcade.key.LEFT or key == arcade.key.A:
+ self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT or key == arcade.key.D:
+ self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.LEFT or key == arcade.key.A:
+ self.player_sprite.change_x = 0
+ elif key == arcade.key.RIGHT or key == arcade.key.D:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.physics_engine.update()
+
+ # See if we hit any coins
+ coin_hit_list = arcade.check_for_collision_with_list(self.player_sprite,
+ self.coin_list)
+
+ # Loop through each coin we hit (if any) and remove it
+ for coin in coin_hit_list:
+ # Remove the coin
+ coin.remove_from_sprite_lists()
+ # Play a sound
+ arcade.play_sound(self.collect_coin_sound)
+ # Add one to the score
+ self.score += 1
+
+ # --- Manage Scrolling ---
+
+ # Track if we need to change the viewport
+
+ changed = False
+
+ # Scroll left
+ left_boundary = self.view_left + LEFT_VIEWPORT_MARGIN
+ if self.player_sprite.left < left_boundary:
+ self.view_left -= left_boundary - self.player_sprite.left
+ changed = True
+
+ # Scroll right
+ right_boundary = self.view_left + SCREEN_WIDTH - RIGHT_VIEWPORT_MARGIN
+ if self.player_sprite.right > right_boundary:
+ self.view_left += self.player_sprite.right - right_boundary
+ changed = True
+
+ # Scroll up
+ top_boundary = self.view_bottom + SCREEN_HEIGHT - TOP_VIEWPORT_MARGIN
+ if self.player_sprite.top > top_boundary:
+ self.view_bottom += self.player_sprite.top - top_boundary
+ changed = True
+
+ # Scroll down
+ bottom_boundary = self.view_bottom + BOTTOM_VIEWPORT_MARGIN
+ if self.player_sprite.bottom < bottom_boundary:
+ self.view_bottom -= bottom_boundary - self.player_sprite.bottom
+ changed = True
+
+ if changed:
+ # Only scroll to integers. Otherwise we end up with pixels that
+ # don't line up on the screen
+ self.view_bottom = int(self.view_bottom)
+ self.view_left = int(self.view_left)
+
+ # Do the scrolling
+ arcade.set_viewport(self.view_left,
+ SCREEN_WIDTH + self.view_left,
+ self.view_bottom,
+ SCREEN_HEIGHT + self.view_bottom)
+
+
+def main():
+ """ Main method """
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/platform_tutorial/08_load_map.py b/arcade/examples/platform_tutorial/08_load_map.py
new file mode 100644
index 0000000..c9c0513
--- /dev/null
+++ b/arcade/examples/platform_tutorial/08_load_map.py
@@ -0,0 +1,222 @@
+"""
+Platformer Game
+"""
+import arcade
+
+# Constants
+SCREEN_WIDTH = 1000
+SCREEN_HEIGHT = 650
+SCREEN_TITLE = "Platformer"
+
+# Constants used to scale our sprites from their original size
+CHARACTER_SCALING = 1
+TILE_SCALING = 0.5
+COIN_SCALING = 0.5
+SPRITE_PIXEL_SIZE = 128
+GRID_PIXEL_SIZE = (SPRITE_PIXEL_SIZE * TILE_SCALING)
+
+# Movement speed of player, in pixels per frame
+PLAYER_MOVEMENT_SPEED = 10
+GRAVITY = 1
+PLAYER_JUMP_SPEED = 15
+
+# How many pixels to keep as a minimum margin between the character
+# and the edge of the screen.
+LEFT_VIEWPORT_MARGIN = 150
+RIGHT_VIEWPORT_MARGIN = 150
+BOTTOM_VIEWPORT_MARGIN = 100
+TOP_VIEWPORT_MARGIN = 100
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self):
+
+ # Call the parent class and set up the window
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # These are 'lists' that keep track of our sprites. Each sprite should
+ # go into a list.
+ self.coin_list = None
+ self.wall_list = None
+ self.player_list = None
+
+ # Separate variable that holds the player sprite
+ self.player_sprite = None
+
+ # Our physics engine
+ self.physics_engine = None
+
+ # Used to keep track of our scrolling
+ self.view_bottom = 0
+ self.view_left = 0
+
+ # Keep track of the score
+ self.score = 0
+
+ # Load sounds
+ self.collect_coin_sound = arcade.load_sound("sounds/coin1.wav")
+ self.jump_sound = arcade.load_sound("sounds/jump1.wav")
+
+ arcade.set_background_color(arcade.csscolor.CORNFLOWER_BLUE)
+
+ def setup(self):
+ """ Set up the game here. Call this function to restart the game. """
+
+ # Used to keep track of our scrolling
+ self.view_bottom = 0
+ self.view_left = 0
+
+ # Keep track of the score
+ self.score = 0
+
+ # Create the Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.wall_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Set up the player, specifically placing it at these coordinates.
+ self.player_sprite = arcade.Sprite("images/player_1/player_stand.png", CHARACTER_SCALING)
+ self.player_sprite.center_x = 64
+ self.player_sprite.center_y = 96
+ self.player_list.append(self.player_sprite)
+
+ # --- Load in a map from the tiled editor ---
+
+ # Name of map file to load
+ map_name = "map.tmx"
+ # Name of the layer in the file that has our platforms/walls
+ platforms_layer_name = 'Platforms'
+ # Name of the layer that has items for pick-up
+ coins_layer_name = 'Coins'
+
+ # Read in the tiled map
+ my_map = arcade.tilemap.read_tmx(map_name)
+
+ # -- Platforms
+ self.wall_list = arcade.tilemap.process_layer(my_map, platforms_layer_name, TILE_SCALING)
+
+ # -- Coins
+ self.coin_list = arcade.tilemap.process_layer(my_map, coins_layer_name, TILE_SCALING)
+
+ # --- Other stuff
+ # Set the background color
+ if my_map.background_color:
+ arcade.set_background_color(my_map.background_color)
+
+ # Create the 'physics engine'
+ self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
+ self.wall_list,
+ GRAVITY)
+
+ def on_draw(self):
+ """ Render the screen. """
+
+ # Clear the screen to the background color
+ arcade.start_render()
+
+ # Draw our sprites
+ self.wall_list.draw()
+ self.coin_list.draw()
+ self.player_list.draw()
+
+ # Draw our score on the screen, scrolling it with the viewport
+ score_text = f"Score: {self.score}"
+ arcade.draw_text(score_text, 10 + self.view_left, 10 + self.view_bottom,
+ arcade.csscolor.WHITE, 18)
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ if key == arcade.key.UP or key == arcade.key.W:
+ if self.physics_engine.can_jump():
+ self.player_sprite.change_y = PLAYER_JUMP_SPEED
+ arcade.play_sound(self.jump_sound)
+ elif key == arcade.key.LEFT or key == arcade.key.A:
+ self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT or key == arcade.key.D:
+ self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.LEFT or key == arcade.key.A:
+ self.player_sprite.change_x = 0
+ elif key == arcade.key.RIGHT or key == arcade.key.D:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.physics_engine.update()
+
+ # See if we hit any coins
+ coin_hit_list = arcade.check_for_collision_with_list(self.player_sprite,
+ self.coin_list)
+
+ # Loop through each coin we hit (if any) and remove it
+ for coin in coin_hit_list:
+ # Remove the coin
+ coin.remove_from_sprite_lists()
+ # Play a sound
+ arcade.play_sound(self.collect_coin_sound)
+ # Add one to the score
+ self.score += 1
+
+ # --- Manage Scrolling ---
+
+ # Track if we need to change the viewport
+
+ changed = False
+
+ # Scroll left
+ left_boundary = self.view_left + LEFT_VIEWPORT_MARGIN
+ if self.player_sprite.left < left_boundary:
+ self.view_left -= left_boundary - self.player_sprite.left
+ changed = True
+
+ # Scroll right
+ right_boundary = self.view_left + SCREEN_WIDTH - RIGHT_VIEWPORT_MARGIN
+ if self.player_sprite.right > right_boundary:
+ self.view_left += self.player_sprite.right - right_boundary
+ changed = True
+
+ # Scroll up
+ top_boundary = self.view_bottom + SCREEN_HEIGHT - TOP_VIEWPORT_MARGIN
+ if self.player_sprite.top > top_boundary:
+ self.view_bottom += self.player_sprite.top - top_boundary
+ changed = True
+
+ # Scroll down
+ bottom_boundary = self.view_bottom + BOTTOM_VIEWPORT_MARGIN
+ if self.player_sprite.bottom < bottom_boundary:
+ self.view_bottom -= bottom_boundary - self.player_sprite.bottom
+ changed = True
+
+ if changed:
+ # Only scroll to integers. Otherwise we end up with pixels that
+ # don't line up on the screen
+ self.view_bottom = int(self.view_bottom)
+ self.view_left = int(self.view_left)
+
+ # Do the scrolling
+ arcade.set_viewport(self.view_left,
+ SCREEN_WIDTH + self.view_left,
+ self.view_bottom,
+ SCREEN_HEIGHT + self.view_bottom)
+
+
+def main():
+ """ Main method """
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/platform_tutorial/09_endgame.py b/arcade/examples/platform_tutorial/09_endgame.py
new file mode 100644
index 0000000..d4d08f7
--- /dev/null
+++ b/arcade/examples/platform_tutorial/09_endgame.py
@@ -0,0 +1,306 @@
+"""
+Platformer Game
+"""
+import arcade
+
+# Constants
+SCREEN_WIDTH = 1000
+SCREEN_HEIGHT = 650
+SCREEN_TITLE = "Platformer"
+
+# Constants used to scale our sprites from their original size
+CHARACTER_SCALING = 1
+TILE_SCALING = 0.5
+COIN_SCALING = 0.5
+SPRITE_PIXEL_SIZE = 128
+GRID_PIXEL_SIZE = (SPRITE_PIXEL_SIZE * TILE_SCALING)
+
+# Movement speed of player, in pixels per frame
+PLAYER_MOVEMENT_SPEED = 10
+GRAVITY = 1
+PLAYER_JUMP_SPEED = 20
+
+# How many pixels to keep as a minimum margin between the character
+# and the edge of the screen.
+LEFT_VIEWPORT_MARGIN = 200
+RIGHT_VIEWPORT_MARGIN = 200
+BOTTOM_VIEWPORT_MARGIN = 150
+TOP_VIEWPORT_MARGIN = 100
+
+PLAYER_START_X = 64
+PLAYER_START_Y = 225
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self):
+
+ # Call the parent class and set up the window
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # These are 'lists' that keep track of our sprites. Each sprite should
+ # go into a list.
+ self.coin_list = None
+ self.wall_list = None
+ self.foreground_list = None
+ self.background_list = None
+ self.dont_touch_list = None
+ self.player_list = None
+
+ # Separate variable that holds the player sprite
+ self.player_sprite = None
+
+ # Our physics engine
+ self.physics_engine = None
+
+ # Used to keep track of our scrolling
+ self.view_bottom = 0
+ self.view_left = 0
+
+ # Keep track of the score
+ self.score = 0
+
+ # Where is the right edge of the map?
+ self.end_of_map = 0
+
+ # Level
+ self.level = 1
+
+ # Load sounds
+ self.collect_coin_sound = arcade.load_sound("sounds/coin1.wav")
+ self.jump_sound = arcade.load_sound("sounds/jump1.wav")
+ self.game_over = arcade.load_sound("sounds/gameover1.wav")
+
+ def setup(self, level):
+ """ Set up the game here. Call this function to restart the game. """
+
+ # Used to keep track of our scrolling
+ self.view_bottom = 0
+ self.view_left = 0
+
+ # Keep track of the score
+ self.score = 0
+
+ # Create the Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.foreground_list = arcade.SpriteList()
+ self.background_list = arcade.SpriteList()
+ self.wall_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Set up the player, specifically placing it at these coordinates.
+ self.player_sprite = arcade.Sprite("images/player_1/player_stand.png",
+ CHARACTER_SCALING)
+ self.player_sprite.center_x = PLAYER_START_X
+ self.player_sprite.center_y = PLAYER_START_Y
+ self.player_list.append(self.player_sprite)
+
+ # --- Load in a map from the tiled editor ---
+
+ # Name of the layer in the file that has our platforms/walls
+ platforms_layer_name = 'Platforms'
+ # Name of the layer that has items for pick-up
+ coins_layer_name = 'Coins'
+ # Name of the layer that has items for foreground
+ foreground_layer_name = 'Foreground'
+ # Name of the layer that has items for background
+ background_layer_name = 'Background'
+ # Name of the layer that has items we shouldn't touch
+ dont_touch_layer_name = "Don't Touch"
+
+ # Map name
+ map_name = f"map2_level_{level}.tmx"
+
+ # Read in the tiled map
+ my_map = arcade.tilemap.read_tmx(map_name)
+
+ # Calculate the right edge of the my_map in pixels
+ self.end_of_map = my_map.map_size.width * GRID_PIXEL_SIZE
+
+ # -- Background
+ self.background_list = arcade.tilemap.process_layer(my_map,
+ background_layer_name,
+ TILE_SCALING)
+
+ # -- Foreground
+ self.foreground_list = arcade.tilemap.process_layer(my_map,
+ foreground_layer_name,
+ TILE_SCALING)
+
+ # -- Platforms
+ self.wall_list = arcade.tilemap.process_layer(my_map,
+ platforms_layer_name,
+ TILE_SCALING)
+
+ # -- Coins
+ self.coin_list = arcade.tilemap.process_layer(my_map,
+ coins_layer_name,
+ TILE_SCALING)
+
+ # -- Don't Touch Layer
+ self.dont_touch_list = arcade.tilemap.process_layer(my_map,
+ dont_touch_layer_name,
+ TILE_SCALING)
+
+ # --- Other stuff
+ # Set the background color
+ if my_map.background_color:
+ arcade.set_background_color(my_map.background_color)
+
+ # Create the 'physics engine'
+ self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
+ self.wall_list,
+ GRAVITY)
+
+ def on_draw(self):
+ """ Render the screen. """
+
+ # Clear the screen to the background color
+ arcade.start_render()
+
+ # Draw our sprites
+ self.wall_list.draw()
+ self.background_list.draw()
+ self.wall_list.draw()
+ self.coin_list.draw()
+ self.dont_touch_list.draw()
+ self.player_list.draw()
+ self.foreground_list.draw()
+
+ # Draw our score on the screen, scrolling it with the viewport
+ score_text = f"Score: {self.score}"
+ arcade.draw_text(score_text, 10 + self.view_left, 10 + self.view_bottom,
+ arcade.csscolor.BLACK, 18)
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ if key == arcade.key.UP or key == arcade.key.W:
+ if self.physics_engine.can_jump():
+ self.player_sprite.change_y = PLAYER_JUMP_SPEED
+ arcade.play_sound(self.jump_sound)
+ elif key == arcade.key.LEFT or key == arcade.key.A:
+ self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT or key == arcade.key.D:
+ self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.LEFT or key == arcade.key.A:
+ self.player_sprite.change_x = 0
+ elif key == arcade.key.RIGHT or key == arcade.key.D:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.physics_engine.update()
+
+ # See if we hit any coins
+ coin_hit_list = arcade.check_for_collision_with_list(self.player_sprite,
+ self.coin_list)
+
+ # Loop through each coin we hit (if any) and remove it
+ for coin in coin_hit_list:
+ # Remove the coin
+ coin.remove_from_sprite_lists()
+ # Play a sound
+ arcade.play_sound(self.collect_coin_sound)
+ # Add one to the score
+ self.score += 1
+
+ # Track if we need to change the viewport
+ changed_viewport = False
+
+ # Did the player fall off the map?
+ if self.player_sprite.center_y < -100:
+ self.player_sprite.center_x = PLAYER_START_X
+ self.player_sprite.center_y = PLAYER_START_Y
+
+ # Set the camera to the start
+ self.view_left = 0
+ self.view_bottom = 0
+ changed_viewport = True
+ arcade.play_sound(self.game_over)
+
+ # Did the player touch something they should not?
+ if arcade.check_for_collision_with_list(self.player_sprite,
+ self.dont_touch_list):
+ self.player_sprite.change_x = 0
+ self.player_sprite.change_y = 0
+ self.player_sprite.center_x = PLAYER_START_X
+ self.player_sprite.center_y = PLAYER_START_Y
+
+ # Set the camera to the start
+ self.view_left = 0
+ self.view_bottom = 0
+ changed_viewport = True
+ arcade.play_sound(self.game_over)
+
+ # See if the user got to the end of the level
+ if self.player_sprite.center_x >= self.end_of_map:
+ # Advance to the next level
+ self.level += 1
+
+ # Load the next level
+ self.setup(self.level)
+
+ # Set the camera to the start
+ self.view_left = 0
+ self.view_bottom = 0
+ changed_viewport = True
+
+ # --- Manage Scrolling ---
+
+ # Scroll left
+ left_boundary = self.view_left + LEFT_VIEWPORT_MARGIN
+ if self.player_sprite.left < left_boundary:
+ self.view_left -= left_boundary - self.player_sprite.left
+ changed_viewport = True
+
+ # Scroll right
+ right_boundary = self.view_left + SCREEN_WIDTH - RIGHT_VIEWPORT_MARGIN
+ if self.player_sprite.right > right_boundary:
+ self.view_left += self.player_sprite.right - right_boundary
+ changed_viewport = True
+
+ # Scroll up
+ top_boundary = self.view_bottom + SCREEN_HEIGHT - TOP_VIEWPORT_MARGIN
+ if self.player_sprite.top > top_boundary:
+ self.view_bottom += self.player_sprite.top - top_boundary
+ changed_viewport = True
+
+ # Scroll down
+ bottom_boundary = self.view_bottom + BOTTOM_VIEWPORT_MARGIN
+ if self.player_sprite.bottom < bottom_boundary:
+ self.view_bottom -= bottom_boundary - self.player_sprite.bottom
+ changed_viewport = True
+
+ if changed_viewport:
+ # Only scroll to integers. Otherwise we end up with pixels that
+ # don't line up on the screen
+ self.view_bottom = int(self.view_bottom)
+ self.view_left = int(self.view_left)
+
+ # Do the scrolling
+ arcade.set_viewport(self.view_left,
+ SCREEN_WIDTH + self.view_left,
+ self.view_bottom,
+ SCREEN_HEIGHT + self.view_bottom)
+
+
+def main():
+ """ Main method """
+ window = MyGame()
+ window.setup(window.level)
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/platform_tutorial/10_ladders_and_more.py b/arcade/examples/platform_tutorial/10_ladders_and_more.py
new file mode 100644
index 0000000..d3c28fc
--- /dev/null
+++ b/arcade/examples/platform_tutorial/10_ladders_and_more.py
@@ -0,0 +1,293 @@
+"""
+Platformer Game
+"""
+import arcade
+import os
+
+# Constants
+SCREEN_WIDTH = 1000
+SCREEN_HEIGHT = 650
+SCREEN_TITLE = "Platformer"
+
+# Constants used to scale our sprites from their original size
+CHARACTER_SCALING = 1
+TILE_SCALING = 0.5
+COIN_SCALING = 0.5
+SPRITE_PIXEL_SIZE = 128
+GRID_PIXEL_SIZE = (SPRITE_PIXEL_SIZE * TILE_SCALING)
+
+# Movement speed of player, in pixels per frame
+PLAYER_MOVEMENT_SPEED = 7
+GRAVITY = 1.5
+PLAYER_JUMP_SPEED = 30
+
+# How many pixels to keep as a minimum margin between the character
+# and the edge of the screen.
+LEFT_VIEWPORT_MARGIN = 200
+RIGHT_VIEWPORT_MARGIN = 200
+BOTTOM_VIEWPORT_MARGIN = 150
+TOP_VIEWPORT_MARGIN = 100
+
+PLAYER_START_X = 64
+PLAYER_START_Y = 256
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self):
+ """
+ Initializer for the game
+ """
+
+ # Call the parent class and set up the window
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the path to start with this program
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # These are 'lists' that keep track of our sprites. Each sprite should
+ # go into a list.
+ self.coin_list = None
+ self.wall_list = None
+ self.background_list = None
+ self.ladder_list = None
+ self.player_list = None
+
+ # Separate variable that holds the player sprite
+ self.player_sprite = None
+
+ # Our 'physics' engine
+ self.physics_engine = None
+
+ # Used to keep track of our scrolling
+ self.view_bottom = 0
+ self.view_left = 0
+
+ self.end_of_map = 0
+
+ # Keep track of the score
+ self.score = 0
+
+ # Load sounds
+ self.collect_coin_sound = arcade.load_sound("sounds/coin1.wav")
+ self.jump_sound = arcade.load_sound("sounds/jump1.wav")
+ self.game_over = arcade.load_sound("sounds/gameover1.wav")
+
+ def setup(self):
+ """ Set up the game here. Call this function to restart the game. """
+
+ # Used to keep track of our scrolling
+ self.view_bottom = 0
+ self.view_left = 0
+
+ # Keep track of the score
+ self.score = 0
+
+ # Create the Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.background_list = arcade.SpriteList()
+ self.wall_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Set up the player, specifically placing it at these coordinates.
+ # self.player_sprite = arcade.Sprite("images/player_1/player_stand.png", CHARACTER_SCALING)
+ self.player_sprite = \
+ arcade.Sprite("../../../arcade/examples/platform_tutorial/images/player_1/player_stand.png")
+ self.player_sprite.center_x = PLAYER_START_X
+ self.player_sprite.center_y = PLAYER_START_Y
+ self.player_list.append(self.player_sprite)
+
+ # --- Load in a map from the tiled editor ---
+
+ # Name of the layer in the file that has our platforms/walls
+ platforms_layer_name = 'Platforms'
+ moving_platforms_layer_name = 'Moving Platforms'
+
+ # Name of the layer that has items for pick-up
+ coins_layer_name = 'Coins'
+
+ # Map name
+ map_name = f"map_with_ladders.tmx"
+
+ # Read in the tiled map
+ my_map = arcade.tilemap.read_tmx(map_name)
+
+ # Calculate the right edge of the my_map in pixels
+ self.end_of_map = my_map.map_size.width * GRID_PIXEL_SIZE
+
+ # -- Platforms
+ self.wall_list = arcade.tilemap.process_layer(my_map, platforms_layer_name, TILE_SCALING)
+
+ # -- Moving Platforms
+ moving_platforms_list = arcade.tilemap.process_layer(my_map, moving_platforms_layer_name, TILE_SCALING)
+ for sprite in moving_platforms_list:
+ self.wall_list.append(sprite)
+
+ # -- Background objects
+ self.background_list = arcade.tilemap.process_layer(my_map, "Background", TILE_SCALING)
+
+ # -- Background objects
+ self.ladder_list = arcade.tilemap.process_layer(my_map, "Ladders", TILE_SCALING)
+
+ # -- Coins
+ self.coin_list = arcade.tilemap.process_layer(my_map, coins_layer_name, TILE_SCALING)
+
+ # --- Other stuff
+ # Set the background color
+ if my_map.background_color:
+ arcade.set_background_color(my_map.background_color)
+
+ # Create the 'physics engine'
+ self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
+ self.wall_list,
+ gravity_constant=GRAVITY,
+ ladders=self.ladder_list)
+
+ def on_draw(self):
+ """ Render the screen. """
+
+ # Clear the screen to the background color
+ arcade.start_render()
+
+ # Draw our sprites
+ self.wall_list.draw()
+ self.background_list.draw()
+ self.ladder_list.draw()
+ self.coin_list.draw()
+ self.player_list.draw()
+
+ # Draw our score on the screen, scrolling it with the viewport
+ score_text = f"Score: {self.score}"
+ arcade.draw_text(score_text, 10 + self.view_left, 10 + self.view_bottom,
+ arcade.csscolor.BLACK, 18)
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ if key == arcade.key.UP or key == arcade.key.W:
+ if self.physics_engine.is_on_ladder():
+ self.player_sprite.change_y = PLAYER_MOVEMENT_SPEED
+ elif self.physics_engine.can_jump():
+ self.player_sprite.change_y = PLAYER_JUMP_SPEED
+ arcade.play_sound(self.jump_sound)
+ elif key == arcade.key.DOWN or key == arcade.key.S:
+ if self.physics_engine.is_on_ladder():
+ self.player_sprite.change_y = -PLAYER_MOVEMENT_SPEED
+ elif key == arcade.key.LEFT or key == arcade.key.A:
+ self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT or key == arcade.key.D:
+ self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.UP or key == arcade.key.W:
+ if self.physics_engine.is_on_ladder():
+ self.player_sprite.change_y = 0
+ elif key == arcade.key.DOWN or key == arcade.key.S:
+ if self.physics_engine.is_on_ladder():
+ self.player_sprite.change_y = 0
+ elif key == arcade.key.LEFT or key == arcade.key.A:
+ self.player_sprite.change_x = 0
+ elif key == arcade.key.RIGHT or key == arcade.key.D:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.physics_engine.update()
+
+ # Update animations
+ self.coin_list.update_animation(delta_time)
+ self.background_list.update_animation(delta_time)
+
+ # Update walls, used with moving platforms
+ self.wall_list.update()
+
+ # See if the wall hit a boundary and needs to reverse direction.
+ for wall in self.wall_list:
+
+ if wall.boundary_right and wall.right > wall.boundary_right and wall.change_x > 0:
+ wall.change_x *= -1
+ if wall.boundary_left and wall.left < wall.boundary_left and wall.change_x < 0:
+ wall.change_x *= -1
+ if wall.boundary_top and wall.top > wall.boundary_top and wall.change_y > 0:
+ wall.change_y *= -1
+ if wall.boundary_bottom and wall.bottom < wall.boundary_bottom and wall.change_y < 0:
+ wall.change_y *= -1
+
+ # See if we hit any coins
+ coin_hit_list = arcade.check_for_collision_with_list(self.player_sprite,
+ self.coin_list)
+
+ # Loop through each coin we hit (if any) and remove it
+ for coin in coin_hit_list:
+
+ # Figure out how many points this coin is worth
+ if 'Points' not in coin.properties:
+ print("Warning, collected a coing without a Points property.")
+ else:
+ points = int(coin.properties['Points'])
+ self.score += points
+
+ # Remove the coin
+ coin.remove_from_sprite_lists()
+ arcade.play_sound(self.collect_coin_sound)
+
+ # Track if we need to change the viewport
+ changed_viewport = False
+
+ # --- Manage Scrolling ---
+
+ # Scroll left
+ left_boundary = self.view_left + LEFT_VIEWPORT_MARGIN
+ if self.player_sprite.left < left_boundary:
+ self.view_left -= left_boundary - self.player_sprite.left
+ changed_viewport = True
+
+ # Scroll right
+ right_boundary = self.view_left + SCREEN_WIDTH - RIGHT_VIEWPORT_MARGIN
+ if self.player_sprite.right > right_boundary:
+ self.view_left += self.player_sprite.right - right_boundary
+ changed_viewport = True
+
+ # Scroll up
+ top_boundary = self.view_bottom + SCREEN_HEIGHT - TOP_VIEWPORT_MARGIN
+ if self.player_sprite.top > top_boundary:
+ self.view_bottom += self.player_sprite.top - top_boundary
+ changed_viewport = True
+
+ # Scroll down
+ bottom_boundary = self.view_bottom + BOTTOM_VIEWPORT_MARGIN
+ if self.player_sprite.bottom < bottom_boundary:
+ self.view_bottom -= bottom_boundary - self.player_sprite.bottom
+ changed_viewport = True
+
+ if changed_viewport:
+ # Only scroll to integers. Otherwise we end up with pixels that
+ # don't line up on the screen
+ self.view_bottom = int(self.view_bottom)
+ self.view_left = int(self.view_left)
+
+ # Do the scrolling
+ arcade.set_viewport(self.view_left,
+ SCREEN_WIDTH + self.view_left,
+ self.view_bottom,
+ SCREEN_HEIGHT + self.view_bottom)
+
+
+def main():
+ """ Main method """
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/platform_tutorial/images/Kenney Donate.url b/arcade/examples/platform_tutorial/images/Kenney Donate.url
new file mode 100644
index 0000000..a310f49
--- /dev/null
+++ b/arcade/examples/platform_tutorial/images/Kenney Donate.url
@@ -0,0 +1,2 @@
+[InternetShortcut]
+URL=http://kenney.nl/support
\ No newline at end of file
diff --git a/arcade/examples/platform_tutorial/images/Kenney Facebook.url b/arcade/examples/platform_tutorial/images/Kenney Facebook.url
new file mode 100644
index 0000000..e37cfb3
--- /dev/null
+++ b/arcade/examples/platform_tutorial/images/Kenney Facebook.url
@@ -0,0 +1,2 @@
+[InternetShortcut]
+URL=http://social.kenney.nl/facebook
\ No newline at end of file
diff --git a/arcade/examples/platform_tutorial/images/Kenney Twitter.url b/arcade/examples/platform_tutorial/images/Kenney Twitter.url
new file mode 100644
index 0000000..10be373
--- /dev/null
+++ b/arcade/examples/platform_tutorial/images/Kenney Twitter.url
@@ -0,0 +1,2 @@
+[InternetShortcut]
+URL=http://social.kenney.nl/twitter
\ No newline at end of file
diff --git a/arcade/examples/platform_tutorial/images/Kenney Website.url b/arcade/examples/platform_tutorial/images/Kenney Website.url
new file mode 100644
index 0000000..fbdde43
--- /dev/null
+++ b/arcade/examples/platform_tutorial/images/Kenney Website.url
@@ -0,0 +1,2 @@
+[InternetShortcut]
+URL=http://www.kenney.nl/
\ No newline at end of file
diff --git a/arcade/examples/platform_tutorial/images/License.txt b/arcade/examples/platform_tutorial/images/License.txt
new file mode 100644
index 0000000..8a9c62c
--- /dev/null
+++ b/arcade/examples/platform_tutorial/images/License.txt
@@ -0,0 +1,17 @@
+
+
+
+ Everything in this package is licensed CC0 (see below).
+
+ ------------------------------
+
+ License (Creative Commons Zero, CC0)
+ http://creativecommons.org/publicdomain/zero/1.0/
+
+ You may use these graphics in personal and commercial projects.
+ Credit (Kenney or www.kenney.nl) would be nice but is not mandatory.
+
+ ------------------------------
+
+ Donate: http://donate.kenney.nl/
+ Request: http://request.kenney.nl/
\ No newline at end of file
diff --git a/arcade/examples/platform_tutorial/images/alien/alienBlue_climb1.png b/arcade/examples/platform_tutorial/images/alien/alienBlue_climb1.png
new file mode 100644
index 0000000..415de24
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/alien/alienBlue_climb1.png differ
diff --git a/arcade/examples/platform_tutorial/images/alien/alienBlue_climb2.png b/arcade/examples/platform_tutorial/images/alien/alienBlue_climb2.png
new file mode 100644
index 0000000..5d7cddf
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/alien/alienBlue_climb2.png differ
diff --git a/arcade/examples/platform_tutorial/images/alien/alienBlue_front.png b/arcade/examples/platform_tutorial/images/alien/alienBlue_front.png
new file mode 100644
index 0000000..aa64b7c
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/alien/alienBlue_front.png differ
diff --git a/arcade/examples/platform_tutorial/images/alien/alienBlue_jump.png b/arcade/examples/platform_tutorial/images/alien/alienBlue_jump.png
new file mode 100644
index 0000000..79f0231
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/alien/alienBlue_jump.png differ
diff --git a/arcade/examples/platform_tutorial/images/alien/alienBlue_walk1.png b/arcade/examples/platform_tutorial/images/alien/alienBlue_walk1.png
new file mode 100644
index 0000000..7922b84
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/alien/alienBlue_walk1.png differ
diff --git a/arcade/examples/platform_tutorial/images/alien/alienBlue_walk2.png b/arcade/examples/platform_tutorial/images/alien/alienBlue_walk2.png
new file mode 100644
index 0000000..48245ae
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/alien/alienBlue_walk2.png differ
diff --git a/arcade/examples/platform_tutorial/images/enemies/bee.png b/arcade/examples/platform_tutorial/images/enemies/bee.png
new file mode 100644
index 0000000..8f2b2d0
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/enemies/bee.png differ
diff --git a/arcade/examples/platform_tutorial/images/enemies/fishGreen.png b/arcade/examples/platform_tutorial/images/enemies/fishGreen.png
new file mode 100644
index 0000000..0ee8a64
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/enemies/fishGreen.png differ
diff --git a/arcade/examples/platform_tutorial/images/enemies/fishPink.png b/arcade/examples/platform_tutorial/images/enemies/fishPink.png
new file mode 100644
index 0000000..925cc60
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/enemies/fishPink.png differ
diff --git a/arcade/examples/platform_tutorial/images/enemies/fly.png b/arcade/examples/platform_tutorial/images/enemies/fly.png
new file mode 100644
index 0000000..a733879
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/enemies/fly.png differ
diff --git a/arcade/examples/platform_tutorial/images/enemies/frog.png b/arcade/examples/platform_tutorial/images/enemies/frog.png
new file mode 100644
index 0000000..8107bb4
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/enemies/frog.png differ
diff --git a/arcade/examples/platform_tutorial/images/enemies/frog_move.png b/arcade/examples/platform_tutorial/images/enemies/frog_move.png
new file mode 100644
index 0000000..87a39af
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/enemies/frog_move.png differ
diff --git a/arcade/examples/platform_tutorial/images/enemies/ladybug.png b/arcade/examples/platform_tutorial/images/enemies/ladybug.png
new file mode 100644
index 0000000..3f95a2d
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/enemies/ladybug.png differ
diff --git a/arcade/examples/platform_tutorial/images/enemies/mouse.png b/arcade/examples/platform_tutorial/images/enemies/mouse.png
new file mode 100644
index 0000000..5d98c41
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/enemies/mouse.png differ
diff --git a/arcade/examples/platform_tutorial/images/enemies/saw.png b/arcade/examples/platform_tutorial/images/enemies/saw.png
new file mode 100644
index 0000000..4494ec7
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/enemies/saw.png differ
diff --git a/arcade/examples/platform_tutorial/images/enemies/sawHalf.png b/arcade/examples/platform_tutorial/images/enemies/sawHalf.png
new file mode 100644
index 0000000..c5f7732
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/enemies/sawHalf.png differ
diff --git a/arcade/examples/platform_tutorial/images/enemies/slimeBlock.png b/arcade/examples/platform_tutorial/images/enemies/slimeBlock.png
new file mode 100644
index 0000000..790350a
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/enemies/slimeBlock.png differ
diff --git a/arcade/examples/platform_tutorial/images/enemies/slimeBlue.png b/arcade/examples/platform_tutorial/images/enemies/slimeBlue.png
new file mode 100644
index 0000000..6d18e27
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/enemies/slimeBlue.png differ
diff --git a/arcade/examples/platform_tutorial/images/enemies/slimeBlue_move.png b/arcade/examples/platform_tutorial/images/enemies/slimeBlue_move.png
new file mode 100644
index 0000000..e676dcc
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/enemies/slimeBlue_move.png differ
diff --git a/arcade/examples/platform_tutorial/images/enemies/slimeGreen.png b/arcade/examples/platform_tutorial/images/enemies/slimeGreen.png
new file mode 100644
index 0000000..397b623
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/enemies/slimeGreen.png differ
diff --git a/arcade/examples/platform_tutorial/images/enemies/slimePurple.png b/arcade/examples/platform_tutorial/images/enemies/slimePurple.png
new file mode 100644
index 0000000..e72ac04
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/enemies/slimePurple.png differ
diff --git a/arcade/examples/platform_tutorial/images/enemies/wormGreen.png b/arcade/examples/platform_tutorial/images/enemies/wormGreen.png
new file mode 100644
index 0000000..045161a
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/enemies/wormGreen.png differ
diff --git a/arcade/examples/platform_tutorial/images/enemies/wormGreen_dead.png b/arcade/examples/platform_tutorial/images/enemies/wormGreen_dead.png
new file mode 100644
index 0000000..fba0f23
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/enemies/wormGreen_dead.png differ
diff --git a/arcade/examples/platform_tutorial/images/enemies/wormGreen_move.png b/arcade/examples/platform_tutorial/images/enemies/wormGreen_move.png
new file mode 100644
index 0000000..999416e
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/enemies/wormGreen_move.png differ
diff --git a/arcade/examples/platform_tutorial/images/enemies/wormPink.png b/arcade/examples/platform_tutorial/images/enemies/wormPink.png
new file mode 100644
index 0000000..4ee861a
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/enemies/wormPink.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/coinBronze.png b/arcade/examples/platform_tutorial/images/items/coinBronze.png
new file mode 100644
index 0000000..cf346cb
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/coinBronze.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/coinGold.png b/arcade/examples/platform_tutorial/images/items/coinGold.png
new file mode 100644
index 0000000..4df83e2
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/coinGold.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/coinSilver.png b/arcade/examples/platform_tutorial/images/items/coinSilver.png
new file mode 100644
index 0000000..dc36564
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/coinSilver.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/flagGreen1.png b/arcade/examples/platform_tutorial/images/items/flagGreen1.png
new file mode 100644
index 0000000..b7b4278
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/flagGreen1.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/flagGreen2.png b/arcade/examples/platform_tutorial/images/items/flagGreen2.png
new file mode 100644
index 0000000..57534e3
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/flagGreen2.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/flagGreen_down.png b/arcade/examples/platform_tutorial/images/items/flagGreen_down.png
new file mode 100644
index 0000000..1b1a1e9
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/flagGreen_down.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/flagRed1.png b/arcade/examples/platform_tutorial/images/items/flagRed1.png
new file mode 100644
index 0000000..fae407e
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/flagRed1.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/flagRed2.png b/arcade/examples/platform_tutorial/images/items/flagRed2.png
new file mode 100644
index 0000000..4193385
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/flagRed2.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/flagRed_down.png b/arcade/examples/platform_tutorial/images/items/flagRed_down.png
new file mode 100644
index 0000000..a97a990
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/flagRed_down.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/flagYellow1.png b/arcade/examples/platform_tutorial/images/items/flagYellow1.png
new file mode 100644
index 0000000..909099d
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/flagYellow1.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/flagYellow2.png b/arcade/examples/platform_tutorial/images/items/flagYellow2.png
new file mode 100644
index 0000000..c5e1bbd
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/flagYellow2.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/flagYellow_down.png b/arcade/examples/platform_tutorial/images/items/flagYellow_down.png
new file mode 100644
index 0000000..c2d5fc5
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/flagYellow_down.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/gemBlue.png b/arcade/examples/platform_tutorial/images/items/gemBlue.png
new file mode 100644
index 0000000..dfd6025
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/gemBlue.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/gemGreen.png b/arcade/examples/platform_tutorial/images/items/gemGreen.png
new file mode 100644
index 0000000..6cdc8f0
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/gemGreen.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/gemRed.png b/arcade/examples/platform_tutorial/images/items/gemRed.png
new file mode 100644
index 0000000..27dcf03
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/gemRed.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/gemYellow.png b/arcade/examples/platform_tutorial/images/items/gemYellow.png
new file mode 100644
index 0000000..5a8201a
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/gemYellow.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/keyBlue.png b/arcade/examples/platform_tutorial/images/items/keyBlue.png
new file mode 100644
index 0000000..b4099ef
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/keyBlue.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/keyGreen.png b/arcade/examples/platform_tutorial/images/items/keyGreen.png
new file mode 100644
index 0000000..00d5140
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/keyGreen.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/keyRed.png b/arcade/examples/platform_tutorial/images/items/keyRed.png
new file mode 100644
index 0000000..d4c1c59
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/keyRed.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/keyYellow.png b/arcade/examples/platform_tutorial/images/items/keyYellow.png
new file mode 100644
index 0000000..bcfd368
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/keyYellow.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/ladderMid.png b/arcade/examples/platform_tutorial/images/items/ladderMid.png
new file mode 100644
index 0000000..7ef8720
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/ladderMid.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/ladderTop.png b/arcade/examples/platform_tutorial/images/items/ladderTop.png
new file mode 100644
index 0000000..d363693
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/ladderTop.png differ
diff --git a/arcade/examples/platform_tutorial/images/items/star.png b/arcade/examples/platform_tutorial/images/items/star.png
new file mode 100644
index 0000000..20f5dd1
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/items/star.png differ
diff --git a/arcade/examples/platform_tutorial/images/player_1/female_back.png b/arcade/examples/platform_tutorial/images/player_1/female_back.png
new file mode 100644
index 0000000..0b2e3b1
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/player_1/female_back.png differ
diff --git a/arcade/examples/platform_tutorial/images/player_1/female_jump.png b/arcade/examples/platform_tutorial/images/player_1/female_jump.png
new file mode 100644
index 0000000..79ae316
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/player_1/female_jump.png differ
diff --git a/arcade/examples/platform_tutorial/images/player_1/female_walk1.png b/arcade/examples/platform_tutorial/images/player_1/female_walk1.png
new file mode 100644
index 0000000..8d0680d
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/player_1/female_walk1.png differ
diff --git a/arcade/examples/platform_tutorial/images/player_1/female_walk2.png b/arcade/examples/platform_tutorial/images/player_1/female_walk2.png
new file mode 100644
index 0000000..373de43
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/player_1/female_walk2.png differ
diff --git a/arcade/examples/platform_tutorial/images/player_1/player_stand.png b/arcade/examples/platform_tutorial/images/player_1/player_stand.png
new file mode 100644
index 0000000..e98af64
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/player_1/player_stand.png differ
diff --git a/arcade/examples/platform_tutorial/images/player_2/player_back.png b/arcade/examples/platform_tutorial/images/player_2/player_back.png
new file mode 100644
index 0000000..df00a2d
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/player_2/player_back.png differ
diff --git a/arcade/examples/platform_tutorial/images/player_2/player_jump.png b/arcade/examples/platform_tutorial/images/player_2/player_jump.png
new file mode 100644
index 0000000..af3661a
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/player_2/player_jump.png differ
diff --git a/arcade/examples/platform_tutorial/images/player_2/player_stand.png b/arcade/examples/platform_tutorial/images/player_2/player_stand.png
new file mode 100644
index 0000000..ff5655a
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/player_2/player_stand.png differ
diff --git a/arcade/examples/platform_tutorial/images/player_2/player_walk1.png b/arcade/examples/platform_tutorial/images/player_2/player_walk1.png
new file mode 100644
index 0000000..46c2a5f
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/player_2/player_walk1.png differ
diff --git a/arcade/examples/platform_tutorial/images/player_2/player_walk2.png b/arcade/examples/platform_tutorial/images/player_2/player_walk2.png
new file mode 100644
index 0000000..1fb6de2
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/player_2/player_walk2.png differ
diff --git a/arcade/examples/platform_tutorial/images/readme.txt b/arcade/examples/platform_tutorial/images/readme.txt
new file mode 100644
index 0000000..3d9ed43
--- /dev/null
+++ b/arcade/examples/platform_tutorial/images/readme.txt
@@ -0,0 +1,4 @@
+These assets are a subset of what is available from Kenney.nl.
+
+If you like his work, please go support is by purchasing his full asset packs
+at https://kenney.nl/
\ No newline at end of file
diff --git a/arcade/examples/platform_tutorial/images/tiles/boxCrate.png b/arcade/examples/platform_tutorial/images/tiles/boxCrate.png
new file mode 100644
index 0000000..8f5737f
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/boxCrate.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/boxCrate_double.png b/arcade/examples/platform_tutorial/images/tiles/boxCrate_double.png
new file mode 100644
index 0000000..86ed133
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/boxCrate_double.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/boxCrate_single.png b/arcade/examples/platform_tutorial/images/tiles/boxCrate_single.png
new file mode 100644
index 0000000..c96f660
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/boxCrate_single.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/brickBrown.png b/arcade/examples/platform_tutorial/images/tiles/brickBrown.png
new file mode 100644
index 0000000..f919d25
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/brickBrown.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/brickGrey.png b/arcade/examples/platform_tutorial/images/tiles/brickGrey.png
new file mode 100644
index 0000000..00f38ec
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/brickGrey.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/bridgeA.png b/arcade/examples/platform_tutorial/images/tiles/bridgeA.png
new file mode 100644
index 0000000..ac2d8ec
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/bridgeA.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/bridgeB.png b/arcade/examples/platform_tutorial/images/tiles/bridgeB.png
new file mode 100644
index 0000000..71ae128
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/bridgeB.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/bush.png b/arcade/examples/platform_tutorial/images/tiles/bush.png
new file mode 100644
index 0000000..ee353ba
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/bush.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/cactus.png b/arcade/examples/platform_tutorial/images/tiles/cactus.png
new file mode 100644
index 0000000..3e06c71
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/cactus.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/dirt.png b/arcade/examples/platform_tutorial/images/tiles/dirt.png
new file mode 100644
index 0000000..9cd4c2b
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/dirt.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/dirtCenter.png b/arcade/examples/platform_tutorial/images/tiles/dirtCenter.png
new file mode 100644
index 0000000..f6458f0
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/dirtCenter.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/dirtCenter_rounded.png b/arcade/examples/platform_tutorial/images/tiles/dirtCenter_rounded.png
new file mode 100644
index 0000000..d3be245
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/dirtCenter_rounded.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/dirtCliffAlt_left.png b/arcade/examples/platform_tutorial/images/tiles/dirtCliffAlt_left.png
new file mode 100644
index 0000000..ee77778
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/dirtCliffAlt_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/dirtCliffAlt_right.png b/arcade/examples/platform_tutorial/images/tiles/dirtCliffAlt_right.png
new file mode 100644
index 0000000..f5d19aa
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/dirtCliffAlt_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/dirtCliff_left.png b/arcade/examples/platform_tutorial/images/tiles/dirtCliff_left.png
new file mode 100644
index 0000000..0aa844f
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/dirtCliff_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/dirtCliff_right.png b/arcade/examples/platform_tutorial/images/tiles/dirtCliff_right.png
new file mode 100644
index 0000000..508bfbf
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/dirtCliff_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/dirtCorner_left.png b/arcade/examples/platform_tutorial/images/tiles/dirtCorner_left.png
new file mode 100644
index 0000000..7919775
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/dirtCorner_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/dirtCorner_right.png b/arcade/examples/platform_tutorial/images/tiles/dirtCorner_right.png
new file mode 100644
index 0000000..fb0cb54
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/dirtCorner_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/dirtHalf.png b/arcade/examples/platform_tutorial/images/tiles/dirtHalf.png
new file mode 100644
index 0000000..8e3a462
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/dirtHalf.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/dirtHalf_left.png b/arcade/examples/platform_tutorial/images/tiles/dirtHalf_left.png
new file mode 100644
index 0000000..497d658
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/dirtHalf_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/dirtHalf_mid.png b/arcade/examples/platform_tutorial/images/tiles/dirtHalf_mid.png
new file mode 100644
index 0000000..a656a0f
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/dirtHalf_mid.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/dirtHalf_right.png b/arcade/examples/platform_tutorial/images/tiles/dirtHalf_right.png
new file mode 100644
index 0000000..9b4de8c
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/dirtHalf_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/dirtHill_left.png b/arcade/examples/platform_tutorial/images/tiles/dirtHill_left.png
new file mode 100644
index 0000000..313d33c
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/dirtHill_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/dirtHill_right.png b/arcade/examples/platform_tutorial/images/tiles/dirtHill_right.png
new file mode 100644
index 0000000..f45ef32
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/dirtHill_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/dirtLeft.png b/arcade/examples/platform_tutorial/images/tiles/dirtLeft.png
new file mode 100644
index 0000000..0d18085
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/dirtLeft.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/dirtMid.png b/arcade/examples/platform_tutorial/images/tiles/dirtMid.png
new file mode 100644
index 0000000..d9612cf
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/dirtMid.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/dirtRight.png b/arcade/examples/platform_tutorial/images/tiles/dirtRight.png
new file mode 100644
index 0000000..695eaf8
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/dirtRight.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/doorClosed_mid.png b/arcade/examples/platform_tutorial/images/tiles/doorClosed_mid.png
new file mode 100644
index 0000000..2b32291
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/doorClosed_mid.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/doorClosed_top.png b/arcade/examples/platform_tutorial/images/tiles/doorClosed_top.png
new file mode 100644
index 0000000..9001993
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/doorClosed_top.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/grass.png b/arcade/examples/platform_tutorial/images/tiles/grass.png
new file mode 100644
index 0000000..e23656a
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/grass.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/grassCenter.png b/arcade/examples/platform_tutorial/images/tiles/grassCenter.png
new file mode 100644
index 0000000..d595bdc
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/grassCenter.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/grassCenter_round.png b/arcade/examples/platform_tutorial/images/tiles/grassCenter_round.png
new file mode 100644
index 0000000..acb578f
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/grassCenter_round.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/grassCliffAlt_left.png b/arcade/examples/platform_tutorial/images/tiles/grassCliffAlt_left.png
new file mode 100644
index 0000000..f92955f
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/grassCliffAlt_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/grassCliffAlt_right.png b/arcade/examples/platform_tutorial/images/tiles/grassCliffAlt_right.png
new file mode 100644
index 0000000..b3d5aa4
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/grassCliffAlt_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/grassCliff_left.png b/arcade/examples/platform_tutorial/images/tiles/grassCliff_left.png
new file mode 100644
index 0000000..c5d47fc
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/grassCliff_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/grassCliff_right.png b/arcade/examples/platform_tutorial/images/tiles/grassCliff_right.png
new file mode 100644
index 0000000..92be008
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/grassCliff_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/grassCorner_left.png b/arcade/examples/platform_tutorial/images/tiles/grassCorner_left.png
new file mode 100644
index 0000000..385aa96
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/grassCorner_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/grassCorner_right.png b/arcade/examples/platform_tutorial/images/tiles/grassCorner_right.png
new file mode 100644
index 0000000..407a7d0
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/grassCorner_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/grassHalf.png b/arcade/examples/platform_tutorial/images/tiles/grassHalf.png
new file mode 100644
index 0000000..42f62ff
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/grassHalf.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/grassHalf_left.png b/arcade/examples/platform_tutorial/images/tiles/grassHalf_left.png
new file mode 100644
index 0000000..0ac58a9
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/grassHalf_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/grassHalf_mid.png b/arcade/examples/platform_tutorial/images/tiles/grassHalf_mid.png
new file mode 100644
index 0000000..f1cd7c1
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/grassHalf_mid.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/grassHalf_right.png b/arcade/examples/platform_tutorial/images/tiles/grassHalf_right.png
new file mode 100644
index 0000000..4b65788
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/grassHalf_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/grassHill_left.png b/arcade/examples/platform_tutorial/images/tiles/grassHill_left.png
new file mode 100644
index 0000000..d9aab2e
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/grassHill_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/grassHill_right.png b/arcade/examples/platform_tutorial/images/tiles/grassHill_right.png
new file mode 100644
index 0000000..2d769d7
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/grassHill_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/grassLeft.png b/arcade/examples/platform_tutorial/images/tiles/grassLeft.png
new file mode 100644
index 0000000..f7cdc03
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/grassLeft.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/grassMid.png b/arcade/examples/platform_tutorial/images/tiles/grassMid.png
new file mode 100644
index 0000000..acd03cd
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/grassMid.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/grassRight.png b/arcade/examples/platform_tutorial/images/tiles/grassRight.png
new file mode 100644
index 0000000..2e15ec4
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/grassRight.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/grass_sprout.png b/arcade/examples/platform_tutorial/images/tiles/grass_sprout.png
new file mode 100644
index 0000000..132ad54
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/grass_sprout.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/ladderMid.png b/arcade/examples/platform_tutorial/images/tiles/ladderMid.png
new file mode 100644
index 0000000..7ef8720
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/ladderMid.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/ladderTop.png b/arcade/examples/platform_tutorial/images/tiles/ladderTop.png
new file mode 100644
index 0000000..d363693
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/ladderTop.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/lava.png b/arcade/examples/platform_tutorial/images/tiles/lava.png
new file mode 100644
index 0000000..c23d440
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/lava.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/lavaTop_high.png b/arcade/examples/platform_tutorial/images/tiles/lavaTop_high.png
new file mode 100644
index 0000000..bf7a954
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/lavaTop_high.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/lavaTop_low.png b/arcade/examples/platform_tutorial/images/tiles/lavaTop_low.png
new file mode 100644
index 0000000..a220af4
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/lavaTop_low.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/leverLeft.png b/arcade/examples/platform_tutorial/images/tiles/leverLeft.png
new file mode 100644
index 0000000..3a57cae
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/leverLeft.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/leverMid.png b/arcade/examples/platform_tutorial/images/tiles/leverMid.png
new file mode 100644
index 0000000..523d012
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/leverMid.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/leverRight.png b/arcade/examples/platform_tutorial/images/tiles/leverRight.png
new file mode 100644
index 0000000..a8cb42c
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/leverRight.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/lockRed.png b/arcade/examples/platform_tutorial/images/tiles/lockRed.png
new file mode 100644
index 0000000..00ce745
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/lockRed.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/lockYellow.png b/arcade/examples/platform_tutorial/images/tiles/lockYellow.png
new file mode 100644
index 0000000..7173a53
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/lockYellow.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/mushroomRed.png b/arcade/examples/platform_tutorial/images/tiles/mushroomRed.png
new file mode 100644
index 0000000..ca88eec
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/mushroomRed.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/planet.png b/arcade/examples/platform_tutorial/images/tiles/planet.png
new file mode 100644
index 0000000..15a48b8
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/planet.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/planetCenter.png b/arcade/examples/platform_tutorial/images/tiles/planetCenter.png
new file mode 100644
index 0000000..b635b6f
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/planetCenter.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/planetCenter_rounded.png b/arcade/examples/platform_tutorial/images/tiles/planetCenter_rounded.png
new file mode 100644
index 0000000..acba26d
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/planetCenter_rounded.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/planetCliffAlt_left.png b/arcade/examples/platform_tutorial/images/tiles/planetCliffAlt_left.png
new file mode 100644
index 0000000..3206176
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/planetCliffAlt_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/planetCliffAlt_right.png b/arcade/examples/platform_tutorial/images/tiles/planetCliffAlt_right.png
new file mode 100644
index 0000000..10ee32e
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/planetCliffAlt_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/planetCliff_left.png b/arcade/examples/platform_tutorial/images/tiles/planetCliff_left.png
new file mode 100644
index 0000000..f4a8d47
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/planetCliff_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/planetCliff_right.png b/arcade/examples/platform_tutorial/images/tiles/planetCliff_right.png
new file mode 100644
index 0000000..152db3e
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/planetCliff_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/planetCorner_left.png b/arcade/examples/platform_tutorial/images/tiles/planetCorner_left.png
new file mode 100644
index 0000000..62e3e2a
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/planetCorner_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/planetCorner_right.png b/arcade/examples/platform_tutorial/images/tiles/planetCorner_right.png
new file mode 100644
index 0000000..2197fd2
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/planetCorner_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/planetHalf.png b/arcade/examples/platform_tutorial/images/tiles/planetHalf.png
new file mode 100644
index 0000000..59522fc
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/planetHalf.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/planetHalf_left.png b/arcade/examples/platform_tutorial/images/tiles/planetHalf_left.png
new file mode 100644
index 0000000..2ae6221
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/planetHalf_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/planetHalf_mid.png b/arcade/examples/platform_tutorial/images/tiles/planetHalf_mid.png
new file mode 100644
index 0000000..db09438
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/planetHalf_mid.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/planetHalf_right.png b/arcade/examples/platform_tutorial/images/tiles/planetHalf_right.png
new file mode 100644
index 0000000..7f8c4e0
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/planetHalf_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/planetHill_left.png b/arcade/examples/platform_tutorial/images/tiles/planetHill_left.png
new file mode 100644
index 0000000..601e362
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/planetHill_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/planetHill_right.png b/arcade/examples/platform_tutorial/images/tiles/planetHill_right.png
new file mode 100644
index 0000000..aa36c85
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/planetHill_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/planetLeft.png b/arcade/examples/platform_tutorial/images/tiles/planetLeft.png
new file mode 100644
index 0000000..1140caf
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/planetLeft.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/planetMid.png b/arcade/examples/platform_tutorial/images/tiles/planetMid.png
new file mode 100644
index 0000000..530cd69
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/planetMid.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/planetRight.png b/arcade/examples/platform_tutorial/images/tiles/planetRight.png
new file mode 100644
index 0000000..5a1a0f8
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/planetRight.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/plantPurple.png b/arcade/examples/platform_tutorial/images/tiles/plantPurple.png
new file mode 100644
index 0000000..41effce
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/plantPurple.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/rock.png b/arcade/examples/platform_tutorial/images/tiles/rock.png
new file mode 100644
index 0000000..d8624b1
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/rock.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/sand.png b/arcade/examples/platform_tutorial/images/tiles/sand.png
new file mode 100644
index 0000000..13194f0
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/sand.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/sandCenter.png b/arcade/examples/platform_tutorial/images/tiles/sandCenter.png
new file mode 100644
index 0000000..c2517e3
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/sandCenter.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/sandCenter_rounded.png b/arcade/examples/platform_tutorial/images/tiles/sandCenter_rounded.png
new file mode 100644
index 0000000..659b8a0
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/sandCenter_rounded.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/sandCliffAlt_left.png b/arcade/examples/platform_tutorial/images/tiles/sandCliffAlt_left.png
new file mode 100644
index 0000000..f358511
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/sandCliffAlt_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/sandCliffAlt_right.png b/arcade/examples/platform_tutorial/images/tiles/sandCliffAlt_right.png
new file mode 100644
index 0000000..ac61f89
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/sandCliffAlt_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/sandCliff_left.png b/arcade/examples/platform_tutorial/images/tiles/sandCliff_left.png
new file mode 100644
index 0000000..a919e7b
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/sandCliff_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/sandCliff_right.png b/arcade/examples/platform_tutorial/images/tiles/sandCliff_right.png
new file mode 100644
index 0000000..e4f0032
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/sandCliff_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/sandCorner_leftg.png b/arcade/examples/platform_tutorial/images/tiles/sandCorner_leftg.png
new file mode 100644
index 0000000..56690f2
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/sandCorner_leftg.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/sandCorner_right.png b/arcade/examples/platform_tutorial/images/tiles/sandCorner_right.png
new file mode 100644
index 0000000..164ea4e
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/sandCorner_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/sandHalf.png b/arcade/examples/platform_tutorial/images/tiles/sandHalf.png
new file mode 100644
index 0000000..210abd2
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/sandHalf.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/sandHalf_left.png b/arcade/examples/platform_tutorial/images/tiles/sandHalf_left.png
new file mode 100644
index 0000000..e46782c
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/sandHalf_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/sandHalf_mid.png b/arcade/examples/platform_tutorial/images/tiles/sandHalf_mid.png
new file mode 100644
index 0000000..b48e2ca
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/sandHalf_mid.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/sandHalf_right.png b/arcade/examples/platform_tutorial/images/tiles/sandHalf_right.png
new file mode 100644
index 0000000..58c67a2
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/sandHalf_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/sandHill_left.png b/arcade/examples/platform_tutorial/images/tiles/sandHill_left.png
new file mode 100644
index 0000000..3404419
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/sandHill_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/sandHill_right.png b/arcade/examples/platform_tutorial/images/tiles/sandHill_right.png
new file mode 100644
index 0000000..34e0d12
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/sandHill_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/sandLeft.png b/arcade/examples/platform_tutorial/images/tiles/sandLeft.png
new file mode 100644
index 0000000..7767e14
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/sandLeft.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/sandMid.png b/arcade/examples/platform_tutorial/images/tiles/sandMid.png
new file mode 100644
index 0000000..8ecd234
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/sandMid.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/sandRight.png b/arcade/examples/platform_tutorial/images/tiles/sandRight.png
new file mode 100644
index 0000000..e51d3e2
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/sandRight.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/signExit.png b/arcade/examples/platform_tutorial/images/tiles/signExit.png
new file mode 100644
index 0000000..fac42c0
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/signExit.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/signLeft.png b/arcade/examples/platform_tutorial/images/tiles/signLeft.png
new file mode 100644
index 0000000..0d12fff
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/signLeft.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/signRight.png b/arcade/examples/platform_tutorial/images/tiles/signRight.png
new file mode 100644
index 0000000..334b79e
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/signRight.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/snow.png b/arcade/examples/platform_tutorial/images/tiles/snow.png
new file mode 100644
index 0000000..1932a4a
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/snow.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/snowCenter.png b/arcade/examples/platform_tutorial/images/tiles/snowCenter.png
new file mode 100644
index 0000000..5908ca1
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/snowCenter.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/snowCenter_rounded.png b/arcade/examples/platform_tutorial/images/tiles/snowCenter_rounded.png
new file mode 100644
index 0000000..8c4689a
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/snowCenter_rounded.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/snowCliffAlt_left.png b/arcade/examples/platform_tutorial/images/tiles/snowCliffAlt_left.png
new file mode 100644
index 0000000..7fc57e5
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/snowCliffAlt_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/snowCliffAlt_right.png b/arcade/examples/platform_tutorial/images/tiles/snowCliffAlt_right.png
new file mode 100644
index 0000000..d797efc
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/snowCliffAlt_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/snowCliff_left.png b/arcade/examples/platform_tutorial/images/tiles/snowCliff_left.png
new file mode 100644
index 0000000..75c7cc6
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/snowCliff_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/snowCliff_right.png b/arcade/examples/platform_tutorial/images/tiles/snowCliff_right.png
new file mode 100644
index 0000000..0a78861
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/snowCliff_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/snowCorner_left.png b/arcade/examples/platform_tutorial/images/tiles/snowCorner_left.png
new file mode 100644
index 0000000..59882cb
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/snowCorner_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/snowCorner_right.png b/arcade/examples/platform_tutorial/images/tiles/snowCorner_right.png
new file mode 100644
index 0000000..e235c82
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/snowCorner_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/snowHalf.png b/arcade/examples/platform_tutorial/images/tiles/snowHalf.png
new file mode 100644
index 0000000..a9c09c5
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/snowHalf.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/snowHalf_left.png b/arcade/examples/platform_tutorial/images/tiles/snowHalf_left.png
new file mode 100644
index 0000000..179801e
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/snowHalf_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/snowHalf_mid.png b/arcade/examples/platform_tutorial/images/tiles/snowHalf_mid.png
new file mode 100644
index 0000000..4b8387a
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/snowHalf_mid.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/snowHalf_right.png b/arcade/examples/platform_tutorial/images/tiles/snowHalf_right.png
new file mode 100644
index 0000000..dfe967a
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/snowHalf_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/snowHill_left.png b/arcade/examples/platform_tutorial/images/tiles/snowHill_left.png
new file mode 100644
index 0000000..c20fe15
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/snowHill_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/snowHill_right.png b/arcade/examples/platform_tutorial/images/tiles/snowHill_right.png
new file mode 100644
index 0000000..8a4d9c8
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/snowHill_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/snowLeft.png b/arcade/examples/platform_tutorial/images/tiles/snowLeft.png
new file mode 100644
index 0000000..ae82d1d
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/snowLeft.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/snowMid.png b/arcade/examples/platform_tutorial/images/tiles/snowMid.png
new file mode 100644
index 0000000..bf541b4
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/snowMid.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/snowRight.png b/arcade/examples/platform_tutorial/images/tiles/snowRight.png
new file mode 100644
index 0000000..2cf0d64
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/snowRight.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/snow_pile.png b/arcade/examples/platform_tutorial/images/tiles/snow_pile.png
new file mode 100644
index 0000000..4ab1536
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/snow_pile.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/spikes.png b/arcade/examples/platform_tutorial/images/tiles/spikes.png
new file mode 100644
index 0000000..08dc172
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/spikes.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/stone.png b/arcade/examples/platform_tutorial/images/tiles/stone.png
new file mode 100644
index 0000000..9e1fe8f
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/stone.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/stoneCenter.png b/arcade/examples/platform_tutorial/images/tiles/stoneCenter.png
new file mode 100644
index 0000000..05393e0
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/stoneCenter.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/stoneCenter_rounded.png b/arcade/examples/platform_tutorial/images/tiles/stoneCenter_rounded.png
new file mode 100644
index 0000000..4d1be1f
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/stoneCenter_rounded.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/stoneCliffAlt_left.png b/arcade/examples/platform_tutorial/images/tiles/stoneCliffAlt_left.png
new file mode 100644
index 0000000..cc4f5b8
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/stoneCliffAlt_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/stoneCliffAlt_right.png b/arcade/examples/platform_tutorial/images/tiles/stoneCliffAlt_right.png
new file mode 100644
index 0000000..2983549
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/stoneCliffAlt_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/stoneCliff_left.png b/arcade/examples/platform_tutorial/images/tiles/stoneCliff_left.png
new file mode 100644
index 0000000..814b779
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/stoneCliff_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/stoneCliff_right.png b/arcade/examples/platform_tutorial/images/tiles/stoneCliff_right.png
new file mode 100644
index 0000000..a0b4b64
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/stoneCliff_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/stoneCorner_left.png b/arcade/examples/platform_tutorial/images/tiles/stoneCorner_left.png
new file mode 100644
index 0000000..d7e0fdf
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/stoneCorner_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/stoneCorner_right.png b/arcade/examples/platform_tutorial/images/tiles/stoneCorner_right.png
new file mode 100644
index 0000000..7de3cc6
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/stoneCorner_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/stoneHalf.png b/arcade/examples/platform_tutorial/images/tiles/stoneHalf.png
new file mode 100644
index 0000000..adea72f
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/stoneHalf.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/stoneHalf_left.png b/arcade/examples/platform_tutorial/images/tiles/stoneHalf_left.png
new file mode 100644
index 0000000..5b1355a
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/stoneHalf_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/stoneHalf_mid.png b/arcade/examples/platform_tutorial/images/tiles/stoneHalf_mid.png
new file mode 100644
index 0000000..82bdd72
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/stoneHalf_mid.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/stoneHalf_right.png b/arcade/examples/platform_tutorial/images/tiles/stoneHalf_right.png
new file mode 100644
index 0000000..43b6175
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/stoneHalf_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/stoneHill_left.png b/arcade/examples/platform_tutorial/images/tiles/stoneHill_left.png
new file mode 100644
index 0000000..25c3a06
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/stoneHill_left.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/stoneHill_right.png b/arcade/examples/platform_tutorial/images/tiles/stoneHill_right.png
new file mode 100644
index 0000000..8a33781
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/stoneHill_right.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/stoneLeft.png b/arcade/examples/platform_tutorial/images/tiles/stoneLeft.png
new file mode 100644
index 0000000..7cf175f
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/stoneLeft.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/stoneMid.png b/arcade/examples/platform_tutorial/images/tiles/stoneMid.png
new file mode 100644
index 0000000..e11b3c1
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/stoneMid.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/stoneRight.png b/arcade/examples/platform_tutorial/images/tiles/stoneRight.png
new file mode 100644
index 0000000..2f74870
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/stoneRight.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/switchGreen.png b/arcade/examples/platform_tutorial/images/tiles/switchGreen.png
new file mode 100644
index 0000000..713ca25
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/switchGreen.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/switchGreen_pressed.png b/arcade/examples/platform_tutorial/images/tiles/switchGreen_pressed.png
new file mode 100644
index 0000000..dcb5aab
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/switchGreen_pressed.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/switchRed.png b/arcade/examples/platform_tutorial/images/tiles/switchRed.png
new file mode 100644
index 0000000..3d25e67
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/switchRed.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/switchRed_pressed.png b/arcade/examples/platform_tutorial/images/tiles/switchRed_pressed.png
new file mode 100644
index 0000000..52da8db
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/switchRed_pressed.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/torch1.png b/arcade/examples/platform_tutorial/images/tiles/torch1.png
new file mode 100644
index 0000000..7bdfed7
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/torch1.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/torch2.png b/arcade/examples/platform_tutorial/images/tiles/torch2.png
new file mode 100644
index 0000000..91a4c35
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/torch2.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/torchOff.png b/arcade/examples/platform_tutorial/images/tiles/torchOff.png
new file mode 100644
index 0000000..a774b80
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/torchOff.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/water.png b/arcade/examples/platform_tutorial/images/tiles/water.png
new file mode 100644
index 0000000..a36a0bd
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/water.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/waterTop_high.png b/arcade/examples/platform_tutorial/images/tiles/waterTop_high.png
new file mode 100644
index 0000000..f550da5
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/waterTop_high.png differ
diff --git a/arcade/examples/platform_tutorial/images/tiles/waterTop_low.png b/arcade/examples/platform_tutorial/images/tiles/waterTop_low.png
new file mode 100644
index 0000000..d64bca1
Binary files /dev/null and b/arcade/examples/platform_tutorial/images/tiles/waterTop_low.png differ
diff --git a/arcade/examples/platform_tutorial/map.tmx b/arcade/examples/platform_tutorial/map.tmx
new file mode 100644
index 0000000..fedad61
--- /dev/null
+++ b/arcade/examples/platform_tutorial/map.tmx
@@ -0,0 +1,482 @@
+
+
diff --git a/arcade/examples/platform_tutorial/map2_level_1.tmx b/arcade/examples/platform_tutorial/map2_level_1.tmx
new file mode 100644
index 0000000..62f1730
--- /dev/null
+++ b/arcade/examples/platform_tutorial/map2_level_1.tmx
@@ -0,0 +1,574 @@
+
+
diff --git a/arcade/examples/platform_tutorial/map2_level_2.tmx b/arcade/examples/platform_tutorial/map2_level_2.tmx
new file mode 100644
index 0000000..d0ca07e
--- /dev/null
+++ b/arcade/examples/platform_tutorial/map2_level_2.tmx
@@ -0,0 +1,544 @@
+
+
diff --git a/arcade/examples/platform_tutorial/map_with_ladders.tmx b/arcade/examples/platform_tutorial/map_with_ladders.tmx
new file mode 100644
index 0000000..d8a655a
--- /dev/null
+++ b/arcade/examples/platform_tutorial/map_with_ladders.tmx
@@ -0,0 +1,100 @@
+
+
diff --git a/arcade/examples/platform_tutorial/more_tiles.tsx b/arcade/examples/platform_tutorial/more_tiles.tsx
new file mode 100644
index 0000000..7f9601b
--- /dev/null
+++ b/arcade/examples/platform_tutorial/more_tiles.tsx
@@ -0,0 +1,448 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/arcade/examples/platform_tutorial/sounds/Kenney Donate.url b/arcade/examples/platform_tutorial/sounds/Kenney Donate.url
new file mode 100644
index 0000000..a310f49
--- /dev/null
+++ b/arcade/examples/platform_tutorial/sounds/Kenney Donate.url
@@ -0,0 +1,2 @@
+[InternetShortcut]
+URL=http://kenney.nl/support
\ No newline at end of file
diff --git a/arcade/examples/platform_tutorial/sounds/Kenney Facebook.url b/arcade/examples/platform_tutorial/sounds/Kenney Facebook.url
new file mode 100644
index 0000000..e37cfb3
--- /dev/null
+++ b/arcade/examples/platform_tutorial/sounds/Kenney Facebook.url
@@ -0,0 +1,2 @@
+[InternetShortcut]
+URL=http://social.kenney.nl/facebook
\ No newline at end of file
diff --git a/arcade/examples/platform_tutorial/sounds/Kenney Twitter.url b/arcade/examples/platform_tutorial/sounds/Kenney Twitter.url
new file mode 100644
index 0000000..10be373
--- /dev/null
+++ b/arcade/examples/platform_tutorial/sounds/Kenney Twitter.url
@@ -0,0 +1,2 @@
+[InternetShortcut]
+URL=http://social.kenney.nl/twitter
\ No newline at end of file
diff --git a/arcade/examples/platform_tutorial/sounds/Kenney Website.url b/arcade/examples/platform_tutorial/sounds/Kenney Website.url
new file mode 100644
index 0000000..fbdde43
--- /dev/null
+++ b/arcade/examples/platform_tutorial/sounds/Kenney Website.url
@@ -0,0 +1,2 @@
+[InternetShortcut]
+URL=http://www.kenney.nl/
\ No newline at end of file
diff --git a/arcade/examples/platform_tutorial/sounds/License.txt b/arcade/examples/platform_tutorial/sounds/License.txt
new file mode 100644
index 0000000..8a9c62c
--- /dev/null
+++ b/arcade/examples/platform_tutorial/sounds/License.txt
@@ -0,0 +1,17 @@
+
+
+
+ Everything in this package is licensed CC0 (see below).
+
+ ------------------------------
+
+ License (Creative Commons Zero, CC0)
+ http://creativecommons.org/publicdomain/zero/1.0/
+
+ You may use these graphics in personal and commercial projects.
+ Credit (Kenney or www.kenney.nl) would be nice but is not mandatory.
+
+ ------------------------------
+
+ Donate: http://donate.kenney.nl/
+ Request: http://request.kenney.nl/
\ No newline at end of file
diff --git a/arcade/examples/platform_tutorial/sounds/coin1.wav b/arcade/examples/platform_tutorial/sounds/coin1.wav
new file mode 100644
index 0000000..85fe3e6
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/coin1.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/coin2.wav b/arcade/examples/platform_tutorial/sounds/coin2.wav
new file mode 100644
index 0000000..0372638
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/coin2.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/coin3.wav b/arcade/examples/platform_tutorial/sounds/coin3.wav
new file mode 100644
index 0000000..95f9bd9
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/coin3.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/coin4.wav b/arcade/examples/platform_tutorial/sounds/coin4.wav
new file mode 100644
index 0000000..9119c61
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/coin4.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/coin5.wav b/arcade/examples/platform_tutorial/sounds/coin5.wav
new file mode 100644
index 0000000..cad434b
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/coin5.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/error1.wav b/arcade/examples/platform_tutorial/sounds/error1.wav
new file mode 100644
index 0000000..e0203d2
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/error1.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/error2.wav b/arcade/examples/platform_tutorial/sounds/error2.wav
new file mode 100644
index 0000000..aefa6a2
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/error2.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/error3.wav b/arcade/examples/platform_tutorial/sounds/error3.wav
new file mode 100644
index 0000000..5ac209f
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/error3.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/error4.wav b/arcade/examples/platform_tutorial/sounds/error4.wav
new file mode 100644
index 0000000..61ddd8b
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/error4.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/error5.wav b/arcade/examples/platform_tutorial/sounds/error5.wav
new file mode 100644
index 0000000..c2c6b09
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/error5.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/explosion1.wav b/arcade/examples/platform_tutorial/sounds/explosion1.wav
new file mode 100644
index 0000000..184869e
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/explosion1.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/explosion2.wav b/arcade/examples/platform_tutorial/sounds/explosion2.wav
new file mode 100644
index 0000000..a90d9d3
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/explosion2.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/fall1.wav b/arcade/examples/platform_tutorial/sounds/fall1.wav
new file mode 100644
index 0000000..da01bfc
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/fall1.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/fall2.wav b/arcade/examples/platform_tutorial/sounds/fall2.wav
new file mode 100644
index 0000000..fc4d3d7
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/fall2.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/fall3.wav b/arcade/examples/platform_tutorial/sounds/fall3.wav
new file mode 100644
index 0000000..60b9851
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/fall3.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/fall4.wav b/arcade/examples/platform_tutorial/sounds/fall4.wav
new file mode 100644
index 0000000..a9c49ba
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/fall4.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/gameover1.wav b/arcade/examples/platform_tutorial/sounds/gameover1.wav
new file mode 100644
index 0000000..db5431e
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/gameover1.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/gameover2.wav b/arcade/examples/platform_tutorial/sounds/gameover2.wav
new file mode 100644
index 0000000..8242a5a
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/gameover2.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/gameover3.wav b/arcade/examples/platform_tutorial/sounds/gameover3.wav
new file mode 100644
index 0000000..77b9ee9
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/gameover3.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/gameover4.wav b/arcade/examples/platform_tutorial/sounds/gameover4.wav
new file mode 100644
index 0000000..ecfa369
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/gameover4.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/gameover5.wav b/arcade/examples/platform_tutorial/sounds/gameover5.wav
new file mode 100644
index 0000000..f3d5049
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/gameover5.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/hit1.wav b/arcade/examples/platform_tutorial/sounds/hit1.wav
new file mode 100644
index 0000000..251b238
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/hit1.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/hit2.wav b/arcade/examples/platform_tutorial/sounds/hit2.wav
new file mode 100644
index 0000000..d7d0a47
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/hit2.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/hit3.wav b/arcade/examples/platform_tutorial/sounds/hit3.wav
new file mode 100644
index 0000000..7875ced
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/hit3.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/hit4.wav b/arcade/examples/platform_tutorial/sounds/hit4.wav
new file mode 100644
index 0000000..7151a25
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/hit4.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/hit5.wav b/arcade/examples/platform_tutorial/sounds/hit5.wav
new file mode 100644
index 0000000..189da3b
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/hit5.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/hurt1.wav b/arcade/examples/platform_tutorial/sounds/hurt1.wav
new file mode 100644
index 0000000..392314f
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/hurt1.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/hurt2.wav b/arcade/examples/platform_tutorial/sounds/hurt2.wav
new file mode 100644
index 0000000..d3addb6
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/hurt2.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/hurt3.wav b/arcade/examples/platform_tutorial/sounds/hurt3.wav
new file mode 100644
index 0000000..ea3e7c5
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/hurt3.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/hurt4.wav b/arcade/examples/platform_tutorial/sounds/hurt4.wav
new file mode 100644
index 0000000..04384c0
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/hurt4.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/hurt5.wav b/arcade/examples/platform_tutorial/sounds/hurt5.wav
new file mode 100644
index 0000000..c5a8593
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/hurt5.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/jump1.wav b/arcade/examples/platform_tutorial/sounds/jump1.wav
new file mode 100644
index 0000000..ec27da0
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/jump1.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/jump2.wav b/arcade/examples/platform_tutorial/sounds/jump2.wav
new file mode 100644
index 0000000..576e3aa
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/jump2.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/jump3.wav b/arcade/examples/platform_tutorial/sounds/jump3.wav
new file mode 100644
index 0000000..2ecad1a
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/jump3.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/jump4.wav b/arcade/examples/platform_tutorial/sounds/jump4.wav
new file mode 100644
index 0000000..137b71c
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/jump4.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/jump5.wav b/arcade/examples/platform_tutorial/sounds/jump5.wav
new file mode 100644
index 0000000..9bccf3c
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/jump5.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/laser1.wav b/arcade/examples/platform_tutorial/sounds/laser1.wav
new file mode 100644
index 0000000..e2bb70f
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/laser1.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/laser2.wav b/arcade/examples/platform_tutorial/sounds/laser2.wav
new file mode 100644
index 0000000..ee05304
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/laser2.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/laser3.wav b/arcade/examples/platform_tutorial/sounds/laser3.wav
new file mode 100644
index 0000000..c9ed32f
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/laser3.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/laser4.wav b/arcade/examples/platform_tutorial/sounds/laser4.wav
new file mode 100644
index 0000000..700bde2
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/laser4.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/laser5.wav b/arcade/examples/platform_tutorial/sounds/laser5.wav
new file mode 100644
index 0000000..1af501b
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/laser5.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/lose1.wav b/arcade/examples/platform_tutorial/sounds/lose1.wav
new file mode 100644
index 0000000..a6f9cf5
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/lose1.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/lose2.wav b/arcade/examples/platform_tutorial/sounds/lose2.wav
new file mode 100644
index 0000000..e292bcf
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/lose2.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/lose3.wav b/arcade/examples/platform_tutorial/sounds/lose3.wav
new file mode 100644
index 0000000..d2e178d
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/lose3.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/lose4.wav b/arcade/examples/platform_tutorial/sounds/lose4.wav
new file mode 100644
index 0000000..3e45bae
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/lose4.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/lose5.wav b/arcade/examples/platform_tutorial/sounds/lose5.wav
new file mode 100644
index 0000000..ca2df76
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/lose5.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/readme.txt b/arcade/examples/platform_tutorial/sounds/readme.txt
new file mode 100644
index 0000000..3d9ed43
--- /dev/null
+++ b/arcade/examples/platform_tutorial/sounds/readme.txt
@@ -0,0 +1,4 @@
+These assets are a subset of what is available from Kenney.nl.
+
+If you like his work, please go support is by purchasing his full asset packs
+at https://kenney.nl/
\ No newline at end of file
diff --git a/arcade/examples/platform_tutorial/sounds/secret2.wav b/arcade/examples/platform_tutorial/sounds/secret2.wav
new file mode 100644
index 0000000..b35ba6e
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/secret2.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/secret4.wav b/arcade/examples/platform_tutorial/sounds/secret4.wav
new file mode 100644
index 0000000..c190191
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/secret4.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/upgrade1.wav b/arcade/examples/platform_tutorial/sounds/upgrade1.wav
new file mode 100644
index 0000000..0877e8e
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/upgrade1.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/upgrade2.wav b/arcade/examples/platform_tutorial/sounds/upgrade2.wav
new file mode 100644
index 0000000..71d44b1
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/upgrade2.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/upgrade3.wav b/arcade/examples/platform_tutorial/sounds/upgrade3.wav
new file mode 100644
index 0000000..19919b5
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/upgrade3.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/upgrade4.wav b/arcade/examples/platform_tutorial/sounds/upgrade4.wav
new file mode 100644
index 0000000..a5ab16e
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/upgrade4.wav differ
diff --git a/arcade/examples/platform_tutorial/sounds/upgrade5.wav b/arcade/examples/platform_tutorial/sounds/upgrade5.wav
new file mode 100644
index 0000000..37c81dc
Binary files /dev/null and b/arcade/examples/platform_tutorial/sounds/upgrade5.wav differ
diff --git a/arcade/examples/procedural_caves_bsp.py b/arcade/examples/procedural_caves_bsp.py
new file mode 100644
index 0000000..2a1ac37
--- /dev/null
+++ b/arcade/examples/procedural_caves_bsp.py
@@ -0,0 +1,455 @@
+"""
+This example procedurally develops a random cave based on
+Binary Space Partitioning (BSP)
+
+For more information, see:
+http://roguebasin.roguelikedevelopment.org/index.php?title=Basic_BSP_Dungeon_generation
+https://github.com/DanaL/RLDungeonGenerator
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.procedural_caves_bsp
+"""
+
+import random
+import arcade
+import timeit
+import math
+import os
+
+# Sprite scaling. Make this larger, like 0.5 to zoom in and add
+# 'mystery' to what you can see. Make it smaller, like 0.1 to see
+# more of the map.
+WALL_SPRITE_SCALING = 0.25
+PLAYER_SPRITE_SCALING = 0.20
+
+WALL_SPRITE_SIZE = 128 * WALL_SPRITE_SCALING
+
+# How big the grid is
+GRID_WIDTH = 100
+GRID_HEIGHT = 100
+
+AREA_WIDTH = GRID_WIDTH * WALL_SPRITE_SIZE
+AREA_HEIGHT = GRID_HEIGHT * WALL_SPRITE_SIZE
+
+# How fast the player moves
+MOVEMENT_SPEED = 5
+
+# How close the player can get to the edge before we scroll.
+VIEWPORT_MARGIN = 300
+
+# How big the window is
+WINDOW_WIDTH = 800
+WINDOW_HEIGHT = 600
+WINDOW_TITLE = "Procedural Caves BSP Example"
+
+MERGE_SPRITES = True
+
+
+class Room:
+ def __init__(self, r, c, h, w):
+ self.row = r
+ self.col = c
+ self.height = h
+ self.width = w
+
+
+class RLDungeonGenerator:
+ def __init__(self, w, h):
+ self.MAX = 15 # Cutoff for when we want to stop dividing sections
+ self.width = w
+ self.height = h
+ self.leaves = []
+ self.dungeon = []
+ self.rooms = []
+
+ for h in range(self.height):
+ row = []
+ for w in range(self.width):
+ row.append('#')
+
+ self.dungeon.append(row)
+
+ def random_split(self, min_row, min_col, max_row, max_col):
+ # We want to keep splitting until the sections get down to the threshold
+ seg_height = max_row - min_row
+ seg_width = max_col - min_col
+
+ if seg_height < self.MAX and seg_width < self.MAX:
+ self.leaves.append((min_row, min_col, max_row, max_col))
+ elif seg_height < self.MAX <= seg_width:
+ self.split_on_vertical(min_row, min_col, max_row, max_col)
+ elif seg_height >= self.MAX > seg_width:
+ self.split_on_horizontal(min_row, min_col, max_row, max_col)
+ else:
+ if random.random() < 0.5:
+ self.split_on_horizontal(min_row, min_col, max_row, max_col)
+ else:
+ self.split_on_vertical(min_row, min_col, max_row, max_col)
+
+ def split_on_horizontal(self, min_row, min_col, max_row, max_col):
+ split = (min_row + max_row) // 2 + random.choice((-2, -1, 0, 1, 2))
+ self.random_split(min_row, min_col, split, max_col)
+ self.random_split(split + 1, min_col, max_row, max_col)
+
+ def split_on_vertical(self, min_row, min_col, max_row, max_col):
+ split = (min_col + max_col) // 2 + random.choice((-2, -1, 0, 1, 2))
+ self.random_split(min_row, min_col, max_row, split)
+ self.random_split(min_row, split + 1, max_row, max_col)
+
+ def carve_rooms(self):
+ for leaf in self.leaves:
+ # We don't want to fill in every possible room or the
+ # dungeon looks too uniform
+ if random.random() > 0.80:
+ continue
+ section_width = leaf[3] - leaf[1]
+ section_height = leaf[2] - leaf[0]
+
+ # The actual room's height and width will be 60-100% of the
+ # available section.
+ room_width = round(random.randrange(60, 100) / 100 * section_width)
+ room_height = round(random.randrange(60, 100) / 100 * section_height)
+
+ # If the room doesn't occupy the entire section we are carving it from,
+ # 'jiggle' it a bit in the square
+ if section_height > room_height:
+ room_start_row = leaf[0] + random.randrange(section_height - room_height)
+ else:
+ room_start_row = leaf[0]
+
+ if section_width > room_width:
+ room_start_col = leaf[1] + random.randrange(section_width - room_width)
+ else:
+ room_start_col = leaf[1]
+
+ self.rooms.append(Room(room_start_row, room_start_col, room_height, room_width))
+ for r in range(room_start_row, room_start_row + room_height):
+ for c in range(room_start_col, room_start_col + room_width):
+ self.dungeon[r][c] = '.'
+
+ def are_rooms_adjacent(self, room1, room2):
+ adj_rows = []
+ adj_cols = []
+ for r in range(room1.row, room1.row + room1.height):
+ if room2.row <= r < room2.row + room2.height:
+ adj_rows.append(r)
+
+ for c in range(room1.col, room1.col + room1.width):
+ if room2.col <= c < room2.col + room2.width:
+ adj_cols.append(c)
+
+ return adj_rows, adj_cols
+
+ def distance_between_rooms(self, room1, room2):
+ centre1 = (room1.row + room1.height // 2, room1.col + room1.width // 2)
+ centre2 = (room2.row + room2.height // 2, room2.col + room2.width // 2)
+
+ return math.sqrt((centre1[0] - centre2[0]) ** 2 + (centre1[1] - centre2[1]) ** 2)
+
+ def carve_corridor_between_rooms(self, room1, room2):
+ if room2[2] == 'rows':
+ row = random.choice(room2[1])
+ # Figure out which room is to the left of the other
+ if room1.col + room1.width < room2[0].col:
+ start_col = room1.col + room1.width
+ end_col = room2[0].col
+ else:
+ start_col = room2[0].col + room2[0].width
+ end_col = room1.col
+ for c in range(start_col, end_col):
+ self.dungeon[row][c] = '.'
+
+ if end_col - start_col >= 4:
+ self.dungeon[row][start_col] = '+'
+ self.dungeon[row][end_col - 1] = '+'
+ elif start_col == end_col - 1:
+ self.dungeon[row][start_col] = '+'
+ else:
+ col = random.choice(room2[1])
+ # Figure out which room is above the other
+ if room1.row + room1.height < room2[0].row:
+ start_row = room1.row + room1.height
+ end_row = room2[0].row
+ else:
+ start_row = room2[0].row + room2[0].height
+ end_row = room1.row
+
+ for r in range(start_row, end_row):
+ self.dungeon[r][col] = '.'
+
+ if end_row - start_row >= 4:
+ self.dungeon[start_row][col] = '+'
+ self.dungeon[end_row - 1][col] = '+'
+ elif start_row == end_row - 1:
+ self.dungeon[start_row][col] = '+'
+
+ # Find two nearby rooms that are in difference groups, draw
+ # a corridor between them and merge the groups
+ def find_closest_unconnect_groups(self, groups, room_dict):
+ shortest_distance = 99999
+ start = None
+ start_group = None
+ nearest = None
+
+ for group in groups:
+ for room in group:
+ key = (room.row, room.col)
+ for other in room_dict[key]:
+ if not other[0] in group and other[3] < shortest_distance:
+ shortest_distance = other[3]
+ start = room
+ nearest = other
+ start_group = group
+
+ self.carve_corridor_between_rooms(start, nearest)
+
+ # Merge the groups
+ other_group = None
+ for group in groups:
+ if nearest[0] in group:
+ other_group = group
+ break
+
+ start_group += other_group
+ groups.remove(other_group)
+
+ def connect_rooms(self):
+ # Build a dictionary containing an entry for each room. Each bucket will
+ # hold a list of the adjacent rooms, weather they are adjacent along rows or
+ # columns and the distance between them.
+ #
+ # Also build the initial groups (which start of as a list of individual rooms)
+ groups = []
+ room_dict = {}
+ for room in self.rooms:
+ key = (room.row, room.col)
+ room_dict[key] = []
+ for other in self.rooms:
+ other_key = (other.row, other.col)
+ if key == other_key:
+ continue
+ adj = self.are_rooms_adjacent(room, other)
+ if len(adj[0]) > 0:
+ room_dict[key].append((other, adj[0], 'rows', self.distance_between_rooms(room, other)))
+ elif len(adj[1]) > 0:
+ room_dict[key].append((other, adj[1], 'cols', self.distance_between_rooms(room, other)))
+
+ groups.append([room])
+
+ while len(groups) > 1:
+ self.find_closest_unconnect_groups(groups, room_dict)
+
+ def generate_map(self):
+ self.random_split(1, 1, self.height - 1, self.width - 1)
+ self.carve_rooms()
+ self.connect_rooms()
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ self.grid = None
+ self.wall_list = None
+ self.player_list = None
+ self.player_sprite = None
+ self.view_bottom = 0
+ self.view_left = 0
+ self.physics_engine = None
+
+ self.processing_time = 0
+ self.draw_time = 0
+
+ arcade.set_background_color(arcade.color.BLACK)
+
+ def setup(self):
+ self.wall_list = arcade.SpriteList()
+ self.player_list = arcade.SpriteList()
+
+ # Create cave system using a 2D grid
+ dg = RLDungeonGenerator(GRID_WIDTH, GRID_HEIGHT)
+ dg.generate_map()
+
+ # Create sprites based on 2D grid
+ if not MERGE_SPRITES:
+ # This is the simple-to-understand method. Each grid location
+ # is a sprite.
+ for row in range(dg.height):
+ for column in range(dg.width):
+ value = dg.dungeon[row][column]
+ if value.sqr == '#':
+ wall = arcade.Sprite("images/grassCenter.png", WALL_SPRITE_SCALING)
+ wall.center_x = column * WALL_SPRITE_SIZE + WALL_SPRITE_SIZE / 2
+ wall.center_y = row * WALL_SPRITE_SIZE + WALL_SPRITE_SIZE / 2
+ self.wall_list.append(wall)
+ else:
+ # This uses new Arcade 1.3.1 features, that allow me to create a
+ # larger sprite with a repeating texture. So if there are multiple
+ # cells in a row with a wall, we merge them into one sprite, with a
+ # repeating texture for each cell. This reduces our sprite count.
+ for row in range(dg.height):
+ column = 0
+ while column < dg.width:
+ while column < dg.width and dg.dungeon[row][column] != '#':
+ column += 1
+ start_column = column
+ while column < dg.width and dg.dungeon[row][column] == '#':
+ column += 1
+ end_column = column - 1
+
+ column_count = end_column - start_column + 1
+ column_mid = (start_column + end_column) / 2
+
+ wall = arcade.Sprite("images/grassCenter.png", WALL_SPRITE_SCALING,
+ repeat_count_x=column_count)
+ wall.center_x = column_mid * WALL_SPRITE_SIZE + WALL_SPRITE_SIZE / 2
+ wall.center_y = row * WALL_SPRITE_SIZE + WALL_SPRITE_SIZE / 2
+ wall.width = WALL_SPRITE_SIZE * column_count
+ self.wall_list.append(wall)
+
+ # Set up the player
+ self.player_sprite = arcade.Sprite("images/character.png", PLAYER_SPRITE_SCALING)
+ self.player_list.append(self.player_sprite)
+
+ # Randomly place the player. If we are in a wall, repeat until we aren't.
+ placed = False
+ while not placed:
+
+ # Randomly position
+ self.player_sprite.center_x = random.randrange(AREA_WIDTH)
+ self.player_sprite.center_y = random.randrange(AREA_HEIGHT)
+
+ # Are we in a wall?
+ walls_hit = arcade.check_for_collision_with_list(self.player_sprite, self.wall_list)
+ if len(walls_hit) == 0:
+ # Not in a wall! Success!
+ placed = True
+
+ self.physics_engine = arcade.PhysicsEngineSimple(self.player_sprite,
+ self.wall_list)
+
+ def on_draw(self):
+ """ Render the screen. """
+
+ # Start timing how long this takes
+ draw_start_time = timeit.default_timer()
+
+ # This command should happen before we start drawing. It will clear
+ # the screen to the background color, and erase what we drew last frame.
+ arcade.start_render()
+
+ # Draw the sprites
+ self.wall_list.draw()
+ self.player_list.draw()
+
+ # Draw info on the screen
+ sprite_count = len(self.wall_list)
+
+ output = f"Sprite Count: {sprite_count}"
+ arcade.draw_text(output,
+ self.view_left + 20,
+ WINDOW_HEIGHT - 20 + self.view_bottom,
+ arcade.color.WHITE, 16)
+
+ output = f"Drawing time: {self.draw_time:.3f}"
+ arcade.draw_text(output,
+ self.view_left + 20,
+ WINDOW_HEIGHT - 40 + self.view_bottom,
+ arcade.color.WHITE, 16)
+
+ output = f"Processing time: {self.processing_time:.3f}"
+ arcade.draw_text(output,
+ self.view_left + 20,
+ WINDOW_HEIGHT - 60 + self.view_bottom,
+ arcade.color.WHITE, 16)
+
+ self.draw_time = timeit.default_timer() - draw_start_time
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ if key == arcade.key.UP:
+ self.player_sprite.change_y = MOVEMENT_SPEED
+ elif key == arcade.key.DOWN:
+ self.player_sprite.change_y = -MOVEMENT_SPEED
+ elif key == arcade.key.LEFT:
+ self.player_sprite.change_x = -MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT:
+ self.player_sprite.change_x = MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.UP or key == arcade.key.DOWN:
+ self.player_sprite.change_y = 0
+ elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ start_time = timeit.default_timer()
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.physics_engine.update()
+
+ # --- Manage Scrolling ---
+
+ # Track if we need to change the viewport
+
+ changed = False
+
+ # Scroll left
+ left_bndry = self.view_left + VIEWPORT_MARGIN
+ if self.player_sprite.left < left_bndry:
+ self.view_left -= left_bndry - self.player_sprite.left
+ changed = True
+
+ # Scroll right
+ right_bndry = self.view_left + WINDOW_WIDTH - VIEWPORT_MARGIN
+ if self.player_sprite.right > right_bndry:
+ self.view_left += self.player_sprite.right - right_bndry
+ changed = True
+
+ # Scroll up
+ top_bndry = self.view_bottom + WINDOW_HEIGHT - VIEWPORT_MARGIN
+ if self.player_sprite.top > top_bndry:
+ self.view_bottom += self.player_sprite.top - top_bndry
+ changed = True
+
+ # Scroll down
+ bottom_bndry = self.view_bottom + VIEWPORT_MARGIN
+ if self.player_sprite.bottom < bottom_bndry:
+ self.view_bottom -= bottom_bndry - self.player_sprite.bottom
+ changed = True
+
+ if changed:
+ arcade.set_viewport(self.view_left,
+ WINDOW_WIDTH + self.view_left,
+ self.view_bottom,
+ WINDOW_HEIGHT + self.view_bottom)
+
+ # Save the time it took to do this.
+ self.processing_time = timeit.default_timer() - start_time
+
+
+def main():
+ game = MyGame(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE)
+ game.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/procedural_caves_cellular.py b/arcade/examples/procedural_caves_cellular.py
new file mode 100644
index 0000000..0ac9671
--- /dev/null
+++ b/arcade/examples/procedural_caves_cellular.py
@@ -0,0 +1,317 @@
+"""
+This example procedurally develops a random cave based on cellular automata.
+
+For more information, see:
+https://gamedevelopment.tutsplus.com/tutorials/generate-random-cave-levels-using-cellular-automata--gamedev-9664
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.procedural_caves_cellular
+"""
+
+import random
+import arcade
+import timeit
+import os
+
+# Sprite scaling. Make this larger, like 0.5 to zoom in and add
+# 'mystery' to what you can see. Make it smaller, like 0.1 to see
+# more of the map.
+SPRITE_SCALING = 0.125
+SPRITE_SIZE = 128 * SPRITE_SCALING
+
+# How big the grid is
+GRID_WIDTH = 400
+GRID_HEIGHT = 300
+
+# Parameters for cellular automata
+CHANCE_TO_START_ALIVE = 0.4
+DEATH_LIMIT = 3
+BIRTH_LIMIT = 4
+NUMBER_OF_STEPS = 4
+
+# How fast the player moves
+MOVEMENT_SPEED = 5
+
+# How close the player can get to the edge before we scroll.
+VIEWPORT_MARGIN = 300
+
+# How big the window is
+WINDOW_WIDTH = 800
+WINDOW_HEIGHT = 600
+WINDOW_TITLE = "Procedural Caves Cellular Automata Example"
+# If true, rather than each block being a separate sprite, blocks on rows
+# will be merged into one sprite.
+MERGE_SPRITES = False
+
+
+def create_grid(width, height):
+ """ Create a two-dimensional grid of specified size. """
+ return [[0 for x in range(width)] for y in range(height)]
+
+
+def initialize_grid(grid):
+ """ Randomly set grid locations to on/off based on chance. """
+ height = len(grid)
+ width = len(grid[0])
+ for row in range(height):
+ for column in range(width):
+ if random.random() <= CHANCE_TO_START_ALIVE:
+ grid[row][column] = 1
+
+
+def count_alive_neighbors(grid, x, y):
+ """ Count neighbors that are alive. """
+ height = len(grid)
+ width = len(grid[0])
+ alive_count = 0
+ for i in range(-1, 2):
+ for j in range(-1, 2):
+ neighbor_x = x + i
+ neighbor_y = y + j
+ if i == 0 and j == 0:
+ continue
+ elif neighbor_x < 0 or neighbor_y < 0 or neighbor_y >= height or neighbor_x >= width:
+ # Edges are considered alive. Makes map more likely to appear naturally closed.
+ alive_count += 1
+ elif grid[neighbor_y][neighbor_x] == 1:
+ alive_count += 1
+ return alive_count
+
+
+def do_simulation_step(old_grid):
+ """ Run a step of the cellular automaton. """
+ height = len(old_grid)
+ width = len(old_grid[0])
+ new_grid = create_grid(width, height)
+ for x in range(width):
+ for y in range(height):
+ alive_neighbors = count_alive_neighbors(old_grid, x, y)
+ if old_grid[y][x] == 1:
+ if alive_neighbors < DEATH_LIMIT:
+ new_grid[y][x] = 0
+ else:
+ new_grid[y][x] = 1
+ else:
+ if alive_neighbors > BIRTH_LIMIT:
+ new_grid[y][x] = 1
+ else:
+ new_grid[y][x] = 0
+ return new_grid
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self):
+ super().__init__(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE, resizable=True)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ self.grid = None
+ self.wall_list = None
+ self.player_list = None
+ self.player_sprite = None
+ self.view_bottom = 0
+ self.view_left = 0
+ self.draw_time = 0
+ self.physics_engine = None
+
+ arcade.set_background_color(arcade.color.BLACK)
+
+ def setup(self):
+ self.wall_list = arcade.SpriteList(use_spatial_hash=True)
+ self.player_list = arcade.SpriteList()
+
+ # Create cave system using a 2D grid
+ self.grid = create_grid(GRID_WIDTH, GRID_HEIGHT)
+ initialize_grid(self.grid)
+ for step in range(NUMBER_OF_STEPS):
+ self.grid = do_simulation_step(self.grid)
+
+ # Create sprites based on 2D grid
+ if not MERGE_SPRITES:
+ # This is the simple-to-understand method. Each grid location
+ # is a sprite.
+ for row in range(GRID_HEIGHT):
+ for column in range(GRID_WIDTH):
+ if self.grid[row][column] == 1:
+ wall = arcade.Sprite("images/grassCenter.png", SPRITE_SCALING)
+ wall.center_x = column * SPRITE_SIZE + SPRITE_SIZE / 2
+ wall.center_y = row * SPRITE_SIZE + SPRITE_SIZE / 2
+ self.wall_list.append(wall)
+ else:
+ # This uses new Arcade 1.3.1 features, that allow me to create a
+ # larger sprite with a repeating texture. So if there are multiple
+ # cells in a row with a wall, we merge them into one sprite, with a
+ # repeating texture for each cell. This reduces our sprite count.
+ for row in range(GRID_HEIGHT):
+ column = 0
+ while column < GRID_WIDTH:
+ while column < GRID_WIDTH and self.grid[row][column] == 0:
+ column += 1
+ start_column = column
+ while column < GRID_WIDTH and self.grid[row][column] == 1:
+ column += 1
+ end_column = column - 1
+
+ column_count = end_column - start_column + 1
+ column_mid = (start_column + end_column) / 2
+
+ wall = arcade.Sprite("images/grassCenter.png", SPRITE_SCALING,
+ repeat_count_x=column_count)
+ wall.center_x = column_mid * SPRITE_SIZE + SPRITE_SIZE / 2
+ wall.center_y = row * SPRITE_SIZE + SPRITE_SIZE / 2
+ wall.width = SPRITE_SIZE * column_count
+ self.wall_list.append(wall)
+
+ # Set up the player
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING)
+ self.player_list.append(self.player_sprite)
+
+ # Randomly place the player. If we are in a wall, repeat until we aren't.
+ placed = False
+ while not placed:
+
+ # Randomly position
+ max_x = GRID_WIDTH * SPRITE_SIZE
+ max_y = GRID_HEIGHT * SPRITE_SIZE
+ self.player_sprite.center_x = random.randrange(max_x)
+ self.player_sprite.center_y = random.randrange(max_y)
+
+ # Are we in a wall?
+ walls_hit = arcade.check_for_collision_with_list(self.player_sprite, self.wall_list)
+ if len(walls_hit) == 0:
+ # Not in a wall! Success!
+ placed = True
+
+ self.physics_engine = arcade.PhysicsEngineSimple(self.player_sprite,
+ self.wall_list)
+
+ def on_draw(self):
+ """ Render the screen. """
+
+ # Start timing how long this takes
+ draw_start_time = timeit.default_timer()
+
+ # This command should happen before we start drawing. It will clear
+ # the screen to the background color, and erase what we drew last frame.
+ arcade.start_render()
+
+ # Draw the sprites
+ self.wall_list.draw()
+ self.player_list.draw()
+
+ # Draw info on the screen
+ sprite_count = len(self.wall_list)
+
+ output = f"Sprite Count: {sprite_count}"
+ arcade.draw_text(output,
+ self.view_left + 20,
+ self.height - 20 + self.view_bottom,
+ arcade.color.WHITE, 16)
+
+ output = f"Drawing time: {self.draw_time:.3f}"
+ arcade.draw_text(output,
+ self.view_left + 20,
+ self.height - 40 + self.view_bottom,
+ arcade.color.WHITE, 16)
+
+ output = f"Processing time: {self.processing_time:.3f}"
+ arcade.draw_text(output,
+ self.view_left + 20,
+ self.height - 60 + self.view_bottom,
+ arcade.color.WHITE, 16)
+
+ self.draw_time = timeit.default_timer() - draw_start_time
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ if key == arcade.key.UP:
+ self.player_sprite.change_y = MOVEMENT_SPEED
+ elif key == arcade.key.DOWN:
+ self.player_sprite.change_y = -MOVEMENT_SPEED
+ elif key == arcade.key.LEFT:
+ self.player_sprite.change_x = -MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT:
+ self.player_sprite.change_x = MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.UP or key == arcade.key.DOWN:
+ self.player_sprite.change_y = 0
+ elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
+ self.player_sprite.change_x = 0
+
+ def on_resize(self, width, height):
+
+ arcade.set_viewport(self.view_left,
+ self.width + self.view_left,
+ self.view_bottom,
+ self.height + self.view_bottom)
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ start_time = timeit.default_timer()
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.physics_engine.update()
+
+ # --- Manage Scrolling ---
+
+ # Track if we need to change the viewport
+
+ changed = False
+
+ # Scroll left
+ left_bndry = self.view_left + VIEWPORT_MARGIN
+ if self.player_sprite.left < left_bndry:
+ self.view_left -= left_bndry - self.player_sprite.left
+ changed = True
+
+ # Scroll right
+ right_bndry = self.view_left + WINDOW_WIDTH - VIEWPORT_MARGIN
+ if self.player_sprite.right > right_bndry:
+ self.view_left += self.player_sprite.right - right_bndry
+ changed = True
+
+ # Scroll up
+ top_bndry = self.view_bottom + WINDOW_HEIGHT - VIEWPORT_MARGIN
+ if self.player_sprite.top > top_bndry:
+ self.view_bottom += self.player_sprite.top - top_bndry
+ changed = True
+
+ # Scroll down
+ bottom_bndry = self.view_bottom + VIEWPORT_MARGIN
+ if self.player_sprite.bottom < bottom_bndry:
+ self.view_bottom -= bottom_bndry - self.player_sprite.bottom
+ changed = True
+
+ if changed:
+ arcade.set_viewport(self.view_left,
+ self.width + self.view_left,
+ self.view_bottom,
+ self.height + self.view_bottom)
+
+ # Save the time it took to do this.
+ self.processing_time = timeit.default_timer() - start_time
+
+
+def main():
+ game = MyGame()
+ game.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/pyinstaller/build-exe.bat b/arcade/examples/pyinstaller/build-exe.bat
new file mode 100644
index 0000000..8a03bc9
--- /dev/null
+++ b/arcade/examples/pyinstaller/build-exe.bat
@@ -0,0 +1,8 @@
+rem @echo off
+for /f %%i in ('python -c "import site; print(site.getsitepackages()[0])"') do set PYTHON_ROOT="%%i"
+rem copy %PYTHON_ROOT%\Lib\site-packages\arcade\Win64\avbin.dll .
+rem copy avbin.dll avbin64.dll
+rem pyinstaller --exclude-module tkinter --add-data resources;resources --add-data ./avbin64.dll;. --add-data ./avbin.dll;Win64 --onefile --noconsole sample.py
+rem del avbin.dll
+rem del avbin64.dll
+rem pause
diff --git a/arcade/examples/pyinstaller/build-exe.py b/arcade/examples/pyinstaller/build-exe.py
new file mode 100644
index 0000000..f10764a
--- /dev/null
+++ b/arcade/examples/pyinstaller/build-exe.py
@@ -0,0 +1,16 @@
+import site
+import shutil
+import os
+import subprocess
+
+
+python_root = site.getsitepackages()[0]
+
+shutil.copyfile(f"{python_root}/Lib/site-packages/arcade/Win64/avbin.dll", "avbin.dll")
+shutil.copyfile(f"avbin.dll", "avbin64.dll")
+sp = subprocess.run(["pyinstaller", "--exclude-module", "tkinter", "--add-data", "resources;resources", "--add-data", "./avbin64.dll;.", "--add-data", "./avbin.dll;Win64", "--onefile", "--noconsole", "sample.py"], stdout=subprocess.PIPE)
+# rem pyinstaller --exclude-module tkinter --add-data resources;resources --add-data ./avbin64.dll;. --add-data ./avbin.dll;Win64 --onefile --noconsole sample.py
+# print(sp.stdout)
+os.unlink("avbin.dll")
+os.unlink("avbin64.dll")
+
diff --git a/arcade/examples/pyinstaller/resources/cat-meow.wav b/arcade/examples/pyinstaller/resources/cat-meow.wav
new file mode 100644
index 0000000..9eb0a7c
Binary files /dev/null and b/arcade/examples/pyinstaller/resources/cat-meow.wav differ
diff --git a/arcade/examples/pyinstaller/resources/character.png b/arcade/examples/pyinstaller/resources/character.png
new file mode 100644
index 0000000..9ad34ca
Binary files /dev/null and b/arcade/examples/pyinstaller/resources/character.png differ
diff --git a/arcade/examples/pyinstaller/sample.py b/arcade/examples/pyinstaller/sample.py
new file mode 100644
index 0000000..6b9c5f4
--- /dev/null
+++ b/arcade/examples/pyinstaller/sample.py
@@ -0,0 +1,36 @@
+import arcade
+import pyglet
+import time
+import sys
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 500
+TITLE = 'Arcade cx_Freeze Sample'
+BACKGROUND_COLOR = arcade.color.WHITE
+
+
+def resource_path(file):
+ path = 'resources/' + file
+ # are we in a frozen environment (e.g. pyInstaller)?
+ if getattr(sys, 'frozen', False):
+ path = sys._MEIPASS.replace('\\', '/') + '/' + path
+ return path
+
+
+def main():
+ arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, TITLE)
+ arcade.set_background_color(BACKGROUND_COLOR)
+ arcade.start_render()
+ arcade.draw_circle_filled(400, 250, 100, arcade.color.BLACK)
+ # load image
+ image = arcade.load_texture(resource_path('character.png'))
+ arcade.draw_texture_rectangle(200, 250, image.width, image.height, image)
+ # load sound
+ sound = arcade.sound.load_sound(resource_path('cat-meow.wav'))
+ arcade.sound.play_sound(sound)
+ arcade.finish_render()
+ arcade.run()
+ return
+
+
+main()
diff --git a/arcade/examples/pymunk_box_stacks.py b/arcade/examples/pymunk_box_stacks.py
new file mode 100644
index 0000000..2c2f3c5
--- /dev/null
+++ b/arcade/examples/pymunk_box_stacks.py
@@ -0,0 +1,215 @@
+"""
+Use Pymunk physics engine.
+
+For more info on Pymunk see:
+http://www.pymunk.org/en/latest/
+
+To install pymunk:
+pip install pymunk
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.pymunk_box_stacks
+
+Click and drag with the mouse to move the boxes.
+"""
+
+import arcade
+import pymunk
+import timeit
+import math
+import os
+
+SCREEN_WIDTH = 1800
+SCREEN_HEIGHT = 800
+SCREEN_TITLE = "Pymunk test"
+
+
+class PhysicsSprite(arcade.Sprite):
+ def __init__(self, pymunk_shape, filename):
+ super().__init__(filename, center_x=pymunk_shape.body.position.x, center_y=pymunk_shape.body.position.y)
+ self.pymunk_shape = pymunk_shape
+
+
+class CircleSprite(PhysicsSprite):
+ def __init__(self, pymunk_shape, filename):
+ super().__init__(pymunk_shape, filename)
+ self.width = pymunk_shape.radius * 2
+ self.height = pymunk_shape.radius * 2
+
+
+class BoxSprite(PhysicsSprite):
+ def __init__(self, pymunk_shape, filename, width, height):
+ super().__init__(pymunk_shape, filename)
+ self.width = width
+ self.height = height
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ arcade.set_background_color(arcade.color.DARK_SLATE_GRAY)
+
+ # -- Pymunk
+ self.space = pymunk.Space()
+ self.space.iterations = 35
+ self.space.gravity = (0.0, -900.0)
+
+ # Lists of sprites or lines
+ self.sprite_list = arcade.SpriteList()
+ self.static_lines = []
+
+ # Used for dragging shapes around with the mouse
+ self.shape_being_dragged = None
+ self.last_mouse_position = 0, 0
+
+ self.draw_time = 0
+ self.processing_time = 0
+
+ # Create the floor
+ floor_height = 80
+ body = pymunk.Body(body_type=pymunk.Body.STATIC)
+ shape = pymunk.Segment(body, [0, floor_height], [SCREEN_WIDTH, floor_height], 0.0)
+ shape.friction = 10
+ self.space.add(shape)
+ self.static_lines.append(shape)
+
+ # Create the stacks of boxes
+ for row in range(10):
+ for column in range(10):
+ size = 32
+ mass = 1.0
+ x = 500 + column * 32
+ y = (floor_height + size / 2) + row * size
+ moment = pymunk.moment_for_box(mass, (size, size))
+ body = pymunk.Body(mass, moment)
+ body.position = pymunk.Vec2d(x, y)
+ shape = pymunk.Poly.create_box(body, (size, size))
+ shape.elasticity = 0.2
+ shape.friction = 0.9
+ self.space.add(body, shape)
+ # body.sleep()
+
+ sprite = BoxSprite(shape, "images/boxCrate_double.png", width=size, height=size)
+ self.sprite_list.append(sprite)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Start timing how long this takes
+ draw_start_time = timeit.default_timer()
+
+ # Draw all the sprites
+ self.sprite_list.draw()
+
+ # Draw the lines that aren't sprites
+ for line in self.static_lines:
+ body = line.body
+
+ pv1 = body.position + line.a.rotated(body.angle)
+ pv2 = body.position + line.b.rotated(body.angle)
+ arcade.draw_line(pv1.x, pv1.y, pv2.x, pv2.y, arcade.color.WHITE, 2)
+
+ # Display timings
+ output = f"Processing time: {self.processing_time:.3f}"
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 20, arcade.color.WHITE, 12)
+
+ output = f"Drawing time: {self.draw_time:.3f}"
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 40, arcade.color.WHITE, 12)
+
+ self.draw_time = timeit.default_timer() - draw_start_time
+
+ def on_mouse_press(self, x, y, button, modifiers):
+ if button == arcade.MOUSE_BUTTON_LEFT:
+ self.last_mouse_position = x, y
+ # See if we clicked on anything
+ shape_list = self.space.point_query((x, y), 1, pymunk.ShapeFilter())
+
+ # If we did, remember what we clicked on
+ if len(shape_list) > 0:
+ self.shape_being_dragged = shape_list[0]
+
+ elif button == arcade.MOUSE_BUTTON_RIGHT:
+ # With right mouse button, shoot a heavy coin fast.
+ mass = 60
+ radius = 10
+ inertia = pymunk.moment_for_circle(mass, 0, radius, (0, 0))
+ body = pymunk.Body(mass, inertia)
+ body.position = x, y
+ body.velocity = 2000, 0
+ shape = pymunk.Circle(body, radius, pymunk.Vec2d(0, 0))
+ shape.friction = 0.3
+ self.space.add(body, shape)
+
+ sprite = CircleSprite(shape, "images/coin_01.png")
+ self.sprite_list.append(sprite)
+
+ def on_mouse_release(self, x, y, button, modifiers):
+ if button == arcade.MOUSE_BUTTON_LEFT:
+ # Release the item we are holding (if any)
+ self.shape_being_dragged = None
+
+ def on_mouse_motion(self, x, y, dx, dy):
+ if self.shape_being_dragged is not None:
+ # If we are holding an object, move it with the mouse
+ self.last_mouse_position = x, y
+ self.shape_being_dragged.shape.body.position = self.last_mouse_position
+ self.shape_being_dragged.shape.body.velocity = dx * 20, dy * 20
+
+ def update(self, delta_time):
+ start_time = timeit.default_timer()
+
+ # Check for balls that fall off the screen
+ for sprite in self.sprite_list:
+ if sprite.pymunk_shape.body.position.y < 0:
+ # Remove balls from physics space
+ self.space.remove(sprite.pymunk_shape, sprite.pymunk_shape.body)
+ # Remove balls from physics list
+ sprite.kill()
+
+ # Update physics
+ # Use a constant time step, don't use delta_time
+ # See "Game loop / moving time forward"
+ # http://www.pymunk.org/en/latest/overview.html#game-loop-moving-time-forward
+ self.space.step(1 / 60.0)
+
+ # If we are dragging an object, make sure it stays with the mouse. Otherwise
+ # gravity will drag it down.
+ if self.shape_being_dragged is not None:
+ self.shape_being_dragged.shape.body.position = self.last_mouse_position
+ self.shape_being_dragged.shape.body.velocity = 0, 0
+
+ # Move sprites to where physics objects are
+ for sprite in self.sprite_list:
+ sprite.center_x = sprite.pymunk_shape.body.position.x
+ sprite.center_y = sprite.pymunk_shape.body.position.y
+ sprite.angle = math.degrees(sprite.pymunk_shape.body.angle)
+
+ # Save the time it took to do this.
+ self.processing_time = timeit.default_timer() - start_time
+
+
+def main():
+ MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/pymunk_joint_builder.py b/arcade/examples/pymunk_joint_builder.py
new file mode 100644
index 0000000..91e2962
--- /dev/null
+++ b/arcade/examples/pymunk_joint_builder.py
@@ -0,0 +1,322 @@
+"""
+Pymunk 2
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.pymunk_joint_builder
+"""
+import arcade
+import pymunk
+import timeit
+import math
+import os
+
+SCREEN_WIDTH = 1200
+SCREEN_HEIGHT = 800
+SCREEN_TITLE = "Pymunk 2 Example"
+
+"""
+Key bindings:
+
+1 - Drag mode
+2 - Make box mode
+3 - Make PinJoint mode
+4 - Make DampedSpring mode
+
+S - No gravity or friction
+L - Layout, no gravity, lots of friction
+G - Gravity, little bit of friction
+
+Right-click, fire coin
+
+"""
+
+
+class PhysicsSprite(arcade.Sprite):
+ def __init__(self, pymunk_shape, filename):
+ super().__init__(filename, center_x=pymunk_shape.body.position.x, center_y=pymunk_shape.body.position.y)
+ self.pymunk_shape = pymunk_shape
+
+
+class CircleSprite(PhysicsSprite):
+ def __init__(self, pymunk_shape, filename):
+ super().__init__(pymunk_shape, filename)
+ self.width = pymunk_shape.radius * 2
+ self.height = pymunk_shape.radius * 2
+
+
+class BoxSprite(PhysicsSprite):
+ def __init__(self, pymunk_shape, filename, width, height):
+ super().__init__(pymunk_shape, filename)
+ self.width = width
+ self.height = height
+
+
+class MyApplication(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ arcade.set_background_color(arcade.color.DARK_SLATE_GRAY)
+
+ # -- Pymunk
+ self.space = pymunk.Space()
+ self.space.gravity = (0.0, -900.0)
+
+ # Lists of sprites or lines
+ self.sprite_list = arcade.SpriteList()
+ self.static_lines = []
+
+ # Used for dragging shapes aruond with the mouse
+ self.shape_being_dragged = None
+ self.last_mouse_position = 0, 0
+
+ self.processing_time_text = None
+ self.draw_time_text = None
+ self.draw_mode_text = None
+ self.shape_1 = None
+ self.shape_2 = None
+ self.draw_time = 0
+ self.processing_time = 0
+ self.joints = []
+
+ self.physics = "Normal"
+ self.mode = "Make Box"
+
+ # Create the floor
+ self.floor_height = 80
+ body = pymunk.Body(body_type=pymunk.Body.STATIC)
+ shape = pymunk.Segment(body, [0, self.floor_height], [SCREEN_WIDTH, self.floor_height], 0.0)
+ shape.friction = 10
+ self.space.add(shape)
+ self.static_lines.append(shape)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Start timing how long this takes
+ draw_start_time = timeit.default_timer()
+
+ # Draw all the sprites
+ self.sprite_list.draw()
+
+ # Draw the lines that aren't sprites
+ for line in self.static_lines:
+ body = line.body
+
+ pv1 = body.position + line.a.rotated(body.angle)
+ pv2 = body.position + line.b.rotated(body.angle)
+ arcade.draw_line(pv1.x, pv1.y, pv2.x, pv2.y, arcade.color.WHITE, 2)
+
+ for joint in self.joints:
+ color = arcade.color.WHITE
+ if isinstance(joint, pymunk.DampedSpring):
+ color = arcade.color.DARK_GREEN
+ arcade.draw_line(joint.a.position.x, joint.a.position.y, joint.b.position.x, joint.b.position.y, color, 3)
+
+ # arcade.draw_text(output, 10, 20, arcade.color.WHITE, 14)
+ # Display timings
+ output = f"Processing time: {self.processing_time:.3f}"
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 20, arcade.color.WHITE)
+
+ output = f"Drawing time: {self.draw_time:.3f}"
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 40, arcade.color.WHITE)
+
+ self.draw_time = timeit.default_timer() - draw_start_time
+
+ output = f"Mode: {self.mode}"
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 60, arcade.color.WHITE)
+
+ output = f"Physics: {self.physics}"
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 80, arcade.color.WHITE)
+
+ def make_box(self, x, y):
+ size = 45
+ mass = 12.0
+ moment = pymunk.moment_for_box(mass, (size, size))
+ body = pymunk.Body(mass, moment)
+ body.position = pymunk.Vec2d(x, y)
+ shape = pymunk.Poly.create_box(body, (size, size))
+ shape.friction = 0.3
+ self.space.add(body, shape)
+
+ sprite = BoxSprite(shape, "images/boxCrate_double.png", width=size, height=size)
+ self.sprite_list.append(sprite)
+
+ def make_circle(self, x, y):
+ size = 20
+ mass = 12.0
+ moment = pymunk.moment_for_circle(mass, 0, size, (0, 0))
+ body = pymunk.Body(mass, moment)
+ body.position = pymunk.Vec2d(x, y)
+ shape = pymunk.Circle(body, size, pymunk.Vec2d(0, 0))
+ shape.friction = 0.3
+ self.space.add(body, shape)
+
+ sprite = CircleSprite(shape, "images/coin_01.png")
+ self.sprite_list.append(sprite)
+
+ def make_pin_joint(self, x, y):
+ shape_selected = self.get_shape(x, y)
+ if shape_selected is None:
+ return
+
+ if self.shape_1 is None:
+ print("Shape 1 Selected")
+ self.shape_1 = shape_selected
+ elif self.shape_2 is None:
+ print("Shape 2 Selected")
+ self.shape_2 = shape_selected
+ joint = pymunk.PinJoint(self.shape_1.shape.body, self.shape_2.shape.body)
+ self.space.add(joint)
+ self.joints.append(joint)
+ self.shape_1 = None
+ self.shape_2 = None
+ print("Joint Made")
+
+ def make_damped_spring(self, x, y):
+ shape_selected = self.get_shape(x, y)
+ if shape_selected is None:
+ return
+
+ if self.shape_1 is None:
+ print("Shape 1 Selected")
+ self.shape_1 = shape_selected
+ elif self.shape_2 is None:
+ print("Shape 2 Selected")
+ self.shape_2 = shape_selected
+ joint = pymunk.DampedSpring(self.shape_1.shape.body, self.shape_2.shape.body, (0, 0), (0, 0), 45, 300, 30)
+ self.space.add(joint)
+ self.joints.append(joint)
+ self.shape_1 = None
+ self.shape_2 = None
+ print("Joint Made")
+
+ def get_shape(self, x, y):
+ # See if we clicked on anything
+ shape_list = self.space.point_query((x, y), 1, pymunk.ShapeFilter())
+
+ # If we did, remember what we clicked on
+ if len(shape_list) > 0:
+ shape = shape_list[0]
+ else:
+ shape = None
+
+ return shape
+
+ def on_mouse_press(self, x, y, button, modifiers):
+
+ if button == 1 and self.mode == "Drag":
+ self.last_mouse_position = x, y
+ self.shape_being_dragged = self.get_shape(x, y)
+
+ elif button == 1 and self.mode == "Make Box":
+ self.make_box(x, y)
+
+ elif button == 1 and self.mode == "Make Circle":
+ self.make_circle(x, y)
+
+ elif button == 1 and self.mode == "Make PinJoint":
+ self.make_pin_joint(x, y)
+
+ elif button == 1 and self.mode == "Make DampedSpring":
+ self.make_damped_spring(x, y)
+
+ elif button == 4:
+ # With right mouse button, shoot a heavy coin fast.
+ mass = 60
+ radius = 10
+ inertia = pymunk.moment_for_circle(mass, 0, radius, (0, 0))
+ body = pymunk.Body(mass, inertia)
+ body.position = x, y
+ body.velocity = 2000, 0
+ shape = pymunk.Circle(body, radius, pymunk.Vec2d(0, 0))
+ shape.friction = 0.3
+ self.space.add(body, shape)
+
+ sprite = CircleSprite(shape, "images/coin_01.png")
+ self.sprite_list.append(sprite)
+
+ def on_mouse_release(self, x, y, button, modifiers):
+ if button == 1:
+ # Release the item we are holding (if any)
+ self.shape_being_dragged = None
+
+ def on_mouse_motion(self, x, y, dx, dy):
+ if self.shape_being_dragged is not None:
+ # If we are holding an object, move it with the mouse
+ self.last_mouse_position = x, y
+ self.shape_being_dragged.shape.body.position = self.last_mouse_position
+ self.shape_being_dragged.shape.body.velocity = dx * 20, dy * 20
+
+ def on_key_press(self, symbol: int, modifiers: int):
+ if symbol == arcade.key.KEY_1:
+ self.mode = "Drag"
+ elif symbol == arcade.key.KEY_2:
+ self.mode = "Make Box"
+ elif symbol == arcade.key.KEY_3:
+ self.mode = "Make Circle"
+
+ elif symbol == arcade.key.KEY_4:
+ self.mode = "Make PinJoint"
+ elif symbol == arcade.key.KEY_5:
+ self.mode = "Make DampedSpring"
+
+ elif symbol == arcade.key.S:
+ self.space.gravity = (0.0, 0.0)
+ self.space.damping = 1
+ self.physics = "Outer Space"
+ elif symbol == arcade.key.L:
+ self.space.gravity = (0.0, 0.0)
+ self.space.damping = 0
+ self.physics = "Layout"
+ elif symbol == arcade.key.G:
+ self.space.damping = 0.95
+ self.space.gravity = (0.0, -900.0)
+ self.physics = "Normal"
+
+ def update(self, delta_time):
+ start_time = timeit.default_timer()
+
+ # Check for balls that fall off the screen
+ for sprite in self.sprite_list:
+ if sprite.pymunk_shape.body.position.y < 0:
+ # Remove balls from physics space
+ self.space.remove(sprite.pymunk_shape, sprite.pymunk_shape.body)
+ # Remove balls from physics list
+ sprite.kill()
+
+ # Update physics
+ self.space.step(1 / 80.0)
+
+ # If we are dragging an object, make sure it stays with the mouse. Otherwise
+ # gravity will drag it down.
+ if self.shape_being_dragged is not None:
+ self.shape_being_dragged.shape.body.position = self.last_mouse_position
+ self.shape_being_dragged.shape.body.velocity = 0, 0
+
+ # Move sprites to where physics objects are
+ for sprite in self.sprite_list:
+ sprite.center_x = sprite.pymunk_shape.body.position.x
+ sprite.center_y = sprite.pymunk_shape.body.position.y
+ sprite.angle = math.degrees(sprite.pymunk_shape.body.angle)
+
+ # Save the time it took to do this.
+ self.processing_time = timeit.default_timer() - start_time
+
+
+window = MyApplication(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+arcade.run()
diff --git a/arcade/examples/pymunk_pegboard.py b/arcade/examples/pymunk_pegboard.py
new file mode 100644
index 0000000..8aa3757
--- /dev/null
+++ b/arcade/examples/pymunk_pegboard.py
@@ -0,0 +1,178 @@
+"""
+Use Pymunk physics engine.
+
+For more info on Pymunk see:
+http://www.pymunk.org/en/latest/
+
+To install pymunk:
+pip install pymunk
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.pymunk_pegboard
+
+Click and drag with the mouse to move the boxes.
+"""
+
+import arcade
+import pymunk
+import random
+import timeit
+import math
+import os
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 800
+SCREEN_TITLE = "Pymunk Pegboard Example"
+
+
+class CircleSprite(arcade.Sprite):
+ def __init__(self, filename, pymunk_shape):
+ super().__init__(filename, center_x=pymunk_shape.body.position.x, center_y=pymunk_shape.body.position.y)
+ self.width = pymunk_shape.radius * 2
+ self.height = pymunk_shape.radius * 2
+ self.pymunk_shape = pymunk_shape
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ self.peg_list = arcade.SpriteList()
+ self.ball_list = arcade.SpriteList()
+ arcade.set_background_color(arcade.color.DARK_SLATE_GRAY)
+
+ self.draw_time = 0
+ self.processing_time = 0
+ self.time = 0
+
+ # -- Pymunk
+ self.space = pymunk.Space()
+ self.space.gravity = (0.0, -900.0)
+
+ self.static_lines = []
+
+ self.ticks_to_next_ball = 10
+
+ body = pymunk.Body(body_type=pymunk.Body.STATIC)
+ shape = pymunk.Segment(body, [0, 10], [SCREEN_WIDTH, 10], 0.0)
+ shape.friction = 10
+ self.space.add(shape)
+ self.static_lines.append(shape)
+
+ body = pymunk.Body(body_type=pymunk.Body.STATIC)
+ shape = pymunk.Segment(body, [SCREEN_WIDTH - 50, 10], [SCREEN_WIDTH, 30], 0.0)
+ shape.friction = 10
+ self.space.add(shape)
+ self.static_lines.append(shape)
+
+ body = pymunk.Body(body_type=pymunk.Body.STATIC)
+ shape = pymunk.Segment(body, [50, 10], [0, 30], 0.0)
+ shape.friction = 10
+ self.space.add(shape)
+ self.static_lines.append(shape)
+
+ radius = 20
+ separation = 150
+ for row in range(6):
+ for column in range(6):
+ x = column * separation + (separation // 2 * (row % 2))
+ y = row * separation + separation // 2
+ body = pymunk.Body(body_type=pymunk.Body.STATIC)
+ body.position = x, y
+ shape = pymunk.Circle(body, radius, pymunk.Vec2d(0, 0))
+ shape.friction = 0.3
+ self.space.add(body, shape)
+
+ sprite = CircleSprite("images/bumper.png", shape)
+ self.peg_list.append(sprite)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ draw_start_time = timeit.default_timer()
+ self.peg_list.draw()
+ self.ball_list.draw()
+
+ for line in self.static_lines:
+ body = line.body
+
+ pv1 = body.position + line.a.rotated(body.angle)
+ pv2 = body.position + line.b.rotated(body.angle)
+ arcade.draw_line(pv1.x, pv1.y, pv2.x, pv2.y, arcade.color.WHITE, 2)
+
+ # Display timings
+ output = f"Processing time: {self.processing_time:.3f}"
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 20, arcade.color.WHITE, 12)
+
+ output = f"Drawing time: {self.draw_time:.3f}"
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 40, arcade.color.WHITE, 12)
+
+ self.draw_time = timeit.default_timer() - draw_start_time
+
+ def update(self, delta_time):
+ start_time = timeit.default_timer()
+
+ self.ticks_to_next_ball -= 1
+ if self.ticks_to_next_ball <= 0:
+ self.ticks_to_next_ball = 20
+ mass = 0.5
+ radius = 15
+ inertia = pymunk.moment_for_circle(mass, 0, radius, (0, 0))
+ body = pymunk.Body(mass, inertia)
+ x = random.randint(0, SCREEN_WIDTH)
+ y = SCREEN_HEIGHT
+ body.position = x, y
+ shape = pymunk.Circle(body, radius, pymunk.Vec2d(0, 0))
+ shape.friction = 0.3
+ self.space.add(body, shape)
+
+ sprite = CircleSprite("images/coin_01.png", shape)
+ self.ball_list.append(sprite)
+
+ # Check for balls that fall off the screen
+ for ball in self.ball_list:
+ if ball.pymunk_shape.body.position.y < 0:
+ # Remove balls from physics space
+ self.space.remove(ball.pymunk_shape, ball.pymunk_shape.body)
+ # Remove balls from physics list
+ ball.kill()
+
+ # Update physics
+ # Use a constant time step, don't use delta_time
+ # See "Game loop / moving time forward"
+ # http://www.pymunk.org/en/latest/overview.html#game-loop-moving-time-forward
+ self.space.step(1 / 60.0)
+
+ # Move sprites to where physics objects are
+ for ball in self.ball_list:
+ ball.center_x = ball.pymunk_shape.body.position.x
+ ball.center_y = ball.pymunk_shape.body.position.y
+ ball.angle = math.degrees(ball.pymunk_shape.body.angle)
+
+ self.time = timeit.default_timer() - start_time
+
+
+def main():
+ MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/pymunk_platformer/__init__.py b/arcade/examples/pymunk_platformer/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/arcade/examples/pymunk_platformer/constants.py b/arcade/examples/pymunk_platformer/constants.py
new file mode 100644
index 0000000..b1b5261
--- /dev/null
+++ b/arcade/examples/pymunk_platformer/constants.py
@@ -0,0 +1,24 @@
+# Size of the window
+SCREEN_WIDTH = 1200
+SCREEN_HEIGHT = 800
+SCREEN_TITLE = 'Pymunk Platformer'
+
+# Default friction used for sprites, unless otherwise specified
+DEFAULT_FRICTION = 0.2
+
+# Default mass used for sprites
+DEFAULT_MASS = 1
+
+# Gravity
+GRAVITY = (0.0, -900.0)
+
+# Player forces
+PLAYER_MOVE_FORCE = 700
+PLAYER_JUMP_IMPULSE = 600
+PLAYER_PUNCH_IMPULSE = 600
+
+# Grid-size
+SPRITE_SIZE = 64
+
+# How close we get to the edge before scrolling
+VIEWPORT_MARGIN = 100
diff --git a/arcade/examples/pymunk_platformer/create_level.py b/arcade/examples/pymunk_platformer/create_level.py
new file mode 100644
index 0000000..65af252
--- /dev/null
+++ b/arcade/examples/pymunk_platformer/create_level.py
@@ -0,0 +1,43 @@
+"""
+This code creates the layout of level one.
+"""
+import pymunk
+
+from arcade.examples.pymunk_platformer.physics_utility import (
+ PymunkSprite,
+)
+from constants import *
+
+
+def create_floor(space, sprite_list):
+ """ Create a bunch of blocks for the floor. """
+ for x in range(-1000, 2000, SPRITE_SIZE):
+ y = SPRITE_SIZE / 2
+ sprite = PymunkSprite("../images/grassMid.png", x, y, scale=0.5, body_type=pymunk.Body.STATIC)
+ sprite_list.append(sprite)
+ space.add(sprite.body, sprite.shape)
+
+
+def create_platform(space, sprite_list, start_x, y, count):
+ """ Create a platform """
+ for x in range(start_x, start_x + count * SPRITE_SIZE + 1, SPRITE_SIZE):
+ sprite = PymunkSprite("../images/grassMid.png", x, y, scale=0.5, body_type=pymunk.Body.STATIC)
+ sprite_list.append(sprite)
+ space.add(sprite.body, sprite.shape)
+
+
+def create_level_1(space, static_sprite_list, dynamic_sprite_list):
+ """ Create level one. """
+ create_floor(space, static_sprite_list)
+ create_platform(space, static_sprite_list, 200, SPRITE_SIZE * 3, 3)
+ create_platform(space, static_sprite_list, 500, SPRITE_SIZE * 6, 3)
+ create_platform(space, static_sprite_list, 200, SPRITE_SIZE * 9, 3)
+
+ # Create the stacks of boxes
+ for column in range(6):
+ for row in range(column):
+ x = 600 + column * SPRITE_SIZE
+ y = (3 * SPRITE_SIZE / 2) + row * SPRITE_SIZE
+ sprite = PymunkSprite("../images/boxCrate_double.png", x, y, scale=0.5, friction=0.4)
+ dynamic_sprite_list.append(sprite)
+ space.add(sprite.body, sprite.shape)
diff --git a/arcade/examples/pymunk_platformer/main_window.py b/arcade/examples/pymunk_platformer/main_window.py
new file mode 100644
index 0000000..1be3d95
--- /dev/null
+++ b/arcade/examples/pymunk_platformer/main_window.py
@@ -0,0 +1,309 @@
+"""
+Use Pymunk physics engine.
+
+For more info on Pymunk see:
+http://www.pymunk.org/en/latest/
+
+To install pymunk:
+pip install pymunk
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.pymunk_platformer.main_window
+
+Click and drag with the mouse to move the boxes.
+"""
+
+import timeit
+import os
+import arcade
+import pymunk
+
+from arcade.examples.pymunk_platformer.create_level import create_level_1
+from arcade.examples.pymunk_platformer.physics_utility import (
+ PymunkSprite,
+ check_grounding,
+ resync_physics_sprites,
+)
+
+from constants import *
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ arcade.set_background_color(arcade.color.DARK_SLATE_GRAY)
+
+ # -- Pymunk
+ self.space = pymunk.Space()
+ self.space.gravity = GRAVITY
+
+ # Physics joint used for grabbing items
+ self.grab_joint = None
+
+ # Lists of sprites
+ self.dynamic_sprite_list = arcade.SpriteList()
+ self.static_sprite_list = arcade.SpriteList()
+
+ # Used for dragging shapes around with the mouse
+ self.shape_being_dragged = None
+ self.last_mouse_position = 0, 0
+
+ # Draw and processing timings
+ self.draw_time = 0
+ self.processing_time = 0
+
+ # Current force applied to the player for movement by keyboard
+ self.force = (0, 0)
+
+ # Set the viewport boundaries
+ # These numbers set where we have 'scrolled' to.
+ self.view_left = 0
+ self.view_bottom = 0
+
+ create_level_1(self.space, self.static_sprite_list, self.dynamic_sprite_list)
+
+ # Create player
+ x = 50
+ y = (SPRITE_SIZE + SPRITE_SIZE / 2)
+ self.player = PymunkSprite("../images/character.png", x, y, scale=0.5, moment=pymunk.inf, mass=1)
+ self.dynamic_sprite_list.append(self.player)
+ self.space.add(self.player.body, self.player.shape)
+
+ def on_draw(self):
+ """ Render the screen. """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Start timing how long this takes
+ draw_start_time = timeit.default_timer()
+
+ # Draw all the sprites
+ self.static_sprite_list.draw()
+ self.dynamic_sprite_list.draw()
+
+ # Display timings
+ output = f"Processing time: {self.processing_time:.3f}"
+ arcade.draw_text(output, 20 + self.view_left, SCREEN_HEIGHT - 20 + self.view_bottom, arcade.color.WHITE, 12)
+
+ output = f"Drawing time: {self.draw_time:.3f}"
+ arcade.draw_text(output, 20 + self.view_left, SCREEN_HEIGHT - 40 + self.view_bottom, arcade.color.WHITE, 12)
+
+ # Display instructions
+ output = "Use the mouse to move boxes, space to punch, hold G to grab an item to the right."
+ arcade.draw_text(output, 20 + self.view_left, SCREEN_HEIGHT - 60 + self.view_bottom, arcade.color.WHITE, 12)
+
+ self.draw_time = timeit.default_timer() - draw_start_time
+
+ def on_mouse_press(self, x, y, button, modifiers):
+ """ Handle mouse down events """
+
+ if button == arcade.MOUSE_BUTTON_LEFT:
+
+ # Store where the mouse is clicked. Adjust accordingly if we've
+ # scrolled the viewport.
+ self.last_mouse_position = (x + self.view_left, y + self.view_bottom)
+
+ # See if we clicked on any physics object
+ shape_list = self.space.point_query(self.last_mouse_position, 1, pymunk.ShapeFilter())
+
+ # If we did, remember what we clicked on
+ if len(shape_list) > 0:
+ self.shape_being_dragged = shape_list[0]
+
+ def on_mouse_release(self, x, y, button, modifiers):
+ """ Handle mouse up events """
+
+ if button == arcade.MOUSE_BUTTON_LEFT:
+ # Release the item we are holding (if any)
+ self.shape_being_dragged = None
+
+ def on_mouse_motion(self, x, y, dx, dy):
+ """ Handle mouse motion events """
+
+ if self.shape_being_dragged is not None:
+ # If we are holding an object, move it with the mouse
+ self.last_mouse_position = (x + self.view_left, y + self.view_bottom)
+ self.shape_being_dragged.shape.body.position = self.last_mouse_position
+ self.shape_being_dragged.shape.body.velocity = dx * 20, dy * 20
+
+ def scroll_viewport(self):
+ """ Manage scrolling of the viewport. """
+
+ # Flipped to true if we need to scroll
+ changed = False
+
+ # Scroll left
+ left_bndry = self.view_left + VIEWPORT_MARGIN
+ if self.player.left < left_bndry:
+ self.view_left -= left_bndry - self.player.left
+ changed = True
+
+ # Scroll right
+ right_bndry = self.view_left + SCREEN_WIDTH - VIEWPORT_MARGIN
+ if self.player.right > right_bndry:
+ self.view_left += self.player.right - right_bndry
+ changed = True
+
+ # Scroll up
+ top_bndry = self.view_bottom + SCREEN_HEIGHT - VIEWPORT_MARGIN
+ if self.player.top > top_bndry:
+ self.view_bottom += self.player.top - top_bndry
+ changed = True
+
+ # Scroll down
+ bottom_bndry = self.view_bottom + VIEWPORT_MARGIN
+ if self.player.bottom < bottom_bndry:
+ self.view_bottom -= bottom_bndry - self.player.bottom
+ changed = True
+
+ if changed:
+ arcade.set_viewport(self.view_left,
+ SCREEN_WIDTH + self.view_left,
+ self.view_bottom,
+ SCREEN_HEIGHT + self.view_bottom)
+
+ def update(self, delta_time):
+ """ Update the sprites """
+
+ # Keep track of how long this function takes.
+ start_time = timeit.default_timer()
+
+ # If we have force to apply to the player (from hitting the arrow
+ # keys), apply it.
+ self.player.body.apply_force_at_local_point(self.force, (0, 0))
+
+ # check_collision(self.player)
+
+ # See if the player is standing on an item.
+ # If she is, apply opposite force to the item below her.
+ # So if she moves left, the box below her will have
+ # a force to move to the right.
+ grounding = check_grounding(self.player)
+ if self.force[0] and grounding and grounding['body']:
+ grounding['body'].apply_force_at_world_point((-self.force[0], 0), grounding['position'])
+
+ # Check for sprites that fall off the screen.
+ # If so, get rid of them.
+ for sprite in self.dynamic_sprite_list:
+ if sprite.shape.body.position.y < 0:
+ # Remove sprites from physics space
+ self.space.remove(sprite.shape, sprite.shape.body)
+ # Remove sprites from physics list
+ sprite.kill()
+
+ # Update physics
+ # Use a constant time step, don't use delta_time
+ # See "Game loop / moving time forward"
+ # http://www.pymunk.org/en/latest/overview.html#game-loop-moving-time-forward
+ self.space.step(1 / 60.0)
+
+ # If we are dragging an object, make sure it stays with the mouse. Otherwise
+ # gravity will drag it down.
+ if self.shape_being_dragged is not None:
+ self.shape_being_dragged.shape.body.position = self.last_mouse_position
+ self.shape_being_dragged.shape.body.velocity = 0, 0
+
+ # Resync the sprites to the physics objects that shadow them
+ resync_physics_sprites(self.dynamic_sprite_list)
+
+ # Scroll the viewport if needed
+ self.scroll_viewport()
+
+ # Save the time it took to do this.
+ self.processing_time = timeit.default_timer() - start_time
+
+ def punch(self):
+ # --- Punch left
+ # See if we have a physics object to our right
+ self.check_point = (self.player.right + 10, self.player.center_y)
+ shape_list = self.space.point_query(self.check_point, 1, pymunk.ShapeFilter())
+
+ # Apply force to any object to our right
+ for shape in shape_list:
+ shape.shape.body.apply_impulse_at_world_point((PLAYER_PUNCH_IMPULSE, PLAYER_PUNCH_IMPULSE),
+ self.check_point)
+
+ # --- Punch right
+ # See if we have a physics object to our left
+ self.check_point = (self.player.left - 10, self.player.center_y)
+ shape_list = self.space.point_query(self.check_point, 1, pymunk.ShapeFilter())
+
+ # Apply force to any object to our right
+ for shape in shape_list:
+ shape.shape.body.apply_impulse_at_world_point((-PLAYER_PUNCH_IMPULSE, PLAYER_PUNCH_IMPULSE),
+ self.check_point)
+
+ def grab(self):
+ """ Grab something """
+ # See if we have a physics object to our right
+ self.check_point = (self.player.right + 10, self.player.center_y)
+ shape_list = self.space.point_query(self.check_point, 1, pymunk.ShapeFilter())
+
+ # Create a joint for an item to our right
+ for shape in shape_list:
+ self.grab_joint = pymunk.PinJoint(self.player.shape.body, shape.shape.body)
+ self.space.add(self.grab_joint)
+
+ def let_go(self):
+ """ Let go of whatever we are holding """
+ if self.grab_joint:
+ self.space.remove(self.grab_joint)
+ self.grab_joint = None
+
+ def on_key_press(self, symbol: int, modifiers: int):
+ """ Handle keyboard presses. """
+ if symbol == arcade.key.RIGHT:
+ # Add force to the player, and set the player friction to zero
+ self.force = (PLAYER_MOVE_FORCE, 0)
+ self.player.shape.friction = 0
+ elif symbol == arcade.key.LEFT:
+ # Add force to the player, and set the player friction to zero
+ self.force = (-PLAYER_MOVE_FORCE, 0)
+ self.player.shape.friction = 0
+ elif symbol == arcade.key.UP:
+ # find out if player is standing on ground
+ grounding = check_grounding(self.player)
+ if grounding['body'] is not None and abs(
+ grounding['normal'].x / grounding['normal'].y) < self.player.shape.friction:
+ # She is! Go ahead and jump
+ self.player.body.apply_impulse_at_local_point((0, PLAYER_JUMP_IMPULSE))
+ elif symbol == arcade.key.SPACE:
+ self.punch()
+ elif symbol == arcade.key.G:
+ self.grab()
+
+ def on_key_release(self, symbol: int, modifiers: int):
+ """ Handle keyboard releases. """
+ if symbol == arcade.key.RIGHT:
+ # Remove force from the player, and set the player friction to a high number so she stops
+ self.force = (0, 0)
+ self.player.shape.friction = 15
+ elif symbol == arcade.key.LEFT:
+ # Remove force from the player, and set the player friction to a high number so she stops
+ self.force = (0, 0)
+ self.player.shape.friction = 15
+ elif symbol == arcade.key.G:
+ self.let_go()
+
+
+def main():
+ MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/pymunk_platformer/physics_utility.py b/arcade/examples/pymunk_platformer/physics_utility.py
new file mode 100644
index 0000000..8d10847
--- /dev/null
+++ b/arcade/examples/pymunk_platformer/physics_utility.py
@@ -0,0 +1,70 @@
+import arcade
+from arcade.examples.pymunk_platformer.constants import (
+ DEFAULT_FRICTION,
+ DEFAULT_MASS,
+)
+
+import pymunk
+import math
+
+
+class PymunkSprite(arcade.Sprite):
+ """
+ We need a Sprite and a Pymunk physics object. This class blends them
+ together.
+ """
+ def __init__(self,
+ filename,
+ center_x=0,
+ center_y=0,
+ scale=1,
+ mass=DEFAULT_MASS,
+ moment=None,
+ friction=DEFAULT_FRICTION,
+ body_type=pymunk.Body.DYNAMIC):
+
+ super().__init__(filename, scale=scale, center_x=center_x, center_y=center_y)
+
+ width = self.texture.width * scale
+ height = self.texture.height * scale
+
+ if moment is None:
+ moment = pymunk.moment_for_box(mass, (width, height))
+
+ self.body = pymunk.Body(mass, moment, body_type=body_type)
+ self.body.position = pymunk.Vec2d(center_x, center_y)
+
+ self.shape = pymunk.Poly.create_box(self.body, (width, height))
+ self.shape.friction = friction
+
+
+def check_grounding(player):
+ """ See if the player is on the ground. Used to see if we can jump. """
+ grounding = {
+ 'normal': pymunk.Vec2d.zero(),
+ 'penetration': pymunk.Vec2d.zero(),
+ 'impulse': pymunk.Vec2d.zero(),
+ 'position': pymunk.Vec2d.zero(),
+ 'body': None
+ }
+
+ def f(arbiter):
+ n = -arbiter.contact_point_set.normal
+ if n.y > grounding['normal'].y:
+ grounding['normal'] = n
+ grounding['penetration'] = -arbiter.contact_point_set.points[0].distance
+ grounding['body'] = arbiter.shapes[1].body
+ grounding['impulse'] = arbiter.total_impulse
+ grounding['position'] = arbiter.contact_point_set.points[0].point_b
+
+ player.body.each_arbiter(f)
+
+ return grounding
+
+
+def resync_physics_sprites(sprite_list):
+ """ Move sprites to where physics objects are """
+ for sprite in sprite_list:
+ sprite.center_x = sprite.shape.body.position.x
+ sprite.center_y = sprite.shape.body.position.y
+ sprite.angle = math.degrees(sprite.shape.body.angle)
diff --git a/arcade/examples/radar_sweep.py b/arcade/examples/radar_sweep.py
new file mode 100644
index 0000000..26f3eca
--- /dev/null
+++ b/arcade/examples/radar_sweep.py
@@ -0,0 +1,68 @@
+"""
+This animation example shows how perform a radar sweep animation.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.radar_sweep
+"""
+
+import arcade
+import math
+
+# Set up the constants
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Radar Sweep Example"
+
+# These constants control the particulars about the radar
+CENTER_X = SCREEN_WIDTH // 2
+CENTER_Y = SCREEN_HEIGHT // 2
+RADIANS_PER_FRAME = 0.02
+SWEEP_LENGTH = 250
+
+
+def on_draw(delta_time):
+ """ Use this function to draw everything to the screen. """
+
+ # Move the angle of the sweep.
+ on_draw.angle += RADIANS_PER_FRAME
+
+ # Calculate the end point of our radar sweep. Using math.
+ x = SWEEP_LENGTH * math.sin(on_draw.angle) + CENTER_X
+ y = SWEEP_LENGTH * math.cos(on_draw.angle) + CENTER_Y
+
+ # Start the render. This must happen before any drawing
+ # commands. We do NOT need an stop render command.
+ arcade.start_render()
+
+ # Draw the radar line
+ arcade.draw_line(CENTER_X, CENTER_Y, x, y, arcade.color.OLIVE, 4)
+
+ # Draw the outline of the radar
+ arcade.draw_circle_outline(CENTER_X, CENTER_Y, SWEEP_LENGTH,
+ arcade.color.DARK_GREEN, 10)
+
+
+# This is a function-specific variable. Before we
+# use them in our function, we need to give them initial
+# values.
+on_draw.angle = 0
+
+
+def main():
+
+ # Open up our window
+ arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ arcade.set_background_color(arcade.color.BLACK)
+
+ # Tell the computer to call the draw command at the specified interval.
+ arcade.schedule(on_draw, 1 / 80)
+
+ # Run the program
+ arcade.run()
+
+ # When done running the program, close the window.
+ arcade.close_window()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/resizable_window.py b/arcade/examples/resizable_window.py
new file mode 100644
index 0000000..c8712ad
--- /dev/null
+++ b/arcade/examples/resizable_window.py
@@ -0,0 +1,62 @@
+"""
+Example showing how handle screen resizing.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.resizable_window
+"""
+import arcade
+
+SCREEN_WIDTH = 500
+SCREEN_HEIGHT = 500
+SCREEN_TITLE = "Resizing Window Example"
+START = 0
+END = 2000
+STEP = 50
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title, resizable=True)
+
+ arcade.set_background_color(arcade.color.WHITE)
+
+ def on_resize(self, width, height):
+ """ This method is automatically called when the window is resized. """
+
+ # Call the parent. Failing to do this will mess up the coordinates, and default to 0,0 at the center and the
+ # edges being -1 to 1.
+ super().on_resize(width, height)
+
+ print(f"Window resized to: {width}, {height}")
+
+ def on_draw(self):
+ """ Render the screen. """
+
+ arcade.start_render()
+
+ # Draw the y labels
+ i = 0
+ for y in range(START, END, STEP):
+ arcade.draw_point(0, y, arcade.color.BLUE, 5)
+ arcade.draw_text(f"{y}", 5, y, arcade.color.BLACK, 12, anchor_x="left", anchor_y="bottom")
+ i += 1
+
+ # Draw the x labels.
+ i = 1
+ for x in range(START + STEP, END, STEP):
+ arcade.draw_point(x, 0, arcade.color.BLUE, 5)
+ arcade.draw_text(f"{x}", x, 5, arcade.color.BLACK, 12, anchor_x="left", anchor_y="bottom")
+ i += 1
+
+
+def main():
+ MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/shape_list_demo_1.py b/arcade/examples/shape_list_demo_1.py
new file mode 100644
index 0000000..90dab6d
--- /dev/null
+++ b/arcade/examples/shape_list_demo_1.py
@@ -0,0 +1,65 @@
+"""
+This demo shows the speed of drawing a full grid of squares using no buffering.
+
+For me this takes about 0.850 seconds per frame.
+
+It is slow because we load all the points and all the colors to the card every
+time.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.shape_list_demo_1
+"""
+
+import arcade
+import timeit
+
+SCREEN_WIDTH = 1200
+SCREEN_HEIGHT = 800
+SCREEN_TITLE = "Shape List Demo 1"
+
+SQUARE_WIDTH = 5
+SQUARE_HEIGHT = 5
+SQUARE_SPACING = 40
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title)
+
+ arcade.set_background_color(arcade.color.DARK_SLATE_GRAY)
+
+ self.draw_time = 0
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Start timing how long this takes
+ draw_start_time = timeit.default_timer()
+
+ # --- Draw all the rectangles
+ for x in range(0, SCREEN_WIDTH, SQUARE_SPACING):
+ for y in range(0, SCREEN_HEIGHT, SQUARE_SPACING):
+ arcade.draw_rectangle_filled(x, y, SQUARE_WIDTH, SQUARE_HEIGHT, arcade.color.DARK_BLUE)
+
+ # Print the timing
+ output = f"Drawing time: {self.draw_time:.3f} seconds per frame."
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 40, arcade.color.WHITE, 18)
+
+ self.draw_time = timeit.default_timer() - draw_start_time
+
+
+def main():
+ MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/shape_list_demo_2.py b/arcade/examples/shape_list_demo_2.py
new file mode 100644
index 0000000..53ee880
--- /dev/null
+++ b/arcade/examples/shape_list_demo_2.py
@@ -0,0 +1,75 @@
+"""
+This demo shows using buffered rectangles to draw a grid of squares on the
+screen.
+
+For me this starts at 0.500 seconds and goes down to 0.220 seconds after the
+graphics card figures out some optimizations.
+
+It is faster than demo 1 because we aren't loading the vertices and color
+to the card again and again. It isn't very fast because we are still sending
+individual draw commands to the graphics card for each square.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.shape_list_demo_2
+"""
+
+import arcade
+import timeit
+
+SCREEN_WIDTH = 1200
+SCREEN_HEIGHT = 800
+SCREEN_TITLE = "Shape List Demo 2"
+
+SQUARE_WIDTH = 5
+SQUARE_HEIGHT = 5
+SQUARE_SPACING = 10
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title)
+
+ arcade.set_background_color(arcade.color.DARK_SLATE_GRAY)
+
+ self.draw_time = 0
+ self.shape_list = None
+
+ def setup(self):
+ # --- Create the vertex buffers objects for each square before we do
+ # any drawing.
+ self.shape_list = arcade.ShapeElementList()
+ for x in range(0, SCREEN_WIDTH, SQUARE_SPACING):
+ for y in range(0, SCREEN_HEIGHT, SQUARE_SPACING):
+ shape = arcade.create_rectangle_filled(x, y, SQUARE_WIDTH, SQUARE_HEIGHT, arcade.color.DARK_BLUE)
+ self.shape_list.append(shape)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Start timing how long this takes
+ draw_start_time = timeit.default_timer()
+
+ # --- Draw all the rectangles
+ self.shape_list.draw()
+
+ output = f"Drawing time: {self.draw_time:.3f} seconds per frame."
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 40, arcade.color.WHITE, 18)
+
+ self.draw_time = timeit.default_timer() - draw_start_time
+
+
+def main():
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/shape_list_demo_3.py b/arcade/examples/shape_list_demo_3.py
new file mode 100644
index 0000000..ff8b775
--- /dev/null
+++ b/arcade/examples/shape_list_demo_3.py
@@ -0,0 +1,101 @@
+"""
+This demo shows drawing a grid of squares using a single buffer.
+
+We calculate the points of each rectangle and add them to a point list.
+We create a list of colors for each point.
+We then draw all the squares with one drawing command.
+
+This runs in about 0.000 seconds for me. It is much more complex in code
+than the prior two examples, but the pay-off in speed is huge.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.shape_list_demo_3
+"""
+
+import arcade
+import timeit
+
+SCREEN_WIDTH = 1200
+SCREEN_HEIGHT = 800
+SCREEN_TITLE = "Shape List Demo 3"
+
+HALF_SQUARE_WIDTH = 2.5
+HALF_SQUARE_HEIGHT = 2.5
+SQUARE_SPACING = 10
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title)
+
+ arcade.set_background_color(arcade.color.DARK_SLATE_GRAY)
+
+ self.draw_time = 0
+ self.shape_list = None
+
+ def setup(self):
+ self.shape_list = arcade.ShapeElementList()
+
+ # --- Create all the rectangles
+
+ # We need a list of all the points and colors
+ point_list = []
+ color_list = []
+
+ # Now calculate all the points
+ for x in range(0, SCREEN_WIDTH, SQUARE_SPACING):
+ for y in range(0, SCREEN_HEIGHT, SQUARE_SPACING):
+
+ # Calculate where the four points of the rectangle will be if
+ # x and y are the center
+ top_left = (x - HALF_SQUARE_WIDTH, y + HALF_SQUARE_HEIGHT)
+ top_right = (x + HALF_SQUARE_WIDTH, y + HALF_SQUARE_HEIGHT)
+ bottom_right = (x + HALF_SQUARE_WIDTH, y - HALF_SQUARE_HEIGHT)
+ bottom_left = (x - HALF_SQUARE_WIDTH, y - HALF_SQUARE_HEIGHT)
+
+ # Add the points to the points list.
+ # ORDER MATTERS!
+ # Rotate around the rectangle, don't append points caty-corner
+ point_list.append(top_left)
+ point_list.append(top_right)
+ point_list.append(bottom_right)
+ point_list.append(bottom_left)
+
+ # Add a color for each point. Can be different colors if you want
+ # gradients.
+ for i in range(4):
+ color_list.append(arcade.color.DARK_BLUE)
+
+ shape = arcade.create_rectangles_filled_with_colors(point_list, color_list)
+ self.shape_list.append(shape)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Start timing how long this takes
+ draw_start_time = timeit.default_timer()
+
+ # --- Draw all the rectangles
+ self.shape_list.draw()
+
+ output = f"Drawing time: {self.draw_time:.3f} seconds per frame."
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 40, arcade.color.WHITE, 18)
+
+ self.draw_time = timeit.default_timer() - draw_start_time
+
+
+def main():
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/shape_list_demo_person.py b/arcade/examples/shape_list_demo_person.py
new file mode 100644
index 0000000..be853a3
--- /dev/null
+++ b/arcade/examples/shape_list_demo_person.py
@@ -0,0 +1,131 @@
+"""
+Simple program showing how to use a shape list to create a more complex shape
+out of basic ones.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.shape_list_demo_person
+"""
+import arcade
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Shape List Demo Person"
+
+
+def make_person(head_radius,
+ chest_height,
+ chest_width,
+ leg_width,
+ leg_height,
+ arm_width,
+ arm_length,
+ arm_gap,
+ shoulder_height):
+
+ shape_list = arcade.ShapeElementList()
+
+ # Head
+ shape = arcade.create_ellipse_filled(0, chest_height / 2 + head_radius, head_radius, head_radius,
+ arcade.color.WHITE)
+ shape_list.append(shape)
+
+ # Chest
+ shape = arcade.create_rectangle_filled(0, 0, chest_width, chest_height, arcade.color.BLACK)
+ shape_list.append(shape)
+
+ # Left leg
+ shape = arcade.create_rectangle_filled(-(chest_width / 2) + leg_width / 2, -(chest_height / 2) - leg_height / 2,
+ leg_width, leg_height, arcade.color.RED)
+ shape_list.append(shape)
+
+ # Right leg
+ shape = arcade.create_rectangle_filled((chest_width / 2) - leg_width / 2, -(chest_height / 2) - leg_height / 2,
+ leg_width, leg_height, arcade.color.RED)
+ shape_list.append(shape)
+
+ # Left arm
+ shape = arcade.create_rectangle_filled(-(chest_width / 2) - arm_width / 2 - arm_gap,
+ (chest_height / 2) - arm_length / 2 - shoulder_height, arm_width, arm_length,
+ arcade.color.BLUE)
+ shape_list.append(shape)
+
+ # Left shoulder
+ shape = arcade.create_rectangle_filled(-(chest_width / 2) - (arm_gap + arm_width) / 2,
+ (chest_height / 2) - shoulder_height / 2, arm_gap + arm_width,
+ shoulder_height, arcade.color.BLUE_BELL)
+ shape_list.append(shape)
+
+ # Right arm
+ shape = arcade.create_rectangle_filled((chest_width / 2) + arm_width / 2 + arm_gap,
+ (chest_height / 2) - arm_length / 2 - shoulder_height, arm_width, arm_length,
+ arcade.color.BLUE)
+ shape_list.append(shape)
+
+ # Right shoulder
+ shape = arcade.create_rectangle_filled((chest_width / 2) + (arm_gap + arm_width) / 2,
+ (chest_height / 2) - shoulder_height / 2, arm_gap + arm_width,
+ shoulder_height, arcade.color.BLUE_BELL)
+ shape_list.append(shape)
+
+ return shape_list
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self):
+ """ Initializer """
+ # Call the parent class initializer
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ head_radius = 30
+ chest_height = 110
+ chest_width = 70
+ leg_width = 20
+ leg_height = 80
+ arm_width = 15
+ arm_length = 70
+ arm_gap = 10
+ shoulder_height = 15
+
+ self.shape_list = make_person(head_radius,
+ chest_height,
+ chest_width,
+ leg_width,
+ leg_height,
+ arm_width,
+ arm_length,
+ arm_gap,
+ shoulder_height)
+
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def setup(self):
+
+ """ Set up the game and initialize the variables. """
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ self.shape_list.draw()
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+ self.shape_list.center_x += 1
+ self.shape_list.center_y += 1
+ self.shape_list.angle += .1
+
+
+def main():
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/shape_list_demo_skylines.py b/arcade/examples/shape_list_demo_skylines.py
new file mode 100644
index 0000000..8edb65a
--- /dev/null
+++ b/arcade/examples/shape_list_demo_skylines.py
@@ -0,0 +1,178 @@
+"""
+City Scape Generator
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.shape_list_skylines
+"""
+import random
+import arcade
+import time
+
+SCREEN_WIDTH = 1200
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Skyline Using Buffered Shapes"
+
+
+def make_star_field(star_count):
+ """ Make a bunch of circles for stars. """
+
+ shape_list = arcade.ShapeElementList()
+
+ for star_no in range(star_count):
+ x = random.randrange(SCREEN_WIDTH)
+ y = random.randrange(SCREEN_HEIGHT)
+ radius = random.randrange(1, 4)
+ brightness = random.randrange(127, 256)
+ color = (brightness, brightness, brightness)
+ shape = arcade.create_rectangle_filled(x, y, radius, radius, color)
+ shape_list.append(shape)
+
+ return shape_list
+
+
+def make_skyline(width, skyline_height, skyline_color,
+ gap_chance=0.70, window_chance=0.30, light_on_chance=0.5,
+ window_color=(255, 255, 200), window_margin=3, window_gap=2,
+ cap_chance=0.20):
+ """ Make a skyline """
+
+ shape_list = arcade.ShapeElementList()
+
+ # Add the "base" that we build the buildings on
+ shape = arcade.create_rectangle_filled(width / 2, skyline_height / 2, width, skyline_height, skyline_color)
+ shape_list.append(shape)
+
+ building_center_x = 0
+
+ skyline_point_list = []
+ color_list = []
+
+ while building_center_x < width:
+
+ # Is there a gap between the buildings?
+ if random.random() < gap_chance:
+ gap_width = random.randrange(10, 50)
+ else:
+ gap_width = 0
+
+ # Figure out location and size of building
+ building_width = random.randrange(20, 70)
+ building_height = random.randrange(40, 150)
+ building_center_x += gap_width + (building_width / 2)
+ building_center_y = skyline_height + (building_height / 2)
+
+ x1 = building_center_x - building_width / 2
+ x2 = building_center_x + building_width / 2
+ y1 = skyline_height
+ y2 = skyline_height + building_height
+
+ skyline_point_list.append([x1, y1])
+
+ skyline_point_list.append([x1, y2])
+
+ skyline_point_list.append([x2, y2])
+
+ skyline_point_list.append([x2, y1])
+
+ for i in range(4):
+ color_list.append([skyline_color[0], skyline_color[1], skyline_color[2]])
+
+ if random.random() < cap_chance:
+ x1 = building_center_x - building_width / 2
+ x2 = building_center_x + building_width / 2
+ x3 = building_center_x
+
+ y1 = y2 = building_center_y + building_height / 2
+ y3 = y1 + building_width / 2
+
+ shape = arcade.create_polygon([[x1, y1], [x2, y2], [x3, y3]], skyline_color)
+ shape_list.append(shape)
+
+ # See if we should have some windows
+ if random.random() < window_chance:
+ # Yes windows! How many windows?
+ window_rows = random.randrange(10, 15)
+ window_columns = random.randrange(1, 7)
+
+ # Based on that, how big should they be?
+ window_height = (building_height - window_margin * 2) / window_rows
+ window_width = (building_width - window_margin * 2 - window_gap * (window_columns - 1)) / window_columns
+
+ # Find the bottom left of the building so we can start adding widows
+ building_base_y = building_center_y - building_height / 2
+ building_left_x = building_center_x - building_width / 2
+
+ # Loop through each window
+ for row in range(window_rows):
+ for column in range(window_columns):
+ if random.random() < light_on_chance:
+ x1 = building_left_x + column * (window_width + window_gap) + window_margin
+ x2 = building_left_x + column * (window_width + window_gap) + window_width + window_margin
+ y1 = building_base_y + row * window_height
+ y2 = building_base_y + row * window_height + window_height * .8
+
+ skyline_point_list.append([x1, y1])
+ skyline_point_list.append([x1, y2])
+ skyline_point_list.append([x2, y2])
+ skyline_point_list.append([x2, y1])
+
+ for i in range(4):
+ color_list.append((window_color[0], window_color[1], window_color[2]))
+
+ building_center_x += (building_width / 2)
+
+ shape = arcade.create_rectangles_filled_with_colors(skyline_point_list, color_list)
+ shape_list.append(shape)
+
+ return shape_list
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self):
+ """ Initializer """
+ # Call the parent class initializer
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ self.stars = make_star_field(150)
+ self.skyline1 = make_skyline(SCREEN_WIDTH * 5, 250, (80, 80, 80))
+ self.skyline2 = make_skyline(SCREEN_WIDTH * 5, 150, (50, 50, 50))
+
+ arcade.set_background_color(arcade.color.BLACK)
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+
+ start_time = int(round(time.time() * 1000))
+ arcade.start_render()
+
+ self.stars.draw()
+ self.skyline1.draw()
+ self.skyline2.draw()
+ end_time = int(round(time.time() * 1000))
+ total_time = end_time - start_time
+
+ arcade.draw_text(f"Time: {total_time}", 10, 10, arcade.color.WHITE)
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+ self.skyline1.center_x -= 0.5
+ self.skyline2.center_x -= 1
+
+
+def main():
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/shape_list_non_center_rotate.py b/arcade/examples/shape_list_non_center_rotate.py
new file mode 100644
index 0000000..4c260b0
--- /dev/null
+++ b/arcade/examples/shape_list_non_center_rotate.py
@@ -0,0 +1,67 @@
+"""
+Shape List Non-center Rotation Demo
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.shape_list_non_center_rotate
+"""
+import arcade
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Shape List Non-center Rotation Demo"
+
+
+def make_shape():
+
+ shape_list = arcade.ShapeElementList()
+
+ # Shape center around which we will rotate
+ center_x = 20
+ center_y = 30
+
+ width = 30
+ height = 40
+
+ shape = arcade.create_ellipse_filled(center_x, center_y, width, height, arcade.color.WHITE)
+ shape_list.append(shape)
+
+ return shape_list
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self):
+ # Call the parent class initializer
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ self.shape_list = make_shape()
+
+ # This specifies where on the screen the center of the shape will go
+ self.shape_list.center_x = SCREEN_WIDTH / 2
+ self.shape_list.center_y = SCREEN_HEIGHT / 2
+
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ self.shape_list.draw()
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+ self.shape_list.angle += 1
+
+
+def main():
+ window = MyGame()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/shapes.py b/arcade/examples/shapes.py
new file mode 100644
index 0000000..bdb5597
--- /dev/null
+++ b/arcade/examples/shapes.py
@@ -0,0 +1,128 @@
+"""
+This simple animation example shows how to use classes to animate
+multiple objects on the screen at the same time.
+
+Because this is redraws the shapes from scratch each frame, this is SLOW
+and inefficient.
+
+Using buffered drawing commands (Vertex Buffer Objects) is a bit more complex,
+but faster.
+
+See http://arcade.academy/examples/index.html#shape-lists for some examples.
+
+Also, any Sprite class put in a SpriteList and drawn with the SpriteList will
+be drawn using Vertex Buffer Objects for better performance.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.shapes
+"""
+
+import arcade
+import random
+
+# Set up the constants
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Shapes!"
+
+RECT_WIDTH = 50
+RECT_HEIGHT = 50
+
+NUMBER_OF_SHAPES = 200
+
+
+class Shape:
+
+ def __init__(self, x, y, width, height, angle, delta_x, delta_y,
+ delta_angle, color):
+ self.x = x
+ self.y = y
+ self.width = width
+ self.height = height
+ self.angle = angle
+ self.delta_x = delta_x
+ self.delta_y = delta_y
+ self.delta_angle = delta_angle
+ self.color = color
+
+ def move(self):
+ self.x += self.delta_x
+ self.y += self.delta_y
+ self.angle += self.delta_angle
+
+
+class Ellipse(Shape):
+
+ def draw(self):
+ arcade.draw_ellipse_filled(self.x, self.y, self.width, self.height,
+ self.color, self.angle)
+
+
+class Rectangle(Shape):
+
+ def draw(self):
+ arcade.draw_rectangle_filled(self.x, self.y, self.width, self.height,
+ self.color, self.angle)
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self):
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ self.shape_list = None
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+ self.shape_list = []
+
+ for i in range(NUMBER_OF_SHAPES):
+ x = random.randrange(0, SCREEN_WIDTH)
+ y = random.randrange(0, SCREEN_HEIGHT)
+ width = random.randrange(10, 30)
+ height = random.randrange(10, 30)
+ angle = random.randrange(0, 360)
+
+ d_x = random.randrange(-3, 4)
+ d_y = random.randrange(-3, 4)
+ d_angle = random.randrange(-3, 4)
+
+ red = random.randrange(256)
+ green = random.randrange(256)
+ blue = random.randrange(256)
+ alpha = random.randrange(256)
+
+ shape_type = random.randrange(2)
+
+ if shape_type == 0:
+ shape = Rectangle(x, y, width, height, angle, d_x, d_y,
+ d_angle, (red, green, blue, alpha))
+ else:
+ shape = Ellipse(x, y, width, height, angle, d_x, d_y,
+ d_angle, (red, green, blue, alpha))
+ self.shape_list.append(shape)
+
+ def update(self, dt):
+ """ Move everything """
+
+ for shape in self.shape_list:
+ shape.move()
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+ arcade.start_render()
+
+ for shape in self.shape_list:
+ shape.draw()
+
+
+def main():
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/shapes_buffered.py b/arcade/examples/shapes_buffered.py
new file mode 100644
index 0000000..d666201
--- /dev/null
+++ b/arcade/examples/shapes_buffered.py
@@ -0,0 +1,118 @@
+"""
+Shapes buffered in ShapeElementList
+
+Show how to use a ShapeElementList to display multiple shapes on-screen.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.shapes_buffered
+"""
+import arcade
+import random
+
+# Do the math to figure out our screen dimensions
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Buffered Shapes"
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ """
+ Set up the application.
+ """
+ super().__init__(width, height, title)
+
+ self.shape_list = arcade.ShapeElementList()
+ self.shape_list.center_x = SCREEN_WIDTH // 2
+ self.shape_list.center_y = SCREEN_HEIGHT // 2
+ self.shape_list.angle = 0
+ point_list = ((0, 50),
+ (10, 10),
+ (50, 0),
+ (10, -10),
+ (0, -50),
+ (-10, -10),
+ (-50, 0),
+ (-10, 10),
+ (0, 50))
+ colors = [
+ getattr(arcade.color, color)
+ for color in dir(arcade.color)
+ if not color.startswith("__")
+ ]
+ for i in range(5):
+ x = SCREEN_WIDTH // 2 - random.randrange(SCREEN_WIDTH - 50)
+ y = SCREEN_HEIGHT // 2 - random.randrange(SCREEN_HEIGHT - 50)
+ color = random.choice(colors)
+ points = [(px + x, py + y) for px, py in point_list]
+
+ my_line_strip = arcade.create_line_strip(points, color, 5)
+ self.shape_list.append(my_line_strip)
+
+ point_list = ((-50, -50),
+ (0, 40),
+ (50, -50))
+ for i in range(5):
+ x = SCREEN_WIDTH // 2 - random.randrange(SCREEN_WIDTH - 50)
+ y = SCREEN_HEIGHT // 2 - random.randrange(SCREEN_HEIGHT - 50)
+ points = [(px + x, py + y) for px, py in point_list]
+ triangle_filled = arcade.create_triangles_filled_with_colors(
+ points,
+ random.sample(colors, 3)
+ )
+ self.shape_list.append(triangle_filled)
+
+ point_list = ((-50, -70),
+ (-50, 70),
+ (50, 70),
+ (50, -70))
+ for i in range(5):
+ x = SCREEN_WIDTH // 2 - random.randrange(SCREEN_WIDTH - 50)
+ y = SCREEN_HEIGHT // 2 - random.randrange(SCREEN_HEIGHT - 50)
+ points = [(px + x, py + y) for px, py in point_list]
+ rect_filled = arcade.create_rectangle_filled_with_colors(
+ points,
+ random.sample(colors, 4)
+ )
+ self.shape_list.append(rect_filled)
+
+ point_list = ((100, 100),
+ (50, 150),
+ (100, 200),
+ (200, 200),
+ (250, 150),
+ (200, 100))
+ poly = arcade.create_polygon(point_list, (255, 10, 10))
+ self.shape_list.append(poly)
+
+ ellipse = arcade.create_ellipse(20, 30, 50, 20, (230, 230, 0))
+ self.shape_list.append(ellipse)
+
+ arcade.set_background_color(arcade.color.BLACK)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ self.shape_list.draw()
+
+ def update(self, delta_time):
+ self.shape_list.angle += 0.2
+ self.shape_list.center_x += 0.1
+ self.shape_list.center_y += 0.1
+
+
+def main():
+ MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/snow.py b/arcade/examples/snow.py
new file mode 100644
index 0000000..65e7919
--- /dev/null
+++ b/arcade/examples/snow.py
@@ -0,0 +1,115 @@
+"""
+Simple Snow
+Based primarily on: http://arcade.academy/examples/sprite_collect_coins_move_down.html
+
+Contributed to Python Arcade Library by Nicholas Hartunian
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.snow
+"""
+
+import random
+import math
+import arcade
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Snow"
+
+
+class Snowflake:
+ """
+ Each instance of this class represents a single snowflake.
+ Based on drawing filled-circles.
+ """
+
+ def __init__(self):
+ self.x = 0
+ self.y = 0
+
+ def reset_pos(self):
+ # Reset flake to random position above screen
+ self.y = random.randrange(SCREEN_HEIGHT, SCREEN_HEIGHT + 100)
+ self.x = random.randrange(SCREEN_WIDTH)
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self, width, height, title):
+ """
+ Initializer
+ :param width:
+ :param height:
+ """
+ # Calls "__init__" of parent class (arcade.Window) to setup screen
+ super().__init__(width, height, title)
+
+ # Sprite lists
+ self.snowflake_list = None
+
+ def start_snowfall(self):
+ """ Set up snowfall and initialize variables. """
+ self.snowflake_list = []
+
+ for i in range(50):
+ # Create snowflake instance
+ snowflake = Snowflake()
+
+ # Randomly position snowflake
+ snowflake.x = random.randrange(SCREEN_WIDTH)
+ snowflake.y = random.randrange(SCREEN_HEIGHT + 200)
+
+ # Set other variables for the snowflake
+ snowflake.size = random.randrange(4)
+ snowflake.speed = random.randrange(20, 40)
+ snowflake.angle = random.uniform(math.pi, math.pi * 2)
+
+ # Add snowflake to snowflake list
+ self.snowflake_list.append(snowflake)
+
+ # Don't show the mouse pointer
+ self.set_mouse_visible(False)
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.BLACK)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command is necessary before drawing
+ arcade.start_render()
+
+ # Draw the current position of each snowflake
+ for snowflake in self.snowflake_list:
+ arcade.draw_circle_filled(snowflake.x, snowflake.y,
+ snowflake.size, arcade.color.WHITE)
+
+ def update(self, delta_time):
+ """
+ All the logic to move, and the game logic goes here.
+ """
+
+ # Animate all the snowflakes falling
+ for snowflake in self.snowflake_list:
+ snowflake.y -= snowflake.speed * delta_time
+
+ # Check if snowflake has fallen below screen
+ if snowflake.y < 0:
+ snowflake.reset_pos()
+
+ # Some math to make the snowflakes move side to side
+ snowflake.x += snowflake.speed * math.cos(snowflake.angle) * delta_time
+ snowflake.angle += 1 * delta_time
+
+
+def main():
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.start_snowfall()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sound.py b/arcade/examples/sound.py
new file mode 100644
index 0000000..46c14d3
--- /dev/null
+++ b/arcade/examples/sound.py
@@ -0,0 +1,20 @@
+"""
+Sound Demo
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sound
+"""
+import arcade
+import os
+
+# Set the working directory (where we expect to find files) to the same
+# directory this .py file is in. You can leave this out of your own
+# code, but it is needed to easily run the examples using "python -m"
+# as mentioned at the top of this program.
+file_path = os.path.dirname(os.path.abspath(__file__))
+os.chdir(file_path)
+
+arcade.open_window(300, 300, "Sound Demo")
+laser_sound = arcade.load_sound("sounds/laser1.wav")
+arcade.play_sound(laser_sound)
+arcade.run()
diff --git a/arcade/examples/sound_test.py b/arcade/examples/sound_test.py
new file mode 100644
index 0000000..0c90764
--- /dev/null
+++ b/arcade/examples/sound_test.py
@@ -0,0 +1,67 @@
+""" Test for sound in Arcade.
+(May only work for windows at current time)
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sound_test
+"""
+
+import arcade
+import os
+
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sound Test Example"
+
+window = None
+
+
+class MyGame(arcade.Window):
+ """ Main sound test class """
+
+ def __init__(self):
+ """ Initializer """
+ # Call the parent class initializer
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Set background color to black
+ arcade.set_background_color(arcade.color.BLACK)
+
+ def on_draw(self):
+ """Render the screen"""
+
+ arcade.start_render()
+
+ # Text on screen
+ text = "Press left mouse to make noise"
+
+ # Render text
+ arcade.draw_text(text, 150, 300, arcade.color.WHITE, 30)
+
+ def on_mouse_press(self, x, y, button, modifiers):
+ """Plays sound on key press"""
+
+ # Load sound
+ loaded_sound = arcade.sound.load_sound("sounds/laser1.wav")
+
+ # Play Sound
+ arcade.sound.play_sound(loaded_sound)
+
+ def update(self, delta_time):
+ """animations"""
+
+
+def main():
+ MyGame()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sounds/laser1.mp3 b/arcade/examples/sounds/laser1.mp3
new file mode 100644
index 0000000..f8d7192
Binary files /dev/null and b/arcade/examples/sounds/laser1.mp3 differ
diff --git a/arcade/examples/sounds/laser1.ogg b/arcade/examples/sounds/laser1.ogg
new file mode 100644
index 0000000..383035b
Binary files /dev/null and b/arcade/examples/sounds/laser1.ogg differ
diff --git a/arcade/examples/sounds/laser1.wav b/arcade/examples/sounds/laser1.wav
new file mode 100644
index 0000000..8c3db0f
Binary files /dev/null and b/arcade/examples/sounds/laser1.wav differ
diff --git a/arcade/examples/sounds/phaseJump1.ogg b/arcade/examples/sounds/phaseJump1.ogg
new file mode 100644
index 0000000..30fe73b
Binary files /dev/null and b/arcade/examples/sounds/phaseJump1.ogg differ
diff --git a/arcade/examples/sounds/phaseJump1.wav b/arcade/examples/sounds/phaseJump1.wav
new file mode 100644
index 0000000..decb9ba
Binary files /dev/null and b/arcade/examples/sounds/phaseJump1.wav differ
diff --git a/arcade/examples/sounds/rockHit2.ogg b/arcade/examples/sounds/rockHit2.ogg
new file mode 100644
index 0000000..d7838e3
Binary files /dev/null and b/arcade/examples/sounds/rockHit2.ogg differ
diff --git a/arcade/examples/sounds/rockHit2.wav b/arcade/examples/sounds/rockHit2.wav
new file mode 100644
index 0000000..fe6a9be
Binary files /dev/null and b/arcade/examples/sounds/rockHit2.wav differ
diff --git a/arcade/examples/sprite_bouncing_coins.py b/arcade/examples/sprite_bouncing_coins.py
new file mode 100644
index 0000000..030518e
--- /dev/null
+++ b/arcade/examples/sprite_bouncing_coins.py
@@ -0,0 +1,152 @@
+"""
+Sprite Simple Bouncing
+
+Simple program to show how to bounce items.
+This only works for straight vertical and horizontal angles.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_bouncing_coins
+"""
+
+import arcade
+import os
+import random
+
+SPRITE_SCALING = 0.5
+
+SCREEN_WIDTH = 832
+SCREEN_HEIGHT = 632
+SCREEN_TITLE = "Sprite Bouncing Coins"
+
+MOVEMENT_SPEED = 5
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self, width, height, title):
+ """
+ Initializer
+ """
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Sprite lists
+ self.coin_list = None
+ self.wall_list = None
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.wall_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # -- Set up the walls
+
+ # Create horizontal rows of boxes
+ for x in range(32, SCREEN_WIDTH, 64):
+ # Bottom edge
+ wall = arcade.Sprite("images/boxCrate_double.png", SPRITE_SCALING)
+ wall.center_x = x
+ wall.center_y = 32
+ self.wall_list.append(wall)
+
+ # Top edge
+ wall = arcade.Sprite("images/boxCrate_double.png", SPRITE_SCALING)
+ wall.center_x = x
+ wall.center_y = SCREEN_HEIGHT - 32
+ self.wall_list.append(wall)
+
+ # Create vertical columns of boxes
+ for y in range(96, SCREEN_HEIGHT, 64):
+ # Left
+ wall = arcade.Sprite("images/boxCrate_double.png", SPRITE_SCALING)
+ wall.center_x = 32
+ wall.center_y = y
+ self.wall_list.append(wall)
+
+ # Right
+ wall = arcade.Sprite("images/boxCrate_double.png", SPRITE_SCALING)
+ wall.center_x = SCREEN_WIDTH - 32
+ wall.center_y = y
+ self.wall_list.append(wall)
+
+ # Create boxes in the middle
+ for x in range(128, SCREEN_WIDTH, 196):
+ for y in range(128, SCREEN_HEIGHT, 196):
+ wall = arcade.Sprite("images/boxCrate_double.png", SPRITE_SCALING)
+ wall.center_x = x
+ wall.center_y = y
+ # wall.angle = 45
+ self.wall_list.append(wall)
+
+ # Create coins
+ for i in range(10):
+ coin = arcade.Sprite("images/coin_01.png", 0.25)
+ coin.center_x = random.randrange(100, 700)
+ coin.center_y = random.randrange(100, 500)
+ while coin.change_x == 0 and coin.change_y == 0:
+ coin.change_x = random.randrange(-4, 5)
+ coin.change_y = random.randrange(-4, 5)
+
+ self.coin_list.append(coin)
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.wall_list.draw()
+ self.coin_list.draw()
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ for coin in self.coin_list:
+
+ coin.center_x += coin.change_x
+ walls_hit = arcade.check_for_collision_with_list(coin, self.wall_list)
+ for wall in walls_hit:
+ if coin.change_x > 0:
+ coin.right = wall.left
+ elif coin.change_x < 0:
+ coin.left = wall.right
+ if len(walls_hit) > 0:
+ coin.change_x *= -1
+
+ coin.center_y += coin.change_y
+ walls_hit = arcade.check_for_collision_with_list(coin, self.wall_list)
+ for wall in walls_hit:
+ if coin.change_y > 0:
+ coin.top = wall.bottom
+ elif coin.change_y < 0:
+ coin.bottom = wall.top
+ if len(walls_hit) > 0:
+ coin.change_y *= -1
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_bullets.py b/arcade/examples/sprite_bullets.py
new file mode 100644
index 0000000..0a89467
--- /dev/null
+++ b/arcade/examples/sprite_bullets.py
@@ -0,0 +1,176 @@
+"""
+Sprite Bullets
+
+Simple program to show basic sprite usage.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_bullets
+"""
+import random
+import arcade
+import os
+
+SPRITE_SCALING_PLAYER = 0.5
+SPRITE_SCALING_COIN = 0.2
+SPRITE_SCALING_LASER = 0.8
+COIN_COUNT = 50
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprites and Bullets Example"
+
+BULLET_SPEED = 5
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self):
+ """ Initializer """
+ # Call the parent class initializer
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Variables that will hold sprite lists
+ self.player_list = None
+ self.coin_list = None
+ self.bullet_list = None
+
+ # Set up the player info
+ self.player_sprite = None
+ self.score = 0
+
+ # Don't show the mouse cursor
+ self.set_mouse_visible(False)
+
+ # Load sounds. Sounds from kenney.nl
+ self.gun_sound = arcade.sound.load_sound("sounds/laser1.wav")
+ self.hit_sound = arcade.sound.load_sound("sounds/phaseJump1.wav")
+
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def setup(self):
+
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+ self.bullet_list = arcade.SpriteList()
+
+ # Set up the player
+ self.score = 0
+
+ # Image from kenney.nl
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING_PLAYER)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 70
+ self.player_list.append(self.player_sprite)
+
+ # Create the coins
+ for i in range(COIN_COUNT):
+
+ # Create the coin instance
+ # Coin image from kenney.nl
+ coin = arcade.Sprite("images/coin_01.png", SPRITE_SCALING_COIN)
+
+ # Position the coin
+ coin.center_x = random.randrange(SCREEN_WIDTH)
+ coin.center_y = random.randrange(120, SCREEN_HEIGHT)
+
+ # Add the coin to the lists
+ self.coin_list.append(coin)
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.coin_list.draw()
+ self.bullet_list.draw()
+ self.player_list.draw()
+
+ # Render the text
+ arcade.draw_text(f"Score: {self.score}", 10, 20, arcade.color.WHITE, 14)
+
+ def on_mouse_motion(self, x, y, dx, dy):
+ """
+ Called whenever the mouse moves.
+ """
+ self.player_sprite.center_x = x
+
+ def on_mouse_press(self, x, y, button, modifiers):
+ """
+ Called whenever the mouse button is clicked.
+ """
+ # Gunshot sound
+ arcade.sound.play_sound(self.gun_sound)
+ # Create a bullet
+ bullet = arcade.Sprite("images/laserBlue01.png", SPRITE_SCALING_LASER)
+
+ # The image points to the right, and we want it to point up. So
+ # rotate it.
+ bullet.angle = 90
+
+ # Give the bullet a speed
+ bullet.change_y = BULLET_SPEED
+
+ # Position the bullet
+ bullet.center_x = self.player_sprite.center_x
+ bullet.bottom = self.player_sprite.top
+
+ # Add the bullet to the appropriate lists
+ self.bullet_list.append(bullet)
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on bullet sprites
+ self.bullet_list.update()
+
+ # Loop through each bullet
+ for bullet in self.bullet_list:
+
+ # Check this bullet to see if it hit a coin
+ hit_list = arcade.check_for_collision_with_list(bullet, self.coin_list)
+
+ # If it did, get rid of the bullet
+ if len(hit_list) > 0:
+ bullet.kill()
+
+ # For every coin we hit, add to the score and remove the coin
+ for coin in hit_list:
+ coin.kill()
+ self.score += 1
+
+ # Hit Sound
+ arcade.sound.play_sound(self.hit_sound)
+
+ # If the bullet flies off-screen, remove it.
+ if bullet.bottom > SCREEN_HEIGHT:
+ bullet.kill()
+
+
+def main():
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_bullets_aimed.py b/arcade/examples/sprite_bullets_aimed.py
new file mode 100644
index 0000000..7ae9f59
--- /dev/null
+++ b/arcade/examples/sprite_bullets_aimed.py
@@ -0,0 +1,186 @@
+"""
+Sprite Bullets
+
+Simple program to show basic sprite usage.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_bullets_aimed
+"""
+
+import random
+import arcade
+import math
+import os
+
+SPRITE_SCALING_PLAYER = 0.5
+SPRITE_SCALING_COIN = 0.2
+SPRITE_SCALING_LASER = 0.8
+COIN_COUNT = 50
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprites and Bullets Aimed Example"
+
+BULLET_SPEED = 5
+
+window = None
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self):
+ """ Initializer """
+ # Call the parent class initializer
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Variables that will hold sprite lists
+ self.player_list = None
+ self.coin_list = None
+ self.bullet_list = None
+
+ # Set up the player info
+ self.player_sprite = None
+ self.score = 0
+ self.score_text = None
+
+ # Load sounds. Sounds from kenney.nl
+ self.gun_sound = arcade.sound.load_sound("sounds/laser1.wav")
+ self.hit_sound = arcade.sound.load_sound("sounds/phaseJump1.wav")
+
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def setup(self):
+
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+ self.bullet_list = arcade.SpriteList()
+
+ # Set up the player
+ self.score = 0
+
+ # Image from kenney.nl
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING_PLAYER)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 70
+ self.player_list.append(self.player_sprite)
+
+ # Create the coins
+ for i in range(COIN_COUNT):
+
+ # Create the coin instance
+ # Coin image from kenney.nl
+ coin = arcade.Sprite("images/coin_01.png", SPRITE_SCALING_COIN)
+
+ # Position the coin
+ coin.center_x = random.randrange(SCREEN_WIDTH)
+ coin.center_y = random.randrange(120, SCREEN_HEIGHT)
+
+ # Add the coin to the lists
+ self.coin_list.append(coin)
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.coin_list.draw()
+ self.bullet_list.draw()
+ self.player_list.draw()
+
+ # Put the text on the screen.
+ output = f"Score: {self.score}"
+ arcade.draw_text(output, 10, 20, arcade.color.WHITE, 14)
+
+ def on_mouse_press(self, x, y, button, modifiers):
+ """
+ Called whenever the mouse moves.
+ """
+ # Create a bullet
+ bullet = arcade.Sprite("images/laserBlue01.png", SPRITE_SCALING_LASER)
+
+ # Position the bullet at the player's current location
+ start_x = self.player_sprite.center_x
+ start_y = self.player_sprite.center_y
+ bullet.center_x = start_x
+ bullet.center_y = start_y
+
+ # Get from the mouse the destination location for the bullet
+ # IMPORTANT! If you have a scrolling screen, you will also need
+ # to add in self.view_bottom and self.view_left.
+ dest_x = x
+ dest_y = y
+
+ # Do math to calculate how to get the bullet to the destination.
+ # Calculation the angle in radians between the start points
+ # and end points. This is the angle the bullet will travel.
+ x_diff = dest_x - start_x
+ y_diff = dest_y - start_y
+ angle = math.atan2(y_diff, x_diff)
+
+ # Angle the bullet sprite so it doesn't look like it is flying
+ # sideways.
+ bullet.angle = math.degrees(angle)
+ print(f"Bullet angle: {bullet.angle:.2f}")
+
+ # Taking into account the angle, calculate our change_x
+ # and change_y. Velocity is how fast the bullet travels.
+ bullet.change_x = math.cos(angle) * BULLET_SPEED
+ bullet.change_y = math.sin(angle) * BULLET_SPEED
+
+ # Add the bullet to the appropriate lists
+ self.bullet_list.append(bullet)
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites
+ self.bullet_list.update()
+
+ # Loop through each bullet
+ for bullet in self.bullet_list:
+
+ # Check this bullet to see if it hit a coin
+ hit_list = arcade.check_for_collision_with_list(bullet, self.coin_list)
+
+ # If it did, get rid of the bullet
+ if len(hit_list) > 0:
+ bullet.kill()
+
+ # For every coin we hit, add to the score and remove the coin
+ for coin in hit_list:
+ coin.kill()
+ self.score += 1
+
+ # If the bullet flies off-screen, remove it.
+ if bullet.bottom > self.width or bullet.top < 0 or bullet.right < 0 or bullet.left > self.width:
+ bullet.kill()
+
+
+def main():
+ game = MyGame()
+ game.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_bullets_enemy_aims.py b/arcade/examples/sprite_bullets_enemy_aims.py
new file mode 100644
index 0000000..2a8cb10
--- /dev/null
+++ b/arcade/examples/sprite_bullets_enemy_aims.py
@@ -0,0 +1,140 @@
+"""
+Show how to have enemies shoot bullets aimed at the player.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_bullets_enemy_aims
+"""
+
+import arcade
+import math
+import os
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprites and Bullets Enemy Aims Example"
+BULLET_SPEED = 4
+
+
+class MyGame(arcade.Window):
+ """ Main application class """
+
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ arcade.set_background_color(arcade.color.BLACK)
+
+ self.frame_count = 0
+
+ self.enemy_list = None
+ self.bullet_list = None
+ self.player_list = None
+ self.player = None
+
+ def setup(self):
+ self.enemy_list = arcade.SpriteList()
+ self.bullet_list = arcade.SpriteList()
+ self.player_list = arcade.SpriteList()
+
+ # Add player ship
+ self.player = arcade.Sprite("images/playerShip1_orange.png", 0.5)
+ self.player_list.append(self.player)
+
+ # Add top-left enemy ship
+ enemy = arcade.Sprite("images/playerShip1_green.png", 0.5)
+ enemy.center_x = 120
+ enemy.center_y = SCREEN_HEIGHT - enemy.height
+ enemy.angle = 180
+ self.enemy_list.append(enemy)
+
+ # Add top-right enemy ship
+ enemy = arcade.Sprite("images/playerShip1_green.png", 0.5)
+ enemy.center_x = SCREEN_WIDTH - 120
+ enemy.center_y = SCREEN_HEIGHT - enemy.height
+ enemy.angle = 180
+ self.enemy_list.append(enemy)
+
+ def on_draw(self):
+ """Render the screen. """
+
+ arcade.start_render()
+
+ self.enemy_list.draw()
+ self.bullet_list.draw()
+ self.player_list.draw()
+
+ def update(self, delta_time):
+ """All the logic to move, and the game logic goes here. """
+
+ self.frame_count += 1
+
+ # Loop through each enemy that we have
+ for enemy in self.enemy_list:
+
+ # First, calculate the angle to the player. We could do this
+ # only when the bullet fires, but in this case we will rotate
+ # the enemy to face the player each frame, so we'll do this
+ # each frame.
+
+ # Position the start at the enemy's current location
+ start_x = enemy.center_x
+ start_y = enemy.center_y
+
+ # Get the destination location for the bullet
+ dest_x = self.player.center_x
+ dest_y = self.player.center_y
+
+ # Do math to calculate how to get the bullet to the destination.
+ # Calculation the angle in radians between the start points
+ # and end points. This is the angle the bullet will travel.
+ x_diff = dest_x - start_x
+ y_diff = dest_y - start_y
+ angle = math.atan2(y_diff, x_diff)
+
+ # Set the enemy to face the player.
+ enemy.angle = math.degrees(angle)-90
+
+ # Shoot every 60 frames change of shooting each frame
+ if self.frame_count % 60 == 0:
+ bullet = arcade.Sprite("images/laserBlue01.png")
+ bullet.center_x = start_x
+ bullet.center_y = start_y
+
+ # Angle the bullet sprite
+ bullet.angle = math.degrees(angle)
+
+ # Taking into account the angle, calculate our change_x
+ # and change_y. Velocity is how fast the bullet travels.
+ bullet.change_x = math.cos(angle) * BULLET_SPEED
+ bullet.change_y = math.sin(angle) * BULLET_SPEED
+
+ self.bullet_list.append(bullet)
+
+ # Get rid of the bullet when it flies off-screen
+ for bullet in self.bullet_list:
+ if bullet.top < 0:
+ bullet.kill()
+
+ self.bullet_list.update()
+
+ def on_mouse_motion(self, x, y, delta_x, delta_y):
+ """Called whenever the mouse moves. """
+ self.player.center_x = x
+ self.player.center_y = y
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_bullets_periodic.py b/arcade/examples/sprite_bullets_periodic.py
new file mode 100644
index 0000000..7171411
--- /dev/null
+++ b/arcade/examples/sprite_bullets_periodic.py
@@ -0,0 +1,104 @@
+"""
+Show how to have enemies shoot bullets at regular intervals.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_bullets_periodic
+"""
+import arcade
+import os
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprites and Periodic Bullets Example"
+
+
+class MyGame(arcade.Window):
+ """ Main application class """
+
+ def __init__(self):
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ arcade.set_background_color(arcade.color.BLACK)
+
+ # --- Keep track of a frame count.
+ # --- This is important for doing things every x frames
+ self.frame_count = 0
+
+ self.player_list = arcade.SpriteList()
+ self.enemy_list = arcade.SpriteList()
+ self.bullet_list = arcade.SpriteList()
+
+ # Add player ship
+ self.player = arcade.Sprite("images/playerShip1_orange.png", 0.5)
+ self.player_list.append(self.player)
+
+ # Add top-left enemy ship
+ enemy = arcade.Sprite("images/playerShip1_green.png", 0.5)
+ enemy.center_x = 120
+ enemy.center_y = SCREEN_HEIGHT - enemy.height
+ enemy.angle = 180
+ self.enemy_list.append(enemy)
+
+ # Add top-right enemy ship
+ enemy = arcade.Sprite("images/playerShip1_green.png", 0.5)
+ enemy.center_x = SCREEN_WIDTH - 120
+ enemy.center_y = SCREEN_HEIGHT - enemy.height
+ enemy.angle = 180
+ self.enemy_list.append(enemy)
+
+ def on_draw(self):
+ """Render the screen. """
+
+ arcade.start_render()
+
+ self.enemy_list.draw()
+ self.bullet_list.draw()
+ self.player_list.draw()
+
+ def update(self, delta_time):
+ """All the logic to move, and the game logic goes here. """
+
+ # --- Add one to the frame count
+ self.frame_count += 1
+
+ # Loop through each enemy that we have
+ for enemy in self.enemy_list:
+
+ # --- Use the modulus to trigger doing something every 120 frames
+ if self.frame_count % 120 == 0:
+ bullet = arcade.Sprite("images/laserBlue01.png")
+ bullet.center_x = enemy.center_x
+ bullet.angle = -90
+ bullet.top = enemy.bottom
+ bullet.change_y = -2
+ self.bullet_list.append(bullet)
+
+ # Get rid of the bullet when it flies off-screen
+ for bullet in self.bullet_list:
+ if bullet.top < 0:
+ bullet.kill()
+
+ self.bullet_list.update()
+
+ def on_mouse_motion(self, x, y, delta_x, delta_y):
+ """
+ Called whenever the mouse moves.
+ """
+ self.player.center_x = x
+ self.player.center_y = 20
+
+
+def main():
+ MyGame()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_bullets_random.py b/arcade/examples/sprite_bullets_random.py
new file mode 100644
index 0000000..46f47fd
--- /dev/null
+++ b/arcade/examples/sprite_bullets_random.py
@@ -0,0 +1,106 @@
+"""
+Show how to have enemies shoot bullets at random intervals.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_bullets_random
+"""
+import arcade
+import random
+import os
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprites and Random Bullets Example"
+
+
+class MyGame(arcade.Window):
+ """ Main application class """
+
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ arcade.set_background_color(arcade.color.BLACK)
+
+ self.frame_count = 0
+ self.player_list = None
+ self.enemy_list = None
+ self.bullet_list = None
+
+ self.player = None
+
+ def setup(self):
+ self.player_list = arcade.SpriteList()
+ self.enemy_list = arcade.SpriteList()
+ self.bullet_list = arcade.SpriteList()
+
+ # Add player ship
+ self.player = arcade.Sprite("images/playerShip1_orange.png", 0.5)
+ self.player_list.append(self.player)
+
+ # Add top-left enemy ship
+ enemy = arcade.Sprite("images/playerShip1_green.png", 0.5)
+ enemy.center_x = 120
+ enemy.center_y = SCREEN_HEIGHT - enemy.height
+ enemy.angle = 180
+ self.enemy_list.append(enemy)
+
+ # Add top-right enemy ship
+ enemy = arcade.Sprite("images/playerShip1_green.png", 0.5)
+ enemy.center_x = SCREEN_WIDTH - 120
+ enemy.center_y = SCREEN_HEIGHT - enemy.height
+ enemy.angle = 180
+ self.enemy_list.append(enemy)
+
+ def on_draw(self):
+ """Render the screen. """
+
+ arcade.start_render()
+
+ self.enemy_list.draw()
+ self.bullet_list.draw()
+ self.player_list.draw()
+
+ def update(self, delta_time):
+ """All the logic to move, and the game logic goes here. """
+
+ # Loop through each enemy that we have
+ for enemy in self.enemy_list:
+
+ # Have a random 1 in 200 change of shooting each frame
+ if random.randrange(200) == 0:
+ bullet = arcade.Sprite("images/laserBlue01.png")
+ bullet.center_x = enemy.center_x
+ bullet.angle = -90
+ bullet.top = enemy.bottom
+ bullet.change_y = -2
+ self.bullet_list.append(bullet)
+
+ # Get rid of the bullet when it flies off-screen
+ for bullet in self.bullet_list:
+ if bullet.top < 0:
+ bullet.kill()
+
+ self.bullet_list.update()
+
+ def on_mouse_motion(self, x, y, delta_x, delta_y):
+ """ Called whenever the mouse moves. """
+ self.player.center_x = x
+ self.player.center_y = 20
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_change_coins.py b/arcade/examples/sprite_change_coins.py
new file mode 100644
index 0000000..183ff64
--- /dev/null
+++ b/arcade/examples/sprite_change_coins.py
@@ -0,0 +1,143 @@
+"""
+Sprite Change Coins
+
+This shows how you can change a sprite once it is hit, rather than eliminate it.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_change_coins
+"""
+
+import random
+import arcade
+import os
+
+SPRITE_SCALING = 1
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite Change Coins"
+
+
+class Collectable(arcade.Sprite):
+ """ This class represents something the player collects. """
+
+ def __init__(self, filename, scale):
+ super().__init__(filename, scale)
+ # Flip this once the coin has been collected.
+ self.changed = False
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.a
+ """
+
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Sprite lists
+ self.player_list = None
+ self.coin_list = None
+
+ # Set up the player
+ self.score = 0
+ self.player_sprite = None
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Set up the player
+ self.score = 0
+ self.player_sprite = arcade.Sprite("images/character.png", 0.5)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 50
+ self.player_list.append(self.player_sprite)
+
+ for i in range(50):
+ # Create the coin instance
+ coin = Collectable("images/coin_01.png", SPRITE_SCALING)
+ coin.width = 30
+ coin.height = 30
+
+ # Position the coin
+ coin.center_x = random.randrange(SCREEN_WIDTH)
+ coin.center_y = random.randrange(SCREEN_HEIGHT)
+
+ # Add the coin to the lists
+ self.coin_list.append(coin)
+
+ # Don't show the mouse cursor
+ self.set_mouse_visible(False)
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.coin_list.draw()
+ self.player_list.draw()
+
+ # Put the text on the screen.
+ output = f"Score: {self.score}"
+ arcade.draw_text(output, 10, 20, arcade.color.WHITE, 14)
+
+ def on_mouse_motion(self, x, y, dx, dy):
+ """
+ Called whenever the mouse moves.
+ """
+ self.player_sprite.center_x = x
+ self.player_sprite.center_y = y
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.player_list.update()
+ self.coin_list.update()
+
+ # Generate a list of all sprites that collided with the player.
+ hit_list = arcade.check_for_collision_with_list(self.player_sprite, self.coin_list)
+
+ # Loop through each colliding sprite, change it, and add to the score.
+ for coin in hit_list:
+ # Have we collected this?
+ if not coin.changed:
+ # No? Then do so
+ coin.append_texture(arcade.load_texture("images/bumper.png"))
+ coin.set_texture(1)
+ coin.changed = True
+ coin.width = 30
+ coin.height = 30
+ self.score += 1
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_collect_coins.py b/arcade/examples/sprite_collect_coins.py
new file mode 100644
index 0000000..f2aa224
--- /dev/null
+++ b/arcade/examples/sprite_collect_coins.py
@@ -0,0 +1,126 @@
+"""
+Sprite Collect Coins
+
+Simple program to show basic sprite usage.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_collect_coins
+"""
+
+import random
+import arcade
+import os
+
+# --- Constants ---
+SPRITE_SCALING_PLAYER = 0.5
+SPRITE_SCALING_COIN = 0.2
+COIN_COUNT = 50
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite Collect Coins Example"
+
+
+class MyGame(arcade.Window):
+ """ Our custom Window Class"""
+
+ def __init__(self):
+ """ Initializer """
+ # Call the parent class initializer
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Variables that will hold sprite lists
+ self.player_list = None
+ self.coin_list = None
+
+ # Set up the player info
+ self.player_sprite = None
+ self.score = 0
+
+ # Don't show the mouse cursor
+ self.set_mouse_visible(False)
+
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Score
+ self.score = 0
+
+ # Set up the player
+ # Character image from kenney.nl
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING_PLAYER)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 50
+ self.player_list.append(self.player_sprite)
+
+ # Create the coins
+ for i in range(COIN_COUNT):
+
+ # Create the coin instance
+ # Coin image from kenney.nl
+ coin = arcade.Sprite("images/coin_01.png", SPRITE_SCALING_COIN)
+
+ # Position the coin
+ coin.center_x = random.randrange(SCREEN_WIDTH)
+ coin.center_y = random.randrange(SCREEN_HEIGHT)
+
+ # Add the coin to the lists
+ self.coin_list.append(coin)
+
+ def on_draw(self):
+ """ Draw everything """
+ arcade.start_render()
+ self.coin_list.draw()
+ self.player_list.draw()
+
+ # Put the text on the screen.
+ output = f"Score: {self.score}"
+ arcade.draw_text(output, 10, 20, arcade.color.WHITE, 14)
+
+ def on_mouse_motion(self, x, y, dx, dy):
+ """ Handle Mouse Motion """
+
+ # Move the center of the player sprite to match the mouse x, y
+ self.player_sprite.center_x = x
+ self.player_sprite.center_y = y
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.coin_list.update()
+
+ # Generate a list of all sprites that collided with the player.
+ coins_hit_list = arcade.check_for_collision_with_list(self.player_sprite, self.coin_list)
+
+ # Loop through each colliding sprite, remove it, and add to the score.
+ for coin in coins_hit_list:
+ coin.kill()
+ self.score += 1
+
+
+def main():
+ """ Main method """
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_collect_coins_background.py b/arcade/examples/sprite_collect_coins_background.py
new file mode 100644
index 0000000..4953b80
--- /dev/null
+++ b/arcade/examples/sprite_collect_coins_background.py
@@ -0,0 +1,139 @@
+"""
+Sprite Collect Coins with Background
+
+Simple program to show basic sprite usage.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_collect_coins_background
+"""
+import random
+import arcade
+import os
+
+SPRITE_SCALING = 0.5
+
+SCREEN_WIDTH = 1024
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite Collect Coins with Background Example"
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ """ Initializer """
+
+ # Call the parent class initializer
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Background image will be stored in this variable
+ self.background = None
+
+ # Variables that will hold sprite lists
+ self.player_list = None
+ self.coin_list = None
+
+ # Set up the player info
+ self.player_sprite = None
+ self.score = 0
+ self.score_text = None
+
+ # Don't show the mouse cursor
+ self.set_mouse_visible(False)
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Load the background image. Do this in the setup so we don't keep reloading it all the time.
+ # Image from:
+ # http://wallpaper-gallery.net/single/free-background-images/free-background-images-22.html
+ self.background = arcade.load_texture("images/background.jpg")
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Set up the player
+ self.score = 0
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 50
+ self.player_list.append(self.player_sprite)
+
+ for i in range(50):
+
+ # Create the coin instance
+ coin = arcade.Sprite("images/coin_01.png", SPRITE_SCALING / 3)
+
+ # Position the coin
+ coin.center_x = random.randrange(SCREEN_WIDTH)
+ coin.center_y = random.randrange(SCREEN_HEIGHT)
+
+ # Add the coin to the lists
+ self.coin_list.append(coin)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw the background texture
+ arcade.draw_texture_rectangle(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2,
+ SCREEN_WIDTH, SCREEN_HEIGHT, self.background)
+
+ # Draw all the sprites.
+ self.coin_list.draw()
+ self.player_list.draw()
+
+ # Render the text
+ arcade.draw_text(f"Score: {self.score}", 10, 20, arcade.color.WHITE, 14)
+
+ def on_mouse_motion(self, x, y, dx, dy):
+ """
+ Called whenever the mouse moves.
+ """
+ self.player_sprite.center_x = x
+ self.player_sprite.center_y = y
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on the coin sprites (The sprites don't do much in this
+ # example though.)
+ self.coin_list.update()
+
+ # Generate a list of all sprites that collided with the player.
+ hit_list = arcade.check_for_collision_with_list(self.player_sprite, self.coin_list)
+
+ # Loop through each colliding sprite, remove it, and add to the score.
+ for coin in hit_list:
+ coin.kill()
+ self.score += 1
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_collect_coins_diff_levels.py b/arcade/examples/sprite_collect_coins_diff_levels.py
new file mode 100644
index 0000000..a0552e8
--- /dev/null
+++ b/arcade/examples/sprite_collect_coins_diff_levels.py
@@ -0,0 +1,202 @@
+"""
+Sprite Collect Coins with Different Levels
+
+Simple program to show basic sprite usage.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_collect_coins_diff_levels
+"""
+
+import random
+import arcade
+import os
+
+SPRITE_SCALING = 0.5
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite Collect Coins with Different Levels Example"
+
+
+class FallingCoin(arcade.Sprite):
+ """ Simple sprite that falls down """
+
+ def update(self):
+ """ Move the coin """
+
+ # Fall down
+ self.center_y -= 2
+
+ # Did we go off the screen? If so, pop back to the top.
+ if self.top < 0:
+ self.bottom = SCREEN_HEIGHT
+
+
+class RisingCoin(arcade.Sprite):
+ """ Simple sprite that falls up """
+
+ def update(self):
+ """ Move the coin """
+
+ # Move up
+ self.center_y += 2
+
+ # Did we go off the screen? If so, pop back to the bottom.
+ if self.bottom > SCREEN_HEIGHT:
+ self.top = 0
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ """ Initialize """
+
+ # Call the parent class initializer
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Variables that will hold sprite lists
+ self.player_list = None
+ self.coin_list = None
+
+ # Set up the player info
+ self.player_sprite = None
+ self.score = 0
+
+ self.level = 1
+
+ # Don't show the mouse cursor
+ self.set_mouse_visible(False)
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def level_1(self):
+ for i in range(20):
+
+ # Create the coin instance
+ coin = arcade.Sprite("images/coin_01.png", SPRITE_SCALING / 3)
+
+ # Position the coin
+ coin.center_x = random.randrange(SCREEN_WIDTH)
+ coin.center_y = random.randrange(SCREEN_HEIGHT)
+
+ # Add the coin to the lists
+ self.coin_list.append(coin)
+
+ def level_2(self):
+ for i in range(30):
+
+ # Create the coin instance
+ coin = FallingCoin("images/gold_1.png", SPRITE_SCALING / 2)
+
+ # Position the coin
+ coin.center_x = random.randrange(SCREEN_WIDTH)
+ coin.center_y = random.randrange(SCREEN_HEIGHT, SCREEN_HEIGHT * 2)
+
+ # Add the coin to the lists
+ self.coin_list.append(coin)
+
+ def level_3(self):
+ for i in range(30):
+
+ # Create the coin instance
+ coin = RisingCoin("images/gold_1.png", SPRITE_SCALING / 2)
+
+ # Position the coin
+ coin.center_x = random.randrange(SCREEN_WIDTH)
+ coin.center_y = random.randrange(-SCREEN_HEIGHT, 0)
+
+ # Add the coin to the lists
+ self.coin_list.append(coin)
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ self.score = 0
+ self.level = 1
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Set up the player
+ self.player_sprite = arcade.Sprite("images/character.png",
+ SPRITE_SCALING)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 50
+ self.player_list.append(self.player_sprite)
+
+ self.level_1()
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.player_sprite.draw()
+ self.coin_list.draw()
+
+ # Put the text on the screen.
+ output = f"Score: {self.score}"
+ arcade.draw_text(output, 10, 20, arcade.color.WHITE, 15)
+
+ output = f"Level: {self.level}"
+ arcade.draw_text(output, 10, 35, arcade.color.WHITE, 15)
+
+ def on_mouse_motion(self, x, y, dx, dy):
+ """
+ Called whenever the mouse moves.
+ """
+ self.player_sprite.center_x = x
+ self.player_sprite.center_y = y
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.coin_list.update()
+
+ # Generate a list of all sprites that collided with the player.
+ hit_list = arcade.check_for_collision_with_list(self.player_sprite, self.coin_list)
+
+ # Loop through each colliding sprite, remove it, and add to the score.
+ for coin in hit_list:
+ coin.kill()
+ self.score += 1
+
+ # See if we should go to level 2
+ if len(self.coin_list) == 0 and self.level == 1:
+ self.level += 1
+ self.level_2()
+ # See if we should go to level 3
+ elif len(self.coin_list) == 0 and self.level == 2:
+ self.level += 1
+ self.level_3()
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_collect_coins_move_bouncing.py b/arcade/examples/sprite_collect_coins_move_bouncing.py
new file mode 100644
index 0000000..78fbd39
--- /dev/null
+++ b/arcade/examples/sprite_collect_coins_move_bouncing.py
@@ -0,0 +1,157 @@
+"""
+Sprite Collect Moving and Bouncing Coins
+
+Simple program to show basic sprite usage.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_collect_coins_move_bouncing
+"""
+
+import random
+import arcade
+import os
+
+# --- Constants ---
+SPRITE_SCALING_PLAYER = 0.5
+SPRITE_SCALING_COIN = 0.2
+COIN_COUNT = 50
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite Collect Moving and Bouncing Coins Example"
+
+
+class Coin(arcade.Sprite):
+
+ def __init__(self, filename, sprite_scaling):
+
+ super().__init__(filename, sprite_scaling)
+
+ self.change_x = 0
+ self.change_y = 0
+
+ def update(self):
+
+ # Move the coin
+ self.center_x += self.change_x
+ self.center_y += self.change_y
+
+ # If we are out-of-bounds, then 'bounce'
+ if self.left < 0:
+ self.change_x *= -1
+
+ if self.right > SCREEN_WIDTH:
+ self.change_x *= -1
+
+ if self.bottom < 0:
+ self.change_y *= -1
+
+ if self.top > SCREEN_HEIGHT:
+ self.change_y *= -1
+
+
+class MyGame(arcade.Window):
+ """ Our custom Window Class"""
+
+ def __init__(self):
+ """ Initializer """
+ # Call the parent class initializer
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Variables that will hold sprite lists
+ self.all_sprites_list = None
+ self.coin_list = None
+
+ # Set up the player info
+ self.player_sprite = None
+ self.score = 0
+
+ # Don't show the mouse cursor
+ self.set_mouse_visible(False)
+
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.all_sprites_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Score
+ self.score = 0
+
+ # Set up the player
+ # Character image from kenney.nl
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING_PLAYER)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 50
+ self.all_sprites_list.append(self.player_sprite)
+
+ # Create the coins
+ for i in range(50):
+
+ # Create the coin instance
+ # Coin image from kenney.nl
+ coin = Coin("images/coin_01.png", SPRITE_SCALING_COIN)
+
+ # Position the coin
+ coin.center_x = random.randrange(SCREEN_WIDTH)
+ coin.center_y = random.randrange(SCREEN_HEIGHT)
+ coin.change_x = random.randrange(-3, 4)
+ coin.change_y = random.randrange(-3, 4)
+
+ # Add the coin to the lists
+ self.all_sprites_list.append(coin)
+ self.coin_list.append(coin)
+
+ def on_draw(self):
+ """ Draw everything """
+ arcade.start_render()
+ self.all_sprites_list.draw()
+
+ # Put the text on the screen.
+ output = f"Score: {self.score}"
+ arcade.draw_text(output, 10, 20, arcade.color.WHITE, 14)
+
+ def on_mouse_motion(self, x, y, dx, dy):
+ """ Handle Mouse Motion """
+
+ # Move the center of the player sprite to match the mouse x, y
+ self.player_sprite.center_x = x
+ self.player_sprite.center_y = y
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.all_sprites_list.update()
+
+ # Generate a list of all sprites that collided with the player.
+ hit_list = arcade.check_for_collision_with_list(self.player_sprite,
+ self.coin_list)
+
+ # Loop through each colliding sprite, remove it, and add to the score.
+ for coin in hit_list:
+ coin.kill()
+ self.score += 1
+
+
+def main():
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_collect_coins_move_circle.py b/arcade/examples/sprite_collect_coins_move_circle.py
new file mode 100644
index 0000000..5d3bcc6
--- /dev/null
+++ b/arcade/examples/sprite_collect_coins_move_circle.py
@@ -0,0 +1,160 @@
+"""
+Sprite Collect Coins Moving in Circles
+
+Simple program to show basic sprite usage.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_collect_coins_move_circle
+"""
+
+import random
+import arcade
+import math
+import os
+
+SPRITE_SCALING = 0.5
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite Collect Coins Moving in Circles Example"
+
+
+class Coin(arcade.Sprite):
+
+ def __init__(self, filename, sprite_scaling):
+ """ Constructor. """
+ # Call the parent class (Sprite) constructor
+ super().__init__(filename, sprite_scaling)
+
+ # Current angle in radians
+ self.circle_angle = 0
+
+ # How far away from the center to orbit, in pixels
+ self.circle_radius = 0
+
+ # How fast to orbit, in radians per frame
+ self.circle_speed = 0.008
+
+ # Set the center of the point we will orbit around
+ self.circle_center_x = 0
+ self.circle_center_y = 0
+
+ def update(self):
+
+ """ Update the ball's position. """
+ # Calculate a new x, y
+ self.center_x = self.circle_radius * math.sin(self.circle_angle) \
+ + self.circle_center_x
+ self.center_y = self.circle_radius * math.cos(self.circle_angle) \
+ + self.circle_center_y
+
+ # Increase the angle in prep for the next round.
+ self.circle_angle += self.circle_speed
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self, width, height, title):
+
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Sprite lists
+ self.all_sprites_list = None
+ self.coin_list = None
+
+ # Set up the player
+ self.score = 0
+ self.player_sprite = None
+
+ def start_new_game(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.all_sprites_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Set up the player
+ self.score = 0
+ # Character image from kenney.nl
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 70
+ self.all_sprites_list.append(self.player_sprite)
+
+ for i in range(50):
+
+ # Create the coin instance
+ # Coin image from kenney.nl
+ coin = Coin("images/coin_01.png", SPRITE_SCALING / 3)
+
+ # Position the center of the circle the coin will orbit
+ coin.circle_center_x = random.randrange(SCREEN_WIDTH)
+ coin.circle_center_y = random.randrange(SCREEN_HEIGHT)
+
+ # Random radius from 10 to 200
+ coin.circle_radius = random.randrange(10, 200)
+
+ # Random start angle from 0 to 2pi
+ coin.circle_angle = random.random() * 2 * math.pi
+
+ # Add the coin to the lists
+ self.all_sprites_list.append(coin)
+ self.coin_list.append(coin)
+
+ # Don't show the mouse cursor
+ self.set_mouse_visible(False)
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def on_draw(self):
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.all_sprites_list.draw()
+
+ # Put the text on the screen.
+ output = "Score: " + str(self.score)
+ arcade.draw_text(output, 10, 20, arcade.color.WHITE, 14)
+
+ def on_mouse_motion(self, x, y, dx, dy):
+ self.player_sprite.center_x = x
+ self.player_sprite.center_y = y
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.all_sprites_list.update()
+
+ # Generate a list of all sprites that collided with the player.
+ hit_list = arcade.check_for_collision_with_list(self.player_sprite,
+ self.coin_list)
+
+ # Loop through each colliding sprite, remove it, and add to the score.
+ for coin in hit_list:
+ self.score += 1
+ coin.kill()
+
+
+def main():
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.start_new_game()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_collect_coins_move_down.py b/arcade/examples/sprite_collect_coins_move_down.py
new file mode 100644
index 0000000..747eb37
--- /dev/null
+++ b/arcade/examples/sprite_collect_coins_move_down.py
@@ -0,0 +1,151 @@
+"""
+Sprite Collect Coins Moving Down
+
+Simple program to show basic sprite usage.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_collect_coins_move_down
+"""
+
+import random
+import arcade
+import os
+
+# --- Constants ---
+SPRITE_SCALING_PLAYER = 0.5
+SPRITE_SCALING_COIN = 0.2
+COIN_COUNT = 50
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite Collect Coins Moving Down Example"
+
+
+class Coin(arcade.Sprite):
+ """
+ This class represents the coins on our screen. It is a child class of
+ the arcade library's "Sprite" class.
+ """
+
+ def reset_pos(self):
+
+ # Reset the coin to a random spot above the screen
+ self.center_y = random.randrange(SCREEN_HEIGHT + 20,
+ SCREEN_HEIGHT + 100)
+ self.center_x = random.randrange(SCREEN_WIDTH)
+
+ def update(self):
+
+ # Move the coin
+ self.center_y -= 1
+
+ # See if the coin has fallen off the bottom of the screen.
+ # If so, reset it.
+ if self.top < 0:
+ self.reset_pos()
+
+
+class MyGame(arcade.Window):
+ """ Our custom Window Class"""
+
+ def __init__(self):
+ """ Initializer """
+
+ # Call the parent class initializer
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Variables that will hold sprite lists
+ self.player_sprite_list = None
+ self.coin_sprite_list = None
+
+ # Set up the player info
+ self.player_sprite = None
+ self.score = 0
+
+ # Don't show the mouse cursor
+ self.set_mouse_visible(False)
+
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_sprite_list = arcade.SpriteList()
+ self.coin_sprite_list = arcade.SpriteList()
+
+ # Score
+ self.score = 0
+
+ # Set up the player
+ # Character image from kenney.nl
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING_PLAYER)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 50
+ self.player_sprite_list.append(self.player_sprite)
+
+ # Create the coins
+ for i in range(COIN_COUNT):
+
+ # Create the coin instance
+ # Coin image from kenney.nl
+ coin = Coin("images/coin_01.png", SPRITE_SCALING_COIN)
+
+ # Position the coin
+ coin.center_x = random.randrange(SCREEN_WIDTH)
+ coin.center_y = random.randrange(SCREEN_HEIGHT)
+
+ # Add the coin to the lists
+ self.coin_sprite_list.append(coin)
+
+ def on_draw(self):
+ """ Draw everything """
+ arcade.start_render()
+ self.coin_sprite_list.draw()
+ self.player_sprite_list.draw()
+
+ # Put the text on the screen.
+ output = f"Score: {self.score}"
+ arcade.draw_text(output, 10, 20, arcade.color.WHITE, 14)
+
+ def on_mouse_motion(self, x, y, dx, dy):
+ """ Handle Mouse Motion """
+
+ # Move the center of the player sprite to match the mouse x, y
+ self.player_sprite.center_x = x
+ self.player_sprite.center_y = y
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.coin_sprite_list.update()
+
+ # Generate a list of all sprites that collided with the player.
+ hit_list = arcade.check_for_collision_with_list(self.player_sprite,
+ self.coin_sprite_list)
+
+ # Loop through each colliding sprite, remove it, and add to the score.
+ for coin in hit_list:
+ coin.kill()
+ self.score += 1
+
+
+def main():
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_collect_coins_with_stats.py b/arcade/examples/sprite_collect_coins_with_stats.py
new file mode 100644
index 0000000..1ab6689
--- /dev/null
+++ b/arcade/examples/sprite_collect_coins_with_stats.py
@@ -0,0 +1,158 @@
+"""
+Sprite Collect Coins with Stats
+
+Simple program to show basic sprite usage.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_collect_coins_with_stats
+"""
+
+import random
+import arcade
+import os
+import timeit
+
+# --- Constants ---
+SPRITE_SCALING_PLAYER = 0.5
+SPRITE_SCALING_COIN = 0.2
+COIN_COUNT = 50
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite Collect Coins with Stats Example"
+
+
+class MyGame(arcade.Window):
+ """ Our custom Window Class"""
+
+ def __init__(self):
+ """ Initializer """
+ # Call the parent class initializer
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Variables that will hold sprite lists
+ self.player_list = None
+ self.coin_list = None
+
+ # Set up the player info
+ self.player_sprite = None
+ self.score = 0
+
+ # Don't show the mouse cursor
+ self.set_mouse_visible(False)
+
+ self.processing_time = 0
+ self.draw_time = 0
+ self.frame_count = 0
+ self.fps_start_timer = None
+ self.fps = None
+
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Score
+ self.score = 0
+
+ # Set up the player
+ # Character image from kenney.nl
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING_PLAYER)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 50
+ self.player_list.append(self.player_sprite)
+
+ # Create the coins
+ for i in range(COIN_COUNT):
+
+ # Create the coin instance
+ # Coin image from kenney.nl
+ coin = arcade.Sprite("images/coin_01.png", SPRITE_SCALING_COIN)
+
+ # Position the coin
+ coin.center_x = random.randrange(SCREEN_WIDTH)
+ coin.center_y = random.randrange(SCREEN_HEIGHT)
+
+ # Add the coin to the lists
+ self.coin_list.append(coin)
+
+ def on_draw(self):
+ """ Draw everything """
+
+ # Start timing how long this takes
+ draw_start_time = timeit.default_timer()
+
+ if self.frame_count % 60 == 0:
+ if self.fps_start_timer is not None:
+ total_time = timeit.default_timer() - self.fps_start_timer
+ self.fps = 60 / total_time
+ self.fps_start_timer = timeit.default_timer()
+ self.frame_count += 1
+
+ arcade.start_render()
+ self.coin_list.draw()
+ self.player_list.draw()
+
+ # Put the text on the screen.
+ output = f"Score: {self.score}"
+ arcade.draw_text(output, 10, 20, arcade.color.WHITE, 14)
+
+ # Display timings
+ output = f"Processing time: {self.processing_time:.3f}"
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 20, arcade.color.BLACK, 16)
+
+ output = f"Drawing time: {self.draw_time:.3f}"
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 40, arcade.color.BLACK, 16)
+
+ if self.fps is not None:
+ output = f"FPS: {self.fps:.0f}"
+ arcade.draw_text(output, 20, SCREEN_HEIGHT - 60, arcade.color.BLACK, 16)
+
+ self.draw_time = timeit.default_timer() - draw_start_time
+
+ def on_mouse_motion(self, x, y, dx, dy):
+ """ Handle Mouse Motion """
+
+ # Move the center of the player sprite to match the mouse x, y
+ self.player_sprite.center_x = x
+ self.player_sprite.center_y = y
+ # print(x, y)
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.coin_list.update()
+
+ # Generate a list of all sprites that collided with the player.
+ coins_hit_list = arcade.check_for_collision_with_list(self.player_sprite, self.coin_list)
+
+ # Loop through each colliding sprite, remove it, and add to the score.
+ for coin in coins_hit_list:
+ coin.kill()
+ self.score += 1
+
+
+def main():
+ """ Main method """
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_collect_rotating.py b/arcade/examples/sprite_collect_rotating.py
new file mode 100644
index 0000000..839e880
--- /dev/null
+++ b/arcade/examples/sprite_collect_rotating.py
@@ -0,0 +1,138 @@
+"""
+Sprite Collect Rotating Coins
+
+Simple program to show basic sprite usage.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_collect_rotating
+"""
+
+import random
+import arcade
+import os
+
+# --- Constants ---
+SPRITE_SCALING_PLAYER = 0.5
+SPRITE_SCALING_COIN = 0.2
+COIN_COUNT = 50
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite Collect Rotating Coins Example"
+
+
+class Coin(arcade.Sprite):
+
+ def update(self):
+ # Rotate the coin.
+ # The arcade.Sprite class has an "angle" attribute that controls
+ # the sprite rotation. Change this, and the sprite rotates.
+ self.angle += self.change_angle
+
+
+class MyGame(arcade.Window):
+ """ Our custom Window Class"""
+
+ def __init__(self):
+ """ Initializer """
+ # Call the parent class initializer
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Variables that will hold sprite lists
+ self.player_list = None
+ self.coin_list = None
+
+ # Set up the player info
+ self.player_sprite = None
+ self.score = 0
+
+ # Don't show the mouse cursor
+ self.set_mouse_visible(False)
+
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Score
+ self.score = 0
+
+ # Set up the player
+ # Character image from kenney.nl
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING_PLAYER)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 50
+ self.player_list.append(self.player_sprite)
+
+ # Create the coins
+ for i in range(COIN_COUNT):
+ # Create the coin instance
+ # Coin image from kenney.nl
+ coin = arcade.Sprite("images/coin_01.png", SPRITE_SCALING_COIN)
+
+ # Position the coin
+ coin.center_x = random.randrange(SCREEN_WIDTH)
+ coin.center_y = random.randrange(SCREEN_HEIGHT)
+
+ # Set up the initial angle, and the "spin"
+ coin.angle = random.randrange(360)
+ coin.change_angle = random.randrange(-5, 6)
+
+ # Add the coin to the lists
+ self.coin_list.append(coin)
+
+ def on_draw(self):
+ """ Draw everything """
+ arcade.start_render()
+ self.coin_list.draw()
+ self.player_list.draw()
+
+ # Put the text on the screen.
+ output = f"Score: {self.score}"
+ arcade.draw_text(output, 10, 20, arcade.color.WHITE, 14)
+
+ def on_mouse_motion(self, x, y, dx, dy):
+ """ Handle Mouse Motion """
+
+ # Move the center of the player sprite to match the mouse x, y
+ self.player_sprite.center_x = x
+ self.player_sprite.center_y = y
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.coin_list.update()
+
+ # Generate a list of all sprites that collided with the player.
+ hit_list = arcade.check_for_collision_with_list(self.player_sprite, self.coin_list)
+
+ # Loop through each colliding sprite, remove it, and add to the score.
+ for coin in hit_list:
+ coin.remove_from_sprite_lists()
+ self.score += 1
+
+
+def main():
+ """ Main method """
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_csv_map.py b/arcade/examples/sprite_csv_map.py
new file mode 100644
index 0000000..a3feb95
--- /dev/null
+++ b/arcade/examples/sprite_csv_map.py
@@ -0,0 +1,234 @@
+"""
+Load a map stored in csv format, as exported by the program 'Tiled.'
+
+Artwork from: http://kenney.nl
+Tiled available from: http://www.mapeditor.org/
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_csv_map
+"""
+
+import arcade
+import os
+
+SPRITE_SCALING = 0.5
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite CSV Map Example"
+SPRITE_PIXEL_SIZE = 128
+GRID_PIXEL_SIZE = (SPRITE_PIXEL_SIZE * SPRITE_SCALING)
+
+# How many pixels to keep as a minimum margin between the character
+# and the edge of the screen.
+VIEWPORT_MARGIN = 40
+RIGHT_MARGIN = 150
+
+# Physics
+MOVEMENT_SPEED = 5
+JUMP_SPEED = 14
+GRAVITY = 0.5
+
+
+def get_map(filename):
+ """
+ This function loads an array based on a map stored as a list of
+ numbers separated by commas.
+ """
+ map_file = open(filename)
+ map_array = []
+ for line in map_file:
+ line = line.strip()
+ map_row = line.split(",")
+ for index, item in enumerate(map_row):
+ map_row[index] = int(item)
+ map_array.append(map_row)
+ return map_array
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self):
+ """
+ Initializer
+ """
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Sprite lists
+ self.wall_list = None
+ self.player_list = None
+
+ # Set up the player
+ self.player_sprite = None
+
+ self.physics_engine = None
+ self.view_left = 0
+ self.view_bottom = 0
+ self.game_over = False
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.wall_list = arcade.SpriteList()
+
+ # Set up the player
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING)
+
+ # Starting position of the player
+ self.player_sprite.center_x = 64
+ self.player_sprite.center_y = 270
+ self.player_list.append(self.player_sprite)
+
+ # Get a 2D array made of numbers based on the map
+ map_array = get_map("map.csv")
+
+ # Right edge of the map in pixels
+ self.end_of_map = len(map_array[0]) * GRID_PIXEL_SIZE
+
+ for row_index, row in enumerate(map_array):
+ for column_index, item in enumerate(row):
+
+ # For this map, the numbers represent:
+ # -1 = empty
+ # 0 = box
+ # 1 = grass left edge
+ # 2 = grass middle
+ # 3 = grass right edge
+ if item == -1:
+ continue
+ elif item == 0:
+ wall = arcade.Sprite("images/boxCrate_double.png", SPRITE_SCALING)
+ elif item == 1:
+ wall = arcade.Sprite("images/grassLeft.png", SPRITE_SCALING)
+ elif item == 2:
+ wall = arcade.Sprite("images/grassMid.png", SPRITE_SCALING)
+ elif item == 3:
+ wall = arcade.Sprite("images/grassRight.png", SPRITE_SCALING)
+
+ wall.right = column_index * 64
+ wall.top = (7 - row_index) * 64
+ self.wall_list.append(wall)
+
+ self.physics_engine = \
+ arcade.PhysicsEnginePlatformer(self.player_sprite,
+ self.wall_list,
+ gravity_constant=GRAVITY)
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ # Set the view port boundaries
+ # These numbers set where we have 'scrolled' to.
+ self.view_left = 0
+ self.view_bottom = 0
+
+ self.game_over = False
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.player_list.draw()
+ self.wall_list.draw()
+
+ # Put the text on the screen.
+ # Adjust the text position based on the view port so that we don't
+ # scroll the text too.
+ distance = self.player_sprite.right
+ output = f"Distance: {distance}"
+ arcade.draw_text(output, self.view_left + 10, self.view_bottom + 20, arcade.color.WHITE, 14)
+
+ if self.game_over:
+ arcade.draw_text("Game Over", self.view_left + 200, self.view_bottom + 200, arcade.color.WHITE, 30)
+
+ def on_key_press(self, key, modifiers):
+ """
+ Called whenever the mouse moves.
+ """
+ if key == arcade.key.UP:
+ if self.physics_engine.can_jump():
+ self.player_sprite.change_y = JUMP_SPEED
+ elif key == arcade.key.LEFT:
+ self.player_sprite.change_x = -MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT:
+ self.player_sprite.change_x = MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """
+ Called when the user presses a mouse button.
+ """
+ if key == arcade.key.LEFT or key == arcade.key.RIGHT:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ if self.player_sprite.right >= self.end_of_map:
+ self.game_over = True
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ if not self.game_over:
+ self.physics_engine.update()
+
+ # --- Manage Scrolling ---
+
+ # Track if we need to change the view port
+
+ changed = False
+
+ # Scroll left
+ left_bndry = self.view_left + VIEWPORT_MARGIN
+ if self.player_sprite.left < left_bndry:
+ self.view_left -= left_bndry - self.player_sprite.left
+ changed = True
+
+ # Scroll right
+ right_bndry = self.view_left + SCREEN_WIDTH - RIGHT_MARGIN
+ if self.player_sprite.right > right_bndry:
+ self.view_left += self.player_sprite.right - right_bndry
+ changed = True
+
+ # Scroll up
+ top_bndry = self.view_bottom + SCREEN_HEIGHT - VIEWPORT_MARGIN
+ if self.player_sprite.top > top_bndry:
+ self.view_bottom += self.player_sprite.top - top_bndry
+ changed = True
+
+ # Scroll down
+ bottom_bndry = self.view_bottom + VIEWPORT_MARGIN
+ if self.player_sprite.bottom < bottom_bndry:
+ self.view_bottom -= bottom_bndry - self.player_sprite.bottom
+ changed = True
+
+ # If we need to scroll, go ahead and do it.
+ if changed:
+ arcade.set_viewport(self.view_left,
+ SCREEN_WIDTH + self.view_left,
+ self.view_bottom,
+ SCREEN_HEIGHT + self.view_bottom)
+
+
+def main():
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_enemies_in_platformer.py b/arcade/examples/sprite_enemies_in_platformer.py
new file mode 100644
index 0000000..9fbf528
--- /dev/null
+++ b/arcade/examples/sprite_enemies_in_platformer.py
@@ -0,0 +1,197 @@
+"""
+Show how to do enemies in a platformer
+
+Artwork from: http://kenney.nl
+Tiled available from: http://www.mapeditor.org/
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_enemies_in_platformer
+"""
+
+import arcade
+import os
+
+SPRITE_SCALING = 0.5
+SPRITE_NATIVE_SIZE = 128
+SPRITE_SIZE = int(SPRITE_NATIVE_SIZE * SPRITE_SCALING)
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite Enemies in a Platformer Example"
+
+# How many pixels to keep as a minimum margin between the character
+# and the edge of the screen.
+VIEWPORT_MARGIN = 40
+RIGHT_MARGIN = 150
+
+# Physics
+MOVEMENT_SPEED = 5
+JUMP_SPEED = 14
+GRAVITY = 0.5
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self):
+ """
+ Initializer
+ """
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Sprite lists
+ self.wall_list = None
+ self.enemy_list = None
+ self.player_list = None
+
+ # Set up the player
+ self.player_sprite = None
+ self.physics_engine = None
+ self.view_left = 0
+ self.view_bottom = 0
+ self.game_over = False
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.wall_list = arcade.SpriteList()
+ self.enemy_list = arcade.SpriteList()
+ self.player_list = arcade.SpriteList()
+
+ # Draw the walls on the bottom
+ for x in range(0, SCREEN_WIDTH, SPRITE_SIZE):
+ wall = arcade.Sprite("images/grassMid.png", SPRITE_SCALING)
+
+ wall.bottom = 0
+ wall.left = x
+ self.wall_list.append(wall)
+
+ # Draw the platform
+ for x in range(SPRITE_SIZE * 3, SPRITE_SIZE * 8, SPRITE_SIZE):
+ wall = arcade.Sprite("images/grassMid.png", SPRITE_SCALING)
+
+ wall.bottom = SPRITE_SIZE * 3
+ wall.left = x
+ self.wall_list.append(wall)
+
+ # Draw the crates
+ for x in range(0, SCREEN_WIDTH, SPRITE_SIZE * 5):
+ wall = arcade.Sprite("images/boxCrate_double.png", SPRITE_SCALING)
+
+ wall.bottom = SPRITE_SIZE
+ wall.left = x
+ self.wall_list.append(wall)
+
+ # -- Draw an enemy on the ground
+ enemy = arcade.Sprite("images/wormGreen.png", SPRITE_SCALING)
+
+ enemy.bottom = SPRITE_SIZE
+ enemy.left = SPRITE_SIZE * 2
+
+ # Set enemy initial speed
+ enemy.change_x = 2
+ self.enemy_list.append(enemy)
+
+ # -- Draw a enemy on the platform
+ enemy = arcade.Sprite("images/wormGreen.png", SPRITE_SCALING)
+
+ enemy.bottom = SPRITE_SIZE * 4
+ enemy.left = SPRITE_SIZE * 4
+
+ # Set boundaries on the left/right the enemy can't cross
+ enemy.boundary_right = SPRITE_SIZE * 8
+ enemy.boundary_left = SPRITE_SIZE * 3
+ enemy.change_x = 2
+ self.enemy_list.append(enemy)
+
+ # -- Set up the player
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING)
+ self.player_list.append(self.player_sprite)
+
+ # Starting position of the player
+ self.player_sprite.center_x = 64
+ self.player_sprite.center_y = 270
+
+ self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
+ self.wall_list,
+ gravity_constant=GRAVITY)
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.player_list.draw()
+ self.wall_list.draw()
+ self.enemy_list.draw()
+
+ def on_key_press(self, key, modifiers):
+ """
+ Called whenever the mouse moves.
+ """
+ if key == arcade.key.UP:
+ if self.physics_engine.can_jump():
+ self.player_sprite.change_y = JUMP_SPEED
+ elif key == arcade.key.LEFT:
+ self.player_sprite.change_x = -MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT:
+ self.player_sprite.change_x = MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """
+ Called when the user presses a mouse button.
+ """
+ if key == arcade.key.LEFT or key == arcade.key.RIGHT:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Update the player based on the physics engine
+ if not self.game_over:
+ # Move the enemies
+ self.enemy_list.update()
+
+ # Check each enemy
+ for enemy in self.enemy_list:
+ # If the enemy hit a wall, reverse
+ if len(arcade.check_for_collision_with_list(enemy, self.wall_list)) > 0:
+ enemy.change_x *= -1
+ # If the enemy hit the left boundary, reverse
+ elif enemy.boundary_left is not None and enemy.left < enemy.boundary_left:
+ enemy.change_x *= -1
+ # If the enemy hit the right boundary, reverse
+ elif enemy.boundary_right is not None and enemy.right > enemy.boundary_right:
+ enemy.change_x *= -1
+
+ # Update the player using the physics engine
+ self.physics_engine.update()
+
+ # See if the player hit a worm. If so, game over.
+ if len(arcade.check_for_collision_with_list(self.player_sprite, self.enemy_list)) > 0:
+ self.game_over = True
+
+
+def main():
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_explosion.py b/arcade/examples/sprite_explosion.py
new file mode 100644
index 0000000..b609b2c
--- /dev/null
+++ b/arcade/examples/sprite_explosion.py
@@ -0,0 +1,226 @@
+"""
+Sprite Explosion
+
+Simple program to show basic sprite usage.
+
+Artwork from http://kenney.nl
+Explosion graphics from http://www.explosiongenerator.com/
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_explosion
+"""
+import random
+import arcade
+import os
+
+SPRITE_SCALING_PLAYER = 0.5
+SPRITE_SCALING_COIN = 0.2
+SPRITE_SCALING_LASER = 0.8
+COIN_COUNT = 50
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite Explosion Example"
+
+BULLET_SPEED = 5
+
+EXPLOSION_TEXTURE_COUNT = 60
+
+
+class Explosion(arcade.Sprite):
+ """ This class creates an explosion animation """
+
+ # Static variable that holds all the explosion textures
+ explosion_textures = []
+
+ def __init__(self, texture_list):
+ super().__init__("images/explosion/explosion0000.png")
+
+ # Start at the first frame
+ self.current_texture = 0
+ self.textures = texture_list
+
+ def update(self):
+
+ # Update to the next frame of the animation. If we are at the end
+ # of our frames, then delete this sprite.
+ self.current_texture += 1
+ if self.current_texture < len(self.textures):
+ self.set_texture(self.current_texture)
+ else:
+ self.kill()
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self):
+ """ Initializer """
+ # Call the parent class initializer
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Variables that will hold sprite lists
+ self.player_list = None
+ self.coin_list = None
+ self.bullet_list = None
+ self.explosions_list = None
+
+ # Set up the player info
+ self.player_sprite = None
+ self.score = 0
+
+ # Don't show the mouse cursor
+ self.set_mouse_visible(False)
+
+ # Pre-load the animation frames. We don't do this in the __init__
+ # of the explosion sprite because it
+ # takes too long and would cause the game to pause.
+ self.explosion_texture_list = []
+
+ for i in range(EXPLOSION_TEXTURE_COUNT):
+ # Files from http://www.explosiongenerator.com are numbered sequentially.
+ # This code loads all of the explosion0000.png to explosion0270.png files
+ # that are part of this explosion.
+ texture_name = f"images/explosion/explosion{i:04d}.png"
+
+ self.explosion_texture_list.append(arcade.load_texture(texture_name))
+
+ # Load sounds. Sounds from kenney.nl
+ self.gun_sound = arcade.sound.load_sound("sounds/laser1.wav")
+ self.hit_sound = arcade.sound.load_sound("sounds/phaseJump1.wav")
+
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def setup(self):
+
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+ self.bullet_list = arcade.SpriteList()
+ self.explosions_list = arcade.SpriteList()
+
+ # Set up the player
+ self.score = 0
+
+ # Image from kenney.nl
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING_PLAYER)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 70
+ self.player_list.append(self.player_sprite)
+
+ # Create the coins
+ for coin_index in range(COIN_COUNT):
+
+ # Create the coin instance
+ # Coin image from kenney.nl
+ coin = arcade.Sprite("images/coin_01.png", SPRITE_SCALING_COIN)
+
+ # Position the coin
+ coin.center_x = random.randrange(SCREEN_WIDTH)
+ coin.center_y = random.randrange(150, SCREEN_HEIGHT)
+
+ # Add the coin to the lists
+ self.coin_list.append(coin)
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.coin_list.draw()
+ self.bullet_list.draw()
+ self.player_list.draw()
+ self.explosions_list.draw()
+
+ # Render the text
+ arcade.draw_text(f"Score: {self.score}", 10, 20, arcade.color.WHITE, 14)
+
+ def on_mouse_motion(self, x, y, dx, dy):
+ """
+ Called whenever the mouse moves.
+ """
+ self.player_sprite.center_x = x
+
+ def on_mouse_press(self, x, y, button, modifiers):
+ """
+ Called whenever the mouse button is clicked.
+ """
+
+ # Gunshot sound
+ arcade.sound.play_sound(self.gun_sound)
+
+ # Create a bullet
+ bullet = arcade.Sprite("images/laserBlue01.png", SPRITE_SCALING_LASER)
+
+ # The image points to the right, and we want it to point up. So
+ # rotate it.
+ bullet.angle = 90
+
+ # Give it a speed
+ bullet.change_y = BULLET_SPEED
+
+ # Position the bullet
+ bullet.center_x = self.player_sprite.center_x
+ bullet.bottom = self.player_sprite.top
+
+ # Add the bullet to the appropriate lists
+ self.bullet_list.append(bullet)
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on bullet sprites
+ self.bullet_list.update()
+ self.explosions_list.update()
+
+ # Loop through each bullet
+ for bullet in self.bullet_list:
+
+ # Check this bullet to see if it hit a coin
+ hit_list = arcade.check_for_collision_with_list(bullet, self.coin_list)
+
+ # If it did, get rid of the bullet
+ if len(hit_list) > 0:
+ explosion = Explosion(self.explosion_texture_list)
+ explosion.center_x = hit_list[0].center_x
+ explosion.center_y = hit_list[0].center_y
+ self.explosions_list.append(explosion)
+ bullet.kill()
+
+ # For every coin we hit, add to the score and remove the coin
+ for coin in hit_list:
+ coin.kill()
+ self.score += 1
+
+ # Hit Sound
+ arcade.sound.play_sound(self.hit_sound)
+
+ # If the bullet flies off-screen, remove it.
+ if bullet.bottom > SCREEN_HEIGHT:
+ bullet.kill()
+
+
+def main():
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_face_left_or_right.py b/arcade/examples/sprite_face_left_or_right.py
new file mode 100644
index 0000000..68112f3
--- /dev/null
+++ b/arcade/examples/sprite_face_left_or_right.py
@@ -0,0 +1,152 @@
+"""
+Sprite Facing Left or Right
+Face left or right depending on our direction
+
+Simple program to show basic sprite usage.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_face_left_or_right
+"""
+
+import arcade
+import os
+
+SPRITE_SCALING = 0.5
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite Face Left or Right Example"
+
+MOVEMENT_SPEED = 5
+
+TEXTURE_LEFT = 0
+TEXTURE_RIGHT = 1
+
+
+class Player(arcade.Sprite):
+
+ def __init__(self):
+ super().__init__()
+
+ # Load a left facing texture and a right facing texture.
+ # mirrored=True will mirror the image we load.
+ texture = arcade.load_texture("images/character.png", mirrored=True, scale=SPRITE_SCALING)
+ self.textures.append(texture)
+ texture = arcade.load_texture("images/character.png", scale=SPRITE_SCALING)
+ self.textures.append(texture)
+
+ # By default, face right.
+ self.set_texture(TEXTURE_RIGHT)
+
+ def update(self):
+ self.center_x += self.change_x
+ self.center_y += self.change_y
+
+ # Figure out if we should face left or right
+ if self.change_x < 0:
+ self.set_texture(TEXTURE_LEFT)
+ if self.change_x > 0:
+ self.set_texture(TEXTURE_RIGHT)
+
+ if self.left < 0:
+ self.left = 0
+ elif self.right > SCREEN_WIDTH - 1:
+ self.right = SCREEN_WIDTH - 1
+
+ if self.bottom < 0:
+ self.bottom = 0
+ elif self.top > SCREEN_HEIGHT - 1:
+ self.top = SCREEN_HEIGHT - 1
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ """
+ Initializer
+ """
+
+ # Call the parent class initializer
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Variables that will hold sprite lists
+ self.all_sprites_list = None
+
+ # Set up the player info
+ self.player_sprite = None
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.all_sprites_list = arcade.SpriteList()
+
+ # Set up the player
+ self.player_sprite = Player()
+ self.player_sprite.center_x = SCREEN_WIDTH / 2
+ self.player_sprite.center_y = SCREEN_HEIGHT / 2
+ self.all_sprites_list.append(self.player_sprite)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.all_sprites_list.draw()
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.all_sprites_list.update()
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ if key == arcade.key.UP:
+ self.player_sprite.change_y = MOVEMENT_SPEED
+ elif key == arcade.key.DOWN:
+ self.player_sprite.change_y = -MOVEMENT_SPEED
+ elif key == arcade.key.LEFT:
+ self.player_sprite.change_x = -MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT:
+ self.player_sprite.change_x = MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.UP or key == arcade.key.DOWN:
+ self.player_sprite.change_y = 0
+ elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
+ self.player_sprite.change_x = 0
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_follow_simple.py b/arcade/examples/sprite_follow_simple.py
new file mode 100644
index 0000000..2b68d5c
--- /dev/null
+++ b/arcade/examples/sprite_follow_simple.py
@@ -0,0 +1,153 @@
+"""
+Sprite Follow Player
+
+This moves towards the player in both the x and y direction.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_follow_simple
+"""
+
+import random
+import arcade
+import os
+
+# --- Constants ---
+SPRITE_SCALING_PLAYER = 0.5
+SPRITE_SCALING_COIN = 0.2
+COIN_COUNT = 50
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite Follow Player Simple Example"
+
+SPRITE_SPEED = 0.5
+
+
+class Coin(arcade.Sprite):
+ """
+ This class represents the coins on our screen. It is a child class of
+ the arcade library's "Sprite" class.
+ """
+
+ def follow_sprite(self, player_sprite):
+ """
+ This function will move the current sprite towards whatever
+ other sprite is specified as a parameter.
+
+ We use the 'min' function here to get the sprite to line up with
+ the target sprite, and not jump around if the sprite is not off
+ an exact multiple of SPRITE_SPEED.
+ """
+
+ if self.center_y < player_sprite.center_y:
+ self.center_y += min(SPRITE_SPEED, player_sprite.center_y - self.center_y)
+ elif self.center_y > player_sprite.center_y:
+ self.center_y -= min(SPRITE_SPEED, self.center_y - player_sprite.center_y)
+
+ if self.center_x < player_sprite.center_x:
+ self.center_x += min(SPRITE_SPEED, player_sprite.center_x - self.center_x)
+ elif self.center_x > player_sprite.center_x:
+ self.center_x -= min(SPRITE_SPEED, self.center_x - player_sprite.center_x)
+
+
+class MyGame(arcade.Window):
+ """ Our custom Window Class"""
+
+ def __init__(self):
+ """ Initializer """
+ # Call the parent class initializer
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Variables that will hold sprite lists
+ self.player_list = None
+ self.coin_list = None
+
+ # Set up the player info
+ self.player_sprite = None
+ self.score = 0
+
+ # Don't show the mouse cursor
+ self.set_mouse_visible(False)
+
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Score
+ self.score = 0
+
+ # Set up the player
+ # Character image from kenney.nl
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING_PLAYER)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 50
+ self.player_list.append(self.player_sprite)
+
+ # Create the coins
+ for i in range(COIN_COUNT):
+ # Create the coin instance
+ # Coin image from kenney.nl
+ coin = Coin("images/coin_01.png", SPRITE_SCALING_COIN)
+
+ # Position the coin
+ coin.center_x = random.randrange(SCREEN_WIDTH)
+ coin.center_y = random.randrange(SCREEN_HEIGHT)
+
+ # Add the coin to the lists
+ self.coin_list.append(coin)
+
+ def on_draw(self):
+ """ Draw everything """
+ arcade.start_render()
+ self.coin_list.draw()
+ self.player_list.draw()
+
+ # Put the text on the screen.
+ output = f"Score: {self.score}"
+ arcade.draw_text(output, 10, 20, arcade.color.WHITE, 14)
+
+ def on_mouse_motion(self, x, y, dx, dy):
+ """ Handle Mouse Motion """
+
+ # Move the center of the player sprite to match the mouse x, y
+ self.player_sprite.center_x = x
+ self.player_sprite.center_y = y
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ for coin in self.coin_list:
+ coin.follow_sprite(self.player_sprite)
+
+ # Generate a list of all sprites that collided with the player.
+ hit_list = arcade.check_for_collision_with_list(self.player_sprite, self.coin_list)
+
+ # Loop through each colliding sprite, remove it, and add to the score.
+ for coin in hit_list:
+ coin.kill()
+ self.score += 1
+
+
+def main():
+ """ Main method """
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_follow_simple_2.py b/arcade/examples/sprite_follow_simple_2.py
new file mode 100644
index 0000000..8c1d19f
--- /dev/null
+++ b/arcade/examples/sprite_follow_simple_2.py
@@ -0,0 +1,172 @@
+"""
+Sprite Follow Player 2
+
+This calculates a 'vector' towards the player and randomly updates it based
+on the player's location. This is a bit more complex, but more interesting
+way of following the player.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_follow_simple_2
+"""
+
+import random
+import arcade
+import math
+import os
+
+# --- Constants ---
+SPRITE_SCALING_PLAYER = 0.5
+SPRITE_SCALING_COIN = 0.2
+COIN_COUNT = 5
+COIN_SPEED = 0.5
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite Follow Player Simple Example 2"
+
+SPRITE_SPEED = 0.5
+
+
+class Coin(arcade.Sprite):
+ """
+ This class represents the coins on our screen. It is a child class of
+ the arcade library's "Sprite" class.
+ """
+
+ def follow_sprite(self, player_sprite):
+ """
+ This function will move the current sprite towards whatever
+ other sprite is specified as a parameter.
+
+ We use the 'min' function here to get the sprite to line up with
+ the target sprite, and not jump around if the sprite is not off
+ an exact multiple of SPRITE_SPEED.
+ """
+
+ self.center_x += self.change_x
+ self.center_y += self.change_y
+
+ # Random 1 in 100 chance that we'll change from our old direction and
+ # then re-aim toward the player
+ if random.randrange(100) == 0:
+ start_x = self.center_x
+ start_y = self.center_y
+
+ # Get the destination location for the bullet
+ dest_x = player_sprite.center_x
+ dest_y = player_sprite.center_y
+
+ # Do math to calculate how to get the bullet to the destination.
+ # Calculation the angle in radians between the start points
+ # and end points. This is the angle the bullet will travel.
+ x_diff = dest_x - start_x
+ y_diff = dest_y - start_y
+ angle = math.atan2(y_diff, x_diff)
+
+ # Taking into account the angle, calculate our change_x
+ # and change_y. Velocity is how fast the bullet travels.
+ self.change_x = math.cos(angle) * COIN_SPEED
+ self.change_y = math.sin(angle) * COIN_SPEED
+
+
+class MyGame(arcade.Window):
+ """ Our custom Window Class"""
+
+ def __init__(self):
+ """ Initializer """
+ # Call the parent class initializer
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Variables that will hold sprite lists
+ self.player_list = None
+ self.coin_list = None
+
+ # Set up the player info
+ self.player_sprite = None
+ self.score = 0
+
+ # Don't show the mouse cursor
+ self.set_mouse_visible(False)
+
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Score
+ self.score = 0
+
+ # Set up the player
+ # Character image from kenney.nl
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING_PLAYER)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 50
+ self.player_list.append(self.player_sprite)
+
+ # Create the coins
+ for i in range(COIN_COUNT):
+ # Create the coin instance
+ # Coin image from kenney.nl
+ coin = Coin("images/coin_01.png", SPRITE_SCALING_COIN)
+
+ # Position the coin
+ coin.center_x = random.randrange(SCREEN_WIDTH)
+ coin.center_y = random.randrange(SCREEN_HEIGHT)
+
+ # Add the coin to the lists
+ self.coin_list.append(coin)
+
+ def on_draw(self):
+ """ Draw everything """
+ arcade.start_render()
+ self.coin_list.draw()
+ self.player_list.draw()
+
+ # Put the text on the screen.
+ output = f"Score: {self.score}"
+ arcade.draw_text(output, 10, 20, arcade.color.WHITE, 14)
+
+ def on_mouse_motion(self, x, y, dx, dy):
+ """ Handle Mouse Motion """
+
+ # Move the center of the player sprite to match the mouse x, y
+ self.player_sprite.center_x = x
+ self.player_sprite.center_y = y
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ for coin in self.coin_list:
+ coin.follow_sprite(self.player_sprite)
+
+ # Generate a list of all sprites that collided with the player.
+ hit_list = arcade.check_for_collision_with_list(self.player_sprite, self.coin_list)
+
+ # Loop through each colliding sprite, remove it, and add to the score.
+ for coin in hit_list:
+ coin.kill()
+ self.score += 1
+
+
+def main():
+ """ Main method """
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_move_angle.py b/arcade/examples/sprite_move_angle.py
new file mode 100644
index 0000000..e587f1e
--- /dev/null
+++ b/arcade/examples/sprite_move_angle.py
@@ -0,0 +1,140 @@
+"""
+Move Sprite by Angle
+
+Simple program to show basic sprite usage.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_move_angle
+"""
+import arcade
+import os
+import math
+
+SPRITE_SCALING = 0.5
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Move Sprite by Angle Example"
+
+MOVEMENT_SPEED = 5
+ANGLE_SPEED = 5
+
+
+class Player(arcade.Sprite):
+ """ Player class """
+
+ def __init__(self, image, scale):
+ """ Set up the player """
+
+ # Call the parent init
+ super().__init__(image, scale)
+
+ # Create a variable to hold our speed. 'angle' is created by the parent
+ self.speed = 0
+
+ def update(self):
+ # Convert angle in degrees to radians.
+ angle_rad = math.radians(self.angle)
+
+ # Rotate the ship
+ self.angle += self.change_angle
+
+ # Use math to find our change based on our speed and angle
+ self.center_x += -self.speed * math.sin(angle_rad)
+ self.center_y += self.speed * math.cos(angle_rad)
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ """
+ Initializer
+ """
+
+ # Call the parent class initializer
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Variables that will hold sprite lists
+ self.player_list = None
+
+ # Set up the player info
+ self.player_sprite = None
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.BLACK)
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+
+ # Set up the player
+ self.player_sprite = Player("images/playerShip1_orange.png", SPRITE_SCALING)
+ self.player_sprite.center_x = SCREEN_WIDTH / 2
+ self.player_sprite.center_y = SCREEN_HEIGHT / 2
+ self.player_list.append(self.player_sprite)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.player_list.draw()
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.player_list.update()
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ # Forward/back
+ if key == arcade.key.UP:
+ self.player_sprite.speed = MOVEMENT_SPEED
+ elif key == arcade.key.DOWN:
+ self.player_sprite.speed = -MOVEMENT_SPEED
+
+ # Rotate left/right
+ elif key == arcade.key.LEFT:
+ self.player_sprite.change_angle = ANGLE_SPEED
+ elif key == arcade.key.RIGHT:
+ self.player_sprite.change_angle = -ANGLE_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.UP or key == arcade.key.DOWN:
+ self.player_sprite.speed = 0
+ elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
+ self.player_sprite.change_angle = 0
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_move_animation.py b/arcade/examples/sprite_move_animation.py
new file mode 100644
index 0000000..65a2667
--- /dev/null
+++ b/arcade/examples/sprite_move_animation.py
@@ -0,0 +1,179 @@
+"""
+Move with a Sprite Animation
+
+Simple program to show basic sprite usage.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_move_animation
+"""
+import arcade
+import random
+import os
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Move with a Sprite Animation Example"
+
+COIN_SCALE = 0.5
+COIN_COUNT = 50
+
+MOVEMENT_SPEED = 5
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self, width, height, title):
+ """
+ Initializer
+ """
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = None
+ self.coin_list = None
+
+ # Set up the player
+ self.score = 0
+ self.player = None
+
+ def setup(self):
+ self.player_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Set up the player
+ self.score = 0
+ self.player = arcade.AnimatedWalkingSprite()
+
+ character_scale = 0.75
+ self.player.stand_right_textures = []
+ self.player.stand_right_textures.append(arcade.load_texture("images/character_sprites/character0.png",
+ scale=character_scale))
+ self.player.stand_left_textures = []
+ self.player.stand_left_textures.append(arcade.load_texture("images/character_sprites/character0.png",
+ scale=character_scale, mirrored=True))
+
+ self.player.walk_right_textures = []
+
+ self.player.walk_right_textures.append(arcade.load_texture("images/character_sprites/characterw0.png",
+ scale=character_scale))
+ self.player.walk_right_textures.append(arcade.load_texture("images/character_sprites/characterw1.png",
+ scale=character_scale))
+ self.player.walk_right_textures.append(arcade.load_texture("images/character_sprites/characterw2.png",
+ scale=character_scale))
+ self.player.walk_right_textures.append(arcade.load_texture("images/character_sprites/characterw3.png",
+ scale=character_scale))
+
+ self.player.walk_left_textures = []
+
+ self.player.walk_left_textures.append(arcade.load_texture("images/character_sprites/characterw0.png",
+ scale=character_scale, mirrored=True))
+ self.player.walk_left_textures.append(arcade.load_texture("images/character_sprites/characterw1.png",
+ scale=character_scale, mirrored=True))
+ self.player.walk_left_textures.append(arcade.load_texture("images/character_sprites/characterw2.png",
+ scale=character_scale, mirrored=True))
+ self.player.walk_left_textures.append(arcade.load_texture("images/character_sprites/characterw3.png",
+ scale=character_scale, mirrored=True))
+
+ self.player.texture_change_distance = 20
+
+ self.player.center_x = SCREEN_WIDTH // 2
+ self.player.center_y = SCREEN_HEIGHT // 2
+ self.player.scale = 0.8
+
+ self.player_list.append(self.player)
+
+ for i in range(COIN_COUNT):
+ coin = arcade.AnimatedTimeSprite(scale=0.5)
+ coin.center_x = random.randrange(SCREEN_WIDTH)
+ coin.center_y = random.randrange(SCREEN_HEIGHT)
+
+ coin.textures = []
+ coin.textures.append(arcade.load_texture("images/gold_1.png", scale=COIN_SCALE))
+ coin.textures.append(arcade.load_texture("images/gold_2.png", scale=COIN_SCALE))
+ coin.textures.append(arcade.load_texture("images/gold_3.png", scale=COIN_SCALE))
+ coin.textures.append(arcade.load_texture("images/gold_4.png", scale=COIN_SCALE))
+ coin.textures.append(arcade.load_texture("images/gold_3.png", scale=COIN_SCALE))
+ coin.textures.append(arcade.load_texture("images/gold_2.png", scale=COIN_SCALE))
+ coin.cur_texture_index = random.randrange(len(coin.textures))
+
+ self.coin_list.append(coin)
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.coin_list.draw()
+ self.player_list.draw()
+
+ # Put the text on the screen.
+ output = f"Score: {self.score}"
+ arcade.draw_text(output, 10, 20, arcade.color.WHITE, 14)
+
+ def on_key_press(self, key, modifiers):
+ """
+ Called whenever a key is pressed.
+ """
+ if key == arcade.key.UP:
+ self.player.change_y = MOVEMENT_SPEED
+ elif key == arcade.key.DOWN:
+ self.player.change_y = -MOVEMENT_SPEED
+ elif key == arcade.key.LEFT:
+ self.player.change_x = -MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT:
+ self.player.change_x = MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """
+ Called when the user releases a key.
+ """
+ if key == arcade.key.UP or key == arcade.key.DOWN:
+ self.player.change_y = 0
+ elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
+ self.player.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ self.coin_list.update()
+ self.coin_list.update_animation()
+ self.player_list.update()
+ self.player_list.update_animation()
+
+ # Generate a list of all sprites that collided with the player.
+ hit_list = arcade.check_for_collision_with_list(self.player, self.coin_list)
+
+ # Loop through each colliding sprite, remove it, and add to the score.
+ for coin in hit_list:
+ coin.remove_from_sprite_lists()
+ self.score += 1
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_move_joystick.py b/arcade/examples/sprite_move_joystick.py
new file mode 100644
index 0000000..1f5e75a
--- /dev/null
+++ b/arcade/examples/sprite_move_joystick.py
@@ -0,0 +1,152 @@
+"""
+Move Sprite with Joystick
+
+Simple program to show basic sprite usage.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_move_joystick
+"""
+
+import arcade
+import os
+
+SPRITE_SCALING = 0.5
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Move Sprite with Joystick Example"
+
+MOVEMENT_SPEED = 5
+DEAD_ZONE = 0.05
+
+
+class Player(arcade.Sprite):
+
+ def __init__(self, filename, scale):
+ super().__init__(filename, scale)
+
+ joysticks = arcade.get_joysticks()
+ if joysticks:
+ self.joystick = joysticks[0]
+ self.joystick.open()
+ else:
+ print("There are no Joysticks")
+ self.joystick = None
+
+ def update(self):
+ if self.joystick:
+ self.change_x = self.joystick.x * MOVEMENT_SPEED
+ # Set a "dead zone" to prevent drive from a centered joystick
+ if abs(self.change_x) < DEAD_ZONE:
+ self.change_x = 0
+
+ self.change_y = -self.joystick.y * MOVEMENT_SPEED
+ # Set a "dead zone" to prevent drive from a centered joystick
+ if abs(self.change_y) < DEAD_ZONE:
+ self.change_y = 0
+
+ self.center_x += self.change_x
+ self.center_y += self.change_y
+
+ if self.left < 0:
+ self.left = 0
+ elif self.right > SCREEN_WIDTH - 1:
+ self.right = SCREEN_WIDTH - 1
+
+ if self.bottom < 0:
+ self.bottom = 0
+ elif self.top > SCREEN_HEIGHT - 1:
+ self.top = SCREEN_HEIGHT - 1
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ """
+ Initializer
+ """
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Call the parent class initializer
+ super().__init__(width, height, title)
+
+ # Variables that will hold sprite lists
+ self.all_sprites_list = None
+
+ # Set up the player info
+ self.player_sprite = None
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.all_sprites_list = arcade.SpriteList()
+
+ # Set up the player
+ self.player_sprite = Player("images/character.png", SPRITE_SCALING)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 50
+ self.all_sprites_list.append(self.player_sprite)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.all_sprites_list.draw()
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.all_sprites_list.update()
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ if key == arcade.key.UP:
+ self.player_sprite.change_y = MOVEMENT_SPEED
+ elif key == arcade.key.DOWN:
+ self.player_sprite.change_y = -MOVEMENT_SPEED
+ elif key == arcade.key.LEFT:
+ self.player_sprite.change_x = -MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT:
+ self.player_sprite.change_x = MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.UP or key == arcade.key.DOWN:
+ self.player_sprite.change_y = 0
+ elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
+ self.player_sprite.change_x = 0
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_move_keyboard.py b/arcade/examples/sprite_move_keyboard.py
new file mode 100644
index 0000000..030e10a
--- /dev/null
+++ b/arcade/examples/sprite_move_keyboard.py
@@ -0,0 +1,131 @@
+"""
+Move Sprite With Keyboard
+
+Simple program to show moving a sprite with the keyboard.
+The sprite_move_keyboard_better.py example is slightly better
+in how it works, but also slightly more complex.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_move_keyboard
+"""
+
+import arcade
+import os
+
+SPRITE_SCALING = 0.5
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Move Sprite with Keyboard Example"
+
+MOVEMENT_SPEED = 5
+
+
+class Player(arcade.Sprite):
+
+ def update(self):
+ self.center_x += self.change_x
+ self.center_y += self.change_y
+
+ if self.left < 0:
+ self.left = 0
+ elif self.right > SCREEN_WIDTH - 1:
+ self.right = SCREEN_WIDTH - 1
+
+ if self.bottom < 0:
+ self.bottom = 0
+ elif self.top > SCREEN_HEIGHT - 1:
+ self.top = SCREEN_HEIGHT - 1
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ """
+ Initializer
+ """
+
+ # Call the parent class initializer
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Variables that will hold sprite lists
+ self.player_list = None
+
+ # Set up the player info
+ self.player_sprite = None
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+
+ # Set up the player
+ self.player_sprite = Player("images/character.png", SPRITE_SCALING)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 50
+ self.player_list.append(self.player_sprite)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.player_list.draw()
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.player_list.update()
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ if key == arcade.key.UP:
+ self.player_sprite.change_y = MOVEMENT_SPEED
+ elif key == arcade.key.DOWN:
+ self.player_sprite.change_y = -MOVEMENT_SPEED
+ elif key == arcade.key.LEFT:
+ self.player_sprite.change_x = -MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT:
+ self.player_sprite.change_x = MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.UP or key == arcade.key.DOWN:
+ self.player_sprite.change_y = 0
+ elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
+ self.player_sprite.change_x = 0
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_move_keyboard_better.py b/arcade/examples/sprite_move_keyboard_better.py
new file mode 100644
index 0000000..19b8150
--- /dev/null
+++ b/arcade/examples/sprite_move_keyboard_better.py
@@ -0,0 +1,155 @@
+"""
+Better Move Sprite With Keyboard
+
+Simple program to show moving a sprite with the keyboard.
+This is slightly better than sprite_move_keyboard.py example
+in how it works, but also slightly more complex.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_move_keyboard_better
+"""
+
+import arcade
+import os
+
+SPRITE_SCALING = 0.5
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Better Move Sprite with Keyboard Example"
+
+MOVEMENT_SPEED = 5
+
+
+class Player(arcade.Sprite):
+
+ def update(self):
+ self.center_x += self.change_x
+ self.center_y += self.change_y
+
+ if self.left < 0:
+ self.left = 0
+ elif self.right > SCREEN_WIDTH - 1:
+ self.right = SCREEN_WIDTH - 1
+
+ if self.bottom < 0:
+ self.bottom = 0
+ elif self.top > SCREEN_HEIGHT - 1:
+ self.top = SCREEN_HEIGHT - 1
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ """
+ Initializer
+ """
+
+ # Call the parent class initializer
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Variables that will hold sprite lists
+ self.player_list = None
+
+ # Set up the player info
+ self.player_sprite = None
+
+ # Track the current state of what key is pressed
+ self.left_pressed = False
+ self.right_pressed = False
+ self.up_pressed = False
+ self.down_pressed = False
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+
+ # Set up the player
+ self.player_sprite = Player("images/character.png", SPRITE_SCALING)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 50
+ self.player_list.append(self.player_sprite)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.player_list.draw()
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Calculate speed based on the keys pressed
+ self.player_sprite.change_x = 0
+ self.player_sprite.change_y = 0
+
+ if self.up_pressed and not self.down_pressed:
+ self.player_sprite.change_y = MOVEMENT_SPEED
+ elif self.down_pressed and not self.up_pressed:
+ self.player_sprite.change_y = -MOVEMENT_SPEED
+ if self.left_pressed and not self.right_pressed:
+ self.player_sprite.change_x = -MOVEMENT_SPEED
+ elif self.right_pressed and not self.left_pressed:
+ self.player_sprite.change_x = MOVEMENT_SPEED
+
+ # Call update to move the sprite
+ # If using a physics engine, call update on it instead of the sprite
+ # list.
+ self.player_list.update()
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ if key == arcade.key.UP:
+ self.up_pressed = True
+ elif key == arcade.key.DOWN:
+ self.down_pressed = True
+ elif key == arcade.key.LEFT:
+ self.left_pressed = True
+ elif key == arcade.key.RIGHT:
+ self.right_pressed = True
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.UP:
+ self.up_pressed = False
+ elif key == arcade.key.DOWN:
+ self.down_pressed = False
+ elif key == arcade.key.LEFT:
+ self.left_pressed = False
+ elif key == arcade.key.RIGHT:
+ self.right_pressed = False
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_move_scrolling.py b/arcade/examples/sprite_move_scrolling.py
new file mode 100644
index 0000000..0c20481
--- /dev/null
+++ b/arcade/examples/sprite_move_scrolling.py
@@ -0,0 +1,173 @@
+"""
+Use sprites to scroll around a large screen.
+
+Simple program to show basic sprite usage.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_move_scrolling
+"""
+
+import random
+import arcade
+import os
+
+SPRITE_SCALING = 0.5
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite Move with Scrolling Screen Example"
+
+# How many pixels to keep as a minimum margin between the character
+# and the edge of the screen.
+VIEWPORT_MARGIN = 40
+
+MOVEMENT_SPEED = 5
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self, width, height, title):
+ """
+ Initializer
+ """
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Sprite lists
+ self.player_list = None
+ self.coin_list = None
+
+ # Set up the player
+ self.player_sprite = None
+ self.wall_list = None
+ self.physics_engine = None
+ self.view_bottom = 0
+ self.view_left = 0
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.wall_list = arcade.SpriteList()
+
+ # Set up the player
+ self.player_sprite = arcade.Sprite("images/character.png", 0.4)
+ self.player_sprite.center_x = 64
+ self.player_sprite.center_y = 270
+ self.player_list.append(self.player_sprite)
+
+ # -- Set up several columns of walls
+ for x in range(200, 1650, 210):
+ for y in range(0, 1000, 64):
+ # Randomly skip a box so the player can find a way through
+ if random.randrange(5) > 0:
+ wall = arcade.Sprite("images/boxCrate_double.png", SPRITE_SCALING)
+ wall.center_x = x
+ wall.center_y = y
+ self.wall_list.append(wall)
+
+ self.physics_engine = arcade.PhysicsEngineSimple(self.player_sprite, self.wall_list)
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ # Set the viewport boundaries
+ # These numbers set where we have 'scrolled' to.
+ self.view_left = 0
+ self.view_bottom = 0
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.wall_list.draw()
+ self.player_list.draw()
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ if key == arcade.key.UP:
+ self.player_sprite.change_y = MOVEMENT_SPEED
+ elif key == arcade.key.DOWN:
+ self.player_sprite.change_y = -MOVEMENT_SPEED
+ elif key == arcade.key.LEFT:
+ self.player_sprite.change_x = -MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT:
+ self.player_sprite.change_x = MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.UP or key == arcade.key.DOWN:
+ self.player_sprite.change_y = 0
+ elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.physics_engine.update()
+
+ # --- Manage Scrolling ---
+
+ # Track if we need to change the viewport
+
+ changed = False
+
+ # Scroll left
+ left_bndry = self.view_left + VIEWPORT_MARGIN
+ if self.player_sprite.left < left_bndry:
+ self.view_left -= left_bndry - self.player_sprite.left
+ changed = True
+
+ # Scroll right
+ right_bndry = self.view_left + SCREEN_WIDTH - VIEWPORT_MARGIN
+ if self.player_sprite.right > right_bndry:
+ self.view_left += self.player_sprite.right - right_bndry
+ changed = True
+
+ # Scroll up
+ top_bndry = self.view_bottom + SCREEN_HEIGHT - VIEWPORT_MARGIN
+ if self.player_sprite.top > top_bndry:
+ self.view_bottom += self.player_sprite.top - top_bndry
+ changed = True
+
+ # Scroll down
+ bottom_bndry = self.view_bottom + VIEWPORT_MARGIN
+ if self.player_sprite.bottom < bottom_bndry:
+ self.view_bottom -= bottom_bndry - self.player_sprite.bottom
+ changed = True
+
+ if changed:
+ arcade.set_viewport(self.view_left,
+ SCREEN_WIDTH + self.view_left,
+ self.view_bottom,
+ SCREEN_HEIGHT + self.view_bottom)
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_move_walls.py b/arcade/examples/sprite_move_walls.py
new file mode 100644
index 0000000..03e616d
--- /dev/null
+++ b/arcade/examples/sprite_move_walls.py
@@ -0,0 +1,132 @@
+"""
+Sprite Move With Walls
+
+Simple program to show basic sprite usage.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_move_walls
+"""
+
+import arcade
+import os
+
+SPRITE_SCALING = 0.5
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite Move with Walls Example"
+
+MOVEMENT_SPEED = 5
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self, width, height, title):
+ """
+ Initializer
+ """
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Sprite lists
+ self.coin_list = None
+ self.wall_list = None
+ self.player_list = None
+
+ # Set up the player
+ self.player_sprite = None
+ self.physics_engine = None
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.wall_list = arcade.SpriteList()
+
+ # Set up the player
+ self.player_sprite = arcade.Sprite("images/character.png",
+ SPRITE_SCALING)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 64
+ self.player_list.append(self.player_sprite)
+
+ # -- Set up the walls
+ # Create a row of boxes
+ for x in range(173, 650, 64):
+ wall = arcade.Sprite("images/boxCrate_double.png", SPRITE_SCALING)
+ wall.center_x = x
+ wall.center_y = 200
+ self.wall_list.append(wall)
+
+ # Create a column of boxes
+ for y in range(273, 500, 64):
+ wall = arcade.Sprite("images/boxCrate_double.png", SPRITE_SCALING)
+ wall.center_x = 465
+ wall.center_y = y
+ self.wall_list.append(wall)
+
+ self.physics_engine = arcade.PhysicsEngineSimple(self.player_sprite,
+ self.wall_list)
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.wall_list.draw()
+ self.player_list.draw()
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ if key == arcade.key.UP:
+ self.player_sprite.change_y = MOVEMENT_SPEED
+ elif key == arcade.key.DOWN:
+ self.player_sprite.change_y = -MOVEMENT_SPEED
+ elif key == arcade.key.LEFT:
+ self.player_sprite.change_x = -MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT:
+ self.player_sprite.change_x = MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.UP or key == arcade.key.DOWN:
+ self.player_sprite.change_y = 0
+ elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.physics_engine.update()
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_moving_platforms.py b/arcade/examples/sprite_moving_platforms.py
new file mode 100644
index 0000000..0ecf8ab
--- /dev/null
+++ b/arcade/examples/sprite_moving_platforms.py
@@ -0,0 +1,287 @@
+"""
+Sprite with Moving Platforms
+
+Load a map stored in csv format, as exported by the program 'Tiled.'
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_moving_platforms
+"""
+import arcade
+import os
+
+SPRITE_SCALING = 0.5
+
+SCREEN_WIDTH = 1000
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite with Moving Platforms Example"
+SPRITE_PIXEL_SIZE = 128
+GRID_PIXEL_SIZE = (SPRITE_PIXEL_SIZE * SPRITE_SCALING)
+
+# How many pixels to keep as a minimum margin between the character
+# and the edge of the screen.
+VIEWPORT_MARGIN = SPRITE_PIXEL_SIZE * SPRITE_SCALING
+RIGHT_MARGIN = 4 * SPRITE_PIXEL_SIZE * SPRITE_SCALING
+
+# Physics
+MOVEMENT_SPEED = 10 * SPRITE_SCALING
+JUMP_SPEED = 28 * SPRITE_SCALING
+GRAVITY = .9 * SPRITE_SCALING
+
+
+def get_map():
+ map_file = open("map.csv")
+ map_array = []
+ for line in map_file:
+ line = line.strip()
+ map_row = line.split(",")
+ for index, item in enumerate(map_row):
+ map_row[index] = int(item)
+ map_array.append(map_row)
+ return map_array
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self, width, height, title):
+ """
+ Initializer
+ """
+
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Sprite lists
+ self.all_sprites_list = None
+ self.all_wall_list = None
+ self.static_wall_list = None
+ self.moving_wall_list = None
+ self.player_list = None
+ self.coin_list = None
+
+ # Set up the player
+ self.player_sprite = None
+ self.physics_engine = None
+ self.view_left = 0
+ self.view_bottom = 0
+ self.game_over = False
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.all_sprites_list = arcade.SpriteList()
+ self.all_wall_list = arcade.SpriteList()
+ self.static_wall_list = arcade.SpriteList()
+ self.moving_wall_list = arcade.SpriteList()
+ self.player_list = arcade.SpriteList()
+
+ # Set up the player
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING)
+ self.player_sprite.center_x = 2 * GRID_PIXEL_SIZE
+ self.player_sprite.center_y = 3 * GRID_PIXEL_SIZE
+ self.player_list.append(self.player_sprite)
+
+ map_array = get_map()
+
+ # Right edge of the map in pixels
+ self.end_of_map = len(map_array[0]) * GRID_PIXEL_SIZE
+
+ for row_index, row in enumerate(map_array):
+ for column_index, item in enumerate(row):
+
+ if item == -1:
+ continue
+ elif item == 0:
+ wall = arcade.Sprite("images/boxCrate_double.png", SPRITE_SCALING)
+ elif item == 1:
+ wall = arcade.Sprite("images/grassLeft.png", SPRITE_SCALING)
+ elif item == 2:
+ wall = arcade.Sprite("images/grassMid.png", SPRITE_SCALING)
+ elif item == 3:
+ wall = arcade.Sprite("images/grassRight.png", SPRITE_SCALING)
+
+ wall.left = column_index * GRID_PIXEL_SIZE
+ wall.top = (7 - row_index) * GRID_PIXEL_SIZE
+ self.all_sprites_list.append(wall)
+ self.all_wall_list.append(wall)
+ self.static_wall_list.append(wall)
+
+ # Create platform side to side
+ wall = arcade.Sprite("images/grassMid.png", SPRITE_SCALING)
+ wall.center_y = 3 * GRID_PIXEL_SIZE
+ wall.center_x = 3 * GRID_PIXEL_SIZE
+ wall.boundary_left = 2 * GRID_PIXEL_SIZE
+ wall.boundary_right = 5 * GRID_PIXEL_SIZE
+ wall.change_x = 2 * SPRITE_SCALING
+
+ self.all_sprites_list.append(wall)
+ self.all_wall_list.append(wall)
+ self.moving_wall_list.append(wall)
+
+ # Create platform side to side
+ wall = arcade.Sprite("images/grassMid.png", SPRITE_SCALING)
+ wall.center_y = 3 * GRID_PIXEL_SIZE
+ wall.center_x = 7 * GRID_PIXEL_SIZE
+ wall.boundary_left = 5 * GRID_PIXEL_SIZE
+ wall.boundary_right = 9 * GRID_PIXEL_SIZE
+ wall.change_x = -2 * SPRITE_SCALING
+
+ self.all_sprites_list.append(wall)
+ self.all_wall_list.append(wall)
+ self.moving_wall_list.append(wall)
+
+ # Create platform moving up and down
+ wall = arcade.Sprite("images/grassMid.png", SPRITE_SCALING)
+ wall.center_y = 5 * GRID_PIXEL_SIZE
+ wall.center_x = 5 * GRID_PIXEL_SIZE
+ wall.boundary_top = 8 * GRID_PIXEL_SIZE
+ wall.boundary_bottom = 4 * GRID_PIXEL_SIZE
+ wall.change_y = 2 * SPRITE_SCALING
+
+ self.all_sprites_list.append(wall)
+ self.all_wall_list.append(wall)
+ self.moving_wall_list.append(wall)
+
+ # Create platform moving diagonally
+ wall = arcade.Sprite("images/grassMid.png", SPRITE_SCALING)
+ wall.center_y = 5 * GRID_PIXEL_SIZE
+ wall.center_x = 8 * GRID_PIXEL_SIZE
+ wall.boundary_left = 7 * GRID_PIXEL_SIZE
+ wall.boundary_right = 9 * GRID_PIXEL_SIZE
+
+ wall.boundary_top = 8 * GRID_PIXEL_SIZE
+ wall.boundary_bottom = 4 * GRID_PIXEL_SIZE
+ wall.change_x = 2 * SPRITE_SCALING
+ wall.change_y = 2 * SPRITE_SCALING
+
+ self.all_sprites_list.append(wall)
+ self.all_wall_list.append(wall)
+ self.moving_wall_list.append(wall)
+
+ self.physics_engine = \
+ arcade.PhysicsEnginePlatformer(self.player_sprite,
+ self.all_wall_list,
+ gravity_constant=GRAVITY)
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ # Set the viewport boundaries
+ # These numbers set where we have 'scrolled' to.
+ self.view_left = 0
+ self.view_bottom = 0
+
+ self.game_over = False
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw the sprites.
+ self.static_wall_list.draw()
+ self.moving_wall_list.draw()
+ self.player_list.draw()
+
+ # Put the text on the screen.
+ # Adjust the text position based on the viewport so that we don't
+ # scroll the text too.
+ distance = self.player_sprite.right
+ output = f"Distance: {distance}"
+ arcade.draw_text(output, self.view_left + 10, self.view_bottom + 20,
+ arcade.color.WHITE, 14)
+
+ if self.game_over:
+ output = "Game Over"
+ arcade.draw_text(output, self.view_left + 200,
+ self.view_bottom + 200,
+ arcade.color.WHITE, 30)
+
+ def on_key_press(self, key, modifiers):
+ """
+ Called whenever the mouse moves.
+ """
+ if key == arcade.key.UP:
+ if self.physics_engine.can_jump():
+ self.player_sprite.change_y = JUMP_SPEED
+ elif key == arcade.key.LEFT:
+ self.player_sprite.change_x = -MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT:
+ self.player_sprite.change_x = MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """
+ Called when the user presses a mouse button.
+ """
+ if key == arcade.key.LEFT or key == arcade.key.RIGHT:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ if self.player_sprite.right >= self.end_of_map:
+ self.game_over = True
+
+ # Call update on all sprites
+ if not self.game_over:
+ self.physics_engine.update()
+
+ # --- Manage Scrolling ---
+
+ # Track if we need to change the viewport
+
+ changed = False
+
+ # Scroll left
+ left_boundary = self.view_left + VIEWPORT_MARGIN
+ if self.player_sprite.left < left_boundary:
+ self.view_left -= left_boundary - self.player_sprite.left
+ changed = True
+
+ # Scroll right
+ right_boundary = self.view_left + SCREEN_WIDTH - RIGHT_MARGIN
+ if self.player_sprite.right > right_boundary:
+ self.view_left += self.player_sprite.right - right_boundary
+ changed = True
+
+ # Scroll up
+ top_boundary = self.view_bottom + SCREEN_HEIGHT - VIEWPORT_MARGIN
+ if self.player_sprite.top > top_boundary:
+ self.view_bottom += self.player_sprite.top - top_boundary
+ changed = True
+
+ # Scroll down
+ bottom_boundary = self.view_bottom + VIEWPORT_MARGIN
+ if self.player_sprite.bottom < bottom_boundary:
+ self.view_bottom -= bottom_boundary - self.player_sprite.bottom
+ changed = True
+
+ # If we need to scroll, go ahead and do it.
+ if changed:
+ arcade.set_viewport(self.view_left,
+ SCREEN_WIDTH + self.view_left,
+ self.view_bottom,
+ SCREEN_HEIGHT + self.view_bottom)
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_no_coins_on_walls.py b/arcade/examples/sprite_no_coins_on_walls.py
new file mode 100644
index 0000000..97842fa
--- /dev/null
+++ b/arcade/examples/sprite_no_coins_on_walls.py
@@ -0,0 +1,163 @@
+"""
+No coins on the walls
+
+Simple program to show basic sprite usage. Specifically, create coin sprites that
+aren't on top of any walls, and don't have coins on top of each other.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_no_coins_on_walls
+"""
+import arcade
+import random
+import os
+
+SPRITE_SCALING = 0.5
+SPRITE_SCALING_COIN = 0.2
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite No Coins on Walls Example"
+
+NUMBER_OF_COINS = 50
+
+MOVEMENT_SPEED = 5
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self, width, height, title):
+ """
+ Initializer
+ """
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Sprite lists
+ self.all_sprites_list = None
+ self.coin_list = None
+
+ # Set up the player
+ self.player_sprite = None
+ self.wall_list = None
+ self.physics_engine = None
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.all_sprites_list = arcade.SpriteList()
+ self.wall_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Set up the player
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 64
+
+ # -- Set up the walls
+ # Create a series of horizontal walls
+ for y in range(0, 800, 200):
+ for x in range(100, 700, 64):
+ wall = arcade.Sprite("images/boxCrate_double.png", SPRITE_SCALING)
+ wall.center_x = x
+ wall.center_y = y
+ self.wall_list.append(wall)
+
+ # -- Randomly place coins where there are no walls
+ # Create the coins
+ for i in range(NUMBER_OF_COINS):
+
+ # Create the coin instance
+ # Coin image from kenney.nl
+ coin = arcade.Sprite("images/coin_01.png", SPRITE_SCALING_COIN)
+
+ # --- IMPORTANT PART ---
+
+ # Boolean variable if we successfully placed the coin
+ coin_placed_successfully = False
+
+ # Keep trying until success
+ while not coin_placed_successfully:
+ # Position the coin
+ coin.center_x = random.randrange(SCREEN_WIDTH)
+ coin.center_y = random.randrange(SCREEN_HEIGHT)
+
+ # See if the coin is hitting a wall
+ wall_hit_list = arcade.check_for_collision_with_list(coin, self.wall_list)
+
+ # See if the coin is hitting another coin
+ coin_hit_list = arcade.check_for_collision_with_list(coin, self.coin_list)
+
+ if len(wall_hit_list) == 0 and len(coin_hit_list) == 0:
+ # It is!
+ coin_placed_successfully = True
+
+ # Add the coin to the lists
+ self.coin_list.append(coin)
+
+ # --- END OF IMPORTANT PART ---
+
+ self.physics_engine = arcade.PhysicsEngineSimple(self.player_sprite, self.wall_list)
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.wall_list.draw()
+ self.coin_list.draw()
+ self.player_sprite.draw()
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ if key == arcade.key.UP:
+ self.player_sprite.change_y = MOVEMENT_SPEED
+ elif key == arcade.key.DOWN:
+ self.player_sprite.change_y = -MOVEMENT_SPEED
+ elif key == arcade.key.LEFT:
+ self.player_sprite.change_x = -MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT:
+ self.player_sprite.change_x = MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.UP or key == arcade.key.DOWN:
+ self.player_sprite.change_y = 0
+ elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.physics_engine.update()
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_properties.py b/arcade/examples/sprite_properties.py
new file mode 100644
index 0000000..d8567b1
--- /dev/null
+++ b/arcade/examples/sprite_properties.py
@@ -0,0 +1,135 @@
+"""
+Sprites with Properties Example
+
+Simple program to show how to store properties on sprites.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_properties
+
+"""
+
+import arcade
+import os
+
+# --- Constants ---
+SPRITE_SCALING_PLAYER = 0.5
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprites with Properties Example"
+
+
+class MyGame(arcade.Window):
+ """ Our custom Window Class"""
+
+ def __init__(self):
+ """ Initializer """
+ # Call the parent class initializer
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Variables that will hold sprite lists
+ self.player_list = None
+ self.coin_list = None
+
+ # Set up the player info
+ self.player_sprite = None
+
+ # Set up sprite that will serve as trigger
+ self.trigger_sprite = None
+
+ # Don't show the mouse cursor
+ self.set_mouse_visible(False)
+
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Set up the player
+ # Character image from kenney.nl
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING_PLAYER)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 150
+ self.player_list.append(self.player_sprite)
+
+ # Create the sprites
+ for x in range(100, 800, 100):
+ coin = arcade.Sprite("images/coin_01.png", scale=0.3, center_x=x, center_y=400)
+ coin.intensity = 'dim'
+ coin.alpha = 64
+ self.coin_list.append(coin)
+
+ # Create trigger
+ self.trigger_sprite = arcade.Sprite("images/bumper.png", scale=0.5, center_x=750, center_y=50)
+
+ def on_draw(self):
+ """ Draw everything """
+ arcade.start_render()
+ self.coin_list.draw()
+ self.trigger_sprite.draw()
+ self.player_list.draw()
+
+ # Put the instructions on the screen.
+ instructions1 = "Touch a coin to set its intensity property to 'bright'."
+ arcade.draw_text(instructions1, 10, 90, arcade.color.WHITE, 14)
+ instructions2 = "Touch the trigger at the bottom-right to destroy all 'bright' sprites."
+ arcade.draw_text(instructions2, 10, 70, arcade.color.WHITE, 14)
+
+ # Query the property on the coins and show results.
+ coins_are_bright = [coin.intensity == 'bright' for coin in self.coin_list]
+ output_any = f"Any sprites have intensity=bright? : {any(coins_are_bright)}"
+ arcade.draw_text(output_any, 10, 40, arcade.color.WHITE, 14)
+ output_all = f"All sprites have intensity=bright? : {all(coins_are_bright)}"
+ arcade.draw_text(output_all, 10, 20, arcade.color.WHITE, 14)
+
+ def on_mouse_motion(self, x, y, dx, dy):
+ """ Handle Mouse Motion """
+
+ # Move the center of the player sprite to match the mouse x, y
+ self.player_sprite.center_x = x
+ self.player_sprite.center_y = y
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.coin_list.update()
+
+ # Generate a list of all sprites that collided with the player.
+ coins_hit_list = arcade.check_for_collision_with_list(self.player_sprite, self.coin_list)
+
+ # Loop through each colliding sprite to set intensity=bright
+ for coin in coins_hit_list:
+ coin.intensity = 'bright'
+ coin.alpha = 255
+
+ hit_trigger = arcade.check_for_collision(self.player_sprite, self.trigger_sprite)
+ if hit_trigger:
+ intense_sprites = [sprite for sprite in self.coin_list if sprite.intensity == 'bright']
+ for coin in intense_sprites:
+ coin.kill()
+
+
+def main():
+ """ Main method """
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_ramps.py b/arcade/examples/sprite_ramps.py
new file mode 100644
index 0000000..4e8a597
--- /dev/null
+++ b/arcade/examples/sprite_ramps.py
@@ -0,0 +1,246 @@
+"""
+Load a map stored in csv format, as exported by the program 'Tiled.'
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_ramps
+"""
+import arcade
+import os
+
+SPRITE_SCALING = 0.5
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite with Ramps Example"
+SPRITE_PIXEL_SIZE = 128
+GRID_PIXEL_SIZE = (SPRITE_PIXEL_SIZE * SPRITE_SCALING)
+
+# How many pixels to keep as a minimum margin between the character
+# and the edge of the screen.
+VIEWPORT_MARGIN = 40
+RIGHT_MARGIN = 150
+
+# Physics
+MOVEMENT_SPEED = 5
+JUMP_SPEED = 14
+GRAVITY = 0.5
+
+
+def get_map():
+ map_file = open("map_with_ramps_2.csv")
+ map_array = []
+ for line in map_file:
+ line = line.strip()
+ map_row = line.split(",")
+ for index, item in enumerate(map_row):
+ map_row[index] = int(item)
+ map_array.append(map_row)
+ return map_array
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self, width, height, title):
+ """
+ Initializer
+ """
+
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Sprite lists
+ self.all_sprites_list = None
+ self.coin_list = None
+ self.player_list = None
+
+ # Set up the player
+ self.player_sprite = None
+ self.wall_list = None
+ self.physics_engine = None
+ self.view_left = 0
+ self.view_bottom = 0
+ self.game_over = False
+
+ def start_new_game(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.all_sprites_list = arcade.SpriteList()
+ self.wall_list = arcade.SpriteList()
+ self.player_list = arcade.SpriteList()
+
+ # Set up the player
+ self.player_sprite = arcade.Sprite("images/character.png",
+ SPRITE_SCALING)
+ self.player_sprite.center_x = 64
+ self.player_sprite.center_y = 270
+ self.player_list.append(self.player_sprite)
+ self.all_sprites_list.append(self.player_sprite)
+
+ map_array = get_map()
+
+ # Right edge of the map in pixels
+ self.end_of_map = len(map_array[0]) * GRID_PIXEL_SIZE
+
+ map_items = ["images/boxCrate_double.png",
+ "images/grassCenter.png",
+ "images/grassCorner_left.png",
+ "images/grassCorner_right.png",
+ "images/grassHill_left.png",
+ "images/grassHill_right.png",
+ "images/grassLeft.png",
+ "images/grassMid.png",
+ "images/grassRight.png",
+ "images/stoneHalf.png"
+ ]
+ for row_index, row in enumerate(map_array):
+ for column_index, item in enumerate(row):
+
+ if item == -1:
+ continue
+ else:
+ wall = arcade.Sprite(map_items[item],
+ SPRITE_SCALING)
+
+ # Change the collision polygon to be a ramp instead of
+ # a rectangle
+ if item == 4:
+ wall.points = ((-wall.width // 2, wall.height // 2),
+ (wall.width // 2, -wall.height // 2),
+ (-wall.width // 2, -wall.height // 2))
+ elif item == 5:
+ wall.points = ((-wall.width // 2, -wall.height // 2),
+ (wall.width // 2, -wall.height // 2),
+ (wall.width // 2, wall.height // 2))
+
+ wall.right = column_index * 64
+ wall.top = (7 - row_index) * 64
+ self.all_sprites_list.append(wall)
+ self.wall_list.append(wall)
+
+ self.physics_engine = \
+ arcade.PhysicsEnginePlatformer(self.player_sprite,
+ self.wall_list,
+ gravity_constant=GRAVITY)
+
+ # Set the background color
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ # Set the viewport boundaries
+ # These numbers set where we have 'scrolled' to.
+ self.view_left = 0
+ self.view_bottom = 0
+
+ self.game_over = False
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.wall_list.draw()
+ self.player_list.draw()
+
+ # Put the text on the screen.
+ # Adjust the text position based on the viewport so that we don't
+ # scroll the text too.
+ distance = self.player_sprite.right
+ output = "Distance: {}".format(distance)
+ arcade.draw_text(output, self.view_left + 10, self.view_bottom + 20,
+ arcade.color.WHITE, 14)
+
+ if self.game_over:
+ output = "Game Over"
+ arcade.draw_text(output, self.view_left + 200,
+ self.view_bottom + 200,
+ arcade.color.WHITE, 30)
+
+ def on_key_press(self, key, modifiers):
+ """
+ Called whenever a key is pressed down.
+ """
+ if key == arcade.key.UP:
+ if self.physics_engine.can_jump():
+ self.player_sprite.change_y = JUMP_SPEED
+ elif key == arcade.key.LEFT:
+ self.player_sprite.change_x = -MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT:
+ self.player_sprite.change_x = MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """
+ Called when the user releases a key.
+ """
+ if key == arcade.key.LEFT or key == arcade.key.RIGHT:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ if self.player_sprite.right >= self.end_of_map:
+ self.game_over = True
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ if not self.game_over:
+ self.physics_engine.update()
+
+ # --- Manage Scrolling ---
+
+ # Track if we need to change the viewport
+
+ changed = False
+
+ # Scroll left
+ left_bndry = self.view_left + VIEWPORT_MARGIN
+ if self.player_sprite.left < left_bndry:
+ self.view_left -= int(left_bndry - self.player_sprite.left)
+ changed = True
+
+ # Scroll right
+ right_bndry = self.view_left + SCREEN_WIDTH - RIGHT_MARGIN
+ if self.player_sprite.right > right_bndry:
+ self.view_left += int(self.player_sprite.right - right_bndry)
+ changed = True
+
+ # Scroll up
+ top_bndry = self.view_bottom + SCREEN_HEIGHT - VIEWPORT_MARGIN
+ if self.player_sprite.top > top_bndry:
+ self.view_bottom += int(self.player_sprite.top - top_bndry)
+ changed = True
+
+ # Scroll down
+ bottom_bndry = self.view_bottom + VIEWPORT_MARGIN
+ if self.player_sprite.bottom < bottom_bndry:
+ self.view_bottom -= int(bottom_bndry - self.player_sprite.bottom)
+ changed = True
+
+ # If we need to scroll, go ahead and do it.
+ if changed:
+ arcade.set_viewport(self.view_left,
+ SCREEN_WIDTH + self.view_left,
+ self.view_bottom,
+ SCREEN_HEIGHT + self.view_bottom)
+
+
+def main():
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.start_new_game()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_rooms.py b/arcade/examples/sprite_rooms.py
new file mode 100644
index 0000000..ab3e20b
--- /dev/null
+++ b/arcade/examples/sprite_rooms.py
@@ -0,0 +1,245 @@
+"""
+Sprite move between different rooms.
+
+Artwork from http://kenney.nl
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_rooms
+"""
+
+import arcade
+import os
+
+SPRITE_SCALING = 0.5
+SPRITE_NATIVE_SIZE = 128
+SPRITE_SIZE = int(SPRITE_NATIVE_SIZE * SPRITE_SCALING)
+
+SCREEN_WIDTH = SPRITE_SIZE * 14
+SCREEN_HEIGHT = SPRITE_SIZE * 10
+SCREEN_TITLE = "Sprite Rooms Example"
+
+MOVEMENT_SPEED = 5
+
+
+class Room:
+ """
+ This class holds all the information about the
+ different rooms.
+ """
+ def __init__(self):
+ # You may want many lists. Lists for coins, monsters, etc.
+ self.wall_list = None
+
+ # This holds the background images. If you don't want changing
+ # background images, you can delete this part.
+ self.background = None
+
+
+def setup_room_1():
+ """
+ Create and return room 1.
+ If your program gets large, you may want to separate this into different
+ files.
+ """
+ room = Room()
+
+ """ Set up the game and initialize the variables. """
+ # Sprite lists
+ room.wall_list = arcade.SpriteList()
+
+ # -- Set up the walls
+ # Create bottom and top row of boxes
+ # This y loops a list of two, the coordinate 0, and just under the top of window
+ for y in (0, SCREEN_HEIGHT - SPRITE_SIZE):
+ # Loop for each box going across
+ for x in range(0, SCREEN_WIDTH, SPRITE_SIZE):
+ wall = arcade.Sprite("images/boxCrate_double.png", SPRITE_SCALING)
+ wall.left = x
+ wall.bottom = y
+ room.wall_list.append(wall)
+
+ # Create left and right column of boxes
+ for x in (0, SCREEN_WIDTH - SPRITE_SIZE):
+ # Loop for each box going across
+ for y in range(SPRITE_SIZE, SCREEN_HEIGHT - SPRITE_SIZE, SPRITE_SIZE):
+ # Skip making a block 4 and 5 blocks up on the right side
+ if (y != SPRITE_SIZE * 4 and y != SPRITE_SIZE * 5) or x == 0:
+ wall = arcade.Sprite("images/boxCrate_double.png", SPRITE_SCALING)
+ wall.left = x
+ wall.bottom = y
+ room.wall_list.append(wall)
+
+ wall = arcade.Sprite("images/boxCrate_double.png", SPRITE_SCALING)
+ wall.left = 7 * SPRITE_SIZE
+ wall.bottom = 5 * SPRITE_SIZE
+ room.wall_list.append(wall)
+
+ # If you want coins or monsters in a level, then add that code here.
+
+ # Load the background image for this level.
+ room.background = arcade.load_texture("images/background.jpg")
+
+ return room
+
+
+def setup_room_2():
+ """
+ Create and return room 2.
+ """
+ room = Room()
+
+ """ Set up the game and initialize the variables. """
+ # Sprite lists
+ room.wall_list = arcade.SpriteList()
+
+ # -- Set up the walls
+ # Create bottom and top row of boxes
+ # This y loops a list of two, the coordinate 0, and just under the top of window
+ for y in (0, SCREEN_HEIGHT - SPRITE_SIZE):
+ # Loop for each box going across
+ for x in range(0, SCREEN_WIDTH, SPRITE_SIZE):
+ wall = arcade.Sprite("images/boxCrate_double.png", SPRITE_SCALING)
+ wall.left = x
+ wall.bottom = y
+ room.wall_list.append(wall)
+
+ # Create left and right column of boxes
+ for x in (0, SCREEN_WIDTH - SPRITE_SIZE):
+ # Loop for each box going across
+ for y in range(SPRITE_SIZE, SCREEN_HEIGHT - SPRITE_SIZE, SPRITE_SIZE):
+ # Skip making a block 4 and 5 blocks up
+ if (y != SPRITE_SIZE * 4 and y != SPRITE_SIZE * 5) or x != 0:
+ wall = arcade.Sprite("images/boxCrate_double.png", SPRITE_SCALING)
+ wall.left = x
+ wall.bottom = y
+ room.wall_list.append(wall)
+
+ wall = arcade.Sprite("images/boxCrate_double.png", SPRITE_SCALING)
+ wall.left = 5 * SPRITE_SIZE
+ wall.bottom = 6 * SPRITE_SIZE
+ room.wall_list.append(wall)
+ room.background = arcade.load_texture("images/background_2.jpg")
+
+ return room
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self, width, height, title):
+ """
+ Initializer
+ """
+ super().__init__(width, height, title)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Sprite lists
+ self.current_room = 0
+
+ # Set up the player
+ self.rooms = None
+ self.player_sprite = None
+ self.player_list = None
+ self.physics_engine = None
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+ # Set up the player
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING)
+ self.player_sprite.center_x = 100
+ self.player_sprite.center_y = 100
+ self.player_list = arcade.SpriteList()
+ self.player_list.append(self.player_sprite)
+
+ # Our list of rooms
+ self.rooms = []
+
+ # Create the rooms. Extend the pattern for each room.
+ room = setup_room_1()
+ self.rooms.append(room)
+
+ room = setup_room_2()
+ self.rooms.append(room)
+
+ # Our starting room number
+ self.current_room = 0
+
+ # Create a physics engine for this room
+ self.physics_engine = arcade.PhysicsEngineSimple(self.player_sprite, self.rooms[self.current_room].wall_list)
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw the background texture
+ arcade.draw_texture_rectangle(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2,
+ SCREEN_WIDTH, SCREEN_HEIGHT, self.rooms[self.current_room].background)
+
+ # Draw all the walls in this room
+ self.rooms[self.current_room].wall_list.draw()
+
+ # If you have coins or monsters, then copy and modify the line
+ # above for each list.
+
+ self.player_list.draw()
+
+ def on_key_press(self, key, modifiers):
+ """Called whenever a key is pressed. """
+
+ if key == arcade.key.UP:
+ self.player_sprite.change_y = MOVEMENT_SPEED
+ elif key == arcade.key.DOWN:
+ self.player_sprite.change_y = -MOVEMENT_SPEED
+ elif key == arcade.key.LEFT:
+ self.player_sprite.change_x = -MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT:
+ self.player_sprite.change_x = MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """Called when the user releases a key. """
+
+ if key == arcade.key.UP or key == arcade.key.DOWN:
+ self.player_sprite.change_y = 0
+ elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.physics_engine.update()
+
+ # Do some logic here to figure out what room we are in, and if we need to go
+ # to a different room.
+ if self.player_sprite.center_x > SCREEN_WIDTH and self.current_room == 0:
+ self.current_room = 1
+ self.physics_engine = arcade.PhysicsEngineSimple(self.player_sprite,
+ self.rooms[self.current_room].wall_list)
+ self.player_sprite.center_x = 0
+ elif self.player_sprite.center_x < 0 and self.current_room == 1:
+ self.current_room = 0
+ self.physics_engine = arcade.PhysicsEngineSimple(self.player_sprite,
+ self.rooms[self.current_room].wall_list)
+ self.player_sprite.center_x = SCREEN_WIDTH
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_tiled_map.py b/arcade/examples/sprite_tiled_map.py
new file mode 100644
index 0000000..19df073
--- /dev/null
+++ b/arcade/examples/sprite_tiled_map.py
@@ -0,0 +1,238 @@
+"""
+Load a Tiled map file
+
+Artwork from: http://kenney.nl
+Tiled available from: http://www.mapeditor.org/
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_tiled_map
+"""
+
+import arcade
+import os
+import time
+
+SPRITE_SCALING = 0.5
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite Tiled Map Example"
+SPRITE_PIXEL_SIZE = 128
+GRID_PIXEL_SIZE = (SPRITE_PIXEL_SIZE * SPRITE_SCALING)
+
+# How many pixels to keep as a minimum margin between the character
+# and the edge of the screen.
+VIEWPORT_MARGIN_TOP = 60
+VIEWPORT_MARGIN_BOTTOM = 60
+VIEWPORT_RIGHT_MARGIN = 270
+VIEWPORT_LEFT_MARGIN = 270
+
+# Physics
+MOVEMENT_SPEED = 5
+JUMP_SPEED = 23
+GRAVITY = 1.1
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self):
+ """
+ Initializer
+ """
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Sprite lists
+ self.wall_list = None
+ self.player_list = None
+ self.coin_list = None
+
+ # Set up the player
+ self.score = 0
+ self.player_sprite = None
+
+ self.physics_engine = None
+ self.view_left = 0
+ self.view_bottom = 0
+ self.game_over = False
+ self.last_time = None
+ self.frame_count = 0
+ self.fps_message = None
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Set up the player
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING)
+
+ # Starting position of the player
+ self.player_sprite.center_x = 64
+ self.player_sprite.center_y = 270
+ self.player_list.append(self.player_sprite)
+
+ platforms_layer_name = 'Platforms'
+ coins_layer_name = 'Coins'
+ map_name = "map.tmx"
+
+ # Read in the tiled map
+ my_map = arcade.read_tiled_map(map_name, SPRITE_SCALING)
+
+ # --- Walls ---
+ # Grab the layer of items we can't move through
+ map_array = my_map.layers_int_data[platforms_layer_name]
+
+ # Calculate the right edge of the my_map in pixels
+ self.end_of_map = len(map_array[0]) * GRID_PIXEL_SIZE
+
+ # --- Platforms ---
+ self.wall_list = arcade.generate_sprites(my_map, platforms_layer_name, SPRITE_SCALING)
+
+ # --- Coins ---
+ self.coin_list = arcade.generate_sprites(my_map, coins_layer_name, SPRITE_SCALING)
+
+ # --- Other stuff
+ # Set the background color
+ if my_map.backgroundcolor:
+ arcade.set_background_color(my_map.backgroundcolor)
+
+ # Keep player from running through the wall_list layer
+ self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
+ self.wall_list,
+ gravity_constant=GRAVITY)
+
+ # Set the view port boundaries
+ # These numbers set where we have 'scrolled' to.
+ self.view_left = 0
+ self.view_bottom = 0
+
+ self.game_over = False
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ self.frame_count += 1
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.player_list.draw()
+ self.wall_list.draw()
+ self.coin_list.draw()
+
+ if self.last_time and self.frame_count % 60 == 0:
+ fps = 1.0 / (time.time() - self.last_time) * 60
+ self.fps_message = f"FPS: {fps:5.0f}"
+
+ if self.fps_message:
+ arcade.draw_text(self.fps_message, self.view_left + 10, self.view_bottom + 40, arcade.color.BLACK, 14)
+
+ if self.frame_count % 60 == 0:
+ self.last_time = time.time()
+
+ # Put the text on the screen.
+ # Adjust the text position based on the view port so that we don't
+ # scroll the text too.
+ distance = self.player_sprite.right
+ output = f"Distance: {distance}"
+ arcade.draw_text(output, self.view_left + 10, self.view_bottom + 20, arcade.color.BLACK, 14)
+
+ if self.game_over:
+ arcade.draw_text("Game Over", self.view_left + 200, self.view_bottom + 200, arcade.color.BLACK, 30)
+
+ def on_key_press(self, key, modifiers):
+ """
+ Called whenever the mouse moves.
+ """
+ if key == arcade.key.UP:
+ if self.physics_engine.can_jump():
+ self.player_sprite.change_y = JUMP_SPEED
+ elif key == arcade.key.LEFT:
+ self.player_sprite.change_x = -MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT:
+ self.player_sprite.change_x = MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """
+ Called when the user presses a mouse button.
+ """
+ if key == arcade.key.LEFT or key == arcade.key.RIGHT:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ if self.player_sprite.right >= self.end_of_map:
+ self.game_over = True
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ if not self.game_over:
+ self.physics_engine.update()
+
+ coins_hit = arcade.check_for_collision_with_list(self.player_sprite, self.coin_list)
+ for coin in coins_hit:
+ coin.kill()
+ self.score += 1
+
+ # --- Manage Scrolling ---
+
+ # Track if we need to change the view port
+
+ changed = False
+
+ # Scroll left
+ left_bndry = self.view_left + VIEWPORT_LEFT_MARGIN
+ if self.player_sprite.left < left_bndry:
+ self.view_left -= left_bndry - self.player_sprite.left
+ changed = True
+
+ # Scroll right
+ right_bndry = self.view_left + SCREEN_WIDTH - VIEWPORT_RIGHT_MARGIN
+ if self.player_sprite.right > right_bndry:
+ self.view_left += self.player_sprite.right - right_bndry
+ changed = True
+
+ # Scroll up
+ top_bndry = self.view_bottom + SCREEN_HEIGHT - VIEWPORT_MARGIN_TOP
+ if self.player_sprite.top > top_bndry:
+ self.view_bottom += self.player_sprite.top - top_bndry
+ changed = True
+
+ # Scroll down
+ bottom_bndry = self.view_bottom + VIEWPORT_MARGIN_BOTTOM
+ if self.player_sprite.bottom < bottom_bndry:
+ self.view_bottom -= bottom_bndry - self.player_sprite.bottom
+ changed = True
+
+ # If we need to scroll, go ahead and do it.
+ if changed:
+ self.view_left = int(self.view_left)
+ self.view_bottom = int(self.view_bottom)
+ arcade.set_viewport(self.view_left,
+ SCREEN_WIDTH + self.view_left,
+ self.view_bottom,
+ SCREEN_HEIGHT + self.view_bottom)
+
+
+def main():
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/sprite_tiled_map_with_levels.py b/arcade/examples/sprite_tiled_map_with_levels.py
new file mode 100644
index 0000000..3d87d3c
--- /dev/null
+++ b/arcade/examples/sprite_tiled_map_with_levels.py
@@ -0,0 +1,242 @@
+"""
+Load a Tiled map file with Levels
+
+Artwork from: http://kenney.nl
+Tiled available from: http://www.mapeditor.org/
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.sprite_tiled_map_with_levels
+"""
+
+import arcade
+import os
+import time
+
+SPRITE_SCALING = 0.5
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Sprite Tiled Map with Levels Example"
+SPRITE_PIXEL_SIZE = 128
+GRID_PIXEL_SIZE = (SPRITE_PIXEL_SIZE * SPRITE_SCALING)
+
+# How many pixels to keep as a minimum margin between the character
+# and the edge of the screen.
+VIEWPORT_MARGIN_TOP = 60
+VIEWPORT_MARGIN_BOTTOM = 60
+VIEWPORT_RIGHT_MARGIN = 270
+VIEWPORT_LEFT_MARGIN = 270
+
+# Physics
+MOVEMENT_SPEED = 5
+JUMP_SPEED = 23
+GRAVITY = 1.1
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self):
+ """
+ Initializer
+ """
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+
+ # Set the working directory (where we expect to find files) to the same
+ # directory this .py file is in. You can leave this out of your own
+ # code, but it is needed to easily run the examples using "python -m"
+ # as mentioned at the top of this program.
+ file_path = os.path.dirname(os.path.abspath(__file__))
+ os.chdir(file_path)
+
+ # Sprite lists
+ self.wall_list = None
+ self.player_list = None
+ self.coin_list = None
+
+ # Set up the player
+ self.score = 0
+ self.player_sprite = None
+
+ self.physics_engine = None
+ self.view_left = 0
+ self.view_bottom = 0
+ self.game_over = False
+ self.last_time = None
+ self.frame_count = 0
+ self.fps_message = None
+
+ self.level = 1
+ self.max_level = 2
+
+ def setup(self):
+ """ Set up the game and initialize the variables. """
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Set up the player
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING)
+
+ # Starting position of the player
+ self.player_sprite.center_x = 64
+ self.player_sprite.center_y = 64
+ self.player_list.append(self.player_sprite)
+
+ self.load_level(self.level)
+
+ self.game_over = False
+
+ def load_level(self, level):
+ # Read in the tiled map
+ my_map = arcade.tilemap.read_tmx(f"level_{level}.tmx")
+
+ # --- Walls ---
+
+ # Calculate the right edge of the my_map in pixels
+ self.end_of_map = my_map.map_size.width * GRID_PIXEL_SIZE
+
+ # Grab the layer of items we can't move through
+ self.wall_list = arcade.tilemap.process_layer(my_map, 'Platforms', SPRITE_SCALING)
+
+ self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
+ self.wall_list,
+ gravity_constant=GRAVITY)
+
+ # --- Other stuff
+ # Set the background color
+ if my_map.background_color:
+ arcade.set_background_color(my_map.background_color)
+
+ # Set the view port boundaries
+ # These numbers set where we have 'scrolled' to.
+ self.view_left = 0
+ self.view_bottom = 0
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ self.frame_count += 1
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+
+ # Draw all the sprites.
+ self.player_list.draw()
+ self.wall_list.draw()
+ self.coin_list.draw()
+
+ if self.last_time and self.frame_count % 60 == 0:
+ fps = 1.0 / (time.time() - self.last_time) * 60
+ self.fps_message = f"FPS: {fps:5.0f}"
+
+ if self.fps_message:
+ arcade.draw_text(self.fps_message, self.view_left + 10, self.view_bottom + 40, arcade.color.BLACK, 14)
+
+ if self.frame_count % 60 == 0:
+ self.last_time = time.time()
+
+ # Put the text on the screen.
+ # Adjust the text position based on the view port so that we don't
+ # scroll the text too.
+ distance = self.player_sprite.right
+ output = f"Distance: {distance}"
+ arcade.draw_text(output, self.view_left + 10, self.view_bottom + 20, arcade.color.BLACK, 14)
+
+ if self.game_over:
+ arcade.draw_text("Game Over", self.view_left + 200, self.view_bottom + 200, arcade.color.BLACK, 30)
+
+ def on_key_press(self, key, modifiers):
+ """
+ Called whenever the mouse moves.
+ """
+ if key == arcade.key.UP:
+ if self.physics_engine.can_jump():
+ self.player_sprite.change_y = JUMP_SPEED
+ elif key == arcade.key.LEFT:
+ self.player_sprite.change_x = -MOVEMENT_SPEED
+ elif key == arcade.key.RIGHT:
+ self.player_sprite.change_x = MOVEMENT_SPEED
+
+ def on_key_release(self, key, modifiers):
+ """
+ Called when the user presses a mouse button.
+ """
+ if key == arcade.key.LEFT or key == arcade.key.RIGHT:
+ self.player_sprite.change_x = 0
+
+ def update(self, delta_time):
+ """ Movement and game logic """
+
+ if self.player_sprite.right >= self.end_of_map:
+ if self.level < self.max_level:
+ self.level += 1
+ self.load_level(self.level)
+ self.player_sprite.center_x = 64
+ self.player_sprite.center_y = 64
+ self.player_sprite.change_x = 0
+ self.player_sprite.change_y = 0
+ else:
+ self.game_over = True
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ if not self.game_over:
+ self.physics_engine.update()
+
+ coins_hit = arcade.check_for_collision_with_list(self.player_sprite, self.coin_list)
+ for coin in coins_hit:
+ coin.kill()
+ self.score += 1
+
+ # --- Manage Scrolling ---
+
+ # Track if we need to change the view port
+
+ changed = False
+
+ # Scroll left
+ left_bndry = self.view_left + VIEWPORT_LEFT_MARGIN
+ if self.player_sprite.left < left_bndry:
+ self.view_left -= left_bndry - self.player_sprite.left
+ changed = True
+
+ # Scroll right
+ right_bndry = self.view_left + SCREEN_WIDTH - VIEWPORT_RIGHT_MARGIN
+ if self.player_sprite.right > right_bndry:
+ self.view_left += self.player_sprite.right - right_bndry
+ changed = True
+
+ # Scroll up
+ top_bndry = self.view_bottom + SCREEN_HEIGHT - VIEWPORT_MARGIN_TOP
+ if self.player_sprite.top > top_bndry:
+ self.view_bottom += self.player_sprite.top - top_bndry
+ changed = True
+
+ # Scroll down
+ bottom_bndry = self.view_bottom + VIEWPORT_MARGIN_BOTTOM
+ if self.player_sprite.bottom < bottom_bndry:
+ self.view_bottom -= bottom_bndry - self.player_sprite.bottom
+ changed = True
+
+ # If we need to scroll, go ahead and do it.
+ if changed:
+ self.view_left = int(self.view_left)
+ self.view_bottom = int(self.view_bottom)
+ arcade.set_viewport(self.view_left,
+ SCREEN_WIDTH + self.view_left,
+ self.view_bottom,
+ SCREEN_HEIGHT + self.view_bottom)
+
+
+def main():
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/standard_tileset.tsx b/arcade/examples/standard_tileset.tsx
new file mode 100644
index 0000000..b66af37
--- /dev/null
+++ b/arcade/examples/standard_tileset.tsx
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/arcade/examples/stars.jpg b/arcade/examples/stars.jpg
new file mode 100644
index 0000000..4c847ef
Binary files /dev/null and b/arcade/examples/stars.jpg differ
diff --git a/arcade/examples/starting_template.py b/arcade/examples/starting_template.py
new file mode 100644
index 0000000..db7fab4
--- /dev/null
+++ b/arcade/examples/starting_template.py
@@ -0,0 +1,99 @@
+"""
+Starting Template
+
+Once you have learned how to use classes, you can begin your program with this
+template.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.starting_template
+"""
+import arcade
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Starting Template"
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+
+ NOTE: Go ahead and delete the methods you don't need.
+ If you do need a method, delete the 'pass' and replace it
+ with your own code. Don't leave 'pass' in this program.
+ """
+
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title)
+
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ # If you have sprite lists, you should create them here,
+ # and set them to None
+
+ def setup(self):
+ # Create your sprites and sprite lists here
+ pass
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command should happen before we start drawing. It will clear
+ # the screen to the background color, and erase what we drew last frame.
+ arcade.start_render()
+
+ # Call draw() on all your sprite lists below
+
+ def update(self, delta_time):
+ """
+ All the logic to move, and the game logic goes here.
+ Normally, you'll call update() on the sprite lists that
+ need it.
+ """
+ pass
+
+ def on_key_press(self, key, key_modifiers):
+ """
+ Called whenever a key on the keyboard is pressed.
+
+ For a full list of keys, see:
+ http://arcade.academy/arcade.key.html
+ """
+ pass
+
+ def on_key_release(self, key, key_modifiers):
+ """
+ Called whenever the user lets off a previously pressed key.
+ """
+ pass
+
+ def on_mouse_motion(self, x, y, delta_x, delta_y):
+ """
+ Called whenever the mouse moves.
+ """
+ pass
+
+ def on_mouse_press(self, x, y, button, key_modifiers):
+ """
+ Called when the user presses a mouse button.
+ """
+ pass
+
+ def on_mouse_release(self, x, y, button, key_modifiers):
+ """
+ Called when a user releases a mouse button.
+ """
+ pass
+
+
+def main():
+ """ Main method """
+ game = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ game.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/starting_template_simple.py b/arcade/examples/starting_template_simple.py
new file mode 100644
index 0000000..716283d
--- /dev/null
+++ b/arcade/examples/starting_template_simple.py
@@ -0,0 +1,53 @@
+"""
+Starting Template Simple
+
+Once you have learned how to use classes, you can begin your program with this
+template.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.starting_template_simple
+"""
+import arcade
+
+SCREEN_WIDTH = 500
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Starting Template Simple"
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title)
+
+ arcade.set_background_color(arcade.color.WHITE)
+
+ def setup(self):
+ """ Set up the game here. Call this function to restart the game. """
+ pass
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ arcade.start_render()
+
+ def on_mouse_press(self, x, y, button, key_modifiers):
+ """
+ Called when the user presses a mouse button.
+ """
+ pass
+
+
+def main():
+ """ Main method """
+ window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/tetris.py b/arcade/examples/tetris.py
new file mode 100644
index 0000000..8d2f049
--- /dev/null
+++ b/arcade/examples/tetris.py
@@ -0,0 +1,276 @@
+"""
+Tetris
+
+Tetris clone, with some ideas from silvasur's code:
+https://gist.github.com/silvasur/565419/d9de6a84e7da000797ac681976442073045c74a4
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.tetris
+"""
+import arcade
+import random
+import PIL
+
+# Set how many rows and columns we will have
+ROW_COUNT = 24
+COLUMN_COUNT = 10
+
+# This sets the WIDTH and HEIGHT of each grid location
+WIDTH = 30
+HEIGHT = 30
+
+# This sets the margin between each cell
+# and on the edges of the screen.
+MARGIN = 5
+
+# Do the math to figure out our screen dimensions
+SCREEN_WIDTH = (WIDTH + MARGIN) * COLUMN_COUNT + MARGIN
+SCREEN_HEIGHT = (HEIGHT + MARGIN) * ROW_COUNT + MARGIN
+SCREEN_TITLE = "Tetris"
+
+colors = [
+ (0, 0, 0 ),
+ (255, 0, 0 ),
+ (0, 150, 0 ),
+ (0, 0, 255),
+ (255, 120, 0 ),
+ (255, 255, 0 ),
+ (180, 0, 255),
+ (0, 220, 220)
+ ]
+
+# Define the shapes of the single parts
+tetris_shapes = [
+ [[1, 1, 1],
+ [0, 1, 0]],
+
+ [[0, 2, 2],
+ [2, 2, 0]],
+
+ [[3, 3, 0],
+ [0, 3, 3]],
+
+ [[4, 0, 0],
+ [4, 4, 4]],
+
+ [[0, 0, 5],
+ [5, 5, 5]],
+
+ [[6, 6, 6, 6]],
+
+ [[7, 7],
+ [7, 7]]
+]
+
+
+def create_textures():
+ """ Create a list of images for sprites based on the global colors. """
+ texture_list = []
+ for color in colors:
+ image = PIL.Image.new('RGB', (WIDTH, HEIGHT), color)
+ texture_list.append(arcade.Texture(str(color), image=image))
+ return texture_list
+
+
+texture_list = create_textures()
+
+
+def rotate_clockwise(shape):
+ """ Rotates a matrix clockwise """
+ return [[shape[y][x] for y in range(len(shape))] for x in range(len(shape[0]) - 1, -1, -1)]
+
+
+def check_collision(board, shape, offset):
+ """
+ See if the matrix stored in the shape will intersect anything
+ on the board based on the offset. Offset is an (x, y) coordinate.
+ """
+ off_x, off_y = offset
+ for cy, row in enumerate(shape):
+ for cx, cell in enumerate(row):
+ if cell and board[cy + off_y][cx + off_x]:
+ return True
+ return False
+
+
+def remove_row(board, row):
+ """ Remove a row from the board, add a blank row on top. """
+ del board[row]
+ return [[0 for i in range(COLUMN_COUNT)]] + board
+
+
+def join_matrixes(matrix_1, matrix_2, matrix_2_offset):
+ """ Copy matrix 2 onto matrix 1 based on the passed in x, y offset coordinate """
+ offset_x, offset_y = matrix_2_offset
+ for cy, row in enumerate(matrix_2):
+ for cx, val in enumerate(row):
+ matrix_1[cy + offset_y - 1][cx + offset_x] += val
+ return matrix_1
+
+
+def new_board():
+ """ Create a grid of 0's. Add 1's to the bottom for easier collision detection. """
+ # Create the main board of 0's
+ board = [[0 for x in range(COLUMN_COUNT)] for y in range(ROW_COUNT)]
+ # Add a bottom border of 1's
+ board += [[1 for x in range(COLUMN_COUNT)]]
+ return board
+
+
+class MyGame(arcade.Window):
+ """ Main application class. """
+
+ def __init__(self, width, height, title):
+ """ Set up the application. """
+
+ super().__init__(width, height, title)
+
+ arcade.set_background_color(arcade.color.WHITE)
+
+ self.board = None
+ self.frame_count = 0
+ self.game_over = False
+ self.paused = False
+ self.board_sprite_list = None
+
+ def new_stone(self):
+ """
+ Randomly grab a new stone and set the stone location to the top.
+ If we immediately collide, then game-over.
+ """
+ self.stone = random.choice(tetris_shapes)
+ self.stone_x = int(COLUMN_COUNT / 2 - len(self.stone[0]) / 2)
+ self.stone_y = 0
+
+ if check_collision(self.board, self.stone, (self.stone_x, self.stone_y)):
+ self.game_over = True
+
+ def setup(self):
+ self.board = new_board()
+
+ self.board_sprite_list = arcade.SpriteList()
+ for row in range(len(self.board)):
+ for column in range(len(self.board[0])):
+ sprite = arcade.Sprite()
+ for texture in texture_list:
+ sprite.append_texture(texture)
+ sprite.set_texture(0)
+ sprite.center_x = (MARGIN + WIDTH) * column + MARGIN + WIDTH // 2
+ sprite.center_y = SCREEN_HEIGHT - (MARGIN + HEIGHT) * row + MARGIN + HEIGHT // 2
+
+ self.board_sprite_list.append(sprite)
+
+ self.new_stone()
+ self.update_board()
+
+ def drop(self):
+ """
+ Drop the stone down one place.
+ Check for collision.
+ If collided, then
+ join matrixes
+ Check for rows we can remove
+ Update sprite list with stones
+ Create a new stone
+ """
+ if not self.game_over and not self.paused:
+ self.stone_y += 1
+ if check_collision(self.board, self.stone, (self.stone_x, self.stone_y)):
+ self.board = join_matrixes(self.board, self.stone, (self.stone_x, self.stone_y))
+ while True:
+ for i, row in enumerate(self.board[:-1]):
+ if 0 not in row:
+ self.board = remove_row(self.board, i)
+ break
+ else:
+ break
+ self.update_board()
+ self.new_stone()
+
+ def rotate_stone(self):
+ """ Rotate the stone, check collision. """
+ if not self.game_over and not self.paused:
+ new_stone = rotate_clockwise(self.stone)
+ if not check_collision(self.board, new_stone, (self.stone_x, self.stone_y)):
+ self.stone = new_stone
+
+ def update(self, dt):
+ """ Update, drop stone if warrented """
+ self.frame_count += 1
+ if self.frame_count % 10 == 0:
+ self.drop()
+
+ def move(self, delta_x):
+ """ Move the stone back and forth based on delta x. """
+ if not self.game_over and not self.paused:
+ new_x = self.stone_x + delta_x
+ if new_x < 0:
+ new_x = 0
+ if new_x > COLUMN_COUNT - len(self.stone[0]):
+ new_x = COLUMN_COUNT - len(self.stone[0])
+ if not check_collision(self.board, self.stone, (new_x, self.stone_y)):
+ self.stone_x = new_x
+
+ def on_key_press(self, key, modifiers):
+ """
+ Handle user key presses
+ User goes left, move -1
+ User goes right, move 1
+ Rotate stone,
+ or drop down
+ """
+ if key == arcade.key.LEFT:
+ self.move(-1)
+ elif key == arcade.key.RIGHT:
+ self.move(1)
+ elif key == arcade.key.UP:
+ self.rotate_stone()
+ elif key == arcade.key.DOWN:
+ self.drop()
+
+ def draw_grid(self, grid, offset_x, offset_y):
+ """
+ Draw the grid. Used to draw the falling stones. The board is drawn
+ by the sprite list.
+ """
+ # Draw the grid
+ for row in range(len(grid)):
+ for column in range(len(grid[0])):
+ # Figure out what color to draw the box
+ if grid[row][column]:
+ color = colors[grid[row][column]]
+ # Do the math to figure out where the box is
+ x = (MARGIN + WIDTH) * (column + offset_x) + MARGIN + WIDTH // 2
+ y = SCREEN_HEIGHT - (MARGIN + HEIGHT) * (row + offset_y) + MARGIN + HEIGHT // 2
+
+ # Draw the box
+ arcade.draw_rectangle_filled(x, y, WIDTH, HEIGHT, color)
+
+ def update_board(self):
+ """
+ Update the sprite list to reflect the contents of the 2d grid
+ """
+ for row in range(len(self.board)):
+ for column in range(len(self.board[0])):
+ v = self.board[row][column]
+ i = row * COLUMN_COUNT + column
+ self.board_sprite_list[i].set_texture(v)
+
+ def on_draw(self):
+ """ Render the screen. """
+
+ # This command has to happen before we start drawing
+ arcade.start_render()
+ self.board_sprite_list.draw()
+ self.draw_grid(self.stone, self.stone_x, self.stone_y)
+
+
+def main():
+ """ Create the game window, setup, run """
+ my_game = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ my_game.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/text_loc_example.po b/arcade/examples/text_loc_example.po
new file mode 100644
index 0000000..558ad7d
--- /dev/null
+++ b/arcade/examples/text_loc_example.po
@@ -0,0 +1,21 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR ORGANIZATION
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: 2019-05-06 12:19-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.5\n"
+
+
+#: text_loc_example.py:46
+msgid "Simple line of text in 12 point"
+msgstr "Línea simple de texto en 12 puntos."
+
diff --git a/arcade/examples/text_loc_example_done.py b/arcade/examples/text_loc_example_done.py
new file mode 100644
index 0000000..672b2b0
--- /dev/null
+++ b/arcade/examples/text_loc_example_done.py
@@ -0,0 +1,70 @@
+"""
+Example showing how to draw text to the screen.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.text_loc_example_done
+"""
+import arcade
+import gettext
+import os
+
+# Set the working directory (where we expect to find files) to the same
+# directory this .py file is in. You can leave this out of your own
+# code, but it is needed to easily run the examples using "python -m"
+# as mentioned at the top of this program.
+file_path = os.path.dirname(os.path.abspath(__file__))
+os.chdir(file_path)
+
+# Try to auto-detect the user's language and translate to it
+gettext.install('text_loc_example', localedir='text_loc_example_locale')
+
+SCREEN_WIDTH = 500
+SCREEN_HEIGHT = 500
+SCREEN_TITLE = "Localizing Text Example"
+_ = gettext.gettext
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title)
+
+ arcade.set_background_color(arcade.color.WHITE)
+ self.text_angle = 0
+ self.time_elapsed = 0.0
+
+ def update(self, delta_time):
+ self.text_angle += 1
+ self.time_elapsed += delta_time
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command should happen before we start drawing. It will clear
+ # the screen to the background color, and erase what we drew last frame.
+ arcade.start_render()
+
+ # start_x and start_y make the start point for the text.
+ # We draw a dot to make it easy too see
+ # the text in relation to its start x and y.
+ start_x = 50
+ start_y = 450
+ arcade.draw_point(start_x, start_y, arcade.color.BLUE, 5)
+ arcade.draw_text(
+ _("Simple line of text in 12 point"), start_x, start_y, arcade.color.BLACK, 12
+ )
+
+
+def main():
+ MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
+
diff --git a/arcade/examples/text_loc_example_start.py b/arcade/examples/text_loc_example_start.py
new file mode 100644
index 0000000..d6ea5e9
--- /dev/null
+++ b/arcade/examples/text_loc_example_start.py
@@ -0,0 +1,65 @@
+"""
+Example showing how to draw text to the screen.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.text_loc_example_start
+"""
+import arcade
+import os
+
+# Set the working directory (where we expect to find files) to the same
+# directory this .py file is in. You can leave this out of your own
+# code, but it is needed to easily run the examples using "python -m"
+# as mentioned at the top of this program.
+file_path = os.path.dirname(os.path.abspath(__file__))
+os.chdir(file_path)
+
+SCREEN_WIDTH = 500
+SCREEN_HEIGHT = 500
+SCREEN_TITLE = "Localizing Text Example"
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self, width, height, title):
+ super().__init__(width, height, title)
+
+ arcade.set_background_color(arcade.color.WHITE)
+ self.text_angle = 0
+ self.time_elapsed = 0.0
+
+ def update(self, delta_time):
+ self.text_angle += 1
+ self.time_elapsed += delta_time
+
+ def on_draw(self):
+ """
+ Render the screen.
+ """
+
+ # This command should happen before we start drawing. It will clear
+ # the screen to the background color, and erase what we drew last frame.
+ arcade.start_render()
+
+ # start_x and start_y make the start point for the text.
+ # We draw a dot to make it easy too see
+ # the text in relation to its start x and y.
+ start_x = 50
+ start_y = 450
+ arcade.draw_point(start_x, start_y, arcade.color.BLUE, 5)
+ arcade.draw_text(
+ "Simple line of text in 12 point", start_x, start_y, arcade.color.BLACK, 12
+ )
+
+
+def main():
+ MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
+
diff --git a/arcade/examples/timer.py b/arcade/examples/timer.py
new file mode 100644
index 0000000..9cad2d8
--- /dev/null
+++ b/arcade/examples/timer.py
@@ -0,0 +1,64 @@
+"""
+Show a timer on-screen.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.timer
+"""
+
+import arcade
+
+SCREEN_WIDTH = 800
+SCREEN_HEIGHT = 600
+SCREEN_TITLE = "Timer Example"
+
+
+class MyGame(arcade.Window):
+ """
+ Main application class.
+ """
+
+ def __init__(self):
+ super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
+ self.total_time = 0.0
+
+ def setup(self):
+ """
+ Set up the application.
+ """
+ arcade.set_background_color(arcade.color.WHITE)
+ self.total_time = 0.0
+
+ def on_draw(self):
+ """ Use this function to draw everything to the screen. """
+
+ # Start the render. This must happen before any drawing
+ # commands. We do NOT need an stop render command.
+ arcade.start_render()
+
+ # Calculate minutes
+ minutes = int(self.total_time) // 60
+
+ # Calculate seconds by using a modulus (remainder)
+ seconds = int(self.total_time) % 60
+
+ # Figure out our output
+ output = f"Time: {minutes:02d}:{seconds:02d}"
+
+ # Output the timer text.
+ arcade.draw_text(output, 300, 300, arcade.color.BLACK, 30)
+
+ def update(self, delta_time):
+ """
+ All the logic to move, and the game logic goes here.
+ """
+ self.total_time += delta_time
+
+
+def main():
+ window = MyGame()
+ window.setup()
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/view_instructions_and_game_over.py b/arcade/examples/view_instructions_and_game_over.py
new file mode 100644
index 0000000..ffea7ed
--- /dev/null
+++ b/arcade/examples/view_instructions_and_game_over.py
@@ -0,0 +1,185 @@
+"""
+This program shows how to:
+ * Have one or more instruction screens
+ * Show a 'Game over' text and halt the game
+ * Allow the user to restart the game
+
+Make a separate class for each view (screen) in your game.
+The class will inherit from arcade.View. The structure will
+look like an arcade.Window as each view will need to have its own draw,
+update and window event methods. To switch a view, simply create a view
+with `view = MyView()` and then use the view.show() method.
+
+This example shows how you can set data from one View on another View to pass data
+around (see: time_taken), or you can store data on the Window object to share data between
+all Views (see: total_score).
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.view_instructions_and_game_over.py
+"""
+
+import arcade
+import random
+import os
+
+
+file_path = os.path.dirname(os.path.abspath(__file__))
+os.chdir(file_path)
+
+
+WIDTH = 800
+HEIGHT = 600
+SPRITE_SCALING = 0.5
+
+
+class MenuView(arcade.View):
+ def on_show(self):
+ arcade.set_background_color(arcade.color.WHITE)
+
+ def on_draw(self):
+ arcade.start_render()
+ arcade.draw_text("Menu Screen", WIDTH/2, HEIGHT/2,
+ arcade.color.BLACK, font_size=50, anchor_x="center")
+ arcade.draw_text("Click to advance", WIDTH/2, HEIGHT/2-75,
+ arcade.color.GRAY, font_size=20, anchor_x="center")
+
+ def on_mouse_press(self, x, y, button, modifiers):
+ instructions_view = InstructionView()
+ self.window.show_view(instructions_view)
+
+
+class InstructionView(arcade.View):
+ def on_show(self):
+ arcade.set_background_color(arcade.color.ORANGE_PEEL)
+
+ def on_draw(self):
+ arcade.start_render()
+ arcade.draw_text("Instructions Screen", WIDTH/2, HEIGHT/2,
+ arcade.color.BLACK, font_size=50, anchor_x="center")
+ arcade.draw_text("Click to advance", WIDTH/2, HEIGHT/2-75,
+ arcade.color.GRAY, font_size=20, anchor_x="center")
+
+ def on_mouse_press(self, x, y, button, modifiers):
+ game_view = GameView()
+ self.window.show_view(game_view)
+
+
+class GameView(arcade.View):
+ def __init__(self):
+ super().__init__()
+
+ self.time_taken = 0
+
+ # Sprite lists
+ self.player_list = arcade.SpriteList()
+ self.coin_list = arcade.SpriteList()
+
+ # Set up the player
+ self.score = 0
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 50
+ self.player_list.append(self.player_sprite)
+
+ for i in range(5):
+
+ # Create the coin instance
+ coin = arcade.Sprite("images/coin_01.png", SPRITE_SCALING / 3)
+
+ # Position the coin
+ coin.center_x = random.randrange(WIDTH)
+ coin.center_y = random.randrange(HEIGHT)
+
+ # Add the coin to the lists
+ self.coin_list.append(coin)
+
+ def on_show(self):
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ # Don't show the mouse cursor
+ self.window.set_mouse_visible(False)
+
+ def on_draw(self):
+ arcade.start_render()
+ # Draw all the sprites.
+ self.player_list.draw()
+ self.coin_list.draw()
+
+ # Put the text on the screen.
+ output = f"Score: {self.score}"
+ arcade.draw_text(output, 10, 30, arcade.color.WHITE, 14)
+ output_total = f"Total Score: {self.window.total_score}"
+ arcade.draw_text(output_total, 10, 10, arcade.color.WHITE, 14)
+
+ def update(self, delta_time):
+ self.time_taken += delta_time
+
+ # Call update on all sprites (The sprites don't do much in this
+ # example though.)
+ self.coin_list.update()
+ self.player_list.update()
+
+ # Generate a list of all sprites that collided with the player.
+ hit_list = arcade.check_for_collision_with_list(self.player_sprite, self.coin_list)
+
+ # Loop through each colliding sprite, remove it, and add to the
+ # score.
+ for coin in hit_list:
+ coin.kill()
+ self.score += 1
+ self.window.total_score += 1
+
+ # If we've collected all the games, then move to a "GAME_OVER"
+ # state.
+ if len(self.coin_list) == 0:
+ game_over_view = GameOverView()
+ game_over_view.time_taken = self.time_taken
+ self.window.set_mouse_visible(True)
+ self.window.show_view(game_over_view)
+
+ def on_mouse_motion(self, x, y, dx, dy):
+ """
+ Called whenever the mouse moves.
+ """
+ self.player_sprite.center_x = x
+ self.player_sprite.center_y = y
+
+
+class GameOverView(arcade.View):
+ def on_show(self):
+ arcade.set_background_color(arcade.color.BLACK)
+
+ def on_draw(self):
+ arcade.start_render()
+ """
+ Draw "Game over" across the screen.
+ """
+ arcade.draw_text("Game Over", 240, 400, arcade.color.WHITE, 54)
+ arcade.draw_text("Click to restart", 310, 300, arcade.color.WHITE, 24)
+
+ time_taken_formatted = f"{round(self.time_taken, 2)} seconds"
+ arcade.draw_text(f"Time taken: {time_taken_formatted}",
+ WIDTH/2,
+ 200,
+ arcade.color.GRAY,
+ font_size=15,
+ anchor_x="center")
+
+ output_total = f"Total Score: {self.window.total_score}"
+ arcade.draw_text(output_total, 10, 10, arcade.color.WHITE, 14)
+
+ def on_mouse_press(self, x, y, button, modifiers):
+ game_view = GameView()
+ self.window.show_view(game_view)
+
+
+def main():
+ window = arcade.Window(WIDTH, HEIGHT, "Different Views Example")
+ window.total_score = 0
+ menu_view = MenuView()
+ window.show_view(menu_view)
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/view_pause_screen.py b/arcade/examples/view_pause_screen.py
new file mode 100644
index 0000000..5b00217
--- /dev/null
+++ b/arcade/examples/view_pause_screen.py
@@ -0,0 +1,142 @@
+"""
+This program shows how to have a pause screen without resetting the game.
+
+Make a separate class for each view (screen) in your game.
+The class will inherit from arcade.View. The structure will
+look like an arcade.Window as each View will need to have its own draw,
+update and window event methods. To switch a View, simply create a view
+with `view = MyView()` and then use the "self.window.set_view(view)" method.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.view_pause_screen
+"""
+
+import arcade
+import random
+import os
+
+
+file_path = os.path.dirname(os.path.abspath(__file__))
+os.chdir(file_path)
+
+
+WIDTH = 800
+HEIGHT = 600
+SPRITE_SCALING = 0.5
+
+
+class MenuView(arcade.View):
+ def on_show(self):
+ arcade.set_background_color(arcade.color.WHITE)
+
+ def on_draw(self):
+ arcade.start_render()
+ arcade.draw_text("Menu Screen", WIDTH/2, HEIGHT/2,
+ arcade.color.BLACK, font_size=50, anchor_x="center")
+ arcade.draw_text("Click to advance.", WIDTH/2, HEIGHT/2-75,
+ arcade.color.GRAY, font_size=20, anchor_x="center")
+
+ def on_mouse_press(self, x, y, button, modifiers):
+ game = GameView()
+ self.window.show_view(game)
+
+
+class GameView(arcade.View):
+ def __init__(self):
+ super().__init__()
+ self.player_sprite = arcade.Sprite("images/character.png", SPRITE_SCALING)
+ self.player_sprite.center_x = 50
+ self.player_sprite.center_y = 50
+ self.player_sprite.velocity = [3, 3]
+
+ def on_show(self):
+ arcade.set_background_color(arcade.color.AMAZON)
+
+ def on_draw(self):
+ arcade.start_render()
+ # Draw all the sprites.
+ self.player_sprite.draw()
+
+ # Show tip to pause screen
+ arcade.draw_text("Press Esc. to pause",
+ WIDTH/2,
+ HEIGHT-100,
+ arcade.color.BLACK,
+ font_size=20,
+ anchor_x="center")
+
+ def update(self, delta_time):
+ # Call update on all sprites
+ self.player_sprite.update()
+
+ # Bounce off the edges
+ if self.player_sprite.left < 0 or self.player_sprite.right > WIDTH:
+ self.player_sprite.change_x *= -1
+ if self.player_sprite.bottom < 0 or self.player_sprite.top > HEIGHT:
+ self.player_sprite.change_y *= -1
+
+ def on_key_press(self, key, modifiers):
+ if key == arcade.key.ESCAPE:
+ # pass self, the current view, to preserve this view's state
+ pause = PauseView(self)
+ self.window.show_view(pause)
+
+
+class PauseView(arcade.View):
+ def __init__(self, game_view):
+ super().__init__()
+ self.game_view = game_view
+
+ def on_show(self):
+ arcade.set_background_color(arcade.color.ORANGE)
+
+ def on_draw(self):
+ arcade.start_render()
+
+ # Draw player, for effect, on pause screen.
+ # The previous View (GameView) was passed in
+ # and saved in self.game_view.
+ player_sprite = self.game_view.player_sprite
+ player_sprite.draw()
+
+ # draw an orange filter over him
+ arcade.draw_lrtb_rectangle_filled(left=player_sprite.left,
+ right=player_sprite.right,
+ top=player_sprite.top,
+ bottom=player_sprite.bottom,
+ color=(*arcade.color.ORANGE, 200))
+
+ arcade.draw_text("PAUSED", WIDTH/2, HEIGHT/2+50,
+ arcade.color.BLACK, font_size=50, anchor_x="center")
+
+ # Show tip to return or reset
+ arcade.draw_text("Press Esc. to return",
+ WIDTH/2,
+ HEIGHT/2,
+ arcade.color.BLACK,
+ font_size=20,
+ anchor_x="center")
+ arcade.draw_text("Press Enter to reset",
+ WIDTH/2,
+ HEIGHT/2-30,
+ arcade.color.BLACK,
+ font_size=20,
+ anchor_x="center")
+
+ def on_key_press(self, key, modifiers):
+ if key == arcade.key.ESCAPE: # resume game
+ self.window.show_view(self.game_view)
+ elif key == arcade.key.ENTER: # reset game
+ game = GameView()
+ self.window.show_view(game)
+
+
+def main():
+ window = arcade.Window(WIDTH, HEIGHT, "Instruction and Game Over Views Example")
+ menu = MenuView()
+ window.show_view(menu)
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/examples/view_screens_minimal.py b/arcade/examples/view_screens_minimal.py
new file mode 100644
index 0000000..580f243
--- /dev/null
+++ b/arcade/examples/view_screens_minimal.py
@@ -0,0 +1,84 @@
+"""
+This program shows how to:
+ * Display a sequence of screens in your game. The "arcade.View"
+ class makes it easy to separate the code for each screen into
+ its own class.
+ * This example shows the absolute basics of using "arcade.View".
+ See the "different_screens_example.py" for how to handle
+ screen-specific data.
+
+Make a separate class for each view (screen) in your game.
+The class will inherit from arcade.View. The structure will
+look like an arcade.Window as each View will need to have its own draw,
+update and window event methods. To switch a View, simply create a View
+with `view = MyView()` and then use the "self.window.set_view(view)" method.
+
+If Python and Arcade are installed, this example can be run from the command line with:
+python -m arcade.examples.view_screens_minimal
+"""
+
+import arcade
+import os
+
+
+file_path = os.path.dirname(os.path.abspath(__file__))
+os.chdir(file_path)
+
+
+WIDTH = 800
+HEIGHT = 600
+
+
+class MenuView(arcade.View):
+ def on_show(self):
+ arcade.set_background_color(arcade.color.WHITE)
+
+ def on_draw(self):
+ arcade.start_render()
+ arcade.draw_text("Menu Screen - click to advance", WIDTH/2, HEIGHT/2,
+ arcade.color.BLACK, font_size=30, anchor_x="center")
+
+ def on_mouse_press(self, x, y, button, modifiers):
+ game_view = GameView()
+ self.window.show_view(game_view)
+
+
+class GameView(arcade.View):
+ def on_show(self):
+ arcade.set_background_color(arcade.color.ORANGE_PEEL)
+
+ def on_draw(self):
+ arcade.start_render()
+ arcade.draw_text("Game - press SPACE to advance", WIDTH/2, HEIGHT/2,
+ arcade.color.BLACK, font_size=30, anchor_x="center")
+
+ def on_key_press(self, key, modifiers):
+ if key == arcade.key.SPACE:
+ game_over_view = GameOverView()
+ self.window.show_view(game_over_view)
+
+
+class GameOverView(arcade.View):
+ def on_show(self):
+ arcade.set_background_color(arcade.color.BLACK)
+
+ def on_draw(self):
+ arcade.start_render()
+ arcade.draw_text("Game Over - press ESCAPE to advance", WIDTH/2, HEIGHT/2,
+ arcade.color.WHITE, 30, anchor_x="center")
+
+ def on_key_press(self, key, modifiers):
+ if key == arcade.key.ESCAPE:
+ menu_view = MenuView()
+ self.window.show_view(menu_view)
+
+
+def main():
+ window = arcade.Window(WIDTH, HEIGHT, "Different Views Minimal Example")
+ menu_view = MenuView()
+ window.show_view(menu_view)
+ arcade.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/arcade/geometry.py b/arcade/geometry.py
new file mode 100644
index 0000000..4379210
--- /dev/null
+++ b/arcade/geometry.py
@@ -0,0 +1,196 @@
+"""
+Functions for calculating geometry.
+"""
+
+from arcade.sprite import Sprite
+from arcade.sprite_list import SpriteList
+from typing import List
+from arcade.arcade_types import PointList
+from arcade.arcade_types import Point
+
+PRECISION = 2
+
+
+def are_polygons_intersecting(poly_a: PointList,
+ poly_b: PointList) -> bool:
+ """
+ Return True if two polygons intersect.
+
+ :param PointList poly_a: List of points that define the first polygon.
+ :param PointList poly_b: List of points that define the second polygon.
+ :Returns: True or false depending if polygons intersect
+
+ :rtype bool:
+ """
+
+ for polygon in (poly_a, poly_b):
+
+ for i1 in range(len(polygon)):
+ i2 = (i1 + 1) % len(polygon)
+ projection_1 = polygon[i1]
+ projection_2 = polygon[i2]
+
+ normal = (projection_2[1] - projection_1[1],
+ projection_1[0] - projection_2[0])
+
+ min_a, max_a, min_b, max_b = (None,) * 4
+
+ for poly in poly_a:
+ projected = normal[0] * poly[0] + normal[1] * poly[1]
+
+ if min_a is None or projected < min_a:
+ min_a = projected
+ if max_a is None or projected > max_a:
+ max_a = projected
+
+ for poly in poly_b:
+ projected = normal[0] * poly[0] + normal[1] * poly[1]
+
+ if min_b is None or projected < min_b:
+ min_b = projected
+ if max_b is None or projected > max_b:
+ max_b = projected
+
+ if max_a <= min_b or max_b <= min_a:
+ return False
+
+ return True
+
+
+def check_for_collision(sprite1: Sprite, sprite2: Sprite) -> bool:
+ """
+ Check for a collision between two sprites.
+
+ :param sprite1: First sprite
+ :param sprite2: Second sprite
+
+ :Returns: True or False depending if the sprites intersect.
+ """
+ if not isinstance(sprite1, Sprite):
+ raise TypeError("Parameter 1 is not an instance of the Sprite class.")
+ if isinstance(sprite2, SpriteList):
+ raise TypeError("Parameter 2 is a instance of the SpriteList instead of a required Sprite. See if you meant to "
+ "call check_for_collision_with_list instead of check_for_collision.")
+ elif not isinstance(sprite2, Sprite):
+ raise TypeError("Parameter 2 is not an instance of the Sprite class.")
+
+ return _check_for_collision(sprite1, sprite2)
+
+
+def _check_for_collision(sprite1: Sprite, sprite2: Sprite) -> bool:
+ """
+ Check for collision between two sprites.
+
+ :param Sprite sprite1: Sprite 1
+ :param Sprite sprite2: Sprite 2
+
+ :returns: Boolean
+ """
+ collision_radius_sum = sprite1.collision_radius + sprite2.collision_radius
+
+ diff_x = sprite1.position[0] - sprite2.position[0]
+ diff_x2 = diff_x * diff_x
+
+ if diff_x2 > collision_radius_sum * collision_radius_sum:
+ return False
+
+ diff_y = sprite1.position[1] - sprite2.position[1]
+ diff_y2 = diff_y * diff_y
+ if diff_y2 > collision_radius_sum * collision_radius_sum:
+ return False
+
+ distance = diff_x2 + diff_y2
+ if distance > collision_radius_sum * collision_radius_sum:
+ return False
+
+ return are_polygons_intersecting(sprite1.points, sprite2.points)
+
+
+def check_for_collision_with_list(sprite: Sprite,
+ sprite_list: SpriteList) -> List[Sprite]:
+ """
+ Check for a collision between a sprite, and a list of sprites.
+
+ :param Sprite sprite: Sprite to check
+ :param SpriteList sprite_list: SpriteList to check against
+
+ :returns: List of sprites colliding, or an empty list.
+ """
+ if not isinstance(sprite, Sprite):
+ raise TypeError("Parameter 1 is not an instance of the Sprite class.")
+ if not isinstance(sprite_list, SpriteList):
+ raise TypeError(f"Parameter 2 is a {type(sprite_list)} instead of expected SpriteList.")
+
+ if sprite_list.use_spatial_hash:
+ sprite_list_to_check = sprite_list.spatial_hash.get_objects_for_box(sprite)
+ # checks_saved = len(sprite_list) - len(sprite_list_to_check)
+ else:
+ sprite_list_to_check = sprite_list
+
+ collision_list = [sprite2
+ for sprite2 in sprite_list_to_check
+ if sprite is not sprite2 and _check_for_collision(sprite, sprite2)]
+
+ # collision_list = []
+ # for sprite2 in sprite_list_to_check:
+ # if sprite1 is not sprite2 and sprite2 not in collision_list:
+ # if _check_for_collision(sprite1, sprite2):
+ # collision_list.append(sprite2)
+ return collision_list
+
+
+def is_point_in_polygon(x, y, polygon_point_list):
+ """
+ Use ray-tracing to see if point is inside a polygon
+
+ Args:
+ x:
+ y:
+ polygon_point_list:
+
+ Returns: bool
+
+ """
+
+ n = len(polygon_point_list)
+ inside = False
+
+ p1x, p1y = polygon_point_list[0]
+ for i in range(n+1):
+ p2x, p2y = polygon_point_list[i % n]
+ if y > min(p1y, p2y):
+ if y <= max(p1y, p2y):
+ if x <= max(p1x, p2x):
+ if p1y != p2y:
+ xints = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
+ if p1x == p2x or x <= xints:
+ inside = not inside
+ p1x, p1y = p2x, p2y
+
+ return inside
+
+
+def get_sprites_at_point(point: Point,
+ sprite_list: SpriteList) -> List[Sprite]:
+ """
+ Get a list of sprites at a particular point
+
+ :param Point point: Point to check
+ :param SpriteList sprite_list: SpriteList to check against
+
+ :returns: List of sprites colliding, or an empty list.
+ """
+ if not isinstance(sprite_list, SpriteList):
+ raise TypeError(f"Parameter 2 is a {type(sprite_list)} instead of expected SpriteList.")
+
+ if sprite_list.use_spatial_hash:
+ sprite_list_to_check = sprite_list.spatial_hash.get_objects_for_point(point)
+ # checks_saved = len(sprite_list) - len(sprite_list_to_check)
+ else:
+ sprite_list_to_check = sprite_list
+
+ collision_list = [sprite2
+ for sprite2 in sprite_list_to_check
+ if is_point_in_polygon(point[0], point[1], sprite2.points)]
+
+ return collision_list
diff --git a/arcade/isometric.py b/arcade/isometric.py
new file mode 100644
index 0000000..5af2464
--- /dev/null
+++ b/arcade/isometric.py
@@ -0,0 +1,49 @@
+from arcade import ShapeElementList
+from arcade.buffered_draw_commands import create_line
+
+
+def isometric_grid_to_screen(tile_x, tile_y, width, height, tile_width, tile_height):
+ screen_x = tile_width * tile_x // 2 + height * tile_width // 2 - tile_y * tile_width // 2
+ screen_y = (height - tile_y - 1) * tile_height // 2 + width * tile_height // 2 - tile_x * tile_height // 2
+ return screen_x, screen_y
+
+
+def screen_to_isometric_grid(screen_x, screen_y, width, height, tile_width, tile_height):
+ x2 = (1 / tile_width * screen_x / 2 - 1 / tile_height * screen_y / 2 + width / 2) * 2 - (width / 2 + 0.5)
+ y2 = (height - 1) - ((1 / tile_width * screen_x / 2 + 1 / tile_height * screen_y / 2) * 2 - (width / 2 + 0.5))
+ x2 = round(x2)
+ y2 = round(y2)
+ return x2, y2
+
+
+def create_isometric_grid_lines(width, height, tile_width, tile_height, color, line_width):
+
+ # Grid lines 1
+ shape_list = ShapeElementList()
+
+ for tile_row in range(-1, height):
+ tile_x = 0
+ start_x, start_y = isometric_grid_to_screen(tile_x, tile_row, width, height, tile_width, tile_height)
+ tile_x = width - 1
+ end_x, end_y = isometric_grid_to_screen(tile_x, tile_row, width, height, tile_width, tile_height)
+
+ start_x -= tile_width // 2
+ end_y -= tile_height // 2
+
+ line = create_line(start_x, start_y, end_x, end_y, color, line_width=line_width)
+ shape_list.append(line)
+
+ # Grid lines 2
+ for tile_column in range(-1, width):
+ tile_y = 0
+ start_x, start_y = isometric_grid_to_screen(tile_column, tile_y, width, height, tile_width, tile_height)
+ tile_y = height - 1
+ end_x, end_y = isometric_grid_to_screen(tile_column, tile_y, width, height, tile_width, tile_height)
+
+ start_x += tile_width // 2
+ end_y -= tile_height // 2
+
+ line = create_line(start_x, start_y, end_x, end_y, color, line_width=line_width)
+ shape_list.append(line)
+
+ return shape_list
diff --git a/arcade/joysticks.py b/arcade/joysticks.py
new file mode 100644
index 0000000..0ea3d15
--- /dev/null
+++ b/arcade/joysticks.py
@@ -0,0 +1,21 @@
+import pyglet.input
+
+
+def get_joysticks():
+ """
+ Get a list of all the game controllers
+
+ This is an alias of ``get_game_controllers``, which is better worded.
+
+ :return: List of game controllers
+ """
+ return pyglet.input.get_joysticks()
+
+
+def get_game_controllers():
+ """
+ Get a list of all the game controllers
+
+ :return: List of game controllers
+ """
+ return get_joysticks()
diff --git a/arcade/key/__init__.py b/arcade/key/__init__.py
new file mode 100644
index 0000000..d572bb6
--- /dev/null
+++ b/arcade/key/__init__.py
@@ -0,0 +1,211 @@
+"""
+Constants used to signify what keys on the keyboard were pressed.
+"""
+
+# Key modifiers
+# Done in powers of two, so you can do a bit-wise 'and' to detect
+# multiple modifiers.
+MOD_SHIFT = 1
+MOD_CTRL = 2
+MOD_ALT = 4
+MOD_CAPSLOCK = 8
+MOD_NUMLOCK = 16
+MOD_WINDOWS = 32
+MOD_COMMAND = 64
+MOD_OPTION = 128
+MOD_SCROLLLOCK = 256
+MOD_ACCEL = 2
+
+# Keys
+BACKSPACE = 65288
+TAB = 65289
+LINEFEED = 65290
+CLEAR = 65291
+RETURN = 65293
+ENTER = 65293
+PAUSE = 65299
+SCROLLLOCK = 65300
+SYSREQ = 65301
+ESCAPE = 65307
+HOME = 65360
+LEFT = 65361
+UP = 65362
+RIGHT = 65363
+DOWN = 65364
+PAGEUP = 65365
+PAGEDOWN = 65366
+END = 65367
+BEGIN = 65368
+DELETE = 65535
+SELECT = 65376
+PRINT = 65377
+EXECUTE = 65378
+INSERT = 65379
+UNDO = 65381
+REDO = 65382
+MENU = 65383
+FIND = 65384
+CANCEL = 65385
+HELP = 65386
+BREAK = 65387
+MODESWITCH = 65406
+SCRIPTSWITCH = 65406
+MOTION_UP = 65362
+MOTION_RIGHT = 65363
+MOTION_DOWN = 65364
+MOTION_LEFT = 65361
+MOTION_NEXT_WORD = 1
+MOTION_PREVIOUS_WORD = 2
+MOTION_BEGINNING_OF_LINE = 3
+MOTION_END_OF_LINE = 4
+MOTION_NEXT_PAGE = 65366
+MOTION_PREVIOUS_PAGE = 65365
+MOTION_BEGINNING_OF_FILE = 5
+MOTION_END_OF_FILE = 6
+MOTION_BACKSPACE = 65288
+MOTION_DELETE = 65535
+NUMLOCK = 65407
+NUM_SPACE = 65408
+NUM_TAB = 65417
+NUM_ENTER = 65421
+NUM_F1 = 65425
+NUM_F2 = 65426
+NUM_F3 = 65427
+NUM_F4 = 65428
+NUM_HOME = 65429
+NUM_LEFT = 65430
+NUM_UP = 65431
+NUM_RIGHT = 65432
+NUM_DOWN = 65433
+NUM_PRIOR = 65434
+NUM_PAGE_UP = 65434
+NUM_NEXT = 65435
+NUM_PAGE_DOWN = 65435
+NUM_END = 65436
+NUM_BEGIN = 65437
+NUM_INSERT = 65438
+NUM_DELETE = 65439
+NUM_EQUAL = 65469
+NUM_MULTIPLY = 65450
+NUM_ADD = 65451
+NUM_SEPARATOR = 65452
+NUM_SUBTRACT = 65453
+NUM_DECIMAL = 65454
+NUM_DIVIDE = 65455
+
+# Numbers on the numberpad
+NUM_0 = 65456
+NUM_1 = 65457
+NUM_2 = 65458
+NUM_3 = 65459
+NUM_4 = 65460
+NUM_5 = 65461
+NUM_6 = 65462
+NUM_7 = 65463
+NUM_8 = 65464
+NUM_9 = 65465
+
+F1 = 65470
+F2 = 65471
+F3 = 65472
+F4 = 65473
+F5 = 65474
+F6 = 65475
+F7 = 65476
+F8 = 65477
+F9 = 65478
+F10 = 65479
+F11 = 65480
+F12 = 65481
+F13 = 65482
+F14 = 65483
+F15 = 65484
+F16 = 65485
+LSHIFT = 65505
+RSHIFT = 65506
+LCTRL = 65507
+RCTRL = 65508
+CAPSLOCK = 65509
+LMETA = 65511
+RMETA = 65512
+LALT = 65513
+RALT = 65514
+LWINDOWS = 65515
+RWINDOWS = 65516
+LCOMMAND = 65517
+RCOMMAND = 65518
+LOPTION = 65488
+ROPTION = 65489
+SPACE = 32
+EXCLAMATION = 33
+DOUBLEQUOTE = 34
+HASH = 35
+POUND = 35
+DOLLAR = 36
+PERCENT = 37
+AMPERSAND = 38
+APOSTROPHE = 39
+PARENLEFT = 40
+PARENRIGHT = 41
+ASTERISK = 42
+PLUS = 43
+COMMA = 44
+MINUS = 45
+PERIOD = 46
+SLASH = 47
+
+# Numbers on the main keyboard
+KEY_0 = 48
+KEY_1 = 49
+KEY_2 = 50
+KEY_3 = 51
+KEY_4 = 52
+KEY_5 = 53
+KEY_6 = 54
+KEY_7 = 55
+KEY_8 = 56
+KEY_9 = 57
+COLON = 58
+SEMICOLON = 59
+LESS = 60
+EQUAL = 61
+GREATER = 62
+QUESTION = 63
+AT = 64
+BRACKETLEFT = 91
+BACKSLASH = 92
+BRACKETRIGHT = 93
+ASCIICIRCUM = 94
+UNDERSCORE = 95
+GRAVE = 96
+QUOTELEFT = 96
+A = 97
+B = 98
+C = 99
+D = 100
+E = 101
+F = 102
+G = 103
+H = 104
+I = 105
+J = 106
+K = 107
+L = 108
+M = 109
+N = 110
+O = 111
+P = 112
+Q = 113
+R = 114
+S = 115
+T = 116
+U = 117
+V = 118
+W = 119
+X = 120
+Y = 121
+Z = 122
+BRACELEFT = 123
+BAR = 124
+BRACERIGHT = 125
+ASCIITILDE = 126
diff --git a/arcade/particle.py b/arcade/particle.py
new file mode 100644
index 0000000..6e0656d
--- /dev/null
+++ b/arcade/particle.py
@@ -0,0 +1,134 @@
+"""
+Particle - Object produced by an Emitter. Often used in large quantity to produce visual effects effects
+"""
+
+from arcade.sprite import Sprite
+from arcade.draw_commands import Texture
+import arcade.utils
+from arcade.arcade_types import Point, Vector
+from typing import Union
+
+FilenameOrTexture = Union[str, Texture]
+
+
+class Particle(Sprite):
+ """Sprite that is emitted from an Emitter"""
+
+ def __init__(
+ self,
+ filename_or_texture: FilenameOrTexture,
+ change_xy: Vector,
+ center_xy: Point = (0.0, 0.0),
+ angle: float = 0,
+ change_angle: float = 0,
+ scale: float = 1.0,
+ alpha: int = 255,
+ mutation_callback=None
+ ):
+ if isinstance(filename_or_texture, Texture):
+ super().__init__(None, scale=scale)
+ self.append_texture(filename_or_texture)
+ self.set_texture(0)
+ else:
+ super().__init__(filename_or_texture, scale=scale)
+
+ self.center_x = center_xy[0]
+ self.center_y = center_xy[1]
+ self.change_x = change_xy[0]
+ self.change_y = change_xy[1]
+ self.angle = angle
+ self.change_angle = change_angle
+ self.alpha = alpha
+ self.mutation_callback = mutation_callback
+
+ def update(self):
+ """Advance the Particle's simulation"""
+ super().update()
+ if self.mutation_callback:
+ self.mutation_callback(self)
+
+ # def draw(self):
+ # raise NotImplementedError("Particle.draw needs to be implemented")
+
+ def can_reap(self):
+ """Determine if Particle can be deleted"""
+ raise NotImplementedError("Particle.can_reap needs to be implemented")
+
+
+class EternalParticle(Particle):
+ """Particle that has no end to its life"""
+
+ def __init__(
+ self,
+ filename_or_texture: FilenameOrTexture,
+ change_xy: Vector,
+ center_xy: Point = (0.0, 0.0),
+ angle: float = 0,
+ change_angle: float = 0,
+ scale: float = 1.0,
+ alpha: int = 255,
+ mutation_callback=None
+ ):
+ super().__init__(filename_or_texture, change_xy, center_xy, angle, change_angle, scale, alpha,
+ mutation_callback)
+
+ def can_reap(self):
+ """Determine if Particle can be deleted"""
+ return False
+
+
+class LifetimeParticle(Particle):
+ """Particle that lives for a given amount of time and is then deleted"""
+
+ def __init__(
+ self,
+ filename_or_texture: FilenameOrTexture,
+ change_xy: Vector,
+ lifetime: float,
+ center_xy: Point = (0.0, 0.0),
+ angle: float = 0,
+ change_angle: float = 0,
+ scale: float = 1.0,
+ alpha: int = 255,
+ mutation_callback=None
+ ):
+ super().__init__(filename_or_texture, change_xy, center_xy, angle, change_angle, scale, alpha,
+ mutation_callback)
+ self.lifetime_original = lifetime
+ self.lifetime_elapsed = 0.0
+
+ def update(self):
+ """Advance the Particle's simulation"""
+ super().update()
+ self.lifetime_elapsed += 1 / 60
+
+ def can_reap(self):
+ """Determine if Particle can be deleted"""
+ return self.lifetime_elapsed >= self.lifetime_original
+
+
+class FadeParticle(LifetimeParticle):
+ """Particle that animates its alpha between two values during its lifetime"""
+
+ def __init__(
+ self,
+ filename_or_texture: FilenameOrTexture,
+ change_xy: Vector,
+ lifetime: float,
+ center_xy: Point = (0.0, 0.0),
+ angle: float = 0,
+ change_angle: float = 0,
+ scale: float = 1.0,
+ start_alpha: int = 255,
+ end_alpha: int = 0,
+ mutation_callback=None
+ ):
+ super().__init__(filename_or_texture, change_xy, lifetime, center_xy, angle, change_angle, scale, start_alpha,
+ mutation_callback)
+ self.start_alpha = start_alpha
+ self.end_alpha = end_alpha
+
+ def update(self):
+ """Advance the Particle's simulation"""
+ super().update()
+ self.alpha = arcade.utils.lerp(self.start_alpha, self.end_alpha, self.lifetime_elapsed / self.lifetime_original)
diff --git a/arcade/physics_engines.py b/arcade/physics_engines.py
new file mode 100644
index 0000000..6241036
--- /dev/null
+++ b/arcade/physics_engines.py
@@ -0,0 +1,307 @@
+"""
+Physics engines for top-down or platformers.
+"""
+# pylint: disable=too-many-arguments, too-many-locals, too-few-public-methods
+
+from arcade.geometry import check_for_collision_with_list
+from arcade.geometry import check_for_collision
+from arcade.sprite import Sprite
+from arcade.sprite_list import SpriteList
+
+
+class PhysicsEngineSimple:
+ """
+ This class will move everything, and take care of collisions.
+ """
+
+ def __init__(self, player_sprite: Sprite, walls: SpriteList):
+ """
+ Constructor.
+ """
+ assert(isinstance(player_sprite, Sprite))
+ assert(isinstance(walls, SpriteList))
+ self.player_sprite = player_sprite
+ self.walls = walls
+
+ def update(self):
+ """
+ Move everything and resolve collisions.
+ """
+ # --- Move in the x direction
+ self.player_sprite.center_x += self.player_sprite.change_x
+
+ # Check for wall hit
+ hit_list = \
+ check_for_collision_with_list(self.player_sprite,
+ self.walls)
+
+ # If we hit a wall, move so the edges are at the same point
+ if len(hit_list) > 0:
+ if self.player_sprite.change_x > 0:
+ for item in hit_list:
+ self.player_sprite.right = min(item.left,
+ self.player_sprite.right)
+ elif self.player_sprite.change_x < 0:
+ for item in hit_list:
+ self.player_sprite.left = max(item.right,
+ self.player_sprite.left)
+ else:
+ print("Error, collision while player wasn't moving.")
+
+ # --- Move in the y direction
+ self.player_sprite.center_y += self.player_sprite.change_y
+
+ # Check for wall hit
+ hit_list = \
+ check_for_collision_with_list(self.player_sprite,
+ self.walls)
+
+ # If we hit a wall, move so the edges are at the same point
+ if len(hit_list) > 0:
+ if self.player_sprite.change_y > 0:
+ for item in hit_list:
+ self.player_sprite.top = min(item.bottom,
+ self.player_sprite.top)
+ elif self.player_sprite.change_y < 0:
+ for item in hit_list:
+ self.player_sprite.bottom = max(item.top,
+ self.player_sprite.bottom)
+ else:
+ print("Error, collision while player wasn't moving.")
+
+
+class PhysicsEnginePlatformer:
+ """
+ This class will move everything, and take care of collisions.
+ """
+
+ def __init__(self,
+ player_sprite: Sprite,
+ platforms: SpriteList,
+ gravity_constant: float = 0.5,
+ ladders: SpriteList = None,
+ ):
+ """
+ Constructor.
+ """
+ if ladders is not None and not isinstance(ladders, SpriteList):
+ raise TypeError("Fourth parameter should be a SpriteList of ladders")
+
+ self.player_sprite = player_sprite
+ self.platforms = platforms
+ self.gravity_constant = gravity_constant
+ self.jumps_since_ground = 0
+ self.allowed_jumps = 1
+ self.allow_multi_jump = False
+ self.ladders = ladders
+
+ def is_on_ladder(self):
+ # Check for touching a ladder
+ if self.ladders:
+ hit_list = check_for_collision_with_list(self.player_sprite, self.ladders)
+ if len(hit_list) > 0:
+ return True
+ return False
+
+ def can_jump(self, y_distance=5) -> bool:
+ """
+ Method that looks to see if there is a floor under
+ the player_sprite. If there is a floor, the player can jump
+ and we return a True.
+
+ :returns: True if there is a platform below us
+ :rtype: bool
+ """
+
+ # Check for touching a ladder
+ if self.is_on_ladder():
+ return False
+
+ # Move down to see if we are on a platform
+ self.player_sprite.center_y -= y_distance
+
+ # Check for wall hit
+ hit_list = check_for_collision_with_list(self.player_sprite, self.platforms)
+
+ self.player_sprite.center_y += y_distance
+
+ if len(hit_list) > 0:
+ self.jumps_since_ground = 0
+
+ if len(hit_list) > 0 or self.allow_multi_jump and self.jumps_since_ground < self.allowed_jumps:
+ return True
+ else:
+ return False
+
+ def enable_multi_jump(self, allowed_jumps: int):
+ """
+ Enables multi-jump.
+ allowed_jumps should include the initial jump.
+ (1 allows only a single jump, 2 enables double-jump, etc)
+
+ If you enable multi-jump, you MUST call increment_jump_counter()
+ every time the player jumps. Otherwise they can jump infinitely.
+
+ :param int allowed_jumps:
+ """
+ self.allowed_jumps = allowed_jumps
+ self.allow_multi_jump = True
+
+ def disable_multi_jump(self):
+ """
+ Disables multi-jump.
+
+ Calling this function also removes the requirement to
+ call increment_jump_counter() every time the player jumps.
+ """
+ self.allow_multi_jump = False
+ self.allowed_jumps = 1
+ self.jumps_since_ground = 0
+
+ def jump(self, velocity: int):
+ self.player_sprite.change_y = velocity
+ self.increment_jump_counter()
+
+ def increment_jump_counter(self):
+ """
+ Updates the jump counter for multi-jump tracking
+ """
+ if self.allow_multi_jump:
+ self.jumps_since_ground += 1
+
+ def update(self):
+ """
+ Move everything and resolve collisions.
+ """
+ # print(f"Spot A ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
+
+ # --- Add gravity if we aren't on a ladder
+ if not self.is_on_ladder():
+ self.player_sprite.change_y -= self.gravity_constant
+
+ # --- Move in the y direction
+ self.player_sprite.center_y += self.player_sprite.change_y
+
+ # Check for wall hit
+ hit_list = check_for_collision_with_list(self.player_sprite, self.platforms)
+
+ # If we hit a wall, move so the edges are at the same point
+ if len(hit_list) > 0:
+ if self.player_sprite.change_y > 0:
+ for item in hit_list:
+ self.player_sprite.top = min(item.bottom,
+ self.player_sprite.top)
+ # print(f"Spot X ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
+ elif self.player_sprite.change_y < 0:
+ # Reset number of jumps
+ for item in hit_list:
+ while check_for_collision(self.player_sprite, item):
+ # self.player_sprite.bottom = item.top <- Doesn't work for ramps
+ self.player_sprite.bottom += 0.25
+
+ if item.change_x != 0:
+ self.player_sprite.center_x += item.change_x
+ # print(f"Spot Y ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
+ else:
+ pass
+ # TODO: The code below can't execute, as "item" doesn't
+ # exist. In theory, this condition should never be arrived at.
+ # Collision while player wasn't moving, most likely
+ # moving platform.
+ # if self.player_sprite.center_y >= item.center_y:
+ # self.player_sprite.bottom = item.top
+ # else:
+ # self.player_sprite.top = item.bottom
+ self.player_sprite.change_y = min(0.0, hit_list[0].change_y)
+
+ # print(f"Spot B ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
+ self.player_sprite.center_y = round(self.player_sprite.center_y, 2)
+ # print(f"Spot Q ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
+
+ # --- Move in the x direction
+ self.player_sprite.center_x += self.player_sprite.change_x
+
+ check_again = True
+ while check_again:
+ check_again = False
+ # Check for wall hit
+ hit_list = check_for_collision_with_list(self.player_sprite, self.platforms)
+
+ # If we hit a wall, move so the edges are at the same point
+ if len(hit_list) > 0:
+ change_x = self.player_sprite.change_x
+ if change_x > 0:
+ for item in hit_list:
+ # print(f"Spot 1 ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
+ # See if we can "run up" a ramp
+ self.player_sprite.center_y += change_x
+ if len(check_for_collision_with_list(self.player_sprite, self.platforms)) > 0:
+ self.player_sprite.center_y -= change_x
+ self.player_sprite.right = min(item.left, self.player_sprite.right)
+ # print(f"Spot R ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
+ check_again = True
+ break
+ # else:
+ # print("Run up ok 1")
+ # print(f"Spot 2 ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
+
+ elif change_x < 0:
+ for item in hit_list:
+ # See if we can "run up" a ramp
+ self.player_sprite.center_y -= change_x
+ if len(check_for_collision_with_list(self.player_sprite, self.platforms)) > 0:
+ # Can't run up the ramp, reverse
+ self.player_sprite.center_y += change_x
+ self.player_sprite.left = max(item.right, self.player_sprite.left)
+ # print(f"Reverse 1 {item.right}, {self.player_sprite.left}")
+ # Ok, if we were shoved back to the right, we need to check this whole thing again.
+ check_again = True
+ break
+ # print(f"Spot 4 ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
+
+ else:
+ print("Error, collision while player wasn't moving.\n"
+ "Make sure you aren't calling multiple updates, like "
+ "a physics engine update and an all sprites list update.")
+
+ # print(f"Spot E ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
+
+ for platform in self.platforms:
+ if platform.change_x != 0 or platform.change_y != 0:
+ platform.center_x += platform.change_x
+
+ if platform.boundary_left is not None \
+ and platform.left <= platform.boundary_left:
+ platform.left = platform.boundary_left
+ if platform.change_x < 0:
+ platform.change_x *= -1
+
+ if platform.boundary_right is not None \
+ and platform.right >= platform.boundary_right:
+ platform.right = platform.boundary_right
+ if platform.change_x > 0:
+ platform.change_x *= -1
+
+ if check_for_collision(self.player_sprite, platform):
+ if platform.change_x < 0:
+ self.player_sprite.right = platform.left
+ if platform.change_x > 0:
+ self.player_sprite.left = platform.right
+
+ platform.center_y += platform.change_y
+
+ if platform.boundary_top is not None \
+ and platform.top >= platform.boundary_top:
+ platform.top = platform.boundary_top
+ if platform.change_y > 0:
+ platform.change_y *= -1
+
+ if platform.boundary_bottom is not None \
+ and platform.bottom <= platform.boundary_bottom:
+ platform.bottom = platform.boundary_bottom
+ if platform.change_y < 0:
+ platform.change_y *= -1
+
+ # self.player_sprite.center_x = round(self.player_sprite.center_x, 2)
+ # print(f"Spot C ({self.player_sprite.center_x}, {self.player_sprite.center_y})")
+ # print()
diff --git a/arcade/read_tiled_map.py b/arcade/read_tiled_map.py
new file mode 100644
index 0000000..b16a688
--- /dev/null
+++ b/arcade/read_tiled_map.py
@@ -0,0 +1,351 @@
+"""
+Functions and classes for managing a map created in the "Tiled Map Editor"
+"""
+
+import xml.etree.ElementTree as ElementTree
+import base64
+import zlib
+import gzip
+
+from pathlib import Path
+
+from arcade.isometric import isometric_grid_to_screen
+from arcade import Sprite
+from arcade import SpriteList
+
+
+class TiledMap:
+ """ This class holds a tiled map, and tile set from the map. """
+ def __init__(self):
+ self.global_tile_set = {}
+ self.layers_int_data = {}
+ self.layers = {}
+ self.version = None
+ self.orientation = None
+ self.renderorder = None
+ self.width = None
+ self.height = None
+ self.tilewidth = None
+ self.tileheight = None
+ self.backgroundcolor = None
+ self.nextobjectid = None
+
+
+class Tile:
+ """ This class represents an individual tile from a tileset. """
+ def __init__(self):
+ self.local_id = 0
+ self.width = 0
+ self.height = 0
+ self.source = None
+ self.points = None
+
+
+class GridLocation:
+ """ This represents a location on the grid. Contains the x/y of the
+ grid location, and the tile that is on it. """
+ def __init__(self):
+ self.tile = None
+ self.center_x = 0
+ self.center_y = 0
+
+
+def _process_csv_encoding(data_text):
+ layer_grid_ints = []
+ lines = data_text.split("\n")
+ for line in lines:
+ line_list = line.split(",")
+ while '' in line_list:
+ line_list.remove('')
+ line_list_int = [int(item) for item in line_list]
+ layer_grid_ints.append(line_list_int)
+
+ return layer_grid_ints
+
+
+def _process_base64_encoding(data_text, compression, layer_width):
+ layer_grid_ints = [[]]
+
+ unencoded_data = base64.b64decode(data_text)
+ if compression == "zlib":
+ unzipped_data = zlib.decompress(unencoded_data)
+ elif compression == "gzip":
+ unzipped_data = gzip.decompress(unencoded_data)
+ elif compression is None:
+ unzipped_data = unencoded_data
+ else:
+ raise ValueError(f"Unsupported compression type '{compression}'.")
+
+ # Turn bytes into 4-byte integers
+ byte_count = 0
+ int_count = 0
+ int_value = 0
+ row_count = 0
+ for byte in unzipped_data:
+ int_value += byte << (byte_count * 8)
+ byte_count += 1
+ if byte_count % 4 == 0:
+ byte_count = 0
+ int_count += 1
+ layer_grid_ints[row_count].append(int_value)
+ int_value = 0
+ if int_count % layer_width == 0:
+ row_count += 1
+ layer_grid_ints.append([])
+
+ layer_grid_ints.pop()
+ return layer_grid_ints
+
+
+def _parse_points(point_text: str):
+ result = []
+ point_list = point_text.split(" ")
+ for point in point_list:
+ z = point.split(",")
+ result.append([round(float(z[0])), round(float(z[1]))])
+
+ return result
+
+
+def read_tiled_map(tmx_file: str, scaling: float = 1, tsx_file: str = None) -> TiledMap:
+ """
+ read_tiled_map has been deprecated. Use arcade.tilemap.read_tmx instead.
+
+ Given a tmx_file, this will read in a tiled map, and return
+ a TiledMap object.
+
+ Given a tsx_file, the map will use it as the tileset.
+ If tsx_file is not specified, it will use the tileset specified
+ within the tmx_file.
+
+ Important: Tiles must be a "collection" of images.
+
+ Hitboxes can be drawn around tiles in the tileset editor,
+ but only polygons are supported.
+ (This is a great area for PR's to improve things.)
+
+ :param str tmx_file: String with name of our TMX file
+ :param float scaling: Scaling factor. 0.5 will half all widths and heights
+ :param str tsx_file: Tileset to use (can be specified in TMX file)
+
+ :returns: Map
+ :rtype: TiledMap
+ """
+ from warnings import warn
+ warn('read_tiled_map has been deprecated. Use arcade.tilemap.read_tmx instead.', DeprecationWarning)
+
+ # Create a map object to store this stuff in
+ my_map = TiledMap()
+
+ # Read in and parse the file
+ tree = ElementTree.parse(tmx_file)
+
+ # Root node should be 'map'
+ map_tag = tree.getroot()
+
+ # Pull attributes that should be in the file for the map
+ my_map.version = map_tag.attrib["version"]
+ my_map.orientation = map_tag.attrib["orientation"]
+ my_map.renderorder = map_tag.attrib["renderorder"]
+ my_map.width = int(map_tag.attrib["width"])
+ my_map.height = int(map_tag.attrib["height"])
+ my_map.tilewidth = int(map_tag.attrib["tilewidth"])
+ my_map.tileheight = int(map_tag.attrib["tileheight"])
+
+ # Background color is optional, and may or may not be in there
+ if "backgroundcolor" in map_tag.attrib:
+ # Decode the background color string
+ background_color_string = map_tag.attrib["backgroundcolor"]
+ red_hex = "0x" + background_color_string[1:3]
+ green_hex = "0x" + background_color_string[3:5]
+ blue_hex = "0x" + background_color_string[5:7]
+ red = int(red_hex, 16)
+ green = int(green_hex, 16)
+ blue = int(blue_hex, 16)
+ my_map.backgroundcolor = (red, green, blue)
+
+ my_map.nextobjectid = map_tag.attrib["nextobjectid"]
+
+ # Grab all the tilesets
+ tileset_tag_list = map_tag.findall('./tileset')
+
+ # --- Tileset Data ---
+
+ # Loop through each tileset
+ for tileset_tag in tileset_tag_list:
+ firstgid = int(tileset_tag.attrib["firstgid"])
+ if tsx_file is not None or "source" in tileset_tag.attrib:
+ if tsx_file is not None:
+ tileset_tree = ElementTree.parse(tsx_file)
+ else:
+ source = tileset_tag.attrib["source"]
+ try:
+ tileset_tree = ElementTree.parse(source)
+ except FileNotFoundError:
+ source = Path(tmx_file).parent / Path(source)
+ tileset_tree = ElementTree.parse(source)
+ # Root node should be 'map'
+ tileset_root = tileset_tree.getroot()
+ tile_tag_list = tileset_root.findall("tile")
+ else:
+ # Grab each tile
+ tile_tag_list = tileset_tag.findall("tile")
+
+ # Loop through each tile
+ for tile_tag in tile_tag_list:
+ # Make a tile object
+ my_tile = Tile()
+ image = tile_tag.find("image")
+ my_tile.local_id = tile_tag.attrib["id"]
+ my_tile.width = int(image.attrib["width"])
+ my_tile.height = int(image.attrib["height"])
+ my_tile.source = image.attrib["source"]
+ key = str(int(my_tile.local_id) + 1)
+ my_map.global_tile_set[key] = my_tile
+ firstgid += 1
+
+ objectgroup = tile_tag.find("objectgroup")
+ if objectgroup:
+ my_object = objectgroup.find("object")
+ if my_object:
+ offset_x = round(float(my_object.attrib['x']))
+ offset_y = round(float(my_object.attrib['y']))
+
+ polygon = my_object.find("polygon")
+ if polygon is not None:
+ point_list = _parse_points(polygon.attrib['points'])
+ for point in point_list:
+ point[0] += offset_x
+ point[1] += offset_y
+ point[1] = my_tile.height - point[1]
+ point[0] -= my_tile.width // 2
+ point[1] -= my_tile.height // 2
+ point[0] *= scaling
+ point[1] *= scaling
+ point[0] = int(point[0])
+ point[1] = int(point[1])
+
+ my_tile.points = point_list
+
+ polygon = my_object.find("polyline")
+ if polygon is not None:
+ point_list = _parse_points(polygon.attrib['points'])
+ for point in point_list:
+ point[0] += offset_x
+ point[1] += offset_y
+ point[1] = my_tile.height - point[1]
+ point[0] -= my_tile.width // 2
+ point[1] -= my_tile.height // 2
+ point[0] *= scaling
+ point[1] *= scaling
+ point[0] = int(point[0])
+ point[1] = int(point[1])
+
+ if point_list[0][0] != point_list[-1][0] or point_list[0][1] != point_list[-1][1]:
+ point_list.append([point_list[0][0], point_list[0][1]])
+
+ my_tile.points = point_list
+
+ # --- Map Data ---
+
+ # Grab each layer
+ layer_tag_list = map_tag.findall('./layer')
+ for layer_tag in layer_tag_list:
+ layer_width = int(layer_tag.attrib['width'])
+
+ # Unzip and unencode each layer
+ data = layer_tag.find("data")
+ data_text = data.text.strip()
+ encoding = data.attrib['encoding']
+ if 'compression' in data.attrib:
+ compression = data.attrib['compression']
+ else:
+ compression = None
+
+ if encoding == "csv":
+ layer_grid_ints = _process_csv_encoding(data_text)
+ elif encoding == "base64":
+ layer_grid_ints = _process_base64_encoding(data_text, compression, layer_width)
+ else:
+ print(f"Error, unexpected encoding: {encoding}.")
+ break
+
+ # Great, we have a grid of ints. Save that according to the layer name
+ my_map.layers_int_data[layer_tag.attrib["name"]] = layer_grid_ints
+
+ # Now create grid objects for each tile
+ layer_grid_objs = []
+ for row_index, row in enumerate(layer_grid_ints):
+ layer_grid_objs.append([])
+ for column_index, column in enumerate(row):
+ grid_loc = GridLocation()
+ if layer_grid_ints[row_index][column_index] != 0:
+ key = str(layer_grid_ints[row_index][column_index])
+
+ if key not in my_map.global_tile_set:
+ print(f"Warning, tried to load '{key}' and it is not in the tileset.")
+ else:
+ grid_loc.tile = my_map.global_tile_set[key]
+
+ if my_map.renderorder == "right-down":
+ adjusted_row_index = my_map.height - row_index - 1
+ else:
+ adjusted_row_index = row_index
+
+ if my_map.orientation == "orthogonal":
+ grid_loc.center_x = column_index * my_map.tilewidth + my_map.tilewidth // 2
+ grid_loc.center_y = adjusted_row_index * my_map.tileheight + my_map.tilewidth // 2
+ else:
+ grid_loc.center_x, grid_loc.center_y = isometric_grid_to_screen(column_index,
+ row_index,
+ my_map.width,
+ my_map.height,
+ my_map.tilewidth,
+ my_map.tileheight)
+
+ layer_grid_objs[row_index].append(grid_loc)
+
+ my_map.layers[layer_tag.attrib["name"]] = layer_grid_objs
+
+ return my_map
+
+
+def generate_sprites(map_object: TiledMap, layer_name: str, scaling: float, base_directory="") -> SpriteList:
+ """
+ generate_sprites has been deprecated. Use arcade.tilemap.process_layer instead.
+ Generate the sprites for a layer in a map.
+
+ :param TiledMap map_object: Map previously read in from read_tiled_map function
+ :param layer_name: Name of the layer we want to generate sprites from. Case sensitive.
+ :param scaling: Scaling factor.
+ :param base_directory: Directory to read images from. Defaults to current directory.
+ :return: List of sprites
+ :rtype: SpriteList
+ """
+ from warnings import warn
+ sprite_list = SpriteList()
+
+ if layer_name not in map_object.layers_int_data:
+ print(f"Warning, no layer named '{layer_name}'.")
+ return sprite_list
+
+ map_array = map_object.layers_int_data[layer_name]
+
+ # Loop through the layer and add in the wall list
+ for row_index, row in enumerate(map_array):
+ for column_index, item in enumerate(row):
+ if str(item) in map_object.global_tile_set:
+ tile_info = map_object.global_tile_set[str(item)]
+ tmx_file = base_directory + tile_info.source
+
+ my_sprite = Sprite(tmx_file, scaling)
+ my_sprite.right = column_index * (map_object.tilewidth * scaling)
+ my_sprite.top = (map_object.height - row_index) * (map_object.tileheight * scaling)
+
+ if tile_info.points is not None:
+ my_sprite.set_points(tile_info.points)
+ sprite_list.append(my_sprite)
+ elif item != 0:
+ print(f"Warning, could not find {item} image to load.")
+
+ return sprite_list
diff --git a/arcade/shader.py b/arcade/shader.py
new file mode 100644
index 0000000..e9f9b55
--- /dev/null
+++ b/arcade/shader.py
@@ -0,0 +1,549 @@
+"""Utilities for dealing with Shaders in OpenGL 3.3+.
+"""
+
+from ctypes import *
+from collections import namedtuple
+import weakref
+from typing import Tuple, Iterable
+
+from pyglet.gl import *
+from pyglet import gl
+
+import numpy as np
+
+
+class ShaderException(Exception):
+ pass
+
+
+# Thank you Benjamin Moran for writing part of this code!
+# https://bitbucket.org/HigashiNoKaze/pyglet/src/shaders/pyglet/graphics/shader.py
+
+_uniform_getters = {
+ GLint: glGetUniformiv,
+ GLfloat: glGetUniformfv,
+}
+
+_uniform_setters = {
+ # uniform type: (gl_type, setter, length, count)
+ GL_INT: (GLint, glUniform1iv, 1, 1),
+ GL_INT_VEC2: (GLint, glUniform2iv, 2, 1),
+ GL_INT_VEC3: (GLint, glUniform3iv, 3, 1),
+ GL_INT_VEC4: (GLint, glUniform4iv, 4, 1),
+
+ GL_FLOAT: (GLfloat, glUniform1fv, 1, 1),
+ GL_FLOAT_VEC2: (GLfloat, glUniform2fv, 2, 1),
+ GL_FLOAT_VEC3: (GLfloat, glUniform3fv, 3, 1),
+ GL_FLOAT_VEC4: (GLfloat, glUniform4fv, 4, 1),
+
+ GL_SAMPLER_2D: (GLint, glUniform1iv, 1, 1),
+
+ GL_FLOAT_MAT2: (GLfloat, glUniformMatrix2fv, 4, 1),
+ GL_FLOAT_MAT3: (GLfloat, glUniformMatrix3fv, 6, 1),
+ GL_FLOAT_MAT4: (GLfloat, glUniformMatrix4fv, 16, 1),
+
+ # TODO: test/implement these:
+ # GL_FLOAT_MAT2x3: glUniformMatrix2x3fv,
+ # GL_FLOAT_MAT2x4: glUniformMatrix2x4fv,
+ #
+ # GL_FLOAT_MAT3x2: glUniformMatrix3x2fv,
+ # GL_FLOAT_MAT3x4: glUniformMatrix3x4fv,
+ #
+ # GL_FLOAT_MAT4x2: glUniformMatrix4x2fv,
+ # GL_FLOAT_MAT4x3: glUniformMatrix4x3fv,
+}
+
+
+def _create_getter_func(program_id, location, gl_getter, c_array, length):
+
+ if length == 1:
+ def getter_func():
+ gl_getter(program_id, location, c_array)
+ return c_array[0]
+ else:
+ def getter_func():
+ gl_getter(program_id, location, c_array)
+ return c_array[:]
+
+ return getter_func
+
+
+def _create_setter_func(location, gl_setter, c_array, length, count, ptr, is_matrix):
+
+ if is_matrix:
+ def setter_func(value):
+ c_array[:] = value
+ gl_setter(location, count, GL_FALSE, ptr)
+
+ elif length == 1 and count == 1:
+ def setter_func(value):
+ c_array[0] = value
+ gl_setter(location, count, ptr)
+ elif length > 1 and count == 1:
+ def setter_func(values):
+ c_array[:] = values
+ gl_setter(location, count, ptr)
+
+ else:
+ raise NotImplementedError("Uniform type not yet supported.")
+
+ return setter_func
+
+
+Uniform = namedtuple('Uniform', 'getter, setter')
+ShaderCode = str
+ShaderType = GLuint
+Shader = type(Tuple[ShaderCode, ShaderType])
+
+
+class Program:
+ """Compiled and linked shader program.
+
+ Access Uniforms via the [] operator.
+ Example:
+ program['MyUniform'] = value
+ For Matrices, pass the flatten array.
+ Example:
+ matrix = np.array([[...]])
+ program['MyMatrix'] = matrix.flatten()
+ """
+ def __init__(self, *shaders: Shader):
+ self.prog_id = prog_id = glCreateProgram()
+ shaders_id = []
+ for shader_code, shader_type in shaders:
+ shader = compile_shader(shader_code, shader_type)
+ glAttachShader(self.prog_id, shader)
+ shaders_id.append(shader)
+
+ glLinkProgram(self.prog_id)
+
+ for shader in shaders_id:
+ # Flag shaders for deletion. Will only be deleted once detached from program.
+ glDeleteShader(shader)
+
+ self._uniforms = {}
+ self._introspect_uniforms()
+ weakref.finalize(self, Program._delete, shaders_id, prog_id)
+
+ @staticmethod
+ def _delete(shaders_id, prog_id):
+ # Check to see if the context was already cleaned up from program
+ # shut down. If so, we don't need to delete the shaders.
+ if gl.current_context is None:
+ return
+
+ for shader_id in shaders_id:
+ glDetachShader(prog_id, shader_id)
+
+ glDeleteProgram(prog_id)
+
+ def release(self):
+ if self.prog_id != 0:
+ glDeleteProgram(self.prog_id)
+ self.prog_id = 0
+
+ def __getitem__(self, item):
+ try:
+ uniform = self._uniforms[item]
+ except KeyError:
+ raise ShaderException(f"Uniform with the name `{item}` was not found.")
+
+ return uniform.getter()
+
+ def __setitem__(self, key, value):
+ try:
+ uniform = self._uniforms[key]
+ except KeyError:
+ raise ShaderException(f"Uniform with the name `{key}` was not found.")
+
+ uniform.setter(value)
+
+ def __enter__(self):
+ glUseProgram(self.prog_id)
+
+ def __exit__(self, exception_type, exception_value, traceback):
+ glUseProgram(0)
+
+ def get_num_active(self, variable_type: GLenum) -> int:
+ """Get the number of active variables of the passed GL type.
+
+ variable_type can be GL_ACTIVE_ATTRIBUTES, GL_ACTIVE_UNIFORMS, etc.
+ """
+ num_active = GLint(0)
+ glGetProgramiv(self.prog_id, variable_type, byref(num_active))
+ return num_active.value
+
+ def _introspect_uniforms(self):
+ for index in range(self.get_num_active(GL_ACTIVE_UNIFORMS)):
+ uniform_name, u_type, u_size = self.query_uniform(index)
+ loc = glGetUniformLocation(self.prog_id, uniform_name.encode('utf-8'))
+
+ if loc == -1: # Skip uniforms that may be in Uniform Blocks
+ continue
+
+ try:
+ gl_type, gl_setter, length, count = _uniform_setters[u_type]
+ except KeyError:
+ raise ShaderException(f"Unsupported Uniform type {u_type}")
+
+ gl_getter = _uniform_getters[gl_type]
+
+ is_matrix = u_type in (GL_FLOAT_MAT2, GL_FLOAT_MAT3, GL_FLOAT_MAT4)
+
+ # Create persistant mini c_array for getters and setters:
+ c_array = (gl_type * length)()
+ ptr = cast(c_array, POINTER(gl_type))
+
+ # Create custom dedicated getters and setters for each uniform:
+ getter = _create_getter_func(self.prog_id, loc, gl_getter, c_array, length)
+ setter = _create_setter_func(loc, gl_setter, c_array, length, count, ptr, is_matrix)
+
+ # print(f"Found uniform: {uniform_name}, type: {u_type}, size: {u_size}, "
+ # f"location: {loc}, length: {length}, count: {count}")
+
+ self._uniforms[uniform_name] = Uniform(getter, setter)
+
+ def query_uniform(self, index: int) -> Tuple[str, int, int]:
+ """Retrieve Uniform information at given location.
+
+ Returns the name, the type as a GLenum (GL_FLOAT, ...) and the size. Size is
+ greater than 1 only for Uniform arrays, like an array of floats or an array
+ of Matrices.
+ """
+ usize = GLint()
+ utype = GLenum()
+ buf_size = 192
+ uname = create_string_buffer(buf_size)
+ glGetActiveUniform(self.prog_id, index, buf_size, None, usize, utype, uname)
+ return uname.value.decode(), utype.value, usize.value
+
+
+def program(vertex_shader: str, fragment_shader: str) -> Program:
+ """Create a new program given the vertex_shader and fragment shader code.
+ """
+ return Program(
+ (vertex_shader, GL_VERTEX_SHADER),
+ (fragment_shader, GL_FRAGMENT_SHADER)
+ )
+
+
+def compile_shader(source: str, shader_type: GLenum) -> GLuint:
+ """Compile the shader code of the given type.
+
+ `shader_type` could be GL_VERTEX_SHADER, GL_FRAGMENT_SHADER, ...
+
+ Returns the shader id as a GLuint
+ """
+ shader = glCreateShader(shader_type)
+ source = source.encode('utf-8')
+ # Turn the source code string into an array of c_char_p arrays.
+ strings = byref(
+ cast(
+ c_char_p(source),
+ POINTER(c_char)
+ )
+ )
+ # Make an array with the strings lengths
+ lengths = pointer(c_int(len(source)))
+ glShaderSource(shader, 1, strings, lengths)
+ glCompileShader(shader)
+ result = c_int()
+ glGetShaderiv(shader, GL_COMPILE_STATUS, byref(result))
+ if result.value == GL_FALSE:
+ msg = create_string_buffer(512)
+ length = c_int()
+ glGetShaderInfoLog(shader, 512, byref(length), msg)
+ raise ShaderException(
+ f"Shader compile failure ({result.value}): {msg.value.decode('utf-8')}")
+ return shader
+
+
+class Buffer:
+ """OpenGL Buffer object of type GL_ARRAY_BUFFER.
+
+ Apparently it's possible to initialize a GL_ELEMENT_ARRAY_BUFFER with
+ GL_ARRAY_BUFFER, provided we later on bind to it with the right type.
+
+ The buffer knows its id `buffer_id` and its `size` in bytes.
+ """
+ usages = {
+ 'static': GL_STATIC_DRAW,
+ 'dynamic': GL_DYNAMIC_DRAW,
+ 'stream': GL_STREAM_DRAW
+ }
+
+ def __init__(self, data: bytes, usage: str = 'static'):
+ self.buffer_id = buffer_id = GLuint()
+ self.size = len(data)
+
+ glGenBuffers(1, byref(self.buffer_id))
+ if self.buffer_id.value == 0:
+ raise ShaderException("Cannot create Buffer object.")
+
+ glBindBuffer(GL_ARRAY_BUFFER, self.buffer_id)
+ self.usage = Buffer.usages[usage]
+ glBufferData(GL_ARRAY_BUFFER, self.size, data, self.usage)
+ weakref.finalize(self, Buffer.release, buffer_id)
+
+ @classmethod
+ def create_with_size(cls, size: int, usage: str = 'static'):
+ """Create an empty Buffer storage of the given size."""
+ buffer = Buffer(b"", usage=usage)
+ glBindBuffer(GL_ARRAY_BUFFER, buffer.buffer_id)
+ glBufferData(GL_ARRAY_BUFFER, size, None, Buffer.usages[usage])
+ buffer.size = size
+ return buffer
+
+ @staticmethod
+ def release(buffer_id):
+
+ # If we have no context, then we are shutting down, so skip this
+ if gl.current_context is None:
+ return
+
+ if buffer_id.value != 0:
+ glDeleteBuffers(1, byref(buffer_id))
+ buffer_id.value = 0
+
+ def write(self, data: bytes, offset: int = 0):
+ glBindBuffer(GL_ARRAY_BUFFER, self.buffer_id)
+ glBufferSubData(GL_ARRAY_BUFFER, GLintptr(offset), len(data), data)
+ # print(f"Writing data:\n{data[:60]}")
+ # ptr = glMapBufferRange(GL_ARRAY_BUFFER, GLintptr(0), 20, GL_MAP_READ_BIT)
+ # print(f"Reading back from buffer:\n{string_at(ptr, size=60)}")
+ # glUnmapBuffer(GL_ARRAY_BUFFER)
+
+ def orphan(self):
+ glBindBuffer(GL_ARRAY_BUFFER, self.buffer_id)
+ glBufferData(GL_ARRAY_BUFFER, self.size, None, self.usage)
+
+ def _read(self, size):
+ """ Debug method to read data from the buffer. """
+
+ glBindBuffer(GL_ARRAY_BUFFER, self.buffer_id)
+ ptr = glMapBufferRange(GL_ARRAY_BUFFER, GLintptr(0), size, GL_MAP_READ_BIT)
+ print(f"Reading back from buffer:\n{string_at(ptr, size=size)}")
+ glUnmapBuffer(GL_ARRAY_BUFFER)
+
+
+def buffer(data: bytes, usage: str = 'static') -> Buffer:
+ """Create a new OpenGL Buffer object.
+ """
+ return Buffer(data, usage)
+
+
+class BufferDescription:
+ """Vertex Buffer Object description, allowing easy use with VAOs.
+
+ This class provides a Buffer object with a description of its content, allowing
+ a VertexArray object to correctly enable its shader attributes with the
+ vertex Buffer object.
+
+ The formats is a string providing the number and type of each attribute. Currently
+ we only support f (float), i (integer) and B (unsigned byte).
+
+ `normalized` enumerates the attributes which must have their values normalized.
+ This is useful for instance for colors attributes given as unsigned byte and
+ normalized to floats with values between 0.0 and 1.0.
+
+ `instanced` allows this buffer to be used as instanced buffer. Each value will
+ be used once for the whole geometry. The geometry will be repeated a number of
+ times equal to the number of items in the Buffer.
+ """
+ GL_TYPES_ENUM = {
+ 'B': GL_UNSIGNED_BYTE,
+ 'f': GL_FLOAT,
+ 'i': GL_INT,
+ }
+ GL_TYPES = {
+ 'B': GLubyte,
+ 'f': GLfloat,
+ 'i': GLint,
+ }
+
+ def __init__(self,
+ buffer: Buffer,
+ formats: str,
+ attributes: Iterable[str],
+ normalized: Iterable[str] = None,
+ instanced: bool = False):
+ self.buffer = buffer
+ self.attributes = list(attributes)
+ self.normalized = set() if normalized is None else set(normalized)
+ self.instanced = instanced
+
+ if self.normalized > set(self.attributes):
+ raise ShaderException("Normalized attribute not found in attributes.")
+
+ formats = formats.split(" ")
+
+ if len(formats) != len(self.attributes):
+ raise ShaderException(
+ f"Different lengths of formats ({len(formats)}) and "
+ f"attributes ({len(self.attributes)})"
+ )
+
+ self.formats = []
+ for i, fmt in enumerate(formats):
+ size, type_ = fmt
+ if size not in '1234' or type_ not in 'fiB':
+ raise ShaderException("Wrong format {fmt}.")
+ size = int(size)
+ gl_type_enum = BufferDescription.GL_TYPES_ENUM[type_]
+ gl_type = BufferDescription.GL_TYPES[type_]
+ attribsize = size * sizeof(gl_type)
+ self.formats.append((size, attribsize, gl_type_enum))
+
+
+class VertexArray:
+ """Vertex Array Object (VAO) is holding all the different OpenGL objects
+ together.
+
+ A VAO is the glue between a Shader program and buffers data.
+
+ Buffer information is provided through a list of tuples `content`
+ content = [
+ (buffer, 'format str', 'attrib1', 'attrib2', ...),
+ ]
+ The first item is a Buffer object. Then comes a format string providing information
+ about the count and type of data in the buffer. Type can be `f` for floats or `i`
+ for integers. Count can be 1, 2, 3 or 4.
+ Finally comes the strings representing the attributes found in the shader.
+
+ Example:
+ Providing a buffer with data of interleaved positions (x, y) and colors
+ (r, g, b, a):
+ content = [(buffer, '2f 4f', 'in_pos', 'in_color')]
+
+ You can use the VAO as a context manager. This is required for setting Uniform
+ variables or for rendering.
+
+ vao = VertexArrax(...)
+ with vao:
+ vao['MyUniform'] = value
+ vao.render
+ """
+
+ def __init__(self,
+ program: Program,
+ content: Iterable[BufferDescription],
+ index_buffer: Buffer = None):
+ self.program = program.prog_id
+ self.vao = vao = GLuint()
+ self.num_vertices = -1
+ self.ibo = index_buffer
+
+ glGenVertexArrays(1, byref(self.vao))
+ glBindVertexArray(self.vao)
+
+ for buffer_desc in content:
+ self._enable_attrib(buffer_desc)
+
+ if self.ibo is not None:
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.ibo.buffer_id)
+ weakref.finalize(self, VertexArray.release, vao)
+
+ @staticmethod
+ def release(vao):
+ # If we have no context, then we are shutting down, so skip this
+ if gl.current_context is None:
+ return
+
+ if vao.value != 0:
+ glDeleteVertexArrays(1, byref(vao))
+ vao.value = 0
+
+ def __enter__(self):
+ glBindVertexArray(self.vao)
+ glUseProgram(self.program)
+
+ def __exit__(self, exception_type, exception_value, traceback):
+ glUseProgram(0)
+
+ def _enable_attrib(self, buf_desc: BufferDescription):
+ buffer = buf_desc.buffer
+ stride = sum(attribsize for _, attribsize, _ in buf_desc.formats)
+
+ if buf_desc.instanced:
+ if self.num_vertices == -1:
+ raise ShaderException(
+ "The first vertex attribute cannot be a per instance attribute."
+ )
+ else:
+ self.num_vertices = max(self.num_vertices, buffer.size // stride)
+ # print(f"Number of vertices: {self.num_vertices}")
+
+ glBindBuffer(GL_ARRAY_BUFFER, buffer.buffer_id)
+ offset = 0
+ for (size, attribsize, gl_type_enum), attrib in zip(buf_desc.formats, buf_desc.attributes):
+ loc = glGetAttribLocation(self.program, attrib.encode('utf-8'))
+ if loc == -1:
+ raise ShaderException(f"Attribute {attrib} not found in shader program")
+ normalized = GL_TRUE if attrib in buf_desc.normalized else GL_FALSE
+ glVertexAttribPointer(
+ loc, size, gl_type_enum,
+ normalized, stride, c_void_p(offset)
+ )
+ # print(f"{attrib} of size {size} with stride {stride} and offset {offset}")
+ if buf_desc.instanced:
+ glVertexAttribDivisor(loc, 1)
+ offset += attribsize
+ glEnableVertexAttribArray(loc)
+
+ def render(self, mode: GLuint, instances: int = 1):
+ if self.ibo is not None:
+ count = self.ibo.size // 4
+ glDrawElementsInstanced(mode, count, GL_UNSIGNED_INT, None, instances)
+ else:
+ glDrawArraysInstanced(mode, 0, self.num_vertices, instances)
+
+
+def vertex_array(program: GLuint, content, index_buffer=None):
+ """Create a new Vertex Array.
+ """
+ return VertexArray(program, content, index_buffer)
+
+
+class Texture:
+ def __init__(self, size: Tuple[int, int], component: int, data: np.array):
+ self.width, self.height = size
+ sized_format = (GL_R8, GL_RG8, GL_RGB8, GL_RGBA8)[component - 1]
+ self.format = (GL_R, GL_RG, GL_RGB, GL_RGBA)[component - 1]
+ glActiveTexture(GL_TEXTURE0 + 0) # If we need other texture unit...
+ self.texture_id = texture_id = GLuint()
+ glGenTextures(1, byref(self.texture_id))
+
+ if self.texture_id.value == 0:
+ raise ShaderException("Cannot create Texture.")
+
+ glBindTexture(GL_TEXTURE_2D, self.texture_id)
+ glPixelStorei(GL_PACK_ALIGNMENT, 1)
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
+ try:
+ glTexImage2D(
+ GL_TEXTURE_2D, 0, sized_format, self.width, self.height, 0,
+ self.format, GL_UNSIGNED_BYTE, data.ctypes.data_as(c_void_p)
+ )
+ except GLException:
+ raise GLException(f"Unable to create texture. {GL_MAX_TEXTURE_SIZE} {size}")
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
+ weakref.finalize(self, Texture.release, texture_id)
+
+ @staticmethod
+ def release(texture_id):
+ # If we have no context, then we are shutting down, so skip this
+ if gl.current_context is None:
+ return
+
+ if texture_id.value != 0:
+ glDeleteTextures(1, byref(texture_id))
+
+ def use(self, texture_unit: int = 0):
+ glActiveTexture(GL_TEXTURE0 + texture_unit)
+ glBindTexture(GL_TEXTURE_2D, self.texture_id)
+
+
+def texture(size: Tuple[int, int], component: int, data: np.array) -> Texture:
+ return Texture(size, component, data)
diff --git a/arcade/sound.py b/arcade/sound.py
new file mode 100644
index 0000000..9227d39
--- /dev/null
+++ b/arcade/sound.py
@@ -0,0 +1,97 @@
+"""
+Sound library.
+"""
+
+from platform import system
+import typing
+import pyglet
+from pathlib import Path
+
+
+class Sound:
+
+ def __init__(self, file_name: str):
+ if not Path(file_name).is_file():
+ raise FileNotFoundError(f"The sound file '{file_name}' is not a file or can't be read")
+ self.file_name = file_name
+ self.player = pyglet.media.load(file_name)
+
+ def play(self):
+ if self.player.is_queued:
+ player = pyglet.media.load(self.file_name)
+ player.play()
+ else:
+ self.player.play()
+
+
+class PlaysoundException(Exception):
+ pass
+
+
+def _load_sound_library():
+ """
+ Special code for Windows so we grab the proper avbin from our directory.
+ Otherwise hope the correct package is installed.
+ """
+
+ # lazy loading
+ if not _load_sound_library._sound_library_loaded:
+ _load_sound_library._sound_library_loaded = True
+ else:
+ return
+
+ import pyglet_ffmpeg2
+ pyglet_ffmpeg2.load_ffmpeg()
+
+
+# Initialize static function variable
+_load_sound_library._sound_library_loaded = False
+
+
+def load_sound(file_name: str):
+ """
+ Load a sound. Support for .wav files. If ffmpeg is available, will work
+ with ogg and mp3 as well.
+
+ :param str file_name: Name of the sound file to load.
+
+ :returns: Sound object
+ :rtype: Sound
+ """
+
+ try:
+ sound = Sound(file_name)
+ return sound
+ except Exception as e:
+ print("Unable to load {str}.", e)
+ return None
+
+
+def play_sound(sound: Sound):
+ """
+ Play a sound.
+
+ :param Sound sound: Sound loaded by load_sound. Do NOT use a string here for the filename.
+ """
+ if sound is None:
+ print("Unable to play sound, no data passed in.")
+ elif isinstance(sound, str):
+ msg = "Error, passed in a string as a sound. " +\
+ "Make sure to use load_sound first, and use that result in play_sound."
+ raise Exception(msg)
+ try:
+ sound.play()
+ except Exception as e:
+ print("Error playing sound.", e)
+
+
+def stop_sound(sound: pyglet.media.Source):
+ """
+ Stop a sound that is currently playing.
+
+ :param sound:
+ """
+ sound.pause()
+
+
+_load_sound_library()
diff --git a/arcade/sprite.py b/arcade/sprite.py
new file mode 100644
index 0000000..6c45b92
--- /dev/null
+++ b/arcade/sprite.py
@@ -0,0 +1,900 @@
+"""
+This module manages all of the code around Sprites.
+
+For information on Spatial Hash Maps, see:
+https://www.gamedev.net/articles/programming/general-and-gameplay-programming/spatial-hashing-r2697/
+"""
+
+import math
+import dataclasses
+
+import PIL.Image
+
+from arcade.draw_commands import load_texture
+from arcade.draw_commands import draw_texture_rectangle
+from arcade.draw_commands import Texture
+from arcade.draw_commands import rotate_point
+from arcade.arcade_types import RGB, Point
+
+from typing import Sequence
+from typing import Tuple
+
+FACE_RIGHT = 1
+FACE_LEFT = 2
+FACE_UP = 3
+FACE_DOWN = 4
+
+
+class Sprite:
+ """
+ Class that represents a 'sprite' on-screen.
+
+ Attributes:
+ :alpha: Transparency of sprite. 0 is invisible, 255 is opaque.
+ :angle: Rotation angle in degrees.
+ :radians: Rotation angle in radians.
+ :bottom: Set/query the sprite location by using the bottom coordinate. \
+ This will be the 'y' of the bottom of the sprite.
+ :boundary_left: Used in movement. Left boundary of moving sprite.
+ :boundary_right: Used in movement. Right boundary of moving sprite.
+ :boundary_top: Used in movement. Top boundary of moving sprite.
+ :boundary_bottom: Used in movement. Bottom boundary of moving sprite.
+ :center_x: X location of the center of the sprite
+ :center_y: Y location of the center of the sprite
+ :change_x: Movement vector, in the x direction.
+ :change_y: Movement vector, in the y direction.
+ :change_angle: Change in rotation.
+ :color: Color tint the sprite
+ :collision_radius: Used as a fast-check to see if this item is close \
+ enough to another item. If this check works, we do a slower more accurate check.
+ :cur_texture_index: Index of current texture being used.
+ :guid: Unique identifier for the sprite. Useful when debugging.
+ :height: Height of the sprite.
+ :force: Force being applied to the sprite. Useful when used with Pymunk \
+ for physics.
+ :left: Set/query the sprite location by using the left coordinate. This \
+ will be the 'x' of the left of the sprite.
+ :points: Points, in relation to the center of the sprite, that are used \
+ for collision detection. Arcade defaults to creating points for a rectangle \
+ that encompass the image. If you are creating a ramp or making better \
+ hit-boxes, you can custom-set these.
+ :position: A list with the (x, y) of where the sprite is.
+ :repeat_count_x: Unused
+ :repeat_count_y: Unused
+ :right: Set/query the sprite location by using the right coordinate. \
+ This will be the 'y=x' of the right of the sprite.
+ :sprite_lists: List of all the sprite lists this sprite is part of.
+ :texture: `Texture` class with the current texture.
+ :textures: List of textures associated with this sprite.
+ :top: Set/query the sprite location by using the top coordinate. This \
+ will be the 'y' of the top of the sprite.
+ :scale: Scale the image up or down. Scale of 1.0 is original size, 0.5 \
+ is 1/2 height and width.
+ :velocity: Change in x, y expressed as a list. (0, 0) would be not moving.
+ :width: Width of the sprite
+
+ It is common to over-ride the `update` method and provide mechanics on
+ movement or other sprite updates.
+
+ """
+
+ def __init__(self,
+ filename: str = None,
+ scale: float = 1,
+ image_x: float = 0, image_y: float = 0,
+ image_width: float = 0, image_height: float = 0,
+ center_x: float = 0, center_y: float = 0,
+ repeat_count_x: int = 1, repeat_count_y: int = 1):
+ """
+ Create a new sprite.
+
+ Args:
+ filename (str): Filename of an image that represents the sprite.
+ scale (float): Scale the image up or down. Scale of 1.0 is none.
+ image_x (float): Scale the image up or down. Scale of 1.0 is none.
+ image_y (float): Scale the image up or down. Scale of 1.0 is none.
+ image_width (float): Width of the sprite
+ image_height (float): Height of the sprite
+ center_x (float): Location of the sprite
+ center_y (float): Location of the sprite
+
+ """
+
+ if image_width < 0:
+ raise ValueError("Width of image can't be less than zero.")
+
+ if image_height < 0:
+ raise ValueError("Height entered is less than zero. Height must be a positive float.")
+
+ if image_width == 0 and image_height != 0:
+ raise ValueError("Width can't be zero.")
+
+ if image_height == 0 and image_width != 0:
+ raise ValueError("Height can't be zero.")
+
+ self.sprite_lists = []
+
+ if filename is not None:
+ self._texture = load_texture(filename, image_x, image_y,
+ image_width, image_height)
+
+ self.textures = [self._texture]
+ self._width = self._texture.width * scale
+ self._height = self._texture.height * scale
+ self._texture.scale = scale
+ else:
+ self.textures = []
+ self._texture = None
+ self._width = 0
+ self._height = 0
+
+ self.cur_texture_index = 0
+
+ self._scale = scale
+ self._position = [center_x, center_y]
+ self._angle = 0.0
+
+ self.velocity = [0, 0]
+ self.change_angle = 0
+
+ self.boundary_left = None
+ self.boundary_right = None
+ self.boundary_top = None
+ self.boundary_bottom = None
+
+ self.properties = {}
+
+ self._alpha = 255
+ self._collision_radius = None
+ self._color = (255, 255, 255)
+
+ self._points = None
+ self._point_list_cache = None
+
+ self.force = [0, 0]
+ self.guid = None
+
+ self.repeat_count_x = repeat_count_x
+ self.repeat_count_y = repeat_count_y
+
+ def append_texture(self, texture: Texture):
+ """
+ Appends a new texture to the list of textures that can be
+ applied to this sprite.
+
+ :param Texture texture: Texture to add ot the list of available textures
+
+ """
+ self.textures.append(texture)
+
+ def _get_position(self) -> (float, float):
+ """
+ Get the center x coordinate of the sprite.
+
+ Returns:
+ (width, height)
+ """
+ return self._position
+
+ def _set_position(self, new_value: (float, float)):
+ """
+ Set the center x coordinate of the sprite.
+
+ Args:
+ new_value:
+
+ Returns:
+
+ """
+ if new_value[0] != self._position[0] or new_value[1] != self._position[1]:
+ self.clear_spatial_hashes()
+ self._point_list_cache = None
+ self._position[0], self._position[1] = new_value
+ self.add_spatial_hashes()
+
+ for sprite_list in self.sprite_lists:
+ sprite_list.update_location(self)
+
+ position = property(_get_position, _set_position)
+
+ def set_position(self, center_x: float, center_y: float):
+ """
+ Set a sprite's position
+
+ :param float center_x: New x position of sprite
+ :param float center_y: New y position of sprite
+ """
+ self._set_position((center_x, center_y))
+
+ def set_points(self, points: Sequence[Sequence[float]]):
+ """
+ Set a sprite's position
+ """
+ self._points = points
+
+ def forward(self, speed: float = 1.0):
+ """
+ Set a Sprite's position to speed by its angle
+ :param speed: speed factor
+ """
+ self.change_x += math.cos(self.radians) * speed
+ self.change_y += math.sin(self.radians) * speed
+
+ def reverse(self, speed: float = 1.0):
+ self.forward(-speed)
+
+ def strafe(self, speed: float = 1.0):
+ """
+ Set a sprites position perpendicular to its angle by speed
+ :param speed: speed factor
+ """
+ self.change_x += -math.sin(self.radians) * speed
+ self.change_y += math.cos(self.radians) * speed
+
+ def turn_right(self, theta: float = 90):
+ self.angle -= theta
+
+ def turn_left(self, theta: float = 90):
+ self.angle += theta
+
+ def stop(self):
+ """
+ Stop the Sprite's motion
+ """
+ self.change_x = 0
+ self.change_y = 0
+ self.change_angle = 0
+
+ def get_points(self) -> Tuple[Tuple[float, float]]:
+ """
+ Get the corner points for the rect that makes up the sprite.
+ """
+ if self._point_list_cache is not None:
+ return self._point_list_cache
+
+ if self._points is not None:
+ point_list = []
+ for point in range(len(self._points)):
+ point = (self._points[point][0] + self.center_x,
+ self._points[point][1] + self.center_y)
+ point_list.append(point)
+ self._point_list_cache = tuple(point_list)
+ else:
+ x1, y1 = rotate_point(self.center_x - self.width / 2,
+ self.center_y - self.height / 2,
+ self.center_x,
+ self.center_y,
+ self.angle)
+ x2, y2 = rotate_point(self.center_x + self.width / 2,
+ self.center_y - self.height / 2,
+ self.center_x,
+ self.center_y,
+ self.angle)
+ x3, y3 = rotate_point(self.center_x + self.width / 2,
+ self.center_y + self.height / 2,
+ self.center_x,
+ self.center_y,
+ self.angle)
+ x4, y4 = rotate_point(self.center_x - self.width / 2,
+ self.center_y + self.height / 2,
+ self.center_x,
+ self.center_y,
+ self.angle)
+
+ self._point_list_cache = ((x1, y1), (x2, y2), (x3, y3), (x4, y4))
+
+ return self._point_list_cache
+
+ points = property(get_points, set_points)
+
+ def _set_collision_radius(self, collision_radius: float):
+ """
+ Set the collision radius.
+
+ .. note:: Final collision checking is done via geometry that was
+ set in get_points/set_points. These points are used in the
+ check_for_collision function. This collision_radius variable
+ is used as a "pre-check." We do a super-fast check with
+ collision_radius and see if the sprites are close. If they are,
+ then we look at the geometry and figure if they really are colliding.
+
+ :param float collision_radius: Collision radius
+ """
+ self._collision_radius = collision_radius
+
+ def _get_collision_radius(self):
+ """
+ Get the collision radius.
+
+ .. note:: Final collision checking is done via geometry that was
+ set in get_points/set_points. These points are used in the
+ check_for_collision function. This collision_radius variable
+ is used as a "pre-check." We do a super-fast check with
+ collision_radius and see if the sprites are close. If they are,
+ then we look at the geometry and figure if they really are colliding.
+
+ """
+ if not self._collision_radius:
+ self._collision_radius = max(self.width, self.height)
+ return self._collision_radius
+
+ collision_radius = property(_get_collision_radius, _set_collision_radius)
+
+ def __lt__(self, other):
+ return self._texture.texture_id.value < other.texture.texture_id.value
+
+ def clear_spatial_hashes(self):
+ """
+ Search the sprite lists this sprite is a part of, and remove it
+ from any spatial hashes it is a part of.
+
+ """
+ for sprite_list in self.sprite_lists:
+ if sprite_list.use_spatial_hash and sprite_list.spatial_hash is not None:
+ try:
+ sprite_list.spatial_hash.remove_object(self)
+ except ValueError:
+ print("Warning, attempt to remove item from spatial hash that doesn't exist in the hash.")
+
+ def add_spatial_hashes(self):
+ for sprite_list in self.sprite_lists:
+ if sprite_list.use_spatial_hash:
+ sprite_list.spatial_hash.insert_object_for_box(self)
+
+ def _get_bottom(self) -> float:
+ """
+ Return the y coordinate of the bottom of the sprite.
+ """
+ points = self.get_points()
+ my_min = points[0][1]
+ for point in range(1, len(points)):
+ my_min = min(my_min, points[point][1])
+ return my_min
+
+ def _set_bottom(self, amount: float):
+ """
+ Set the location of the sprite based on the bottom y coordinate.
+ """
+ lowest = self._get_bottom()
+ diff = lowest - amount
+ self.center_y -= diff
+
+ bottom = property(_get_bottom, _set_bottom)
+
+ def _get_top(self) -> float:
+ """
+ Return the y coordinate of the top of the sprite.
+ """
+ points = self.get_points()
+ my_max = points[0][1]
+ for i in range(1, len(points)):
+ my_max = max(my_max, points[i][1])
+ return my_max
+
+ def _set_top(self, amount: float):
+ """ The highest y coordinate. """
+ highest = self._get_top()
+ diff = highest - amount
+ self.center_y -= diff
+
+ top = property(_get_top, _set_top)
+
+ def _get_width(self) -> float:
+ """ Get the width of the sprite. """
+ return self._width
+
+ def _set_width(self, new_value: float):
+ """ Set the width in pixels of the sprite. """
+ if new_value != self._width:
+ self.clear_spatial_hashes()
+ self._point_list_cache = None
+ self._width = new_value
+ self.add_spatial_hashes()
+
+ for sprite_list in self.sprite_lists:
+ sprite_list.update_position(self)
+
+ width = property(_get_width, _set_width)
+
+ def _get_height(self) -> float:
+ """ Get the height in pixels of the sprite. """
+ return self._height
+
+ def _set_height(self, new_value: float):
+ """ Set the center x coordinate of the sprite. """
+ if new_value != self._height:
+ self.clear_spatial_hashes()
+ self._point_list_cache = None
+ self._height = new_value
+ self.add_spatial_hashes()
+
+ for sprite_list in self.sprite_lists:
+ sprite_list.update_position(self)
+
+ height = property(_get_height, _set_height)
+
+ def _get_scale(self) -> float:
+ """ Get the scale of the sprite. """
+ return self._scale
+
+ def _set_scale(self, new_value: float):
+ """ Set the center x coordinate of the sprite. """
+ if new_value != self._height:
+ self.clear_spatial_hashes()
+ self._point_list_cache = None
+ self._scale = new_value
+ if self._texture:
+ self._width = self._texture.width * self._scale
+ self._height = self._texture.height * self._scale
+ self.add_spatial_hashes()
+
+ for sprite_list in self.sprite_lists:
+ sprite_list.update_position(self)
+
+ scale = property(_get_scale, _set_scale)
+
+ def _get_center_x(self) -> float:
+ """ Get the center x coordinate of the sprite. """
+ return self._position[0]
+
+ def _set_center_x(self, new_value: float):
+ """ Set the center x coordinate of the sprite. """
+ if new_value != self._position[0]:
+ self.clear_spatial_hashes()
+ self._point_list_cache = None
+ self._position[0] = new_value
+ self.add_spatial_hashes()
+
+ for sprite_list in self.sprite_lists:
+ sprite_list.update_location(self)
+
+ center_x = property(_get_center_x, _set_center_x)
+
+ def _get_center_y(self) -> float:
+ """ Get the center y coordinate of the sprite. """
+ return self._position[1]
+
+ def _set_center_y(self, new_value: float):
+ """ Set the center y coordinate of the sprite. """
+ if new_value != self._position[1]:
+ self.clear_spatial_hashes()
+ self._point_list_cache = None
+ self._position[1] = new_value
+ self.add_spatial_hashes()
+
+ for sprite_list in self.sprite_lists:
+ sprite_list.update_location(self)
+
+ center_y = property(_get_center_y, _set_center_y)
+
+ def _get_change_x(self) -> float:
+ """ Get the velocity in the x plane of the sprite. """
+ return self.velocity[0]
+
+ def _set_change_x(self, new_value: float):
+ """ Set the velocity in the x plane of the sprite. """
+ self.velocity[0] = new_value
+
+ change_x = property(_get_change_x, _set_change_x)
+
+ def _get_change_y(self) -> float:
+ """ Get the velocity in the y plane of the sprite. """
+ return self.velocity[1]
+
+ def _set_change_y(self, new_value: float):
+ """ Set the velocity in the y plane of the sprite. """
+ self.velocity[1] = new_value
+
+ change_y = property(_get_change_y, _set_change_y)
+
+ def _get_angle(self) -> float:
+ """ Get the angle of the sprite's rotation. """
+ return self._angle
+
+ def _set_angle(self, new_value: float):
+ """ Set the angle of the sprite's rotation. """
+ if new_value != self._angle:
+ self.clear_spatial_hashes()
+ self._angle = new_value
+ self._point_list_cache = None
+ self.add_spatial_hashes()
+
+ for sprite_list in self.sprite_lists:
+ sprite_list.update_angle(self)
+
+ angle = property(_get_angle, _set_angle)
+
+ def _to_radians(self) -> float:
+ """
+ Converts the degrees representation of self.angle into radians.
+ :return: float
+ """
+ return self.angle / 180.0 * math.pi
+
+ def _from_radians(self, new_value: float):
+ """
+ Converts a radian value into degrees and stores it into angle.
+ """
+ self.angle = new_value * 180.0 / math.pi
+
+ radians = property(_to_radians, _from_radians)
+
+ def _get_left(self) -> float:
+ """
+ Left-most coordinate.
+ """
+ points = self.get_points()
+ my_min = points[0][0]
+ for i in range(1, len(points)):
+ my_min = min(my_min, points[i][0])
+ return my_min
+
+ def _set_left(self, amount: float):
+ """ The left most x coordinate. """
+ leftmost = self._get_left()
+ diff = amount - leftmost
+ self.center_x += diff
+
+ left = property(_get_left, _set_left)
+
+ def _get_right(self) -> float:
+ """
+ Return the x coordinate of the right-side of the sprite.
+ """
+
+ points = self.get_points()
+ my_max = points[0][0]
+ for point in range(1, len(points)):
+ my_max = max(my_max, points[point][0])
+ return my_max
+
+ def _set_right(self, amount: float):
+ """ The right most x coordinate. """
+ rightmost = self._get_right()
+ diff = rightmost - amount
+ self.center_x -= diff
+
+ right = property(_get_right, _set_right)
+
+ def set_texture(self, texture_no: int):
+ """
+ Sets texture by texture id. Should be renamed because it takes
+ a number rather than a texture, but keeping
+ this for backwards compatibility.
+ """
+ if self.textures[texture_no] == self._texture:
+ return
+
+ texture = self.textures[texture_no]
+ self.clear_spatial_hashes()
+ self._point_list_cache = None
+ self._texture = texture
+ self._width = texture.width * texture.scale
+ self._height = texture.height * texture.scale
+ self.add_spatial_hashes()
+ for sprite_list in self.sprite_lists:
+ sprite_list.update_texture(self)
+
+ def _set_texture2(self, texture: Texture):
+ """ Sets texture by texture id. Should be renamed but keeping
+ this for backwards compatibility. """
+ if texture == self._texture:
+ return
+
+ self.clear_spatial_hashes()
+ self._point_list_cache = None
+ self._texture = texture
+ self._width = texture.width * texture.scale
+ self._height = texture.height * texture.scale
+ self.add_spatial_hashes()
+ for sprite_list in self.sprite_lists:
+ sprite_list.update_texture(self)
+
+ def _get_texture(self):
+ return self._texture
+
+ texture = property(_get_texture, _set_texture2)
+
+ def _get_color(self) -> RGB:
+ """
+ Return the RGB color associated with the sprite.
+ """
+ return self._color
+
+ def _set_color(self, color: RGB):
+ """
+ Set the current sprite color as a RGB value
+ """
+ self._color = color
+ for sprite_list in self.sprite_lists:
+ sprite_list.update_position(self)
+
+ color = property(_get_color, _set_color)
+
+ def _get_alpha(self) -> int:
+ """
+ Return the alpha associated with the sprite.
+ """
+ return self._alpha
+
+ def _set_alpha(self, alpha: int):
+ """
+ Set the current sprite color as a value
+ """
+ self._alpha = alpha
+ for sprite_list in self.sprite_lists:
+ sprite_list.update_position(self)
+
+ alpha = property(_get_alpha, _set_alpha)
+
+ def register_sprite_list(self, new_list):
+ """
+ Register this sprite as belonging to a list. We will automatically
+ remove ourselves from the the list when kill() is called.
+ """
+ self.sprite_lists.append(new_list)
+
+ def draw(self):
+ """ Draw the sprite. """
+
+ draw_texture_rectangle(self.center_x, self.center_y,
+ self.width, self.height,
+ self._texture, self.angle, self.alpha, # TODO: review this function
+ repeat_count_x=self.repeat_count_x,
+ repeat_count_y=self.repeat_count_y)
+
+ def update(self):
+ """
+ Update the sprite.
+ """
+ self.position = [self._position[0] + self.change_x, self._position[1] + self.change_y]
+ self.angle += self.change_angle
+
+ def update_animation(self):
+ """
+ Override this to add code that will change
+ what image is shown, so the sprite can be
+ animated.
+ """
+ pass
+
+ def remove_from_sprite_lists(self):
+ """
+ Remove the sprite from all sprite lists.
+ """
+ for sprite_list in self.sprite_lists:
+ if self in sprite_list:
+ sprite_list.remove(self)
+ self.sprite_lists.clear()
+
+ def kill(self):
+ """
+ Alias of `remove_from_sprite_lists`
+ """
+ self.remove_from_sprite_lists()
+
+ def collides_with_point(self, point: Point) -> bool:
+ """Check if point is within the current sprite.
+
+ Args:
+ self: Current sprite
+ point: Point to check.
+
+ Returns:
+ True if the point is contained within the sprite's boundary.
+ """
+ from arcade.geometry import is_point_in_polygon
+
+ x, y = point
+ return is_point_in_polygon(x, y, self.points)
+
+ def collides_with_sprite(self, other: 'Sprite') -> bool:
+ """Will check if a sprite is overlapping (colliding) another Sprite.
+
+ Args:
+ self: Current Sprite.
+ other: The other sprite to check against.
+
+ Returns:
+ True or False, whether or not they are overlapping.
+ """
+ from arcade.geometry import check_for_collision
+
+ return check_for_collision(self, other)
+
+ def collides_with_list(self, sprite_list: list) -> list:
+ """Check if current sprite is overlapping with any other sprite in a list
+
+ Args:
+ self: current Sprite
+ sprite_list: SpriteList to check against
+
+ Returns:
+ SpriteList of all overlapping Sprites from the original SpriteList
+ """
+ from arcade.geometry import check_for_collision_with_list
+ return check_for_collision_with_list(self, sprite_list)
+
+
+class AnimatedTimeSprite(Sprite):
+ """
+ Sprite for platformer games that supports animations.
+ """
+
+ def __init__(self, scale: float = 1,
+ image_x: float = 0, image_y: float = 0,
+ center_x: float = 0, center_y: float = 0):
+
+ super().__init__(scale=scale, image_x=image_x, image_y=image_y,
+ center_x=center_x, center_y=center_y)
+ self.state = FACE_RIGHT
+ self.cur_texture_index = 0
+ self.texture_change_frames = 5
+ self.frame = 0
+
+ def update_animation(self):
+ """
+ Logic for selecting the proper texture to use.
+ """
+ if self.frame % self.texture_change_frames == 0:
+ self.cur_texture_index += 1
+ if self.cur_texture_index >= len(self.textures):
+ self.cur_texture_index = 0
+ self.set_texture(self.cur_texture_index)
+ self.frame += 1
+
+
+@dataclasses.dataclass
+class AnimationKeyframe:
+ tile_id: int
+ duration: int
+ image: PIL.Image
+
+
+class AnimatedTimeBasedSprite(Sprite):
+ """
+ Sprite for platformer games that supports animations.
+ """
+
+ def __init__(self,
+ filename: str = None,
+ scale: float = 1,
+ image_x: float = 0, image_y: float = 0,
+ image_width: float = 0, image_height: float = 0,
+ center_x: float = 0, center_y: float = 0,
+ repeat_count_x=1, repeat_count_y=1):
+
+ super().__init__(filename=filename, scale=scale, image_x=image_x, image_y=image_y,
+ image_width=image_width, image_height=image_height,
+ center_x=center_x, center_y=center_y)
+ self.cur_frame = 0
+ self.frames = []
+ self.time_counter = 0.0
+
+ def update_animation(self, delta_time: float):
+ """
+ Logic for selecting the proper texture to use.
+ """
+ self.time_counter += delta_time
+ while self.time_counter > self.frames[self.cur_frame].duration / 1000.0:
+ self.time_counter -= self.frames[self.cur_frame].duration / 1000.0
+ self.cur_frame += 1
+ if self.cur_frame >= len(self.frames):
+ self.cur_frame = 0
+ source = self.frames[self.cur_frame].image.source
+ # print(f"Advance to frame {self.cur_frame}: {source}")
+ self.texture = load_texture(source, scale=self.scale)
+
+
+class AnimatedWalkingSprite(Sprite):
+ """
+ Sprite for platformer games that supports animations.
+ """
+
+ def __init__(self, scale: float = 1,
+ image_x: float = 0, image_y: float = 0,
+ center_x: float = 0, center_y: float = 0):
+ super().__init__(scale=scale, image_x=image_x, image_y=image_y,
+ center_x=center_x, center_y=center_y)
+ self.state = FACE_RIGHT
+ self.stand_right_textures = None
+ self.stand_left_textures = None
+ self.walk_left_textures = None
+ self.walk_right_textures = None
+ self.walk_up_textures = None
+ self.walk_down_textures = None
+ self.cur_texture_index = 0
+ self.texture_change_distance = 20
+ self.last_texture_change_center_x = 0
+ self.last_texture_change_center_y = 0
+
+ def update_animation(self):
+ """
+ Logic for selecting the proper texture to use.
+ """
+ x1 = self.center_x
+ x2 = self.last_texture_change_center_x
+ y1 = self.center_y
+ y2 = self.last_texture_change_center_y
+ distance = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
+ texture_list = []
+
+ change_direction = False
+ if self.change_x > 0 \
+ and self.change_y == 0 \
+ and self.state != FACE_RIGHT \
+ and self.walk_right_textures \
+ and len(self.walk_right_textures) > 0:
+ self.state = FACE_RIGHT
+ change_direction = True
+ elif self.change_x < 0 and self.change_y == 0 and self.state != FACE_LEFT \
+ and self.walk_left_textures and len(self.walk_left_textures) > 0:
+ self.state = FACE_LEFT
+ change_direction = True
+ elif self.change_y < 0 and self.change_x == 0 and self.state != FACE_DOWN \
+ and self.walk_down_textures and len(self.walk_down_textures) > 0:
+ self.state = FACE_DOWN
+ change_direction = True
+ elif self.change_y > 0 and self.change_x == 0 and self.state != FACE_UP \
+ and self.walk_up_textures and len(self.walk_up_textures) > 0:
+ self.state = FACE_UP
+ change_direction = True
+
+ if self.change_x == 0 and self.change_y == 0:
+ if self.state == FACE_LEFT:
+ self.texture = self.stand_left_textures[0]
+ elif self.state == FACE_RIGHT:
+ self.texture = self.stand_right_textures[0]
+ elif self.state == FACE_UP:
+ self.texture = self.walk_up_textures[0]
+ elif self.state == FACE_DOWN:
+ self.texture = self.walk_down_textures[0]
+
+ elif change_direction or distance >= self.texture_change_distance:
+ self.last_texture_change_center_x = self.center_x
+ self.last_texture_change_center_y = self.center_y
+
+ if self.state == FACE_LEFT:
+ texture_list = self.walk_left_textures
+ if texture_list is None or len(texture_list) == 0:
+ raise RuntimeError("update_animation was called on a sprite that doesn't have a "
+ "list of walk left textures.")
+ elif self.state == FACE_RIGHT:
+ texture_list = self.walk_right_textures
+ if texture_list is None or len(texture_list) == 0:
+ raise RuntimeError("update_animation was called on a sprite that doesn't have a list of "
+ "walk right textures.")
+ elif self.state == FACE_UP:
+ texture_list = self.walk_up_textures
+ if texture_list is None or len(texture_list) == 0:
+ raise RuntimeError("update_animation was called on a sprite that doesn't have a list of "
+ "walk up textures.")
+ elif self.state == FACE_DOWN:
+ texture_list = self.walk_down_textures
+ if texture_list is None or len(texture_list) == 0:
+ raise RuntimeError(
+ "update_animation was called on a sprite that doesn't have a list of walk down textures.")
+
+ self.cur_texture_index += 1
+ if self.cur_texture_index >= len(texture_list):
+ self.cur_texture_index = 0
+
+ self.texture = texture_list[self.cur_texture_index]
+
+ if self._texture is None:
+ print("Error, no texture set")
+ else:
+ self.width = self._texture.width * self.scale
+ self.height = self._texture.height * self.scale
+
+
+def get_distance_between_sprites(sprite1: Sprite, sprite2: Sprite) -> float:
+ """
+ Returns the distance between the center of two given sprites
+ :param Sprite sprite1: Sprite one
+ :param Sprite sprite2: Sprite two
+ :return: Distance
+ :rtype: float
+ """
+ distance = math.sqrt((sprite1.center_x - sprite2.center_x) ** 2 + (sprite1.center_y - sprite2.center_y) ** 2)
+ return distance
diff --git a/arcade/sprite_list.py b/arcade/sprite_list.py
new file mode 100644
index 0000000..021dbc1
--- /dev/null
+++ b/arcade/sprite_list.py
@@ -0,0 +1,688 @@
+"""
+This module provides functionality to manage Sprites in a list.
+
+"""
+
+from typing import Iterable
+from typing import TypeVar
+from typing import Generic
+from typing import List
+
+import pyglet.gl as gl
+
+import math
+import numpy as np
+
+from PIL import Image
+
+from arcade.sprite import Sprite
+from arcade.sprite import get_distance_between_sprites
+from arcade.sprite import AnimatedTimeBasedSprite
+
+from arcade.draw_commands import rotate_point
+from arcade.window_commands import get_projection
+from arcade import shader
+from arcade.arcade_types import Point
+
+VERTEX_SHADER = """
+#version 330
+uniform mat4 Projection;
+
+// per vertex
+in vec2 in_vert;
+in vec2 in_texture;
+
+// per instance
+in vec2 in_pos;
+in float in_angle;
+in vec2 in_scale;
+in vec4 in_sub_tex_coords;
+in vec4 in_color;
+
+out vec2 v_texture;
+out vec4 v_color;
+
+void main() {
+ mat2 rotate = mat2(
+ cos(in_angle), sin(in_angle),
+ -sin(in_angle), cos(in_angle)
+ );
+ vec2 pos;
+ pos = in_pos + vec2(rotate * (in_vert * in_scale));
+ gl_Position = Projection * vec4(pos, 0.0, 1.0);
+
+ vec2 tex_offset = in_sub_tex_coords.xy;
+ vec2 tex_size = in_sub_tex_coords.zw;
+
+ v_texture = (in_texture * tex_size + tex_offset) * vec2(1, -1);
+ v_color = in_color;
+}
+"""
+
+FRAGMENT_SHADER = """
+#version 330
+uniform sampler2D Texture;
+
+in vec2 v_texture;
+in vec4 v_color;
+
+out vec4 f_color;
+
+void main() {
+ vec4 basecolor = texture(Texture, v_texture);
+ basecolor = basecolor * v_color;
+ if (basecolor.a == 0.0){
+ discard;
+ }
+ f_color = basecolor;
+}
+"""
+
+
+def _create_rects(rect_list: Iterable[Sprite]) -> List[float]:
+ """
+ Create a vertex buffer for a set of rectangles.
+ """
+
+ v2f = []
+ for shape in rect_list:
+ x1 = -shape.width / 2 + shape.center_x
+ x2 = shape.width / 2 + shape.center_x
+ y1 = -shape.height / 2 + shape.center_y
+ y2 = shape.height / 2 + shape.center_y
+
+ p1 = x1, y1
+ p2 = x2, y1
+ p3 = x2, y2
+ p4 = x1, y2
+
+ if shape.angle:
+ p1 = rotate_point(p1[0], p1[1], shape.center_x, shape.center_y, shape.angle)
+ p2 = rotate_point(p2[0], p2[1], shape.center_x, shape.center_y, shape.angle)
+ p3 = rotate_point(p3[0], p3[1], shape.center_x, shape.center_y, shape.angle)
+ p4 = rotate_point(p4[0], p4[1], shape.center_x, shape.center_y, shape.angle)
+
+ v2f.extend([p1[0], p1[1],
+ p2[0], p2[1],
+ p3[0], p3[1],
+ p4[0], p4[1]])
+
+ return v2f
+
+
+class _SpatialHash:
+ """
+ Structure for fast collision checking.
+
+ See: https://www.gamedev.net/articles/programming/general-and-gameplay-programming/spatial-hashing-r2697/
+ """
+
+ def __init__(self, cell_size):
+ self.cell_size = cell_size
+ self.contents = {}
+
+ def _hash(self, point):
+ return int(point[0] / self.cell_size), int(point[1] / self.cell_size)
+
+ def reset(self):
+ self.contents = {}
+
+ def insert_object_for_box(self, new_object: Sprite):
+ """
+ Insert a sprite.
+ """
+ # Get the corners
+ min_x = new_object.left
+ max_x = new_object.right
+ min_y = new_object.bottom
+ max_y = new_object.top
+
+ # print(f"New - Center: ({new_object.center_x}, {new_object.center_y}), Angle: {new_object.angle}, "
+ # f"Left: {new_object.left}, Right {new_object.right}")
+
+ min_point = (min_x, min_y)
+ max_point = (max_x, max_y)
+
+ # print(f"Add 1: {min_point} {max_point}")
+
+ # hash the minimum and maximum points
+ min_point, max_point = self._hash(min_point), self._hash(max_point)
+
+ # print(f"Add 2: {min_point} {max_point}")
+ # print("Add: ", min_point, max_point)
+
+ # iterate over the rectangular region
+ for i in range(min_point[0], max_point[0] + 1):
+ for j in range(min_point[1], max_point[1] + 1):
+ # append to each intersecting cell
+ bucket = self.contents.setdefault((i, j), [])
+ if new_object in bucket:
+ # print(f"Error, {new_object.guid} already in ({i}, {j}) bucket. ")
+ pass
+ else:
+ bucket.append(new_object)
+ # print(f"Adding {new_object.guid} to ({i}, {j}) bucket. "
+ # f"{new_object._position} {min_point} {max_point}")
+
+ def remove_object(self, sprite_to_delete: Sprite):
+ """
+ Remove a Sprite.
+
+ :param Sprite sprite_to_delete: Pointer to sprite to be removed.
+ """
+ # Get the corners
+ min_x = sprite_to_delete.left
+ max_x = sprite_to_delete.right
+ min_y = sprite_to_delete.bottom
+ max_y = sprite_to_delete.top
+
+ # print(f"Del - Center: ({sprite_to_delete.center_x}, {sprite_to_delete.center_y}), "
+ # f"Angle: {sprite_to_delete.angle}, Left: {sprite_to_delete.left}, Right {sprite_to_delete.right}")
+
+ min_point = (min_x, min_y)
+ max_point = (max_x, max_y)
+
+ # print(f"Remove 1: {min_point} {max_point}")
+
+ # hash the minimum and maximum points
+ min_point, max_point = self._hash(min_point), self._hash(max_point)
+
+ # print(f"Remove 2: {min_point} {max_point}")
+ # print("Remove: ", min_point, max_point)
+
+ # iterate over the rectangular region
+ for i in range(min_point[0], max_point[0] + 1):
+ for j in range(min_point[1], max_point[1] + 1):
+ bucket = self.contents.setdefault((i, j), [])
+ try:
+ bucket.remove(sprite_to_delete)
+ # print(f"Removing {sprite_to_delete.guid} from ({i}, {j}) bucket. {sprite_to_delete._position} "
+ # f"{min_point} {max_point}")
+
+ except ValueError:
+ print(f"Warning, tried to remove item {sprite_to_delete.guid} from spatial hash {i} {j} when "
+ f"it wasn't there. {min_point} {max_point}")
+
+ def get_objects_for_box(self, check_object: Sprite) -> List[Sprite]:
+ """
+ Returns colliding Sprites.
+
+ :param Sprite check_object: Sprite we are checking to see if there are
+ other sprites in the same box(es)
+
+ :return: List of close-by sprites
+ :rtype: List
+
+
+ """
+ # Get the corners
+ min_x = check_object.left
+ max_x = check_object.right
+ min_y = check_object.bottom
+ max_y = check_object.top
+
+ min_point = (min_x, min_y)
+ max_point = (max_x, max_y)
+
+ # hash the minimum and maximum points
+ min_point, max_point = self._hash(min_point), self._hash(max_point)
+
+ close_by_sprites = []
+ # iterate over the rectangular region
+ for i in range(min_point[0], max_point[0] + 1):
+ for j in range(min_point[1], max_point[1] + 1):
+ # print(f"Checking {i}, {j}")
+ # append to each intersecting cell
+ new_items = self.contents.setdefault((i, j), [])
+ # for item in new_items:
+ # print(f"Found {item.guid} in {i}, {j}")
+ close_by_sprites.extend(new_items)
+
+ return close_by_sprites
+
+ def get_objects_for_point(self, check_point: Point) -> List[Sprite]:
+ """
+ Returns Sprites at or close to a point.
+
+ :param Point check_point: Point we are checking to see if there are
+ other sprites in the same box(es)
+
+ :return: List of close-by sprites
+ :rtype: List
+
+
+ """
+
+ hash_point = self._hash(check_point)
+
+ close_by_sprites = []
+ new_items = self.contents.setdefault(hash_point, [])
+ close_by_sprites.extend(new_items)
+
+ return close_by_sprites
+
+
+T = TypeVar('T', bound=Sprite)
+
+
+class SpriteList(Generic[T]):
+
+ next_texture_id = 0
+
+ def __init__(self, use_spatial_hash=False, spatial_hash_cell_size=128, is_static=False):
+ """
+ Initialize the sprite list
+
+ :param bool use_spatial_hash: If set to True, this will make moving a sprite
+ in the SpriteList slower, but it will speed up collision detection
+ with items in the SpriteList. Great for doing collision detection
+ with walls/platforms.
+ :param int spatial_hash_cell_size:
+ :param bool is_static: Speeds drawing if this list won't change.
+ """
+ # List of sprites in the sprite list
+ self.sprite_list = []
+ self.sprite_idx = dict()
+
+ # Used in drawing optimization via OpenGL
+ self.program = None
+
+ self.sprite_data = None
+ self.sprite_data_buf = None
+ self.texture_id = None
+ self._texture = None
+ self.vao = None
+ self.vbo_buf = None
+
+ self.array_of_texture_names = []
+ self.array_of_images = []
+
+ # Used in collision detection optimization
+ self.is_static = is_static
+ self.use_spatial_hash = use_spatial_hash
+ if use_spatial_hash:
+ self.spatial_hash = _SpatialHash(cell_size=spatial_hash_cell_size)
+ else:
+ self.spatial_hash = None
+
+ def append(self, item: T):
+ """
+ Add a new sprite to the list.
+
+ :param Sprite item: Sprite to add to the list.
+ """
+ idx = len(self.sprite_list)
+ self.sprite_list.append(item)
+ self.sprite_idx[item] = idx
+ item.register_sprite_list(self)
+ self.vao = None
+ if self.use_spatial_hash:
+ self.spatial_hash.insert_object_for_box(item)
+
+ def _recalculate_spatial_hash(self, item: T):
+ """ Recalculate the spatial hash for a particular item. """
+ if self.use_spatial_hash:
+ self.spatial_hash.remove_object(item)
+ self.spatial_hash.insert_object_for_box(item)
+
+ def _recalculate_spatial_hashes(self):
+ if self.use_spatial_hash:
+ self.spatial_hash.reset()
+ for sprite in self.sprite_list:
+ self.spatial_hash.insert_object_for_box(sprite)
+
+ def remove(self, item: T):
+ """
+ Remove a specific sprite from the list.
+ :param Sprite item: Item to remove from the list
+ """
+ self.sprite_list.remove(item)
+
+ # Rebuild index list
+ self.sprite_idx[item] = dict()
+ for idx, sprite in enumerate(self.sprite_list):
+ self.sprite_idx[sprite] = idx
+
+ self.vao = None
+ if self.use_spatial_hash:
+ self.spatial_hash.remove_object(item)
+
+ def update(self):
+ """
+ Call the update() method on each sprite in the list.
+ """
+ for sprite in self.sprite_list:
+ sprite.update()
+
+ def update_animation(self, delta_time=1/60):
+ for sprite in self.sprite_list:
+ if isinstance(sprite, AnimatedTimeBasedSprite):
+ sprite.update_animation(delta_time)
+ else:
+ sprite.update_animation()
+
+ def move(self, change_x: float, change_y: float):
+ """
+ Moves all Sprites in the list by the same amount.
+
+ :param float change_x: Amount to change all x values by
+ :param float change_y: Amount to change all y values by
+ """
+ for sprite in self.sprite_list:
+ sprite.center_x += change_x
+ sprite.center_y += change_y
+
+ def preload_textures(self, texture_names: List):
+ """
+ Preload a set of textures that will be used for sprites in this
+ sprite list.
+
+ :param array texture_names: List of file names to load in as textures.
+ """
+ self.array_of_texture_names.extend(texture_names)
+ self.array_of_images = None
+
+ def _calculate_sprite_buffer(self):
+
+ if len(self.sprite_list) == 0:
+ return
+
+ # Loop through each sprite and grab its position, and the texture it will be using.
+ array_of_positions = []
+ array_of_sizes = []
+ array_of_colors = []
+ array_of_angles = []
+
+ for sprite in self.sprite_list:
+ array_of_positions.append([sprite.center_x, sprite.center_y])
+ array_of_angles.append(math.radians(sprite.angle))
+ size_h = sprite.height / 2
+ size_w = sprite.width / 2
+ array_of_sizes.append([size_w, size_h])
+ array_of_colors.append(sprite.color + (sprite.alpha, ))
+
+ new_array_of_texture_names = []
+ new_array_of_images = []
+ new_texture = False
+ if self.array_of_images is None:
+ new_texture = True
+
+ # print()
+ # print("New texture start: ", new_texture)
+
+ for sprite in self.sprite_list:
+
+ if sprite._texture is None:
+ raise Exception("Error: Attempt to draw a sprite without a texture set.")
+
+ name_of_texture_to_check = sprite._texture.name
+ if name_of_texture_to_check not in self.array_of_texture_names:
+ new_texture = True
+ # print("New because of ", name_of_texture_to_check)
+
+ if name_of_texture_to_check not in new_array_of_texture_names:
+ new_array_of_texture_names.append(name_of_texture_to_check)
+ image = sprite._texture.image
+ new_array_of_images.append(image)
+
+ # print("New texture end: ", new_texture)
+ # print(new_array_of_texture_names)
+ # print(self.array_of_texture_names)
+ # print()
+
+ if new_texture:
+ # Add back in any old textures. Chances are we'll need them.
+ for index, old_texture_name in enumerate(self.array_of_texture_names):
+ if old_texture_name not in new_array_of_texture_names and self.array_of_images is not None:
+ new_array_of_texture_names.append(old_texture_name)
+ image = self.array_of_images[index]
+ new_array_of_images.append(image)
+
+ self.array_of_texture_names = new_array_of_texture_names
+
+ self.array_of_images = new_array_of_images
+ # print(f"New Texture Atlas with names {self.array_of_texture_names}")
+
+ # Get their sizes
+ widths, heights = zip(*(i.size for i in self.array_of_images))
+
+ # Figure out what size a composite would be
+ total_width = sum(widths)
+ max_height = max(heights)
+
+ if new_texture:
+
+ # TODO: This code isn't valid, but I think some releasing might be in order.
+ # if self.texture is not None:
+ # shader.Texture.release(self.texture_id)
+
+ # Make the composite image
+ new_image = Image.new('RGBA', (total_width, max_height))
+
+ x_offset = 0
+ for image in self.array_of_images:
+ new_image.paste(image, (x_offset, 0))
+ x_offset += image.size[0]
+
+ # Create a texture out the composite image
+ self._texture = shader.texture(
+ (new_image.width, new_image.height),
+ 4,
+ np.asarray(new_image)
+ )
+
+ if self.texture_id is None:
+ self.texture_id = SpriteList.next_texture_id
+
+ # Create a list with the coordinates of all the unique textures
+ tex_coords = []
+ start_x = 0.0
+ for image in self.array_of_images:
+ end_x = start_x + (image.width / total_width)
+ normalized_width = image.width / total_width
+ start_height = 1 - (image.height / max_height)
+ normalized_height = image.height / max_height
+ tex_coords.append([start_x, start_height, normalized_width, normalized_height])
+ start_x = end_x
+
+ # Go through each sprite and pull from the coordinate list, the proper
+ # coordinates for that sprite's image.
+ array_of_sub_tex_coords = []
+ for sprite in self.sprite_list:
+ index = self.array_of_texture_names.index(sprite._texture.name)
+ array_of_sub_tex_coords.append(tex_coords[index])
+
+ # Create numpy array with info on location and such
+ buffer_type = np.dtype([('position', '2f4'), ('angle', 'f4'), ('size', '2f4'),
+ ('sub_tex_coords', '4f4'), ('color', '4B')])
+ self.sprite_data = np.zeros(len(self.sprite_list), dtype=buffer_type)
+ self.sprite_data['position'] = array_of_positions
+ self.sprite_data['angle'] = array_of_angles
+ self.sprite_data['size'] = array_of_sizes
+ self.sprite_data['sub_tex_coords'] = array_of_sub_tex_coords
+ self.sprite_data['color'] = array_of_colors
+
+ if self.is_static:
+ usage = 'static'
+ else:
+ usage = 'stream'
+
+ self.sprite_data_buf = shader.buffer(
+ self.sprite_data.tobytes(),
+ usage=usage
+ )
+
+ vertices = np.array([
+ # x, y, u, v
+ -1.0, -1.0, 0.0, 0.0,
+ -1.0, 1.0, 0.0, 1.0,
+ 1.0, -1.0, 1.0, 0.0,
+ 1.0, 1.0, 1.0, 1.0,
+ ], dtype=np.float32
+ )
+ self.vbo_buf = shader.buffer(vertices.tobytes())
+ vbo_buf_desc = shader.BufferDescription(
+ self.vbo_buf,
+ '2f 2f',
+ ('in_vert', 'in_texture')
+ )
+ pos_angle_scale_buf_desc = shader.BufferDescription(
+ self.sprite_data_buf,
+ '2f 1f 2f 4f 4B',
+ ('in_pos', 'in_angle', 'in_scale', 'in_sub_tex_coords', 'in_color'),
+ normalized=['in_color'], instanced=True)
+
+ vao_content = [vbo_buf_desc, pos_angle_scale_buf_desc]
+
+ # Can add buffer to index vertices
+ self.vao = shader.vertex_array(self.program, vao_content)
+
+ def dump(self):
+ buffer = self.sprite_data.tobytes()
+ record_size = len(buffer) / len(self.sprite_list)
+ for i, char in enumerate(buffer):
+ if i % record_size == 0:
+ print()
+ print(f"{char:02x} ", end="")
+
+ def _update_positions(self):
+ """ Called by the Sprite class to update position, angle, size and color
+ of all sprites in the list.
+ Necessary for batch drawing of items. """
+
+ if self.vao is None:
+ return
+
+ for i, sprite in enumerate(self.sprite_list):
+ self.sprite_data[i]['position'] = [sprite.center_x, sprite.center_y]
+ self.sprite_data[i]['angle'] = math.radians(sprite.angle)
+ self.sprite_data[i]['size'] = [sprite.width / 2, sprite.height / 2]
+ self.sprite_data[i]['color'] = sprite.color + (sprite.alpha, )
+
+ def update_texture(self, sprite):
+ """ Make sure we update the texture for this sprite for the next batch
+ drawing"""
+ if self.vao is None:
+ return
+
+ self._calculate_sprite_buffer()
+
+ def update_position(self, sprite: Sprite):
+ """
+ Called by the Sprite class to update position, angle, size and color
+ of the specified sprite.
+ Necessary for batch drawing of items.
+
+ :param Sprite sprite: Sprite to update.
+ """
+ if self.vao is None:
+ return
+
+ i = self.sprite_idx[sprite]
+
+ self.sprite_data[i]['position'] = [sprite.center_x, sprite.center_y]
+ self.sprite_data[i]['angle'] = math.radians(sprite.angle)
+ self.sprite_data[i]['size'] = [sprite.width / 2, sprite.height / 2]
+ self.sprite_data[i]['color'] = sprite.color + (sprite.alpha, )
+
+ def update_location(self, sprite: Sprite):
+ """
+ Called by the Sprite class to update the location in this sprite.
+ Necessary for batch drawing of items.
+
+ :param Sprite sprite: Sprite to update.
+ """
+ if self.vao is None:
+ return
+
+ i = self.sprite_idx[sprite]
+
+ self.sprite_data[i]['position'] = sprite.position
+
+ def update_angle(self, sprite: Sprite):
+ """
+ Called by the Sprite class to update the angle in this sprite.
+ Necessary for batch drawing of items.
+
+ :param Sprite sprite: Sprite to update.
+ """
+ if self.vao is None:
+ return
+
+ i = self.sprite_idx[sprite]
+ self.sprite_data[i]['angle'] = math.radians(sprite.angle)
+
+ def draw(self):
+ """ Draw this list of sprites. """
+ if self.program is None:
+ # Used in drawing optimization via OpenGL
+ self.program = shader.program(
+ vertex_shader=VERTEX_SHADER,
+ fragment_shader=FRAGMENT_SHADER
+ )
+
+ if len(self.sprite_list) == 0:
+ return
+
+ if self.vao is None:
+ self._calculate_sprite_buffer()
+
+ self._texture.use(0)
+
+ gl.glEnable(gl.GL_BLEND)
+ gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
+ # gl.glTexParameterf(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_NEAREST)
+ # gl.glTexParameterf(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_NEAREST)
+
+ with self.vao:
+ self.program['Texture'] = self.texture_id
+ self.program['Projection'] = get_projection().flatten()
+
+ if not self.is_static:
+ self.sprite_data_buf.write(self.sprite_data.tobytes())
+
+ self.vao.render(gl.GL_TRIANGLE_STRIP, instances=len(self.sprite_list))
+
+ if not self.is_static:
+ self.sprite_data_buf.orphan()
+
+ def __len__(self) -> int:
+ """ Return the length of the sprite list. """
+ return len(self.sprite_list)
+
+ def __iter__(self) -> Iterable[T]:
+ """ Return an iterable object of sprites. """
+ return iter(self.sprite_list)
+
+ def __getitem__(self, i):
+ return self.sprite_list[i]
+
+ def pop(self) -> Sprite:
+ """
+ Pop off the last sprite in the list.
+ """
+ self.program = None
+ return self.sprite_list.pop()
+
+
+def get_closest_sprite(sprite: Sprite, sprite_list: SpriteList) -> (Sprite, float):
+ """
+ Given a Sprite and SpriteList, returns the closest sprite, and its distance.
+
+ :param Sprite sprite: Target sprite
+ :param SpriteList sprite_list: List to search for closest sprite.
+
+ :return: Closest sprite.
+ :rtype: Sprite
+ """
+ if len(sprite_list) == 0:
+ return None
+
+ min_pos = 0
+ min_distance = get_distance_between_sprites(sprite, sprite_list[min_pos])
+ for i in range(1, len(sprite_list)):
+ distance = get_distance_between_sprites(sprite, sprite_list[i])
+ if distance < min_distance:
+ min_pos = i
+ min_distance = distance
+ return sprite_list[min_pos], min_distance
diff --git a/arcade/text.py b/arcade/text.py
new file mode 100644
index 0000000..ec35e51
--- /dev/null
+++ b/arcade/text.py
@@ -0,0 +1,293 @@
+# --- BEGIN TEXT FUNCTIONS # # #
+
+import PIL.Image
+import PIL.ImageDraw
+import PIL.ImageFont
+
+from arcade.sprite import Sprite
+from arcade.arcade_types import Color
+from arcade.draw_commands import Texture
+
+
+class Text:
+ """ Class used for managing text. """
+
+ def __init__(self):
+ self.size = (0, 0)
+ self.text_sprite_list = None
+
+
+class CreateText:
+ """ Class used for managing text """
+
+ def __init__(self,
+ text: str,
+ color: Color,
+ font_size: float = 12,
+ width: int = 20,
+ align="left",
+ font_name=('Calibri', 'Arial'),
+ bold: bool = False,
+ italic: bool = False,
+ anchor_x="left",
+ anchor_y="baseline",
+ rotation=0):
+ self.text = text
+ self.color = color
+ self.font_size = font_size
+ self.width = width
+ self.align = align
+ self.font_name = font_name
+ self.bold = bold
+ self.italic = italic
+ self.anchor_x = anchor_x
+ self.anchor_y = anchor_y
+ self.rotation = rotation
+
+
+def create_text(text: str,
+ color: Color,
+ font_size: float = 12,
+ width: int = 0,
+ align="left",
+ font_name=('Calibri', 'Arial'),
+ bold: bool = False,
+ italic: bool = False,
+ anchor_x: str = "left",
+ anchor_y: str = "baseline",
+ rotation=0):
+ """ Deprecated. Two step text drawing for backwards compatibility. """
+
+ import warnings
+ warnings.warn("create_text has been deprecated, please use draw_text instead.", DeprecationWarning)
+ my_text = CreateText(text, color, font_size, width, align, font_name, bold, italic, anchor_x, anchor_y, rotation)
+ return my_text
+
+
+def render_text(text: CreateText, start_x: float, start_y: float):
+ """ Deprecated. Two step text drawing for backwards compatibility. """
+
+ import warnings
+ warnings.warn("render_text has been deprecated, please use draw_text instead.", DeprecationWarning)
+
+ draw_text(text.text,
+ start_x,
+ start_y,
+ color=text.color,
+ font_size=text.font_size,
+ width=text.width,
+ align=text.align,
+ font_name=text.font_name,
+ bold=text.bold,
+ italic=text.italic,
+ anchor_x=text.anchor_x,
+ anchor_y=text.anchor_y,
+ rotation=text.rotation)
+
+
+def draw_text(text: str,
+ start_x: float, start_y: float,
+ color: Color,
+ font_size: float = 12,
+ width: int = 0,
+ align: str = "left",
+ font_name=('calibri', 'arial'),
+ bold: bool = False,
+ italic: bool = False,
+ anchor_x: str = "left",
+ anchor_y: str = "baseline",
+ rotation: float = 0
+ ):
+ """
+
+ :param str text: Text to draw
+ :param float start_x:
+ :param float start_y:
+ :param Color color: Color of the text
+ :param float font_size: Size of the text
+ :param float width:
+ :param str align:
+ :param str font_name:
+ :param bool bold:
+ :param bool italic:
+ :param str anchor_x:
+ :param str anchor_y:
+ :param float rotation:
+ """
+
+ # Scale the font up, so it matches with the sizes of the old code back
+ # when Pyglet drew the text.
+ font_size *= 1.25
+
+ # Text isn't anti-aliased, so we'll draw big, and then shrink
+ scale_up = 1
+ scale_down = 1
+
+ # font_size *= scale_up
+
+ # If the cache gets too large, dump it and start over.
+ if len(draw_text.cache) > 5000:
+ draw_text.cache = {}
+
+ key = f"{text}{color}{font_size}{width}{align}{font_name}{bold}{italic}"
+ if key in draw_text.cache:
+ label = draw_text.cache[key]
+ text_sprite = label.text_sprite_list[0]
+
+ if anchor_x == "left":
+ text_sprite.center_x = start_x + text_sprite.width / 2
+ elif anchor_x == "center":
+ text_sprite.center_x = start_x
+ elif anchor_x == "right":
+ text_sprite.right = start_x
+ else:
+ raise ValueError(f"anchor_x should be 'left', 'center', or 'right'. Not '{anchor_x}'")
+
+ if anchor_y == "top":
+ text_sprite.center_y = start_y - text_sprite.height / 2
+ elif anchor_y == "center":
+ text_sprite.center_y = start_y
+ elif anchor_y == "bottom" or anchor_y == "baseline":
+ text_sprite.bottom = start_y
+ else:
+ raise ValueError(f"anchor_x should be 'left', 'center', or 'right'. Not '{anchor_y}'")
+
+ text_sprite.angle = rotation
+ else:
+ label = Text()
+
+ # Figure out the font to use
+ font = None
+
+ # Font was specified with a string
+ if isinstance(font_name, str):
+ try:
+ font = PIL.ImageFont.truetype(font_name, int(font_size))
+ except OSError:
+ # print(f"1 Can't find font: {font_name}")
+ pass
+
+ if font is None:
+ try:
+ temp_font_name = f"{font_name}.ttf"
+ font = PIL.ImageFont.truetype(temp_font_name, int(font_size))
+ except OSError:
+ # print(f"2 Can't find font: {temp_font_name}")
+ pass
+
+ # We were instead given a list of font names, in order of preference
+ else:
+ for font_string_name in font_name:
+ try:
+ font = PIL.ImageFont.truetype(font_string_name, int(font_size))
+ # print(f"3 Found font: {font_string_name}")
+ except OSError:
+ # print(f"3 Can't find font: {font_string_name}")
+ pass
+
+ if font is None:
+ try:
+ temp_font_name = f"{font_name}.ttf"
+ font = PIL.ImageFont.truetype(temp_font_name, int(font_size))
+ except OSError:
+ # print(f"4 Can't find font: {temp_font_name}")
+ pass
+
+ if font is not None:
+ break
+
+ # Default font if no font
+ if font is None:
+ font_names = ("arial.ttf",
+ 'NotoSans-Regular.ttf',
+ "/usr/share/fonts/truetype/freefont/FreeMono.ttf",
+ '/System/Library/Fonts/SFNSDisplay.ttf')
+ for font_string_name in font_names:
+ try:
+ font = PIL.ImageFont.truetype(font_string_name, int(font_size))
+ break
+ except OSError:
+ # print(f"5 Can't find font: {font_string_name}")
+ pass
+
+ # This is stupid. We have to have an image to figure out what size
+ # the text will be when we draw it. Of course, we don't know how big
+ # to make the image. Catch-22. So we just make a small image we'll trash
+ text_image_size = (10, 10)
+ image = PIL.Image.new("RGBA", text_image_size)
+ draw = PIL.ImageDraw.Draw(image)
+
+ # Get size the text will be
+ text_image_size = draw.multiline_textsize(text, font=font)
+
+ # Create image of proper size
+ text_height = text_image_size[1]
+ text_width = text_image_size[0]
+
+ image_start_x = 0
+ if width == 0:
+ width = text_image_size[0]
+ else:
+ # Wait! We were given a field width.
+ if align == "center":
+ # Center text on given field width
+ field_width = width * scale_up
+ text_image_size = field_width, text_height
+ image_start_x = (field_width - text_width) // 2
+ width = field_width
+ else:
+ image_start_x = 0
+
+ # If we draw a y at 0, then the text is drawn with a baseline of 0,
+ # cutting off letters that drop below the baseline. This shoves it
+ # up a bit.
+ image_start_y = - font_size * scale_up * 0.02
+ image = PIL.Image.new("RGBA", text_image_size)
+ draw = PIL.ImageDraw.Draw(image)
+
+ # Convert to tuple if needed, because the multiline_text does not take a
+ # list for a color
+ if isinstance(color, list):
+ color = tuple(color)
+ draw.multiline_text((image_start_x, image_start_y), text, color, align=align, font=font)
+ # image = image.resize((width // scale_down, text_height // scale_down), resample=PIL.Image.LANCZOS)
+
+ text_sprite = Sprite()
+ text_sprite._texture = Texture(key)
+ text_sprite._texture.image = image
+
+ text_sprite.image = image
+ text_sprite.texture_name = key
+ text_sprite.width = image.width
+ text_sprite.height = image.height
+
+ if anchor_x == "left":
+ text_sprite.center_x = start_x + text_sprite.width / 2
+ elif anchor_x == "center":
+ text_sprite.center_x = start_x
+ elif anchor_x == "right":
+ text_sprite.right = start_x
+ else:
+ raise ValueError(f"anchor_x should be 'left', 'center', or 'right'. Not '{anchor_x}'")
+
+ if anchor_y == "top":
+ text_sprite.center_y = start_y + text_sprite.height / 2
+ elif anchor_y == "center":
+ text_sprite.center_y = start_y
+ elif anchor_y == "bottom" or anchor_y == "baseline":
+ text_sprite.bottom = start_y
+ else:
+ raise ValueError(f"anchor_x should be 'top', 'center', 'bottom', or 'baseline'. Not '{anchor_y}'")
+
+ text_sprite.angle = rotation
+
+ from arcade.sprite_list import SpriteList
+ label.text_sprite_list = SpriteList()
+ label.text_sprite_list.append(text_sprite)
+
+ draw_text.cache[key] = label
+
+ label.text_sprite_list.draw()
+
+
+draw_text.cache = {}
diff --git a/arcade/tilemap.py b/arcade/tilemap.py
new file mode 100644
index 0000000..9c761c7
--- /dev/null
+++ b/arcade/tilemap.py
@@ -0,0 +1,321 @@
+"""
+Functions and classes for managing a map saved in the .tmx format.
+
+Typically these .tmx maps are created using the `Tiled Map Editor`_.
+
+For more information, see the `Platformer Tutorial`_.
+
+.. _Tiled Map Editor: https://www.mapeditor.org/
+.. _Platformer Tutorial: http://arcade.academy/examples/platform_tutorial/index.html
+
+
+"""
+
+from typing import Optional
+import math
+import copy
+import pytiled_parser
+
+from arcade import Sprite
+from arcade import AnimatedTimeBasedSprite
+from arcade import AnimationKeyframe
+from arcade import SpriteList
+
+FLIPPED_HORIZONTALLY_FLAG = 0x80000000
+FLIPPED_VERTICALLY_FLAG = 0x40000000
+FLIPPED_DIAGONALLY_FLAG = 0x20000000
+
+
+def read_tmx(tmx_file: str) -> pytiled_parser.objects.TileMap:
+ """
+ Given a .tmx, this will read in a tiled map, and return
+ a TiledMap object.
+
+ Given a tsx_file, the map will use it as the tileset.
+ If tsx_file is not specified, it will use the tileset specified
+ within the tmx_file.
+
+ Important: Tiles must be a "collection" of images.
+
+ Hitboxes can be drawn around tiles in the tileset editor,
+ but only polygons are supported.
+ (This is a great area for PR's to improve things.)
+
+ :param str tmx_file: String with name of our TMX file
+
+ :returns: Map
+ :rtype: TiledMap
+ """
+
+ tile_map = pytiled_parser.parse_tile_map(tmx_file)
+
+ return tile_map
+
+
+def get_tilemap_layer(map_object: pytiled_parser.objects.TileMap,
+ layer_name: str) -> Optional[pytiled_parser.objects.Layer]:
+ """
+ Given a TileMap and a layer name, this returns the TileLayer.
+
+ :param pytiled_parser.objects.TileMap map_object: The map read in by the read_tmx function.
+ :param str layer_name: A string to match the layer name. Case sensitive.
+
+ :returns: A TileLayer, or None if no layer was found.
+
+ """
+
+ assert isinstance(map_object, pytiled_parser.objects.TileMap)
+ assert isinstance(layer_name, str)
+
+ for layer in map_object.layers:
+ if layer.name == layer_name:
+ return layer
+
+ return None
+
+
+def _get_tile_by_gid(map_object: pytiled_parser.objects.TileMap,
+ tile_gid: int) -> Optional[pytiled_parser.objects.Tile]:
+
+ flipped_diagonally = False
+ flipped_horizontally = False
+ flipped_vertically = False
+
+ if tile_gid & FLIPPED_HORIZONTALLY_FLAG:
+ flipped_horizontally = True
+ tile_gid -= FLIPPED_HORIZONTALLY_FLAG
+
+ if tile_gid & FLIPPED_DIAGONALLY_FLAG:
+ flipped_diagonally = True
+ tile_gid -= FLIPPED_DIAGONALLY_FLAG
+
+ if tile_gid & FLIPPED_VERTICALLY_FLAG:
+ flipped_vertically = True
+ tile_gid -= FLIPPED_VERTICALLY_FLAG
+
+ for tileset_key, tileset in map_object.tile_sets.items():
+ for tile_key, tile in tileset.tiles.items():
+ cur_tile_gid = tile.id_ + tileset_key
+ # print(f"-- {tile_gid} {cur_tile_gid} {tile.image.source}")
+ if cur_tile_gid == tile_gid:
+ my_tile = copy.copy(tile)
+ my_tile.tileset = tileset
+ my_tile.flipped_vertically = flipped_vertically
+ my_tile.flipped_diagonally = flipped_diagonally
+ my_tile.flipped_horizontally = flipped_horizontally
+ return my_tile
+ return None
+
+
+def _get_tile_by_id(map_object: pytiled_parser.objects.TileMap,
+ tileset: pytiled_parser.objects.TileSet,
+ tile_id: int) -> Optional[pytiled_parser.objects.Tile]:
+ for tileset_key, cur_tileset in map_object.tile_sets.items():
+ if cur_tileset is tileset:
+ for tile_key, tile in cur_tileset.tiles.items():
+ if tile_id == tile.id_:
+ return tile
+ return None
+
+
+def _create_sprite_from_tile(map_object, tile: pytiled_parser.objects.Tile,
+ scaling,
+ base_directory: str = ""):
+
+ tmx_file = base_directory + tile.image.source
+
+ # print(f"Creating tile: {tmx_file}")
+ if tile.animation:
+ # my_sprite = AnimatedTimeSprite(tmx_file, scaling)
+ my_sprite = AnimatedTimeBasedSprite(tmx_file, scaling)
+ else:
+ my_sprite = Sprite(tmx_file, scaling)
+
+ if tile.properties is not None and len(tile.properties) > 0:
+ for my_property in tile.properties:
+ my_sprite.properties[my_property.name] = my_property.value
+
+ # print(tile.image.source, my_sprite.center_x, my_sprite.center_y)
+ if tile.objectgroup is not None:
+
+ if len(tile.objectgroup) > 1:
+ print(f"Warning, only one hit box supported for tile with image {tile.image.source}.")
+
+ for hitbox in tile.objectgroup:
+ half_width = map_object.tile_size.width / 2
+ half_height = map_object.tile_size.height / 2
+ points = []
+ if isinstance(hitbox, pytiled_parser.objects.RectangleObject):
+ if hitbox.size is None:
+ print(f"Warning: Rectangle hitbox created for without a "
+ f"height or width for {tile.image.source}. Ignoring.")
+ continue
+
+ p1 = [hitbox.location[0] - half_width, half_height - hitbox.location[0]]
+ p2 = [hitbox.location[0] + hitbox.size[0] - half_width, half_height - hitbox.size[0]]
+ p3 = [hitbox.location[0] + hitbox.size[0] - half_width, half_height
+ - (hitbox.location[1] + hitbox.size[1])]
+ p4 = [hitbox.location[0] - half_width, half_height - (hitbox.location[1] + hitbox.size[1])]
+ points = [p4, p3, p2, p1]
+
+ elif isinstance(hitbox, pytiled_parser.objects.PolygonObject):
+ for point in hitbox.points:
+ adj_x = point[0] + hitbox.location[0] - half_width
+ adj_y = half_height - (point[1] + hitbox.location[1])
+ adj_point = [adj_x, adj_y]
+ points.append(adj_point)
+
+ elif isinstance(hitbox, pytiled_parser.objects.PolylineObject):
+ for point in hitbox.points:
+ adj_x = point[0] + hitbox.location[0] - half_width
+ adj_y = half_height - (point[1] + hitbox.location[1])
+ adj_point = [adj_x, adj_y]
+ points.append(adj_point)
+
+ # If we have a polyline, and it is closed, we need to
+ # remove the duplicate end-point
+ if points[0][0] == points[-1][0] and points[0][1] == points[-1][1]:
+ points.pop()
+
+ elif isinstance(hitbox, pytiled_parser.objects.ElipseObject):
+ if hitbox.size is None:
+ print(f"Warning: Ellipse hitbox created for without a height "
+ f"or width for {tile.image.source}. Ignoring.")
+ continue
+ w = hitbox.size[0] / 2
+ h = hitbox.size[1] / 2
+ cx = (hitbox.location[0] + hitbox.size[0] / 2) - half_width
+ cy = map_object.tile_size.height - (hitbox.location[1] + hitbox.size[1] / 2) - half_height
+ total_steps = 8
+ angles = [step / total_steps * 2 * math.pi for step in range(total_steps)]
+ for angle in angles:
+ x = w * math.cos(angle) + cx
+ y = h * math.sin(angle) + cy
+ point = [x, y]
+ points.append(point)
+
+ else:
+ print(f"Warning: Hitbox type {type(hitbox)} not supported.")
+
+ # Scale the points to our sprite scaling
+ for point in points:
+ point[0] *= scaling
+ point[1] *= scaling
+ my_sprite.points = points
+
+ if tile.animation is not None:
+ key_frame_list = []
+ for frame in tile.animation:
+ frame_tile = _get_tile_by_id(map_object, tile.tileset, frame.tile_id)
+ key_frame = AnimationKeyframe(frame.tile_id, frame.duration, frame_tile.image)
+ key_frame_list.append(key_frame)
+
+ my_sprite.frames = key_frame_list
+
+ return my_sprite
+
+
+def _process_object_layer(map_object: pytiled_parser.objects.TileMap,
+ layer: pytiled_parser.objects.Layer,
+ scaling: float = 1,
+ base_directory: str = "") -> SpriteList:
+ sprite_list = SpriteList()
+
+ for cur_object in layer.tiled_objects:
+ if cur_object.gid is None:
+ print("Warning: Currently only tiles (not objects) are supported in object layers.")
+ continue
+
+ tile = _get_tile_by_gid(map_object, cur_object.gid)
+ my_sprite = _create_sprite_from_tile(map_object, tile, scaling=scaling,
+ base_directory=base_directory)
+
+ my_sprite.right = cur_object.location.x * scaling
+ my_sprite.top = (map_object.map_size.height * map_object.tile_size[1] - cur_object.location.y) * scaling
+
+ if cur_object.properties is not None and 'change_x' in cur_object.properties:
+ my_sprite.change_x = float(cur_object.properties['change_x'])
+
+ if cur_object.properties is not None and 'change_y' in cur_object.properties:
+ my_sprite.change_y = float(cur_object.properties['change_y'])
+
+ if cur_object.properties is not None and 'boundary_bottom' in cur_object.properties:
+ my_sprite.boundary_bottom = float(cur_object.properties['boundary_bottom'])
+
+ if cur_object.properties is not None and 'boundary_top' in cur_object.properties:
+ my_sprite.boundary_top = float(cur_object.properties['boundary_top'])
+
+ if cur_object.properties is not None and 'boundary_left' in cur_object.properties:
+ my_sprite.boundary_left = float(cur_object.properties['boundary_left'])
+
+ if cur_object.properties is not None and 'boundary_right' in cur_object.properties:
+ my_sprite.boundary_right = float(cur_object.properties['boundary_right'])
+
+ my_sprite.properties.update(cur_object.properties)
+ # sprite.properties
+ sprite_list.append(my_sprite)
+ return sprite_list
+
+
+def _process_tile_layer(map_object: pytiled_parser.objects.TileMap,
+ layer: pytiled_parser.objects.TileLayer,
+ scaling: float = 1,
+ base_directory: str = "") -> SpriteList:
+ sprite_list = SpriteList()
+ map_array = layer.data
+
+ # Loop through the layer and add in the wall list
+ for row_index, row in enumerate(map_array):
+ for column_index, item in enumerate(row):
+ # Check for empty square
+ if item == 0:
+ continue
+
+ tile = _get_tile_by_gid(map_object, item)
+ if tile is None:
+ print(f"Warning, couldn't find tile for item {item} in layer "
+ f"'{layer.name}' in file '{map_object.tmx_file}'.")
+ continue
+
+ my_sprite = _create_sprite_from_tile(map_object, tile, scaling=scaling,
+ base_directory=base_directory)
+
+ my_sprite.right = column_index * (map_object.tile_size[0] * scaling)
+ my_sprite.top = (map_object.map_size.height - row_index - 1) * (map_object.tile_size[1] * scaling)
+
+ sprite_list.append(my_sprite)
+
+ return sprite_list
+
+
+def process_layer(map_object: pytiled_parser.objects.TileMap,
+ layer_name: str,
+ scaling: float = 1,
+ base_directory: str = "") -> SpriteList:
+ """
+ This takes a map layer returned by the read_tmx function, and creates Sprites for it.
+
+ :param map_object: The TileMap read in by read_tmx.
+ :param layer_name: The name of the layer that we are creating sprites for.
+ :param scaling: Scaling the layer up or down.
+ (Note, any number besides 1 can create a tearing effect,
+ if numbers don't evenly divide.)
+ :param base_directory: Base directory of the file, that we start from to
+ load images.
+ :returns: A SpriteList.
+
+ """
+
+ if len(base_directory) > 0 and not base_directory.endswith("/"):
+ base_directory += "/"
+
+ layer = get_tilemap_layer(map_object, layer_name)
+ if layer is None:
+ print(f"Warning, no layer named '{layer_name}'.")
+ return SpriteList()
+
+ if isinstance(layer, pytiled_parser.objects.TileLayer):
+ return _process_tile_layer(map_object, layer, scaling, base_directory)
+
+ elif isinstance(layer, pytiled_parser.objects.ObjectLayer):
+ return _process_object_layer(map_object, layer, scaling, base_directory)
diff --git a/arcade/utils.py b/arcade/utils.py
new file mode 100644
index 0000000..0aab2bf
--- /dev/null
+++ b/arcade/utils.py
@@ -0,0 +1,139 @@
+import math
+import random
+from arcade.arcade_types import Point, Vector
+
+
+def lerp(v1: float, v2: float, u: float) -> float:
+ """linearly interpolate between two values"""
+ return v1 + ((v2 - v1) * u)
+
+
+def lerp_vec(v1: Vector, v2: Vector, u: float) -> Vector:
+ return (
+ lerp(v1[0], v2[0], u),
+ lerp(v1[1], v2[1], u)
+ )
+
+
+def rand_in_rect(bottom_left: Point, width: float, height: float) -> Point:
+ return (
+ random.uniform(bottom_left[0], bottom_left[0] + width),
+ random.uniform(bottom_left[1], bottom_left[1] + height)
+ )
+
+
+def rand_in_circle(center: Point, radius: float):
+ """
+ Generate a point in a circle, or can think of it as a vector pointing
+ a random direction with a random magnitude <= radius
+ Reference: http://stackoverflow.com/a/30564123
+ Note: This algorithm returns a higher concentration of points around the center of the circle
+ """
+ # random angle
+ angle = 2 * math.pi * random.random()
+ # random radius
+ r = radius * random.random()
+ # calculating coordinates
+ return (
+ r * math.cos(angle) + center[0],
+ r * math.sin(angle) + center[1]
+ )
+
+
+def rand_on_circle(center: Point, radius: float) -> Point:
+ """Note: by passing a random value in for float, you can achieve what rand_in_circle() does"""
+ angle = 2 * math.pi * random.random()
+ return (
+ radius * math.cos(angle) + center[0],
+ radius * math.sin(angle) + center[1]
+ )
+
+
+def rand_on_line(pos1: Point, pos2: Point) -> Point:
+ u = random.uniform(0.0, 1.0)
+ return lerp_vec(pos1, pos2, u)
+
+
+def rand_angle_360_deg():
+ return random.uniform(0.0, 360.0)
+
+
+def rand_angle_spread_deg(angle: float, half_angle_spread: float) -> float:
+ s = random.uniform(-half_angle_spread, half_angle_spread)
+ return angle + s
+
+
+def rand_vec_spread_deg(angle: float, half_angle_spread: float, length: float) -> Vector:
+ a = rand_angle_spread_deg(angle, half_angle_spread)
+ vel = _Vec2.from_polar(a, length)
+ return vel.as_tuple()
+
+
+def rand_vec_magnitude(angle: float, lo_magnitude: float, hi_magnitude: float) -> Vector:
+ mag = random.uniform(lo_magnitude, hi_magnitude)
+ vel = _Vec2.from_polar(angle, mag)
+ return vel.as_tuple()
+
+
+class _Vec2:
+ """
+ 2D vector used to do operate points and vectors
+
+ Note: intended to be used for internal implementations only. Should not be part of public interfaces
+ (ex: function parameters or return values).
+ """
+
+ __slots__ = ['x', 'y']
+
+ def __init__(self, x, y=None):
+ try:
+ # see if first argument is an iterable with two items
+ self.x = x[0]
+ self.y = x[1]
+ except TypeError:
+ self.x = x
+ self.y = y
+
+ @staticmethod
+ def from_polar(angle, radius):
+ rads = math.radians(angle)
+ return _Vec2(radius * math.cos(rads), radius * math.sin(rads))
+
+ def __add__(self, other):
+ return _Vec2(self.x + other.x, self.y + other.y)
+
+ def __sub__(self, other):
+ return _Vec2(self.x - other.x, self.y - other.y)
+
+ def __mul__(self, other):
+ return _Vec2(self.x * other.x, self.y * other.y)
+
+ def __truediv__(self, other):
+ return _Vec2(self.x / other.x, self.y / other.y)
+
+ def __iter__(self):
+ yield self.x
+ yield self.y
+
+ def length(self):
+ """return the length (magnitude) of the vector"""
+ return math.sqrt(self.x**2 + self.y**2)
+
+ def dot(self, other):
+ return self.x * other.x + self.y * other.y
+
+ def __repr__(self):
+ return f"Vec2({self.x},{self.y})"
+
+ def rotated(self, angle):
+ """Returns the new vector resulting when this vector is rotated by the given angle in degrees"""
+ rads = math.radians(angle)
+ cosine = math.cos(rads)
+ sine = math.sin(rads)
+ return _Vec2(
+ (self.x*cosine) - (self.y*sine),
+ (self.y*cosine) + (self.x*sine)
+ )
+
+ def as_tuple(self) -> Point:
+ return self.x, self.y
diff --git a/arcade/version.py b/arcade/version.py
new file mode 100644
index 0000000..fe318a9
--- /dev/null
+++ b/arcade/version.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+BUILD = 0
+VERSION = "2.1.3"
+RELEASE = VERSION
diff --git a/arcade/window_commands.py b/arcade/window_commands.py
new file mode 100644
index 0000000..89169b8
--- /dev/null
+++ b/arcade/window_commands.py
@@ -0,0 +1,295 @@
+"""
+This submodule has functions that control opening, closing, rendering, and otherwise managing windows.
+It also has commands for scheduling pauses and scheduling interval functions.
+"""
+
+import gc
+import time
+import os
+
+import pyglet.gl as gl
+import pyglet
+
+import numpy as np
+
+from numbers import Number
+from typing import Callable
+from typing import Union
+from arcade.arcade_types import Color
+
+_left = -1
+_right = 1
+_bottom = -1
+_top = 1
+_scaling = None
+
+_window = None
+
+_projection = None
+_opengl_context = None
+
+
+def get_projection():
+ """
+ Returns the current projection.
+
+ :return: Numpy array with projection.
+
+ """
+ return _projection
+
+
+def create_orthogonal_projection(
+ left,
+ right,
+ bottom,
+ top,
+ near,
+ far,
+ dtype=None
+):
+ """
+ Creates an orthogonal projection matrix. Used internally with the
+ OpenGL shaders.
+
+ :param float left: The left of the near plane relative to the plane's centre.
+ :param float right: The right of the near plane relative to the plane's centre.
+ :param float top: The top of the near plane relative to the plane's centre.
+ :param float bottom: The bottom of the near plane relative to the plane's centre.
+ :param float near: The distance of the near plane from the camera's origin.
+ It is recommended that the near plane is set to 1.0 or above to avoid
+ rendering issues at close range.
+ :param float far: The distance of the far plane from the camera's origin.
+ :param dtype:
+ :return: A projection matrix representing the specified orthogonal perspective.
+ :rtype: numpy.array
+
+ .. seealso:: http://msdn.microsoft.com/en-us/library/dd373965(v=vs.85).aspx
+ """
+
+ rml = right - left
+ tmb = top - bottom
+ fmn = far - near
+
+ a = 2. / rml
+ b = 2. / tmb
+ c = -2. / fmn
+ tx = -(right + left) / rml
+ ty = -(top + bottom) / tmb
+ tz = -(far + near) / fmn
+
+ return np.array((
+ (a, 0., 0., 0.),
+ (0., b, 0., 0.),
+ (0., 0., c, 0.),
+ (tx, ty, tz, 1.),
+ ), dtype=dtype)
+
+
+def pause(seconds: Number):
+ """
+ Pause for the specified number of seconds. This is a convenience function that just calls time.sleep()
+
+ :param float seconds: Time interval to pause in seconds.
+ """
+ time.sleep(seconds)
+
+
+def get_window() -> Union[pyglet.window.Window, None]:
+ """
+ Return a handle to the current window.
+
+ :return: Handle to the current window.
+ """
+ global _window
+ return _window
+
+
+def set_window(window: pyglet.window.Window):
+ """
+ Set a handle to the current window.
+
+ :param Window window: Handle to the current window.
+ """
+ global _window
+ _window = window
+
+
+def get_scaling_factor(window):
+ """
+ Tries to get the scaling factor of the given Window. Currently works
+ on MacOS only. Useful in figuring out what's going on with Retina and
+ high-res displays.
+
+ :param Window window: Handle to window we want to get scaling factor of.
+
+ :return: Scaling factor. E.g., 2 would indicate scaled up twice.
+ :rtype: int
+
+ """
+ from pyglet import compat_platform
+ if compat_platform == 'darwin':
+ from pyglet.libs.darwin.cocoapy import NSMakeRect
+ view = window.context._nscontext.view()
+ content_rect = NSMakeRect(0, 0, window._width, window._height) # Get size, possibly scaled
+ bounds = view.convertRectFromBacking_(content_rect) # Convert to actual pixel sizes
+ return int(content_rect.size.width / bounds.size.width)
+ else:
+ return 1
+
+
+def set_viewport(left: Number, right: Number, bottom: Number, top: Number):
+ """
+ This sets what coordinates the window will cover.
+
+ By default, the lower left coordinate will be (0, 0) and the top y
+ coordinate will be the height of the window in pixels, and the right x
+ coordinate will be the width of the window in pixels.
+
+ If a program is making a game where the user scrolls around a larger
+ world, this command can help out.
+
+ Note: It is recommended to only set the view port to integer values that
+ line up with the pixels on the screen. Otherwise if making a tiled game
+ the blocks may not line up well, creating rectangle artifacts.
+
+ :param Number left: Left-most (smallest) x value.
+ :param Number right: Right-most (largest) x value.
+ :param Number bottom: Bottom (smallest) y value.
+ :param Number top: Top (largest) y value.
+ """
+ global _left
+ global _right
+ global _bottom
+ global _top
+ global _projection
+ global _scaling
+
+ _left = left
+ _right = right
+ _bottom = bottom
+ _top = top
+
+ # Needed for sprites
+ if _scaling is None:
+ _scaling = get_scaling_factor(_window)
+ gl.glViewport(0, 0, _window.width * _scaling, _window.height * _scaling)
+
+ # Needed for drawing
+ # gl.glMatrixMode(gl.GL_PROJECTION)
+ # gl.glLoadIdentity()
+ # gl.glOrtho(_left, _right, _bottom, _top, -1, 1)
+ # gl.glMatrixMode(gl.GL_MODELVIEW)
+ # gl.glLoadIdentity()
+
+ _projection = create_orthogonal_projection(left=_left, right=_right,
+ bottom=_bottom, top=_top,
+ near=-1000, far=100, dtype=np.float32)
+
+
+def get_viewport() -> (float, float, float, float):
+ """
+ Get the current viewport settings.
+
+ :return: Tuple of floats, with left, right, bottom, top
+
+ """
+ return _left, _right, _bottom, _top
+
+
+def close_window():
+ """
+ Closes the current window, and then runs garbage collection. The garbage collection
+ is necessary to prevent crashing when opening/closing windows rapidly (usually during
+ unit tests).
+ """
+ global _window
+
+ _window.close()
+ _window = None
+
+ # Have to do a garbage collection or Python will crash
+ # if we do a lot of window open and closes. Like for
+ # unit tests.
+ gc.collect()
+
+
+def finish_render():
+ """
+ Swap buffers and displays what has been drawn.
+ If programs use derive from the Window class, this function is
+ automatically called.
+ """
+ global _window
+
+ _window.flip()
+
+
+def run():
+ """
+ Run the main loop.
+ After the window has been set up, and the event hooks are in place, this is usually one of the last
+ commands on the main program.
+ """
+ if 'ARCADE_TEST' in os.environ and os.environ['ARCADE_TEST'].upper() == "TRUE":
+ # print("Testing!!!")
+ window = get_window()
+ if window:
+ window.update(1/60)
+ window.on_draw()
+ else:
+ pyglet.app.run()
+
+
+def quick_run(time_to_pause: Number):
+ """
+ Only run the application for the specified time in seconds.
+ Useful for unit testing or continuous integration (CI) testing
+ where there is no user interaction.
+
+ :param Number time_to_pause: Number of seconds to pause before automatically
+ closing.
+
+ """
+ pause(time_to_pause)
+ close_window()
+
+
+def start_render():
+ """
+ Get set up to render. Required to be called before drawing anything to the
+ screen.
+ """
+ gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+ # gl.glMatrixMode(gl.GL_MODELVIEW)
+ # gl.glEnableClientState(gl.GL_VERTEX_ARRAY)
+
+
+def set_background_color(color: Color):
+ """
+ This specifies the background color of the window.
+
+ :param Color color: List of 3 or 4 bytes in RGB/RGBA format.
+ """
+
+ gl.glClearColor(color[0]/255, color[1]/255, color[2]/255, 1)
+
+
+def schedule(function_pointer: Callable, interval: Number):
+ """
+ Schedule a function to be automatically called every ``interval``
+ seconds.
+
+ :param Callable function_pointer: Pointer to the function to be called.
+ :param Number interval: Interval to call the function.
+ """
+ pyglet.clock.schedule_interval(function_pointer, interval)
+
+
+def unschedule(function_pointer: Callable):
+ """
+ Unschedule a function being automatically called.
+
+ :param Callable function_pointer: Pointer to the function to be unscheduled.
+ """
+ pyglet.clock.unschedule(function_pointer)
diff --git a/requirements.txt b/requirements.txt
index ab9354e..47550e4 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1 +1,2 @@
-arcade
+Pyglet>=1.4.2
+pyglet-ffmpeg2
diff --git a/tetrarcade.py b/tetrarcade.py
index 090c9c1..21e8e1c 100644
--- a/tetrarcade.py
+++ b/tetrarcade.py
@@ -24,6 +24,7 @@ WINDOW_TITLE = "TETRARCADE"
TEXT_COLOR = arcade.color.BUBBLES
FONT_NAME = "joystix monospace.ttf"
TEXT_MARGIN = 20
+FONT_SIZE = 8
# Sprites paths
WINDOW_BG = "images/bg.jpg"
@@ -692,11 +693,11 @@ class UI(arcade.Window):
self.current_piece_sprites.draw()
self.ghost_piece_sprites.draw()
self.next_pieces_sprites.draw()
- """arcade.render_text(
+ arcade.render_text(
self.text,
self.matrix_sprite.left - TEXT_MARGIN,
self.matrix_sprite.bottom
- )"""
+ )
def clock(self, delta_time=0):
self.game.time += 1
@@ -705,36 +706,24 @@ class UI(arcade.Window):
def update_text(self):
t = time.localtime(self.game.time)
text = """
-score
-{:>16n}
-high score
-{:>16n}
-time
- {:02d}:{:02d}:{:02d}
-level
-{:>16n}
-lines
-{:>16n}
-goal
-{:>16n}
+score{:>13n}
+high score{:>8n}
+time {:02d}:{:02d}:{:02d}
+leveL{:>13n}
+lines{:>13n}
+goal{:>14n}
-move left
- ←
-move right
- →
-soft drop
- ↓
-hard drop
- space
-rotate clockwise
- ↑
-rotate
+
+move left ←
+move right →
+soft drop ↓
+hard drop SPACE
+rotate ↑
+clockwise
+rotate Z
counterclockwise
- Z
-hold
- C
-pause
- escape""".format(
+hold C
+pause ESC""".format(
self.game.score,
self.game.high_score,
t.tm_hour-1, t.tm_min, t.tm_sec,
@@ -742,64 +731,13 @@ pause
self.game.nb_lines,
self.game.goal
)
- """self.text = arcade.create_text(
+ self.text = arcade.create_text(
text = text,
color = TEXT_COLOR,
- font_size = 8,
+ font_size = FONT_SIZE,
font_name = FONT_NAME,
anchor_x = 'right'
- )"""
- print("""
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-score {:>16n}
-high score {:>16n}
-time {:02d}:{:02d}:{:02d}
-level {:>16n}
-lines {:>16n}
-goal {:>16n}""".format(
- self.game.score,
- self.game.high_score,
- t.tm_hour-1, t.tm_min, t.tm_sec,
- self.game.level,
- self.game.nb_lines,
- self.game.goal
)
- )
-
update_score = update_text
def update_matrix(self):