如何在Tkinter画布上旋转多边形?
我正在用Python和Tkinter制作一个小行星游戏。当按下左箭头或右箭头键时,飞船需要旋转。飞船在Tkinter的画布上是一个三角形。我在调整三角形的坐标时遇到了困难。我觉得这可能和正弦(sin)和余弦(cos)有关,但我不太确定。目前我有两个类,一个是飞船类,另一个是游戏类。在飞船类中,我为按键按下设置了回调方法。任何帮助都将非常感激。谢谢。
飞船类
import math
class Ship:
def __init__(self,canvas,x,y,width,height):
self.canvas = canvas
self.x = x - width/2
self.y = y + height/2
self.width = width
self.height = height
self.x0 = self.x
self.y0 = self.y
self.x1 = self.x0 + self.width/2
self.y1 = self.y0-self.height
self.x2 = self.x0 + self.width
self.y2 = self.y0
self.ship = self.canvas.create_polygon((self.x0, self.y0, self.x1, self.y1, self.x2, self.y2), outline="white", width=3)
def changeCoords(self):
self.canvas.coords(self.ship,self.x0, self.y0, self.x1, self.y1, self.x2, self.y2)
def rotateLeft(self, event=None):
# Should rotate one degree left.
pass
def rotateRight(self, event=None):
# Should rotate one degree right.
self.x0 = self.x0 -1
self.y0 = self.y0 - 1
self.x1 = self.x1 + 1
self.y1 = self.y1 + 1
self.x2 = self.x2 - 1
self.y2 = self.y2 + 1
self.changeCoords()
游戏类
from Tkinter import *
from ship import *
class Game:
def __init__(self, gameWidth, gameHeight):
self.root = Tk()
self.gameWidth = gameWidth
self.gameHeight = gameHeight
self.gameWindow()
self.ship = Ship(self.canvas, x=self.gameWidth/2,y=self.gameHeight/2, width=50, height=50)
self.root.bind('<Left>', self.ship.rotateLeft)
self.root.bind('<Right>', self.ship.rotateRight)
self.root.mainloop()
def gameWindow(self):
self.frame = Frame(self.root)
self.frame.pack(fill=BOTH, expand=YES)
self.canvas = Canvas(self.frame,width=self.gameWidth, height=self.gameHeight, bg="black", takefocus=1)
self.canvas.pack(fill=BOTH, expand=YES)
asteroids = Game(600,600)
1 个回答
13
首先,你需要围绕三角形的一个中心点进行旋转。三角形的重心可能是最合适的选择。要找到这个重心,你可以使用这个公式 C = (1/3*(x0 + x1 + x2), 1/3*(y0 + y1 + y2))
,这个公式其实就是把三角形所有点的坐标取个平均值。然后,你就可以以这个点为中心进行旋转。大概就是这样……
import math
class Ship:
def centroid(self):
return 1 / 3 * (self.x0 + self.x1 + self.x2), 1 / 3 * (self.y0 + self.y1 + self.y2)
def __init__(self, canvas, x, y, width, height, turnspeed, acceleration=1):
self._d = {'Up':1, 'Down':-1, 'Left':1, 'Right':-1}
self.canvas = canvas
self.width = width
self.height = height
self.speed = 0
self.turnspeed = turnspeed
self.acceleration = acceleration
self.x0, self.y0 = x, y
self.bearing = -math.pi / 2
self.x1 = self.x0 + self.width / 2
self.y1 = self.y0 - self.height
self.x2 = self.x0 + self.width
self.y2 = self.y0
self.x, self.y = self.centroid()
self.ship = self.canvas.create_polygon((self.x0, self.y0, self.x1, self.y1, self.x2, self.y2), outline="white", width=3)
def changeCoords(self):
self.canvas.coords(self.ship,self.x0, self.y0, self.x1, self.y1, self.x2, self.y2)
def rotate(self, event=None):
t = self._d[event.keysym] * self.turnspeed * math.pi / 180 # the trig functions generally take radians as their arguments rather than degrees; pi/180 radians is equal to 1 degree
self.bearing -= t
def _rot(x, y):
#note: the rotation is done in the opposite fashion from for a right-handed coordinate system due to the left-handedness of computer coordinates
x -= self.x
y -= self.y
_x = x * math.cos(t) + y * math.sin(t)
_y = -x * math.sin(t) + y * math.cos(t)
return _x + self.x, _y + self.y
self.x0, self.y0 = _rot(self.x0, self.y0)
self.x1, self.y1 = _rot(self.x1, self.y1)
self.x2, self.y2 = _rot(self.x2, self.y2)
self.x, self.y = self.centroid()
self.changeCoords()
def accel(self, event=None):
mh = int(self.canvas['height'])
mw = int(self.canvas['width'])
self.speed += self.acceleration * self._d[event.keysym]
self.x0 += self.speed * math.cos(self.bearing)
self.x1 += self.speed * math.cos(self.bearing)
self.x2 += self.speed * math.cos(self.bearing)
self.y0 += self.speed * math.sin(self.bearing)
self.y1 += self.speed * math.sin(self.bearing)
self.y2 += self.speed * math.sin(self.bearing)
self.x, self.y = self.centroid()
if self.y < - self.height / 2:
self.y0 += mh
self.y1 += mh
self.y2 += mh
elif self.y > mh + self.height / 2:
self.y0 += mh
self.y1 += mh
self.y2 += mh
if self.x < -self.width / 2:
self.x0 += mw
self.x1 += mw
self.x2 += mw
elif self.x > mw + self.width / 2:
self.x0 -= mw
self.x1 -= mw
self.x2 -= mw
self.x, self.y = self.centroid()
self.changeCoords()
顺便说一下,我对控制方式做了一些调整,让这个游戏更像《小行星》那样。不过,我没有实现射击功能。老实说,我可能比预想的更投入这个项目了,但我不会做所有的事情。另外,当你尝试同时使用多个移动键时,会有点问题,这主要是因为Tk处理事件的方式。它并不是为游戏设计的,所以你需要花点时间去调整,才能让它在Tk/Tkinter中正常工作。
from tkinter import *
from ship import *
class Game:
def __init__(self, gameWidth, gameHeight):
self.root = Tk()
self.gameWidth = gameWidth
self.gameHeight = gameHeight
self.gameWindow()
self.ship = Ship(self.canvas, x=self.gameWidth / 2,y=self.gameHeight / 2, width=50, height=50, turnspeed=10, acceleration=5)
self.root.bind('<Left>', self.ship.rotate)
self.root.bind('<Right>', self.ship.rotate)
self.root.bind('<Up>', self.ship.accel)
self.root.bind('<Down>', self.ship.accel)
self.root.mainloop()
def gameWindow(self):
self.frame = Frame(self.root)
self.frame.pack(fill=BOTH, expand=YES)
self.canvas = Canvas(self.frame,width=self.gameWidth, height=self.gameHeight, bg="black", takefocus=1)
self.canvas.pack(fill=BOTH, expand=YES)
asteroids = Game(600,600)
另外,你可能想使用属性来更方便地处理这些点之类的东西。