Making the Entrance

We are now onto the last part of our floor tutorial! Congratulations. You've almost finished. now we just need to make the entrance.

For this example, I will make a pit to load the floor, but you can use other things as well.

Make the entrance of your floor in RAT. Do not add any pits.

Go back to ModRoomPrefabs and add a new PrototypeDungeonRoom

public static PrototypeDungeonRoom Keep_Entrance_Room;

Ideally, we want our room to have an icon, if we want this we go add new objects to our asset bundle, one empty gameobject, and one texture (the icon).

Go into ModPrefabs and add public static GameObject Entrance_Icon; in InitCustomPrefabs drop

Entrance_Icon = ModAssets.LoadAsset<GameObject>("Entrance_Icon");
ItemAPI.ItemBuilder.AddSpriteToObject(Entrance_Icon, ModAssets.LoadAsset<Texture2D>("Entrance_Icon"), false, false);

inside the InitCustomRoom add

Keep_Entrance_Room = RoomFactory.BuildFromResource("DPSMod/Resources/ModRooms/Keep_Entrance_Room.room");
Keep_Entrance_Room.associatedMinimapIcon = ModPrefabs.Entrance_Icon; //not required

(Tiny pumpkin is the room icon lol. its blurry here but i just forgot to change its filter mode to point)

now we head back towards FloorNameDungeonFlows inside InitDungeonFlows

This section has to do with our entrance

m_KeepEntranceRooms = ScriptableObject.CreateInstance<GenericRoomTable>();
            m_KeepEntranceRooms.includedRoomTables = new List<GenericRoomTable>(0);
            m_KeepEntranceRooms.includedRooms = new WeightedRoomCollection()
            {
                elements = new List<WeightedRoom>() {
                    //we will place the entrance to our floor here.
                }
            };


            SecretFloorNameEntranceInjector = new ProceduralFlowModifierData()
            {
                annotation = "Secret Floor Entrance Room",
                DEBUG_FORCE_SPAWN = false,
                OncePerRun = false,
                placementRules = new List<ProceduralFlowModifierData.FlowModifierPlacementType>() {
                    ProceduralFlowModifierData.FlowModifierPlacementType.RANDOM_NODE_CHILD
                },
                roomTable = m_KeepEntranceRooms,
                // exactRoom = SewersInjectionData.InjectionData[0].exactRoom,
                exactRoom = null,
                RequiresMasteryToken = false,
                chanceToLock = 0,
                selectionWeight = 1,
                chanceToSpawn = 1,
                RequiredValidPlaceable = null,
                prerequisites = new DungeonPrerequisite[0],
                CanBeForcedSecret = true,
                RandomNodeChildMinDistanceFromEntrance = 0,
                exactSecondaryRoom = null,
                framedCombatNodes = 0,
            };

            CastleInjectionData.InjectionData.Add(SecretFloorNameEntranceInjector); 

here we add our room

m_KeepEntranceRooms.includedRooms = new WeightedRoomCollection()
            {
                elements = new List<WeightedRoom>() {
                    //we will place the entrance to our floor here.
                }
            };
m_KeepEntranceRooms.includedRooms = new WeightedRoomCollection()
            {
                elements = new List<WeightedRoom>() {
                    ModRoomPrefabs.GenerateWeightedRoom(ModRoomPrefabs.Keep_Entrance_Room, Weight: 1f)
                }
            };

we can add more rooms if we want some variance and it will randomly select one of these rooms.

Add this to your toolbox:

public static SpeculativeRigidbody GenerateOrAddToRigidBody(GameObject targetObject, CollisionLayer collisionLayer, PixelCollider.PixelColliderGeneration colliderGenerationMode = PixelCollider.PixelColliderGeneration.Tk2dPolygon, bool collideWithTileMap = false, bool CollideWithOthers = true, bool CanBeCarried = true, bool CanBePushed = false, bool RecheckTriggers = false, bool IsTrigger = false, bool replaceExistingColliders = false, bool UsesPixelsAsUnitSize = false, IntVector2? dimensions = null, IntVector2? offset = null)
        {
            SpeculativeRigidbody m_CachedRigidBody = GameObjectExtensions.GetOrAddComponent<SpeculativeRigidbody>(targetObject);
            m_CachedRigidBody.CollideWithOthers = CollideWithOthers;
            m_CachedRigidBody.CollideWithTileMap = collideWithTileMap;
            m_CachedRigidBody.Velocity = Vector2.zero;
            m_CachedRigidBody.MaxVelocity = Vector2.zero;
            m_CachedRigidBody.ForceAlwaysUpdate = false;
            m_CachedRigidBody.CanPush = false;
            m_CachedRigidBody.CanBePushed = CanBePushed;
            m_CachedRigidBody.PushSpeedModifier = 1f;
            m_CachedRigidBody.CanCarry = false;
            m_CachedRigidBody.CanBeCarried = CanBeCarried;
            m_CachedRigidBody.PreventPiercing = false;
            m_CachedRigidBody.SkipEmptyColliders = false;
            m_CachedRigidBody.RecheckTriggers = RecheckTriggers;
            m_CachedRigidBody.UpdateCollidersOnRotation = false;
            m_CachedRigidBody.UpdateCollidersOnScale = false;

            IntVector2 Offset = IntVector2.Zero;
            IntVector2 Dimensions = IntVector2.Zero;
            if (colliderGenerationMode != PixelCollider.PixelColliderGeneration.Tk2dPolygon)
            {
                if (dimensions.HasValue)
                {
                    Dimensions = dimensions.Value;
                    if (!UsesPixelsAsUnitSize)
                    {
                        Dimensions = (new IntVector2(Dimensions.x * 16, Dimensions.y * 16));
                    }
                }
                if (offset.HasValue)
                {
                    Offset = offset.Value;
                    if (!UsesPixelsAsUnitSize)
                    {
                        Offset = (new IntVector2(Offset.x * 16, Offset.y * 16));
                    }
                }
            }
            PixelCollider m_CachedCollider = new PixelCollider()
            {
                ColliderGenerationMode = colliderGenerationMode,
                CollisionLayer = collisionLayer,
                IsTrigger = IsTrigger,
                BagleUseFirstFrameOnly = (colliderGenerationMode == PixelCollider.PixelColliderGeneration.Tk2dPolygon),
                SpecifyBagelFrame = string.Empty,
                BagelColliderNumber = 0,
                ManualOffsetX = Offset.x,
                ManualOffsetY = Offset.y,
                ManualWidth = Dimensions.x,
                ManualHeight = Dimensions.y,
                ManualDiameter = 0,
                ManualLeftX = 0,
                ManualLeftY = 0,
                ManualRightX = 0,
                ManualRightY = 0
            };

            if (replaceExistingColliders | m_CachedRigidBody.PixelColliders == null)
            {
                m_CachedRigidBody.PixelColliders = new List<PixelCollider> { m_CachedCollider };
            }
            else
            {
                m_CachedRigidBody.PixelColliders.Add(m_CachedCollider);
            }

            if (m_CachedRigidBody.sprite && colliderGenerationMode == PixelCollider.PixelColliderGeneration.Tk2dPolygon)
            {
                Bounds bounds = m_CachedRigidBody.sprite.GetBounds();
                m_CachedRigidBody.sprite.GetTrueCurrentSpriteDef().colliderVertices = new Vector3[] { bounds.center - bounds.extents, bounds.center + bounds.extents };
                // m_CachedRigidBody.ForceRegenerate();
                // m_CachedRigidBody.RegenerateCache();
            }

            return m_CachedRigidBody;
        }

now we need a new object in our assetbundle. we will place this in our entrance room. Go to ModPrefabs and add public static GameObject FloorNameEntrance;

Get it like so FloorNameEntrance = ModAssets.LoadAsset("FloorNameEntrance"); you can add a sprite to this object if you wish.

create a new file called KeepEntranceControllerthat inherits from BraveBehaviour, IPlaceConfigurable.

just copy AAAALLLLLLLL this in

public KeepEntranceController()
        {
            targetLevelName = "tt_floorname";
            PitOffset = new IntVector2(5, 2);
            m_Triggered = false;
            m_Destroyed = false;
        }

        public string targetLevelName;
        public IntVector2 PitOffset;

        private bool m_Triggered;
        private bool m_Destroyed;

        private RoomHandler m_ParentRoom;

        private IEnumerator Start()
        {
            yield return null;
            while (GameManager.Instance.IsLoadingLevel && Dungeon.IsGenerating) { yield return null; }
            yield return null;

            //IntVector2 baseCellPosition = (transform.position.IntXY(VectorConversions.Floor) + new IntVector2(4, 1.5));

            // specRigidbody.OnHitByBeam = (Action<BasicBeamController>)Delegate.Combine(specRigidbody.OnHitByBeam, new Action<BasicBeamController>(HandleBeamCollision));
            GameObject PitManager = new GameObject("Pit Manager") { layer = 0 };
            PitManager.transform.position = (transform.position + new Vector3(5, 1.5f));
            tk2dSprite PitDummySprite = PitManager.AddComponent<tk2dSprite>();
            //Toolbox.DuplicateSprite(PitDummySprite, null);
            tk2dSprite pitSprite = PitManager.GetComponent<tk2dSprite>();
            pitSprite.renderer.enabled = false;

            toolbox.GenerateOrAddToRigidBody(PitManager, CollisionLayer.Trap, PixelCollider.PixelColliderGeneration.Manual, IsTrigger: true, dimensions: new IntVector2(2, 2));

            KeepEntrancePitController HallPitManager = PitManager.AddComponent<KeepEntrancePitController>();
            HallPitManager.targetLevelName = targetLevelName;
            yield break;
        }

        


        /*private void HandleBeamCollision(BasicBeamController obj) {
            GoopModifier component = obj.GetComponent<GoopModifier>();
            if (component && component.goopDefinition != null && component.goopDefinition.CanBeIgnited && component.goopDefinition.fireEffect != null) { OnFireStarted(); }
        }*/

        public void ConfigureOnPlacement(RoomHandler room)
        {
            m_ParentRoom = room;

            

            IntVector2 basePosition = (transform.position.IntXY(VectorConversions.Floor) + PitOffset);
            IntVector2 cellPos = basePosition;
            IntVector2 cellPos2 = (basePosition + new IntVector2(1, 0));
            IntVector2 cellPos3 = (basePosition + new IntVector2(1, 1));
            IntVector2 cellPos4 = (basePosition + new IntVector2(0, 1));
            CellData cellData = GameManager.Instance.Dungeon.data[cellPos];
            CellData cellData2 = GameManager.Instance.Dungeon.data[cellPos2];
            CellData cellData3 = GameManager.Instance.Dungeon.data[cellPos3];
            CellData cellData4 = GameManager.Instance.Dungeon.data[cellPos4];

            cellData.type = CellType.PIT;
            cellData2.type = CellType.PIT;
            cellData3.type = CellType.PIT;
            cellData4.type = CellType.PIT;

            cellData.forceAllowGoop = false;
            cellData2.forceAllowGoop = false;
            cellData3.forceAllowGoop = false;
            cellData4.forceAllowGoop = false;

            cellData.fallingPrevented = false;
            cellData2.fallingPrevented = false;
            cellData3.fallingPrevented = false;
            cellData4.fallingPrevented = false;
        }

        private void Update() { }
        private void LateUpdate() { }

        protected override void OnDestroy()
        {
            m_Destroyed = true;
            base.OnDestroy();
        }
    }

    public class KeepEntrancePitController : DungeonPlaceableBehaviour, IPlaceConfigurable
    {

        public KeepEntrancePitController() { targetLevelName = "tt_floorname"; }

        public string targetLevelName;

        private void Start()
        {
            
            SpeculativeRigidbody Rigidbody = specRigidbody;
            Rigidbody.OnEnterTrigger = (SpeculativeRigidbody.OnTriggerDelegate)Delegate.Combine(Rigidbody.OnEnterTrigger, new SpeculativeRigidbody.OnTriggerDelegate(HandleTriggerEntered));
            SpeculativeRigidbody Rigidbody2 = specRigidbody;
            Rigidbody2.OnExitTrigger = (SpeculativeRigidbody.OnTriggerExitDelegate)Delegate.Combine(Rigidbody2.OnExitTrigger, new SpeculativeRigidbody.OnTriggerExitDelegate(HandleTriggerExited));
        }

        private void HandleTriggerEntered(SpeculativeRigidbody specRigidbody, SpeculativeRigidbody sourceSpecRigidbody, CollisionData collisionData)
        {
            PlayerController component = specRigidbody.GetComponent<PlayerController>();
            if (component) { component.LevelToLoadOnPitfall = targetLevelName; }
        }

        private void HandleTriggerExited(SpeculativeRigidbody specRigidbody, SpeculativeRigidbody sourceSpecRigidbody)
        {
            PlayerController component = specRigidbody.GetComponent<PlayerController>();
            if (component) { component.LevelToLoadOnPitfall = string.Empty; }
        }

        public void ConfigureOnPlacement(RoomHandler room) { }

        private void Update() {
            

        }

        protected override void OnDestroy() { base.OnDestroy(); }
    }

make sure to change values according to your needs.

now we head back to ModPrefabs and do

FloorNameEntrance.AddComponent<KeepEntranceController>();

And in ModRoomPrefabs do

RoomBuilder.AddObjectToRoom(Keep_Entrance_Room, new Vector2(3.5f, 12) /* needs tweaking */ , toolbox.GenerateDungeonPlacable(ModPrefabs.FloorNameEntrance, useExternalPrefab: true));

tada! congratulations, you have just done one of the hardest things in modding! nice work and i cant wait to see the results :).

Last updated