はじめに
ReactNativeで太⚫︎の達人を作ろうとしたけどスペックが足りなくてMACBookがフリーズした ので、強いボードの自作PCでも買えるまで誰かに希望を託そうと思います。
楽器音オブジェクトのプーリングにより連打に耐えられるようコーディングした方の検証もしたかったのですがスペックの問題で出来なかった為、簡単なスケルトンの紹介に留めます。
この記事で多分役に立つこと
- 2025年3月4日時点でのReactNativeCLIを用いたアプリケーションの初期化法
- 音ゲームのアルゴリズム初歩スケルトン
- 簡単なおまけ(ウェルカムページをHelloworldにする)
ReactNativeアプリの初期化
ReactNativeの初期化に最近まで使用されていたreact-native initコマンドは、2024年12月31日をもって非推奨となりました。
今は、下記書式が最新の初期化コマンドです。(2025年3月4日時点)
npx @react-native-community/cli init AppName
React Native 0.71以降はTypeScriptがデフォルトで含まれているため、TypeScriptテンプレートを指定する必要はありません。 これで勝手にTypeScript仕様になります。逆にJavaScriptで書きたい人は明示的にJS仕様で初期化も可能なようです。
恐らくReactNativeを使うくらいの人であればそもそもあまり基本的すぎる部分は重複するかと思いますので細かい操作は省略します。
私がAndroidユーザーなのもあり、今回はAndroidで紹介しますが、初期化時に最新のコマンドではcocoapodsを入れるかどうかも丁寧に聞かれるので楽になったかと思います。
音ゲーム位でしたらプラットフォーム固有のコンポーネントもそんなに大差ないのでクロスプラットフォーム開発もしやすいです。
音ゲームのアルゴリズム初歩スケルトン
著作権的なとこが怖いので敢えて楽器のファイル名を変えたりしています。
察してください。。
MusicApp/
│── android/ # Android用ネイティブコード(React Native CLIのみ)
│── ios/ # iOS用ネイティブコード(React Native CLIのみ)
│── assets/ # 画像や音声ファイル
│ ├── images/
│ │ ├── piano.png
│ │ ├── drum.png
│ ├── sounds/
│ │ ├── piano.mp3
│ │ ├── drum.mp3
│── data # 画像や音声ファイル
│ ├── notes.ts # 譜面データ
│── src/
│ ├── components/
│ │ ├── Note.tsx # 譜面の音符
│ │ ├── DrumPad.tsx # 叩くボタン
│ ├── utils/
│ │ ├── sound.ts # 音の再生
│ ├── screens
│ │ ├──GameScreen.tsx # ゲーム画面
│ ├── hooks/ # カスタムフック
│ │ ├── useSound.ts
│ ├── types/ # 型定義
│ │ ├── index.ts
│ ├── App.tsx # メインのアプリケーションコンポーネント
│── index.js # エントリーポイント(React Nativeが参照する)
│── tsconfig.json # TypeScriptの設定ファイル
│── babel.config.js # Babelの設定ファイル
│── package.json # 依存関係管理
│── metro.config.js # Metroバンドラーの設定
│── .gitignore # Git管理外ファイル指定
│── README.md # プロジェクトの説明
譜面データ
export const notes = [
{ type: 'piano', time: 1000 }, // 1秒後
{ type: 'drum', time: 2000 }, // 2秒後
{ type: 'piano', time: 3000 }, // 3秒後
];
楽器を鳴らす
import Sound from 'react-native-sound';
Sound.setCategory('Playback');
export const piano = new Sound(require('../../assets/piano.mp3'), Sound.MAIN_BUNDLE);
export const drum = new Sound(require('../../assets/drum.mp3'), Sound.MAIN_BUNDLE);
export const playSound = (type: 'piano' | 'drum') => {
if (type === 'piano') piano.play();
if (type === 'drum') drum.play();
};
楽器をタップするボタン
import React from 'react';
import { TouchableOpacity, Text, StyleSheet } from 'react-native';
import { playSound } from '../utils/sound';
type DrumPadProps = {
type: 'piano' | 'drum';
};
const DrumPad: React.FC<DrumPadProps> = ({ type }) => {
return (
<TouchableOpacity style={styles.button} onPress={() => playSound(type)}>
<Text style={styles.text}>{type.toUpperCase()}</Text>
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
button: {
padding: 20,
margin: 10,
backgroundColor: '#FF6347',
borderRadius: 10,
alignItems: 'center',
},
text: {
color: 'white',
fontSize: 18,
},
});
export default DrumPad;
ゲーム画面
import React, { useEffect, useState } from 'react';
import { View, StyleSheet, Text } from 'react-native';
import DrumPad from '../components/DrumPad';
import { notes } from '../data/notes';
const GameScreen: React.FC = () => {
const [time, setTime] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setTime((prev) => prev + 100);
}, 100);
return () => clearInterval(interval);
}, []);
return (
<View style={styles.container}>
{/* 譜面エリア */}
<View style={styles.noteArea}>
{notes.map((note, index) => (
<Text key={index} style={styles.note}>
{time >= note.time ? '🎵' : ' '}
</Text>
))}
</View>
{/* ボタンエリア */}
<View style={styles.buttonArea}>
<DrumPad type="piano" />
<DrumPad type="drum" />
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#000',
},
noteArea: {
flex: 2,
width: '100%',
justifyContent: 'center',
alignItems: 'center',
},
note: {
fontSize: 30,
color: 'white',
},
buttonArea: {
flexDirection: 'row',
padding: 20,
},
});
export default GameScreen;
メインのアプリケーションコンポーネントを書き換える
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import GameScreen from './src/screens/GameScreen';
const Stack = createStackNavigator();
const App = () => {
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Game" component={GameScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
export default App;
最初に長々と必要なモジュールをコツコツインストールすると面倒なので、
スケルトン作成がひと段落してから、npm install
で必要なモジュールを一括でインストールしてビルドしてください。
ビルド時、同時にAndroidエミュレーターを起動しておいてください。
AppROOT¥ npx react-native start
↑起動中にもう一つターミナルを開いて
AppROOT¥ npx react-native run-android
を実行するとエミュレーターにウェルカムページが表示されます。
おまけ
ReactNative初期化直後にビルドすると、ウェルカムページが専用のリストなので、初期のJavaScript時代のようにHelloworldになるテンプレート
を作りました。
初期化直後にApp.tsx をこちらに差し替えるとシンプルになりますので、慣れている人は使いやすいと思います。
import React from "react";
import { StyleSheet, Text, View } from "react-native";
interface AppProps {}
interface AppState {}
export default class App extends React.Component<AppProps, AppState> {
render() {
return (
<View style={styles.container}>
<Text>Hello, world!</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center"
}
});