Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Class used to get game systems, using generics and then making cache of said systems.
The SystemLocator is a class used to easily find any system that has been registered on it.
It helps us avoid falling into the Singleton pattern, and does something similar to the Service Locator pattern. Also good for performance reasons, It can handle over a million calls every frame with no issues.
To register a new system you can call the SystemLocator.Register(this) method, it'll add that system into a dictionary of systems, preventing two of the same system from existing.
Then you can get this system from somewhere else using SystemLocator.Get<T>()
Note that is done automatically by classes inheriting System and NetworkedSystem. They register on Awake:
On the whole, naming should follow C# standards.
Classes are written in PascalCase. For example RadialSlider
.
Interfaces should always have the prefix I
, as in IOpenable
.
This style guide is based on this other C# style guide and expands upon it.
You can use whatever file editor you want, but Visual Studio is probably the most popular and recommended choice. It's free, has many features, and integrates well with Unity and Git.
If you're a student, we recommend getting a copy of JetBrain's Rider which adds a lot of code hints and shortcuts that will significantly improve your code quality.
Our overarching goals are clarity, readability and simplicity. Also, this guide is written to keep Unity in mind.
This style guide is based on C# and Unity conventions, and discussions within the development team.
We recently added an EditorConfig file to automate the code review process, it is a file you can add to Visual Studio or Rider and it will apply the formatting and style we use to your analyzers, telling you when something is out of order.
Namespaces are all PascalCase, multiple words concatenated together, without hypens ( - ) or underscores ( _ ):
Namespaces should be used for major systems, such as Atmospherics, or Electrics. Everything else should be without namespace.
BAD:
GOOD:
Methods are written in PascalCase. For example DoSomething
()
.
Parameters are written in camelCase.
BAD:
GOOD:
Single character values are to be avoided except for temporary looping variables. You can also use _
for unused variables, m
for network messages and e
for event bus's events.
Delegates are written in PascalCase.
When declaring delegates, DO add the suffix EventHandler to names of delegates that are used in events.
BAD:
GOOD:
DO add the suffix Callback to names of delegates other than those used as event handlers.
BAD:
GOOD:
Using built.in C# features, such as Action, is encouraged in place of delegates.
Prefix event methods with the prefix On.
BAD:
GOOD:
In code, acronyms should be treated as words. For example:
BAD:
GOOD:
Exactly one class, struct, enum, or interface per source file, although inner classes are encouraged where scoping appropriate.
Access level modifiers should be explicitly defined for classes, methods and member variables. This includes defining private even though C# will implicitly add it.
Use the least accessible access modifier, except for public member that is expected to be used by other classes in the future.
Prefer single declaration per line.
BAD:
GOOD:
A class must respect the following order, from top to bottom
Events and delegates declaration.
Private enums declaration.
Private internal classes.
Member variables.
Properties.
Methods.
BAD:
GOOD:
All braces get their own line as it is a C# convention:
BAD:
GOOD:
Conditional statements are preferred to be enclosed with braces, irrespective of the number of lines required. Some cases can be pardoned.
BAD:
GOOD:
Spacing is especially important to make code more readable.
Indentation should be done using tabs — never spaces .
Blocks
Indentation for blocks uses tabs for optimal readability:
BAD:
GOOD:
Line Wraps
Indentation tab for line wraps should use 4 spaces (not the default 8):
BAD:
GOOD:
Lines should be no longer than 100 characters long.
There should be just one or two blank lines between methods to aid in visual clarity and organization. Whitespace within methods should separate functionality, but having too many sections in a method often means you should refactor into several methods.
Switch-statements come with default
case by default (heh). If the default
case is never reached, be sure to remove it.
If the default
case is an unexpected value, it is encouraged to log and return an error
BAD
GOOD
BETTER
Programming Definitions & Important notes
Always ask the lead programmer if you're in doubt.
Remember, we gotta network stuff.
Use _variableName for private variables.
Use VariableName for public, const and static variables.
Never use underscores.
Never use abbreviated variables, except (if any missing or you'd like to suggest one please contact the lead programmer): i, j, rb, ui, gui, id, x, y, z, args, config.
Avoid singletons at all costs.
Avoid numbers in variables.
Always unsubscribe from events.
Document everything you can, the idea is: anyone with beginner understanding of code should read and understand the code, knowledge is everything and we got to provide that to newcomers and instruct them. This is the way.
Don't leave Debug.Logs, and always remove unused commented code.
Do not touch other people's code, if you're not directly working with it.
Whenever someone add a script implementing NetworkBehaviour, they have access to the following attributes, indicating if a given method should be server only, client only, or both :
[Server] for server only code.
[Client] for client only code.
[ServerOrClient] for both.
Make sure no methods goes without an attribute in a script inheriting NetworkBehaviour. The goal is to increase readability and to generate warnings or exceptions when clients or server are attempting to call something they should not.
Methods with [ServerOrClient] attributes should stay uncommon, they make debugging harder. If possible, try to refactor the code to avoid them.
Use US English spelling.
BAD:
GOOD:
The exception here is MonoBehaviour
as that's what the class is actually called.
This section includes some rules of thumb for design patterns and code structure
Avoid throwing exceptions. Instead log and error. Methods returning values should return null in addition to logging an error
BAD:
GOOD:
Don't use Find or in other ways refer to GameObjects by name or child index when possible. Reference by reference is less error prone and more flexible. Expect people to set the fields in the inspector and log warnings if they don't.
BAD:
Prefer RequireComponent and GetComponent over AddComponent. Having the components in the inspector let's us edit them. AddComponent limits us.
BAD:
GOOD:
Properties can be used for access control to fields, and when using backing fields they can be private and let us change them in the inspector. Consider when a fields should be public and prefer properties with backing fields.
Sometimes it's just nice to see them for debugging, even if we don't change them, so consider making more of your private fields visible.
OKAY:
BETTER:
This refers to a technical concept, not as a generic "container system" or a "interactions system"
A system is a class that manages something, it should work on its own, the only exception is when it needs information about other system, but it is preferable that you do that by using events or event buses or .
A system can be networked or not, for that you can inherit your class from System or NetworkSystem.
Here's a snipped of now you can declare a system that creates explosion somewhere.
It is also important that you know all the systems work with the class
Always add the postfix "System" in the name of the class for your systems.
The event bus is a design pattern, it serves the purpose of a "global event system" as it can be invoked from anyone, anywhere and can be listened by anyone, anywhere.
The implementation we use was created by the CS Framework and we use it to create new event buses. To understand further on the design pattern itself, you can read this article.
We use these events when we want something that will be used in more than one place, without having to care about object relation.
We usually want events to be fired by systems when we want to inform other objects something has happened, like a round timer being updated, a player that spawned, an object that was picked up.
Pretty straight-forward isn't it? Well, now for the usage.
A view is a controller for an user interface, it sets the parameters, controls buttons, etc. A view's existence is never known by systems.
If we need to network a view, we can either use a direct call on a system or use network messages. Do not use the event bus's events for that.
Always add the postfix "View" in the name of the class for your views.
I'll fill these subpages later, promise.
I'll fill these subpages later, promise.
I'll fill these subpages later, promise.
An Action is an event that depends on an object to exist.
The Action event should be used when we don't need something to be accessed globally, like a button being pressed, or when we need to listen from something from a specific object.
Let's imagine you have a granade in your character's hand. Pretty common sight in a Low RP server. In the code, usually we would have something like:
The OnPinRemoved event will be used to to tell listeners that subscribed to that granade's event that the granade's pin was removed. The relevance of that OnPinRemoved event is usually for UI related purposes, or running a local animation of a granade pin flying through your screen.
Something like:
You might even think it would be easier to call it directly, and depending on your logic it might be, but we are assuming that in this case its better to keep the classes decoupled.
The criteria used for accepting assets.
All assets should meet the specified criteria when contributing to the project, and maintainers will refer back to this when reviewing GitHub PRs and direct art submissions in Discord.
General assets are categorized by 2 types in our project as you should know from our . These two types, internal and external, have different criteria based on their nature.
The sub pages contained under this one will be split based on these 2 types, but we will also be adding a 3rd page to represent the importing criteria of external assets.
The criteria used to check art assets BEFORE importing them into Unity.
This criteria specifically focuses on the details of the file as it was created in whatever external (non-Unity) program.
Many of these art assets were already check based on our criteria when they were originally submitted, but many (especially the older ones) have not. So all art assets should be double checked prior to being imported into the Unity project via a GitHub pr.
Due to the nature of different art asset types, we have different acceptance criteria for each. This includes 3d models, 3d animations, textures, graphics, and audio.
Also, each art asset type is not bound by 1 set of criteria. Because assets of the same type are used in different ways inside the project they may have different acceptance criteria based on their use.
The following subpages are based on asset types while they may have sub pages but will be based on .
The criteria used to check art assets WHILE importing them into Unity.
Art assets have different options and settings in Unity based on their file type. Also, files of the same type may have different criteria here depending on how they will be used in the project.
Dependent assets like prefabs may not function properly if the options are not correctly set on these art assets.
The following subpages are based on asset types while they may have sub pages but will be based on .
The criteria used to check non-art assets CREATED within Unity itself.
This part focuses on those assets that are created in Unity and like .prefabs, .materials, etc.. Again these criteria may vary depending on usage.
The following subpages are based on asset types while they may have sub pages but will be based on .
This is a draft to remind us we have to write this little guide.
Avoid using Debug.Log, use Punpun logger instead. This is because we get much more relevant data using Punpun compared to Debug.Log.
Use SystemLocator instead of the Find method from Unity when trying to get a reference. This is less prone to break and more efficient.
To get an input, don't use Unity's built in methods, use instead our input system (put link there)
This pages contains a list of code patterns that we use in developing SS3D. We recommend you to take a look as those patterns improve the code quality, testability, reusability.
This design came from the necessity to answer the following question : How do we make code testable in a class tightly coupled with its environment ?
In our case, that's what happen in our codebase with everything inheriting from monobehaviour, or worse, from networkbehaviour. All networkbehaviour objects are tightly coupled to both Unity framework, and Fishnet framework.
To remedy to this issue, we split the object in two, one "Plain Old Class Object", containing the logic we want to test, and one object tightly coupled to our framework, handling only framework stuff.
A great introduction to this concept in the following video :
If you want to learn more about it, XunitPatterns.com goes into great details.
In our codebase, you can see this pattern in action with the Container and AttachedContainer class. The container class is the testable object containing most of the logic, the AttachedContainer class contains the framework logic, networking and Unity stuff.
Sometimes, applying a strict humble object pattern causes more trouble than good. Duplicating code, loss of encapsulation, code difficult to read ... It's not always worth it.
We encounter many of those issues while working on the Item class. After many attempts, ending with overly complicated and hacky codes, we were not satisfied.
That's when we decided to not apply a strict humble object pattern (splitting a class in two different classes), but rather to draw some inspiration from it to come with the following set of rules :
Keep everything in the same class.
Separate framework and logic code in different methods.
Make logic in method involving framework code as simple as possible, consider this method untestable.
Write code to make it work in both framework and unit test context.
Instead of splitting in two different classes, we split our code in different methods, and/or we rewrite it so it works as well in both framework and unit test context.