Last active
October 1, 2020 22:16
-
-
Save James-Frowen/fb65f82bb242f1208e431e9393659a9f to your computer and use it in GitHub Desktop.
Working, but unfinished Projectile Manager for the Wolves games, built with Mirror
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Mirror; | |
using ProjectWolves.Networking; | |
using UnityEngine; | |
namespace ProjectWolves.Weapons | |
{ | |
public class NetworkProjectile : MonoBehaviour | |
{ | |
public uint ProjectileId { get; set; } | |
public uint LocalId { get; set; } | |
private Rigidbody _rigidBody; | |
public ProjectileBehaviour projectileBehaviour; | |
private ServerState _lastGoal; | |
private ServerState _currentGoal; | |
private void Awake() | |
{ | |
this._rigidBody = this.GetComponent<Rigidbody>(); | |
this.projectileBehaviour = this.GetComponent<ProjectileBehaviour>(); | |
} | |
public void ApplyState(ProjectileState state, float serverTime) | |
{ | |
Debug.Assert(state.projectileId == this.ProjectileId, "Wrong projectile id"); | |
//this._rigidBody.position = state.position; | |
//this._rigidBody.rotation = state.rotation; | |
//this._rigidBody.velocity = state.velocity; | |
var nextGoal = new ServerState | |
{ | |
position = state.position, | |
rotation = state.rotation, | |
velocity = state.velocity, | |
timeStamp = serverTime, | |
}; | |
// is this first message? | |
if (this._currentGoal.timeStamp == 0) | |
{ | |
var goal = new ServerState(); | |
goal.position = this._rigidBody.position; | |
goal.rotation = this._rigidBody.rotation; | |
goal.velocity = this._rigidBody.velocity; | |
goal.timeStamp = (float)NetworkTime.time - (1f / WolvesNetworkManager.Instance.serverTickRate); | |
this._lastGoal = goal; | |
} | |
else | |
{ | |
this._lastGoal = this._currentGoal; | |
this._currentGoal = nextGoal; | |
} | |
// client disables self if it collides | |
// if server didnt collide then it will send another sync package | |
// so re-enable projectile | |
if (!this.gameObject.activeSelf) | |
{ | |
this.gameObject.SetActive(true); | |
} | |
} | |
public ProjectileState GetProjectileState() | |
{ | |
return new ProjectileState | |
{ | |
projectileId = this.ProjectileId, | |
position = this._rigidBody.position, | |
rotation = this._rigidBody.rotation, | |
velocity = this._rigidBody.velocity, | |
}; | |
} | |
public void SendDestroy() | |
{ | |
NetworkProjectileManager.DestroyProjectile(this); | |
} | |
public void SendRpcHitEffect(HitData data) | |
{ | |
NetworkProjectileManager.SendRpcHitEffect(this, data); | |
} | |
public void CallRpcHitEffect(HitData data) | |
{ | |
this.projectileBehaviour.RpcHitEffect(data); | |
} | |
private void FixedUpdate() | |
{ | |
// server doesnt need to apply sync | |
if (NetworkServer.active) { return; } | |
// has not recieved data from server yet? | |
if (this._currentGoal.timeStamp == 0) { return; } | |
this.interpolatePosition(); | |
this.interpolateRotation(); | |
this.interpolateVelocity(); | |
} | |
private void interpolateVelocity() | |
{ | |
var msgDelta = this._currentGoal.timeStamp - this._lastGoal.timeStamp; | |
var localDelta = Time.fixedDeltaTime; | |
if (msgDelta == 0 || localDelta == 0) { return; } | |
var timeSinceMsg = (float)(NetworkTime.time - this._currentGoal.timeStamp); | |
var localTicksSinceMsg = timeSinceMsg / localDelta; | |
var msgAcceleration = (this._currentGoal.velocity - this._lastGoal.velocity) / msgDelta; | |
var deltaVel = msgAcceleration * localTicksSinceMsg; | |
var localVel = this._currentGoal.velocity + deltaVel; | |
this._rigidBody.velocity = localVel; | |
} | |
private void interpolatePosition() | |
{ | |
var msgDelta = this._currentGoal.timeStamp - this._lastGoal.timeStamp; | |
var localDelta = Time.fixedDeltaTime; | |
if (msgDelta == 0 || localDelta == 0) { return; } | |
var timeSinceMsg = (float)(NetworkTime.time - this._currentGoal.timeStamp); | |
var localTicksSinceMsg = timeSinceMsg / localDelta; | |
var msgSpeed = (this._currentGoal.position - this._lastGoal.position) / msgDelta; | |
var posOneTickDelta = this._currentGoal.velocity + msgSpeed; | |
var localPos = Vector3.Lerp(this._currentGoal.velocity, posOneTickDelta, localTicksSinceMsg); | |
this._rigidBody.position = localPos; | |
} | |
private void interpolateRotation() | |
{ | |
var msgDelta = this._currentGoal.timeStamp - this._lastGoal.timeStamp; | |
var localDelta = Time.fixedDeltaTime; | |
if (msgDelta == 0 || localDelta == 0) { return; } | |
var timeSinceMsg = (float)(NetworkTime.time - this._currentGoal.timeStamp); | |
var localTicksSinceMsg = timeSinceMsg / localDelta; | |
var localRot = Quaternion.Slerp(this._lastGoal.rotation, this._currentGoal.rotation, localTicksSinceMsg); | |
this._rigidBody.rotation = localRot; | |
} | |
private struct ServerState | |
{ | |
public float timeStamp; | |
public Vector3 position; | |
public Quaternion rotation; | |
public Vector3 velocity; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using JamesFrowen; | |
using Mirror; | |
using ProjectWolves.Networking; | |
using System.Collections.Generic; | |
using UnityEngine; | |
namespace ProjectWolves.Weapons | |
{ | |
public static class NetworkProjectileManager | |
{ | |
static Dictionary<uint, NetworkProjectile> _local; | |
static Dictionary<uint, NetworkProjectile> _all; | |
// start at 1 so that 0 can be send for no local | |
private static uint _nextLocalIndex = 1; | |
private static uint _nextAllIndex = 1; | |
private static bool clientRegistered; | |
private static bool serverRegistered; | |
// shooters spawns projectiles | |
// shooter gives it local id | |
// sends server projectileinfo and local id | |
// Server spawns projectiles | |
// Server sends message to nonshooters to spawn projectile | |
// Server Sends link message with local id to shooter | |
// shooters links projectile | |
public static void Init() | |
{ | |
WolvesLog.ProjectileLog("NetworkProjectileManager.init()"); | |
_all = new Dictionary<uint, NetworkProjectile>(); | |
_nextAllIndex = 1; | |
if (NetworkServer.active) | |
{ | |
RegisterServerMessages(); | |
} | |
if (NetworkClient.active) | |
{ | |
RegisterClientMessages(); | |
} | |
} | |
internal static void ShutDown() | |
{ | |
WolvesLog.ProjectileLog("NetworkProjectileManager.shutDown()"); | |
_all.Clear(); | |
_all = null; | |
if (NetworkServer.active) | |
{ | |
UnregisterServerMessages(); | |
} | |
if (NetworkClient.active) | |
{ | |
UnregisterClientMessages(); | |
} | |
} | |
public static void RegisterServerMessages() | |
{ | |
if (serverRegistered) { return; } | |
NetworkServer.RegisterHandler<SpawnProjectileMessage>(serverHandleSpawnProjectileMessage); | |
serverRegistered = true; | |
} | |
public static void RegisterClientMessages() | |
{ | |
if (clientRegistered) { return; } | |
_local = new Dictionary<uint, NetworkProjectile>(); | |
_nextLocalIndex = 1; | |
NetworkClient.RegisterHandler<SpawnProjectileMessage>(clientHandleSpawnProjectileMessage); | |
NetworkClient.RegisterHandler<LinkProjectileMessage>(handleLinkProjectileMessage); | |
NetworkClient.RegisterHandler<SyncProjectilesMessage>(handleSyncProjectilesMessage); | |
NetworkClient.RegisterHandler<DestroyProjectileMessage>(handleDestroyProjectileMessage); | |
NetworkClient.RegisterHandler<RpcHitEffectMessage>(HandleRpcHitEffect); | |
clientRegistered = true; | |
} | |
public static void UnregisterServerMessages() | |
{ | |
if (!serverRegistered) { return; } | |
NetworkServer.UnregisterHandler<SpawnProjectileMessage>(); | |
serverRegistered = false; | |
} | |
public static void UnregisterClientMessages() | |
{ | |
if (!clientRegistered) { return; } | |
_local.Clear(); | |
_local = null; | |
NetworkClient.UnregisterHandler<SpawnProjectileMessage>(); | |
NetworkClient.UnregisterHandler<LinkProjectileMessage>(); | |
NetworkClient.UnregisterHandler<SyncProjectilesMessage>(); | |
NetworkClient.UnregisterHandler<DestroyProjectileMessage>(); | |
NetworkClient.UnregisterHandler<RpcHitEffectMessage>(); | |
clientRegistered = false; | |
} | |
public static void Update() | |
{ | |
if (!NetworkServer.active) { Debug.LogWarning("NetworkProjectileManager.Update called on client"); return; } | |
// dont sync if nothing | |
if (_all.Count == 0) { return; } | |
var i = 0; | |
foreach (var item in _all.Values) | |
{ | |
var state = item.GetProjectileState(); | |
SyncProjectilesMessage.buffer[i] = state; | |
i++; | |
} | |
var msg = new SyncProjectilesMessage | |
{ | |
count = i, | |
serverTime = (float)NetworkTime.time, | |
}; | |
NetworkServer.SendToAll(msg); | |
} | |
public static void ClearProjectiles() | |
{ | |
_all.Clear(); | |
} | |
internal static void SendRpcHitEffect(NetworkProjectile networkProjectile, HitData data) | |
{ | |
var msg = new RpcHitEffectMessage | |
{ | |
projectileId = networkProjectile.ProjectileId, | |
hitData = data | |
}; | |
NetworkServer.SendToAll(msg); | |
} | |
internal static void HandleRpcHitEffect(NetworkConnection conn, RpcHitEffectMessage message) | |
{ | |
if (NetworkServer.active) { return; } // host | |
if (_all.TryGetValue(message.projectileId, out var projectile)) | |
{ | |
projectile.CallRpcHitEffect(message.hitData); | |
} | |
else | |
{ | |
if (WolvesLog.Projectile) { WolvesLog.ProjectileWarn("Projectile with id {0} not in all", message.projectileId); } | |
} | |
} | |
private static void handleLinkProjectileMessage(NetworkConnection conn, LinkProjectileMessage message) | |
{ | |
Debug.Assert(!NetworkServer.active, "handleLinkProjectileMessages should not be called on server"); | |
var local = _local[message.localId]; | |
_local.Remove(message.localId); | |
local.ProjectileId = message.projectileId; | |
_all.Add(message.projectileId, local); | |
} | |
private static void clientHandleSpawnProjectileMessage(NetworkConnection conn, SpawnProjectileMessage message) | |
{ | |
Debug.Assert(!NetworkServer.active, "clientHandleSpawnProjectileMessage should not be called on server"); | |
Debug.Assert(message.projectileId != 0, "SpawnProjectileMessage should bot have projectileId = 0 in clientHandleSpawnProjectileMessage"); | |
// get projectile type | |
var playerIdentity = NetworkIdentity.spawned[message.ownerNetId]; | |
var weaponHolder = playerIdentity.GetComponent<WeaponHolder>(); | |
var activeWeapon = weaponHolder.ActiveWeapon; | |
if (activeWeapon.FireType is ProjectileFireType projectileFireType) | |
{ | |
var projectileType = projectileFireType.ProjectileType; | |
// spawn projectile | |
var projectile = projectileType.RemoteClientSpawnProjectile(message, activeWeapon.Weapon, weaponHolder.Core); | |
// add to all | |
var networkProjectile = projectile.GetComponent<NetworkProjectile>(); | |
networkProjectile.ProjectileId = message.projectileId; | |
_all.Add(message.projectileId, networkProjectile); | |
} | |
else | |
{ | |
GameLog.LogError("Active weapon firetype was not a ProjectileFireType"); | |
} | |
} | |
private static void serverHandleSpawnProjectileMessage(NetworkConnection conn, SpawnProjectileMessage message) | |
{ | |
Debug.Assert(NetworkServer.active, "serverHandleSpawnProjectileMessage should not be called on client"); | |
// get projectile type | |
var playerIdentity = NetworkIdentity.spawned[message.ownerNetId]; | |
Debug.Assert(playerIdentity == conn.identity, "OwnerNetId should be the conn.identity"); | |
var weaponHolder = playerIdentity.GetComponent<WeaponHolder>(); | |
var activeWeapon = weaponHolder.ActiveWeapon; | |
if (activeWeapon.FireType is ProjectileFireType projectileFireType) | |
{ | |
var projectileType = projectileFireType.ProjectileType; | |
// spawn projectile | |
var clone = projectileType.ServerSpawnProjectile(message, activeWeapon.Weapon, weaponHolder.Core, conn); | |
// call ServerSpawn | |
ServerSpawn(message, clone, conn); | |
} | |
else | |
{ | |
GameLog.LogError("Active weapon firetype was not a ProjectileFireType"); | |
} | |
} | |
private static void handleSyncProjectilesMessage(NetworkConnection conn, SyncProjectilesMessage message) | |
{ | |
if (NetworkServer.active) { return; } // host | |
for (var i = 0; i < message.count; i++) | |
{ | |
var state = SyncProjectilesMessage.buffer[i]; | |
if (_all.TryGetValue(state.projectileId, out var projectile)) | |
{ | |
projectile.ApplyState(state, message.serverTime); | |
} | |
else | |
{ | |
if (WolvesLog.Projectile) { WolvesLog.ProjectileWarn("Projectile with id {0} not in all", state.projectileId); } | |
} | |
} | |
} | |
//[Client] | |
private static void handleDestroyProjectileMessage(NetworkConnection conn, DestroyProjectileMessage message) | |
{ | |
if (NetworkServer.active) { return; } // host | |
var projectile = _all[message.projectileId]; | |
_all.Remove(message.projectileId); | |
projectile.projectileBehaviour.HandleDestroy(); | |
} | |
public static void DestroyProjectile(NetworkProjectile networkProjectile) | |
{ | |
var msg = new DestroyProjectileMessage | |
{ | |
projectileId = networkProjectile.ProjectileId | |
}; | |
NetworkServer.SendToAll(msg); | |
if (NetworkServer.active) // server and host | |
{ | |
_all.Remove(networkProjectile.ProjectileId); | |
} | |
} | |
public static void ServerSpawn(SpawnProjectileMessage message, GameObject gameObject, NetworkConnection spawner) | |
{ | |
var networkProjectile = gameObject.GetComponent<NetworkProjectile>(); | |
if (networkProjectile == null) | |
{ | |
Debug.LogError($"{nameof(NetworkProjectileManager)}.{nameof(ServerSpawn)} needs an object with NetworkProjectile"); | |
} | |
ServerSpawn(message, networkProjectile, spawner); | |
} | |
public static void ServerSpawn(SpawnProjectileMessage message, NetworkProjectile projectile, NetworkConnection spawner) | |
{ | |
// give id | |
// add to all | |
projectile.ProjectileId = _nextAllIndex++; | |
_all.Add(projectile.ProjectileId, projectile); | |
// send message to clients | |
message.projectileId = projectile.ProjectileId; | |
foreach (var conn in NetworkServer.connections.Values) | |
{ | |
if (conn == spawner) | |
{ | |
var linkMsg = new LinkProjectileMessage | |
{ | |
localId = message.localId, | |
projectileId = message.projectileId, | |
}; | |
conn.Send(linkMsg); | |
} | |
// dont send to host's client | |
else if (!isLocalConnection(conn)) | |
{ | |
Debug.Assert(message.projectileId != 0, "SpawnProjectileMessage should bot have projectileId = 0 in ServerSpawn"); | |
conn.Send(message); | |
} | |
} | |
} | |
private static bool isLocalConnection(NetworkConnection conn) | |
{ | |
return NetworkClient.active && NetworkClient.connection.connectionId == conn.connectionId; | |
} | |
public static void ClientSpawn(SpawnProjectileMessage message, GameObject gameObject) | |
{ | |
var networkProjectile = gameObject.GetComponent<NetworkProjectile>(); | |
if (networkProjectile == null) | |
{ | |
Debug.LogError($"{nameof(NetworkProjectileManager)}.{nameof(ClientSpawn)} needs an object with NetworkProjectile"); | |
} | |
ClientSpawn(message, networkProjectile); | |
} | |
public static void ClientSpawn(SpawnProjectileMessage message, NetworkProjectile projectile) | |
{ | |
// gives it local id | |
projectile.LocalId = _nextLocalIndex++; | |
_local.Add(projectile.LocalId, projectile); | |
// sends server projectileinfo and local id | |
message.localId = projectile.LocalId; | |
message.expectedRtt = (float)NetworkTime.rtt; | |
NetworkClient.Send(message); | |
} | |
} | |
public struct Charge | |
{ | |
public bool hasValue; | |
public float value; | |
public static implicit operator Charge(float? nullable) | |
{ | |
return new Charge | |
{ | |
hasValue = nullable.HasValue, | |
value = nullable.HasValue ? nullable.Value : 0 | |
}; | |
} | |
} | |
public struct SpawnProjectileMessage : IMessageBase | |
{ | |
// spawn data | |
/// <summary> | |
/// Used for spawning on client | |
/// </summary> | |
public uint localId; | |
public float expectedRtt; | |
public uint projectileId; | |
// projectile data | |
public Vector3 position; | |
public Quaternion rotation; | |
public Charge charge; | |
public IWeaponTarget target; | |
// owner data | |
public uint ownerNetId; | |
public byte weaponIndex; | |
public void Deserialize(NetworkReader reader) | |
{ | |
this.charge.hasValue = reader.ReadBoolean(); | |
if (WolvesLog.Weapon) { WolvesLog.WeaponLog($"hasCharge: {this.charge.hasValue}"); } | |
this.charge.value = this.charge.hasValue | |
? reader.ReadFloatFromByte(0, 1) | |
: 0; | |
this.position = reader.ReadVector3(); | |
this.rotation = reader.ReadQuaternion(); | |
var hasTarget = reader.ReadBoolean(); | |
this.target = null; | |
if (hasTarget) | |
{ | |
var netID = reader.ReadUInt32(); | |
var targetIdentity = NetworkIdentity.spawned[netID]; | |
this.target = targetIdentity.GetComponent<IWeaponTarget>(); | |
} | |
this.localId = reader.ReadUInt32(); | |
if (this.localId != 0) | |
{ | |
this.expectedRtt = reader.ReadSingle(); | |
} | |
this.projectileId = reader.ReadUInt32(); | |
this.ownerNetId = reader.ReadUInt32(); | |
this.weaponIndex = reader.ReadByte(); | |
} | |
public void Serialize(NetworkWriter writer) | |
{ | |
writer.WriteBoolean(this.charge.hasValue); | |
if (this.charge.hasValue) | |
{ | |
writer.WriteFloatToByte(this.charge.value, 0, 1); | |
} | |
writer.WriteVector3(this.position); | |
writer.WriteQuaternion(this.rotation); | |
var hasTarget = this.target != null; | |
uint netId = 0; | |
if (hasTarget) | |
{ | |
var target = this.target.netIdentity; | |
hasTarget = target != null; | |
if (hasTarget) | |
{ | |
netId = target.netId; | |
} | |
} | |
writer.WriteBoolean(hasTarget); | |
if (hasTarget) | |
{ | |
writer.WriteUInt32(netId); | |
} | |
writer.WriteUInt32(this.localId); | |
if (this.localId != 0) | |
{ | |
writer.WriteSingle((float)NetworkTime.rtt); | |
} | |
writer.WriteUInt32(this.projectileId); | |
writer.WriteUInt32(this.ownerNetId); | |
writer.WriteByte(this.weaponIndex); | |
} | |
} | |
public struct LinkProjectileMessage : IMessageBase | |
{ | |
// spawn data | |
/// <summary> | |
/// Used for spawning on client | |
/// </summary> | |
public uint localId; | |
public uint projectileId; | |
public void Deserialize(NetworkReader reader) | |
{ | |
this.localId = reader.ReadUInt32(); | |
this.projectileId = reader.ReadUInt32(); | |
} | |
public void Serialize(NetworkWriter writer) | |
{ | |
writer.WriteUInt32(this.localId); | |
writer.WriteUInt32(this.projectileId); | |
} | |
} | |
public struct ProjectileState | |
{ | |
public uint projectileId; | |
public Vector3 position; | |
public Quaternion rotation; | |
public Vector3 velocity; | |
} | |
public struct SyncProjectilesMessage : IMessageBase | |
{ | |
public static ProjectileState[] buffer = new ProjectileState[1000]; | |
public int count; | |
public float serverTime; | |
public void Deserialize(NetworkReader reader) | |
{ | |
this.count = reader.ReadInt32(); | |
this.serverTime = reader.ReadSingle(); | |
for (var i = 0; i < this.count; i++) | |
{ | |
buffer[i].projectileId = reader.ReadUInt32(); | |
buffer[i].position = reader.ReadVector3(); | |
buffer[i].rotation = reader.ReadQuaternion(); | |
buffer[i].velocity = reader.ReadVector3(); | |
} | |
} | |
public void Serialize(NetworkWriter writer) | |
{ | |
writer.WriteInt32(this.count); | |
writer.WriteSingle(this.serverTime); | |
for (var i = 0; i < this.count; i++) | |
{ | |
writer.WriteUInt32(buffer[i].projectileId); | |
writer.WriteVector3(buffer[i].position); | |
writer.WriteQuaternion(buffer[i].rotation); | |
writer.WriteVector3(buffer[i].velocity); | |
} | |
} | |
} | |
public struct DestroyProjectileMessage : IMessageBase | |
{ | |
public uint projectileId; | |
public void Deserialize(NetworkReader reader) | |
{ | |
this.projectileId = reader.ReadUInt32(); | |
} | |
public void Serialize(NetworkWriter writer) | |
{ | |
writer.WriteUInt32(this.projectileId); | |
} | |
} | |
public struct RpcHitEffectMessage : IMessageBase | |
{ | |
public uint projectileId; | |
public HitData hitData; | |
public void Deserialize(NetworkReader reader) | |
{ | |
this.projectileId = reader.ReadUInt32(); | |
this.hitData = reader.ReadHitData(); | |
} | |
public void Serialize(NetworkWriter writer) | |
{ | |
writer.WriteUInt32(this.projectileId); | |
writer.WriteHitData(this.hitData); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment