-
Notifications
You must be signed in to change notification settings - Fork 38
Tutorial
This tutorial will go over custom character creation for Risk of Rain 2. It assumes a basic understanding of Unity and C# at the very least. If you don't have that and get stuck, look up tutorials! I can't teach you everything here.
Creating a character is a daunting task. Between modeling, animation, coding, skill icons, sounds, VFX and more there's a whole lot that goes into it. Don't expect it to be an easy process.
The first step would be cloning this repo and opening up HenryMod.sln
in Visual Studio
.
The recommended starting point is skill creation. This tutorial is a good place to start. Keep in mind, step 1 of that tutorial goes over adding the skill to a body, which is already streamlined with Henry. As you follow the skills tutorial, step 2 and onwards is what will transfer over here.
Creating skills is a pretty specific process that involves a lot of hard coding that greatly differs depending on what you want the skill to do, so there's not a whole lot that can be explained here. 3 basic types of attacks, OverlapAttacks (melee), BulletAttacks (hitscan) and Projectiles have been included as examples.
Once you have skills down, then it's time to move on to creating assets. Of course you can always start with the assets if that's what you want.
The software generally used to create and animate models is Blender- even Hopoo himself uses it. There's lots of tutorials out there so Blender is the one I recommend. However if you're comfortable with something else feel free to use that.
When creating a character model:
- it's important to note that you're limited to having one material per mesh.
- There is, however, no limit to the amount of meshes you can have on your model.
- See the Survivor Model Tips page for more info on how we've learned to make survivors over the years.
When exporting your character model from blender into Unity, it's important to fix a scaling issue that can happen if your export settings aren't set correctly.
Here are the recommended FBX export settings:
The important things to note are to set Apply Scalings
to FBX All
, and turn off Add Leaf Bones
. Other than that, it doesn't have to look exactly like this.
You're gonna want Unity version 2021.3.33f1, the one Risk of Rain 2 currently uses. Download here
Once you have this, if you haven't already, grab Henry's Unity project from the repo. This is recommended over creating a new one so that you can use Henry as a reference as well as add several scripts I've included to make things easier.
Once that's done, follow these steps:
- Import your model into unity. Drag it into the scene. Rename the
GameObject
to "mdlYourCharacterName", replacing YourCharacterName with your character's name of course. Remember the name you set here. - Add
Animator
component if one doesn't already exist. I highly recommend copying Henry's animator (animHenry
) and adding your own animations in. - Add a
ChildLocator
component to your object. - Add a empty
GameObject
as a child of your object. Call this "MainHurtbox" and add aCapsule Collider
. Scale it to fit your character and make sure the pivot of the object is set around the center of your character. This determines thecorePosition
, where enemies will aim at and where effects likeBerzerker's Pauldron
will show up. - In your
ChildLocator
, create a new entry called "MainHurtbox" and drag your hurtbox object into it. Once you've done this the code will handle the rest. - Create more entries in your
ChildLocator
for every mesh you have in your model and drag those in where necessary. The names don't matter because you'll fill in that part in the code. - Drag your
GameObject
into theProject
window to create a new prefab. when prompted, make aPrefab Variant
. Make sure the name of this prefab in the project window is "mdlYourCharacterName" as before. - Duplicate the object in the scene. Drag that object into the
Project
window to create another prefab, and when prompted, make anotherPrefab Variant
. Then change the name of this to "YourCharacterNameDisplay". Remember the name you set here as well.- This will be your display prefab used in the lobby. Here we usually the replace
Animator
to a new one that just plays the lobby animation.
- This will be your display prefab used in the lobby. Here we usually the replace
- Once this is all done, open the AssetBundle Browser and create a new AssetBundle (If you don't have the AssetBundle Browser window, go to Windows > Packages and there you can install it). Drag whatever assets you're using into there and build it. I like to have everything I need in a single folder and just drag that to keep things simple.
- If this was done right, you should be able to navigate to where you built the AssetBundle and see a game ready bundle.
- If you've cloned the HenryTutorial repo, I recommend to navigate to
Build\plugins\AssetBundles
and put your new assetbundle into this folder, replacing themyassetbundle
file that you see there.- If you're skipping the character model process and just want to build with henry, you must at least rename
myassetbundle
to something else.
- If you're skipping the character model process and just want to build with henry, you must at least rename
- If you've cloned the HenryTutorial repo, I recommend to navigate to
Next step is hooking it all up in your code.
Assuming you've cloned the HenryTutorial repo for this one, and have a basic understanding of how to navigate a C# project. Before you start, there's a few components that you're gonna be working with that it's important to understand.
CharacterBody
is the main component that handles most things relating to a character. This handles things like base stats, crosshairs, item interactions, etc. There's way too many things for me to go over so it's best to learn about this through experience.
Henry's code takes care of all of the setup for this when creating your own body so don't stress over it too much.
CharacterModel is the component attached to every character model. It uses something called 'BaseRendererInfos' to handle things like overlays and skins.
A BaseRendererInfo
consists of mainly a renderer
(MeshRenderer, SkinnedMeshRenderer, ParticleSystemRenderer, etc.) and a defaultMaterial
. The ignoreOverlays
field (self-explanatory what this does) should be set to false if you encounter issues like weird shield outlines.
A simple component that's used to easily reference child objects. This is useful for item displays, particle effects, muzzle flashes, and more. I make use of this component a lot to cleanly create characters.
The InputBank
is used to cache inputs from players as well as fake inputs from AI. You shouldn't need to use this much aside from a few skill interactions, like charge attacks.
There's plenty more to go over but these are the main ones you need to know. Reading through the game code with dnSpy
always helps. Now it's time to move on to Henry's code.
This step is very important in order to avoid conflicts with other mods.
-
The first thing you wanna do is go through and change the name of the character to your own.
-
Open up
HenryPlugin.cs
-
Rename the
HenryMod
namespace to a name of you're choosing. -
Then navigate to this section, and change these fields to yours:
- Standard naming convention is
com.AuthorName.YourModName
so go ahead and fill in the fields accordingly. For the version, it's good practice to follow Semantic Versioning. This isn't enforced but you'll probably be made fun of if you don't follow it.
- Standard naming convention is
-
Now, in your Solution Explorer. rename all the instances of
Henry
to a name of your choice.
It's a lot, but everything related to henry is contained within the Henry folder there. -
Open up the (now renamed)
HenryTokens.cs
You can save this for later, but this is where all the text for your character is registered into the game. Remember to use this prefix to avoid conflicts with other mods. You'll set this in the next step.
-
Next you're going to want to create a class for the survivor you're creating.
SurvivorBase
is an abstract class that you can inherit from that takes care of most of the work for you. You can work with or copy theHenrySurvivor
class which implements this.
-
Open up this (hopefully renamed) class and you'll see some important fields you need to fill out for your character setup.
-
Set
assetBundleName
to the name of the assetbundle you created before. This must be changed to avoid conflicts.- If you skipped the unity section above and just want to build the project with henry, you must still rename this field, and as such rename the file for the assetbundle to match.
-
Set
bodyName
to a unique name for your character, conventionally ending in Body.- This is the internal name that's not seen by the player, so it can be whatever you want, but make sure it's unique, as having duplicate bodies in the game tends to break things horribly.
-
Set
masterName
to a unique name for your character. This will house the AI of your character when spawned with vengeance or goobo. Again, this is to avoid duplicate conflicts. - Set
modelPrefabName
anddisplayPrefabName
to the according prefabs you created in unity. This is what the template's code will use to load your character. - Rename
HENRY_PREFIX
as you see fit, and use theDEVELOPER_PREFIX
from earlier. This will make adding unique language tokens easy. - Now's a good chance to rename the
HenryMod.Survivors.Henry
namespace if you haven't.
-
Set
The most important conflict-avoiding part of the process is out of the way. Now we can finally get to the fun stuff.
- This is the
bodyInfo
of your character. This code right here is what sets up your entireCharacterBody
prefab. - Feel free to look at the
BodyInfo
class to see more options like other stats and camera stuff. The most important ones are here so you're likely not to need to.
A few things to note:
- The names of all the crosshair prefabs can be found here
- Stat scaling fields aren't shown here as there's a standard for those that should be followed, but there's nothing stopping you from changing it manually
If this is done properly, you'll now have everything in place for the code to build a functioning character body! However there's still a few more steps.
This step is crucial if you want your character to look good and authentic ingame. All that ChildLocator
stuff from before, linking all the meshes, comes into play here.
Recall that the CharacterModel
component holds BaseRendererInfos
which reference to your model's renderers
and materials
. Since we're setting up our character in code, we're going to use our ChildLocator
and to find these renderers and add our BaseRendererInfos
.
For the Unity inclined, there is a more editor friendly approach(under construction), but continue on if you prefer code.
This is the magic code that adds a CharacterModel
component with proper RendererInfos
to your character:
-
CustomRendererInfo
is a class defined in thePrefabs
modules that's designed to store basic information that's used to create yourRendererInfos
. You're gonna need one for every renderer you linked in yourChildLocator
.-
childName
would be the name of the transform pair whilematerial
would be whatever material you created just before this.
-
-
We use
Materials.LoadMaterial
extension to load a material from your assetbundle, and apply Hopoo's shader using the properties of the material as you set it up in unity.- Make sure the name matches here or it'll error and show up white.
- To add emission and normal/bump maps, set up all that stuff in editor, and it should seamlessly transfer here.
-
So, if you had a character with a body mesh and a helmet mesh, you would do the following:
public override CustomRendererInfo[] customRendererInfos => new CustomRendererInfo[]
{
new CustomRendererInfo
{
childName = "BodyModel",
material = assetBundle.LoadMaterial("matBody"),
},
new CustomRendererInfo
{
childName = "HelmetModel",
material = assetBundle.LoadMaterial("matHelmet"),
}
};
- Note: if you have all your materials on your model in unity, you may be able to avoid all this by simply specifying the childnames:
public override CustomRendererInfo[] customRendererInfos => new CustomRendererInfo[]
{
new CustomRendererInfo
{
childName = "BodyModel",
},
new CustomRendererInfo
{
childName = "HelmetModel",
}
};
- If no material is passed in, whatever material that's currently on your renderers in unity will be transferred to hopoo materials and applied to rendererinfos.
With all that taken care of, you should now have a working character that can be selected and played with ingame! If you run into any issues at this point be sure to go back through and make sure you've followed every step properly.
Follow the Building the Mod page to make sure you structure your mod install properly.
Now that setup part of the character is done there's a few more things to do to truly complete your survivor. These are aspects that have been streamlined with this template, but are ultimately up to you to figure out how you go about!
Past the CharacterBody
setup and CharacterModel
setup from earlier, there are a few more fields
-
characterUnlockableDef
anditemDisplays
will be explained below. It's possible to set these as null for now. - The
assetBundle
is set on initialization using theassetBundleName
you set earlier. As you can guess, you'll be using this to load the assets for your character.- With this, if you have multiple characters, you can have separate assetbundles for each.
- The fields following are all set in character initialization. They are exposed here for you to use as you further build up your character if you need (mainly the bodyPrefab)
-
CharacterBase
is a singleton. Not shown here is aninstance
field that will allow you to access information from this class from anywhere else (mainly to use the assetBundle).- for example
HenrySurvivor.instance.assetBundle.LoadAsset<Sprite>("someIcon")
- for example
Initialization:
The code here is self explanatory or commented, this is where all the magic happens. We'll go into further detail in the sections below
First and foremost, your character needs EntityStateMachines
:
- A State Machine, as it sounds, is what governs the state your character is in. In ror2 you can have multiple state machines so that multiple
EntityStates
can happen at the same time, within your control. - When you create skills, you will assign them to these state machines.
- The "Body" state machine typically has
GenericCharacterMain
as its main state. It handles your character's movement and ability casting, you can check it out in the game's code to see more.- You can also create your own
EpicCharacterMain
class that inherits from it, to add your own functionality to the character as it is in its main state.
- You can also create your own
A brief overview:
- The action that you take when you execute a skill is called an
EntityState
. - The information about the skill is held in what's called a
SkillDef
, which includes whatEntityState
to enter. It governs the icon and cooldown and all that good stuff on the bottom of your screen. - On your character are
GenericSkill
components that hold your currently selectedSkillDef
- You then have a
SkillLocator
component which holds a reference to your respectiveGenericSkill
components for Primary, Secondary, Utility, and Special, so your main state can call on them and perform your skills. - Sounds like a lot, but tl;dr:
SkillLocator
=>GenericSkill
=>SkillDef
=>EntityState
=> ??? => Profit
You can get familiar with this as you go. Henry streamlines the setup of all these moving parts for you.
InitializeSkills
method contains all the code for setting up your character's SkillDefs
. It should be fairly self-explanatory and easy to work with.
Similar to the EntityStateMachines
, we'll clear the GenericSkills
off the Commando prefab we cloned. Within their respective functions, we'll be creating and adding GenericSkills
on the bodyPrefab for the four skill slots, and create SkillFamilies
for them.
As you can guess, SkillFamilies
are what hold your Skill Variants
that you can select in loadout. At the end of each function, we'll add our skills to this family.
So this is about as simple as it can get. Using Skills.CreateSkillDef
you create the SkillDef
for your skill and fill in all the values with what you need.
-
skillName
is the internal name of the skill used by saving/loading profiles -
skillNameToken
andskillDescriptionToken
are the self-explanatory language tokens. -
keywordTokens
are the keywords that pop up when you hover over the skill in the character select screen -
activationState
is theEntityState
you're entering when you use this skill. -
activationStateMachineName
is theEntityStateMachine
that runs your skill, as explained above.- set to "Body" and it'll overwrite your character's main state. a.k.a. you can't move while doing it.
- set to "Weapon" and you can freely use it while moving
- in general, set to any state machine, so other skills can be casted in the other state machines without interrupting
-
interruptPriority
the priority with which this skill will interrupt other skills. -
baseRechargeInterval
is the skill cooldown -
baseMaxStock
is the stock you start with -
rechargeStock
is how many stocks you regain after every cooldown. Usually for "ammo" type skills like visions of heresy -
requiredStock
is how many stocks are required to use the skill. Usually set to 0 for primary attacks, or other unique kind of skills -
stockToConsume
is how many stocks this skill loses on use. Usually set to 0 for fancy skills that will deduct the cooldown in different ways. -
resetCooldownTimerOnUse
usually used for "ammo" type skills like visions of heresy. -
fullRestockOnAssign
is usually set to true. -
dontAllowPastMaxStocks
is usually set to false. -
mustKeyPress
determines whether the skill is automatically reactivated when held. false is recommended in most cases -
beginSkillCooldownOnSkillEnd
is usually used for charged or aimed moves and the like. -
isCombatSkill
determines whether out of combat effects likeRed Whip
are cancelled on use -
canceledFromSprinting
determines whether the skill is cancelled by sprint inputs -
cancelSprintingOnActivation
determines whether the skill cancels sprint on use -
forceSprintDuringState
determines whether your character is forced to sprint during the skill
So once you've created your SkillDef
, Skills.AddPrimarySkill()
, Skills.AddSecondarySkill()
, etc, will add it to the character pain free. You can add as many skills as you want with this method.
Take a peek at the AddPassiveSkill for more information on that if you need it. It will also cover adding additional arbitrary GenericSkills
like Mul-T's second primary or Captain's beacons.
- Go nuts.
- Some states have been included as examples, but there's also an entire library of skills in the base game and other mods to look to for reference.
- Don't forget to register your state as henry does in the
HenryStates
class
- A
SkillDef
is more than just a vessel that holds your skill information. It governs your ability to use the skill, and can even help pass information into the state as it's entered.- For example, Henry's primary is a
SteppedSkillDef
. This will increment astep
counter and pass that into the entity state, so you can do combo primaries.
- For example, Henry's primary is a
- I highly recommend reading into the
SkillDef
class in the game's code, and looking at examples of existing customSkillDefs
, for exampleHuntressTrackingSkillDef
Feel free to skip this section if you aren't going to do any melee attacks.
- Here's an example of how you can set up a
HitBoxGroup
for your character to use inOverlapAttacks
(most commonly melee attacks).- To use this, create an empty gameobject in unity under your character prefab, and add it to your ChildLocator.
- Then use the name of that child here.
- A HitBox in this game is just a transform, where the game runs
Physics.OverlapBox
using the dimensions of the transform.- These are the cube hitboxes you see if you check out the hitbox viewer.
- You can look at the scene in the henry unity project for an example of what a hitbox looks like.
If you don't have any skins, you don't need to worry about this section. A basic default skin is set up. All you might need to mess with are the name token and the icon.
Skin creation isn't streamlined as much as I'd like but it's not too bad to work with as of now. Inside the InitializeSkins
method, you'll see this code:
Most of it can be ignored. It's just grabbing the necessary components and adding the ModelSkinController
as well as creating a List
of our SkinDefs
. The rest of the skin code is below.
Not too keen on explaining it all since it's so poorly written but the rest of this method should be fairly easy to understand. If not feel free to harass timesweeper with your questions.
All survivors and enemies and others in this game are Characters.
- A Character is very modular in RoR2, where a survivor is simply a player-controlled character, and an enemy is simply an ai-controlled character.
- This is why enemies can have items, why you can spawn as an enemy and use its attacks, and why AI can be applied to survivors.
When you create a character, you should also create a Character Master for it to be controlled by AI.
- A character master is simply an object that performs inputs on a character body.
- AI in this game has been simplified to components on the master, so it's easy for anyone to write AI for their character.
it's as the comments say
There are a lot of helper functions in the Modules.Assets
class used to load prefabs from your unity project to use as projectiles or effects. Examples of this are seen in the HenryAssets
class.
As you get more and more experience creating assets for your character, you'll want to experiment with what workflow works best with you. Maybe you like cloning the game's assets and recoloring them. Maybe you like having just visuals set up in unity and putting together the necessary components in code. Maybe you prefer setting it up mostly in unity and just loading them into your mod ready to go. Feel free to ask questions as you go about these.
I will probably be creating a page dedicated to projectiles. stay tuned for that. For now there are plenty of projectiles you can steal from the game. Henry's bomb is actually an example of this :P.
Ah yes, who doesn't love 2400 lines of grueling, tedious code, doing nothing but copy pasting blocks and blocks of code and then typing in values.
Thankfully KingEnderBrine
has made a tool that streamlines this process to an absurd degree, so no one else has to ever experience that pain again.
Now, all you need to do is install that tool, run the game, press F2
on the main menu, select your character and start dragging items around.
Seriously, this section would've been a lot longer without that tool.
-
The code responsible for setting it up can be found back up top near your bodyInfo and customRendererInfos:
All of the initialization is handled inCharacterBase
. If you don't want displays yet, simply set that to null. -
Go into the
HenryItemDisplays
class and all the Item Display Rules have been generated.
If you want to generate these rules yourself, or the game has updated and you want to generate the remaining rules, see theModules.ItemDisplayCheck
class.
You'll notice Henry has a bunch of entries on his ChildLocator for his various body parts. These are how item displays attach to your bones.
- You don't have to create an entry for every bone on your rig, just the ones you want to put displays on.
- There is an
ArrayTransferTool
component in the Unity Project that makes this a little easier.- Attach it next to your child locator in editor, drag many objects at once into the
objeys
field, right click and choose "Send to ChildLocator". remove the Array Transfer tool when you're done
- Attach it next to your child locator in editor, drag many objects at once into the
- Even if your bones are named differently, I recommend naming the child locator entries for your main body parts (arms, legs, head, chest) the same as henry's
- There are a few spots in the game where some code is looking for these names
- In the template's
HenryItemDisplays
, all items are initialized with the "Chest" child, so make sure you have an entry in your child locator called "Chest". - Head to the top of the
HenryItemDisplays
class and copy this block here:
- Run the game, and in the Item Display Placement Helper mod:
- select any character on the top left
- select any item display on the middle left
- select one of the display prefabs on the right
- click this cog:
- paste the block you copied above into the
custom
field
You're good to start setting your item displays!
I know you're just here for Mastery Unlocks.
-
Unlockables
, as named, are the challenges that you can unlock.Achievements
are the code used to check if you've completed a certain task. - These two things are connected by an
identifier
that you set.
Creating a mastery achievement has been greatly simplified. All you need to do is plug in your character's name and RequiredDifficultyCoefficient
The RegisterAchievement
attribute is how you get your achievement in the game. I recommend storing those identifiers in const strings so you can reference them elsewhere:
Achievements also have a field for lunar coin rewards. From a quick look, mastery achievements grant 10, skill achievements grant 3 or 5. take a look through the game's existing achievements for more examples.
If you're curious about writing your own achievements, you can take a look at the code for BaseMasteryAchievement
there, and any of the vanilla achievements for reference
One thing to note is that certain things like on kill achievements must be server tracked, otherwise only the host can unlock them. HAN-D Overclocked has a good example of this, as well as, again, many of the vanilla achievements.
Unlocks are generally optional but people like having them, and it's a good way to get people to experiment mechanics with your character. If they don't there's the CheatUnlock mod so no biggie.
That should cover just about everything. See this page for how to properly build and install your mod.
If there's anything I'm missing or anything that doesn't make sense feel free to let me (thetimesweeper
) know in the modding discord.
Remember, the discord is there to answer your questions and help you along the journey. There's loads of experience you can call upon there.
Happy modding!