Pythonで学ぶゲーム数理工学：スプラトゥーン編1


##  はじめに

スプラトゥーンのようなアクションゲームでも、実はその動きの裏側には数学がしっかりと活用されています。
本記事では、スプラトゥーンに登場するキャラクターの操作や弾の挙動を例に、ゲーム内の動きがどのような数式で表現され、どのように計算されているのかを、Pythonコードを使って解説します。


##  参考リンクまとめ


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4046871/ba1fe4f3-9be8-45d6-ad82-138934d5902a.png)
この人を参考にしました


https://www.youtube.com/watch?v=awuDOztxNLs&list=PLvEnnG_Zh-5CUDiPwsWBE_-ZFUdPIlBRd


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4046871/2ee516a9-e7ac-4d1e-88a9-7b613501e1e1.png)

https://www.youtube.com/watch?v=567g7PFkJfs

フレームの計算ものせておきます




https://note.com/kanamoji_1027/n/nfd4a961652a6

命中率を考慮したキル速の期待値

##  Pythonコード

フレーム速度計算とベクトル回転と 弧長と角度の関係　

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4046871/2c8419b2-875b-4c6b-a0d0-8354b62bf2d5.png)



https://www.youtube.com/watch?v=awuDOztxNLs&list=PLvEnnG_Zh-5CUDiPwsWBE_-ZFUdPIlBRd


```python
# 日本語と英語のコメント付き / With Japanese and English comments

# === 与えられた値 Given values ===
speed_per_frame = 3.2     # 1フレームあたりの移動距離 / Distance per frame [m/frame]
frame_rate = 50           # フレームレート / Frame rate: frames per second

# === 各時間単位ごとの速度計算 Compute speeds per second, minute, and hour ===
speed_per_second = speed_per_frame * frame_rate               # m/s
speed_per_minute = speed_per_second * 60                      # m/min
speed_per_hour = speed_per_minute * 60                        # m/h

# === プリントで表示 Print the results ===
print("=== 速度の情報 / Speed Information ===")
print(f"1フレームあたりの移動距離 / Distance per frame: {speed_per_frame} m/frame")
print(f"フレームレートの定義 / Frame rate: {frame_rate} frames per second")
print(f"1秒あたりの移動距離 / Speed per second: {speed_per_second:.2f} m/s")
print(f"1分あたりの移動距離 / Speed per minute: {speed_per_minute:.2f} m/min")
print(f"1時間あたりの移動距離 / Speed per hour: {speed_per_hour:.2f} m/h")


```



```python


import numpy as np
import matplotlib.pyplot as plt

# === パラメータ設定 Parameters ===
distance = 10             # ベクトルの長さ / Vector length [m]
base_angle_deg = 90       # 元の角度（度） / Base angle (degrees)
deviation_angle_deg = 15  # 回転させる角度 / Rotation angle (degrees)

# === ラジアンへの変換 Convert to radians ===
base_angle_rad = np.radians(base_angle_deg)
deviation_angle_rad = np.radians(deviation_angle_deg)

# === ベクトルの定義 Define vectors using complex numbers ===
v0 = distance * np.exp(1j * base_angle_rad)          # 元のベクトル Original vector
v1 = v0 * np.exp(1j * deviation_angle_rad)           # 回転後のベクトル Rotated vector

# === 座標の差の計算 Compute coordinate differences ===
x_diff = v1.real - v0.real  # X座標の差 / Difference in X [m]
y_diff = v1.imag - v0.imag  # Y座標の差 / Difference in Y [m]

# === 結果表示 Print results ===
print("=== ベクトルの座標と変化量 / Vector Coordinates and Differences ===")
print(f"Original vector ({base_angle_deg}°): x = {v0.real:.2f} m, y = {v0.imag:.2f} m")
print(f"Rotated vector ({base_angle_deg + deviation_angle_deg}°): x = {v1.real:.2f} m, y = {v1.imag:.2f} m")
print(f"X座標の差 / ΔX: {x_diff:.2f} m")
print(f"Y座標の差 / ΔY: {y_diff:.2f} m")

# === プロット設定 Plot ===
fig, ax = plt.subplots()
ax.set_aspect('equal')
ax.grid(True)

margin = 5
ax.set_xlim(-distance - margin, distance + margin)
ax.set_ylim(-distance - margin, distance + margin)

# 円を描画 Draw a reference circle
circle = plt.Circle((0, 0), distance, color='lightblue', fill=False, linestyle='--', label=f'Radius = {distance} m')
ax.add_artist(circle)

# ベクトルの描画 Draw vectors
ax.arrow(0, 0, v0.real, v0.imag, head_width=0.5, head_length=1, fc='blue', ec='blue', label=f'Original ({base_angle_deg}°)')
ax.arrow(0, 0, v1.real, v1.imag, head_width=0.5, head_length=1, fc='red', ec='red', label=f'Rotated (+{deviation_angle_deg}°)')

ax.plot(0, 0, 'ko')  # 原点 / Origin
ax.legend()

# タイトルに差を表示 Show differences in title
plt.title(f"Vector Rotation ΔX = {x_diff:.2f} m, ΔY = {y_diff:.2f} m")
plt.xlabel("X [m]")
plt.ylabel("Y [m]")
plt.show()

```

```python

import numpy as np
import matplotlib.pyplot as plt

# 弧の長さ fixed arc length
arc_length = (5 * np.pi) / 3  # = 5π/3

# 射程 before and after (range)
r_before = 10
r_after = 13

# 角度（度）を求める Calculate angles (degrees)
theta_before = 360 * arc_length / (2 * np.pi * r_before)
theta_after = 360 * arc_length / (2 * np.pi * r_after)

# 各角度を半分にして左右に分ける（扇形描画用） Split into left/right half angles
left_before = -theta_before / 2
right_before = theta_before / 2
left_after = -theta_after / 2
right_after = theta_after / 2

# プロット設定 Plotting
fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
ax.set_theta_zero_location("N")  # 上向きを0度に設定 / Set 0° to top
ax.set_theta_direction(-1)       # 時計回り / Clockwise

# 角度をラジアンに変換 Convert to radians
theta1 = np.radians(np.linspace(left_before, right_before, 100))
theta2 = np.radians(np.linspace(left_after, right_after, 100))

# 扇形を描く Draw the sectors
ax.plot(theta1, [r_before]*len(theta1), label=f'Before: {theta_before:.3f}° (r=10 m)', color='red')
ax.plot(theta2, [r_after]*len(theta2), label=f'After: {theta_after:.3f}° (r=13 m)', color='blue')

# 装飾 Styling
ax.set_rmax(15)
ax.set_rticks([5, 10, 13]) 
ax.set_title("Same Arc Length, Different Range", va='bottom')
ax.legend(loc='lower left')

# 角度差の確認用プリント Print coordinate angles
print("=== Calculation Results ===")
print(f"弧の長さ (Arc Length): {arc_length:.4f} m")
print(f"元の角度 θ1: {theta_before:.4f}°")
print(f"射程13m時の角度 θ2: {theta_after:.4f}°")
print(f"角度差: {theta_before - theta_after:.4f}°")

plt.show()

```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4046871/c45e529f-e99c-49c3-a04c-f1030995151e.png)



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4046871/38ae33c7-bb89-477e-b45f-373cc787fd88.png)



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4046871/fafd0109-d73c-419c-9af3-568b266f9504.png)



```python

import numpy as np
import matplotlib.pyplot as plt

# パラメータ設定 / Parameter settings
s = 5  # 最大ブレ角（最大でs度までブレる） / Max possible deviation angle in degrees
b = 0.01  # ブレ値 / Blur coefficient (smaller means more accurate)

# 乱数xとブレ角度yの関係 (式1) / Equation (1): y = s * x^(log_0.5(b))
x = np.linspace(0, 1, 1000)
exponent = np.log(b) / np.log(0.5)  # log base 0.5 of b
y = s * x ** exponent  # ブレ角度 / Deviation angle in degrees

# 命中率計算 (式2) / Equation (2): x = (y/s)^(log_b(0.5)) = (y/s)**(log(0.5)/log(b))
y_vals = np.linspace(0, s, 1000)
x_cdf = (y_vals / s) ** (np.log(0.5) / np.log(b))  # 累積分布関数 / Cumulative probability

# --- Plot 1: x vs y (Deviation angle for random number) ---
plt.figure(figsize=(10, 5))
plt.plot(x, y, label=f'b = {b}')
plt.xlabel('Random value x (0 to 1)')
plt.ylabel('Deviation angle y [°]')
plt.title('Deviation angle y versus random value x')
plt.grid(True)
plt.legend()
plt.show()

# --- Plot 2: y vs probability (CDF) ---
plt.figure(figsize=(10, 5))
plt.plot(y_vals, x_cdf, label=f'b = {b}')
plt.xlabel('Deviation angle y [°]')
plt.ylabel('Probability that deviation ≤ y')
plt.title('Probability of deviation angle being less than y (Hit rate)')
plt.grid(True)
plt.legend()
plt.show()

```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4046871/38df9975-3451-417c-8a15-6cb9c8b0a9bf.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4046871/7f90c9db-2956-43ca-9d19-c76c05dc8175.png)