2022年08月26日
新人がリアルタイムオセロゲームを実装してみた 前編
はじめに
V-CUBEのAgora技術チームの若手メンバーで作った、リアルタイムに盛り上がることのできるオセロゲームの制作過程を大公開します!
AgoraとReact.jsでWebアプリ(オセロゲーム)を実装しました。
今回はAgora Video SDKをオセロゲームに実装するところまでの記事になります。
目次
[非表示]アプリの概要
対局者映像と盤面の状況を見ながら、勝敗予想がリアルタイムで出来たら面白そう!
ということで
対局者:二人で対局
視聴者:盤面をリアルタイムで見ながら勝敗予想できる
こんな仕様のWebアプリを作っていきます。
具体的な機能一覧はこんな感じ!
対局者機能:
・オセロ対戦
・映像/音声の送受信
・映像/音声ミュート
・チャット(視聴者とも交流可能)
・集中モード(自分の画面にはオセロ盤面のみ表示)
視聴者機能:
・オセロ観戦
・オセロ勝敗予想
・チャット(視聴者とも交流可能)
ゲームイメージ概要
まず開発前にどんなゲームを作りたいか、イメージ図を作成しました!
ゲームフローイメージについては以下のようになっています!
出場者と観客それぞれが単一のページでアプリケーションを構成するSPA(シングルページアプリケーション)を作成していこうと思います!
入室画面で出場するか、観戦するかを選ぶことができます。
出場者になる場合、プレイヤー名を入力して対戦相手と観客に表示できるようにします。
出場者で入るとお互いの映像が両側に表示され、対局スタートです。
ミュート機能・チャット機能・集中モード等が使える画面のイメージです。
観戦者になった場合、対局者映像と盤面がリアルタイムで見られるようになっています。観戦しながらチャット機能、勝敗予想機能を使えるようなイメージです。
ゲームイメージはひとまず上のように決まったので、開発環境を用意していざ実装!!
開発環境
Windows10 pro 64bit
Google Chrome
Visual Studio Code
GitHub
Agora Video SDK for Web 4.12.1
Agora RTM SDK for Web 1.4.5
実装
GitHub
実装概要
今回のwebアプリは、React(8.5.5)を用いたSPAとなっています。
SPAの実装にはreact-router-domを使用しました。
各要素を配置
まずやった事として、我々がまだReactにあまり慣れていないこともあった為、「スタート画面」「タイトル部分」「プレイヤーの映像枠」「映像出力用(後述)」「チャット欄」「オセロ盤面」「フッター」等の各要素をコンポーネントに分け、必要なファイルを作成した後に各要素の配置を決めました。
AgoraRTCの導入
ここから、いよいよ本題です。
プレイヤー同士の映像、音声の送受信の為にAgoraRTCをinstallします。
npm install agora-rtc-sdk-ng
また、Agoraの記述にはtypeScriptを用いるので、tsが使えるようにします。
tsconfig.json
{
"compilerOptions": {
"target": "es2016",
"jsx": "react-jsx",
"module": "esnext",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"allowSyntheticDefaultImports": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
},
"include": [
"src"
]
}
Agoraの記述はuseAgora.tsxにまとめます。
また、映像を出力する為のコンポーネントはMediaPlayer.tsxです。
useAgora.tsx
import React, { useState, useEffect } from 'react';
import AgoraRTC, {
IAgoraRTCClient, IAgoraRTCRemoteUser, MicrophoneAudioTrackInitConfig, CameraVideoTrackInitConfig, IMicrophoneAudioTrack, ICameraVideoTrack, ILocalVideoTrack, ILocalAudioTrack } from 'agora-rtc-sdk-ng';
const appId = "your appId";
const channel = "ChannelName";
function useAgora(client: IAgoraRTCClient | undefined)
:
{
localAudioTrack: ILocalAudioTrack | undefined,
localVideoTrack: ILocalVideoTrack | undefined,
joinState: boolean,
leave: Function,
join: Function,
remoteUsers: IAgoraRTCRemoteUser[],
}
{
const [localVideoTrack, setLocalVideoTrack] = useState(undefined);
const [localAudioTrack, setLocalAudioTrack] = useState(undefined);
const [joinState, setJoinState] = useState(false);
const [remoteUsers, setRemoteUsers] = useState<iagorartcremoteuser[]>([]);
async function createLocalTracks(audioConfig?: MicrophoneAudioTrackInitConfig, videoConfig?: CameraVideoTrackInitConfig)
: Promise<[IMicrophoneAudioTrack, ICameraVideoTrack]> {
const [microphoneTrack, cameraTrack] = await AgoraRTC.createMicrophoneAndCameraTracks(audioConfig, videoConfig);
setLocalAudioTrack(microphoneTrack);
setLocalVideoTrack(cameraTrack);
return [microphoneTrack, cameraTrack];
}
async function join(){
if (!client) return;
const [microphoneTrack, cameraTrack] = await createLocalTracks();
await client.join(appId, channel, null);
await client.publish([microphoneTrack, cameraTrack]);
(window as any).client = client;
(window as any).videoTrack = cameraTrack;
setJoinState(true);
}
async function leave() {
// window.confirm("投了して退出します");
if (localAudioTrack) {
localAudioTrack.stop();
localAudioTrack.close();
}
if (localVideoTrack) {
localVideoTrack.stop();
localVideoTrack.close();
}
setRemoteUsers([]);
setJoinState(false);
await client?.leave();
}
useEffect(() => {
if (!client) return;
setRemoteUsers(client.remoteUsers);
const handleUserPublished = async (user: IAgoraRTCRemoteUser, mediaType: 'audio' | 'video') => {
await client.subscribe(user, mediaType);
// toggle rerender while state of remoteUsers changed.
setRemoteUsers(remoteUsers => Array.from(client.remoteUsers));
}
const handleUserUnpublished = (user: IAgoraRTCRemoteUser) => {
setRemoteUsers(remoteUsers => Array.from(client.remoteUsers));
}
const handleUserJoined = (user: IAgoraRTCRemoteUser) => {
setRemoteUsers(remoteUsers => Array.from(client.remoteUsers));
}
const handleUserLeft = (user: IAgoraRTCRemoteUser) => {
setRemoteUsers(remoteUsers => Array.from(client.remoteUsers));
}
client.on('user-published', handleUserPublished);
client.on('user-unpublished', handleUserUnpublished);
client.on('user-joined', handleUserJoined);
client.on('user-left', handleUserLeft);
return () => {
client.off('user-published', handleUserPublished);
client.off('user-unpublished', handleUserUnpublished);
client.off('user-joined', handleUserJoined);
client.off('user-left', handleUserLeft);
};
}, [client]);
return {
localAudioTrack,
localVideoTrack,
joinState,
leave,
join,
remoteUsers,
};
}
export default useAgora;
MediaPlayer.tsx
import { ILocalVideoTrack, IRemoteVideoTrack, ILocalAudioTrack, IRemoteAudioTrack } from "agora-rtc-sdk-ng";
import React, { useRef, useEffect } from "react";
export interface VideoPlayerProps {
videoTrack: ILocalVideoTrack | IRemoteVideoTrack | undefined;
audioTrack: ILocalAudioTrack | IRemoteAudioTrack | undefined;
}
const MediaPlayer = (props: any) => {
const container = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!container.current) return;
if(props.who == "localUser"){
props.videoTrack?.play("left-video");
}else if(props.who == "remoteUser"){
props.videoTrack?.play("right-video");
if(props.name) document.getElementById('right-name')!.innerHTML = props.name.substring(6);
}
return () => {
props.videoTrack?.stop();
};
}, [container, props.videoTrack]);
useEffect(() => {
if(props.audioTrack){
props.audioTrack?.play();
}
return () => {
props.audioTrack?.stop();
};
}, [props.audioTrack]);
// document.getElementById("left-video")?.innerHTML = '<div ref={container} className="video-player"}></div>'
return (
<div ref={container} className="video-player" style=></div>
);
}
export default MediaPlayer;
後は、各コンポーネントから必要に応じてこれらをimportして使用します。
これで映像音声の送受信は完了です。
左上の枠に自分の映像、右上の枠に相手の映像を出力します。
Agoraの利用用途
Agora Video SDK for Web : 映像音声の送受信、映像音声のミュート・ミュート解除(今回はこちらの実装記事になります。)
Agora RTM (Real-time Messaging) SDK for Web : ゲーム参加者数の取得、ゲーム開始情報の送受信
参照
Agora.io Developer Center : Agora Web SDK API Reference
Agora.io Developer Center : RTM API Reference
Bootstrap
まとめ
今回はReactを用いたwebアプリにAgora Video SDK for Webを導入するまでを行いました。
後編では、ここからオセロとして二人で対戦できるようにしていきます。
また、チャット機能の実装に伴いAgora RTM (Real-time Messaging) SDK for Webも導入します。
※『オセロ』
執筆者菊名 良一&礒部 洸生
Agoraの技術サポートを担当。