【问题标题】:Moving a Tkinter canvas object multiple steps using Pygame's joystick class使用 Pygame 的操纵杆类多步移动 Tkinter 画布对象
【发布时间】:2017-09-30 17:42:03
【问题描述】:

这是一个我已经做了一段时间的项目。

情况如下:

代码完成了最终目标。它旨在模拟由四根电缆悬浮在空中的球的位置,并且应该由一个简单的游戏手柄控制。代码就是这样做的,所以它完成了最基本的定义中的目标。

问题来了:

当按下“LT”按钮时,球只会移动一点,这是它应该做的。然而,在这一次移动之后,球不会移动,直到再次按下按钮,即使“LT”按钮被按住。同样的事情也适用于“RT”按钮将球移开。 (虽然操纵杆的轨迹球并不完全像向内向外的问题,但我认为这是同一个问题。) 正如我所说,这个项目的目标最基本的定义已经完成。然而,目标的真正精神是短暂的。

这是我的问题:

只要按下“LT/RT”按钮和/或移动轨迹球,我应该如何让球移动?

代码:

#Import modules
from tkinter import *
import pygame


#Initialize joystick
pygame.joystick.init()
if pygame.joystick.get_count() == 0:
    print('No joystick detected')
else:
    joystick = pygame.joystick.Joystick(0)
    joystick.init()


###
# Interpret Joystick inputs
###


class Find_Joystick:
    def __init__(self, root):
        self.root = root

        ## initialize pygame and joystick
        pygame.init()
        if(pygame.joystick.get_count() < 1):
            # no joysticks found
            print("Please connect a joystick.\n")
            self.quit()
        else:
            # create a new joystick object from
            # ---the first joystick in the list of joysticks
            joystick = pygame.joystick.Joystick(0)
            # tell pygame to record joystick events
            joystick.init()

        ## bind the event I'm defining to a callback function


        ## start looking for events
        self.root.after(0, self.find_events)

    def find_events(self):
        ## check everything in the queue of pygame events
        events = pygame.event.get()
        joystick = pygame.joystick.Joystick(0)
        axisX = joystick.get_axis(2)
        axisY = joystick.get_axis(3)
        LT = joystick.get_button(6)
        RT = joystick.get_button(7)
        for event in events:
            # event type for pressing any of the joystick buttons down
            if event.type == pygame.JOYBUTTONDOWN:
                if LT == 1:
                    self.root.event_generate('<<LT>>')
                if RT == 1:
                    self.root.event_generate('<<RT>>')

            if event.type == pygame.JOYAXISMOTION:
                #Move left
                if axisX < 0:
                    self.root.event_generate('<<Left>>')
                #Move right
                if axisX > 0:
                    self.root.event_generate('<<Right>>')
                #Move upwards
                if axisY < -0.008:
                    self.root.event_generate('<<Up>>')
                #Move downwards
                if axisY > -0.008:
                    self.root.event_generate('<<Down>>')

        ## return to check for more events in a moment
        self.root.after(20, self.find_events)


def main():
#################
### Setup Window
#################


    ###
    #Build All Components
    ###


    #Window 1
    root = Tk()
    app = Find_Joystick(root)
    frame = Canvas(root, width=1000, height = 1000)
    frame.pack()

    #Ball
    global ball
    global ballBox1
    global ballBox2
    ballBox1 = [495, 245]
    ballBox2 = [505, 255]
    ball = frame.create_oval(ballBox1, ballBox2, outline = 'red', fill = 'red')
    ballCenter = [500, 250]
    ballCoords = [50, 50, 50]

    global ballLine1
    global ballLine2
    global ballLine3
    global ballLine4
    ballLine1 = frame.create_line((300, 150), ballCenter, fill = 'red')
    ballLine2 = frame.create_line((400, 100), ballCenter, fill = 'red')
    ballLine3 = frame.create_line((600, 150), ballCenter, fill = 'red')
    ballLine4 = frame.create_line((700, 100), ballCenter, fill = 'red')

    ballLines = (ballLine1, ballLine2, ballLine3, ballLine4)


    #Cube
    cubeFront = frame.create_rectangle([300, 150], [600, 400], width = 4.0, outline = 'blue')   #Front Face
    cubeBack = frame.create_rectangle([400, 100], [700, 350], outline = 'blue')                 #Back Face

    leftEdgeTop = frame.create_line([400, 100], [300, 150], width = 4.0, fill = 'blue')         #Top Left Edge
    leftEdgeBottom = frame.create_line([300, 400], [400, 350], fill = 'blue')                   #Bottom Left Edge
    rightEdgeTop = frame.create_line([700, 100], [600, 150], width = 4.0, fill = 'blue')        #Top Right Edge
    rightEdgeBottom = frame.create_line([600, 400], [700, 350], width = 4.0, fill = 'blue')     #Bottom Right Edge
    backEdgeTop = frame.create_line([400,100],[700,100], width = 4.0, fill = 'blue')            #Top Back Edge
    backEdgeRight = frame.create_line([700, 100], [700, 350], width = 4.0, fill = 'blue')       #Right Back Edge


    ###
    # Position Chart
    ###

    #Box
    positionBox = frame.create_rectangle([450, 475], [550, 550])

    positionBoxLine1 = frame.create_line([500, 475], [500, 550])
    positionBoxLine2 = frame.create_line([450, 500], [550, 500])
    positionBoxLine3 = frame.create_line([450, 525], [550, 525])

    positionBoxTextX = frame.create_text([475, 487.5], font = ('Arial', 14), text = 'X')
    positionBoxTextY = frame.create_text([475, 512.5], font = ('Arial', 14), text = 'Y')
    positionBoxTextZ = frame.create_text([475, 537.5], font = ('Arial', 14), text = 'Z')


    global positionBoxCoordsX
    global positionBoxCoordsY
    global positionBoxCoordsZ
    positionBoxCoordsX = frame.create_text([525, 487.5], font = ('Arial', 14), text = ballCoords[0])
    positionBoxCoordsY = frame.create_text([525, 512.5], font = ('Arial', 14), text = ballCoords[1])
    positionBoxCoordsZ = frame.create_text([525, 537.5], font = ('Arial', 14), text = ballCoords[2])

    positionCoords = [positionBoxCoordsX, positionBoxCoordsY, positionBoxCoordsZ]

    ##################
    ### Move the Ball       
    ##################

    ###
    # Redrawing Function
    ###


    #Redraw the object for it's position
    def redrawObjects():
        global ballLine1
        global ballLine2
        global ballLine3
        global ballLine4
        global positionBoxCoordsX
        global positionBoxCoordsY
        global positionBoxCoordsZ
        global ball
        frame.delete(ballLine1)
        frame.delete(ballLine2)
        frame.delete(ballLine3)
        frame.delete(ballLine4)
        frame.delete(ball)

        frame.delete(positionBoxCoordsX)
        frame.delete(positionBoxCoordsY)
        frame.delete(positionBoxCoordsZ)


        ballLine1 = frame.create_line((300, 150), ballCenter, fill = 'red')
        ballLine2 = frame.create_line((400, 100), ballCenter, fill = 'red')
        ballLine3 = frame.create_line((600, 150), ballCenter, fill = 'red')
        ballLine4 = frame.create_line((700, 100), ballCenter, fill = 'red')

        ball = frame.create_oval(ballBox1, ballBox2, outline = 'red', fill = 'red')

        positionBoxCoordsX = frame.create_text([525, 487.5], font = ('Arial', 14), text = ballCoords[0])
        positionBoxCoordsY = frame.create_text([525, 512.5], font = ('Arial', 14), text = ballCoords[1])
        positionBoxCoordsZ = frame.create_text([525, 537.5], font = ('Arial', 14), text = ballCoords[2])


    ###
    # Define each movement function
    ###


    #Indicate that the window is selected
    def callback(event):
        frame.focus_set()

    #Ball moves left
    def moveLeftFunc(event):

        if ballCoords[0] > 0:
            global ballBox1
            global ballBox2

            ballBox1[0] -= 3
            ballBox2[0] -= 3
            ballCenter[0] -= 3
            ballCoords[0] -= 1

        redrawObjects() 

    #Ball moves right
    def moveRightFunc(event):
        if ballCoords[0] < 100:
            global ballBox1
            global ballBox2


            ballBox1[0] += 3
            ballBox2[0] += 3
            ballCenter[0] += 3
            ballCoords[0] += 1

        redrawObjects() 

    #Ball moves up
    def moveUpFunc(event):
        if ballCoords[1] < 100:
            global ballBox1
            global ballBox2

            ballBox1[1] -= 2.5
            ballBox2[1] -= 2.5
            ballCenter[1] -= 2.5
            ballCoords[1] += 1

        redrawObjects()

    #Ball moves down
    def moveDownFunc(event):
        if ballCoords[1] > 0:
            global ballBox1
            global ballBox2

            ballBox1[1] += 2.5
            ballBox2[1] += 2.5
            ballCenter[1] += 2.5
            ballCoords[1] -= 1

        redrawObjects()

    #Ball moves inward
    def moveInFunc(event):
        if ballCoords[2] > 0:
            global ballBox1
            global ballBox2

            ballBox1[0] -= 1
            ballBox1[1] += 0.5
            ballBox2[0] -= 1
            ballBox2[1] += 0.5

            ballCenter[0] -= 1
            ballCenter[1] += 0.5
            ballCoords[2] -= 1

        redrawObjects()

    #Ball moves outwards
    def moveOutFunc(event):
        if ballCoords[2] < 100:
            global ballBox1
            global ballBox2

            ballBox1[0] += 1
            ballBox2[0] += 1
            ballBox1[1] -= 0.5
            ballBox2[1] -= 0.5
            ballCenter[0] += 1
            ballCenter[1] -= 0.5
            ballCoords[2] += 1

        redrawObjects()


    ###
    # Bind keys to movement
    ###


    #Initiate movement when window is clicked
    frame.bind("<Button-1>", callback)

    #Move ball left with "Left Arrow" Key
    frame.bind('<Left>', moveLeftFunc)

    #Move ball right with "Right Arrow" Key
    frame.bind('<Right>', moveRightFunc)

    #Move ball up with "Up Arrow" Key
    frame.bind('<Up>', moveUpFunc)

    #Move ball down with "Down Arrow" Key
    frame.bind('<Down>', moveDownFunc)

    #Move ball inwards with "Tab" Key
    frame.bind('<Tab>', moveInFunc)

    #Move ball outwards with "Return" Key
    frame.bind("<\>", moveOutFunc)


    #############
    #I think this is where the problem is
    #############


    def find_events():
        ## check everything in the queue of pygame events
        events = pygame.event.get()
        joystick = pygame.joystick.Joystick(0)
        axisX = joystick.get_axis(2)
        axisY = joystick.get_axis(3)
        LT = joystick.get_button(6)
        RT = joystick.get_button(7)
        for event in events:
            # event type for pressing any of the joystick buttons down
            if event.type == pygame.JOYBUTTONDOWN:
                if LT == 1:
                    global ballBox1
                    global ballBox2

                    ballBox1[0] += 1
                    ballBox2[0] += 1
                    ballBox1[1] -= 0.5
                    ballBox2[1] -= 0.5
                    ballCenter[0] += 1
                    ballCenter[1] -= 0.5
                    ballCoords[2] += 1

                if RT == 1:
                    #root.event_generate('<<RT>>')
                    global ballBox1
                    global ballBox2

                    ballBox1[0] -= 1
                    ballBox1[1] += 0.5
                    ballBox2[0] -= 1
                    ballBox2[1] += 0.5

                    ballCenter[0] -= 1
                    ballCenter[1] += 0.5
                    ballCoords[2] -= 1

            if event.type == pygame.JOYAXISMOTION:
                #Move left
                if axisX < 0:
                    #root.event_generate('<<Left>>')

                    global ballBox1
                    global ballBox2

                    ballBox1[0] -= 3
                    ballBox2[0] -= 3
                    ballCenter[0] -= 3
                    ballCoords[0] -= 1
                #Move right
                if axisX > 0:
                    #root.event_generate('<<Right>>')

                    global ballBox1
                    global ballBox2


                    ballBox1[0] += 3
                    ballBox2[0] += 3
                    ballCenter[0] += 3
                    ballCoords[0] += 1
                #Move upwards
                if axisY < -0.008:
                    #root.event_generate('<<Up>>')

                    global ballBox1
                    global ballBox2

                    ballBox1[0] -= 1
                    ballBox1[1] += 0.5
                    ballBox2[0] -= 1
                    ballBox2[1] += 0.5

                    ballCenter[0] -= 1
                    ballCenter[1] += 0.5
                    ballCoords[2] -= 1
                #Move downwards
                if axisY > -0.008:
                    #root.event_generate('<<Down>>')

                    global ballBox1
                    global ballBox2

                    ballBox1[1] += 2.5
                    ballBox2[1] += 2.5
                    ballCenter[1] += 2.5
                    ballCoords[1] -= 1


    #Move In
    root.bind('<<LT>>', moveInFunc)

    #Move Out
    root.bind('<<RT>>', moveOutFunc)

    #Move Left
    root.bind('<<Left>>', moveLeftFunc)

    #Move Right
    root.bind('<<Right>>', moveRightFunc)

    #Move Up
    root.bind('<<Up>>', moveUpFunc)

    #Move Down
    root.bind('<<Down>>', moveDownFunc)


    #Main loop
    root.mainloop()


if __name__ == '__main__':
    main()

抱歉,代码太长了。我强调了我认为问题所在的区域,但我包含了整个代码,因为(就像大多数代码一样)整个程序是高度互连的,我希望你们能够自己测试程序。

【问题讨论】:

    标签: python-3.x tkinter pygame joystick


    【解决方案1】:

    有趣的演示!快速浏览你的代码,它的组织有点混乱。特别是,似乎有两个 find_events() 函数,您似乎在其中循环遍历 pygame 事件。做一些重构来清理这个和一些讨厌的全局变量可能不会有什么坏处。

    但是,除了代码判断我认为您对操纵杆输入的问题所在是正确的。让我们看一下代码的相关部分:

    def find_events():
        ## check everything in the queue of pygame events
        events = pygame.event.get()
        #bunch of joystick button-fetching (skipping this)
        for event in events:
            # event type for pressing any of the joystick buttons down
            if event.type == pygame.JOYBUTTONDOWN:
                pass#lots of stuff I am cutting out here
            if event.type == pygame.JOYAXISMOTION:
                pass#lots of stuff I am cutting out here as well :)
    

    根本问题是您仅在 pygame 事件最初触发时才对模拟进行更改。即使您大概每帧调用find_events() 并更新 pygame 队列,pygame 只会在发生变化时将事件添加到队列中。 这解释了为什么您需要反复按下这些操纵杆按钮再次;每次按下按钮时,都会将pygame.JOYBUTTONDOWN 事件添加到队列中。结果是您的按钮按下仅持续一帧。

    那么解决方法是什么?我们需要跟踪每个按钮的当前状态。实际上有两种方法可以做到这一点:

    1. 在您自己的字典中循环遍历 pygame.event.get() 队列后跟踪和更新每个按钮的状态,并使用该字典来控制模拟中的输入。

    2. 忽略整个 pygame.event.get() 队列并使用 pygame 在其操纵杆类中提供的 get_button() 函数来获取按钮的当前状态,无论您是否在给定的时间内按下或释放它框架。

    我将只关注选项 1,因为使用选项 2,可能会错过用户输入(例如,想想如果您只检查一次帧,并且用户具有类似忍者的反应并按下并释放在该框架内快速按钮?)。对于操纵杆输入而言,这不是问题,而是对于键盘输入而言,您可能会错过键入的字母,但同样的想法也适用。结束前言,让我们看一些代码:

    def find_events():
        #your dictionary for updating button states (pressed == True)
        #also, ew, globals!
        global joystick_button_state
    
        events = pygame.event.get()
        joystick = pygame.joystick.Joystick(0)
        axisX = joystick.get_axis(2)
        axisY = joystick.get_axis(3)
        LT = joystick.get_button(6)
        RT = joystick.get_button(7)
        buttons = [joystick, axisX, axisY, LT, RT]#ideally, do NOT create a list of buttons every time you call this!
    
        for event in events:
            if event.type == pygame.JOYBUTTONDOWN:
                for button in buttons:
                    if event.button == button:
                        joystick_button_state[button] = True
    
            elif event.type == pygame.JOYBUTTONUP:
                for button in buttons:
                    if event.button = button:
                        joystick_button_state[button] = False
    

    一旦你这样做了,现在你所要做的就是查看joystick_button_state[the_one_button_i_care_about],看看它是否是True/False,看看它是否被按下。这应该适用于基本按钮,并且您可以以类似的方式存储来自pygame.JOYAXISMOTION 的事件(可能是另一个dict?)。希望这个解释可以解决您的问题!

    【讨论】:

    • 我认为这可能有效,但是,我不能 100% 确定如何实现它。另外,当我运行它时,我得到一个错误 joystick_button_state is undefined (不确定这个字典中的值是什么)。很抱歉我的困惑,因为答案很可能就在我面前。
    • joystick_button_state 只是一个全局字典变量。您需要在此函数之外创建一个空字典(编写类似joystick_button_state = {} 的所有主要代码所在的位置应该很可能解决该问题)。字典一开始是空的,然后用来自find_events() 函数的值填充。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多