Skip to content

Commit

Permalink
Refactor render_opt and squash bugs with Button
Browse files Browse the repository at this point in the history
  • Loading branch information
George-lewis committed Nov 16, 2019
1 parent 3cd506e commit 5689100
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 21 deletions.
39 changes: 39 additions & 0 deletions Progress Report - George.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
## Progress Report - George

#### Main.py

The `main.py` is a file I created which is meant to bootstrap the application. This file initiates *PyGame*, imports `App`, and starts the game. Run this file to run the game.

Originally the `pygame` was initialized in the `App` class, see the **App** header for more.

#### The App Class

PyGame itself provides very little framework. All you really need to do is call `pygame.init()` and start drawing to the window. For this reason I created the `App` class which provides a better framework and separation of logic.

The class follows the singleton pattern (i.e. there is one instance of the class that can be accessed statically) and the instance is accessed using `App.instance`.

The class encapsulates all the objects and logic required to run the game. When `App.start()` is called, PyGame is instructed to display a window and `App` enters the game-loop by running `App.loop()`. The game loop runs while `App._running` is set to `True` and it has several discrete steps:

1. Handle PyGame events
1. The `App.handle_events()` method loops through the current PyGame events and dispatches appropriately.
1. If the event is `pygame.QUIT` then the `_running` flag is set to false and the game loop exits.
2. If the event is a keyboard event, it is passed to `App.keyboard(event)`, and if it's a `MOUSEBUTTONDOWN` event, `App.click(event)`
2. Run game logic
1. The `App.logic()` method is called, which is where all the game logic is meant to be encapsulated. If this were a live-action game then this is where the code to move physics objects would be applied.
3. Render the game: After all the events have been handled and the game logic has been run, the game needs to be drawn to the screen!
1. The `App.render()` method is invoked, in this method:
1. The screen is cleared using `App.fill_color`
2. Stuff is rendered to the screen, which "stuff" will depend on the `App.render_option` flag. This is because the game has multiple states like the title screen, maybe a pause menu, and the game itself.
3. We call `pygame.display.flip()` which applies all the rendering operations we have done to the screen

Once the loop has exited, `App.cleanup()` is called. This is where all necessary cleaning up operations like quitting PyGame, closing file handles, and maybe writing log data can be handled.

#### The Button Class

This class is my naive implementation of the classic button UI element. It's initialized with an xy position, width, height, style object, text string, and an on click (callable) handler.

##### How to use it

1. Instantiate an instance of `Button`, give it an appropriate position, width, height, etc and give it a proper `on_click` handler. The `on_click` handler can be a function or a lambda.
2. Handle clicking events. It's a button! We need to have it respond to when it's clicked. The way to do this is to call `Button.handle_click(MOUSEVENT)`. This method takes a mouse event and determines if the click happened within its own bounds and triggers the `on_click` handler if necessary. I recommend you place this in `App.click()` and pass the mouse event.
3. Ensure that the button gets rendered. Do this by inserting a `Button.render()` call inside `App.render()`. Please ensure that the button is only being rendered if you are checking mouse events and vice versa, you can do both of these optionally.
44 changes: 27 additions & 17 deletions src/gui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class App:

button_font = pygame.font.SysFont("Calibri", 30)

class render_option(Enum):
class mode(Enum):
title_screen = 0
game = 1
settings = 2
Expand All @@ -30,16 +30,23 @@ def __init__(self):

self.size = self.width, self.height = (1000, 1000)

self.render_opt = App.render_option.title_screen
self.mode = App.mode.title_screen

self.button = Button(100, 100, 100, 100, Style(bg = (255,255,255), hbg = (0,0,0), tcolor = (255,0,0)),
"abc", lambda: print(crayons.cyan('clicked!')))
self.button = Button(100, 100, 100, 100,
"abc", lambda: print(crayons.cyan('clicked!')), Style(bg = (255,255,255), hbg = (0,0,0), tcolor = (255,0,0)))

self.exit = Button(0, 0, 200, 70, Style(bg = (233, 30, 99), hbg = (255, 96, 144), tcolor = (0,0,0)),
"Exit", lambda: App.instance.stop())
self.exit = Button(0, 0, 200, 70,
"Exit", lambda: App.instance.stop(), Style(bg = (233, 30, 99), hbg = (255, 96, 144), tcolor = (0,0,0)))

self.play_button = Button(300, 450, 300, 200, "Play", lambda: App.instance.set_mode(App.mode.game))



print(crayons.green('Instantiated App'))

def set_mode(self, mode):
self.mode = mode

def stop(self):
self._running = False

Expand All @@ -65,10 +72,12 @@ def click(self, event):

pos = pygame.mouse.get_pos()

if self.render_opt == App.render_option.game:
self.button.check_mouse(pos)
if self.mode == App.mode.game:
self.button.handle_click(pos)
elif self.mode == App.mode.title_screen:
self.play_button.handle_click(pos)

self.exit.check_mouse(pos)
self.exit.handle_click(pos)

def handle_events(self):
"""Loops through pygame events and handle them"""
Expand Down Expand Up @@ -98,19 +107,20 @@ def render(self):
self.screen.fill(self.fill_color) # Clear screen

# Begin drawing code
if self.render_opt == App.render_option.title_screen:
# Render Title Screen
if self.mode == App.mode.title_screen:
# Render Title Screenpasspasspass

self.screen.blit(self.background, (0, 0))
self.screen.blit(self.title, (235, 150))
self.screen.blit(self.play_button, (300, 450))
self.screen.blit(self.info_button, (300, 600))
elif self.render_opt == App.render_option.game:
self.screen.blit(App.background, (0, 0))
self.screen.blit(App.title, (235, 150))
self.screen.blit(App.play_button, (300, 450))
self.screen.blit(App.info_button, (300, 600))
self.play_button.render()
elif self.mode == App.mode.game:
# Render game

self.button.render()

elif self.render_opt == App.render_option.settings:
elif self.mode == App.mode.settings:
pass

self.exit.render()
Expand Down
15 changes: 11 additions & 4 deletions src/gui/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,40 @@

class Button:

def __init__(self, x, y, width, height, style, text, on_click):
def __init__(self, x, y, width, height, text, on_click, style = Style((255,255,255), (255,255,255), (0,0,0))):
self.x = x
self.y = y
self.width = width
self.height = height
self.style = style
self.text = gui.app.App.button_font.render(text, True, (0, 0, 0)) if text else None
self.text = gui.app.App.button_font.render(text, True, style.tcolor) if text else None
self.text_size = gui.app.App.button_font.size(text)
self.text_pos = ((self.x+self.width-self.text_size[0])//2, (self.y+self.height-self.text_size[1])//2)
self.text_pos = ((self.x)+(self.width-self.text_size[0])//2, (self.y)+(self.height-self.text_size[1])//2)
self.on_click = on_click
self.disabled = False

def check_bounds(self, x, y):
return self.x < x < self.x + self.width and self.y < y < self.y + self.height

def handle_click(self, mouse):

if self.disabled:
return

x = mouse[0]
y = mouse[1]

if self.check_bounds(x, y):
self.on_click()

def render(self):

if self.disabled:
return

x, y = pygame.mouse.get_pos()

pygame.draw.rect(gui.app.App.instance.screen, self.style.hbg if self.check_bounds(x,y) else self.style.bg, (self.x, self.y, self.width, self.height))
pygame.draw.rect(gui.app.App.instance.screen, self.style.hbg if self.style.hbg and self.check_bounds(x,y) else self.style.bg, (self.x, self.y, self.width, self.height))

if self.text:
gui.app.App.instance.screen.blit(self.text, self.text_pos)
1 change: 1 addition & 0 deletions src/launch.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/usr/bin/env python3 main.py

0 comments on commit 5689100

Please sign in to comment.