Read the description for detailed changes.

- Added `Sandbox.Players`: a synchronized list of `NetworkPlayerId` structs representing connected players.
- Added `Sandbox.Events.OnPlayerJoined` and `Sandbox.Events.OnPlayerLeft` callbacks, synchronized across all clients.
- Changed internal interpolation of quaternions to use `Slerp` instead of `Lerp`.
- Fixed a potential crash caused by undefined behavior when reaching `NetickConfig.MaxPlayers` and destroying network objects.
- Fixed an issue where sandbox-loaded scenes were not being unloaded during shutdown.
- Fixed a bug preventing Prediction Error Correction from functioning correctly.
This commit is contained in:
Karrar
2025-06-27 05:47:00 +03:00
parent 8e2db0f357
commit 1b0d33d8f0
25 changed files with 1843 additions and 1529 deletions

View File

@@ -38,7 +38,6 @@ RenderSettings:
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
m_IndirectSpecularColor: {r: 0.45137393, g: 0.50092196, b: 0.57263935, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
@@ -123,6 +122,37 @@ NavMeshSettings:
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
--- !u!1 &293298009
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 293298010}
m_Layer: 0
m_Name: GameObject
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &293298010
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 293298009}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -133.02965, y: 72.981926, z: 47.2627}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1070948396
GameObject:
m_ObjectHideFlags: 0
@@ -7204,3 +7234,4 @@ SceneRoots:
- {fileID: 1070948398}
- {fileID: 1809360484}
- {fileID: 3762179930698311635}
- {fileID: 293298010}

View File

@@ -4,22 +4,22 @@ using Netick.Unity;
namespace Netick.Samples.Bomberman
{
public class Block : NetworkBehaviour
public class Block : NetworkBehaviour
{
// Networked Properties
[Networked]
public NetworkBool Visible { get; set; } = true;
[OnChanged(nameof(Visible))]
private void OnVisibleChanged(OnChangedData onChangedData)
{
// Networked Properties
[Networked]
public NetworkBool Visible { get; set; } = true;
// for visual components, don't use "enabled" property when you want to disable/enable it, instead use SetEnabled().
// -- GetComponent<Renderer>().enabled = Visible; #### Not like this.
[OnChanged(nameof(Visible))]
private void OnVisibleChanged(OnChangedData onChangedData)
{
// for visual components, don't use "enabled" property when you want to disable/enable it, instead use SetEnabled().
// -- GetComponent<Renderer>().enabled = Visible; #### Not like this.
GetComponent<Renderer>().SetEnabled(Sandbox, Visible); // #### Like this.
GetComponent<Renderer>().SetEnabled(Sandbox, Visible); // #### Like this.
GetComponent<BoxCollider>().enabled = Visible;
}
GetComponent<BoxCollider>().enabled = Visible;
}
}
}

View File

@@ -5,68 +5,68 @@ using Netick.Unity;
namespace Netick.Samples.Bomberman
{
public class Bomb : NetworkBehaviour
{
public GameObject ExplosionPrefab;
public class Bomb : NetworkBehaviour
{
public GameObject ExplosionPrefab;
public BombermanController Bomber;
public float ExplosionDelay = 3.0f;
public BombermanController Bomber;
public float ExplosionDelay = 3.0f;
private readonly Vector3[] _directionsAroundBomb = new Vector3[4] { Vector3.right, Vector3.left, Vector3.up, Vector3.down };
private static RaycastHit[] _hits = new RaycastHit[20];
private readonly Vector3[] _directionsAroundBomb = new Vector3[4] { Vector3.right, Vector3.left, Vector3.up, Vector3.down };
private static RaycastHit[] _hits = new RaycastHit[20];
public override void NetworkStart()
{
Bomber?.SpawnedBombs.Add(this);
GetComponent<Renderer>().enabled = true;
public override void NetworkStart()
{
Bomber?.SpawnedBombs.Add(this);
GetComponent<Renderer>().enabled = true;
}
public override void NetworkDestroy()
{
Bomber?.SpawnedBombs.Remove(this);
public override void NetworkDestroy()
{
Bomber?.SpawnedBombs.Remove(this);
// spawn explosion.
if (ExplosionPrefab != null)
Instantiate(ExplosionPrefab, transform.position, Quaternion.identity);
}
// spawn explosion.
if (ExplosionPrefab != null)
Instantiate(ExplosionPrefab, transform.position, Quaternion.identity);
}
public override void NetworkFixedUpdate()
{
if (Sandbox.TickToTime(Sandbox.Tick - Object.SpawnTick) >= ExplosionDelay)
Explode();
}
public override void NetworkFixedUpdate()
{
if (Sandbox.TickToTime(Sandbox.Tick - Object.SpawnTick) >= ExplosionDelay)
Explode();
}
private void Explode()
{
// hide bomb after delay.
GetComponent<Renderer>().enabled = false;
private void Explode()
{
// hide bomb after delay.
GetComponent<Renderer>().enabled = false;
// dealing damage is done on the server only.
if (IsServer)
{
DamageTargetsAroundBomb(transform.position);
Sandbox.Destroy(Object);
}
}
private void DamageTargetsAroundBomb(Vector3 pos)
{
// find all objects around the bomb position.
foreach (var dir in _directionsAroundBomb)
{
var hitsCount = Sandbox.Physics.Raycast(pos, dir, _hits, 1f);
// dealing damage is done on the server only.
if (IsServer)
{
DamageTargetsAroundBomb(transform.position);
Sandbox.Destroy(Object);
}
}
for (int i = 0; i < hitsCount; i++)
{
var target =_hits[i].collider.gameObject;
var block = target.GetComponent<Block>();
var bomber = target.GetComponent<BombermanController>();
private void DamageTargetsAroundBomb(Vector3 pos)
{
// find all objects around the bomb position.
foreach (var dir in _directionsAroundBomb)
{
var hitsCount = Sandbox.Physics.Raycast(pos, dir, _hits, 1f);
if (block != null)
block.Visible = false;
bomber?.Die();
}
}
}
}
for (int i = 0; i < hitsCount; i++)
{
var target = _hits[i].collider.gameObject;
var block = target.GetComponent<Block>();
var bomber = target.GetComponent<BombermanController>();
if (block != null)
block.Visible = false;
bomber?.Die();
}
}
}
}
}

View File

@@ -4,7 +4,7 @@ MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 10
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:

View File

@@ -7,129 +7,132 @@ using Netick.Unity;
namespace Netick.Samples.Bomberman
{
public class BombermanController : NetworkBehaviour
public class BombermanController : NetworkBehaviour
{
public List<Bomb> SpawnedBombs = new(4);
[HideInInspector]
public Vector3 SpawnPos;
[SerializeField]
private float _speed = 6.0f;
[SerializeField]
private float _speedBoostMultiplayer = 2f;
private GameObject _bombPrefab;
private CharacterController _CC;
private BombermanInput _lastInput;
// Networked Properties
[Networked]
public int Score { get; set; } = 0;
[Networked]
public NetworkBool Alive { get; set; } = true;
[Networked(relevancy: Relevancy.InputSource)]
public int MaxBombs { get; set; } = 1;
[Networked(relevancy: Relevancy.InputSource)]
public float SpeedPowerUpTimer { get; set; } = 0;
[Networked(relevancy: Relevancy.InputSource)]
public float BombPowerUpTimer { get; set; } = 0;
public override void NetworkStart()
{
public List<Bomb> SpawnedBombs = new(4);
[HideInInspector]
public Vector3 SpawnPos;
[SerializeField]
private float _speed = 6.0f;
[SerializeField]
private float _speedBoostMultiplayer = 2f;
private GameObject _bombPrefab;
private CharacterController _CC;
// Networked Properties
[Networked]
public int Score { get; set; } = 0;
[Networked]
public NetworkBool Alive { get; set; } = true;
[Networked(relevancy: Relevancy.InputSource)]
public int MaxBombs { get; set; } = 1;
[Networked(relevancy: Relevancy.InputSource)]
public float SpeedPowerUpTimer { get; set; } = 0;
[Networked(relevancy: Relevancy.InputSource)]
public float BombPowerUpTimer { get; set; } = 0;
public override void NetworkStart()
{
_bombPrefab = Sandbox.GetPrefab("Bomb");
// we store the spawn pos so that we use it later during respawn.
SpawnPos = transform.position;
_CC = GetComponent<CharacterController>();
}
public override void OnInputSourceLeft()
{
Sandbox.GetComponent<BombermanEventsHandler>().KillPlayer(this);
// destroy the player object when its input source (controller player) leaves the game.
Sandbox.Destroy(Object);
}
public override void NetworkFixedUpdate()
{
if (!Alive)
return;
if (FetchInput(out BombermanInput input))
{
// clamp movement inputs.
input.Movement = new Vector3(Mathf.Clamp(input.Movement.x, -1f, 1f), Mathf.Clamp(input.Movement.y, -1f, 1f));
if (BombPowerUpTimer > 0)
BombPowerUpTimer -= Sandbox.FixedDeltaTime;
else
MaxBombs = 1;
if (SpeedPowerUpTimer > 0)
SpeedPowerUpTimer -= Sandbox.FixedDeltaTime;
var hasSpeedBoost = SpeedPowerUpTimer > 0;
var speed = hasSpeedBoost ? _speed * _speedBoostMultiplayer : _speed;
_CC.Move(input.Movement * speed * Sandbox.FixedDeltaTime);
// we make sure the z coord of the pos of the player is always zero.
transform.position = new Vector3(transform.position.x, transform.position.y, 0f);
if (IsServer && input.PlantBomb && SpawnedBombs.Count < MaxBombs)
{
// round the bomb pos so that it snaps to the nearest square.
var bomb = Sandbox.NetworkInstantiate(_bombPrefab, Round(transform.position), Quaternion.identity).GetComponent<Bomb>();
bomb.Bomber = this;
}
}
}
public void ReceivePowerUp(PowerUpType type, float boostTime)
{
if (type == PowerUpType.IncreaseBombs)
{
SpeedPowerUpTimer += boostTime;
}
else if (type == PowerUpType.Speed)
{
BombPowerUpTimer += boostTime;
MaxBombs += 1;
}
}
public void Die()
{
Alive = false;
Sandbox.GetComponent<BombermanEventsHandler>().KillPlayer(this);
}
public void Respawn()
{
Sandbox.GetComponent<BombermanEventsHandler>().RespawnPlayer(this);
Alive = true;
SpeedPowerUpTimer = 0;
BombPowerUpTimer = 0;
MaxBombs = 1;
transform.position = SpawnPos;
}
[OnChanged(nameof(Alive))]
private void OnAliveChanged(OnChangedData onChangedData)
{
// based on state of Alive:
// * hide/show player object.
GetComponentInChildren<Renderer>().SetEnabled(Sandbox,Alive);
// * enable/disable the CharacterController.
_CC.enabled = Alive;
}
public Vector3 Round(Vector3 vec)
{
return new Vector3(Mathf.Round(vec.x), Mathf.Round(vec.y), Mathf.Round(vec.z));
}
_bombPrefab = Sandbox.GetPrefab("Bomb");
// we store the spawn pos so that we use it later during respawn.
SpawnPos = transform.position;
_CC = GetComponent<CharacterController>();
}
public override void OnInputSourceLeft()
{
Sandbox.GetComponent<BombermanEventsHandler>().KillPlayer(this);
// destroy the player object when its input source (controller player) leaves the game.
Sandbox.Destroy(Object);
}
public override void NetworkFixedUpdate()
{
if (!Alive)
return;
FetchInput(out _lastInput);
if (IsInputSource || IsServer)
{
// clamp movement inputs.
_lastInput.Movement = new Vector3(Mathf.Clamp(_lastInput.Movement.x, -1f, 1f), Mathf.Clamp(_lastInput.Movement.y, -1f, 1f));
if (BombPowerUpTimer > 0)
BombPowerUpTimer -= Sandbox.FixedDeltaTime;
else
MaxBombs = 1;
if (SpeedPowerUpTimer > 0)
SpeedPowerUpTimer -= Sandbox.FixedDeltaTime;
var hasSpeedBoost = SpeedPowerUpTimer > 0;
var speed = hasSpeedBoost ? _speed * _speedBoostMultiplayer : _speed;
_CC.Move(_lastInput.Movement * speed * Sandbox.FixedDeltaTime);
// we make sure the z coord of the pos of the player is always zero.
transform.position = new Vector3(transform.position.x, transform.position.y, 0f);
if (IsServer && _lastInput.PlantBomb && SpawnedBombs.Count < MaxBombs)
{
// round the bomb pos so that it snaps to the nearest square.
var bomb = Sandbox.NetworkInstantiate(_bombPrefab, Round(transform.position), Quaternion.identity).GetComponent<Bomb>();
bomb.Bomber = this;
}
}
}
public void ReceivePowerUp(PowerUpType type, float boostTime)
{
if (type == PowerUpType.IncreaseBombs)
{
SpeedPowerUpTimer += boostTime;
}
else if (type == PowerUpType.Speed)
{
BombPowerUpTimer += boostTime;
MaxBombs += 1;
}
}
public void Die()
{
Alive = false;
Sandbox.GetComponent<BombermanEventsHandler>().KillPlayer(this);
}
public void Respawn()
{
Sandbox.GetComponent<BombermanEventsHandler>().RespawnPlayer(this);
Alive = true;
SpeedPowerUpTimer = 0;
BombPowerUpTimer = 0;
MaxBombs = 1;
transform.position = SpawnPos;
}
[OnChanged(nameof(Alive))]
private void OnAliveChanged(OnChangedData onChangedData)
{
// based on state of Alive:
// * hide/show player object.
GetComponentInChildren<Renderer>().SetEnabled(Sandbox, Alive);
// * enable/disable the CharacterController.
_CC.enabled = Alive;
}
public Vector3 Round(Vector3 vec)
{
return new Vector3(Mathf.Round(vec.x), Mathf.Round(vec.y), Mathf.Round(vec.z));
}
}
}

View File

@@ -5,157 +5,160 @@ using Netick.Unity;
namespace Netick.Samples.Bomberman
{
public class BombermanEventsHandler : NetworkBehaviour
public class BombermanEventsHandler : NetworkBehaviour
{
public List<BombermanController> AlivePlayers = new(4);
private GameObject _playerPrefab;
private Vector3[] _spawnPositions = new Vector3[4] { new Vector3(11, 9, 0), new Vector3(11, 1, 0), new Vector3(1, 9, 0), new Vector3(1, 1, 0) };
private Queue<Vector3> _freePositions = new(4);
public override void NetworkAwake()
{
public List<BombermanController> Players = new(4);
public List<BombermanController> AlivePlayers = new(4);
private GameObject _playerPrefab;
private Vector3[] _spawnPositions = new Vector3[4] { new Vector3(11, 9, 0), new Vector3(11, 1, 0), new Vector3(1, 9, 0), new Vector3(1, 1, 0) };
private Queue<Vector3> _freePositions = new(4);
public override void NetworkStart()
{
Sandbox.Events.OnInputRead += OnInput;
Sandbox.Events.OnConnectRequest += OnConnectRequest;
Sandbox.Events.OnPlayerConnected += OnPlayerConnected;
Sandbox.Events.OnPlayerDisconnected += OnPlayerDisconnected;
Sandbox.Events.OnInputRead += OnInput;
Sandbox.Events.OnConnectRequest += OnConnectRequest;
Sandbox.Events.OnPlayerJoined += OnPlayerJoined;
Sandbox.Events.OnPlayerLeft += OnPlayerLeft;
_playerPrefab = Sandbox.GetPrefab("Bomberman Player");
Sandbox.InitializePool(Sandbox.GetPrefab("Bomb"), 5);
Sandbox.InitializePool(_playerPrefab, 4);
_playerPrefab = Sandbox.GetPrefab("Bomberman Player");
Sandbox.InitializePool(Sandbox.GetPrefab("Bomb"), 5);
Sandbox.InitializePool(_playerPrefab,4);
for (int i = 0; i < 4; i++)
_freePositions.Enqueue(_spawnPositions[i]);
if (IsServer)
RestartGame();
}
public void OnConnectRequest(NetworkSandbox sandbox, NetworkConnectionRequest request)
{
if (Sandbox.ConnectedPlayers.Count >= 4)
request.Refuse();
}
// This is called on the server when a playerObj has connected.
public void OnPlayerConnected(NetworkSandbox sandbox, NetworkPlayer player)
{
var playerObj = sandbox.NetworkInstantiate(_playerPrefab, _spawnPositions[Sandbox.ConnectedPlayers.Count], Quaternion.identity, player).GetComponent<BombermanController>();
player.PlayerObject = playerObj.gameObject;
AlivePlayers. Add(playerObj);
Players. Add(playerObj);
}
// This is called on the server when a client has disconnected.
public void OnPlayerDisconnected(NetworkSandbox sandbox, Netick.NetworkPlayer player, TransportDisconnectReason reason)
{
_freePositions.Enqueue(((GameObject)player.PlayerObject).GetComponent<BombermanController>().SpawnPos);
Players. Remove (((GameObject)player.PlayerObject).GetComponent<BombermanController>());
}
// This is called to read inputs.
public void OnInput(NetworkSandbox sandbox)
{
var input = sandbox.GetInput<BombermanInput>();
input.Movement = GetMovementDir();
input.PlantBomb |= Input.GetKeyDown(KeyCode.Space);
sandbox.SetInput(input);
}
public void RestartGame()
{
// destroy level.
foreach (var block in Sandbox.FindObjectsOfType<Block>())
Sandbox.Destroy(block.Object);
foreach (var bomb in Sandbox.FindObjectsOfType<Bomb>())
Sandbox.Destroy(bomb.Object);
// create new level.
var blockPrefab = Sandbox.GetPrefab("DestroyableBlock");
var powerUpPrefab = Sandbox.GetPrefab("Power Up");
var numberOfBoosters = Random.Range(2, 4+1);
var takenPositions = new List<Vector3>();
var maxX = 11;
var maxY = 9;
for (int x = 1; x <= maxX; x++)
{
for (int y = 1; y <= maxY; y++)
{
var spawn = Random.value > 0.5f;
var pos = new Vector3(x, y);
if (spawn && IsValidPos(pos))
{
Sandbox.NetworkInstantiate(blockPrefab, pos, Quaternion.identity);
takenPositions.Add(pos);
}
}
}
while (numberOfBoosters > 0)
{
var randomPos = new Vector3(Random.Range(1, 11+1), Random.Range(1, 9+1), 0);
var type = (Random.value > 0.5f) ? PowerUpType.Speed : PowerUpType.IncreaseBombs;
if (!takenPositions.Contains(randomPos) && IsValidPos(randomPos))
{
var booster = Sandbox.NetworkInstantiate(powerUpPrefab, randomPos, Quaternion.identity).GetComponent<PowerUp>();
booster.Type = type;
numberOfBoosters--;
}
}
// reset players.
foreach (var player in Players)
player.Respawn();
}
private bool IsValidPos(Vector3 pos)
{
// if the pos is the position of a static block, we ignore it.
if ((pos.x >= 2 && pos.x <= 10) && (pos.y >= 2 && pos.y <= 8))
if (pos.x % 2 == 0 && pos.y % 2 == 0)
return false;
// if the pos is near the position of the spawn locations of the players, we ignore it.
foreach (var loc in _spawnPositions)
{
if (pos == loc) return false;
if (pos == loc + Vector3.up || pos == loc + Vector3.down) return false;
if (pos == loc + Vector3.left || pos == loc + Vector3.right) return false;
}
return true;
}
public void KillPlayer(BombermanController bomber)
{
AlivePlayers.Remove(bomber);
if (AlivePlayers.Count == 1)
{
AlivePlayers[0].Score++;
RestartGame();
}
else if (AlivePlayers.Count < 1)
RestartGame();
}
public void RespawnPlayer(BombermanController bomber)
{
if (!AlivePlayers.Contains(bomber))
AlivePlayers.Add(bomber);
}
private Vector2 GetMovementDir()
{
if (Input.GetKey(KeyCode.W)) return Vector2.up;
else if (Input.GetKey(KeyCode.D)) return Vector2.right;
else if (Input.GetKey(KeyCode.S)) return Vector2.down;
else if (Input.GetKey(KeyCode.A)) return Vector2.left;
else return Vector2.zero;
}
for (int i = 0; i < 4; i++)
_freePositions.Enqueue(_spawnPositions[i]);
}
public override void NetworkStart()
{
if (IsServer)
RestartGame();
}
public void OnConnectRequest(NetworkSandbox sandbox, NetworkConnectionRequest request)
{
if (Sandbox.Players.Count >= 4)
request.Refuse();
}
// This is called when a player has has joined the game.
public void OnPlayerJoined(NetworkSandbox sandbox, NetworkPlayerId player)
{
if (IsClient)
return;
var playerObj = sandbox.NetworkInstantiate(_playerPrefab, _spawnPositions[Sandbox.Players.Count], Quaternion.identity, player).GetComponent<BombermanController>();
sandbox.SetPlayerObject(player, playerObj.Object);
AlivePlayers.Add(playerObj);
}
// This is called when a player has has left the game.
public void OnPlayerLeft(NetworkSandbox sandbox, NetworkPlayerId player)
{
if (IsClient)
return;
var playerObj = sandbox.GetPlayerObject<BombermanController>(player);
_freePositions.Enqueue(playerObj.SpawnPos);
}
// This is called to read inputs.
public void OnInput(NetworkSandbox sandbox)
{
var input = sandbox.GetInput<BombermanInput>();
input.Movement = GetMovementDir();
input.PlantBomb |= Input.GetKeyDown(KeyCode.Space);
sandbox.SetInput(input);
}
public void RestartGame()
{
// destroy level.
foreach (var block in Sandbox.FindObjectsOfType<Block>())
Sandbox.Destroy(block.Object);
foreach (var bomb in Sandbox.FindObjectsOfType<Bomb>())
Sandbox.Destroy(bomb.Object);
// create new level.
var blockPrefab = Sandbox.GetPrefab("DestroyableBlock");
var powerUpPrefab = Sandbox.GetPrefab("Power Up");
var numberOfBoosters = Random.Range(2, 4 + 1);
var takenPositions = new List<Vector3>();
var maxX = 11;
var maxY = 9;
for (int x = 1; x <= maxX; x++)
{
for (int y = 1; y <= maxY; y++)
{
var spawn = Random.value > 0.5f;
var pos = new Vector3(x, y);
if (spawn && IsValidPos(pos))
{
Sandbox.NetworkInstantiate(blockPrefab, pos, Quaternion.identity);
takenPositions.Add(pos);
}
}
}
while (numberOfBoosters > 0)
{
var randomPos = new Vector3(Random.Range(1, 11 + 1), Random.Range(1, 9 + 1), 0);
var type = (Random.value > 0.5f) ? PowerUpType.Speed : PowerUpType.IncreaseBombs;
if (!takenPositions.Contains(randomPos) && IsValidPos(randomPos))
{
var booster = Sandbox.NetworkInstantiate(powerUpPrefab, randomPos, Quaternion.identity).GetComponent<PowerUp>();
booster.Type = type;
numberOfBoosters--;
}
}
// reset players.
foreach (var player in Sandbox.Players)
Sandbox.GetPlayerObject<BombermanController>(player).Respawn();
}
private bool IsValidPos(Vector3 pos)
{
// if the pos is the position of a static block, we ignore it.
if ((pos.x >= 2 && pos.x <= 10) && (pos.y >= 2 && pos.y <= 8))
if (pos.x % 2 == 0 && pos.y % 2 == 0)
return false;
// if the pos is near the position of the spawn locations of the players, we ignore it.
foreach (var loc in _spawnPositions)
{
if (pos == loc) return false;
if (pos == loc + Vector3.up || pos == loc + Vector3.down) return false;
if (pos == loc + Vector3.left || pos == loc + Vector3.right) return false;
}
return true;
}
public void KillPlayer(BombermanController bomber)
{
AlivePlayers.Remove(bomber);
if (AlivePlayers.Count == 1)
{
AlivePlayers[0].Score++;
RestartGame();
}
else if (AlivePlayers.Count < 1)
RestartGame();
}
public void RespawnPlayer(BombermanController bomber)
{
if (!AlivePlayers.Contains(bomber))
AlivePlayers.Add(bomber);
}
private Vector2 GetMovementDir()
{
if (Input.GetKey(KeyCode.W)) return Vector2.up;
else if (Input.GetKey(KeyCode.D)) return Vector2.right;
else if (Input.GetKey(KeyCode.S)) return Vector2.down;
else if (Input.GetKey(KeyCode.A)) return Vector2.left;
else return Vector2.zero;
}
}
}

View File

@@ -4,11 +4,11 @@ using Netick.Unity;
namespace Netick.Samples.Bomberman
{
[Networked]
public struct BombermanInput : INetworkInput
{
[Networked]
public struct BombermanInput : INetworkInput
{
[Networked]
public Vector2 Movement { get; set; }
public NetworkBool PlantBomb;
}
public Vector2 Movement { get; set; }
public NetworkBool PlantBomb;
}
}

View File

@@ -4,44 +4,44 @@ using Netick.Unity;
namespace Netick.Samples.Bomberman
{
public enum PowerUpType
public enum PowerUpType
{
Speed,
IncreaseBombs
}
public class PowerUp : NetworkBehaviour
{
public float PowerUpTime = 35;
private Material _mat;
// Networked Properties
[Networked]
public PowerUpType Type { get; set; }
private void Awake()
{
Speed,
IncreaseBombs
_mat = GetComponentInChildren<Renderer>().material;
}
public class PowerUp : NetworkBehaviour
public override void NetworkRender()
{
public float PowerUpTime = 35;
private Material _mat;
// Networked Properties
[Networked]
public PowerUpType Type { get; set; }
private void Awake()
{
_mat = GetComponentInChildren<Renderer>().material;
}
public override void NetworkRender()
{
var color = Type == PowerUpType.IncreaseBombs ? Color.green : Color.blue;
_mat.color = Color.Lerp(color, color * 0.5f, Mathf.InverseLerp(-1f, 1f, Mathf.Sin(15f * Time.time)));
}
public void OnTriggerEnter(Collider other)
{
if (Sandbox == null)
return;
var player = other.gameObject.GetComponent<BombermanController>();
if (Sandbox.IsServer && player != null)
{
player.ReceivePowerUp(Type, PowerUpTime);
Sandbox.Destroy(Object);
}
}
var color = Type == PowerUpType.IncreaseBombs ? Color.green : Color.blue;
_mat.color = Color.Lerp(color, color * 0.5f, Mathf.InverseLerp(-1f, 1f, Mathf.Sin(15f * Time.time)));
}
public void OnTriggerEnter(Collider other)
{
if (Sandbox == null)
return;
var player = other.gameObject.GetComponent<BombermanController>();
if (Sandbox.IsServer && player != null)
{
player.ReceivePowerUp(Type, PowerUpTime);
Sandbox.Destroy(Object);
}
}
}
}

View File

@@ -38,7 +38,6 @@ RenderSettings:
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
m_IndirectSpecularColor: {r: 0.43668216, g: 0.48427725, b: 0.56452835, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
@@ -503,12 +502,15 @@ MonoBehaviour:
m_GameObject: {fileID: 1293352707}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fb5a4e3a1e885b04e8f36bf0b0597274, type: 3}
m_Script: {fileID: 11500000, guid: 13d95c28de1bac54f8b26fc8dd960077, type: 3}
m_Name:
m_EditorClassIdentifier:
SpawnPos: {fileID: 10129275}
PlayerPrefab: {fileID: 7011933354638177272, guid: d66d5a97430d3364890108321123b309,
type: 3}
SpawnPosition: {fileID: 10129275}
HorizontalOffset: 5
StaggerSpawns: 1
DestroyPlayerObjectWhenLeaving: 1
--- !u!1 &1318413499
GameObject:
m_ObjectHideFlags: 0
@@ -672,6 +674,7 @@ Terrain:
m_DetailObjectDensity: 1
m_HeightmapPixelError: 5
m_SplatMapDistance: 1000
m_HeightmapMinimumLODSimplification: 0
m_HeightmapMaximumLOD: 0
m_ShadowCastingMode: 2
m_DrawHeightmap: 1

View File

@@ -4,127 +4,131 @@ using Netick.Unity;
namespace Netick.Samples.FPS
{
public class FPSController : NetworkBehaviour
public class FPSController : NetworkBehaviour
{
[SerializeField]
private Transform _renderTransform;
[SerializeField]
private float _movementSpeed = 10;
[SerializeField]
private float _sensitivityX = 1.6f;
[SerializeField]
private float _sensitivityY = -1f;
[SerializeField]
private Transform _cameraParent;
private CharacterController _CC;
private Vector2 _camAngles;
private FPSInput _lastInput;
// Networked Properties
[Networked]
[Smooth]
public Vector2 YawPitch { get; set; }
public override void NetworkStart()
{
[SerializeField]
private Transform _renderTransform;
_CC = GetComponent<CharacterController>();
[SerializeField]
private float _movementSpeed = 10;
[SerializeField]
private float _sensitivityX = 1.6f;
[SerializeField]
private float _sensitivityY = -1f;
[SerializeField]
private Transform _cameraParent;
private CharacterController _CC;
private Vector2 _camAngles;
// Networked Properties
[Networked][Smooth]
public Vector2 YawPitch { get; set; }
public override void NetworkStart()
{
_CC = GetComponent<CharacterController>();
if (IsInputSource)
{
var cam = Sandbox.FindObjectOfType<Camera>();
cam.transform.parent = _cameraParent;
cam.transform.localPosition = Vector3.zero;
cam.transform.localRotation = Quaternion.identity;
}
}
public override void OnInputSourceLeft()
{
// destroy the player object when its input source (controller player) leaves the game.
Sandbox.Destroy(Object);
}
public override void NetworkUpdate()
{
if (!IsInputSource || !Sandbox.InputEnabled)
return;
Vector2 mouseInputs = new Vector2(Input.GetAxisRaw("Mouse X") * _sensitivityX, Input.GetAxisRaw("Mouse Y") * _sensitivityY);
var networkInput = Sandbox.GetInput<FPSInput>();
networkInput.Movement = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
networkInput.ShootInput |= Input.GetMouseButton(0);
networkInput.YawPitch += mouseInputs;
Sandbox.SetInput(networkInput);
// we apply the rotation in update too to have smooth camera control.
_camAngles = ClampAngles(_camAngles.x + mouseInputs.x, _camAngles.y + mouseInputs.y);
ApplyRotations(_camAngles, false);
}
public override void NetworkFixedUpdate()
{
if (FetchInput(out FPSInput input))
MoveAndRotate(input);
}
private void MoveAndRotate(FPSInput input)
{
// clamp movement inputs.
input.Movement = new Vector3(Mathf.Clamp(input.Movement.x, -1f, 1f), Mathf.Clamp(input.Movement.y, -1f, 1f));
// rotation.
YawPitch = ClampAngles(YawPitch.x + input.YawPitch.x, YawPitch.y + input.YawPitch.y);
ApplyRotations(YawPitch,false);
// movement direction.
var movement = transform.TransformVector(new Vector3(input.Movement.x, 0, input.Movement.y)) * _movementSpeed;
movement.y = 0;
var gravity = 15f * Vector3.down;
// move.
_CC.Move((movement + gravity) * Sandbox.FixedDeltaTime);
}
[OnChanged(nameof(YawPitch), invokeDuringResimulation: true)]
private void OnYawPitchChanged(OnChangedData onChanged)
{
ApplyRotations(YawPitch, false);
}
public override void NetworkRender()
{
if (IsProxy)
ApplyRotations(YawPitch, true);
}
private void ApplyRotations(Vector2 camAngles, bool isProxy)
{
// on the player transform, we apply yaw.
if (isProxy)
_renderTransform.rotation = Quaternion.Euler(new Vector3(0, camAngles.x, 0));
else
transform.rotation = Quaternion.Euler(new Vector3(0, camAngles.x, 0));
// on the weapon/camera holder, we apply the pitch angle.
_cameraParent.localEulerAngles = new Vector3(camAngles.y, 0, 0);
_camAngles = camAngles;
}
private Vector2 ClampAngles(float yaw, float pitch)
{
return new Vector2(ClampAngle(yaw, -360, 360), ClampAngle(pitch, -80, 80));
}
private float ClampAngle(float angle, float min, float max)
{
if (angle < -360F)
angle += 360F;
if (angle > 360F)
angle -= 360F;
return Mathf.Clamp(angle, min, max);
}
if (IsInputSource)
{
var cam = Sandbox.FindObjectOfType<Camera>();
cam.transform.parent = _cameraParent;
cam.transform.localPosition = Vector3.zero;
cam.transform.localRotation = Quaternion.identity;
}
}
public override void OnInputSourceLeft()
{
// destroy the player object when its input source (controller player) leaves the game.
Sandbox.Destroy(Object);
}
public override void NetworkUpdate()
{
if (!IsInputSource || !Sandbox.InputEnabled)
return;
Vector2 mouseInputs = new Vector2(Input.GetAxisRaw("Mouse X") * _sensitivityX, Input.GetAxisRaw("Mouse Y") * _sensitivityY);
var networkInput = Sandbox.GetInput<FPSInput>();
networkInput.Movement = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
networkInput.ShootInput |= Input.GetMouseButton(0);
networkInput.YawPitch += mouseInputs;
Sandbox.SetInput(networkInput);
// we apply the rotation in update too to have smooth camera control.
_camAngles = ClampAngles(_camAngles.x + mouseInputs.x, _camAngles.y + mouseInputs.y);
ApplyRotations(_camAngles, false);
}
public override void NetworkFixedUpdate()
{
FetchInput(out _lastInput);
if (IsInputSource || IsServer)
MoveAndRotate(_lastInput);
}
private void MoveAndRotate(FPSInput input)
{
// clamp movement inputs.
input.Movement = new Vector3(Mathf.Clamp(input.Movement.x, -1f, 1f), Mathf.Clamp(input.Movement.y, -1f, 1f));
// rotation.
YawPitch = ClampAngles(YawPitch.x + input.YawPitch.x, YawPitch.y + input.YawPitch.y);
ApplyRotations(YawPitch, false);
// movement direction.
var movement = transform.TransformVector(new Vector3(input.Movement.x, 0, input.Movement.y)) * _movementSpeed;
movement.y = 0;
var gravity = 15f * Vector3.down;
// move.
_CC.Move((movement + gravity) * Sandbox.FixedDeltaTime);
}
[OnChanged(nameof(YawPitch), invokeDuringResimulation: true)]
private void OnYawPitchChanged(OnChangedData onChanged)
{
ApplyRotations(YawPitch, false);
}
public override void NetworkRender()
{
if (!IsInputSource)
ApplyRotations(YawPitch, true);
}
private void ApplyRotations(Vector2 camAngles, bool isProxy)
{
// on the player transform, we apply yaw.
if (isProxy)
_renderTransform.rotation = Quaternion.Euler(new Vector3(0, camAngles.x, 0));
else
transform.rotation = Quaternion.Euler(new Vector3(0, camAngles.x, 0));
// on the weapon/camera holder, we apply the pitch angle.
_cameraParent.localEulerAngles = new Vector3(camAngles.y, 0, 0);
_camAngles = camAngles;
}
private Vector2 ClampAngles(float yaw, float pitch)
{
return new Vector2(ClampAngle(yaw, -360, 360), ClampAngle(pitch, -80, 80));
}
private float ClampAngle(float angle, float min, float max)
{
if (angle < -360F)
angle += 360F;
if (angle > 360F)
angle -= 360F;
return Mathf.Clamp(angle, min, max);
}
}
}

View File

@@ -3,13 +3,13 @@ using Netick;
namespace Netick.Samples.FPS
{
[Networked]
public struct FPSInput : INetworkInput
{
[Networked]
public struct FPSInput : INetworkInput
{
[Networked]
public Vector2 YawPitch { get; set; }
[Networked]
public Vector2 Movement { get; set; }
public NetworkBool ShootInput;
}
public Vector2 YawPitch { get; set; }
[Networked]
public Vector2 Movement { get; set; }
public NetworkBool ShootInput;
}
}