Я работал над переводом версии игры «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()
Ваш код отлично работает для меня (Linux, python3.6).
Я только что проверил здравомыслие, чтобы убедиться, что я не использовал блокировку заглавных букв раньше, и получил те же результаты. В качестве еще одной проверки работоспособности я также попытался запустить его с помощью командной строки (вместо Spyder) и получил там те же результаты. Я использую python 2.7 на MacOSX Mojave (10.14.3), кстати. Может быть, это что-то странное с MacOS? Fwiw, у меня также есть беспроводная волшебная клавиатура Apple, подключенная к моему ноутбуку, хотя я тестировал ее как на этой клавиатуре, так и на встроенной клавиатуре моего ноутбука, без разницы.
Вы уверены насчет 2.7? Ваш импорт tkinter предназначен для python3. Вы можете использовать их так, как они у вас есть, если вы установите пакеты совместимости, такие как six
.
@ Роман, ты прав, я случайно опечатался в операторе импорта. Спасибо, что поймали это. Я исправил это в своем коде выше, а также повторно протестировал его с исправлением, хотя все еще столкнулся с той же проблемой. Просто для фона я использую Anaconda, и моя корневая установка python — 2.7.
Попробуйте добавить print(repr(key))
к методу keydown() и сообщите нам, что будет напечатано, когда вы нажмете «w».
@Novel, если он помещен перед оператором key = key.keysym
, он возвращает экземпляр события tkinter, например <Tkinter.Event instance at 0x10e736170>
. Если он помещен после оператора key = key.keysym
, он возвращает 's'
или 'w'
.
Ваш код очень беспорядочный и нуждается в рефакторинге, поэтому я так и сделал. Некоторые примечания:
update()
или update_idletasks()
используются в крайнем случае; в обычном коде их нет. Вместо этого используйте основной цикл tkinter через after()
. Это сделает ваше окно намного более реактивным._
ничего не делает и затрудняет чтение и ввод; оставьте это выключенным.-
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, не работают. На самом деле они не переведутся, как сказал профессор. Тем не менее, спасибо за советы, я постараюсь использовать их для обновления основного кода игры.
Я не понимаю, почему это поможет, но в качестве выстрела в темноте вы можете попробовать установить python3, который поставляется с обновленной версией tcl (движок позади tkinter).
@Travis: еще один выстрел в темноте: попробуй использовать key.keycode
вместо key.keysym
. Я отредактирую свой ответ, чтобы включить это.
FWIW Я тестировал это в python2.7 и python3.6, и у меня это работает нормально.
Я повторно протестировал ваш и мой код на python 3, прежде чем переключиться на key.keycode, и все сработало отлично! Использование key.keycode у меня тоже не сработало в python 2.7. Я все еще озадачен тем, почему он отказывается работать на python 2 для меня. Думаю, это вопрос, на который, возможно, никогда не будет ответа. Спасибо за всю твою помощь!
Я просто хотел добавить, для всех, кто сталкивается с этой проблемой, что я смог отследить ее источник до моих виртуальных сред 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).
У тебя капс лок включен? Если да, то
'w'
и'W'
не одно и то же.