Python Forum
while movinig an object how do i keep it inbounds
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
while movinig an object how do i keep it inbounds
#1
a few days ago i asked python fourms about appending to a list with this same object one person added a portion where the ship would stay in bounds , ive tried to recreate that
but when i do something like
if p[0] + self.imgw <= wide:
    x =-x
this just reverses the controls ,it doesnt stop the ship at the edge of the canvas.
i tried setting the xvelocity and yvelocity as the varibles that didnt help.
i am supposed to check the image to the position and if that position is greater or less than the height or width of canvas reverse the image.. but i cant seem to figure out how to do it.

from tkinter import *
from PIL import ImageTk,Image
high=400
wide=720
xvel=4
yvel=4
r =Tk()

canvas=Canvas(r,width=wide,height=high)
bg=PhotoImage(file="./nightsky.png")
bg1= canvas.create_image(0,0,anchor=NW,image=bg)
canvas.pack()


class ship:
    def __init__(self,x,y):
        self.img = PhotoImage(file="./8bitship.png")
        self.id = canvas.create_image(0,0,anchor=NW,image=self.img)
        self.x = x
        self.y = y
        self.imgh = self.img.height()
        self.imgw = self.img.width()
       

    def move(self,x,y ):
        global xvel,yvel 
        p = canvas.coords(self.id)
        print(p)
        if p[0] + self.imgw <= wide:
            xvel =-xvel
            #x =-x
        
        
        canvas.move(self.id,x,y)

mship = ship(0,0)

r.bind("<Right>", lambda e: mship.move(x=5,y=0))
r.bind("<Left>",lambda e: mship.move(x=-5,y=0))
r.bind("<Up>",lambda e: mship.move(x=0,y=-5))
r.bind("<Down>", lambda e: mship.move(x=0,y=5))
Reply
#2
May I suggest if statements to test if the vehicle is at the edge or not.

from tkinter import *
from PIL import ImageTk,Image
high=400
wide=720
xvel=4
yvel=4
r =Tk()
 
canvas=Canvas(r,width=wide,height=high)
bg=PhotoImage(file="./nightsky.png")
bg1= canvas.create_image(0,0,anchor=NW,image=bg)
canvas.pack()
 
 
class ship:
    def __init__(self,x,y):
        self.img = PhotoImage(file="./8bitship.png")
        self.id = canvas.create_image(0,0,anchor=NW,image=self.img)
        self.x = x
        self.y = y
        self.imgh = self.img.height()
        self.imgw = self.img.width()
        
 
    def move(self,x,y ):
        p = canvas.coords(self.id)
        if p [0] <= 0 : x = 1
        if p [0] + self.imgw > wide : x = -1
        if p [1] <=0 : y = 1
        if p [1] + self.imgh > high : y = -1
        canvas.move(self.id,x,y)

mship = ship(0,0)
 
r.bind("<Right>", lambda e: mship.move(x=5,y=0))
r.bind("<Left>",lambda e: mship.move(x=-5,y=0))
r.bind("<Up>",lambda e: mship.move(x=0,y=-5))
r.bind("<Down>", lambda e: mship.move(x=0,y=5))
r.mainloop()
Reply
#3
You need to have a bounding box that limits the range of X and Y for the image. If you use the upper left corner of the image as the origin, the upper left corner of the bounding box is 0, 0 and the lower right corner is area width - image width, area height - image height. If you chose the center of the image the equations change slightly. For the rest of this discussion we'll upper left corner, image is 50x50 and area is 250x250. The bounding box is upper left = 0, 0, lower right = 100, 200.

You are using relative moves to move the image. x=5 moves the image 5 pixels right, y=2 moves the image 2 pixels down. When you get a move command you need to clip the x and y of the move to values that prevent the image from leaving the bounding box. There are two ways to do this.

Clip in absolute coordinates:
With this method we compute the absolute position of the image and clip that to the bounding box. The clipped move is converted to be relative to the image position before calling canvas.move().
# Convert to absolute coordinates and clip.
x = max(0, min(200, self.x + x)
y = max(0, min(100, self.y + y)

# Do the move in relative coordinates
canvas.move(image, x-self.x, y-self.y)

# Update image position
self.x = x
self.y = y
Clip in relative coordinates:
In the example I provided earlier I recomputed the bounding box so it is relative to the image. To convert from absolute to relative coordinates subtract the current image position.
# Clip how far we can move in x and y
x = max(-self.x, min(x, 100 - self.x))
y = max(-self.y, min(y, 200 - self.y))

# Update image postion
self.x += x
self.y += y

# Move the image
canvas.move(image, x, y)
I didn't know about canvas.coords. Since this is the real location of the image it is better to use that than an accumulator in the class (self.x, self.y). The code above changes to this for absolute coordinates.
imgx, imgy = canvas.coords(image)
x = max(0, min(200, imgx + x)
y = max(0, min(100, imgy + y)

# Do the move in relative coordinates
canvas.move(image, x-imgx], y-imgy)
Or this for relative coordinates.
# Clip how far we can move in x and y
imgx, imgy = canvas.coords(image)
x = max(-imgx, min(x, 100 - imgx))
y = max(-imgy, min(y, 200 - imgy))

# Move the image
canvas.move(image, x, y)
Reply
#4
Another way to do this is do everything in absolute coordinates except the move request.
import tkinter as tk

def clip(x, lower, upper):
    """Clip value to be in range lower..uper"""
    return lower if x < lower else upper if x > upper else x

class Ship():
    def __init__(self, parent, wide, high):
        self.canvas = tk.Canvas(parent, width=wide, height=high)
        self.canvas.pack()
        self.img = tk.PhotoImage(file = "test_image.png")
        img_wide = self.img.width()
        img_high = self.img.height()
        self.xmax = wide - img_wide
        self.ymax = high - img_high
        x = int((wide - img_wide) / 2)
        y = int((high - img_high) / 2)
        self.img_id = self.canvas.create_image(x, y, anchor=tk.NW, image=self.img)
 
    def move(self, x, y):
        # Convert to absolute coordinates and clip to range
        imgx, imgy = self.canvas.coords(self.img_id)
        x = clip(x + imgx, 0, self.xmax)
        y = clip(y + imgy, 0, self.ymax)
        self.canvas.moveto(self.img_id, x, y)

def main():
    root = tk.Tk()
    ship = Ship(root, 500, 400)
    root.bind("<Right>", lambda e: ship.move(x=5, y=0))
    root.bind("<Left>",  lambda e: ship.move(x=-5, y=0))
    root.bind("<Up>",    lambda e: ship.move(x=0, y=-5))
    root.bind("<Down>",  lambda e: ship.move(x=0, y=5))
    root.mainloop()

main()
Reply
#5
thank you for explaining this , it really is appreciated, i took the time last night to to study this, i didn't realize that there were relative AND absolute coordinates, i used the absolute coordinates .
now let me ask this, i put into my code this and it only stops at two sides of the screen but keeps going off the other two , should i write two more lines like these, or did i miss something.


from tkinter import *
from PIL import ImageTk,Image
high=400
wide=720
xvel=4
yvel=4
r =Tk()

canvas=Canvas(r,width=wide,height=high)
bg=PhotoImage(file="./nightsky.png")
bg1= canvas.create_image(0,0,anchor=NW,image=bg)
canvas.pack()


class ship:
    def __init__(self,x,y):
        self.img = PhotoImage(file="./8bitship.png")
        self.id = canvas.create_image(0,0,anchor=NW,image=self.img)
        self.x = x
        self.y = y
        self.imgH = self.img.height()
        self.imgW = self.img.width()
        

    def move(self,x,y ):
        
        x = max(-self.x ,min(x, 720 - self.x))
        y = max(-self.y ,min(y, 400 - self.y))

        self.x += x
        self.y += y
        
        canvas.move(self.id ,x,y)


        ''' 
        p = canvas.coords(self.id)
        print(p)
        if p[0] <=0: x=1
        if p[0] + self.imgw > wide: x=-1
        if p[1] <=0: y=1
        if p[1] + self.imgh > high: y=-1
        canvas.move(self.id,x,y)
        '''
mship = ship(0,0)

r.bind("<Right>", lambda e: mship.move(x=5,y=0))
r.bind("<Left>",lambda e: mship.move(x=-5,y=0))
r.bind("<Up>",lambda e: mship.move(x=0,y=-5))
r.bind("<Down>", lambda e: mship.move(x=0,y=5))
Reply
#6
You used the relative coordinates. These instructions change the bounding box to be relative to the ship position.
x = max(-self.x ,min(x, 720 - self.x))
y = max(-self.y ,min(y, 400 - self.y))
You should not use 720 and 400. I know I did that in my post, but that was to aid the explanation. In practice you should use variables instead of literals. You already have variables for the size of your canvas (wide and high). Your code should look like this:
x = max(-self.x ,min(x, wide - self.x))
        y = max(-self.y ,min(y, high - self.y))
This code works fine for upper and left, but scrolls off on lower and right, correct? You are bounding the upper left corner of the image to remain on the canvas. You need to take the size of the image into account.
x = max(-self.x ,min(x, wide - self.x - self.imgW))
        y = max(-self.y ,min(y, high - self.y - self.imgH))
Now that I know about canvas.coords() it would be stupid for me to use self.x and self.y to keep track of the ship. The canvas is going to do that, and do a better job. The same goes for knowing about canvas.moveto() and using relative coordinates. The canvas reports the absolute position of the ship and it is easier to clip position to a static, non-relative bounding box. It only makes sense to specify the ship's position in absolute coordinates. I'd still have the x, y in Ship.move(x, y) be relative to the ship, but everything else just works easier in absolute.
def clip(x, lower, upper):
    """Clip value to be in range lower..upper"""
    return lower if x < lower else upper if x > upper else x

    def move(self, x, y):
        # Convert to absolute coordinates and clip to range
        imgx, imgy = self.canvas.coords(self.id)
        x = clip(x + imgx, 0, wide - self.imgW)
        y = clip(y + imgy, 0, high - self.imgH)
        self.canvas.moveto(self.id, x, y)
Yesterday I ran across a post talking about the fastest way to clip a value to a range in Python. The if..elif approach was fastest by far. Twice as fast as using max(..min(..)). Even after wrapping it in a function it is still faster. I also think a function named "clip" with a docstring is easier to understand than the max(..min(..)) thing.
Reply
#7
if i add a variable for the image like so this does work, but i still have to manually figure out what positions are the limits and then enter those numbers,
the 680 for x and 326 for y. if my image is 720X400 give or take and the object is 60X60
def move(self,x,y):
       imgx,imgy = canvas.coords(self.id)
       print(imgx,imgy)
       x = max(-imgx, min(x , 680 -imgx ))
       y = max(-imgy, min(y, 326  -imgy ))

       self.x += x
       self.y += y

       canvas.move(self.id, x,y)
Reply
#8
If the canvas is 720x400 and the image is 60x60, the clipping rectangle should be 660x340. If this is not the size of your clipping rectangle either the canvas or the image are not the size you think they are.
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020