networking first draft, stair jumping, clip materials

This commit is contained in:
2022-05-01 21:43:33 +03:00
parent 10ff8132cb
commit 06fc68b6a1
18 changed files with 532 additions and 29 deletions

View File

@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using FlaxEngine;
using Debug = System.Diagnostics.Debug;
namespace Cabrito
{
@@ -27,9 +29,17 @@ namespace Cabrito
public static void Init()
{
if (instance != null)
return;
Destroy();
instance = new ConsoleInstance();
instance.InitConsoleSubsystems();
#if FLAX_EDITOR
FlaxEditor.ScriptsBuilder.ScriptsReload += Destroy;
FlaxEditor.Editor.Instance.StateMachine.StateChanged += OnEditorStateChanged;
#endif
}
public static void Destroy()
@@ -38,9 +48,26 @@ namespace Cabrito
{
instance.Dispose();
instance = null;
#if FLAX_EDITOR
FlaxEditor.ScriptsBuilder.ScriptsReload -= Destroy;
FlaxEditor.Editor.Instance.StateMachine.StateChanged -= OnEditorStateChanged;
#endif
}
}
#if FLAX_EDITOR
private static void OnEditorStateChanged()
{
if (!FlaxEditor.Editor.Instance.StateMachine.IsPlayMode)
{
// Clear console buffer when leaving play mode
if (instance != null)
Console.Clear();
}
}
#endif
// Returns if Console window open right now.
public static bool IsOpen => instance.IsOpen;

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using FlaxEngine;
using Console = Cabrito.Console;
@@ -13,16 +14,15 @@ namespace Game
{
public override void Initialize()
{
Debug.Log("ConsolePlugin init");
#if !FLAX_EDITOR
base.Initialize();
Debug.Log("ConsolePlugin Initialize");
Console.Init();
#else
Console.Clear();
#endif
}
public override void Deinitialize()
{
base.Deinitialize();
Debug.Log("ConsolePlugin Deinitialize");
}
@@ -33,6 +33,7 @@ namespace Game
Description = "Quake-like console",
Version = Version.Parse("0.1.0"),
IsAlpha = true,
Category = "Game",
};
}
@@ -41,7 +42,7 @@ namespace Game
{
public override void Initialize()
{
Debug.Log("ConsoleEditorPlugin init");
Debug.Log("ConsoleEditorPlugin Initialize");
Console.Init();
}

View File

@@ -190,5 +190,28 @@ namespace Cabrito
Level.FindActor("ViewModelCamera").IsActive = boolValue;
}
}
// Horizontal field of view of the Camera
[ConsoleVariable("fov")]
public static string CameraFov
{
get
{
float valueFloat = Level.FindActor("PlayerCamera").As<Camera>().FieldOfView;
float horizontalFov = (float)(180.0f / Math.PI * (2*Math.Atan((4f/3f) * Math.Tan(Math.PI / 180.0f * valueFloat / 2.0f))));
return horizontalFov.ToString();
}
set
{
if (float.TryParse(value, out float valueFloat))
{
valueFloat = Mathf.Clamp(valueFloat, 0.01f, 360.0f);
float verticalFov = (float)(180.0f / Math.PI * (2*Math.Atan((3f/4f) * Math.Tan(Math.PI / 180.0f * valueFloat / 2.0f))));
Level.FindActor("PlayerCamera").As<Camera>().FieldOfView = verticalFov;
}
}
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
using FlaxEngine;
namespace Game
{
public static class Utilities
{
public class ScopeProfiler : IDisposable
{
public ScopeProfiler(string eventName)
{
Profiler.BeginEvent(eventName);
}
public void Dispose()
{
Profiler.EndEvent();
}
}
public static ScopeProfiler ProfileScope(string eventName)
{
return new ScopeProfiler(eventName);
}
}
}

View File

@@ -0,0 +1,94 @@
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using FlaxEditor;
using FlaxEngine;
using FlaxEngine.Networking;
using Console = Cabrito.Console;
using Debug = FlaxEngine.Debug;
namespace Game
{
[AttributeUsage(AttributeTargets.Class)]
public class NetworkPredictedAttribute : Attribute
{
public NetworkPredictedAttribute()
{
}
}
// TODO: insert code to update variables with this attribute?
// rename to NetworkReplicatedAttribute?
[AttributeUsage(AttributeTargets.Class)]
public class NetworkedAttribute : Attribute
{
public NetworkedAttribute()
{
}
}
// NetworkMulticastAttribute: calls methods marked with this in all clients
public static partial class NetworkManager
{
private static bool initialized = false;
private static NetworkPeer server;
private static NetworkPeer client;
private static ushort ServerPort = 59183;
private static string ServerAddress = null;
private static ushort MTU = 1500;
private static ushort MaximumClients = 32;
public static uint LocalPlayerClientId { get; private set; } = 0;
public static void Init()
{
if (initialized)
return;
if (Engine.CommandLine.Contains("-server"))
{
StartServer();
ServerAddress = "localhost";
ConnectServer();
}
else if (Engine.CommandLine.Contains("-client"))
{
ServerAddress = "localhost";
ConnectServer();
}
#if FLAX_EDITOR
else
{
StartServer();
}
#endif
initialized = true;
}
public static void Deinitialize()
{
if (server != null)
{
Scripting.FixedUpdate -= OnServerUpdate;
Scripting.Exit -= Deinitialize;
Level.ActorSpawned -= OnServerActorSpawned;
NetworkPeer.ShutdownPeer(server);
server = null;
}
if (client != null)
{
Scripting.FixedUpdate -= OnClientUpdate;
Scripting.Exit -= Deinitialize;
Level.ActorSpawned -= OnClientActorSpawned;
NetworkPeer.ShutdownPeer(client);
client = null;
}
}
}
}

View File

@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using FlaxEngine;
using Console = Cabrito.Console;
#if FLAX_EDITOR
using FlaxEditor;
#endif
namespace Game
{
public class NetworkManagerPlugin : GamePlugin
{
public override void Initialize()
{
base.Initialize();
Debug.Log("NetworkManagerPlugin Initialize");
Console.Init();
NetworkManager.Init();
Level.SceneLoaded += (scene, guid) => Console.Print("scene loaded: " + scene.Name);
}
public override void Deinitialize()
{
base.Deinitialize();
Debug.Log("NetworkManagerPlugin Deinitialize");
}
public static PluginDescription DescriptionInternal = new PluginDescription()
{
Author = "Ari Vuollet",
Name = "NetworkManager",
Description = "NetworkManager for Goake",
Version = Version.Parse("0.1.0"),
IsAlpha = true,
Category = "Game",
};
}
#if FLAX_EDITOR
public class NetworkManagerEditorPlugin : EditorPlugin
{
public override void Initialize()
{
Debug.Log("NetworkManagerEditorPlugin Initialize");
Console.Init();
}
public override void Deinitialize()
{
Debug.Log("NetworkManagerEditorPlugin Deinitialize");
}
public override PluginDescription Description => NetworkManagerPlugin.DescriptionInternal;
public override Type GamePluginType => typeof(NetworkManagerPlugin);
}
#endif
}

View File

@@ -0,0 +1,93 @@
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using FlaxEngine;
using FlaxEngine.Networking;
using Console = Cabrito.Console;
using Debug = FlaxEngine.Debug;
namespace Game
{
public static partial class NetworkManager
{
public static bool ConnectServer()
{
client = NetworkPeer.CreatePeer(new NetworkConfig
{
NetworkDriver = FlaxEngine.Object.New(typeof(ENetDriver)),
ConnectionsLimit = MaximumClients,
MessagePoolSize = 2048,
MessageSize = MTU,
Address = ServerAddress == "localhost" ? "127.0.0.1" : ServerAddress,
Port = ServerPort,
});
if (!client.Connect())
{
Console.PrintError("Failed to connect to the server.");
return false;
}
Scripting.FixedUpdate += OnClientUpdate;
Scripting.Exit += Deinitialize;
Level.ActorSpawned += OnClientActorSpawned;
return true;
}
private static void OnClientUpdate()
{
using var _ = Utilities.ProfileScope("NetworkManager_OnClientUpdate");
while (client.PopEvent(out NetworkEvent eventData))
{
switch (eventData.EventType)
{
case NetworkEventType.Connected:
{
LocalPlayerClientId = eventData.Sender.ConnectionId;
Console.Print("Connected to server, ConnectionId: " + eventData.Sender.ConnectionId);
break;
}
case NetworkEventType.Disconnected:
{
Console.Print("Disconnected from server, timeout.");
LocalPlayerClientId = 0;
break;
}
case NetworkEventType.Timeout:
{
Console.Print("Disconnected from server, connection closed.");
LocalPlayerClientId = 0;
break;
}
case NetworkEventType.Message:
{
// Read the message contents
var message = eventData.Message;
var messageData = message.ReadString();
Console.Print($"Received message from Client({eventData.Sender.ConnectionId}): {messageData}");
// Send hello message to the client back
{
var sendmessage = client.BeginSendMessage();
sendmessage.WriteString($"Hello, Server({eventData.Sender.ConnectionId})!");
client.EndSendMessage(NetworkChannelType.Reliable, sendmessage);
}
client.RecycleMessage(message);
break;
}
default:
throw new ArgumentOutOfRangeException();
}
}
}
private static void OnClientActorSpawned(Actor actor)
{
}
}
}

View File

@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using FlaxEngine;
using FlaxEngine.Networking;
using Console = Cabrito.Console;
using Debug = FlaxEngine.Debug;
namespace Game
{
public static partial class NetworkManager
{
private static List<NetworkConnection> ConnectedClients;
private static List<Type> NetworkedTypes;
public static bool StartServer()
{
ConnectedClients = new List<NetworkConnection>(MaximumClients);
server = NetworkPeer.CreatePeer(new NetworkConfig
{
NetworkDriver = FlaxEngine.Object.New(typeof(ENetDriver)),
ConnectionsLimit = MaximumClients,
MessagePoolSize = 2048,
MessageSize = MTU,
Address = "any",
Port = ServerPort
});
if (!server.Listen())
{
Console.PrintError("Failed to start the server.");
return false;
}
Scripting.FixedUpdate += OnServerUpdate;
Scripting.Exit += Deinitialize;
Level.ActorSpawned += OnClientActorSpawned;
NetworkedTypes = new List<Type>();
AppDomain currentDomain = AppDomain.CurrentDomain;
Assembly[] assemblies = currentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
// Skip common assemblies
var assemblyName = assembly.GetName().Name;
if (assemblyName == "System" ||
assemblyName.StartsWith("System.") ||
assemblyName.StartsWith("Mono.") ||
assemblyName == "mscorlib" ||
assemblyName == "Newtonsoft.Json" ||
assemblyName.StartsWith("FlaxEngine.") ||
assemblyName.StartsWith("JetBrains.") ||
assemblyName.StartsWith("Microsoft.") ||
assemblyName.StartsWith("nunit."))
{
continue;
}
foreach (var type in assembly.GetTypes())
{
if (type.GetCustomAttributes().Any(x => x is NetworkedAttribute))
NetworkedTypes.Add(type);
}
}
foreach (Type type in NetworkedTypes)
{
Console.Print("tracking networked type: " + type.Name);
}
return true;
}
private static void OnServerUpdate()
{
using var _ = Utilities.ProfileScope("NetworkManager_OnServerUpdate");
while (server.PopEvent(out NetworkEvent eventData))
{
switch (eventData.EventType)
{
case NetworkEventType.Connected:
{
Console.Print($"Client({eventData.Sender.ConnectionId}) connected!");
ConnectedClients.Add(eventData.Sender);
Console.Print("Connected clients: " + ConnectedClients.Count);
// Send hello message to the client back
{
var sendmessage = server.BeginSendMessage();
sendmessage.WriteString($"Welcome, ({eventData.Sender.ConnectionId})");
server.EndSendMessage(NetworkChannelType.Reliable, sendmessage, eventData.Sender);
}
break;
}
case NetworkEventType.Disconnected:
case NetworkEventType.Timeout:
{
Console.Print($"Client({eventData.Sender.ConnectionId}) disconnected!");
ConnectedClients.Remove(eventData.Sender);
Console.Print("Connected clients: " + ConnectedClients.Count);
break;
}
case NetworkEventType.Message:
{
// Read the message contents
var message = eventData.Message;
var messageData = message.ReadString();
Console.Print($"Received message from Client({eventData.Sender.ConnectionId}): {messageData}");
server.RecycleMessage(message);
break;
}
default:
throw new ArgumentOutOfRangeException();
}
}
}
private static void OnServerActorSpawned(Actor actor)
{
Console.Print($"actor spawned: {actor.Name} ({actor.TypeName})");
}
}
}

View File

@@ -26,6 +26,7 @@ namespace Game
//public Vector3 maxEndPosition;
}
[Networked]
public class PlayerMovement : Script
{
[Limit(0, 9000), Tooltip("Base Movement speed")]
@@ -617,7 +618,8 @@ namespace Game
return slideMoveHit;
}
return slideMoveStepHit;
//return slideMoveStepHit;
return slideMoveHit;
}
[Flags]
@@ -792,6 +794,7 @@ namespace Game
private const float jumpBoostTime = 0.5f;
private const float jumpBoostVelocity = 100f;
private const int jumpBoostMaxJumps = 2;
private const bool jumpStairBehavior = true;
private const float maxAirSpeed = 320f;
private const float maxAirStrafeSpeed = 30f;
@@ -867,9 +870,8 @@ namespace Game
// jump
if (onGround && jumpAction && !jumped)
{
if (OnJump(traceGround, ref velocity))
if (OnJump(traceGround, ref position, ref velocity))
{
onGround = false;
jumped = true;
lastJumped = Time.GameTime;
numJumps++;
@@ -966,19 +968,17 @@ namespace Game
return traceGround;
}
private bool OnJump(TraceInfo traceGround, ref Vector3 velocity)
private bool OnJump(TraceInfo traceGround, ref Vector3 position, ref Vector3 velocity)
{
float jumpVel = jumpVelocity;
if (Time.GameTime - lastJumped < jumpBoostTime)
jumpVel += jumpBoostVelocity;
// reset velocity from gravity
// Reset velocity from gravity
if (-Vector3.Dot(Gravity.Normalized, velocity) < 0 &&
Vector3.Dot(velocity, traceGround.hitNormal) < -0.1)
{
Console.Print("vel before: " + velocity.Y);
velocity = Vector3.ProjectOnPlane(velocity, traceGround.hitNormal);
Console.Print("vel after: " + velocity.Y);
}
velocity += Vector3.Up * jumpVel;
@@ -990,7 +990,25 @@ namespace Game
velocity.Y = jumpVel;
}
Console.Print("jumpvel: " + velocity.Y);
onGround = false;
// Allow stairs to eat the first jump to allow easy stair jumps
if (jumpStairBehavior && jumpBoostMaxJumps >= 2 && numJumps == 0 && -Vector3.Dot(Gravity.Normalized, traceGround.hitNormal) > 0.85)
{
// Try stepping into stairs without vertical velocity
Vector3 stairCheckPosition = position;
Vector3 stairCheckVelocity = velocity.Normalized * (stepHeight / Time.DeltaTime);
stairCheckVelocity.Y = 0f;
SlideMoveHit blocked = StepSlideMove(Actor, ref stairCheckPosition, ref stairCheckVelocity, true);
float movedUp = stairCheckPosition.Y - position.Y;
if (movedUp > 0 && blocked.HasFlag(SlideMoveHit.Step))
{
velocity.Y = 0f;
onGround = true;
}
}
if (!predicting)
{

View File

@@ -191,7 +191,7 @@ namespace Game
vertices = new Vector3[0];
}
#if FLAX_EDITOR
#if false//FLAX_EDITOR
[OnSerializing]
internal void OnSerializing(StreamingContext context)
{
@@ -248,7 +248,7 @@ namespace Game
ScriptsBuilder.ScriptsReloadEnd += onScriptsReloadEnd;
#endif
Debug.Log("LoadMap");
//Debug.Log("LoadMap");
LoadMap(false);
}
@@ -280,7 +280,7 @@ namespace Game
root = MapParser.Parse(mapChars);
sw.Stop();
Console.Print("Map parsing time: " + sw.Elapsed.TotalMilliseconds + "ms");
//Console.Print("Map parsing time: " + sw.Elapsed.TotalMilliseconds + "ms");
bool oneMesh = false;
bool convexMesh = true;
@@ -303,7 +303,7 @@ namespace Game
worldSpawnActor = Actor.AddChild<Actor>();
worldSpawnActor.Name = "WorldSpawn";
worldSpawnActor.HideFlags |= HideFlags.DontSave;
worldSpawnActor.HideFlags |= HideFlags.DontSelect;
//worldSpawnActor.HideFlags |= HideFlags.DontSelect;
}
List<BrushGeometry> brushGeometries = new List<BrushGeometry>(root.entities[0].brushes.Count);
@@ -351,7 +351,7 @@ namespace Game
else
{
// TODO: engine doesn't seem to always load the asset even though it exists, bug? seems to happen at low framerate
Console.Print("Material '" + textureName + "' not found for brush, assetPath: " + assetPath);
//Console.Print("Material '" + textureName + "' not found for brush, assetPath: " + assetPath);
materials.Add(textureName, missingMaterial);
brushMaterial = missingMaterial;
}
@@ -374,7 +374,7 @@ namespace Game
brushIndex++;
}
sw.Stop();
Console.Print("Pass 1: triangulation: " + sw.Elapsed.TotalMilliseconds + "ms");
//Console.Print("Pass 1: triangulation: " + sw.Elapsed.TotalMilliseconds + "ms");
// pass 2: texturing
brushIndex = 0;
@@ -494,10 +494,11 @@ namespace Game
geom.model.LODs[0].Meshes[i].MaterialSlotIndex = i;
geom.model.MaterialSlots[i].Material = geom.meshes[i].material;
}
brushIndex++;
}
sw.Stop();
Console.Print("Pass 2: texturing: " + sw.Elapsed.TotalMilliseconds + "ms");
//Console.Print("Pass 2: texturing: " + sw.Elapsed.TotalMilliseconds + "ms");
// pass 3: collision
sw.Restart();
@@ -516,6 +517,17 @@ namespace Game
for (uint i = 0; i < indices.Length; i++)
indices[i] = i;
if (geom.meshes.Length == 1)
{
var info = geom.meshes[0].material.GetParameter("IsClipMaterial");
if (info != null && (bool)info.Value)
{
var entries = childModel.Entries;
entries[0].Visible = false;
childModel.Entries = entries;
}
}
CollisionData collisionData = Content.CreateVirtualAsset<CollisionData>();
if (collisionData.CookCollision(convexMesh ? CollisionDataType.ConvexMesh : CollisionDataType.TriangleMesh, geom.vertices,
indices))
@@ -539,7 +551,7 @@ namespace Game
brushIndex++;
}
sw.Stop();
Console.Print("Pass 3: collision: " + sw.Elapsed.TotalMilliseconds + "ms");
//Console.Print("Pass 3: collision: " + sw.Elapsed.TotalMilliseconds + "ms");
}
else
{
@@ -699,11 +711,11 @@ namespace Game
return new Vector3(float.Parse(points[0]), float.Parse(points[2]), float.Parse(points[1]));
}
/*Color ParseColor(string origin)
Color ParseColor(string origin)
{
string[] points = origin.Split(new char[] { ' ' });
return new Color(float.Parse(points[0]), float.Parse(points[1]), float.Parse(points[2]));
}*/
}
//Console.Print("light");
PointLight light = worldSpawnActor.AddChild<PointLight>();
@@ -715,7 +727,7 @@ namespace Game
if (lightEntity.properties.TryGetValue("_color", out string colorStr))
{
//light.Color = ParseColor(colorStr);
light.Color = ParseColor(colorStr);
}
float lightamm = 200f;