目录
序言
Python模拟键盘鼠标输入
一些简单的小连段
一键出招的实现
结语
序言
前排声明本文只是以KOF13为例介绍python脚本编写的应用,虽然格斗游戏圈小,但是并无意冒犯KOF13的玩家。
笔者本身是个空闲时间打打拳皇街机的键盘小白,偶尔会连线菜鸡互啄。KOF97至今连特瑞无限连都敲得不妥当,KOF98之后的各个版本再也不能按出反摇拉前的指令投,更不用说跑抓这种高难度动作,大学看了三年Abang的视频,去年入手KOF13,到现在除了能站桩打咬草的一套民工BC,实战BC从来都是用来凹NEOMAX,KOF13里的蓄力,八稚女取消乃至最简单的HD取消都做不到,把键盘上的D键都敲坏了也没能按出一次波草的鬼胧取消,实在是悲惨。
昨晚难得打馆长的MISSION挑战,笔者其他角色正常都只能过一两关,熟练些的八神草剃能打通四关,然而第一次玩馆长竟然打到了第六关,那时候就特别想打通,结果第六关的跳A下A目押站D敲了半天都确认不到接半月,心灰意冷下笔者决定开始用科技武装自己通关,这才引出了利用python脚本实现KOF13的连段。
但是在编写连段的过程中,笔者遇到一些python模拟键盘中的一些坑点,并且逐渐发现可以实现一键出招以及实现一些自己永远也打不出的连段,比如八稚女取消乃至无限葵花,于是写了这篇博客来分享。本身格斗游戏圈子就小,KOF13圈就更小了,本文主要是分享python在模拟键盘鼠标这类输入器上的方法。
序言的最后挂上自己的战绩,笔者真的是小白中的小白,编写脚本仅仅是自娱自乐,不会在实战里恶心其他玩家的,万望包涵
PS:笔者之所以对战完成率很低就是最近不知道怎么回事一连线就卡死在获取对手资料的界面,挂VPN也不管用,然后就不得不强退,还被封了几天号,实在是太冤枉了。
Python模拟键盘鼠标输入
目前就笔者所知,python可用于操控模拟键盘和鼠标的包有以下三个
- pymouse包与pykeyboard包;
- pynput包中的pynput.keyboard与pynput.mouse模块;
- win32con包与win32api包;
1与2中的包都可以用pip简单安装,3中的两个包大概是python自带的两个包,或者可能是在pywin32包安装时附带安装的两个包。下面给出1与2两种包操控键盘鼠标的代码示例
pymouse包与pykeyboard包使用示例
from pykeyboard import PyKeyboard
k = PyKeyboard()
x_dim, y_dim = m.screen_size()
m.click(x_dim//2, y_dim//2, 1) #取整除 - 向下取接近除数的整数
k.type_string('Hello, World!')
# pressing a key
k.press_key('H')
# which you then follow with a release of the key
k.release_key('H')
# or you can 'tap' a key which does both
k.tap_key('e')
# note that that tap_key does support a way of repeating keystrokes with a interval time between each
k.tap_key('l',n=2,interval=5)
# and you can send a string if needed too
k.type_string('o World!')
#Create an Alt+Tab combo
k.press_key(k.alt_key)
k.tap_key(k.tab_key)
k.release_key(k.alt_key)
k.tap_key(k.function_keys[5]) # Tap F5
k.tap_key(k.numpad_keys['Home']) # Tap 'Home' on the numpad
k.tap_key(k.numpad_keys[5], n=3) # Tap 5 on the numpad, thrice
k.press_key(k.alt_key)
k.press_key(k.control_key)
####################################################
from pymouse import PyMouse
# instantiate an mouse object
m = PyMouse()
# move the mouse to int x and int y (these are absolute positions)
m.move(200, 200)
# click works about the same, except for int button possible values are 1: left, 2: right, 3: middle
m.click(500, 300, 1)
# get the screen size
m.screen_size()
# (1024, 768)
# get the mouse position
m.position()
# (500, 300)
pynput模拟键盘鼠标输入
# pynput模拟键盘
from pynput.keyboard import Key, Controller
k.press(Key.space)
k.release(Key.space)
k.press('a')
k.press('A')
with k.pressed(Key.shift):
k.press('a')
k.release('a')
k.type('Hello')
# pynput模拟鼠标
from pynput.mouse import Button, Controller
m = Controller()
m.position # 鼠标位置
m.position = (10,20) # 调整位置
m.move(5,-5) # 相对位置移动
m.press(Button.left)
m.release(Button.left)
m.click(Button.left,2)
m.scroll(0,2) # 滚两圈滚轮
可以发现pykeyboard和pymouse以及pynput在操控鼠标键盘上的代码是极为相似且非常通俗易懂,笔者没有完善注释应该也可以很明白每一句代码是什么意思。如模拟键盘主要是模拟点击按键和输入字符串两种效果,模拟鼠标则是移动鼠标位置以及点击鼠标按键。事实上pynput应该相对功能更加完善,它可以实现键盘及鼠标事件的监听,这个在本文后面介绍如何编写一键出招时会介绍到事件监听的方法。
但是本文的脚本是基于3中的两个包来实现的。原因是1与2中的包都不能在KOF13运行的环境下成功模拟输入指令,笔者在测试过程中发现如果使用pynput或者pykeyboard来实现模拟键盘输入指令在KOF13中只会起到让角色挑衅一下的效果,虽然不是很明白这里面的机制,笔者猜想可能是因为3中的两个包更贴近硬件底层,而KOF13可能在控制器输入这块做了一些转换。
以下为利用win32con与win32api两个包编写的自定义模拟按键函数
# -*- coding: UTF-8 -*-
# @author: caoyang
# @email: lzwcy110@163.com
import time
import win32con
import win32api
key2code = { # 键盘上的每个按键对应的键码
"0": 49, "1": 50, "2": 51, "3": 52, "4": 53,
"5": 54, "6": 55, "7": 56, "8": 57, "9": 58,
"A": 65, "B": 66, "C": 67, "D": 68, "E": 69, "F": 70, "G": 71,
"H": 72, "I": 73, "J": 74, "K": 75, "L": 76, "M": 77, "N": 78,
"O": 79, "P": 80, "Q": 81, "R": 82, "S": 83, "T": 84,
"U": 85, "V": 86, "W": 87, "X": 88, "Y": 89, "Z": 90,
}
def key_down(key): # 按下键盘上的按键key
key = key.upper()
vk_code = key2code[key]
win32api.keybd_event(vk_code,win32api.MapVirtualKey(vk_code,0),0,0)
def key_up(key): # 抬起键盘上的按键key
key = key.upper()
vk_code = key2code[key]
win32api.keybd_event(vk_code, win32api.MapVirtualKey(vk_code,0),win32con.KEYEVENTF_KEYUP,0)
def key_press(key,interval=0.016): # 按下-->抬起键盘上的按键key, 停顿interval时间
key_down(key)
time.sleep(interval)
key_up(key)
注意到这里主要是涉及按键按下与按键松开两个动作,本质上只要有两个动作就完全足够了。核心函数是win32api.keybd_event,这个函数的详细用法可以参照polyhedronx的博客keybd_event模拟键盘输入,限于篇幅笔者不多作介绍。
有了必要的知识储备后笔者开始编写KOF13的连段脚本。注意以下代码中的key_press, key_up, key_down函数都已经在上述代码中写好了
一些简单的小连段
笔者的上下左右按键为WSAD,ABCD的按键分别为JKUI,AC组合键Y,BD组合键O,AB组合键H,BC组合键L。
先以八神的葵花三段作为一个简单的例子。
注意KOF13对指令的要求是较为精确的,如果只是单纯敲击三遍 ↓ ← + LP 是不会有任何动作发生的
# 错误的葵花三段指令
interval = 0.016
for i in range(3):
key_press("s")
time.sleep(interval)
key_press("a")
time.sleep(interval)
key_press("j")
time.sleep(interval)
time.sleep(0.1)
代码执行效果
修正代码后我们再来测试一次葵花三段
# 正确的葵花三段指令
interval = 0.016
for i in range(3):
key_down("s")
time.sleep(interval)
key_down("a")
time.sleep(interval)
key_up("s")
time.sleep(interval)
key_up("a")
time.sleep(interval)
key_press("j",interval=interval)
time.sleep(0.1)
代码执行效果
从左边的一列指令可以看到上述代码非常精准地实现了葵花三段的指令。其中每次敲击键盘的时间间隔interval设置为0.016是考虑到KOF全系列都是60帧的画面,每帧大约0.016秒,每段葵花间一定要间隔一定时间,上述代码中设置为0.1秒,否则连续一顿按反摇拳是出不了三段葵花的。
同理我们可以精确实现八稚女的指令
# 八稚女脚本
interval = 0.016
key_down("s")
time.sleep(interval)
key_down("d")
time.sleep(interval)
key_up("s")
time.sleep(interval)
key_down("s")
time.sleep(interval)
key_up("d")
time.sleep(interval)
key_down("a")
time.sleep(interval)
key_up("s")
time.sleep(interval)
key_up("a")
time.sleep(interval)
key_press("j")
代码执行效果
接下来为了实现无限葵花,必不可少的是八稚女取消,即在两段小葵花后立刻输入八稚女指令,然后接着输入两段小葵花,为了测得八稚女取消的精准放帧时间,笔者采用两段小葵花→八稚女取消→大升龙的简易连段来测量,最终得到下面代码中的放帧时间
# 八稚女取消示例脚本
interval = 0.016
# 1. 两段小葵花
## 1.1 第一段
key_down("s")
time.sleep(interval)
key_down("a")
time.sleep(interval)
key_up("s")
time.sleep(interval)
key_up("a")
key_press("j",interval=interval)
time.sleep(0.25)
## 1.2 第二段
key_down("s")
time.sleep(interval)
key_down("a")
time.sleep(interval)
key_up("s")
time.sleep(interval)
key_up("a")
key_press("j",interval=interval)
time.sleep(0.03)
# 2. 八稚女取消
key_down("s")
time.sleep(interval)
key_down("d")
time.sleep(interval)
key_up("s")
time.sleep(interval)
key_down("s")
time.sleep(interval)
key_up("d")
time.sleep(interval)
key_down("a")
time.sleep(interval)
key_up("s")
time.sleep(interval)
key_up("a")
time.sleep(interval)
key_press("j")
time.sleep(0.4)
# 3. 大升龙
key_press("d")
time.sleep(interval)
key_down("s")
time.sleep(interval)
key_down("d")
time.sleep(interval)
key_up("s")
key_up("d")
key_press("u")
可知两段小葵花后放帧0.03秒,八稚女指令输入完后放帧0.4秒,调参过程中笔者发现这些放帧的秒数精确度要求几乎已经达到1/50秒,即差不多是1帧的级别,而且这还是笔者在零HD槽的情况下测试出的放帧秒数,有HD槽这段代码就直接会把八稚女放出来,真的难以想象这些能每次都能精确打出八稚女取消的大佬是怎么做到的。代码测试实际效果如下所示(两段小葵花→八稚女取消→大升龙)
注意可以看到八神在升龙时没有消耗HD槽,这表明八稚女取消是成功的,但是为了调出这个放帧时间,几乎是花了将近一个小时,因为靠人眼真的很难捕捉到指令的输入的速度是否是合理的。
最后笔者试图实现无限葵花,不过不知道是因为笔者对无限葵花指令的理解有偏差,还是说真的无限葵花对输入指令的节奏要求特别高,总之一直不能实现到无限葵花,即便是EX葵花起接大升龙取消的起手,也葵不出两循环以上,真的是佩服那些用手把无限葵花搓出来的巨佬,这里只能放上无限葵花的脚本代码,但是其中time.sleep()的参数是不能真正实现无限葵花的
# 无限葵花脚本
interval = 0.016
# 1. 无限葵花前先用EX葵花打出高浮空
key_down("s")
time.sleep(interval)
key_down("a")
time.sleep(interval)
key_up("s")
time.sleep(interval)
key_up("a")
key_press("y",interval=interval)
time.sleep(0.5)
key_down("s")
time.sleep(interval)
key_down("a")
time.sleep(interval)
key_up("s")
time.sleep(interval)
key_up("a")
key_press("j",interval=interval)
time.sleep(0.75)
# 2. 再用大升龙进一步提高浮空高度
key_press("d")
time.sleep(interval)
key_down("s")
time.sleep(interval)
key_down("d")
time.sleep(interval)
key_up("s")
key_up("d")
key_press("u")
time.sleep(0.4)
# 3. 无限葵花开始
for i in range(10):
# 3.1 第一段小葵花
key_down("s")
time.sleep(interval)
key_down("a")
time.sleep(interval)
key_up("s")
time.sleep(interval)
key_up("a")
key_press("j",interval=0.016)
time.sleep(0.05)
# 3.2 第二段小葵花
key_down("s")
time.sleep(interval)
key_down("a")
time.sleep(interval)
key_up("s")
time.sleep(interval)
key_up("a")
time.sleep(interval)
key_press("j",interval=0.016)
time.sleep(0.25)
# 3.3 八稚女取消
key_down("s")
time.sleep(interval)
key_down("d")
time.sleep(interval)
key_up("s")
time.sleep(interval)
key_down("s")
time.sleep(interval)
key_up("d")
time.sleep(interval)
key_down("a")
time.sleep(interval)
key_up("s")
time.sleep(interval)
key_up("a")
time.sleep(interval)
key_press("j")
time.sleep(2)
其他一些零碎的指令也不多作介绍了,笔者单单测试出一个后后后撤步的指令就发现非常困难,很难做到一个又快又短的后撤步,这也就导致很难实现44236A的无限空雷,但是实际上可以用2369A打有限空雷,这个用脚本实现还是非常容易的
interval = 0.016
# 1. EX黄石公打出高浮空
key_down("s")
time.sleep(interval)
key_down("a")
time.sleep(interval)
key_up("s")
time.sleep(interval)
key_up("a")
time.sleep(interval)
key_press("y")
time.sleep(0.2)
# 2. 2369A有限空雷
for i in range(10):
key_down("s")
time.sleep(interval)
key_down("d")
time.sleep(interval)
key_up("s")
time.sleep(interval)
key_up("d")
time.sleep(interval)
key_down("d")
key_down("w")
time.sleep(0.1)
key_up("d")
key_up("w")
key_press("j")
time.sleep(0.5)
代码执行效果如下
其实像笔者这样每个单招都要手动写确实太不经济,笔者有想过把每个角色的出招表上的每个招都写成脚本存起来,以后连段里需要直接调用就可以了,KOF13的36个角色的文件夹都建好了,但是肯定是有生之年系列了,大约笔者是很难把每个人的招都能写完,确实是太浩繁的工作量了。而且也许这种事情用TAS是更适合的
一键出招的实现
其实上面的连段脚本如果真的想用到实战几乎不太可能,还需要对实战画面进行监听分析才能真正生效,这是极为复杂的。那么接下来的一键出招脚本可能确实是有所用处的。
笔者一直困扰于反摇拉前的投技总是按不出来,比如八神的屑风,红丸的红丸投,大门的天地返,键盘确实按起来是很别扭的,在MISSION凡是遇到这些招就只能干瞪眼根本出不来(比如八神下A接屑风,这辈子是安不出来了),那么这里就可以用一键出招来完美解决。
利用pynput的监听器对象Listener可以很容易的实现这件事情
import time
from pynput import keyboard
def on_press(key):
try:
print('字母键{}被点击'.format(key.char))
if key.char=="q":
'''
# 升龙
key_press("d",0.016)
key_down("s")
time.sleep(0.016)
key_down("d")
time.sleep(0.016)
key_up("s")
key_up("d")
key_press("k")
'''
# 红丸投
key_down("d")
time.sleep(0.016)
key_down("s")
time.sleep(0.016)
key_up("d")
time.sleep(0.016)
key_down("a")
time.sleep(0.016)
key_up("s")
time.sleep(0.016)
key_up("a")
key_press("d")
key_press("j")
except AttributeError:
print('特殊键{}被点击'.format(key))
def on_release(key):
print('{} 释放'.format(key))
if key==keyboard.Key.esc: # 停止监听
return False
with keyboard.Listener(
on_press=on_press,
on_release=on_release,
) as listener:
listener.join()
keyboard类中的Listener模块接收两个函数参数,分别是对应按键点击和按键松开需要执行的脚本,只需要将升龙或者红丸投的脚本天价到on_press函数中就可以轻松实现一键出招(上述代码中将一键出招设置在Q键上)
通过这样的脚本,可以轻松实现前冲跑抓的效果
一键跑红丸投效果展示
当然你可以将一键出招变成一键出一个大连段,这个也是可以很容易的实现的,只是一些在放帧或目押要求非常高的连段是很难编写的。
结语
KOF13是个好游戏,希望大家不要用脚本来恶心对手!笔者本人兴趣使然,本文仅是对Python中的脚本应用做一个简要的介绍。笔者认为这类脚本的应用可以有很多。
分享学习,共同进步!