How do I use a .jpg or .png as a background picture? - python

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)

Related

Why aren't my map chunks being drawn correctly?

I'm trying to make a very simple game with my son in Kivy and Python. We are trying to make our viewport (camera) centered over the player as they move around our map that is self generating. We get the initial view, then as the player moves the initial chunk is shown in the correct place, but new chunks aren't being drawn at all.
By debugging, we can tell that we are creating chunks, that they have good values, and that our draw_chunks function knows to grab more chunks and to draw them. They just aren't being drawn. We think that our code for drawing the rectangles is probably wrong, but works for the initial load of the game. We've spent a couple hours trying to fix it. We've adjusted the viewport position a couple different ways as well as the rectangle code, but nothing seems to work. I'm hoping someone can point out what we missed. It is probably something very obvious or silly that we are overlooking. Does anyone have any ideas?
import kivy
import random
from kivy.app import App
from kivy.clock import Clock
from kivy.graphics import Color, Ellipse, Line, Rectangle
from kivy.uix.widget import Widget
from kivy.config import Config
from kivy.core.window import Window
import enum
# Constants for the chunk size and map dimensions
CHUNK_SIZE = 48
TILE_SIZE = 16
MAP_WIDTH = 256
MAP_HEIGHT = 256
#**************************
#* Tile Int Enum Class *
#**************************
class TileEnum(enum.IntEnum):
GRASS = 0
DIRT = 1
CONCRETE = 2
ASPHALT = 3
# Class to represent the game map
class GameMap:
def __init__(self, seed=None):
self.seed = seed
if seed is not None:
random.seed(seed)
self.chunks = {}
self.first_chunk = False
def generate_chunk(self, chunk_x, chunk_y):
# Use the RNG to generate the terrain for this chunk
terrain = []
for x in range(0, CHUNK_SIZE):
column = []
for y in range(0, CHUNK_SIZE):
column.append(random.randint(0, 3))
terrain.append(column)
return terrain
def get_chunk(self, chunk_x, chunk_y):
# Check if the chunk has already been generated
if (chunk_x, chunk_y) in self.chunks:
print("found it",chunk_x, chunk_y)
return self.chunks[(chunk_x, chunk_y)]
else:
# Generate the chunk and store it in the chunk cache
chunk = self.generate_chunk(chunk_x, chunk_y)
self.chunks[(chunk_x, chunk_y)] = chunk
print("made it",chunk_x,chunk_y)
return chunk
# Class to represent the player
class Player:
def __init__(self, pos=(0, 0)):
self.x, self.y = pos
self.speed = TILE_SIZE/2
def move_left(self):
self.x += self.speed
def move_right(self):
self.x -= self.speed
def move_up(self):
self.y -= self.speed
def move_down(self):
self.y += self.speed
class GameScreen(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.viewport_size = (TILE_SIZE*CHUNK_SIZE, TILE_SIZE*CHUNK_SIZE)
self.viewport_pos = (0, 0)
self.size = self.viewport_size
self.map = GameMap(seed=123)
self.player = Player((self.viewport_size[0]/2, self.viewport_size[1]/2))
self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
self._keyboard.bind(on_key_down=self._on_keyboard_down)
def draw_chunks(self):
# Determine the chunks that are currently in view
viewport_left = int(self.viewport_pos[0] // (CHUNK_SIZE * TILE_SIZE))
viewport_top = int(self.viewport_pos[1] // (CHUNK_SIZE * TILE_SIZE))
viewport_right = int((self.viewport_pos[0] + self.viewport_size[0]) // (CHUNK_SIZE * TILE_SIZE))
viewport_bottom = int((self.viewport_pos[1] + self.viewport_size[1]) // (CHUNK_SIZE * TILE_SIZE))
print(viewport_left, viewport_top, viewport_right, viewport_bottom)
# Iterate over the visible chunks and draw them
for x in range(viewport_left, viewport_right + 1):
for y in range(viewport_top, viewport_bottom + 1):
chunk = self.map.get_chunk(x, y)
#print(chunk)
for i in range(len(chunk)):
for j in range(len(chunk[i])):
if chunk[i][j] == TileEnum.GRASS:
# Draw a green square for grass
with self.canvas:
Color(0.25, 0.75, 0.25)
elif chunk[i][j] == TileEnum.DIRT:
# Draw a brown square for dirt
with self.canvas:
Color(0.75, 0.5, 0.25)
elif chunk[i][j] == TileEnum.CONCRETE:
# Draw a gray square for concrete
with self.canvas:
Color(0.5, 0.5, 0.75)
elif chunk[i][j] == TileEnum.ASPHALT:
# Draw a black square for asphalt
with self.canvas:
Color(0.25, 0.25, 0.5)
with self.canvas:
Rectangle(pos=(
(x * CHUNK_SIZE + i) * TILE_SIZE + self.viewport_pos[0],
(y * CHUNK_SIZE + j) * TILE_SIZE + self.viewport_pos[1]),
size=(TILE_SIZE, TILE_SIZE))
def draw_player(self):
# Draw a circle for the player
with self.canvas:
Color(0, 0.5, 0)
Ellipse(pos=(self.viewport_size[0]/2 - (TILE_SIZE/2), self.viewport_size[0]/2 - (TILE_SIZE/2)), size=(TILE_SIZE, TILE_SIZE))
def update(self, dt):
# Update the viewport position to keep the player centered
self.viewport_pos = (self.player.x - self.viewport_size[0]/2, self.player.y - self.viewport_size[1]/2)
print(self.viewport_pos)
# Redraw the chunks and player
self.canvas.clear()
self.draw_chunks()
self.draw_player()
def _keyboard_closed(self):
self._keyboard.unbind(on_key_down=self._on_keyboard_down)
self._keyboard = None
def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
#print(keycode)
if keycode[1] == 'left':
self.player.move_left()
elif keycode[1] == 'right':
self.player.move_right()
elif keycode[1] == 'up':
self.player.move_up()
elif keycode[1] == 'down':
self.player.move_down()
# Main application class
class ProceduralGenerationGameApp(App):
def build(self):
self.title = "Procedural Generation Game"
Config.set("graphics", "width", "768")
Config.set("graphics", "height", "768")
Config.set("graphics", "resizable", False)
Config.set("graphics", "borderless", False)
Config.set("graphics", "fullscreen", False)
Config.set("graphics", "window_state", "normal")
Config.set("graphics", "show_cursor", True)
Config.write()
window_width = Config.getint("graphics", "width")
window_height = Config.getint("graphics", "height")
# Create the game screen and schedule the update function to be called every frame
game_screen = GameScreen()
Window.size = (window_width, window_height)
Clock.schedule_interval(game_screen.update, 1)# 1.0 / 60.0)
return game_screen
if __name__ == "__main__":
ProceduralGenerationGameApp().run()
We updated the Rectangle code to this and reversed the players direction in his move functions:
with self.canvas:
x_chunk_offset = (x * CHUNK_SIZE * TILE_SIZE)
y_chunk_offset = (y * CHUNK_SIZE * TILE_SIZE)
x_tile_offset = (i * TILE_SIZE)
y_tile_offset = (j * TILE_SIZE)
actual_x = x_chunk_offset + x_tile_offset - self.viewport_pos[0]
actual_y = y_chunk_offset + y_tile_offset - self.viewport_pos[1]
Rectangle(pos=(actual_x, actual_y), size=(TILE_SIZE, TILE_SIZE))

Snake with pygame method - pixel overlap problem

I started playing a little with pygame and so far I'm not doing badly. I encountered a problem and managed to solve it. The solution is possible. It is not 100% correct. I want to implement an eat method when the head of the subject meets the food.
It is probably a relatively simple method when the rest of the value of X and Y are equal to the head of the snake being eaten.
For some reason I was not able to fully understand the overlap of pixels and I am not really correct in the method.
The problem at the moment is that there is no overlap between the pixels and "eating is not done".
BACKGROUND = pygame.image.load("background.jpg")
WIDTH, HEIGHT = BACKGROUND.get_width(), BACKGROUND.get_height()
pygame.font.init()
WIN = pygame.display.set_mode((WIDTH, HEIGHT))
def checkForEat(self):
head = self.body[-1]
x = self.food.getPos()[0]
y= self.food.getPos()[1]
# if abs(head[0] - x ) < 9 and abs(head[1] - y ) < 9: -- This is my temporary solution
if head[0] == x and head[1] == y:
self.food = Food()
self.eat()
I try not to add too much unnecessary code.
class Food:
def __init__(self):
self.color = (5, 5, 255)
self.pos = (random.randint(10,WIDTH-50),random.randint(10,HEIGHT-50))
def draw(self,win):
pygame.draw.circle(win,self.color, self.pos, 5)
def getPos(self):
return self.pos
class Snake:
START_POS = (85, 85)
def __init__(self):
self.food = Food()
self.block_size = 11
self.x , self.y = self.START_POS
self.body = self.create_body()
def create_body(self):
body = []
for i in range(self.length):
body.append((85,85+i*self.block_size))
return body
def draw(self,win):
WIN.blit(BACKGROUND, (0, 0))
self.food.draw(win)
for i in range(self.length):
pygame.draw.circle(win, (255, 0, 0), self.body[i], 5)
I'm not adding the rest of the program.
Just saying that apart from the problem I wrote above everything works fine.
Use pygame.Rect/pygame.Rect.colliderect to check if the bounding rectangle of the food overlaps with the head of the snake:
class Food:
def __init__(self):
self.color = (5, 5, 255)
self.pos = (random.randint(10,WIDTH-50),random.randint(10,HEIGHT-50))
def draw(self,win):
pygame.draw.circle(win,self.color, self.pos, 5)
def getPos(self):
return self.pos
def getRect(self):
return pygame.Rect(self.pos[0]-5, self.pos[1]-5, 10, 10)
class Snake:
START_POS = (85, 85)
def __init__(self):
self.food = Food()
self.block_size = 11
self.x , self.y = self.START_POS
self.body = self.create_body()
# [...]
def checkForEat(self):
head = self.body[-1]
head_rect = pygame.Rect(head[0]-5, head[1]-5, self.block_size, self.block_size)
food_rect = self.food.getRect()
if food_rect.colliderect(head_rect):
self.food = Food()
self.eat()
Also see How do I detect collision in pygame?.
Alternatively you can compute the Euclidean distance between the circle center of the circles and compare the distance to the sum of the radii:
class Snake:
# [...]
def checkForEat(self):
dx = self.food.getPos()[0] - self.body[-1][0]
dy = self.food.getPos()[1] - self.body[-1][1]
dist_center = math.hypot(dx, dy)
if dist_center <= 20:
self.food = Food()
self.eat()

Is there a way to move an object in pygame in random directions smoothly? [duplicate]

This question already has an answer here:
Pygame game help: Easing/Acceleration
(1 answer)
Closed 2 years ago.
I'm trying to make blobs move in a random direction for several frames rather than just once so that it appears less jerky and more smooth, but have been unable to do so. Is there any way to make each object move in the same direction for several ticks before choosing another random direction and doing the same?
My code (most is irrelevant):
import pygame
import random
import numpy as np
WIDTH = 1800
HEIGHT = 1000
BLUE = (15,15,180)
RED = (150,0,0)
class Blob:
def __init__(self, colour, x_boundary, y_boundary, size):
self.colour = colour
self.size = size
self.x_boundary = x_boundary
self.y_boundary = y_boundary
self.x = random.randrange(0, self.x_boundary)
self.y = random.randrange(0, self.y_boundary)
def move(self):
self.x += random.randrange(-6,7)
self.y += random.randrange(-6,7)
def limits(self):
if self.x < 0:
self.x = 0
elif self.x > self.x_boundary:
self.x = self.x_boundary
if self.y < 0:
self.y = 0
elif self.y > self.y_boundary:
self.y = self.y_boundary
def __add__(self, other_blob):
if other_blob.size > self.size:
other_blob.size += int(self.size * 0.5)
self.size = 0
class FastBlob(Blob):
def __init__(self, colour, x_boundary, y_boundary, size):
super().__init__(colour, x_boundary, y_boundary, size)
def move(self):
self.x += random.randrange(-20,21)
self.y += random.randrange(-20,21)
pygame.init()
game_display = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption('Blob world')
clock = pygame.time.Clock()
def is_touching(b1,b2):
return np.linalg.norm(np.array([b1.x,b1.y])-np.array([b2.x,b2.y])) < (b1.size + b2.size)
def handle_collisions(blob_list):
blues, reds, slow_reds = blob_list
for first_blobs in blues, reds, slow_reds:
for first_blob_id, first_blob in first_blobs.copy().items():
for other_blobs in blues, reds, slow_reds:
for other_blob_id, other_blob in other_blobs.copy().items():
if first_blob == other_blob:
pass
else:
if is_touching(first_blob, other_blob):
first_blob + other_blob
return blues, reds, slow_reds
def draw_environment(blob_list):
game_display.fill((210,210,210))
handle_collisions(blob_list)
for blob_dict in blob_list:
for blob_id in blob_dict:
blob = blob_dict[blob_id]
pygame.draw.circle(game_display, blob.colour, [blob.x, blob.y], blob.size)
blob.move()
blob.limits()
pygame.display.update()
def main():
blue_blobs = dict(enumerate([FastBlob(BLUE, WIDTH, HEIGHT, random.randrange(10,15)) for i in range(20)]))
red_blobs = dict(enumerate([FastBlob(RED, WIDTH, HEIGHT, random.randrange(5,10)) for i in range(30)]))
slow_red_blobs = dict(enumerate([Blob(RED, WIDTH, HEIGHT, random.randrange(20,30)) for i in range(5)]))
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
draw_environment([blue_blobs, red_blobs, slow_red_blobs])
clock.tick(7)
if __name__ == '__main__':
main()
Here, I have similar problem in my game, when enemy has to randomly change directions so it is unpredictable to the player.
def goblin_move(): #Goblin auto (random) movement
if goblin.x < 0:
goblin.go_right()
elif goblin.x > 500:
goblin.go_left()
else:
if goblin.x > (rnd_1 * win_width) and goblin.move_flag == -1:
goblin.go_left()
goblin.move_flag = -1
else:
goblin.go_right()
goblin.move_flag = 1
if goblin.x > (rnd_2 * win_width):
goblin.move_flag = -1
def set_random(rnd_1, rnd_2): #Random function generator
rnd_1 = round(random.uniform(0, 0.45), 2)
rnd_2 = round(random.uniform(0.65, 0.95), 2)
return rnd_1, rnd_2
And this is how I set it in the main loop:
if round(pg.time.get_ticks()/1000) % 3 == 0: #Calling random function
(rnd_1, rnd_2) = set_random(rnd_1, rnd_2)
Hope you will find it useful.
Use pygame.math.Vector2 to do the computations. Store the coordinates of the blob to a Vector2 and define a maximum distance (self.maxdist), a velocity (self.speed), a random distance (self.dist) a nd a random direction (self.dir). The random direction is a vector with length 1 (Unit vector) and a random angel (rotate()):
class Blob:
def __init__(self, colour, x_boundary, y_boundary, size):
self.colour = colour
self.size = size
self.x_boundary = x_boundary
self.y_boundary = y_boundary
self.x = random.randrange(0, self.x_boundary)
self.y = random.randrange(0, self.y_boundary)
self.pos = pygame.math.Vector2(self.x, self.y)
self.maxdist = 7
self.speed = 1
self.dist = random.randrange(self.maxdist)
self.dir = pygame.math.Vector2(1, 0).rotate(random.randrange(360))
When the blob moves, then scale the direction by the speed and add it to the position (self.pos += self.dir * self.speed). Decrement the distance (self.dist -= self.speed) and update self.x, self.y by the rounded (round) position. If self.dist falls below 0, the create a new random direction and distance:
class Blob:
# [...]
def move(self):
self.pos += self.dir * self.speed
self.dist -= self.speed
self.x, self.y = round(self.pos.x), round(self.pos.y)
if self.dist <= 0:
self.dist = random.randrange(self.maxdist)
self.dir = pygame.math.Vector2(1, 0).rotate(random.randrange(360))
In the method limit you have to ensure that self.pos is in bounds. Finally you have to update self.x, self.y:
class Blob:
# [...]
def limits(self):
if self.pos.x < 0:
self.pos.x = 0
elif self.pos.x > self.x_boundary:
self.pos.x = self.x_boundary
if self.pos.y < 0:
self.pos.y = 0
elif self.pos.y > self.y_boundary:
self.pos.y = self.y_boundary
self.x, self.y = round(self.pos.x), round(self.pos.y)
The class FastBlob does not need its own move method. It is sufficient do define its own self.maxdist and self.speed:
class FastBlob(Blob):
def __init__(self, colour, x_boundary, y_boundary, size):
super().__init__(colour, x_boundary, y_boundary, size)
self.maxdist = 35
self.speed = 5

Generate enemies around the player from all directions randomly

The enemy are being generated from above the screen and then move toward player in the middle, I want to generate enemies randomly around the screen from all directions but not inside the screen directly and proceed to move towards the player and also enemy sprites are sometimes joining combining and moving together how to repel the enemy sprites.
I have tried changing x,y coordinates of enemy objects using a random range but sometimes they generate objects inside the play screen, I want enemies to generate outside the playing window.
class Mob(pg.sprite.Sprite):
def __init__(self):
pg.sprite.Sprite.__init__(self)
self.image = pg.image.load('enemy.png').convert_alpha()
self.image = pg.transform.smoothscale(pg.image.load('enemy.png'), (33, 33))
self.image_orig = self.image.copy()
self.radius = int(29 * .80 / 2)
self.rect = self.image.get_rect()
self.rect.x = random.randrange(width - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.speed = 4
self.rot = 0
self.rot_speed = 5
self.last_update = pg.time.get_ticks()
def rotate(self):
now = pg.time.get_ticks()
if now - self.last_update > 50:
self.last_update = now
self.rot = (self.rot + self.rot_speed) % 360
new_image = pg.transform.rotozoom(self.image_orig, self.rot, 1)
old_center = self.rect.center
self.image = new_image
self.rect = self.image.get_rect()
self.rect.center = old_center
def update(self):
self.rotate()
dirvect = pg.math.Vector2(rotator.rect.x - self.rect.x,
rotator.rect.y- self.rect.y)
if dirvect.length_squared() > 0:
dirvect = dirvect.normalize()
# Move along this normalized vector towards the player at current speed.
if dirvect.length_squared() > 0:
dirvect.scale_to_length(self.speed)
self.rect.move_ip(dirvect)
if self.rect.top > height + 10 or self.rect.left < -25 or self.rect.right > width + 20:
self.rect.x = random.randrange(width - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.speed = random.randrange(1, 4)
[UPDATE]
This the remaining code:
import math
import random
import os
import pygame as pg
import sys
pg.init()
height = 650
width = 1200
os_x = 100
os_y = 45
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (os_x, os_y)
screen = pg.display.set_mode((width, height), pg.NOFRAME)
screen_rect = screen.get_rect()
background = pg.image.load('background.png').convert()
background = pg.transform.smoothscale(pg.image.load('background.png'), (width, height))
clock = pg.time.Clock()
running = True
font_name = pg.font.match_font('Bahnschrift', bold=True)
def draw_text(surf, text, size, x, y, color):
[...]
class Mob(pg.sprite.Sprite):
[...]
class Rotator(pg.sprite.Sprite):
def __init__(self, screen_rect):
pg.sprite.Sprite.__init__(self)
self.screen_rect = screen_rect
self.master_image = pg.image.load('spaceship.png').convert_alpha()
self.master_image = pg.transform.smoothscale(pg.image.load('spaceship.png'), (33, 33))
self.radius = 12
self.image = self.master_image.copy()
self.rect = self.image.get_rect(center=[width / 2, height / 2])
self.delay = 10
self.timer = 0.0
self.angle = 0
self.distance = 0
self.angle_offset = 0
def get_angle(self):
mouse = pg.mouse.get_pos()
offset = (self.rect.centerx - mouse[0], self.rect.centery - mouse[1])
self.angle = math.degrees(math.atan2(*offset)) - self.angle_offset
old_center = self.rect.center
self.image = pg.transform.rotozoom(self.master_image, self.angle, 1)
self.rect = self.image.get_rect(center=old_center)
self.distance = math.sqrt((offset[0] * offset[0]) + (offset[1] * offset[1]))
def update(self):
self.get_angle()
self.display = 'angle:{:.2f} distance:{:.2f}'.format(self.angle, self.distance)
self.dx = 1
self.dy = 1
self.rect.clamp_ip(self.screen_rect)
def draw(self, surf):
surf.blit(self.image, self.rect)
def shoot(self, mousepos):
dx = mousepos[0] - self.rect.centerx
dy = mousepos[1] - self.rect.centery
if abs(dx) > 0 or abs(dy) > 0:
bullet = Bullet(self.rect.centerx, self.rect.centery, dx, dy)
all_sprites.add(bullet)
bullets.add(bullet)
There's not much informations to go by here, but you probably need to check the x and y range your play window has and make sure the random spawn coordinates you generate are outside of it:
In your init:
# These are just example min/max values. Maybe pass these as arguments to your __init__ method.
min_x = min_y = -1000
max_x = max_y = 1000
min_playwindow_x = min_playwindow_y = 500
max_playwindow_x = max_playwindow_y = 600
self.x = (random.randrange(min_x, min_playwindow_x), random.randrange(max_playwindow_x, max_x))[random.randrange(0,2)]
self.y = (random.randrange(min_y, min_playwindow_y), random.randrange(max_playwindow_y, max_y))[random.randrange(0,2)]
This solution should work in basically any setup. For x and y it generates a tuple of values outside the playing window. Then a coinflip decides on the value. This will only spawn mobs that are diagonally outside the playing field, but it will always generate valid random coordinates.
Another approach would be just generating as many random variables as needed to get a valid pair like this:
while min_playingwindow_x <= self.x <= max_playingwindow_x and
min_playingwindow_y <= self.y <= max_playingwindow_y:
# While within screen(undesired) calculate new random positions
self.x = random.randrange(min_x, max_x)
self.y = random.randrange(min_y, max_y)
This can be really slow however if your valid amount of positions is (for example) only 1% of the total positions.
IF you need something really fleshed out, you need to know the corners of both your map and the rectangle that is actually displayed, which is I assume smaller than the entire map(otherwise you cannot spawn enemies outside your view.
(0,0)
+----------------------+
| A |
|-----+-----------+----|
| D | W | B |
|-----+-----------+----|
| C |
+----------------------+(max_x, max_y)
In this diagram W is the window that is acutally visible to the player, and A,B,C,D together are the part of your map that is not currently visible. Since you only want to spawn mobs outside the player's view, you'll need to make sure that the coordinates you generate are inside your map and outside your view:
def generate_coordinates_outside_of_view(map_width=1000, map_height=1000, view_window_top_left=(100, 100),
view_width=600, view_height=400):
"""
A very over the top way to generate coordinates outside surrounding a rectangle within a map almost without bias
:param map_width: width of map in pixels (note that 0,0 on the map is top left)
:param map_height: height of map in pixels
:param view_window_top_left: top left point(2-tuple of ints) of visible part of map
:param view_width: width of view in pixels
:param view_height: height of view in pixels
"""
from random import randrange
# generate 2 samples for each x and y, one guaranteed to be random, and one outside the view for sure.
x = (randrange(0, map_width), (randrange(0, view_window_top_left[0]),
randrange(view_window_top_left[0] + view_width, map_width))[randrange(0, 2)])
y = (randrange(0, map_height), (randrange(0, view_window_top_left[1]),
randrange(view_window_top_left[1] + view_height, map_height))[randrange(0, 2)])
# now we have 4 values. To get a point outside our view we have to return a point where at least 1 of the
# values x/y is guaranteed to be outside the view.
if randrange(0, 2) == 1: # to be almost completely unbiased we randomize the check
selection_x = randrange(0, 2)
selection_y = randrange(0, 2) if selection_x == 1 else 1
else:
selection_y = randrange(0, 2)
selection_x = randrange(0, 2) if selection_y == 1 else 1
return x[selection_x], y[selection_y]
HTH

How to limit the amount of times a button is pressed

I am trying to make a game where you can shoot bullets to kill emojis. However, i can't manage to figure out how to stop spamming the space key to shoot bullets. If you keep on spamming, the game would be too easy. I am not exactly sure if what command I should use. Please help! thanks!
Here is my code:
# import everything from turtle
from turtle import *
import random
import math
#create a link to the object (creates the environment)
screen = Screen()
speed1 = 1.3
ht()
amountOfEmojis = 11
#set a boundary for screen, if touches end, goes to the other side
screenMinX = -screen.window_width()/2
screenMinY = -screen.window_height()/2
screenMaxX = screen.window_width()/2
screenMaxY = screen.window_height()/2
#establish important data for screen environment
screen.setworldcoordinates(screenMinX,screenMinY,screenMaxX,screenMaxY)
screen.bgcolor("black")
#turtle setup
penup()
ht()
speed(0)
goto(0, screenMaxY - 50)
color('white')
write("Welcome to Emoji Run!", align="center", font=("Courier New",26))
goto(0, screenMaxY - 70)
write("Use the arrow keys to move and space to fire. The point of the game is to kill the emojis", align="center")
goto(0, 0)
color("red")
emojis = ["Poop_Emoji_7b204f05-eec6-4496-91b1-351acc03d2c7_grande.png", "1200px-Noto_Emoji_KitKat_263a.svg.png",
"annoyningface.png", "Emoji_Icon_-_Sunglasses_cool_emoji_large.png"]
class Bullet(Turtle):
#constructor, object for a class, pass in information
def __init__(self,screen,x,y,heading):
#create a bullet
Turtle.__init__(self)#clones bullet
self.speed(0)
self.penup()
self.goto(x,y)
self.seth(heading)#pointing to itself
self.screen = screen
self.color('yellow')
self.max_distance = 500
self.distance = 0
self.delta = 20
self.shape("bullet")
#logic to move bullet
def move(self):
self.distance = self.distance + self.delta#how fast it's going to move
self.forward(self.delta)
if self.done():
self.reset()
def getRadius(self):
return 4#collision detection helper function
def blowUp(self):
self.goto(-300,0)#function that makes something go off the screen
def done(self):
return self.distance >= self.max_distance # append to list
class Asteroid(Turtle):
def __init__(self,screen,dx,dy,x,y,size,emoji):#spawn asteroid randomly
Turtle.__init__(self)#clone itself
self.speed(0)
self.penup()
self.goto(x,y)
self.color('lightgrey')
self.size = size
self.screen = screen
self.dx = dx
self.dy = dy
r = random.randint(0, len(emoji) - 1)
screen.addshape(emojis[r])
self.shape(emojis[r])
#self.shape("rock" + str(size)) #sets size and shape for asteroid
def getSize(self):#part of collision detection
return self.size
#getter and setter functions
def getDX(self):
return self.dx
def getDY(self):
return self.dy
def setDX(self,dx):
self.dx = dx
def setDY(self,dy):
self.dy = dy
def move(self):
x = self.xcor()
y = self.ycor()
#if on edge of screen. go to opposite side
x = (self.dx + x - screenMinX) % (screenMaxX - screenMinX) + screenMinX
y = (self.dy + y - screenMinY) % (screenMaxY - screenMinY) + screenMinY
self.goto(x,y)
def blowUp(self):
self.goto(-300,0)#function that makes something go off the screen
def getRadius(self):
return self.size * 10 - 5
class SpaceShip(Turtle):
def __init__(self,screen,dx,dy,x,y):
Turtle.__init__(self)
self.speed(0)
self.penup()
self.color("white")
self.goto(x,y)
self.dx = dx
self.dy = dy
self.screen = screen
self.bullets = []
self.shape("turtle")
def move(self):
x = self.xcor()
y = self.ycor()
x = (self.dx + x - screenMinX) % (screenMaxX - screenMinX) + screenMinX
y = (self.dy + y - screenMinY) % (screenMaxY - screenMinY) + screenMinY
self.goto(x,y)
#logic for collision
def powPow(self, asteroids):
dasBullets = []
for bullet in self.bullets:
bullet.move()
hit = False
for asteroid in asteroids:
if intersect(asteroid, bullet):#counts every asteroid to see if it hits
asteroids.remove(asteroid)
asteroid.blowUp()
bullet.blowUp()
hit = True
if (not bullet.done() and not hit):
dasBullets.append(bullet)
self.bullets = dasBullets
def fireBullet(self):
self.bullets.append(Bullet(self.screen, self.xcor(), self.ycor(), self.heading()))
def fireEngine(self):#how turtle moves
angle = self.heading()
x = math.cos(math.radians(angle))
y = math.sin(math.radians(angle))
self.dx = self.dx + x#how it rotates
self.dy = self.dy + y
self.dx = self.dx / speed1
self.dy = self.dy / speed1
#extra function
def turnTowards(self,x,y):
if x < self.xcor():
self.left(7)
if x > self.xcor():
self.right(7)
def getRadius(self):
return 10
def getDX(self):
return self.dx
def getDY(self):
return self.dy
#collision detection
def intersect(object1,object2):
dist = math.sqrt((object1.xcor() - object2.xcor())**2 + (object1.ycor() - object2.ycor())**2)
radius1 = object1.getRadius()
radius2 = object2.getRadius()
# The following if statement could be written as
# return dist <= radius1+radius2
if dist <= radius1+radius2:
return True
else:
return False
#adds object to screen
screen.register_shape("rock3",((-20, -16),(-21, 0), (-20,18),(0,27),(17,15),(25,0),(16,-15),(0,-21)))
screen.register_shape("rock2",((-15, -10),(-16, 0), (-13,12),(0,19),(12,10),(20,0),(12,-10),(0,-13)))
screen.register_shape("rock1",((-10,-5),(-12,0),(-8,8),(0,13),(8,6),(14,0),(12,0),(8,-6),(0,-7)))
screen.register_shape("ship",((-10,-10),(0,-5),(10,-10),(0,10)))
screen.register_shape("bullet",((-2,-4),(-2,4),(2,4),(2,-4)))
#ship spawn exactly the middle everytime
ship = SpaceShip(screen,0,0,(screenMaxX-screenMinX)/2+screenMinX,(screenMaxY-screenMinY)/2 + screenMinY)
#randomize where they spawn
asteroids = []
for k in range(amountOfEmojis):
dx = random.random() * 6 - 3
dy = random.random() * 6 - 3
x = random.randrange(10) * (screenMaxX - screenMinX) + screenMinX
y = random.random() * (screenMaxY - screenMinY) + screenMinY
asteroid = Asteroid(screen,dx,dy,x,y,random.randint(1,3), emojis)
asteroids.append(asteroid)
def play():
# Tell all the elements of the game to move
ship.move()
gameover = False
for asteroid in asteroids:
r = random.randint(0, 1)
if r == 1:
asteroid.right(50)
else:
asteroid.left(20)
asteroid.move()
if intersect(ship,asteroid):
write("You Got Killed :(",font=("Verdana",25),align="center")
gameover = True
ship.powPow(asteroids)
screen.update()
if not asteroids:
color('green')
write("You Killed the Emojis!!",font=("Arial",30),align="center")
ht()
if not gameover:
screen.ontimer(play, 30)
bullets = []
#controls
def turnLeft():
ship.left(7)
def turnRight():
ship.right(7)
def go():
ship.fireEngine()
def fire():
ship.fireBullet()
ht()
screen.tracer(0);
screen.onkey(turnLeft, 'left')
screen.onkey(turnRight, 'right')
screen.onkey(go, 'up')
screen.onkey(fire, 'space')
screen.listen()
play()
You can use a threaded timer to prevent the method to be called everytime you click the button, and just two attributes in your SpaceShip class.
Everytime the method fireBullet is called, a check is made on the variable can_shoot. If it's true, the bullet is spawned like you did and then a timer runs (with a thread, for not blocking the main flow) that just put can_shoot to False, sleep for any amount of ms you want, and then put can_shoot to True and the method is callable again.
import time
import threading
def __init__(self):
# your stuff
self.wait_between_fire = 300 / 1000 # amount of ms / 1000 to convert in seconds
self.can_shoot = True
class TimerThread(threading.Thread):
def __init__(self, ref):
threading.Thread.__init__(self)
self.ref = ref
def run():
self.ref.can_shoot = False
time.sleep(ref.wait_between_fire)
self.ref.can_shoot = True
def set_timer(self):
TimerThread(self).start()
def fireBullet(self):
if self.can_shoot:
self.bullets.append(Bullet(self.screen, self.xcor(), self.ycor(), self.heading()))
self.set_timer()
We don't need to introduce time nor threading to solve this. We can use turtle's own timer events to control the rate of fire:
def fire():
screen.onkey(None, 'space')
ship.fireBullet()
screen.ontimer(lambda: screen.onkey(fire, 'space'), 250)
Here I've limited the rate of fire to 4 rounds per second. (250 / 1000 milliseconds.) Adjust as you see fit. Below is a rework of your program with this modification as well as other fixes and style tweaks:
from turtle import Screen, Turtle
from random import random, randint, randrange, choice
from math import radians, sin as sine, cos as cosine
class Bullet(Turtle):
MAX_DISTANCE = 500
DELTA = 20
RADIUS = 4
def __init__(self, position, heading):
super().__init__(shape="bullet")
self.hideturtle()
self.penup()
self.goto(position)
self.setheading(heading)
self.color('yellow')
self.showturtle()
self.distance = 0
def move(self):
self.distance += self.DELTA
self.forward(self.DELTA)
if self.done():
self.reset()
def getRadius(self):
''' collision detection helper method '''
return self.RADIUS
def blowUp(self):
''' method that makes something go off the screen '''
self.hideturtle()
def done(self):
return self.distance >= self.MAX_DISTANCE
class Asteroid(Turtle):
def __init__(self, dx, dy, position, size, emoji):
super().__init__()
self.hideturtle()
self.penup()
self.goto(position)
self.color('lightgrey')
emoji = choice(emojis)
# screen.addshape(emoji) # for StackOverflow debugging
self.shape(emoji)
self.showturtle()
self.size = size
self.dx = dx
self.dy = dy
def move(self):
x, y = self.position()
# if on edge of screen. go to opposite side
x = (self.dx + x - screenMinX) % (screenMaxX - screenMinX) + screenMinX
y = (self.dy + y - screenMinY) % (screenMaxY - screenMinY) + screenMinY
self.goto(x, y)
def blowUp(self):
''' method that makes something go off the screen '''
self.hideturtle()
def getRadius(self):
return self.size * 10 - 5
class SpaceShip(Turtle):
RADIUS = 10
def __init__(self, screen, dx, dy, x, y):
super().__init__(shape='turtle')
self.hideturtle()
self.penup()
self.color("white")
self.goto(x, y)
self.showturtle()
self.dx = dx
self.dy = dy
self.screen = screen
self.bullets = []
def move(self):
x, y = self.position()
x = (self.dx + x - screenMinX) % (screenMaxX - screenMinX) + screenMinX
y = (self.dy + y - screenMinY) % (screenMaxY - screenMinY) + screenMinY
self.goto(x, y)
def powPow(self, asteroids):
''' logic for collision '''
dasBullets = []
for bullet in self.bullets:
bullet.move()
hit = False
for asteroid in asteroids:
if intersect(asteroid, bullet): # counts every asteroid to see if it hits
asteroids.remove(asteroid)
asteroid.blowUp()
hit = True
if not bullet.done() and not hit:
dasBullets.append(bullet)
else:
bullet.blowUp()
self.bullets = dasBullets
def fireBullet(self):
bullet = Bullet(self.position(), self.heading())
self.bullets.append(bullet)
def fireEngine(self):
angle = self.heading() # how turtle moves
x = cosine(radians(angle))
y = sine(radians(angle))
self.dx = self.dx + x # how it rotates
self.dy = self.dy + y
self.dx = self.dx / speed1
self.dy = self.dy / speed1
def getRadius(self):
return self.RADIUS
def turnLeft(self):
self.left(7)
def turnRight(self):
self.right(7)
def intersect(object1, object2):
''' collision detection '''
return object1.distance(object2) <= object1.getRadius() + object2.getRadius()
def play():
# Tell all the elements of the game to move
ship.move()
gameover = False
for asteroid in asteroids:
r = randint(0, 1)
if r == 1:
asteroid.right(50)
else:
asteroid.left(20)
asteroid.move()
if intersect(ship, asteroid):
turtle.write("You Got Killed :(", font=("Verdana", 25), align="center")
gameover = True
ship.powPow(asteroids)
screen.update()
if not asteroids:
turtle.color('green')
turtle.write("You Killed the Emojis!!", font=("Arial", 30), align="center")
if not gameover:
screen.ontimer(play, 30)
# controls
def fire():
screen.onkey(None, 'space')
ship.fireBullet()
screen.ontimer(lambda: screen.onkey(fire, 'space'), 250)
# create a link to the object (creates the environment)
speed1 = 1.3
amountOfEmojis = 11
# establish important data for screen environment
screen = Screen()
screen.bgcolor("black")
# set a boundary for screen, if touches end, goes to the other side
screenMinX = -screen.window_width()/2
screenMinY = -screen.window_height()/2
screenMaxX = screen.window_width()/2
screenMaxY = screen.window_height()/2
screen.setworldcoordinates(screenMinX, screenMinY, screenMaxX, screenMaxY)
# adds object to screen
screen.register_shape("rock3", ((-20, -16), (-21, 0), (-20, 18), (0, 27), (17, 15), (25, 0), (16, -15), (0, -21)))
screen.register_shape("rock2", ((-15, -10), (-16, 0), (-13, 12), (0, 19), (12, 10), (20, 0), (12, -10), (0, -13)))
screen.register_shape("rock1", ((-10, -5), (-12, 0), (-8, 8), (0, 13), (8, 6), (14, 0), (12, 0), (8, -6), (0, -7)))
screen.register_shape("ship", ((-10, -10), (0, -5), (10, -10), (0, 10)))
screen.register_shape("bullet", ((-2, -4), (-2, 4), (2, 4), (2, -4)))
screen.tracer(0)
# turtle setup
turtle = Turtle()
turtle.hideturtle()
turtle.penup()
turtle.goto(0, screenMaxY - 50)
turtle.color('white')
turtle.write("Welcome to Emoji Run!", align="center", font=("Courier New", 26))
turtle.goto(0, screenMaxY - 70)
turtle.write("Use the arrow keys to move, and space to fire. The point of the game is to kill the emojis.", align="center", font=("Courier New", 13))
turtle.goto(0, 0)
turtle.color("red")
emojis = [
"Poop_Emoji_7b204f05-eec6-4496-91b1-351acc03d2c7_grande.png",
"1200px-Noto_Emoji_KitKat_263a.svg.png",
"annoyningface.png",
"Emoji_Icon_-_Sunglasses_cool_emoji_large.png"
]
emojis = ['rock1', 'rock2', 'rock3'] # for StackOverflow debugging purposes
# ship spawn exactly the middle everytime
ship = SpaceShip(screen, 0, 0, (screenMaxX - screenMinX)/2 + screenMinX, (screenMaxY - screenMinY)/2 + screenMinY)
# randomize where they spawn
asteroids = []
for k in range(amountOfEmojis):
dx, dy = random() * 6 - 3, random() * 6 - 3
x = randrange(10) * (screenMaxX - screenMinX) + screenMinX
y = random() * (screenMaxY - screenMinY) + screenMinY
asteroid = Asteroid(dx, dy, (x, y), randint(1, 3), emojis)
asteroids.append(asteroid)
screen.onkey(ship.turnLeft, 'Left')
screen.onkey(ship.turnRight, 'Right')
screen.onkey(ship.fireEngine, 'Up')
screen.onkey(fire, 'space')
screen.listen()
screen.update()
play()
screen.mainloop()
Something to consider is that turtles are global entities that don't get garbage collected. So, you might want to collect your spent bullets in a list to reuse, only creating new ones when you need them.

Categories