diff --git a/Debris/__pycache__/spgl.cpython-35.pyc b/Debris/__pycache__/spgl.cpython-35.pyc new file mode 100644 index 0000000..8e9612a Binary files /dev/null and b/Debris/__pycache__/spgl.cpython-35.pyc differ diff --git a/Debris/debris.py b/Debris/debris.py new file mode 100644 index 0000000..3a28d3a --- /dev/null +++ b/Debris/debris.py @@ -0,0 +1,267 @@ +# Debris by /u/wynand1004 AKA @TokyoEdTech +# Requires SPGL Version 0.8 +# SPGL Documentation on Github: https://wynand1004.github.io/SPGL +# +# How to Play +# Navigate using the arrow keys +# Shoot your missile using the space bar +# Use the ESC key to end the game + +# The object of the game is to shoot as many objects as possible for points. +# Colliding with an object results in points lost + +# Green objects are worth +10 / -10 points +# Yellow objects are worth +5 / -5 points +# Red objects are worth +20 / -20 points + +# Sound effects courtesy of http://www.freesfx.co.uk + +#Import SPGL +import spgl +import random +import math + +# Create Classes +class Player(spgl.Sprite): + def __init__(self, shape, color, x, y): + spgl.Sprite.__init__(self, shape, color, x, y) + self.shapesize(stretch_wid=0.6, stretch_len=1.1, outline=None) + self.speed = 3 + self.score = 0 + self.thrust = 1 + self.dx = 0 + self.dy = 0 + self.rotation_speed = 0 + + def tick(self): + self.move() + + def move(self): + player.goto(player.xcor()+self.dx, player.ycor()+self.dy) + + if self.xcor() > game.SCREEN_WIDTH / 2: + self.goto(-game.SCREEN_WIDTH / 2, self.ycor()) + + if self.xcor() < -game.SCREEN_WIDTH /2 : + self.goto(game.SCREEN_WIDTH / 2, self.ycor()) + + if self.ycor() > game.SCREEN_HEIGHT / 2: + self.goto(self.xcor(), -game.SCREEN_HEIGHT / 2) + + if self.ycor() < -game.SCREEN_HEIGHT / 2: + self.goto(self.xcor(), game.SCREEN_HEIGHT / 2) + + def rotate_left(self): + self.rotation_speed = 30 + h = self.heading() + self.rotation_speed + player.setheading(h) + + def rotate_right(self): + self.rotation_speed = -30 + h = self.heading() + self.rotation_speed + player.setheading(h) + + def accelerate(self): + h = player.heading() + self.dx += math.cos(h*math.pi/180)*self.thrust + self.dy += math.sin(h*math.pi/180)*self.thrust + +class Orb(spgl.Sprite): + def __init__(self, shape, color, x, y): + spgl.Sprite.__init__(self, shape, color, x, y) + self.speed = 2 + self.setheading(random.randint(0,360)) + self.turn = 0 + + def tick(self): + self.move() + if random.randint(0, 100) < 5: + self.clear() + + def move(self): + self.rt(random.randint(-10, 10)) + self.fd(self.speed) + + if self.xcor() > game.SCREEN_WIDTH / 2: + self.goto(-game.SCREEN_WIDTH / 2, self.ycor()) + + if self.xcor() < -game.SCREEN_WIDTH / 2: + self.goto(game.SCREEN_WIDTH / 2, self.ycor()) + + if self.ycor() > game.SCREEN_HEIGHT / 2: + self.goto(self.xcor(), -game.SCREEN_HEIGHT / 2) + + if self.ycor() < -game.SCREEN_HEIGHT / 2: + self.goto(self.xcor(), game.SCREEN_HEIGHT / 2) + +class Particle(spgl.Sprite): + def __init__(self, spriteshape, color): + spgl.Sprite.__init__(self, shape, color, 1000, 1000) + self.shapesize(stretch_wid=0.1, stretch_len=0.1, outline=None) + self.goto(-1000,-1000) + self.frame = 0.0 + self.max_frame = random.randint(5, 20) + self.state = "ready" + + def tick(self): + if self.frame != 0: + self.fd(self.myspeed) + self.frame += 1 + + if self.frame > self.max_frame: + self.goto(1000, 1000) + self.frame = 0 + self.state = "ready" + + def explode(self, startx, starty): + if self.state == "ready": + self.goto(startx,starty) + self.setheading(random.randint(0,360)) + self.frame = 1.0 + self.myspeed = random.randint(3, 10) + self.state = "exploding" + +class Explosion(object): + def __init__(self): + self.particles = [] + for _ in range(30): + color = random.choice(["red", "yellow", "orange"]) + self.particles.append(Particle("circle", color)) + + def explode(self, x, y): + for particle in self.particles: + particle.explode(x, y) + +class Missile(spgl.Sprite): + def __init__(self, spriteshape, color, startx, starty): + spgl.Sprite.__init__(self, spriteshape, color, startx, starty) + self.shapesize(stretch_wid=0.2, stretch_len=0.4, outline=None) + self.speed = 20 + self.status = "ready" + self.goto(-1000, 1000) + + def fire(self): + if self.status == "ready": + game.play_sound("fire_missile.wav") + self.goto(player.xcor(), player.ycor()) + self.setheading(player.heading()) + self.status = "firing" + + def tick(self): + if self.status == "ready": + self.goto(-1000, 1000) + + elif self.status == "firing": + self.fd(self.speed) + + #Border check + if self.xcor() < -game.SCREEN_WIDTH / 2 or self.xcor() > game.SCREEN_WIDTH / 2 or \ + self.ycor()< -game.SCREEN_HEIGHT / 2 or self.ycor()> game.SCREEN_HEIGHT / 2: + self.goto(-1000,1000) + self.status = "ready" + +# Initial Game setup +game = spgl.Game(800, 600, "black", "Debris (SPGL Demo) by @TokyoEdTech", 5) + +# Game attributes +game.highscore = 0 + +# Load high score +highscore = game.load_data("highscore") +if highscore: + game.highscore = highscore +else: + game.highscore = 0 + +# Create Sprites +# Create Player +player = Player("triangle", "white", -400, 0) + +# Create Orbs +for i in range(75): + color = random.choice(["red", "yellow", "green"]) + shape = random.choice(["circle", "square", "triangle", "arrow"]) + orb = Orb(shape, color, 0, 0) + speed = random.randint(1, 5) + orb.speed = speed + +# Create Explosion +explosion = Explosion() + +# Create Missile +missile = Missile("triangle", "white", 1000, 1000) + +# Create Labels +score_label = spgl.Label("Score: 0 Highscore: {}".format(game.highscore), "white", -380, 280) + +# Create Buttons + +# Set Keyboard Bindings +game.set_keyboard_binding(spgl.KEY_UP, player.accelerate) +game.set_keyboard_binding(spgl.KEY_LEFT, player.rotate_left) +game.set_keyboard_binding(spgl.KEY_RIGHT, player.rotate_right) +game.set_keyboard_binding(spgl.KEY_SPACE, missile.fire) +game.set_keyboard_binding(spgl.KEY_ESCAPE, game.exit) + +# Set background image +game.set_background("starfield.gif") + +while True: + # Call the game tick method + game.tick() + + # Put your game logic here + for sprite in game.sprites: + # Check collisions with Orbs + if sprite.state and isinstance(sprite, Orb): + + # Collision with the missile + if game.is_collision(sprite, missile): + game.play_sound("missile_collides.wav") + + middle_x = (sprite.xcor() + missile.xcor()) / 2 + middle_y = (sprite.ycor() + missile.ycor()) / 2 + + explosion.explode(middle_x, middle_y) + sprite.destroy() + missile.goto(1000, 1000) + missile.status = "ready" + + # Update Score + if sprite.pencolor() == "red": + player.score += 20 + if sprite.pencolor() == "green": + player.score += 10 + if sprite.pencolor() == "yellow": + player.score += 5 + + # Collision with the player + if game.is_collision(sprite, player): + game.play_sound("player_collides.wav") + + middle_x = (sprite.xcor() + player.xcor()) / 2 + middle_y = (sprite.ycor() + player.ycor()) / 2 + + explosion.explode(middle_x, middle_y) + sprite.destroy() + + # Update Score + if sprite.pencolor() == "red": + player.score -= 20 + if sprite.pencolor() == "green": + player.score -= 10 + if sprite.pencolor() == "yellow": + player.score -= 5 + + # Update High Score + if player.score > game.highscore: + game.highscore = player.score + game.save_data("highscore", game.highscore) + + # Update the Game Score, High Score, and Player Speed + speed_string = "-" * int(player.speed) + score_label.update("Score: {} High Score: {} Speed: {}".format(player.score, game.highscore, speed_string)) + + # Show game info in terminal + game.clear_terminal_screen() + game.print_game_info() diff --git a/Debris/fire_missile.wav b/Debris/fire_missile.wav new file mode 100644 index 0000000..6abeab1 Binary files /dev/null and b/Debris/fire_missile.wav differ diff --git a/Debris/game.dat b/Debris/game.dat new file mode 100644 index 0000000..e7f43c6 Binary files /dev/null and b/Debris/game.dat differ diff --git a/Debris/missile_collides.wav b/Debris/missile_collides.wav new file mode 100644 index 0000000..8275bdb Binary files /dev/null and b/Debris/missile_collides.wav differ diff --git a/Debris/player_collides.wav b/Debris/player_collides.wav new file mode 100644 index 0000000..8dde086 Binary files /dev/null and b/Debris/player_collides.wav differ diff --git a/Debris/spgl.py b/Debris/spgl.py new file mode 100644 index 0000000..5834d81 --- /dev/null +++ b/Debris/spgl.py @@ -0,0 +1,518 @@ +# Simple Python Game Library Version 0.8.3 by /u/wynand1004 AKA @TokyoEdTech +# Documentation on Github: https://wynand1004.github.io/SPGL +# Python 2.x and 3.x Compatible + +import os +import turtle +import time +import random +import math +import pickle +import platform + +# Import message box +# This code is necessary for Python 2.x and 3.x compatibility +try: + import tkMessageBox as messagebox +except: + from tkinter import messagebox + +# Import filedialog +try: + from tkinter import filedialog +except: + import tkFileDialog as filedialog + + +# If on Windows, import winsound or, better yet, switch to Linux! +if platform.system() == "Windows": + try: + import winsound + except: + print ("Winsound module not available.") + + +# Use for Keyboard Bindings +KEY_UP = "Up" +KEY_DOWN = "Down" +KEY_LEFT = "Left" +KEY_RIGHT = "Right" +KEY_SPACE = "space" +KEY_ESCAPE = "Escape" +KEY_ENTER = "Return" +KEY_RETURN = "Return" +KEY_SHIFT_LEFT = "Shift_L" +KEY_SHIFT_RIGHT = "Shift_R" +KEY_CONTROL_LEFT = "Control_L" +KEY_CONTROL_RIGHT = "Control_R" +KEY_ALT_LEFT = "Alt_L" +KEY_ALT_RIGHT = "Alt_R" +KEY_CAPS_LOCK = "Caps_Lock" +KEY_F1 = "F1" +KEY_F2 = "F2" +KEY_F3 = "F3" +KEY_F4 = "F4" +KEY_F5 = "F5" +KEY_F6 = "F6" +KEY_F7 = "F7" +KEY_F8 = "F8" +KEY_F9 = "F9" +KEY_F10 = "F10" +KEY_F11 = "F11" +KEY_F12 = "F12" + +# Game Class +class Game(object): + + # Keep List of Sprites + sprites = [] + + # Keep List of Labels + labels = [] + + # Keep List of Buttons + buttons = [] + + # Logs + logs = [] + + def __init__( + self, + screen_width = 800, + screen_height = 600, + background_color = "black", + title = "Simple Game Library by /u/wynand1004 AKA @TokyoEdTech", + splash_time = 3): + + # Setup using Turtle module methods + turtle.setup(width=screen_width, height=screen_height) + turtle.bgcolor(background_color) + turtle.title(title) + turtle.tracer(0) # Stop automatic screen refresh + turtle.listen() # Listen for keyboard input + turtle.hideturtle() # Hides default turtle + turtle.penup() # Puts pen up for defaut turtle + turtle.setundobuffer(0) # Do not keep turtle history in memory + turtle.onscreenclick(self.click) + + # Game Attributes + self.FPS = 30.0 # Lower this on slower computers or with large number of sprites + self.SCREEN_WIDTH = screen_width + self.SCREEN_HEIGHT = screen_height + self.DATAFILE = "game.dat" + self.SPLASHFILE = "splash.gif" # Must be in the same folder as game file + + self.title = title + self.gravity = 0 + self.state = "showsplash" + self.splash_time = splash_time + + self.time = time.time() + + # Clear the terminal and print the game title + self.clear_terminal_screen() + print (self.title) + + # Show splash + self.show_splash(self.splash_time) + + # Pop ups + def ask_yes_no(self, title, message): + return messagebox.askyesno(title, message) + + def show_info(self, title, message): + return messagebox.showinfo(title, message) + + def show_warning(self, title, message): + return messagebox.showwarning(title, message) + + def show_error(self, title, message): + return messagebox.showerror(title, message) + + def ask_question(self, title, message): + return messagebox.askquestion(title, message) + + def ask_ok_cancel(self, title, message): + return messagebox.askokcancel(title, message) + + def ask_retry_cancel(self, title, message): + return messagebox.askretrycancel(title, message) + + def ask_open_filename(self): + return filedialog.askopenfilename() + + def print_error_logs(self): + print ("Error Logs:") + + if len(Game.logs) == 0: + print ("No errors") + else: + for error in Game.logs: + print (error) + + + print ("") + + def tick(self): + # Check the game state + # showsplash, running, gameover, paused + + if self.state == "showsplash": + self.show_splash(self.splash_time) + + elif self.state == "paused": + pass + + elif self.state == "gameover": + pass + + else: + # Iterate through all sprites and call their tick method + for sprite in Game.sprites: + if sprite.state: + sprite.tick() + + # Iterate through all labels and call their update method + for label in Game.labels: + if label.text != "": + label.tick() + + # Update the screen + self.update_screen() + + def click(self, x, y): + print ("The window was clicked at ({},{})".format(x, y)) + + def show_splash(self, seconds): + # Show splash screen + # To be implemented + + try: + # Load self.SPLASHFILE + turtle.bgpic(self.SPLASHFILE) + + self.update_screen() + + # Pause + self.time = time.time() + while time.time() < self.time + (self.splash_time): + pass + + # Hide Splash + turtle.bgpic("") + + except: + Game.logs.append("Warning: {} missing from disk.".format(self.SPLASHFILE)) + + # Change state to running + self.state = "running" + + def destroy_all_sprites(self): + for sprite in Game.sprites: + if sprite.state: + sprite.destroy() + + def save_data(self, key, value): + # Load DATAFILE + try: + data = pickle.load(open(self.DATAFILE, "rb")) + except: + data = {} + Game.logs.append("Warning: Creating new {} file on disk.".format(self.DATAFILE)) + + data[key] = value + + #Save DATAFILE + pickle.dump(data, open(self.DATAFILE, "wb")) + + def load_data(self, key): + # Load DATAFILE + try: + data = pickle.load(open(self.DATAFILE, "rb")) + except: + data = {} + Game.logs.append("Warning: {} missing from disk.".format(self.DATAFILE)) + + if key in data: + return data[key] + else: + return None + + def set_title(self, title): + turtle.title(title) + self.title = title + + def set_keyboard_binding(self, key, function): + #Python 3 + try: + turtle.onkeypress(function, key) + #Python 2 + except: + turtle.onkey(function, key) + + def update_screen(self): + while time.time() < self.time + (1.0 / self.FPS): + pass + turtle.update() + self.time = time.time() + + def play_sound(self, sound_file, time = 0): + # Windows + if platform.system() == 'Windows': + winsound.PlaySound(sound_file, winsound.SND_ASYNC) + # Linux + elif platform.system() == "Linux": + os.system("aplay -q {}&".format(sound_file)) + # Mac + else: + os.system("afplay {}&".format(sound_file)) + + if time > 0: + turtle.ontimer(lambda: self.play_sound(sound_file, time), t=int(time * 1000)) + + def stop_all_sounds(self): + # Windows + if platform.system() == 'Windows': + Game.logs.append("Warning: .stop_all_sounds not implemened on Windows yet.") + # Linux + elif platform.system() == "Linux": + os.system("killall aplay") + # Mac + else: + os.system("killall afplay") + + def clear_terminal_screen(self): + # Windows + if platform.system() == 'Windows': + os.system("cls") + # Linux and Mac + else: + os.system("clear") + + def print_game_info(self): + print (self.title) + print ("") + print ("Window Dimensions: {}x{}".format(self.SCREEN_WIDTH, self.SCREEN_HEIGHT)) + print ("") + + # Calcuate number of active sprites + active_sprites = 0 + for sprite in Game.sprites: + if sprite.state: + active_sprites += 1 + + print ("Number of Sprites (Active / Total): {} / {}".format(active_sprites, len(Game.sprites))) + + print ("Number of Labels: {}".format(len(Game.labels))) + print ("Number of Buttons: {}".format(len(Game.buttons))) + print ("") + print ("Frames Per Second (Target): {}".format(self.FPS)) + print ("") + self.print_error_logs() + + def is_collision(self, sprite_1, sprite_2): + # Axis Aligned Bounding Box + x_collision = (math.fabs(sprite_1.xcor() - sprite_2.xcor()) * 2) < (sprite_1.width + sprite_2.width) + y_collision = (math.fabs(sprite_1.ycor() - sprite_2.ycor()) * 2) < (sprite_1.height + sprite_2.height) + return (x_collision and y_collision) + + def is_circle_collision(sprite_1, sprite_2, distance): + # Collision based on distance + a=sprite_1.xcor()-sprite_2.xcor() + b=sprite_1.ycor()-sprite_2.ycor() + distance = math.sqrt((a**2) + (b**2)) + + if distance < distance: + return True + else: + return False + + def show_game_over(self): + self.state = "gameover" + self.hide_all_sprites() + print ("Game Over!") + self.state = "paused" + + def set_background(self, image): + if image.endswith(".gif"): + turtle.bgpic(image) + else: + Game.logs.append("Warning: Background image {} must be a gif.".format(image)) + + def exit(self): + self.stop_all_sounds() + os._exit(0) + +# Sprite Class +class Sprite(turtle.Turtle): + def __init__(self, + shape, + color, + x = 0, + y = 0, + width = 20, + height = 20): + + turtle.Turtle.__init__(self) + self.speed(0) # Animation Speed + # Register shape if it is a .gif file + if shape.endswith(".gif"): + try: + turtle.register_shape(shape) + except: + Game.logs.append("Warning: {} file missing from disk.".format(shape)) + + # Set placeholder shape + shape = "square" + width = 20 # This is the default for turtle module primitives + height = 20 # This is the default for turtle module primitives + + self.shape(shape) + self.color(color) + self.penup() + self.goto(x, y) + + # Attributes + self.width = width + self.height = width + + self.speed = 0.0 # Speed of motion + self.dx = 0.0 + self.dy = 0.0 + self.acceleration = 0.0 + self.friction = 0.0 + + self.state = "active" + self.solid = True + + #Set click binding + self.onclick(self.click) + + # Append to master sprite list + Game.sprites.append(self) + + def tick(self): + # This is the function that is called each frame of the game + # For most sprites, you'll want to call the move method here + # self.move() + pass + + def move(self): + self.fd(self.speed) + + def destroy(self): + # When a sprite is destoyed move it off screen, hide it, and set state to None + # This is a workaround as there is no way to delete a sprite from memory in the turtle module. + self.hideturtle() + self.goto(10000, 10000) + self.state = None + + def set_image(self, image, width, height): + # Allows the use of custom images (must be .gif) due to turtle/tkinter limitation + # Register shape if it is a .gif file + if image.endswith(".gif"): + try: + turtle.register_shape(image) + except: + Game.logs.append("Warning: {} file missing from disk.".format(image)) + + # Set placeholder shape + shape = "square" + width = 20 # This is the default for turtle module primitives + height = 20 # This is the default for turtle module primitives + + self.shape(image) + self.width = width + self.height = height + + # Click binding needs to be set again after image change + self.onclick(self.click) + + def click(self, x, y): + print ("The sprite was clicked at ({},{})".format(x, y)) + +#Label Class +class Label(turtle.Turtle): + def __init__(self, + text, + color, + x = 0, + y = 0, + font_name = "Arial", + font_size = 12, + font_type = "normal", + align = "left"): + + turtle.Turtle.__init__(self) + self.hideturtle() + self.penup() + self.goto(x, y) + self.color(color) + self.font = (font_name, font_size, font_type) + self.align = align + + # Attributes + self.text = text + + + # Append to master label list + Game.labels.append(self) + + def tick(self): + self.clear() + self.write(self.text, False, align =self.align, font = self.font) + + def update(self, text): + self.text = text + self.tick() + +#Button Class +class Button(turtle.Turtle): + def __init__(self, + shape, + color, + x = 0, + y = 0): + + turtle.Turtle.__init__(self) + # self.hideturtle() + self.penup() + # Register shape if it is a .gif file + if shape.endswith(".gif"): + try: + turtle.register_shape(shape) + except: + Game.logs.append("Warning: {} file missing from disk.".format(shape)) + + # Set placeholder shape + shape = "square" + + self.shape(shape) + self.color(color) + self.goto(x, y) + + #Set click binding + self.onclick(self.click) + + # Append to master button list + Game.buttons.append(self) + + def set_image(self, image): + # Register shape if it is a .gif file + if shape.endswith(".gif"): + try: + turtle.register_shape(shape) + except: + Game.logs.append("Warning: {} file missing from disk.".format(shape)) + + # Set placeholder shape + shape = "square" + + # Allows the use of custom images (must be .gif) due to turtle/tkinter limitation + self.shape(image) + + # Click binding needs to be set again after image change + self.onclick(self.click) + + def click(self, x, y): + print ("The button was clicked at ({},{})".format(x, y)) diff --git a/Debris/splash.gif b/Debris/splash.gif new file mode 100644 index 0000000..4d839bf Binary files /dev/null and b/Debris/splash.gif differ diff --git a/Debris/splash.xcf b/Debris/splash.xcf new file mode 100644 index 0000000..b8a34a0 Binary files /dev/null and b/Debris/splash.xcf differ diff --git a/Debris/starfield.gif b/Debris/starfield.gif new file mode 100644 index 0000000..f0cf758 Binary files /dev/null and b/Debris/starfield.gif differ diff --git a/Debris/starfield.xcf b/Debris/starfield.xcf new file mode 100644 index 0000000..e666781 Binary files /dev/null and b/Debris/starfield.xcf differ diff --git a/README.md b/README.md index dd3d645..c89b325 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,37 @@ -# SGE (Version 0.5) +# SPGL (Version 0.8.6) ## Overview: -The purpose of the Simple Game Engine is to give beginning Python coders a simple framework to make basic 2D games. It is intended as a simple alternative to Pygame. As it is built on the Turtle module, it has the same features and limitations of that module. It does not require any external libraries to be added. +The purpose of the Simple Python Game Library is to give beginning Python coders a simple framework to make basic 2D games. It is intended as an alternative to Pygame. As it is built on the Turtle module, it has the same features and limitations of that module. It does not require any external libraries to be added. **Design Principles** - - Use only built-in modules + - Use only standard modules - Python 2.x and 3.x compatibility - Cross-platform (Linux, Mac, and Windows) ## Getting Started -Download the repository to your computer. There are 3 demo files, SGE_Demo.py which is a simple game using primitives (triangles and circles). Use the left arrow, right arrow, and up arrow to control the player. There is also SGE_Minimal_Example.py contains a simple game framework which will create a window - use this as the basis of your own programs. Finally, there is SGE_Space_Invaders.py, a simple demo I am working on to test SGE features. **Note:** I used tabs, not spaces. +Download the repository to your computer. There are 2 demo files, SPGL_Demo.py which is a simple game using primitives (squares, triangles, arrows, and circles). Use the arrow keys to control the player. There is also SPGL_Minimal_Example.py which contains a simple game framework which will create a window - use this as the basis of your own programs. ## Performance -I have tested this on Linux only at this point. I know from experience that the turtle module on Mac is slower. On my Linux machine, I can easily get 100 sprites moving around the screen at 30 frames per second. See the SGE_Demo.py program for an example. +On my Linux machine, I can easily get 100 sprites moving around the screen at 30 frames per second. See the [SPGL_Demo2.py](https://github.com/wynand1004/SPGL/blob/master/SPGL_Demo2.py) program for an example. ## Known Issues -Image files need to be in .gif format. This is a limitation of the turtle module. + - Image files need to be in .gif format. This is a limitation of the turtle module. -Sound files should be in .wav format for widest compatibility. + - Sound files should be in .wav format for widest compatibility. Compatibility with other formats (.ogg, .mp3, etc.) will vary widely based on the operating system and installed codecs. -On Windows, SGE uses the winsound module to play sounds. I have not tested this yet as I do not have access to Windows at the moment. +## Documentation -## Documentation Available on the [Wiki](https://github.com/wynand1004/SGE/wiki) + - Available on the [Wiki](https://github.com/wynand1004/SPGL/wiki) -Follow me on Twitter [@tokyoedtech](https://twitter.com/tokyoedtech) +## Connect with Me -Various tutorials available on my [YouTube Channel](https://www.youtube.com/channel/UC2vm-0XX5RkWCXWwtBZGOXg) + - Subscribe to my [YouTube Channel](https://www.youtube.com/channel/UC2vm-0XX5RkWCXWwtBZGOXg). + + - Follow me on Twitter - [@tokyoedtech](https://twitter.com/tokyoedtech). + + - Check out my [blog](http://www.christianthompson.com/). diff --git a/SPGL.py b/SPGL.py deleted file mode 100644 index 2a2aa5c..0000000 --- a/SPGL.py +++ /dev/null @@ -1,420 +0,0 @@ -# Simple Python Game Library Version 0.6 by /u/wynand1004 AKA @TokyoEdTech -# Documentation on Github: https://wynand1004.github.io/SPGL -# Python 2.x and 3.x Compatible - -import os -import turtle -import time -import random -import math -import pickle - -# Import message box -# This code is necessary for Python 2.x and 3.x compatibility -try: - import tkMessageBox as messagebox -except: - from tkinter import messagebox - -# If on Windows, import winsound or, better yet, switch to Linux! -if os.name == "nt": - try: - import winsound - except: - print ("Winsound module not available.") - -# SPGL Class -class SPGL(object): - - # Class Constants - # Use for Keyboard Bindings - KEY_UP = "Up" - KEY_DOWN = "Down" - KEY_LEFT = "Left" - KEY_RIGHT = "Right" - KEY_SPACE = "space" - KEY_ESCAPE = "Escape" - - # Keep List of Sprites - sprites = [] - - # Keep List of Labels - labels = [] - - # Keep List of Buttons - buttons = [] - - # Logs - logs = [] - - def __init__( - self, - screen_width = 800, - screen_height = 600, - background_color = "black", - title = "Simple Game Engine by /u/wynand1004 AKA @TokyoEdTech", - splash_time = 3): - - # Setup using Turtle module methods - turtle.setup(width=screen_width, height=screen_height) - turtle.bgcolor(background_color) - turtle.title(title) - turtle.tracer(0) # Stop automatic screen refresh - turtle.listen() # Listen for keyboard input - turtle.hideturtle() # Hides default turtle - turtle.penup() # Puts pen up for defaut turtle - turtle.setundobuffer(0) # Do not keep turtle history in memory - turtle.onscreenclick(self.click) - - # Game Attributes - self.FPS = 30.0 # Lower this on slower computers or with large number of sprites - self.SCREEN_WIDTH = screen_width - self.SCREEN_HEIGHT = screen_height - self.DATAFILE = "game.dat" - self.SPLASHFILE = "splash.gif" # Must be in the same folder as game file - - self.title = title - self.gravity = 0 - self.state = "showsplash" - self.splash_time = splash_time - - self.time = time.time() - - # Clear the terminal and print the game title - self.clear_terminal_screen() - print (self.title) - - # Show splash - self.show_splash(self.splash_time) - - # Pop ups - def ask_yes_no(self, title, message): - return messagebox.askyesno(title, message) - - def show_info(self, title, message): - return messagebox.showinfo(title, message) - - def show_warning(self, title, message): - return messagebox.showwarning(title, message) - - def print_error_logs(self): - print ("Error Logs:") - for error in SPGL.logs: - print (error) - - if len(SPGL.logs) == 0: - print ("No errors") - print ("") - - def tick(self): - # Check the game state - # showsplash, running, gameover, paused - - if self.state == "showsplash": - self.show_splash(self.splash_time) - - elif self.state == "paused": - pass - - elif self.state == "gameover": - pass - - else: - # Iterate through all sprites and call their tick method - for sprite in SPGL.sprites: - if sprite.state: - sprite.tick() - - # Iterate through all labels and call their update method - for label in SPGL.labels: - if label.text != "": - label.tick() - - # Update the screen - self.update_screen() - - def click(self, x, y): - print ("The window was clicked at ({},{})".format(x, y)) - - def show_splash(self, seconds): - # Show splash screen - # To be implemented - - try: - # Load self.SPLASHFILE - turtle.bgpic(self.SPLASHFILE) - - self.update_screen() - - # Pause - self.time = time.time() - while time.time() < self.time + (self.splash_time): - pass - - # Hide Splash - turtle.bgpic("") - - except: - SPGL.logs.append("Warning: {} missing from disk.".format(self.SPLASHFILE)) - - # Change state to running - self.state = "running" - - def destroy_all_sprites(self): - for sprite in SPGL.sprites: - if sprite.state: - sprite.destroy() - - def save_data(self, key, value): - # Load DATAFILE - try: - data = pickle.load(open(self.DATAFILE, "rb")) - except: - data = {} - SPGL.logs.append("Warning: Creating new {} file on disk.".format(self.DATAFILE)) - - data[key] = value - - #Save DATAFILE - pickle.dump(data, open(self.DATAFILE, "wb")) - - def load_data(self, key): - # Load DATAFILE - try: - data = pickle.load(open(self.DATAFILE, "rb")) - except: - data = {} - SPGL.logs.append("Warning: {} missing from disk.".format(self.DATAFILE)) - - if key in data: - return data[key] - else: - return None - - def set_title(self, title): - turtle.title(title) - self.title = title - - def set_keyboard_binding(self, key, function): - turtle.onkey(function, key) - - def update_screen(self): - while time.time() < self.time + (1.0 / self.FPS): - pass - turtle.update() - self.time = time.time() - - def play_sound(self, sound_file): - # Windows - if os.name == 'nt': - winsound.play(sound_file, winsound.SND_ASYNC) - # Linux - elif os.name == "posix": - os.system("aplay -q {}&".format(sound_file)) - # Mac - else: - os.system("afplay {}&".format(sound_file)) - - def stop_all_sounds(self): - # Windows - if os.name == 'nt': - SPGL.logs.append("Warning: .stop_all_sounds not implemened on Windows yet.") - # Linux - elif os.name == "posix": - os.system("killall aplay") - # Mac - else: - os.system("killall afplay") - - def clear_terminal_screen(self): - # Windows - if os.name == 'nt': - os.system("cls") - # Linux and Mac - else: - os.system("clear") - - def print_game_info(self): - print (self.title) - print ("") - print ("Window Dimensions: {}x{}".format(self.SCREEN_WIDTH, self.SCREEN_HEIGHT)) - print ("") - - # Calcuate number of active sprites - active_sprites = 0 - for sprite in SPGL.sprites: - if sprite.state: - active_sprites += 1 - - print ("Number of Sprites (Active / Total): {} / {}".format(active_sprites, len(SPGL.sprites))) - - print ("Number of Labels: {}".format(len(SPGL.labels))) - print ("Number of Buttons: {}".format(len(SPGL.buttons))) - print ("") - print ("Frames Per Second (Target): {}".format(self.FPS)) - print ("") - self.print_error_logs() - - def is_collision(self, sprite_1, sprite_2): - # Axis Aligned Bounding Box - x_collision = (math.fabs(sprite_1.xcor() - sprite_2.xcor()) * 2) < (sprite_1.width + sprite_2.width) - y_collision = (math.fabs(sprite_1.ycor() - sprite_2.ycor()) * 2) < (sprite_1.height + sprite_2.height) - return (x_collision and y_collision) - - def show_game_over(self): - self.state = "gameover" - self.hide_all_sprites() - print ("Game Over!") - self.state = "paused" - - def exit(self): - self.stop_all_sounds() - os._exit(0) - - # Sprite Class - class Sprite(turtle.Turtle): - def __init__(self, - shape, - color, - x = 0, - y = 0, - width = 20, - height = 20): - - turtle.Turtle.__init__(self) - self.speed(0) # Animation Speed - # Register shape if it is a .gif file - if shape.endswith(".gif"): - try: - turtle.register_shape(shape) - except: - SPGL.logs.append("Warning: {} file missing from disk.".format(shape)) - - # Set placeholder shape - shape = "square" - width = 20 # This is the default for turtle module primitives - height = 20 # This is the default for turtle module primitives - - self.shape(shape) - self.color(color) - self.penup() - self.goto(x, y) - - # Attributes - self.width = width - self.height = width - - self.speed = 0.0 # Speed of motion - self.dx = 0.0 - self.dy = 0.0 - self.acceleration = 0.0 - self.friction = 0.0 - - self.state = "active" - self.solid = True - - # Append to master sprite list - SPGL.sprites.append(self) - - def tick(self): - # This is the function that is called each frame of the game - # For most sprites, you'll want to call the move method here - # self.move() - pass - - def move(self): - self.fd(self.speed) - - def destroy(self): - # When a sprite is destoyed move it off screen, hide it, and set state to None - # This is a workaround as there is no way to delete a sprite from memory in the turtle module. - self.hideturtle() - self.goto(10000, 10000) - self.state = None - - def set_image(self, image, width, height): - # Allows the use of custom images (must be .gif) due to turtle/tkinter limitation - # Register shape if it is a .gif file - if image.endswith(".gif"): - try: - turtle.register_shape(image) - except: - SPGL.logs.append("Warning: {} file missing from disk.".format(image)) - - # Set placeholder shape - shape = "square" - width = 20 # This is the default for turtle module primitives - height = 20 # This is the default for turtle module primitives - - self.shape(image) - self.width = width - self.height = height - - #Label Class - class Label(turtle.Turtle): - def __init__(self, - text, - color, - x = 0, - y = 0): - - turtle.Turtle.__init__(self) - self.hideturtle() - self.penup() - self.goto(x, y) - self.color(color) - - # Attributes - self.text = text - - # Append to master label list - SPGL.labels.append(self) - - def tick(self): - self.clear() - self.write(self.text) - - def update(self, text): - self.text = text - self.tick() - - #Button Class - class Button(turtle.Turtle): - def __init__(self, - shape, - color, - x = 0, - y = 0): - - turtle.Turtle.__init__(self) - # self.hideturtle() - self.penup() - # Register shape if it is a .gif file - if shape.endswith(".gif"): - try: - turtle.register_shape(shape) - except: - SPGL.logs.append("Warning: {} file missing from disk.".format(shape)) - - # Set placeholder shape - shape = "square" - - self.shape(shape) - self.color(color) - self.goto(x, y) - - #Set click binding - self.onclick(self.click) - - # Append to master button list - SPGL.buttons.append(self) - - def set_image(self, image): - # Allows the use of custom images (must be .gif) due to turtle/tkinter limitation - turtle.register_shape(image) - self.shape(image) - # Click binding needs to be set again after image change - self.onclick(self.click) - - def click(self, x, y): - print ("The button was clicked at ({},{})".format(x, y)) diff --git a/SPGL_Demo.py b/SPGL_Demo.py index 97a757e..cf04d38 100644 --- a/SPGL_Demo.py +++ b/SPGL_Demo.py @@ -1,5 +1,5 @@ # SPGL Game Demo by /u/wynand1004 AKA @TokyoEdTech -# Requires SPGL Version 0.6 +# Requires SPGL Version 0.8 # SPGL Documentation on Github: https://wynand1004.github.io/SPGL # # How to Play @@ -9,12 +9,13 @@ # Red objects are worth -10 points #Import SPGL -from SPGL import * +import spgl +import random # Create Classes -class Player(SPGL.Sprite): +class Player(spgl.Sprite): def __init__(self, shape, color, x, y): - SPGL.Sprite.__init__(self, shape, color, x, y) + spgl.Sprite.__init__(self, shape, color, x, y) self.speed = 3 self.score = 0 @@ -50,9 +51,9 @@ def decelerate(self): if self.speed < 0: self.speed = 0 -class Orb(SPGL.Sprite): +class Orb(spgl.Sprite): def __init__(self, shape, color, x, y): - SPGL.Sprite.__init__(self, shape, color, x, y) + spgl.Sprite.__init__(self, shape, color, x, y) self.speed = 2 self.setheading(random.randint(0,360)) self.turn = 0 @@ -79,7 +80,7 @@ def move(self): self.goto(self.xcor(), game.SCREEN_HEIGHT / 2) # Initial Game setup -game = SPGL(800, 600, "black", "SPGL Game Demo by /u/wynand1004 AKA @TokyoEdTech", 5) +game = spgl.Game(800, 600, "black", "SPGL Game Demo by /u/wynand1004 AKA @TokyoEdTech", 0) # Game attributes game.highscore = 0 @@ -104,23 +105,23 @@ def move(self): orb.speed = speed # Create Labels -score_label = SPGL.Label("Score: 0 Highscore: {}".format(game.highscore), "white", -380, 280) +score_label = spgl.Label("Score: 0 Highscore: {}".format(game.highscore), "white", -380, 280) # Create Buttons # Set Keyboard Bindings -game.set_keyboard_binding(SPGL.KEY_UP, player.accelerate) -game.set_keyboard_binding(SPGL.KEY_DOWN, player.decelerate) -game.set_keyboard_binding(SPGL.KEY_LEFT, player.rotate_left) -game.set_keyboard_binding(SPGL.KEY_RIGHT, player.rotate_right) -game.set_keyboard_binding(SPGL.KEY_ESCAPE, game.exit) +game.set_keyboard_binding(spgl.KEY_UP, player.accelerate) +game.set_keyboard_binding(spgl.KEY_DOWN, player.decelerate) +game.set_keyboard_binding(spgl.KEY_LEFT, player.rotate_left) +game.set_keyboard_binding(spgl.KEY_RIGHT, player.rotate_right) +game.set_keyboard_binding(spgl.KEY_ESCAPE, game.exit) while True: # Call the game tick method game.tick() # Put your game logic here - for sprite in SPGL.sprites: + for sprite in game.sprites: # Check collisions with Orbs if sprite.state and isinstance(sprite, Orb): if game.is_collision(sprite, player): diff --git a/SPGL_Demo2.py b/SPGL_Demo2.py new file mode 100644 index 0000000..6baed82 --- /dev/null +++ b/SPGL_Demo2.py @@ -0,0 +1,193 @@ +# SPGL Game Demo 2 by /u/wynand1004 AKA @TokyoEdTech +# Requires SPGL Version 0.8 +# SPGL Documentation on Github: https://wynand1004.github.io/SPGL +# This Demo includes a simple particle system +# +# How to Play +# Navigate using the arrow keys +# Green objects are worth 10 points +# Yellow objects are worth 5 points +# Red objects are worth -10 points + +#Import SPGL +import spgl +import random + +# Create Classes +class Player(spgl.Sprite): + def __init__(self, shape, color, x, y): + spgl.Sprite.__init__(self, shape, color, x, y) + self.speed = 3 + self.score = 0 + + def tick(self): + self.move() + + def move(self): + self.fd(self.speed) + + if self.xcor() > game.SCREEN_WIDTH / 2: + self.goto(-game.SCREEN_WIDTH / 2, self.ycor()) + + if self.xcor() < -game.SCREEN_WIDTH /2 : + self.goto(game.SCREEN_WIDTH / 2, self.ycor()) + + if self.ycor() > game.SCREEN_HEIGHT / 2: + self.goto(self.xcor(), -game.SCREEN_HEIGHT / 2) + + if self.ycor() < -game.SCREEN_HEIGHT / 2: + self.goto(self.xcor(), game.SCREEN_HEIGHT / 2) + + def rotate_left(self): + self.lt(30) + + def rotate_right(self): + self.rt(30) + + def accelerate(self): + self.speed += 1 + + def decelerate(self): + self.speed -= 1 + if self.speed < 0: + self.speed = 0 + +class Orb(spgl.Sprite): + def __init__(self, shape, color, x, y): + spgl.Sprite.__init__(self, shape, color, x, y) + self.speed = 2 + self.setheading(random.randint(0,360)) + self.turn = 0 + + def tick(self): + self.move() + if random.randint(0, 100) < 5: + self.clear() + + def move(self): + self.rt(random.randint(-10, 10)) + self.fd(self.speed) + + if self.xcor() > game.SCREEN_WIDTH / 2: + self.goto(-game.SCREEN_WIDTH / 2, self.ycor()) + + if self.xcor() < -game.SCREEN_WIDTH / 2: + self.goto(game.SCREEN_WIDTH / 2, self.ycor()) + + if self.ycor() > game.SCREEN_HEIGHT / 2: + self.goto(self.xcor(), -game.SCREEN_HEIGHT / 2) + + if self.ycor() < -game.SCREEN_HEIGHT / 2: + self.goto(self.xcor(), game.SCREEN_HEIGHT / 2) + +class Particle(spgl.Sprite): + def __init__(self, spriteshape, color): + spgl.Sprite.__init__(self, shape, color, 1000, 1000) + self.shapesize(stretch_wid=0.1, stretch_len=0.1, outline=None) + self.goto(-1000,-1000) + self.frame = 0.0 + self.max_frame = random.randint(5, 20) + + def tick(self): + if self.frame != 0: + self.fd(self.myspeed) + self.frame += 1 + + if self.frame > self.max_frame: + self.goto(1000, 1000) + self.frame = 0 + + def explode(self, startx, starty): + self.goto(startx,starty) + self.setheading(random.randint(0,360)) + self.frame = 1.0 + self.myspeed = random.randint(3, 10) + +class Explosion(object): + def __init__(self): + self.particles = [] + for _ in range(30): + color = random.choice(["red", "yellow", "orange"]) + self.particles.append(Particle("circle", color)) + + def explode(self, x, y): + for particle in self.particles: + particle.explode(x, y) + +# Initial Game setup +game = spgl.Game(800, 600, "black", "SPGL Game Demo by /u/wynand1004 AKA @TokyoEdTech", 0) + +# Game attributes +game.highscore = 0 + +# Load high score +highscore = game.load_data("highscore") +if highscore: + game.highscore = highscore +else: + game.highscore = 0 + +# Create Sprites +# Create Player +player = Player("triangle", "white", -400, 0) + +# Create Orbs +for i in range(100): + color = random.choice(["red", "yellow", "green"]) + shape = random.choice(["circle", "square", "triangle", "arrow"]) + orb = Orb(shape, color, 0, 0) + speed = random.randint(1, 5) + orb.speed = speed + +# Create Explosion +explosion = Explosion() + +# Create Labels +score_label = spgl.Label("Score: 0 Highscore: {}".format(game.highscore), "white", -380, 280) + +# Create Buttons + +# Set Keyboard Bindings +game.set_keyboard_binding(spgl.KEY_UP, player.accelerate) +game.set_keyboard_binding(spgl.KEY_DOWN, player.decelerate) +game.set_keyboard_binding(spgl.KEY_LEFT, player.rotate_left) +game.set_keyboard_binding(spgl.KEY_RIGHT, player.rotate_right) +game.set_keyboard_binding(spgl.KEY_ESCAPE, game.exit) + +while True: + # Call the game tick method + game.tick() + + # Put your game logic here + for sprite in game.sprites: + # Check collisions with Orbs + if sprite.state and isinstance(sprite, Orb): + if game.is_collision(sprite, player): + game.play_sound("collision.wav") + + middle_x = (sprite.xcor() + player.xcor()) / 2 + middle_y = (sprite.ycor() + player.ycor()) / 2 + + explosion.explode(middle_x, middle_y) + sprite.destroy() + + # Update Score + if sprite.pencolor() == "red": + player.score -= 10 + if sprite.pencolor() == "green": + player.score += 10 + if sprite.pencolor() == "yellow": + player.score += 5 + + # Update High Score + if player.score > game.highscore: + game.highscore = player.score + game.save_data("highscore", game.highscore) + + # Update the Game Score, High Score, and Player Speed + speed_string = "-" * int(player.speed) + score_label.update("Score: {} High Score: {} Speed: {}".format(player.score, game.highscore, speed_string)) + + # Show game info in terminal + game.clear_terminal_screen() + game.print_game_info() diff --git a/SPGL_Demo_Import_Into_Namespace.py b/SPGL_Demo_Import_Into_Namespace.py new file mode 100644 index 0000000..3e8355d --- /dev/null +++ b/SPGL_Demo_Import_Into_Namespace.py @@ -0,0 +1,149 @@ +# SPGL Game Demo by /u/wynand1004 AKA @TokyoEdTech +# Requires SPGL Version 0.8 +# SPGL Documentation on Github: https://wynand1004.github.io/SPGL +# +# How to Play +# Navigate using the arrow keys +# Green objects are worth 10 points +# Yellow objects are worth 5 points +# Red objects are worth -10 points + +#Import SPGL +from spgl import * +import random + +# Create Classes +class Player(Sprite): + def __init__(self, shape, color, x, y): + Sprite.__init__(self, shape, color, x, y) + self.speed = 3 + self.score = 0 + + def tick(self): + self.move() + + def move(self): + self.fd(self.speed) + + if self.xcor() > game.SCREEN_WIDTH / 2: + self.goto(-game.SCREEN_WIDTH / 2, self.ycor()) + + if self.xcor() < -game.SCREEN_WIDTH /2 : + self.goto(game.SCREEN_WIDTH / 2, self.ycor()) + + if self.ycor() > game.SCREEN_HEIGHT / 2: + self.goto(self.xcor(), -game.SCREEN_HEIGHT / 2) + + if self.ycor() < -game.SCREEN_HEIGHT / 2: + self.goto(self.xcor(), game.SCREEN_HEIGHT / 2) + + def rotate_left(self): + self.lt(30) + + def rotate_right(self): + self.rt(30) + + def accelerate(self): + self.speed += 1 + + def decelerate(self): + self.speed -= 1 + if self.speed < 0: + self.speed = 0 + +class Orb(Sprite): + def __init__(self, shape, color, x, y): + Sprite.__init__(self, shape, color, x, y) + self.speed = 2 + self.setheading(random.randint(0,360)) + self.turn = 0 + + def tick(self): + self.move() + if random.randint(0, 100) < 5: + self.clear() + + def move(self): + self.rt(random.randint(-10, 10)) + self.fd(self.speed) + + if self.xcor() > game.SCREEN_WIDTH / 2: + self.goto(-game.SCREEN_WIDTH / 2, self.ycor()) + + if self.xcor() < -game.SCREEN_WIDTH / 2: + self.goto(game.SCREEN_WIDTH / 2, self.ycor()) + + if self.ycor() > game.SCREEN_HEIGHT / 2: + self.goto(self.xcor(), -game.SCREEN_HEIGHT / 2) + + if self.ycor() < -game.SCREEN_HEIGHT / 2: + self.goto(self.xcor(), game.SCREEN_HEIGHT / 2) + +# Initial Game setup +game = Game(800, 600, "black", "SPGL Game Demo by /u/wynand1004 AKA @TokyoEdTech", 5) + +# Game attributes +game.highscore = 0 + +# Load high score +highscore = game.load_data("highscore") +if highscore: + game.highscore = highscore +else: + game.highscore = 0 + +# Create Sprites +# Create Player +player = Player("triangle", "white", -400, 0) + +# Create Orbs +for i in range(100): + color = random.choice(["red", "yellow", "green"]) + shape = random.choice(["circle", "square", "triangle", "arrow"]) + orb = Orb(shape, color, 0, 0) + speed = random.randint(1, 5) + orb.speed = speed + +# Create Labels +score_label = Label("Score: 0 Highscore: {}".format(game.highscore), "white", -380, 280) + +# Create Buttons + +# Set Keyboard Bindings +game.set_keyboard_binding(KEY_UP, player.accelerate) +game.set_keyboard_binding(KEY_DOWN, player.decelerate) +game.set_keyboard_binding(KEY_LEFT, player.rotate_left) +game.set_keyboard_binding(KEY_RIGHT, player.rotate_right) +game.set_keyboard_binding(KEY_ESCAPE, game.exit) + +while True: + # Call the game tick method + game.tick() + + # Put your game logic here + for sprite in game.sprites: + # Check collisions with Orbs + if sprite.state and isinstance(sprite, Orb): + if game.is_collision(sprite, player): + game.play_sound("collision.wav") + sprite.destroy() + # Update Score + if sprite.pencolor() == "red": + player.score -= 10 + if sprite.pencolor() == "green": + player.score += 10 + if sprite.pencolor() == "yellow": + player.score += 5 + + # Update High Score + if player.score > game.highscore: + game.highscore = player.score + game.save_data("highscore", game.highscore) + + # Update the Game Score, High Score, and Player Speed + speed_string = "-" * int(player.speed) + score_label.update("Score: {} High Score: {} Speed: {}".format(player.score, game.highscore, speed_string)) + + # Show game info in terminal + game.clear_terminal_screen() + game.print_game_info() diff --git a/SPGL_Minimum_Example.py b/SPGL_Minimum_Example.py index 2854a7c..5eece36 100644 --- a/SPGL_Minimum_Example.py +++ b/SPGL_Minimum_Example.py @@ -1,15 +1,17 @@ # SPGL Minimal Code by /u/wynand1004 AKA @TokyoEdTech -# Requires SPGL Version 0.3 or Above -# SPGL Documentation on Github: https://wynand1004.github.io/SGE +# Requires SPGL Version 0.8 or Above +# SPGL Documentation on Github: https://wynand1004.github.io/SPGL # Use this as the starting point for your own games # Import SPGL -from SPGL import * +import spgl # Create Classes +# Create Functions + # Initial Game setup -game = SPGL(800, 600, "black", "SPGL Minimum Code Example by /u/wynand1004 AKA @TokyoEdTech") +game = spgl.Game(800, 600, "black", "SPGL Minimum Code Example by /u/wynand1004 AKA @TokyoEdTech") # Create Sprites diff --git a/spgl.py b/spgl.py new file mode 100644 index 0000000..1913210 --- /dev/null +++ b/spgl.py @@ -0,0 +1,564 @@ +# Simple Python Game Library Version 0.8.6.0 by /u/wynand1004 AKA @TokyoEdTech +# Documentation on Github: https://wynand1004.github.io/SPGL +# Python 2.x and 3.x Compatible + +import os +import turtle +import time +import random +import math +import pickle +import platform + +# Import message box +# This code is necessary for Python 2.x and 3.x compatibility +try: + import tkMessageBox as messagebox +except: + from tkinter import messagebox + +# Import filedialog +try: + from tkinter import filedialog +except: + import tkFileDialog as filedialog + + +# If on Windows, import winsound or, better yet, switch to Linux! +if platform.system() == "Windows": + try: + import winsound + except: + print ("Winsound module not available.") + + +# Use for Keyboard Bindings +KEY_UP = "Up" +KEY_DOWN = "Down" +KEY_LEFT = "Left" +KEY_RIGHT = "Right" +KEY_SPACE = "space" +KEY_ESCAPE = "Escape" +KEY_ENTER = "Return" +KEY_RETURN = "Return" +KEY_SHIFT_LEFT = "Shift_L" +KEY_SHIFT_RIGHT = "Shift_R" +KEY_CONTROL_LEFT = "Control_L" +KEY_CONTROL_RIGHT = "Control_R" +KEY_ALT_LEFT = "Alt_L" +KEY_ALT_RIGHT = "Alt_R" +KEY_CAPS_LOCK = "Caps_Lock" +KEY_F1 = "F1" +KEY_F2 = "F2" +KEY_F3 = "F3" +KEY_F4 = "F4" +KEY_F5 = "F5" +KEY_F6 = "F6" +KEY_F7 = "F7" +KEY_F8 = "F8" +KEY_F9 = "F9" +KEY_F10 = "F10" +KEY_F11 = "F11" +KEY_F12 = "F12" + +# Game Class +class Game(object): + + # Keep List of Sprites + sprites = [] + + # Keep List of Labels + labels = [] + + # Keep List of Buttons + buttons = [] + + # Logs + logs = [] + + def __init__( + self, + screen_width = 800, + screen_height = 600, + background_color = "black", + title = "Simple Game Library by /u/wynand1004 AKA @TokyoEdTech", + splash_time = 3): + + # Setup using Turtle module methods + turtle.setup(width=screen_width, height=screen_height) + turtle.bgcolor(background_color) + turtle.title(title) + turtle.tracer(0) # Stop automatic screen refresh + turtle.listen() # Listen for keyboard input + turtle.hideturtle() # Hides default turtle + turtle.penup() # Puts pen up for defaut turtle + turtle.setundobuffer(0) # Do not keep turtle history in memory + turtle.onscreenclick(self.click) + + # Game Attributes + self.SCREEN_WIDTH = screen_width + self.SCREEN_HEIGHT = screen_height + self.DATAFILE = "game.dat" + self.SPLASHFILE = "splash.gif" # Must be in the same folder as game file + + self.fps = 30.0 # Lower this on slower computers or with large number of sprites + self.title = title + self.gravity = 0 + self.state = "showsplash" + self.splash_time = splash_time + + self.time = time.time() + + # Clear the terminal and print the game title + self.clear_terminal_screen() + print (self.title) + + # Show splash + self.show_splash(self.splash_time) + + # Pop ups + def ask_yes_no(self, title, message): + return messagebox.askyesno(title, message) + + def show_info(self, title, message): + return messagebox.showinfo(title, message) + + def show_warning(self, title, message): + return messagebox.showwarning(title, message) + + def show_error(self, title, message): + return messagebox.showerror(title, message) + + def ask_question(self, title, message): + return messagebox.askquestion(title, message) + + def ask_ok_cancel(self, title, message): + return messagebox.askokcancel(title, message) + + def ask_retry_cancel(self, title, message): + return messagebox.askretrycancel(title, message) + + def ask_open_filename(self): + return filedialog.askopenfilename() + + def print_error_logs(self): + print ("Error Logs:") + + if len(Game.logs) == 0: + print ("No errors") + else: + for error in Game.logs: + print (error) + + + print ("") + + def tick(self): + # Check the game state + # showsplash, running, gameover, paused + + if self.state == "showsplash": + self.show_splash(self.splash_time) + + elif self.state == "paused": + pass + + elif self.state == "gameover": + pass + + else: + # Iterate through all sprites and call their tick method + for sprite in Game.sprites: + if sprite.state: + sprite.tick() + + # Iterate through all labels and call their update method + for label in Game.labels: + if label.text != "": + label.tick() + + # Update the screen + self.update_screen() + + def click(self, x, y): + print ("The window was clicked at ({},{})".format(x, y)) + + def show_splash(self, seconds): + # Show splash screen + # To be implemented + + try: + # Load self.SPLASHFILE + turtle.bgpic(self.SPLASHFILE) + + self.update_screen() + + # Pause + self.time = time.time() + while time.time() < self.time + (self.splash_time): + pass + + # Hide Splash + turtle.bgpic("") + + except: + Game.logs.append("Warning: {} missing from disk.".format(self.SPLASHFILE)) + + # Change state to running + self.state = "running" + + def destroy_all_sprites(self): + for sprite in Game.sprites: + if sprite.state: + sprite.destroy() + + def save_data(self, key, value): + # Load DATAFILE + try: + data = pickle.load(open(self.DATAFILE, "rb")) + except: + data = {} + Game.logs.append("Warning: Creating new {} file on disk.".format(self.DATAFILE)) + + data[key] = value + + #Save DATAFILE + pickle.dump(data, open(self.DATAFILE, "wb")) + + def load_data(self, key): + # Load DATAFILE + try: + data = pickle.load(open(self.DATAFILE, "rb")) + except: + data = {} + Game.logs.append("Warning: {} missing from disk.".format(self.DATAFILE)) + + if key in data: + return data[key] + else: + return None + + def set_title(self, title): + turtle.title(title) + self.title = title + + def set_keyboard_binding(self, key, function): + # Allow any order of arguments as this is reversed from Tkinter + # Check if key is a string. If not, reverse the arguments + if type(key) is not str: + temp = key + key = function + function = temp + + # Python 3 + try: + turtle.onkeypress(function, key) + # Python 2 + except: + turtle.onkey(function, key) + + def update_screen(self): + while time.time() < self.time + (1.0 / self.fps): + pass + turtle.update() + self.time = time.time() + + def play_sound(self, sound_file, time = 0): + # Windows + if platform.system() == 'Windows': + winsound.PlaySound(sound_file, winsound.SND_ASYNC) + # Linux + elif platform.system() == "Linux": + os.system("aplay -q {}&".format(sound_file)) + # Mac + else: + os.system("afplay {}&".format(sound_file)) + + if time > 0: + turtle.ontimer(lambda: self.play_sound(sound_file, time), t=int(time * 1000)) + + def stop_all_sounds(self): + # Windows + if platform.system() == 'Windows': + Game.logs.append("Warning: .stop_all_sounds not implemened on Windows yet.") + # Linux + elif platform.system() == "Linux": + os.system("killall aplay") + # Mac + else: + os.system("killall afplay") + + def clear_terminal_screen(self): + # Windows + if platform.system() == 'Windows': + os.system("cls") + # Linux and Mac + else: + os.system("clear") + + def print_game_info(self): + print (self.title) + print ("") + print ("Window Dimensions: {}x{}".format(self.SCREEN_WIDTH, self.SCREEN_HEIGHT)) + print ("") + + # Calcuate number of active sprites + active_sprites = 0 + for sprite in Game.sprites: + if sprite.state: + active_sprites += 1 + + print ("Number of Sprites (Active / Total): {} / {}".format(active_sprites, len(Game.sprites))) + + print ("Number of Labels: {}".format(len(Game.labels))) + print ("Number of Buttons: {}".format(len(Game.buttons))) + print ("") + print ("Frames Per Second (Target): {}".format(self.fps)) + print ("") + self.print_error_logs() + + def is_collision(self, sprite_1, sprite_2): + # Axis Aligned Bounding Box + x_collision = (math.fabs(sprite_1.xcor() - sprite_2.xcor()) * 2) < (sprite_1.width + sprite_2.width) + y_collision = (math.fabs(sprite_1.ycor() - sprite_2.ycor()) * 2) < (sprite_1.height + sprite_2.height) + return (x_collision and y_collision) + + def is_circle_collision(self, sprite_1, sprite_2, radius): + # Collision based on distance + a=sprite_1.xcor()-sprite_2.xcor() + b=sprite_1.ycor()-sprite_2.ycor() + distance = math.sqrt((a**2) + (b**2)) + + if distance < radius: + return True + else: + return False + + def show_game_over(self): + self.state = "gameover" + self.hide_all_sprites() + print ("Game Over!") + self.state = "paused" + + def set_background(self, image): + if image.endswith(".gif"): + turtle.bgpic(image) + else: + Game.logs.append("Warning: Background image {} must be a gif.".format(image)) + + def set_fps(self, fps): + self.fps = fps + + def after(self, function, milliseconds): + turtle.ontimer(function, milliseconds) + + def exit(self): + self.stop_all_sounds() + os._exit(0) + +# Sprite Class +class Sprite(turtle.Turtle): + def __init__(self, + shape, + color, + x = 0, + y = 0, + width = 20, + height = 20): + + turtle.Turtle.__init__(self) + self.speed(0) # Animation Speed + # Register shape if it is a .gif file + if shape.endswith(".gif"): + try: + turtle.register_shape(shape) + except: + Game.logs.append("Warning: {} file missing from disk.".format(shape)) + + # Set placeholder shape + shape = "square" + width = 20 # This is the default for turtle module primitives + height = 20 # This is the default for turtle module primitives + + self.shape(shape) + self.color(color) + self.penup() + self.goto(x, y) + + # Attributes + self.width = width + self.height = width + + self.speed = 0.0 # Speed of motion + self.dx = 0.0 + self.dy = 0.0 + self.acceleration = 0.0 + self.friction = 0.0 + + self.state = "active" + self.solid = True + + #Set click binding + self.onclick(self.click) + + # Append to master sprite list + Game.sprites.append(self) + + def tick(self): + # This is the function that is called each frame of the game + # For most sprites, you'll want to call the move method here + # self.move() + pass + + def move(self): + self.fd(self.speed) + + def destroy(self): + # When a sprite is destoyed move it off screen, hide it, and set state to None + # This is a workaround as there is no way to delete a sprite from memory in the turtle module. + self.hideturtle() + self.goto(10000, 10000) + self.state = None + + def set_image(self, image, width, height): + # Allows the use of custom images (must be .gif) due to turtle/tkinter limitation + # Register shape if it is a .gif file + if image.endswith(".gif"): + try: + turtle.register_shape(image) + except: + Game.logs.append("Warning: {} file missing from disk.".format(image)) + + # Set placeholder shape + image = "square" + width = 20 # This is the default for turtle module primitives + height = 20 # This is the default for turtle module primitives + + self.shape(image) + self.width = width + self.height = height + + # Click binding needs to be set again after image change + self.onclick(self.click) + + def set_bounding_box(self, width, height): + self.width = width + self.height = height + + def click(self, x, y): + print ("The sprite was clicked at ({},{})".format(x, y)) + + def rotate_left(self, degrees): + self.lt(degrees) + + def rotate_right(self, degrees): + self.rt(degrees) + + def go_forward(self, distance): + self.fd(distance) + + def go_backward(self, distance): + self.fd(-distance) + + +#Label Class +class Label(turtle.Turtle): + def __init__(self, + text, + color, + x = 0, + y = 0, + font_name = "Arial", + font_size = 12, + font_type = "normal", + align = "left"): + + turtle.Turtle.__init__(self) + self.hideturtle() + self.penup() + self.goto(x, y) + self.color(color) + self.font_name = font_name + self.font_size = font_size + self.font_type = font_type + self.font = (font_name, font_size, font_type) + self.align = align + + # Attributes + self.text = text + + # Append to master label list + Game.labels.append(self) + + def tick(self): + self.clear() + self.write(self.text, False, align =self.align, font = self.font) + + def update(self, text): + self.text = text + self.tick() + + def set_font_name(self, font_name): + self.font_name = font_name + self.font = (self.font_name, self.font_size, self.font_type) + + def set_font_size(self, font_size): + self.font_size = font_size + self.font = (self.font_name, self.font_size, self.font_type) + + def set_font_type(self, font_type): + self.font_type = font_type + self.font = (self.font_name, self.font_size, self.font_type) + + + +#Button Class +class Button(turtle.Turtle): + def __init__(self, + shape, + color, + x = 0, + y = 0): + + turtle.Turtle.__init__(self) + # self.hideturtle() + self.penup() + # Register shape if it is a .gif file + if shape.endswith(".gif"): + try: + turtle.register_shape(shape) + except: + Game.logs.append("Warning: {} file missing from disk.".format(shape)) + + # Set placeholder shape + shape = "square" + + self.shape(shape) + self.color(color) + self.goto(x, y) + + #Set click binding + self.onclick(self.click) + + # Append to master button list + Game.buttons.append(self) + + def set_image(self, image): + # Register shape if it is a .gif file + if shape.endswith(".gif"): + try: + turtle.register_shape(shape) + except: + Game.logs.append("Warning: {} file missing from disk.".format(shape)) + + # Set placeholder shape + shape = "square" + + # Allows the use of custom images (must be .gif) due to turtle/tkinter limitation + self.shape(image) + + # Click binding needs to be set again after image change + self.onclick(self.click) + + def click(self, x, y): + print ("The button was clicked at ({},{})".format(x, y))