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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eJxtkEsOgCAMRLmN5+CzNuGzceMBVIT7ryzJa0KIi5d2YEpHgzHGCYl6CV3wYH9qEF5qE25mdN6iA7qiI9487fT4duHEVznzE453N+HgfujCO1obvWXG4Y30mtNPnkzOTL7hf7iLyzdqrsZMXPZ1+sY/KvgTWndrpoo/c/4BPasgKg== + + + + + eJybxcDAUATFM5HYRWjiJQwIsJMBOyiFqpuIxC9Hws1QejaaeaUkmIdPHQxspdC8nSS6jxh1c4C4GIpnI7GL0cQBoyMZ0g== + + + + + eJx7wUB98IBC/d/R+GcpNG8oAQAw7AON + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + 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 @@ + + + + + + eJxjYKAOYKSSObQ2D5e53ATE0fURMg+bWkJi2MSxuYuQedw4xHEBYtxHSvhRwzxSAcwcDiDmpBLmAmIAlSABAg== + + + 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 @@ + + + + + + eJy9lMEKwDAIQ4Vetv3/B48dChJmjAoVeli1fVZdlpmtQyuzEwxkdfL57A5W1Rgj8kW8C3zPgIG87rvYW3G/yqn2XDVfQ6z59L79rc4wcqN5q/bV38d6haYyonNqDTsMI/tdRtb3qB9+fjyvq10TDc3yVhn4T2TxfxpSOcMYai5Mm9WZrcQxH+oz0wEWx3wvd0cEsw== + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,4,0,0,0,0,0,0,0,2,3,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +1,0,0,0,2,3,4,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,2,3,4,0,0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,1, +1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,1,1,1,1,1,0,0,0,0,1, +2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4 + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,4,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0, +1,0,0,0,0,0,0,0,0,0,0,0,2,3,4,0,0,2,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,4,0,0,0,0,0,0,0,0,0,0, +1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,4,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,0,0,0,2,3,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,4,0,0,0,0,0,0,1,1,0,0,0,0,10,3,3,3,9,0,0,0,0, +1,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,10,8,11,11,11,7,9,0,0,0, +1,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,10,8,11,11,11,11,11,7,9,0,0, +2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,8,11,11,11,11,11,11,11,7,3,4 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,6,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + 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 @@ + + + + + + eJxjYmBgYCISEwLI6hiJVIcOGIlUR6x5hNRh00fIPEYs6ojxLwcBDFIDAEJ8AH8= + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eJxjYKAMsCNhWgBamUst83PR+LVAXAfE9RSaOxgByG8gf7EhYXL8ix5mtAYgN1bT2I58IC5lgIQFpfE/FNIQteNQCoilgVhmgM0YaoCa4UaqOUZAzESBvcSYr0MlOxjxmK9KBTsYsdiBbD4IGwOxCZUxsvkgDADk9hI/ + + + + + eJxjYBi+YDGRYsMZjDT/0hoMhfAcrG4crO4a7GA03HADWofNYiQ8FAAAmrUHAg== + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eJxjYBgFo2AUjILBA1xxsEcBKnAEYh8odiJC/WhYEgcKgLgQiIvwqBkNy8EBvBkQeYBSjA4oNc8XiG1HMUUYAGcUIUM= + + + + + eJxjYBgFo2DoghUD7YBRMGzBaNqiHRgN2+EDRuNyFNAKAAAbowKh + + + + + eJxjYBgFo2AUjIJRMApGwSgYucAUDY+CgQcAVEcA1Q== + + + + + eJxjYBgFo2AUjIJRMApGwSggHqQMsDlJVLJ/FAxeAACGBgEr + + + + + eJxjYBgFo2AUjIJRMApGwSjADfzorG8UjAIAeE8AnQ== + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eJxjYBgFo2AUjIJRMApGwXACnWj8bijuGAC3oAOQ25qR+CB31UBxEwPxbpwLxPPw4PlIaueRoK+GCIxPP8xuALbUGsc= + + + + + eJxjYBgFo2AUjIJRMApGwSggH/BSWd1wAAAjYAAb + + + + + eJxjYBgFo2AUjIJRMApGwXADS5HwYARLkPBwBACPMAPc + + + + + eJxjYBgFo2AUjIJRMApGwSjAD6pppHa4AgAYawD3 + + + + + eJxjYBgFo2AUjIJRMApGwSgYfqAXitHZ9AAACP8CNQ== + + + 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 @@ + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,13,13,14,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,97,108,108,108,108,108,98,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,97,108,98,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,16,18,18,18,15,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,16,10,3,3,3,3,0,0,0,0,0, +0,0,0,0,0,0,0,0,16,10,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,16,10,3,0,0,0,0,0,0,0,0,0,0, +17,18,18,18,18,18,18,10,3,3,3,3,3,3,3,18,18,18,18,19 + + + + + + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,111,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,110,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,110,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,110,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,110,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,110,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,42,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,40,0,0,0,0,0,0,38,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,38,38,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,38,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,38,40,39,0,0,0,0,42,0,0,39,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,60,0,0,60,0,0,0,0,0, +0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + 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):