-
Notifications
You must be signed in to change notification settings - Fork 0
Programming Overview
Use C# naming conventions. Briefly: camelCase for local vars and non-public member vars, PascalCase for types, public member vars, properties. Make sure code is formatted using default VS formatter (on windows, Ctrl+K Ctrl+D). You can also name private variables with an underscore, notably for properties.
Name ScriptableObjects with "NameSO".
Use ScriptableObject
s wherever you need a data structure that needs to be instantiated by hand and/or a data structure that is used across multiple classes. ScriptableObject
s lets you modify the data structure in the editor, which is friendlier and does not require modifying code, and allows you to easily pass around the data without any code coupling.
ScriptableObject
s are preferred to static variables, since static variables require coupling. However, this is not as enforced.
Another use of ScriptableObject
s is to get a referentially unique ID. This is useful for identifying things. For example, instead of using strings to identify things, you can use a ScriptableObject
. For example, to check if the player is holding a certain fish, compare the FishSO
instead of the fish's name.
You can also use ScriptableObject
s to simplify components by moving non-scene parameters to the Project
pane instead of the scene. This makes it so you can modify these settings without commiting any scene files. This is completely optional and shouldn't be overdone.
Items are anything the player can hold, and currently includes:
- Fishing Rod
- Fish Item
Items are described by an ItemSO
asset, which contains the item's name and its prefab (at this point). The prefab must have a script deriving Item
on it.
Implementation note:
ItemSO
s are referentially unique (as compared to Prefabs, which are usually duplicated at runtime), so you can do comparisons with them (eg.if (playerItem.EnabledItem == fishingRod)
, which would not work if we were comparing a prefab to the instantiated GameObject).
Items are managed by the PlayerItem
script (on the Player). This is what manages what items are owned by the player, switching between items, and activating items. When an item is enabled, the current item's GameObject is destroyed and the new item's prefab is instantiated.
The player holds some items in their "inventory" (represented by an ItemSO
list). You can cycle the items using the scroll wheel.
All items that aren't in the item wheel are called temporary items, and can be enabled programmatically (eg. the Fishing Rod enables the Fish Item as a temporary item when you collect a fish).
Since the PlayerItem
script instantiates a new item immediately but destroys the old item next frame (that's how Destroy
works), the old item can interact with the new item even after the items get swapped. For example, the Fishing Rod enables the Fish Item and then calls the Fish Item's SetFish
function to set which fish is being held, which would not be possible if the Fishing Rod got destroyed immediately.
There are some utility functions for Items, seen in the Item
base class. Notably, a callback for when the item is used (OnUse
, spacebar), a callback for immediately after the item is Destroyed (OnStopUsingItem
), and an initialization function (Initialize
) that stores protected member values for the player GameObject
(player
), the PlayerInput
(playerInput
), and the PlayerItem
(playerItem
).
PlayerInput
sends events that you can configure in the editor. Since items are created at runtime, you cannot hook up input events to them in the editor. Thus, they have to be done by hand using InputAction
s (see Input System's docs for Responding to Actions)
The input system has an InputActionReference
class which allows you to reference a specific input action (eg. UseItem
, Move
) in the editor, which you can then access the InputAction
via InputActionReference.action
. You can then register callbacks to the InputAction
by creating a function with the signature void (InputAction.CallbackContext ctx)
(eg. void OnMove(InputAction.CallbackContext ctx)
) and subscribing to the InputAction.started
, InputAction.performed
, and/or InputAction.canceled
callbacks by doing inputActionAsset.asset.performed += MyFunction
. Usually, you subscribe to performed
for buttons and performed
and canceled
for values. Importantly, once your object gets destroyed (in OnDestroy
), you must unregister from these events, or else there will be null reference exception, by doing inputActionAsset.asset.performed -= MyFunction
.
Check the FishingRod
Initialize
and OnDestroy
for an example of subscribing and unsubscribing to input actions.
There are a lot of types of fish instances, for example, the fish in the water, the fish in your hand, the fish in the notebook. These are all gathered into one FishSO asset.
The FishSO asset currently holds:
- The Fish's name
- The Fish's in-water prefab
- The Fish's in-hand prefab
Implementation note: you cannot swap animation controllers at runtime. If there are two animators for a fish, the only way to swap between them is to have two separate prefabs. The alternative is to use separate state loops for the in-water and in-hand.
We want to have global state mainly managed by ScriptableObject assets, since they can be referenced in the editor.
Global parameters are in the GlobalParametersSO
class.
Data for many objects, including fish, items, fishing zones, etc. are stored in respective ScriptableObject
s next to their scripts.