I'm working on a choice based adventure game with python, and I'm using pygame to create some graphics for the game.
As I was trying to create a screen so the player can input his real name and the main character name I faced an issue: as I try to create both text inputs (using pygame_textinput module) on the same screen, it just clones what I write in one of them.
I thought I could solve this by putting the other input on a new screen but when I hit enter on the first screen it just passes through the rest of the code and the second input stays empty.
How could I solve this issue?
#Imports
import contextlib
with contextlib.redirect_stdout(None):
import pygame
import pickle
import time
import random
import pygame_textinput.pygame_textinput as textinput
#Really messy, I know#
#Save Stuff
Choices = {}
def save(to_save, save_filepath):
pickle.dump(to_save, open(save_filepath, "wb"))
def load(save_filepath):
return pickle.load(open(save_filepath, "rb"))
#Initializations
pygame.init()
#Screen
scrWidth = 640
scrHeight = 480
screen = pygame.display.set_mode((scrWidth, scrHeight))
pygame.display.set_caption('Something')
#Images
startscreenbg = pygame.image.load('assets/Images/startscreen.jpg').convert()
#Text Input
real_nametxtinput = textinput.TextInput(text_color=(255,255,255))
char_nametxtinput = textinput.TextInput(text_color=(255,255,255))
#Clock
clock = pygame.time.Clock()
#Game Constants
next = False
real_name = ''
char_name = ''
real_name_done = False
char_name_done = False
##### Global Functions #####
#Buttons stuff
buttonTextFont = pygame.font.SysFont("comicsansms", 20)
def createButton(msg, msgcolor,x,y,width,height,color1,color2,action=None):
mouse = pygame.mouse.get_pos()
click = pygame.mouse.get_pressed()
if x+width > mouse[0] > x and y+height > mouse[1] > y:
pygame.draw.rect(screen, color2,(x,y,width,height))
if click[0] == 1 and action != None:
action()
else:
pygame.draw.rect(screen, color1,(x,y,width,height))
buttontext = buttonTextFont.render(msg, 1, msgcolor)
screen.blit(buttontext, (x + (width//2) - (buttontext.get_width()//2), (y + (height//2) - (buttontext.get_height()//2))))
##### Screens/Chapters #####
def update_next1():
global next
next = True
def start_screen():
screen.blit(startscreenbg, (0, 0))
new_game = createButton('New Game',(255,255,255), 80, 100, 200, 50, (0,180,0), (0,255,0), update_next1)
load_game = createButton('Load Game', (255,255,255), 360, 100, 200, 50, (0,0,180), (0,0,255))
#Names
def real_name_screen():
global real_name, real_name_done
screen.blit(startscreenbg, (0,0))
pygame.draw.rect(screen, (102, 255, 102), (150, 90, 200, 50))
screen.blit(real_nametxtinput.get_surface(), (150,100))
if real_nametxtinput.update(events):
real_name = real_nametxtinput.get_text()
real_name_done = True
def char_name_screen():
global char_name, char_name_done
screen.blit(startscreenbg, (0, 0))
pygame.draw.rect(screen, (255, 102, 255), (150, 90, 200, 50))
screen.blit(char_nametxtinput.get_surface(), (150, 100))
if char_nametxtinput.update(events):
char_name = char_nametxtinput.get_text()
if char_name != '':
char_name_done = True
run = True
while run:
clock.tick(27)
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
run = False
if not next:
start_screen()
if next:
real_name_screen()
if real_name_done:
char_name_screen()
if char_name_done:
#This is just so I could test it
print(real_name, char_name)
run = False
pygame.display.update()
pygame.quit()
quit()
You need a way to keep track if one of the text input widgets is active, and a way to switch between all widgets.
I hacked together this example, adjust as needed (there's a lot that could be improved):
import os.path
import pygame
import pygame.locals as pl
pygame.font.init()
class WidgetManager:
def __init__(self):
self.widgets = []
def update(self, events, dt):
for e in events:
if e.type == pygame.MOUSEBUTTONDOWN:
for w in self.widgets:
w.active = False
if w.rect.collidepoint(e.pos):
w.active = True
for w in self.widgets:
w.update(events, dt)
def draw(self, surface):
for w in self.widgets:
surface.blit(w.surface, w.rect)
class TextInput:
"""
This class lets the user input a piece of text, e.g. a name or a message.
This class let's the user input a short, one-lines piece of text at a blinking cursor
that can be moved using the arrow-keys. Delete, home and end work as well.
"""
def __init__(
self,
initial_string="",
font_family="",
font_size=35,
antialias=True,
active=False,
text_color=(0, 0, 0),
rect=pygame.Rect(0, 0, 10, 10),
cursor_color=(0, 0, 1),
repeat_keys_initial_ms=400,
repeat_keys_interval_ms=35):
"""
:param initial_string: Initial text to be displayed
:param font_family: name or list of names for font (see pygame.font.match_font for precise format)
:param font_size: Size of font in pixels
:param antialias: Determines if antialias is applied to font (uses more processing power)
:param text_color: Color of text (duh)
:param cursor_color: Color of cursor
:param repeat_keys_initial_ms: Time in ms before keys are repeated when held
:param repeat_keys_interval_ms: Interval between key press repetition when helpd
"""
# Text related vars:
self.antialias = antialias
self.text_color = text_color
self.font_size = font_size
self.input_string = initial_string # Inputted text
self.active = active
self.rect = rect
if not os.path.isfile(font_family):
font_family = pygame.font.match_font(font_family)
self.font_object = pygame.font.Font(font_family, font_size)
# Text-surface will be created during the first update call:
self.surface = pygame.Surface((1, 1))
self.surface.set_alpha(0)
# Vars to make keydowns repeat after user pressed a key for some time:
self.keyrepeat_counters = {} # {event.key: (counter_int, event.unicode)} (look for "***")
self.keyrepeat_intial_interval_ms = repeat_keys_initial_ms
self.keyrepeat_interval_ms = repeat_keys_interval_ms
# Things cursor:
self.cursor_surface = pygame.Surface((int(self.font_size/20+1), self.font_size))
self.cursor_surface.fill(cursor_color)
self.cursor_position = len(initial_string) # Inside text
self.cursor_visible = True # Switches every self.cursor_switch_ms ms
self.cursor_switch_ms = 500 # /|\
self.cursor_ms_counter = 0
def update(self, events, dt):
for event in events:
if event.type == pygame.KEYDOWN and self.active:
self.cursor_visible = True # So the user sees where he writes
# If none exist, create counter for that key:
if event.key not in self.keyrepeat_counters:
self.keyrepeat_counters[event.key] = [0, event.unicode]
if event.key == pl.K_BACKSPACE:
self.input_string = (
self.input_string[:max(self.cursor_position - 1, 0)]
+ self.input_string[self.cursor_position:]
)
# Subtract one from cursor_pos, but do not go below zero:
self.cursor_position = max(self.cursor_position - 1, 0)
elif event.key == pl.K_DELETE:
self.input_string = (
self.input_string[:self.cursor_position]
+ self.input_string[self.cursor_position + 1:]
)
elif event.key == pl.K_RETURN:
return True
elif event.key == pl.K_RIGHT:
# Add one to cursor_pos, but do not exceed len(input_string)
self.cursor_position = min(self.cursor_position + 1, len(self.input_string))
elif event.key == pl.K_LEFT:
# Subtract one from cursor_pos, but do not go below zero:
self.cursor_position = max(self.cursor_position - 1, 0)
elif event.key == pl.K_END:
self.cursor_position = len(self.input_string)
elif event.key == pl.K_HOME:
self.cursor_position = 0
else:
# If no special key is pressed, add unicode of key to input_string
self.input_string = (
self.input_string[:self.cursor_position]
+ event.unicode
+ self.input_string[self.cursor_position:]
)
self.cursor_position += len(event.unicode) # Some are empty, e.g. K_UP
elif event.type == pl.KEYUP:
# *** Because KEYUP doesn't include event.unicode, this dict is stored in such a weird way
if event.key in self.keyrepeat_counters:
del self.keyrepeat_counters[event.key]
# Update key counters:
for key in self.keyrepeat_counters:
self.keyrepeat_counters[key][0] += dt # Update clock
# Generate new key events if enough time has passed:
if self.keyrepeat_counters[key][0] >= self.keyrepeat_intial_interval_ms:
self.keyrepeat_counters[key][0] = (
self.keyrepeat_intial_interval_ms
- self.keyrepeat_interval_ms
)
event_key, event_unicode = key, self.keyrepeat_counters[key][1]
pygame.event.post(pygame.event.Event(pl.KEYDOWN, key=event_key, unicode=event_unicode))
# Re-render text surface:
self.surface = pygame.Surface(self.rect.size)
self.surface.blit(self.font_object.render(self.input_string, self.antialias, self.text_color), (0, 0))
pygame.draw.rect(self.surface, self.text_color, (0, 0, *self.rect.size), 1)
# Update self.cursor_visible
self.cursor_ms_counter += dt
if self.cursor_ms_counter >= self.cursor_switch_ms:
self.cursor_ms_counter %= self.cursor_switch_ms
self.cursor_visible = not self.cursor_visible
if self.cursor_visible and self.active:
cursor_y_pos = self.font_object.size(self.input_string[:self.cursor_position])[0]
# Without this, the cursor is invisible when self.cursor_position > 0:
if self.cursor_position > 0:
cursor_y_pos -= self.cursor_surface.get_width()
self.surface.blit(self.cursor_surface, (cursor_y_pos, 0))
return False
def get_surface(self):
return self.surface
def get_text(self):
return self.input_string
def get_cursor_position(self):
return self.cursor_position
def set_text_color(self, color):
self.text_color = color
def set_cursor_color(self, color):
self.cursor_surface.fill(color)
def clear_text(self):
self.input_string = ""
self.cursor_position = 0
def main():
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
manager = WidgetManager()
manager.widgets.append(TextInput(text_color=pygame.Color('grey'), cursor_color=pygame.Color('grey'), rect=pygame.Rect(5, 5, 790, 35)))
manager.widgets.append(TextInput(text_color=pygame.Color('orange'), cursor_color=pygame.Color('orange'), rect=pygame.Rect(5, 55, 790, 35), active=True))
dt = 0
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
screen.fill((30, 30, 30))
manager.draw(screen)
manager.update(events, dt)
dt = clock.tick()
pygame.display.update()
if __name__ == '__main__':
main()
You see TextInput now has an active flag. If it's not set, the key input is ignored. Also, we store the position and size of TextInput in the new attribute rect, so the new class WidgetManager can switch between the widgets by clicking one with the mouse.
I also removed the Clock from the TextInput class, since calling self.clock.tick() is something the main loop should do.
Related
I'm trying to make my first GUI quiz game. But for some reason when I run the code to loop through the questions, it does it too fast and doesn't wait for the user to choose one on the choices before moving onto the next question.
The number of iterations are supposed to be variable as the user should be able to choose the number of questions, so I can't make a function for each questions and instead am trying to loop though a dictionary and displaying it on the screen. But ran onto more problems. Is there any way for the program to wait until the user chooses one of the choices before moving onto the next question.
import pygame
from pygame.locals import *
from gui import Button, FlagButton
from os import path
from q_gen import generate_question
import sys
pygame.init()
WIDTH, HEIGHT = 1000, 600
win = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("World game")
font_path = path.join("fonts", "PartyConfetti.ttf")
small_font = pygame.font.Font(font_path, 50)
centre = ((win.get_width())//2, (win.get_height())//2)
WHITE = (255, 255, 255)
BLACK = ( 0, 0, 0)
PASTEL_BLUE = (189, 242, 255)
button_img = pygame.image.load(path.join("assets", "button.png"))
button_img = pygame.transform.scale(button_img, (300, 100))
arrow = pygame.image.load(path.join("assets", "arrow.png"))
arrow = pygame.transform.scale(arrow, (50, 50))
up = pygame.transform.rotate(arrow, 90)
down = pygame.transform.rotate(arrow, 270)
def game():
questions = generate_question("capital", 5, 1)
pressed = False
running = True
while running:
for q in questions:
win.fill(PASTEL_BLUE)
mouse_pos = pygame.mouse.get_pos()
title = small_font.render(f"{q}. {questions[q][0]}", True, "black")
title_rect = title.get_rect(center=(centre[0], centre[1]-200))
win.blit(title, title_rect)
choice1 = Button(win, button_img, (300, 500), "CHOICE")
choice1.changeColor(mouse_pos)
choice1.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
if choice1.checkForInput(mouse_pos):
pressed = True
break
pygame.display.update()
if pressed:
continue
pygame.quit()
sys.exit(0)
The question generator and class for the button, if needed, is below.
from info import info # Large dictionary containing info of countries
from random import choice, randint
modes = ("capital", "flag", "currency")
def generate_question(mode, q_no, difficulty):
questions = {}
new_mode = mode
for i in range(1, q_no+1):
reverse = choice((True, False))
country = choice(list(info.keys()))
choices = []
if mode == "mixed":
new_mode = choice(modes)
if new_mode != "flag":
query = info[country][new_mode]
if reverse:
question = f"Which country is {query} the {new_mode} of?"
answer = country
while len(choices)<=(difficulty-1)*2:
rand_choice = choice(list(info.keys()))
if rand_choice != answer and rand_choice not in choices:
choices.append(rand_choice)
else:
question = f"What is the {new_mode} of {country}?"
answer = query
while len(choices)<=(difficulty-1)*2:
rand_country = choice(list(info.keys()))
rand_choice = info[rand_country][new_mode]
if rand_choice != answer and rand_choice not in choices:
choices.append(rand_choice)
choices.insert(randint(0, len(choices)), answer)
questions[i] = (question, country, new_mode, answer, choices)
else:
question = f"Which one is the flag of {country}?"
answer = f"{country}.png"
return questions
import os
import pygame
pygame.init()
class Button():
def __init__(self, screen, image, pos, text_input, font=os.path.join("fonts", "PartyConfetti.ttf"), text_size=50):
self.screen = screen
self.image = image
self.x_pos = pos[0]
self.y_pos = pos[1]
self.font = pygame.font.Font(font, text_size)
self.rect = self.image.get_rect(center=(self.x_pos, self.y_pos))
self.text_input = text_input
self.text = self.font.render(self.text_input, True, "black")
self.text_rect = self.text.get_rect(center=(self.x_pos, self.y_pos))
def update(self):
self.screen.blit(self.image, self.rect)
self.screen.blit(self.text, self.text_rect)
def checkForInput(self, position):
if position[0] in range(self.rect.left, self.rect.right) and position[1] in range(self.rect.top, self.rect.bottom):
return True
return False
def changeColor(self, position):
if position[0] in range(self.rect.left, self.rect.right) and position[1] in range(self.rect.top, self.rect.bottom):
self.text = self.font.render(self.text_input, True, "white")
else:
self.text = self.font.render(self.text_input, True, "black")
class FlagButton(Button):
def __init__(self, screen, image, pos, text_input, flag_image, font=os.path.join("fonts", "PartyConfetti.ttf"), text_size=50):
super().__init__(screen, image, pos, text_input, font, text_size)
self.flag_image = flag_image
self.flag_image_rect = self.flag_image.get_rect(center=(self.x_pos, self.y_pos))
def update(self):
self.screen.blit(self.image, self.rect)
self.screen.blit(self.flag_image, self.flag_image_rect)
The problem is here
if pressed:
continue
continue does not do what you think it does, it only skips the rest of the current iteration and continues with the loop.
Instead, you could use a while loop to keep looping over the click check until a button is pressed.
import pygame
from pygame.locals import *
from gui import Button, FlagButton
from os import path
from q_gen import generate_question
import sys
pygame.init()
WIDTH, HEIGHT = 1000, 600
win = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("World game")
font_path = path.join("fonts", "PartyConfetti.ttf")
small_font = pygame.font.Font(font_path, 50)
centre = ((win.get_width())//2, (win.get_height())//2)
WHITE = (255, 255, 255)
BLACK = ( 0, 0, 0)
PASTEL_BLUE = (189, 242, 255)
button_img = pygame.image.load(path.join("assets", "button.png"))
button_img = pygame.transform.scale(button_img, (300, 100))
arrow = pygame.image.load(path.join("assets", "arrow.png"))
arrow = pygame.transform.scale(arrow, (50, 50))
up = pygame.transform.rotate(arrow, 90)
down = pygame.transform.rotate(arrow, 270)
def game():
questions = generate_question("capital", 5, 1)
pressed = False
running = True
while running:
for q in questions:
win.fill(PASTEL_BLUE)
title = small_font.render(f"{q}. {questions[q][0]}", True, "black")
title_rect = title.get_rect(center=(centre[0], centre[1]-200))
win.blit(title, title_rect)
choice1 = Button(win, button_img, (300, 500), "CHOICE")
while not pressed and running:
mouse_pos = pygame.mouse.get_pos()
choice1.changeColor(mouse_pos)
choice1.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
if choice1.checkForInput(mouse_pos):
pressed = True
pygame.display.update()
if not running:
break
pygame.quit()
sys.exit(0)
There are probably still some problems with this code but hopefully this helps fix your problem.
This question already has answers here:
How can I play an mp3 with pygame?
(7 answers)
how to play wav file in python?
(5 answers)
Closed 5 months ago.
I'm not so sure why but my game screen window/pygame window sometimes freezes or is not responding because of the added SFX or music in my game. If I remove the SFX or music the game runs smoothly. Hoping that anyone can help me or at least tell me why it keeps freezing. Thanks very much.
The specific code for music and sounds in class Game can be found in load_data method, run method, and update method.
This is where you can find my music, target SFX when hit, bonus SFX when hit.
Below is from my main.py module
import pygame as py
import sys
from os import kill, path
from Setting import *
from Sprite import *
import random
#BUGs (BULLET DISAPPEAR WHEN PAUSED [PROBABLY FROM get_tick()])
#add highscore later in end screen
class Game():
def __init__(self):
py.mixer.pre_init(44100, -16, 1, 512)
py.init()
self.screen = py.display.set_mode((Width, Height),py.RESIZABLE)
py.display.set_caption("Hit Me!")
self.clock = py.time.Clock()
self.load_data()
self.score_value = 0
self.time_value = 90
self.paused = False
self.channel1 = py.mixer.Channel(1)
self.channel2 = py.mixer.Channel(2)
self.channel3 = py.mixer.Channel(3)
def draw_data(self, text, font_name, size, color, x, y,type=0, align="center", value=""):
font = py.font.Font(font_name, size)
if type == 0:
text_surface = font.render(text + str(value), True, color)
if self.paused == True:
text_surface.set_alpha(125)
else:
text_surface = font.render(text, True, color)
text_rect = text_surface.get_rect()
if align == "nw":
text_rect.topleft = (x, y)
if align == "ne":
text_rect.topright = (x, y)
if align == "sw":
text_rect.bottomleft = (x, y)
if align == "se":
text_rect.bottomright = (x, y)
if align == "n":
text_rect.midtop = (x, y)
if align == "s":
text_rect.midbottom = (x, y)
if align == "e":
text_rect.midright = (x, y)
if align == "w":
text_rect.midleft = (x, y)
if align == "center":
text_rect.center = (x, y)
self.screen.blit(text_surface, text_rect)
def load_data(self):
game_folder = path.dirname(__file__)
self.map_data = []
with open(path.join(game_folder, 'map.txt'), 'rt') as f:
for line in f:
self.map_data.append(line)
image_folder = path.join(game_folder, 'images')
sfx_folder = path.join(game_folder, 'sfx_music')
self.dim_screen = py.Surface(self.screen.get_size()).convert_alpha()
self.dim_screen.fill((0, 0, 0, 150))
self.player_img = py.image.load(
path.join(image_folder, character)).convert_alpha()
self.bullet_img = py.image.load(
path.join(image_folder, bullets)).convert_alpha()
self.target_img = py.image.load(
path.join(image_folder, targets)).convert_alpha()
self.target_img = py.transform.scale(
self.target_img, (TileSize + 20, TileSize + 20))
self.bonus_img = py.image.load(
path.join(image_folder, time)).convert_alpha()
self.bonus_img = py.transform.scale(
self.bonus_img, (TileSize + 30, TileSize + 30))
#sound loading
self.music = py.mixer.Sound(path.join(sfx_folder,background_music))
self.music.set_volume(0.25)
self.gun_sfx = py.mixer.Sound(path.join(sfx_folder,gun_sound))
self.bonus_sfx = py.mixer.Sound(path.join(sfx_folder,bonus_sound))
self.target_sfx = py.mixer.Sound(path.join(sfx_folder,target_sound))
self.gun_sfx.set_volume(0.4)
self.target_sfx.set_volume(0.5)
def new(self):
self.sprites = py.sprite.Group()
self.walls = py.sprite.Group()
self.grass = py.sprite.Group()
self.targets = py.sprite.Group()
self.bullets = py.sprite.Group()
self.bonuss = py.sprite.Group()
for row, tiles in enumerate(self.map_data):
for col, tile in enumerate(tiles):
if tile == 'P':
self.player = Player(self, col, row)
py.time.set_timer(py.USEREVENT, 1000)
def newTarget(self):
self.targets = py.sprite.Group()
for row, tiles in enumerate(self.map_data):
for col, tile in enumerate(tiles):
if tile == '.':
target_chances = random.randint(0, 100)
if target_chances <= 10: # 10% chance
tile = 'T'
if tile == 'M':
bonus_chances = random.randint(0,100)
if bonus_chances <= 5: # 5% chance bonus time (add 2 or 3 seconds to timer) [specified area]
tile = 'B'
if tile == 'T':
Target(self, col, row)
if tile == 'B':
Bonus(self,col,row)
def run(self):
self.playing = True
self.music_played = False
if not self.music_played:
self.channel3.play(self.music, loops = -1)
self.music_played = True
while self.playing:
self.dt = self.clock.tick(FPS) / 1000
self.events()
if not self.paused:
self.update()
self.channel3.unpause()
if self.paused:
self.channel3.pause()
self.draw()
def quit(self):
py.quit()
sys.exit()
def update(self):
self.sprites.update()
hits = py.sprite.groupcollide(
self.targets, self.bullets, True, True, py.sprite.collide_mask)
bonus_hits = py.sprite.groupcollide(
self.bonuss, self.bullets, True, True, py.sprite.collide_mask)
#for bullets & targets
for hit in hits:
hit.kill()
self.score_value += 1
self.target_sfx_played = False
if not self.target_sfx_played:
self.channel1.play(self.target_sfx)
self.target_sfx_played = True
#for bullets & bonus
for hit in bonus_hits:
hit.kill()
self.time_value += 3
self.bonus_sfx_played = False
if not self.bonus_sfx_played:
self.channel2.play(self.bonus_sfx)
self.bonus_sfx_played = True
#if there is no target in screen, it will create a new set
if len(self.targets) == 0:
self.newTarget()
def drawGrid(self):
for x in range(0, Width, TileSize):
py.draw.line(self.screen, Black, (x, 0), (x, Height))
for y in range(0, Height, TileSize):
py.draw.line(self.screen, Black, (0, y), (Width, y))
def draw(self):
py.display.set_caption("{:.2f}".format(self.clock.get_fps()))
self.screen.fill(BGcolor)
self.drawGrid()
self.sprites.draw(self.screen)
py.draw.rect(self.screen, Green, self.player.rect, 1)
if self.paused:
self.screen.blit(self.dim_screen, (0, 0))
self.draw_data("Paused",'freesansbold.ttf', 105, White, Width / 2, Height / 2,1)
self.draw_data("Score: ",'freesansbold.ttf', 40, White, 100, 35,value = self.score_value)
self.draw_data("Time: ",'freesansbold.ttf', 40, White, 450, 35 ,value = self.time_value)
self.draw_data("HighScore: ",'freesansbold.ttf', 40, White, Width - 225, 35) # add data in end game
py.display.flip()
def events(self):
for event in py.event.get():
if event.type == py.QUIT:
self.quit()
if event.type == py.KEYDOWN:
if event.key == py.K_ESCAPE:
self.paused = not self.paused
if event.type == py.MOUSEBUTTONDOWN:
if event.button == 1:
if not self.paused:
self.player.shoot()
if event.type == py.USEREVENT:
if not self.paused:
self.time_value -=1
def show_start_screen(self):
pass
def show_go_screen(self):
pass
# main loop
g = Game()
g.show_start_screen()
while True:
g.new()
g.run()
g.show_go_screen()
In the Sprite.py module, this is where you can find the SFX for gun shooting whenever I press/hold the left mouse button.
Below is from my Sprite.py module
import pygame as py
from Setting import *
from pygame.locals import *
vec = py.math.Vector2
class Player(py.sprite.Sprite):
def __init__(self, game,x,y):
self.groups = game.sprites
py.sprite.Sprite.__init__(self, self.groups)
self.game = game
self.orig_image = game.player_img
self.rect = self.orig_image.get_rect()
self.pos = vec(x,y) * TileSize
self.rect.center = self.pos
self.last_shot = 0
self.gun_sfx = game.gun_sfx
self.channel4 = py.mixer.Channel(4)
def shoot(self):
if py.mouse.get_pressed()[0]:
now = py.time.get_ticks()
if now - self.last_shot > bullet_rate:
self.gun_sfx_played = False
if not self.gun_sfx_played:
self.channel4.play(self.gun_sfx)
self.gun_sfx_played = True
self.last_shot = now
dir = py.mouse.get_pos() - self.pos
radius, angle = dir.as_polar()
direction = vec(1, 0).rotate(angle - 3)
pos = self.pos + Barrel_offset.rotate(angle)
Bullet(self.game, pos, direction, -angle)
def rotate(self):
dir = py.mouse.get_pos() - self.pos
radius, angle = dir.as_polar()
self.image = py.transform.rotate(self.orig_image, -angle)
self.rect = self.image.get_rect(center=self.rect.center)
def update(self):
self.shoot()
self.rotate()
Below is from my Setting.py
# sounds
gun_sound = 'gun_sfx.mp3'
background_music = 'bg_music.mp3'
target_sound = 'target_sfx_broken.mp3'
bonus_sound = 'hourglass_sfx.mp3'
import random
from time import sleep
import pygame
class InlineOverdrive:
def __init__(self):
pygame.init()
self.display_width = 600
self.display_height = 600
self.black = (0, 0, 0)
self.white = (255, 255, 255)
self.clock = pygame.time.Clock()
self.gameDisplay = None
self.initialize()
def initialize(self):
self.crash = False
self.carImg = pygame.image.load('.\\img\\Car.png')
self.car_x_coordinate = (self.display_width * 0.45)
self.car_y_coordinate = (self.display_height * 0.8)
self.car_width = 49
# Background
self.bgImg = pygame.image.load(".\\img\\Background.png")
rect = self.bgImg.get_rect ()
self.bg_x2 = 0
self.bg_y2 = -600
self.bg_x1 = 0
self.bg_y1 = 0
self.bg_speed = 3
self.count = 0
def car(self, car_x_coordinate, car_y_coordinate):
self.gameDisplay.blit(self.carImg, (car_x_coordinate, car_y_coordinate))
def racing_window(self):
self.gameDisplay = pygame.display.set_mode((self.display_width, self.display_height))
pygame.display.set_caption('Inline Overdrive')
self.run_car()
def run_car(self):
while not self.crash:
for event in pygame.event.get():
self.backgroundroad()
if event.type == pygame.QUIT:
self.crash = True
# print(event)
if (event.type == pygame.KEYDOWN):
if (event.key == pygame.K_LEFT):
self.car_x_coordinate -= 50
print ("CAR X COORDINATES: %s" % self.car_x_coordinate)
if (event.key == pygame.K_RIGHT):
self.car_x_coordinate += 50
print ("CAR X COORDINATES: %s" % self.car_x_coordinate)
print ("x: {x}, y: {y}".format(x=self.car_x_coordinate, y=self.car_y_coordinate))
self.gameDisplay.fill(self.black)
self.backgroundroad()
self.car(self.car_x_coordinate, self.car_y_coordinate)
self.highscore(self.count)
self.count += 1
if (self.count % 100 == 0):
self.bg_speed += 1
if self.car_x_coordinate < 100 or self.car_x_coordinate > 360:
self.crash = True
self.display_message("You Have Crashed!")
pygame.display.update()
self.clock.tick(60)
def display_message(self, msg):
font = pygame.font.SysFont("NFS_by_JLTV.ttf", 72, True)
text = font.render(msg, True, (255, 255, 255))
self.gameDisplay.blit(text, (400 - text.get_width() // 2, 240 - text.get_height() // 2))
pygame.display.update()
self.clock.tick(60)
sleep(1)
inline_overdrive.initialize()
inline_overdrive.racing_window()
def backgroundroad(self):
self.gameDisplay.blit(self.bgImg, (self.bg_x1, self.bg_y1))
self.gameDisplay.blit(self.bgImg, (self.bg_x2, self.bg_y2))
self.bg_y1 += self.bg_speed
self.bg_y2 += self.bg_speed
if self.bg_y1 >= self.display_height:
self.bg_y1 = 0
elif self.bg_y2 >= self.display_height:
self.bg_y2 = 0
def highscore(self, count):
font = pygame.font.SysFont("NFS_by_JLTV.ttf", 20)
text = font.render("SCORE : " + str(count), True, self.white)
self.gameDisplay.blit(text, (0, 0))
if __name__ == '__main__':
inline_overdrive = InlineOverdrive()
inline_overdrive.racing_window()
I want this code to be able to loop the background infinitely as it moves along with the player. The screen currently does not scroll properly. The crash text is also not centered. How can I center it? If I can get my background to work properly, then my game is mostly complete. My car is also off screen for some reason. What needs to be done to make it be on the screen in the background?Image of My Car and Image of My Background
To center text on screen (or on any other Surface()) you can use Rect().center and
text_rect.center = display_rect.center # center on screen
text_rect.center = button_rect.center # center on button
font.render gives Surface(), and it has .get_rect() to get Rect() with its size and position - at start it may have position (0,0) but self.gameDisplay is also a Surface() and it has .get_rect() which gives Rect() with its size and position. If you copy .center from display to text_rect then you will have centered rect and you can use it in blit() to center text
def display_message(self, msg):
font = pygame.font.SysFont("NFS_by_JLTV.ttf", 72, True)
text_image = font.render(msg, True, (255, 255, 255))
text_rect = text_image.get_rect()
text_rect.center = self.gameDisplay.get_rect().center
self.gameDisplay.blit(text_image, text_rect)
# ... rest ...
As for background mistake is self.bg_y1 = 0 and self.bg_y2 = 0 because you have to set -600 to put background above visible area.
if self.bg_y1 >= self.display_height:
self.bg_y1 = -600
elif self.bg_y2 >= self.display_height:
self.bg_y2 = -600
Maybe it would be simpler if you would use Rect() which has .top and .bottom
and you could set as start
self.display_rect = self.gameDisplay.get_rect()
self.bg2_rect.bottom = self.display_rect.top
self.bg1_rect.bottom = self.display_rect.bottom
and later
if self.bg1_rect.top >= self.display_rect.bottom:
self.bg1_rect.bottom = self.display_rect.top
elif self.bg2_rect.top >= self.display_rect.bottom:
self.bg2_rect.bottom = self.display_rect.top
BTW:
Rect() has also .left, .right, .centerx, .centery, etc. It has also functions to detect collisions with point (ie. mouse position) - ie. rect.collidepoint(mouse_pos) - or with other rectange - ie. car_rect.colliderect( obstacle_rect )
I am making a game similar to Adventure Capitialist. The desired outcome is to have multiple "buy buttons". I have a function that creates one, but if I copy and paste, and change coordinates, it still doesn't work. I will put code from before I attempted to copy the function, because I think I was pretty far off from my desired outcome. The Function I want to copy is capitialistOne
import pygame, sys, shelve, pickle
import time as Time
from decimal import Decimal
pygame.init()
WHITE = (255,255,255)
BLACK = (0,0,0)
GREEN = (10, 200, 40)
RED = pygame.Color('red')
DISPLAYSURF = pygame.display.set_mode((460, 720))
clock = pygame.time.Clock()
logo = pygame.image.load('Logo.png')
menu = pygame.image.load('menu.png')
storeBoard = pygame.image.load('storeBoard.png')
loadingBar = pygame.image.load('loadingBar.png')
mainMenu = pygame.image.load('mainMenu.png')
Font1 = pygame.font.SysFont('monaco', 24)
Font2 = pygame.font.SysFont('monaco', 30)
cash = 5
barlength = 102 # the lenght of the growing bar
def buyDraw(amount, minxbuy, minybuy):
buySurface = Font1.render('{0}'.format(amount), True, BLACK)
buyRect = buySurface.get_rect()
buyRect.midtop = (85, minybuy)
DISPLAYSURF.blit(buySurface, buyRect)
def cashDraw(cash):
cashSurface = Font2.render(' ${0}'.format(cash), True, GREEN)
cashRect = cashSurface.get_rect()
cashRect.midtop = (387, 10)
DISPLAYSURF.blit(cashSurface, cashRect)
def capitalistOne(amount, cost, timez, gain, minxbuy, maxxbuy, minybuy, maxybuy, minxgain, maxxgain, minygain, maxygain, cash):
pygame.display.set_caption('capitalist')
buy_button = pygame.Rect(minxbuy, minybuy, maxxbuy, maxybuy)
gain_button = pygame.Rect(minxgain, minygain, maxxgain, maxygain)
menuRect = pygame.Rect(400, 680, 30, 30)
coefficient = maxxgain / timez #
time = 0
dt = 0
upgrade = amount * gain
loop = False
while True:
mouse_pressed = pygame.mouse.get_pressed()
mouse_pos = pygame.mouse.get_pos()
inc = time * coefficient
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
#Return to menu
if menuRect.collidepoint(mouse_pos) and mouse_pressed[0]:
f = open('store.pckl', 'wb')
pickle.dump(amount, f)
f.close()
opening()
# Max LVL
if buy_button.collidepoint(mouse_pos) and mouse_pressed[0] and amount >= 1000:
maxLvlSurface = Font1.render('Max Lvl Reached', True, RED)
maxLvlRect = maxLvlSurface.get_rect()
maxLvlRect.midtop = (215, 5)
DISPLAYSURF.blit(maxLvlSurface, maxLvlRect)
pygame.display.flip()
Time.sleep(0.5)
# buy button
if buy_button.collidepoint(mouse_pos) and mouse_pressed[0] and cash >= cost and amount < 1000:
amount += 1
cash -= cost
upgrade = amount * gain
#Gain Button
if gain_button.collidepoint(mouse_pos) and mouse_pressed[0] and amount > 0:
loop = True # alows the user to click, then have the bar grow, rather than while they are clicking run it
if loop == True:
if time < timez: # if the bar isnt full, add to it
time += dt
if time >= timez: # if the bar is full, reset time, give cash.
cash += upgrade
time = 0
loop = False
# Draw everything
else:
DISPLAYSURF.blit(storeBoard, (0,0)) # Draw the background, icons, ect.
pygame.draw.rect(DISPLAYSURF, GREEN, (minxgain + 1, minygain, inc, maxygain)) # Draws a portion of the green bar
pygame.draw.rect(DISPLAYSURF, BLACK, gain_button, 2) # draws a border around the gain bar
DISPLAYSURF.blit(mainMenu, (400, 680))# draws a main menu button to return to main menu
buyDraw(amount, minxbuy, minybuy)# draws the buy button
# Should the cash be displayed in Sci Notation or in standar
if int(cash) < 1000000:
cashDraw(cash)
if int(cash) > 1000000:
SciNot = '%.2E' % Decimal(str(cash))
cashDraw(SciNot)
pygame.display.flip()
dt = clock.tick(60) / 1000
def opening():
DISPLAYSURF.fill(WHITE)
DISPLAYSURF.blit(logo, (155, 50))
DISPLAYSURF.blit(menu, (0 , 125))
saveRect = pygame.Rect(400, 680, 30, 30)
pygame.display.set_icon(logo)
pygame.display.flip()
f = open('store.pckl', 'rb')
gShovelAmount = pickle.load(f)
f.close()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
(x, y) = pygame.mouse.get_pos()
if x < 375 and x > 80 and y < 545 and y > 395:
capitalistOne(
amount=gShovelAmount, cost=5, timez=10, gain=5, minxbuy=21,
maxxbuy=41, minybuy=21, maxybuy=41, minxgain=120,
maxxgain=204, minygain=21, maxygain=41, cash=cash)
opening()
Here is a button that should suit your needs:
class Button(object):
global screen_width,screen_height,screen
def __init__(self,x,y,width,height,text_color,background_color,text):
self.rect=pygame.Rect(x,y,width,height)
self.x=x
self.y=y
self.width=width
self.height=height
self.text=text
self.text_color=text_color
self.background_color=background_color
def check(self):
return self.rect.collidepoint(pygame.mouse.get_pos())
def draw(self):
pygame.draw.rect(screen, self.background_color,(self.rect),0)
drawTextcenter(self.text,font,screen,self.x+self.width/2,self.y+self.height/2,self.text_color)
pygame.draw.rect(screen,self.text_color,self.rect,3)
Implemented into the main loop:
button=Button(x,y,width,height,text_color,background_color,text)
while not done:
for event in pygame.event.get():
if event.type==QUIT:
terminate()
elif event.type==pygame.MOUSEBUTTONDOWN:
if dodger_button.check():
#what to do when button is pressed
#fill screen with background
screen.fill(background)
button.draw()
pygame.display.flip()
clock.tick(fps)
This question is really difficult to ask, but I know you guys here at Stack Overflow are the brightest minds.
I'm totally blinded by why this issue happens (I'm fairly at Python and Pygame, so any suggestions on how to improve the code will be received with the love of improving my skills).
What I'm creating:
It's really a gimmick project, I have a little 2.5" screen (PiTFT) attached to a Raspberry Pi and the code is creating a typewriter effect with a moving cursor in front of the text as it's being written.
Challenge 1 was that every time you move a sprite in pygame, you must redraw everything, otherwise you will see a trail, and since the cursor is moving in front of the text, the result would look like this:
I managed to solve this issue by blackening / clearing the screen. But then I lost all the previously written letters.
So I created a list (entireword), which I'm populing with all the previously written characters. I use this list every time I cycle through the loop to redraw all the previous written text.
So now:
As you can see, the text looks funny.
It's supposed to read:
[i] Initializing ...
[i] Entering ghost mode ... []
I've been spending hours and hours getting to this point - and the code ALMOST works perfectly! The magic happens in the function print_screen(), but WHAT in my code is causing the text to include a letter from the other line in the end? :>
Help is GREATLY appreciated <3
Here's the entire code:
import pygame
import time
import os
import sys
from time import sleep
from pygame.locals import *
positionx = 10
positiony = 10
entireword = []
entireword_pos = 10
counter = 0
entire_newline = False
#Sets the width and height of the screen
WIDTH = 320
HEIGHT = 240
speed = 0.05
#Importing the external screen
os.putenv('SDL_FBDEV', '/dev/fb1')
os.putenv('SDL_MOUSEDRV', 'TSLIB')
os.putenv('SDL_MOUSEDEV', '/dev/input/touchscreen')
#Initializes the screen - Careful: all pygame commands must come after the init
pygame.init()
#Sets mouse cursor visibility
pygame.mouse.set_visible(False)
#Sets the screen note: must be after pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
# initialize font; must be called after 'pygame.init()' to avoid 'Font not Initialized' error
myfont = pygame.font.SysFont("monospace", 18)
#Class
class cursors(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((10, 20))
self.image.fill((0,255,0))
self.rect = self.image.get_rect()
self.rect.center = (positionx + 10, positiony + 10)
def update(self):
self.rect.x = positionx + 10
self.rect.y = positiony
#Functions
#Prints to the screen
def print_screen(words, speed):
rel_speed = speed
for char in words:
#speed of writing
if char == ".":
sleep(0.3)
else:
sleep(rel_speed)
#re-renders previous written letters
global entireword
# Old Typewriter functionality - Changes position of cursor and text a newline
#Makes sure the previous letters are rendered and not lost
#xx is a delimter so the program can see when to make a newline and ofcourse ignore writing the delimiter
entireword.append(char)
if counter > 0:
loopcount = 1
linecount = 0 # This is to which line we are on
for prev in entireword:
if prev == 'xx':
global linecount
global positiony
global loopcount
linecount = linecount + 1
positiony = 17 * linecount
loopcount = 1
if prev != 'xx': #ignore writing the delimiter
pchar = myfont.render(prev, 1, (255,255,0))
screen.blit(pchar, (loopcount * 10, positiony))
loopcount = loopcount + 1
if char != 'xx':
# render text
letter = myfont.render(char, 1, (255,255,0))
#blits the latest letter to the screen
screen.blit(letter, (positionx, positiony))
# Appends xx as a delimiter to indicate a new line
if entire_newline == True:
entireword.append('xx')
global entire_newline
entire_newline = False
global positionx
positionx = positionx + 10
all_sprites.update()
all_sprites.draw(screen)
pygame.display.flip()
screen.fill((0,0,0)) # blackens / clears the screen
global counter
counter = counter + 1
#Positions cursor at new line
def newline():
global positionx
global positiony
positionx = 10
positiony = positiony + 17
all_sprites = pygame.sprite.Group()
cursor = cursors()
all_sprites.add(cursor)
#Main loop
running = True
while running:
global speed
global entire_newline
words = "[i] Initializing ..."
entire_newline = True
newline()
print_screen(words,speed)
words = "[i] Entering ghost mode ..."
entire_newline = True
newline()
print_screen(words,speed)
#Stops the endless loop if False
running = False
sleep(10)
Sorry if I don't answer your question directly, because your code is too confusing for me now, so I took the liberty to rewrite your code to get done what you want.
The idea is to have two sprites:
the cursor, which is a) displayed on the screen and b) keeps track of what text to write and where
the board, which is basically just a surface that the text is rendered on
Note how all the writing logic is on the Cursor class, and we have a nice, simple and dumb main loop.
import pygame
import os
#Sets the width and height of the screen
WIDTH = 320
HEIGHT = 240
#Importing the external screen
os.putenv('SDL_FBDEV', '/dev/fb1')
os.putenv('SDL_MOUSEDRV', 'TSLIB')
os.putenv('SDL_MOUSEDEV', '/dev/input/touchscreen')
#Initializes the screen - Careful: all pygame commands must come after the init
pygame.init()
clock = pygame.time.Clock()
#Sets mouse cursor visibility
pygame.mouse.set_visible(False)
#Sets the screen note: must be after pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
class Board(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((WIDTH, HEIGHT))
self.image.fill((13,13,13))
self.image.set_colorkey((13,13,13))
self.rect = self.image.get_rect()
self.font = pygame.font.SysFont("monospace", 18)
def add(self, letter, pos):
s = self.font.render(letter, 1, (255, 255, 0))
self.image.blit(s, pos)
class Cursor(pygame.sprite.Sprite):
def __init__(self, board):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((10, 20))
self.image.fill((0,255,0))
self.text_height = 17
self.text_width = 10
self.rect = self.image.get_rect(topleft=(self.text_width, self.text_height))
self.board = board
self.text = ''
self.cooldown = 0
self.cooldowns = {'.': 12,
'[': 18,
']': 18,
' ': 5,
'\n': 30}
def write(self, text):
self.text = list(text)
def update(self):
if not self.cooldown and self.text:
letter = self.text.pop(0)
if letter == '\n':
self.rect.move_ip((0, self.text_height))
self.rect.x = self.text_width
else:
self.board.add(letter, self.rect.topleft)
self.rect.move_ip((self.text_width, 0))
self.cooldown = self.cooldowns.get(letter, 8)
if self.cooldown:
self.cooldown -= 1
all_sprites = pygame.sprite.Group()
board = Board()
cursor = Cursor(board)
all_sprites.add(cursor, board)
text = """[i] Initializing ...
[i] Entering ghost mode ...
done ...
"""
cursor.write(text)
#Main loop
running = True
while running:
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
all_sprites.update()
screen.fill((0, 0, 0))
all_sprites.draw(screen)
pygame.display.flip()
clock.tick(60)
Use the pygame.event module. Use pygame.time.set_timer() to repeatedly create a USEREVENT in the event queue. The time has to be set in milliseconds. e.g.:
typewriter_event = pygame.USEREVENT+1
pygame.time.set_timer(typewriter_event, 100)
Add a new letter to the text, when the timer event occurs:
while run:
for event in pygame.event.get():
# [...]
if event.type == typewriter_event:
text_len += 1
See also Typewriter
Minimal example:
repl.it/#Rabbid76/PyGame-Typewriter
import pygame
pygame.init()
window = pygame.display.set_mode((500, 150))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 100)
background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 50, *window.get_size(), (32, 32, 32), (64, 64, 64)
tiles = [((x*ts, y*ts, ts, ts), c1 if (x+y) % 2 == 0 else c2) for x in range((w+ts-1)//ts) for y in range((h+ts-1)//ts)]
for rect, color in tiles:
pygame.draw.rect(background, color, rect)
text = 'Hello World'
text_len = 0
typewriter_event = pygame.USEREVENT+1
pygame.time.set_timer(typewriter_event, 100)
text_surf = None
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == typewriter_event:
text_len += 1
if text_len > len(text):
text_len = 0
text_surf = None if text_len == 0 else font.render(text[:text_len], True, (255, 255, 128))
window.blit(background, (0, 0))
if text_surf:
window.blit(text_surf, text_surf.get_rect(midleft = window.get_rect().midleft).move(40, 0))
pygame.display.flip()
pygame.quit()
exit()