OpenBCI-实战二:脑波控制小游戏开发

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
注意力条
低注意力:红色
中注意力:黄色
高注意力:绿色
跳跃力:小
跳跃力:中
跳跃力:大


测试与优化

测试策略

  1. 无BCI设备测试:使用模拟数据运行游戏
  2. 有BCI设备测试:佩戴电极帽进行实际测试
  3. 性能测试:确保帧率稳定在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()

总结

通过这个项目,你学会了:

  1. 游戏开发:使用Pygame创建简单的2D游戏
  2. BCI集成:将脑电信号与游戏控制结合
  3. 实时反馈:根据注意力水平动态调整游戏行为
  4. 用户体验:设计直观的游戏界面和反馈机制

这是一个很好的起点,你可以继续扩展这个游戏,添加更多功能和玩法!


思考与练习

  1. 思考:如何优化游戏的响应速度?
  2. 练习:添加音效系统,根据游戏状态播放不同音效
  3. 练习:实现排行榜功能,保存最高分

系列文章导航


声明:本文仅供学习交流,文中涉及的硬件设备和软件工具请从官方渠道获取。脑机接口技术涉及生物医学领域,实际应用请遵循相关法律法规和伦理规范。


相关推荐
smj2302_796826521 小时前
解决leetcode第3948题字典序最大的MEX数组
python·算法·leetcode
程序大视界1 小时前
【Python系列课程】Pandas(六):数据读写——CSV与Excel文件操作
python·excel·pandas
weixin_407443872 小时前
OCR材料信息提取工具(附件中含代码和数据)
人工智能·python·计算机视觉·ocr
码农阿强2 小时前
PixVerse 全系列视频生成模型技术架构详解 + Python 基于 StartAPI.top 接口实战调用
python·ai·架构·音视频·ai编程
Smilecoc2 小时前
风控评分卡模型原理与应用(四):WOE编码的单调性
python
许彰午2 小时前
04_Java数组操作全解
java·开发语言·python
废弃的小码农2 小时前
APP测试--adb使用介绍
python·测试工具·adb
曲幽2 小时前
你的FastAPI又在服务器上“跑不起来”了?来,今天咱把打包这件事彻底聊透
linux·windows·python·docker·fastapi·web·pyinstaller·nssm·services
AI玫瑰助手2 小时前
Python函数:局部变量与全局变量的作用域
开发语言·python·信息可视化