近日下载了一款Justin Armstrong在github中的马里奥第一关的python游戏,点击作者名字,可以到作者的Github中下载源码。虽然Justin只开发了第一关,但是通过简易的调整,也可以玩出新花样,比如可以设置漫天的加命蘑菇,把每个砖块都设为无敌星,或者变更游戏背景画面,把魂斗罗的地面移到马里奥中,当然还可以增加一些隐形的问号罐,调整地面水沟的距离等,总之和小朋友玩的很愉快,惊喜连连。

但是也有一个比较麻烦的问题,就是这款游戏的代码中不支持游戏手柄,通过键盘来玩,还是不太适应,怎么办呢? 当然是改代码,把游戏手柄连接进来喽。
于是我查了很多的资料,可惜就是没有找到如何能方便的映射游戏手柄的事件,一般都是讲游戏手柄的初始化,按钮的代号,定义,如何调用等内容,比如:小黑LLB 的译文 Pygame 官方文档 - pygame.joystick,写得非常详细,虽然没能解决最终的问题,也给了我很多启发,非常感谢。
另一方面,由于原作者Justin Armstrong的代码已经很完善,各种逻辑关系,也非常合理,对马里奥的操作,都是通过对Keys的状态进行获取并处理的,所以,如果要加入手柄的参数传递,一定会较大幅度的变更原游戏中的代码,为了不要太麻烦,有没有什么好的办法呢?我想到应该可以通过映射键盘事件,来完成这个功能的传递。可是找了好多资料,却没有找到合适的内容,有一篇文章也讲了对pygame.key.get_pressed() 函数的疑惑,Confused by pygame key.get_pressed() method 里面对于这个函数的功能讲得很清楚,就是会返回一系列的布什尔值,我试着打印了一下,可以看一下:
import pygame as pg
print(pg.key.get_pressed())
# 打印这个函数,会输出以下内容,共计512个布尔值,其中为True的表示该键被按下
# 比如说回车键,是第41个的值为True
"""
pygame.key.ScancodeWrapper(False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False)
"""
# 与此同时,也通过打印event事件,查看当前键盘的状态:
print(event)
"""
<Event(768-KeyDown {'unicode': '\r', 'key': 13, 'mod': 4096, 'scancode': 40, 'window': None})>
# 可以看到 scancode = 40, 也就是当前按钮的定位,由于计数是从0开始的,所以前文提到的是第41个。
"""
虽然了解了这个函数的功能和状态,也知道了与keys的关系,但是直接把这个结果,赋值给keys是行不通的,看来还得再想想其它办法。那就是如何更直接的传递手柄的按键给键盘呢,有没有一个可以模拟键盘按键的模块呢?最后在https://pypi.org/中找到了一个keyboard模块,通过 pip install keyboard 进行导入。
可以直接模拟键盘按键,主要用到两个功能,keyboard.press 代表按下,keyboard.release 代表松开。
由于手柄的按键识别很多地方都有讲,我这里不再浪费大家的时间,以下是这部分是源代码tools.py中增加游戏手柄按键映射的代码,供参考:
# python 3.10
import pygame as pg
# pip install keyboard
import keyboard
# 初始化joystick
pg.joystick.init()
# 这里是按一个手柄进行操作,所以对应的是 pg.joystick.Joystick(0)
joystick = pg.joystick.Joystick(0)
joystick.init()
"""
# 如果需要操作多个手柄,则需要循环进行检测,可以参考以下代码:
joystick_lists = [pg.joystick.Joystick(i) for i in range(pg.joystick.get_count())]
# 打印其中第一个手柄的名称
print(joystick_lists[0].get_name())
"""
"""# 这里是对键盘按键功能的定义,"""
keybinding = {
'action':pg.K_j, # 发射子弹
'jump' :pg.K_k,
'left' :pg.K_a,
'right' :pg.K_d,
'down' :pg.K_s
}
"""# 接下来的部分,就是获取手柄按键,并对应以上的键盘按键。"""
def event_loop(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.done = True
elif event.type == pg.KEYDOWN:
self.keys = pg.key.get_pressed()
self.toggle_show_fps(event.key) # event.key 表示键盘按键的值,比如k 的值是107, 回车键的值是13,等
elif event.type == pg.KEYUP:
self.keys = pg.key.get_pressed()
"""# 以下增加游戏手柄的控制检测"""
elif event.type == pg.JOYBUTTONDOWN: # 检测到手柄上的键按下
# print(event)
if joystick.get_button(7)==1: # get_button(7) 是我这个手柄上的start键
keyboard.press('enter') # 调用键盘中的回车键。
if joystick.get_button(0)==1: # get_button(0) 是我这个手柄上的A键
keyboard.press('k') # 对应键盘的k键,也就是对应跳的功能。
if joystick.get_button(2)==1: # get_button(2) 是我这个手柄上的X键
keyboard.press('j')
if joystick.get_button(1)==1: # get_button(1) 是我这个手柄上的B键
keyboard.press('k')
if joystick.get_button(3)==1: # get_button(3) 是我这个手柄上的Y键
keyboard.press('j')
elif event.type ==pg.JOYBUTTONUP: # 检测到手柄上的键放开
if joystick.get_button(7)==0: # get_button(7) 是我这个手柄上的start键
keyboard.press('enter') # 手柄按键放天的时候,键盘中的回车键也要放开。
if joystick.get_button(0)==0:
keyboard.release('k')
if joystick.get_button(2)==0:
keyboard.release('j')
if joystick.get_button(1)==0:
keyboard.release('k')
if joystick.get_button(3)==0:
keyboard.release('j')
elif event.type == pg.JOYHATMOTION: # 检测到手柄上的十字方向键按下
#该事件的返回值是一个元组类型的数据,有两个元素,分别表示左右,和上下
if joystick.get_hat(0)[0]==-1: # 检测到手柄上的十字方向键 左键按下
keyboard.press('a') # 调用键盘中按下a键的函数
else: # 如果没有检测到手柄上的十字方向键 左键按下
keyboard.release('a') # 调用键盘中释放a键的函数
if joystick.get_hat(0)[0]==1: # 检测到手柄上的十字方向键 右键按下
keyboard.press('d')
else:
keyboard.release('d')
if joystick.get_hat(0)[1]==-1: # 检测到手柄上的十字方向键 往下的按键按下
keyboard.press('s')
else:
keyboard.release('s')
elif event.type == pg.JOYAXISMOTION: # 检测到手柄上的方向轴按下
if joystick.get_axis(0)<-0.3: # 方向轴往左的偏移值get_axis(0)从0 到-1,这里设定到-0.3的时候调用按键。
keyboard.press('a')
else:
keyboard.release('a')
if joystick.get_axis(0)>0.3: # 方向轴往右的偏移值get_axis(0)从0 到 1,这里设定到0.3的时候调用按键。
keyboard.press('d')
else:
keyboard.release('d')
if joystick.get_axis(1)>0.7: # 方向轴往下的偏移值get_axis(1)从0 到 1,这里设定到0.7的时候调用按键。
keyboard.press('s')
else:
keyboard.release('s')
self.state.get_event(event)
以上就是关于游戏手柄与键盘映射的一个初步解决方案,供参考。如果您有更好的解决方案,望能分享。
若需要马里奥第一关的源代码请到作者的Github中下载,有任何疑问,请留言,谢谢!
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass
在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击
我想设置一个默认日期,例如实际日期,我该如何设置?还有如何在组合框中设置默认值顺便问一下,date_field_tag和date_field之间有什么区别? 最佳答案 试试这个:将默认日期作为第二个参数传递。youcorrectlysetthedefaultvalueofcomboboxasshowninyourquestion. 关于ruby-on-rails-date_field_tag,如何设置默认日期?[rails上的ruby],我们在StackOverflow上找到一个类似的问
这是一道面试题,我没有答对,但还是很好奇怎么解。你有N个人的大家庭,分别是1,2,3,...,N岁。你想给你的大家庭拍张照片。所有的家庭成员都排成一排。“我是家里的friend,建议家庭成员安排如下:”1岁的家庭成员坐在这一排的最左边。每两个坐在一起的家庭成员的年龄相差不得超过2岁。输入:整数N,1≤N≤55。输出:摄影师可以拍摄的照片数量。示例->输入:4,输出:4符合条件的数组:[1,2,3,4][1,2,4,3][1,3,2,4][1,3,4,2]另一个例子:输入:5输出:6符合条件的数组:[1,2,3,4,5][1,2,3,5,4][1,2,4,3,5][1,2,4,5,3][
我正在尝试在Ruby中制作一个cli应用程序,它接受一个给定的数组,然后将其显示为一个列表,我可以使用箭头键浏览它。我觉得我已经在Ruby中看到一个库已经这样做了,但我记不起它的名字了。我正在尝试对soundcloud2000中的代码进行逆向工程做类似的事情,但他的代码与SoundcloudAPI的使用紧密耦合。我知道cursesgem,我正在考虑更抽象的东西。广告有没有人见过可以做到这一点的库或一些概念证明的Ruby代码可以做到这一点? 最佳答案 我不知道这是否是您正在寻找的,但也许您可以使用我的想法。由于我没有关于您要完成的工作
我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r