Posts: 419
Threads: 34
Joined: May 2019
Dec-09-2019, 12:41 AM
(This post was last modified: Dec-09-2019, 12:41 AM by michael1789.)
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()
Posts: 3,458
Threads: 101
Joined: Sep 2016
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.
Posts: 419
Threads: 34
Joined: May 2019
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
Posts: 3,458
Threads: 101
Joined: Sep 2016
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()
Posts: 419
Threads: 34
Joined: May 2019
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.
Thanks again, your help will allow a lot of rapid progress.
Posts: 5,151
Threads: 396
Joined: Sep 2016
Dec-10-2019, 10:09 PM
(This post was last modified: Dec-10-2019, 10:09 PM by metulburr.)
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:
|