交互


鼠标数据

Processing 变量 mouseX 和 mouseY(注意 X 和 Y 是大写)分别存储了鼠标指针相对于显示窗口左上角原点的横坐标和纵坐标.

当程序刚启动时,mouseX 和 mouseY 的值都是 0。如果鼠标指针移动到显示窗口内,这两个变量就会被设置为当前鼠标的位置。如果鼠标在窗口最左侧,mouseX 的值为 0,向右移动时数值会增大;如果鼠标在窗口最上方,mouseY 的值为 0,向下移动时数值会增大。如果你的程序没有 draw() 函数,或者在 setup() 里调用了 noLoop(),那么 mouseX 和 mouseY 的值将始终为 0。

鼠标位置最常见的用途是用来控制屏幕上图形元素的位置。更有趣的是,如果让图形元素与鼠标位置之间建立不同的关系(而不仅仅是“跟随”鼠标),就能实现丰富的交互效果。比如:对 mouseX 和 mouseY 加减常数,可以让图形与鼠标保持固定的偏移关系;而乘除操作则可以让图形与鼠标之间的关系随位置动态变化。

def setup():
          size(100, 100)
          noStroke()
      
      def draw():
          background(126)
          ellipse(mouseX, mouseY, 33, 33)
def setup():
  size(100, 100)
  noStroke()

def draw():
  background(126)
  ellipse(mouseX, 16, 33, 33)   # Top circle
  ellipse(mouseX/2, 50, 33, 33)   # Middle circle
  ellipse(mouseX*2, 84, 33, 33)   # Bottom circle

要让鼠标的值“反向”,只需用画布宽度减去 mouseX,画布高度减去 mouseY。这样,鼠标在画布左上角时,圆会出现在右下角;鼠标在右下角时,圆会出现在左上角。

def setup():
  size(100, 100)
noStroke()

def draw():
  x = mouseX
  y = mouseY
  ix = width - mouseX   # 反向X
  iy = height - mouseY  # 反向Y
  background(126)
  fill(255, 150)        # 半透明白色
  ellipse(x, height/2, y, y)
  fill(0, 159)          # 半透明黑色
  ellipse(ix, height/2, iy, iy)

Processing 变量 pmouseX 和 pmouseY 用于记录上一帧鼠标的位置。 如果鼠标没动,这两个值和当前的 mouseX、mouseY 一样;如果鼠标移动很快,两者的差值就会很大。 你可以用如下代码在控制台输出两帧之间的横坐标变化:


  def draw():
    frameRate(12)
    println(pmouseX - mouseX)

要可视化鼠标的移动轨迹,可以用一条线连接上一帧和当前帧的鼠标位置:


  def setup():
  size(100, 100)
  strokeWeight(8)


def draw():
  background(204)
  line(mouseX, mouseY, pmouseX, pmouseY)

将 mouseX 和 mouseY 变量与 if 结构一起使用,以允许光标选择屏幕区域。以下示例演示了光标在显示窗口的不同区域之间进行选择。第一个将屏幕分成两半,第二个将屏幕分成三部分。


  def setup():
    size(100, 100)
    noStroke()
    fill(0)

def draw():
    background(204)
    if (mouseX < 50):
        rect(0, 0, 50, 100)   # Left
    else:
        rect(50, 0, 50, 100)   # Righ

  def setup():
  size(100, 100)
  noStroke()
  fill(0)

def draw():
  background(204)
  if (mouseX < 33):
      rect(0, 0, 33, 100)   # Left
  elif (mouseX < 66):
      rect(33, 0, 33, 100)   # Middle
  else:
      rect(66, 0, 33, 100)   # Right

使用带有 if 结构的逻辑运算符来选择屏幕的矩形区域。如以下示例所示,当关系表达式 用于测试矩形的每个边(左、右、上、下),这些边与逻辑 AND 连接起来,只有当光标位于矩形内时,整个关系表达式才为 true。


  def setup():
  size(100, 100)
  noStroke()
  fill(0)

def draw():
  background(204)
  if ((mouseX > 40) and (mouseX < 80) and
    (mouseY > 20) and (mouseY < 80)):
      fill(255)
  else:
      fill(0)
  rect(40, 20, 40, 60)

该代码问:“光标是否位于左边缘的右侧,光标是否位于右边缘的左侧,光标是否位于顶部边缘之外,光标是否位于底部上方?下一个示例的代码询问一组类似的问题,并将它们与关键字 else 组合在一起,以确定哪个定义区域包含游标。


  def setup():
  size(100, 100)
  noStroke()
  fill(0)

def draw():
  background(204)
  if ((mouseX <= 50) and (mouseY <= 50)):
      rect(0, 0, 50, 50)   # Upper-left
  elif ((mouseX <= 50) and (mouseY > 50)):
      rect(0, 50, 50, 50)   # Lower-left
  elif ((mouseX > 50) and (mouseY <= 50)):
      rect(50, 0, 50, 50)   # Upper-right
  else:
      rect(50, 50, 50, 50)   # Lower-right

鼠标按钮(Mouse buttons)

计算机鼠标及相关输入设备通常有一到三个按键。Processing 可以通过 mousePressed 和 mouseButton 变量检测鼠标按键的状态。结合鼠标位置和按键状态,可以实现丰富的交互,比如:当鼠标指针在某个图标上并按下按钮时,可以选中该图标,然后拖动到屏幕的其他位置。 mousePressed:如果有任意鼠标按键被按下,值为 true,否则为 false。只要松开所有按键,mousePressed 就会变回 false。 mouseButton:表示最近一次被按下的鼠标按键,可能的值有 LEFT(左键)、CENTER(中键/滚轮)、RIGHT(右键)。注意,mouseButton 会一直保持上一次按下的按钮类型,直到你按下另一个键。 这两个变量可以单独使用,也可以组合使用,实现不同的交互逻辑。例如: 只用 mousePressed 可以判断“是否有鼠标按下”。 结合 mouseButton 可以区分是左键、右键还是中键,从而实现不同的操作。


  def setup():
  size(100, 100)

def draw():
  background(204)
  if (mousePressed == True):
      fill(255)   # White
  else:
      fill(0)   # Black
  rect(25, 25, 50, 50)

  def setup():
  size(100, 100)

def draw():
  if (mouseButton == LEFT):
      fill(0)   # Black
  elif (mouseButton == RIGHT):
      fill(255)   # White
  else: 
      fill(126)   # Gray

  rect(25, 25, 50, 50)

  def setup():
    size(100, 100)

def draw():
    if (mousePressed == True):
        if (mouseButton == LEFT):
            fill(0)   # Black
        elif (mouseButton == RIGHT):
            fill(255)   # White
    else:
        fill(126)   # Gray

    rect(25, 25, 50, 50)

键盘数据(Keyboard data)

Processing 会记录最近被按下的键,以及当前是否有键被按下。 布尔变量 keyPressed 用于表示当前是否有键被按下:如果有任意键被按下,keyPressed 的值为 true,否则为 false。 你可以在 if 结构中使用 keyPressed 变量,这样只有在有键被按下时,某些代码才会执行。

keyPressed 变量在你按住键盘时会一直保持为 true,只有当你松开所有按键时才会变为 false。


  def setup():
  size(100, 100)
  strokeWeight(4)

def draw():
  background(204)
  if (keyPressed):   # If the key is pressed,
      line(20, 20, 80, 80)   # draw a line
  else:   # Otherwise,
      rect(40, 40, 20, 20)   # draw a rectangle

  x = 20
  def setup():
      size(100, 100)
      strokeWeight(4)
  
  def draw():
      global x
      background(204)
      if (keyPressed):  # If the key is pressed
          x += 1   # add 1 to x 
      line(x, 20, x-60, 80)

key 变量用于存储最近一次被按下的单个字母或数字字符。 具体来说,key 保存的是“最近按下的那个键”的字符值(比如 'a'、'1'、'B' 等)。 你可以用 text() 函数把 key 变量的内容直接显示在屏幕上。你可以用 text() 函数把 key 变量的内容直接显示在屏幕上。例如:


  def setup():
    size(100, 100)
    textSize(60)

def draw():
    background(0)
    text(key, 20, 75)   # Draw at coordinate (20,75)

key 变量可以用来判断是否按下了某个特定的键。例如,下面的表达式 key == 'A' 用于检测当前按下的键是否为大写字母 A。 这里的单引号 'A' 表示字符类型(char),而不是字符串类型(String)。如果你写成 key == "A"(双引号),会报错,因为双引号表示字符串,不能直接和字符类型比较。 通常会结合 keyPressed 变量和逻辑与(and)运算符一起使用,确保只有在按下某个特定键时才执行某些操作


def setup():
  size(100, 100)
  strokeWeight(4)

def draw():
  background(204)
  # If the 'A' key is pressed draw a line
  if ((keyPressed) and (key == 'A')):
      line(50, 25, 50, 75)
  else:   # Otherwise, draw an ellipse
      ellipse(50, 50, 50, 50)

上例只能识别大写字母 A,如果你按下小写 a 是不会触发的。 如果你想同时检测大写和小写,可以用逻辑或(or)扩展条件表达式。例如:


  if (keyPressed) and ((key == 'a') or (key == 'A')):
    # 只有按下 a 或 A 时才会执行这里的代码

这里用到了逻辑或(or)运算符,把两个条件连接起来,只要有一个为真就会执行。

每个字符在计算机中都有一个对应的数值,这就是 ASCII 码(可以查 ASCII 表)。 在 Python Mode 下,可以用 ord() 函数把字符转换为对应的 ASCII 数值。例如: ord('A') 的结果是 65 ord('1') 的结果是 49 你可以用这个数值来控制图形的位置、颜色等视觉属性。


    def setup():
    size(100, 100)
    stroke(0)

def draw():
    if (keyPressed):
        x = ord(key) - 32
        line(x, 0, x, height)

    angle = 0

    def setup():
        size(100, 100)
        fill(0)
    
    
    def draw():
        global angle
        background(204)
        if (keyPressed):
            if ((ord(key) >= 32) and (ord(key) <= 126)):
                # If the key is alphanumeric, use its value as an angle
                angle = (ord(key) - 32) * 3
    
        arc(50, 50, 66, 66, 0, radians(angle))

编码键(Coded keys)

除了可以读取数字、字母和符号等普通按键的值,Processing 还可以读取其他特殊按键(coded keys),比如方向键(箭头键)、Alt、Control、Shift、Backspace、Tab、Enter、Return、Escape 和 Delete 等。 变量 keyCode 用于存储 ALT、CONTROL、SHIFT、UP、DOWN、LEFT 和 RIGHT 这些特殊按键的常量值。 在判断按下的是哪一个特殊按键之前,首先要判断当前按下的键是不是“coded key”。表达式 key == CODED 为真时,说明当前按下的是特殊按键,否则为假。 需要注意的是,虽然 BACKSPACE、TAB、ENTER、RETURN、ESC 和 DELETE 这些键不是字母数字键,但它们属于 ASCII 规范中的一部分,并不会被识别为 coded key。 如果你要做跨平台的项目,需要注意:在 PC 和 UNIX 系统上通常用 Enter 键,而在 Mac 上用 Return 键。为了保证你的程序在所有平台上都能正常工作,建议同时检测 Enter 和 Return


    y = 35

    def setup():
      size(100, 100)
    
    def draw():
        global y
        background(204)
        line(10, 50, 90, 50)
        if (key == CODED):
            if (keyCode == UP):
                y = 20
            elif (keyCode == DOWN):
                y = 50
        else:
            y = 35
        rect(25, y, 50, 30)

事件(Events)

有一类被称为“事件(event)”的函数,可以在发生某些动作(比如按下键盘、移动鼠标)时,改变程序的正常流程。事件就像是对程序正常流程的“礼貌打断”。 键盘按下和鼠标移动等事件会被暂存,直到 draw() 结束后再处理,这样就不会打扰当前正在进行的绘制操作。 每当对应的事件发生时,事件函数里的代码会被执行一次。例如,当鼠标按钮被按下时,mousePressed() 函数里的代码会被执行一次,只有再次按下鼠标按钮时才会再次执行。这种机制让你可以在不影响主程序流程的情况下,独立地读取鼠标和键盘产生的数据

鼠标事件

鼠标相关的事件函数有:mousePressed()、mouseReleased()、mouseMoved() 和 mouseDragged(): mousePressed():当鼠标按钮被按下时,函数内的代码会被执行一次。 mouseReleased():当鼠标按钮被松开时,函数内的代码会被执行一次。 mouseMoved():当鼠标移动时,函数内的代码会被执行一次。 mouseDragged():当鼠标在按下状态下移动时,函数内的代码会被执行一次。 mousePressed() 函数和 mousePressed 变量的作用不同。 mousePressed 变量在鼠标按钮按下时为 true,松开时为 false,可以在 draw() 里用来持续检测鼠标是否按下,从而让某段代码在鼠标按下期间一直运行。 而 mousePressed() 函数只会在鼠标按钮刚被按下的那一刻执行一次,非常适合用来触发一次性操作,比如点击清屏。 例如,下面这个例子中,每次点击鼠标,背景色都会变得更亮。你可以在自己的电脑上运行这个例子,体验鼠标点击带来的变化。


  gray = 0

  def setup():
      size(100, 100)
  
  def draw():
      global gray
      background(gray)
  
  def mousePressed():
      global gray
      gray += 20

下面这个例子和上面的例子类似,但这次 gray 变量是在 mouseReleased() 事件函数中设置的。mouseReleased() 会在每次鼠标按钮被松开时调用一次。 这种差别只有在实际运行程序并点击鼠标按钮时才能体会到。你可以长时间按住鼠标按钮,会发现背景的灰度值只有在你松开鼠标按钮的那一刻才会发生变化。


  gray = 0

def setup():
    size(100, 100)

def draw():
    global gray
    background(gray)

def mouseReleased():
    global gray
    gray += 20

通常情况下,不建议在事件函数(如 mousePressed())内部进行绘制操作,但在某些特定情况下是可以的。在事件函数里绘制之前,必须仔细考虑程序的执行流程。 在下面这个例子中,方块是在 mousePressed() 事件函数里绘制的,并且它们会一直保留在屏幕上,因为 draw() 里没有调用 background()。 但如果在 draw() 里用了 background(),那么在事件函数里绘制的图形只会在屏幕上显示一帧(默认是 1/60 秒),很快就会被清除。 实际上,你会注意到这个例子的 draw() 里什么都没有,但它必须存在,这样 Processing 才会持续监听事件。如果 draw() 里加了 background(),那么在事件函数里画的矩形就只会一闪而过,然后消失。


  def setup():
  size(100, 100)
  fill(0, 102)

def draw():
  pass
# pass just tells Python mode to not do anything here, draw() keeps the program running

def mousePressed():
  rect(mouseX, mouseY, 33, 33)

当鼠标位置发生变化时,mouseMoved() 和 mouseDragged() 事件函数中的代码会被执行。 当鼠标移动且没有按下任何按钮时,mouseMoved() 里的代码会在每一帧结束时运行。 当鼠标移动且有按钮被按下时,mouseDragged() 里的代码会在每一帧结束时运行。 如果鼠标在连续的帧之间保持不动,这两个函数里的代码都不会被执行。 在下面这个例子中: 当没有按下鼠标按钮时,灰色圆圈会跟随鼠标移动(由 mouseMoved() 控制)。 当按下鼠标按钮并移动时,黑色圆圈会跟随鼠标移动(由 mouseDragged() 控制)。


    #Initialize moveX, moveY, dragX, dragY off screen

    moveX = moveY = dragX = dragY = -20
    
    def setup():
        size(100, 100)
        noStroke()
    
    def draw():
        global moveX, moveY, dragX, dragY
        background(204)
        fill(0)
        ellipse(dragX, dragY, 33, 33)   # Black circle
        fill(153)
        ellipse(moveX, moveY, 33, 33)   # Gray circle
    
    def mouseMoved():  # Move gray circle
        global moveX, moveY
        moveX = mouseX
        moveY = mouseY
    
    def mouseDragged():   # Move black circle
        global dragX, dragY
        dragX = mouseX
        dragY = mouseY
  

键盘事件

每次按下键盘上的按键,都会通过键盘事件函数 keyPressed() 和 keyReleased() 被注册: keyPressed():当任意键被按下时,函数内的代码会被执行一次 keyReleased():当任意键被松开时,函数内的代码会被执行一次 每次按下一个键,keyPressed() 里的代码就会执行一次。在这个函数内部,你可以检测具体按下了哪个键,并根据需要使用这个值。 如果你长时间按住某个键,keyPressed() 里的代码可能会被快速连续地多次执行,因为大多数操作系统会自动重复触发按键事件。开始重复的延迟和重复的速度会因电脑和键盘设置的不同而有所差异。 在下面这个例子中,当按下 T 键时,布尔变量 drawT 的值会从 false 变为 true,这会让 draw() 里的代码开始绘制矩形。


    drawT = False

    def setup():
        size(100, 100)
noStroke()

    def draw():
        global drawT
        background(204)
        if (drawT):
            rect(20, 20, 60, 20)
            rect(39, 40, 22, 45)
      
    
    def keyPressed():
        global drawT
        if ((key == 'T') or (key == 't')):
            drawT = True
  

每次松开一个键时,keyReleased() 代码块中的内容会被执行一次。 下面这个例子是在前面代码的基础上改进的:每次松开键盘时,布尔变量 drawT 会被重新设为 false,这样 draw() 里的图形就会停止显示。


    drawT = False

    def setup():
      size(100, 100)
      noStroke()
    
    def draw():
        global drawT
        background(204)
        if (drawT):
            rect(20, 20, 60, 20)
            rect(39, 40, 22, 45)
      
    
    def keyPressed():
        global drawT
        if ((key == 'T') or (key == 't')):
            drawT = True
    
    def keyReleased():
        global drawT
        drawT = False
  

事件流程

如前所述,包含 draw() 的程序每秒会在屏幕上显示 60 帧。可以用 frameRate() 函数设置每秒显示的最大帧数,也可以用 noLoop() 函数让 draw() 停止循环。 另外还有 loop() 和 redraw() 函数,配合鼠标和键盘事件函数可以实现更多控制。 如果程序通过 noLoop() 暂停了,调用 loop() 可以让它恢复运行。因为当程序被 noLoop() 暂停时,只有事件函数(如鼠标、键盘事件)还会继续响应,这时可以在事件函数里调用 loop(),让 draw() 继续运行。 下面这个例子中,每次点击鼠标,draw() 会连续运行大约两秒,然后程序再次暂停。 redraw() 函数会让 draw() 只运行一次,然后程序就会暂停。这在不需要持续刷新画面时非常有用。 下面这个例子中,每次点击鼠标,draw() 只会运行一次。

简要总结:
  • noLoop():让 draw() 停止循环
  • loop():恢复 draw() 的循环
  • redraw():只让 draw() 执行一次
  • 事件函数在 noLoop() 状态下依然会响应,可以在事件里用 loop()redraw() 控制刷新

  frame = 0

  def setup():
      size(100, 100)
  
  def draw():
      global frame
      if (frame > 120):   # If 120 frames since the mouse
          noLoop()   # was pressed, stop the program
          background(0)   # and turn the background black.
      else:   # Otherwise, set the background
          background(204)   # to light gray and draw lines
          line(mouseX, 0, mouseX, 100)   # at the mouse position
          line(0, mouseY, 100, mouseY)
          frame += 1
  
  def mousePressed():
      global frame
      loop()
      frame = 0

redraw() 函数在 draw() 中运行一次代码,然后停止执行。当显示器不需要持续更新时,这很有帮助。以下示例在每次按下鼠标按钮时在 draw() 中运行一次代码。


  def setup():
    size(100, 100)
    noLoop()

def draw():
    background(204)
    line(mouseX, 0, mouseX, 100)
    line(0, mouseY, 100, mouseY)

def mousePressed():
    redraw()    # Run the code in draw one time

光标图标(Cursor icon)

可以使用 noCursor() 函数隐藏光标,也可以用 cursor() 函数将光标设置为不同的图标或图片。当运行 noCursor() 时,鼠标指针移动到显示窗口内会消失。 如果你希望在软件中给出光标位置的反馈,可以用 mouseX 和 mouseY 变量自己绘制一个自定义光标。 只要运行了 noCursor(),光标就会一直隐藏,直到再次调用 cursor() 函数才会重新显示。 你可以为 cursor() 函数添加参数,将光标更换为其他图标或图片。可以加载并使用图片,也可以使用一些自带的选项:ARROW、CROSS、HAND、MOVE、TEXT 和 WAIT。 这些光标图标属于你的操作系统,不同的电脑上显示效果可能会有所不同。

光标控制要点:
  • noCursor():隐藏鼠标光标
  • cursor():恢复光标或切换为其他图标/图片
  • 可选参数:ARROWCROSSHANDMOVETEXTWAIT,也可自定义图片
  • 不同操作系统的光标样式可能不同
  • 可用 mouseXmouseY 自己绘制自定义光标

  def setup():
  size(100, 100)
  strokeWeight(7)
  noCursor()

def draw():
  background(204)
  ellipse(mouseX, mouseY, 10, 10)

如果运行noCursor(),则程序运行时光标将隐藏,直到运行cursor(()函数将其显示出来。


  def setup():
    size(100, 100)
    noCursor()

def draw():
    background(204)
    if (mousePressed):
        cursor()

向 cursor() 函数添加一个参数,将其更改为另一个图标或图像。加载并使用图像,或使用自描述性选项,即 ARROW、CROSS、HAND、MOVE、TEXT 和 WAIT。


  def setup():
    size(100, 100)

def draw():
    background(204)
    if (mousePressed):
        cursor(HAND)    # Draw cursor as hand
    else:
        cursor(CROSS)
    line(mouseX, 0, mouseX, height)
    line(0, mouseY, height, mouseY)