feat: game start countdown added

This commit is contained in:
Syntriax 2025-07-27 18:55:32 +03:00
parent 863c3a8d7a
commit a781d92996
4 changed files with 140 additions and 27 deletions

View File

@ -1,11 +1,15 @@
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Syntriax.Engine.Core; using Syntriax.Engine.Core;
using Syntriax.Engine.Integration.MonoGame;
using Syntriax.Engine.Network; using Syntriax.Engine.Network;
using Syntriax.Engine.Physics2D; using Syntriax.Engine.Physics2D;
using Syntriax.Engine.Systems.Tween; using Syntriax.Engine.Systems.Tween;
namespace Pong.Behaviours; namespace Pong.Behaviours;
public class Ball : Behaviour2D, IFirstFrameUpdate, IPhysicsUpdate, INetworkEntity, public class Ball : Behaviour2D, IFirstFrameUpdate, ILoadContent, IPhysicsUpdate, INetworkEntity,
IPacketListenerClient<Ball.BallUpdatePacket>, IPacketListenerClient<Ball.BallUpdatePacket>,
IPacketListenerClient<Ball.BallResetPacket> IPacketListenerClient<Ball.BallResetPacket>
{ {
@ -19,6 +23,8 @@ public class Ball : Behaviour2D, IFirstFrameUpdate, IPhysicsUpdate, INetworkEnti
private INetworkCommunicatorServer? networkServer = null; private INetworkCommunicatorServer? networkServer = null;
private ITween? networkTween = null; private ITween? networkTween = null;
private SoundEffect? bounceSoundEffect = null;
public void FirstActiveFrame() public void FirstActiveFrame()
{ {
BehaviourController.GetRequiredBehaviour<ICollider2D>().OnCollisionDetected.AddListener(OnCollisionDetected); BehaviourController.GetRequiredBehaviour<ICollider2D>().OnCollisionDetected.AddListener(OnCollisionDetected);
@ -28,6 +34,11 @@ public class Ball : Behaviour2D, IFirstFrameUpdate, IPhysicsUpdate, INetworkEnti
networkServer = Universe.FindBehaviour<INetworkCommunicatorServer>(); networkServer = Universe.FindBehaviour<INetworkCommunicatorServer>();
} }
public void LoadContent(ContentManager content)
{
bounceSoundEffect = content.Load<SoundEffect>("Audio/Bounce");
}
public void LaunchBall(Vector2D launchDirection) public void LaunchBall(Vector2D launchDirection)
{ {
ResetBall(); ResetBall();
@ -74,6 +85,9 @@ public class Ball : Behaviour2D, IFirstFrameUpdate, IPhysicsUpdate, INetworkEnti
else else
Transform.Position = packet.Position; Transform.Position = packet.Position;
if (RigidBody.Velocity.MagnitudeSquared >= .01f)
bounceSoundEffect?.Play();
RigidBody.Velocity = packet.Velocity; RigidBody.Velocity = packet.Velocity;
physicsEngine2D.StepIndividual(RigidBody, sender.Ping); physicsEngine2D.StepIndividual(RigidBody, sender.Ping);
} }

View File

@ -0,0 +1,96 @@
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Input;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Debug;
using Syntriax.Engine.Core.Exceptions;
using Syntriax.Engine.Integration.MonoGame;
using Syntriax.Engine.Network;
using Syntriax.Engine.Systems.Input;
using Syntriax.Engine.Systems.Time;
using Syntriax.Engine.Systems.Tween;
namespace Pong.Behaviours;
public class PongGameStarter : Behaviour, INetworkEntity, IFirstFrameUpdate,
IPacketListenerServer<PongGameStarter.RequestStartPacket>,
IPacketListenerClient<PongGameStarter.RequestStartPacket>
{
private const float START_COUNTDOWN = 3f;
private INetworkCommunicatorServer? networkServer = null;
private INetworkCommunicatorClient? networkClient = null;
private ITweenManager? tweenManager = null;
private PongManager pongManager = null!;
private ILogger? logger = null;
private Label? label = null;
private TickerTimer timer = null!;
public void FirstActiveFrame()
{
IButtonInputs<Keys>? buttonInputs = Universe.FindBehaviour<IButtonInputs<Keys>>();
buttonInputs?.RegisterOnRelease(Keys.Space, (_, _1) => networkClient?.SendToServer(new RequestStartPacket()));
networkClient = Universe.FindBehaviour<INetworkCommunicatorClient>();
networkServer = Universe.FindBehaviour<INetworkCommunicatorServer>();
tweenManager = Universe.FindBehaviour<ITweenManager>();
pongManager = BehaviourController.GetRequiredBehaviourInParent<PongManager>();
label = BehaviourController.GetRequiredBehaviour<Label>();
logger = Universe.FindBehaviour<ILogger>();
if (!BehaviourController.TryGetBehaviour(out timer!))
{
timer = BehaviourController.AddBehaviour<TickerTimer>();
timer.OnStarted.AddListener(OnCountdownStart);
timer.OnTick.AddListener(DisplayCountdown);
timer.OnStopped.AddListener(StartPong);
}
}
private void OnCountdownStart(IReadOnlyTimer sender)
{
pongManager.Reset();
DisplayCountdown(timer);
}
private void DisplayCountdown(ITicker sender)
{
if (label != null)
{
label.Text = $"{START_COUNTDOWN - timer.TickCounter}";
label.Color = new ColorRGBA(255, 255, 255, 255);
if (tweenManager is not null)
label.Color.TweenColor(tweenManager, 1f, new ColorRGBA(255, 255, 255, 0), (x) => label.Color = x);
}
}
private void StartPong(IReadOnlyTimer sender)
{
pongManager.Start();
}
void IPacketListenerServer<RequestStartPacket>.OnServerPacketArrived(IConnection sender, RequestStartPacket packet)
{
logger?.Log(this, $"{sender} requested start");
if (pongManager.IsGameInProgress)
{
logger?.Log(this, $"The game is already in progress");
return;
}
timer.Start(START_COUNTDOWN);
networkServer?.SendToAll(packet);
}
void IPacketListenerClient<RequestStartPacket>.OnClientPacketArrived(IConnection sender, RequestStartPacket packet)
{
logger?.Log(this, $"Server is starting the game");
timer.Start(START_COUNTDOWN - sender.Ping);
}
private class RequestStartPacket : INetworkPacket;
}

View File

@ -1,16 +1,12 @@
using System; using System;
using Microsoft.Xna.Framework.Input;
using Syntriax.Engine.Core; using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Debug; using Syntriax.Engine.Core.Debug;
using Syntriax.Engine.Network; using Syntriax.Engine.Network;
using Syntriax.Engine.Systems.Input;
namespace Pong.Behaviours; namespace Pong.Behaviours;
public class PongManager : Behaviour, INetworkEntity, IFirstFrameUpdate, public class PongManager : Behaviour, INetworkEntity, IFirstFrameUpdate,
IPacketListenerServer<PongManager.RequestStartPacket>,
IPacketListenerClient<PongManager.ScorePacket> IPacketListenerClient<PongManager.ScorePacket>
{ {
public Action<PongManager>? OnReset { get; set; } = null; public Action<PongManager>? OnReset { get; set; } = null;
@ -18,9 +14,7 @@ public class PongManager : Behaviour, INetworkEntity, IFirstFrameUpdate,
public Action<PongManager>? OnScoreUpdated { get; set; } = null; public Action<PongManager>? OnScoreUpdated { get; set; } = null;
private Random random = new(); private Random random = new();
private Ball ball = null!;
private INetworkCommunicatorClient? networkClient = null!;
private INetworkCommunicatorServer? networkServer = null; private INetworkCommunicatorServer? networkServer = null;
private ILogger? logger = null; private ILogger? logger = null;
@ -29,17 +23,16 @@ public class PongManager : Behaviour, INetworkEntity, IFirstFrameUpdate,
public int ScoreSum => ScoreLeft + ScoreRight; public int ScoreSum => ScoreLeft + ScoreRight;
public int WinScore { get; } = 5; public int WinScore { get; } = 5;
public Ball Ball { get; private set; } = null!;
public bool IsGameInProgress { get; private set; } = false;
public PongManager() => WinScore = 5; public PongManager() => WinScore = 5;
public PongManager(int winScore) => WinScore = winScore; public PongManager(int winScore) => WinScore = winScore;
public void FirstActiveFrame() public void FirstActiveFrame()
{ {
IButtonInputs<Keys>? buttonInputs = Universe.FindBehaviour<IButtonInputs<Keys>>(); Ball = Universe.FindRequiredBehaviour<Ball>();
buttonInputs?.RegisterOnRelease(Keys.Space, (_, _1) => networkClient?.SendToServer(new RequestStartPacket()));
ball = Universe.FindRequiredBehaviour<Ball>();
networkClient = Universe.FindBehaviour<INetworkCommunicatorClient>();
networkServer = Universe.FindBehaviour<INetworkCommunicatorServer>(); networkServer = Universe.FindBehaviour<INetworkCommunicatorServer>();
logger = Universe.FindBehaviour<ILogger>(); logger = Universe.FindBehaviour<ILogger>();
} }
@ -60,18 +53,34 @@ public class PongManager : Behaviour, INetworkEntity, IFirstFrameUpdate,
PostScoreUpdate(); PostScoreUpdate();
} }
public bool Start()
{
if (networkServer is null)
return false;
if (Ball.RigidBody.Velocity.MagnitudeSquared > 0.01f)
return false;
Reset();
IsGameInProgress = true;
PostScoreUpdate();
logger?.Log(this, $"Game started");
return true;
}
public void Reset() public void Reset()
{ {
ScoreLeft = ScoreRight = 0; ScoreLeft = ScoreRight = 0;
IsGameInProgress = false;
PostScoreUpdate(); Ball.ResetBall();
OnReset?.Invoke(this); OnReset?.Invoke(this);
} }
private void PostScoreUpdate() private void PostScoreUpdate()
{ {
ball.ResetBall(); Ball.ResetBall();
if (networkServer is not null) if (networkServer is not null)
{ {
@ -82,12 +91,13 @@ public class PongManager : Behaviour, INetworkEntity, IFirstFrameUpdate,
int halfwayScore = (int)(WinScore * .5f); int halfwayScore = (int)(WinScore * .5f);
if (ScoreLeft > halfwayScore || ScoreRight > halfwayScore) if (ScoreLeft > halfwayScore || ScoreRight > halfwayScore)
{ {
IsGameInProgress = false;
OnFinished?.Invoke(this); OnFinished?.Invoke(this);
logger?.Log(this, $"Game finished"); logger?.Log(this, $"Game finished");
return; return;
} }
ball.LaunchBall(GetBallLaunchDirection()); Ball.LaunchBall(GetBallLaunchDirection());
} }
private Vector2D GetBallLaunchDirection() private Vector2D GetBallLaunchDirection()
@ -107,18 +117,6 @@ public class PongManager : Behaviour, INetworkEntity, IFirstFrameUpdate,
logger?.Log(this, $"Client score update packet arrived: {packet.Left} - {packet.Right}"); logger?.Log(this, $"Client score update packet arrived: {packet.Left} - {packet.Right}");
} }
void IPacketListenerServer<RequestStartPacket>.OnServerPacketArrived(IConnection sender, RequestStartPacket packet)
{
logger?.Log(this, $"{sender} requested start");
if (ball.RigidBody.Velocity.MagnitudeSquared > 0.01f)
return;
Reset();
ball.LaunchBall(GetBallLaunchDirection());
logger?.Log(this, $"Game started");
}
private class RequestStartPacket : INetworkPacket;
private class ScorePacket : INetworkPacket private class ScorePacket : INetworkPacket
{ {
public int Left { get; set; } public int Left { get; set; }

View File

@ -66,6 +66,11 @@ public static class PongUniverse
PongManager pongManager = universe.InstantiateUniverseObject().SetUniverseObject("Pong Game Manager") PongManager pongManager = universe.InstantiateUniverseObject().SetUniverseObject("Pong Game Manager")
.BehaviourController.AddBehaviour<PongManager>(5); .BehaviourController.AddBehaviour<PongManager>(5);
universe.InstantiateUniverseObject().SetUniverseObject("Pong Game Starter", parent: pongManager.UniverseObject)
.BehaviourController.AddBehaviour<PongGameStarter>()
.BehaviourController.AddBehaviour<Transform2D>().SetTransform(position: new Vector2D(-24, 250f), scale: Vector2D.One * .5f)
.BehaviourController.AddBehaviour<Label>();
//////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////
universe.InstantiateUniverseObject().SetUniverseObject("Ball") universe.InstantiateUniverseObject().SetUniverseObject("Ball")