パーティクルとかで鯖をイイ感じに演出しようって記事です!
数学の知識はガバガバですが許してください、、、
演出の必要性
理由は単純!なんとなく凄そうな鯖に見えるからです!
簡単な言葉でまとめちゃったケド、鯖運営をしていく上で見た目はとてつもなく重要な要素です!
人間も第一印象は見た目だろ?
動作環境
- PHP8.0以降
- PMMP 4.x
数学的知識
ガチの数学は先生や教授から習ってください。
三角関数
なんか便利なヤツ。色んなとこで使います。
高一で習うと思います。
例として、角度60°のsinとcosの値は次のように出します。
sin・cos関数の引数はラジアンなので、deg2rad関数で角度をラジアンに変換する必要があります。
sin(deg2rad(60));
cos(deg2rad(60));
ベクトル
座標みたいなものと思ってください。
PMMPではVector3クラスで定義されています。
今後使いそうなVector3クラスのメソッドをいくつか紹介します。
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 // 割り算
// $posからの距離を求めます。
public function distance(Vector3 $pos) : float
public function length() : float
// 演算を行う上で大きさを1にすると便利
public function normalize() : Vector3
回転行列
とある座標を〇軸を中心にθだけ回転させた時の座標を求めることができるのが回転行列です。
以下の数式っぽいのが回転行列です。
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}
オジサンの僕にはよく分かりませんでした
数式を見ても全く分からないと思うので、図で回転のイメージだけでも掴んでいただけたらと思います。
下図は(x, y)
を原点を中心にθだけ回転させた時、(x', y')
になりますよの図です。
この座標の回転を先程の数式を使って計算して、(x', y')
を導き出してるワケです。
まぁこんな説明じゃ Do you(´・ω・`)kotoyanen? って人が大半だと思いますが、、、
x軸を中心に1°回転させた場所にパーティクルを出す
x軸を中心に2°回転させた場所にパーティクルを出す
...
x軸を中心に359°回転させた場所にパーティクルを出す
このような処理を書くと$x$軸を中心に円が描くことができます。
使用例として微妙
$x$軸を中心とした回転行列を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に記載しておくので適宜使ってください。
色んな図形を出してみる
円
// @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);
}
線
// @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); // 方向ベクトルを足す
}
ハート
そういう時期なのでネ
去年のアドカレでもハートをパーティクルで作る記事がありましたね!(計算式は違うけど)
$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);
}
星
$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);
}
}
スポーン地点をなんか良い感じにする
ウン十年前に配布されていたプラグインの再現です!
三角関数で円を作って、yを徐々に上げていきます。
— ツキミヤ (@deceitya) November 28, 2022
$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を使うと真ん中の速度が速くて上下の頂点付近は遅くなるモーションで滑らかイイ感じ!
パーティクルが邪魔
— ツキミヤ (@deceitya) December 10, 2022
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("テレポートします。");
// テレポートの処理
}
}
}
$entity = new TeleportEntity(Location::fromObject($pos, $world));
$entity->setNameTag("§a§l資源");
$entity->setNameTagAlwaysVisible(true);
$entity->spawnToAll();
Hypixelとよばれる世界最大級のMinecraftサーバーなどにあるような羽根のパーティクル
ここで言われてたヤツですね。
プレイヤーの体のYawに合わせて背後に羽を出したい!が、PMMPは頭のYawしか取得できないらしい。
仕方ないので頭のYawを使用、羽を作るのはメンドクサイので円で代用します。
漂うラスボス感があって超超超いい感じ!
VIPプレイヤーに付けてあげたら喜びそう。
基本的には方程式で作った図形を回転行列で回転させるだけです。
他の図形でも方程式さえ分かれば応用できます。
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
);
}
// @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);
}
鯖に入った時のタイトルコール的な
全く数学ではないけど。
ゲームでもよくタイトルを最初に表示させますよね。
ウマ娘プリチーダービーとかが良い例。
サーバーの進捗です。 pic.twitter.com/GcrRdVtvqe
— ツキミヤ (@deceitya) February 23, 2020
作ったのは3年以上前なのでコードは無いです。
こういう例があるよとだけ。
老害ポエマー
大切な事なので2度以上言いますが、視覚情報はとても大事です!
今回は情報を与える方でしたが、逆にある種の情報は見せないようにするのも手です。
例えばこのプラグインとか。
悪賢くてズルいけど効果はあるかも?病院の先生が言うなら間違いないな!
あと機能を全部Formに突っ込むのはやめよう!
とても見にくいから。見た目的にもつまらないし面白くないから。
ブロックとかエンティティとかあるのにFormに全て実装してしまうのはもったいないと思います。
以前はブロックとかエンティティをタップしてワープするとか、ショップは看板で作るとか、マインクラフトらしさがあって良かったなというおぢさんの感想です。
おわり
お疲れ様でした!
いいねとTwitterのフォローお願いします!
いつもは原神してます。お友達募集中。
Lobi生まれTwitter育ち マイクラのグルにいるのにほぼマイクラしてない奴は友達
— ツキミヤ (@deceitya) March 2, 2021