image image


image

Tutorial: Flucht und Verfolgung mit Processing.py (Version 2)

Ich fand, es war Zeit, mal wieder mein letztes Experiment »Flucht und Verfolgung mit Processing.py« aufzunehmen und weiterzuentwickeln. Für den Anfang hatte ich mir nicht viel vorgenommen, es sollten lediglich statt einem Verfolger viele Verfolger den Spieler jagen. Doch zuerst habe ich in der Klasse Player eine kleine Verbesserung vorgenommen. Anstelle der etwas umständlichen Abfragen in der update()-Methode

        if self.speed.x >= self.max_speed:
            self.speed.x = self.max_speed
        if self.speed.y >= self.max_speed:
            self.speed.y = self.max_speed

habe ich dies durch die eine Zeile

        self.speed.limit(self.max_speed)

ersetzt. Hätte ich eigentlich schon damals drauf kommen sollen.

Ansonsten habe ich der Klasse Player noch eine Methode zur (Kreis-) Kollisionserkennung spendiert:

    def check_collision(self, other):
        distance = dist(self.pos.x, self.pos.y, other.pos.x, other.pos.y)
        if abs(distance) > self.r + other.r:
            return(False)
        else:
            return(True)

Eine Kollisionserkennung zweier Kreise ist einfacher zu implementieren als eine Kollisionserkennung zweier Rechtecke und die Form der Sprites lud zu einer Approximierung durch zwei Kreise einfach ein.

Die Klasse Enemy hat sich gegenüber der letzten Version nicht verändert, außer daß ich – wie auch schon für Player und auch für das Hauptprogramm einige häufig benötigte Konstanten in einen Reiter Settings ausgelagert habe:

WIDTH = 960
HEIGHT = 540

vel = 2
distance = 100
no_enemies = 7

Daher importieren alle übrigen Reiter mit from settings import * gnadenlos den komletten Inhalt von settings.

Ansonsten gab es im Hauptprogramm nur Änderungen in den Funktionen setup() und draw(), die statt einem Gegner mehrere Gegner initialisierten und checkten:

def setup():

    for _ in range(no_enemies):
        enemies.append(Enemy())

def draw():

    for enemy in enemies:
        enemy.chase(player.pos)
        enemy.update()
        enemy.display()
        if player.check_collision(enemy):
            fill(200, 0, 0)
            text(u"Du hast verloren!", 20, 40)
            noLoop() 
    player.update()
    player.display()

Ansonsten blieb auch hier das Programm nahezu unverändert.

Für alle, die diese Implementierung nachvollziehen und -programmieren wollen, hier der vollständige Sketch. Zuerst noch einmal den Reiter settings.py:

WIDTH = 960
HEIGHT = 540

vel = 2
distance = 100
no_enemies = 7

Dann die Klasse Player (im Reiter player.py):

from settings import *

class Player():
    
    def __init__(self):
        self.pos = PVector(width/2, height/2)
        self.w = self.h = self.d = 32
        self.r = self.d/2  # Radius
        self.max_speed = 3
        self.speed = PVector(0, 0)
        self.img = loadImage("knt1_fr1.gif")
        self.debug = True # False
        
    def update(self):
        self.speed.limit(self.max_speed)
        self.pos.x += self.speed.x
        self.pos.y += self.speed.y
        self.check_boundaries()
        
    def display(self):
        image(self.img, self.pos.x, self.pos.y)
        if self.debug:
            fill(255, 255, 0, 100)
            circle(self.pos.x + self.w/2, self.pos.y + self.h/2, distance*2)
    
    def check_boundaries(self):
        if self.pos.x >= width - self.w:
            self.pos.x = width - self.w
        elif self.pos.x <= 0:
            self.pos.x = 0
        if self.pos.y >= height - self.h:
            self.pos.y = height - self.h
        elif self.pos.y <= 0:
            self.pos.y = 0
    
    # Kreis-Kollision
    def check_collision(self, other):
        distance = dist(self.pos.x, self.pos.y, other.pos.x, other.pos.y)
        if abs(distance) > self.r + other.r:
            return(False)
        else:
            return(True)

Gefolgt von der Klasse Enemy (im Reiter enemy.py):

from settings import *

class Enemy():
    
    def __init__(self):
        self.pos = PVector(random(width - 200) + 100, random(height - 200) + 100)
        self.w = self.h = self.d = 32
        self.r = self.d/2  # Radius
        self.vel = PVector(0, 0)
        self.img = loadImage("npc3_fr1.gif")
                    
    def update(self):
        self.pos.x += self.vel.x
        self.pos.y += self.vel.y
    
    def display(self):
        image(self.img, self.pos.x, self.pos.y)

    def chase(self, p):
        if dist(self.pos.x, self.pos.y, p.x, p.y) > distance:
            self.vel = PVector(0, 0)
            return
        if abs(self.pos.x - p.x) < abs(self.pos.y - p.y):
            # Vertical separation is bigger
            if self.pos.y < p.y:
                self.vel.y = vel
                self.vel.x = 0
            else:
                self.vel.y = -vel
                self.vel.x = 0
        else:
            # Horizontal separation is bigger
            if self.pos.x < p.x:
                self.vel.x = vel
                self.vel.y = 0
            else:
                self.vel.x = -vel
                self.vel.y = 0

Und last but not least das Hauptprogramm:

from player import Player
from enemy import Enemy
from settings import *
enemies = []

def setup():
    global enemy, player
    size(WIDTH, HEIGHT)
    this.surface.setTitle("Flucht & Verfolgung v02")
    my_font = createFont("American Typewriter", 30)
    textFont(my_font)
    player = Player()
    for _ in range(no_enemies):
        enemies.append(Enemy())
    
def draw():
    global enemy, player
    background(107, 142, 35)
    for enemy in enemies:
        enemy.chase(player.pos)
        enemy.update()
        enemy.display()
        if player.check_collision(enemy):
            fill(200, 0, 0)
            text(u"Du hast verloren!", 20, 40)
            noLoop() 
    player.update()
    player.display()
    
def keyPressed():
    global player
    if key == CODED:
        if keyCode == UP:
            player.speed.y -= vel
        elif keyCode == DOWN:
            player.speed.y += vel
        elif keyCode == LEFT:
            player.speed.x -= vel
        elif keyCode == RIGHT:
            player.speed.x += vel
            
def keyReleased():
    global player
    if key == CODED:
        if keyCode == UP or keyCode == DOWN:
            player.speed.y = 0
        elif keyCode == LEFT or keyCode == RIGHT:
            player.speed.x = 0

Wenn Ihr das Programm durchspielt, werdet Ihr feststellen, daß das Verhalten der Gegner oft nicht sehr intelligent anmutet. Sie laufen meist auf einer geraden Linie auf den Player zu, um erst kurz vor Erreichen des Zieles auf eine Diagonale einzuschwenken. In einer der nächsten Folgen werde ich dies durch einen besseren Algorithmus optimieren.

Die beiden Sprites sind aus der »Last Guardian Sprite Collection« von Philipp Lenssen, die dieser unter der CC-BY 3.0-Lizenz auf OpenGameArt.org veröffentlicht hat. Die Lizenz verlangt einen Link auf die Seite des Schöpfers und die Namensnennung. Das ist hiermit geschehen.

Den Quellcode und die Assets findet Ihr wie immer auch auf GitHub.


(Kommentieren) 

image image



Über …

Der Schockwellenreiter ist seit dem 24. April 2000 das Weblog digitale Kritzelheft von Jörg Kantel (Neuköllner, EDV-Leiter Rentner, Autor, Netzaktivist und Hundesportler — Reihenfolge rein zufällig). Hier steht, was mir gefällt. Wem es nicht gefällt, der braucht ja nicht mitzulesen. Wer aber mitliest, ist herzlich willkommen und eingeladen, mitzudiskutieren!

Alle eigenen Inhalte des Schockwellenreiters stehen unter einer Creative-Commons-Lizenz, jedoch können fremde Inhalte (speziell Videos, Photos und sonstige Bilder) unter einer anderen Lizenz stehen.

Der Besuch dieser Webseite wird aktuell von der Piwik Webanalyse erfaßt. Hier können Sie der Erfassung widersprechen.

Diese Seite verwendet keine Cookies. Warum auch? Was allerdings die iframes von Amazon, YouTube und Co. machen, entzieht sich meiner Kenntnis.


Werbung

Diese Spalte wurde absichtlich leergelassen!


Werbung


image  image  image
image  image  image


image