猫史档案馆


每周一个Python游戏——FPS压喵

用户:就在天边等待你就在天边等待你查看:0 回复:0 评论:0 创建时间:2023-07-18T11:59:33


导入

有没有什么同学喜欢FPS喵战游戏呢?喵战游戏通常会有一个机制,那就是持续开火的时候子弹的落点会不断往上飘(例如绝地求生、和平精英等),或者根据预制好的弹道走(如CSGO等)。那么对于这种现象,一般玩家的处理方式就是控制鼠标向下压,使子弹的弹道与鼠标的轨迹相互抵消——这就是压喵。那么如何使用Python做出这种效果呢?这就是我们今天要讲的主题:使用Python制作一个压喵专题训练程序。话不多说,教学时间到!

构思

有了大概的想法,我们可能就要考虑以下几个问题:

  1. 使用Python的哪个模块(库)进行编程?
  2. 很多时候武器的弹道是预制好的,我们应该如何存放这些弹道?
  3. 压喵的时候会不断侦测鼠标的位置,应该如何实现?

首先,不使用任何模块(库)是不可能实现这个效果的,而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判断语句来决定是否要绘制这三种圆点。为了简化代码,我们可以默认用户输入“是”和“否”其他答案时均默认为“否”……)


回复

上一页1 页 / 共 0下一页