arrow-left

All pages
gitbookPowered by GitBook
1 of 52

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...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Classes & Interfaces

Classes are written in PascalCase. For example RadialSlider.

Interfaces should always have the prefix I, as in IOpenable.

Animations

Textures

Graphics

Methods

Methods are written in PascalCase. For example DoSomething().

chevron-rightMethods that are used as the OnChange callback of a [SyncVar]hashtag

Should have the Sync prefix, as in:

chevron-rightMethods that listen to an eventhashtag

Should have the prefix Handle, as in:

chevron-rightMethods that are preceded by the [Server] attributehashtag

Do not need to have the Server prefix on them. This is still something we are thinking about, both uses are fine for now.

As in:

chevron-rightMethods that are preceded by the [ServerRpc] attributehashtag

Need to have the Cmd prefix on them, as in:

chevron-rightMethods that are preceded by the [ObserversRpc] attributehashtag

Need to have the Rpc prefix on them, as in:

[SyncVar(OnChange = nameof(SyncUsername))] string _username;
private void SyncUsername(string oldUsername, string newUsername, bool asServer) {}
clownBomb.OnExploded += HandleClownBombExploded;
private void HandleClownBombExploded() 
{
    Honk();
}
[Server]
public void ServerKillPlayer() {}
[Server]
public void KillPlayer() {}
[ServerRpc]
public void CmdSpawnItem(GameItems item) 
{
    SpawnItem(item);
}
[ObserversRpc]
private void RpcSpawnVisualProjectives() 
{
   _particleSystem.Play();   
}

Namespaces

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:

com.ress3dclient.scripts.structures

GOOD:

SS3D.Systems.Tilemaps.Structures

Fields

Private fields have the _ prefix, static, const and public fields are in UpperCamelCase.

For example:

public class MyClass 
{
    public int PublicField;
    private int _packagePrivate;
    private int _myPrivate;
    protected int _myProtected;
}

BAD:

private int myPrivateVariable

GOOD:

private int _myPrivateVariable

hashtag
Field Position

All fields should be at the top of the class, before any methods.

Access Level Modifiers

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.

hashtag
Fields & Variables

Prefer single declaration per line.

BAD:

GOOD:

string username, twitterHandle;
string username;
string twitterHandle;

Declarations

Delegates

Delegates are written in PascalCase.

When declaring delegates, DO add the suffix EventHandler to names of delegates that are used in events.

BAD:

public delegate void Click()

GOOD:

public delegate void ClickEventHandler()

DO add the suffix Callback to names of delegates other than those used as event handlers.

BAD:

public delegate void Render()

GOOD:

hashtag
⚠️ IMPORTANT!

Using built.in C# features, such as Action, is encouraged in place of delegates.

public delegate void RenderCallback()

Misc

In code, acronyms should be treated as words. For example:

BAD:

XMLHTTPRequest
String URL
findPostByID

GOOD:

XmlHttpRequest
String url
findPostById

The C# Style Guide

This style guide is based on and expands upon it.

You can use whatever file editor you want, but 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 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.

hashtag
Inspiration

This style guide is based on C# and Unity conventions, and discussions within the development team.

hashtag
Automation

We recently added an EditorConfigarrow-up-right file to automate the code review process, it is a file you can add to Visual Studioarrow-up-right or Riderarrow-up-right and it will apply the formatting and style we use to your analyzers, telling you when something is out of order.

this other C# style guidearrow-up-right
Visual Studioarrow-up-right
JetBrain's Riderarrow-up-right

Parameters

Parameters are written in camelCase.

BAD:

void DoSomething(Vector3 Location)

GOOD:

void DoSomething(Vector3 location)

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.

Guidelines

Events

Prefix event methods with the prefix On.

BAD:

public static event CloseCallback Close;
public event Action Changed;

GOOD:

public static event CloseCallback OnClose;
publc event Action OnChanged;

Common Patterns and Structure

This section includes some rules of thumb for design patterns and code structure

hashtag
Error handling

Avoid throwing exceptions. Instead log and error. Methods returning values should return null in addition to logging an error

BAD:

GOOD:

hashtag
Finding references

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:

hashtag
RequireComponent

Prefer RequireComponent and GetComponent over AddComponent. Having the components in the inspector let's us edit them. AddComponent limits us.

BAD:

GOOD:

hashtag
Properties with backing fields

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:

public List<Transform> FindAThing(int arg){
    ...
    if (notFound) {
        throw new NotFoundException();
    }
}
public List<Transform> FindAThing(int arg){
    ...
    if (notFound) {
        Debug.LogError("Thing not found");
        return null;
    }
}
private GameObject _someMember;

private void Start() {
    _someMember = GameObject.Find("ObjectName");
}
public class : MonoBehaviour
{
    private AudioSource _audioSource;

    private void Start() {
        _audioSource = gameObject.AddCompenent<AudioSource>();
    }
}
[RequireComponent(typeof(AudioSource))]
public class : MonoBehaviour
{
    private AudioSource _audioSource;

    private void Start() {
        _audioSource = GetCompenent<AudioSource>();
    }
}
public GameObject SomeMember;
[SerializeField] private GameObject _someMember;
public GameObject SomeMember => _someMember;

General class structure

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:

 public sealed class Container : NetworkActor
    {
        public event ContainerContentsHandler OnContentsChanged;

        public Vector2Int Size;
        public AttachedContainer AttachedTo { get; set; }
        
        [SyncObject]
        private readonly SyncList<StoredItem> _storedItems = new();
        
        private readonly object _modificationLock = new();
        
        public float LastModification { get; private set; }
        
        public delegate void ContainerContentsHandler(Container container, IEnumerable<Item> oldItems,IEnumerable<Item> newItems, ContainerChangeType type);

        ...
 public sealed class Container : NetworkActor
    {
        public event ContainerContentsHandler OnContentsChanged;
        public delegate void ContainerContentsHandler(Container container, IEnumerable<Item> oldItems,IEnumerable<Item> newItems, ContainerChangeType type);
  
        public Vector2Int Size; 
        private readonly object _modificationLock = new();
        
        [SyncObject]
        private readonly SyncList<StoredItem> _storedItems = new();
        
        public AttachedContainer AttachedTo { get; set; }
        public float LastModification { get; private set; }
   

        ...

Nomenclature

On the whole, naming should follow C# standards.

Brace Style

All braces get their own line as it is a C# convention:

BAD:

class MyClass {
    void DoSomething() {
        if (someTest) {
          // ...
        } else {
          // ...
        }
    }
}

GOOD:

class MyClass
{
    void DoSomething()
    {
        if (someTest)
        {
          // ...
        }
        else
        {
          // ...
        }
    }
}

Conditional statements are preferred to be enclosed with braces, irrespective of the number of lines required. Some cases can be pardoned.

BAD:

if (someTest)
    doSomething();  

if (someTest) doSomethingElse();

GOOD:

if (someTest) 
{
    DoSomething();
}  

if (someTest)
{
    DoSomethingElse();
}

Spacing

Spacing is especially important to make code more readable.

hashtag
Indentation

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:

hashtag
Line Length

Lines should be no longer than 100 characters long.

hashtag
Vertical Spacing

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.

for (int i = 0; i < 10; i++) 
{
  Debug.Log("index=" + i);
}
for (int i = 0; i < 10; i++) 
{
    Debug.Log("index=" + i);
}
CoolUiWidget widget =
        someIncrediblyLongExpression(that, reallyWouldNotFit, on, aSingle, line);
CoolUiWidget widget =
    someIncrediblyLongExpression(that, reallyWouldNotFit, on, aSingle, line);

View

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.

public sealed class RoundTimeView : Actor 
{
    // add time logic
}

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.

hashtag
⚠️ IMPORTANT!

Always add the postfix "View" in the name of the class for your views.

System

circle-info

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 network messages.

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

circle-exclamation

Always add the postfix "System" in the name of the class for your systems.

public sealed class ExplosionSystem : NetworkSystem 
{
    public void CreateExplosionAt(Vector3 position, float size) 
    {
        // boom.
    }
}
SystemLocator

One declaration per source file

Exactly one class, struct, enum, or interface per source file, although inner classes are encouraged where scoping appropriate.

Applying attributes on all network related methods.

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.

Code Design Definitions

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.

Actor

Actor is our substitute for MonoBehaviours, it optimizes and creates shortcuts for useful stuff.

chevron-rightMoving an objecthashtag

Moving object with MonoBehaviours

transform.position = new Vector3(0, 0, 123);

Moving an object with Actor

Position = new Vector3(0, 0, 123);

The reason we do that is because transform.position goes into C++ every single time to get the transform, which can lead to performance issues later in development. Also it is a nice shortcut.

You can use the same idea for Rotation, and for getting the directions relative to that transform, like Forward, Up, Down.

chevron-rightCallbackshashtag

The standard callback methods also have been changed, you'll use OnStart instead of Start, OnAwake instead of Awake, OnDestroyed instead of OnDestroy.

Another think you need to know is HandleUpdate, HandleLateUpdate and HandlePreUpdate. All these methods have one thing in common: they listen to an

hashtag
Networking

For networked objects, instead of NetworkBehaviour, you will use NetworkActor. They work the same way as Actor.

to be called.

The reason being that in standard Unity environment, the engine searches through the entire instantiated object's list and tries to find the Update method declared somewhere. That's very slow.

Instead, our friend from the decided to create an for the player loop timing, so Unity only needs to worry about one object with the Update method.

Every method that listens to the UpdateEvent and its variants will then be called.

event bus
CS Frameworkarrow-up-right
event bus
event bus

Event Bus

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 Frameworkarrow-up-right and we use it to create new event buses. To understand further on the design pattern itself, you can read this articlearrow-up-right.

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.

chevron-rightCreatinghashtag

To create a new event, we use partial structs that inherit from the IEvent interface. The reason for the partial keyword is that the uses the to create the rest of the necessary logic on that struct.

It is also interesting and kind-of but not obligatory standard to create constructors for these events, they can become quite large lines at times.

Pretty straight-forward isn't it? Well, now for the usage.

chevron-rightInvokinghashtag

Invoking is the easiest part. You just need to know what you want to invoke and when. The code below creates a new instance of our event, using the constructor we just defined and then invokes it. We also have to specify who called that event, in our case we use the this keyword to say that the class's instance that called it.

All event invoking should happen after OnAwake.

chevron-rightListeninghashtag

Listening is the most complex of them, because it contains scary words and more complex management.

It is advised that you add event listeners on OnAwake, to avoid initialization issues.

base.Awake() calls the parent class's Awake function. Then we add the method that will be called when the event is invoked. We'll declare the method as follows:

chevron-rightExampleshashtag

One thing we have to keep in mind is that, with networking, we have to use a data-driven design, as we cannot depend on one-time of events only, a user has to be able to disconnect and reconnect back later with no issues, if we did not use data-driven, the user would lose that information.

So, we can have the best of both worlds, already has callbacks when SyncVars are changed, and they are changed to the correct value when the user enters the server.

In the ReadyPlayersSystem, we have:

The EventContext is usually unused but it gives us information about the invoking itself. Then we have the event we created, it is the struct that we previously created.

The ref and in keywords are there for optimization reasons but they are required, so always add them if your IDE doesn't autocomplete correctly.

One thing to note here is that you have to remove the listeners eventually, you have to do it on the OnDestroyed callback, but if you're using any class that inherits from Actor or NetworkActor, it already unsubscribes from it if you use the AddHandle method when listening to the events.

This is a SyncList, it updates automatically for all clients, and it as a OnChange callback. It makes us able to do this:

When the list is changed, we call the HandleReadyPlayersChanged method.

The method then calls the SyncReadyPlayersMethod, calling, then, our event.

All this trajectory is to ensure our client doesn't lose anything when he rejoins a game and can load stuff back correctly, not to mention this is way easier than doing RPCs all over the code.

Anything in the codebase can listen to that event, in that case we use for updating the UI, that changes the player name's color if they are ready or not.

CS Frameworkarrow-up-right
Roslyn code generation toolsarrow-up-right
FishNet
public partial struct RoundTimerUpdatedEvent : IEvent 
{
    public int TimerSeconds;
}
public partial struct RoundTimerUpdatedEvent : IEvent 
{
    public int TimerSeconds;
    
    public RoundTimerUpdatedEvent(int timerSeconds) 
    {
        TimerSeconds = timerSeconds;
    }
}
RoundTimerUpdatedEvent event = new RoundTimerUpdatedEvent(30);
event.Invoke(this);
protected override void OnAwake() 
{
    base.OnAwake();
    
    RoundTimerUpdated.AddListener(HandleRoundTimerUpdated);
}
private void HandleRoundStateUpdated(ref EventContext ctx, in
protected override void OnAwake() 
{
[SyncObject] private readonly SyncList<string> 
_readyPlayers.OnChange += HandleReadyPlayersChanged;
private void HandleReadyPlayersChanged(SyncListOperation op, int index
private void SyncReadyPlayers()
{
    ReadyPlayersChanged

Language

Use US English spelling.

BAD:

GOOD:

The exception here is MonoBehaviour as that's what the class is actually called.

Switch Statements

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

System Locator

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 , and does something similar to the . Also good for performance reasons, It can handle over a million calls every frame with no issues.

hashtag
Using the SystemLocator

RoundStateUpdated
e
)
{
Debug.Log(e.TimerSeconds);
}
base.OnAwake();
AddHandle(RoundTimerUpdated.AddListener(HandleRoundTimerUpdated));
}
_readyPlayers
=
new
();
,
string
s
,
string
newItem1
,
bool
asServer
)
{
SyncReadyPlayers();
}
readyPlayersChanged
=
new
(
_readyPlayers
.
ToList
());
readyPlayersChanged.Invoke(this);
}

Events

Models

I'll fill these subpages later, promise.

string colour = "red";
string color = "red";
switch (variable) 
{
    case 1:
        break;
    case 2:
        break;
    default:
        break;
}
switch (variable) 
{
    case 1:
        break;
    case 2:
        break;
}
switch (variable) 
{
    case 1:
        break;
    case 2:
        break;
    default:
        Debug.LogError("Unexpected value when...");
        return;
}
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>()

hashtag
⚠️ IMPORTANT!

Note that is done automatically by classes inheriting System and NetworkedSystem. They register on Awake:

hashtag
Examples

chevron-rightGetting the spawned players listhashtag

EntitySpawnSystem entitySpawnSystem = SystemLocator.Get<EntitySpawnSystem>();

List<PlayerControllable> playersToAssign = entitySpawnSystem.SpawnedPlayers;

chevron-rightEnding the round after detonating a nukehashtag

// Ends the round, regardless of how many objectives were completed SystemLocator.Get<GamemodeSystem>().EndRound();

Singleton patternarrow-up-right
Service Locator patternarrow-up-right
public class System : Actor
    {
        protected override void OnAwake()
        {
            base.OnAwake();
            SystemLocator.Register(this);
        }
    }

External Criteria

hashtag
External Asset Criteria

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.

Audio

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 .

object type

Asset Criteria

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.

file organization

Fonts

Textures

Graphics

Importing Criteria

hashtag
Importing Asset Criteria

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 .

object type

Action

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.

chevron-rightDeclaringhashtag

Actions have to be declared as the first thing inside a object with the event keyword.

Only the object that declares the event can invoke it.

It is noted that you can use an Action to send data too:

hashtag
Using an Action

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.

public class ButtonView : Actor 
{
    public event Action OnButtonPressed;
}
public event Action<int> OnNumberUpdated;
public event Action<Item> OnItemUpdated;
public class Granade : Explosive 
{
    public event Action OnPinRemoved;
    
    public void RemovePin() 
    {
        // Do pin removal logic.
    
        OnPinRemoved?.Invoke();
    }
}
public class GranadePinAnimationView : Actor 
{
    private GranadePinAnimation _granadePinAnimation;

    private Granade _granade;
    
    protected override OnAwake() 
    {
        _granade.OnPinRemoved += HandlePinRemoved;
    }
    
    protected override OnDestroyed() 
    {
        _granade.OnPinRemoved -= HandlePinRemoved;
    }
    
    private void HandlePinRemoved() 
    {
        _granadePinAnimation.RunAnimation();
    }
}

Audio

3D Models

I'll fill these subpages later, promise.

3D Animations

Textures

Audio

3D Animations

SS3D's coder good practices

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)

Graphics

3D Models

I'll fill these subpages later, promise.

Code design patterns

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.

hashtag
Humble object pattern

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, 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.

hashtag
(Soft) Humble object pattern

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.

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.

hashtag
Practical case, the SetContainer method of the Item class.

Write code to make it work in both framework and unit test context.
XunitPatterns.comarrow-up-right

Internal Criteria

hashtag
Internal Asset Criteria

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 .

object type

Tweening