🦑
discord.jsでnode-canvasを利用したゲーム募集コマンド
Splatoon3のコミュニティサーバーで使用しているDiscord Botの開発をお手伝いしています。
その中で得た知見としてnode-canvasを使用した画像の動的生成について貼っておきます。
動作例
募集時にステージ情報などをAPIから取得して画像を作成したり、ボタンを押して参加状況が変わると画像を再生成しています。
コマンドからのインタラクション処理
ここではSplatoon3のサーモンランの募集の一部について抜粋して紹介します。
interactionから各情報を取得し、splatoon3.inkのAPIにステージ情報やブキ情報の問い合わせを行い、そのデータをキャンバス作成を行う関数に投げます。
let recruitBuffer;
let ruleBuffer;
if (type === RecruitType.SalmonRecruit) {
recruitBuffer = await recruitSalmonCanvas(
RecruitOpCode.open,
recruitNum,
count,
recruiter,
attendee1,
attendee2,
null,
condition,
channelName,
);
ruleBuffer = await ruleSalmonCanvas(await getSalmonData(schedule, 0));
} else if (type === RecruitType.BigRunRecruit) {
recruitBuffer = await recruitBigRunCanvas(
RecruitOpCode.open,
recruitNum,
count,
recruiter,
attendee1,
attendee2,
null,
condition,
channelName,
);
ruleBuffer = await ruleBigRunCanvas(schedule);
} else if (type === RecruitType.TeamContestRecruit) {
recruitBuffer = await recruitSalmonCanvas(
RecruitOpCode.open,
recruitNum,
count,
recruiter,
attendee1,
attendee2,
null,
condition,
channelName,
'コンテスト',
);
ruleBuffer = await ruleSalmonCanvas(await getTeamContestData(schedule, 0));
}
バッファーをチャンネルに投稿
作成されたCanvasのCanvasRenderingContext2DをAttachmentBuilderで送信可能な画像に変換し、channel.sendのファイルオプションとして追加します。
const recruit = new AttachmentBuilder(recruitBuffer, {
name: 'ikabu_recruit.png',
});
const rule = new AttachmentBuilder(ruleBuffer, { name: 'schedule.png' });
const image2Message = await recruitChannel.send({ files: [rule] });
const sentMessage = await recruitChannel.send({
content:
mention + ` ボタンを押して参加表明するでし!\n${getMemberMentions(recruitNum, [])}`,
});
// 募集文を削除してもボタンが動くように、bot投稿メッセージのメッセージIDでボタンを作る
const deleteButtonMsg = await recruitChannel.send({
components: [recruitDeleteButton(sentMessage, image1Message, image2Message)],
});
キャンバスの作成例
- Canvasの作成
const recruitCanvas = Canvas.createCanvas(720, 550);
const recruitCtx = recruitCanvas.getContext('2d');
- 四角形の作成
// 角丸の四角形を作成する関数
export function createRoundRect(
ctx: CanvasRenderingContext2D,
x: number,
y: number,
width: number,
height: number,
radius: number,
) {
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.arcTo(x + width, y, x + width, y + radius, radius);
ctx.lineTo(x + width, y + height - radius);
ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius);
ctx.lineTo(x + radius, y + height);
ctx.arcTo(x, y + height, x, y + height - radius, radius);
ctx.lineTo(x, y + radius);
ctx.arcTo(x, y, x + radius, y, radius);
ctx.closePath();
}
// 使用例
createRoundRect(recruitCtx, 1, 1, 718, 548, 30);
recruitCtx.fillStyle = '#2F3136';
recruitCtx.fill();
recruitCtx.strokeStyle = '#FFFFFF';
recruitCtx.lineWidth = 4;
recruitCtx.stroke();
- 画像の読み込み
const salmonIcon = await Canvas.loadImage('icon_url_here');
recruitCtx.drawImage(salmonIcon, 22, 32, 82, 60);
募集関連のコード
Discussion