Map loading¶
There's only at most 1 MapControl that can be loaded at once and it will be the current map assigned to instance.map.
It means that in order to go to another map, the current one needs to be destroyed before instantiating the new one. Since this game doesn't involve traditional Unity scene loading, all of this is done through code. This implies the game is entirely managing the map loading system by itself.
There's only one way to safely change the current map: via the LoadMap method. This method however only does the map unloading/loading part. To have the complete transition done when using the transfer SetText command or when going through a DoorOtherMap, the TransferMap coroutine is used which ends up calling LoadMap, but with scripted party movement and fading transitions.
Basically, to have the complete map loading transition, the TransferMap coroutine is used and for any kinds of scripted map loading, LoadMap is called directly. It is very common for events to prefer the latter option because they can control the game so they can do the loading transition by themselves and simply call LoadMap directly. Both LoadMap and TransferMap are in MainManager.
LoadMap¶
There are 3 overloads, but all of them ends up at the first one:
(1) The main method that all overloads and TransferMap ends up using. The id parameter is the int representation of the new map value to change the current map to. If this value is the current map's id, it will be reloaded.
public static void LoadMap(int id)
(2) which calls (1) where id is map.name which should always be the matching maps of the current map. Effectively, it reloads the current map to the same map.
public static void LoadMap()
(3) This calls (1), but before doing so, if recreateplayers is true, all playerdata entity gets destroyed and the player is set to null. This will have the (1) overload recreate the player and the playerdata entities from scratch.
public static void LoadMap(int id, bool recreateplayers)
Here's what the method does:
Setup¶
- The camera angling logic is reset by setting
changecamspeedandcamanglechangeto false and settingcamanglespeedto 0.1 - flag 400 (temporary global flag) is reset to false
Current map destruction¶
This section only happens if instance.map isn't null and it involves its destruction:
- All
playerdata(if they exists) gets rooted to the scene and theirfollowedbyreset to null. Effectively, it breaks the follow chain and makes sure they not childed to anyone in the map - All of the instance.
map.tempfollowersgets destroyed - instance.
mapgets destroyed - If not
inevent, the existing ressources are cleaned up by doing the following:- GC.Collect is called. NOTE: This doesn't actually do anything useful because of the method call right after this
- Ressources.UnloadUnusedAssets is called. Since this ends up calling GC.Collect, the above call didn't do anything useful
New map load¶
This section always happen:
- The new map gets instantiated using the prefab located at
Ressources/Prefabs/Maps/XwhereXis the string representation of the map value whose int representation is the id parameter of the method mapgets set to the MapControl of the newly instantiated GameObject and its name is set to the string representation of the id parameter (it means the GameObject's name is an integer string representing the map value)
playerdata recreation¶
If player is null which can only happen if overload (3) was used with true as recreateplayer, SetPlayers will be called since the playerdata needs to be created as they just got destroyed by this point. This is what the method does on each playerdata:
entityis set to a new entity created vis CreateNewEntity with name beingPlayer XwhereXis theplayerdataindexentity's animid is set to theplayerdata'sanimidwhich is actually the fixed party order id, but they are aligned here to match the animidsentity'salwaysactiveis set to true since it's a player entity that shouldn't be inactiveentity's conditions is reset to a new empty list- If it's the first
playerdata, it's the main player:- A PlayerControl is added to the
entity's GameObject - instance.
camtargetis set to theentity's transform so the camera follows it entity'stagis set toPlayer
- A PlayerControl is added to the
- Otherwise, it's a player follower:
entity'sfollowingis set toplayerdata[0].entityif it's the second or toplayerdata[1].entityif it's the third so it setups the follow chain whereplayerdata[2]followsplayerdata[1]which followsplayerdata[0](the main player)entity.followoffsetis set to the index * 0.2 to prevent z fightingentity's GameObject layer is set to 9 (Follower)- The z position of the
entityis incremented y itsfollowoffsetwhich further prevents z fighting entitytag is set toPFollowerentitybecomes part of themainparty- The previous
playerdata'sentity.followedbyis set to thisentitywhich resolves the follow chain in reverse order
ForceLoadSprites¶
This section always happen:
- ForceLoadSprites is called which starts a coroutine called FLS which will doe the following in paralel(assuming the
map.entitiesisn't null):- All the
map.entities.sprite.sprite gets assigned to new dummy GameObjects's SpriteRenderer created just for this. They are childed to themapand positioned at theplayer - A fame gets yielded
- All the dummy GameObjects created earlier gets destroyed
- All the
player cleanup¶
This section only happens if player isn't null meaning that the overload (3) wasn't used (while the playerdata are recreated by this point, the PlayerControl hasn't started yet so player is still null here):
player'sentity.soundis set to stop looping and stops playingplayer.pausecooldownis set to 120.0 so no pausing is allowed for the next 120.0 frames
TransferMap¶
There are 3 overloads, but all of them ends up at the first one:
(1) The main method that all overloads ends up using. Here are the parameters:
targetmap: The int representation of the maps value corresponding to the new map to change tomoveto: The position the party will go towards before laoding the maptppos: The position the party will be placed after loading the mapothermovepos: The position the party will go towards after loading the map and after they were placed attppposcaller: The DoorOtherMap that is configuring this map transfer. This is optional, if it's null, no configuration from one will happen
public static IEnumerator TransferMap(int targetmap, Vector3 moveto, Vector3 tppos, Vector3 othermovepos, NPCControl caller)
(2) Starts a coroutine with (1) where caller is null then yield null. This is only used by the transfer SetText command which simulates a transfer, but there's no DoorOtherMap involved.
public static IEnumerator TransferMap(int targetmap, Vector3 moveto, Vector3 tppos, Vector3 othermovepos)
(3) Starts a coroutine with (1) where moveto is the player position, both tppos and othermovepos are the sent targetpos and caller is null, then yield null. This overload is UNUSED.
public static IEnumerator TransferMap(int targetmap, Vector3 targetpos)
What follows is what the coroutine does.
Setup¶
roomtransitionis set to true which prevents DoClock from cleaning up unused ressources- If there is a caller DoorOtherMap, its
data,emoticonoffset.xandvectordata[3]throughvectordata[6]are saved locally since they are the data fields that configures this map transfer - All of the current
map'sentitieshas BreakIce called on them and theirnpcdata'sboxcoldisabled if it exists - We enter a
minipause - CancelAction is called on the
player - Yield all frames until
pausemenugets null (meaning the game is unpaused)
Move out and fade in¶
- PlayTransition is called with id and data set to 0 (fade out) at a speed of 0.1 and a color of pure black
- If there is a caller DoorOtherMap and its
data[4]isn't 1, it means the movement during the fade in will happen:playerMoveTowards moveto at 1.0 speed using 1 (Walk) as state and 0 (Idle) as stopstate with ignore_y- instance.
camtargetposis set to theplayerand instance.camtargetis set to null so it fixes the camera in place to where theplayeris - Yield all frames until the
player'sforcemoveis done
- Otherwise (the caller's
data[4]is 1), no movment will happen and instead, allplayerdatahas theirrigidgravity disabled and put in isKinematic followed by a frame yield - Yield all frames until the color of the
transitionobj[0]'s SpriteRender gets to 0.9 or above (it basically yields until the fade out is 90% done) transitionobj[0]'s SpriteRender's color alpha is set to 1.0 (fully opaque). This forces the end of the PlayTransition call from earlier because it's been waiting for that to happen- Yield for a frame
Map load and move in preparation¶
- LoadMap is called with the targetmap as new map id
- If we aren't instance.
inevent, instance.insideidis set to -1 which marks the player not being in any inside - instance.
globalcooldownset to 30.0 which prevents most inputs for 30.0 frames - instance.
inmusicrangeis set to -1 which clears any MusicRange from the previous map if it existed - Yield for a frame
MainCameraposition is manually set toplayerdata[0]position + instance.camoffset- instance.
camspeedis set to 1.0, but its existing value is saved for restoring it later - Yield for a frame
- All
playerdata'sentityhas the following happen on them:- position set to tppos +
MainCamera's forward vector * theplayerdataindex / 10.0 (this essentially prevents z fightings) rigidvelocity zeroed out with gravity and without isKinematicongroundset to false (forces an animation update)
- position set to tppos +
- Yield for a frame
- All
map'sentities(the new map as it's loaded by this point) has CheckNear called on them - Yield for a frame
- ForceLoadSprite is called which does the same than what was already done in LoadMap so this doesn't do anything useful
- Yield for 0.1 seconds
- All
playerdataFaceTowards othermovepos - instance.
camtargetis set to theplayerso the camera is back to following theplayer - If there was a caller DoorOtherMap, this is where its configuration fields are used to configure the camera which will override the ones used for the new map camera system:
- If
data[1]is 1, instance.camoffsetis set tovectordata[3] - If
data[2]is 1, instance.camangleoffsetis set tovectordata[4] - If
data[3]is 1,map.camlimitposis set tovectordata[5]andmap.camlimitnegis set tovectordata[6]
- If
- Yield for 0.3 seconds
- instance.
camspeedis reset to its value saved earlier - Yield for a frame
Fade in and move in¶
- PlayTransition is called with id and data set to 1 (fade in) at a speed of 0.1 and a color of pure black
- Yield for a frame
- If there was a caller DoorOtherMap and its
emoticonoffset.xwas above 0.1, the player party needs to be moved via a jump:player'sjumproutineis set to a new JumpTo call towards othermovepos with a height of the caller'semoticonoffset.x- Yield until the
player'sjumproutineis done. Before each frame yield, allplayerdata'sentity's animstate is set to 3 (Jump) and theirongroundto false (force an animation update)
- Otherwise (normal movement without jump):
player'sentityMoveTowards othermovepos at a speed of 1.0 using 1 (Walk) as state, 0 (Idle) as stopstate and with ignore_y- Yield all frames until the
player'sforcemoveis done
Cleanup¶
player'sentityhas DetectIgnoreSphere called with ignore meaning the player entity'sdetectis set to ignore all collision with any NPCControl'sscol- We exit the
minipause player'slastposandlastloadzoneare set to their current position- Yield for a frame
player'sentity.hitwallis set to false forcing its wall detector offplayer'snpcis reset to a new empty listplayer'spausecolldownis set to 7.0 so no pausing can happen for the next 7.0 framesroomtransitionis reset to false which allows DoClock to cleanup unused ressources again