Kollisions-Abfrage in Tkinter mit zwei Sprites

Fragen zu Tkinter.
Antworten
Benutzeravatar
granpatomic
User
Beiträge: 6
Registriert: Sonntag 18. Oktober 2015, 13:19

Hallo ich probiere gerade ein Spiel in Tkinter zu Programmieren (Ich bin 12 und anfänger also sry vielleicht für die dümmliche Frage) man steuert ein Raumschiff und fliegt zu Planeten um dort Level anzunehmen. Nun mein Problem: Wie kann ich Python sagen dass einen Befehl ausführt wenn das Raumschiff den Planeten berührt hat?

Code: Alles auswählen

import time
import random
from tkinter import *

class Raumschiff:
    def __init__(self, canvas, p):
        self.tk = tk
        self.canvas = canvas
        self.planeten = p
        self.raumschiffv = PhotoImage(file="shuttlev1.gif")
        self.raumschiffr = PhotoImage(file="shuttler1.gif")
        self.raumschiffu = PhotoImage(file="shuttleu1.gif")
        self.raumschiffl = PhotoImage(file="shuttlel1.gif")
        self.canvas_height = self.canvas.winfo_height()
        self.canvas_width = self.canvas.winfo_width()
        self.canvas.bind_all('<w>', self.nachoben)
        self.canvas.bind_all('<a>', self.nachlinks)
        self.canvas.bind_all('<s>', self.nachunten)
        self.canvas.bind_all('<d>', self.nachrechts)
        self.da = self.canvas.create_image(250, 250, image=self.raumschiffv)

    def nachoben(self, evt):
        self.canvas.itemconfig(self.da, image=self.raumschiffv)
        self.canvas.move(self.da, 0, -3)
        self.tk.update()
        time.sleep(0.01)

    def nachlinks(self, evt):
        self.canvas.itemconfig(self.da, image=self.raumschiffr)
        self.canvas.move(self.da, -3, 0)
        self.tk.update()

    def nachunten(self, evt):
        self.canvas.itemconfig(self.da, image=self.raumschiffu)
        self.canvas.move(self.da, 0, 3)

    def nachrechts(self, evt):
        self.canvas.itemconfig(self.da, image=self.raumschiffl)
        self.canvas.move(self.da, 3, 0)
        self.tk.update()

class Planeten:
    def __init__(self, canvas):
        self.tk = tk
        self.canvas = canvas
        self.planet1 = PhotoImage(file="planet1.gif")
        self.planet2 = PhotoImage(file="planet2.gif")
        self.planet3 = PhotoImage(file="planet3.gif")
        self.da1 = self.canvas.create_image(140, 380, image= self.planet1)


    


tk = Tk()
tk.title("SpaceInvate")
tk.resizable(0, 0)
tk.wm_attributes("-topmost", 1)
canvas = Canvas(tk, width=500, height=500, highlightthickness=0)
canvas.pack()
tk.update()
canvas_height = 500
canvas_width = 500
bg = PhotoImage(file="hg1.gif")
w = bg.width()
h = bg.height()
for x in range(0, 5):
    for y in range(0, 5):
        canvas.create_image(x * w, y * h, image=bg, anchor='nw')

p = Planeten(canvas)
r = Raumschiff(canvas, p)

while 1:
    tk.update_idletasks()
    tk.update()
Zuletzt geändert von Anonymous am Samstag 28. November 2015, 22:49, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Du mußt abfragen, an welcher Koordinate sich Dein Raumschiff befindet. Mit self.da hast Du die Item ID mit shuttle_coords = my_canvas.coords(item_id) bekommst Du eine Liste der Koordinaten Deines Raumschiffes. Das wäre aber nur eine einzige, bzw zwei Werte (X und Y).Aus Länge und Breite Deines GIF Bildes folgen daraus die Koordinaten der vier Eckpunke. Dann mußt Du berechnen ob sich einer dieser vier Eckpunkte innerhalb eines Rechtecks aus Koordinate und Länge und Breite eines Planetens befindet.

Das wäre aber nur grob. Genau wird es erst, wenn Du die Form auf deinem Gif Blld berücksichtigst. Und da wäre wohl anzunehmen, dass der Planet rund ist. Da sollte man dann kein Rechteck für den Planeten nehmen sondern vielleicht einen Kreis um den Mittelpunkt des Gif Bildes für den Planeten.

Für die Rakete müßtest Du dann auch statt einem Rechteck entsprechende Punkte auf dem Gif Bild zur Kennzeichnung der Grenzen der Hülle festlegen.

Mein Vorschlag: rechne erst mit Rechtecken und dann mache es genauer durch Berücksichtigung der Form rund bei Planeten und andere Form bei dem Raumschiff.

Bei einer runden Formk des Planeten mußt Du berechnen, ob sich Hüllenpunkte der Rakete innerhalb oder außerhalb des Planetenradius befinden: math.sqrt(dx*dx + dy*dy)

Übrigens: wenn Du kein sleep machst, brauchst Du auch kein tk.update oder tk.update_idletasks. Wenn Du etwas mit Zeit machen willst, verwende statt sleep after. Bei sleep läßt Du die GUI einschlafen, sodass Du sie dann updaten mußt, weil sie nichts macht, während sie schläft. Lass die GUI einfach im Normalzustand und triggere zeitliche Aktionen durch after.
Benutzeravatar
granpatomic
User
Beiträge: 6
Registriert: Sonntag 18. Oktober 2015, 13:19

Wie soll ich das im Code hinzufügen (Beispiel bitte). Ich würde erstmal auf die Genauigkeit verzichten, ich will das wenn die Raumschiff-gif die Planten-gif Berührt das dann etwas geschieh. Danke für deinen Beitrag aber ich weiß nicht wo ich den code hinzufügen soll :oops: .
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Wie Du das Deinem Code hinzufügen sollst, ist die falsche Frage. Denn die Bewegung des Shuttles von selber, ist das Letzte was Du hinzufügen solltest. Starte erst einmal eine Testumgebung mit Widget für Planeten und Raumschiffe und Bewegegung per Maus. Dann mach die Kollision.

Und am Ende überlege Dir, wie das Raumschiff sich bewegen soll. Zur Koolisionsabfrage gibt es übrigens: find_overlapping
Damit ist das dann ganz einfach.

Dein Raumschiff hat den tag 'shuttle'
Mit coords bekommst Du die Koordinaten. Solltest also noch ein Rechteck in Deinem Code damit verknüpfen.
Dann brauchst Du für diese Koordinaaten nur find_overlapping abfragen. Wenn hier mehr als ein item gefunden wird oder bei Dir mit zusätzlichem Image mehr als zwei, dann hast Du eine Kollision.

Jetzt aber mal ein zusammenkopiertes Beispiel (da schimpfen bestimmt Sirius3 un BlackJack) wieder, weil keine Klassen - aber sollte man sich auch erst später überlegen. Zu früh auf bestimmte Strukturen oder Klassen festlegen, kann auch in Sackgassen führen.

Hier kannst Du mal Raumschiff und auch Planeten per Maus bewegen:

Code: Alles auswählen

import tkinter as tk

# GUI =============================================================
root = tk.Tk()

canvas = tk.Canvas(root)

coords = (295.0,197.0,335.0,237.0)
item = canvas.create_oval(*coords)
canvas.itemconfig(item,**{'fill':'orange'})

coords = (40.0,135.0,72.0,167.0)
item = canvas.create_oval(*coords)
canvas.itemconfig(item,**{'fill':'orange'})

coords = (152.0,211.0,177.60000000000002,236.6)
item = canvas.create_oval(*coords)
canvas.itemconfig(item,**{'fill':'orange'})

coords = (90.60000000000002,37.39999999999999,111.08000000000001,57.88000000000001)
item = canvas.create_oval(*coords)
canvas.itemconfig(item,**{'fill':'orange'})

coords = (304.08000000000004,60.91999999999999,320.464,77.304)
item = canvas.create_oval(*coords)
canvas.itemconfig(item,**{'fill':'orange'})

coords = (232.46400000000003,24.535999999999973,245.57120000000003,37.64319999999998)
item = canvas.create_oval(*coords)
canvas.itemconfig(item,**{'fill':'orange'})

coords = (225.0,129.0,235.0,149.0)
item = canvas.create_oval(*coords)
canvas.itemconfig(item,**{'fill':'red','tags':'shuttle'})

canvas.pack()

# CODE ===========================================================================

canvas_item = [None]
paint_enabled=[False]
bbox_coord = [0,0,0,0]

def get_coord():
    xw = canvas.winfo_pointerx()-canvas.winfo_rootx()
    yw = canvas.winfo_pointery()-canvas.winfo_rooty()
    return canvas.canvasx(xw), canvas.canvasy(yw)
    
def mouse_move():
    if paint_enabled[0]:
        step = 10
        xc,yc = get_coord()
        diffx = xc - bbox_coord[0]
        diffy = yc - bbox_coord[1]
        bbox_coord[0] = xc
        bbox_coord[1] = yc
        canvas.move(canvas_item[0],diffx,diffy)
        canvas.after(step,mouse_move)

def stop_move(event = None):
    paint_enabled[0] = False

def do_move(event=None):
    xc,yc = get_coord()
    bbox_coord[0] = xc
    bbox_coord[1] = yc
    canvas_item[0] = canvas.find_closest(xc,yc)
    paint_enabled[0] = True
    mouse_move()

canvas.bind('<Button-1>',do_move)
canvas.bind('<ButtonRelease-1>',stop_move)

# START =================================================================================

root.mainloop()
Benutzeravatar
granpatomic
User
Beiträge: 6
Registriert: Sonntag 18. Oktober 2015, 13:19

Ich habe jetzt schonmal eine Kollisions-Funktion erstellt aber irgendwie funktioniert sie nicht:

Code: Alles auswählen

def treffen(self):
        self.treffen = False
        if self.shuttle_coords[0] <= p.planet1_coords[0] or self.shuttle_coords[1] <= p.planet1_coords[1]:
            print('hallo')
das Programm printet schon 'Hallo' obwohl das Raumschiff den Planeten noch garnicht berührt hat. Warum?
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Am besten zeichnest Du alle mögliche Kollisionen auf Papier und ermittelst welche Bedingungen gegeben sein müssen (Koordinaten). Dann gießt Du Deine (neuen) Erkenntnisse in Code. Anfangen solltest Du lediglich auf einer Ebene (1-dimensional) auf einem einfachen Zahlenstrahl (-n .. 0 .. n), je ein "Rechteck" wird hier durch 2 Punkte auf diesem Zahlenstrahl abgebildet.
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@bwbg Denk nicht so kompliziert. Da braucht man kein Papier, denn das ist alles ganz einfach.

@granpatomic ich habe mal am Ende von meinem Code, den ich hier nicht mehr ganz zeige, die Kollisionsabfrage eingebaut. Wenn Du auch noch Images für Dein Raumschiff dabei hast, mußt Du die Anzahl 1 für Dein Raumschiff eventuell entsprechend erhöhen.

Code: Alles auswählen

# Kollisionsabfrage ============================================================

def check_collision():
    coords = canvas.coords('shuttle')
    if len(canvas.find_overlapping(*coords)) > 1:
        print("Hallo Kollision!!!")
    canvas.after(100,check_collision)

check_collision()

# START =================================================================================

root.mainloop()
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Granpatomic

Hab gesehen, dass Dein Code auch Images für den Hintergrund enthält. Daher muss dann die Lösung für die Kollision etwas anders aussehen. In diesem Falle tagge auch die Planeten mit tags='planet'.

Und dann wäre das die Kollisionsabfrage:

Code: Alles auswählen

def check_collision():
    coords = canvas.coords('shuttle')
    overlapp_list = canvas.find_overlapping(*coords)
    for item in overlapp_list:
        if 'planet' in canvas.gettags(item):
            print("Hallo Kollision!!!")
            break
    canvas.after(100,check_collision)

check_collision()
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Granpatomic

Was dann bei Dir noch fehlt, ist die Umstellung von time.sleew auf after für die Raumschiffbewegung und die Behandlung an den Canvas Grenzen. Das Raumschiff soll dann nicht verschwinden, sondern wieder in umgekehrter Richtung sich bewegen.

Kannst ja diese Lösung auf Deine Widgets (mit images) anpassen:

Code: Alles auswählen

import tkinter as tk

# GUI =============================================================
root = tk.Tk()

canvas = tk.Canvas(root)

coords = (295.0,197.0,335.0,237.0)
item = canvas.create_oval(*coords)
canvas.itemconfig(item,**{'fill':'orange','tags':'planet'})

coords = (40.0,135.0,72.0,167.0)
item = canvas.create_oval(*coords)
canvas.itemconfig(item,**{'fill':'orange','tags':'planet'})

coords = (152.0,211.0,177.60000000000002,236.6)
item = canvas.create_oval(*coords)
canvas.itemconfig(item,**{'fill':'orange','tags':'planet'})

coords = (90.60000000000002,37.39999999999999,111.08000000000001,57.88000000000001)
item = canvas.create_oval(*coords)
canvas.itemconfig(item,**{'fill':'orange','tags':'planet'})

coords = (304.08000000000004,60.91999999999999,320.464,77.304)
item = canvas.create_oval(*coords)
canvas.itemconfig(item,**{'fill':'orange','tags':'planet'})

coords = (232.46400000000003,24.535999999999973,245.57120000000003,37.64319999999998)
item = canvas.create_oval(*coords)
canvas.itemconfig(item,**{'fill':'orange','tags':'planet'})

coords = (225.0,129.0,235.0,149.0)
item = canvas.create_oval(*coords)
canvas.itemconfig(item,**{'fill':'red','tags':'shuttle'})

canvas.pack()

# CODE MOVE BY MOUSE ( Kann man rauswerfen, da es fertig ist) ========================================================

canvas_item = [None]
paint_enabled=[False]
bbox_coord = [0,0,0,0]

def get_coord():
    xw = canvas.winfo_pointerx()-canvas.winfo_rootx()
    yw = canvas.winfo_pointery()-canvas.winfo_rooty()
    return canvas.canvasx(xw), canvas.canvasy(yw)
    
def mouse_move():
    if paint_enabled[0]:
        step = 10
        xc,yc = get_coord()
        diffx = xc - bbox_coord[0]
        diffy = yc - bbox_coord[1]
        bbox_coord[0] = xc
        bbox_coord[1] = yc
        canvas.move(canvas_item[0],diffx,diffy)
        canvas.after(step,mouse_move)

def stop_move(event = None):
    paint_enabled[0] = False

def do_move(event=None):
    xc,yc = get_coord()
    bbox_coord[0] = xc
    bbox_coord[1] = yc
    canvas_item[0] = canvas.find_closest(xc,yc)
    paint_enabled[0] = True
    mouse_move()

canvas.bind('<Button-1>',do_move)
canvas.bind('<ButtonRelease-1>',stop_move)

# SHUTTLE STEUERUNG ===========================================================

move_delta = [0,0]

def move_shuttle():
    canvas.move('shuttle',*move_delta)
    coords = canvas.coords('shuttle')
    center_x = (coords[0]+coords[2])/2
    center_y = (coords[1]+coords[3])/2
    if center_x < 0: move_delta[0] *= -1
    if center_y < 0: move_delta[1] *= -1
    if center_x > int(canvas['width']): move_delta[0] *= -1
    if center_y > int(canvas['height']): move_delta[1] *= -1
    canvas.after(10,move_shuttle)

move_shuttle()

def change_shuttle_move(event):
    
    pressed = event.char
    if pressed == 'w': move_delta[1] = -3
    elif pressed == 's': move_delta[1] = 3
    elif pressed == 'a': move_delta[0] = -3
    elif pressed == 'd': move_delta[0] = 3
    
root.event_add('<<move>>','<w>','<a>','<s>','<d>')
root.bind('<<move>>',change_shuttle_move)

# Kollisionsabfrage ============================================================

def check_collision():
    coords = canvas.coords('shuttle')
    overlapp_list = canvas.find_overlapping(*coords)
    for item in overlapp_list:
        if 'planet' in canvas.gettags(item):
            print("Hallo Kollision!!!")
            break
    canvas.after(100,check_collision)

check_collision()

# START =================================================================================

root.mainloop()
Ach so, in move_shuttle evtl noch ein root.update oder canvas.update wäre auch nicht schlecht.

Außerdem musst Du das center_x und center_y in move_shuttle ändern, denn bei Dir war es ein Image und das hat nur eine Koordinate. Es sei denn, Du führst ein zusätzliches Rechteck ein.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Also update ist doch nicht so gut. Da wird zwar das Raumschiff immer korrekt dargestellt, führt aber zu unregelmäßigerer Bewegung mit gelegentlichen Stockungen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Außerdem, wie sieht es bei einem Image aus?

Image mit anchor='nw' positionieren. Daraus ergibt sich folgende Kollisionsabfrage:

Code: Alles auswählen

def check_collision():
    coords = canvas.coords('shuttle')
    coords.extend((coords[0]+raumschiff_img.width(),coords[1]+raumschiff_img.height()))
    overlapp_list = canvas.find_overlapping(*coords)
    for item in overlapp_list:
        if 'planet' in canvas.gettags(item):
            print("Hallo Kollision!!!")
            break
    canvas.after(100,check_collision)
Und bei shuttle_move berechnen sich die Koordinaten so:

Code: Alles auswählen

def move_shuttle():
    canvas.move('shuttle',*move_delta)
    coords = canvas.coords('shuttle')
    center_x = coords[0] + raumschiff_img.width()/2
    center_y = coords[1] + raumschiff_img.height()/2
   ...
Jetzt müsstest Du wohl alles hinbekommen, oder?

Allerdings hast Du vier verschiedene Images je nach Richtung. Daher doch wohl mit 'center' positioniert lassen und dann den Überlappungsbereich entsprechend vom Mittelpunkt ausgehend berechnen mit halber Breite nach links und halber Breite nach rechts usw.

Und center_x ist dann einfach coords[0] und center_y coords[1]
Antworten