
OpenBCI-实战二:脑波控制小游戏开发
文章目录
引言
在上一篇文章中,我们学习了如何用脑波控制LED灯。现在,让我们更进一步,开发一个脑波控制的小游戏!
游戏是BCI技术非常有趣的应用场景,可以让用户在娱乐中体验"意念控制"的神奇效果。本文将带你从零开始开发一个基于注意力的小游戏------脑波小球跳跃。
项目概述
游戏介绍
脑波小球跳跃是一个简单但有趣的小游戏:
- 玩家通过调节注意力水平来控制小球的跳跃高度
- 注意力越高,跳跃越高
- 躲避障碍物,获取尽可能高的分数
系统架构
#mermaid-svg-NUbktpRvF1vNKC2G{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NUbktpRvF1vNKC2G .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NUbktpRvF1vNKC2G .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NUbktpRvF1vNKC2G .error-icon{fill:#552222;}#mermaid-svg-NUbktpRvF1vNKC2G .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NUbktpRvF1vNKC2G .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NUbktpRvF1vNKC2G .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NUbktpRvF1vNKC2G .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NUbktpRvF1vNKC2G .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NUbktpRvF1vNKC2G .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NUbktpRvF1vNKC2G .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NUbktpRvF1vNKC2G .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NUbktpRvF1vNKC2G .marker.cross{stroke:#333333;}#mermaid-svg-NUbktpRvF1vNKC2G svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NUbktpRvF1vNKC2G p{margin:0;}#mermaid-svg-NUbktpRvF1vNKC2G .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NUbktpRvF1vNKC2G .cluster-label text{fill:#333;}#mermaid-svg-NUbktpRvF1vNKC2G .cluster-label span{color:#333;}#mermaid-svg-NUbktpRvF1vNKC2G .cluster-label span p{background-color:transparent;}#mermaid-svg-NUbktpRvF1vNKC2G .label text,#mermaid-svg-NUbktpRvF1vNKC2G span{fill:#333;color:#333;}#mermaid-svg-NUbktpRvF1vNKC2G .node rect,#mermaid-svg-NUbktpRvF1vNKC2G .node circle,#mermaid-svg-NUbktpRvF1vNKC2G .node ellipse,#mermaid-svg-NUbktpRvF1vNKC2G .node polygon,#mermaid-svg-NUbktpRvF1vNKC2G .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NUbktpRvF1vNKC2G .rough-node .label text,#mermaid-svg-NUbktpRvF1vNKC2G .node .label text,#mermaid-svg-NUbktpRvF1vNKC2G .image-shape .label,#mermaid-svg-NUbktpRvF1vNKC2G .icon-shape .label{text-anchor:middle;}#mermaid-svg-NUbktpRvF1vNKC2G .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NUbktpRvF1vNKC2G .rough-node .label,#mermaid-svg-NUbktpRvF1vNKC2G .node .label,#mermaid-svg-NUbktpRvF1vNKC2G .image-shape .label,#mermaid-svg-NUbktpRvF1vNKC2G .icon-shape .label{text-align:center;}#mermaid-svg-NUbktpRvF1vNKC2G .node.clickable{cursor:pointer;}#mermaid-svg-NUbktpRvF1vNKC2G .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NUbktpRvF1vNKC2G .arrowheadPath{fill:#333333;}#mermaid-svg-NUbktpRvF1vNKC2G .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NUbktpRvF1vNKC2G .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NUbktpRvF1vNKC2G .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NUbktpRvF1vNKC2G .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NUbktpRvF1vNKC2G .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NUbktpRvF1vNKC2G .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NUbktpRvF1vNKC2G .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NUbktpRvF1vNKC2G .cluster text{fill:#333;}#mermaid-svg-NUbktpRvF1vNKC2G .cluster span{color:#333;}#mermaid-svg-NUbktpRvF1vNKC2G div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NUbktpRvF1vNKC2G .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NUbktpRvF1vNKC2G rect.text{fill:none;stroke-width:0;}#mermaid-svg-NUbktpRvF1vNKC2G .icon-shape,#mermaid-svg-NUbktpRvF1vNKC2G .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NUbktpRvF1vNKC2G .icon-shape p,#mermaid-svg-NUbktpRvF1vNKC2G .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NUbktpRvF1vNKC2G .icon-shape .label rect,#mermaid-svg-NUbktpRvF1vNKC2G .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NUbktpRvF1vNKC2G .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NUbktpRvF1vNKC2G .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NUbktpRvF1vNKC2G :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} OpenBCI设备
EEG数据采集
特征提取
注意力预测
游戏引擎
渲染画面
用户反馈
碰撞检测
分数计算
技术栈
| 组件 | 技术 | 说明 |
|---|---|---|
| 游戏引擎 | Pygame | Python游戏库 |
| EEG采集 | BrainFlow | 统一BCI API |
| 机器学习 | scikit-learn | 注意力检测模型 |
| 可视化 | Matplotlib | 可选:实时信号显示 |
环境搭建
安装依赖
bash
# 安装Pygame游戏库
pip install pygame
# 安装BrainFlow
pip install brainflow
# 安装scikit-learn
pip install scikit-learn
# 安装其他依赖
pip install numpy matplotlib
Pygame初始化
python
import pygame
import sys
# 初始化Pygame
pygame.init()
# 设置窗口大小
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("脑波小球跳跃")
# 颜色定义
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
BLUE = (0, 0, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
游戏核心模块
小球类
python
class Ball:
def __init__(self, x, y):
self.x = x
self.y = y
self.width = 40
self.height = 40
self.velocity_y = 0
self.gravity = 0.5
self.jump_force = 0
self.max_jump_force = 15
self.on_ground = True
def update(self, attention_level):
# 根据注意力水平计算跳跃力
if self.on_ground and attention_level > 0.7:
self.jump_force = attention_level * self.max_jump_force
self.velocity_y = -self.jump_force
self.on_ground = False
# 应用重力
self.velocity_y += self.gravity
self.y += self.velocity_y
# 地面检测
if self.y >= HEIGHT - self.height:
self.y = HEIGHT - self.height
self.velocity_y = 0
self.on_ground = True
def draw(self, screen):
pygame.draw.ellipse(screen, BLUE, (self.x, self.y, self.width, self.height))
障碍物类
python
class Obstacle:
def __init__(self, x):
self.x = x
self.y = HEIGHT - 80
self.width = 30
self.height = 40 + pygame.time.get_ticks() % 40 # 随机高度
self.speed = 5
def update(self):
self.x -= self.speed
def draw(self, screen):
pygame.draw.rect(screen, RED, (self.x, self.y, self.width, self.height))
def off_screen(self):
return self.x < -self.width
游戏主循环
python
def game_loop(model, board):
ball = Ball(100, HEIGHT - 40)
obstacles = []
score = 0
game_over = False
clock = pygame.time.Clock()
last_obstacle_time = pygame.time.get_ticks()
while True:
screen.fill(WHITE)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_r and game_over:
game_loop(model, board)
if not game_over:
# 获取EEG数据并预测注意力
try:
data = board.get_current_board_data(256)
if data.shape[1] >= 256:
eeg_data = data[1:9, :256].T
prediction, prob = model.predict(eeg_data)
attention_level = prob[1] # 获取高注意力概率
else:
attention_level = 0.5
except:
attention_level = 0.5
# 更新小球
ball.update(attention_level)
# 生成障碍物
current_time = pygame.time.get_ticks()
if current_time - last_obstacle_time > 1500:
obstacles.append(Obstacle(WIDTH))
last_obstacle_time = current_time
# 更新障碍物
for obstacle in obstacles[:]:
obstacle.update()
if obstacle.off_screen():
obstacles.remove(obstacle)
score += 10
# 碰撞检测
ball_rect = pygame.Rect(ball.x, ball.y, ball.width, ball.height)
for obstacle in obstacles:
obstacle_rect = pygame.Rect(obstacle.x, obstacle.y, obstacle.width, obstacle.height)
if ball_rect.colliderect(obstacle_rect):
game_over = True
break
# 绘制游戏元素
ball.draw(screen)
for obstacle in obstacles:
obstacle.draw(screen)
# 显示分数和注意力水平
font = pygame.font.Font(None, 36)
score_text = font.render(f"分数: {score}", True, BLACK)
attention_text = font.render(f"注意力: {attention_level:.2f}", True, BLACK)
screen.blit(score_text, (10, 10))
screen.blit(attention_text, (10, 50))
# 注意力指示器
indicator_height = int(attention_level * 200)
pygame.draw.rect(screen, GREEN, (WIDTH - 30, HEIGHT - indicator_height - 50, 20, indicator_height))
else:
# 游戏结束画面
font = pygame.font.Font(None, 72)
game_over_text = font.render("游戏结束!", True, RED)
score_text = font.render(f"最终分数: {score}", True, BLACK)
restart_text = font.render("按 R 重新开始", True, BLUE)
screen.blit(game_over_text, (WIDTH//2 - 150, HEIGHT//2 - 100))
screen.blit(score_text, (WIDTH//2 - 180, HEIGHT//2))
screen.blit(restart_text, (WIDTH//2 - 200, HEIGHT//2 + 100))
pygame.display.flip()
clock.tick(60)
注意力检测模型
模型定义
python
import numpy as np
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
import joblib
class AttentionModel:
def __init__(self):
self.scaler = StandardScaler()
self.model = SVC(kernel='rbf', C=10, gamma='scale', probability=True)
def extract_features(self, eeg_data):
"""提取EEG特征"""
features = []
# 时间域特征
for channel in eeg_data.T:
features.append(np.mean(channel))
features.append(np.std(channel))
features.append(np.var(channel))
features.append(np.max(channel))
features.append(np.min(channel))
features.append(np.mean(np.abs(channel)))
# 频率域特征
fft_vals = np.fft.fft(eeg_data, axis=0)
fft_freqs = np.fft.fftfreq(len(eeg_data), d=1/250)
# Alpha波 (8-12Hz)
alpha_mask = (fft_freqs >= 8) & (fft_freqs <= 12)
alpha_power = np.mean(np.abs(fft_vals[alpha_mask]))
# Beta波 (13-30Hz)
beta_mask = (fft_freqs >= 13) & (fft_freqs <= 30)
beta_power = np.mean(np.abs(fft_vals[beta_mask]))
# Theta波 (4-7Hz)
theta_mask = (fft_freqs >= 4) & (fft_freqs <= 7)
theta_power = np.mean(np.abs(fft_vals[theta_mask]))
features.append(alpha_power)
features.append(beta_power)
features.append(theta_power)
features.append(beta_power / (alpha_power + theta_power + 1e-10))
return np.array(features)
def train(self, X, y):
"""训练模型"""
X_scaled = self.scaler.fit_transform(X)
self.model.fit(X_scaled, y)
def predict(self, eeg_data):
"""预测注意力水平"""
features = self.extract_features(eeg_data)
features = features.reshape(1, -1)
X_scaled = self.scaler.transform(features)
prediction = self.model.predict(X_scaled)
probability = self.model.predict_proba(X_scaled)
return prediction[0], probability[0]
训练脚本
python
# 生成模拟训练数据
np.random.seed(42)
n_samples = 1000
n_channels = 8
n_timepoints = 256
low_attention = []
high_attention = []
for _ in range(n_samples//2):
# 低注意力:alpha波多
data = np.random.normal(loc=4, scale=1.5, size=(n_timepoints, n_channels))
low_attention.append(AttentionModel().extract_features(data))
for _ in range(n_samples//2):
# 高注意力:beta波多
data = np.random.normal(loc=7, scale=1.5, size=(n_timepoints, n_channels))
high_attention.append(AttentionModel().extract_features(data))
X = np.vstack([low_attention, high_attention])
y = np.array([0]*(n_samples//2) + [1]*(n_samples//2))
# 训练模型
model = AttentionModel()
model.train(X, y)
# 保存模型
joblib.dump(model, 'game_attention_model.pkl')
print("模型训练完成并保存")
完整游戏集成
主程序
python
import pygame
import sys
import numpy as np
from brainflow.board_shim import BoardShim, BrainFlowInputParams, BoardIds
import joblib
def main():
# 初始化Pygame
pygame.init()
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("脑波小球跳跃")
# 颜色定义
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
BLUE = (0, 0, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
# 加载模型
model = joblib.load('game_attention_model.pkl')
# 初始化OpenBCI
params = BrainFlowInputParams()
board = BoardShim(BoardIds.GANGLION_BOARD.value, params)
try:
board.prepare_session()
board.start_stream()
print("OpenBCI连接成功")
except Exception as e:
print(f"OpenBCI连接失败: {e}")
print("将使用模拟数据运行游戏")
board = None
class Ball:
def __init__(self, x, y):
self.x = x
self.y = y
self.width = 40
self.height = 40
self.velocity_y = 0
self.gravity = 0.5
self.on_ground = True
def update(self, attention_level):
if self.on_ground and attention_level > 0.6:
jump_force = attention_level * 12
self.velocity_y = -jump_force
self.on_ground = False
self.velocity_y += self.gravity
self.y += self.velocity_y
if self.y >= HEIGHT - self.height:
self.y = HEIGHT - self.height
self.velocity_y = 0
self.on_ground = True
def draw(self, screen):
pygame.draw.ellipse(screen, BLUE, (self.x, self.y, self.width, self.height))
class Obstacle:
def __init__(self, x):
self.x = x
self.y = HEIGHT - 80
self.width = 30
self.height = 40 + np.random.randint(0, 40)
self.speed = 5
def update(self):
self.x -= self.speed
def draw(self, screen):
pygame.draw.rect(screen, RED, (self.x, self.y, self.width, self.height))
def off_screen(self):
return self.x < -self.width
ball = Ball(100, HEIGHT - 40)
obstacles = []
score = 0
game_over = False
clock = pygame.time.Clock()
last_obstacle_time = pygame.time.get_ticks()
simulated_attention = 0.5
attention_direction = 1
while True:
screen.fill(WHITE)
for event in pygame.event.get():
if event.type == pygame.QUIT:
if board:
board.stop_stream()
board.release_session()
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_r and game_over:
ball = Ball(100, HEIGHT - 40)
obstacles = []
score = 0
game_over = False
if not game_over:
# 获取注意力水平
if board:
try:
data = board.get_current_board_data(256)
if data.shape[1] >= 256:
eeg_data = data[1:9, :256].T
_, prob = model.predict(eeg_data)
attention_level = prob[1]
else:
attention_level = simulated_attention
except:
attention_level = simulated_attention
else:
# 模拟注意力变化
simulated_attention += 0.01 * attention_direction
if simulated_attention >= 1.0 or simulated_attention <= 0.3:
attention_direction *= -1
attention_level = simulated_attention
# 更新小球
ball.update(attention_level)
# 生成障碍物
current_time = pygame.time.get_ticks()
if current_time - last_obstacle_time > 1200:
obstacles.append(Obstacle(WIDTH))
last_obstacle_time = current_time
# 更新障碍物
for obstacle in obstacles[:]:
obstacle.update()
if obstacle.off_screen():
obstacles.remove(obstacle)
score += 10
# 碰撞检测
ball_rect = pygame.Rect(ball.x, ball.y, ball.width, ball.height)
for obstacle in obstacles:
obstacle_rect = pygame.Rect(obstacle.x, obstacle.y, obstacle.width, obstacle.height)
if ball_rect.colliderect(obstacle_rect):
game_over = True
break
# 绘制
ball.draw(screen)
for obstacle in obstacles:
obstacle.draw(screen)
# UI显示
font = pygame.font.Font(None, 36)
score_text = font.render(f"分数: {score}", True, BLACK)
attention_text = font.render(f"注意力: {attention_level:.2f}", True, BLACK)
screen.blit(score_text, (10, 10))
screen.blit(attention_text, (10, 50))
# 注意力条
bar_width = 20
bar_height = int(attention_level * 200)
pygame.draw.rect(screen, GREEN, (WIDTH - bar_width - 10, HEIGHT - bar_height - 50, bar_width, bar_height))
else:
font = pygame.font.Font(None, 72)
game_over_text = font.render("游戏结束!", True, RED)
score_text = font.render(f"最终分数: {score}", True, BLACK)
restart_text = font.render("按 R 重新开始", True, BLUE)
screen.blit(game_over_text, (WIDTH//2 - 150, HEIGHT//2 - 100))
screen.blit(score_text, (WIDTH//2 - 180, HEIGHT//2))
screen.blit(restart_text, (WIDTH//2 - 200, HEIGHT//2 + 100))
pygame.display.flip()
clock.tick(60)
if __name__ == "__main__":
main()
游戏流程图
#mermaid-svg-5p4YfQ4Px6VQq5ID{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5p4YfQ4Px6VQq5ID .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5p4YfQ4Px6VQq5ID .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5p4YfQ4Px6VQq5ID .error-icon{fill:#552222;}#mermaid-svg-5p4YfQ4Px6VQq5ID .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5p4YfQ4Px6VQq5ID .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5p4YfQ4Px6VQq5ID .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5p4YfQ4Px6VQq5ID .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5p4YfQ4Px6VQq5ID .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5p4YfQ4Px6VQq5ID .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5p4YfQ4Px6VQq5ID .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5p4YfQ4Px6VQq5ID .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5p4YfQ4Px6VQq5ID .marker.cross{stroke:#333333;}#mermaid-svg-5p4YfQ4Px6VQq5ID svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5p4YfQ4Px6VQq5ID p{margin:0;}#mermaid-svg-5p4YfQ4Px6VQq5ID .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5p4YfQ4Px6VQq5ID .cluster-label text{fill:#333;}#mermaid-svg-5p4YfQ4Px6VQq5ID .cluster-label span{color:#333;}#mermaid-svg-5p4YfQ4Px6VQq5ID .cluster-label span p{background-color:transparent;}#mermaid-svg-5p4YfQ4Px6VQq5ID .label text,#mermaid-svg-5p4YfQ4Px6VQq5ID span{fill:#333;color:#333;}#mermaid-svg-5p4YfQ4Px6VQq5ID .node rect,#mermaid-svg-5p4YfQ4Px6VQq5ID .node circle,#mermaid-svg-5p4YfQ4Px6VQq5ID .node ellipse,#mermaid-svg-5p4YfQ4Px6VQq5ID .node polygon,#mermaid-svg-5p4YfQ4Px6VQq5ID .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5p4YfQ4Px6VQq5ID .rough-node .label text,#mermaid-svg-5p4YfQ4Px6VQq5ID .node .label text,#mermaid-svg-5p4YfQ4Px6VQq5ID .image-shape .label,#mermaid-svg-5p4YfQ4Px6VQq5ID .icon-shape .label{text-anchor:middle;}#mermaid-svg-5p4YfQ4Px6VQq5ID .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5p4YfQ4Px6VQq5ID .rough-node .label,#mermaid-svg-5p4YfQ4Px6VQq5ID .node .label,#mermaid-svg-5p4YfQ4Px6VQq5ID .image-shape .label,#mermaid-svg-5p4YfQ4Px6VQq5ID .icon-shape .label{text-align:center;}#mermaid-svg-5p4YfQ4Px6VQq5ID .node.clickable{cursor:pointer;}#mermaid-svg-5p4YfQ4Px6VQq5ID .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5p4YfQ4Px6VQq5ID .arrowheadPath{fill:#333333;}#mermaid-svg-5p4YfQ4Px6VQq5ID .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5p4YfQ4Px6VQq5ID .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5p4YfQ4Px6VQq5ID .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5p4YfQ4Px6VQq5ID .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5p4YfQ4Px6VQq5ID .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5p4YfQ4Px6VQq5ID .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5p4YfQ4Px6VQq5ID .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5p4YfQ4Px6VQq5ID .cluster text{fill:#333;}#mermaid-svg-5p4YfQ4Px6VQq5ID .cluster span{color:#333;}#mermaid-svg-5p4YfQ4Px6VQq5ID div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5p4YfQ4Px6VQq5ID .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5p4YfQ4Px6VQq5ID rect.text{fill:none;stroke-width:0;}#mermaid-svg-5p4YfQ4Px6VQq5ID .icon-shape,#mermaid-svg-5p4YfQ4Px6VQq5ID .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5p4YfQ4Px6VQq5ID .icon-shape p,#mermaid-svg-5p4YfQ4Px6VQq5ID .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5p4YfQ4Px6VQq5ID .icon-shape .label rect,#mermaid-svg-5p4YfQ4Px6VQq5ID .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5p4YfQ4Px6VQq5ID .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5p4YfQ4Px6VQq5ID .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5p4YfQ4Px6VQq5ID :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
是
否
否
是
是
否
开始
初始化游戏
加载模型
连接OpenBCI
连接成功?
使用模拟数据
启动数据流
游戏主循环
获取注意力
更新小球位置
生成障碍物
碰撞检测
碰撞发生?
游戏结束
更新分数
渲染画面
用户退出?
清理资源
结束
用户重新开始?
游戏界面设计
界面布局
#mermaid-svg-4rzWcDbi9lRhwcmA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4rzWcDbi9lRhwcmA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4rzWcDbi9lRhwcmA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4rzWcDbi9lRhwcmA .error-icon{fill:#552222;}#mermaid-svg-4rzWcDbi9lRhwcmA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4rzWcDbi9lRhwcmA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4rzWcDbi9lRhwcmA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4rzWcDbi9lRhwcmA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4rzWcDbi9lRhwcmA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4rzWcDbi9lRhwcmA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4rzWcDbi9lRhwcmA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4rzWcDbi9lRhwcmA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4rzWcDbi9lRhwcmA .marker.cross{stroke:#333333;}#mermaid-svg-4rzWcDbi9lRhwcmA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4rzWcDbi9lRhwcmA p{margin:0;}#mermaid-svg-4rzWcDbi9lRhwcmA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4rzWcDbi9lRhwcmA .cluster-label text{fill:#333;}#mermaid-svg-4rzWcDbi9lRhwcmA .cluster-label span{color:#333;}#mermaid-svg-4rzWcDbi9lRhwcmA .cluster-label span p{background-color:transparent;}#mermaid-svg-4rzWcDbi9lRhwcmA .label text,#mermaid-svg-4rzWcDbi9lRhwcmA span{fill:#333;color:#333;}#mermaid-svg-4rzWcDbi9lRhwcmA .node rect,#mermaid-svg-4rzWcDbi9lRhwcmA .node circle,#mermaid-svg-4rzWcDbi9lRhwcmA .node ellipse,#mermaid-svg-4rzWcDbi9lRhwcmA .node polygon,#mermaid-svg-4rzWcDbi9lRhwcmA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4rzWcDbi9lRhwcmA .rough-node .label text,#mermaid-svg-4rzWcDbi9lRhwcmA .node .label text,#mermaid-svg-4rzWcDbi9lRhwcmA .image-shape .label,#mermaid-svg-4rzWcDbi9lRhwcmA .icon-shape .label{text-anchor:middle;}#mermaid-svg-4rzWcDbi9lRhwcmA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4rzWcDbi9lRhwcmA .rough-node .label,#mermaid-svg-4rzWcDbi9lRhwcmA .node .label,#mermaid-svg-4rzWcDbi9lRhwcmA .image-shape .label,#mermaid-svg-4rzWcDbi9lRhwcmA .icon-shape .label{text-align:center;}#mermaid-svg-4rzWcDbi9lRhwcmA .node.clickable{cursor:pointer;}#mermaid-svg-4rzWcDbi9lRhwcmA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4rzWcDbi9lRhwcmA .arrowheadPath{fill:#333333;}#mermaid-svg-4rzWcDbi9lRhwcmA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4rzWcDbi9lRhwcmA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4rzWcDbi9lRhwcmA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4rzWcDbi9lRhwcmA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4rzWcDbi9lRhwcmA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4rzWcDbi9lRhwcmA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4rzWcDbi9lRhwcmA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4rzWcDbi9lRhwcmA .cluster text{fill:#333;}#mermaid-svg-4rzWcDbi9lRhwcmA .cluster span{color:#333;}#mermaid-svg-4rzWcDbi9lRhwcmA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4rzWcDbi9lRhwcmA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4rzWcDbi9lRhwcmA rect.text{fill:none;stroke-width:0;}#mermaid-svg-4rzWcDbi9lRhwcmA .icon-shape,#mermaid-svg-4rzWcDbi9lRhwcmA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4rzWcDbi9lRhwcmA .icon-shape p,#mermaid-svg-4rzWcDbi9lRhwcmA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4rzWcDbi9lRhwcmA .icon-shape .label rect,#mermaid-svg-4rzWcDbi9lRhwcmA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4rzWcDbi9lRhwcmA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4rzWcDbi9lRhwcmA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4rzWcDbi9lRhwcmA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 游戏窗口
左上角
分数显示
左下角
注意力数值
右侧
注意力条
中央
游戏区域
注意力条设计
#mermaid-svg-PYxLmZrbzIjxPcNs{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PYxLmZrbzIjxPcNs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PYxLmZrbzIjxPcNs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PYxLmZrbzIjxPcNs .error-icon{fill:#552222;}#mermaid-svg-PYxLmZrbzIjxPcNs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PYxLmZrbzIjxPcNs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PYxLmZrbzIjxPcNs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PYxLmZrbzIjxPcNs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PYxLmZrbzIjxPcNs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PYxLmZrbzIjxPcNs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PYxLmZrbzIjxPcNs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PYxLmZrbzIjxPcNs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PYxLmZrbzIjxPcNs .marker.cross{stroke:#333333;}#mermaid-svg-PYxLmZrbzIjxPcNs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PYxLmZrbzIjxPcNs p{margin:0;}#mermaid-svg-PYxLmZrbzIjxPcNs .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PYxLmZrbzIjxPcNs .cluster-label text{fill:#333;}#mermaid-svg-PYxLmZrbzIjxPcNs .cluster-label span{color:#333;}#mermaid-svg-PYxLmZrbzIjxPcNs .cluster-label span p{background-color:transparent;}#mermaid-svg-PYxLmZrbzIjxPcNs .label text,#mermaid-svg-PYxLmZrbzIjxPcNs span{fill:#333;color:#333;}#mermaid-svg-PYxLmZrbzIjxPcNs .node rect,#mermaid-svg-PYxLmZrbzIjxPcNs .node circle,#mermaid-svg-PYxLmZrbzIjxPcNs .node ellipse,#mermaid-svg-PYxLmZrbzIjxPcNs .node polygon,#mermaid-svg-PYxLmZrbzIjxPcNs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PYxLmZrbzIjxPcNs .rough-node .label text,#mermaid-svg-PYxLmZrbzIjxPcNs .node .label text,#mermaid-svg-PYxLmZrbzIjxPcNs .image-shape .label,#mermaid-svg-PYxLmZrbzIjxPcNs .icon-shape .label{text-anchor:middle;}#mermaid-svg-PYxLmZrbzIjxPcNs .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PYxLmZrbzIjxPcNs .rough-node .label,#mermaid-svg-PYxLmZrbzIjxPcNs .node .label,#mermaid-svg-PYxLmZrbzIjxPcNs .image-shape .label,#mermaid-svg-PYxLmZrbzIjxPcNs .icon-shape .label{text-align:center;}#mermaid-svg-PYxLmZrbzIjxPcNs .node.clickable{cursor:pointer;}#mermaid-svg-PYxLmZrbzIjxPcNs .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PYxLmZrbzIjxPcNs .arrowheadPath{fill:#333333;}#mermaid-svg-PYxLmZrbzIjxPcNs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PYxLmZrbzIjxPcNs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PYxLmZrbzIjxPcNs .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PYxLmZrbzIjxPcNs .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PYxLmZrbzIjxPcNs .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PYxLmZrbzIjxPcNs .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PYxLmZrbzIjxPcNs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PYxLmZrbzIjxPcNs .cluster text{fill:#333;}#mermaid-svg-PYxLmZrbzIjxPcNs .cluster span{color:#333;}#mermaid-svg-PYxLmZrbzIjxPcNs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PYxLmZrbzIjxPcNs .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PYxLmZrbzIjxPcNs rect.text{fill:none;stroke-width:0;}#mermaid-svg-PYxLmZrbzIjxPcNs .icon-shape,#mermaid-svg-PYxLmZrbzIjxPcNs .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PYxLmZrbzIjxPcNs .icon-shape p,#mermaid-svg-PYxLmZrbzIjxPcNs .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PYxLmZrbzIjxPcNs .icon-shape .label rect,#mermaid-svg-PYxLmZrbzIjxPcNs .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PYxLmZrbzIjxPcNs .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PYxLmZrbzIjxPcNs .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PYxLmZrbzIjxPcNs :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 0-0.3
0.3-0.7
0.7-1.0
注意力条
低注意力:红色
中注意力:黄色
高注意力:绿色
跳跃力:小
跳跃力:中
跳跃力:大
测试与优化
测试策略
- 无BCI设备测试:使用模拟数据运行游戏
- 有BCI设备测试:佩戴电极帽进行实际测试
- 性能测试:确保帧率稳定在60FPS
优化技巧
python
# 优化1:降低数据采样频率
def get_attention_simple(model, board, sample_size=128):
"""简化版注意力获取"""
data = board.get_current_board_data(sample_size)
if data.shape[1] >= sample_size:
eeg_data = data[1:5, :sample_size].T # 使用前4个通道
_, prob = model.predict(eeg_data)
return prob[1]
return 0.5
# 优化2:添加平滑处理
class SmoothAttention:
def __init__(self, window_size=5):
self.window = []
self.window_size = window_size
def add(self, value):
self.window.append(value)
if len(self.window) > self.window_size:
self.window.pop(0)
return np.mean(self.window)
# 优化3:动态调整游戏难度
def adjust_difficulty(score):
"""根据分数调整难度"""
base_speed = 5
speed_increase = score // 100 * 0.5
obstacle_interval = max(800, 1500 - score // 50 * 50)
return base_speed + speed_increase, obstacle_interval
扩展功能
多人模式
python
class MultiPlayerGame:
def __init__(self):
self.players = []
self.winner = None
def add_player(self, name, model, board):
player = {
'name': name,
'model': model,
'board': board,
'ball': Ball(100, HEIGHT - 40),
'score': 0
}
self.players.append(player)
def update(self):
for player in self.players:
# 获取注意力
data = player['board'].get_current_board_data(256)
if data.shape[1] >= 256:
eeg_data = data[1:9, :256].T
_, prob = player['model'].predict(eeg_data)
attention = prob[1]
player['ball'].update(attention)
道具系统
python
class PowerUp:
def __init__(self, x, type):
self.x = x
self.y = HEIGHT - 60
self.width = 30
self.height = 30
self.type = type # 'speed', 'shield', 'score'
self.speed = 4
def draw(self, screen):
colors = {'speed': YELLOW, 'shield': BLUE, 'score': GREEN}
pygame.draw.rect(screen, colors[self.type], (self.x, self.y, self.width, self.height))
def apply(self, player):
if self.type == 'speed':
player.obstacle_speed -= 1
elif self.type == 'shield':
player.shield = True
pygame.time.set_timer(pygame.USEREVENT, 5000)
elif self.type == 'score':
player.score += 50
完整代码清单
game.py
python
import pygame
import sys
import numpy as np
from brainflow.board_shim import BoardShim, BrainFlowInputParams, BoardIds
import joblib
def main():
pygame.init()
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("脑波小球跳跃")
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
BLUE = (0, 0, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
try:
model = joblib.load('game_attention_model.pkl')
except:
print("模型加载失败,使用模拟模式")
model = None
params = BrainFlowInputParams()
board = None
try:
board = BoardShim(BoardIds.GANGLION_BOARD.value, params)
board.prepare_session()
board.start_stream()
print("OpenBCI连接成功")
except Exception as e:
print(f"OpenBCI连接失败: {e}")
class Ball:
def __init__(self, x, y):
self.x, self.y = x, y
self.width, self.height = 40, 40
self.velocity_y = 0
self.gravity = 0.5
self.on_ground = True
def update(self, attention):
if self.on_ground and attention > 0.6:
self.velocity_y = -attention * 12
self.on_ground = False
self.velocity_y += self.gravity
self.y += self.velocity_y
if self.y >= HEIGHT - self.height:
self.y = HEIGHT - self.height
self.velocity_y = 0
self.on_ground = True
def draw(self, screen):
pygame.draw.ellipse(screen, BLUE, (self.x, self.y, self.width, self.height))
class Obstacle:
def __init__(self, x):
self.x = x
self.y = HEIGHT - 80
self.width = 30
self.height = 40 + np.random.randint(0, 40)
self.speed = 5
def update(self):
self.x -= self.speed
def draw(self, screen):
pygame.draw.rect(screen, RED, (self.x, self.y, self.width, self.height))
ball = Ball(100, HEIGHT - 40)
obstacles = []
score, game_over = 0, False
clock = pygame.time.Clock()
last_obstacle = pygame.time.get_ticks()
sim_attn, sim_dir = 0.5, 1
while True:
screen.fill(WHITE)
for event in pygame.event.get():
if event.type == pygame.QUIT:
if board:
board.stop_stream()
board.release_session()
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN and event.key == pygame.K_r and game_over:
ball = Ball(100, HEIGHT - 40)
obstacles, score, game_over = [], 0, False
if not game_over:
if board and model:
try:
data = board.get_current_board_data(256)
attn = model.predict(data[1:9, :256].T)[1][1] if data.shape[1] >= 256 else sim_attn
except:
attn = sim_attn
else:
sim_attn += 0.01 * sim_dir
if sim_attn >= 1.0 or sim_attn <= 0.3:
sim_dir *= -1
attn = sim_attn
ball.update(attn)
if pygame.time.get_ticks() - last_obstacle > 1200:
obstacles.append(Obstacle(WIDTH))
last_obstacle = pygame.time.get_ticks()
for obs in obstacles[:]:
obs.update()
if obs.x < -obs.width:
obstacles.remove(obs)
score += 10
ball_rect = pygame.Rect(ball.x, ball.y, ball.width, ball.height)
for obs in obstacles:
if ball_rect.colliderect(pygame.Rect(obs.x, obs.y, obs.width, obs.height)):
game_over = True
break
ball.draw(screen)
for obs in obstacles:
obs.draw(screen)
font = pygame.font.Font(None, 36)
screen.blit(font.render(f"分数: {score}", True, BLACK), (10, 10))
screen.blit(font.render(f"注意力: {attn:.2f}", True, BLACK), (10, 50))
pygame.draw.rect(screen, GREEN, (WIDTH-30, HEIGHT-int(attn*200)-50, 20, int(attn*200)))
else:
font = pygame.font.Font(None, 72)
screen.blit(font.render("游戏结束!", True, RED), (WIDTH//2-150, HEIGHT//2-100))
screen.blit(font.render(f"最终分数: {score}", True, BLACK), (WIDTH//2-180, HEIGHT//2))
screen.blit(font.render("按 R 重新开始", True, BLUE), (WIDTH//2-200, HEIGHT//2+100))
pygame.display.flip()
clock.tick(60)
if __name__ == "__main__":
main()
总结
通过这个项目,你学会了:
- 游戏开发:使用Pygame创建简单的2D游戏
- BCI集成:将脑电信号与游戏控制结合
- 实时反馈:根据注意力水平动态调整游戏行为
- 用户体验:设计直观的游戏界面和反馈机制
这是一个很好的起点,你可以继续扩展这个游戏,添加更多功能和玩法!
思考与练习
- 思考:如何优化游戏的响应速度?
- 练习:添加音效系统,根据游戏状态播放不同音效
- 练习:实现排行榜功能,保存最高分
系列文章导航:
- 第1篇:什么是脑机接口(BCI)?OpenBCI入门指南
- 第2篇:OpenBCI硬件选型:Cyton vs Ganglion对比分析
- 第3篇:搭建你的第一个脑电采集系统
- 第4篇:OpenBCI GUI使用指南:从安装到数据采集
- 第5篇:脑电信号基础:EEG波形解读与频段分析
- 第6篇:BrainFlow SDK完全指南:统一API实现多设备兼容
- 第7篇:Python与OpenBCI:实时脑电信号采集实战
- 第8篇:信号预处理:滤波、去噪与伪迹去除
- 第9篇:特征提取技术:频域分析与时频分析
- 第10篇:机器学习入门:从脑电信号到模式识别
- 第11篇:实战一:脑波控制LED灯(基础)
- 第12篇:实战二:脑波控制小游戏开发(当前)
- 第13篇:实战三:注意力监测系统
声明:本文仅供学习交流,文中涉及的硬件设备和软件工具请从官方渠道获取。脑机接口技术涉及生物医学领域,实际应用请遵循相关法律法规和伦理规范。
