7 - マップの境界
概要
現時点では、船とアステロイドの動きには制限がなく、画面外にも移動できます。一般的なアステロイドゲームでは、船やアステロイドが画面外に移動すると、画面の反対側から出現します。この挙動を再現する簡単な方法は、エンティティが範囲内にあるかをチェックして適切にテレポートするシステムを作成することです。
境界
BoundarySystemスクリプトを作成し、以下のコードを追加してください。
C#
using UnityEngine.Scripting;
using Photon.Deterministic;
namespace Quantum.Asteroids
{
    [Preserve]
    public unsafe class BoundarySystem : SystemMainThreadFilter<BoundarySystem.Filter>
    {
        public struct Filter
        {
            public EntityRef Entity;
            public Transform2D* Transform;
        }
        public override void Update(Frame frame, ref Filter filter)
        {
            if (IsOutOfBounds(filter.Transform->Position, new FPVector2(10, 10), out FPVector2 newPosition))
            {
                filter.Transform->Position = newPosition;
                filter.Transform->Teleport(frame, newPosition);
            }
        }
        
        /// <summary>
        /// Test if a position is out of bounds and provide a warped position.
        /// When the entity leaves the bounds it will emerge on the other side.
        /// </summary>
        public bool IsOutOfBounds(FPVector2 position, FPVector2 mapExtends, out FPVector2 newPosition)
        {
            newPosition = position;
            if (position.X >= -mapExtends.X && position.X <= mapExtends.X &&
                position.Y >= -mapExtends.Y && position.Y <= mapExtends.Y)
            {
                // position is inside map bounds
                return false;
            }
            // warp x position
            if (position.X < -mapExtends.X)
            {
                newPosition.X = mapExtends.X;
            }
            else if (position.X > mapExtends.X)
            {
                newPosition.X = -mapExtends.X;
            }
            // warp y position
            if (position.Y < -mapExtends.Y)
            {
                newPosition.Y = mapExtends.Y;
            }
            else if (position.Y > mapExtends.Y)
            {
                newPosition.Y = -mapExtends.Y;
            }
            return true;
        }
    }
}
このシステムはすべてのエンティティのTransformを走査し、マップの範囲外にいる時に反対側にテレポートさせます。より洗練されたアプローチとして、フィルターにタグコンポーネントを使用できます。
TransformのTeleportメソッドは、EntityViewInterpolatorに今回の移動の補間をスキップするように伝えるために使用されます。テレポートを行わないと、エンティティは画面端からもう一方の画面端まで補間されて移動してしまうでしょう。
AsteroidsSystemConfigにBoundarySystemを追加して、ゲームを再生しましょう。船とアステロイドは、境界に到達すると正しくテレポートが行われます。
調整しやすい境界
現在、境界は20x20のマップサイズに固定されています。AsteroidsGameConfigから、マップサイズをより柔軟に設定できるようにしましょう。
AsteroidsGameConfigスクリプトを開き、以下を追加してください。
C#
[Header("Map configuration")]
[Tooltip("Total size of the map. This is used to calculate when an entity is outside de gameplay area and then wrap it to the other side")]
public FPVector2 GameMapSize = new FPVector2(25, 25);
public FPVector2 MapExtends => _mapExtends;
private FPVector2 _mapExtends;
publicメンバーフィールドGameMapSizeは、設定でマップサイズを調整するために使用され、デザイナーがより直感的にマップの幅と高さを決められるようになります。ただし、ゲームプレイコードではExtends(中心からの距離)を使用する方が簡単で効率的です。設定ファイルの一般的なパターンとして、ゲームで設定ファイルが読み込まれた直後に追加データを計算するには、以下のようにLoaded関数を使用することができます。
C#
public override void Loaded(IResourceManager resourceManager, Native.Allocator allocator)
{
    base.Loaded(resourceManager, allocator);
    _mapExtends = GameMapSize / 2;
}
BoundarySystemスクリプトに戻り、ロードした設定ファイルの値を使用するように調整します。
C#
public override void Update(Frame frame, ref Filter filter)
{
    AsteroidsGameConfig config = frame.FindAsset(frame.RuntimeConfig.GameConfig);
    if (IsOutOfBounds(filter.Transform->Position, config.MapExtends, out FPVector2 newPosition))
    {
        filter.Transform->Position = newPosition;
        filter.Transform->Teleport(frame, newPosition);
    }
}
Unityに戻り、設定ファイルからGameMapSizeを調整します。縦横比16:9を使用している場合は、値は(70, 40)が見た目上はちょうど良いでしょう。
ゲームを再生すると、船とアステロイドは設定ファイルの境界を使用するようになります。
