Python and Pygame: Avoiding creating display surface twice - python

Heyo, this is a bit of an extension of the "Imports within imports" question asked earlier by me, so moderators feel free to merge the 2.
I have 2 files: A.py and B.py
#A.py
import pygame
import B
pygame.init()
tv = pygame.display.set_mode((256, 256))
tv.blit(<some surface here>)
#B.py
import pygame
pygame.init()
tv.blit()??? <--- I need to blit to tv, but how do I do it here?
I've tried making a blank file called Globe and assigning global values to it, but most of the time I've found it just makes my code look clunky and hard to write.
As well.. I don't want to init pygame twice either.
Is there any 'Pythonic' way to do it?

This question could really apply to any structured python application.
An executable python script is going to have an entry-point. This is the script that you call to start the application. Within this script, it can import library modules to reuse extended functionality.
Your application needs to have a single entry point. Lets assume it will be A.py.
B.py would be a library module that you will import and use its functions. It should not have to expect a global tv variable to operate on. Instead, it should have, at the very least, functions that take arguments. Or even, a class that is instantiated with a surface to use. The benefit of this approach is that your B module is now reusable and not dependent on some executable main script providing a global surface always called tv
B.py
def blitSpecial(surf):
surf.blit()
A.py
import B
tv = pygame.display.set_mode((256, 256))
B.blitSpecial(tv)
This is a good habit to get into. If all your modules depend on global objects from a main script, they will be far less reusable.
Specifically for your pygame situation, everything with the screen surface should be happening in A.py which is using all of the other modules for custom classes and utility functions.

You can write functions that take pygame.Surface objects as parameters:
class TV():
def __init__(self):
self.img = ...
### insert code to load image here
self.rect = self.img.get_rect()
def draw(self, surface):
surface.blit(self.img, self.rect.topleft)
def erase(self, surface, background):
surface.blit(background, self.rect)
I don't personally know how fast/slow this is compared to other sprite-based engines, but it's a very quick way to build out a class that can draw/erase itself.
To use it, just create a display screen and a TV object.
screen = pygame.display.set_mode((256, 256))
background = pygame.Surface((0,0),(256,256))
background.fill(pygame.Color(0,0,0))
screen.fill(pygame.Color(0,0,0))
myTVobj = TV()
Every time you want to draw a copy of the TV onto the screen you call
myTVobj.draw(screen)
To erase the object, use
myTVobj.erase(screen, background)
Then you can do fun stuff later with objects created from the TV class, like stick them in a list.
tv_list = []
tv_list.append(myTVobj)
You can add a whole bunch of TVs to a list and draw all of them at the same time.
tv_list = []
tv_list.append(myTVobj)
tv_list.append(myTVobj)
tv_list.append(myTVobj)
for tv in tv_list:
tv.draw(screen)
Or you can erase them all just by changing one line
for tv in tv_list:
tv.erase(screen)
Finally, you can add one more function to your TV class that lets you move it around. If you treat the .rect member as a 'position marker', all you have to do is fiddle with its members (hehe) to change your object's onscreen update location.
def move(self, move_amount=(1,0):
self.rect.move_ip(move_amount[0], move_amount[1])

You only need to call pygame.init() once, so I think your code should look something like this:
#A.py
import pygame
import B
def setup():
pygame.init()
tv = pygame.display.set_mode((256, 256))
...
mysurface = ...
tv.blit(mysurface)
return tv
#B.py
import pygame
def mydraw(surface):
...
surface.blit()
# In whatever file you like :)
if __name__ == '__main__':
surface_A = B.setup() # Do this first
mydraw(surface_A)

Related

Why am I able to use the pygame module methods in one .py file even though I imported the module in another file?

I have two .py files. One of them is the main file with all the code and the other one is the file I decided to create to clean up my main file. I wanted to create a separate class in order to get the main file more organised, then simply import the class in the main file.
I was expecting that I'd have to import pygame in the additional file as well in order to use the methods it'd provide, such as .blit or .render. However, I noticed that PyCharm suggested me to get rid of the import, as it was redundant and never used. This is how the code in the additional file looks like. It uses the dis variable (dis = pygame.display.set_mode((DIS_WIDTH, DIS_HEIGHT)) ) that I'm passing in the main.py.
My own guess is that maybe the fact that I'm passing the dis variable accounts for this, but I'm not sure how it's done, since I'm new to OOP and to programming in general. I tried to look for the answer on the web, but I didn't find anything that would remind me my problem. So, I really hope there is an explanation for this kind of behavior.
from constants import * #constants is another additional file, containing some variables
class Scoreboard:
def __init__(self, m_display):
self.display = m_display
self.width = DIS_WIDTH
self.height = DIS_HEIGHT
def message(self, msg, color):
mesg = MAIN_FONT_STYLE.render(msg, True, color)
self.display.blit(mesg, [self.width / 6, self.height / 3])
def your_score(self, score):
value = SCORE_FONT_STYLE.render("Your Score: " + str(score), True, YELLOW)
self.display.blit(value, [0, 0])
constants.py contains import pygame in itself.
import pygame
pygame.init()
MAIN_FONT_STYLE = pygame.font.SysFont(None, 25)
SCORE_FONT_STYLE = pygame.font.SysFont(None, 35)
When you use from constants import * you import everything that is defined in the module constants, including anything it has imported itself. That can be a lot more than you bargained for, and can cause conflicts by defining names that you didn't expect. That's why it's not recommended practice. Instead, you can just qualify each symbol with the module name:
import constants
# ...
mesg = constants.MAIN_FONT_STYLE.render(msg, True, color)
Or you can import only the things that you need:
from constants import MAIN_FONT_STYLE, SCORE_FONT_STYLE
P.S. the suggestion is correct, you don't need to import the module to use the methods on the objects it creates.

Clearing the screen for text based rpg in python

I am trying to make a text based rpg game in python. I just started and this is what my code looks so far:
class Game:
def __init__(self):
self.map = [[" " for i in range(50)]for i in range(30)]
def draw_map(self):
for i in range(len(self.map)):
print(self.map[i])
def add_stuff(self, thing, x, y):
self.map[y][x] = thing
game = Game()
class Player:
def __init__(self):
self.x = 1
self.y = 1
self.player = "ME"
def draw(self):
game.add_stuff(self.player, self.x, self.y)
def move(self):
pass
player = Player()
player.draw()
game.draw_map()
I was trying to find a way to implement a game loop in some way. To do this i was thinking of running the draw_map() and clearing the screen right away and printing the map again, a bit like real game loops. However i am having problems doing this. Based on other answers to other similar questions, i managed to produce the following code(it just shows the main loop and subprocess is imported as sp).
while True:
game.draw_map()
sp.call("cls", shell = True)
However this is not working for me. It simply dosent do anything. I also tried using clear function from clear_screen` module in a similar way and i cant figure out why this wouldnt work. Thanks for any help
so based on your previous comments you want to clear the screen in a Python interpreter. There is no "command" to clear the screen just like this. But you can use a loop to print some new lines until your screen is blank again.
def clear(lines):
for l in range(lines):
print()
while True:
game.draw_map()
clear(35)
time.sleep(0.05)
You may need to increase or decrease the amount of lines cleared. I hope this works for you. P.S.: I would use a normal console.
https://en.wikipedia.org/wiki/Interpreter_(computing)#Efficiency:
Efficiency:
The main disadvantage of interpreters is that an interpreted program typically runs slower than if it had been compiled. The difference in speeds could be tiny or great; often an order of magnitude and sometimes more. It generally takes longer to run a program under an interpreter than to run the compiled code but it can take less time to interpret it than the total time required to compile and run it. This is especially important when prototyping and testing code when an edit-interpret-debug cycle can often be much shorter than an edit-compile-run-debug cycle.
If your problem is clearing the console window, you can just use
import os
os.system('cls')
in place of
sp.call("cls", shell = True)
(Assuming you are working on windows)
EDIT for clarity:
This is the new loop after the change:
while True:
game.draw_map()
os.system('cls')

trying to draw over sprite or change picture pyglet

I am trying to learn pyglet and practice some python coding with a questionnaire thing, but I am unable to find a way to make the background picture be removed or drawn on top of or something for 10 seconds. I am new and am lacking in a lot of the knowledge I would need, thank you for helping!
import pyglet
from pyglet.window import Window
from pyglet.window import key
from pyglet import image
import time
card1 = False
cat_image = pyglet.image.load("cat.png")
dog_image = pyglet.image.load("dog.png")
image = pyglet.image.load("backg.png")
background_sprite = pyglet.sprite.Sprite(image)
cat = pyglet.sprite.Sprite(cat_image)
dog = pyglet.sprite.Sprite(dog_image)
window = pyglet.window.Window(638, 404, "Life")
mouse_pos_x = 0
mouse_pos_y = 0
catmeme = pyglet.image.load("catmeme.png")
sprite_catmeme = pyglet.sprite.Sprite(catmeme)
#window.event
def on_draw():
window.clear()
background_sprite.draw()
card_draw1(63, 192, 385, 192)
def card1():
while time.time() < (time.time() + 10):
window.clear()
sprite_catmeme.draw()
#window.event
def card_draw1(x1, y1, x2, y2):
cat.set_position(x1, y1)
dog.set_position(x2, y2)
cat.draw()
dog.draw()
def card_draw2():
pass
#window.event
def on_mouse_press(x, y, button, modifiers):
if x > cat.x and x < (cat.x + cat.width):
if y > cat.y and y < (cat.y + cat.height):
card1()
game = True
while game:
on_draw()
pyglet.app.run()
There's a few flaws in the order and in which you do things.
I will try my best to describe them and give you a piece of code that might work better for what your need is.
I also think your description of the problem is a bit of an XY Problem which is quite common when asking for help on complex matters where you think you're close to a solution, so you're asking for help on the solution you've come up with and not the problem.
I'm assuming you want to show a "Splash screen" for 10 seconds, which happens to be your background? And then present the cat.png and dog.png ontop of it, correct?
If that's the case, here's where you probably need to change things in order for it to work:
The draw() function
It doesn't really update the screen much, it simply adds things to the graphical memory. What updates the screen is you or something telling the graphics library that you're done adding things to the screen and it's time to update everything you've .draw()'n. So the last thing you need in the loop would be window.flip() in order for the things you've drawn to actually show.
Your things might show if you try to wiggle the window around, it should trigger a re-draw of the scene because of how the internal mechanics of pyglet work..
If you don't call .flip() - odds are probable that the redraw() call will never occur - which again, is a internal mechanism of Pyglet/GL that tells the graphics card that something has been updated, we're done updating and it's time to redraw the scene.
a scene
This is the word most commonly used for what the user is seeing.
I'll probably throw this around a lot in my text, so it's good to know that this is what the user is seeing, not what you've .draw()'n or what's been deleted, it's the last current rendering of the graphics card to the monitor.
But because of how graphical buffers work we've might have removed or added content to the memory without actually drawing it yet. Keep this in mind.
The pyglet.app.run() call
This is a never ending loop in itself, so having that in a while game: loop doesn't really make sense because .run() will "hang" your entire application, any code you want to execute needs to be in def on_draw or an event that is generated from within the graphical code itself.
To better understand this, have a look at my code, i've pasted it around a couple of times here on SO over the years and it's a basic model of two custom classes that inherits the behavior of Pyglet but lets you design your own classes to behave slightly differently.
And most of the functionality is under on_??? functions, which is almost always a function used to catch Events. Pyglet has a lot of these built in, and we're going to override them with our own (but the names must be the same)
import pyglet
from pyglet.gl import *
key = pyglet.window.key
class CustomSprite(pyglet.sprite.Sprite):
def __init__(self, texture_file, x=0, y=0):
## Must load the texture as a image resource before initializing class:Sprite()
self.texture = pyglet.image.load(texture_file)
super(CustomSprite, self).__init__(self.texture)
self.x = x
self.y = y
def _draw(self):
self.draw()
class MainScreen(pyglet.window.Window):
def __init__ (self):
super(MainScreen, self).__init__(800, 600, fullscreen = False)
self.x, self.y = 0, 0
self.bg = CustomSprite('bg.jpg')
self.sprites = {}
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
elif symbol == key.C:
print('Rendering cat')
self.sprites['cat'] = CustomSprite('cat.png', x=10, y=10)
elif symbol == key.D:
self.sprites['dog'] = CustomSprite('dog.png', x=100, y=100)
def render(self):
self.clear()
self.bg.draw()
for sprite_name, sprite_obj in self.sprites.items():
sprite_obj._draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
x = MainScreen()
x.run()
Now, this code is kept simple on purpose, the full code I usually paste on SO can be found at Torxed/PygletGui, the gui.py is where most of this comes from and it's the main loop.
What I do here is simply replace the Decorators by using "actual" functions inside a class. The class itself inherits the functions from a traditional pyglet.window.Window, and as soon as you name the functions the same as the inherited onces, you replace the core functionality of Window() with whatever you decide.. In this case, i mimic the same functions but add a few of my own.
on_key_press
One such example is on_key_press(), which normally just contain a pass call and does nothing, here, we check if key.C is pressed, and if so - we add a item to self.sprites.. self.sprites just so happen to be in our render() loop, anything in there will be rendered ontop of a background.
Here's the pictures I used:
(named bg.jpg, cat.png, dog.png - note the different file endings)
class:CustomSprite
CustomSprite is a very simple class designed to make your life easier at this point, nothing else. It's very limited in functionality but the little it do is awesome.
It's soul purpose is to take a file name, load it as an image and you can treat the object like a traditional pyglet.sprite.Sprite, meaning you can move it around and manipulate it in many ways.
It saves a few lines of code having to load all the images you need and as you can see in gui_classes_generic.py you can add a heap of functions that's "invisible" and normally not readily availbale to a normal sprite class.
I use this a bunch! But the code gets complicated real fast so I kept this post simple on purpose.
the flip function
Even in my class, I still need to use flip() in order to update the contents of the screen. This is because .clear() clears the window as you would expect, that also triggers a redraw of the scene.
bg.draw() might in some cases trigger a redraw if the data is big enough or if something else happens, for instance you move the window.
but calling .flip() will tell the GL backend to force a redraw.
Further optimizations
There's a thing called batched rendering, basically the graphic card is designed to take enormous ammounts of data and render it in one go, so calling .draw() on several items will only clog the CPU before the GPU even gets a chance to shine. Read more about Batched rendering and graphics! It will save you a lot of frame rates.
Another thing is to keep as little functionality as possible in the render() loop and use the event triggers as your main source of coding style.
Pyglet does a good job of being fast, especially if you only do things on event driven tasks.
Try to avoid timers, but if you really do need to use time for things, such as removing cat.png after a certain ammount of time, use the clock/time event to call a function that removes the cat. Do not try to use your own t = time() style of code unless you know where you're putting it and why. There's a good timer, I rarely use it.. But you should if you're starting off.
This has been one hell of a wall of text, I hope it educated you some what in the life of graphics and stuff. Keep going, it's a hurdle to get into this kind of stuff but it's quite rewarding once you've mastered it (I still haven't) :)

Creating an instance of a class existing in a different file(python)

I am trying to learn how to change my programs so that they use code from multiple python scripts. I have two scripts (these are big files, so cutting them down to only what's needed)
main.py
import pygame
import player #Imports the player.py script
p1 = hero("woody.png",[2,2]) #Creates an instance of hero
player.py
import pygame
import main
class hero:
def __init__(self,image,speed):
self.image = pygame.image.load(image)
self.speed = speed
self.pos = self.image.get_rect()
Running this gives me the following error:
AttributeError: 'module' object has no attribute 'hero'
I'm not quite understanding why it's trying to get an attribute instead of creating an instance. I've tried looking at other examples and how they are solving the problem but when I attempt to apply it to the code above it does not solve my issue.
To import hero from another module, you should write player.hero, or simply from player import hero.
Importing player in main and main in player would cause "circular references".
Here is the modified code:
main.py
import pygame
from player import hero # Imports the player.py script
p1 = hero("woody.png",[2,2]) # Creates an instance of hero
player.py
import pygame
class hero:
def __init__(self,image,speed):
self.image = pygame.image.load(image)
self.speed = speed
self.pos = self.image.get_rect()#.....etc
Like Athena said above, don't import main into player and player into main. This causes a import loop. Just simply import player into main
Secondly, you must say player.hero() if you want to use the class hero from the player module. Or if you want to just say hero(), you can say from player import*. This tells python to import all the files from player into the namespace main.
Be careful using this though, as a function or class from your player file may conflict with an already exiting function or class, with the same name.
And as a side note, classes in python general have their first letter capitalized.
here is how your files should look:
main.py
import pygame
import player #Imports the player.py script
p1 = hero("woody.png",[2,2]) #Creates an instance of hero
player.py
import pygame
class hero:
def __init__(self,image,speed):
self.image = pygame.image.load(image)
self.speed = speed
self.pos = self.image.get_rect()#.......etc
Drop the import main in player.py and change the last line in main.py to:
p1 = player.hero("woody.png",[2,2])
Edit:
Python does not know what class/function hero is. It needs you to tell it hero is a class in the player module. And that's what player.hero means.
Also never import one module from another and vice versa. You can get an import loop which is very hard to debug.
Lastly, in python it is common to name a class with a capital letter as Hero instead of hero.

Using Pygame, draw a copy of an image in a different location

This is kind of a follow up to this question:
Note: This in python, using the pygame library.
I'm wanting to get some clarification on this statement which is a comment from the above question:
Another important thing, don't use the same Sprite() nor the same
Rectangle in different locations. If you want to draw another copy of
the same image in a new location make a copy of the rectangle, then
create a new Sprite() object.
I have a group of game assets and I want to pick one at random to add my
sprite group. I am currently doing this as such:
class TestSprite(pygame.sprite.Sprite):
def __init__(self, strFolder):
super(TestSprite, self).__init__()
self.image = load_image(strFolder + 'x.png'))
#....
def GetRandomAsset():
rnd = random.randrange(1, 4)
if rnd == 1:
return TestSprite('assets/A/')
elif rnd == 2:
return TestSprite('assets/B/')
elif rnd == 3:
return TestSprite('assets/C/')
else:
return TestSprite('assets/D/')
#....
my_group = pygame.sprite.Group()
my_group.add(GetRandomAsset())
#.....
#Control FPS
#Check Events
#Update State
#Clear Screen
#Draw Current State
#Update Display
Everything works as expected; Every time I run the above code, a new sprite is displayed to the screen. My problem is every time i add a sprite, im having to load it from the disk, even tho im using the same 4 images over and over again.
I imagine it would be smarter for me to store all my assets in a global list called PRELOADEDASSETS. Then whenever I need one just do: my_group.add(PRELOADEDASSETS[x])
But when I try to do that, i can only have one copy of each of my assets.
How can I preload all my assets, and then grab the stored data when needed?
Thanks!
Another important thing, don't use the same Sprite() nor the same Rectangle in different locations. If you want to draw another copy of the same image in a new location make a copy of the rectangle, then create a new Sprite() object.
This is a valid advice in this context. It's of course no problem to blit a surface multiple times to different locations (using different Rects for example), but if you use a Group to handle/simplify your drawing, you need one object for each location you want to draw your image.
To answer your actual question:
You don't need to cache the Sprite instances. Just cache the Surface instances.
This way, you can have different Sprite objects that draw the same image to different locations.
A simple cache implementation could look like this:
images = {'a': load_image('assets/A/x.png'),
'b': load_image('assets/B/x.png'),
'c': load_image('assets/C/x.png'),
'd': load_image('assets/D/x.png')}
...
class TestSprite(pygame.sprite.Sprite):
def __init__(self, image_key):
super(TestSprite, self).__init__()
self.image = images[image_key]
...
def GetRandomAsset():
image_key = random.choice(images.keys())
return TestSprite(image_key)
This will load all images upfront (may or maynot what you want); adding laziness is easy, but you probably don't want possible lags (what is best depends how many images you actually load and when).
You could even just go through all folders in your game's directory and detect and load all images automatically, but such sprees are out of scope of this answer.

Categories