はじめに
Discordのbotにレベリング(ユーザーにレベルを付与する仕組み)を実装したいという需要が結構あるようだったので書いてみました。
この記事では簡単にレベリングと関連するコマンドの実装をするための方法を解説します。
この記事の対象者
- ある程度Pythonの知識があり、discord.pyでbotにレベリング機能を実装したい。
前提知識
- 基本的なPythonの記法と仕様を理解している
- discord.pyの基本的な使い方を知っている
実装
必要なライブラリのインストール
今回の実装ではSQLAlchemyとdiscord.pyを用いるのでそれらをpipコマンドでインストールします。
pip install -U discord.py sqlalchemy
ディレクトリ構造を作る
今回は可読性のためファイルを分けて処理を記述します。
.
├── main.py
└── database.py
基本的な実装
以下のコードが基本的な実装です:
from sqlalchemy import Column, Integer, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = 'sqlite:///users.db' # データベースの種類と名前をここで指定できます
engine = create_engine(DATABASE_URL) # データベースエンジンを作成
Base = declarative_base() # データベースの親クラスを作成
# ユーザーデータのテーブルを定義
class User(Base):
__tablename__ = 'users'
id = Column(Integer, unique=True)
level = Column(Integer, default=1)
experience = Column(Integer, default=0)
# データベースを作成
Base.metadata.create_all(engine)
# データベースにアクセスするためのセッションを作成
Session = sessionmaker(bind=engine)
session = Session()
import discord
from discord.ext import commands
from database import User, session # 上記のdatabase.pyからUserとsessionをインポート
intents = discord.Intents.all()
bot = commands.Bot(command_prefix='!', intents=intents)
tree = bot.tree
@bot.event
async def on_ready():
await tree.sync() # スラッシュコマンドの同期
print(f"Logged in as {bot.user.name}.")
@bot.event
async def on_message(message):
# botのレベルはカウントしない
if message.author.bot:
return
# 既存のユーザーデータから検索
user = session.query(User).filter_by(id=message.author.id).first()
# 見つからなければ新たに作成
if not user:
user = User(id=message.author.id)
session.add(user)
session.commit()
# レベリングロジックをここに実装(次の項目を参照) #
session.commit() # データの反映
await bot.process_commands(message) # メッセージ処理の完了(commands.Bot()を利用したときのみ)
bot.run(TOKEN)
レベリングロジックの実装
ここではレベリングロジックを3通り紹介します。組み合わせるなどして参考にしてください。
経験値を固定の値で与え、一定の経験値がたまるごとにレベルを上げるロジック
# on_message内での経験値とレベルの処理部分
EXP_PER_MESSAGE = 10 # メッセージごとに与える経験値
LEVEL_UP_EXP = 100 # レベルアップに必要な経験値
user.experience += EXP_PER_MESSAGE
if user.experience >= LEVEL_UP_EXP:
user.level += 1
user.experience = 0
このコードでは、1メッセージあたり経験値10を獲得し、100経験値が貯まるとレベルが1上がって経験値が0になります。
メッセージの長さに応じて経験値を与えるロジック
# on_message内での経験値とレベルの処理部分
LEVEL_UP_EXP = 100 # レベルアップに必要な経験値
EXP_PER_CHARACTER = 1 # 1文字ごとに与える経験値
message_length = len(message.content)
user.experience += int(message_length * EXP_PER_CHARACTER)
if user.experience >= LEVEL_UP_EXP:
user.level += 1
user.experience = 0
このコードでは、メッセージ1文字あたり1の経験値を獲得し、100経験値が貯まるとレベルが1上がって経験値が0になります。
レベルごとに必要な経験値を増やすロジック
# on_message内での経験値とレベルの処理部分
LEVEL_UP_EXP = 100 # レベルアップに必要な経験値
LEVEL_UP_RATE = 30 # レベルごとの必要経験値増加量
EXP_PER_MESSAGE = 10 # メッセージごとに与える経験値
user.experience += EXP_PER_MESSAGE
if user.experience >= LEVEL_UP_EXP + user.level * LEVEL_UP_RATE:
user.level += 1
user.experience = 0
このコードでは、1メッセージあたり経験値10を獲得し、100+現在のレベル*30
経験値が貯まるとレベルが1上がって経験値が0になります。
データベースにアクセスするコマンドの実装
ここからはアプリケーションコマンド(スラッシュコマンド)を使ってデータベースへアクセスするコマンドを実装していきます。
アプリケーションコマンドの実装については前回の記事に詳細を記述しているのでよければ参考にしてください。
レベルを確認するコマンドの実装
コマンドの任意引数にユーザーを指定して表示するユーザーを選択できるようにします。
@tree.command(name="level", description="ユーザーのレベルを表示します")
async def level(ctx: discord.Interaction, user: discord.Menber = None):
user = ctx.author if user is None else user
if user.bot:
embed = discord.Embed(title='Error', descrpition='botのレベルは存在しません')
await ctx.response.send_message(embed=embed)
return
user = session.query(User).filter_by(id=message.author.id).first()
if not user:
user = User(id=user.id)
session.add(user)
session.commit()
text = f"Level:{user.level} - Exp: {user.experience}"
embed = discord.Embed(title=f'{user.name}さんのレベル:', description=text)
await ctx.response.send_message(embed=embed)
1行ごとの解説
user = ctx.author if user is None else user
このコードでは三項演算子を用いて「user
引数が指定されていればそれを、指定が無ければコマンドを打ったユーザーをuser
変数に代入する」ことを表します。
user = session.query(User).filter_by(id=message.author.id).first()
このコードでは、user.idを使ってデータベースからテーブルを検索します。もし見つからなかった場合はNone
が渡されます。
if user.bot:
対象ユーザーがbotかどうかを評価します。
embed = discord.Embed(title='Error', descrpition='botのレベルは存在しません')
エラーを示す埋め込みを作成します。
await ctx.response.send_message(embed=embed)
エラーを示す埋め込みを送信します。
return
if user.bot:
の中の処理だけを行い関数を終了させます。これによりこれ以降の処理は行われません。
if not user:
ユーザーが見つからなかったかどうかを評価します。
session.add(user = User(id=user.id))
新しく作成したUser
インスタンスをデータベースに追加します。
session.commit()
変更を反映します。
text = f"Level:{user.level} - Exp: {user.experience}"
変数text
にレベルと経験値をセットします。
embed = discord.Embed(title=f'{user.name}さんのレベル:', description=text)
ユーザー名とtext
変数を使って埋め込みを作成します。
await ctx.response.send_message(embed=embed)
埋め込みを返します。
レベルの高い順にリーダーボードを表示するコマンドの実装
@tree.command(name="leaderboard", description="レベルの上位10位を表示します")
async def leaderboard(ctx):
top_users = session.query(User).order_by(desc(User.level)).limit(10).all()
text = ""
for index, user in enumerate(top_users, start=1):
text += f'{index}. <@{user.id}> - Level {user.level} Exp: {user.experience}\n'
embed = discord.Embed(title='Leaderboard', description=text)
await ctx.response.send_message(embed=embed)
1行ごとの解説
top_users = session.query(User).order_by(desc(User.level)).limit(10).all()
データベースからレベルの高い順にユーザーを最大10項目検索します。
text = ""
descriptionに指定するテキストを生成するための一時的な定義です。
for index, user in enumerate(top_users, start=1):
enumerate()
関数を使ってユーザーとその順位を取り出します。
text += f'{index}. <@{user.id}> Level: {user.level} Exp: {user.experience}\n'
変数text
に追記を行います:'順位. ユーザーへのメンション Level: レベル Exp: 経験値'
embed = discord.Embed(title='Leaderboard', description=text)
リーダーボードの埋め込みを作成します。
await ctx.response.send_message(embed=embed)
埋め込みを返します。
終わりに
最後まで読んで頂きありがとうございます。不備があれば是非コメントで指摘して下さい。