You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将MIDI JSON音符转换为节奏游戏的x、y坐标与speed参数

类似Guitar Hero节奏游戏的实现方向与MIDI转换方法

一、MIDI音符数据预处理

先从你拿到的MIDI JSON里提取有效触发事件:

  • 过滤出所有type: 'noteOn'且velocity > 0的事件(力度为0的事件常用来替代noteOff,直接忽略)
  • 如果MIDI包含多轨道,只保留主旋律/吉他轨道的事件,过滤鼓点、贝斯等无关轨道

代码示例:

// 假设你的MIDI JSON结构
const midiJson = {
  header: { ppq: 480, bpm: 120 }, // PPQ是每四分音符的tick数,BPM是歌曲速度
  tracks: [{ events: [/* 包含noteOn、noteOff等事件 */] }]
};

// 提取有效音符事件
const validNotes = midiJson.tracks.flatMap(track => 
  track.events.filter(e => e.type === 'noteOn' && e.velocity > 0)
);

二、将MIDI时间转换为实际播放毫秒

MIDI里的time字段是tick数,需要转换成相对于歌曲开头的毫秒数,用来保证音画同步:

const { ppq, bpm } = midiJson.header;
// 计算每个tick对应的毫秒数
const msPerTick = (60000 / bpm) / ppq;

// 给每个音符添加触发时间(即音符到达判定线的时间)
const timedNotes = validNotes.map(note => ({
  ...note,
  triggerTimeMs: note.time * msPerTick
}));

三、映射x坐标(轨道位置)

Guitar Hero核心是5个按键轨道,把MIDI音高(noteNumber,范围21-108)分配到对应轨道,生成x坐标:

  • 把21-108的音高范围平分为5段,每段对应一个轨道
  • 用屏幕宽度比例计算x值,适配不同设备

代码示例:

import { Dimensions } from 'react-native';
const { width: screenWidth } = Dimensions.get('window');
const trackCount = 5;
const trackWidth = screenWidth / trackCount;

// 根据音高计算对应轨道的x坐标(轨道中心位置)
const getTrackX = (noteNumber) => {
  const rangeSize = (108 - 21) / trackCount;
  const trackIndex = Math.floor((noteNumber - 21) / rangeSize);
  return trackIndex * trackWidth + trackWidth / 2;
};

const notesWithX = timedNotes.map(note => ({
  ...note,
  x: getTrackX(note.noteNumber)
}));

四、计算y坐标与下落速度

Guitar Hero用固定下落速度保证视觉一致性,设置规则如下:

  • 初始y设为屏幕外顶部(比如-50,避免刚出现就触发判定)
  • 固定speed值,根据你想要的下落时长调整(比如让音符从出现到到达判定线用2秒)

代码示例:

const { height: screenHeight } = Dimensions.get('window');
const startY = -50; // 音符初始位置(屏幕外顶部)
const judgeLineY = screenHeight - 100; // 判定线位置(距离底部100px)
const fallDuration = 2000; // 音符从出现到到达判定线的时长(毫秒)
const baseSpeed = (judgeLineY - startY) / fallDuration; // 单位:px/ms

// 最终转换为你需要的数组格式
const balls = notesWithX.map(note => ({
  x: note.x,
  y: startY,
  speed: baseSpeed,
  triggerTimeMs: note.triggerTimeMs // 保留触发时间用于后续判定逻辑
}));

React Native核心实现要点

  • 游戏主循环:用requestAnimationFrame实现帧更新,每帧根据时间差更新音符的y位置:y += speed * deltaTime(deltaTime是上一帧到当前帧的毫秒数)
  • 音画同步:用React Native的Audio模块播放MP3,实时获取当前播放时间,和音符的triggerTimeMs比对,处理判定逻辑
  • 判定逻辑:当玩家点击轨道时,检查当前时间是否在triggerTimeMs的误差范围内(比如±100ms),如果是则判定为命中
  • 性能优化:只渲染屏幕范围内的音符,避免过多节点影响性能

内容的提问来源于stack exchange,提问作者Alen.Toma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 02:20:40