<think>
我们需要在之前的动画人物代码基础上增加鼠标点击事件,当用户点击画布时,切换表情。
我们将修改动画代码,添加一个鼠标事件处理函数,该函数将循环切换表情。
同时,为了避免与自动切换表情冲突,我们将取消自动切换,改为完全由用户点击控制。
步骤:
1. 取消原来的定时自动切换表情(即去掉update函数中每40帧切换表情的代码)。
2. 添加一个鼠标事件处理函数,当点击画布时,切换到下一个表情。
3. 在事件处理函数中更新表情并重绘。
我们将修改如下:
- 在初始化后,连接鼠标点击事件。
- 在事件处理函数中,改变当前表情并更新面部特征。
- 同时更新文字标签。
注意:由于我们的动画是持续运行的(人物在移动),所以即使点击事件改变了表情,动画也会继续运行,只是表情改变了。
修改后的代码:
</think># 通过鼠标点击改变动画人物表情的实现
下面我将实现一个Python程序,允许用户通过鼠标点击切换动画人物的表情。这个解决方案基于matplotlib库,通过捕获鼠标事件来改变动画人物的表情。
## 完整实现代码
```python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.patches import Circle, Arc, Ellipse, Polygon, Rectangle
import matplotlib.transforms as transforms
# 创建图形和坐标轴
fig, ax = plt.subplots(figsize=(14, 10))
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
ax.set_aspect('equal')
ax.grid(False)
ax.set_facecolor('#f0f8ff')
ax.set_title('点击表情按钮改变人物表情!', fontsize=18, color='darkblue', pad=20)
# 创建人物各部分
# 头部
head = Ellipse((5, 7), 3.0, 3.5, angle=0, fc='#FFEBCD', ec='#8B4513', lw=2, zorder=10)
ax.add_patch(head)
# 身体和四肢
body = plt.Line2D([5, 5], [4.5, 2.5], lw=8, color='#4682B4', solid_capstyle='round', zorder=5)
left_arm = plt.Line2D([4, 2.5], [4.5, 5], lw=6, color='#4682B4', solid_capstyle='round', zorder=4)
right_arm = plt.Line2D([6, 7.5], [4.5, 5], lw=6, color='#4682B4', solid_capstyle='round', zorder=4)
left_leg = plt.Line2D([4.5, 4], [2.5, 0.5], lw=6, color='#191970', solid_capstyle='round', zorder=4)
right_leg = plt.Line2D([5.5, 6], [2.5, 0.5], lw=6, color='#191970', solid_capstyle='round', zorder=4)
for part in [body, left_arm, right_arm, left_leg, right_leg]:
ax.add_line(part)
# 添加头发
hair_style = [
Arc((5, 8.8), 2.8, 1.0, theta1=0, theta2=180, color='#8B4513', lw=4, zorder=11),
Ellipse((4.2, 8.0), 0.6, 0.8, angle=45, fc='#8B4513', ec='#8B4513', zorder=11),
Ellipse((5.8, 8.0), 0.6, 0.8, angle=-45, fc='#8B4513', ec='#8B4513', zorder=11),
Ellipse((3.8, 7.2), 0.5, 0.7, angle=20, fc='#8B4513', ec='#8B4513', zorder=11),
Ellipse((6.2, 7.2), 0.5, 0.7, angle=-20, fc='#8B4513', ec='#8B4513', zorder=11)
]
for hair in hair_style:
ax.add_patch(hair)
# 表情系统
class FacialExpressionSystem:
def __init__(self, ax, head):
self.ax = ax
self.head = head
self.current_expression = "happy"
self.expressions = {
"happy": self.create_happy_face,
"sad": self.create_sad_face,
"angry": self.create_angry_face,
"surprised": self.create_surprised_face,
"wink": self.create_wink_face,
"confused": self.create_confused_face
}
self.features = []
self.create_initial_face()
def create_initial_face(self):
self.create_happy_face()
def clear_features(self):
for feature in self.features:
try:
if isinstance(feature, plt.Line2D):
if feature in self.ax.lines:
self.ax.lines.remove(feature)
else:
if feature in self.ax.patches:
self.ax.patches.remove(feature)
except:
pass # 如果特征已被移除,则忽略
self.features = []
def create_happy_face(self):
self.clear_features()
# 眼睛
left_eye = Ellipse((4.0, 7.5), 0.8, 0.9, angle=0, fc='white', ec='black', lw=1.5, zorder=12)
right_eye = Ellipse((6.0, 7.5), 0.8, 0.9, angle=0, fc='white', ec='black', lw=1.5, zorder=12)
# 瞳孔
left_pupil = Circle((4.0, 7.5), 0.3, fc='black', zorder=13)
right_pupil = Circle((6.0, 7.5), 0.3, fc='black', zorder=13)
# 微笑的嘴巴
mouth = Arc((5, 6.0), 1.8, 1.2, theta1=0, theta2=180, color='red', lw=3, zorder=12)
self.features.extend([left_eye, right_eye, left_pupil, right_pupil, mouth])
for feature in self.features:
self.ax.add_patch(feature)
def create_sad_face(self):
self.clear_features()
# 眼睛
left_eye = Circle((4.0, 7.5), 0.4, fc='white', ec='black', lw=1.5, zorder=12)
right_eye = Circle((6.0, 7.5), 0.4, fc='white', ec='black', lw=1.5, zorder=12)
# 瞳孔
left_pupil = Circle((4.0, 7.5), 0.2, fc='black', zorder=13)
right_pupil = Circle((6.0, 7.5), 0.2, fc='black', zorder=13)
# 眉毛
left_brow = Arc((4.0, 8.0), 0.8, 0.3, theta1=0, theta2=180, color='#8B4513', lw=3, zorder=12)
right_brow = Arc((6.0, 8.0), 0.8, 0.3, theta1=0, theta2=180, color='#8B4513', lw=3, zorder=12)
# 悲伤的嘴巴
mouth = Arc((5, 5.8), 1.5, 0.8, theta1=180, theta2=360, color='blue', lw=3, zorder=12)
# 眼泪
tear1 = Ellipse((4.2, 7.0), 0.15, 0.3, angle=0, fc='#87CEEB', alpha=0.7, zorder=14)
tear2 = Ellipse((5.8, 7.0), 0.15, 0.3, angle=0, fc='#87CEEB', alpha=0.7, zorder=14)
self.features.extend([left_eye, right_eye, left_pupil, right_pupil,
left_brow, right_brow, mouth, tear1, tear2])
for feature in self.features:
self.ax.add_patch(feature)
def create_angry_face(self):
self.clear_features()
# 眼睛
left_eye = Ellipse((3.8, 7.3), 0.5, 0.6, angle=-15, fc='white', ec='black', lw=1.5, zorder=12)
right_eye = Ellipse((6.2, 7.3), 0.5, 0.6, angle=15, fc='white', ec='black', lw=1.5, zorder=12)
# 瞳孔
left_pupil = Circle((3.8, 7.3), 0.2, fc='black', zorder=13)
right_pupil = Circle((6.2, 7.3), 0.2, fc='black', zorder=13)
# 眉毛
left_brow = plt.Line2D([3.2, 4.4], [7.8, 7.5], lw=4, color='#8B4513', zorder=12)
right_brow = plt.Line2D([5.6, 6.8], [7.5, 7.8], lw=4, color='#8B4513', zorder=12)
# 生气的嘴巴
mouth = plt.Line2D([4.2, 5.8], [5.8, 5.8], lw=4, color='red', zorder=12)
# 怒火符号
anger1 = Polygon([[4.0, 8.5], [4.2, 9.0], [3.8, 9.0]], fc='red', alpha=0.7, zorder=15)
anger2 = Polygon([[6.0, 8.5], [6.2, 9.0], [5.8, 9.0]], fc='red', alpha=0.7, zorder=15)
self.features.extend([left_eye, right_eye, left_pupil, right_pupil,
left_brow, right_brow, mouth, anger1, anger2])
for feature in self.features:
if isinstance(feature, plt.Line2D):
self.ax.add_line(feature)
else:
self.ax.add_patch(feature)
def create_surprised_face(self):
self.clear_features()
# 眼睛
left_eye = Circle((4.0, 7.5), 0.6, fc='white', ec='black', lw=1.5, zorder=12)
right_eye = Circle((6.0, 7.5), 0.6, fc='white', ec='black', lw=1.5, zorder=12)
# 瞳孔
left_pupil = Circle((4.0, 7.5), 0.25, fc='black', zorder=13)
right_pupil = Circle((6.0, 7.5), 0.25, fc='black', zorder=13)
# 眉毛
left_brow = Arc((4.0, 8.0), 1.0, 0.4, theta1=0, theta2=180, color='#8B4513', lw=3, zorder=12)
right_brow = Arc((6.0, 8.0), 1.0, 0.4, theta1=0, theta2=180, color='#8B4513', lw=3, zorder=12)
# 惊讶的嘴巴
mouth = Circle((5, 5.8), 0.6, fc='red', ec='red', alpha=0.7, zorder=12)
self.features.extend([left_eye, right_eye, left_pupil, right_pupil,
left_brow, right_brow, mouth])
for feature in self.features:
self.ax.add_patch(feature)
def create_wink_face(self):
self.clear_features()
# 睁开的左眼
left_eye = Ellipse((4.0, 7.5), 0.8, 0.9, angle=0, fc='white', ec='black', lw=1.5, zorder=12)
left_pupil = Circle((4.0, 7.5), 0.3, fc='black', zorder=13)
# 眨眼的右眼
right_eye = Arc((6.0, 7.5), 0.8, 0.1, theta1=0, theta2=180, color='black', lw=2, zorder=12)
# 微笑的嘴巴
mouth = Arc((5, 6.0), 1.8, 1.2, theta1=0, theta2=180, color='red', lw=3, zorder=12)
# 脸颊红晕
blush_left = Circle((3.5, 6.8), 0.3, fc='#FFB6C1', alpha=0.3, zorder=14)
blush_right = Circle((6.5, 6.8), 0.3, fc='#FFB6C1', alpha=0.3, zorder=14)
self.features.extend([left_eye, left_pupil, right_eye, mouth, blush_left, blush_right])
for feature in self.features:
self.ax.add_patch(feature)
def create_confused_face(self):
self.clear_features()
# 眼睛
left_eye = Ellipse((4.0, 7.5), 0.7, 0.8, angle=-10, fc='white', ec='black', lw=1.5, zorder=12)
right_eye = Ellipse((6.0, 7.5), 0.7, 0.8, angle=10, fc='white', ec='black', lw=1.5, zorder=12)
# 瞳孔
left_pupil = Circle((3.9, 7.5), 0.2, fc='black', zorder=13)
right_pupil = Circle((6.1, 7.5), 0.2, fc='black', zorder=13)
# 眉毛(一高一低)
left_brow = plt.Line2D([3.5, 4.5], [7.9, 7.7], lw=3, color='#8B4513', zorder=12)
right_brow = plt.Line2D([5.5, 6.5], [7.8, 7.9], lw=3, color='#8B4513', zorder=12)
# 困惑的嘴巴(歪嘴)
mouth = Arc((5.2, 6.0), 1.6, 1.0, theta1=190, theta2=350, color='purple', lw=3, zorder=12)
# 问号符号
question_mark = ax.text(5, 5.0, "?", fontsize=30,
ha='center', color='purple', weight='bold', alpha=0.7, zorder=15)
self.features.extend([left_eye, right_eye, left_pupil, right_pupil,
left_brow, right_brow, mouth])
self.ax.add_patch(question_mark)
self.features.append(question_mark)
def set_expression(self, expression):
if expression in self.expressions:
self.current_expression = expression
self.expressions[expression]()
def update_position(self, head_center):
"""更新所有面部特征相对于头部的位置"""
head_offset_x = head_center[0] - 5
head_offset_y = head_center[1] - 7
for feature in self.features:
# 计算相对于头部中心的偏移量
if isinstance(feature, Circle):
center_x = 5 + (feature.center[0] - 5) + head_offset_x
center_y = 7 + (feature.center[1] - 7) + head_offset_y
feature.center = (center_x, center_y)
elif isinstance(feature, Ellipse):
center_x = 5 + (feature.center[0] - 5) + head_offset_x
center_y = 7 + (feature.center[1] - 7) + head_offset_y
feature.center = (center_x, center_y)
elif isinstance(feature, Arc):
center_x = 5 + (feature.center[0] - 5) + head_offset_x
center_y = 7 + (feature.center[1] - 7) + head_offset_y
feature.center = (center_x, center_y)
elif isinstance(feature, Polygon):
# 更新多边形的所有顶点
new_vertices = []
for vertex in feature.get_xy():
new_x = 5 + (vertex[0] - 5) + head_offset_x
new_y = 7 + (vertex[1] - 7) + head_offset_y
new_vertices.append((new_x, new_y))
feature.set_xy(new_vertices)
elif isinstance(feature, Rectangle):
# 更新矩形位置
xy = feature.get_xy()
new_x = 5 + (xy[0] - 5) + head_offset_x
new_y = 7 + (xy[1] - 7) + head_offset_y
feature.set_xy((new_x, new_y))
elif isinstance(feature, plt.Text):
# 更新文本位置
position_x = 5 + (feature.get_position()[0] - 5) + head_offset_x
position_y = 7 + (feature.get_position()[1] - 7) + head_offset_y
feature.set_position((position_x, position_y))
elif isinstance(feature, plt.Line2D):
# 对于线条,更新所有数据点
new_xdata = []
new_ydata = []
for i in range(len(feature.get_xdata())):
new_x = 5 + (feature.get_xdata()[i] - 5) + head_offset_x
new_y = 7 + (feature.get_ydata()[i] - 7) + head_offset_y
new_xdata.append(new_x)
new_ydata.append(new_y)
feature.set_data(new_xdata, new_ydata)
# 初始化面部特征
face = FacialExpressionSystem(ax, head)
# 添加文字标签
text = ax.text(5, 0.5, "你好! 我是动画人物!", fontsize=16,
ha='center', color='purple', weight='bold', zorder=20)
# 表情列表
expressions = list(face.expressions.keys())
expression_index = 0
# 创建表情按钮
class ExpressionButton:
def __init__(self, ax, x, y, width, height, label, expression, color):
self.ax = ax
self.rect = Rectangle((x, y), width, height, fc=color, ec='black', alpha=0.7, zorder=100)
self.label = ax.text(x + width/2, y + height/2, label,
ha='center', va='center', fontsize=12, weight='bold', zorder=101)
self.expression = expression
ax.add_patch(self.rect)
def is_clicked(self, event):
"""检查鼠标点击是否在按钮区域内"""
if event.xdata is None or event.ydata is None:
return False
x, y = self.rect.get_xy()
width = self.rect.get_width()
height = self.rect.get_height()
return (x <= event.xdata <= x + width and
y <= event.ydata <= y + height)
# 创建表情按钮
buttons = [
ExpressionButton(ax, 1.0, 9.0, 1.5, 0.8, "开心", "happy", "#FFD700"),
ExpressionButton(ax, 2.8, 9.0, 1.5, 0.8, "伤心", "sad", "#4169E1"),
ExpressionButton(ax, 4.6, 9.0, 1.5, 0.8, "生气", "angry", "#FF4500"),
ExpressionButton(ax, 6.4, 9.0, 1.5, 0.8, "惊讶", "surprised", "#00CED1"),
ExpressionButton(ax, 8.2, 9.0, 1.5, 0.8, "眨眼", "wink", "#FF69B4"),
ExpressionButton(ax, 1.0, 8.0, 1.5, 0.8, "困惑", "confused", "#9370DB")
]
# 存储所有需要动画更新的对象
artists = [head, body, left_arm, right_arm, left_leg, right_leg, text] + hair_style
for button in buttons:
artists.append(button.rect)
artists.append(button.label)
# 鼠标点击事件处理
def on_click(event):
global expression_index
if event.inaxes != ax:
return
# 检查是否点击了按钮
for button in buttons:
if button.is_clicked(event):
face.set_expression(button.expression)
expression_index = expressions.index(button.expression)
# 更新文字标签
emotions = {
"happy": "我现在很开心! 😊",
"sad": "我感到有点难过... 😢",
"angry": "我生气了! 😠",
"surprised": "哇! 太惊讶了! 😲",
"wink": "嘿嘿,我在眨眼 😉",
"confused": "嗯?这是什么意思? 🤔"
}
text.set_text(emotions[button.expression])
break
# 连接鼠标点击事件
fig.canvas.mpl_connect('button_press_event', on_click)
# 动画更新函数
def update(frame):
# 移动整个身体
offset_x = 2.5 * np.sin(frame * 0.08)
offset_y = 0.5 * np.sin(frame * 0.15)
head_center = [5 + offset_x, 7 + offset_y]
# 更新头部位置
head.center = head_center
# 身体移动
body.set_xdata([head_center[0], head_center[0]])
body.set_ydata([head_center[1] - 2.5, head_center[1] - 4.5])
# 手臂摆动
arm_angle = np.sin(frame * 0.2) * 0.8
left_arm.set_xdata([head_center[0] - 1, head_center[0] - 3 - arm_angle])
left_arm.set_ydata([head_center[1] - 2.5, head_center[1] - 2.0 - arm_angle])
right_arm.set_xdata([head_center[0] + 1, head_center[0] + 3 + arm_angle])
right_arm.set_ydata([head_center[1] - 2.5, head_center[1] - 2.0 + arm_angle])
# 腿部摆动
leg_angle = np.sin(frame * 0.25) * 0.5
left_leg.set_xdata([head_center[0] - 0.5, head_center[0] - 1 - leg_angle])
left_leg.set_ydata([head_center[1] - 4.5, head_center[1] - 6.5 - leg_angle])
right_leg.set_xdata([head_center[0] + 0.5, head_center[0] + 1 + leg_angle])
right_leg.set_ydata([head_center[1] - 4.5, head_center[1] - 6.5 + leg_angle])
# 更新头发位置
for hair in hair_style:
if isinstance(hair, Ellipse):
center_x = 5 + (hair.center[0] - 5) + offset_x
center_y = 7 + (hair.center[1] - 7) + offset_y
hair.center = (center_x, center_y)
elif isinstance(hair, Arc):
center_x = 5 + (hair.center[0] - 5) + offset_x
center_y = 7 + (hair.center[1] - 7) + offset_y
hair.center = (center_x, center_y)
# 更新面部表情位置
face.update_position(head_center)
# 更新文字位置
text.set_x(head_center[0])
text.set_y(0.5 + offset_y)
# 返回所有需要更新的对象
return artists + face.features
# 创建动画
animation = FuncAnimation(fig, update, frames=360,
interval=40, blit=True)
# 添加描述文本
desc = ax.text(5, 9.5, "点击上方按钮改变我的表情!",
ha='center', fontsize=14, color='darkred', weight='bold', zorder=100)
artists.append(desc)
plt.tight_layout()
plt.show()
```
## 实现原理详解
### 1. 表情按钮设计
```python
class ExpressionButton:
def __init__(self, ax, x, y, width, height, label, expression, color):
self.ax = ax
self.rect = Rectangle((x, y), width, height, fc=color, ec='black', alpha=0.7, zorder=100)
self.label = ax.text(x + width/2, y + height/2, label,
ha='center', va='center', fontsize=12, weight='bold', zorder=101)
self.expression = expression
ax.add_patch(self.rect)
def is_clicked(self, event):
"""检查鼠标点击是否在按钮区域内"""
if event.xdata is None or event.ydata is None:
return False
x, y = self.rect.get_xy()
width = self.rect.get_width()
height = self.rect.get_height()
return (x <= event.xdata <= x + width and
y <= event.ydata <= y + height)
```
- **按钮结构**:每个按钮由矩形背景和文本标签组成
- **位置检测**:`is_clicked()`方法检测鼠标点击是否在按钮区域内
- **参数设计**:
- `x, y`:按钮位置
- `width, height`:按钮尺寸
- `label`:按钮显示文本
- `expression`:对应的表情名称
- `color`:按钮背景色
### 2. 创建表情按钮
```python
buttons = [
ExpressionButton(ax, 1.0, 9.0, 1.5, 0.8, "开心", "happy", "#FFD700"),
ExpressionButton(ax, 2.8, 9.0, 1.5, 0.8, "伤心", "sad", "#4169E1"),
ExpressionButton(ax, 4.6, 9.0, 1.5, 0.8, "生气", "angry", "#FF4500"),
ExpressionButton(ax, 6.4, 9.0, 1.5, 0.8, "惊讶", "surprised", "#00CED1"),
ExpressionButton(ax, 8.2, 9.0, 1.5, 0.8, "眨眼", "wink", "#FF69B4"),
ExpressionButton(ax, 1.0, 8.0, 1.5, 0.8, "困惑", "confused", "#9370DB")
]
```
- 创建6个表情按钮,分别对应不同表情
- 按钮排列在画布顶部,便于用户点击
- 每个按钮使用不同颜色增强视觉区分度
### 3. 鼠标事件处理
```python
def on_click(event):
global expression_index
if event.inaxes != ax:
return
# 检查是否点击了按钮
for button in buttons:
if button.is_clicked(event):
face.set_expression(button.expression)
expression_index = expressions.index(button.expression)
# 更新文字标签
emotions = {
"happy": "我现在很开心! 😊",
"sad": "我感到有点难过... 😢",
"angry": "我生气了! 😠",
"surprised": "哇! 太惊讶了! 😲",
"wink": "嘿嘿,我在眨眼 😉",
"confused": "嗯?这是什么意思? 🤔"
}
text.set_text(emotions[button.expression])
break
# 连接鼠标点击事件
fig.canvas.mpl_connect('button_press_event', on_click)
```
- **事件绑定**:使用`mpl_connect`将点击事件绑定到`on_click`处理函数
- **按钮检测**:遍历所有按钮,检测点击位置是否在按钮区域内
- **表情切换**:当检测到按钮点击时,调用`face.set_expression()`切换表情
- **文本更新**:同时更新底部文字描述,反映当前表情状态
### 4. 动画更新优化
```python
def update(frame):
# ...(动画更新逻辑)...
# 返回所有需要更新的对象
return artists + face.features
```
- **动画对象管理**:使用`artists`列表存储所有需要更新的图形对象
- **动态添加**:在更新函数中返回`artists + face.features`确保表情特征被正确更新
- **性能优化**:只更新需要变化的元素,提高动画性能
## 使用说明
1. 运行程序后,会出现一个动画人物窗口
2. 窗口顶部有6个表情按钮:"开心"、"伤心"、"生气"、"惊讶"、"眨眼"、"困惑"
3. 点击任意按钮,动画人物会立即切换到对应的表情
4. 人物会持续移动(左右摆动),表情会跟随头部移动
5. 底部文字会显示当前表情对应的描述
## 扩展功能建议
1. **按钮交互动画**:添加按钮悬停效果(如颜色变化、大小变化)
2. **更多表情**:增加更多表情选项(如疲惫、兴奋、害怕等)
3. **声音反馈**:添加点击时的音效或表情对应的语音
4. **自定义表情**:允许用户创建自定义表情