Python Forum
[PyGame] Variables shared between game states, how to?
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[PyGame] Variables shared between game states, how to?
#1
I've followed metulburr's tutorial: https://python-forum.io/Thread-PyGame-Cr...te-machine

Thanks for this BTW.

I'm able to code new states and switch between them at will, but I'm struggling with where in here to define variables that I want visible and changeable by all states(ie. location, money, etc)

I tried a global "location" variable, changed it in the starmap state ("print(location)" confirmed it) and flipped to planetscreen state, but "print(location)" there showed the original definition.

What would I put in here for shared variables and how would in refer to them in the state modules?

class Control:
    def __init__(self, **control_settings):
        self.__dict__.update(control_settings)
        self.done = False
        self.screen = pygame.display.set_mode(self.size)
        self.screen_rect = self.screen.get_rect()
        self.clock = pygame.time.Clock()
        self.state_dict = {
            'menu': Menu(self.screen_rect),
            'starmap': Starmap(self.screen_rect),
            'planetscreen': Planet_screen(self.screen_rect)
        }
    def setup_states(self, start_state):
        self.state_name = start_state
        self.state = self.state_dict[self.state_name]
    def flip_state(self):
        self.state.done = False
        previous,self.state_name = self.state_name, self.state.next
        self.state.cleanup()
        self.state = self.state_dict[self.state_name]
        self.state.startup()
        self.state.previous = previous
    def update(self, dt):
        if self.state.quit:
            self.done = True
        if self.state.done:
            self.flip_state()
        self.state.update(self.screen, dt)
    def event_loop(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.done = True
            
            self.state.get_event(event)
    def main_game_loop(self):
        while not self.done:
            delta_time = self.clock.tick(self.fps)/1000.0
            self.event_loop()
            
            self.update(delta_time)
            pygame.display.update()
Reply
#2
Location and money aren't global concepts, though. If there's two players, they could both be in different places and have different amounts of money. So I'd suggest a class to hold data and actions that are unique to players. Something like...
class Player:
    def __init__(self, start_location, start_money=0):
        self.location = start_location
        self.money = start_money
Then in the Control.__init__ you'd create your players, and either a) pass that list to the states when you call it's .startup() method so they know about the players, or b) pass the list to both the get_event() and the update() methods.
Reply
#3
So in this example I'd change my player to "lander" or something and change my state machine code to read:
def flip_state(self):
        self.state.done = False
        previous,self.state_name = self.state_name, self.state.next
        self.state.cleanup()
        self.state = self.state_dict[self.state_name]
        self.state.startup(player.location, player.money)  #  <----------add info here?
        self.state.previous = previous
And add info to the state.__init__ as marked?

EXAMPLE:
from settings import *

import pygame
PLAYER_SPEED = 100
PLAYER_ROT_SPEED = 70
PLAYER_HIT_RECT = pygame.Rect(0, 0, 35, 35)
class States(object):
    def __init__(self):
        self.done = False
        self.next = None
        self.quit = False
        self.previous = None

class Landerstate(States):
   
    def __init__(self, screenrect, player.location, player.money):    ##  <--------- and add info here?
        States.__init__(self)
        self.next = 'starmap'
        self.players = pygame.sprite.Group()
        self.all_sprites = pygame.sprite.Group()
        self.planet_surface = Planet_surface()
        self.all_sprites.add(self.planet_surface)
        self.minerals = pygame.sprite.Group()
        self.player = Player(300, 400)
        self.all_sprites.add(self.player)
        self.players.add(self.player)
        self.place_minerals()
        
    def cleanup(self):
        print('lander state')
    def startup(self):
        print('lander state')
    def get_event(self, event):
        
        
        self.player.rot_speed = 0
        self.player.vel = vec(0, 0)
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT] or keys[pygame.K_a]:
            self.player.rot_speed = PLAYER_ROT_SPEED
        if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
            self.player.rot_speed = -PLAYER_ROT_SPEED
        if keys[pygame.K_UP] or keys[pygame.K_w]:
            self.player.vel = vec(PLAYER_SPEED, 0).rotate(-self.player.rot)
        if keys[pygame.K_DOWN] or keys[pygame.K_s]:
            self.player.vel = vec(-PLAYER_SPEED / 2, 0).rotate(-self.player.rot)

        if keys[pygame.K_b]:
            self.next = "planetscreen"
            self.done = True
        hits = pygame.sprite.groupcollide(self.players, self.minerals, False, True)
        for hit in hits:
            cargo += 10
    def update(self, screen, dt):
        
        self.all_sprites.update(dt)
        self.draw(screen)
    def draw(self, screen):
       
        screen.fill(black)
        draw_text(screen, "b for back", 30, 100, 500)
        self.all_sprites.draw(screen)

    def place_minerals(self):
        for mineral in range(1, 30):
            mineral = Minerals()
            mineral.rect.centerx = random.randrange(30, 770)
            mineral.rect.centery = random.randrange(120, 400)
            self.minerals.add(mineral)
            self.all_sprites.add(mineral)
            

class Minerals(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)       
        self.image = pygame.Surface((10, 10))
        pygame.draw.circle(self.image, yellow, (5, 5), 4) 
        self.image.set_colorkey(black)
        self.rect = self.image.get_rect()   
        self.rect.centerx = width / 2
        self.rect.centery = height / 2
        self.pos = vec(self.rect.centerx, self.rect.centery)
        

class Planet_surface(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((750, 400))
        self.image.fill(brown)
        self.rect = self.image.get_rect()
        self.rect.x = 25
        self.rect.centery = height / 2
        

class Player(pygame.sprite.Sprite):
    def __init__(self, x, y):
        
        pygame.sprite.Sprite.__init__(self)
        self.player_img = pygame.Surface((25,25), pygame.SRCALPHA)
        self.player_img.fill(red)
        self.image = self.player_img
        self.rect = self.image.get_rect()
        self.hit_rect = PLAYER_HIT_RECT
        self.hit_rect.center = self.rect.center
        self.vel = vec(0, 0)
        self.pos = vec(x, y) 
        self.rot = 0
        self.rot_speed = 0

    def update(self, dt):
        self.rot = (self.rot + self.rot_speed * dt) % 360
        self.image = pygame.transform.rotate(self.player_img, self.rot)
        self.rect = self.image.get_rect()
        self.rect.center = self.pos
        self.pos += self.vel * dt
        self.hit_rect.centerx = self.pos.x
        
        self.hit_rect.centery = self.pos.y
        
        self.rect.center = self.hit_rect.center
 
Reply
#4
You have a player class already (with position, rotation, hit boxes, etc), add the info there. I'm not sure the LanderState (or any state) should be creating the player, though.

So instead of...
class Landerstate(States):
    
    def __init__(self, screenrect, player.location, player.money):    ##  <--------- and add info here?
        States.__init__(self)
        self.next = 'starmap'
        self.players = pygame.sprite.Group()
        self.all_sprites = pygame.sprite.Group()
        self.planet_surface = Planet_surface()
        self.all_sprites.add(self.planet_surface)
        self.minerals = pygame.sprite.Group()
        self.player = Player(300, 400)
        self.all_sprites.add(self.player)
        self.players.add(self.player)
        self.place_minerals()
...try this...
class Landerstate(States):
    
    def __init__(self, screenrect, player):    ##  <--------- player already exists
        States.__init__(self)
        self.next = 'starmap'
        self.players = pygame.sprite.Group()
        self.all_sprites = pygame.sprite.Group()
        self.planet_surface = Planet_surface()
        self.all_sprites.add(self.planet_surface)
        self.minerals = pygame.sprite.Group()
        self.player = player
### self.player = Player(300, 400)  # no longer needed, player already exists
        self.all_sprites.add(self.player)
        self.players.add(self.player)
        self.place_minerals()
Reply
#5
I think I've got it working.

The "player" was just there because I was integrating code from another game. I've changed it to lander. The lander is more of a instrument used by the player in that state. In the map state the player controls a cross-hair to pick a location, in another state he is guiding a ship in to a planet or space station... basically all the graphical and control information will be specific to the state. The money, the player location(which will be more like "Zeti Alpha 1" instead of a vector), story line switches, etc, needed to be global, that's why I started there. It's a much better idea as you first suggested to make all the shared information as the player class so it can be updated as needed from scene to scene.

It's a clone of the early game in Star Control 2. Third best game ever, after Dwarf Fortress and Dwarf Fortress. Wink

Thanks again, your help will allow a lot of rapid progress.
Reply
#6
The idea of the state machine is also that you only have to modify or add new states. It is rare that you modify the control class itself. Yes i would agree to also put it in player class instead. Once you have control class setup, you only really modify it when adding a new state in the dictionary. And thats even if you have it defined there at all. You dont want to pass specific information in control to a state as that is passed to every single state in the game.

However to anwser your original question....

Anything you put into the super class of States is shareable to all subclass states. If you want to modify it on the fly then you can make a class variable like such:
import pygame as pg
import sys
  
class States(object):
    share = 'share'
    def __init__(self):
        self.done = False
        self.next = None
        self.quit = False
        self.previous = None
  
class Menu(States):
    def __init__(self):
        States.__init__(self)
        self.next = 'game'
    def cleanup(self):
        print('cleaning up Menu state stuff')
    def startup(self):
        print('starting Menu state stuff')
    def get_event(self, event):
        if event.type == pg.KEYDOWN:
            print('Menu State keydown')
        elif event.type == pg.MOUSEBUTTONDOWN:
            self.done = True
            print(States.share)
            States.share = 'menu share'
            print(States.share)
    def update(self, screen, dt):
        self.draw(screen)
    def draw(self, screen):
        screen.fill((255,0,0))
  
class Game(States):
    def __init__(self):
        States.__init__(self)
        self.next = 'menu'
    def cleanup(self):
        print('cleaning up Game state stuff')
    def startup(self):
        print('starting Game state stuff')
    def get_event(self, event):
        if event.type == pg.KEYDOWN:
            print('Game State keydown')
        elif event.type == pg.MOUSEBUTTONDOWN:
            self.done = True
            print(States.share)
            States.share = 'game share'
            print(States.share)
    def update(self, screen, dt):
        self.draw(screen)
    def draw(self, screen):
        screen.fill((0,0,255))
  
class Control:
    def __init__(self, **settings):
        self.__dict__.update(settings)
        self.done = False
        self.screen = pg.display.set_mode(self.size)
        self.clock = pg.time.Clock()
    def setup_states(self, state_dict, start_state):
        self.state_dict = state_dict
        self.state_name = start_state
        self.state = self.state_dict[self.state_name]
    def flip_state(self):
        self.state.done = False
        previous,self.state_name = self.state_name, self.state.next
        self.state.cleanup()
        self.state = self.state_dict[self.state_name]
        self.state.startup()
        self.state.previous = previous
    def update(self, dt):
        if self.state.quit:
            self.done = True
        elif self.state.done:
            self.flip_state()
        self.state.update(self.screen, dt)
    def event_loop(self):
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.done = True
            self.state.get_event(event)
    def main_game_loop(self):
        while not self.done:
            delta_time = self.clock.tick(self.fps)/1000.0
            self.event_loop()
            self.update(delta_time)
            pg.display.update()
  
  
settings = {
    'size':(600,400),
    'fps' :60
}
  
app = Control(**settings)
state_dict = {
    'menu': Menu(),
    'game': Game()
}
app.setup_states(state_dict, 'menu')
app.main_game_loop()
pg.quit()
sys.exit()
Recommended Tutorials:
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [PyGame] Best way to pass data to the few game states with the same superclass? Milosz 5 3,015 Oct-20-2021, 01:47 PM
Last Post: Windspar

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020