Creating Equipment
This page documents the workflow to follow when creating a new equipment
Equipment Inspector Settings
Important: Make sure to clear out any child colliders the equipment object have, only one collider on the root and set to IsTrigger = true
Deferred lighting limitation: up to 4 exclusion layers only! Most equipment setting would probably only need to exclude default & Door layer
Clear out child colliders and set root collider to IsTrigger.
Clear out child GameObjects to UnTagged and Default layer.
Set root GameObject layer to Default, tag to Equipment
Mak all MeshRenderer on root and child to Not Cast Shadow
Make sure the collider, if
Box Collider
, has correct bounds that tightly fit the equipment shape.
Important: Equipment SHOULD NOT cast shadow.
Verify by Testing: Use console command /SpawnEq <index>
where index is the index of the equipment in _GlobalContainer
prefab's Global Equipment
array.
Then, scrollwheel to switch to the equipment and verify that:
Placement in hand is correct
No shadows being casted
One collider at root, and is set to IsTrigger; otherwise, equipment may push or restrict player movement. If the movement feels off, it is likely being pushed by a collider in the equipment.
Equipment Script:
Extend Equipment.cs
, set placeable type, hand IK type, placement preview if necessary, and consult prior examples.
Set
Drop Audio
field toDropped Equipment
or any other drop audio clip.Set
Name
field to a name for the equipment.Set
useBattery
field if object uses battery and need to be rechargedSet
mIsTurnedOn
field if, by default equipment is turned on (i.e. Phone, Infrared Detector)If
useBattery = true
, implementation needs to handleON
/OFF
state, overrideOnEquipmentOnOffStateChangedClient(bool state, bool isOfficial)
for client-side feedback and effect.isOfficial=true
means the change is valid, false would be to play supposed/trying to turnON
/OFF
effect, ie. clicking sound of flashlight buttons without turning on the light if not officialImportant:
OnEquipmentOnOffStateChangedClient
is only intended for client-side feedbackFor Server-Side equipment response logic, MUST USE
OnEquipmentOnOffStateChangedServer
asOnEquipmentOnOffStateChangedClient
is ignored when the local hierarchy player client is in Mirage! (No need for Mirage check here : )
If
useBattery = true
and equipment has per-tick validation logic, ie. EMF Meter needs to check battery state each tick usingmIsTurnedOn
before doing detection calculations, asGetCanUse()
only checks if the equipment can be used (current client player is allowed to use equipment and is holding it), it needs to include the check formIsTurnedOn
explicitly before other logicBy default, the battery drains at
DEFAULT_BATTERY_DEPLETION_DURATION
rate. For specific actions of the equipment that drain the battery in addition to basic drainage, useServerDepleteBattery(uint amount)
, i.e., Phone upload virus significantly depletes the battery.Override
LocalSetCurrentTimeframeVisibility(bool visible)
to handle logic relating to Mirage state change, i.e. hiding Flashlight light when its local client is in Mirage. Must check for mIsTurnedOn for equipment using battery to maintain correct logic.OPTIONAL: Override
IEAnimatedOnBatteryDepleted()
andIEAnimatedOnBatteryRestored()
for effects like flashlight flashing a few times before turning off when the battery is depleted.
Starting mIsTurnedOn value must be set correctly! If not set correctly, the battery death response won't be triggered due to the Hook not firing for the same value! Set with in Inspector window when selecting equipment prefab
LocalSetCurrentTimeframeVisibility
must check for mIsTurnedOn
, but
OnEquipmentOnOffStateChangedClient does not need to check Mirage because it's not invoked when player is in Mirage
// Example of handling client state feedback and server logic
public override void OnEquipmentOnOffStateChangedClient(bool state, bool isOfficial)
{
base.OnEquipmentOnOffStateChangedClient(state, isOfficial);
// Play switch audio regardless, allowing player to mess around even when battery is dead
// This is skipped if the local hierarchy player is in the Mirage, preventing Mirage players from hearing the switch click
Play2DAudio(mSwitchAudioClip, 0.86f);
// Only turn on if it's official (validated to have battery) and trying to be turned on
// Don't worry about Mirage state here because the callback will not invoke if player is in Mirage
mLight.enabled = isOfficial && state;
}
public override void OnEquipmentOnOffStateChangedServer(bool state, bool isOfficial)
{
base.OnEquipmentOnOffStateChangedServer(state, isOfficial);
// Handling Server logic here to make sure if host is in the Mirage, logic is still handled
// And won't break for other players
// Turn on must be official (validated and not supposed/trying action feedback)
if (state && isOfficial)
ServerSetEMFLevel(1);
else
ServerSetEMFLevel(0);
}
// Handling of client logic, ie. Input, should filter for active player
// Since no isLocalPlayer available, must check with GetCanUse()
// GetCanUse() returns true on the realm of current holding player, if that player
// also is able to use equipment (alive and in state of can use equipment)
public override void Update()
{
base.Update();
if (GetCanUse())
{
if (GameManager.GetRewired().GetButtonDown(LIMENDefine.ACTION_TOGGLE_ITEM))
{
// Toggles the equipment On/Off, handles official and non-official state
// ie. Allows player to play with power switch when battery is dead
// Can hear the switch toggle audio but equipment won't actually turn on
CMDToggleEquipmentOnOffState();
}
}
}
// Server equipment tick must check for mIsTurnedOn if uses battery to prevent
// making actions when it's turned off which would be very odd
public void ServerTickEMF()
{
if (!mIsTurnedOn)
return;
int tempLevel = 1;
mHitsCache = mDetectFromPlayerCenter ? Physics.RaycastAll(mPlayerCamera.ViewportPointToRay(new Vector3(0.5F, 0.5F, 0F)), DETECTION_DISTANCE, layers) : Physics.RaycastAll(transform.localPosition + RAYCAST_OFFSET, transform.up, DETECTION_DISTANCE, layers);
if (mHitsCache == null || mHitsCache.Length == 0)
{
// No hit detected
ServerSetEMFLevel(tempLevel);
return;
}
// Other logic below
}
// Some code
public override void LocalSetCurrentTimeframeVisibility(bool visible)
{
base.LocalSetCurrentTimeframeVisibility(visible);
m_light.enabled = visible && mIsTurnedOn;
}
// Automatically turn equipment ON after battery restores if this equipment has no client ON/OFF control, i.e. Infrared Detector
public override void OnBatteryDepletionRestoredServer()
{
base.OnBatteryDepletionRestoredServer();
ServerSetEquipmentTurnedOnState(true);
}
Placeable Equipment (Skip if not):
Check
Is_Placeable
For placeable equipment, make a clone of the Equipment and remove all scripts and make a mesh only prefab, then assign to equipment's
placement_preview
field.Make sure
BoxCollider
is added andisTrigger
set to true.Fill in placement_offset, usually half of the mesh's size along the forward axis plus epsilon for z-fighting
Remove all scripts, especially
NetworkIdentity.cs
script if attached.
Important: Check to make sure Collider on Placement Preview has IsTrigger checked. Otherwise, it could cause unwanted physics behaviors, such as pushing the player off the map!
Multi-Stack Equipment (Skip if not):
Multistack equipment is equipment that you hold, but there are many instances of it being used. One example is a salt pile in a salt bottle, where a player purchases one salt bottle that can place 6 salt piles before being exhausted.
Use or inherit from
MultiStackEquipment
Fill in
mCount
for how many instances it can spawnMake prefab for
PF_InstanceObject
, which is the prefab that will be network spawned when an instance is placed. Script for logic should inherit fromMultiStackInstance
Same setup for
placement_preview
as above. Check Placeable Equipment setup.
Multi-stack PF_InstanceObject needs to inherit from MultiStackInstance
to handle Mirage visibility change for all placed instances.
Equipment Holding in Hand:
Place the equipment as a child of Hand (Transform)
and clear local position, local rotation to 0. We will adjust the position and rotation of the Hand (Transform)
until the desired placement of the equipment in hand is achieved and save the position and euler angles to the Initialization Storage
settings of the equipment.
Adding Equipment to GlobalContainer
Click open the _Global Container
prefab and add a new entry under Global Equipments
Generate Preview Image
Click open the equipment prefab and click Generate Preview Image
button, make sure the generated sprite is referenced, and push the change to version control.
Important: When pushing change to version control, undo any changes done to the current scene as it might be used for testing the equipment.
Also, click Undo Unchanged
, to eliminate bulked garbage check-ins
Add Equipment to Store Catalog
Create a new Scriptable Object in folder
_Store
->Database
->StoreProducts
->Equipment
with the same name as the equipment'sName
field. Hint: can duplicate an existing SO and fill in changes.Fill in
Item Name
,Type
(Equipment), select theImage
(should be same as Equipment'sPreview Image
)Set a
Cost
and update the Cost Table on GitBook.Add a new Page under Equipment in GitBook and link the cost table entry with the page.
Checkout
GBStoreDB
Scriptable Object and add the created equipment SO to the catalog array.
Add Store Detail Translations
Checkout
LeanLocalization
prefabDuplicate an existing entry for the item name with path:
StoreEQ/EQName/EQUIPMENT_NAME
Duplicate an existing entry for the item description with path:
StoreEQ/EQDesc/EQUIPMENT_NAME
Fill in entries and verify translations show up in store
Push Changes
Make sure GBStoreDB
changes and newly added equipment SO are recorded. Now a new equipment is ready in game!
Adding Equipment Use Hints (Controls Display)
In Equipment script, InitControlsHints()
and add new ControlHintInstruction
as needed.
public override void InitControlsHints()
{
base.InitControlsHints();
//Add additional controls as needed
ControlHintInstruction useInstruction = new ControlHintInstruction("Fire", LEAN_USE_KEY, true);
mControlHints.Add(useInstruction);
}
Make sure the lean usage key is present in LeanLocalization
prefab, best to make it a const string
and add it to the localization prefab.
Last updated