best approach to handle game states

best approach to handle game states

Postby metulburr » Wed Dec 18, 2013 10:23 pm

The code i am referring to is:
https://github.com/metulburr/pong

My next task is to create a menu state before the game starts. I was trying to make it simple as possible. I initially started off with if conditions. But after writing the first few if's i noticed this approach is bad.
Code: Select all
    def render(self):
        self.screen.fill((0,0,0))
        if self.state == 'game':
            self.scoreboard.render(self.screen)
            self.ball.render(self.screen)
            self.paddle_left.render(self.screen)
            self.paddle_right.render(self.screen)
            if self.pause:
                self.pause_notice.render(self.screen)
        elif self.state == 'menu':
            ...
        pg.display.update()

The plants versus zombies game the forum was doing was the only really example of states in game that i could find. However, when we were doing it, i pretty much felt like i was way over my experience level. A simple game state repo example would be interesting to add to your examples repo....mekire. Anyways, being the fact that i only plan on having a game state and a menu state for this pong game...I dont plan on having more than these states, i was looking at a simple approach to handling game states.
New Users, Read This
OS Ubuntu 14.04, Arch Linux, Gentoo, Windows 7/8
https://github.com/metulburr
steam
User avatar
metulburr
 
Posts: 1471
Joined: Thu Feb 07, 2013 4:47 pm
Location: Elmira, NY

Re: best approach to handle game states

Postby Yoriz » Wed Dec 18, 2013 11:16 pm

How about refactoring the game states into their own methods
Code: Select all
class Control:

    GAME_STATE = 'game'
    MENU_STATE = 'menu'
    # other code

    def render(self):
        self.screen.fill((0, 0, 0))
        if self.state == self.GAME_STATE:
            self.game_state()
        elif self.state == self.MENU_STATE:
            self.menu_state()
        pg.display.update()

    def game_state(self):
        self.scoreboard.render(self.screen)
        self.ball.render(self.screen)
        self.paddle_left.render(self.screen)
        self.paddle_right.render(self.screen)
        if self.pause:
            self.pause_notice.render(self.screen)

    def menu_state(self):
        # menustate code
New Users, Read This
Join the #python-forum IRC channel on irc.freenode.net!
Spam topic disapproval technician
Windows7, Python 2.7.4., WxPython 2.9.5.0., some Python 3.3
User avatar
Yoriz
 
Posts: 842
Joined: Fri Feb 08, 2013 1:35 am
Location: UK

Re: best approach to handle game states

Postby metulburr » Wed Dec 18, 2013 11:20 pm

wow, lol, sometimes i feel like i am just brain dead

yeah i think that is a good middle of the rope area
New Users, Read This
OS Ubuntu 14.04, Arch Linux, Gentoo, Windows 7/8
https://github.com/metulburr
steam
User avatar
metulburr
 
Posts: 1471
Joined: Thu Feb 07, 2013 4:47 pm
Location: Elmira, NY

Re: best approach to handle game states

Postby Yoriz » Wed Dec 18, 2013 11:22 pm

Also if you then dont want to use if's use a dictonary
Code: Select all
class Control:

    GAME_STATE = 'game'
    MENU_STATE = 'menu'
    # other code

    def __init__(self, fullscreen):
        self.states_dict = {self.GAME_STATE: self.game_state,
                            self.MENU_STATE: self.menu_state}
        # other code

    def render(self):
        self.screen.fill((0, 0, 0))
        self.states_dict[self.state]()
        pg.display.update()

    def game_state(self):
        self.scoreboard.render(self.screen)
        self.ball.render(self.screen)
        self.paddle_left.render(self.screen)
        self.paddle_right.render(self.screen)
        if self.pause:
            self.pause_notice.render(self.screen)

    def menu_state(self):
        # menustate code


Also noticed you could change
Code: Select all
if not self.pause:
    self.pause = True
else:
    self.pause = False
to
Code: Select all
self.pause = not self.pause
New Users, Read This
Join the #python-forum IRC channel on irc.freenode.net!
Spam topic disapproval technician
Windows7, Python 2.7.4., WxPython 2.9.5.0., some Python 3.3
User avatar
Yoriz
 
Posts: 842
Joined: Fri Feb 08, 2013 1:35 am
Location: UK

Re: best approach to handle game states

Postby metulburr » Thu Dec 19, 2013 12:03 am

as i started making game states, i ended up having to split the code up anyways, i guess. https://github.com/metulburr/pong/tree/master/data. Pretty much most of the meat of the program ended up in the state. However i am still not happy with calling stuff like calling gamestates paddle movements
Code: Select all
        if self.keys[pg.K_w]:
            self.gamestate.paddle_left.move(0, -1)
        if self.keys[pg.K_s]:
            self.gamestate.paddle_left.move(0, 1)
        if self.keys[pg.K_UP]:
            self.gamestate.paddle_right.move(0, -1)
        if self.keys[pg.K_DOWN]:
            self.gamestate.paddle_right.move(0, 1)

in Control

for some reason i find this more confusing:
Code: Select all
self.pause = not self.pause
New Users, Read This
OS Ubuntu 14.04, Arch Linux, Gentoo, Windows 7/8
https://github.com/metulburr
steam
User avatar
metulburr
 
Posts: 1471
Joined: Thu Feb 07, 2013 4:47 pm
Location: Elmira, NY

Re: best approach to handle game states

Postby Yoriz » Thu Dec 19, 2013 12:09 am

metulburr wrote:for some reason i find this more confusing:
Code: Select all
self.pause = not self.pause

It simply inverts its state
If its value is currently True, not True is False.
If its value is currently False, not False is True.
New Users, Read This
Join the #python-forum IRC channel on irc.freenode.net!
Spam topic disapproval technician
Windows7, Python 2.7.4., WxPython 2.9.5.0., some Python 3.3
User avatar
Yoriz
 
Posts: 842
Joined: Fri Feb 08, 2013 1:35 am
Location: UK

Re: best approach to handle game states

Postby metulburr » Thu Dec 19, 2013 12:13 am

It simply inverts its state

interesting. I did not know that. I think of not being more used in if conditions than assignments
New Users, Read This
OS Ubuntu 14.04, Arch Linux, Gentoo, Windows 7/8
https://github.com/metulburr
steam
User avatar
metulburr
 
Posts: 1471
Joined: Thu Feb 07, 2013 4:47 pm
Location: Elmira, NY

Re: best approach to handle game states

Postby Mekire » Thu Dec 19, 2013 12:24 am

I strongly recomend you make each scene/state its own class (generally inheriting from a prototype State class). All the main control should do is pass events to the active state and handle state switching. The states themselves need methods to catch events and update.

Here is a basic example, there is no base state class here so there is a lot of repeated code. Left as excercise:
Code: Select all
import os
import sys
import pygame as pg


CAPTION = "Very Basic Game Scenes"
SCREEN_SIZE = (700,500)


class Control(object):
    def __init__(self):
        self.screen = pg.display.get_surface()
        self.screen_rect = self.screen.get_rect()
        self.clock = pg.time.Clock()
        self.fps = 60.0
        self.done = False
        self.keys = pg.key.get_pressed()
        self.state_dict = {"START" : StartState(self.screen_rect),
                           "GAME"  : GameState(self.screen_rect)}
        self.state_name = "START"
        self.state = self.state_dict[self.state_name]

    def event_loop(self):
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.done = True
            elif event.type in (pg.KEYDOWN,pg.KEYUP):
                self.keys = pg.key.get_pressed()
            self.state.get_event(event)

    def change_state(self):
        if self.state.done:
            self.state_name = self.state.next
            self.state.done = False
            self.state = self.state_dict[self.state_name]

    def main_loop(self):
        while not self.done:
            now = pg.time.get_ticks()
            self.event_loop()
            self.change_state()
            self.state.update(now,self.keys)
            self.state.draw(self.screen)
            pg.display.update()
            self.clock.tick(self.fps)


class StartState(object):
    def __init__(self,screen_rect):
        self.done = False
        self.next = "GAME"
        self.text,self.text_rect = self.make_text("The Start State",
                                                  pg.Color("white"),
                                                  (screen_rect.centerx,100))
        self.ne_key,self.ne_key_rect = self.make_text("Press NE Key",(90,200,0),
                                                      (screen_rect.centerx,400))
        self.blink = False
        self.blink_fps = 4.0
        self.timer = 0.0

    def make_text(self,message,color,center):
        text = FONT.render(message,True,color)
        rect = text.get_rect(center=center)
        return text,rect

    def get_event(self,event):
        if event.type == pg.KEYDOWN:
            self.done = True

    def update(self,now,keys):
        if now-self.timer > 1000.0/self.blink_fps:
            self.blink = not self.blink
            self.timer = now
       
    def draw(self,screen):
        screen.fill((20,20,35))
        screen.blit(self.text,self.text_rect)
        if self.blink:
            screen.blit(self.ne_key,self.ne_key_rect)


class GameState(object):
    def __init__(self,screen_rect):
        self.done = False
        self.next = "START"
        self.text,self.text_rect = self.make_text("The Game State",
                                                  pg.Color("black"),
                                                  (screen_rect.centerx,100))
        self.ne_key,self.ne_key_rect = self.make_text("Press NE Key",(200,50,0),
                                                      (screen_rect.centerx,400))
        self.blink = False
        self.blink_fps = 4.0
        self.timer = 0.0

    def make_text(self,message,color,center):
        text = FONT.render(message,True,color)
        rect = text.get_rect(center=center)
        return text,rect

    def get_event(self,event):
        if event.type == pg.KEYDOWN:
            self.done = True

    def update(self,now,keys):
        if now-self.timer > 1000.0/self.blink_fps:
            self.blink = not self.blink
            self.timer = now
       
    def draw(self,screen):
        screen.fill((255,255,155))
        screen.blit(self.text,self.text_rect)
        if self.blink:
            screen.blit(self.ne_key,self.ne_key_rect)
           

def main():
    global FONT
    pg.init()
    pg.display.set_caption(CAPTION)
    pg.display.set_mode(SCREEN_SIZE)
    FONT = pg.font.Font(None,50)
    Control().main_loop()
    pg.quit()
    sys.exit()


if __name__ == "__main__":
    main()

Note in the above the states don't communicate any data. Eventually you will need startup and cleanup functions to handle this, but then you start getting into the complexity of the PvZ game (which I think you are fully capable of).

-Mek
User avatar
Mekire
 
Posts: 988
Joined: Thu Feb 07, 2013 11:33 pm
Location: Amakusa, Japan

Re: best approach to handle game states

Postby metulburr » Thu Dec 19, 2013 12:46 am

ah ok, thats how you did it, each state handles their own list of events.

Its getting at the end of a long day, so i'll end up implementing it into the game tomorrow. But thanks. That stripped down code makes it easy to understand.

(which I think you are fully capable of).

really? I felt like you were just holding my hand as my eyebrows rose in confusion the entire time. I think my biggest problem was i never wrote a lot of the game aspects before in PvZ, and then once you wrote it, we kept just reusing it, so i never got into in depth.
New Users, Read This
OS Ubuntu 14.04, Arch Linux, Gentoo, Windows 7/8
https://github.com/metulburr
steam
User avatar
metulburr
 
Posts: 1471
Joined: Thu Feb 07, 2013 4:47 pm
Location: Elmira, NY

Re: best approach to handle game states

Postby Mekire » Thu Dec 19, 2013 12:55 am

Capable of writing and understanding it. Definately.
Last time however I just handed it to you so you never went through the process, as you said.

This time slowly build it up until you have your own framework you are comfortable with. Also you should be aware that some people prefer to implement their state changes as a stack. You push states onto the stack when you change and then if you want, you can pop back multiple layers. I think building a state machine like this (push down automata technically) is pretty useful for nested submenus, but I have never personally had the need to pop back further than the previous scene (which I keep track of) in my scene manager.

-Mek
User avatar
Mekire
 
Posts: 988
Joined: Thu Feb 07, 2013 11:33 pm
Location: Amakusa, Japan

Re: best approach to handle game states

Postby metulburr » Thu Dec 19, 2013 1:43 pm

ok i implemented it into the pong game. I think i have everything down for the exception of event handing. I thought i had it well, guess not. The paddles dont move when being held down. I thought this appraoch is the best way to have constant flow of movement with key held down:
Code: Select all
    def get_event(self,event):
        if event.type == pg.KEYDOWN:
            if event.key == pg.K_ESCAPE:
                self.done = True
                self.next = 'MENU'
                self.reset()
            elif event.key == pg.K_p:
                if not self.pause:
                    self.pause = True
                else:
                    self.pause = False
        if self.keys[pg.K_w]:
            self.paddle_left.move(0, -1)
        if self.keys[pg.K_s]:
            self.paddle_left.move(0, 1)
        if self.keys[pg.K_UP]:
            self.paddle_right.move(0, -1)
        if self.keys[pg.K_DOWN]:
            self.paddle_right.move(0, 1)

Sometimes the paddle will constantly move and then it will stop. I checked every other file to make sure i didnt have conflicting event movements, but game_state.get_event() is the only location where the paddle moves. I used if if if instead of if elif elif because i am use to handling diagnal movements, and that seems to handle diagnal movment the easiest. I am use to just always leaving movement fi clauses if if if's because of that. I switched them to elif's to check if that might be the issue, but it is not. I am not sure at what point the sticky pad movement, is at?

code:
https://github.com/metulburr/pong

EDIT:
ok i found that if i move the mouse while moving the paddles, the paddles move as they should. I am not sure what this means as the only time i check for anything mouse related is in menu_state.

EDIT:
OK i fixed that problem. A guy on #pygame told me what i did. The fact that my keys were only being executed upon events
New Users, Read This
OS Ubuntu 14.04, Arch Linux, Gentoo, Windows 7/8
https://github.com/metulburr
steam
User avatar
metulburr
 
Posts: 1471
Joined: Thu Feb 07, 2013 4:47 pm
Location: Elmira, NY

Re: best approach to handle game states

Postby Mekire » Thu Dec 19, 2013 10:39 pm

Yeah, this part shouldn't be in your event loop at all:
Code: Select all
if self.keys[pg.K_w]:
    self.paddle_left.move(0, -1)
if self.keys[pg.K_s]:
    self.paddle_left.move(0, 1)
if self.keys[pg.K_UP]:
    self.paddle_right.move(0, -1)
if self.keys[pg.K_DOWN]:
    self.paddle_right.move(0, 1)

It should be in the update method of the scene, or rather in a movement method called from your update method.

-Mek

Edit:
Basically, if you aren't actually catching an event, it doesn't go in the event loop. Checking held keys seems like checking an event, but it actually isn't. Also you might have noticed I only call pygame.key.get_pressed from the control event_loop and pass it to scenes using the update method. No need to recall it in each specific scene (not that it will hurt anything if you do).

Oh, and I agree with Yoriz:
Code: Select all
self.pause = not self.pause
is way better :)
User avatar
Mekire
 
Posts: 988
Joined: Thu Feb 07, 2013 11:33 pm
Location: Amakusa, Japan

Re: best approach to handle game states

Postby metulburr » Thu Dec 19, 2013 11:02 pm

Yeah that had me stumped all day.

I think i understand the basic structure of states now that i meshed your initial example and my code. Looks a lot better than my other pygame code, of course.

self.pause = not self.pause makes sense. I jsut never used that method before to inverse. So it seems foreign at the moment.
New Users, Read This
OS Ubuntu 14.04, Arch Linux, Gentoo, Windows 7/8
https://github.com/metulburr
steam
User avatar
metulburr
 
Posts: 1471
Joined: Thu Feb 07, 2013 4:47 pm
Location: Elmira, NY


Return to Game Development

Who is online

Users browsing this forum: No registered users and 2 guests