Related
im working on a game in python arcade, where the player runs around, shoots zombies, etc. very basic game. just recently, i started implementing the spawn over time part, but i get an error that just makes no sense:
AttributeError: 'MyGame' object has no attribute 'total_time'
it makes no sense because i have stated self.total_time in MyGame.
how do i fix this?
import arcade
import random
import math
import arcade.gui
import time
import timeit
SPRITE_SCALING = 0.35
SPRITE_SCALING_LASER = 0.8
SCREEN_WIDTH = 1280
SCREEN_HEIGHT = 720
SCREEN_TITLE = "zombier shooter"
ENEMY_COUNT = 20
BULLET_SPEED = 30
MOVEMENT_SPEED = 5
SPRITE_SPEED = 1
INDICATOR_BAR_OFFSET = 32
ENEMY_ATTACK_COOLDOWN = 1
PLAYER_HEALTH = 5
SCENE_MENU = 'SCENE_MENU'
SCENE_GAME = 'SCENE_GAME'
class QuitButton(arcade.gui.UIFlatButton):
def on_click(self, event: arcade.gui.UIOnClickEvent):
arcade.exit()
class Player(arcade.Sprite):
def update(self):
""" moves the player """
# move player.
self.center_x += self.change_x
self.center_y += self.change_y
# check for out of bounds
if self.left < 0:
self.left = 0
elif self.right > SCREEN_WIDTH - 1:
self.right = SCREEN_WIDTH - 1
if self.bottom < 0:
self.bottom = 0
elif self.top > SCREEN_HEIGHT - 1:
self.top = SCREEN_HEIGHT - 1
class Enemy(arcade.Sprite):
"""
This class represents the enemies on our screen.
"""
def follow_sprite(self, player_sprite):
"""
This function will move the current sprite towards whatever
other sprite is specified as a parameter.
"""
if self.center_y < player_sprite.center_y:
self.center_y += min(SPRITE_SPEED, player_sprite.center_y - self.center_y)
elif self.center_y > player_sprite.center_y:
self.center_y -= min(SPRITE_SPEED, self.center_y - player_sprite.center_y)
if self.center_x < player_sprite.center_x:
self.center_x += min(SPRITE_SPEED, player_sprite.center_x - self.center_x)
elif self.center_x > player_sprite.center_x:
self.center_x -= min(SPRITE_SPEED, self.center_x - player_sprite.center_x)
class MyGame(arcade.Window):
"""
main game class
"""
def __init__(self, width, height, title):
"""
initialises stuff
"""
# call the parent class initializer
super().__init__(width, height, title)
self.scene = SCENE_MENU
# variables that will hold sprite lists
self.player_list = None
# set up the player info
self.player_sprite = None
# track the current state of what key is pressed
self.left_pressed = False
self.right_pressed = False
self.up_pressed = False
self.down_pressed = False
# --- Required for all code that uses UI element,
# a UIManager to handle the UI.
self.manager = arcade.gui.UIManager()
self.manager.enable()
# Set background color
arcade.set_background_color(arcade.color.DARK_BLUE_GRAY)
# Create a vertical BoxGroup to align buttons
self.v_box = arcade.gui.UIBoxLayout()
# Create the buttons
start_button = arcade.gui.UIFlatButton(text="Start Game", width=200)
self.v_box.add(start_button.with_space_around(bottom=20))
settings_button = arcade.gui.UIFlatButton(text="Settings", width=200)
self.v_box.add(settings_button.with_space_around(bottom=20))
# Again, method 1. Use a child class to handle events.
quit_button = QuitButton(text="Quit", width=200)
self.v_box.add(quit_button)
# --- Method 2 for handling click events,
# assign self.on_click_start as callback
start_button.on_click = self.on_click_start
# --- Method 3 for handling click events,
# use a decorator to handle on_click events
#settings_button.event("on_click")
def on_click_settings(event):
print("Settings:", event)
# Create a widget to hold the v_box widget, that will center the buttons
self.manager.add(
arcade.gui.UIAnchorWidget(
anchor_x="center_x",
anchor_y="center_y",
child=self.v_box)
)
def setup(self):
""" Set up the game and initialize the variables. """
# sprite lists
self.player_list = arcade.SpriteList()
self.enemy_list = arcade.SpriteList()
self.bullet_list = arcade.SpriteList()
#setup timer
self.total_time = 0.0
# setup score
self.score = 0
self.score_text = None
# setup health info
self.health = 5
self.health_text = None
self.dead = None
# set up the player
self.player_sprite = Player(":resources:images/animated_characters/female_person/femalePerson_idle.png",
SPRITE_SCALING)
self.player_sprite.center_x = 50
self.player_sprite.center_y = 50
self.player_list.append(self.player_sprite)
def on_draw(self):
""" render the screen. """
# clear the screen
self.clear()
if self.scene == SCENE_MENU:
self.manager.draw()
elif self.scene == SCENE_GAME:
# draw all the sprites.
self.player_list.draw()
self.enemy_list.draw()
self.bullet_list.draw()
# put score text on the screen
output = f"Score: {self.score}"
arcade.draw_text(output, 10, 20, arcade.color.WHITE, 14)
# put helth text on the screen
output = f"Health: {self.health}"
arcade.draw_text(output, 10, 40, arcade.color.WHITE, 14)
if self.health <= 0:
self.player_sprite.remove_from_sprite_lists()
# put u died text on the screen
output = f"YOU DIED"
arcade.draw_text(output, 500, 400, arcade.color.RED, 50)
output = f"Click to Exit"
arcade.draw_text(output, 550, 300, arcade.color.BLACK, 30)
def on_click_start(self, event):
self.setup()
self.scene = SCENE_GAME
self.manager.disable()
print("Start:", event)
def on_mouse_press(self, x, y, button, modifiers):
""" Called whenever the mouse button is clicked. """
if self.health <= 0:
exit()
# create a bullet
bullet = arcade.Sprite(":resources:images/space_shooter/laserBlue01.png", SPRITE_SCALING_LASER)
# Position the bullet at the player's current location
start_x = self.player_sprite.center_x
start_y = self.player_sprite.center_y
bullet.center_x = start_x
bullet.center_y = start_y
# Get from the mouse the destination location for the bullet
# IMPORTANT! If you have a scrolling screen, you will also need
# to add in self.view_bottom and self.view_left.
dest_x = x
dest_y = y
# Do math to calculate how to get the bullet to the destination.
# Calculation the angle in radians between the start points
# and end points. This is the angle the bullet will travel.
x_diff = dest_x - start_x
y_diff = dest_y - start_y
angle = math.atan2(y_diff, x_diff)
# Angle the bullet sprite so it doesn't look like it is flying
# sideways.
bullet.angle = math.degrees(angle)
print(f"Bullet angle: {bullet.angle:.2f}")
# Taking into account the angle, calculate our change_x
# and change_y. Velocity is how fast the bullet travels.
bullet.change_x = math.cos(angle) * BULLET_SPEED
bullet.change_y = math.sin(angle) * BULLET_SPEED
# Add the bullet to the appropriate lists
self.bullet_list.append(bullet)
def update_player_speed(self):
# calculate speed based on the keys pressed
self.player_sprite.change_x = 0
self.player_sprite.change_y = 0
if self.up_pressed and not self.down_pressed:
self.player_sprite.change_y = MOVEMENT_SPEED
elif self.down_pressed and not self.up_pressed:
self.player_sprite.change_y = -MOVEMENT_SPEED
if self.left_pressed and not self.right_pressed:
self.player_sprite.change_x = -MOVEMENT_SPEED
elif self.right_pressed and not self.left_pressed:
self.player_sprite.change_x = MOVEMENT_SPEED
def on_update(self, delta_time):
""" updates values n stuff """
if self.scene == SCENE_GAME:
# call update to move the sprite
self.player_list.update()
# Call update on all sprites
self.bullet_list.update()
# go through each bullet
for bullet in self.bullet_list:
# check each bullet to see if it hit a zombie
hit_list = arcade.check_for_collision_with_list(bullet, self.enemy_list)
# if it did, remove the bullet
if len(hit_list) > 0:
bullet.remove_from_sprite_lists()
# for each enemy we hit with a bullet, remove enemy and add to the score
for enemy in hit_list:
enemy.remove_from_sprite_lists()
self.score += 1
# if bullet goes off screen, then remove it
if bullet.bottom > self.width or bullet.top < 0 or bullet.right < 0 or bullet.left > self.width:
bullet.remove_from_sprite_lists()
for enemy in self.enemy_list:
Enemy.follow_sprite(enemy, self.player_sprite)
# create a list of all sprites that had a collision with the player.
hit_list = arcade.check_for_collision_with_list(self.player_sprite, self.enemy_list)
# go through each sprite, if it got hit, then remove the sprite and lower score and health
for enemy in hit_list:
enemy.remove_from_sprite_lists()
self.score -= 1
self.health -= 1
# Accumulate the total time
self.total_time += delta_time
# Calculate minutes
minutes = int(self.total_time) // 60
# Calculate seconds by using a modulus (remainder)
seconds = int(self.total_time) % 60
# Calculate 100s of a second
seconds_100s = int((self.total_time - seconds) * 100)
if self.total_time > 5:
for i in range(5):
# enemy texture
enemy = arcade.Sprite(":resources:images/animated_characters/zombie/zombie_idle.png", SPRITE_SCALING)
enemy.center_x = random.randrange(SCREEN_WIDTH)
enemy.center_y = random.randrange(SCREEN_HEIGHT)
self.enemy_list.append(enemy)
self.total_time = 0.0
def on_key_press(self, key, modifiers):
"""called when user presses a key. """
if key == arcade.key.UP:
self.up_pressed = True
self.update_player_speed()
elif key == arcade.key.DOWN:
self.down_pressed = True
self.update_player_speed()
elif key == arcade.key.LEFT:
self.left_pressed = True
self.update_player_speed()
elif key == arcade.key.RIGHT:
self.right_pressed = True
self.update_player_speed()
def on_key_release(self, key, modifiers):
"""called when user releases a key. """
if key == arcade.key.UP:
self.up_pressed = False
self.update_player_speed()
elif key == arcade.key.DOWN:
self.down_pressed = False
self.update_player_speed()
elif key == arcade.key.LEFT:
self.left_pressed = False
self.update_player_speed()
elif key == arcade.key.RIGHT:
self.right_pressed = False
self.update_player_speed()
def main():
""" Main function """
MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
arcade.run()
if __name__ == "__main__":
main()
You defined self.total_time in setup() and not in __init__(), which should be a red-flag, because it could happen that self.total_time is actually accessed before setup() is called. And exactly this happened.
The error appears in on_update() when you try to run self.total_time += delta_time - in order for this line to work, self.total_time has to be initialised before - but setup() wasn't executed before, so self.total_time 'does not exist' yet.
So you can fix your code by moving the line self.total_time = 0.0 from setup() to __init__() - this way you make sure that you create this variable once the class gets initialised and before anything else gets executed.
If you plan on using variables in a class across different methods it's better to define them in __init__() to avoid such problems.
I've been migrating from Pygame to Arcade and overall it's much better. That said, the method I was using to draw the lines of my track for my car game in Pygame is exorbitantly laggy in Arcade. I know it's lagging from drawing all the lines, I'm just wondering if there's a better way to do it that doesn't cause so much lag.
import arcade
import os
import math
import numpy as np
SPRITE_SCALING = 0.5
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
SCREEN_TITLE = "Move Sprite by Angle Example"
MOVEMENT_SPEED = 5
ANGLE_SPEED = 5
class Player(arcade.Sprite):
""" Player class """
def __init__(self, image, scale):
""" Set up the player """
# Call the parent init
super().__init__(image, scale)
# Create a variable to hold our speed. 'angle' is created by the parent
self.speed = 0
def update(self):
# Convert angle in degrees to radians.
angle_rad = math.radians(self.angle)
# Rotate the ship
self.angle += self.change_angle
# Use math to find our change based on our speed and angle
self.center_x += -self.speed * math.sin(angle_rad)
self.center_y += self.speed * math.cos(angle_rad)
def upgraded_distance_check(player_sprite, point_arrays, distance_cap):
center_coord = player_sprite.center
intersect_array = []
distance_array = []
sensor_array = player_sprite.points
for sensor in sensor_array:
intersect_array.append([-10000, -10000])
for point_array in point_arrays:
for i in range(len(point_array[:-1])):
v = line_intersection(
[sensor, center_coord], [point_array[i], point_array[i + 1]]
)
if v == (None, None):
continue
if (
(point_array[i][0] <= v[0] and point_array[i + 1][0] >= v[0])
or (point_array[i][0] >= v[0] and point_array[i + 1][0] <= v[0])
) and (
(point_array[i][1] <= v[1] and point_array[i + 1][1] >= v[1])
or (point_array[i][1] >= v[1] and point_array[i + 1][1] <= v[1])
):
if intersect_array[-1] is None or math.sqrt(
(intersect_array[-1][0] - center_coord[0]) ** 2
+ (intersect_array[-1][1] - center_coord[1]) ** 2
) > math.sqrt(
(v[0] - center_coord[0]) ** 2
+ (v[1] - center_coord[1]) ** 2
):
if not is_between(sensor, center_coord, v):
intersect_array[-1] = v
for i in range(len(sensor_array)):
if distance(sensor_array[i], intersect_array[i]) > distance_cap:
intersect_array[i] = None
distance_array.append(None)
else:
distance_array.append(distance(sensor_array[i], intersect_array[i]))
return intersect_array
class MyGame(arcade.Window):
"""
Main application class.
"""
def __init__(self, width, height, title):
"""
Initializer
"""
# Call the parent class initializer
super().__init__(width, height, title)
# Set the working directory (where we expect to find files) to the same
# directory this .py file is in. You can leave this out of your own
# code, but it is needed to easily run the examples using "python -m"
# as mentioned at the top of this program.
file_path = os.path.dirname(os.path.abspath(__file__))
os.chdir(file_path)
# Variables that will hold sprite lists
self.player_list = None
# Set up the player info
self.player_sprite = None
# Set the background color
arcade.set_background_color(arcade.color.WHITE)
def setup(self):
""" Set up the game and initialize the variables. """
# Sprite lists
self.player_list = arcade.SpriteList()
# Set up the player
self.player_sprite = Player(":resources:images/space_shooter/playerShip1_orange.png", SPRITE_SCALING)
self.player_sprite.center_x = SCREEN_WIDTH / 2
self.player_sprite.center_y = SCREEN_HEIGHT / 2
self.player_list.append(self.player_sprite)
#Setup all the array BS
self.track_arrays = []
self.drawing = False
def on_draw(self):
"""
Render the screen.
"""
# This command has to happen before we start drawing
arcade.start_render()
# Draw all the sprites.
# for i, ele in enumerate(self.player_sprite.points):
# arcade.draw_circle_filled(ele[0], ele[1], 7, arcade.color.AO)
self.player_list.draw()
if len(self.track_arrays) > 0 and len(self.track_arrays[0]) > 2:
for track_array in self.track_arrays:
for i in range(len(track_array[:-1])):
arcade.draw_line(track_array[i][0], track_array[i][1], track_array[i+1][0], track_array[i+1][1], arcade.color.BLACK, 1)
def on_update(self, delta_time):
""" Movement and game logic """
# Call update on all sprites (The sprites don't do much in this
# example though.)
self.player_list.update()
# print(self.drawing)
# print(self.player_sprite.points)
def on_key_press(self, key, modifiers):
"""Called whenever a key is pressed. """
# Forward/back
if key == arcade.key.UP:
self.player_sprite.speed = MOVEMENT_SPEED
elif key == arcade.key.DOWN:
self.player_sprite.speed = -MOVEMENT_SPEED
# Rotate left/right
elif key == arcade.key.LEFT:
self.player_sprite.change_angle = ANGLE_SPEED
elif key == arcade.key.RIGHT:
self.player_sprite.change_angle = -ANGLE_SPEED
def on_key_release(self, key, modifiers):
"""Called when the user releases a key. """
if key == arcade.key.UP or key == arcade.key.DOWN:
self.player_sprite.speed = 0
elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
self.player_sprite.change_angle = 0
def on_mouse_press(self, x, y, button, modifiers):
"""
Called when the user presses a mouse button.
"""
self.track_arrays.append([])
self.drawing = True
def on_mouse_release(self, x, y, button, modifiers):
"""
Called when a user releases a mouse button.
"""
self.drawing = False
def on_mouse_motion(self, x, y, dx, dy):
""" Called to update our objects. Happens approximately 60 times per second."""
if self.drawing:
self.track_arrays[-1].append([x,y])
def main():
""" Main method """
window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
window.setup()
arcade.run()
if __name__ == "__main__":
main()
I've been working on a Pong game, using the turtle module in Python. Below is my code:
from turtle import Turtle, _Screen, TurtleScreen
from random import choice, randrange, randint
from tkinter import *
from tkinter import messagebox
class Field(_Screen):
def __init__(self, width = 1024, height = 600):
# Get __init__ from _Screen
super().__init__()
# Get __init__ from TurtleScreen (parent class of _Screen)
TurtleScreen.__init__(self, self._canvas)
if Turtle._screen is None:
Turtle._screen = self
self.width = width
self.height = height
self.setup(self.width+100,self.height+50)
self.screensize(self.width,self.height)
self.title("Pong")
self.bgcolor("black")
# Define size of score bar, above the play field
self.score_height = self.height/10
# Offset 0 axis line, due to score bar
self.yzero = -(self.height/2 - (self.height-self.score_height)/2)
class Ball(Turtle):
def __init__(self, velocity = 5, size = 1, color = "white"):
super().__init__(shape="circle",visible=False)
self.color(color)
self.speed(0)
self.penup()
self.shapesize(size,size)
self.setposition(0,field.yzero)
self.st()
self.velocity = velocity
self.dirrection = 0
def player_collision(self,player):
bx,by = self.position()
px,py = player.position()
x_off = ball.shapesize()[1]*10 + player.shapesize()[0]*10
y_off = ball.shapesize()[0]*10 + player.shapesize()[1]*10
if px > 0:
if (bx > px-x_off and by <= py+y_off and by >= py-y_off):
return True
elif px < 0:
if (bx < px+x_off and by <= py+y_off and by >= py-y_off):
return True
return False
def court_collision(self,court):
if (ball.ycor() >= ((field.height/2)-
court.score_height-self.shapesize()[0]*10)
or ball.ycor() <= -court.height/2+10): return True
return False
def out_left(self,court):
if self.xcor() <= -court.width/2: return True
return False
def out_right(self,court):
if self.xcor() >= court.width/2: return True
return False
class Player(Turtle):
def __init__(self, x=0, y=0, color="white", up=None, down=None):
super().__init__(shape="square",visible=False)
self.color(color)
self.speed(0)
self.penup()
# setup player paddle
self.shapesize(1,10)
# Rotate turtle, to allow the use of forward method
self.setheading(90)
self.setposition(x,y)
self.st()
self.score = 0
self.height = self.shapesize()[1]*10
self.velocity = 50
self.ondrag(self.drag)
self.upkey = up
self.downkey = down
def drag(self,x,y):
self.ondrag(None) # Disable event handler to avoid recursion
if y >= (field.height/2-field.score_height) - self.height:
y = (field.height/2-field.score_height) - self.height
if y <= -field.height/2+self.height:
y = -field.height/2+self.height
self.goto(self.xcor(),y)
self.ondrag(self.drag) # Reactivate event handler
def up(self):
#field.onkeypress(None, self.upkey)
if (self.ycor()+self.height <=
(field.height-field.score_height)/2+field.yzero):
self.forward(self.velocity)
#field.onkeypress(self.up, self.upkey)
def down(self):
#field.onkeypress(None, self.downkey)
if self.ycor()-self.height >= -field.height/2:
self.forward(-self.velocity)
#field.onkeypress(self.down, self.downkey)
class Score(Turtle):
def __init__(self):
super().__init__(visible=False)
self.speed(0)
self.color("white")
self.pensize(3)
# Draw lower border
self.penup()
self.goto(-field.width,-field.height/2)
self.pendown()
self.goto(field.width,-field.height/2)
# Draw upper border
self.penup()
self.goto(-field.width,field.height/2-field.score_height)
self.pendown()
self.goto(field.width,field.height/2-field.score_height)
self.penup()
# Draw score
self.goto(-100,field.height/2-field.score_height)
self.write(player2.score,font=("Monospace",50,"bold"))
self.goto(100,field.height/2-field.score_height)
self.write(player1.score,font=("Monospace",50,"bold"))
def update(self):
# Clear the previous score
for i in range(3):
self.undo()
# And write the new one
self.write(player2.score,font=("Monospace",50,"bold"))
self.goto(100,field.height/2-field.score_height)
self.write(player1.score,font=("Monospace",50,"bold"))
class Game:
def __init__(self,court,difficulty=0):
# Difficulty = increase in ball speed
self.difficulty = difficulty
# Setup event handlers
court.onkeypress(self.qt, "Escape")
court.onkeypress(player1.up, player1.upkey)
court.onkeypress(player1.down, player1.downkey)
court.onkeypress(player2.up, player2.upkey)
court.onkeypress(player2.down, player2.downkey)
court.onkey(self.pause, "p")
court.listen()
# Try to implement game pause. Not working, for the moment
#self.pause = False
#self.pause_count = 0
def reset(self):
ball.setposition(0,field.yzero)
player1.setposition(player1.xcor(),field.yzero)
player2.setposition(player2.xcor(),field.yzero)
ball.dirrection = choice([0,180]) # Left or right
ball.setheading(ball.dirrection+randrange(-80,80))
def restart(self):
self.reset()
self.player1_score = 0
self.player2_score = 0
self.difficulty = 0
def qt(self):
prompt = Tk()
prompt.eval('tk::PlaceWindow %s center' % prompt.winfo_toplevel())
prompt.withdraw()
answer = messagebox.askyesno("Quit", "Are you sure you want to quit?")
if answer == True:
field.bye()
return
# Not currently working
def pause(self):
if self.pause_count % 2 == 0:
self.pause == True
else:
self.pause = False
class Play(Turtle):
def __init__(self):
super().__init__(visible=False)
self.shape("square")
self.color("white")
self.speed(0)
self.penup()
self.shapesize(2,4)
self.goto(-field.width/2,field.height/2-field.score_height/2)
self.write("Play",font=("Monospace",20,"bold"))
field.onscreenclick(self.click)
def click(self,x,y):
print(x,y)
if (x <= -field.width/2+field.width/2/10 and
x >= -field.width/2 and
y >= field.height/2-field.score_height/2 and y <= field.height/2):
self.color("green")
self.clear()
self.write("Play",font=("Monospace",20,"bold"))
self.color("white")
self.clear()
self.write("Play",font=("Monospace",20,"bold"))
game.reset()
main()
def main():
ball.forward(ball.velocity+game.difficulty)
# Check for paddle collision
if ball.player_collision(player1) or ball.player_collision(player2):
ball.setheading(180 - ball.heading())
# Bounce from upper or lower border
if ball.court_collision(field):
ball.setheading(-ball.heading())
# Check for ball out of field and update player score
elif ball.out_right(field):
game.reset()
player2.score += 1
score.update()
game.difficulty += 0.5
elif ball.out_left(field):
game.reset()
player1.score += 1
score.update()
game.difficulty += 0.5
field.ontimer(main)
if __name__ == "__main__":
field = Field(1280,720)
ball = Ball()
player1 = Player(field.width/2,field.yzero,up = "Up", down = "Down")
player2 = Player(-field.width/2,field.yzero, up = "w", down = "s")
game = Game(field)
score = Score()
play_button = Play()
#field.mainloop()
It kind of works, but if you use the keys to play, it will eventually return an error:
RecursionError: maximum recursion depth exceeded while calling a
Python object
It would first seem that the problem is with the main() function, but the actual problem is with the event-handler for the key presses. If I only play using the mouse, the game will give no error, it will just feel jerky.
I've read the following subjects so far:
maximum recursion depth exceeded
Avoid RecursionError in turtle paint code
Turtle.onkeypress not working (Python)
And tried to implement the solutions found there. The only one that works for me is disabling the event-handler for the ondrag() function. If I try to use the same solution on the player (uncomment the lines in up() and down() methods of Player) it will only work when main() is not running. If I start the main() function it will just run once and deactivate.
So what I need help with is:
Avoiding the maximum recursion error. (it only happens when main() is active);
Making the ondrag function work without jerking the main() function;
the qt() method from the game class is not properly working if main() is running.
So do you guys think I can improve these aspects?
Edit: Below is the full traceback
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\Bogey\AppData\Local\Programs\Python\Python37-32\lib\tkinter\__init__.py", line 1702, in __call__
return self.func(*args)
File "C:\Users\Bogey\AppData\Local\Programs\Python\Python37-32\lib\tkinter\__init__.py", line 746, in callit
func(*args)
File "C:/Users/Bogey/Desktop/asd.py", line 209, in main
ball.forward(ball.velocity+game.difficulty)
File "C:\Users\Bogey\AppData\Local\Programs\Python\Python37-32\lib\turtle.py", line 1637, in forward
self._go(distance)
File "C:\Users\Bogey\AppData\Local\Programs\Python\Python37-32\lib\turtle.py", line 1604, in _go
ende = self._position + self._orient * distance
RecursionError: maximum recursion depth exceeded
The primary issue I see is you're doing too much needless calculation during game play. For example, consider the court_collision() method which gets called on every ball movement:
def court_collision(self,court):
if (ball.ycor() >= ((field.height/2)-
court.score_height-self.shapesize()[0]*10)
or ball.ycor() <= -court.height/2+10): return True
return False
Of all these values, only ball.ycor() is changing, the rest should have been computed before game play began and stashed so that the method looks more like:
def court_collision(self):
return not self.wall_top_offset > ball.ycor() > self.wall_bottom_offset
Ditto for player_collision(), drag(), etc.
The main() function should be really be the move() method of Ball.
I've other nits but they don't have anything to do with game performance.
I'm trying to write a game in pygame, involving a moving object with a "turret" that swivels to follow a mouse. As of now, I'm mostly trying to expand upon examples, so the code's not entirely mine (credit to Sean J. McKiernan for his sample programs); however, this portion is. Below is my code; I use the center of rect (the "base" shape and the point around which the "turret" swivels) as the base point, and the position of the mouse as the other point. By subtracting the mouse's displacement from the displacement of the "center," I effectively get a vector between the two points and find the angle between that vector and the x-axis with atan2. Below is the code I use to do that:
def get_angle(self, mouse):
x_off = (mouse[0]-self.rect.centerx)
y_off = (mouse[1]-self.rect.centery)
self.angle = math.degrees(math.atan2(-y_off, x_off) % 2*math.pi)
self.hand = pg.transform.rotate(self.original_hand, self.angle)
self.hand_rect = self.hand.get_rect(center=self.hand_rect.center)
According to multiple tutorials I've reviewed, this SHOULD be the correct code; however, I discovered later that those tutorials (and, in fact, this tutorial) were all for Python 2.7, while I am trying to write in Python 3.6. I don't think that should make a difference in this scenario, though. As it stands, the view appears to depend entirely upon the "character's" position on the screen. If the "character" is in one corner, the reaction of the "turret" is different than the reaction if the "character" is in the middle of the screen. However, this shouldn't matter; the position of the "character" relative to the mouse is the exact same no matter where on the screen they are. Any ideas, or do I need to supply more code?
Edit: Apparently, more code is required. Rather than attempt to extricate only the entirely necessary parts, I've provided the entire code sample, so everyone can run it. As a side note, the "Bolt" things (intended to fire simple yellow blocks) don't work either, but I'm just trying to get the arm working before I start in on debugging that.
Edit the second: I have discovered that the "Bolt" system works within a certain distance of the origin (0,0 in the window coordinate system), and that the arm also works within a much lesser distance. I added the line Block(pg.Color("chocolate"), (0,0,100,100)) under the "walls" grouping as a decision point, and the block was positioned in the top left corner. I've corrected Bolt by changing screen_rect to viewport in the control loop; however, I don't know why the "arm" swinging is dependent on adjacency to the origin. The positions of the mouse and "character" SHOULD be absolute. Am I missing something?
"""
Basic moving platforms using only rectangle collision.
-Written by Sean J. McKiernan 'Mekire'
Edited for a test of "arms"
"""
import os
import sys
import math
import pygame as pg
CAPTION = "Moving Platforms"
SCREEN_SIZE = (700,700)
BACKGROUND_COLOR = (50, 50, 50)
COLOR_KEY = (255, 255, 255)
class _Physics(object):
"""A simplified physics class. Psuedo-gravity is often good enough."""
def __init__(self):
"""You can experiment with different gravity here."""
self.x_vel = self.y_vel = 0
self.grav = 0.4
self.fall = False
def physics_update(self):
"""If the player is falling, add gravity to the current y velocity."""
if self.fall:
self.y_vel += self.grav
else:
self.y_vel = 0
class Player(_Physics, object):
def __init__(self,location,speed):
_Physics.__init__(self)
HAND = pg.image.load("playertst2.png").convert()
HAND.set_colorkey(COLOR_KEY)
self.image = pg.image.load('playertst.png').convert()
self.rect = self.image.get_rect(topleft=location)
self.speed = speed
self.jump_power = -9.0
self.jump_cut_magnitude = -3.0
self.on_moving = False
self.collide_below = False
self.original_hand = HAND
self.fake_hand = self.original_hand.copy()
self.hand = self.original_hand.copy()
self.hand_rect = self.hand.get_rect(topleft=location)
self.angle = self.get_angle(pg.mouse.get_pos())
def check_keys(self, keys):
"""Find the player's self.x_vel based on currently held keys."""
self.x_vel = 0
if keys[pg.K_LEFT] or keys[pg.K_a]:
self.x_vel -= self.speed
if keys[pg.K_RIGHT] or keys[pg.K_d]:
self.x_vel += self.speed
def get_position(self, obstacles):
"""Calculate the player's position this frame, including collisions."""
if not self.fall:
self.check_falling(obstacles)
else:
self.fall = self.check_collisions((0,self.y_vel), 1, obstacles)
if self.x_vel:
self.check_collisions((self.x_vel,0), 0, obstacles)
def check_falling(self, obstacles):
"""If player is not contacting the ground, enter fall state."""
if not self.collide_below:
self.fall = True
self.on_moving = False
def check_moving(self,obstacles):
"""
Check if the player is standing on a moving platform.
If the player is in contact with multiple platforms, the prevously
detected platform will take presidence.
"""
if not self.fall:
now_moving = self.on_moving
any_moving, any_non_moving = [], []
for collide in self.collide_below:
if collide.type == "moving":
self.on_moving = collide
any_moving.append(collide)
else:
any_non_moving.append(collide)
if not any_moving:
self.on_moving = False
elif any_non_moving or now_moving in any_moving:
self.on_moving = now_moving
def check_collisions(self, offset, index, obstacles):
"""
This function checks if a collision would occur after moving offset
pixels. If a collision is detected, the position is decremented by one
pixel and retested. This continues until we find exactly how far we can
safely move, or we decide we can't move.
"""
unaltered = True
self.rect[index] += offset[index]
self.hand_rect[index] += offset[index]
while pg.sprite.spritecollideany(self, obstacles):
self.rect[index] += (1 if offset[index]<0 else -1)
self.hand_rect[index] += (1 if offset[index]<0 else -1)
unaltered = False
return unaltered
def check_above(self, obstacles):
"""When jumping, don't enter fall state if there is no room to jump."""
self.rect.move_ip(0, -1)
collide = pg.sprite.spritecollideany(self, obstacles)
self.rect.move_ip(0, 1)
return collide
def check_below(self, obstacles):
"""Check to see if the player is contacting the ground."""
self.rect.move_ip((0,1))
collide = pg.sprite.spritecollide(self, obstacles, False)
self.rect.move_ip((0,-1))
return collide
def jump(self, obstacles):
"""Called when the user presses the jump button."""
if not self.fall and not self.check_above(obstacles):
self.y_vel = self.jump_power
self.fall = True
self.on_moving = False
def jump_cut(self):
"""Called if player releases the jump key before maximum height."""
if self.fall:
if self.y_vel < self.jump_cut_magnitude:
self.y_vel = self.jump_cut_magnitude
def get_angle(self, mouse):
x_off = (mouse[0]-self.rect.centerx)
y_off = (mouse[1]-self.rect.centery)
self.angle = math.degrees(math.atan2(-y_off, x_off) % (2*math.pi))
self.hand = pg.transform.rotate(self.original_hand, self.angle)
self.hand_rect = self.hand.get_rect(center=self.hand_rect.center)
"""
offset = (mouse[1]-self.hand_rect.centery, mouse[0]-self.hand_rect.centerx)
self.angle = math.atan2(-offset[0], offset[1]) % (2 * math.pi)
self.angle = math.degrees(self.angle)
self.hand = pg.transform.rotate(self.original_hand, self.angle)
self.hand_rect = self.hand.get_rect(center=self.rect.center)
self.angle = 135-math.degrees(math.atan2(*offset))
self.hand = pg.transform.rotate(self.original_hand, self.angle)
self.hand_rect = self.hand.get_rect(topleft=self.rect.topleft)
"""
def pre_update(self, obstacles):
"""Ran before platforms are updated."""
self.collide_below = self.check_below(obstacles)
self.check_moving(obstacles)
def update(self, obstacles, keys):
"""Everything we need to stay updated; ran after platforms update."""
self.check_keys(keys)
self.get_position(obstacles)
self.physics_update()
def get_event(self, event, bolts):
if event.type == pg.MOUSEBUTTONDOWN and event.button == 1:
bolts.add(Bolt(self.rect.center))
elif event.type == pg.MOUSEMOTION:
self.get_angle(event.pos)
def draw(self, surface):
"""Blit the player to the target surface."""
surface.blit(self.image, self.rect)
surface.blit(self.hand, self.hand_rect)
class Bolt(pg.sprite.Sprite):
def __init__(self, location):
pg.sprite.Sprite.__init__(self)
"""self.original_bolt = pg.image.load('bolt.png')"""
"""self.angle = -math.radians(angle-135)"""
"""self.image = pg.transform.rotate(self.original_bolt, angle)"""
"""self.image = self.original_bolt"""
self.image=pg.Surface((5,10)).convert()
self.image.fill(pg.Color("yellow"))
self.rect = self.image.get_rect(center=location)
self.move = [self.rect.x, self.rect.y]
self.speed_magnitude = 5
"""self.speed = (self.speed_magnitude*math.cos(self.angle), self.speed_magnitude*math.sin(self.angle))"""
"""self.speed = (5,0)"""
self.done = False
def update(self, screen_rect, obstacles):
self.move[0] += self.speed_magnitude
"""self.move[1] += self.speed[1]"""
self.rect.topleft = self.move
self.remove(screen_rect, obstacles)
def remove(self, screen_rect, obstacles):
if not self.rect.colliderect(screen_rect):
self.kill()
class Block(pg.sprite.Sprite):
"""A class representing solid obstacles."""
def __init__(self, color, rect):
"""The color is an (r,g,b) tuple; rect is a rect-style argument."""
pg.sprite.Sprite.__init__(self)
self.rect = pg.Rect(rect)
self.image = pg.Surface(self.rect.size).convert()
self.image.fill(color)
self.type = "normal"
class MovingBlock(Block):
"""A class to represent horizontally and vertically moving blocks."""
def __init__(self, color, rect, end, axis, delay=500, speed=2, start=None):
"""
The moving block will travel in the direction of axis (0 or 1)
between rect.topleft and end. The delay argument is the amount of time
(in miliseconds) to pause when reaching an endpoint; speed is the
platforms speed in pixels/frame; if specified start is the place
within the blocks path to start (defaulting to rect.topleft).
"""
Block.__init__(self, color, rect)
self.start = self.rect[axis]
if start:
self.rect[axis] = start
self.axis = axis
self.end = end
self.timer = 0.0
self.delay = delay
self.speed = speed
self.waiting = False
self.type = "moving"
def update(self, player, obstacles):
"""Update position. This should be done before moving any actors."""
obstacles = obstacles.copy()
obstacles.remove(self)
now = pg.time.get_ticks()
if not self.waiting:
speed = self.speed
start_passed = self.start >= self.rect[self.axis]+speed
end_passed = self.end <= self.rect[self.axis]+speed
if start_passed or end_passed:
if start_passed:
speed = self.start-self.rect[self.axis]
else:
speed = self.end-self.rect[self.axis]
self.change_direction(now)
self.rect[self.axis] += speed
self.move_player(now, player, obstacles, speed)
elif now-self.timer > self.delay:
self.waiting = False
def move_player(self, now, player, obstacles, speed):
"""
Moves the player both when on top of, or bumped by the platform.
Collision checks are in place to prevent the block pushing the player
through a wall.
"""
if player.on_moving is self or pg.sprite.collide_rect(self,player):
axis = self.axis
offset = (speed, speed)
player.check_collisions(offset, axis, obstacles)
if pg.sprite.collide_rect(self, player):
if self.speed > 0:
self.rect[axis] = player.rect[axis]-self.rect.size[axis]
else:
self.rect[axis] = player.rect[axis]+player.rect.size[axis]
self.change_direction(now)
def change_direction(self, now):
"""Called when the platform reaches an endpoint or has no more room."""
self.waiting = True
self.timer = now
self.speed *= -1
"""class Spell(pg.sprite.Sprite):
def __init__(self, location, angle)"""
class Control(object):
"""Class for managing event loop and game states."""
def __init__(self):
"""Initalize the display and prepare game objects."""
self.screen = pg.display.get_surface()
self.screen_rect = self.screen.get_rect()
self.clock = pg.time.Clock()
self.fps = 60.0
self.keys = pg.key.get_pressed()
self.done = False
self.player = Player((50,875), 4)
self.viewport = self.screen.get_rect()
self.level = pg.Surface((1000,1000)).convert()
self.level_rect = self.level.get_rect()
self.win_text,self.win_rect = self.make_text()
self.obstacles = self.make_obstacles()
self.bolts = pg.sprite.Group()
def make_text(self):
"""Renders a text object. Text is only rendered once."""
font = pg.font.Font(None, 100)
message = "You win. Celebrate."
text = font.render(message, True, (100,100,175))
rect = text.get_rect(centerx=self.level_rect.centerx, y=100)
return text, rect
def make_obstacles(self):
"""Adds some arbitrarily placed obstacles to a sprite.Group."""
walls = [Block(pg.Color("chocolate"), (0,980,1000,20)),
Block(pg.Color("chocolate"), (0,0,20,1000)),
Block(pg.Color("chocolate"), (980,0,20,1000))]
static = [Block(pg.Color("darkgreen"), (250,780,200,100)),
Block(pg.Color("darkgreen"), (600,880,200,100)),
Block(pg.Color("darkgreen"), (20,360,880,40)),
Block(pg.Color("darkgreen"), (950,400,30,20)),
Block(pg.Color("darkgreen"), (20,630,50,20)),
Block(pg.Color("darkgreen"), (80,530,50,20)),
Block(pg.Color("darkgreen"), (130,470,200,215)),
Block(pg.Color("darkgreen"), (20,760,30,20)),
Block(pg.Color("darkgreen"), (400,740,30,40))]
moving = [MovingBlock(pg.Color("olivedrab"), (20,740,75,20), 325, 0),
MovingBlock(pg.Color("olivedrab"), (600,500,100,20), 880, 0),
MovingBlock(pg.Color("olivedrab"),
(420,430,100,20), 550, 1, speed=3, delay=200),
MovingBlock(pg.Color("olivedrab"),
(450,700,50,20), 930, 1, start=930),
MovingBlock(pg.Color("olivedrab"),
(500,700,50,20), 730, 0, start=730),
MovingBlock(pg.Color("olivedrab"),
(780,700,50,20), 895, 0, speed=-1)]
return pg.sprite.Group(walls, static, moving)
def update_viewport(self):
"""
The viewport will stay centered on the player unless the player
approaches the edge of the map.
"""
self.viewport.center = self.player.rect.center
self.viewport.clamp_ip(self.level_rect)
def event_loop(self):
"""We can always quit, and the player can sometimes jump."""
for event in pg.event.get():
if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]:
self.done = True
elif event.type == pg.KEYDOWN:
if event.key == pg.K_SPACE:
self.player.jump(self.obstacles)
elif event.type == pg.KEYUP:
if event.key == pg.K_SPACE:
self.player.jump_cut()
elif event.type == pg.MOUSEMOTION or event.type == pg.MOUSEBUTTONDOWN:
self.player.get_event(event, self.bolts)
def update(self):
"""Update the player, obstacles, and current viewport."""
self.keys = pg.key.get_pressed()
self.player.pre_update(self.obstacles)
self.obstacles.update(self.player, self.obstacles)
self.player.update(self.obstacles, self.keys)
self.update_viewport()
self.bolts.update(self.screen_rect, self.obstacles)
def draw(self):
"""
Draw all necessary objects to the level surface, and then draw
the viewport section of the level to the display surface.
"""
self.level.fill(pg.Color("lightblue"))
self.obstacles.draw(self.level)
self.level.blit(self.win_text, self.win_rect)
self.player.draw(self.level)
self.bolts.draw(self.level)
self.screen.blit(self.level, (0,0), self.viewport)
def display_fps(self):
"""Show the programs FPS in the window handle."""
caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps())
pg.display.set_caption(caption)
def main_loop(self):
"""As simple as it gets."""
while not self.done:
self.event_loop()
self.update()
self.draw()
pg.display.update()
self.clock.tick(self.fps)
self.display_fps()
if __name__ == "__main__":
os.environ['SDL_VIDEO_CENTERED'] = '1'
pg.init()
pg.display.set_caption(CAPTION)
pg.display.set_mode(SCREEN_SIZE)
PLAYERIMG = pg.image.load("playertst.png").convert()
PLAYERIMG.set_colorkey(COLOR_KEY)
run_it = Control()
run_it.main_loop()
pg.quit()
sys.exit()
The % 2*pi unnecessary, and your get_angle function has no return value, but you do an assignment to self.angle = self.get_angle, but that is not the issue. The issue is that the mouse position is relative to the screen (i.e. clicking in the top right area of your game screen will always yield (0,480) if your screen is 640x480), while the position of the (character) rectangle is given in your game play area, which is larger than the screen, ergo if you move the character and thus the view shifts, you are getting coordinates in two different coordinate systems. You will have to keep track of where the view is in your game play area and add the offset to the mouse coordinates.
I'm running this python code and having a problem with the accel function. The rotate method works fine when left and right are pressed however when up is pressed nothing happens. I've stepped through the code in a debugger and the my_ship.accel line is executed but it doesn't go to method body, it just continues as if that line isn't there. Idk what's wrong please help. Also my_ship is the name of a Ship object and it is defined properly lower in my code.
import simplegui
WIDTH = 800
HEIGHT = 600
class ImageInfo:
def __init__(self, center, size, radius = 0, lifespan = None, animated = False):
self.center = center
self.size = size
self.radius = radius
if lifespan:
self.lifespan = lifespan
else:
self.lifespan = float('inf')
self.animated = animated
def get_center(self):
return self.center
def get_size(self):
return self.size
def get_radius(self):
return self.radius
def get_lifespan(self):
return self.lifespan
def get_animated(self):
return self.animated
def change_center(self, new_center):
self.center = new_center
# ship image
ship_info = ImageInfo([45, 45], [90, 90], 35)
ship_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/double_ship.png")
class Ship:
def __init__(self, pos, vel, angle, image, info):
self.pos = [pos[0],pos[1]]
self.vel = [vel[0],vel[1]]
self.thrust = False
self.angle = angle
self.angle_vel = 0
self.image = image
self.image_center = info.get_center()
self.image_size = info.get_size()
self.radius = info.get_radius()
self.info = info
self.accel = 10
self.angle_accel = .1
def draw(self,canvas):
if not self.thrust:
self.info.change_center(ship_center)
canvas.draw_image(self.image, self.image_center, self.image_size, self.pos, self.image_size, self.angle)
else:
self.info.change_center(thrust_ship_center)
canvas.draw_image(self.image, self.image_center, self.image_size, self.pos, self.image_size, self.angle)
def update(self):
self.pos[0] += self.vel[0]
self.pos[1] += self.vel[1]
self.angle += self.angle_vel
def accel(self):
self.thrust = True
self.vel[0] += self.accel
self.vel[1] += self.accel
def rotate(self, direction):
if direction == "left":
self.angle_vel -= self.angle_accel
elif direction == "right":
self.angle_vel += self.angle_accel
else:
print "error"
def keydown_handler(key):
if key == simplegui.KEY_MAP['left']:
my_ship.rotate("left")
elif key == simplegui.KEY_MAP['right']:
my_ship.rotate("right")
elif key == simplegui.KEY_MAP['up']:
my_ship.accel
elif key == simplegui.KEY_MAP['space']:
self.angle_vel += self.angle_accel
def keyup_handler(key):
if key == simplegui.KEY_MAP['left']:
my_ship.rotate("right")
elif key == simplegui.KEY_MAP['right']:
my_ship.rotate("left")
my_ship = Ship([WIDTH / 2, HEIGHT / 2], [0, 0], 1, ship_image, ship_info)
This:
my_ship.accel
Doesn't call the method my_ship.accel, any more than 2 calls the number 2. To call something in Python, you need parentheses. So:
my_ship.accel()
(If you're wondering why Python does it this way when other languages, like Ruby, don't… well, this means that you can use the method object my_ship.accel as a value—store it to call later, pass it to map, etc.)
But you've got another problem on top of that.
You define a method accel on Ship objects. But you also assign an integer value 10 to self.accel on Ship objects. There's no way self.accel can mean two different things at once, both the method and the number. So, which one "wins"? In this case, the self.accel = 10 happens at the time you constructed your Ship, which is later, so it wins.
So, when you write my_ship.accel, you're just referring to the number 10. And when you write my_ship.accel(), you're trying to call the number 10 as if it were a function. Hence the TypeError.
The solution is to not reuse the same name for two different things. Often, naming functions after verbs and attributes after nouns is a good way to avoid this problem—although you also have to avoid gratuitous abbreviations, because otherwise you're probably going to abbreviate acceleration and accelerate to the same accel, as you did here.