【问题标题】:using MATLAB 'KeyPressFcn' without console output or making a new function使用没有控制台输出的 MATLAB 'KeyPressFcn' 或创建一个新函数
【发布时间】:2014-12-29 19:45:17
【问题描述】:

我正在使用 Matlab 制作游戏(一种太空入侵者射击游戏) 图形都在一个图上,只是用多个图生成的,我知道 matlab 不适用于这些东西,这使得向同学展示特别有趣。 对于我目前使用的关键输入:

f = figure(1)
set(f, 'KeyPressFcn', @(x,y)disp(get(f,'CurrentCharacter')));
a = get(f,'CurrentCharacter');
if a=='w' %move up..
if a=='d' %move right..
if a=='2' %switch to weapon 2..

这很好用,但每次按下一个键时,它都会显示在控制台上,导致不必要的延迟。

我试过了:

f = figure(1)
set(f, 'KeyPressFcn');
a = get(f,'CurrentCharacter');

但每次按下键时它都会显示“字符串-或-函数句柄-或-单元数组”。

如何识别按键并将其存储到变量中而不会在控制台上弹出任何内容?我也希望不必创建第二个函数,因为此时所有内容都干净地保存在一个 .m 文件中。

谢谢!

【问题讨论】:

    标签: matlab character keypress


    【解决方案1】:

    作为 MATLAB Central 上一些流行游戏的作者,我可以告诉您如何在 MATLAB 中正确执行此操作。我不能保证我的方法不是最优化的,但这是我多年思考这个问题后得出的最佳解决方案。首先,我在编写游戏时通常遵循一些原则:

    1. 使用“CurrentKey”代替“CurrentCharacter”,因为前者可以识别更多未被归类为“字符”的键。

    2. 您可能还需要“KeyReleaseFcn”,因为这是射击游戏。通常您希望飞行器在按住按键时保持飞行,在松开按键时停止;您不想重复按下和释放一个键以使飞机继续移动。它的工作原理是:当玩家按下'w'时,我们调用一次KeyPressedFcn,其中我们将标志变量'w_status'设置为true;当播放器释放'w'时,调用一次KeyReleasedFcn,并将标志设置为flase。在游戏的主循环中,反复检查'w_status'是否为真。如果是,则将飞行器上移一步,否则不更新位置。

    3. 如果要将所有内容保存在一个文件中,请尝试将 KeyPressFcn 和 KeyReleaseFcn 实现为 嵌套 函数。这比把所有的代码都压缩到一个单行里要好。

    4. 避免在 if-else 子句中使用硬编码的键名。您稍后可能希望允许用户重新分配键,因此最好将键名称保留在一个数组中,您可以对其进行修改。

    所以整个游戏会是这样的:

        function MainGame()
    
        KeyStatus = false(1,6);    % Suppose you are using 6 keys in the game
        KeyNames = {'w', 'a','s', 'd', 'j', 'k'};
        KEY.UP = 1;
        KEY.DOWN = 2;
        KEY.LEFT = 3;
        KEY.RIGHT = 4;
        KEY.BULLET = 5;
        KEY.BOMB = 6;
        ...
            gameWin = figure(..., 'KeyPressFcn', @MyKeyDown, 'KeyReleaseFcn', @MyKeyUp)
            ...
        % Main game loop
        while GameNotOver
            if KeyStatus(KEY.UP)  % If left key is pressed
                player.y = player.y - ystep;
            end
            if KeyStatus(KEY.LEFT)  % If left key is pressed
                player.x = player.x - xstep;
            end
            if KeyStatus(KEY.RIGHT)  % If left key is pressed
                %..
            end
            %...
        end
    
        % Nested callbacks...
            function MyKeyDown(hObject, event, handles)
                key = get(hObject,'CurrentKey');
                % e.g., If 'd' and 'j' are already held down, and key == 's'is
                % pressed now
                % then KeyStatus == [0, 0, 0, 1, 1, 0] initially
                % strcmp(key, KeyNames) -> [0, 0, 1, 0, 0, 0, 0]
                % strcmp(key, KeyNames) | KeyStatus -> [0, 0, 1, 1, 1, 0]
                KeyStatus = (strcmp(key, KeyNames) | KeyStatus);
            end
            function MyKeyUp(hObject, event, handles)
                key = get(hObject,'CurrentKey');
                % e.g., If 'd', 'j' and 's' are already held down, and key == 's'is
                % released now
                % then KeyStatus == [0, 0, 1, 1, 1, 0] initially
                % strcmp(key, KeyNames) -> [0, 0, 1, 0, 0, 0]
                % ~strcmp(key, KeyNames) -> [1, 1, 0, 1, 1, 1]
                % ~strcmp(key, KeyNames) & KeyStatus -> [0, 0, 0, 1, 1, 0]
                KeyStatus = (~strcmp(key, KeyNames) & KeyStatus);
            end
    
        end
    

    请注意,回调使用“字典”键名来消除对任何 if-else 子句的需要。这样,无论使用多少个按键,以及它们的实际用途如何,这两个功能都可以插入到任何游戏中而无需任何修改。

    要了解这个想法在现实生活中如何发挥作用,您可以在 MATLAB Central 上查看我的游戏:

    http://www.mathworks.com/matlabcentral/fileexchange/authors/111235

    那里有一款太空射击游戏“Stellaria”。但是,它是在我不知道嵌套函数的时候写的,所以代码被分割成许多小的函数文件,可能很难阅读。请谨慎使用。

    【讨论】:

    • 是的!这正是我想要的,非常感谢!消除了延迟,启用了对角线移动……太棒了。现在必须添加武器冷却时间,只需按住开火键即可。
    【解决方案2】:

    您在没有第三个参数的情况下调用set。由于您没有使用 KeyPressFcn 回调,因此您无需设置任何内容。试试看

    f = figure(1);
    a = get(f,'CurrentCharacter');
    

    这很可能会给你a = '' 作为输出。这是因为您还没有按下任何按钮。如果点击图,按任意键调用

    b = get(f,'CurrentCharacter');
    

    你会得到你按下的按钮作为结果。因此,您可以循环调用它并执行相应的操作。请注意,在按下新按钮之前,您将获得与结果相同的按钮。

    为防止 MATLAB 在每次按下按钮时跳到控制台,您可以将回调设置为 NOP function

    处理此问题的最佳方法当然是使用KeyPressFcn 来控制您的软件,而不是覆盖/忽略它。

    【讨论】:

    • 谢谢,但这是最糟糕的选择。它可以工作,但是每次您按下一个键时,控制台都会跳到图形前面并显示您按下的键(因为所有按键都传递到输出控制台,除非指定了按键功能或其他东西)
    • 这正是您所要求的。
    • 我在我的解决方案中添加了一个解决方法。正如我在答案中所写:使用回调函数可能会允许“更好”的软件设计。
    【解决方案3】:

    为此,编写一个处理击键的完整函数并将该函数的句柄分配给图形回调会更容易。

    以下最小示例在图形处于活动状态时对按下的任何键做出反应,仅出于示例的目的在 gui 中显示检测到的字符,但没有控制台输出。

    function h = guigame
       h.fig = figure(1) ;
       h.txtDebug = uicontrol('Style','text','String','init') ;
       set(h.fig, 'KeyPressFcn', @processinput);
       guidata(h.fig,h)
    
    function processinput(hobj,evt)
       h = guidata(hobj) ;
       a = get(h.fig,'CurrentCharacter');
    
       %// just to show that you captured the right key
       %// note the absence of console output
       set(h.txtDebug,'String',a)
    
       %// now do what you want with your captured key
       %// if a=='w' %move up..
       %// if a=='d' %move right..
       %// if a=='2' %switch to weapon 2..
    

    请注意,轮询图形CurrentCharacter 是最接近您的初始代码的方式,但如果用户开始使用特殊键(ctrl/alt/shift 等),它并非万无一失开)。

    如果您希望能够处理这些情况,请在 processinput 函数中使用 evt 数据。如果我在开头设置一个调试点并查看evt 我得到:

    K>> evt
    evt = 
        Character: 'f'
         Modifier: {1x0 cell}
              Key: 'f'
    

    因此,您的函数无需调用 a = get(f,'CurrentCharacter'); 就已经拥有了它需要知道的一切,并且您还可以处理特殊情况。

    虽然对于小型游戏,您可能不需要处理每个组合键,但知道如何操作是件好事;)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-23
      相关资源
      最近更新 更多