General 2d game design book

  • (2 Pages)
  • +
  • 1
  • 2

21 Replies - 3700 Views - Last Post: 14 August 2019 - 07:18 AM Rate Topic: -----

#16 baavgai   User is online

  • Dreaming Coder
  • member icon


Reputation: 7464
  • View blog
  • Posts: 15,471
  • Joined: 16-October 07

Re: General 2d game design book

Posted 13 August 2019 - 09:21 AM

View Postmrdman, on 13 August 2019 - 06:39 AM, said:

Where you say never do Main()... do you mean I should have done Game() instead to initialise another game? Or I should have a reset method and then do game.reset(); game.run() ?

So, here is what you did:
def thing(depth=0):
    print("thing({}) called".format(depth))
    thing(depth + 1)  # this is the same as your main call

thing()



Stepping back, this is how I like to do a game loop:
def main():
    # start the game engine
    pygame.init()
    # init the display
    screen = pygame.display.set_mode(DISPLAY_SIZE)
    # init the game state
    state = GameState()
    clock = pygame.time.Clock()

    # loop until state says we're done
    while not state.done:
        # deal with events, then might change state
        handle_event(state)
        # make sure an event didn't tell us to stop
        if not state.done:
            # update our display given the current state of the game
            display(screen, state)
            pygame.display.flip()
            # process changes in state
            update(state)
            clock.tick(60)
    # game loop done
    pygame.quit()



The above is applicable to any game, really. You spin until the game is officially done. You deal with input and state change. You update display.

I'm actually inspired by a few folks here taking a stab at ancient games. I'm currently working on pygame version on Sabotage for fun. But it may be too complex an example. Hmm... I'll be back with a pong example.
Was This Post Helpful? 0
  • +
  • -

#17 mrdman   User is offline

  • New D.I.C Head

Reputation: 2
  • View blog
  • Posts: 9
  • Joined: 21-July 19

Re: General 2d game design book

Posted 13 August 2019 - 10:36 AM

View Postbaavgai, on 13 August 2019 - 09:21 AM, said:

View Postmrdman, on 13 August 2019 - 06:39 AM, said:

Where you say never do Main()... do you mean I should have done Game() instead to initialise another game? Or I should have a reset method and then do game.reset(); game.run() ?

So, here is what you did:
def thing(depth=0):
    print("thing({}) called".format(depth))
    thing(depth + 1)  # this is the same as your main call

thing()



Stepping back, this is how I like to do a game loop:
def main():
    # start the game engine
    pygame.init()
    # init the display
    screen = pygame.display.set_mode(DISPLAY_SIZE)
    # init the game state
    state = GameState()
    clock = pygame.time.Clock()

    # loop until state says we're done
    while not state.done:
        # deal with events, then might change state
        handle_event(state)
        # make sure an event didn't tell us to stop
        if not state.done:
            # update our display given the current state of the game
            display(screen, state)
            pygame.display.flip()
            # process changes in state
            update(state)
            clock.tick(60)
    # game loop done
    pygame.quit()



The above is applicable to any game, really. You spin until the game is officially done. You deal with input and state change. You update display.

I'm actually inspired by a few folks here taking a stab at ancient games. I'm currently working on pygame version on Sabotage for fun. But it may be too complex an example. Hmm... I'll be back with a pong example.


Ah ok, I understand.. So the game loop is everything. Once that stops, you are closing the window basically. Any gameover state will be still inside the game loop.

Awesome that you're inspired! I'm loving the speed of making these kinds of games.. A worldwide popular complete game basically in a weekend!
Was This Post Helpful? 0
  • +
  • -

#18 baavgai   User is online

  • Dreaming Coder
  • member icon


Reputation: 7464
  • View blog
  • Posts: 15,471
  • Joined: 16-October 07

Re: General 2d game design book

Posted 13 August 2019 - 11:29 AM

Right, not sure if this is the best design, but I've killed too much of the day on it...

import pygame
import sys
import random


DISPLAY_SIZE = 640, 480
COLOR_BG = (0, 0, 0)
COLOR_FG = (0, 128, 64)


class GameState(object):
    def __init__(self):
        self.done = False
        self.game_over = False
        self.sprites = sprites = pygame.sprite.Group()
        self.sprites.add(Background(self))
        # self.sprites.add(ScoreBoard(self))
        self.sprites.add(Paddle(self))
        self.sprites.add(Ball(self))
        self.sprites.add(GameOver(self))
        self.paddle_move = None
        self.game_begin()

    def draw(self, screen):
        self.sprites.draw(screen)

    def update(self):
        self.sprites.update()

    def game_begin(self):
        self.game_over = False
        self.paddle_move = None
        for x in self.sprites.sprites():
            x.game_begin()

    def game_end(self):
        self.game_over = True
        self.paddle_move = None
        for x in self.sprites.sprites():
            x.game_end()

    def handle_event(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.done = True
                break
            elif self.game_over:
                if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
                    self.game_begin()
            else:
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_UP:
                        self.paddle_move = (0, -Paddle.SPEED)
                    elif event.key == pygame.K_DOWN:
                        self.paddle_move = (0, Paddle.SPEED)
                elif event.type == pygame.KEYUP:
                    if event.key == pygame.K_UP or event.key == pygame.K_DOWN:
                        self.paddle_move = None


class GameSprite(pygame.sprite.Sprite):
    BG, SCORE, PADDLE, BALL, GAME_OVER = range(5)

    def __init__(self, state, sprite_type):
        pygame.sprite.Sprite.__init__(self)
        self.state = state
        self.sprite_type = sprite_type

    def game_begin(self):
        pass

    def game_end(self):
        pass


class Background(GameSprite):
    WALL_RECT = pygame.Rect(DISPLAY_SIZE[0] - 40, 10, 20, DISPLAY_SIZE[1] - 20)

    def __init__(self, state):
        GameSprite.__init__(self, state, GameSprite.BG)
        self.image = pygame.Surface(DISPLAY_SIZE)
        self.rect = self.image.get_rect()
        self.image.fill(COLOR_BG)
        pygame.draw.rect(self.image, COLOR_FG, Background.WALL_RECT)


class Paddle(GameSprite):
    SPEED = 5

    def __init__(self, state):
        GameSprite.__init__(self, state, GameSprite.PADDLE)
        self.image = pygame.Surface((20, 100))
        self.rect = self.image.get_rect()
        self.image.fill(COLOR_FG)

    def game_begin(self):
        self.rect.x = 20
        self.rect.y = DISPLAY_SIZE[1] // 2 - self.rect.size[1] // 2

    def update(self):
        if not self.state.game_over and self.state.paddle_move:
            r = self.rect.move(self.state.paddle_move)
            if r.top > 10 and r.bottom < DISPLAY_SIZE[1] - 10:
                self.rect = r


class GameOver(GameSprite):
    def __init__(self, state):
        GameSprite.__init__(self, state, GameSprite.GAME_OVER)
        self.font = pygame.font.Font("freesansbold.ttf", 36)
        self.game_begin()

    def game_begin(self):
        self.image = pygame.Surface((1, 1))
        self.rect = self.image.get_rect()

    def game_end(self):
        self.image = self.font.render("Press SPACE to play again", True, COLOR_FG)
        self.rect = self.image.get_rect()
        self.rect.center = (DISPLAY_SIZE[0] // 2, DISPLAY_SIZE[1] // 2)


class Ball(GameSprite):
    SPEED = 5

    def __init__(self, state):
        GameSprite.__init__(self, state, GameSprite.BALL)
        self.image = pygame.Surface((20, 20))
        self.rect = self.image.get_rect()
        self.image.fill(COLOR_FG)
        self.paddle = [x for x in state.sprites.sprites() if x.sprite_type == GameSprite.PADDLE][0]
        self.delta = (0, 0)

    def game_begin(self):
        self.rect.x = DISPLAY_SIZE[0] // 2
        self.rect.y = DISPLAY_SIZE[1] // 2
        self.delta = (Ball.SPEED, Ball.SPEED)

    def update(self):
        if not self.state.game_over:
            self.rect.move_ip(self.delta)
            dx, dy = self.delta
            # print(self.rect.colliderect(self.paddle.rect))
            if self.rect.top < 10 or self.rect.bottom > DISPLAY_SIZE[1] - 10:
                dy *= -1
            if self.rect.right > Background.WALL_RECT.left:
                dx *= -1
            elif pygame.sprite.collide_rect(self, self.paddle):
                dx *= -1
            elif self.rect.left < 10:
                self.state.game_end()
            self.delta = (dx, dy)


def main():
    pygame.init()
    screen = pygame.display.set_mode(DISPLAY_SIZE)
    state = GameState()
    clock = pygame.time.Clock()

    while not state.done:
        state.handle_event()
        if not state.done:
            state.draw(screen)
            pygame.display.flip()
            state.update()
            clock.tick(60)
    pygame.quit()

if __name__ == "__main__":
    main()



This folds in an idea I've been playing with: that a game can just be all sprites, as long as every sprite knows about state and implements a base class. So, that's some very evil OOP there.
Was This Post Helpful? 1
  • +
  • -

#19 mrdman   User is offline

  • New D.I.C Head

Reputation: 2
  • View blog
  • Posts: 9
  • Joined: 21-July 19

Re: General 2d game design book

Posted 13 August 2019 - 12:16 PM

Awesome! A lot to take in so will go through it this evening slowly.. sprites is something I've not had a chance to look into yet so will search for some info about them too this evening..
Was This Post Helpful? 0
  • +
  • -

#20 baavgai   User is online

  • Dreaming Coder
  • member icon


Reputation: 7464
  • View blog
  • Posts: 15,471
  • Joined: 16-October 07

Re: General 2d game design book

Posted 14 August 2019 - 01:35 AM

The sprite, as implemented in pygame, is essentially a convenience class. The sprite itself implements image and rect. These funtamentally the two things you need to place something on the screen. It may also implement an update method, which is what you'd need for anything to, say, move around. This is sort of what you'd end up doing yourself, so there's no reason not to comply with the structure and get the extra tools to use.

I will note that rather than the explicit image, I would prefer a draw method overide. You can see me struggling with that one the GameOver sprite.
Was This Post Helpful? 0
  • +
  • -

#21 baavgai   User is online

  • Dreaming Coder
  • member icon


Reputation: 7464
  • View blog
  • Posts: 15,471
  • Joined: 16-October 07

Re: General 2d game design book

Posted 14 August 2019 - 05:21 AM

After a night's sleep, more caffeine, and a dull drive into work, I've redesigned that last pong.

I didn't like how tightly coupled the whole mess was. I was also trying to make all the sprites fit in one bucket and do their own state checks, which is one way to go...

This is more of an MVC design. The state being the model and dead stupid. The other sprites are also stupid, existing only to render and not responding to an update call. The controller talks to the state, makes changes, and readies to the view. More functions here.

I also did kind of a cute hack with this one, which seems to simplify everything else. The controller returns the current state. This allows it to entirely reinitialize the state. Like, you know, a game restart.

import pygame
import sys
import random


DISPLAY_SIZE = 640, 480
COLOR_BG = (0, 0, 0)
COLOR_FG = (0, 128, 64)


class GameState(object):
    def __init__(self):
        self.done = False
        self.game_over = False
        self.paddle = Paddle()
        self.ball = Ball()
        self.sprites = sprites = pygame.sprite.Group()
        self.sprites.add(Background())
        self.sprites.add(self.paddle)
        self.sprites.add(self.ball)


class Background(pygame.sprite.Sprite):
    WALL_RECT = pygame.Rect(DISPLAY_SIZE[0] - 40, 10, 20, DISPLAY_SIZE[1] - 20)

    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface(DISPLAY_SIZE)
        self.rect = self.image.get_rect()
        self.image.fill(COLOR_BG)
        pygame.draw.rect(self.image, COLOR_FG, Background.WALL_RECT)


class Paddle(pygame.sprite.Sprite):
    SPEED = 5

    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((20, 100))
        self.rect = self.image.get_rect()
        self.image.fill(COLOR_FG)
        self.bounds = (10, DISPLAY_SIZE[1] - 10)
        self.delta = None
        self.rect.x = 20
        self.rect.y = DISPLAY_SIZE[1] // 2 - self.rect.size[1] // 2


class GameOver(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        font = pygame.font.Font("freesansbold.ttf", 36)
        self.image = font.render("Press SPACE to play again", True, COLOR_FG)
        self.rect = self.image.get_rect()
        self.rect.center = (DISPLAY_SIZE[0] // 2, DISPLAY_SIZE[1] // 2)


class Ball(pygame.sprite.Sprite):
    SPEED = 5

    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((20, 20))
        self.rect = self.image.get_rect()
        self.image.fill(COLOR_FG)
        self.rect.x = DISPLAY_SIZE[0] // 2
        self.rect.y = DISPLAY_SIZE[1] // 2
        self.delta = (Ball.SPEED, Ball.SPEED)


def game_stop(state):
    state.game_over = True
    state.paddle.delta = None
    state.ball.delta = None
    state.sprites.add(GameOver())


def move_paddle(state):
    sprite = state.paddle
    if sprite.delta:
        r = sprite.rect.move(sprite.delta)
        if r.top > sprite.bounds[0] and r.bottom < sprite.bounds[1]:
            sprite.rect = r


def move_ball(state):
    ball = state.ball
    if ball.delta:
        ball.rect.move_ip(ball.delta)
        dx, dy = ball.delta
        # print(self.rect.colliderect(self.paddle.rect))
        if ball.rect.top < 10 or ball.rect.bottom > DISPLAY_SIZE[1] - 10:
            dy *= -1
        if ball.rect.right > Background.WALL_RECT.left:
            dx *= -1
        elif pygame.sprite.collide_rect(ball, state.paddle):
            dx *= -1
        elif ball.rect.left < 10:
            game_stop(state)
            return
        ball.delta = (dx, dy)


def controller(state):
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            state.done = True
            return state
        elif state.game_over:
            if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
                return GameState()
        else:
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_UP:
                    state.paddle.delta = (0, -Paddle.SPEED)
                elif event.key == pygame.K_DOWN:
                    state.paddle.delta = (0, Paddle.SPEED)
            elif event.type == pygame.KEYUP:
                if event.key == pygame.K_UP or event.key == pygame.K_DOWN:
                    state.paddle.delta = None
    if not state.game_over:
        move_paddle(state)
        move_ball(state)
    return state


def main():
    pygame.init()
    screen = pygame.display.set_mode(DISPLAY_SIZE)
    state = GameState()
    clock = pygame.time.Clock()

    while not state.done:
        if not state.done:
            state.sprites.draw(screen)
            pygame.display.flip()
            state = controller(state)
            if not state.done:
                clock.tick(60)
    pygame.quit()

if __name__ == "__main__":
    main()


Was This Post Helpful? 1
  • +
  • -

#22 NeoTifa   User is online

  • NeoTifa Codebreaker, the Scourge of Devtester
  • member icon





Reputation: 4529
  • View blog
  • Posts: 19,142
  • Joined: 24-September 08

Re: General 2d game design book

Posted 14 August 2019 - 07:18 AM

About the classes, I didn't see an explanation for that yet. I honestly don't remember the syntax for what looks like python, so how to I cannot say. I can say the purpose and usefulness of them though. So if you think of things in terms of, well, things, everything is its own "object". A class is a classification of objects. Say you have a cup. It's classified as a cup (or, if you wanna get fancy, a coffee cup, which is inherently a special type of cup, and extends from the classification of cup). There are millions of cups, you know what a cup is and what it can or cannot do. This is a class, the concept of "cup". Your particular cup in your hand is an instance of the cup concept. If you want another, just get another instance. In this case, your game itself might be a class, the ball would be a class, there would be 2 instances of a paddle class maybe (for pong), etc. Hope it helps.
Was This Post Helpful? 0
  • +
  • -

  • (2 Pages)
  • +
  • 1
  • 2