做一名合格的 Processing 键盘侠

2021-12-15 15:59:25 浏览数 (1)

此『键盘侠』非彼『键盘侠』也!

在 Processing 编程中,我们常常会遇到对键盘按键的一些处理。最近在群里,也看到一些朋友询问这方面的问题,本篇小菜介绍下 Processing 中关于键盘事件的常用处理方式。

要做,就做一名合格的『键盘侠』,稳妥处理 Processing 中键盘的按键处理!哈哈~

键盘事件

键盘是 Processing 中最常见的数据输入方式(常见的还有鼠标、文件以及其他硬件输入等)。

在 Processing 中,关于键盘处理,需要记住 3 变量 3 函数

3变量:key、keyCode、keyPressed

3函数:keyPressed()、keyReleased()、keyTyped()

要想应对各种输入情况,我们需要对这 3 变量 3 函数有着充分的认识。

宫本武藏:排好队,一个一个来!

img

小菜绘制了一张图,总结了下键盘事件的一些关键知识点。

  • 键盘事件分成了三个事件类型,keyPressed()keyReleased()keyTyped()。分别表示按住键盘键,释放键盘键,以及一次完整的键盘敲击
  • 有一个特殊的常量,CODED 值为 65535,是 2 的 16 次方减 1
  • key:键盘敲击过程中的一个变量,使用 ASCII 码值表示,可以与代表 ASCII 码值的字符进行比较,如 key == 'a'
  • keyCode:key 处理不了的非 ASCII 码字符,使用 keyCode 来处理,但需要使用 key == CODED 来进行判断

key

常见的键盘字符如小写的 a-z,大写的 A-Z,以及 0-9 这些,我们可以很方便的使用下面的例子来判断。特别要注意的是 ASCII 码字符表示的时候要用单引号,如 'a' 不是 "a"。

关于 ASCII 码,不太了解的读者朋友们,可以回头查看维基百科ASCII[1]

ASCII只能显示 26 个基本拉丁字母、阿拉伯数字和英式标点符号。每个符号都对应着一个十进制数值。

关于 ASCII 码,可以百度搜索 "ASCII 对照表"。

代码语言:javascript复制
void keyPressed() {
  if (key == 'a') {
    println("敲击了a");
  } else if (key == 's') {
    println("敲击了 s,进行图像保存");
    save("myImage.png");
  } else if (key == '1') {
    println("敲击了1");
  }
}

还有一些特殊的 key,如

  • BACKSPACE 退格删除键
  • TAB
  • ENTER 回车键
  • RETURN 回车键 老的 Mac 系统上可能使用的是 RETURN 回车键表示回车
  • ESC 键盘左上角的 Escape 键
  • DELETE 删除键

都可以直接进行比较:

代码语言:javascript复制
void keyPressed() {
  if (key == BACKSPACE) {
    println("敲击了退格删除键");
  } else if (key == TAB) {
    println("敲击了 TAB 键");
  } else if (key == ENTER || key == RETURN) {
    println("敲击了回车键");
  } else if (key == ESC) {
    println("敲击了 ESC 键");
  } else if (key == DELETE) {
    println("敲击了删除键");
  }
}

keyCode

key 处理不了的非 ASCII 码字符,使用 keyCode 来处理,但需要使用 key == CODED 来进行判断处理。这个CODED判断还是很重要的,像键盘 a/A ,'a' 的 ASCII 码是 97,'A' 的 ASCII 码是 65,但是这个按键敲击出来的 keyCode 都是 65。所以直接用 keyCode 判断等于某个值,会出现多个字符的可能性。我们通过判断key == CODED来首先判断是不是非 ASCII 码字符,再用keyCode判断就不容易出问题。

代码语言:javascript复制
void keyPressed() {
  if (key == CODED) {
    if (keyCode == UP) {
      println("click 上");
    } else if (keyCode == DOWN) {
      println("click 下");
    } else if (keyCode == LEFT) {
      println("click 左");
    } else if (keyCode == RIGHT) {
      println("click 右");
    } else if (keyCode == CONTROL) {
      println("click Ctrl");
    } else if (keyCode == ALT) {
      println("click ALT");
    } else if (keyCode == SHIFT) {
      println("click SHIFT");
    }
  } else {
     println("click", key);
  }
}

keyPressed

key 和 keyCode 是在keyPressed()keyReleased()keyTyped() 三个函数中使用,keyPressed 这个变量可以用在 draw 函数中,根据是否按下了键盘,在每帧绘制中实时地处理一些逻辑。

代码语言:javascript复制
void draw() {
  if (keyPressed == true) {
    fill(0);
  } else {
    fill(255);
  }
  rect(25, 25, 50, 50);
}

keyPressed()

每次按下一个键时都会调用一次 keyPressed() 函数。按下的键存储在 key 变量中。

对于非 ASCII 键,我们需要使用 keyCode 变量。

如果我们的程序需要在多平台如 Windows、Unix、Linux、Mac 上运行,还需注意 ENTER 键在 Windows 和 Unix 上常用,而 RETURN 键在 Mac 上使用。小菜测试自己的 Mac 电脑(Monterey 系统)用的其实是 ENTER 键来表示的回车,猜测在之前的系统上可能使用的是 RETURN 键表示。

由于操作系统处理键重复的方式,按住一个键可能会导致多次调用 keyPressed()。重复率由操作系统设置,并且可能在每台计算机上配置不同。关于这点的阐述可以看本文『按键的连续触发问题』

鼠标和键盘事件仅在程序具有 draw() 时才起作用。如果没有 draw(),代码只运行一次,然后停止监听事件。另外还要注意,是不能 noLoop();的,否则键盘事件也会不生效。

keyReleased()

每次释放键时都会调用一次 keyReleased() 函数。

keyTyped()

每次按下一个键时都会调用一次 keyTyped() 函数,但忽略 Ctrl、Shift 和 Alt 等操作键。和 keyPressed() 一样,该函数也会受到操作系统按键处理重复频率的控制。按住一个键可能会导致多次调用 keyTyped()。重复率由操作系统设置,并且可能在每台计算机上配置不同。

按键的连续触发问题

代码语言:javascript复制
void keyPressed() {
  if (key == '1') {
     println("按下1"); 
  }
}

void keyReleased() {
  if (key == '1') {
     println("松开1"); 
  }
}

大家看下这段代码,如果我们按下1马上松开,就会输出

代码语言:javascript复制
按下1
松开1

但是如果我们按下1不松开呢?正常情况下就会不停的输出

代码语言:javascript复制
按下1
按下1
按下1
按下1
按下1
按下1
按下1
按下1
...

由于操作系统处理键重复的方式,按住一个键可能会导致多次调用 keyPressed()。重复率由操作系统设置,并且可能在每台计算机上配置不同。

比如 Mac 电脑上的键盘的按键重复设置,如果关闭了按键重复,那么按住1不放,就只会输出一次。

调整按键重复的速度,可以看到控制台打印的 『按下1』的频率也会不同。小菜电脑配置的按键重复是最快,是因为经常有时候删除代码,要按住退格删除键不松开,让光标更快的进行移动删除。

我们的程序依赖电脑的『按键重复』配置是否关闭来控制按住键盘按键只触发一次,显然不太合理。每台电脑的配置可能是不同的,有的开启,有的关闭,且按键重复频率也可能有差异,这样就会导致程序在不同的电脑上表现不太一致。

而程序要做到通用性,该怎么处理呢?

我们很容易借助一个[HashMap](https://processing.org/reference/HashMap.html[2])来解决这个问题。

HashMap 的用法很简单,这个数据结构和数组有所不同,简单理解,一个坑(键)对应一个值(值),如 {"name": "小菜与老鸟", "sex": "男"}。

这里我们的 HashMap 字典的键的类型是 Character 字符类型,值是 Boolean 布尔类型。

思路:

  • 如果按住了某个键,就将这个键的 key 当成字典的一个键存储起来,对应的值是 TRUE,表示我已经按住了这个键
  • 当第二次按键要进行重复的时候,检测 HashMap 中这个字母的键是否已经已经设置了为 TRUE,如果有,则什么也不做,不会执行按压事件处理(下面例子中的按压事件处理仅仅是打印下按下的键)
  • 当松开按键的时候,要将 HashMap 中的该键的值还原成 FALSE,表示该键已经停止了按压
代码语言:javascript复制
import java.util.Map;
HashMap<Character, Boolean> keys;

void setup() {
  size(400, 400);
  keys = new HashMap<Character, Boolean>();
}

void draw() {
  background(0);
}


void keyPressed() {
  // 如果已经敲击了键盘某个键,且字典里已经存在该 key 的值为 TRUE,则什么也不做
  if (keys.getOrDefault(key, Boolean.FALSE)) {
    // 啥也不做
  } else {
    keys.put(key, Boolean.TRUE);
    println("click "   key);
  }
}

void keyReleased() {
  keys.put(key, Boolean.FALSE);
  println("release "   key);
}

OK。关于键盘的常用处理就说到这,后续有其他补充的,再继续更新。

参考资料

[1]ASCII: https://zh.wikipedia.org/wiki/ASCII

[2]HashMap: https://processing.org/reference/HashMap.html

0 人点赞