I'm new to python and am working on a final project for my intro to python class. I have the majority of a paddle ball game done, but can't figure out how to make the ball object bounce off of my paddle object.
I've looked on Stackoverflow for a while and have spent a few hours trying to figure it out on my own without any success. If anyone has any ideas I could really use the help.
If there's anything I need to explain better in order for you to get a better understanding please just comment.
GUI FILE:
Import tkinter, random, particle, and helpers
from tkinter import *
from ball import *
from paddle import *
from time import *
class PaddleBall:
def __init__(self, window):
''' Construct the paddle ball GUI '''
self.window = window
self.window.protocol('WM_DELETE_WINDOW', self.safe_exit)
self.width = 700
self.height = 900
self.canvas = Canvas(self.window, bg='black', width=self.width, height=self.height, highlightthickness=0)
self.canvas.bind_all("<KeyPress-Left>", self.move_left)
self.canvas.bind_all("<KeyPress-Right>", self.move_right)
self.canvas.pack()
# Create a label to indicate instructions
instructions = Label(window, text="Controls: Left & Right Arrow Keys")
instructions.pack(side=BOTTOM, expand=YES)
# Create a button to clear Ball
restart_button = Button(window, text="Play", command=self.reset)
restart_button.pack(side=BOTTOM, expand=YES)
self.ball = Ball(350, 350)
self.paddle = Paddle(300, 850, 400, 860, 0, 0)
self.terminated = False
self.render()
def ballobject(self):
self.ball = Ball(350, 350)
self.paddle = Paddle(300, 850, 400, 860, 0, 0)
self.render()
def reset(self):
self.terminated = True
def safe_exit(self):
''' Turn off the event loop before closing the GUI '''
self.terminated = True
self.window.destroy()
# Render everything
def render(self):
# While program is not terminated
if not self.terminated:
# Erase Canvas
self.canvas.delete(ALL)
# Move ball
self.ball.move(self.canvas, self.paddle)
# Render ball
self.ball.render(self.canvas)
# Render paddle
self.paddle.render(self.canvas)
# use distance() to detect collision between ball and paddle.
'''Ball.bounce(self)'''
# Animate the particles movement
self.canvas.after(10, self.render)
else:
# Erase Canvas
self.canvas.delete(ALL)
self.terminated = False
self.canvas.after(50, self.ballobject)
def move_left(self, event):
self.paddle.move_left(event)
def move_right(self, event):
self.paddle.move_right(event)
if __name__ == '__main__':
root = Tk()
root.option_add('*font', ('Verdana', 12, 'bold italic')) # Found at http://effbot.org/tkinterbook/tkinter-widget-styling.htm
root.resizable(0,0) # Found at https://mail.python.org/pipermail/tutor/2001-September/008504.html
root.title('Paddle Ball')
root.wm_attributes("-topmost", -1)
app = PaddleBall(root)
root.mainloop()
BALL CLASS FILE:
class Ball:
'''
Ball models a single ball that may be rendered to a canvas
'''
def __init__(self, x, y, radius = 15,):
'''
Constructor
'''
self._x = x
self._y = y
self._velX = randint(-10,10)
self._velY = randint(-10,-5)
self._radius = radius
self._color = 'white'
self._tx = 350
self._ty = 400
self._t = ""
self._tfill = "red"
self._tfont = ("Arial", 35, "bold italic")
# This method renders the ball
def render(self, canvas):
canvas.create_oval(self._x - self._radius, self._y - self._radius, self._x + self._radius, self._y + self._radius, fill = self._color)
canvas.create_text(self._tx, self._ty, text = self._t, fill = self._tfill, font = self._tfont)
# This method moves the ball
def move(self, canvas, Paddle):
# Update Position
self._x += self._velX
self._y += self._velY
# If the ball hits any of the wall negate the velocity
if (self._x + self._radius > canvas.winfo_reqwidth() and self._velX > 0) or (self._x - self._radius < 0 and self._velX < 0):
self._velX = -self._velX
if (self._y + self._radius < 0 and self._velY < 0):
self._velY = -self._velY
if (self._y + self._radius > canvas.winfo_reqheight() and self._velY > 0):
self._velY = 0
self._velX = 0
self._t = " GAME OVER! \n Click the play button to play again."
#*****THIS IS WHAT I'M HAVING TROUBLE WITH******
# Determine if the ball hits the paddle
if ((self._x + self._radius > Paddle._x(self) and self._velX > 0) or (self._x + self._radius < Paddle._x2(self))) and (self._y < Paddle._y(self)):
self._velX = -self._velX
PADDLE CLASS FILE:
# Import math and helpers
from tkinter import *
import math
from gui import *
class Paddle:
def __init__(self, x, y, x2, y2, velX, velY):
'''
Constructor
'''
self._x = x
self._y = y
self._x2 = x2
self._y2 = y2
self._velX = velX
self._velY = velY
self._color = 'white'
def getpadx(self):
return self._x
def getpady(self):
return self._y
def getpadx1(self):
return self._x2
def getpady2(self):
return self._y2
# This method renders the paddle
def render(self, canvas):
canvas.create_rectangle(self._x, self._y, self._x2, self._y2, fill = self._color)
# This method moves the paddle
def move(self, canvas):
# Update Position
# If the paddle hits any of the wall negate the velocity
if (self._x + self._radius > canvas.winfo_reqwidth() and self._velX > 0) or (self._x - self._radius < 0 and self._velX < 0):
self._velX = -self._velX
def move_left(self, event):
self._x -= 35
self._x2 -= 35
def move_right(self, event):
self._x += 35
self._x2 += 35
I figured it out with the help of a friend. All I had to do was change this code:
From this:
if ((self._x + self._radius > Paddle._x(self) and self._velX > 0) or (self._x + self._radius < Paddle._x2(self))) and (self._y < Paddle._y(self)):
self._velX = -self._velX
To this:
`if (self._x > Paddle._x) and (self._x < Paddle._x2):
if (self._y + self._radius > Paddle._y):
self._velY = -self._velY
self._velX = self._velX + randint(-2,2)`
Related
Im trying to create a game with Python's Kivy but my collision detection system isnt working i've tried many different methods on youtube but still no success it either does detect anything or just gives me error messages
def collides(self, player, ball2):
r1x = player.pos[0]
r1y = player.pos[1]
r2x = ball2.pos[0]
r2y = ball2.pos[1]
r1w = player.size[0]
r1h = player.size[1]
r2w = ball2.size[0]
r2h = ball2.size[1]
if r1x < r2x + r2w and r1x + r1w > r2x and r1y < r2y + r2h and r1y + r1h > r2y:
print("True")
return True
else:
return False
print('False')
Your code in collides seems OK but rest of code (in repo) doesn't look good.
I took code from repo and first I made many changes to make it cleaner - I made class Sprite similar to pygame.Sprite
And next I tried use collisions and they work for me.
I keep all balls on list so I can use for-loop to work with all ball. And I can add more balls and it will still works the same. And I can remove ball from list when it is "killed".
I also run all with one schedule_interval. When I click button then I only change speed vx without running another schedule_interval. And this way in update() I can first I make calculation, next I can check collisions and at the end I can move rect on canvas - and this way rect doesn't blik when I have to move it back to previous position (ie. when I detect collision with border).
from kivy.app import App
from kivy.graphics import Ellipse, Rectangle, Color
from kivy.metrics import dp
from kivy.properties import Clock, ObjectProperty, NumericProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
# How to play the game: Click left and right to move along the 'x' axis to stop click button to move in the opposite
# direction once, to go faster just repeatedly click the direction you want to go BUT there's a catch the faster
# you are going the harder it is to stop sp be carefull. you can teleport to the other side of the screen but only from
# the right side to the left side
# Objective: Dodge all incoming enemies until you reach the next level
# Level layout: Lvl 1: Space invaders type mode Lvl 2: Platform runner type mode Lvl 3: undecided...
# Goal: Make this game playable both on mobile and pc
class Sprite():
def __init__(self, x, y, size, color, vx, vy):
'''Set all values.'''
self.start_x = x
self.start_y = y
self.x = x
self.y = y
self.size = size
self.color = color
self.vx = vx
self.vy = vy
self.rect = None
#self.alive = True
def create_rect(self):
'''Execute it in `with canvas:` in `on_size()`.'''
Color(*self.color)
self.rect = Rectangle(pos=(self.x, self.y), size=(self.size, self.size))
def set_start_pos(self, center_x, center_y):
'''Move to start position.'''
self.x = center_x + self.start_x
self.y = center_y + self.start_y
def move(self):
'''Calculate new position without moving object on `canvas`.'''
self.x += self.vx
self.y += self.vy
def draw(self):
'''Move object on canvas.'''
self.rect.pos = (self.x, self.y)
def check_collision_circle(self, other):
distance = (((self.x-other.x)**2) + ((self.y-other.y)**2)) ** 0.5
#if distance < (self.size + other.size)/2:
# print(True)
# return True
#else:
# return False
return distance < (self.size + other.size)/2:
def check_collision_rect(self, other):
# code `... and ...` gives `True` or `False`
# and it doesn't need `if ...: return True else: return False`
return (
(other.x <= self.x + self.size) and
(self.x <= other.x + other.size) and
(other.y <= self.y + self.size) and
(self.y <= other.y + other.size)
)
class MainCanvas(Widget):
rec_x = NumericProperty(0)
inc = dp(3)
ball_size = dp(35)
my_player = ObjectProperty(Rectangle)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.player = Sprite(x=-self.ball_size/2, y=145, size=dp(15), vx=dp(0), vy=dp(0), color=(1, .3, .5))
self.balls = [
Sprite(x=0, y=-2000, size=dp(15), vx=dp(0), vy=dp(8), color=(1, 0, 0)),
Sprite(x=100, y=-1000, size=dp(15), vx=dp(0), vy=dp(5), color=(1, 1, 0)),
Sprite(x=-200, y=-1000, size=dp(30), vx=dp(0), vy=dp(5), color=(1, 1, 1)),
Sprite(x=300, y=-600, size=dp(15), vx=dp(0), vy=dp(5), color=(1, 1, 1)),
]
with self.canvas:
for ball in self.balls:
ball.create_rect()
self.player.create_rect()
Clock.schedule_interval(self.update, 1/60)
def on_size(self, *args):
print(f'on_size : {self.width}x{self.height}')
for ball in self.balls:
ball.set_start_pos(self.center_x, self.center_y)
self.player.set_start_pos(self.center_x, self.center_y)
self.rec_x = self.player.x
def update(self, dt):
# all in one function to control if it check collision after move, and draw only after all calculations
# --- moves (without draws) ---
self.player_move(dt)
# move green rectangle below player
self.rec_x = self.player.x
self.ball_move(dt)
# --- collisions (without draws) ---
live_balls = []
for ball in self.balls:
if self.player.check_collision_rect(ball):
#if self.player.check_collision_circle(ball):
print('killed')
# hide
#ball.set_start_pos(self.center_x, self.center_y)
#ball.draw()
# or remove from canvas
self.canvas.remove(ball.rect)
else:
live_balls.append(ball)
self.balls = live_balls
# --- draws ---
self.player.draw()
for ball in self.balls:
ball.draw()
def on_left_click(self):
print('Left Clicked')
self.player.vx -= self.inc
def on_right_click(self):
print('Right Clicked')
self.player.vx += self.inc
def ball_move(self, dt):
for ball in self.balls:
ball.move()
if ball.y + ball.size > self.height:
ball.set_start_pos(self.center_x, self.center_y)
def player_move(self, dt):
self.player.move()
# moving left and stop on screen border
if self.player.vx < 0 and self.player.x < 0:
self.player.x = 0
self.player.vx = 0
# moving right and jump to left side when leave screen
if self.player.vx > 0 and self.width < self.player.x:
self.player.x = 0
class TheFalling(App):
pass
app = TheFalling()
app.run()
#app.stop()
app.root_window.close()
I'm trying to put my button borderless so we can we see the progressbar on the background
I tried all the forum but coundt put the border of the button round or make something like the second picture
this is the part of the code for the button :
bg3 = Image.open("blanc.png")
resized_bg03 = bg3.resize((20, 20),Image.ANTIALIAS)
new_bg03 = ImageTk.PhotoImage(resized_bg03)
s = Button(Fenetre,image=new_bg03,borderwidth=0,highlightthickness=0)
s.place(x=600, y=10,height = 20 , width = 20)
I would like to have something more like this with a circle and no border :
can someone help me :)
Try this:
import tkinter as tk
# Kindly plagiarised from: https://stackoverflow.com/a/17985217/11106801
def _create_circle(self, x, y, r, **kwargs):
return self.create_oval(x-r, y-r, x+r, y+r, **kwargs)
tk.Canvas.create_circle = _create_circle
# Please suggest a better name for the class
class ProgressBar(tk.Canvas):
def __init__(self, height=20, radius=6, width=400, circle_colour="black",
colour="red", bar_width=6, bd=0, highlightthickness=0,
anti_bar_colour="white", **kwargs):
super().__init__(height=height, width=width, bd=bd,
highlightthickness=highlightthickness, **kwargs)
self.radius = radius
self.height = height
self.width = width
self.rectangle = None
self.circle_colour = circle_colour
self.colour = colour
self.bar_width = bar_width
self.circle = None
self.button_1_down = False
self.create_anti_bar(anti_bar_colour)
self.progress = 0
super().bind("<Enter>", self.show_circle)
super().bind("<Leave>", self.hide_circle)
super().bind("<Button-1>", self.mouse_click)
super().bind("<ButtonRelease-1>", self.mouse_release)
super().bind("<B1-Motion>", self.mouse_motion)
def create_anti_bar(self, colour):
start_y = (self.height - self.bar_width)/2
end_y = self.height - start_y
start_x = self.radius
end_x = self.width - self.radius
# Change it to >= if you don't what the bar to appear when the
# progress is at 0
if start_x > end_x:
return None
super().create_rectangle(start_x, start_y, end_x, end_y,fill=colour,
outline=colour)
def mouse_click(self, event):
self.button_1_down = True
self.progress = (event.x - self.radius)/(self.width - 2*self.radius)
def mouse_release(self, event=None):
self.button_1_down = False
def mouse_motion(self, event):
if self.button_1_down:
self.mouse_click(event)
def hide_circle(self, event=None):
if self.circle is not None:
super().delete(self.circle)
self.circle = None
def show_circle(self, event=None):
# Try removing the circle if we can
self.hide_circle()
x = (self.width - 2*self.radius)*self._progress + self.radius
self.circle = super().create_circle(x, self.height//2, self.radius,
fill=self.circle_colour,
outline=self.circle_colour)
def update_bar(self):
# Try removing the progress bar
if self.rectangle is not None:
super().delete(self.rectangle)
start_y = (self.height - self.bar_width)/2
end_y = self.height - start_y
start_x = self.radius
end_x = (self.width - 2*self.radius)*self._progress + self.radius
# Change it to >= if you don't what the bar to appear when the
# progress is at 0
if start_x > end_x:
return None
self.rectangle = super().create_rectangle(start_x, start_y, end_x,
end_y, fill=self.colour,
outline=self.colour)
#property
def progress(self):
return self._progress
#progress.setter
def progress(self, new_value):
# Check if the new_value is in the correct range
if new_value < 0:
new_value = 0
elif new_value > 1:
new_value = 1
# Update self._progress
self._progress = new_value
# Update the progress bar
self.update_bar()
# If the circle was shown update it
if self.circle is not None:
self.show_circle()
if __name__ == "__main__":
root = tk.Tk()
pbar = ProgressBar()
pbar.pack()
pbar.progress = 0
# keep incrementing the progress until the end then stop
def increment_progress():
pbar.progress += 0.001
if pbar.progress >= 1:
return None
pbar.after(10, increment_progress)
increment_progress()
root.mainloop()
Tell me if you don't get what any of the methods do. It is too much code to properly annotate
I'm pretty new to python and to programming in general. I'm trying to make the game Bounce. The game runs as expected but as soon as I close the window, it shows an error.
This is the code:
from tkinter import *
import random
import time
# Creating the window:
window = Tk()
window.title("Bounce")
window.geometry('600x600')
window.resizable(False, False)
# Creating the canvas containing the game:
canvas = Canvas(window, width = 450, height = 450, bg = "black")
canvas.pack(padx = 50, pady= 50)
score = canvas.create_text(10, 20, fill = "white")
window.update()
# Creating the ball:
class Ball:
def __init__(self, canvas1, paddle1, color):
self.canvas = canvas1
self.paddle = paddle1
self.id = canvas1.create_oval(10, 10, 25, 25, fill = color) # The starting point of the ball
self.canvas.move(self.id, 190, 160)
starting_direction = [-3, -2, -1, 0, 1, 2, 3]
random.shuffle(starting_direction)
self.x = starting_direction[0]
self.y = -3
self.canvas_height = self.canvas.winfo_height()
self.canvas_width = self.canvas.winfo_width()
# Detecting the collision between the ball and the paddle:
def hit_paddle(self, ballcoords):
paddle_pos = self.canvas.coords(self.paddle.id)
if ballcoords[0] <= paddle_pos[2] and ballcoords[2] >= paddle_pos[0]:
if paddle_pos[3] >= ballcoords[3] >= paddle_pos[1]:
return True
return False
# Detecting the collision between the the ball and the canvas sides:
def draw(self):
self.canvas.move(self.id, self.x, self.y)
ballcoords = self.canvas.coords(self.id)
if ballcoords[1] <= 0:
self.y = 3
if ballcoords[3] >= self.canvas_height:
self.y = 0
self.x = 0
self.canvas.create_text(225, 150, text = "Game Over!", font = ("Arial", 16), fill = "white")
if ballcoords[0] <= 0:
self.x = 3
if ballcoords[2] >= self.canvas_width:
self.x = -3
if self.hit_paddle(ballcoords):
self.y = -3
class Paddle:
def __init__(self, canvas1, color):
self.canvas1 = canvas
self.id = canvas.create_rectangle(0, 0, 100, 10, fill = color)
self.canvas1.move(self.id, 180, 350)
self.x = 0
self.y = 0
self.canvas1_width = canvas1.winfo_width()
self.canvas1.bind_all("<Left>", self.left)
self.canvas1.bind_all("<Right>", self.right)
def draw(self):
self.canvas1.move(self.id, self.x, 0)
paddlecoords = self.canvas1.coords(self.id)
if paddlecoords[0] <= 0:
self.x = 0
if paddlecoords[2] >= self.canvas1_width:
self.x = 0
def right(self, event):
self.x = 3
def left(self, event):
self.x = -3
paddle = Paddle(canvas, color = "white")
ball = Ball(canvas, paddle, color = "red")
while True:
ball.draw()
paddle.draw()
window.update_idletasks()
window.update()
time.sleep(0.001)
This is the error:
Traceback (most recent call last):
File "D:\CSCI201\Arcade Games Project\Bounce\Bounce_Game.py", line 111, in <module>
ball.draw()
File "D:\CSCI201\Arcade Games Project\Bounce\Bounce_Game.py", line 64, in draw
self.canvas.move(self.id, self.x, self.y)
File "C:\Users\M.Youssry\AppData\Local\Programs\Python\Python39\lib\tkinter\__init__.py", line 2916, in move
self.tk.call((self._w, 'move') + args)
_tkinter.TclError: invalid command name ".!canvas"
I've tried inserting .mainloop() as suggested to another user having the same problem but it hasn't worked for me.
It is caused by close button on top-right corner of window, the only way you have to stop script. After you click close button, window destried, so no widget, like canvas, exist.
You can set a flag to identify if while loop should stop and exit in handler of window close button event.
window.protocol("WM_DELETE_WINDOW", handler)
Here, you can exit script any time by click close button of window.
from tkinter import *
import random
import time
# Creating the window:
window = Tk()
window.title("Bounce")
window.geometry('600x600')
window.resizable(False, False)
# Creating the canvas containing the game:
canvas = Canvas(window, width = 450, height = 450, bg = "black")
canvas.pack(padx = 50, pady= 50)
score = canvas.create_text(10, 20, fill = "white")
window.update()
# Creating the ball:
class Ball:
def __init__(self, canvas1, paddle1, color):
self.canvas = canvas1
self.paddle = paddle1
self.id = canvas1.create_oval(10, 10, 25, 25, fill = color) # The starting point of the ball
self.canvas.move(self.id, 190, 160)
starting_direction = [-3, -2, -1, 0, 1, 2, 3]
random.shuffle(starting_direction)
self.x = starting_direction[0]
self.y = -3
self.canvas_height = self.canvas.winfo_height()
self.canvas_width = self.canvas.winfo_width()
# Detecting the collision between the ball and the paddle:
def hit_paddle(self, ballcoords):
paddle_pos = self.canvas.coords(self.paddle.id)
if ballcoords[0] <= paddle_pos[2] and ballcoords[2] >= paddle_pos[0]:
if paddle_pos[3] >= ballcoords[3] >= paddle_pos[1]:
return True
return False
# Detecting the collision between the the ball and the canvas sides:
def draw(self):
self.canvas.move(self.id, self.x, self.y)
ballcoords = self.canvas.coords(self.id)
if ballcoords[1] <= 0:
self.y = 3
if ballcoords[3] >= self.canvas_height:
self.y = 0
self.x = 0
self.canvas.create_text(225, 150, text = "Game Over!", font = ("Arial", 16), fill = "white")
if ballcoords[0] <= 0:
self.x = 3
if ballcoords[2] >= self.canvas_width:
self.x = -3
if self.hit_paddle(ballcoords):
self.y = -3
class Paddle:
def __init__(self, canvas1, color):
self.canvas1 = canvas
self.id = canvas.create_rectangle(0, 0, 100, 10, fill = color)
self.canvas1.move(self.id, 180, 350)
self.x = 0
self.y = 0
self.canvas1_width = canvas1.winfo_width()
self.canvas1.bind_all("<Left>", self.left)
self.canvas1.bind_all("<Right>", self.right)
def draw(self):
self.canvas1.move(self.id, self.x, 0)
paddlecoords = self.canvas1.coords(self.id)
if paddlecoords[0] <= 0:
self.x = 0
if paddlecoords[2] >= self.canvas1_width:
self.x = 0
def right(self, event):
self.x = 3
def left(self, event):
self.x = -3
paddle = Paddle(canvas, color = "white")
ball = Ball(canvas, paddle, color = "red")
# New code after here
def handler():
global run
run = False
window.protocol("WM_DELETE_WINDOW", handler)
run = True
while run:
# New code before here
ball.draw()
paddle.draw()
window.update_idletasks()
window.update()
time.sleep(0.01)
window.destroy() # should always destroy window before exit
I had this problem and solved it by restarting my iPython-console (Spyder)
I found a nice image of space that I'd like sitting in the background of this tiny game I'm working on and can't figure out what and where to write it. It needs to be placed behind all classes to make sure that it doesn't block the screen. I thought it might be in class Window, but I'm not sure. I am brand new to python so any help is much appreciated! This is the entire project so far.
import sys, logging, os, random, math, open_color, arcade
#check to make sure we are running the right version of Python
version = (3,7)
assert sys.version_info >= version, "This script requires at least Python {0}.{1}".format(version[0],version[1])
#turn on logging, in case we have to leave ourselves debugging messages
logging.basicConfig(format='[%(filename)s:%(lineno)d] %(message)s', level=logging.DEBUG)
logger = logging.getLogger(__name__)
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
MARGIN = 30
SCREEN_TITLE = "Intergalactic slam"
NUM_ENEMIES = 5
STARTING_LOCATION = (400,100)
BULLET_DAMAGE = 10
ENEMY_HP = 10
HIT_SCORE = 10
KILL_SCORE = 100
PLAYER_HP = 100
class Bullet(arcade.Sprite):
def __init__(self, position, velocity, damage):
'''
initializes the bullet
Parameters: position: (x,y) tuple
velocity: (dx, dy) tuple
damage: int (or float)
'''
super().__init__("PNG/laserPink3.png", 0.5)
(self.center_x, self.center_y) = position
(self.dx, self.dy) = velocity
self.damage = damage
def update(self):
'''
Moves the bullet
'''
self.center_x += self.dx
self.center_y += self.dy
class Enemy_Bullet(arcade.Sprite):
def __init__(self, position, velocity, damage):
super().__init__("PNG/laserGreen1.png", 0.5)
(self.center_x, self.center_y) = position
(self.dx, self.dy) = velocity
self.damage = damage
def update(self):
self.center_x += self.dx
self.center_y += self.dy
class Player(arcade.Sprite):
def __init__(self):
super().__init__("PNG/shipYellow_manned.png", 0.5)
(self.center_x, self.center_y) = STARTING_LOCATION
self.hp = PLAYER_HP
class Enemy(arcade.Sprite):
def __init__(self, position):
'''
initializes an alien enemy
Parameter: position: (x,y) tuple
'''
super().__init__("PNG/shipGreen_manned.png", 0.5)
self.hp = ENEMY_HP
(self.center_x, self.center_y) = position
class Window(arcade.Window):
def __init__(self, width, height, title):
super().__init__(width, height, title)
file_path = os.path.dirname(os.path.abspath(__file__))
os.chdir(file_path)
self.set_mouse_visible(True)
arcade.set_background_color(open_color.black)
self.bullet_list = arcade.SpriteList()
self.enemy_list = arcade.SpriteList()
self.enemy_bullet_list = arcade.SpriteList()
self.player = Player()
self.score = 0
self.win = False
self.lose = False
def setup(self):
'''
Set up enemies
'''
for i in range(NUM_ENEMIES):
x = 120 * (i+1) + 40
y = 500
enemy = Enemy((x,y))
self.enemy_list.append(enemy)
def update(self, delta_time):
self.bullet_list.update()
self.enemy_bullet_list.update()
if (not (self.win or self.lose)):
for e in self.enemy_list:
for b in self.bullet_list:
if (abs(b.center_x - e.center_x) <= e.width / 2 and abs(b.center_y - e.center_y) <= e.height / 2):
self.score += HIT_SCORE
e.hp -= b.damage
b.kill()
if (e.hp <= 0):
e.kill()
self.score += KILL_SCORE
if (len(self.enemy_list) == 0):
self.win = True
if (random.randint(1, 75) == 1):
self.enemy_bullet_list.append(Enemy_Bullet((e.center_x, e.center_y - 15), (0, -10), BULLET_DAMAGE))
for b in self.enemy_bullet_list:
if (abs(b.center_x - self.player.center_x) <= self.player.width / 2 and abs(b.center_y - self.player.center_y) <= self.player.height / 2):
self.player.hp -= b.damage
b.kill()
if (self.player.hp <= 0):
self.lose = True
def on_draw(self):
arcade.start_render()
arcade.draw_text(str(self.score), 20, SCREEN_HEIGHT - 40, open_color.white, 16)
arcade.draw_text("HP: {}".format(self.player.hp), 20, 40, open_color.white, 16)
if (self.player.hp > 0):
self.player.draw()
self.bullet_list.draw()
self.enemy_bullet_list.draw()
self.enemy_list.draw()
if (self.lose):
self.draw_game_loss()
elif (self.win):
self.draw_game_won()
def draw_game_loss(self):
arcade.draw_text(str("LOSER!"), SCREEN_WIDTH / 2 - 90, SCREEN_HEIGHT / 2 - 10, open_color.white, 30)
def draw_game_won(self):
arcade.draw_text(str("WINNER!"), SCREEN_WIDTH / 2 - 90, SCREEN_HEIGHT / 2 - 10, open_color.white, 30)
def on_mouse_motion(self, x, y, dx, dy):
'''
The player moves left and right with the mouse
'''
self.player.center_x = x
def on_mouse_press(self, x, y, button, modifiers):
if button == arcade.MOUSE_BUTTON_LEFT:
x = self.player.center_x
y = self.player.center_y + 15
bullet = Bullet((x,y),(0,10),BULLET_DAMAGE)
self.bullet_list.append(bullet)
def main():
window = Window(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
window.setup()
arcade.run()
if __name__ == "__main__":
main()
One way to do it would be to load the .jpg or .png as a texture, and draw that texture each frame, as big as the screen is (or bigger!).
I haven't tested this, but as an example, loading the texture could be done in Window.__init__, like so (reference):
self.background = arcade.load_texture('PNG/background.png')
And then in on_draw, just after you call start_render, you would draw it (reference), passing the required center coordinates, as well as width and height:
self.background.draw(SCREEN_WIDTH/2, SCREEN_HEIGHT/2, SCREEN_WIDTH, SCREEN_HEIGHT)
The reason it needs to be the first thing is because everything is drawn back-to-front, like you would do in a painting.
If the image is not the exact same size as your screen/window, your background will probably be stretched/squished. If that's not what you want, the easiest fix would be to change the image so that it's the right size.
Yes, you should be able to add it to class window...
You could do something like this to add it:
def __init__(self, width, height, title):
super().__init__(width, height, title)
file_path = os.path.dirname(os.path.abspath(__file__))
os.chdir(file_path)
self.set_mouse_visible(True)
arcade.set_background_color(open_color.black)
self.bullet_list = arcade.SpriteList()
self.enemy_list = arcade.SpriteList()
self.enemy_bullet_list = arcade.SpriteList()
self.player = Player()
self.score = 0
self.win = False
self.lose = False
self.background = None
def setup(self):
'''
Set up enemies
'''
self.background = arcade.load_texture("images/background.jpg")
for i in range(NUM_ENEMIES):
x = 120 * (i+1) + 40
y = 500
enemy = Enemy((x,y))
self.enemy_list.append(enemy)
As a novice when it comes to Python, I've tried programming my own game to start, with the advice of a guidebook. However, for this game, I'm trying to detect when a key is held down consistently instead of just pressed. The current code I'm using doesn't make the character move, and without the halt(self, evt) code being implemented, causes the ship to speed up uncontrollably after the button is held down for long enough.
from tkinter import *
import random
import time
class Game:
def __init__(self):
self.tk = Tk()
self.tk.title("Shooter")
self.tk.resizable(0, 0)
self.tk.wm_attributes("-topmost", 1)
self.canvas = Canvas(self.tk, width=500, height=1000, highlightthickness=0)
self.canvas.pack()
self.tk.update()
self.canvas_height = 1000
self.canvas_width = 500
self.bg = PhotoImage(file="background.gif")
w = self.bg.width()
h = self.bg.height()
for x in range(0, 5):
for y in range(0, 10):
self.canvas.create_image(x * w, y * h, \
image=self.bg, anchor='nw')
self.sprites = []
self.running = True
def mainloop(self):
while 1:
if self.running == True:
for sprite in self.sprites:
sprite.move()
self.tk.update_idletasks()
self.tk.update()
time.sleep(0.01)
class Coords:
def __init__(self, x1=0, y1=0, x2=0, y2=0):
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
class Sprite:
def __init__(self, game):
self.game = game
self.endgame = False
self.coordinates = None
def move(self):
pass
def coords(self):
return self.coordinates
class PlayerSprite(Sprite):
def __init__(self, game):
Sprite.__init__(self, game)
self.renderimage = [
PhotoImage(file="player_1.gif"),
PhotoImage(file="player_2.gif"),
PhotoImage(file="player_3.gif"),
PhotoImage(file="player_4.gif"),
]
self.image = game.canvas.create_image(225, 900, \
image=self.renderimage[0], anchor='nw')
self.x = 0
self.y = 0
self.velx = 0
self.current_image = 0
self.current_image_add = 1
self.shoot_timer = 0
self.last_time = time.time()
self.coordinates = Coords()
x_move = None
y_move = None
game.canvas.bind_all('<KeyPress-Left>', self.move_left)
game.canvas.bind_all('<KeyPress-Right>', self.move_right)
game.canvas.bind_all('<KeyPress-Up>', self.move_up)
game.canvas.bind_all('<KeyPress-Down>', self.move_down)
game.canvas.bind_all('<KeyPress-Left>', self.halt)
game.canvas.bind_all('<KeyPress-Right>', self.halt)
game.canvas.bind_all('<KeyPress-Up>', self.halt)
game.canvas.bind_all('<KeyPress-Down>', self.halt)
game.canvas.bind_all('<space>', self.shoot)
def move_left(self, evt):
x_move = self.x - 1.5
self.x = x_move
def move_right(self, evt):
x_move = self.x + 1.5
self.x = x_move
def move_up(self, evt):
y_move = self.y - 1.5
self.y = y_move
def move_down(self, evt):
y_move = self.y + 1.5
self.y = y_move
def halt(self, evt):
time.sleep(0.01)
if x_move < 0:
x_move = -1.5
elif x_move > 0:
x_move = 1.5
elif y_move < 0:
y_move = -1.5
elif y_move > 0:
y_move = 1.5
def shoot(self, evt):
print("Placeholder")
def move(self):
self.game.canvas.move(self.image, self.x, self.y)
def coords(self):
xy = self.game.canvas.coords(self.image)
self.coordinates.x1 = xy[0]
self.coordinates.y1 = xy[1]
self.coordinates.x2 = xy[0] + 24
self.coordinates.y2 = xy[1] + 32
return self.coordinates
g = Game()
sp = PlayerSprite(g)
g.sprites.append(sp)
g.mainloop()
My goal is to have my character move at a constant rate (as opposed to uncontrollably fast after a while) when the respective key is pressed.
The most straightforward solution to your question would be to avoid adding a value at every keypress, but rather set a constant value.
def move_left(self, evt):
x_move = -5
self.x = x_move
The movement would however lose its dynamic, but it will be constant. Otherwise, you could create a max value. Something like this:
def move_left(self, evt):
int max_val_left = -10
if( self.x < max_val_left):
x_move = self.x - 1.5
self.x = x_move
Thereby forcing self.x to remain capped and constant if it has reached the max_val.
Holding down a key is essentially the same as pressing that key repeatedly. What you're doing by adding to/subtracting from the self.x/self.y attributes in your move_* functions is you're increasing the amount that the canvas will move your player sprite in each function call (e.g. from 1.5 to 3 to 4.5 to 6, etc. as you hold down a direcitonal key).
Since the canvas will be moving your player by (self.x, self.y) units every time "move" is called under the "PlayerSprite" class, we want self.x and self.y to be either 0 or whatever speed you desire (1.5 in the following code). So instead of adding to self.x and self.y, we should assign it to a constant value:
def move_left(self, evt):
self.x = -1.5
def move_right(self, evt):
self.x = 1.5
def move_up(self, evt):
self.y = -1.5
def move_down(self, evt):
self.y = -1.5
Also, instead of using "halt", what you could do is include 'KeyRelease-*' bindings to stop your player once you've stopped holding down a directional key:
game.canvas.bind_all('KeyRelease-Left'>, self.stop_horz_move)
game.canvas.bind_all('KeyRelease-Right'>, self.stop_horz_move)
game.canvas.bind_all('KeyRelease-Up'>, self.stop_vert_move)
game.canvas.bind_all('KeyRelease-Down'>, self.stop_vert_move)
(I've generalized the left and right directions to horz as well as up and down to vert to save on the number of function definitions.)
Then you can create functions that assign your self.x value or self.y value to 0, so that your player doesn't move once "move" is called.
def stop_move_horz(self, evt):
self.x = 0
def stop_move_vert(self, evt):
self.y = 0