View on GitHub

Making a Game of IT

Michigan State University - Summer 2019

Making a Game of IT - Day 4

Functions

We’ve been using Python’s built-in functions like str(), input(), len(), and print(). Python’s def keyword — followed by the intended name of your function, its arguments inside parentheses, and a colon —lets us create our own. The indented code block under the def line constitutes the body of the function… it’s executed every time the function is called. After we def the function we call it by its name followed by a set of parentheses with the arguments it needs inside. In this first example, the function hello_world() takes no arguments.

def hello_world():
  print("Hello world!")

hello_world()

In this next example, we see how functions can access variables from outside their code block.

name = "Rear Admiral Grace Brewster Murray Hopper"

def hello_person():
  print("Hello", name)

hello_person()

name = "Dr. Alan Mathison Turing"

hello_person()

However, functions can’t change variables outside their code block

name = "Rear Admiral Grace Brewster Murray Hopper"

def name_change():
  name = "inside name"

name_change()

print(name)

Variables inside functions override variables from outside functions.

name = "Scarlin Hernandez"

def hello_person():
  name = "inside name"
  print("Hello", name) # This should print 'inside name'

hello_person()

print("Outside", name) # This should print 'Scarlin Hernandez'

This function takes one argument.

def hello_person(name):
  print("Hello", name)

hello_person("Dr. Mark E. Dean")

hello_person("Dr. Alan Mathison Turing")

This function takes two arguments.

def repeat_hello(n, name):
  for i in range(n):
    print("Hello, name, i)

repeat_hello(1, "Katherine Johnson")

repeat_hello(7, "Roy L. Clay, Sr.")

Functions can use the return keyword to halt and give a value back to the caller.

def oh_hai(name):
  return "oh, hai " + name
  print("do more stuff")    # This isn't executed because it comes after the return

result = oh_hai("Dr. Fei-Fei Li")
print(result)

Why are functions useful?

  1. re-use of code inside the function (you have to type less code)
  2. compartmentalize code so that high-level structure of your program is easier to follow
  3. experiment with/test individual components of your program

Jumping around (platforming)

The example game shown below is a simple platformer where players control sparty with the goal of reaching the giant coin in the top right corner of the screen.

To run this game you will need the coin.png and sparty.png files!

"""
This activity is intended to introduce you to Python and a
library for creating video games, PyGame. Thoughout the activity,
you'll see comments (which have a # in them) giving you instructions.

Important note: Python is very sensitive to spaces and tabs, please
ensure that as you change lines as instructed, that you do not change the
indentation of the code.

By: Josh Nahum
"""

# This section of code is needed to get everything set up.
import pygame
import sys

pygame.init()
clock = pygame.time.Clock()

# This section of code determines the size of the game window
# and sets the title of the window (orginally 800, 600, and Sparty Platformer).
# Try adjusting the game window size and the title.
WINDOW_SIZE = [1200, 600]
screen = pygame.display.set_mode(WINDOW_SIZE)
pygame.display.set_caption("Sparty Platformer")

# These are some names for common colors.
# A color is determined by 3 numbers (how much red, green, and blue a color has).
# Each of these three numbers use be between 0 and 255.
# Try making a custom color (we'll use it later to be the background of our game.)
BLACK = [0, 0, 0]
WHITE = [255, 255, 255]
GREEN = [0, 255, 0]
RED = [255, 0, 0]

# This section of code loads a PNG image of Sparty.
# The name of the file is Sparty.png
# If you download another small image off the web,
# place it in the same folder as this activity.py file,
# and replace the filename with the one you downloaded,
# then you will have a different sprite to play width.
# I've included a file called "helmet.png" as a alternative.
sparty_img = pygame.image.load("Sparty.png")
sparty_rectangle = sparty_img.get_rect()

# This section of code is similar to the one above,
# except it loads an image of the goal Sparty is trying
# to get to, a gold coin.
goal_img = pygame.image.load("coin.png")
goal_rectangle = goal_img.get_rect()


# Try changing Sparty's start location by changing the zeroes
# in the next to lines of code to other positive number.
# Notice that larger y values are lower on the screen.
sparty_location_x = 0
sparty_location_y = 0


# These lines determine the height and color of the platforms,
# feel free to adjust these.
platform_height = 10
platform_color = GREEN

# This code makes a single platform that runs along the bottom of the screen,
# please don't change this code.
screen_rectangle = screen.get_rect()
bottom_platform = pygame.Rect(
    screen_rectangle.left,
    screen_rectangle.bottom - platform_height,
    screen_rectangle.right - screen_rectangle.left,
    platform_height)

# This is a list of platforms, initially just 3.
# Each platform is made with 4 numbers (all denoted in pixels)
# 1. The x coordinate of the left-most edge of the rectangle.
# 2. The y coordinate of the top-most edge of the rectangle.
# 3. The width of the rectangle.
# 4. The height of the rectangle (I like my platforms to be the same height of platform_height).
#    But you can use different heights if you prefer.
# Add enough platforms to enable Sparty to reach the goal.
platforms = [
    bottom_platform,
    pygame.Rect(100, 400, 300, platform_height),
    pygame.Rect(300, 500, 150, platform_height),
    pygame.Rect(400, 300, 150, platform_height),
    pygame.Rect(500, 200, 150, platform_height),
    pygame.Rect(600, 100, 500, platform_height),
]


# These next 2 lines determine the intial x and y velocities of the
# player character. The values are the number of pixels to shift the character
# for each frame of animation drawn.
sparty_velocity_x = 0 # Larger means moving faster rightwards
sparty_velocity_y = 0 # Larger means moving faster downwards

# Sparty is subject to gravity (orginally set to 0.3 for Earth),
# try making this value smaller to simulate
# jumping on the moon (0.05). Warning, setting this value too high
# will break the physics of the game.
GRAVITATIONAL_CONSTANT = 0.1

# This variable is used later to determine if Sparty is standing
# on a platform (to determine if Sparty is allowed to jump.)
is_standing = False

# This code is the game loop that listens to key presses,
# calculates the physics,
# draws the background, platforms and Sparty.
while True:
    # The following section of code is responsible for the event loop,
    # which listens for keyboard events and does things (like
    # changing the player's velocity in response.)
    for event in pygame.event.get():
        # If the user clicks the 'X' to close the window, this closes the game and program
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN:

            # Should Sparty be allowed to jump when not standing?
            # If so uncomment the next line of code and comment out the line after it
            # This will remove the requirement that Sparty be standing to initiate a jump

            if event.key == pygame.K_UP and is_standing:
            # if event.key == pygame.K_UP:


                # Notice the next 3 numbers (orginally -5, 4, and -4)
                # This values are what the x and y velocities are changed to on button presses
                # Try changing them to make Sparty jump or run faster.
                sparty_velocity_y = -5
            if event.key == pygame.K_RIGHT:
                sparty_velocity_x = 4
            if event.key == pygame.K_LEFT:
                sparty_velocity_x = -4


        # This code ensures that when you release the left or right arrow keys
        # Sparty stops moving.
        # Try commenting out the next 5 lines of code to see what happens

        if event.type == pygame.KEYUP:
            if event.key == pygame.K_RIGHT:
                sparty_velocity_x = 0
            if event.key == pygame.K_LEFT:
                sparty_velocity_x = 0

    # Physics
    # With every frame of animation drawn, we need to do some math to calculate
    # where Sparty should be drawn.
    # The next line makes Sparty's downward velocity increase due to gravity
    sparty_velocity_y = sparty_velocity_y + GRAVITATIONAL_CONSTANT
    # The next 2 lines calculate Sparty's coordinates given their x and y velocity.
    sparty_location_y = sparty_location_y + sparty_velocity_y
    sparty_location_x = sparty_location_x + sparty_velocity_x


    # The next four sections of code do all the drawing of objects to the screen.

    # Section 1
    # Set the screen background
    # Fill free to change this color to the name of your custom color.
    screen.fill(WHITE)


    # Section 2
    # Draw all the platforms
    for platform in platforms:
        pygame.draw.rect(screen, platform_color, platform)


    # Section 3
    # Draw Sparty (this is the hardest because we need to use Sparty's
    # x and y location to determine where to place them).
    sparty_rectangle.left = sparty_location_x
    sparty_rectangle.top = sparty_location_y
    # If you would prefer Sparty be replaced with a RED rectangle, comment
    # out the next line of code and uncomment out the line after.
    screen.blit(sparty_img, sparty_rectangle)
    # pygame.draw.rect(screen, RED, sparty_rectangle)


    # Section 4
    # Draw the goal (a gold coin) in the top right of the screen.
    goal_rectangle.top = screen_rectangle.top
    goal_rectangle.right = screen_rectangle.right
    screen.blit(goal_img, goal_rectangle)

    # The order in which things are drawn affects what objects
    # occlude (cover up) others. Try swaping the order of Section 2
    # with Section 3 and see what happens if you try to jump up through a platform.



    # The following section of code determines if Sparty is standing
    # on a platform and ensures they don't pass through a platform.
    # This code is a bit complicated because it needs to check which platform Sparty
    # is touching and ensures that Sparty
    # can only land on a platform if they are descending.
    is_standing = False
    index = sparty_rectangle.collidelist(platforms)
    if index != -1:
        touching_platform = platforms[index]
        is_just_touching_down = sparty_rectangle.bottom - touching_platform.top < platform_height

        # To see what happens if Sparty can land on a platform without descending,
        # Comment out the following line and uncomment out the line following.
        # Be sure to try jumping upwards through a platform.
        is_descending = sparty_velocity_y > 0
        # is_descending = True

        if is_just_touching_down and is_descending:
            sparty_location_y = touching_platform.top - sparty_rectangle.height
            is_standing = True
            sparty_velocity_y = 1


    # This section of code determines if Sparty is touching the goal
    # (the image of the gold coin). If it is, move Sparty to position (0, 0)
    # So that the game can repeat.
    if sparty_rectangle.colliderect(goal_rectangle):
        sparty_location_x = 0
        sparty_location_y = 0






    # These two lines of code are responsible for displaying all objects on the screen
    # and fixing the frame rate of our game to 30 frames per second.
    pygame.display.update()
    # Try adjusting the FPS to 10
    clock.tick(30)

# Congratulations you made it to the end!
# This activity was originally created by Dr. Josh Nahum
# and he can be reached at nahumjos@cse.msu.edu

# For more information about PyGame (and Python), check out
# this free book by Al Sweigart titled, "Invent Your Own Computer Games With Python"
# http://inventwithpython.com/

|>> download sparty-platformer/platformer.py

Angle shooting

import sys
import math
import pygame

pygame.init()

SCREEN_WIDTH = 1200
SCREEN_HEIGHT = 800
FPS = 60

LIGHT_GREY = [230, 230, 230]
RED = [150, 0, 0]
BLACK = [0, 0, 0]
BLUE = [0, 0, 150]

PROJECTILE_SPEED = 5
PROJECTILE_RADIUS = 10

screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT])
pygame.display.set_caption("Pointer game")

clock = pygame.time.Clock()

mid_point = [int(SCREEN_WIDTH / 2), int(SCREEN_HEIGHT / 2)] # We'll be shooting projectiles from the middle of the screen

projectiles = [] # Here's where we'll track projectiles

while True:
    screen.fill(LIGHT_GREY)

    mouse_pos = pygame.mouse.get_pos() # We'll be using the mouse position to fire projectiles from

    pygame.draw.circle(screen, RED, mid_point, 20) # Draw a circle in the middle of the screen (where we'll shoot from)
    pygame.draw.circle(screen, RED, mouse_pos, 10) # Draw a circle to tell the player where they're targetting

    # Draw a line from the mid point to the mouse position
    pygame.draw.line(screen, BLACK, mid_point, mouse_pos, 3)

    ############################################################################
    # Draw all of the projectiles, track on screen vs off screen
    ############################################################################
    # Let's get the unit vector for the line we just drew
    # - at the same time, we'll track what projectiles are still on the screen
    on_screen_projectiles = []
    for projectile in projectiles:
        # Move the projectile in the correct direction
        # - projection["direction"] is a unit vector pointing in the direction we want this projectile to travel
        #   - We multiply the direction vector's x and y by our speed to have the projectile
        #     move in the correction direction at the correct speed
        projectile["current_loc"][0] += (PROJECTILE_SPEED * projectile["direction"][0])
        projectile["current_loc"][1] += (PROJECTILE_SPEED * projectile["direction"][1])

        # For convenience, we'll grab the current location (that we just calculated)
        # for this projectile
        cur_loc = projectile["current_loc"]
        pygame.draw.circle(screen, BLUE, [int(cur_loc[0]), int(cur_loc[1])], PROJECTILE_RADIUS)

        # Detect whether the projectile is off of the screen
        # - To do that, we can make a bounding box around the circle and
        #   detect whether or not that circle's bounding box is colliding with
        #   a bounding box around the entire screen
        projectile_box = pygame.Rect([0,0],[0,0])
        projectile_box.centerx = cur_loc[0]
        projectile_box.centery = cur_loc[1]
        projectile_box.width = 2*PROJECTILE_RADIUS
        projectile_box.height = 2*PROJECTILE_RADIUS
        # Use that bounding box to detect if something goes off screen
        if screen.get_rect().colliderect(projectile_box):
            on_screen_projectiles.append(projectile)
    projectiles = on_screen_projectiles
    ############################################################################

    # Here's our event loop
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.MOUSEBUTTONDOWN:
            # Calculate the vector from where we're shooting (mid_point) to
            # where we're aiming (mouse_pos)
            # - If instead, you were shooting from a character's location, you could use
            #   that character's location instead of the mid_point of the screen.
            line_vec = [mouse_pos[0] - mid_point[0], mouse_pos[1] - mid_point[1]]
            # How long is the vector? (lookup python's math.hypot function in the
            # python docs)
            vec_len = math.hypot(line_vec[0], line_vec[1])
            # Calculate the unit vector => we'll use this to specify the direction of the projectile
            unit_vec = [line_vec[0] / vec_len, line_vec[1] / vec_len]
            # Add new projectile to projectiles list
            projectiles.append({"current_loc": [mid_point[0], mid_point[1]], "direction": unit_vec})

    pygame.display.update()
    clock.tick(FPS)

|>> download pointer.py

Interacting with an NPC

'''
Chatty NPC example
'''

import sys
import pygame

pygame.init()

SCREEN_WIDTH = 500
SCREEN_HEIGHT = 500
FPS = 60


RED = [150, 0, 0]
LIGHT_GREY = [230, 230, 230]
BLACK = [0, 0, 0]
BLUE = [0, 0, 150]
ORANGE = [245, 180, 65]

screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT])
pygame.display.set_caption("Chatty NPC")

clock = pygame.time.Clock()

font = pygame.font.SysFont('impact', 18)

NPC_WIDTH = 25
NPC_HEIGHT = 25
NPC_X = int(0.75 * SCREEN_WIDTH)
NPC_Y = int(0.5 * SCREEN_HEIGHT)
NPC_INTERACT_RANGE = 10 # How many pixels away from the NPC can you interact with it?
best_bud = {"rect":             pygame.Rect([NPC_X, NPC_Y], [NPC_WIDTH, NPC_HEIGHT]),
            "interaction_rect": pygame.Rect([NPC_X - NPC_INTERACT_RANGE, NPC_Y - NPC_INTERACT_RANGE], [NPC_WIDTH + (2*NPC_INTERACT_RANGE), NPC_HEIGHT + (2*NPC_INTERACT_RANGE)]),
            "can_interact":     False,
            "talkietalkie":     "Hi there my good friend!",
            "text_visible":      False}


CHAR_START_X = 0
CHAR_START_Y = 0
CHAR_WIDTH = 15
CHAR_HEIGHT = 15
CHAR_SPEED = 5
character = pygame.Rect([CHAR_START_X, CHAR_START_Y], [CHAR_WIDTH, CHAR_HEIGHT])

while True:
    screen.fill(LIGHT_GREY)

    # Update character
    pressed_keys = pygame.key.get_pressed()
    cur_char_vel = [0, 0]
    # - vertical movement
    if pressed_keys[pygame.K_w]:
        cur_char_vel[1] = -1*CHAR_SPEED
    elif pressed_keys[pygame.K_s]:
        cur_char_vel[1] = CHAR_SPEED
    # - horizontal movement
    if pressed_keys[pygame.K_d]:
        cur_char_vel[0] = CHAR_SPEED
    elif pressed_keys[pygame.K_a]:
        cur_char_vel[0] = -1*CHAR_SPEED
    character.left += cur_char_vel[0]
    character.top += cur_char_vel[1]


    # Is the character in range to interact with the NPC?
    if character.colliderect(best_bud["interaction_rect"]):
        best_bud["can_interact"] = True
    else:
        best_bud["can_interact"] = False
        best_bud["text_visible"] = False

    ############################################################################
    # DRAWING
    ############################################################################

    # Draw our best bud
    # pygame.draw.rect(screen, BLACK, best_bud["interaction_rect"])
    pygame.draw.rect(screen, RED, best_bud["rect"])

    pygame.draw.rect(screen, BLUE, character)

    if best_bud["text_visible"]:
        # Text should be visible!
        text_surf = font.render(best_bud["talkietalkie"], True, BLACK)
        text_box = text_surf.get_rect()
        text_box.centerx = best_bud["rect"].centerx
        text_box.y = best_bud["rect"].y - text_box.height - 5
        pygame.draw.rect(screen, ORANGE, text_box)
        pygame.draw.rect(screen, ORANGE, text_box, 3)
        screen.blit(text_surf, text_box)


    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_e:
                if best_bud["can_interact"]:
                    best_bud["text_visible"] = not best_bud["text_visible"]


    pygame.display.update()
    clock.tick(FPS)

|>> download chatterbot.py