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.Integration.MonoGame;
using Syntriax.Engine.Network;
using Syntriax.Engine.Physics2D;
using Syntriax.Engine.Systems.Tween;
namespace Pong.Behaviours;
public class Ball : Behaviour2D, IFirstFrameUpdate, IPhysicsUpdate, INetworkEntity,
public class Ball : Behaviour2D, IFirstFrameUpdate, ILoadContent, IPhysicsUpdate, INetworkEntity,
IPacketListenerClient<Ball.BallUpdatePacket>,
IPacketListenerClient<Ball.BallResetPacket>
{
@ -19,6 +23,8 @@ public class Ball : Behaviour2D, IFirstFrameUpdate, IPhysicsUpdate, INetworkEnti
private INetworkCommunicatorServer? networkServer = null;
private ITween? networkTween = null;
private SoundEffect? bounceSoundEffect = null;
public void FirstActiveFrame()
{
BehaviourController.GetRequiredBehaviour<ICollider2D>().OnCollisionDetected.AddListener(OnCollisionDetected);
@ -28,6 +34,11 @@ public class Ball : Behaviour2D, IFirstFrameUpdate, IPhysicsUpdate, INetworkEnti
networkServer = Universe.FindBehaviour<INetworkCommunicatorServer>();
}
public void LoadContent(ContentManager content)
{
bounceSoundEffect = content.Load<SoundEffect>("Audio/Bounce");
}
public void LaunchBall(Vector2D launchDirection)
{
ResetBall();
@ -74,6 +85,9 @@ public class Ball : Behaviour2D, IFirstFrameUpdate, IPhysicsUpdate, INetworkEnti
else
Transform.Position = packet.Position;
if (RigidBody.Velocity.MagnitudeSquared >= .01f)
bounceSoundEffect?.Play();
RigidBody.Velocity = packet.Velocity;
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 Microsoft.Xna.Framework.Input;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Debug;
using Syntriax.Engine.Network;
using Syntriax.Engine.Systems.Input;
namespace Pong.Behaviours;
public class PongManager : Behaviour, INetworkEntity, IFirstFrameUpdate,
IPacketListenerServer<PongManager.RequestStartPacket>,
IPacketListenerClient<PongManager.ScorePacket>
{
public Action<PongManager>? OnReset { get; set; } = null;
@ -18,9 +14,7 @@ public class PongManager : Behaviour, INetworkEntity, IFirstFrameUpdate,
public Action<PongManager>? OnScoreUpdated { get; set; } = null;
private Random random = new();
private Ball ball = null!;
private INetworkCommunicatorClient? networkClient = null!;
private INetworkCommunicatorServer? networkServer = null;
private ILogger? logger = null;
@ -29,17 +23,16 @@ public class PongManager : Behaviour, INetworkEntity, IFirstFrameUpdate,
public int ScoreSum => ScoreLeft + ScoreRight;
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(int winScore) => WinScore = winScore;
public void FirstActiveFrame()
{
IButtonInputs<Keys>? buttonInputs = Universe.FindBehaviour<IButtonInputs<Keys>>();
buttonInputs?.RegisterOnRelease(Keys.Space, (_, _1) => networkClient?.SendToServer(new RequestStartPacket()));
ball = Universe.FindRequiredBehaviour<Ball>();
networkClient = Universe.FindBehaviour<INetworkCommunicatorClient>();
Ball = Universe.FindRequiredBehaviour<Ball>();
networkServer = Universe.FindBehaviour<INetworkCommunicatorServer>();
logger = Universe.FindBehaviour<ILogger>();
}
@ -60,18 +53,34 @@ public class PongManager : Behaviour, INetworkEntity, IFirstFrameUpdate,
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()
{
ScoreLeft = ScoreRight = 0;
IsGameInProgress = false;
PostScoreUpdate();
Ball.ResetBall();
OnReset?.Invoke(this);
}
private void PostScoreUpdate()
{
ball.ResetBall();
Ball.ResetBall();
if (networkServer is not null)
{
@ -82,12 +91,13 @@ public class PongManager : Behaviour, INetworkEntity, IFirstFrameUpdate,
int halfwayScore = (int)(WinScore * .5f);
if (ScoreLeft > halfwayScore || ScoreRight > halfwayScore)
{
IsGameInProgress = false;
OnFinished?.Invoke(this);
logger?.Log(this, $"Game finished");
return;
}
ball.LaunchBall(GetBallLaunchDirection());
Ball.LaunchBall(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}");
}
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
{
public int Left { get; set; }

View File

@ -66,6 +66,11 @@ public static class PongUniverse
PongManager pongManager = universe.InstantiateUniverseObject().SetUniverseObject("Pong Game Manager")
.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")