arrow-left

Only this pageAll pages
gitbookPowered by GitBook
1 of 88

Dev Guide

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

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

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();
    }
}

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
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);
}

Graphics

Using Unity

hashtag
Downloading Unity

You will need the correct Unity version to run the project. There are two ways you can acquire it:

hashtag
Method 1:

Check what our is, then find that specific version in the Unity download archives below.

hashtag
Method 2:

Download , then open our project using the hub and it should auto-detect our Unity version and prompt a download for that version.

hashtag
Current Version

To avoid having to update the current version in this document every time it's updated, we will just point you to the version file instead.

current Unity version
Unity Hubarrow-up-right

File Naming

When (re)naming files and folders always do so using 'PascalCase'arrow-up-right, this makes naming consistent which helps with organization and clarity.

hashtag
✔️ Correct

LockerDoorAnimationController.file

hashtag
❌ Wrong

Locker Door Animation Controller.file lockerdoor animation controller.file AnimationController - LockerDoor.file

Also, acronyms should be treated as words. For example: JanitorIdImage.file NOT JanitorIDImage.file

File Organization

hashtag
/Assets

Most of our project lives in this folder and as a result we need to keep it organized. As you may have guess the folder itself it broken up into various sub folders. Here is the ruling:

chevron-right/Assets/Arthashtag

Art assets are "external" assets, meaning they are created via an external source (outside of Unity). EX. pngs, oggs, mp4s, fbxs, etc.. Although some assets will not saved in this folder (materials, shaders, etc.) because those assets are exclusive to Unity and were not created externally (see the "Content" folder below).

Due to the nature of our project being open-source and having a community of contributors with various skills, we have segregated the Art folder by asset type, using the following sub-folders:

  • /Animations: 3D animation files for our 3D models.

  • /Fonts: font files.

  • /Graphics: 2D images used mostly for UI.

This helps reduce clutter in individual folders and also some assets are used in combination with multiple other assets in various ways.

Next we sort the art assets by . This applies for all of the above folders except Graphics and Fonts because those are mostly for UI.

Most files in the Art folder are documented in the Art Asset Contributor list currently saved on our Google Drive.

chevron-right/Assets/Contenthashtag

Content assets are 'internal' assets, meaning they are created within Unity itself. EX. shaders, materials, prefabs, etc..

Unlike art assets, content assets, are NOT sorted by asset type, but rather their broad usage.

chevron-right/Assets/Editorhashtag

Editor assets do not go to built executables!

Editor assets are files exclusive to Unity editors. EX. the tilemap editor, or atmospherics editor.

chevron-right/Assets/Scriptshashtag

Scripts are code files that make up our codebase. This includes things such as UI code, networking code, camera & FOV code, game systems, etc.

They are separated by two main folders, SS3D and External:

  • /SS3D holds all the scripts made by us, it is our codebase.

chevron-right/Assets/Settingshashtag

Holds all the Scriptable Settings assets, which are global settings that can be custom created.

chevron-right/Assets/StreamingAssetshashtag

This is for file saved by the game, like map saves.

hashtag
/Builds

This folder is for storing builds and their related assets like .bat files which we currently use for running the game as a developer.

hashtag
/Documents

Documents is simple, it stores the basic documents for the repo.

hashtag
/ProjectSettings

ProjectSettings stores all various settings and managers for the project.

/Models: 3D models.

  • /Sound: audio files.

  • /Textures: 2D images and animations used as textures on 3D models in-game.

  • /Addressables:
    asset data files.
  • /Data: Holds all the Scriptable Objects and data files that are used by scripts.

  • /Resources: you will see a Resources folder here in the Content folder and maybe a few floating around other places in the project. This is used to house files that specifically require to be held in a folder called resources. I think this only effects 'object type' content assets. I think it had something to do with network IDs or the tilemap or both (Broodje would know).

  • /Scenes: unity scene files for menus, maps, and such.

  • /Systems: technical content relating to game systems like, substances, UI, controls, etc.

  • /WorldObjects: all the prefabs and related content that make up our interactable in-game objects. This folder is sorted by Object Type!

  • /External is where we put all the tools and frameworks we use, and that are not created by us.

    Object Type

    Using GitHub

    hashtag
    Git Clients

    To get a hold of the project, you need a git client. Git is the software that manages the source. GitHub is the website that we use to host it. If you are new, GitHub Desktoparrow-up-right is easiest for beginners.

    hashtag
    Our Repositories

    The game repository is the only primary repository the most of the contributors will be working on. The game is made by a collection of both artists and coder, the two groups couldn't do it without each other.

    The art repo is the one repo where artists can get away from the coders for a bit contribute source files as to make it easier for editing an art asset later. (This repo may get nested into the game repo, reducing work to export assets to the game from here.)

    The server repo is where know one dares to look we host the central server which, as you might have guessed, handles the multiplayer networking.

    hashtag
    Forking the Repository

    To start contributing via GitHub, first you should fork this GitHub repository, using the 'Fork' button in the upper right corner of this page. Naturally, this requires a GitHub account. You will commit your changes to your fork and then make a pull request to merge it into our repository. Learn more about pull requests .

    Over time your fork will become outdated as the main project's repository continues to be added upon. GitHub has made a on how to sync your fork, to keep it up to date with the SS3D repository.

    hashtag
    Reporting Bugs

    Before reporting a bug, please check the to see if the bug has already reported.

    If you are unfamiliar with issues, GitHub has a helpful guide . Use one of the templates when creating your issue, as the information it requests helps you help us help you more efficiently.

    Explain the problem clearly and include any additional details to help maintainers reproduce the problem:

    • Use a clear and descriptive title for the issue to identify the problem.

    • Describe the steps to reproduce the problem as specifically and detailed as possible.

    • Describe the behavior you observed and point out the problem with that behavior.

    hashtag
    Pull Requests

    Pull requests allow the maintainers to review any changes and verify they are wanted and don't break anything in the existing project. Similarly to issues, you should use the template when making a new PR and provide as much detail as possible.

    • Pull requests should merge into the develop branch.

    • The title and description should be clear and concise.

    • If the PR is attempting to fix an issue, reference the issue number in the PR description ("Fixes #number").

    hashtag
    Assigning Issues

    Our GitHub issues can be self-assigned using basic commands (see below). This is helpful for contributors to claim an issue without the assistance of a maintainer.

    Type the following command as a comment on the issue in question. Assign me - assigns you to the issue. Unassign me - unassigns you from the issue.

    hashtag
    Commit Messages

    • Use the present imperative tense ("Add feature" not "Added feature", "Move cursor to..." not "Moves cursor to...").

    • Limit the first line to 72 characters or less.

    • Reference issues and pull requests liberally after the first line.

    Dev Intro

    Intro to developing for Space Station 3D!

    This page outlines the guidelines for contributing to the SS3D project. If you have any questions, feel free to ask on our discussions pagearrow-up-right or discord serverarrow-up-right.

    The game itself is made in Unity and written in C#, check out our C# style guide. We use FishNet for networking.

    For art contributors, read through the subpages under this page, then head over to the .

    Introduction to Game Networking

    Basic networking concepts for game development.

    Well, where do I start. To clear out and not let you disappointed, programming is hard, programming games is hard, and we also added an extra layer with networking programming. Many things we discovered while at it, which makes us rework many systems, but we learn from it which is what matters.

    One thing that should be obligatory to every single programmer up to contributing to this project is to understand basic networking concepts. There’s is no game without networking.

    hashtag
    Network Packets

    Networking is all about communication. The way we communicate between machines is via network packets, which are pieces of data sent over the network. Networking games uses that, and usually we have that part done for us when we use a high-level networking framework, which is why you will rarely see that while developing basic networking code.

    hashtag
    Host

    Never think hosting is another type of network setup, it is the server and one client, at the same time, doing different things. It’s just a quick way to test your application, the only issue is that it can be misleading.

    Ah the infamous website repo, if you can't guess this one then we're no longer friends is where we develop and host our website from.

    Describe the expected behavior that should happen instead.

  • Include pictures/videos to show the steps taken to demonstrate the problem.

  • Include your game version and OS to help track if the problem is version specific.

  • Include your game settings to help track if the problem is settings specific.

  • If you're reporting a game crash, include a crash report with the error log. It's location depends on your operating system, so follow this official guidearrow-up-right.

  • If the problem isn't triggered by a specific action, describe what you were doing before the problem happened.

  • Include pictures/videos in your pull request whenever possible.

    herearrow-up-right
    pretty clear guidearrow-up-right
    our issuesarrow-up-right
    herearrow-up-right

    Licensing

    SS3D project licenses.

    hashtag
    Code

    All our code falls under the license.

    hashtag

    File Naming & Organization

    Art

    All our art assets fall under the license.

    MITarrow-up-right
    CC BY-NC-SA 4.0arrow-up-right

    Networking

    FishNet Networking

    In order to network stuff we need a framework. We aren't doing it from scratch (again). We used to use Mirror but I found FishNet Networkingarrow-up-right, and I haven't used anything else since. It has free and paid features, which is lame, but we can use it at our favour.

    I'll add important notes about developing with in the next sections.

    hashtag
    Related links:

    FishNet's GitHub pagearrow-up-right
    FishNet's API documentationarrow-up-right

    Server RPC

    hashtag
    Code that is called from the client to be run in the server, with the client information

    Continuing the disgrace, there’s code that the client decides when to run and the server actually executes it, with the information the client gave it.

    This is probably one of the most critical parts when we think about security, we sometimes we have to trust the client with what they are sending the server, so extra validation can be welcoming.

    Anyways the attribute is [ServerRpc] this one has another trick at its sleeve, we have to own the object that we are trying to call the method, this can however be completed ignored by using [ServerRpc(ignoreAuthority = true)].

    To guarantee you got it right, the params of the call will use data from the client, and the logic will be executed with the server’s data.

    Object Type

    Object type sorting is specific to object-related assets. Which is all assets in the Art folder and many in the Content folder.

    The Assets folder on the GoogleDrivearrow-up-right (and art repo) are organized the same as the Art folder on the game repo. First sorted by asset type, then by object type. Not all folders will have all types.

    hashtag
    Object Types:

    (The first 4 types in the list below are where 95% of common objects are kept.)

    chevron-rightEntitieshashtag

    In-game objects that have 'life' per-se. This includes player races, animals/monsters, robots/borgs, and even vehicles.

    chevron-rightItemshashtag

    In-game objects which can be picked up, stored on your character, or moved in a variety of ways (physics?). EX. bikehorn, bananas, guns, clothing, etc..

    chevron-rightFurniturehashtag

    In-game objects which are too large to store on your character, most of which cannot even be picked up. EX. tables, chairs, plants, machines, etc..

    chevron-rightStructureshashtag

    In-game objects similar to furniture but are nearly exclusive to the structure of the station (tilemap). EX. walls (& wall mounts), floors, plenums, wires, pipes, etc..

    chevron-rightWorldhashtag

    In-game objects which are 'other' objects pertaining to the world itself. EX. skyboxes or other backgrounds, character customization room, VFX & SFX, etc..

    chevron-rightEditorhashtag

    Non-game objects which are used in the Unity editors for display or other reasons. (This will likely only be used in the /Art/ subfolder, NOT the /Content/ subfolder. Reason being is we use broad Editor folder in the /Content/ folder.)

    ObserversRPC

    hashtag
    Code that is called from the server to be run on the client, with the information from the server

    Usually here is where things get messy for some people, but for starters, the attribute is [ObserversRpc], RPC stands for remote-procedure-call, this one is called by the server, which means the server decides when to run it, and makes the client run it, with the information it wants.

    That information is only valid for params, as the logic inside the code is run by the client, everything there only exists for the client, excepts for the method parameters sent by the server.

    Nomenclature

    On the whole, naming should follow C# standards.

    Network Message

    hashtag
    Network Messages

    Network messages are a way to transmit data without having to worry about object relationships, they are basically global events, which anyone can subscribe and listen to. And we can create structures to send whatever we want via the network.

    The downside of that is that it doesn’t support late joins, the client has to be connected in order to receive that, otherwise it would miss the message. We should only use them when we want to ask something for the server to do, without worrying about object relations.

    Server

    hashtag
    What is a Server?

    The server is an instance of the game that does all the network management, sending network packets (small portions of data) to client game instances that are connected to that server.

    What will be sent to clients and what will remain on the server is on the programmer to decide, that’s what network programming is.

    It guarantees every client game instance is seeing the game game, otherwise it would be analogue as seeing two different games being played in two different PCs.

    hashtag
    Code that runs on the server

    Logic that will only run on the server game instance, so any information not networked from any clients are unknown to the server, we cannot just network everything, that's a lot of information to send over the network, we are here to manage what should be networked and what should not.

    All the code run in the server, will only run on the server. If we were to set a variable on the server and not replicate that logic on the client, that means the client would never know that was changed.

    hashtag
    Server Attribute

    [Server]

    The Server attribute is used when we want to run methods only in the server.

    hashtag
    Examples

    chevron-rightSetting a variablehashtag

    With the example code, you can see the [Server] attribute, it always comes in the line above the method and has to be after the XML documentation of that method.

    The server attribute, prevents anything that isn't the server to run that method.

    This is done to prevent clients from setting who is controlling a player x, its a way to prevent exploiting.

    chevron-rightInitializing the gamemodehashtag

    This code is only run on the server, because it initializes the gamemode, if it ran in every client, we would initialize the gamemode in each client, instead of doing once in the server with trusted data.

    Client

    hashtag
    Client

    The client is a game instance that is connected to a server game instance, it receives all network packets the server wants to send it, so a character position is set by the server so every client sees your player in the same position.

    hashtag

    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

    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.

    Fields

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

    For example:

    BAD:

    GOOD:

    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
    Sync
    prefix, as in:
    chevron-rightMethods that listen to an eventhashtag

    Should have the prefix Handle, as in:

    clownBomb.OnExploded += HandleClownBombExploded;
    private void HandleClownBombExploded() 
    {
        Honk();
    }
    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:

    [Server]
    public void ServerKillPlayer() {}
    [Server]
    public void KillPlayer() {}
    chevron-rightMethods that are preceded by the [ServerRpc] attributehashtag

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

    [ServerRpc]
    public void CmdSpawnItem(GameItems item) 
    {
        SpawnItem(item);
    }
    chevron-rightMethods that are preceded by the [ObserversRpc] attributehashtag

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

    [ObserversRpc]
    private void RpcSpawnVisualProjectives() 
    {
       _particleSystem.Play();   
    }
    [SyncVar(OnChange = nameof(SyncUsername))] string _username;
    private void SyncUsername(string oldUsername, string newUsername, bool asServer) {}
    /// <summary>
    /// Sets a new controlling soul.
    /// </summary>
    [Server]
    public void SetControllingSoul(Soul soul)
    {
            _controllingSoul = soul;
    
            if (soul == null)
            {
            RemoveOwnership();
            }
            else
            {
            GiveOwnership(soul.Owner);
            }
    }
    hashtag
    Field Position

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

    public class MyClass 
    {
        public int PublicField;
        private int _packagePrivate;
        private int _myPrivate;
        protected int _myProtected;
    }
    private int myPrivateVariable
    private int _myPrivateVariable
    [Server]
    private void InitializeGamemode()
    {
                // Creates an instance of the SO, to avoid using the file.
                _gamemode = Instantiate(_gamemode);
    
                _gamemode.OnInitialized += HandleGamemodeInitialized;
                _gamemode.OnFinished += HandleGamemodeFinalized;
                _gamemode.OnObjectiveUpdated += HandleObjectiveUpdated;
    
                _gamemode.InitializeGamemode();
    }
    Code that runs on the client

    Logic that will only run on the client game instance. Usually in Unity game development we use attributes for defining those rules, they are little tags that go above the method declaration, which does depend a lot on the networking framework you’re using in your game.

    We are using FishNet Networking, the attribute is [Client], but code that is not sent to either clients or the server is always local to that game instance, so you only use that when you want to prevent the server game instance running that code.

    hashtag
    Client attribute

    [Client]

    The client attribute prevents the server from running a method.

    hashtag
    Examples

    chevron-rightInitializing a game screenhashtag
    [Client]
    private void Setup()
    {
        LastScreen = ScreenType.None;
    
        if (_canvasGroup != null)
        {
            bool foundCanvas = TryGetComponent(out CanvasGroup canvasGroup);
            _canvasGroup = foundCanvas ? canvasGroup : GameObjectCache.AddComponent<CanvasGroup>();
        }
    
        SetScreenState(ScreenType.Lobby, true);
    
        ChangeGameScreenEvent.AddListener(HandleChangeGameScreen);
    
    

    The [Client] attribute is preventing a server from preparing an UI, as it should not exist in a headless server. The server doesn't play the game, it manages the game.

    ChangeCameraEvent.AddListener(HandleChangeCamera);
    }

    Guidelines

    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.

    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()

    Classes & Interfaces

    Classes are written in PascalCase. For example RadialSlider.

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

    Declarations

    Misc

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

    BAD:

    XMLHTTPRequest
    String URL
    findPostByID

    GOOD:

    XmlHttpRequest
    String url
    findPostById

    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

    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;

    One declaration per source file

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

    Language

    Use US English spelling.

    BAD:

    string colour = "red";

    GOOD:

    string color = "red";

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

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

    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

    switch (variable) 
    {
        case 1:
            break;
        case 2:
            break;
        default:
            break;
    }

    GOOD

    switch (variable) 
    {
        case 1:
            break;
        case 2:
            break;
    }

    BETTER

    switch (variable) 
    {
        case 1:
            break;
        case 2:
            break;
        default:
            Debug.LogError("Unexpected value when...");
            return;
    }

    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

    Events

    Prefer single declaration per line.

    BAD:

    GOOD:

    string username, twitterHandle;
    string username;
    string twitterHandle;

    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:

    Brace Style

    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:

    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.

    class MyClass {
        void DoSomething() {
            if (someTest) {
              // ...
            } else {
              // ...
            }
        }
    }
    class MyClass
    {
        void DoSomething()
        {
            if (someTest)
            {
              // ...
            }
            else
            {
              // ...
            }
        }
    }
    if (someTest)
        doSomething();  
    
    if (someTest) doSomethingElse();
    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;
    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.

    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.

    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 .

    If we need to network a view, we can either use a direct call on a or use . Do not use the for that.

    hashtag

    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.

    Tweening

    Models

    I'll fill these subpages later, promise.

    Animations

    for (int i = 0; i < 10; i++) 
    {
      Debug.Log("index=" + i);
    }
    public sealed class ExplosionSystem : NetworkSystem 
    {
        public void CreateExplosionAt(Vector3 position, float size) 
        {
            // boom.
        }
    }
    SystemLocator
    ⚠️
    IMPORTANT!

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

    public sealed class RoundTimeView : Actor 
    {
        // add time logic
    }
    systems
    system
    network messages
    event bus's events
    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);

    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.

    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.

    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 .

    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 CS Frameworkarrow-up-right decided to create an event bus 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 event bus and its variants will then be called.

    event bus
    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 file organization. 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.

    3D Models

    I'll fill these subpages later, promise.

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

    hashtag
    Using the SystemLocator

    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();

    public class System : Actor
        {
            protected override void OnAwake()
            {
                base.OnAwake();
                SystemLocator.Register(this);
            }
        }

    Fonts

    Textures

    Textures

    Graphics

    3D Animations

    Audio

    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

    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

    Audio

    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)

    3D Animations

    Textures

    3D Models

    I'll fill these subpages later, promise.

    Audio

    Application Settings

    triangle-exclamation

    All the parameters in that window are overridden in built executables. They are then defined by the Command Line Args.

    hashtag
    Command Line Args

    The command line args are various symbols used to define arguments to be used by the SS3D application when it runs. The reason for it existing is to easily add Hub support for it when we eventually have it.

    Here's a little example on how they look, you can find all them in the CommandLineArgs.cs file

    chevron-rightSnippet of what are the args in CommandLineArgs.cshashtag

    hashtag
    Using Application Settings window

    triangle-exclamation

    This is only applicable when using the Unity Engine, which should be avoided if you're not developing.

    The application settings window reflects information used on the Command Line Args, it is useful for Editor overrides and internally we use it along the command line args when using built executables.

    In the project settings window, you can find the Application Settings group, where you can see all the settings related to what configurations the game will use when running.

    This only contains settings related to the application use. Graphics, sound, and user settings will not be defined there.

    chevron-rightFinding the Application Settings windowhashtag

    Open the Project Settings window in the Unity Editor and look for the SS3D group and inside it you'll find Application Settings.

    circle-info

    You can hover the parameters in Application Settings to get a description on what it does and how to use it.

    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.

    /// <summary>
    /// The "-serveronly" arg in the executable.
    /// </summary>
    public const string ServerOnly = "-serveronly";
    /// <summary>
    /// The "-host" arg in the executable.
    /// </summary>
    public const string Host = "-host";
    /// <summary>
    /// String.
    /// </summary>
    public const string Ip = "-ip=";
    /// <summary>
    /// String.
    /// This is temporary, in production use, this will not exist,
    /// and be replaced by the token, and then the server will get the Username.
    /// </summary>
    public const string Ckey = "-ckey=";
    /// <summary>
    /// String.
    /// in production this will be sent by the Hub to the client executable.
    /// </summary>
    public const string AccessToken = "-token=";
    /// <summary>
    /// Bool.
    /// Skips the intro.
    /// </summary>
    public const string SkipIntro = "-skipintro";
    /// <summary>
    /// Bool.
    /// Disables the discord integration.
    /// </summary>
    public const string EnableDiscordIntegration = "-enablediscordintegration";

    Graphics

    Guides

    Configure your firewall and antivirus

    A recurrent issue we have when playing in editor is the game crashing with the exception "Port already in use". We found out that making the SS3D's folder (the whole project's folder) an exception for your firewall and antivirus is solving this issue.

    We might add specific instructions here for specific firewalls and antivirus in the future, but it should be pretty straightforward.

    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.comarrow-up-right 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.

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

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

    Joining a server

    For the purpose of debugging (maybe even playing some day), this page explain how you can join as a client in a SS3D server, in or outside the Unity editor.

    circle-exclamation

    We don't have the SS3D Hub yet, so this is only valid before we have that.

    hashtag
    Using the .bat file

    First, launch a server by following instructions on the page Hosting a server.

    Assuming you built the game and have a SS3D.exe file in the Builds folder, you can simply double left click on the Start SS3D Client - GhostOfBeep.bat to launch a client, with GhostOfBeep as a username.

    hashtag
    Using the Command Line Args

    triangle-exclamation

    This is only applicable for Built Executables.

    Open a command prompt in your project's path to the Builds/Config, and run the following command changing the fields appropriately.

    This will launch the client, replace clientUsername with your username and replace serverAddress with the desired server address, you can use localhost or 127.0.0.1 for a local server.

    hashtag
    Using Application Settings window

    triangle-exclamation

    This is only applicable when using the Unity Engine to join a server, which should be avoided if you're not developing.

    You can set the Network Type to Client, the Editor Server Address to the desiredserver IP, or your local IP (127.0.0.1) if hosting from the same machine, and the Ckey to your desired Ckey.

    circle-info

    You can hover the parameters in Application Settings to get a description on what it does and how to use it.

    Setting up a dedicated server

    For the purpose of debugging (maybe even playing some day), this page explain how you can open a server-only SS3D server, in or outside the Unity editor.

    circle-exclamation

    We don't have the SS3D Hub yet, so this is only valid before we have that.

    The process of opening SS3D as server only, where you can play and have players connected to your server is possible via two paths:

    Running the Project

    hashtag
    Pre-launch instructions

    hashtag
    Using the Command Line Args

    triangle-exclamation

    This is only applicable for Built Executables.

    To open a server outside the Unity Editor, first you have to build SS3D in the Builds folder of your SS3D project. You can then open a command prompt, move to the Builds folder and type :

    hashtag
    Using Application Settings window

    triangle-exclamation

    This is only applicable when using the Unity Engine to open a server, which should be avoided if you're not developing.

    You can set the Network Type to Server.

    circle-info

    You can hover the parameters in Application Settings to get a description on what it does and how to use it.

    start SS3D.exe -ip=serverAddress -ckey=clientUserName -skipintro
    start SS3D.exe -serverOnly

    Building the game

    If you're developing, you'll need at some point to build the game so you can test it with multiple clients.

    You can follow Unity's instructions about buildingarrow-up-right here if you need help with that.

    We currently have two scenes in game, include them both, and you should be fine by leaving everything else default.

    Click the Build button, and select the Builds/Game folder, if you don't, the build won't work !

    That's it !

    Hosting a server

    For the purpose of debugging (maybe even playing some day), this page explain how you can host a SS3D server, in or outside the Unity editor.

    circle-exclamation

    We don't have the SS3D Hub yet, so this is only valid before we have that.

    The process of hosting a server, where you can play and have players connected to your server is possible via two paths:

    hashtag
    Pre-launch instructions

    hashtag

    hashtag
    Using the .bat file

    Assuming you built the game and have a SS3D.exe file in the Builds folder, you can simply double left click on the Start SS3D Host.bat to launch a server, with john as a username.

    hashtag
    Using the Command Line Args

    triangle-exclamation

    This is only applicable for Built Executables.

    To host a server outside the Unity Editor, first you have to build SS3D in the Builds folder of your SS3D project. You can then open a command prompt, move to the Builds folder and type :

    Replace hostUsername by whatever username you want to use. Don't hit enter yet and skip to launch instructions.

    hashtag
    Using Application Settings window

    triangle-exclamation

    This is only applicable when using the Unity Engine for hosting, which should be avoided if you're not developing.

    You can set the Network Type to Host, the Editor Server Address to your server IP, or your local IP (127.0.0.1) if hosting from the same machine, and the Ckey to your desired Ckey.

    circle-info

    You can hover the parameters in Application Settings to get a description on what it does and how to use it.

    hashtag
    Launch Instructions

    circle-info

    This is valid for Unity Editor and Built Executable usage.

    In the permissions.txt file, inside the Builds/Config folder. The username should have Administrator written on the same line in the permissions.txt file, otherwise it will not able to host the server. This should probably be automated eventually.

    You can now hit enter or hit play depending on your case. The server will be launched and you can join with a client now.

    Once started, go to server settings and click on start round like in the picture below.

    hashtag
    Troubleshooting

    chevron-rightNothing appearing on the server page when hostinghashtag

    Check the host username in the command:

    It must exactly correspond to whatever username is present in the permissions.txt file, in the Builds/Config folder.

    Pull request review process

    When needed, contributors might need to review PRs

    To ease out the process, we'll walk through all the points need to be tackled when reviewing.

    hashtag
    Check the Target branch, check the source branch

    1. Check the target branch.

      • Most of the PRs should be develop, but some might be updating a feature or a preview feature, make sure the feature is not supposed to be a test nor experimental.

    2. Source branch should be up-to-date with the develop branch.

      • It is not allowed to merge non up-do-date branches to develop, even if there's no merge conflicts.

    hashtag
    Check PR's information

    1. Revise the PR's textual information.

      • Make sure the title doesn't contain the [WIP] tag. If you feel the PR is completed, warn the author.

      • Make sure the PR describes clearly how that PR will change develop

    hashtag
    Check and make the tests

    You can follow the instruction of the section to learn how to run with the different set ups of this section.

    1. Revise the tests log.

      • Before continuing, it is proper to check if all the checks were passed on the tests runner.

      • If any tests have failed, warn the author.

    hashtag
    Check the file organization and naming

    1. Make sure no unwanted files were committed.

      • we don't want changes that are not related to the PR.

      • Warn the author about, question why that file is being submitted.

    hashtag
    Check the PR content

    1. Make sure no unwanted or unrelated changes are present on the PR.

      • we want PR to be concise as much as possible, changes that do not fit the description of the PR should simply not be there.

    2. Make sure big PR are either :

    hashtag
    Check the code

    1. Make sure all the code follows the .

      • This is not the best way nor it is infallible, but we still don't have any syntax linting.

    2. Make sure the best architecture to your knowledge is being used.

    hashtag
    Ask for documentation

    1. Ask for thorough comments when necessary.

      • Insist on it especially when something is public.

    2. Ask for independent documentation when a new feature is added.

    hashtag
    Check any linked issues

    1. Check any linked issues in the pull request.

      • If a PR links to an issue(s) to close them, check to make sure the PR meets the requirements of the issue. If for any reason parts of the issue cannot be met, make sure the reason is stated in the PR.

    hashtag
    Turn Known Issues into Github Issues

    Not all issues need to be solved in a PR. On the contrary, sometimes it's better to avoid bloating it or delaying a merge. Known issues that can be kept are the one impacting only the newly added feature, not in a fatal way. It can be optimization issue, partial feature..

    Turn all known acceptable issues into Github issues for someone to solve later on.

    hashtag
    All Good !

    If you check all of these, accept the PR and warn a CentCom or a Maintainer member. If you're a CentCom or Maintainer member, you can merge the PR.

    Debugging SS3D

    This page aim to show some practices that could be helpful to help you debug SS3D.

    hashtag
    Always check your code on host and on client

    SS3D is a networked game, some of the code is running server side and some client side. Every time you add or remove something, you should check that it works well both on the host and on the clients.

    Ideally, you should make tests including one host and multiple clients, at least one, but be careful, some bugs involve two clients and it would appear as working between host and client alone. Hence, try to test with two clients and one host, whenever it's possible and relevant.

    Refer to the section to learn how to host and join a server.

    hashtag

    hashtag
    Making attributes of class public

    In Unity, whenever an attribute is public, it will appear in the gameObject inspector. This allows you to modify attributes during runtime in the gameObject Inspector.

    In the below picture, I can modify the Inventory displayed by the inventory UI script, simply by clicking on it, and replacing the reference with the one of my choice.

    This could be useful to quickly go through all inventories during the run, without having to code a special tool for it.

    Making attributes public is also interesting to observe attribute changes during run time without using an actual debugger or the console. It has the advantage of not disturbing the flow of the game, and not overflowing the debug console.

    hashtag
    Making use of the Input.GetKeyDown() function

    Sometimes, what you need to debug involves critical timing, and running some logic. A classic debugger is not good enough or at best makes things really tedious.

    To control when you want to get some debugging info, or run some code at the right time, what you could use is the Input.GetKeyDown() function.

    This function simply returns true whenever the key of your choice is pushed. Put that in the Update loop of a monoBehaviour and you can log anything you want when you push a key, or run some logic.

    Here's an exemple, this little snippet of code allows me to modify a given container by one of my choosing, during run time, when I choose to. It's a simple trick but it's easy to forget about it.

    hashtag
    What's destroying my game object ?

    SS3D is full of calls to the Destroy() method on game objects and as the game grows finding what's destroying a given game object will become increasingly difficult.

    A simple search in your code editor, using Ctrl+f and searching through the whole solution for "Destroy(" can be enough, but it's tedious.

    Here is an useful trick to find what's destorying your game object. Just add this code on any component on the game object that is destroyed. It will print in the Unity Debug console the stack of methods responsible for the call of OnDisable, and this usually allows to to trace back what's destroying your game object.

    Play mode tests

    SS3D, now with play mode tests ! Those are heavy tests, that require building the game, and, depending on the goal, they may require starting a server, launching multiple clients ...

    hashtag
    Play mode tests architecture

    Play mode tests need different setup based on what is tested . For instance, we might want to test picking up an item, for players embarking at the same time as host, or for players embarking after it. We could also test it just for host.

    hashtag
    Manual Tests

    The pages below describe a bunch of tests related to different systems implemented that a reviewer should do in order to check if everything's alright.

    All tests described here should be tests that did pass at some point on the develop branch.

    Please add a mention if the test you add is supposed to pass in the future but not yet.

    Those tests are under the form :

    SetUp : What need to be done before starting the test.

    Test : What need to be done to execute the test.

    Result : The expected result.

    .
  • Make sure the known issues are present in the PR description.

    • See link 'Turn known issues into Github issues' for more info.

  • Playtest in the editor.

    • Playtest all the feature in the editor, as it is the most safe way to playtest most of the time.

    • If any issues appear here, warn the author and create a small video or a very detailed comment on the PR.

  • Make a build.

    • If the build fails, warn the author and screenshot the build error message, add said screenshot in a comment on the PR.

  • Play the build as the host.

    • If any errors appear in this built version, warn the author and create a small video or a very detailed comment on the PR.

    • Note that all playtest in built executables should explore the feature as much as possible, to detect bugs as early as possible.

  • Play the build as the host and another as a client.

    • If any errors appear in this built version, warn the author and create a small video or a very detailed comment on the PR.

  • Play the build as the server and another as a client.

    • If any errors appear in this built version, warn the author and create a small video or a very detailed comment on the PR.

  • Play the build as the server and more than one as clients.

    • If any errors appear in this built version, warn the author and create a small video or a very detailed comment on the PR.

  • Make sure the committed files follow our file naming and organization.

    Completely new addings. It's fine for PRs to be big if they don't modify preexisting part of the code.

  • Complete removal. If we ever need to completely remove something, it's fine if it's big.

  • Changes very focused on one aspect : adding a particular attribute project wise, fixing a given style project wise, those are all acceptable changes.

  • Always ask why things are in that way if you feel something can be done better or don't know how it works.

  • Don't ask for micro optimization unless it's really obvious.

    • We have close to zero performance tests at this point. We don't want to over optimize at the cost of ruining readability and introducing hard bugs to solve, when the performance need is probably not even there.

  • The doc should be user oriented, it should mainly answer the question "how do I use this feature?"

  • If the contributor wants to add some technical details in it, check that it's focusing on a global overview of the feature.

  • Up to date documentation should be put here on SS3D's Gitbook.

  • Running
    C# style guide

    Edit mode tests

    These are edit mode tests that are intended to check the behaviour of public methods within the game systems. They are restricted to editor only functionality, so some code will not function (e.g. IEvent invocation)

    Testing SS3D

    This page describes how testing framework.

    Unity divides tests in two categories, play tests and editor tests. We decided to divide those tests into more subcategories. Here's a summary of the hierarchy, and you can find on the following pages a description of each of those, allowing you to choose the right category when writing a new test.

    • Play tests

    • Editor tests

      • Asset audit

      • Edit mode

    Assets audit tests

    These are edit mode tests that are intended to check basic configuration of objects. These represent actions that a non technical person could check in the editor (given a basic checklist) without needing to look at any code. Examples include making sure no prefabs have missing scripts, making sure all tile prefabs are within a certain size range, all asset data references are not null etc.

    Asset audits have been configured as parameterized tests, so it will be immediately clear which object is causing failures.

    Note that a business rule is that test failures for prefabs should be resolved before test failures for scenes, because the scenes may be broken because the prefabs in them are broken.

    Running arrow-up-right
        public void Update()
        {
            if (Input.GetKeyDown(KeyCode.space))
            {
                // Run some Debug.Log() or some logic
            }
        }
        public void Update()
        {
            if (Input.GetKeyDown(KeyCode.L))
            {
                Debug.Log("updating left container");
    
                var container = FindObjectsOfType<AttachedContainer>()
                    .Where(x => x.gameObject.name == "leftcontainer").First();
                UpdateContainer(container);
            }
        }
        void OnDisable()
        {
            // If object will destroy in the end of current frame...
            if (gameObject.activeInHierarchy)
            {
                Debug.LogError("Log an error with a stack trace in debug mode");
            }
        }

    Inventory test

    hashtag
    Clothings put in clothes slots show on the character

    Should work for backpack, gloves, shoes, jumpsuit, headsets left and right, glasses, mask, PDA.

    SETUP : Just spawn a human, make it naked. Spawn a random piece of cloth.

    TEST : Take the cloth by left clicking it and put it in the appropriate cloth container by dragging it with left click.

    Working with animations

    You might wonder how to trigger an effect during an animation, how to call functions when an animation ends, even better, have your effect still trigger at the right time even if you change your animation. The answer is state machine behaviours.

    To make it simple, SMBs are scripts you're going to put on states in animator controller. It calls some callback methods when entering and leaving a state.

    In this page, I'll provide an example on how to set up properly an airlock, using it.

    hashtag

    start SS3D.exe -host -ckey=hostUsername -skipintro
    start SS3D.exe -host -ckey=hostUsername -skipintro
    RESULT : You should see the cloth displaying on the player.

    hashtag
    Items put in hand slots show in player hands

    SETUP : Just spawn a human.

    TEST : Pick up a random item by left clicking it.

    RESULT : Check the item is well in hand and move accordingly to the player's movements.

    hashtag
    Dragging items out of inventory slots dump them out of inventory

    SETUP : Just spawn a human. the human should have a few items on it already.

    TEST : drag an item with left click and release outside of the container.

    RESULT : Check the item is dropped close to the human, and the sprite is no longer showing in inventory.

    Maintainer Guide

    This guide has for objectives to set common good practices for Maintainers to follow before merging a pull request. Also useful for contributors who want to know about our expectations.

    Airlock example

    Say you want some lights on your airlock to turn green when opening, red when closing and black when idle.

    We have the following state machine for the airlock.

    State machine of the airlock and its four states

    During the opening state, the animator play the "opening" animation.

    During the closingstate, the animator play the "closing" animation.

    When selecting a state, see the "Add behaviour" option. We're going to use that to write a script to handle the airlock lights.

    The inspector for the opening state

    As said earlier, SMB's have callbacks when entering and leaving a state.

    We're going to use two of them here, OnStateExit and OnStateEnter.

    public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)

    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)

    The idea is simple : let's put our behaviour on the two states, opening and closing. When leaving a state, we know that the door is idle, so we're just going to make that call (we'll detail the ChangeColors method later).

    Since we want to use the same script both when closing and opening the door (notice we could write two different scripts for simpler logic but harder management of files), when entering a state, we need to check in which state we're going.

    Pretty simple, the code is self explanatory, we're using the data from the AnimatorStateInfo to retrieve the data we need :

    Now, let's see what's going on in the ChangeColors method.

    Contrary to a classic monobehaviour, we can't define serialised field on a SMB, so if we need

    to access stuff on our game object we need a simple trick. The callback from the SMB are sending back our animator component, so we have an easy reference to the game object the animator is onto. We can use that game object reference to access just any script we need, on our game object.

    Here we use that to retrieve mesh renderers and skinned mesh renderers, that we put into lists on the AirlockOpener script.

    Once we have them, we can do whatever we want with them, here we just modify some material colors and we modify some blend shape.

    Roadmap

    The progress bar of Space Station 3D.

    We use milestones to track large achievements of development. Our tracks the individual tasks in our active milestone(s). The milestones are defined below.

    Purple - indicates the milestone is completed. Green - indicates the milestone is in progress. Yellow - indicates the milestone is on GitHub but NOT in progress yet. White - indicates the milestone is only be theorized at this point and will be elaborated on in the future as we get closer to it.

    Reminder that our milestones are synced with our release versioning for easy tracking, link below.

    (We will add GitHub links to the name of the milestones in the list below as they get added to GitHub.)

    Health tests

    This describe a bunch of tests to perform when messing with things related to health.

    hashtag
    Killing a player by hitting on torso

    SETUP : Just have a healthy human spawn, with the AttackBodyPartByClickingIt attached. Check that the damage amount is above 0 (should be 10 to quick test it).

    TEST : Press F a bunch of time while aiming with the mouse on the player torso until the player explode.

    Releases

    Defining what a release version means.

    hashtag
    Release Versioning

    The release naming is defined by gamestate.major.

        public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
        {
            ChangeColors(_idleColor, animator, stateInfo, layerIndex);
        }
    /ublic override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        if (stateInfo.IsName(Opening))
        {
            ChangeColors(_openingColor, animator, stateInfo, layerIndex);
        }
        if (stateInfo.IsName(Closing))
        {
            ChangeColors(_closingColor, animator, stateInfo, layerIndex);
        }
    }
           
        private void ChangeColors(Color color, Animator animator) 
        { 
             var renderers = animator.GetComponent<AirLockOpener>().MeshesToColor;
             var skinnedRenderers = animator.GetComponent<AirLockOpener>().SkinnedMeshesToColor;
             foreach (var renderer in renderers) 
             { 
                 renderer.materials[DOOR_LIGHT_MATERIAL_INDEX].color = color; 
             } 
             
             foreach (var skinnedRenderer in skinnedRenderers)
             {
                if (color == _openingColor)
                {
                    skinnedRenderer.SetBlendShapeWeight(1, 100);
                    skinnedRenderer.SetBlendShapeWeight(2, 0);
                }
                else if (color == _closingColor)
                {
                    skinnedRenderer.SetBlendShapeWeight(1, 0);
                    skinnedRenderer.SetBlendShapeWeight(2, 100);
                }
                else
                {
                    skinnedRenderer.SetBlendShapeWeight(1, 0);
                    skinnedRenderer.SetBlendShapeWeight(2, 0);
                }
             }
    
        }
    RESULT
    : You should see body parts flying, the camera should focus on the detached head, all body parts should disappear after a while and the head should too. After that, you're a ghost that you can control.

    hashtag
    Killing a player by hitting on head

    SETUP : Just have a healthy human spawn, with the AttackBodyPartByClickingIt attached. Check that the damage amount is above 0 (should be 10 to quick test it).

    TEST : Press F a bunch of time while aiming with the mouse on the player's head until the player dies.

    RESULT : You should immediately become a ghost.

    hashtag
    Bleeding to death

    SETUP : Just have a healthy human spawn, with the AttackBodyPartByClickingIt attached. Check that the damage amount is above 0 (should be 10 to quick test it).

    TEST : Press F a bunch of time while aiming with the mouse on random body parts on the player.

    RESULT : See that blood particles are spawning close to the body part you hit. Check that after a while, the player dies from lack of blood. Should take less than a minute if you hit a lot of time each body part.

    hashtag
    Walking when injured

    SETUP : Just have a healthy human spawn, with the AttackBodyPartByClickingIt attached. Check that the damage amount is above 0 (should be 10 to quick test it).

    TEST : Press F a bunch of time while aiming with the mouse on the feet.

    RESULT : See that player movement is slower with injured feet. Try to destroy completely the feet to see that the player cannot move anymore.

    hashtag
    Loosing hands

    SETUP : Just have a healthy human spawn, with the AttackBodyPartByClickingIt attached. Check that the damage amount is above 0 (should be 10 to quick test it).

    TEST : Take an item in hand. Press F a bunch of time while aiming with the mouse on a hand, until you destroy it.

    RESULT : See that a hand slot in the UI has disappeared, and that the player can't pick up items with its missing hand. Try to destroy the other one to check if the player can't take anything anymore. Check that the item held is dropped when the hand is destroyed.

    minor
    .
    hotfix
    In which the rules are:
    chevron-rightGame Statehashtag

    Refers to the overall state of the game. 0.x.x.x version indicates the game is still in a alpha/beta/pre-complete phase. Once we have reached a point where we have ported over most of the systems and content from ss13, and feel satisfied, we can then move to 1.x.x.x and beyond with new content.

    chevron-rightMajorhashtag

    Refers to large milestone. Major milestones are large changes that may span numerous months and PRs.

    An example of a major milestone would be several new systems or reworked systems that together make up a large change in how the game feels. Milestones will have their version number in their name. e.g. "0.2 - Milestone Name".

    chevron-rightMinorhashtag

    Refers to small milestones. Minor milestones are usually a single large system or a collection of smaller changes.

    An example might be a rework to the health system, or a collection of new content for a couple recently added systems.

    chevron-rightHotfixhashtag

    Refers to quick fixes post a major or minor release. An example would be a critical bug that was undetected until after release and needs to be quickly fixed.

    Any example might be a bug that prevents players from joining and needs to be updated in a release ASAP instead of waiting for the next minor release.

    hashtag
    Automated Releases

    Soon we will move to automated releases much like many of the SS13 servers and such. First they will likely start off as monthly releases but after we gain a lot of momentum we will eventually move to nightly releases.

    These will be registered as hotfix releases. Devs will still be able to push manual releases at anytime for hotfixes as well.

    art guide

    hashtag
    0.0 - Rework

    The rework is the first primary milestone. Its goal is to rework the structure (underlying aspects) of the game (such as networking, tilemaps, character movement, etc.), harmonize contributor guidelines and workflows, and porting over necessary systems.

    chevron-right0.0.1 - Server & Lobbyarrow-up-righthashtag
    chevron-right0.0.2 - Management & Documentationarrow-up-righthashtag
    chevron-right0.0.3 - Round Systemarrow-up-righthashtag
    chevron-right0.0.4 - Simple Character Controllingarrow-up-righthashtag
    chevron-right0.0.5 - Port Interactions Systemarrow-up-righthashtag
    chevron-right0.0.6 - Port Tilemapsarrow-up-righthashtag
    chevron-right0.0.7 - Fixing, Cleaning, Testingarrow-up-righthashtag
    chevron-right0.0.8 - Character Rig & Animationarrow-up-righthashtag
    chevron-right0.0.9 - Basic UI Guidelines & Reworkarrow-up-righthashtag

    It doesn't need to be 100% fool-proof because we are very early in development and the UIs will be reworked in depth in the future to look nicer and be consistent.

    chevron-right0.1.0 - Finalize Reworkarrow-up-righthashtag

    Port Remaining Secondary Systems. None of these will be a "piece of cake" but they shouldn't be rocket science either. The systems are already made, they just need to be ported over and any new issues resolved.

    Add basic SFX & anims:

    Fixes for the Rework major milestone.

    hashtag
    0.1 - Minimal Viable Product (MVP)

    The MVP is the goal to achieve a basic demo-like product to showcase a glimpse of our future game. It should include basic health & combat, crafting & construction, clothing, and shuttles.

    chevron-rightBasic Healthhashtag
    chevron-rightBasic Combathashtag
    chevron-rightClothinghashtag

    Basic clothing system. The belt cannot be put on if the player is not wearing a jumpsuit, and the jumpsuit cannot be removed until the belt is removed first.

    chevron-rightBasic Craftinghashtag
    chevron-rightBasic Constructionhashtag

    Basic construction should be somewhat easy i would think. Its something we have had in the past so maybe could use that as reference, also we have in-game tilemap editor so basically it would just be 'hide the editor from non-admins and add some tool interactions similar to that on the develop branch'

    chevron-rightAdditional Roleshashtag

    I agree its not vital but I kinda see these helping with showcasing basic systems. If we have basic health, may as well add a room and clothing and role for it. Bartender represents basic substances, security represents basic combat, assistant represents basic item crafting, engineer is basic construction, captain can 'call the shuttle' to end the round.

    chevron-rightFinalize MVPhashtag

    hashtag
    0.2 - Alpha: Expand The Basics

    When we really start to expand systems, add interactions, new content etc..

    chevron-rightBasic NPCshashtag
    chevron-rightExpand Interactions & Containershashtag

    chevron-rightExpand Gamemode & Roundshashtag

    Expand Gamemodes:

    chevron-rightExpand & Fix Tilemapshashtag
    chevron-rightExpand Health & Combathashtag

    Expand Health:

    Expand Combat:

    chevron-rightExpand Crafting & Constructionhashtag

    Expand Item crafting:

    chevron-rightHarmonize Clothing & Character Movementhashtag

    Clothing:

    Character Movement:

    chevron-rightPolish Gamemodes & Roleshashtag

    Polish Gamemodes:

    Polish Roles:

    chevron-rightExpand Lobbies, Menus, Settingshashtag

    hashtag
    0.3 - Alpha: More Systems

    Now adding less core but still crucial systems, and expanding previous systems.

    chevron-rightBasic Atmosphericshashtag

    chevron-rightBasic Electricityhashtag

    chevron-rightMore Health & Combathashtag

    More Health:

    GitHub Projects pagearrow-up-right
    Releaseschevron-right
    Melee combat - dodging?
    Start Your Creative Projects and Download the Unity Hub | UnityUnitychevron-right
    Unity Hub downloads.
    Logo
    GitHub - RE-SS3D/SS3D: Space Station 3D, another remake of SS13, but with an extra D.GitHubchevron-right
    Our game repo.
    GitHub - RE-SS3D/SS3D-Art: Art repository for Space Station 3D.GitHubchevron-right
    Our art repo.
    Logo
    ProjectVersion.txt
    Space Station 3DGitHubchevron-right
    Our GitHub organization.
    Logo
    Download ArchiveUnitychevron-right
    Unity download archives.
    Logo
    GitHub - RE-SS3D/SS3D-Website: Website for Space Station 3D.GitHubchevron-right
    Our website repo.
    Logo
    GitHub - RE-SS3D/SS3D-CentCom: Central API for Space Station 3D.GitHubchevron-right
    Our server repo.
    Logo
    Logo
    MIT License (Expat) Explained in Plain English - TLDRLegalwww.tldrlegal.comchevron-right
    MIT
    Deed - Attribution-NonCommercial-ShareAlike 4.0 International - Creative Commonscreativecommons.orgchevron-right
    CC BY-NC-SA 4.0
    Logo
    Unity - Manual: State Machine Behavioursdocs.unity3d.comchevron-right
    Logo
    Logo
    Unexpected error with integration github-files: Integration is not authenticated with GitHub