用户:
就在天边等待你查看:0 回复:0 评论:0 创建时间:2023-07-18T11:59:33
导入
有没有什么同学喜欢FPS喵战游戏呢?喵战游戏通常会有一个机制,那就是持续开火的时候子弹的落点会不断往上飘(例如绝地求生、和平精英等),或者根据预制好的弹道走(如CSGO等)。那么对于这种现象,一般玩家的处理方式就是控制鼠标向下压,使子弹的弹道与鼠标的轨迹相互抵消——这就是压喵。那么如何使用Python做出这种效果呢?这就是我们今天要讲的主题:使用Python制作一个压喵专题训练程序。话不多说,教学时间到!
构思有了大概的想法,我们可能就要考虑以下几个问题:
首先,不使用任何模块(库)是不可能实现这个效果的,而Pygame等库需要下载安装后才能使用,比较麻烦,所以我们使用tkinter库进行编程;
一个物体的位置可以用xy坐标表示,可以写成一个数组(Python列表),形式一般是:
[x坐标,y坐标],多个这样的坐标应该使用数组(Python列表)进行存放,构成一个二维数组。
侦测鼠标位置可以使用canvas.winfo_pointerxy()函数,但是实际使用的时候还需要一些修改。
第一步——tkinter万能框架首先我们来编写tkinter万能框架:
import tkinter as tk
root = Tk()
c = tk.Canvas(root, width=1000, height=1000,bg="black")
c.pack()
root.mainloop()
#一般所有tkinter程序都会用到这种框架,所以被称为tkinter万能框架。
第二步——创建弹道列表
在前面的构思讲到,想要保存一组弹道坐标,可以使用一个数组将其放置在一起,每个元素都是一个坐标,这个步骤非常简单,又非常耗时间,所以我们可以打开Chat GPT,这是一个很有用的聊天软件,我们可以要求它生成一个这样的列表。我的坐标列表形成的弹道是一条垂直往上的直线,如下所示:
SC=500
ballistics_1 = [
[SC, SC], [SC, SC-10], [SC, SC-30], [SC, SC-50], [SC, SC-70], [SC, SC-90], [SC, SC-110], [SC, SC-130], [SC, SC-150], [SC, SC-170],
[SC, SC-190], [SC, SC-210], [SC, SC-230], [SC, SC-250],[SC,SC-270],[SC,SC-290],[SC,SC-310],[SC,SC-330],[SC,SC-350],[SC,SC-370],
[SC,SC-390],[SC,SC-410],[SC,SC-430],[SC,SC-450],[SC,SC-470],[SC,SC-490]
]
为什么这个列表后边跟着一个序号(_1)呢?是因为我们在显示坐标时,往往需要显示这个坐标的相反值,在我们的tkinter画布上,以 500,500为中心,在显示弹道的同时,我们需要显示压喵轨迹,以帮助新手玩家更好地压喵,另一个弹道列表如下所示:
ballistics_2 = [
[SC, SC], [SC, SC+10], [SC, SC+30], [SC, SC+50], [SC, SC+70], [SC, SC+90], [SC, SC+110], [SC, SC+130], [SC, SC+150], [SC, SC+170],
[SC, SC+190], [SC, SC+210], [SC, SC+230], [SC, SC+250], [SC, SC+270], [SC, SC+290], [SC, SC+310], [SC, SC+330], [SC, SC+350], [SC, SC+370],
[SC, SC+390], [SC, SC+410], [SC, SC+430], [SC, SC+450], [SC, SC+470], [SC, SC+490]
]
for x in range(0,ballistics_1.length-1):
circle = c.create_oval(x[0]+4,x[0]-4,x[1]+4,x[1]-4,outline=”red”,fill=”red”)#c是一个画布
这段代码在名叫c的画布上依次创建了红色圆圈,半径是4个像素,这些圆圈在一起构成了一幅弹道图。
当你绘制好了弹道图,请把它插入到代码中第三行(也就是导入库后空一行的位置)
第三步——侦测鼠标位置在前文提到:使用canvas.winfo_pointerxy()函数可以获取鼠标的位置,但是这是一个“绝对坐标”,也就是相对于整个电脑屏幕的坐标,但是我们希望这个坐标是相对于这个canvas画布的,所以可以编写下面的函数:
def get_mouse_position(canvas):
x, y = canvas.winfo_pointerxy()# 获取鼠标的当前位置
canvas_coords = canvas.winfo_rootx(), canvas.winfo_rooty(), canvas.winfo_width(), canvas.winfo_height() # 获取画布在屏幕上的坐标和尺寸
if x<canvas_coords[0] or y<canvas_coords[1] or x>canvas_coords[0] + canvas_coords[2] or y>canvas_coords[1] + canvas_coords[3]:#鼠标不在屏幕内?
return [0, 0]#返回坐标[0,0]
else:
return [x - canvas_coords[0], y - canvas_coords[1]]#返回坐标相对于画布(左上角)的位置
第四步——完成主代码
现在我们已经获取了鼠标的位置以及弹道坐标,那么如何实现压喵功能呢?我们可以在遍历的时候同时取弹道坐标与鼠标坐标,然后X和Y分开取平均值。那么我们可以专门设计一个取平均值的函数,使用这个函数可以降低我们在创建实际弹道落点时的计算量,防止因代码过长而出现语法错误。那么下面是我的代码,仅供参考。实际代码编写方法因人而异。
在往下看之前,你应该复习一下求平均值的方法,就是把所有的项目加起来并除以总项目数,因为我们直接收两个函数(弹道x或y坐标以及鼠标x或y坐标),所以直接除以2就可以了,好了,话不多说,上代码!
def average(a1,a2):
return (a1+a2)/2
非常简单,这个函数就编写好了。那么再创建圆球的代码就应该是:
Canvas.create_oval(average(ballisticsx,mousex)+4,average(ballisticsy,mousey)+4,average(ballisticsx,mousex)-4,average(ballisticsy,mousey)-4,fill=color)
其中,“Canvas”代表画布,实际应替换成你程序中画布的名字;ballisticx/ballisticy指弹道的x或y坐标,需要修改成你的弹道x坐标的名字;eventx/eventy是你鼠标的坐标,需要修改成你的鼠标位置的名字;color代表颜色,需要修改成诸如”red” ”yellow” ”blue”的颜色。这段代码会创建一个半径为4(像素)的圆点,颜色由用户说了算。
刚才提到,可以使用for循环来遍历整个弹道列表,但是这样会造成tkinter陷入“假喵”状态,也就是说会先把所有的弹道全部打印到画布上(此时还没有pack画布),再显示画布。所以我们可以做一个show_circle函数,接受一个index(索引,即列表的下标)作为参数,不断检测这一发弹道是否位于列表的最后一项,如果不是则通过上面的计算显示子弹落点,示例代码如下:
def show_circle(index):
if index < len(M4A4_ballistics_1):
x = M4A4_ballistics_1[index]
circle2_x1 = get_mouse_position(c)[0] + 4
circle2_y1 = get_mouse_position(c)[1] + 4
circle2_x2 = get_mouse_position(c)[0] - 4
circle2_y2 = get_mouse_position(c)[1] - 4
circle2_x3 = random.randint(SC-horizontal_recoil, SC+horizontal_recoil)
line = c.create_line(0, SC, 1000, SC, fill='white')
if circle1_state == "是":
circle = c.create_oval(x3+4, x[1]+4, x3-4, x[1]-4, outline='red', fill='red')
if circle2_state == "是":
circle2 = c.create_oval(circle2_x1, circle2_y1,circle2_x2, circle2_y2, outline='green', fill='green')
circle3 = c.create_oval(Ballistic_calculations([x3,x[1]],get_mouse_position(c)), outline='blue', fill='blue')
root.after(int(1000/Rate_of_fire), show_circle, index + 1)
第五步,也是最后一步——组件代码
我们已经完成了基本所有的代码片段,接下来让我们把它们结合到一起,下面是代码示例:
import tkinter as tk
from tkinter import simpledialog
import random
import time
import math
SC = 500
def average(list1,list2):
return (list1[0]+list2[0])/2,(list1[1]+list2[1])/2
def Ballistic_calculations(Atomic_bomb_drop_site,Mouse_position):
ballistics_x1 = average(Atomic_bomb_drop_site,Mouse_position)[0] + 4
ballistics_y1 = average(Atomic_bomb_drop_site,Mouse_position)[1] + 4
ballistics_x2 = average(Atomic_bomb_drop_site,Mouse_position)[0] - 4
ballistics_y2 = average(Atomic_bomb_drop_site,Mouse_position)[1] - 4
return ballistics_x1,ballistics_y1,ballistics_x2,ballistics_y2
def calculate_distance(coord):
x = coord[0]
y = coord[1]
distance = math.sqrt((x - 500)**2 + (y - 500)**2)
return round(distance,2)
root = tk.Tk()
c = tk.Canvas(root, width=1000, height=1000,bg="black")
waittime = 3
c.pack() # 先显示画布
c.create_oval(SC+4,SC+4,SC-4,SC-4,outline='white', fill='white')
horizontal_recoil = tk.simpledialog.askinteger("水平后坐力", "请输入你期望的水平后坐力(弹道摆动,建议不要超过20)\nPlease enter your desired horizontal recoil\nПожалуйста, введите желаемую горизонтальную отдачу ")
Rate_of_fire = tk.simpledialog.askinteger("武器射速", "请输入你希望的武器射速(输入整数n,表示一秒n发)\nPlease enter the rate of fire of your desired weapon\nПожалуйста, введите скорострельность желаемого оружия ")
M4A4_ballistics_1 = [
[SC, SC], [SC, SC-10], [SC, SC-30], [SC, SC-50], [SC, SC-70], [SC, SC-90], [SC, SC-110], [SC, SC-130], [SC, SC-150], [SC, SC-170],
[SC, SC-190], [SC, SC-210], [SC, SC-230], [SC, SC-250],[SC,SC-270],[SC,SC-290],[SC,SC-310],[SC,SC-330],[SC,SC-350],[SC,SC-370],
[SC,SC-390],[SC,SC-410],[SC,SC-430],[SC,SC-450],[SC,SC-470],[SC,SC-490]
]
M4A4_ballistics_2 = [
[SC, SC], [SC, SC+10], [SC, SC+30], [SC, SC+50], [SC, SC+70], [SC, SC+90], [SC, SC+110], [SC, SC+130], [SC, SC+150], [SC, SC+170],
[SC, SC+190], [SC, SC+210], [SC, SC+230], [SC, SC+250], [SC, SC+270], [SC, SC+290], [SC, SC+310], [SC, SC+330], [SC, SC+350], [SC, SC+370],
[SC, SC+390], [SC, SC+410], [SC, SC+430], [SC, SC+450], [SC, SC+470], [SC, SC+490]
]
def show_circle(index):
if index < len(M4A4_ballistics_1):
x = M4A4_ballistics_1[index]
circle2_x1 = get_mouse_position(c)[0] + 4
circle2_y1 = get_mouse_position(c)[1] + 4
circle2_x2 = get_mouse_position(c)[0] - 4
circle2_y2 = get_mouse_position(c)[1] - 4
circle2_x3 = random.randint(SC-horizontal_recoil, SC+horizontal_recoil)
line = c.create_line(0, SC, 1000, SC, fill='white')
circle = c.create_oval(x3+4, x[1]+4, x3-4, x[1]-4, outline='red', fill='red')
circle2 = c.create_oval(circle2_x1, circle2_y1,circle2_x2, circle2_y2, outline='green', fill='green')
circle3 = c.create_oval(Ballistic_calculations([x3,x[1]],get_mouse_position(c)), outline='blue', fill='blue')
root.after(int(1000/Rate_of_fire), show_circle, index + 1)
for x in M4A4_ballistics_2 :
x3 = random.randint(SC-horizontal_recoil, SC+horizontal_recoil)
circle4 = c.create_oval(x3+4, x[1]+4 , x3-4 , x[1]-4,outline='orange',fill='orange')
root.after(3000, show_circle, 0) # 三秒后开始绘制红点
root.mainloop()
记得不要总是复制粘贴整个代码,请花一些时间理解代码哦。
编程挑战:现在这个游戏的代码已经组件好了,基本可以玩了,但是这个游戏会绘制四种圆点,分别是武器原来的弹道、压喵轨迹、鼠标位置和弹道实际落点。除了弹道实际落点,其它三个圆点可能对于老玩家有些多余,所以我们可以在程序开始前问用户是否要显示这三个信息,如果用户回答“是”,那么就显示,否则就不显示。请试着做出这样的效果。
(提示,可以使用tkinter的simpledialog.askstring显示问题,通过if判断语句来决定是否要绘制这三种圆点。为了简化代码,我们可以默认用户输入“是”和“否”其他答案时均默认为“否”……)