Hello and welcome to the contributing section for the Kittens Rise Up game client! The codebase is constantly changing, if anything comes across as confusing or you notice something is not matching with what is said here please tell me about it in the Kittens Rise Up Discord or create a issue in this repository and I will have a look at it.
Download Unity Hub
Project is currently using 2021.1.7f1
, later versions should work just fine.
Note that you will need to setup the web-server and game-server as well if you want to login properly. (The client logs into the web-server then the game-server)
- Methods should follow PascalFormat
- Most of the time
{}
should be fully expanded - Variables should be camelCase regardless if private or public
- Try to use
var
where ever possible
- Always test the application to see if it works as intended with no additional bugs you may be adding!
- State all the changes you made in the PR, not everyone will understand what you've done!
The networking library called ENet-CSharp should be executed on a separate thread from the Unity thread otherwise Unity will interfere with ENet. If you are unfamiliar with threads please read Using threads and threading.
In Assets/Scripts/Netcode/ENetClient.cs
, add the 'opcode' to the following enum. For example maybe you want to instruct ENet to disconnect from the server entirely, so you would add something like CancelConnection
.
public enum ENetInstruction
{
CancelConnection
}
Enqueue the instruction from the Unity thread
ENetInstructions.Enqueue(ENetInstruction.CancelConnection);
Then dequeue the instruction in the ENet thread
// ENet Instructions (from Unity Thread)
while (ENetInstructions.TryDequeue(out ENetInstruction result))
{
if (result == ENetInstruction.CancelConnection)
{
// some random code here
Debug.Log("Cancel connection");
connectedToServer = false;
tryingToConnect = false;
runningNetCode = false;
break;
}
}
If you wanted to send data along with the opcode then the ENetInstruction
would have to become a class with a opcode enum inside however the need for such a purpose has not risen yet.
Lets say you want to display a message in the Unity UI when something happens in the ENet thread. Since functions like Debug.Log(...)
and gameObject.GetComponent<Text>().text = ...
are Unity related they must be executed on the Unity thread otherwise you will run into a runtime error.
First add the opcode LogMessage
to the UnityInstruction.Type
public class UnityInstruction
{
public enum Type
{
LoadSceneForDisconnectTimeout,
LoadMainScene,
LogMessage,
NotifyUserOfTimeout
}
public Type type;
public string Message;
}
Enqueue the data in the ENet thread
// received a purchased item packet from the server
if (opcode == ServerPacketType.PurchasedItem)
{
var data = new PacketPurchasedItem();
var packetReader = new PacketReader(readBuffer);
data.Read(packetReader);
// enqueue data
unityInstructions.Enqueue(new UnityInstruction {
type = UnityInstruction.Type.LogMessage,
Message = $"You purchased item: {data.itemId} for x gold."
});
}
Dequeue the data in the Unity thread
while (unityInstructions.TryDequeue(out UnityInstruction result))
{
switch (result.type)
{
case UnityInstruction.Type.LogMessage:
Debug.Log(result.Message);
break;
}
}
Never give the client any authority, the server always has the final say in everything. This should always be thought of when sending new packets.
- Creating the Writer Packet
- Adding the Opcode
- Sending the Packet
- Creating the Reader Packet Server Side
Create a new packet class under Assets/Scripts/Netcode/Packets/Client
, the packet class should be called something like WPacketPositionData
or WPacketDisconnect
. (note that the W prefix means "write", if a reader packet was being created the R prefix should go in front)
Use the following code as a template
using Common.Networking.Message;
using Common.Networking.IO;
using Common.Networking.Packet;
namespace KRU.Networking
{
public class WPacketSendSomething : IWritable
{
public ushort ItemId { get; set; }
public void Write(PacketWriter writer)
{
writer.Write(ItemID);
}
}
}
Replace ItemID
with one or more fields, remember to write them all with writer.Write(...)
.
Opcodes are what make packets unique from each other. For example, ClientPacketType.Login
is a opcode used to identify that this packet holds login information.
On the client, navigate to Assets/Scripts/Netcode/Packets/Opcodes.cs
and add your opcode to the enum ClientPacketType
.
On the server, navigate to src/Server/Packets/Opcodes.cs
and add your opcode to the enum ClientPacketType
.
The Opcodes.cs
client-side should look exactly like that of the Opcodes.cs
server-side.
Navigate to Assets/Scripts/Netcode/ENetClient.cs
, this is the main networking script for the client.
First, create the packet
var data = new WPacketSendSomething((ushort)itemId);
var clientPacket = new ClientPacket((byte)ClientPacketType.PurchaseItem, data);
Add the packet to the outgoing concurrent queue
outgoing.Enqueue(clientPacket);
Finally, dequeue the packet from the outgoing concurrent queue and send it to the server. Note that PacketFlags.Unreliable
should be used for sending data that is not important such as positional data while PacketFlags.Reliable
should be used when sending important data such as item the client wants to purchase.
while (outgoing.TryDequeue(out ClientPacket clientPacket))
{
switch ((ClientPacketType)clientPacket.Opcode)
{
case ClientPacketType.PurchaseItem:
Debug.Log("Sending something to game server..");
Send(clientPacket, PacketFlags.Reliable);
break;
}
}
The server needs to know how to read this packet.
In the server, navigate to src/Server/Packets/Client/
and create a class named RPacketSendSomething.cs
Use the following code as a template.
using Common.Networking.Message;
using Common.Networking.IO;
using Common.Networking.Packet;
namespace GameServer.Server.Packets
{
// ================================== Sizes ==================================
// sbyte -128 to 127 SByte
// byte 0 to 255 Byte
// short -32,768 to 32,767 Int16
// ushort 0 to 65,535 UInt16
// int -2,147,483,648 to 2,147,483,647 Int32
// uint 0 to 4,294,967,295 UInt32
// long -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 Int64
// ulong 0 to 18,446,744,073,709,551,615 UInt64
public class RPacketSendSomething : IReadable
{
public ushort ItemId { get; set; }
public void Read(PacketReader reader)
{
itemId = reader.ReadUInt16(); // we sent it as a ushort so we must read it as a ushort (see to the table above)
}
}
}
Finally in src/Server/ENetServer.cs
add the following code.
if (opcode == ClientPacketType.SendSomething)
{
var data = new RPacketSendSomething();
data.Read(packetReader);
ClientPacketHandleSendSomething(data, peer); // create a private static method for readability and organization
}
//...
private static void ClientPacketHandleSendSomething(RPacketSendSomething data, Peer peer)
{
Logger.Log($"Item ID: {data.itemId}");
// Optional: Send a response to the client for feedback
// You will have to create WPacketSendSomething class
var packetData = new WPacketSendSomething {
Opcode = SendSomethingOpcode.InvalidItem
};
var serverPacket = new ServerPacket((byte)ServerPacketType.SendSomethingResponse, packetData);
Send(serverPacket, peer, PacketFlags.Reliable);
}
Sending a packet from the server to the client is much like sending a packet from the client to the server, except instead of creating the packets under Client/<...>
, create them under Server/<...>
.