[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

Re: [pygame] Function for keyboard movement



Hi, Bob,

As Noel said, there are many ways to do what you want.

The attached program uses a class abstraction to handle events, and two sprite types for the game objects. These are extremely basic. They may not fit all your use cases. But the more general purpose you try to get, the more elaborate your abstractions must become--and less ideal for instructing. I hope you find a balance that helps your class.

Collision detection is easy enough for folks to grasp. Collision handling, I think, is an approach that must be grasped in concept because it must accommodate a number of factors that reduce to one simple question: When a couple things collide, then what should happen?
- What are the elements colliding: blobs, bullets, lasers, etc.
- Is there one moving object, or multiple to consider in each time slice?
- In what order should they be checked?
- Should they be allowed to overlap?
- Do you need to know which edges hit?
- Should the one moving be blocked; or should they both bump back and how much?
- For more complex logic, sometimes you might want an arbiter to determine the outcome of a collision.
This is the part that often is custom coded for a game, unless two games can use the same collision system. Even a nice physics library doesn't handle all your cases "out of the box".

The attached program uses a very basic approach to collision handling: the moving and stationary things are blobs, and the moving thing is blocked; also the moving thing is kept within screen bounds. I usually find it undesirable to simply stop movement, though, as if hung up on a sticky surface. Unless it really is a sticky surface, it is "nicer" to slide along an available path. You'll see what I mean when you interact with the program. I decided to include the slightly more complex logic because I found it instructional, myself, to understand how a dumb sprite needs to discover its vicinity and decide how to get around in it.

If you had different questions in mind than were answered so far, please feel free to clarify.

Enjoy,
Gumm

On 10/6/2015 1:12 PM, Bob Irving wrote:
Hi folks,

We're introducing Python for 9th grade this year and moving from Blitz Basic for game programming. Just wondering if anyone has come up with a way to hide the complexity for keyboard movement..... it's one of those things that makes sense to more advanced programmers but not to beginners. We've experimented with making a few functions to detect collisions, mouse clicks....

TIA,
Bob Irving
Porter-Gaud School
Charleston, SC

import pygame
from pygame.locals import *


class EventThing(object):
    horiz_movement = {K_LEFT: -1, K_RIGHT: 1}
    vert_movement = {K_UP: -1, K_DOWN: 1}

    def __init__(self):
        self.running = True
        self.movex = 0
        self.movey = 0

    def get_vector(self):
        return self.movex, self.movey

    def do_event(self, e):
        if e.type == KEYDOWN:
            self._do_keydown(e.key)
        elif e.type == KEYUP:
            self._do_keyup(e.key)
        elif e.type == QUIT:
            self._do_quit()

    def _do_keydown(self, key):
        if key in self.horiz_movement.keys():
            self.movex += self.horiz_movement[e.key]
        elif key in self.vert_movement.keys():
            self.movey += self.vert_movement[e.key]
        elif key == K_ESCAPE:
            self._do_quit()

    def _do_keyup(self, key):
        if e.key in self.horiz_movement.keys():
            self.movex -= self.horiz_movement[e.key]
        elif e.key in self.vert_movement.keys():
            self.movey -= self.vert_movement[e.key]

    def _do_quit(self):
        self.running = False


class Sprite(pygame.sprite.Sprite):
    def __init__(self, position, color):
        self.image = pygame.Surface((60, 60))
        self.image.fill(color)
        self.rect = self.image.get_rect(center=position)


class MobileSprite(Sprite):
    def move_among_obstacles(self, vec, obstacles, clamp_rect=None):

        def test_move(try_vec):
            rect = self.rect.move(try_vec)
            if clamp_rect is not None and not clamp_rect.contains(rect):
                rect.clamp_ip(clamp_rect)
                try_vec = rect.x - self.rect.x, rect.y - self.rect.y
            for obj in obstacles:
                if obj.rect.colliderect(rect):
                    return None
            return try_vec

        for try_vec in vec, (vec[0], 0), (0, vec[1]):
            ok_vec = test_move(try_vec)
            if ok_vec is not None:
                self.rect.move_ip(*ok_vec)


pygame.init()

resolution = 640, 480
screen = pygame.display.set_mode(resolution)
screen_rect = screen.get_rect()
clock = pygame.time.Clock()
max_fps = 60

clear_color = Color('black')
mobile_color = Color('darkviolet')
stationary_color = Color('red')

# Make the moving sprite, obstacles
guy = MobileSprite(screen_rect.center, mobile_color)
rocks = []
for x in resolution[0] * 1/4, resolution[0] * 3/4:
    for y in resolution[1] * 1/4, resolution[1] * 3/4:
        rocks.append(Sprite((x, y), stationary_color))

# Make the event logic handler
event_thing = EventThing()

while event_thing.running:
    # Time
    dt = clock.tick(max_fps) / 1000.0

    # Input
    for e in pygame.event.get():
        event_thing.do_event(e)

    # Logic
    vector = event_thing.get_vector()
    guy.move_among_obstacles(vector, rocks, screen_rect)

    # Visuals
    screen.fill(clear_color)
    for rock in rocks:
        screen.blit(rock.image, rock.rect)
    screen.blit(guy.image, guy.rect)
    pygame.display.flip()