【PMMP数学】なんとなく凄そうな鯖にする

パーティクルとかで鯖をイイ感じに演出しようって記事です！
数学の知識はガバガバですが許してください、、、

# 演出の必要性
理由は単純！なんとなく凄そうな鯖に見えるからです！
簡単な言葉でまとめちゃったケド、鯖運営をしていく上で見た目はとてつもなく重要な要素です！
~~人間も第一印象は見た目だろ？~~

# 動作環境
- PHP8.0以降
- PMMP 4.x

# 数学的知識
ガチの数学は先生や教授から習ってください。

## 三角関数
なんか便利なヤツ。色んなとこで使います。
高一で習うと思います。
例として、角度60°の**sin**と**cos**の値は次のように出します。

:::note warn
sin・cos関数の引数はラジアンなので、deg2rad関数で角度をラジアンに変換する必要があります。
:::

```PHP
sin(deg2rad(60));
cos(deg2rad(60));
```

## ベクトル
**座標**みたいなものと思ってください。
PMMPでは**Vector3**クラスで定義されています。

https://github.com/pmmp/Math/blob/stable/src/Vector3.php

今後使いそうなVector3クラスのメソッドをいくつか紹介します。
```PHP:四則演算
public function add(float|int $x, float|int $y, float|int $z) : Vector3       // 足し算
public function subtract(float|int $x, float|int $y, float|int $z) : Vector3  // 引き算
public function multiply(float $number) : Vector3                             // 掛け算
public function divide(float $number) : Vector3                               // 割り算
```

```PHP:距離を求める
// $posからの距離を求めます。
public function distance(Vector3 $pos) : float
```

```PHP:ベクトルの大きさ（原点からの距離）を求める
public function length() : float
```

```PHP:大きさを1にする（正規化）
// 演算を行う上で大きさを1にすると便利
public function normalize() : Vector3
```

## 回転行列
**とある座標を〇軸を中心にθだけ回転させた時の座標**を求めることができるのが**回転行列**です。
以下の数式っぽいのが回転行列です。

```math
R_x(\theta) = 
\begin{bmatrix}
1 & 0 & 0 \\
0 & cos\theta & -sin\theta \\
0 & sin\theta & cos\theta
\end{bmatrix}
\\
R_y(\theta) =
\begin{bmatrix}
cos\theta & 0 & sin\theta \\
0 & 1 & 0 \\
-sin\theta & 0 & cos\theta
\end{bmatrix}
\\
R_z(\theta) = 
\begin{bmatrix}
cos\theta & -sin\theta & 0 \\
sin\theta & cos\theta & 0 \\
0 & 0 & 1
\end{bmatrix}
```

オジサンの僕にはよく分かりませんでした:sweat_smile:

数式を見ても全く分からないと思うので、図で回転のイメージだけでも掴んでいただけたらと思います。
下図は`(x, y)`を原点を中心にθだけ回転させた時、`(x', y')`になりますよの図です。
<img width="400" src="https://mathlandscape.com/wp-content/uploads/2021/08/rotation-matrix.png">
この座標の回転を先程の数式を使って計算して、`(x', y')`を導き出してるワケです。
まぁこんな説明じゃ *Do you(´・ω・`)kotoyanen?* って人が大半だと思いますが、、、
　
```:使用例
x軸を中心に1°回転させた場所にパーティクルを出す
x軸を中心に2°回転させた場所にパーティクルを出す
...
x軸を中心に359°回転させた場所にパーティクルを出す
```

このような処理を書くと$x$軸を中心に円が描くことができます。
~~使用例として微妙~~

$x$軸を中心とした回転行列をPHPで書くと次のようになります。

```PHP
/*
 * @param Vector3 $vec  ベクトル
 * @param float $degree 角度
 */
function rotationOnX(Vector3 $vec, float $degree): Vector3 {
    $rad = deg2rad($degree);  // ラジアンに変換
    $cos = cos($rad);         // cosの値
    $sin = sin($rad);         // sinの値

    // 回転を計算してVector3で返す
    return new Vector3(
        $vec->x,
        $vec->y * $cos - $vec->z * $sin,
        $vec->y * $sin + $vec->z * $cos
    );
}
```

$y$軸$z$軸の回転行列は以下のgistに記載しておくので適宜使ってください。

https://gist.github.com/deceitya/db229f04c214fc320e9a22e473de3d2a

# 色んな図形を出してみる
## 円
超絶基礎の円です。
cosとsinでxとyを出してます。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404837/22d2709b-fe24-71b5-9a9a-bde9d853d25b.png">

```PHP
// @var World    $world    任意のワールド
// @var Vector3  $pos      任意の座標
// @var Particle $particle 任意のパーティクル

$width = 10; // パーティクルごとの幅みたいなもの
$size = 2; // ハートのサイズ
for ($i = 0; $i < 360; $i += $width) {
    // 度をラジアンに変換
    $rad = deg2rad($i);

    // cosとsinを出す
    $x = cos($rad) * $size;
    $y = sin($rad) * $size;

    // 任意の座標$posに$xと$yを足して、パーティクルを出す。
    $world->addParticle($pos->add($x, $y, 0), $particle);
}
```

## 線
超絶基礎2の線です。
点と点を結ぶコードを書くだけ。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404837/49751f4b-d524-e953-8584-0f357c6963c8.png">

```PHP
// @var Vector3 $end 終点の座標

$width = 0.25; // パーティクルごとの幅
$direction = $end->subtractVector($pos)->normalize()->multiply($width); // 終点-始点で方向ベクトルで始点から終点への方向ベクトルを取得
$distance = $end->distance($pos); // 距離
for ($i = 0; $i <= $distance; $i += $width) {
    $world->addParticle($pos, $particle);
    $pos = $pos->addVector($direction); // 方向ベクトルを足す
}
```


## ハート
そういう時期なのでネ:kissing_heart:
去年のアドカレでもハートをパーティクルで作る記事がありましたね！（計算式は違うけど）

https://qiita.com/yurisi/items/22d93d7668dcd445ecae

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404837/a8a0d17a-e279-5a8c-61b4-dcc978277c2a.png">

```PHP
$width = 10; // パーティクルごとの幅みたいなもの
$size = 2; // ハートのサイズ
for ($i = -90; $i <= 90; $i += $width) {
    // 度をラジアンに変換
    $rad = deg2rad($i);

    // ハートの計算式
    $sin = sqrt(abs(sin($rad) * $size));
    $cos = sqrt(abs(cos($rad) * $size));
    $upY = $sin + $cos;
    $underY = $sin - $cos;
    $x = $rad * sqrt($size);

    // 計算式で求めた値を座標$posに足す
    $upPos = $pos->add($x, $upY, 0);
    $underPos = $pos->add($x, $underY, 0);

    // パーティクルを出す
    $world->addParticle($upPos, $particle);
    $world->addParticle($underPos, $particle);
}
```

## 星
:star:クリスマス:star:
線の応用です。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404837/1de7dfde-8578-51c2-bba3-3dc30263e29f.png">

```PHP
$width = 0.5; // パーティクルごとの幅
$size = 3; // 星のサイズ
for ($i = 90; $i < 450; $i += 72) {
    $from = (new Vector3(cos(deg2rad($i)), sin(deg2rad($i)), 0))->multiply($size); // 始点
    $to = (new Vector3(cos(deg2rad($i + 144)), sin(deg2rad($i + 144)), 0))->multiply($size); // 終点
    $direction = $to->subtractVector($from)->normalize()->multiply($width); // 終点-始点で始点から終点への方向ベクトルを取得
    $distance = $to->distance($from); // 距離
    for ($j = 0; $j < $distance; $j += $width) {
        $world->addParticle($pos->addVector($from), $particle);
        $from = $from->addVector($direction);
    }
}
```

魔法陣完成～
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404837/df27c9a6-80b9-cdab-85fd-a120554e7d2b.png">

# スポーン地点をなんか良い感じにする
ウン十年前に配布されていたプラグインの再現です！
三角関数で円を作って、yを徐々に上げていきます。
<blockquote class="twitter-tweet"><p lang="zxx" dir="ltr"><a href="https://t.co/tz7T6LNicq">pic.twitter.com/tz7T6LNicq</a></p>&mdash; ツキミヤ (@deceitya) <a href="https://twitter.com/deceitya/status/1597293972421898242?ref_src=twsrc%5Etfw">November 28, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

```PHP
$world = $this->getServer()->getWorldManager()->getDefaultWorld();
$pos = $world->getSafeSpawn();
$particle = new DustParticle(new Color(0, 255, 255));
$this->getScheduler()->scheduleRepeatingTask(new class($world, $pos, $particle) extends Task
{
    private float $y = 0;
    private int $deg = 0;
    private float $size = 1;

    public function __construct(
        private World $world,
        private Vector3 $pos,
        private Particle $particle
    ) {}

    public function onRun(): void
    {
        $rad1 = deg2rad($this->deg);
        $rad2 = deg2rad($this->deg + 180);
        $this->world->addParticle($this->pos->add(sin($rad1) * $this->size, $this->y, cos($rad1) * $this->size), $this->particle);
        $this->world->addParticle($this->pos->add(sin($rad2) * $this->size, $this->y, cos($rad2) * $this->size), $this->particle);

        $this->deg += 10;
        if ($this->deg >= 360) {
            $this->deg = 0;
        }

        $this->y += 0.04;
        if ($this->y >= 4) {
            $this->y = 0;
        }
    }
}, 1);
```

# ロビーになんか良い感じのエンティティを置く
資源ワールドとかにテレポートできそうなエンティティ。
三角関数でエンティティのモーションを決定してます。
**sinを使うと真ん中の速度が速くて上下の頂点付近は遅くなる**モーションで滑らかイイ感じ！
~~パーティクルが邪魔~~

<blockquote class="twitter-tweet"><p lang="zxx" dir="ltr"><a href="https://t.co/qPNnjtgyfR">pic.twitter.com/qPNnjtgyfR</a></p>&mdash; ツキミヤ (@deceitya) <a href="https://twitter.com/deceitya/status/1601374749778591744?ref_src=twsrc%5Etfw">December 10, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

```PHP:エンティティクラス
class TeleportEntity extends Living
{
    public static function getNetworkTypeId(): string
    {
        return EntityIds::SHEEP;
    }

    private Vector3 $basePos;
    private float $rad = 0.0;

    public function __construct(Location $location, ?CompoundTag $tag = null)
    {
        parent::__construct($location, $tag);
        $this->basePos = $location;
        $this->gravity = 0.0;
    }

    protected function getInitialSizeInfo(): EntitySizeInfo
    {
        return new EntitySizeInfo(1.3, 0.9); // ここの数値分からないからテキトーです。すみません。
    }

    public function getName(): string
    {
        return "TeleportEntity";
    }

    protected function entityBaseTick(int $tickDiff = 1): bool
    {
        $hasUpdate = parent::entityBaseTick($tickDiff);

        if (!$this->isFlaggedForDespawn()) {
            $this->setPosition($this->basePos->add(0, sin($this->rad) * 0.5, 0));

            $this->rad += M_PI / 40;
            if ($this->rad >= M_PI * 2) {
                $this->rad = 0.0;
            }
        }

        return $hasUpdate;
    }

    public function attack(EntityDamageEvent $source): void
    {
        if ($source instanceof EntityDamageByEntityEvent && $source->getDamager() instanceof Player) {
            $source->cancel();

            $player = $source->getDamager();
            $player->sendMessage("テレポートします。");
            // テレポートの処理
        }
    }
}
```

```PHP:エンティティをスポーンさせる
$entity = new TeleportEntity(Location::fromObject($pos, $world));
$entity->setNameTag("§a§l資源");
$entity->setNameTagAlwaysVisible(true);
$entity->spawnToAll();
```


# Hypixelとよばれる世界最大級のMinecraftサーバーなどにあるような羽根のパーティクル
[ここ](https://qiita.com/Nnse/items/42f58d6c8919d27b3d8c#%E3%83%A2%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E4%BB%A5%E5%A4%96%E3%81%AE%E6%B4%BB%E7%94%A8%E4%BE%8B)で言われてたヤツですね。

https://qiita.com/Nnse/items/42f58d6c8919d27b3d8c

プレイヤーの体のYawに合わせて背後に羽を出したい！が、PMMPは頭のYawしか取得できないらしい。
仕方ないので頭のYawを使用、羽を作るのはメンドクサイので円で代用します。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404837/95ba696c-9de0-5d9c-4dd6-d22f7292ee3d.png"><img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404837/7e1af5ed-0581-3e3d-ff57-f2b6356f22f1.png">

漂うラスボス感があって超超超いい感じ！
VIPプレイヤーに付けてあげたら喜びそう。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404837/567e73d2-9725-79cb-8b40-da62adc1573f.jpeg"><img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404837/706dc46d-13ca-e8b4-13a5-522fbb4442a2.jpeg">

基本的には方程式で作った図形を回転行列で回転させるだけです。
他の図形でも方程式さえ分かれば応用できます。

```PHP:回転行列
function rotationOnY(Vector3 $v, float $degree): Vector3
{
    $rad = deg2rad($degree);
    $cos = cos($rad);
    $sin = sin($rad);
    return new Vector3(
        $v->z * $sin + $v->x * $cos,
        $v->y,
        $v->z * $cos - $v->x * $sin
    );
}
```

```PHP:本体
// @var Player $player

$width = 20;
$size = 1;
for ($i = 0; $i < 360; $i += $width) {
    $rad = deg2rad($i);

    // cosとsinを出す
    $x = cos($rad) * $size;
    $y = sin($rad) * $size;

    // プレイヤーのちょっと後ろの座標を求める
    $location = $player->getLocation();
    $direction = $player->getDirectionVector()->normalize()->multiply(-0.5);
    $back = $location->add($direction->x, $player->getEyeHeight(), $direction->z);

    // 計算式で求めた値をY軸を中心にYawだけ回転させてからプレイヤーの背後の座標に加算する
    // 360 - YawなのはPMMPのYawは左回転方向に加算されていくため
    $rotation = $this->rotationOnY(new Vector3($x, $y, 0), 360 - $location->getYaw()); // Y軸周りの回転行列
    $pos = $back->addVector($rotation);

    // パーティクルを出す
    $location->getWorld()->addParticle($pos, $particle);
}
```

# 鯖に入った時のタイトルコール的な
全く数学ではないけど。
ゲームでもよくタイトルを最初に表示させますよね。
ウマ娘プリチーダービーとかが良い例。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">サーバーの進捗です。 <a href="https://t.co/GcrRdVtvqe">pic.twitter.com/GcrRdVtvqe</a></p>&mdash; ツキミヤ (@deceitya) <a href="https://twitter.com/deceitya/status/1231508415609860096?ref_src=twsrc%5Etfw">February 23, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

作ったのは3年以上前なのでコードは無いです。
こういう例があるよとだけ。

# 老害ポエマー
大切な事なので2度以上言いますが、視覚情報はとても大事です！

今回は情報を与える方でしたが、逆にある種の情報は見せないようにするのも手です。
例えばこのプラグインとか。

https://forum.mcbe.jp/resources/75/

悪賢くてズルいけど効果はあるかも？病院の先生が言うなら間違いないな！

あと**機能を全部Formに突っ込むのはやめよう！**
とても見にくいから。見た目的にもつまらないし面白くないから。
ブロックとかエンティティとかあるのにFormに全て実装してしまうのはもったいないと思います。
以前はブロックとかエンティティをタップしてワープするとか、ショップは看板で作るとか、マインクラフトらしさがあって良かったなというおぢさんの感想です。

# おわり
お疲れ様でした！
いいねとTwitterのフォローお願いします！
いつもは原神してます。お友達募集中。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Lobi生まれTwitter育ち マイクラのグルにいるのにほぼマイクラしてない奴は友達</p>&mdash; ツキミヤ (@deceitya) <a href="https://twitter.com/deceitya/status/1366871069794000897?ref_src=twsrc%5Etfw">March 2, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>