Multiplayer Lobby (C#)
▸A quick start guide for creating a multiplayer lobby using Microsoft.AspNetCore.SignalR
. The sample shows host discovery, player join/leave events, and chat messaging.
using Microsoft.AspNetCore.SignalR;
using System.Collections.Concurrent;
public class LobbyHub : Hub
{
private static readonly ConcurrentDictionary<string, string> Users = new();
public async Task Join(string playerName)
{
Users[Context.ConnectionId] = playerName;
await Clients.All.SendAsync("PlayerJoined", playerName);
}
public async Task SendMessage(string message)
{
var name = Users[Context.ConnectionId];
await Clients.All.SendAsync("ReceiveMessage", name, message);
}
public override async Task OnDisconnectedAsync(Exception? ex)
{
if (Users.TryRemove(Context.ConnectionId, out var name))
{
await Clients.All.SendAsync("PlayerLeft", name);
}
await base.OnDisconnectedAsync(ex);
}
}
Run the project, navigate to /lobby
, and open multiple browsers to test real‑time interaction.
Physics Engine Integration (C# + F#)
▸Demonstrates binding the Unity Physics engine to a .NET Core backend using F# for immutable data structures.
open Microsoft.FSharp.Collections
open UnityEngine
type Entity = {
Id: Guid
Position: Vector3
Velocity: Vector3
}
let update (dt:float) (entities:Map<Guid,Entity>) =
entities
|> Map.map (fun _ e ->
let newPos = e.Position + e.Velocity * float32 dt
{ e with Position = newPos })
Integrate with the server via gRPC to sync game state across clients.
AI Patrol Bot (C#)
▸A simple state‑machine AI that patrols between waypoints and chases the player when in sight.
using System.Collections.Generic;
using UnityEngine;
public class PatrolBot : MonoBehaviour
{
public List<Transform> Waypoints;
public float Speed = 3f;
public float ChaseRange = 10f;
private int _current = 0;
private Transform _player;
void Start() => _player = GameObject.FindWithTag("Player").transform;
void Update()
{
var target = Vector3.Distance(transform.position, _player.position) < ChaseRange
? _player
: Waypoints[_current];
var step = Speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, target.position, step);
if (Vector3.Distance(transform.position, target.position) < 0.1f && target != _player)
_current = (_current + 1) % Waypoints.Count;
}
}
Attach the script to an NPC GameObject and assign waypoints in the inspector.
Procedural Terrain (C#)
▸Generates height‑maps using Perlin noise and creates a mesh at runtime.
using UnityEngine;
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class TerrainGenerator : MonoBehaviour
{
public int Size = 256;
public float Scale = 20f;
public int Octaves = 4;
public float Persistence = 0.5f;
public float Lacunarity = 2f;
void Start() => Generate();
void Generate()
{
var mesh = new Mesh();
var verts = new Vector3[Size * Size];
var uvs = new Vector2[verts.Length];
var tris = new int[(Size - 1) * (Size - 1) * 6];
for (int y = 0; y < Size; y++)
for (int x = 0; x < Size; x++)
{
float height = 0;
float amplitude = 1;
float frequency = 1;
for (int i = 0; i < Octaves; i++)
{
float sampleX = x / Scale * frequency;
float sampleY = y / Scale * frequency;
height += Mathf.PerlinNoise(sampleX, sampleY) * amplitude;
amplitude *= Persistence;
frequency *= Lacunarity;
}
verts[y * Size + x] = new Vector3(x, height * 10f, y);
uvs[y * Size + x] = new Vector2((float)x / Size, (float)y / Size);
}
int triIndex = 0;
for (int y = 0; y < Size - 1; y++)
for (int x = 0; x < Size - 1; x++)
{
int i = y * Size + x;
tris[triIndex++] = i;
tris[triIndex++] = i + Size;
tris[triIndex++] = i + 1;
tris[triIndex++] = i + 1;
tris[triIndex++] = i + Size;
tris[triIndex++] = i + Size + 1;
}
mesh.vertices = verts;
mesh.triangles = tris;
mesh.uv = uvs;
mesh.RecalculateNormals();
GetComponent<MeshFilter>().mesh = mesh;
}
}
Drag the component onto an empty GameObject and hit Play to see the terrain generated.
Save/Load System (C#)
▸Provides asynchronous JSON serialization for game state using System.Text.Json
and FileStream
.
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
public class GameState
{
public int Level { get; set; }
public float PlayerHealth { get; set; }
public Vector3 PlayerPosition { get; set; }
// Add more fields as needed
}
public static class SaveLoad
{
private static readonly string Path = System.Environment.GetFolderPath(
System.Environment.SpecialFolder.ApplicationData) + "/MyGame/state.json";
public static async Task SaveAsync(GameState state)
{
var json = JsonSerializer.Serialize(state, new JsonSerializerOptions { WriteIndented = true });
await File.WriteAllTextAsync(Path, json);
}
public static async Task<GameState?> LoadAsync()
{
if (!File.Exists(Path)) return null;
var json = await File.ReadAllTextAsync(Path);
return JsonSerializer.Deserialize<GameState>(json);
}
}
Call await SaveLoad.SaveAsync(currentState)
before exiting and load on startup with await SaveLoad.LoadAsync()
.