Работают ли привязки клавиш «w» и «s» по-разному в Tkinter?

Я работал над переводом версии игры «Pong», которую я создал для онлайн-класса Python с помощью CodeSkulptor, в «настольный» скрипт Python с использованием Tkinter, чтобы научиться пользоваться Tkinter. Мне удалось в значительной степени заставить работать всю игру, за исключением левого (игрока 1) весла. Я думаю, что у меня правильные привязки клавиш, так как правая (игрок 2) ракетка работает, как и ожидалось, в том смысле, что когда вы удерживаете клавиши со стрелками «вверх» или «вниз», ракетка перемещается, пока не достигнет верхней или нижней границы холст или останавливается при отпускании любой из клавиш. Я передаю нажатия клавиш обработчикам keydown и keyup, где я проверяю, какая клавиша была нажата/отпущена, и действую соответственно. Что меня сбивает с толку, так это то, что если я сопоставляю движение левого манипулятора с разными клавишами (например, «a» или «d» или стрелками «вверх» или «вниз»), он работает, как ожидалось, но отказывается работать, когда у меня сопоставлены клавиши «w» и «s». Кто-нибудь знает, почему это может быть, или что я мог сделать неправильно?

Код, который я предоставил ниже, представляет собой базовый пример, который я собрал, который демонстрирует эту проблему и движение весла, которого я пытаюсь достичь (это в значительной степени отражает мой проект Pong). Правая лопатка движется правильно, а левая - нет. Заранее спасибо за вашу помощь!

from Tkinter import *
import random


WIDTH = 500
HEIGHT = 500
PAD_WIDTH = 10
PAD_HEIGHT = 80
HALF_PAD_WIDTH = PAD_WIDTH / 2
HALF_PAD_HEIGHT = PAD_HEIGHT / 2


class Example(Frame, object):
    def __init__(self, master):
        super(Example, self).__init__(master)

        self._paddle1_pos = 200
        self._paddle2_pos = 200
        self._paddle1_vel = 0
        self._paddle2_vel = 0

        self.initUI()

    def initUI(self):
        scn_cent_height = self.master.winfo_screenheight() // 2 - HEIGHT // 2
        scn_cent_width = self.master.winfo_screenwidth() // 2 - WIDTH // 2
        self.master.geometry("%sx%s+%s+%s" % (WIDTH, HEIGHT, scn_cent_width, scn_cent_height))
        self.master.minsize(WIDTH, HEIGHT)

        self.master.title("Example Pong Paddles")

        self._canvasFrame = Frame(self.master)
        self._canvasFrame.pack(expand=True, fill=BOTH)

        self._canvas = Canvas(self._canvasFrame, bg = "black", highlightthickness=0, bd=0)
        self._canvas.pack(fill=BOTH, expand=True)
        self.update_idletasks()

        # Key handlers
        self.master.bind("<KeyPress>", self.keydown)
        self.master.bind("<KeyRelease>", self.keyup)

        while True:
            self._canvas.after(1)
            self._canvas.delete("all")
            self.draw()
            self._canvas.update()


    def draw(self):
        self._cheight = self._canvasFrame.winfo_height()
        self._cwidth = self._canvasFrame.winfo_width()

        # Draw mid line and gutters
        self._rline = self._canvas.create_line(self._cwidth / 2, 0, self._cwidth / 2, self._cheight, width=1, fill = "White")
        self._mline = self._canvas.create_line(PAD_WIDTH, 0, PAD_WIDTH, self._cheight, width=1, fill = "White")
        self._lline = self._canvas.create_line(self._cwidth - PAD_WIDTH, 0, self._cwidth - PAD_WIDTH, self._cheight, width=1, fill = "White")

        # Update paddle's vertical position, keep paddle on the screen
        # Paddle 1 - Check height and update position
        if self._paddle1_pos + self._paddle1_vel >= HALF_PAD_HEIGHT and self._paddle1_pos + self._paddle1_vel <= HEIGHT - HALF_PAD_HEIGHT:
            self._paddle1_pos += self._paddle1_vel

        # Paddle 2 - Check height and update position
        if self._paddle2_pos + self._paddle2_vel >= HALF_PAD_HEIGHT and self._paddle2_pos + self._paddle2_vel <= HEIGHT - HALF_PAD_HEIGHT:
            self._paddle2_pos += self._paddle2_vel

        # Draw paddles
        self._p1paddle = self._canvas.create_line([HALF_PAD_WIDTH, self._paddle1_pos - HALF_PAD_HEIGHT],
                                                  [HALF_PAD_WIDTH, self._paddle1_pos + HALF_PAD_HEIGHT], width=PAD_WIDTH, fill = "White")
        self._p2paddle = self._canvas.create_line([self._cwidth - HALF_PAD_WIDTH, self._paddle2_pos - HALF_PAD_HEIGHT],
                                                  [self._cwidth - HALF_PAD_WIDTH, self._paddle2_pos + HALF_PAD_HEIGHT], width=PAD_WIDTH, fill = "White")


        # Draw paddles
        self._p1paddle = self._canvas.create_line([HALF_PAD_WIDTH, self._paddle1_pos - HALF_PAD_HEIGHT],
                                                  [HALF_PAD_WIDTH, self._paddle1_pos + HALF_PAD_HEIGHT], width=PAD_WIDTH, fill = "White")
        self._p2paddle = self._canvas.create_line([self._cwidth - HALF_PAD_WIDTH, self._paddle2_pos - HALF_PAD_HEIGHT],
                                                  [self._cwidth - HALF_PAD_WIDTH, self._paddle2_pos + HALF_PAD_HEIGHT], width=PAD_WIDTH, fill = "White")


    def keydown(self, key):
        key = key.keysym

        if key == "w":
            self._paddle1_vel = -10
        elif key == "s":
            self._paddle1_vel = 10
        elif key == "Up":
            self._paddle2_vel = -10
        elif key == "Down":
            self._paddle2_vel = 10


    def keyup(self, key):
        key = key.keysym

        if key == "w":
            self._paddle1_vel = 0
        elif key == "s":
            self._paddle1_vel = 0
        elif key == "Up":
            self._paddle2_vel = 0
        elif key == "Down":
            self._paddle2_vel = 0




def main():
    root = Tk()
    example = Example(root)
    root.mainloop()


if __name__ == '__main__':
    main()

У тебя капс лок включен? Если да, то 'w' и 'W' не одно и то же.

Steven Rumbalski 20.06.2019 21:47

Ваш код отлично работает для меня (Linux, python3.6).

Novel 20.06.2019 21:48

Я только что проверил здравомыслие, чтобы убедиться, что я не использовал блокировку заглавных букв раньше, и получил те же результаты. В качестве еще одной проверки работоспособности я также попытался запустить его с помощью командной строки (вместо Spyder) и получил там те же результаты. Я использую python 2.7 на MacOSX Mojave (10.14.3), кстати. Может быть, это что-то странное с MacOS? Fwiw, у меня также есть беспроводная волшебная клавиатура Apple, подключенная к моему ноутбуку, хотя я тестировал ее как на этой клавиатуре, так и на встроенной клавиатуре моего ноутбука, без разницы.

Travis 20.06.2019 22:04

Вы уверены насчет 2.7? Ваш импорт tkinter предназначен для python3. Вы можете использовать их так, как они у вас есть, если вы установите пакеты совместимости, такие как six.

Novel 20.06.2019 22:06

@ Роман, ты прав, я случайно опечатался в операторе импорта. Спасибо, что поймали это. Я исправил это в своем коде выше, а также повторно протестировал его с исправлением, хотя все еще столкнулся с той же проблемой. Просто для фона я использую Anaconda, и моя корневая установка python — 2.7.

Travis 20.06.2019 22:12

Попробуйте добавить print(repr(key)) к методу keydown() и сообщите нам, что будет напечатано, когда вы нажмете «w».

Novel 20.06.2019 22:14

@Novel, если он помещен перед оператором key = key.keysym, он возвращает экземпляр события tkinter, например <Tkinter.Event instance at 0x10e736170>. Если он помещен после оператора key = key.keysym, он возвращает 's' или 'w'.

Travis 20.06.2019 22:26
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
7
96
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ваш код очень беспорядочный и нуждается в рефакторинге, поэтому я так и сделал. Некоторые примечания:

  • Самое большое замечание заключается в том, что вместо того, чтобы очищать экран и перерисовывать все, гораздо аккуратнее и работает намного лучше, если вы рисуете объекты один раз, а затем просто перемещаете их.
  • Используйте основной цикл tkinter; не делайте свой собственный и не обновляйте его вручную. Методы update() или update_idletasks() используются в крайнем случае; в обычном коде их нет. Вместо этого используйте основной цикл tkinter через after(). Это сделает ваше окно намного более реактивным.
  • Классы Python2 Tkinter имеют старый стиль, не заставляйте их быть в новом стиле, добавляя наследование объектов.
  • рамка холста бесполезна, поэтому я удалил ее.
  • хранение ключей в словаре освобождает от повторяющегося кода.
  • Префикс всех ваших имен переменных с помощью _ ничего не делает и затрудняет чтение и ввод; оставьте это выключенным.

-

try:
    import tkinter as tk # python3 detected
except ImportError:
    import Tkinter as tk # python2 detected

WIDTH = 500
HEIGHT = 500
PAD_WIDTH = 10
PAD_HEIGHT = 80
VELOCITY = 10
HALF_PAD_WIDTH = PAD_WIDTH // 2
HALF_PAD_HEIGHT = PAD_HEIGHT // 2

P1_UP = 111 # Up arrow key
P1_DOWN = 116 # Down arrrow key
P2_UP = 25 # 'w' key
P2_DOWN = 39 # 's' key

class Example(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master)

        self.keys = {}
        self.initUI()

        # Key handlers
        self.master.bind("<KeyPress>", self.keydown)
        self.master.bind("<KeyRelease>", self.keyup)

        self.draw() # add the game loop to the mainloop

    def initUI(self):
        scn_cent_height = self.master.winfo_screenheight() // 2 - HEIGHT // 2
        scn_cent_width = self.master.winfo_screenwidth() // 2 - WIDTH // 2
        self.master.geometry("%sx%s+%s+%s" % (WIDTH, HEIGHT, scn_cent_width, scn_cent_height))
        self.master.minsize(WIDTH, HEIGHT)

        self.master.title("Example Pong Paddles")

        self.canvas = tk.Canvas(self, bg = "black", highlightthickness=0, bd=0, width=WIDTH, height=HEIGHT)
        self.canvas.pack(fill=tk.BOTH, expand=True)

        # Draw mid line and gutters
        self.rline = self.canvas.create_line(WIDTH//2, 0, WIDTH//2, HEIGHT, width=1, fill = "White")
        self.mline = self.canvas.create_line(PAD_WIDTH, 0, PAD_WIDTH, HEIGHT, width=1, fill = "White")
        self.lline = self.canvas.create_line(WIDTH - PAD_WIDTH, 0, WIDTH - PAD_WIDTH, HEIGHT, width=1, fill = "White")

        # Draw paddles
        self.p1paddle = self.canvas.create_line([HALF_PAD_WIDTH, HEIGHT//2 - HALF_PAD_HEIGHT],
                                                  [HALF_PAD_WIDTH, HEIGHT//2 + HALF_PAD_HEIGHT], width=PAD_WIDTH, fill = "White")
        self.p2paddle = self.canvas.create_line([WIDTH - HALF_PAD_WIDTH, HEIGHT//2 - HALF_PAD_HEIGHT],
                                                    [WIDTH - HALF_PAD_WIDTH, HEIGHT//2 + HALF_PAD_HEIGHT], width=PAD_WIDTH, fill = "White")

    def draw(self):
        if self.keys.get(P2_UP) and self.canvas.coords(self.p1paddle)[1] > 0:
            self.canvas.move(self.p1paddle, 0, -VELOCITY)
        if self.keys.get(P2_DOWN) and self.canvas.coords(self.p1paddle)[3] < HEIGHT:
            self.canvas.move(self.p1paddle, 0, VELOCITY)
        if self.keys.get(P1_UP) and self.canvas.coords(self.p2paddle)[1] > 0:
            self.canvas.move(self.p2paddle, 0, -VELOCITY)
        if self.keys.get(P1_DOWN) and self.canvas.coords(self.p2paddle)[3] < HEIGHT:
            self.canvas.move(self.p2paddle, 0, VELOCITY)

        self.after(10, self.draw)

    def keydown(self, key):
        self.keys[key.keycode] = True

    def keyup(self, key):
        self.keys[key.keycode] = False

def main():
    root = tk.Tk()
    example = Example(root)
    example.pack()
    root.mainloop()

if __name__ == '__main__':
    main()

Возможно, случайно какое-то улучшение решит и вашу первоначальную проблему.

К сожалению, это все еще не сработало для меня. Либо это что-то с MacOSX, либо Tkinter иначе интерпретирует команды нажатия клавиш в Python 2, чем в Python 3. Я все еще относительно новичок в tkinter, и я думаю, что все концепции, которые меня учили использовать с SimpleGUI, не работают. На самом деле они не переведутся, как сказал профессор. Тем не менее, спасибо за советы, я постараюсь использовать их для обновления основного кода игры.

Travis 20.06.2019 22:43

Я не понимаю, почему это поможет, но в качестве выстрела в темноте вы можете попробовать установить python3, который поставляется с обновленной версией tcl (движок позади tkinter).

Novel 20.06.2019 23:02

@Travis: еще один выстрел в темноте: попробуй использовать key.keycode вместо key.keysym. Я отредактирую свой ответ, чтобы включить это.

Novel 20.06.2019 23:09

FWIW Я тестировал это в python2.7 и python3.6, и у меня это работает нормально.

Novel 20.06.2019 23:10

Я повторно протестировал ваш и мой код на python 3, прежде чем переключиться на key.keycode, и все сработало отлично! Использование key.keycode у меня тоже не сработало в python 2.7. Я все еще озадачен тем, почему он отказывается работать на python 2 для меня. Думаю, это вопрос, на который, возможно, никогда не будет ответа. Спасибо за всю твою помощь!

Travis 20.06.2019 23:39
Ответ принят как подходящий

Я просто хотел добавить, для всех, кто сталкивается с этой проблемой, что я смог отследить ее источник до моих виртуальных сред Anaconda (у меня есть по одной для Python 2 и Python 3). При тестировании мне удалось воспроизвести эту проблему при использовании именно этих сред, но не удалось воспроизвести ее при использовании Python 2 (сборка фреймворка для Mac) или Python 3 (установленного через Brew) вне Anaconda. Я стер обе виртуальные среды Anaconda на случай, если там что-то испортилось, и пересобрал их обе (т. е. чистую установку как Python 2, так и Python 3), и все же смог воспроизвести эту проблему (без установки каких-либо дополнительных модулей). Я могу только предположить, что есть что-то другое между питоном(ами), установленным/установленными Anaconda, и теми, которые либо являются частью системной структуры, либо устанавливаются отдельно.

Кроме того, просто чтобы отметить, оказывается, когда я успешно протестировал Python 3 в комментарии @Novel выше, я непреднамеренно протестировал системный Python 3, а не в виртуальной среде Anaconda Python 3.

Обновлено: Оказывается, эта проблема на самом деле связана с версией Tkinter (Tcl/Tk), которую использовали мои установки Python. Платформа Python на моем ноутбуке использовала Tk 8.5.9, тогда как установка Anaconda использует 8.6.8. Во время тестирования я столкнулся с этой проблемой при запуске моего скрипта Pong в Python 2.7.16 и 3.7.3 с Tk 8.6.8, но он отлично работал на тех же версиях Python с Tk 8.5.9. Я не уверен, что это проблема с Tk или какая-то несовместимость между Tk 8.6.8 и фреймворком MacOS (поскольку фреймворк изначально использует 8.5.9).

Другие вопросы по теме