This recipe creates one base game rock in the player’s current room. Call SpawnRock(self) from a deliberate event such as a fresh button press in Player ability. Calling it on every update would create objects continuously.

Create the abstract record first

using UnityEngine;
 
private static Rock SpawnRock(Player player)
{
    if (player == null || player.room == null || player.mainBodyChunk == null)
        return null;
 
    Room room = player.room;
    if (room.world == null || room.game == null || room.abstractRoom == null)
        return null;
 
    Vector2 spawnPosition = player.mainBodyChunk.pos + Vector2.up * 25f;
    WorldCoordinate coordinate = room.GetWorldCoordinate(spawnPosition);
 
    var abstractRock = new AbstractPhysicalObject(
        room.world,
        AbstractPhysicalObject.AbstractObjectType.Rock,
        null,
        coordinate,
        room.game.GetNewID());
 
    room.abstractRoom.AddEntity(abstractRock);
    abstractRock.RealizeInRoom();
 
    Rock rock = abstractRock.realizedObject as Rock;
    if (rock == null || rock.room != room || rock.bodyChunks.Length == 0)
    {
        if (abstractRock.realizedObject != null &&
            abstractRock.realizedObject.room == room)
            room.RemoveObject(abstractRock.realizedObject);
        room.abstractRoom.RemoveEntity(abstractRock);
        return null;
    }
 
    rock.firstChunk.HardSetPosition(spawnPosition);
    rock.firstChunk.vel = Vector2.up * 2f;
    return rock;
}

The helper checks the references required by object construction. Its caller must still decide whether the current player state permits spawning. The guard pattern in Player ability rejects deletion, unconscious players, NPC players, and shortcut travel.

global::AbstractPhysicalObject(World, AbstractObjectType, PhysicalObject, WorldCoordinate, EntityID) creates the world record. global::RainWorldGame.GetNewID() supplies an identity owned by the current game. global::AbstractRoom.AddEntity(global::AbstractWorldEntity) registers abstract room membership. global::AbstractPhysicalObject.RealizeInRoom() creates the global::Rock and places it in the realized room.

RealizeInRoom() does not place an ordinary rock’s body chunk at the requested world pixel. The coordinate records its abstract tile. HardSetPosition(Vector2) assigns the current and historical chunk positions, so the new rock begins at spawnPosition without an interpolation trail from its constructor position. The small velocity then moves it upward.

Do not also call room.AddObject(rock). RealizeInRoom() reaches global::PhysicalObject.PlaceInRoom(global::Room), which already calls global::Room.AddObject(global::UpdatableAndDeletable). In the inspected baseline, Room.AddObject appends to updateList and the collision layer without a duplicate check. Abstract and realized objects describes both ownership layers.

Expected result and checks

One call should add one AbstractPhysicalObject to room.abstractRoom.entities and one realized Rock to the active room. The rock should appear 25 world units above the player and move upward slightly.

That position may overlap terrain, another body, or a narrow shortcut. A production ability should choose and validate a free tile before creation. The defensive failure branch removes any realized object placed in this room, removes the abstract record, and returns null. The caller can then log its own feature context. Test room unloading, shelters, gates, repeated calls, and cooperative players. Abstract room membership by itself does not make the rock part of a campaign save. The helper compiled against the recorded baseline with .NET SDK 10.0.400 and C# 7.3. It was not run in the game.

Sources

  • global::AbstractPhysicalObject, global::AbstractRoom.AddEntity(global::AbstractWorldEntity), global::AbstractRoom.RemoveEntity(global::AbstractWorldEntity), global::AbstractPhysicalObject.RealizeInRoom(), global::PhysicalObject.PlaceInRoom(global::Room), global::Room.GetWorldCoordinate(UnityEngine.Vector2), global::RainWorldGame.GetNewID(), and global::BodyChunk.HardSetPosition(UnityEngine.Vector2) in the baseline assembly recorded by Runtime reference.