143 Commits

Author SHA1 Message Date
64ee9a0789 chore: added game icon 2025-07-21 00:22:55 +03:00
452ce426a0 chore: changed desktop executable's name to Pong 2025-07-21 00:07:02 +03:00
ad5f424298 fix: desktop outputs not working on Windows 2025-07-21 00:04:12 +03:00
9302c7e867 chore: improved client paddle prediction 2025-07-20 20:27:08 +03:00
80725f454f chore: updated paddle sync code for server side by rewinding paddle 2025-07-20 20:25:02 +03:00
fcc5350b56 fix: clients unable to connect via hostname sometimes 2025-07-20 18:36:27 +03:00
1bf8238925 chore: updated launch.json logging 2025-07-12 16:32:06 +03:00
b641ebde08 chore: updated launch & tasks json files for better cross-platform development 2025-07-12 16:14:16 +03:00
9bf6cabe23 refactor: removed noise from class names 2025-07-09 22:21:01 +03:00
cd66eeeeef chore: updated launch.json for linux 2025-07-09 22:06:39 +03:00
9f6958e5ba chore: added title changing for clients for better debugging 2025-07-06 22:24:09 +03:00
42394f4625 chore: updated engine events 2025-07-06 22:23:52 +03:00
22f96458a6 refactor: connection ping & round trip in milliseconds properties added 2025-07-06 20:09:25 +03:00
a76905f31e fix: local id tracking added for issues caused by commit 08f52e9b72 2025-07-06 20:08:44 +03:00
e9ca71b87e chore: set server and clients to connect before first update 2025-07-06 19:52:11 +03:00
46991699a1 fix: forgotten send to all refactor of packet delivery types 2025-07-06 17:26:41 +03:00
08f52e9b72 refactor: client & server connections separated for id tracking 2025-07-06 17:22:50 +03:00
9f8578af9b chore: log file naming updated to include milliseconds 2025-07-06 16:40:09 +03:00
ac9cb72914 refactor: added packet delivery type for networking 2025-07-06 16:23:37 +03:00
875f865bd3 fix: lag clamping for the server side 2025-07-05 22:20:41 +03:00
15958ad402 chore: added nulastudio.NetBeauty to tidy up output directory 2025-06-29 23:29:29 +03:00
1230dc84ab chore: renamed logs directory to Logs 2025-06-29 23:08:28 +03:00
5d8f5d39be chore: paddle and ball tweens updated for smoother gameplay 2025-06-29 22:50:09 +03:00
ceea23c5ae chore: added paddle lag compensation for client inputs 2025-06-29 16:09:05 +03:00
afd4ae8327 feat: server health and statistics endpoints added 2025-06-29 14:45:45 +03:00
d41246b6f6 feat: pong reset scores on game start 2025-06-28 23:01:16 +03:00
0ec047cc23 feat: litenetlib connection to string method added 2025-06-28 23:01:01 +03:00
b238235c15 feat: added pong score update packets 2025-06-28 22:44:00 +03:00
9c90a804c3 chore: updated engine 2025-06-28 22:19:11 +03:00
5cfed3ba56 refactor: string id to IConnection parameters for sending network packets 2025-06-22 20:35:00 +03:00
bc6aaa865a feat: client configuration file added 2025-06-21 00:41:50 +03:00
5df09f64e2 refactor: pong universe client and server application code separated 2025-06-21 00:35:40 +03:00
abc467d1ea ci: lowered docker image size by half by using alpine linux 2025-06-19 23:50:48 +03:00
43c4eb6e4c chore: added litenetlib logs 2025-06-18 18:29:43 +03:00
0047111244 feat: added loggers to the universe 2025-06-18 17:43:12 +03:00
92357627a1 chore: updated wrong vscode launch compound 2025-06-18 17:17:49 +03:00
23fa1f06e9 chore: removed timestamp on paddle packet 2025-06-18 16:13:25 +03:00
d89af5ccad feat: IConnection interface added for connection information 2025-06-17 23:29:53 +03:00
7cc3bb4d83 feat: basic dockerfile added for server 2025-06-16 23:27:09 +03:00
997ac72c4e chore: removed content builder from share project 2025-06-16 19:46:00 +03:00
0987bf7e91 refactor: Apos to Triangles 2025-06-15 16:36:01 +03:00
648be2738f fix: ball sync issues caused by network lerp fixed 2025-06-15 13:27:06 +03:00
43875a6069 chore: ball speedup increased 2025-06-14 23:33:19 +03:00
41f6dba5e4 chore: removed unnecessary engine converter 2025-06-07 11:00:13 +03:00
2294c06bf9 refactor: added server project 2025-06-06 23:54:03 +03:00
b835fb1582 fix: paddles being knocked back by the ball collision 2025-06-05 23:32:14 +03:00
a0a3d97395 chore: updated engine again 2025-06-05 23:24:31 +03:00
4710b8364c chore: updated engine with non-universe-object managers 2025-06-04 00:03:26 +03:00
29df4a36e9 chore: updated engine to use MonoGame integrations 2025-06-04 00:00:21 +03:00
1ff26f5be4 refactor: renamed delegate type to be less confusing 2025-06-01 11:28:14 +03:00
e27b825990 perf: network manager registration memory allocation reduced further by removing boxing on some methods 2025-06-01 11:19:33 +03:00
4824496f1a fix: network manager not subscribing to communicator properly 2025-06-01 10:20:06 +03:00
f3b7ccaf70 refactor: network manager refactored for readability 2025-06-01 02:21:24 +03:00
475d82ba02 chore: added one client one server compound for launch.json 2025-05-31 23:22:43 +03:00
b3dd0f72a4 fix: network manager not unmonitoring removed universe objects 2025-05-31 23:22:12 +03:00
f3ff1b74d2 perf: reduced network manager memory allocations 2025-05-31 22:53:53 +03:00
8901a5469f perf: improved networking code to use the new events 2025-05-31 20:25:30 +03:00
52682d42b6 chore: updated engine to use the new events 2025-05-31 00:34:27 +03:00
0acd119099 refactor: implemented new IBehaviourCollector 2025-05-29 23:17:44 +03:00
3c38cb4159 perf: removed obsolete method calls and switched to for in draw calls 2025-05-29 22:49:51 +03:00
59059fa09d feat: paddle networking 2025-05-28 22:09:49 +03:00
cbda44c2d5 feat: ball networking 2025-05-28 17:18:15 +03:00
f20aabcacf chore: unused types removed 2025-05-28 16:56:07 +03:00
da284eda95 fix: build task 2025-05-27 11:36:37 +03:00
d0ef2ef224 chore: de-networked pong manager 2025-05-26 23:07:37 +03:00
bddf00ce7a perf: packet listener methods are now cached for performance 2025-05-26 22:09:40 +03:00
29829bbaa6 refactor: entity packets now directly go into the target entity 2025-05-26 21:58:09 +03:00
12f4950ffb refactor: network implementations switched to universe objects 2025-05-25 13:34:45 +03:00
0da5ac6f57 chore: updated engine to the version that fixes first frame calls 2025-05-24 20:06:09 +03:00
79922fadbe chore: improved paddle synchronization 2025-05-24 17:09:13 +03:00
0f0180d435 refactor: client is now running on a different thread 2025-05-24 17:08:58 +03:00
ed6f783180 chore: engine update 2025-05-22 23:52:13 +03:00
2853a6130b feat: basic pong manager networking added 2025-05-21 22:45:21 +03:00
8f9c9f77f0 chore: formatted usings in network packers 2025-05-21 22:43:26 +03:00
2b29636b4f fix: network manager looking up wrong type of interface to register listeners 2025-05-19 23:59:41 +03:00
4f3a2b4a4e refactor: paddle packets renamed and cleaned up 2025-05-19 22:39:55 +03:00
57868ce178 feat: client & server packet listener interfaces added 2025-05-19 22:24:06 +03:00
214c37e63f feat: working networked paddles 2025-05-18 00:49:14 +03:00
54bdcd93b2 fix: vector 2d & 3d's x values being read as int fixed 2025-05-18 00:48:11 +03:00
721d63cf66 refactor: network entity packets 2025-05-17 22:17:25 +03:00
03082ab43b feat: engine data type packers 2025-05-16 21:55:20 +03:00
6591326b70 feat: network manager listens to all received packets 2025-05-12 19:18:02 +03:00
0153d4cf69 chore: added vscode launch configurations for server and client 2025-05-11 11:51:57 +03:00
94f7d4acfd fix: renamed old classes and fixed packet registration issues 2025-05-11 11:51:25 +03:00
23a0c8e893 feat: added revised version of the old networking system 2025-05-11 00:02:53 +03:00
cd3e23b427 refactor: cleaned up some code 2025-05-11 00:02:38 +03:00
d13233ce77 chore: updated engine 2025-04-15 23:42:06 +03:00
34aa4dbb69 chore: updated engine 2025-04-13 22:24:56 +03:00
51854a3e59 chore: updated engine 2025-04-13 13:18:35 +03:00
67e46cdaf3 chore: updated engine 2025-04-11 20:03:03 +03:00
73ae55e1d4 refactor: Actions to Delegates 2024-07-15 01:17:05 +03:00
a35e25eb31 refactor: Renamed Cacher to Collector 2024-02-09 17:52:23 +03:00
775f24c560 chore: Engine Update 2024-02-09 11:53:57 +03:00
257e414c2a feat: CameraController Rotated Movement 2024-02-01 18:50:06 +03:00
0cb8170530 chore: Engine Time Update 2024-02-01 18:47:27 +03:00
bbf71f0be6 chore: Engine Update 2024-02-01 15:37:16 +03:00
41a17219b6 chore: Engine Update 2024-02-01 15:35:40 +03:00
29e79cdf26 chore: Engine Update 2024-02-01 14:52:12 +03:00
b7a4fe9cb0 chore: Engine Update 2024-02-01 14:43:13 +03:00
da83802427 chore: Engine Update 2024-02-01 11:33:55 +03:00
364c383e08 chore: ICamera2D to Engine 2024-01-31 17:30:37 +03:00
1fd8060857 feat: Applied ICamera2D 2024-01-31 17:28:41 +03:00
35933dfd14 feat: ICamera2D 2024-01-31 17:23:24 +03:00
d66b119119 refactor: Constructor Placements 2024-01-31 14:40:14 +03:00
e7ee460323 feat: Random Ball Start Direction 2024-01-31 13:56:32 +03:00
e86cf71010 fix: Build Errors 2024-01-31 13:43:04 +03:00
a357f0fdcb refactor: Removed Unnecessary Graphics Folder 2024-01-31 12:00:14 +03:00
ff3202eda9 refactor: Renamed Behaviours 2024-01-31 11:52:22 +03:00
8929d3282f refactor: Removed Unused Behaviours 2024-01-31 11:50:38 +03:00
bc7925dbab refactor: Text Behaviour Font Constructor 2024-01-31 11:23:07 +03:00
80ee5d760a refactor: Unnecessary Declarations Removed 2024-01-31 11:20:14 +03:00
de2d5fb446 refactor: Removed Static GamePong Fields 2024-01-31 11:06:42 +03:00
753c0031cd feat: Upgraded Engine to BehaviourCachers 2024-01-31 10:09:41 +03:00
a734f52b38 refactor: Removed Unnecessary Camera Viewport Assignment 2024-01-30 17:31:25 +03:00
4cb9c7dc8d refactor: Shortened Code 2024-01-30 17:03:04 +03:00
fe4618abd3 fix: Warning Fix 2024-01-30 15:14:45 +03:00
283556d329 refactor: Game1 to GamePong 2024-01-30 15:14:09 +03:00
5ef3d491c3 refactor: Math.Pow to Math.Sqr 2024-01-30 15:09:04 +03:00
46f589d3aa feat: Camera Reset 2024-01-30 14:35:24 +03:00
b7d49e41c3 refactor: General 2024-01-30 14:11:58 +03:00
677fec7acc refactor: Pong Reset Key Press Moved Into PongScoreboard 2024-01-30 13:22:29 +03:00
a9c7b8b660 refactor: Camera Controller 2024-01-30 13:16:07 +03:00
958a75552f refactor: MonoGameCamera 2024-01-30 12:07:25 +03:00
3717701171 refactor: Engine.Core ICamera Removal 2024-01-30 12:03:31 +03:00
4e95a3a420 feat: Ball Bounce Depending on Side Distance 2024-01-29 17:58:42 +03:00
ad60412d5f feat: Ball Speed Up Over Time 2024-01-29 16:16:58 +03:00
0b2158cc7b fix: Ball Not Resetting Position On Reset 2024-01-28 16:25:53 +03:00
3580469a07 refactor: Removed Shape Center Circles 2024-01-28 16:09:06 +03:00
a7e8787579 style: Moved Play Area Boxes Further Out 2024-01-28 15:55:35 +03:00
45df32dabb feat: Implemented Basic Mechanics 2024-01-28 15:45:39 +03:00
b3af693678 chore: Engine Update 2024-01-28 15:45:15 +03:00
c87827d1ef fix: UbuntuMono Smooth Text Problems 2024-01-28 15:45:04 +03:00
c2d2e1fd52 feat: TextBehaviour 2024-01-28 15:44:33 +03:00
7ad2cc5912 refactor: MovementBallBehaviour 2024-01-28 15:44:20 +03:00
ca92b12833 feat: Score Boundary 2024-01-28 15:33:45 +03:00
fbe3087502 feat: PongScoreboard 2024-01-28 15:33:36 +03:00
ef347320fd feat: Circle & Shape Behaviours IsActive Check 2024-01-28 15:33:15 +03:00
1207d04d50 feat: Ubuntu Mono Font Added & Removed Old Unusued Sprites 2024-01-28 15:32:45 +03:00
26f11b5cc0 refactor: Circle Behaviour Constructors Improved Readability 2024-01-27 22:45:49 +03:00
481a49bb52 chore: Engine Update 2024-01-27 21:51:12 +03:00
ffe4fc044e feat: Basic Pong Setting 2024-01-27 21:50:28 +03:00
91b05c97c4 Merge branch 'physics' 2024-01-27 20:39:25 +03:00
11074b89d5 chore: Engine Update 2023-11-30 17:54:40 +03:00
92 changed files with 3117 additions and 1149 deletions

484
.gitignore vendored Normal file
View File

@@ -0,0 +1,484 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from `dotnet new gitignore`
# dotenv files
.env
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET
project.lock.json
project.fragment.lock.json
artifacts/
# Tye
.tye/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
.idea
##
## Visual studio for Mac
##
# globs
Makefile.in
*.userprefs
*.usertasks
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.tar.gz
tarballs/
test-results/
# Mac bundle stuff
*.dmg
*.app
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# Vim temporary swap files
*.swp

80
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,80 @@
{
"version": "0.2.0",
"configurations": [
{
"name": ".NET Launch (Client) 1",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build-client",
"program": "${workspaceFolder}/Platforms/Desktop/bin/Debug/net9.0/Desktop.dll",
"args": [],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"console": "internalConsole",
"justMyCode": false,
"logging": {
"moduleLoad": false,
"programOutput": true
}
},
{
"name": ".NET Launch (Client) 2",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build-client",
"program": "${workspaceFolder}/Platforms/Desktop/bin/Debug/net9.0/Desktop.dll",
"args": [],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"console": "internalConsole",
"justMyCode": false,
"logging": {
"moduleLoad": false,
"programOutput": true
}
},
{
"name": ".NET Launch (Server)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build-server",
"program": "${workspaceFolder}/Platforms/Server/bin/Debug/net9.0/Server.dll",
"args": [],
"env": {
"PORT": "8888",
},
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"console": "internalConsole",
"justMyCode": false,
"logging": {
"moduleLoad": false,
"programOutput": true
}
}
],
"compounds": [
{
"name": ".NET Launch Client & Server",
"configurations": [
".NET Launch (Server)",
".NET Launch (Client) 1"
]
},
{
"name": ".NET Launch 2 Client & 1 Server",
"configurations": [
".NET Launch (Server)",
".NET Launch (Client) 1",
".NET Launch (Client) 2"
]
},
{
"name": ".NET Launch 2 Clients",
"configurations": [
".NET Launch (Client) 1",
".NET Launch (Client) 2"
]
}
]
}

33
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,33 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build-client",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Platforms/Desktop"
],
"problemMatcher": "$msCompile",
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "build-server",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Platforms/Server"
],
"problemMatcher": "$msCompile",
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

16
Dockerfile Normal file
View File

@@ -0,0 +1,16 @@
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /App
COPY . ./
RUN dotnet restore
RUN dotnet publish Platforms/Server -c Release -r linux-musl-x64 --self-contained true -o out
FROM alpine:latest
WORKDIR /App
COPY --from=build /App/out .
RUN apk add --no-cache icu-libs
ENTRYPOINT ["./Server"]

2
Engine

Submodule Engine updated: 6a104d8abd...83b155fc5e

View File

@@ -1,27 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/bin/Debug/net8.0/${workspaceFolderBasename}.dll",
"args": [],
"cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"
}
]
}

View File

@@ -1,7 +0,0 @@
{
"cSpell.words": [
"AABB",
"DAABB",
"Syntriax"
]
}

View File

@@ -1,15 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "dotnet",
"task": "build",
"problemMatcher": ["$msCompile"],
"group": {
"kind": "build",
"isDefault": true
},
"label": "build"
}
]
}

View File

@@ -1,23 +0,0 @@
using Microsoft.Xna.Framework;
using Apos.Shapes;
using Syntriax.Engine.Physics2D.Primitives;
namespace Pong.Behaviours;
public class CircleBehaviour : Syntriax.Engine.Physics2D.Collider2DCircleBehaviour, IDisplayableShape
{
public CircleBehaviour(Circle circle) { this.CircleLocal = circle; }
public CircleBehaviour(Circle circle, float Thickness) { this.CircleLocal = circle; this.Thickness = Thickness; }
public CircleBehaviour(Circle circle, Color color) { this.CircleLocal = circle; Color = color; }
public CircleBehaviour(Circle circle, Color color, float Thickness) { this.CircleLocal = circle; this.Thickness = Thickness; Color = color; }
public Color Color { get; set; } = Color.White;
public float Thickness { get; set; } = .5f;
public void Draw(ShapeBatch shapeBatch)
{
shapeBatch.BorderCircle(CircleWorld.Center.ToDisplayVector2(), CircleWorld.Radius, Color);
}
}

View File

@@ -1,8 +0,0 @@
using Apos.Shapes;
namespace Pong.Behaviours;
public interface IDisplayableShape
{
void Draw(ShapeBatch shapeBatch);
}

View File

@@ -1,107 +0,0 @@
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Input;
using Syntriax.Engine.Core;
using Syntriax.Engine.Input;
namespace Pong.Behaviours;
public class KeyboardInputsBehaviour : BehaviourOverride, IButtonInputs<Keys>
{
private readonly Dictionary<Keys, Action<IButtonInputs<Keys>, Keys>> OnPressed = new(256);
private readonly Dictionary<Keys, Action<IButtonInputs<Keys>, Keys>> OnReleased = new(256);
private int cachePressedCurrentlyCount = 0;
private readonly Keys[] cachePressedCurrently = new Keys[256];
private int cachePressedPreviouslyCount = 0;
private readonly Keys[] cachePressedPreviously = new Keys[256];
public void RegisterOnPress(Keys key, Action<IButtonInputs<Keys>, Keys> callback)
{
if (OnPressed.TryGetValue(key, out var action))
{
action += callback;
return;
}
OnPressed.Add(key, callback);
}
public void UnregisterOnPress(Keys key, Action<IButtonInputs<Keys>, Keys> callback)
{
if (OnPressed.TryGetValue(key, out var action))
action -= callback;
}
public void RegisterOnRelease(Keys key, Action<IButtonInputs<Keys>, Keys> callback)
{
if (OnReleased.TryGetValue(key, out var action))
{
action += callback;
return;
}
OnReleased.Add(key, callback);
}
public void UnregisterOnRelease(Keys key, Action<IButtonInputs<Keys>, Keys> callback)
{
if (OnReleased.TryGetValue(key, out var action))
action -= callback;
}
protected override void OnUpdate()
{
KeyboardState keyboardState = Keyboard.GetState();
keyboardState.GetPressedKeys(cachePressedCurrently);
cachePressedCurrentlyCount = keyboardState.GetPressedKeyCount();
for (int i = 0; i < cachePressedCurrentlyCount; i++)
{
Keys currentlyPressedKey = cachePressedCurrently[i];
if (!OnPressed.TryGetValue(currentlyPressedKey, out var action))
continue;
if (WasPressed(currentlyPressedKey))
continue;
action.Invoke(this, currentlyPressedKey);
}
for (int i = 0; i < cachePressedPreviouslyCount; i++)
{
Keys previouslyPressedKey = cachePressedPreviously[i];
if (!OnReleased.TryGetValue(previouslyPressedKey, out var action))
continue;
if (IsPressed(previouslyPressedKey))
continue;
action.Invoke(this, previouslyPressedKey);
}
Array.Copy(cachePressedCurrently, cachePressedPreviously, cachePressedCurrentlyCount);
cachePressedPreviouslyCount = cachePressedCurrentlyCount;
}
public bool IsPressed(Keys key)
{
for (int i = 0; i < cachePressedCurrentlyCount; i++)
if (cachePressedCurrently[i] == key)
return true;
return false;
}
public bool WasPressed(Keys key)
{
for (int i = 0; i < cachePressedPreviouslyCount; i++)
if (cachePressedPreviously[i] == key)
return true;
return false;
}
}

View File

@@ -1,106 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
namespace Pong.Behaviours;
public class MonoGameCameraBehaviour : BehaviourOverride, ICamera
{
public Action<ICamera>? OnPositionChanged { get; set; } = null;
public Action<ICamera>? OnMatrixTransformChanged { get; set; } = null;
public Action<ICamera>? OnViewportChanged { get; set; } = null;
public Action<ICamera>? OnRotationChanged { get; set; } = null;
public Action<ICamera>? OnZoomChanged { get; set; } = null;
private Matrix _matrixTransform = Matrix.Identity;
private Viewport _viewport = default;
private float _zoom = 1f;
public Matrix MatrixTransform
{
get => _matrixTransform;
set
{
if (_matrixTransform == value)
return;
_matrixTransform = value;
OnMatrixTransformChanged?.Invoke(this);
}
}
public Vector2D Position
{
get => Transform.Position;
set => Transform.Position = value;
}
public Viewport Viewport
{
get => _viewport;
set
{
if (_viewport.Equals(value))
return;
_viewport = value;
OnViewportChanged?.Invoke(this);
}
}
public float Zoom
{
get => _zoom;
set
{
float newValue = value >= .1f ? value : .1f;
if (_zoom == newValue)
return;
_zoom = newValue;
OnZoomChanged?.Invoke(this);
}
}
public float Rotation
{
get => Transform.Rotation;
set => Transform.Rotation = value;
}
public Action<IAssignableTransform>? OnTransformAssigned { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
ITransform IAssignableTransform.Transform => throw new NotImplementedException();
public void Update()
{
MatrixTransform =
Matrix.CreateTranslation(new Vector3(-Position.X, Position.Y, 0f)) *
Matrix.CreateRotationZ(Rotation) *
Matrix.CreateScale(Zoom) *
Matrix.CreateTranslation(new Vector3(_viewport.Width * .5f, _viewport.Height * .5f, 0f));
}
protected override void OnInitialize()
{
Transform.OnRotationChanged += OnTransformRotationChanged;
Transform.OnPositionChanged += OnTransformPositionChanged;
}
protected override void OnFinalize()
{
Transform.OnRotationChanged -= OnTransformRotationChanged;
Transform.OnPositionChanged -= OnTransformPositionChanged;
}
private void OnTransformRotationChanged(ITransform _) => OnRotationChanged?.Invoke(this);
private void OnTransformPositionChanged(ITransform _) => OnPositionChanged?.Invoke(this);
public bool Assign(ITransform transform) => GameObject.Assign(transform);
}

View File

@@ -1,50 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Syntriax.Engine.Core;
using Syntriax.Engine.Input;
using Syntriax.Engine.Physics2D.Abstract;
namespace Pong.Behaviours;
public class MovementBallBehaviour(Vector2D StartDirection, float Speed) : BehaviourOverride
{
public Vector2D StartDirection { get; private set; } = Vector2D.Normalize(StartDirection);
public float Speed { get; set; } = Speed;
protected override void OnFirstActiveFrame()
{
if (!BehaviourController.TryGetBehaviour(out IRigidBody2D? rigidBody))
throw new Exception($"Where's my {nameof(IRigidBody2D)}????");
rigidBody.Velocity = StartDirection * Speed;
}
// protected override void OnUpdate(GameTime time)
// {
// GameObject.Transform.Position += StartDirection * (time.ElapsedGameTime.Nanoseconds * .001f) * Speed;
// float absY = MathF.Abs(GameObject.Transform.Position.Y);
// float differenceY = absY - PlayAreaBehaviour.PlayArea.Y * 0.5f;
// if (differenceY > 0f)
// {
// if (GameObject.Transform.Position.Y > 0f)
// GameObject.Transform.Position -= Vector2.UnitY * differenceY * 2f;
// else
// GameObject.Transform.Position += Vector2.UnitY * differenceY * 2f;
// StartDirection = new(StartDirection.X, -StartDirection.Y);
// }
// float absX = MathF.Abs(GameObject.Transform.Position.X);
// float differenceX = absX - PlayAreaBehaviour.PlayArea.X * 0.5f;
// if (differenceX > 0f)
// {
// if (GameObject.Transform.Position.X > 0f)
// GameObject.Transform.Position -= Vector2.UnitX * differenceX * 2f;
// else
// GameObject.Transform.Position += Vector2.UnitX * differenceX * 2f;
// StartDirection = new(-StartDirection.X, StartDirection.Y);
// }
// }
}

View File

@@ -1,62 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Syntriax.Engine.Core;
using Syntriax.Engine.Input;
namespace Pong.Behaviours;
public class MovementBoxBehaviour(Keys Up, Keys Down, float High, float Low, float Speed) : BehaviourOverride
{
private Keys Up { get; } = Up;
private Keys Down { get; } = Down;
public float High { get; } = High;
public float Low { get; } = Low;
public float Speed { get; set; } = Speed;
private bool isUpPressed = false;
private bool isDownPressed = false;
private IButtonInputs<Keys> inputs = null!;
protected override void OnUpdate()
{
if (isUpPressed && isDownPressed)
return;
if (isUpPressed)
GameObject.Transform.Position = GameObject.Transform.Position + Vector2D.Up * (float)Time.Elapsed.TotalSeconds * Speed;
else if (isDownPressed)
GameObject.Transform.Position = GameObject.Transform.Position + -Vector2D.Up * (float)Time.Elapsed.TotalSeconds * Speed;
GameObject.Transform.Position = new Vector2D(GameObject.Transform.Position.X, MathF.Max(MathF.Min(GameObject.Transform.Position.Y, High), Low));
}
protected override void OnFirstActiveFrame()
{
if (!BehaviourController.TryGetBehaviour<IButtonInputs<Keys>>(out var behaviourResult))
throw new Exception($"{nameof(IButtonInputs<Keys>)} is missing on ${GameObject.Name}.");
inputs = behaviourResult;
inputs.RegisterOnPress(Up, OnUpPressed);
inputs.RegisterOnRelease(Up, OnUpReleased);
inputs.RegisterOnPress(Down, OnDownPressed);
inputs.RegisterOnRelease(Down, OnDownReleased);
}
protected override void OnFinalize()
{
inputs.UnregisterOnPress(Up, OnUpPressed);
inputs.UnregisterOnRelease(Up, OnUpReleased);
inputs.UnregisterOnPress(Down, OnDownPressed);
inputs.UnregisterOnRelease(Down, OnDownReleased);
}
private void OnUpPressed(IButtonInputs<Keys> inputs, Keys keys) => isUpPressed = true;
private void OnUpReleased(IButtonInputs<Keys> inputs, Keys keys) => isUpPressed = false;
private void OnDownPressed(IButtonInputs<Keys> inputs, Keys keys) => isDownPressed = true;
private void OnDownReleased(IButtonInputs<Keys> inputs, Keys keys) => isDownPressed = false;
}

View File

@@ -1,13 +0,0 @@
using System;
using Microsoft.Xna.Framework.Input;
using Syntriax.Engine.Core;
namespace Pong.Behaviours;
public class MovementMouseBehaviour : BehaviourOverride
{
protected override void OnUpdate()
{
GameObject.Transform.Position = Mouse.GetState().Position.ToVector2D().ApplyDisplayScale();
}
}

View File

@@ -1,26 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Syntriax.Engine.Core;
namespace Pong.Behaviours;
public class PlayAreaBehaviour : BehaviourOverride
{
public Action<PlayAreaBehaviour>? OnPlayAreaChanged { get; set; } = null;
private Vector2 _playArea = Vector2.Zero;
public Vector2 PlayArea
{
get => _playArea;
set
{
if (_playArea == value)
return;
_playArea = value;
OnPlayAreaChanged?.Invoke(this);
}
}
}

View File

@@ -1,25 +0,0 @@
using Syntriax.Engine.Core;
namespace Pong.Behaviours;
public class RotatableBehaviour : BehaviourOverride
{
private KeyboardInputsBehaviour? inputs = null;
protected override void OnFirstActiveFrame()
{
if (!BehaviourController.TryGetBehaviour(out inputs))
inputs = BehaviourController.AddBehaviour<KeyboardInputsBehaviour>();
}
protected override void OnUpdate()
{
if (inputs is null)
return;
if (inputs.IsPressed(Microsoft.Xna.Framework.Input.Keys.NumPad4))
Transform.Rotation += Time.Elapsed.Nanoseconds * 0.0025f;
if (inputs.IsPressed(Microsoft.Xna.Framework.Input.Keys.NumPad6))
Transform.Rotation -= Time.Elapsed.Nanoseconds * 0.0025f;
}
}

View File

@@ -1,47 +0,0 @@
using Microsoft.Xna.Framework;
using Apos.Shapes;
using Syntriax.Engine.Core;
using Syntriax.Engine.Input;
using Syntriax.Engine.Physics2D.Abstract;
using Syntriax.Engine.Physics2D.Primitives;
namespace Pong.Behaviours;
public class ShapeAABBBehaviour : BehaviourOverride, IDisplayableShape
{
private IShapeCollider2D? shapeCollider = null;
public ShapeAABBBehaviour() { }
public ShapeAABBBehaviour(float Thickness) { this.Thickness = Thickness; }
public ShapeAABBBehaviour(Color color) { Color = color; }
public ShapeAABBBehaviour(Color color, float Thickness) { this.Thickness = Thickness; Color = color; }
public Color Color { get; set; } = Color.White;
public float Thickness { get; set; } = .5f;
public bool display = true;
protected override void OnFirstActiveFrame()
{
BehaviourController.TryGetBehaviour(out shapeCollider);
if (BehaviourController.TryGetBehaviour(out IButtonInputs<Microsoft.Xna.Framework.Input.Keys>? keys))
keys.RegisterOnPress(Microsoft.Xna.Framework.Input.Keys.D, (_1, _2) => display = !display);
}
public void Draw(ShapeBatch shapeBatch)
{
if (!display)
return;
if (shapeCollider is null)
return;
AABB aabb = AABB.FromVectors(shapeCollider.ShapeWorld);
shapeBatch.BorderCircle(aabb.Center.ToDisplayVector2(), 7.5f, Color.Beige);
shapeBatch.DrawRectangle(aabb.Center.ApplyDisplayScale().Subtract(aabb.SizeHalf).ToVector2(), aabb.Size.ToVector2(), Color.Transparent, Color.Blue);
}
}

View File

@@ -1,29 +0,0 @@
using Microsoft.Xna.Framework;
using Apos.Shapes;
using Syntriax.Engine.Physics2D.Primitives;
namespace Pong.Behaviours;
public class ShapeBehaviour : Syntriax.Engine.Physics2D.Collider2DShapeBehaviour, IDisplayableShape
{
public ShapeBehaviour(Shape Shape) { this.ShapeLocal = Shape; }
public ShapeBehaviour(Shape Shape, float Thickness) { this.ShapeLocal = Shape; this.Thickness = Thickness; }
public ShapeBehaviour(Shape Shape, Color color) { this.ShapeLocal = Shape; Color = color; }
public ShapeBehaviour(Shape Shape, Color color, float Thickness) { this.ShapeLocal = Shape; this.Thickness = Thickness; Color = color; }
public Color Color { get; set; } = Color.White;
public float Thickness { get; set; } = .5f;
public void Draw(ShapeBatch shapeBatch)
{
int count = ShapeWorld.Vertices.Count;
shapeBatch.BorderCircle(Transform.Position.ToDisplayVector2(), 5f, Color.DarkRed);
for (int i = 0; i < count - 1; i++)
shapeBatch.DrawLine(ShapeWorld[i].ToDisplayVector2(), ShapeWorld[i + 1].ToDisplayVector2(), Thickness, Color, Color);
shapeBatch.DrawLine(ShapeWorld[0].ToDisplayVector2(), ShapeWorld[^1].ToDisplayVector2(), Thickness, Color, Color);
}
}

View File

@@ -1,63 +0,0 @@
#----------------------------- Global Properties ----------------------------#
/outputDir:bin/$(Platform)
/intermediateDir:obj/$(Platform)
/platform:DesktopGL
/config:
/profile:Reach
/compress:False
#-------------------------------- References --------------------------------#
#---------------------------------- Content ---------------------------------#
#begin Sprites/Circle.png
/importer:TextureImporter
/processor:TextureProcessor
/processorParam:ColorKeyColor=255,0,255,255
/processorParam:ColorKeyEnabled=True
/processorParam:GenerateMipmaps=False
/processorParam:PremultiplyAlpha=True
/processorParam:ResizeToPowerOfTwo=False
/processorParam:MakeSquare=False
/processorParam:TextureFormat=Color
/build:Sprites/Circle.png
#begin Sprites/Circle.png
/importer:TextureImporter
/processor:TextureProcessor
/processorParam:ColorKeyColor=255,0,255,255
/processorParam:ColorKeyEnabled=True
/processorParam:GenerateMipmaps=False
/processorParam:PremultiplyAlpha=True
/processorParam:ResizeToPowerOfTwo=False
/processorParam:MakeSquare=False
/processorParam:TextureFormat=Color
/build:Sprites/Circle.png
#begin Sprites/Pixel.png
/importer:TextureImporter
/processor:TextureProcessor
/processorParam:ColorKeyColor=255,0,255,255
/processorParam:ColorKeyEnabled=True
/processorParam:GenerateMipmaps=False
/processorParam:PremultiplyAlpha=True
/processorParam:ResizeToPowerOfTwo=False
/processorParam:MakeSquare=False
/processorParam:TextureFormat=Color
/build:Sprites/Pixel.png
#begin Sprites/Pixel.png
/importer:TextureImporter
/processor:TextureProcessor
/processorParam:ColorKeyColor=255,0,255,255
/processorParam:ColorKeyEnabled=True
/processorParam:GenerateMipmaps=False
/processorParam:PremultiplyAlpha=True
/processorParam:ResizeToPowerOfTwo=False
/processorParam:MakeSquare=False
/processorParam:TextureFormat=Color
/build:Sprites/Pixel.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 546 B

View File

@@ -1,23 +0,0 @@
using System.Runtime.CompilerServices;
using Microsoft.Xna.Framework;
using Syntriax.Engine.Core;
namespace Pong;
public static class EngineConverter
{
public readonly static Vector2D screenScale = Vector2D.Down + Vector2D.Right;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EngineTime ToEngineTime(this GameTime gameTime) => new(gameTime.TotalGameTime, gameTime.ElapsedGameTime);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2D ToVector2D(this Vector2 vector) => new(vector.X, vector.Y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2D ToVector2D(this Point point) => new(point.X, point.Y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2 ToVector2(this Vector2D vector) => new(vector.X, vector.Y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2 ToDisplayVector2(this Vector2D vector) => vector.Scale(screenScale).ToVector2();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2D ApplyDisplayScale(this Vector2D vector) => vector.Scale(screenScale);
}

View File

@@ -1,36 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RollForward>Major</RollForward>
<PublishReadyToRun>false</PublishReadyToRun>
<TieredCompilation>false</TieredCompilation>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>Icon.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<None Remove="Icon.ico" />
<None Remove="Icon.bmp" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Icon.ico" />
<EmbeddedResource Include="Icon.bmp" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Apos.Shapes" Version="0.2.3" />
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303" />
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.1.303" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Engine\Engine.Core\Engine.Core.csproj" />
<ProjectReference Include="..\Engine\Engine.Input\Engine.Input.csproj" />
<ProjectReference Include="..\Engine\Engine.Physics2D\Engine.Physics2D.csproj" />
</ItemGroup>
<Target Name="RestoreDotnetTools" BeforeTargets="Restore">
<Message Text="Restoring dotnet tools" Importance="High" />
<Exec Command="dotnet tool restore" />
</Target>
</Project>

View File

@@ -1,245 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Pong.Behaviours;
using Apos.Shapes;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Physics2D;
using Syntriax.Engine.Physics2D.Primitives;
namespace Pong;
public class Game1 : Game
{
private GraphicsDeviceManager _graphics = null!;
private PhysicsEngine2D engine;
private SpriteBatch _spriteBatch = null!;
private ShapeBatch _shapeBatch = null!;
public static GameManager gameManager = null!;
public static Sprite spriteBox = null!;
private MonoGameCameraBehaviour cameraBehaviour = null!;
public Game1()
{
engine = new PhysicsEngine2D();
_graphics = new GraphicsDeviceManager(this)
{
PreferredBackBufferWidth = 1024,
PreferredBackBufferHeight = 576,
GraphicsProfile = GraphicsProfile.HiDef
};
Content.RootDirectory = "Content";
IsMouseVisible = true;
}
protected override void Initialize()
{
gameManager = new();
// TODO: Add your initialization logic here
gameManager.Initialize();
base.Initialize();
}
protected override void LoadContent()
{
_spriteBatch = new SpriteBatch(GraphicsDevice);
_shapeBatch = new ShapeBatch(GraphicsDevice, Content);
// spriteBox = new Sprite() { Texture2D = Content.Load<Texture2D>("Sprites/Pixel") };
// Sprite spriteBall = new Sprite() { Texture2D = Content.Load<Texture2D>("Sprites/Circle") };
IGameObject gameObjectCamera = gameManager.InstantiateGameObject<GameObject>();
gameObjectCamera.Name = "Camera";
gameObjectCamera.Transform.Position = Vector2D.Zero;
cameraBehaviour = gameObjectCamera.BehaviourController.AddBehaviour<MonoGameCameraBehaviour>();
cameraBehaviour.Viewport = GraphicsDevice.Viewport;
gameManager.Camera = cameraBehaviour;
// IGameObject gameObjectCircle = gameManager.InstantiateGameObject<GameObject>();
// gameObjectCircle.Name = "Circle";
// gameObjectCircle.Transform.Position = new Vector2D(0f, -50f);
// gameObjectCircle.Transform.Scale = new Vector2D(25f, 25f);
// gameObjectCircle.BehaviourController.AddBehaviour<KeyboardInputsBehaviour>();
// gameObjectCircle.BehaviourController.AddBehaviour<MovementBoxBehaviour>(Keys.W, Keys.S, 268f, -268f, 400f);
// gameObjectCircle.BehaviourController.AddBehaviour<CircleBehaviour>(new Circle(Vector2D.Zero, 1f));
// engine.AddRigidBody(gameObjectCircle.BehaviourController.AddBehaviour<RigidBody2D>());
IGameObject gameObjectCircle2 = gameManager.InstantiateGameObject<GameObject>();
gameObjectCircle2.Name = "Circle2";
gameObjectCircle2.Transform.Position = new Vector2D(5f, 50f);
gameObjectCircle2.Transform.Scale = new Vector2D(25f, 25f);
gameObjectCircle2.BehaviourController.AddBehaviour<MovementMouseBehaviour>();
gameObjectCircle2.BehaviourController.AddBehaviour<CircleBehaviour>(new Circle(Vector2D.Zero, 1f));
engine.AddRigidBody(gameObjectCircle2.BehaviourController.AddBehaviour<RigidBody2D>());
IGameObject gameObjectDiamond = gameManager.InstantiateGameObject<GameObject>();
gameObjectDiamond.Name = "Diamond";
gameObjectDiamond.Transform.Position = new Vector2D(-150f, -150f);
gameObjectDiamond.Transform.Scale = new Vector2D(50f, 50f);
gameObjectDiamond.BehaviourController.AddBehaviour<KeyboardInputsBehaviour>();
gameObjectDiamond.BehaviourController.AddBehaviour<MovementBoxBehaviour>(Keys.W, Keys.S, 268f, -268f, 400f);
gameObjectDiamond.BehaviourController.AddBehaviour<RotatableBehaviour>();
gameObjectDiamond.BehaviourController.AddBehaviour<ShapeBehaviour>(new Shape([Vector2D.Up, Vector2D.One, Vector2D.Right, Vector2D.Down, -Vector2D.One, Vector2D.Left]));
gameObjectDiamond.BehaviourController.AddBehaviour<ShapeAABBBehaviour>();
engine.AddRigidBody(gameObjectDiamond.BehaviourController.AddBehaviour<RigidBody2D>());
IGameObject gameObjectBox = gameManager.InstantiateGameObject<GameObject>();
gameObjectBox.Name = "Box";
gameObjectBox.Transform.Position = new Vector2D(150f, -150f);
gameObjectBox.Transform.Scale = new Vector2D(100f, 100f);
gameObjectBox.BehaviourController.AddBehaviour<KeyboardInputsBehaviour>();
gameObjectBox.BehaviourController.AddBehaviour<MovementBoxBehaviour>(Keys.W, Keys.S, 268f, -268f, 400f);
gameObjectBox.BehaviourController.AddBehaviour<RotatableBehaviour>();
gameObjectBox.BehaviourController.AddBehaviour<ShapeBehaviour>(Shape.Pentagon);
gameObjectBox.BehaviourController.AddBehaviour<ShapeAABBBehaviour>();
engine.AddRigidBody(gameObjectBox.BehaviourController.AddBehaviour<RigidBody2D>());
for (int i = 3; i < 10; i++)
{
IGameObject Test = gameManager.InstantiateGameObject<GameObject>();
Test.Name = i.ToString();
Test.Transform.Position = new Vector2D((i - 6) * 150, 0f);
Test.Transform.Scale = new Vector2D(75f, 75f);
Test.BehaviourController.AddBehaviour<KeyboardInputsBehaviour>();
Test.BehaviourController.AddBehaviour<MovementBoxBehaviour>(Keys.W, Keys.S, 268f, -268f, 400f);
Test.BehaviourController.AddBehaviour<RotatableBehaviour>();
Test.BehaviourController.AddBehaviour<ShapeBehaviour>(Shape.CreateNgon(i));
Test.BehaviourController.AddBehaviour<ShapeAABBBehaviour>();
RigidBody2D rigidBody = Test.BehaviourController.AddBehaviour<RigidBody2D>();
rigidBody.AngularVelocity = 90f;
engine.AddRigidBody(rigidBody);
}
// IGameObject gameObjectShape = gameManager.InstantiateGameObject<GameObject>();
// gameObjectShape.Name = "Shape";
// gameObjectShape.Transform.Position = new Vector2D(250f, 0f);
// gameObjectShape.Transform.Scale = new Vector2D(100f, 100f);
// gameObjectShape.BehaviourController.AddBehaviour<KeyboardInputsBehaviour>();
// gameObjectShape.BehaviourController.AddBehaviour<MovementBoxBehaviour>(Keys.W, Keys.S, 268f, -268f, 400f);
// gameObjectShape.BehaviourController.AddBehaviour<RotatableBehaviour>();
// gameObjectShape.BehaviourController.AddBehaviour<ShapeBehaviour>(new Shape([Vector2D.Up, Vector2D.One, Vector2D.Right, Vector2D.Down, Vector2D.Zero, Vector2D.Left]));
// gameObjectShape.BehaviourController.AddBehaviour<ShapeAABBBehaviour>();
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
if (Keyboard.GetState().IsKeyDown(Keys.F))
{
if (_graphics.IsFullScreen)
return;
_graphics.PreferMultiSampling = false;
_graphics.PreferredBackBufferWidth = GraphicsDevice.Adapter.CurrentDisplayMode.Width;
_graphics.PreferredBackBufferHeight = GraphicsDevice.Adapter.CurrentDisplayMode.Height;
_graphics.IsFullScreen = true;
_graphics.ApplyChanges();
float previousScreenSize = MathF.Sqrt(MathF.Pow(cameraBehaviour.Viewport.Width, 2f) + MathF.Pow(cameraBehaviour.Viewport.Height, 2f));
float currentScreenSize = MathF.Sqrt(MathF.Pow(GraphicsDevice.Viewport.Width, 2f) + MathF.Pow(GraphicsDevice.Viewport.Height, 2f));
cameraBehaviour.Zoom /= previousScreenSize / currentScreenSize;
cameraBehaviour.Viewport = GraphicsDevice.Viewport;
}
if (Keyboard.GetState().IsKeyDown(Keys.U))
cameraBehaviour.Zoom += gameTime.ElapsedGameTime.Nanoseconds * 0.00025f;
if (Keyboard.GetState().IsKeyDown(Keys.J))
cameraBehaviour.Zoom -= gameTime.ElapsedGameTime.Nanoseconds * 0.00025f;
if (Keyboard.GetState().IsKeyDown(Keys.Q))
cameraBehaviour.BehaviourController.GameObject.Transform.Rotation += gameTime.ElapsedGameTime.Nanoseconds * 0.000025f;
if (Keyboard.GetState().IsKeyDown(Keys.E))
cameraBehaviour.BehaviourController.GameObject.Transform.Rotation -= gameTime.ElapsedGameTime.Nanoseconds * 0.000025f;
if (Keyboard.GetState().IsKeyDown(Keys.N))
{
seconds = 70f;
while (physicsTimer + 0.01f < seconds)
{
physicsTimer += 0.01f;
engine.Step(.01f);
}
}
if (Keyboard.GetState().IsKeyDown(Keys.M))
{
seconds = 0f;
while (physicsTimer - 0.01f > seconds)
{
physicsTimer -= 0.01f;
engine.Step(-.01f);
}
}
if (Keyboard.GetState().IsKeyDown(Keys.Space))
{
seconds += gameTime.ElapsedGameTime.Milliseconds * .005f;
while (physicsTimer + 0.01f < seconds)
{
Console.WriteLine($"Physics Timer: {physicsTimer}");
physicsTimer += 0.01f;
engine.Step(.01f);
}
}
if (Keyboard.GetState().IsKeyDown(Keys.B))
{
seconds -= gameTime.ElapsedGameTime.Milliseconds * .005f;
while (physicsTimer - 0.01f > seconds)
{
Console.WriteLine($"Physics Timer: {physicsTimer}");
physicsTimer -= 0.01f;
engine.Step(-.01f);
}
}
while (physicsTimer + 0.01f < gameTime.TotalGameTime.TotalMilliseconds * .001f)//seconds)
{
// Console.WriteLine($"Physics Timer: {physicsTimer}");
physicsTimer += 0.01f;
engine.Step(.01f);
}
gameManager.Update(gameTime.ToEngineTime());
base.Update(gameTime);
}
static float physicsTimer = 0f;
static float seconds = 0f;
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(new Color() { R = 32, G = 32, B = 32 });
// gameManager.Camera.Position = gameObjectBall.Transform.Position;
// Console.WriteLine($"Pos: {gameManager.Camera.Position}");
// TODO: Add your drawing code here
gameManager.PreDraw();
gameManager.Camera.Update();
_spriteBatch.Begin(SpriteSortMode.Deferred, transformMatrix: cameraBehaviour.MatrixTransform);
foreach (IGameObject gameObject in gameManager)
foreach (var displayable in gameObject.BehaviourController.GetBehaviours<IDisplayable>())
displayable.Draw(_spriteBatch);
_spriteBatch.End();
_shapeBatch.Begin(cameraBehaviour.MatrixTransform);
foreach (IGameObject gameObject in gameManager)
foreach (var displayableShape in gameObject.BehaviourController.GetBehaviours<IDisplayableShape>())
displayableShape.Draw(_shapeBatch);
_shapeBatch.End();
base.Draw(gameTime);
}
}

View File

@@ -1,26 +0,0 @@
using System;
namespace Syntriax.Engine.Core.Abstract;
/// <summary>
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="ISprite"/> field.
/// </summary>
public interface IAssignableSprite : IAssignable
{
/// <summary>
/// Callback triggered when the <see cref="ISprite"/> value has has been assigned a new value.
/// </summary>
Action<IAssignableSprite>? OnSpriteAssigned { get; set; }
/// <inheritdoc cref="ISprite" />
ISprite Sprite { get; }
/// <summary>
/// Assign a value to the <see cref="ISprite"/> field of this object
/// </summary>
/// <param name="sprite">New <see cref="ISprite"/> to assign.</param>
/// <returns>
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
/// </returns>
bool Assign(ISprite sprite);
}

View File

@@ -1,13 +0,0 @@
using System;
using Microsoft.Xna.Framework.Graphics;
namespace Syntriax.Engine.Core.Abstract;
// TODO Probably gonna have to rethink this
public interface ISprite
{
Action<ISprite>? OnTextureChanged { get; set; }
Texture2D Texture2D { get; set; }
}

View File

@@ -1,27 +0,0 @@
using System;
using Microsoft.Xna.Framework.Graphics;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
public class Sprite : ISprite
{
public Action<ISprite>? OnTextureChanged { get; set; }
private Texture2D _texture = null!;
public Texture2D Texture2D
{
get => _texture;
set
{
if (_texture == value)
return;
_texture = value;
OnTextureChanged?.Invoke(this);
}
}
}

View File

@@ -1,8 +0,0 @@
using Microsoft.Xna.Framework.Graphics;
namespace Syntriax.Engine.Core.Abstract;
public interface IDisplayable
{
public void Draw(SpriteBatch spriteBatch);
}

View File

@@ -1,20 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Graphics.TwoDimensional.Abstract;
public interface IDisplayableSprite : IDisplayable, IAssignableSprite
{
Action<IDisplayableSprite>? OnSpriteEffectsChanged { get; set; }
Action<IDisplayableSprite>? OnOriginChanged { get; set; }
Action<IDisplayableSprite>? OnColorChanged { get; set; }
Action<IDisplayableSprite>? OnDepthChanged { get; set; }
SpriteEffects SpriteEffects { get; set; }
Vector2 Origin { get; set; }
Color Color { get; set; }
float Depth { get; set; }
}

View File

@@ -1,117 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Graphics.TwoDimensional.Abstract;
namespace Syntriax.Engine.Graphics.TwoDimensional;
public class DisplayableSpriteBehaviour : Behaviour, IDisplayableSprite, IAssignableSprite
{
public Action<IAssignableSprite>? OnSpriteAssigned { get; set; } = null;
public Action<IDisplayableSprite>? OnSpriteEffectsChanged { get; set; } = null;
public Action<IDisplayableSprite>? OnOriginChanged { get; set; } = null;
public Action<IDisplayableSprite>? OnColorChanged { get; set; } = null;
public Action<IDisplayableSprite>? OnDepthChanged { get; set; } = null;
private ISprite _sprite = null!;
private Color _color = Color.White;
private float _depth = 0f;
private SpriteEffects _spriteEffects = SpriteEffects.None;
private Vector2 _origin = Vector2.One * .5f;
public ISprite Sprite => _sprite;
public SpriteEffects SpriteEffects
{
get => _spriteEffects;
set
{
if (_spriteEffects == value)
return;
_spriteEffects = value;
OnSpriteEffectsChanged?.Invoke(this);
}
}
public Vector2 Origin
{
get => _origin;
set
{
if (_origin == value)
return;
_origin = value;
OnOriginChanged?.Invoke(this);
}
}
public Color Color
{
get => _color;
set
{
if (_color == value)
return;
_color = value;
OnColorChanged?.Invoke(this);
}
}
public float Depth
{
get => _depth;
set
{
if (_depth == value)
return;
_depth = value;
OnDepthChanged?.Invoke(this);
}
}
public void Draw(SpriteBatch spriteBatch)
{
if (!BehaviourController.GameObject.StateEnable.Enabled || !StateEnable.Enabled)
return;
ITransform transform = BehaviourController.GameObject.Transform;
Vector2D position = transform.Position;
Vector2D scale = transform.Scale;
Rectangle rectangle = new((int)position.X, -(int)position.Y, (int)(Sprite.Texture2D.Width * scale.X), (int)(Sprite.Texture2D.Height * scale.Y));
spriteBatch.Draw(Sprite.Texture2D, rectangle, null, Color, transform.Rotation, new Vector2(Sprite.Texture2D.Width, Sprite.Texture2D.Height) * Origin, SpriteEffects, Depth);
}
public bool Assign(ISprite sprite)
{
_sprite = sprite;
OnSpriteAssigned?.Invoke(this);
return true;
}
public DisplayableSpriteBehaviour() => OnUnassigned += OnUnassign;
public DisplayableSpriteBehaviour(
Color? color = null,
float? depth = null,
SpriteEffects? spriteEffects = null,
Vector2? origin = null)
{
OnUnassigned += OnUnassign;
_color = color ?? _color;
_depth = depth ?? _depth;
_spriteEffects = spriteEffects ?? _spriteEffects;
_origin = origin ?? _origin;
}
private void OnUnassign(IAssignable assignable) => _sprite = null!;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

View File

@@ -1,3 +0,0 @@

using var game = new Pong.Game1();
game.Run();

View File

@@ -3,31 +3,31 @@
"isRoot": true,
"tools": {
"dotnet-mgcb": {
"version": "3.8.1.303",
"version": "3.8.2.1105",
"commands": [
"mgcb"
]
},
"dotnet-mgcb-editor": {
"version": "3.8.1.303",
"version": "3.8.2.1105",
"commands": [
"mgcb-editor"
]
},
"dotnet-mgcb-editor-linux": {
"version": "3.8.1.303",
"version": "3.8.2.1105",
"commands": [
"mgcb-editor-linux"
]
},
"dotnet-mgcb-editor-windows": {
"version": "3.8.1.303",
"version": "3.8.2.1105",
"commands": [
"mgcb-editor-windows"
]
},
"dotnet-mgcb-editor-mac": {
"version": "3.8.1.303",
"version": "3.8.2.1105",
"commands": [
"mgcb-editor-mac"
]

View File

@@ -0,0 +1,11 @@
public class ClientConfiguration
{
public bool Host = false;
public int HostPort = 8888;
public string ServerAddress = "localhost";
public int ServerPort = 8888;
public int windowWidth = 1024;
public int windowHeight = 576;
}

View File

@@ -0,0 +1,15 @@
#----------------------------- Global Properties ----------------------------#
/outputDir:bin/$(Platform)
/intermediateDir:obj/$(Platform)
/platform:DesktopGL
/config:
/profile:Reach
/compress:False
#-------------------------------- References --------------------------------#
#---------------------------------- Content ---------------------------------#

View File

@@ -0,0 +1,90 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<RollForward>Major</RollForward>
<PublishReadyToRun>false</PublishReadyToRun>
<TieredCompilation>false</TieredCompilation>
<Nullable>enable</Nullable>
<RootNamespace>Pong.Platforms.Desktop</RootNamespace>
<AssemblyName>Pong</AssemblyName>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>Icon.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<None Remove="Icon.ico" />
<None Remove="Icon.bmp" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Icon.ico">
<LogicalName>Icon.ico</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Icon.bmp">
<LogicalName>Icon.bmp</LogicalName>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.2.1105" />
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.2.1105" />
<PackageReference Include="nulastudio.NetBeauty" Version="2.1.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../Shared/Shared.csproj" />
</ItemGroup>
<ItemGroup>
<MonoGameContentReference Include="../../Shared/Content/Content.mgcb">
<Link>Content/Content.mgcb</Link>
</MonoGameContentReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../Shared/Shared.csproj" />
<ProjectReference Include="..\..\Engine\Engine.Serializers\Engine.Serializers.Yaml\Engine.Serializers.Yaml.csproj" />
</ItemGroup>
<Target Name="RestoreDotnetTools" BeforeTargets="Restore">
<Message Text="Restoring dotnet tools" Importance="High" />
<Exec Command="dotnet tool restore" />
</Target>
<PropertyGroup>
<BeautySharedRuntimeMode>False</BeautySharedRuntimeMode>
<!-- beauty into sub-directory, default is libs, quote with "" if contains space -->
<BeautyLibsDir Condition="$(BeautySharedRuntimeMode) == 'True'">../Libraries</BeautyLibsDir>
<BeautyLibsDir Condition="$(BeautySharedRuntimeMode) != 'True'">./Libraries</BeautyLibsDir>
<!-- dlls that you don't want to be moved or can not be moved -->
<!-- <BeautyExcludes>dll1.dll;lib*;...</BeautyExcludes> -->
<BeautyExcludes>SDL2.dll</BeautyExcludes>
<!-- dlls that end users never needed, so hide them -->
<BeautyHiddens>hostfxr;hostpolicy;*.deps.json;*.runtimeconfig*.json</BeautyHiddens>
<!-- set to True if you want to disable -->
<DisableBeauty>False</DisableBeauty>
<!-- set to False if you want to beauty on build -->
<BeautyOnPublishOnly>False</BeautyOnPublishOnly>
<!-- DO NOT TOUCH THIS OPTION -->
<BeautyNoRuntimeInfo>False</BeautyNoRuntimeInfo>
<!-- valid values: auto|with|without -->
<BeautyNBLoaderVerPolicy>auto</BeautyNBLoaderVerPolicy>
<!-- set to True if you want to allow 3rd debuggers(like dnSpy) debugs the app -->
<BeautyEnableDebugging>False</BeautyEnableDebugging>
<!-- the patch can reduce the file count -->
<!-- set to False if you want to disable -->
<!-- SCD Mode Feature Only -->
<BeautyUsePatch>True</BeautyUsePatch>
<!-- App Entry Dll = BeautyDir + BeautyAppHostDir + BeautyAppHostEntry -->
<!-- see https://github.com/nulastudio/NetBeauty2#customize-apphost for more details -->
<!-- relative path based on AppHostDir -->
<!-- .NET Core Non Single-File Only -->
<!-- <BeautyAppHostEntry>bin/MyApp.dll</BeautyAppHostEntry> -->
<!-- relative path based on BeautyDir -->
<!-- .NET Core Non Single-File Only -->
<!-- <BeautyAppHostDir>..</BeautyAppHostDir> -->
<!-- <BeautyAfterTasks></BeautyAfterTasks> -->
<!-- valid values: Error|Detail|Info -->
<BeautyLogLevel>Info</BeautyLogLevel>
<!-- set to a repo mirror if you have troble in connecting github -->
<!-- <BeautyGitCDN>https://gitee.com/liesauer/HostFXRPatcher</BeautyGitCDN> -->
<!-- <BeautyGitTree>master</BeautyGitTree> -->
</PropertyGroup>
</Project>

BIN
Platforms/Desktop/Icon.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

BIN
Platforms/Desktop/Icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
Platforms/Desktop/Icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -0,0 +1,59 @@
using System;
using System.IO;
using Microsoft.Xna.Framework.Graphics;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Debug;
using Syntriax.Engine.Core.Serialization;
using Syntriax.Engine.Integration.MonoGame;
using Syntriax.Engine.Serializers.Yaml;
Universe universe = new();
ISerializer serializer = new YamlSerializer();
ILogger logger = new FileLogger($"Logs/{DateTime.UtcNow:yyyy-MM-dd_HH-mm-ss-ffffff}.log");
#if DEBUG
logger = new LoggerWrapper(logger, new ConsoleLogger());
#endif
universe.InstantiateUniverseObject().SetUniverseObject("Logger").BehaviourController.AddBehaviour<LoggerContainer>().Logger = logger;
string settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings.yaml");
ClientConfiguration clientConfiguration;
try
{
clientConfiguration = serializer.Deserialize<ClientConfiguration>(File.ReadAllText(settingsPath));
logger.Log(clientConfiguration, $"{settingsPath} read");
}
catch (Exception exception)
{
clientConfiguration = new();
logger.LogError(serializer, $"Error loading {settingsPath}. Creating new settings file");
logger.LogException(serializer, exception);
File.WriteAllText(settingsPath, serializer.Serialize(clientConfiguration));
}
if (clientConfiguration.Host)
Pong.PongUniverse.ApplyPongServer(universe, clientConfiguration.HostPort);
Pong.PongUniverse.ApplyPongClient(universe, clientConfiguration.ServerAddress, clientConfiguration.ServerPort);
Pong.PongUniverse.ApplyPongUniverse(universe);
universe.InstantiateUniverseObject().SetUniverseObject("Desktop HO")
.BehaviourController.AddBehaviour<KeyboardInputs>();
using MonoGameWindow monoGameWindow = new(universe);
monoGameWindow.Graphics.PreferredBackBufferWidth = clientConfiguration.windowWidth;
monoGameWindow.Graphics.PreferredBackBufferHeight = clientConfiguration.windowHeight;
monoGameWindow.Graphics.GraphicsProfile = GraphicsProfile.HiDef;
universe.FindBehaviour<Syntriax.Engine.Network.INetworkCommunicatorClient>()?
.OnConnectionEstablished.AddOneTimeListener(
(sender, connection) => monoGameWindow.Window.Title = $"Client {connection.Id}"
);
monoGameWindow.Run();

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="Game"/>
<assemblyIdentity version="1.0.0.0" name="Desktop"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">

131
Platforms/Desktop/icon.svg Normal file
View File

@@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="100mm"
height="100mm"
viewBox="0 0 100 100"
version="1.1"
id="svg5"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="icon.svg"
inkscape:export-filename="Icon.png"
inkscape:export-xdpi="65.024002"
inkscape:export-ydpi="65.024002"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.76791526"
inkscape:cx="-14.975611"
inkscape:cy="272.16545"
inkscape:window-width="1920"
inkscape:window-height="1008"
inkscape:window-x="0"
inkscape:window-y="36"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2">
<inkscape:path-effect
effect="taper_stroke"
id="path-effect1965"
is_visible="true"
lpeversion="1"
stroke_width="14.57"
attach_start="0.99999"
end_offset="1e-07"
start_smoothing="0"
end_smoothing="0"
jointype="extrapolated"
start_shape="center"
end_shape="center"
miter_limit="1000.5" />
<inkscape:path-effect
effect="join_type"
id="path-effect1959"
is_visible="true"
lpeversion="1"
linecap_type="square"
line_width="0.26458299"
linejoin_type="extrp_arc"
miter_limit="100"
attempt_force_join="true" />
<inkscape:path-effect
effect="powerstroke"
id="path-effect1957"
is_visible="true"
lpeversion="1"
offset_points="0.2,0.1322915 | 1,0.1322915 | 1.8,0.1322915"
not_jump="false"
sort_points="true"
interpolator_type="Linear"
interpolator_beta="1"
start_linecap_type="zerowidth"
linejoin_type="miter"
miter_limit="11100"
scale_width="20"
end_linecap_type="butt" />
<inkscape:path-effect
effect="fill_between_many"
method="bsplinespiro"
linkedpaths="#path1175,0,1"
id="path-effect1961"
is_visible="true"
lpeversion="0"
join="true"
close="true"
autoreverse="true" />
<inkscape:path-effect
effect="fill_between_many"
method="bsplinespiro"
linkedpaths="#path1175,0,1"
id="path-effect1967"
is_visible="true"
lpeversion="0"
join="true"
close="true"
autoreverse="true" />
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#25202c;fill-opacity:1;stroke:none;stroke-width:0.264583;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1;paint-order:fill markers stroke"
id="rect4712"
width="100"
height="100"
x="0"
y="0"
ry="24.342688" />
<circle
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.296213;stroke-linecap:square;stroke-linejoin:bevel;paint-order:fill markers stroke"
id="path234"
cx="32.183838"
cy="37.277283"
r="11.198684" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.296213;stroke-linecap:square;stroke-linejoin:bevel;paint-order:fill markers stroke"
id="rect1054"
width="19.398724"
height="71.576004"
x="59.616123"
y="14.211996" />
<path
id="path1175"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.31583;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1;paint-order:fill markers stroke"
d="m 45.710924,37.909235 c -0.08308,1.7843 -0.506133,3.552079 -1.278577,5.16464 -1.067261,2.404981 -2.906892,4.366553 -5.129349,5.714834 l 10.696654,4.40096 -13.418115,24.765056 21.254461,-19.595787 c 5.44e-4,-4.297076 -4.96e-4,-8.593943 5.78e-4,-12.891057 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,39 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Syntriax.Engine.Core;
using Syntriax.Engine.Network;
namespace Server;
public class PongEndpoints : Behaviour, IFirstFrameUpdate
{
private INetworkCommunicatorServer? server = null!;
public PongEndpoints()
{
Task.Run(() =>
{
WebApplicationBuilder builder = WebApplication.CreateBuilder();
builder.Services.AddHealthChecks();
WebApplication app = builder.Build();
app.MapHealthChecks("/health");
app.MapGet("/stats", GetStats);
app.Run($"http://0.0.0.0:{Environment.GetEnvironmentVariable("PORT") ?? "8888"}");
});
}
private IResult GetStats() => Results.Json(new { Count = server?.Connections.Count ?? 0 });
public void FirstActiveFrame() => server = Universe.FindRequiredBehaviour<INetworkCommunicatorServer>();
protected override void OnExitedUniverse(IUniverse universe) => server = null;
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Threading;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Debug;
Universe universe = new();
FileLogger fileLogger = new($"Logs/{DateTime.UtcNow:yyyy-MM-dd_HH-mm-ss-ffffff}.log");
universe.InstantiateUniverseObject().SetUniverseObject("Logger").BehaviourController
.AddBehaviour<LoggerContainer>().Logger = new LoggerWrapper(fileLogger, new ConsoleLogger());
Pong.PongUniverse.ApplyPongServer(universe, int.Parse(Environment.GetEnvironmentVariable("PORT") ?? "8888"));
Pong.PongUniverse.ApplyPongUniverse(universe);
universe.InstantiateUniverseObject().SetUniverseObject("Endpoints").BehaviourController
.AddBehaviour<Server.PongEndpoints>();
DateTime lastRun = DateTime.UtcNow;
TimeSpan interval = new(0, 0, 0, 0, 16);
TimeSpan timeSinceStart = new(0);
universe.Initialize();
while (true)
{
if (lastRun + interval <= DateTime.UtcNow)
{
lastRun += interval;
timeSinceStart += interval;
universe.Update(new(timeSinceStart, interval));
}
Thread.Sleep(1);
}

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../Shared/Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="2.3.0" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="10.0.0-preview.5.25277.114" />
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.2.1105" />
</ItemGroup>
</Project>

151
Pong.sln
View File

@@ -7,41 +7,160 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Engine", "Engine", "{F7F626
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Core", "Engine\Engine.Core\Engine.Core.csproj", "{990CA10C-1EBB-4395-A43A-456B7029D8C9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Game", "Game\Game.csproj", "{500E1B05-39D7-4232-8051-E7351D745306}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Input", "Engine\Engine.Input\Engine.Input.csproj", "{7EED4EC3-79D5-4C6C-A54D-1B396213C0E4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Physics2D", "Engine\Engine.Physics2D\Engine.Physics2D.csproj", "{0D97F83C-B043-48B1-B155-7354C4E84FC0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine", "Engine\Engine\Engine.csproj", "{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared", "Shared\Shared.csproj", "{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Platforms", "Platforms", "{FECFFD54-338F-4060-9161-1E5770D1DC33}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Desktop", "Platforms\Desktop\Desktop.csproj", "{2B627F66-5A61-4F69-B479-62EEAB603D01}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Systems", "Engine\Engine.Systems\Engine.Systems.csproj", "{8863A1BA-2E83-419F-BACB-D4A4156EC71C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Engine.Integration", "Engine.Integration", "{9059393F-4073-9273-0EEC-2B1BA61B620B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Integration.MonoGame", "Engine\Engine.Integration\Engine.Integration.MonoGame\Engine.Integration.MonoGame.csproj", "{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server", "Platforms\Server\Server.csproj", "{A15263DB-DF65-4A07-8CA1-33A2919501A0}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Engine.Serializers", "Engine.Serializers", "{F1257AE1-A8BC-DE44-CB86-406F1335A1D0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Serializers.Yaml", "Engine\Engine.Serializers\Engine.Serializers.Yaml\Engine.Serializers.Yaml.csproj", "{79F870AB-249E-4CA0-9DF0-F265514581DF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Debug|x64.ActiveCfg = Debug|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Debug|x64.Build.0 = Debug|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Debug|x86.ActiveCfg = Debug|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Debug|x86.Build.0 = Debug|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Release|Any CPU.Build.0 = Release|Any CPU
{500E1B05-39D7-4232-8051-E7351D745306}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{500E1B05-39D7-4232-8051-E7351D745306}.Debug|Any CPU.Build.0 = Debug|Any CPU
{500E1B05-39D7-4232-8051-E7351D745306}.Release|Any CPU.ActiveCfg = Release|Any CPU
{500E1B05-39D7-4232-8051-E7351D745306}.Release|Any CPU.Build.0 = Release|Any CPU
{7EED4EC3-79D5-4C6C-A54D-1B396213C0E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7EED4EC3-79D5-4C6C-A54D-1B396213C0E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7EED4EC3-79D5-4C6C-A54D-1B396213C0E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7EED4EC3-79D5-4C6C-A54D-1B396213C0E4}.Release|Any CPU.Build.0 = Release|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Release|x64.ActiveCfg = Release|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Release|x64.Build.0 = Release|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Release|x86.ActiveCfg = Release|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Release|x86.Build.0 = Release|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Debug|x64.ActiveCfg = Debug|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Debug|x64.Build.0 = Debug|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Debug|x86.ActiveCfg = Debug|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Debug|x86.Build.0 = Debug|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Release|Any CPU.Build.0 = Release|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Release|x64.ActiveCfg = Release|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Release|x64.Build.0 = Release|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Release|x86.ActiveCfg = Release|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Release|x86.Build.0 = Release|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Debug|x64.ActiveCfg = Debug|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Debug|x64.Build.0 = Debug|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Debug|x86.ActiveCfg = Debug|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Debug|x86.Build.0 = Debug|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Release|Any CPU.Build.0 = Release|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Release|x64.ActiveCfg = Release|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Release|x64.Build.0 = Release|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Release|x86.ActiveCfg = Release|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Release|x86.Build.0 = Release|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Debug|x64.ActiveCfg = Debug|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Debug|x64.Build.0 = Debug|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Debug|x86.ActiveCfg = Debug|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Debug|x86.Build.0 = Debug|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Release|Any CPU.Build.0 = Release|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Release|x64.ActiveCfg = Release|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Release|x64.Build.0 = Release|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Release|x86.ActiveCfg = Release|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Release|x86.Build.0 = Release|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Debug|x64.ActiveCfg = Debug|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Debug|x64.Build.0 = Debug|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Debug|x86.ActiveCfg = Debug|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Debug|x86.Build.0 = Debug|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Release|Any CPU.Build.0 = Release|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Release|x64.ActiveCfg = Release|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Release|x64.Build.0 = Release|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Release|x86.ActiveCfg = Release|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Release|x86.Build.0 = Release|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Debug|x64.ActiveCfg = Debug|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Debug|x64.Build.0 = Debug|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Debug|x86.ActiveCfg = Debug|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Debug|x86.Build.0 = Debug|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Release|Any CPU.Build.0 = Release|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Release|x64.ActiveCfg = Release|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Release|x64.Build.0 = Release|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Release|x86.ActiveCfg = Release|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Release|x86.Build.0 = Release|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Debug|x64.ActiveCfg = Debug|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Debug|x64.Build.0 = Debug|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Debug|x86.ActiveCfg = Debug|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Debug|x86.Build.0 = Debug|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Release|Any CPU.Build.0 = Release|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Release|x64.ActiveCfg = Release|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Release|x64.Build.0 = Release|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Release|x86.ActiveCfg = Release|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Release|x86.Build.0 = Release|Any CPU
{A15263DB-DF65-4A07-8CA1-33A2919501A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A15263DB-DF65-4A07-8CA1-33A2919501A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A15263DB-DF65-4A07-8CA1-33A2919501A0}.Debug|x64.ActiveCfg = Debug|Any CPU
{A15263DB-DF65-4A07-8CA1-33A2919501A0}.Debug|x64.Build.0 = Debug|Any CPU
{A15263DB-DF65-4A07-8CA1-33A2919501A0}.Debug|x86.ActiveCfg = Debug|Any CPU
{A15263DB-DF65-4A07-8CA1-33A2919501A0}.Debug|x86.Build.0 = Debug|Any CPU
{A15263DB-DF65-4A07-8CA1-33A2919501A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A15263DB-DF65-4A07-8CA1-33A2919501A0}.Release|Any CPU.Build.0 = Release|Any CPU
{A15263DB-DF65-4A07-8CA1-33A2919501A0}.Release|x64.ActiveCfg = Release|Any CPU
{A15263DB-DF65-4A07-8CA1-33A2919501A0}.Release|x64.Build.0 = Release|Any CPU
{A15263DB-DF65-4A07-8CA1-33A2919501A0}.Release|x86.ActiveCfg = Release|Any CPU
{A15263DB-DF65-4A07-8CA1-33A2919501A0}.Release|x86.Build.0 = Release|Any CPU
{79F870AB-249E-4CA0-9DF0-F265514581DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{79F870AB-249E-4CA0-9DF0-F265514581DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{79F870AB-249E-4CA0-9DF0-F265514581DF}.Debug|x64.ActiveCfg = Debug|Any CPU
{79F870AB-249E-4CA0-9DF0-F265514581DF}.Debug|x64.Build.0 = Debug|Any CPU
{79F870AB-249E-4CA0-9DF0-F265514581DF}.Debug|x86.ActiveCfg = Debug|Any CPU
{79F870AB-249E-4CA0-9DF0-F265514581DF}.Debug|x86.Build.0 = Debug|Any CPU
{79F870AB-249E-4CA0-9DF0-F265514581DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{79F870AB-249E-4CA0-9DF0-F265514581DF}.Release|Any CPU.Build.0 = Release|Any CPU
{79F870AB-249E-4CA0-9DF0-F265514581DF}.Release|x64.ActiveCfg = Release|Any CPU
{79F870AB-249E-4CA0-9DF0-F265514581DF}.Release|x64.Build.0 = Release|Any CPU
{79F870AB-249E-4CA0-9DF0-F265514581DF}.Release|x86.ActiveCfg = Release|Any CPU
{79F870AB-249E-4CA0-9DF0-F265514581DF}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{990CA10C-1EBB-4395-A43A-456B7029D8C9} = {F7F62670-237A-4C93-A30E-CE661C6FC401}
{7EED4EC3-79D5-4C6C-A54D-1B396213C0E4} = {F7F62670-237A-4C93-A30E-CE661C6FC401}
{0D97F83C-B043-48B1-B155-7354C4E84FC0} = {F7F62670-237A-4C93-A30E-CE661C6FC401}
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969} = {F7F62670-237A-4C93-A30E-CE661C6FC401}
{2B627F66-5A61-4F69-B479-62EEAB603D01} = {FECFFD54-338F-4060-9161-1E5770D1DC33}
{8863A1BA-2E83-419F-BACB-D4A4156EC71C} = {F7F62670-237A-4C93-A30E-CE661C6FC401}
{9059393F-4073-9273-0EEC-2B1BA61B620B} = {F7F62670-237A-4C93-A30E-CE661C6FC401}
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283} = {9059393F-4073-9273-0EEC-2B1BA61B620B}
{A15263DB-DF65-4A07-8CA1-33A2919501A0} = {FECFFD54-338F-4060-9161-1E5770D1DC33}
{F1257AE1-A8BC-DE44-CB86-406F1335A1D0} = {F7F62670-237A-4C93-A30E-CE661C6FC401}
{79F870AB-249E-4CA0-9DF0-F265514581DF} = {F1257AE1-A8BC-DE44-CB86-406F1335A1D0}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,36 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-mgcb": {
"version": "3.8.2.1105",
"commands": [
"mgcb"
]
},
"dotnet-mgcb-editor": {
"version": "3.8.2.1105",
"commands": [
"mgcb-editor"
]
},
"dotnet-mgcb-editor-linux": {
"version": "3.8.2.1105",
"commands": [
"mgcb-editor-linux"
]
},
"dotnet-mgcb-editor-windows": {
"version": "3.8.2.1105",
"commands": [
"mgcb-editor-windows"
]
},
"dotnet-mgcb-editor-mac": {
"version": "3.8.2.1105",
"commands": [
"mgcb-editor-mac"
]
}
}
}

94
Shared/Behaviours/Ball.cs Normal file
View File

@@ -0,0 +1,94 @@
using Syntriax.Engine.Core;
using Syntriax.Engine.Network;
using Syntriax.Engine.Physics2D;
using Syntriax.Engine.Systems.Tween;
namespace Pong.Behaviours;
public class Ball : Behaviour2D, IFirstFrameUpdate, IPhysicsUpdate, INetworkEntity,
IPacketListenerClient<Ball.BallUpdatePacket>,
IPacketListenerClient<Ball.BallResetPacket>
{
public float Speed { get; set; } = 500f;
public float SpeedUpMultiplier { get; set; } = .025f;
public IRigidBody2D RigidBody { get; private set; } = null!;
private IPhysicsEngine2D physicsEngine2D = null!;
private ITweenManager tweenManager = null!;
private INetworkCommunicatorServer? networkServer = null;
private ITween? networkTween = null;
public void FirstActiveFrame()
{
BehaviourController.GetRequiredBehaviour<ICollider2D>().OnCollisionDetected.AddListener(OnCollisionDetected);
physicsEngine2D = Universe.FindRequiredBehaviour<IPhysicsEngine2D>();
tweenManager = Universe.FindRequiredBehaviour<ITweenManager>();
RigidBody = BehaviourController.GetRequiredBehaviour<IRigidBody2D>();
networkServer = Universe.FindBehaviour<INetworkCommunicatorServer>();
}
public void LaunchBall(Vector2D launchDirection)
{
ResetBall();
RigidBody.Velocity = launchDirection * Speed;
networkServer?.SendToAll(new BallUpdatePacket(this));
}
public void ResetBall()
{
if (networkTween is not null)
tweenManager.CancelTween(networkTween);
Transform.Position = Vector2D.Zero;
RigidBody.Velocity = Vector2D.Zero;
networkServer?.SendToAll(new BallResetPacket());
}
public void PhysicsUpdate(float delta)
{
if (RigidBody.Velocity.MagnitudeSquared <= 0.01f)
return;
Vector2D direction = RigidBody.Velocity.Normalized;
RigidBody.Velocity += direction * delta * SpeedUpMultiplier;
}
private void OnCollisionDetected(ICollider2D collider2D, CollisionDetectionInformation information)
{
if (Math.Abs(information.Normal.Dot(Vector2D.Right)) > .25)
RigidBody.Velocity = information.Detected.Transform.Position.FromTo(information.Detector.Transform.Position).Normalized * RigidBody.Velocity.Magnitude;
else
RigidBody.Velocity = RigidBody.Velocity.Reflect(information.Normal);
networkServer?.SendToAll(new BallUpdatePacket(this));
}
public Ball() { }
public Ball(float speed) => Speed = speed;
void IPacketListenerClient<BallResetPacket>.OnClientPacketArrived(IConnection sender, BallResetPacket packet) => ResetBall();
void IPacketListenerClient<BallUpdatePacket>.OnClientPacketArrived(IConnection sender, BallUpdatePacket packet)
{
Vector2D localToServerPosition = Transform.Position.FromTo(packet.Position);
if (localToServerPosition.MagnitudeSquared < 4f)
networkTween = Transform.TweenPositionAdditive(tweenManager, .25f, localToServerPosition);
else
Transform.Position = packet.Position;
RigidBody.Velocity = packet.Velocity;
physicsEngine2D.StepIndividual(RigidBody, sender.Ping);
}
private class BallResetPacket : INetworkPacket;
private class BallUpdatePacket : INetworkPacket
{
public Vector2D Position { get; set; } = Vector2D.Zero;
public Vector2D Velocity { get; set; } = Vector2D.Zero;
public BallUpdatePacket() { }
public BallUpdatePacket(Ball ballBehaviour)
{
Position = ballBehaviour.Transform.Position;
Velocity = ballBehaviour.RigidBody.Velocity;
}
}
}

View File

@@ -0,0 +1,69 @@
using Microsoft.Xna.Framework.Input;
using Syntriax.Engine.Core;
using Syntriax.Engine.Integration.MonoGame;
using Syntriax.Engine.Systems.Input;
namespace Pong.Behaviours;
public class CameraController : Behaviour, IFirstFrameUpdate, IUpdate
{
private MonoGameCamera2D cameraBehaviour = null!;
private IButtonInputs<Keys> buttonInputs = null!;
private float defaultZoomLevel = 1f;
public void FirstActiveFrame()
{
cameraBehaviour = BehaviourController.GetRequiredBehaviour<MonoGameCamera2D>();
buttonInputs = Universe.FindRequiredBehaviour<IButtonInputs<Keys>>();
buttonInputs.RegisterOnPress(Keys.F, SwitchToFullScreen);
buttonInputs.RegisterOnPress(Keys.R, ResetCamera);
}
public void Update()
{
if (buttonInputs.IsPressed(Keys.U))
cameraBehaviour.Zoom += Universe.Time.DeltaTime * 5f;
if (buttonInputs.IsPressed(Keys.J))
cameraBehaviour.Zoom -= Universe.Time.DeltaTime * 5f;
if (buttonInputs.IsPressed(Keys.NumPad8)) cameraBehaviour.Transform.LocalPosition += Vector2D.Up * Universe.Time.DeltaTime * 500f;
if (buttonInputs.IsPressed(Keys.NumPad2)) cameraBehaviour.Transform.LocalPosition -= Vector2D.Up * Universe.Time.DeltaTime * 500f;
if (buttonInputs.IsPressed(Keys.NumPad6)) cameraBehaviour.Transform.LocalPosition += Vector2D.Right * Universe.Time.DeltaTime * 500f;
if (buttonInputs.IsPressed(Keys.NumPad4)) cameraBehaviour.Transform.LocalPosition -= Vector2D.Right * Universe.Time.DeltaTime * 500f;
if (buttonInputs.IsPressed(Keys.Q))
cameraBehaviour.Transform.Rotation += Universe.Time.DeltaTime * 45f;
if (buttonInputs.IsPressed(Keys.E))
cameraBehaviour.Transform.Rotation -= Universe.Time.DeltaTime * 45f;
}
private void SwitchToFullScreen(IButtonInputs<Keys> sender, IButtonInputs<Keys>.ButtonCallbackArguments args)
{
if (cameraBehaviour.Graphics.IsFullScreen)
return;
cameraBehaviour.Graphics.PreferMultiSampling = false;
cameraBehaviour.Graphics.PreferredBackBufferWidth = cameraBehaviour.Graphics.GraphicsDevice.Adapter.CurrentDisplayMode.Width;
cameraBehaviour.Graphics.PreferredBackBufferHeight = cameraBehaviour.Graphics.GraphicsDevice.Adapter.CurrentDisplayMode.Height;
cameraBehaviour.Graphics.IsFullScreen = true;
cameraBehaviour.Graphics.ApplyChanges();
float previousScreenSize = Math.Sqrt(Math.Sqr(cameraBehaviour.Viewport.Width) + Math.Sqr(cameraBehaviour.Viewport.Height));
float currentScreenSize = Math.Sqrt(Math.Sqr(cameraBehaviour.Graphics.GraphicsDevice.Viewport.Width) + Math.Sqr(cameraBehaviour.Graphics.GraphicsDevice.Viewport.Height));
defaultZoomLevel /= previousScreenSize / currentScreenSize;
cameraBehaviour.Zoom /= previousScreenSize / currentScreenSize;
cameraBehaviour.Viewport = cameraBehaviour.Graphics.GraphicsDevice.Viewport;
}
private void ResetCamera(IButtonInputs<Keys> sender, IButtonInputs<Keys>.ButtonCallbackArguments args)
{
cameraBehaviour.Zoom = defaultZoomLevel;
cameraBehaviour.Transform.LocalPosition = Vector2D.Zero;
cameraBehaviour.Transform.LocalRotation = 0f;
}
}

View File

@@ -0,0 +1,29 @@
using Syntriax.Engine.Core;
using Syntriax.Engine.Integration.MonoGame;
namespace Pong.Behaviours;
public class DrawableColliderCircle : Syntriax.Engine.Physics2D.Collider2DCircle, IDrawableTriangle
{
private const float CIRCLE_SEGMENT_COUNT = 32f;
public ColorRGBA Color { get; set; } = new ColorRGBA(255, 255, 255);
public void Draw(ITriangleBatch triangleBatch)
{
Recalculate();
for (int i = 0; i < CIRCLE_SEGMENT_COUNT; i++)
{
float iPi1 = i / CIRCLE_SEGMENT_COUNT * 2f * Math.Pi;
float iPi2 = (i + 1f).Mod(CIRCLE_SEGMENT_COUNT) / CIRCLE_SEGMENT_COUNT * 2f * Math.Pi;
Vector2D firstVertex = new Vector2D(Math.Sin(iPi1), Math.Cos(iPi1)) * CircleWorld.Radius;
Vector2D secondVertex = new Vector2D(Math.Sin(iPi2), Math.Cos(iPi2)) * CircleWorld.Radius;
triangleBatch.Draw(new(CircleWorld.Center, CircleWorld.Center + firstVertex, CircleWorld.Center + secondVertex), Color);
}
}
public DrawableColliderCircle(Circle circle) : base(circle) { }
public DrawableColliderCircle(Circle circle, ColorRGBA color) : base(circle) { Color = color; }
}

View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
using Syntriax.Engine.Core;
using Syntriax.Engine.Integration.MonoGame;
namespace Pong.Behaviours;
public class DrawableColliderShape : Syntriax.Engine.Physics2D.Collider2DShape, IDrawableTriangle
{
private readonly IList<Triangle> triangles = [];
public ColorRGBA Color { get; set; } = new ColorRGBA(255, 255, 255);
public void Draw(ITriangleBatch triangleBatch)
{
Recalculate();
ShapeWorld.ToTrianglesConvex(triangles);
foreach (Triangle triangle in triangles)
triangleBatch.Draw(triangle, Color);
}
public DrawableColliderShape(Shape2D shape) : base(shape) { }
public DrawableColliderShape(Shape2D shape, ColorRGBA color) : base(shape) { Color = color; }
}

View File

@@ -0,0 +1,25 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Syntriax.Engine.Core;
using Syntriax.Engine.Integration.MonoGame;
namespace Pong.Behaviours;
public class Label : Behaviour2D, IDrawableSprite
{
public SpriteFont? Font { get; set; } = null;
public int Size { get; set; } = 16;
public string Text { get; set; } = string.Empty;
public void Draw(ISpriteBatch spriteBatch)
{
if (Font is null)
return;
spriteBatch.DrawString(Font, Text, Transform.Position, Color.White, Transform.Rotation, Vector2D.One * .5f, Transform.Scale.Magnitude, SpriteEffects.None, 0f);
}
public Label() { }
public Label(SpriteFont font) => Font = font;
}

126
Shared/Behaviours/Paddle.cs Normal file
View File

@@ -0,0 +1,126 @@
using System;
using Microsoft.Xna.Framework.Input;
using Syntriax.Engine.Core;
using Syntriax.Engine.Network;
using Syntriax.Engine.Physics2D;
using Syntriax.Engine.Systems.Input;
using Syntriax.Engine.Systems.Tween;
namespace Pong.Behaviours;
public class Paddle(Keys Up, Keys Down, float High, float Low, float Speed) : Behaviour2D,
IFirstFrameUpdate, IPhysicsIteration, IPostPhysicsUpdate,
IPacketListenerServer<Paddle.PaddleKeyStatePacket>, IPacketListenerClient<Paddle.PaddleKeyStatePacket>
{
private Keys Up { get; } = Up;
private Keys Down { get; } = Down;
public float High { get; } = High;
public float Low { get; } = Low;
public float Speed { get; set; } = Speed;
private bool isUpPressed = false;
private bool isDownPressed = false;
private IButtonInputs<Keys>? inputs = null;
private INetworkCommunicatorClient? networkClient = null!;
private INetworkCommunicatorServer? networkServer = null;
private IRigidBody2D rigidBody = null!;
private IPhysicsEngine2D physicsEngine2D = null!;
private ITween? networkTween = null;
private ITweenManager tweenManager = null!;
public void PhysicsIterate(float delta)
{
if (isUpPressed)
rigidBody.Transform.Position += Vector2D.Up * Speed * delta;
else if (isDownPressed)
rigidBody.Transform.Position -= Vector2D.Up * Speed * delta;
}
public void PostPhysicsUpdate(float delta)
{
Transform.Position = new Vector2D(Transform.Position.X, MathF.Max(MathF.Min(Transform.Position.Y, High), Low));
}
public void FirstActiveFrame()
{
physicsEngine2D = Universe.FindRequiredBehaviour<IPhysicsEngine2D>();
inputs = Universe.FindBehaviour<IButtonInputs<Keys>>();
networkClient = Universe.FindBehaviour<INetworkCommunicatorClient>();
networkServer = Universe.FindBehaviour<INetworkCommunicatorServer>();
rigidBody = BehaviourController.GetRequiredBehaviour<IRigidBody2D>();
tweenManager = Universe.FindRequiredBehaviour<ITweenManager>();
inputs?.RegisterOnPress(Up, OnUpPressed);
inputs?.RegisterOnRelease(Up, OnUpReleased);
inputs?.RegisterOnPress(Down, OnDownPressed);
inputs?.RegisterOnRelease(Down, OnDownReleased);
}
protected override void OnFinalize()
{
inputs?.UnregisterOnPress(Up, OnUpPressed);
inputs?.UnregisterOnRelease(Up, OnUpReleased);
inputs?.UnregisterOnPress(Down, OnDownPressed);
inputs?.UnregisterOnRelease(Down, OnDownReleased);
}
private void OnUpPressed(IButtonInputs<Keys> sender, IButtonInputs<Keys>.ButtonCallbackArguments args) { isUpPressed = true; networkClient?.SendToServer(new PaddleKeyStatePacket(this)); }
private void OnUpReleased(IButtonInputs<Keys> sender, IButtonInputs<Keys>.ButtonCallbackArguments args) { isUpPressed = false; networkClient?.SendToServer(new PaddleKeyStatePacket(this)); }
private void OnDownPressed(IButtonInputs<Keys> sender, IButtonInputs<Keys>.ButtonCallbackArguments args) { isDownPressed = true; networkClient?.SendToServer(new PaddleKeyStatePacket(this)); }
private void OnDownReleased(IButtonInputs<Keys> sender, IButtonInputs<Keys>.ButtonCallbackArguments args) { isDownPressed = false; networkClient?.SendToServer(new PaddleKeyStatePacket(this)); }
public void OnServerPacketArrived(IConnection sender, PaddleKeyStatePacket packet)
{
physicsEngine2D.StepIndividual(rigidBody, -sender.Ping.Min(.05f));
isUpPressed = packet.IsUpPressed;
isDownPressed = packet.IsDownPressed;
physicsEngine2D.StepIndividual(rigidBody, sender.Ping.Min(.05f));
networkServer?.SendToAll(new PaddleKeyStatePacket(this));
}
public void OnClientPacketArrived(IConnection sender, PaddleKeyStatePacket packet)
{
if (packet.IsDownPressed || packet.IsUpPressed) // Check if the server paddle is moving
if (isDownPressed == packet.IsDownPressed && isUpPressed == packet.IsUpPressed) // Check if we are the ones giving the inputs
return;
physicsEngine2D.StepIndividual(rigidBody, -sender.Ping);
isUpPressed = packet.IsUpPressed;
isDownPressed = packet.IsDownPressed;
physicsEngine2D.StepIndividual(rigidBody, sender.Ping);
Vector2D localToServerPosition = Transform.Position.FromTo(packet.Position);
networkTween = Transform.TweenPositionAdditive(tweenManager, .1f, localToServerPosition);
}
public class PaddleKeyStatePacket : IEntityNetworkPacket
{
public string EntityId { get; set; } = default!;
public Vector2D Position { get; set; } = default!;
public bool IsUpPressed { get; set; } = default!;
public bool IsDownPressed { get; set; } = default!;
public PaddleKeyStatePacket Set(Paddle paddleBehaviour)
{
EntityId = paddleBehaviour.Id;
Position = paddleBehaviour.Transform.Position;
IsUpPressed = paddleBehaviour.isUpPressed;
IsDownPressed = paddleBehaviour.isDownPressed;
return this;
}
public PaddleKeyStatePacket() { }
public PaddleKeyStatePacket(Paddle paddleBehaviour) => Set(paddleBehaviour);
}
}

View File

@@ -0,0 +1,134 @@
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;
public Action<PongManager>? OnFinished { get; set; } = null;
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;
public int ScoreLeft { get; private set; } = 0;
public int ScoreRight { get; private set; } = 0;
public int ScoreSum => ScoreLeft + ScoreRight;
public int WinScore { get; } = 5;
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>();
networkServer = Universe.FindBehaviour<INetworkCommunicatorServer>();
logger = Universe.FindBehaviour<ILogger>();
}
public void ScoreToLeft()
{
ScoreLeft++;
OnScoreUpdated?.Invoke(this);
PostScoreUpdate();
}
public void ScoreToRight()
{
ScoreRight++;
OnScoreUpdated?.Invoke(this);
PostScoreUpdate();
}
public void Reset()
{
ScoreLeft = ScoreRight = 0;
PostScoreUpdate();
OnReset?.Invoke(this);
}
private void PostScoreUpdate()
{
ball.ResetBall();
if (networkServer is not null)
{
networkServer.SendToAll(new ScorePacket(this));
logger?.Log(this, $"Sending score update packet to all: {ScoreLeft} - {ScoreRight}");
}
int halfwayScore = (int)(WinScore * .5f);
if (ScoreLeft > halfwayScore || ScoreRight > halfwayScore)
{
OnFinished?.Invoke(this);
logger?.Log(this, $"Game finished");
return;
}
ball.LaunchBall(GetBallLaunchDirection());
}
private Vector2D GetBallLaunchDirection()
{
const float AllowedRadians = 45f * Syntriax.Engine.Core.Math.DegreeToRadian;
float rotation = (float)random.NextDouble() * 2f * AllowedRadians - AllowedRadians;
bool isBackwards = (random.Next() % 2) == 1;
return Vector2D.Right.Rotate(isBackwards ? rotation + Syntriax.Engine.Core.Math.Pi : rotation);
}
void IPacketListenerClient<ScorePacket>.OnClientPacketArrived(IConnection sender, ScorePacket packet)
{
ScoreLeft = packet.Left;
ScoreRight = packet.Right;
OnScoreUpdated?.Invoke(this);
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; }
public int Right { get; set; }
public ScorePacket() { }
public ScorePacket(PongManager pongManagerBehaviour)
{
Left = pongManagerBehaviour.ScoreLeft;
Right = pongManagerBehaviour.ScoreRight;
}
}
}

View File

@@ -0,0 +1,34 @@
using Microsoft.Xna.Framework.Graphics;
using Syntriax.Engine.Core;
using Syntriax.Engine.Integration.MonoGame;
namespace Pong.Behaviours;
public class ScoreLabel(bool IsLeft) : Label, IFirstFrameUpdate
{
public readonly bool IsLeft = IsLeft;
private PongManager pongManager = null!;
public void FirstActiveFrame()
{
MonoGameWindow monoGameWindow = Universe.FindRequiredBehaviour<MonoGameWindowContainer>().Window;
Font = monoGameWindow.Content.Load<SpriteFont>("UbuntuMono");
pongManager = Universe.FindRequiredBehaviour<PongManager>();
pongManager.OnScoreUpdated += UpdateScores;
pongManager.OnReset += UpdateScores;
UpdateScores(pongManager);
}
private void UpdateScores(PongManager pongManager)
{
if (IsLeft)
Text = pongManager.ScoreLeft.ToString();
else
Text = pongManager.ScoreRight.ToString();
}
}

View File

@@ -0,0 +1,19 @@
using System;
using Syntriax.Engine.Core;
using Syntriax.Engine.Physics2D;
namespace Pong.Behaviours;
public class ScoreWall(Action OnCollision) : Behaviour2D, IFirstFrameUpdate
{
private Action OnCollision { get; } = OnCollision;
public void FirstActiveFrame()
{
if (!BehaviourController.TryGetBehaviour(out ICollider2D? collider2D))
return;
collider2D.OnCollisionDetected.AddListener((_, _1) => OnCollision?.Invoke());
}
}

View File

@@ -0,0 +1,22 @@
#----------------------------- Global Properties ----------------------------#
/outputDir:bin/$(Platform)
/intermediateDir:obj/$(Platform)
/platform:DesktopGL
/config:
/profile:Reach
/compress:False
#-------------------------------- References --------------------------------#
#---------------------------------- Content ---------------------------------#
#begin UbuntuMono.spritefont
/importer:FontDescriptionImporter
/processor:FontDescriptionProcessor
/processorParam:PremultiplyAlpha=True
/processorParam:TextureFormat=Compressed
/build:UbuntuMono.spritefont

Binary file not shown.

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file contains an xml description of a font, and will be read by the XNA
Framework Content Pipeline. Follow the comments to customize the appearance
of the font in your game, and to change the characters which are available to draw
with.
-->
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
<Asset Type="Graphics:FontDescription">
<!--
Modify this string to change the font that will be imported.
-->
<FontName>Ubuntu Mono</FontName>
<!--
Size is a float value, measured in points. Modify this value to change
the size of the font.
-->
<Size>144</Size>
<!--
Spacing is a float value, measured in pixels. Modify this value to change
the amount of spacing in between characters.
-->
<Spacing>0</Spacing>
<!--
UseKerning controls the layout of the font. If this value is true, kerning information
will be used when placing characters.
-->
<UseKerning>true</UseKerning>
<!--
Style controls the style of the font. Valid entries are "Regular", "Bold", "Italic",
and "Bold, Italic", and are case sensitive.
-->
<Style>Bold</Style>
<!--
If you uncomment this line, the default character will be substituted if you draw
or measure text that contains characters which were not included in the font.
-->
<!-- <DefaultCharacter>*</DefaultCharacter> -->
<!--
CharacterRegions control what letters are available in the font. Every
character from Start to End will be built and made available for drawing. The
default range is from 32, (ASCII space), to 126, ('~'), covering the basic Latin
character set. The characters are ordered according to the Unicode standard.
See the documentation for more information.
-->
<CharacterRegions>
<CharacterRegion>
<Start>&#32;</Start>
<End>&#126;</End>
</CharacterRegion>
</CharacterRegions>
</Asset>
</XnaContent>

View File

@@ -0,0 +1,12 @@
namespace Syntriax.Engine.Network;
public interface IConnection
{
string Id { get; }
float Ping { get; }
float RoundTrip { get; }
int PingMs { get; }
int RoundTripMs { get; }
}

View File

@@ -0,0 +1,6 @@
namespace Syntriax.Engine.Network;
public interface IEntityNetworkPacket : INetworkPacket
{
string EntityId { get; }
}

View File

@@ -0,0 +1,37 @@
using System.Collections.Generic;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Network;
public interface INetworkCommunicator
{
Event<INetworkCommunicator, IConnection> OnConnectionEstablished { get; }
Event<INetworkCommunicator, IConnection> OnConnectionAbolished { get; }
IReadOnlyDictionary<string, IConnection> Connections { get; }
INetworkCommunicator Stop();
INetworkCommunicator SubscribeToPackets<T>(Event<IConnection, T>.EventHandler callback);
INetworkCommunicator UnsubscribeFromPackets<T>(Event<IConnection, T>.EventHandler callback);
}
public interface INetworkCommunicatorClient : INetworkCommunicator
{
INetworkCommunicatorClient Connect(string address, int port, string? password = null);
INetworkCommunicatorClient SendToServer<T>(T packet, PacketDelivery packetDelivery = PacketDelivery.ReliableInOrder) where T : class, new();
}
public interface INetworkCommunicatorServer : INetworkCommunicator
{
string Password { get; }
int MaxConnectionCount { get; }
int Port { get; }
INetworkCommunicatorServer Start(int port, int maxConnectionCount, string? password = null);
INetworkCommunicatorServer SendToClient<T>(IConnection connection, T packet, PacketDelivery packetDelivery = PacketDelivery.ReliableInOrder) where T : class, new();
INetworkCommunicatorServer SendToAll<T>(T packet, PacketDelivery packetDelivery = PacketDelivery.ReliableInOrder) where T : class, new();
}

View File

@@ -0,0 +1,5 @@
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Network;
public interface INetworkEntity : IEntity;

View File

@@ -0,0 +1,11 @@
using System.Collections.Generic;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Network;
public interface INetworkManager
{
IReadOnlyDictionary<string, INetworkEntity> NetworkEntities { get; }
IBehaviourCollector<INetworkEntity> NetworkEntityCollector { get; }
}

View File

@@ -0,0 +1,3 @@
namespace Syntriax.Engine.Network;
public interface INetworkPacket;

View File

@@ -0,0 +1,6 @@
namespace Syntriax.Engine.Network;
public interface IPacketListenerClient<T> : INetworkEntity
{
void OnClientPacketArrived(IConnection sender, T packet);
}

View File

@@ -0,0 +1,6 @@
namespace Syntriax.Engine.Network;
public interface IPacketListenerServer<T> : INetworkEntity
{
void OnServerPacketArrived(IConnection sender, T packet);
}

View File

@@ -0,0 +1,9 @@
namespace Syntriax.Engine.Network;
public enum PacketDelivery
{
ReliableInOrder,
ReliableOutOfOrder,
UnreliableInOrder,
UnreliableOutOfOrder,
};

View File

@@ -0,0 +1,89 @@
using System;
using System.Linq;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using LiteNetLib;
using LiteNetLib.Utils;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Debug;
namespace Syntriax.Engine.Network;
public class LiteNetLibClient : LiteNetLibCommunicatorBase, INetworkCommunicatorClient
{
private readonly NetDataWriter netDataWriter = new();
private CancellationTokenSource? cancellationTokenSource = null;
protected override IConnection GetConnection(NetPeer peer) => new LiteNetLibServerConnection(peer);
public INetworkCommunicatorClient Connect(string address, int port, string? password = null)
{
if (!UniverseObject.IsInUniverse)
throw new($"{nameof(LiteNetLibClient)} must be in an universe to connect");
password ??= string.Empty;
// Okay, for some reason sometimes LiteNetLib goes dumb when the server hostname has IPv6 address as well as IPv4
// but the client doesn't support IPv6, it still tries to use the v6 unless we explicitly tell the ip to connect to,
// which fails to connect... So for the time being I am preferring IPv4 below over IPv6 for clients.
// TODO: I think this is something that happens on Linux only? I need to check on Windows as well just to be sure.
System.Net.IPAddress[] addresses = System.Net.Dns.GetHostAddresses(address);
string connectionAddress = addresses.FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork, addresses[0]).ToString();
logger?.Log(this, $"Connecting to server at '{address}:{port}' with password '{password}'");
logger?.Log(this, $"Resolved address for {address}: {connectionAddress}");
Manager.Start();
Manager.Connect(connectionAddress, port, password);
return this;
}
public INetworkCommunicatorClient SendToServer<T>(T packet, PacketDelivery packetDelivery) where T : class, new()
{
netDataWriter.Reset();
netPacketProcessor.Write(netDataWriter, packet);
switch (packetDelivery)
{
case PacketDelivery.ReliableInOrder: Manager.FirstPeer.Send(netDataWriter, DeliveryMethod.ReliableOrdered); break;
case PacketDelivery.UnreliableInOrder: Manager.FirstPeer.Send(netDataWriter, DeliveryMethod.Sequenced); break;
case PacketDelivery.ReliableOutOfOrder: Manager.FirstPeer.Send(netDataWriter, DeliveryMethod.ReliableUnordered); break;
case PacketDelivery.UnreliableOutOfOrder: Manager.FirstPeer.Send(netDataWriter, DeliveryMethod.Unreliable); break;
default: Manager.FirstPeer.Send(netDataWriter, DeliveryMethod.ReliableOrdered); break;
}
return this;
}
protected override void OnEnteredUniverse(IUniverse universe)
{
base.OnEnteredUniverse(universe);
cancellationTokenSource = new CancellationTokenSource();
PollEvents(cancellationTokenSource.Token);
}
protected override void OnExitedUniverse(IUniverse universe)
{
base.OnExitedUniverse(universe);
cancellationTokenSource?.Cancel();
}
/// <summary>
/// Client needs to send everything as soon as possible so
/// the events are polled a separate thread running constantly
/// </summary>
private async void PollEvents(CancellationToken cancellationToken) => await Task.Run(() =>
{
while (true)
{
Manager.PollEvents();
Thread.Sleep(1);
}
}, cancellationToken);
}

View File

@@ -0,0 +1,16 @@
using LiteNetLib;
namespace Syntriax.Engine.Network;
public record class LiteNetLibClientConnection(NetPeer NetPeer) : IConnection
{
public string Id { get; } = NetPeer.Id.ToString();
public float Ping => NetPeer.Ping * .001f;
public float RoundTrip => NetPeer.RoundTripTime * .001f;
public int PingMs => NetPeer.Ping;
public int RoundTripMs => NetPeer.RoundTripTime;
public override string ToString() => $"Connection({Id})";
}

View File

@@ -0,0 +1,189 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using LiteNetLib;
using LiteNetLib.Utils;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Debug;
namespace Syntriax.Engine.Network;
public abstract class LiteNetLibCommunicatorBase : Behaviour, INetworkCommunicator
{
protected readonly NetPacketProcessor netPacketProcessor = new();
private readonly Dictionary<Type, Event<IConnection, object>> listeners = [];
private readonly Dictionary<string, IConnection> _connections = [];
private readonly Dictionary<int, IConnection> localPeerIdToConnectionDictionary = [];
protected ILogger? logger = null;
public IReadOnlyDictionary<string, IConnection> Connections => _connections;
public EventBasedNetListener Listener { get; private set; } = null!;
public NetManager Manager { get; private set; } = null!;
public Event<INetworkCommunicator, IConnection> OnConnectionEstablished { get; } = new();
public Event<INetworkCommunicator, IConnection> OnConnectionAbolished { get; } = new();
public INetworkCommunicator Stop()
{
Manager.Stop();
return this;
}
protected override void OnEnteredUniverse(IUniverse universe)
{
base.OnEnteredUniverse(universe);
logger = universe.FindBehaviour<ILogger>();
}
protected override void OnExitedUniverse(IUniverse universe)
{
base.OnExitedUniverse(universe);
logger = null;
Stop();
}
protected virtual void OnPacketArrived<T>(T packet, NetPeer peer) where T : INetworkPacket
{
if (!listeners.TryGetValue(typeof(T), out Event<IConnection, object>? @event))
return;
@event.Invoke(localPeerIdToConnectionDictionary[peer.Id], packet);
}
private void NetworkReceiveEvent(NetPeer peer, NetPacketReader reader, byte channel, DeliveryMethod deliveryMethod)
{
try { netPacketProcessor.ReadAllPackets(reader, peer); }
catch (Exception exception) { logger?.LogException(this, exception, force: true); }
}
protected abstract IConnection GetConnection(NetPeer peer);
private void ConnectionEstablished(NetPeer peer)
{
IConnection connection = GetConnection(peer);
localPeerIdToConnectionDictionary.Add(peer.Id, connection);
_connections.Add(connection.Id, connection);
logger?.Log(this, $"Connection established with ip '{peer.Address}' and id '{connection.Id}'");
OnConnectionEstablished.Invoke(this, connection);
}
private void ConnectionAbolished(NetPeer peer, DisconnectInfo disconnectInfo)
{
if (!localPeerIdToConnectionDictionary.TryGetValue(peer.Id, out var connection))
return;
localPeerIdToConnectionDictionary.Remove(peer.Id);
_connections.Remove(connection.Id);
logger?.Log(this, $"Connection abolished with ip '{peer.Address}' and id '{connection.Id}'");
OnConnectionAbolished.Invoke(this, connection);
}
private void SetupPackets()
{
// Find network packets implementing INetworkPacket
IEnumerable<Type> packetTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes())
.Where(t => typeof(INetworkPacket).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract && !t.IsGenericType);
MethodInfo subscribeReusableMethod = typeof(NetPacketProcessor)
.GetMethods()
.FirstOrDefault(m => m.Name == nameof(NetPacketProcessor.SubscribeReusable) &&
m.GetParameters().Length == 1 &&
m.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(Action<,>)
)!;
MethodInfo registerNestedTypeMethod = typeof(NetPacketProcessor)
.GetMethods()
.FirstOrDefault(m => m.Name == nameof(NetPacketProcessor.RegisterNestedType) &&
m.GetParameters().Length == 0
)!;
MethodInfo onPacketArrivedMethod = typeof(LiteNetLibCommunicatorBase)
.GetMethod(nameof(OnPacketArrived), BindingFlags.NonPublic | BindingFlags.Instance)!;
// Register all network packets by calling the methods bellow where T is our found network packet type
// NetPacketProcessor.SubscribeReusable<T, NetPeer>(Action<T, NetPeer> onReceive)
// NetPacketProcessor.RegisterNestedType<T>()
foreach (Type packetType in packetTypes)
{
if (!packetType.IsClass)
{
MethodInfo genericRegisterNestedTypeMethod = registerNestedTypeMethod.MakeGenericMethod(packetType);
genericRegisterNestedTypeMethod.Invoke(netPacketProcessor, []);
continue;
}
MethodInfo genericOnPacketArrivedMethod = onPacketArrivedMethod.MakeGenericMethod(packetType);
Type delegateType = typeof(Action<,>).MakeGenericType(packetType, typeof(NetPeer));
Delegate delegateHandler = Delegate.CreateDelegate(delegateType, this, genericOnPacketArrivedMethod);
var genericSubscribeReusableMethod = subscribeReusableMethod.MakeGenericMethod(packetType, typeof(NetPeer));
genericSubscribeReusableMethod.Invoke(netPacketProcessor, [delegateHandler]);
}
}
public LiteNetLibCommunicatorBase()
{
Listener = new EventBasedNetListener();
Manager = new NetManager(Listener);
Listener.NetworkReceiveEvent += NetworkReceiveEvent;
Listener.PeerConnectedEvent += ConnectionEstablished;
Listener.PeerDisconnectedEvent += ConnectionAbolished;
SetupEnginePackets();
SetupPackets();
}
private void SetupEnginePackets()
{
// I know, ugly af. I need to find a better way
netPacketProcessor.RegisterNestedType(AABBNetPacker.Write, AABBNetPacker.Read);
netPacketProcessor.RegisterNestedType(CircleNetPacker.Write, CircleNetPacker.Read);
netPacketProcessor.RegisterNestedType(ColorHSVNetPacker.Write, ColorHSVNetPacker.Read);
netPacketProcessor.RegisterNestedType(ColorRGBANetPacker.Write, ColorRGBANetPacker.Read);
netPacketProcessor.RegisterNestedType(ColorRGBNetPacker.Write, ColorRGBNetPacker.Read);
netPacketProcessor.RegisterNestedType(Line2DEquationNetPacker.Write, Line2DEquationNetPacker.Read);
netPacketProcessor.RegisterNestedType(Line2DNetPacker.Write, Line2DNetPacker.Read);
netPacketProcessor.RegisterNestedType(Projection1DNetPacker.Write, Projection1DNetPacker.Read);
netPacketProcessor.RegisterNestedType(QuaternionNetPacker.Write, QuaternionNetPacker.Read);
netPacketProcessor.RegisterNestedType(Shape2DNetPacker.Write, Shape2DNetPacker.Read);
netPacketProcessor.RegisterNestedType(TriangleNetPacker.Write, TriangleNetPacker.Read);
netPacketProcessor.RegisterNestedType(Vector2DNetPacker.Write, Vector2DNetPacker.Read);
netPacketProcessor.RegisterNestedType(Vector3DNetPacker.Write, Vector3DNetPacker.Read);
}
public INetworkCommunicator SubscribeToPackets<T>(Event<IConnection, T>.EventHandler callback)
{
Type type = typeof(T);
if (!listeners.TryGetValue(type, out Event<IConnection, object>? @event))
{
@event = new();
listeners.Add(type, @event);
}
@event.AddListener(EventDelegateWrapper(callback));
return this;
}
public INetworkCommunicator UnsubscribeFromPackets<T>(Event<IConnection, T>.EventHandler callback)
{
Type type = typeof(T);
if (!listeners.TryGetValue(type, out Event<IConnection, object>? @event))
return this;
@event.RemoveListener(EventDelegateWrapper(callback));
return this;
}
private static Event<IConnection, object>.EventHandler EventDelegateWrapper<T>(Event<IConnection, T>.EventHandler callback) => (sender, @object) => callback.Invoke(sender, (T)@object);
}

View File

@@ -0,0 +1,104 @@
using System.Linq;
using LiteNetLib;
using LiteNetLib.Utils;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Debug;
namespace Syntriax.Engine.Network;
public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicatorServer
{
public string Password { get; private set; } = string.Empty;
public int MaxConnectionCount { get; private set; } = 2;
public int Port { get; private set; } = 8888;
private readonly NetDataWriter netDataWriter = new();
public LiteNetLibServer() : this(8888, 2) { }
public LiteNetLibServer(int port, int maxConnectionCount) : base()
{
MaxConnectionCount = maxConnectionCount;
Port = port;
Listener.ConnectionRequestEvent += OnConnectionRequest;
}
protected override IConnection GetConnection(NetPeer peer) => new LiteNetLibClientConnection(peer);
private void OnConnectionRequest(ConnectionRequest request)
{
logger?.Log(this, $"Connection request from ip {request.RemoteEndPoint}");
logger?.Log(this, $"Current connection count: {Connections.Count}");
if (Manager.ConnectedPeersCount < MaxConnectionCount)
request.AcceptIfKey(Password);
else
request.Reject();
}
public INetworkCommunicatorServer Start(int port, int maxConnectionCount, string? password = null)
{
if (!UniverseObject.IsInUniverse)
throw new($"{nameof(LiteNetLibServer)} must be in an universe to start");
Password = password ?? string.Empty;
MaxConnectionCount = maxConnectionCount;
Port = port;
logger?.Log(this, $"Starting server on port '{port}' with password '{Password}' and max connection count '{maxConnectionCount}'");
logger?.Log(this, $"Server status: {(Manager.Start(port) ? "Active" : "Failed")}");
return this;
}
public INetworkCommunicatorServer SendToClient<T>(IConnection connection, T packet, PacketDelivery packetDelivery) where T : class, new()
{
netDataWriter.Reset();
netPacketProcessor.Write(netDataWriter, packet);
if (Manager.ConnectedPeerList.FirstOrDefault(p => p.Id.CompareTo(connection.Id) == 0) is not NetPeer netPeer)
throw new($"Peer {connection} couldn't be found.");
switch (packetDelivery)
{
case PacketDelivery.ReliableInOrder: netPeer.Send(netDataWriter, DeliveryMethod.ReliableOrdered); break;
case PacketDelivery.UnreliableInOrder: netPeer.Send(netDataWriter, DeliveryMethod.Sequenced); break;
case PacketDelivery.ReliableOutOfOrder: netPeer.Send(netDataWriter, DeliveryMethod.ReliableUnordered); break;
case PacketDelivery.UnreliableOutOfOrder: netPeer.Send(netDataWriter, DeliveryMethod.Unreliable); break;
default: netPeer.Send(netDataWriter, DeliveryMethod.ReliableOrdered); break;
}
return this;
}
public INetworkCommunicatorServer SendToAll<T>(T packet, PacketDelivery packetDelivery) where T : class, new()
{
netDataWriter.Reset();
netPacketProcessor.Write(netDataWriter, packet);
switch (packetDelivery)
{
case PacketDelivery.ReliableInOrder: Manager.SendToAll(netDataWriter, DeliveryMethod.ReliableOrdered); break;
case PacketDelivery.UnreliableInOrder: Manager.SendToAll(netDataWriter, DeliveryMethod.Sequenced); break;
case PacketDelivery.ReliableOutOfOrder: Manager.SendToAll(netDataWriter, DeliveryMethod.ReliableUnordered); break;
case PacketDelivery.UnreliableOutOfOrder: Manager.SendToAll(netDataWriter, DeliveryMethod.Unreliable); break;
default: Manager.SendToAll(netDataWriter, DeliveryMethod.ReliableOrdered); break;
}
return this;
}
private void PollEvents(IUniverse sender, IUniverse.UpdateArguments args) => Manager.PollEvents();
protected override void OnEnteredUniverse(IUniverse universe)
{
base.OnEnteredUniverse(universe);
universe.OnPostUpdate.AddListener(PollEvents);
}
protected override void OnExitedUniverse(IUniverse universe)
{
base.OnExitedUniverse(universe);
universe.OnPostUpdate.RemoveListener(PollEvents);
}
}

View File

@@ -0,0 +1,16 @@
using LiteNetLib;
namespace Syntriax.Engine.Network;
public record class LiteNetLibServerConnection(NetPeer NetPeer) : IConnection
{
public string Id => NetPeer.RemoteId.ToString();
public float Ping => NetPeer.Ping * .001f;
public float RoundTrip => NetPeer.RoundTripTime * .001f;
public int PingMs => NetPeer.Ping;
public int RoundTripMs => NetPeer.RoundTripTime;
public override string ToString() => $"Connection({Id})";
}

View File

@@ -0,0 +1,22 @@
using LiteNetLib.Utils;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Network;
internal static class AABBNetPacker
{
internal static void Write(NetDataWriter writer, AABB data)
{
Vector2DNetPacker.Write(writer, data.LowerBoundary);
Vector2DNetPacker.Write(writer, data.UpperBoundary);
}
internal static AABB Read(NetDataReader reader)
{
Vector2D lowerBoundary = Vector2DNetPacker.Read(reader);
Vector2D upperBoundary = Vector2DNetPacker.Read(reader);
return new AABB(lowerBoundary, upperBoundary);
}
}

View File

@@ -0,0 +1,22 @@
using LiteNetLib.Utils;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Network;
internal static class CircleNetPacker
{
internal static void Write(NetDataWriter writer, Circle data)
{
Vector2DNetPacker.Write(writer, data.Center);
writer.Put(data.Radius);
}
internal static Circle Read(NetDataReader reader)
{
Vector2D center = Vector2DNetPacker.Read(reader);
float radius = reader.GetFloat();
return new Circle(center, radius);
}
}

View File

@@ -0,0 +1,24 @@
using LiteNetLib.Utils;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Network;
internal static class ColorHSVNetPacker
{
internal static void Write(NetDataWriter writer, ColorHSV data)
{
writer.Put(data.Hue);
writer.Put(data.Saturation);
writer.Put(data.Value);
}
internal static ColorHSV Read(NetDataReader reader)
{
float hue = reader.GetFloat();
float saturation = reader.GetFloat();
float value = reader.GetFloat();
return new ColorHSV(hue, saturation, value);
}
}

View File

@@ -0,0 +1,26 @@
using LiteNetLib.Utils;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Network;
internal static class ColorRGBANetPacker
{
internal static void Write(NetDataWriter writer, ColorRGBA data)
{
writer.Put(data.R);
writer.Put(data.G);
writer.Put(data.B);
writer.Put(data.A);
}
internal static ColorRGBA Read(NetDataReader reader)
{
byte red = reader.GetByte();
byte green = reader.GetByte();
byte blue = reader.GetByte();
byte alpha = reader.GetByte();
return new ColorRGBA(red, green, blue, alpha);
}
}

View File

@@ -0,0 +1,24 @@
using LiteNetLib.Utils;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Network;
internal static class ColorRGBNetPacker
{
internal static void Write(NetDataWriter writer, ColorRGB data)
{
writer.Put(data.R);
writer.Put(data.G);
writer.Put(data.B);
}
internal static ColorRGB Read(NetDataReader reader)
{
byte red = reader.GetByte();
byte green = reader.GetByte();
byte blue = reader.GetByte();
return new ColorRGB(red, green, blue);
}
}

View File

@@ -0,0 +1,22 @@
using LiteNetLib.Utils;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Network;
internal static class Line2DEquationNetPacker
{
internal static void Write(NetDataWriter writer, Line2DEquation data)
{
writer.Put(data.Slope);
writer.Put(data.OffsetY);
}
internal static Line2DEquation Read(NetDataReader reader)
{
float slope = reader.GetFloat();
float offsetY = reader.GetFloat();
return new Line2DEquation(slope, offsetY);
}
}

View File

@@ -0,0 +1,22 @@
using LiteNetLib.Utils;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Network;
internal static class Line2DNetPacker
{
internal static void Write(NetDataWriter writer, Line2D data)
{
Vector2DNetPacker.Write(writer, data.From);
Vector2DNetPacker.Write(writer, data.To);
}
internal static Line2D Read(NetDataReader reader)
{
Vector2D from = Vector2DNetPacker.Read(reader);
Vector2D to = Vector2DNetPacker.Read(reader);
return new Line2D(from, to);
}
}

View File

@@ -0,0 +1,22 @@
using LiteNetLib.Utils;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Network;
internal static class Projection1DNetPacker
{
internal static void Write(NetDataWriter writer, Projection1D data)
{
writer.Put(data.Min);
writer.Put(data.Max);
}
internal static Projection1D Read(NetDataReader reader)
{
float min = reader.GetFloat();
float max = reader.GetFloat();
return new Projection1D(min, max);
}
}

View File

@@ -0,0 +1,26 @@
using LiteNetLib.Utils;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Network;
internal static class QuaternionNetPacker
{
internal static void Write(NetDataWriter writer, Quaternion data)
{
writer.Put(data.X);
writer.Put(data.Y);
writer.Put(data.Z);
writer.Put(data.W);
}
internal static Quaternion Read(NetDataReader reader)
{
float x = reader.GetFloat();
float y = reader.GetFloat();
float z = reader.GetFloat();
float w = reader.GetFloat();
return new Quaternion(x, y, z, w);
}
}

View File

@@ -0,0 +1,28 @@
using System.Collections.Generic;
using LiteNetLib.Utils;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Network;
internal static class Shape2DNetPacker
{
internal static void Write(NetDataWriter writer, Shape2D data)
{
writer.Put(data.Vertices.Count);
foreach (Vector2D vertex in data.Vertices)
Vector2DNetPacker.Write(writer, vertex);
}
internal static Shape2D Read(NetDataReader reader)
{
int verticesCount = reader.GetInt();
List<Vector2D> vertices = new(verticesCount);
for (int i = 0; i < verticesCount; i++)
vertices.Add(Vector2DNetPacker.Read(reader));
return new Shape2D(vertices);
}
}

View File

@@ -0,0 +1,24 @@
using LiteNetLib.Utils;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Network;
internal static class TriangleNetPacker
{
internal static void Write(NetDataWriter writer, Triangle data)
{
Vector2DNetPacker.Write(writer, data.A);
Vector2DNetPacker.Write(writer, data.B);
Vector2DNetPacker.Write(writer, data.C);
}
internal static Triangle Read(NetDataReader reader)
{
Vector2D a = Vector2DNetPacker.Read(reader);
Vector2D b = Vector2DNetPacker.Read(reader);
Vector2D c = Vector2DNetPacker.Read(reader);
return new Triangle(a, b, c);
}
}

View File

@@ -0,0 +1,22 @@
using LiteNetLib.Utils;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Network;
internal static class Vector2DNetPacker
{
internal static void Write(NetDataWriter writer, Vector2D data)
{
writer.Put(data.X);
writer.Put(data.Y);
}
internal static Vector2D Read(NetDataReader reader)
{
float x = reader.GetFloat();
float y = reader.GetFloat();
return new Vector2D(x, y);
}
}

View File

@@ -0,0 +1,24 @@
using LiteNetLib.Utils;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Network;
internal static class Vector3DNetPacker
{
internal static void Write(NetDataWriter writer, Vector3D data)
{
writer.Put(data.X);
writer.Put(data.Y);
writer.Put(data.Z);
}
internal static Vector3D Read(NetDataReader reader)
{
float x = reader.GetFloat();
float y = reader.GetFloat();
float z = reader.GetFloat();
return new Vector3D(x, y, z);
}
}

View File

@@ -0,0 +1,333 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Network;
/// <summary>
/// Intermediary manager that looks up in it's hierarchy for a <see cref="INetworkCommunicator"/> to route/broadcast it's received packets to their destinations.
/// </summary>
public class NetworkManager : Behaviour, INetworkManager
{
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> clientPacketArrivalMethods = [];
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> serverPacketArrivalMethods = [];
private readonly Dictionary<Type, Dictionary<string, object>> clientPacketRouters = [];
private readonly Dictionary<Type, Dictionary<string, object>> serverPacketRouters = [];
private readonly List<(Type packetType, Delegate @delegate)> packetRetrievalDelegates = [];
private readonly Dictionary<Type, MethodInfo> clearRoutesMethods = [];
private readonly Dictionary<Type, MethodInfo> registerPacketListenersMethods = [];
private readonly Dictionary<string, INetworkEntity> _networkEntities = [];
public IReadOnlyDictionary<string, INetworkEntity> NetworkEntities => _networkEntities;
private readonly BehaviourCollector<INetworkEntity> _networkEntityCollector = new();
public IBehaviourCollector<INetworkEntity> NetworkEntityCollector => _networkEntityCollector;
private INetworkCommunicator _networkCommunicator = null!;
public INetworkCommunicator NetworkCommunicator
{
get => _networkCommunicator;
set
{
if (_networkCommunicator == value)
return;
var previousCommunicator = _networkCommunicator;
_networkCommunicator = value;
if (previousCommunicator is not null) UnsubscribeCommunicatorMethods(previousCommunicator);
if (_networkCommunicator is not null) SubscribeCommunicatorMethods(_networkCommunicator);
}
}
#region Communicator Subscriptions
private void SubscribeCommunicatorMethods(INetworkCommunicator networkCommunicator)
{
MethodInfo subscribeToPacketsMethod = typeof(INetworkCommunicator)
.GetMethod(nameof(INetworkCommunicator.SubscribeToPackets), BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)!;
foreach ((Type packetType, Delegate @delegate) in packetRetrievalDelegates)
{
MethodInfo genericSubscribeMethod = subscribeToPacketsMethod.MakeGenericMethod(packetType);
genericSubscribeMethod.Invoke(networkCommunicator, [@delegate]);
}
}
private void UnsubscribeCommunicatorMethods(INetworkCommunicator networkCommunicator)
{
MethodInfo unsubscribeFromPacketsMethod = typeof(INetworkCommunicator)
.GetMethod(nameof(INetworkCommunicator.UnsubscribeFromPackets), BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)!;
foreach ((Type packetType, Delegate @delegate) in packetRetrievalDelegates)
{
MethodInfo genericUnsubscribeMethod = unsubscribeFromPacketsMethod.MakeGenericMethod(packetType);
genericUnsubscribeMethod.Invoke(networkCommunicator, [@delegate]);
}
}
#endregion
#region Packet Routing
private void OnPacketReceived<T>(IConnection sender, T entityDataPacket)
{
if (entityDataPacket is IEntityNetworkPacket entityPacket)
RoutePacket(sender, entityDataPacket, entityPacket);
else
BroadcastPacket(sender, entityDataPacket);
}
private void RoutePacket<T>(IConnection sender, T entityDataPacket, IEntityNetworkPacket entityPacket)
{
if (NetworkCommunicator is INetworkCommunicatorClient)
RoutePacket(clientPacketRouters, entityPacket.EntityId, sender, entityDataPacket);
if (NetworkCommunicator is INetworkCommunicatorServer)
RoutePacket(serverPacketRouters, entityPacket.EntityId, sender, entityDataPacket);
}
private void BroadcastPacket<T>(IConnection sender, T entityDataPacket)
{
if (NetworkCommunicator is INetworkCommunicatorClient)
BroadcastPacket(clientPacketRouters, sender, entityDataPacket);
if (NetworkCommunicator is INetworkCommunicatorServer)
BroadcastPacket(serverPacketRouters, sender, entityDataPacket);
}
private static void BroadcastPacket<T>(
Dictionary<Type, Dictionary<string, object>> packetRouters,
IConnection sender,
T entityDataPacket)
{
if (!packetRouters.TryGetValue(entityDataPacket!.GetType(), out Dictionary<string, object>? routers))
return;
foreach ((string behaviourId, object routerEventReference) in routers)
{
Event<IConnection, T> routerEvent = (Event<IConnection, T>)routerEventReference;
routerEvent.Invoke(sender, entityDataPacket!);
}
}
private static void RoutePacket<T>(
Dictionary<Type, Dictionary<string, object>> packetRouters,
string entityId,
IConnection sender,
T entityDataPacket)
{
if (!packetRouters.TryGetValue(entityDataPacket!.GetType(), out Dictionary<string, object>? routers))
return;
if (!routers.TryGetValue(entityId, out object? routerEventReference))
return;
Event<IConnection, T> routerEvent = (Event<IConnection, T>)routerEventReference;
routerEvent.Invoke(sender, entityDataPacket!);
}
#endregion
#region Packet Routers
private void RegisterPacketRoutersFor(
INetworkEntity behaviour,
Dictionary<Type, Dictionary<string, object>> packetRouters,
Dictionary<Type, Dictionary<Type, List<MethodInfo>>> packetArrivalMethods,
NetworkType networkType)
{
if (!packetArrivalMethods.TryGetValue(behaviour.GetType(), out Dictionary<Type, List<MethodInfo>>? arrivalMethods))
return;
foreach ((Type packetType, List<MethodInfo> methods) in arrivalMethods)
foreach (MethodInfo receiveMethod in methods)
{
if (!packetRouters.TryGetValue(packetType, out Dictionary<string, object>? routers))
{
routers = [];
packetRouters.Add(packetType, routers);
}
object packetListenerEvent = CreateEventAndRegister(packetType, behaviour, networkType);
routers.Add(behaviour.Id, packetListenerEvent);
}
}
private object CreateEventAndRegister(Type packetType, INetworkEntity behaviour, NetworkType networkType)
{
Type genericEventType = typeof(Event<,>).MakeGenericType(typeof(IConnection), packetType);
object packetListenerEvent = Activator.CreateInstance(genericEventType)!;
if (!registerPacketListenersMethods.TryGetValue(packetType, out MethodInfo? registerPacketListenerMethod))
throw new($"{nameof(RegisterPacketListenerEvent)} for {packetType.Name} has not been cached.");
registerPacketListenerMethod.Invoke(this, [behaviour, packetListenerEvent, networkType]);
return packetListenerEvent;
}
private static void RegisterPacketListenerEvent<T>(
INetworkEntity behaviour,
Event<IConnection, T> packetListenerEvent,
NetworkType networkType)
{
switch (networkType)
{
case NetworkType.Client: packetListenerEvent.AddListener((sender, packet) => ((IPacketListenerClient<T>)behaviour).OnClientPacketArrived(sender, packet)); break;
case NetworkType.Server: packetListenerEvent.AddListener((sender, packet) => ((IPacketListenerServer<T>)behaviour).OnServerPacketArrived(sender, packet)); break;
}
}
private void UnregisterPacketRoutersFor(
INetworkEntity behaviour,
Dictionary<Type, Dictionary<string, object>> packetRouters,
Dictionary<Type, Dictionary<Type, List<MethodInfo>>> packetArrivalMethods)
{
if (!packetArrivalMethods.TryGetValue(behaviour.GetType(), out Dictionary<Type, List<MethodInfo>>? arrivalMethods))
return;
foreach ((Type packetType, List<MethodInfo> methods) in arrivalMethods)
{
if (!packetRouters.TryGetValue(packetType, out Dictionary<string, object>? routers))
continue;
if (!routers.TryGetValue(behaviour.Id, out object? routerEventReference))
continue;
if (!clearRoutesMethods.TryGetValue(packetType, out MethodInfo? clearRouterMethod))
continue;
clearRouterMethod.Invoke(this, [routerEventReference]);
}
}
private static void ClearRouter<T>(object routerEventReference)
{
Event<IConnection, T> routerEvent = (Event<IConnection, T>)routerEventReference;
routerEvent.Clear();
}
#endregion
#region Engine Callbacks
private void OnCollected(IBehaviourCollector<INetworkEntity> sender, IBehaviourCollector<INetworkEntity>.BehaviourCollectedArguments args)
{
INetworkEntity collectedBehaviour = args.BehaviourCollected;
if (!_networkEntities.TryAdd(collectedBehaviour.Id, collectedBehaviour))
throw new($"Unable to add {collectedBehaviour.Id} to {nameof(NetworkManager)}");
RegisterPacketRoutersFor(collectedBehaviour, clientPacketRouters, clientPacketArrivalMethods, NetworkType.Client);
RegisterPacketRoutersFor(collectedBehaviour, serverPacketRouters, serverPacketArrivalMethods, NetworkType.Server);
}
private void OnRemoved(IBehaviourCollector<INetworkEntity> sender, IBehaviourCollector<INetworkEntity>.BehaviourRemovedArguments args)
{
INetworkEntity removedBehaviour = args.BehaviourRemoved;
if (!_networkEntities.Remove(args.BehaviourRemoved.Id))
return;
UnregisterPacketRoutersFor(removedBehaviour, clientPacketRouters, clientPacketArrivalMethods);
UnregisterPacketRoutersFor(removedBehaviour, serverPacketRouters, serverPacketArrivalMethods);
}
protected override void OnExitedUniverse(IUniverse universe) => _networkEntityCollector.Unassign();
protected override void OnEnteredUniverse(IUniverse universe)
{
_networkEntityCollector.Assign(universe);
NetworkCommunicator = BehaviourController.GetRequiredBehaviourInParent<INetworkCommunicator>();
}
#endregion
#region Initialization
public NetworkManager()
{
CachePacketRetrievalDelegates();
CacheRegistrationMethods();
CachePacketArrivalMethods();
_networkEntityCollector.OnCollected.AddListener(OnCollected);
_networkEntityCollector.OnRemoved.AddListener(OnRemoved);
}
private void CachePacketRetrievalDelegates()
{
IEnumerable<Type> packetTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes())
.Where(t => typeof(INetworkPacket).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract && !t.IsGenericType);
MethodInfo onPacketArrivedMethod = GetType()
.GetMethod(nameof(OnPacketReceived), BindingFlags.NonPublic | BindingFlags.Instance)!;
foreach (Type packetType in packetTypes)
{
MethodInfo genericOnPacketArrivedMethod = onPacketArrivedMethod.MakeGenericMethod(packetType);
Type genericDelegateType = typeof(Event<,>.EventHandler).MakeGenericType(typeof(IConnection), packetType);
Delegate genericPacketReceivedDelegate = Delegate.CreateDelegate(genericDelegateType, this, genericOnPacketArrivedMethod);
packetRetrievalDelegates.Add((packetType, genericPacketReceivedDelegate));
}
}
private void CacheRegistrationMethods()
{
CacheRegistrationMethods(registerPacketListenersMethods, nameof(RegisterPacketListenerEvent));
CacheRegistrationMethods(clearRoutesMethods, nameof(ClearRouter));
}
private void CacheRegistrationMethods(Dictionary<Type, MethodInfo> registrationMethods, string methodName)
{
MethodInfo registerPacketMethod = typeof(NetworkManager).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static)!;
foreach ((Type packetType, Delegate @delegate) in packetRetrievalDelegates)
{
MethodInfo genericMethod = registerPacketMethod.MakeGenericMethod(packetType);
registrationMethods.Add(packetType, genericMethod);
}
}
private void CachePacketArrivalMethods()
{
CachePacketArrivalMethods(clientPacketArrivalMethods, typeof(IPacketListenerClient<>), nameof(IPacketListenerClient<INetworkEntity>.OnClientPacketArrived));
CachePacketArrivalMethods(serverPacketArrivalMethods, typeof(IPacketListenerServer<>), nameof(IPacketListenerServer<INetworkEntity>.OnServerPacketArrived));
}
private static void CachePacketArrivalMethods(Dictionary<Type, Dictionary<Type, List<MethodInfo>>> packetArrivalMethods, Type listenerType, string packetArrivalMethodName)
{
foreach (Type listenerClass in GetGenericsWith(listenerType))
{
Dictionary<Type, List<MethodInfo>> packetRouters = [];
packetArrivalMethods.Add(listenerClass, packetRouters);
foreach (Type packetListener in GetGenericInterfacesWith(listenerType, listenerClass))
{
Type packetType = packetListener.GetGenericArguments().First();
List<MethodInfo> arrivalMethods = packetListener
.GetMethods()
.Where(m => m.Name == packetArrivalMethodName)
.ToList();
packetRouters.Add(packetType, arrivalMethods);
}
}
}
private static IEnumerable<Type> GetGenericsWith(Type type)
=> AppDomain.CurrentDomain
.GetAssemblies()
.SelectMany(a =>
a.GetTypes().Where(
t => t.GetInterfaces().Any(
i => i.IsGenericType && i.GetGenericTypeDefinition() == type
)
)
);
private static IEnumerable<Type> GetGenericInterfacesWith(Type interfaceType, Type type)
=> type.GetInterfaces().Where(
i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType
);
#endregion
private enum NetworkType { Client, Server }
}

123
Shared/PongUniverse.cs Normal file
View File

@@ -0,0 +1,123 @@
using System;
using Microsoft.Xna.Framework.Input;
using Pong.Behaviours;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Factory;
using Syntriax.Engine.Integration.MonoGame;
using Syntriax.Engine.Network;
using Syntriax.Engine.Physics2D;
using Syntriax.Engine.Systems.Tween;
namespace Pong;
public static class PongUniverse
{
public static IUniverse ApplyPongClient(Universe universe, string server, int port)
{
LiteNetLibClient client = universe.InstantiateUniverseObject().SetUniverseObject("Client").BehaviourController.AddBehaviour<LiteNetLibClient>();
client.BehaviourController.AddBehaviour<NetworkManager>();
universe.OnPreUpdate.AddOneTimeListener((_, _) => client.Connect(server, port));
DrawManager drawManager = universe.InstantiateUniverseObject().SetUniverseObject("Draw Manager").BehaviourController.AddBehaviour<DrawManager>();
universe.InstantiateUniverseObject().SetUniverseObject("Triangle Batcher", drawManager.UniverseObject).BehaviourController.AddBehaviour<TriangleBatcher>();
universe.InstantiateUniverseObject().SetUniverseObject("Sprite Batcher", drawManager.UniverseObject).BehaviourController.AddBehaviour<SpriteBatcher>();
////////////////////////////////////////////////////////////////////////////////////
universe.InstantiateUniverseObject().SetUniverseObject("Camera")
.BehaviourController.AddBehaviour<Transform2D>()
.BehaviourController.AddBehaviour<CameraController>()
.BehaviourController.AddBehaviour<MonoGameCamera2D>();
////////////////////////////////////////////////////////////////////////////////////
universe.InstantiateUniverseObject().SetUniverseObject("Score Left")
.BehaviourController.AddBehaviour<Transform2D>().SetTransform(position: new Vector2D(-250f, 250f), scale: Vector2D.One * .25f)
.BehaviourController.AddBehaviour<ScoreLabel>(true);
universe.InstantiateUniverseObject().SetUniverseObject("Score Right")
.BehaviourController.AddBehaviour<Transform2D>().SetTransform(position: new Vector2D(250f, 250f), scale: Vector2D.One * .25f)
.BehaviourController.AddBehaviour<ScoreLabel>(false);
return universe;
}
public static IUniverse ApplyPongServer(Universe universe, int port)
{
LiteNetLibServer server = universe.InstantiateUniverseObject().SetUniverseObject("Server").BehaviourController.AddBehaviour<LiteNetLibServer>();
server.BehaviourController.AddBehaviour<NetworkManager>();
universe.OnPreUpdate.AddOneTimeListener((_, _) => server.Start(port, 2));
return universe;
}
public static IUniverse ApplyPongUniverse(Universe universe)
{
universe.InstantiateUniverseObject().SetUniverseObject("Update Manager").BehaviourController.AddBehaviour<UpdateManager>();
universe.InstantiateUniverseObject().SetUniverseObject("Coroutine Manager").BehaviourController.AddBehaviour<CoroutineManager>();
universe.InstantiateUniverseObject().SetUniverseObject("Tween Manager").BehaviourController.AddBehaviour<TweenManager>();
universe.InstantiateUniverseObject().SetUniverseObject("Physics Engine 2D").BehaviourController.AddBehaviour<PhysicsEngine2D>();
////////////////////////////////////////////////////////////////////////////////////
PongManager pongManager = universe.InstantiateUniverseObject().SetUniverseObject("Pong Game Manager")
.BehaviourController.AddBehaviour<PongManager>(5);
////////////////////////////////////////////////////////////////////////////////////
universe.InstantiateUniverseObject().SetUniverseObject("Ball")
.BehaviourController.AddBehaviour<Transform2D>().SetTransform(position: new Vector2D(0, 0f), scale: new Vector2D(10f, 10f))
.BehaviourController.AddBehaviour<DrawableColliderCircle>(new Circle(Vector2D.Zero, 1f))
.BehaviourController.AddBehaviour<Ball>()
.BehaviourController.AddBehaviour<RigidBody2D>();
////////////////////////////////////////////////////////////////////////////////////
IUniverseObject leftPaddle = UniverseObjectFactory.Instantiate().SetUniverseObject("Left Paddle");
leftPaddle.BehaviourController.AddBehaviour<Transform2D>().SetTransform(position: new Vector2D(-468f, 0f), scale: new Vector2D(15f, 60f))
.BehaviourController.AddBehaviour<DrawableColliderShape>(Shape2D.Square)
.BehaviourController.AddBehaviour<RigidBody2D>().IsStatic = true;
Paddle leftPaddleBehaviour = BehaviourFactory.Instantiate<Paddle>(Keys.W, Keys.S, 228f, -228f, 400f);
leftPaddleBehaviour.Id = "lp";
leftPaddle.BehaviourController.AddBehaviour(leftPaddleBehaviour);
universe.Register(leftPaddle);
IUniverseObject rightPaddle = UniverseObjectFactory.Instantiate().SetUniverseObject("Right Paddle");
rightPaddle.BehaviourController.AddBehaviour<Transform2D>().SetTransform(position: new Vector2D(468f, 0f), scale: new Vector2D(15f, 60f))
.BehaviourController.AddBehaviour<DrawableColliderShape>(Shape2D.Square)
.BehaviourController.AddBehaviour<RigidBody2D>().IsStatic = true;
Paddle rightPaddleBehaviour = BehaviourFactory.Instantiate<Paddle>(Keys.Up, Keys.Down, 228f, -228f, 400f);
rightPaddleBehaviour.Id = "rp";
rightPaddle.BehaviourController.AddBehaviour(rightPaddleBehaviour);
universe.Register(rightPaddle);
////////////////////////////////////////////////////////////////////////////////////
universe.InstantiateUniverseObject().SetUniverseObject("Wall Top")
.BehaviourController.AddBehaviour<Transform2D>().SetTransform(position: new Vector2D(0f, 308f), scale: new Vector2D(552f, 20f))
.BehaviourController.AddBehaviour<DrawableColliderShape>(Shape2D.Square)
.BehaviourController.AddBehaviour<RigidBody2D>().IsStatic = true;
universe.InstantiateUniverseObject().SetUniverseObject("Wall Bottom")
.BehaviourController.AddBehaviour<Transform2D>().SetTransform(position: new Vector2D(0f, -308f), scale: new Vector2D(552f, 20f))
.BehaviourController.AddBehaviour<DrawableColliderShape>(Shape2D.Square)
.BehaviourController.AddBehaviour<RigidBody2D>().IsStatic = true;
universe.InstantiateUniverseObject().SetUniverseObject("Wall Right")
.BehaviourController.AddBehaviour<Transform2D>().SetTransform(position: new Vector2D(532f, 0f), scale: new Vector2D(20f, 328f))
.BehaviourController.AddBehaviour<ScoreWall>((Action)pongManager.ScoreToLeft)
.BehaviourController.AddBehaviour<DrawableColliderShape>(Shape2D.Square)
.BehaviourController.AddBehaviour<RigidBody2D>().IsStatic = true;
universe.InstantiateUniverseObject().SetUniverseObject("Wall Left")
.BehaviourController.AddBehaviour<Transform2D>().SetTransform(position: new Vector2D(-532f, 0f), scale: new Vector2D(20f, 328f))
.BehaviourController.AddBehaviour<ScoreWall>((Action)pongManager.ScoreToRight)
.BehaviourController.AddBehaviour<DrawableColliderShape>(Shape2D.Square)
.BehaviourController.AddBehaviour<RigidBody2D>().IsStatic = true;
return universe;
}
}

16
Shared/Shared.csproj Normal file
View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LiteNetLib" Version="1.3.1" />
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.2.1105">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../Engine/Engine/Engine.csproj" />
<ProjectReference Include="../Engine/Engine.Integration/Engine.Integration.MonoGame/Engine.Integration.MonoGame.csproj" />
</ItemGroup>
</Project>