diff --git a/demo/Blackholio/client-unity/Assets/PlayModeTests/PlayModeExampleTest.cs b/demo/Blackholio/client-unity/Assets/PlayModeTests/PlayModeExampleTest.cs index 09d5622dd99..fde14ca216a 100644 --- a/demo/Blackholio/client-unity/Assets/PlayModeTests/PlayModeExampleTest.cs +++ b/demo/Blackholio/client-unity/Assets/PlayModeTests/PlayModeExampleTest.cs @@ -89,7 +89,7 @@ public IEnumerator CreatePlayerAndTestDecay() Debug.Assert(circle != null, nameof(circle) + " != null"); var ourEntity = GameManager.Conn.Db.Entity.EntityId.Find(circle.EntityId); var toChosenFood = new UnityEngine.Vector2(1000, 0); - uint chosenFoodId = 0; + int chosenFoodId = 0; foreach (var food in GameManager.Conn.Db.Food.Iter()) { var thisFoodId = food.EntityId; diff --git a/demo/Blackholio/client-unity/Assets/Scripts/EntityController.cs b/demo/Blackholio/client-unity/Assets/Scripts/EntityController.cs index 29d3d569858..8e23f92a24e 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/EntityController.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/EntityController.cs @@ -12,14 +12,14 @@ public abstract class EntityController : MonoBehaviour private static readonly int ShaderColorProperty = Shader.PropertyToID("_Color"); - [DoNotSerialize] public uint EntityId; + [DoNotSerialize] public int EntityId; protected float LerpTime; protected Vector3 LerpStartPosition; protected Vector3 LerpTargetPosition; protected Vector3 TargetScale; - protected virtual void Spawn(uint entityId) + protected virtual void Spawn(int entityId) { EntityId = entityId; @@ -82,12 +82,12 @@ public virtual void Update() transform.localScale = Vector3.Lerp(transform.localScale, TargetScale, Time.deltaTime * 8); } - public static Vector3 MassToScale(uint mass) + public static Vector3 MassToScale(int mass) { var diameter = MassToDiameter(mass); return new Vector3(diameter, diameter, 1); } - public static float MassToRadius(uint mass) => Mathf.Sqrt(mass); - public static float MassToDiameter(uint mass) => MassToRadius(mass) * 2; + public static float MassToRadius(int mass) => Mathf.Sqrt(mass); + public static float MassToDiameter(int mass) => MassToRadius(mass) * 2; } \ No newline at end of file diff --git a/demo/Blackholio/client-unity/Assets/Scripts/GameManager.cs b/demo/Blackholio/client-unity/Assets/Scripts/GameManager.cs index e56bddc82e0..3996b5d4fc6 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/GameManager.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/GameManager.cs @@ -24,8 +24,8 @@ public class GameManager : MonoBehaviour public static Identity LocalIdentity { get; private set; } public static DbConnection Conn { get; private set; } - public static Dictionary Entities = new Dictionary(); - public static Dictionary Players = new Dictionary(); + public static Dictionary Entities = new Dictionary(); + public static Dictionary Players = new Dictionary(); private void Start() { @@ -102,19 +102,19 @@ private void HandleSubscriptionApplied(SubscriptionEventContext ctx) { Debug.Log("Subscription applied!"); OnSubscriptionApplied?.Invoke(); - + // Once we have the initial subscription sync'd to the client cache // Get the world size from the config table and set up the arena var worldSize = Conn.Db.Config.Id.Find(0).WorldSize; SetupArena(worldSize); - + // Check to see if we already have a player, if we don't we'll need to create one var player = ctx.Db.Player.Identity.Find(LocalIdentity); if (string.IsNullOrEmpty(player.Name)) { // The player has to choose a username UIUsernameChooser.Instance.Show(true); - + // When our username is updated, hide the username chooser Conn.Db.Player.OnUpdate += (_, _, newPlayer) => { @@ -123,14 +123,18 @@ private void HandleSubscriptionApplied(SubscriptionEventContext ctx) UIUsernameChooser.Instance.Show(false); } }; - } else { + } + else + { // We already have a player if (ctx.Db.Circle.PlayerId.Filter(player.PlayerId).Any()) { // We already have at least one circle, we should just be able to start // playing immediately. UIUsernameChooser.Instance.Show(false); - } else { + } + else + { // Create a new circle for our player. ctx.Reducers.EnterGame(player.Name); } @@ -209,7 +213,7 @@ private static void PlayerOnDelete(EventContext context, Player deletedvalue) } } - private static PlayerController GetOrCreatePlayer(uint playerId) + private static PlayerController GetOrCreatePlayer(int playerId) { if (!Players.TryGetValue(playerId, out var playerController)) { diff --git a/demo/Blackholio/client-unity/Assets/Scripts/LeaderboardRow.cs b/demo/Blackholio/client-unity/Assets/Scripts/LeaderboardRow.cs index 1a11fe98871..908f95fa29c 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/LeaderboardRow.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/LeaderboardRow.cs @@ -8,7 +8,7 @@ public class LeaderboardRow : MonoBehaviour public TextMeshProUGUI UsernameText; public TextMeshProUGUI MassText; - public void SetData(string username, uint mass) + public void SetData(string username, int mass) { UsernameText.text = username; MassText.text = mass.ToString(); diff --git a/demo/Blackholio/client-unity/Assets/Scripts/PlayerController.cs b/demo/Blackholio/client-unity/Assets/Scripts/PlayerController.cs index 89fa0088739..f2fbf008b65 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/PlayerController.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/PlayerController.cs @@ -11,7 +11,7 @@ public class PlayerController : MonoBehaviour public static PlayerController Local { get; private set; } - private uint PlayerId; + private int PlayerId; private float LastMovementSendTimestamp; private Vector2? LockInputPosition; private List OwnedCircles = new List(); @@ -56,9 +56,9 @@ public void OnCircleDeleted(CircleController deletedCircle) } } - public uint TotalMass() + public int TotalMass() { - return (uint)OwnedCircles + return (int)OwnedCircles .Select(circle => GameManager.Conn.Db.Entity.EntityId.Find(circle.EntityId)) .Sum(e => e?.Mass ?? 0); //If this entity is being deleted on the same frame that we're moving, we can have a null entity here. } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/SpacetimeDBClient.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/SpacetimeDBClient.g.cs index 2096f4ad972..29d5d5196e1 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/SpacetimeDBClient.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/SpacetimeDBClient.g.cs @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 1.4.0 (commit 9e4684ee0f7b78689c60efbdcb7a9e23e34eea15). +// This was generated using spacetimedb cli version 1.4.0 (commit ce1adea804b92e0038999d9237cfabca7bd82940). #nullable enable diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Circle.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Circle.g.cs index 391d3727b78..bc664fb15fc 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Circle.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Circle.g.cs @@ -17,18 +17,18 @@ public sealed class CircleHandle : RemoteTableHandle { protected override string RemoteTableName => "circle"; - public sealed class EntityIdUniqueIndex : UniqueIndexBase + public sealed class EntityIdUniqueIndex : UniqueIndexBase { - protected override uint GetKey(Circle row) => row.EntityId; + protected override int GetKey(Circle row) => row.EntityId; public EntityIdUniqueIndex(CircleHandle table) : base(table) { } } public readonly EntityIdUniqueIndex EntityId; - public sealed class PlayerIdIndex : BTreeIndexBase + public sealed class PlayerIdIndex : BTreeIndexBase { - protected override uint GetKey(Circle row) => row.PlayerId; + protected override int GetKey(Circle row) => row.PlayerId; public PlayerIdIndex(CircleHandle table) : base(table) { } } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Config.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Config.g.cs index 8c8b5a79743..9c24535cf38 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Config.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Config.g.cs @@ -17,9 +17,9 @@ public sealed class ConfigHandle : RemoteTableHandle { protected override string RemoteTableName => "config"; - public sealed class IdUniqueIndex : UniqueIndexBase + public sealed class IdUniqueIndex : UniqueIndexBase { - protected override uint GetKey(Config row) => row.Id; + protected override int GetKey(Config row) => row.Id; public IdUniqueIndex(ConfigHandle table) : base(table) { } } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Entity.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Entity.g.cs index 0404666a069..c2ca1e53672 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Entity.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Entity.g.cs @@ -17,9 +17,9 @@ public sealed class EntityHandle : RemoteTableHandle { protected override string RemoteTableName => "entity"; - public sealed class EntityIdUniqueIndex : UniqueIndexBase + public sealed class EntityIdUniqueIndex : UniqueIndexBase { - protected override uint GetKey(Entity row) => row.EntityId; + protected override int GetKey(Entity row) => row.EntityId; public EntityIdUniqueIndex(EntityHandle table) : base(table) { } } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Food.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Food.g.cs index ee7b5fc4356..d36b66ed744 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Food.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Food.g.cs @@ -17,9 +17,9 @@ public sealed class FoodHandle : RemoteTableHandle { protected override string RemoteTableName => "food"; - public sealed class EntityIdUniqueIndex : UniqueIndexBase + public sealed class EntityIdUniqueIndex : UniqueIndexBase { - protected override uint GetKey(Food row) => row.EntityId; + protected override int GetKey(Food row) => row.EntityId; public EntityIdUniqueIndex(FoodHandle table) : base(table) { } } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutCircle.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutCircle.g.cs index 9b06b194056..5d3d6fa6f60 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutCircle.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutCircle.g.cs @@ -17,18 +17,18 @@ public sealed class LoggedOutCircleHandle : RemoteTableHandle "logged_out_circle"; - public sealed class EntityIdUniqueIndex : UniqueIndexBase + public sealed class EntityIdUniqueIndex : UniqueIndexBase { - protected override uint GetKey(Circle row) => row.EntityId; + protected override int GetKey(Circle row) => row.EntityId; public EntityIdUniqueIndex(LoggedOutCircleHandle table) : base(table) { } } public readonly EntityIdUniqueIndex EntityId; - public sealed class PlayerIdIndex : BTreeIndexBase + public sealed class PlayerIdIndex : BTreeIndexBase { - protected override uint GetKey(Circle row) => row.PlayerId; + protected override int GetKey(Circle row) => row.PlayerId; public PlayerIdIndex(LoggedOutCircleHandle table) : base(table) { } } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutEntity.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutEntity.g.cs index 39679158437..fe22ef0eea3 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutEntity.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutEntity.g.cs @@ -17,9 +17,9 @@ public sealed class LoggedOutEntityHandle : RemoteTableHandle "logged_out_entity"; - public sealed class EntityIdUniqueIndex : UniqueIndexBase + public sealed class EntityIdUniqueIndex : UniqueIndexBase { - protected override uint GetKey(Entity row) => row.EntityId; + protected override int GetKey(Entity row) => row.EntityId; public EntityIdUniqueIndex(LoggedOutEntityHandle table) : base(table) { } } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutPlayer.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutPlayer.g.cs index 71ac0a7832d..eb4590f2264 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutPlayer.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/LoggedOutPlayer.g.cs @@ -26,9 +26,9 @@ public IdentityUniqueIndex(LoggedOutPlayerHandle table) : base(table) { } public readonly IdentityUniqueIndex Identity; - public sealed class PlayerIdUniqueIndex : UniqueIndexBase + public sealed class PlayerIdUniqueIndex : UniqueIndexBase { - protected override uint GetKey(Player row) => row.PlayerId; + protected override int GetKey(Player row) => row.PlayerId; public PlayerIdUniqueIndex(LoggedOutPlayerHandle table) : base(table) { } } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Player.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Player.g.cs index 8934ec93194..e4db13e2ad9 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Player.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Tables/Player.g.cs @@ -26,9 +26,9 @@ public IdentityUniqueIndex(PlayerHandle table) : base(table) { } public readonly IdentityUniqueIndex Identity; - public sealed class PlayerIdUniqueIndex : UniqueIndexBase + public sealed class PlayerIdUniqueIndex : UniqueIndexBase { - protected override uint GetKey(Player row) => row.PlayerId; + protected override int GetKey(Player row) => row.PlayerId; public PlayerIdUniqueIndex(PlayerHandle table) : base(table) { } } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/Circle.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/Circle.g.cs index 2afa3afe79b..6d74053cadc 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/Circle.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/Circle.g.cs @@ -14,9 +14,9 @@ namespace SpacetimeDB.Types public sealed partial class Circle { [DataMember(Name = "entity_id")] - public uint EntityId; + public int EntityId; [DataMember(Name = "player_id")] - public uint PlayerId; + public int PlayerId; [DataMember(Name = "direction")] public DbVector2 Direction; [DataMember(Name = "speed")] @@ -25,8 +25,8 @@ public sealed partial class Circle public SpacetimeDB.Timestamp LastSplitTime; public Circle( - uint EntityId, - uint PlayerId, + int EntityId, + int PlayerId, DbVector2 Direction, float Speed, SpacetimeDB.Timestamp LastSplitTime diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/CircleRecombineTimer.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/CircleRecombineTimer.g.cs index 0c370499a5b..92676f91569 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/CircleRecombineTimer.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/CircleRecombineTimer.g.cs @@ -18,12 +18,12 @@ public sealed partial class CircleRecombineTimer [DataMember(Name = "scheduled_at")] public SpacetimeDB.ScheduleAt ScheduledAt; [DataMember(Name = "player_id")] - public uint PlayerId; + public int PlayerId; public CircleRecombineTimer( ulong ScheduledId, SpacetimeDB.ScheduleAt ScheduledAt, - uint PlayerId + int PlayerId ) { this.ScheduledId = ScheduledId; diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/Config.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/Config.g.cs index c7c0d766860..82439095ed5 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/Config.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/Config.g.cs @@ -14,13 +14,13 @@ namespace SpacetimeDB.Types public sealed partial class Config { [DataMember(Name = "id")] - public uint Id; + public int Id; [DataMember(Name = "world_size")] - public ulong WorldSize; + public long WorldSize; public Config( - uint Id, - ulong WorldSize + int Id, + long WorldSize ) { this.Id = Id; diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/ConsumeEntityTimer.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/ConsumeEntityTimer.g.cs index cf120601bd0..fc715f9fa7e 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/ConsumeEntityTimer.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/ConsumeEntityTimer.g.cs @@ -18,15 +18,15 @@ public sealed partial class ConsumeEntityTimer [DataMember(Name = "scheduled_at")] public SpacetimeDB.ScheduleAt ScheduledAt; [DataMember(Name = "consumed_entity_id")] - public uint ConsumedEntityId; + public int ConsumedEntityId; [DataMember(Name = "consumer_entity_id")] - public uint ConsumerEntityId; + public int ConsumerEntityId; public ConsumeEntityTimer( ulong ScheduledId, SpacetimeDB.ScheduleAt ScheduledAt, - uint ConsumedEntityId, - uint ConsumerEntityId + int ConsumedEntityId, + int ConsumerEntityId ) { this.ScheduledId = ScheduledId; diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/Entity.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/Entity.g.cs index 578a64bdfca..0efa0f6ce15 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/Entity.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/Entity.g.cs @@ -14,16 +14,16 @@ namespace SpacetimeDB.Types public sealed partial class Entity { [DataMember(Name = "entity_id")] - public uint EntityId; + public int EntityId; [DataMember(Name = "position")] public DbVector2 Position; [DataMember(Name = "mass")] - public uint Mass; + public int Mass; public Entity( - uint EntityId, + int EntityId, DbVector2 Position, - uint Mass + int Mass ) { this.EntityId = EntityId; diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/Food.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/Food.g.cs index 7b56ec0d1a7..5bf79dbd7e9 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/Food.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/Food.g.cs @@ -14,9 +14,9 @@ namespace SpacetimeDB.Types public sealed partial class Food { [DataMember(Name = "entity_id")] - public uint EntityId; + public int EntityId; - public Food(uint EntityId) + public Food(int EntityId) { this.EntityId = EntityId; } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/Player.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/Player.g.cs index 5e9176c4293..48b59304e2c 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/Player.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Types/Player.g.cs @@ -16,13 +16,13 @@ public sealed partial class Player [DataMember(Name = "identity")] public SpacetimeDB.Identity Identity; [DataMember(Name = "player_id")] - public uint PlayerId; + public int PlayerId; [DataMember(Name = "name")] public string Name; public Player( SpacetimeDB.Identity Identity, - uint PlayerId, + int PlayerId, string Name ) { diff --git a/demo/Blackholio/client-unreal/Content/Blackholio.umap b/demo/Blackholio/client-unreal/Content/Blackholio.umap index bb7bd7420b8..82664782e00 100644 Binary files a/demo/Blackholio/client-unreal/Content/Blackholio.umap and b/demo/Blackholio/client-unreal/Content/Blackholio.umap differ diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/Entity.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/Entity.cpp index cfa53f53ee7..9d307293b52 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Private/Entity.cpp +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/Entity.cpp @@ -49,7 +49,7 @@ void AEntity::ConsumeDespawn(float DeltaTime) } } -void AEntity::Spawn(uint32 InEntityId) +void AEntity::Spawn(int32 InEntityId) { EntityId = InEntityId; @@ -89,7 +89,7 @@ bool AEntity::ConsumeDelete(const FEventContext& Context) return false; const FConsumeEntityArgs Args = Reducer.GetAsConsumeEntity(); - const uint32 ConsumerId = Args.Request.ConsumerEntityId; + const int32 ConsumerId = Args.Request.ConsumerEntityId; ConsumingEntity = AGameManager::Instance->GetEntity(ConsumerId); if (!ConsumingEntity) return false; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/GameManager.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/GameManager.cpp index 2e0ff76265d..52a778b46fc 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Private/GameManager.cpp +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/GameManager.cpp @@ -31,7 +31,7 @@ AGameManager::AGameManager() } } -AEntity* AGameManager::GetEntity(uint32 EntityId) const +AEntity* AGameManager::GetEntity(int32 EntityId) const { if (const TWeakObjectPtr* WeakEntity = EntityMap.Find(EntityId)) { @@ -58,7 +58,7 @@ void AGameManager::BeginPlay() FOnDisconnectDelegate DisconnectDelegate; BIND_DELEGATE_SAFE(DisconnectDelegate, this, AGameManager, HandleDisconnect); FOnConnectErrorDelegate ConnectErrorDelegate; - BIND_DELEGATE_SAFE(ConnectErrorDelegate, this, AGameManager, HandleConnect); + BIND_DELEGATE_SAFE(ConnectErrorDelegate, this, AGameManager, HandleConnectError); UCredentials::Init(FString::Printf(TEXT("%s-%s"), *TokenFilePath, *ServerUri)); FString Token = UCredentials::LoadToken(); @@ -137,7 +137,7 @@ void AGameManager::HandleSubscriptionApplied(FSubscriptionEventContext& Context) // Once we have the initial subscription sync'd to the client cache // Get the world size from the config table and set up the arena - uint64 WorldSize = Conn->Db->Config->Id->Find(0).WorldSize; + int64 WorldSize = Conn->Db->Config->Id->Find(0).WorldSize; SetupArena(WorldSize); FPlayerType Player = Context.Db->Player->Identity->Find(LocalIdentity); @@ -151,7 +151,7 @@ void AGameManager::HandleSubscriptionApplied(FSubscriptionEventContext& Context) } } -void AGameManager::SetupArena(uint64 WorldSizeMeters) +void AGameManager::SetupArena(int64 WorldSizeMeters) { if (!BorderISM || !CubeMesh) return; @@ -162,7 +162,7 @@ void AGameManager::SetupArena(uint64 WorldSizeMeters) BorderISM->SetMaterial(0, BorderMaterial); } - // Convert from meters (uint64) → centimeters (double for precision) + // Convert from meters (int64) → centimeters (double for precision) const double worldSizeCmDouble = static_cast(WorldSizeMeters) * 100.0; // Clamp to avoid float overflow in transforms diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/LeaderboardWidget.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/LeaderboardWidget.cpp index 5f56228f198..463a9633f33 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/LeaderboardWidget.cpp +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/Gameplay/LeaderboardWidget.cpp @@ -52,11 +52,11 @@ void ULeaderboardWidget::CollectPlayers(TArray& Out) const const AGameManager* GM = AGameManager::Instance; if (!GM) return; - TMap> PlayerMap = GM->GetPlayerMap(); + TMap> PlayerMap = GM->GetPlayerMap(); if (PlayerMap.Num() == 0) return; // 2) Build entries: mass > 0 only - for (const TPair>& Pair : PlayerMap) + for (const TPair>& Pair : PlayerMap) { APlayerPawn* Pawn = Pair.Value.Get(); if (!Pawn) continue; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/SpacetimeDBClient.g.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/SpacetimeDBClient.g.cpp index b9742d6fb6d..7025959723c 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/SpacetimeDBClient.g.cpp +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/SpacetimeDBClient.g.cpp @@ -4,19 +4,19 @@ #include "ModuleBindings/SpacetimeDBClient.g.h" #include "DBCache/WithBsatn.h" #include "BSATN/UEBSATNHelpers.h" -#include "ModuleBindings/Tables/EntityTable.g.h" -#include "ModuleBindings/Tables/MoveAllPlayersTimerTable.g.h" -#include "ModuleBindings/Tables/ConsumeEntityTimerTable.g.h" +#include "ModuleBindings/Tables/CircleTable.g.h" #include "ModuleBindings/Tables/PlayerTable.g.h" #include "ModuleBindings/Tables/ConfigTable.g.h" -#include "ModuleBindings/Tables/FoodTable.g.h" -#include "ModuleBindings/Tables/CircleTable.g.h" -#include "ModuleBindings/Tables/SpawnFoodTimerTable.g.h" -#include "ModuleBindings/Tables/CircleDecayTimerTable.g.h" +#include "ModuleBindings/Tables/MoveAllPlayersTimerTable.g.h" +#include "ModuleBindings/Tables/PlayerTable.g.h" #include "ModuleBindings/Tables/CircleRecombineTimerTable.g.h" #include "ModuleBindings/Tables/EntityTable.g.h" +#include "ModuleBindings/Tables/SpawnFoodTimerTable.g.h" +#include "ModuleBindings/Tables/CircleDecayTimerTable.g.h" #include "ModuleBindings/Tables/CircleTable.g.h" -#include "ModuleBindings/Tables/PlayerTable.g.h" +#include "ModuleBindings/Tables/ConsumeEntityTimerTable.g.h" +#include "ModuleBindings/Tables/FoodTable.g.h" +#include "ModuleBindings/Tables/EntityTable.g.h" static FReducer DecodeReducer(const FReducerEvent& Event) { @@ -108,19 +108,19 @@ UDbConnection::UDbConnection(const FObjectInitializer& ObjectInitializer) : Supe Reducers->SetCallReducerFlags = SetReducerFlags; Reducers->Conn = this; - RegisterTable(TEXT("logged_out_entity"), Db->LoggedOutEntity); + RegisterTable(TEXT("logged_out_circle"), Db->LoggedOutCircle); + RegisterTable(TEXT("logged_out_player"), Db->LoggedOutPlayer); + RegisterTable(TEXT("config"), Db->Config); RegisterTable(TEXT("move_all_players_timer"), Db->MoveAllPlayersTimer); - RegisterTable(TEXT("consume_entity_timer"), Db->ConsumeEntityTimer); RegisterTable(TEXT("player"), Db->Player); - RegisterTable(TEXT("config"), Db->Config); - RegisterTable(TEXT("food"), Db->Food); - RegisterTable(TEXT("circle"), Db->Circle); + RegisterTable(TEXT("circle_recombine_timer"), Db->CircleRecombineTimer); + RegisterTable(TEXT("logged_out_entity"), Db->LoggedOutEntity); RegisterTable(TEXT("spawn_food_timer"), Db->SpawnFoodTimer); RegisterTable(TEXT("circle_decay_timer"), Db->CircleDecayTimer); - RegisterTable(TEXT("circle_recombine_timer"), Db->CircleRecombineTimer); + RegisterTable(TEXT("circle"), Db->Circle); + RegisterTable(TEXT("consume_entity_timer"), Db->ConsumeEntityTimer); + RegisterTable(TEXT("food"), Db->Food); RegisterTable(TEXT("entity"), Db->Entity); - RegisterTable(TEXT("logged_out_circle"), Db->LoggedOutCircle); - RegisterTable(TEXT("logged_out_player"), Db->LoggedOutPlayer); } FContextBase::FContextBase(UDbConnection* InConn) @@ -155,35 +155,35 @@ void URemoteTables::Initialize() { /** Creating tables */ - LoggedOutEntity = NewObject(this); + LoggedOutCircle = NewObject(this); + LoggedOutPlayer = NewObject(this); + Config = NewObject(this); MoveAllPlayersTimer = NewObject(this); - ConsumeEntityTimer = NewObject(this); Player = NewObject(this); - Config = NewObject(this); - Food = NewObject(this); - Circle = NewObject(this); + CircleRecombineTimer = NewObject(this); + LoggedOutEntity = NewObject(this); SpawnFoodTimer = NewObject(this); CircleDecayTimer = NewObject(this); - CircleRecombineTimer = NewObject(this); + Circle = NewObject(this); + ConsumeEntityTimer = NewObject(this); + Food = NewObject(this); Entity = NewObject(this); - LoggedOutCircle = NewObject(this); - LoggedOutPlayer = NewObject(this); /**/ /** Initialization */ - LoggedOutEntity->PostInitialize(); + LoggedOutCircle->PostInitialize(); + LoggedOutPlayer->PostInitialize(); + Config->PostInitialize(); MoveAllPlayersTimer->PostInitialize(); - ConsumeEntityTimer->PostInitialize(); Player->PostInitialize(); - Config->PostInitialize(); - Food->PostInitialize(); - Circle->PostInitialize(); + CircleRecombineTimer->PostInitialize(); + LoggedOutEntity->PostInitialize(); SpawnFoodTimer->PostInitialize(); CircleDecayTimer->PostInitialize(); - CircleRecombineTimer->PostInitialize(); + Circle->PostInitialize(); + ConsumeEntityTimer->PostInitialize(); + Food->PostInitialize(); Entity->PostInitialize(); - LoggedOutCircle->PostInitialize(); - LoggedOutPlayer->PostInitialize(); /**/ } diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/CircleTable.g.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/CircleTable.g.cpp index 58f27292ad5..198f20ae88f 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/CircleTable.g.cpp +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/CircleTable.g.cpp @@ -13,14 +13,14 @@ void UCircleTable::PostInitialize() Data = MakeShared>(); TSharedPtr> CircleTable = Data->GetOrAdd(TableName); - CircleTable->AddUniqueConstraint("entity_id", [](const FCircleType& Row) -> const uint32& { + CircleTable->AddUniqueConstraint("entity_id", [](const FCircleType& Row) -> const int32& { return Row.EntityId; }); EntityId = NewObject(this); EntityId->SetCache(CircleTable); // Register a new multi-key B-Tree index named "player_id" on the CircleTable. - CircleTable->AddMultiKeyBTreeIndex>( + CircleTable->AddMultiKeyBTreeIndex>( TEXT("player_id"), [](const FCircleType& Row) { @@ -39,7 +39,7 @@ FTableAppliedDiff UCircleTable::Update(TArray Diff = BaseUpdate(InsertsRef, DeletesRef, Data, TableName); - Diff.DeriveUpdatesByPrimaryKey( + Diff.DeriveUpdatesByPrimaryKey( [](const FCircleType& Row) { return Row.EntityId; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/ConfigTable.g.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/ConfigTable.g.cpp index 95b589d1e0b..d6523596f27 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/ConfigTable.g.cpp +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/ConfigTable.g.cpp @@ -13,7 +13,7 @@ void UConfigTable::PostInitialize() Data = MakeShared>(); TSharedPtr> ConfigTable = Data->GetOrAdd(TableName); - ConfigTable->AddUniqueConstraint("id", [](const FConfigType& Row) -> const uint32& { + ConfigTable->AddUniqueConstraint("id", [](const FConfigType& Row) -> const int32& { return Row.Id; }); Id = NewObject(this); @@ -26,7 +26,7 @@ FTableAppliedDiff UConfigTable::Update(TArray Diff = BaseUpdate(InsertsRef, DeletesRef, Data, TableName); - Diff.DeriveUpdatesByPrimaryKey( + Diff.DeriveUpdatesByPrimaryKey( [](const FConfigType& Row) { return Row.Id; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/EntityTable.g.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/EntityTable.g.cpp index ca6f02875f7..33aa332bbf9 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/EntityTable.g.cpp +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/EntityTable.g.cpp @@ -13,7 +13,7 @@ void UEntityTable::PostInitialize() Data = MakeShared>(); TSharedPtr> EntityTable = Data->GetOrAdd(TableName); - EntityTable->AddUniqueConstraint("entity_id", [](const FEntityType& Row) -> const uint32& { + EntityTable->AddUniqueConstraint("entity_id", [](const FEntityType& Row) -> const int32& { return Row.EntityId; }); EntityId = NewObject(this); @@ -26,7 +26,7 @@ FTableAppliedDiff UEntityTable::Update(TArray Diff = BaseUpdate(InsertsRef, DeletesRef, Data, TableName); - Diff.DeriveUpdatesByPrimaryKey( + Diff.DeriveUpdatesByPrimaryKey( [](const FEntityType& Row) { return Row.EntityId; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/FoodTable.g.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/FoodTable.g.cpp index 6d439a84e77..313969e8b47 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/FoodTable.g.cpp +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/FoodTable.g.cpp @@ -13,7 +13,7 @@ void UFoodTable::PostInitialize() Data = MakeShared>(); TSharedPtr> FoodTable = Data->GetOrAdd(TableName); - FoodTable->AddUniqueConstraint("entity_id", [](const FFoodType& Row) -> const uint32& { + FoodTable->AddUniqueConstraint("entity_id", [](const FFoodType& Row) -> const int32& { return Row.EntityId; }); EntityId = NewObject(this); @@ -26,7 +26,7 @@ FTableAppliedDiff UFoodTable::Update(TArray> In { FTableAppliedDiff Diff = BaseUpdate(InsertsRef, DeletesRef, Data, TableName); - Diff.DeriveUpdatesByPrimaryKey( + Diff.DeriveUpdatesByPrimaryKey( [](const FFoodType& Row) { return Row.EntityId; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/PlayerTable.g.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/PlayerTable.g.cpp index 2a2c29896f6..f8c52f4d4e6 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/PlayerTable.g.cpp +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/Tables/PlayerTable.g.cpp @@ -15,7 +15,7 @@ void UPlayerTable::PostInitialize() TSharedPtr> PlayerTable = Data->GetOrAdd(TableName); PlayerTable->AddUniqueConstraint("identity", [](const FPlayerType& Row) -> const FSpacetimeDBIdentity& { return Row.Identity; }); - PlayerTable->AddUniqueConstraint("player_id", [](const FPlayerType& Row) -> const uint32& { + PlayerTable->AddUniqueConstraint("player_id", [](const FPlayerType& Row) -> const int32& { return Row.PlayerId; }); Identity = NewObject(this); diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/PlayerPawn.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/PlayerPawn.cpp index 4326e026da9..edf86351fcf 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Private/PlayerPawn.cpp +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/PlayerPawn.cpp @@ -100,16 +100,16 @@ void APlayerPawn::Suicide() AGameManager::Instance->Conn->Reducers->Suicide(); } -uint32 APlayerPawn::TotalMass() const +int32 APlayerPawn::TotalMass() const { - uint32 Total = 0; + int32 Total = 0; for (int32 Index = 0; Index < OwnedCircles.Num(); ++Index) { const TWeakObjectPtr& Weak = OwnedCircles[Index]; if (!Weak.IsValid()) continue; const ACircle* Circle = Weak.Get(); - const uint32 Id = Circle->EntityId; + const int32 Id = Circle->EntityId; const FEntityType Entity = AGameManager::Instance->Conn->Db->Entity->EntityId->Find(Id); Total += Entity.Mass; @@ -135,7 +135,7 @@ FVector APlayerPawn::CenterOfMass() const if (!Weak.IsValid()) continue; const ACircle* Circle = Weak.Get(); - const uint32 Id = Circle->EntityId; + const int32 Id = Circle->EntityId; const FEntityType Entity = AGameManager::Instance->Conn->Db->Entity->EntityId->Find(Id); const double Mass = Entity.Mass; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/Circle.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/Circle.h index 0042f6f031b..eb4f6e7fc5a 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/Circle.h +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/Circle.h @@ -15,7 +15,8 @@ class CLIENT_UNREAL_API ACircle : public AEntity public: ACircle(); - uint32 OwnerPlayerId = 0; + UPROPERTY(BlueprintReadOnly, Category="BH|Circle") + int32 OwnerPlayerId = 0; UPROPERTY(BlueprintReadOnly, Category="BH|Circle") FString Username; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/Entity.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/Entity.h index 4fe24883e57..11016809f31 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/Entity.h +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/Entity.h @@ -28,19 +28,20 @@ class CLIENT_UNREAL_API AEntity : public AActor float TargetScale = 1.f; public: - uint32 EntityId = 0; + UPROPERTY(BlueprintReadOnly, Category="BH|Entity") + int32 EntityId = 0; virtual void Tick(float DeltaTime) override; void ConsumeDespawn(float DeltaTime); - void Spawn(uint32 InEntityId); + void Spawn(int32 InEntityId); virtual void OnEntityUpdated(const FEntityType& NewVal); virtual void OnDelete(const FEventContext& Context); bool ConsumeDelete(const FEventContext& Context); void SetColor(const FLinearColor& Color) const; - static float MassToRadius(uint32 Mass) { return FMath::Sqrt(static_cast(Mass)); } - static float MassToDiameter(uint32 Mass) { return MassToRadius(Mass) * 2.f; } + static float MassToRadius(int32 Mass) { return FMath::Sqrt(static_cast(Mass)); } + static float MassToDiameter(int32 Mass) { return MassToRadius(Mass) * 2.f; } private: UPROPERTY() diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/GameManager.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/GameManager.h index bdb0347f927..939319b3ce1 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/GameManager.h +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/GameManager.h @@ -60,10 +60,10 @@ class CLIENT_UNREAL_API AGameManager : public AActor } UFUNCTION() - AEntity* GetEntity(uint32 EntityId) const; + AEntity* GetEntity(int32 EntityId) const; UFUNCTION() - TMap> GetPlayerMap() const { return PlayerMap; }; + TMap> GetPlayerMap() const { return PlayerMap; }; protected: virtual void BeginPlay() override; @@ -84,7 +84,7 @@ class CLIENT_UNREAL_API AGameManager : public AActor /* Border */ UFUNCTION() - void SetupArena(uint64 WorldSizeMeters); + void SetupArena(int64 WorldSizeMeters); UFUNCTION() void CreateBorderCube(const FVector2f Position, const FVector2f Size) const; @@ -102,9 +102,9 @@ class CLIENT_UNREAL_API AGameManager : public AActor /* Data Bindings */ UPROPERTY() - TMap> EntityMap; + TMap> EntityMap; UPROPERTY() - TMap> PlayerMap; + TMap> PlayerMap; APlayerPawn* SpawnOrGetPlayer(const FPlayerType& PlayerRow); ACircle* SpawnCircle(const FCircleType& CircleRow); diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/SpacetimeDBClient.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/SpacetimeDBClient.g.h index 54f211c51f5..c28ccadc98e 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/SpacetimeDBClient.g.h +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/SpacetimeDBClient.g.h @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 1.4.0 (commit 26e99fe5e5b8848cccde94ec4c1a56148977dfa1). +// This was generated using spacetimedb cli version 1.4.0 (commit ce1adea804b92e0038999d9237cfabca7bd82940). #pragma once #include "CoreMinimal.h" @@ -79,7 +79,7 @@ struct CLIENT_UNREAL_API FContextBase { GENERATED_BODY() - FContextBase() : Db(nullptr), Reducers(nullptr), SetReducerFlags(nullptr), Conn(nullptr) {}; + FContextBase() = default; FContextBase(UDbConnection* InConn); UPROPERTY(BlueprintReadOnly, Category = "SpacetimeDB") @@ -103,25 +103,6 @@ struct CLIENT_UNREAL_API FContextBase }; -UCLASS() -class CLIENT_UNREAL_API UContextBaseBpLib : public UBlueprintFunctionLibrary -{ - GENERATED_BODY() - -private: - UFUNCTION(BlueprintPure, Category="SpacetimeDB") - static URemoteTables* GetDb(const FContextBase& Ctx) { return Ctx.Db; } - - UFUNCTION(BlueprintPure, Category="SpacetimeDB") - static URemoteReducers* GetReducers(const FContextBase& Ctx) { return Ctx.Reducers; } - - UFUNCTION(BlueprintPure, Category="SpacetimeDB") - static USetReducerFlags* GetSetReducerFlags(const FContextBase& Ctx) { return Ctx.SetReducerFlags; } - - UFUNCTION(BlueprintPure, Category="SpacetimeDB") - static bool IsActive(const FContextBase& Ctx) { return Ctx.IsActive(); } -}; - UENUM(BlueprintType, Category = "SpacetimeDB") enum class EReducerTag : uint8 { @@ -146,7 +127,7 @@ struct CLIENT_UNREAL_API FReducer public: UPROPERTY(BlueprintReadOnly, Category = "SpacetimeDB") - EReducerTag Tag = static_cast(0); + EReducerTag Tag; TVariant Data; @@ -912,25 +893,25 @@ class CLIENT_UNREAL_API URemoteTables : public UObject void Initialize(); UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") - UEntityTable* LoggedOutEntity; + UCircleTable* LoggedOutCircle; UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") - UMoveAllPlayersTimerTable* MoveAllPlayersTimer; + UPlayerTable* LoggedOutPlayer; UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") - UConsumeEntityTimerTable* ConsumeEntityTimer; + UConfigTable* Config; UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") - UPlayerTable* Player; + UMoveAllPlayersTimerTable* MoveAllPlayersTimer; UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") - UConfigTable* Config; + UPlayerTable* Player; UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") - UFoodTable* Food; + UCircleRecombineTimerTable* CircleRecombineTimer; UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") - UCircleTable* Circle; + UEntityTable* LoggedOutEntity; UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") USpawnFoodTimerTable* SpawnFoodTimer; @@ -939,16 +920,16 @@ class CLIENT_UNREAL_API URemoteTables : public UObject UCircleDecayTimerTable* CircleDecayTimer; UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") - UCircleRecombineTimerTable* CircleRecombineTimer; + UCircleTable* Circle; UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") - UEntityTable* Entity; + UConsumeEntityTimerTable* ConsumeEntityTimer; UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") - UCircleTable* LoggedOutCircle; + UFoodTable* Food; UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") - UPlayerTable* LoggedOutPlayer; + UEntityTable* Entity; }; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/CircleTable.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/CircleTable.g.h index 1061d1e09f0..3bb3f394a9b 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/CircleTable.g.h +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/CircleTable.g.h @@ -20,7 +20,7 @@ class CLIENT_UNREAL_API UCircleEntityIdUniqueIndex : public UObject private: // Declare an instance of your templated helper. // It's private because the UObject wrapper will expose its functionality. - FUniqueIndexHelper> EntityIdIndexHelper; + FUniqueIndexHelper> EntityIdIndexHelper; public: UCircleEntityIdUniqueIndex() @@ -33,8 +33,8 @@ class CLIENT_UNREAL_API UCircleEntityIdUniqueIndex : public UObject * @param Key The entityid to search for. * @return The found FCircleType, or a default-constructed FCircleType if not found. */ - // NOTE: Not exposed to Blueprint because uint32 types are not Blueprint-compatible - FCircleType Find(uint32 Key) + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|CircleIndex") + FCircleType Find(int32 Key) { // Simply delegate the call to the internal helper return EntityIdIndexHelper.FindUniqueIndex(Key); @@ -55,11 +55,11 @@ class UCirclePlayerIdIndex : public UObject GENERATED_BODY() public: - TArray Filter(const uint32& PlayerId) const + TArray Filter(const int32& PlayerId) const { TArray OutResults; - LocalCache->FindByMultiKeyBTreeIndex>( + LocalCache->FindByMultiKeyBTreeIndex>( OutResults, TEXT("player_id"), MakeTuple(PlayerId) @@ -74,8 +74,8 @@ class UCirclePlayerIdIndex : public UObject } private: - // NOTE: Not exposed to Blueprint because some parameter types are not Blueprint-compatible - void FilterPlayerId(TArray& OutResults, const uint32& PlayerId) + UFUNCTION(BlueprintCallable) + void FilterPlayerId(TArray& OutResults, const int32& PlayerId) { OutResults = Filter(PlayerId); } diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/ConfigTable.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/ConfigTable.g.h index 3efa76559f8..57d5532ba38 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/ConfigTable.g.h +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/ConfigTable.g.h @@ -20,7 +20,7 @@ class CLIENT_UNREAL_API UConfigIdUniqueIndex : public UObject private: // Declare an instance of your templated helper. // It's private because the UObject wrapper will expose its functionality. - FUniqueIndexHelper> IdIndexHelper; + FUniqueIndexHelper> IdIndexHelper; public: UConfigIdUniqueIndex() @@ -33,8 +33,8 @@ class CLIENT_UNREAL_API UConfigIdUniqueIndex : public UObject * @param Key The id to search for. * @return The found FConfigType, or a default-constructed FConfigType if not found. */ - // NOTE: Not exposed to Blueprint because uint32 types are not Blueprint-compatible - FConfigType Find(uint32 Key) + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|ConfigIndex") + FConfigType Find(int32 Key) { // Simply delegate the call to the internal helper return IdIndexHelper.FindUniqueIndex(Key); diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/EntityTable.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/EntityTable.g.h index ac15afa5100..ac3dd913121 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/EntityTable.g.h +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/EntityTable.g.h @@ -20,7 +20,7 @@ class CLIENT_UNREAL_API UEntityEntityIdUniqueIndex : public UObject private: // Declare an instance of your templated helper. // It's private because the UObject wrapper will expose its functionality. - FUniqueIndexHelper> EntityIdIndexHelper; + FUniqueIndexHelper> EntityIdIndexHelper; public: UEntityEntityIdUniqueIndex() @@ -33,8 +33,8 @@ class CLIENT_UNREAL_API UEntityEntityIdUniqueIndex : public UObject * @param Key The entityid to search for. * @return The found FEntityType, or a default-constructed FEntityType if not found. */ - // NOTE: Not exposed to Blueprint because uint32 types are not Blueprint-compatible - FEntityType Find(uint32 Key) + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|EntityIndex") + FEntityType Find(int32 Key) { // Simply delegate the call to the internal helper return EntityIdIndexHelper.FindUniqueIndex(Key); diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/FoodTable.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/FoodTable.g.h index 1e189379659..5f3e0013a82 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/FoodTable.g.h +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/FoodTable.g.h @@ -20,7 +20,7 @@ class CLIENT_UNREAL_API UFoodEntityIdUniqueIndex : public UObject private: // Declare an instance of your templated helper. // It's private because the UObject wrapper will expose its functionality. - FUniqueIndexHelper> EntityIdIndexHelper; + FUniqueIndexHelper> EntityIdIndexHelper; public: UFoodEntityIdUniqueIndex() @@ -33,8 +33,8 @@ class CLIENT_UNREAL_API UFoodEntityIdUniqueIndex : public UObject * @param Key The entityid to search for. * @return The found FFoodType, or a default-constructed FFoodType if not found. */ - // NOTE: Not exposed to Blueprint because uint32 types are not Blueprint-compatible - FFoodType Find(uint32 Key) + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|FoodIndex") + FFoodType Find(int32 Key) { // Simply delegate the call to the internal helper return EntityIdIndexHelper.FindUniqueIndex(Key); diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/PlayerTable.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/PlayerTable.g.h index f843e063a16..9e8734b5c6e 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/PlayerTable.g.h +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Tables/PlayerTable.g.h @@ -57,7 +57,7 @@ class CLIENT_UNREAL_API UPlayerPlayerIdUniqueIndex : public UObject private: // Declare an instance of your templated helper. // It's private because the UObject wrapper will expose its functionality. - FUniqueIndexHelper> PlayerIdIndexHelper; + FUniqueIndexHelper> PlayerIdIndexHelper; public: UPlayerPlayerIdUniqueIndex() @@ -70,8 +70,8 @@ class CLIENT_UNREAL_API UPlayerPlayerIdUniqueIndex : public UObject * @param Key The playerid to search for. * @return The found FPlayerType, or a default-constructed FPlayerType if not found. */ - // NOTE: Not exposed to Blueprint because uint32 types are not Blueprint-compatible - FPlayerType Find(uint32 Key) + UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|PlayerIndex") + FPlayerType Find(int32 Key) { // Simply delegate the call to the internal helper return PlayerIdIndexHelper.FindUniqueIndex(Key); diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/CircleRecombineTimerType.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/CircleRecombineTimerType.g.h index e443176ec08..39c19079f51 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/CircleRecombineTimerType.g.h +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/CircleRecombineTimerType.g.h @@ -13,13 +13,13 @@ struct CLIENT_UNREAL_API FCircleRecombineTimerType GENERATED_BODY() // NOTE: uint64 field not exposed to Blueprint due to non-blueprintable elements - uint64 ScheduledId = 0; + uint64 ScheduledId; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") FSpacetimeDBScheduleAt ScheduledAt; - // NOTE: uint32 field not exposed to Blueprint due to non-blueprintable elements - uint32 PlayerId = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + int32 PlayerId; FORCEINLINE bool operator==(const FCircleRecombineTimerType& Other) const { diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/CircleType.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/CircleType.g.h index 3a2e17d26a6..dfc2d3a9689 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/CircleType.g.h +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/CircleType.g.h @@ -13,17 +13,17 @@ struct CLIENT_UNREAL_API FCircleType { GENERATED_BODY() - // NOTE: uint32 field not exposed to Blueprint due to non-blueprintable elements - uint32 EntityId = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + int32 EntityId; - // NOTE: uint32 field not exposed to Blueprint due to non-blueprintable elements - uint32 PlayerId = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + int32 PlayerId; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") FDbVector2Type Direction; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") - float Speed = 0.f; + float Speed; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") FSpacetimeDBTimestamp LastSplitTime; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/ConfigType.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/ConfigType.g.h index 1bee89922a7..c6786d706a1 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/ConfigType.g.h +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/ConfigType.g.h @@ -11,11 +11,11 @@ struct CLIENT_UNREAL_API FConfigType { GENERATED_BODY() - // NOTE: uint32 field not exposed to Blueprint due to non-blueprintable elements - uint32 Id = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + int32 Id; - // NOTE: uint64 field not exposed to Blueprint due to non-blueprintable elements - uint64 WorldSize = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + int64 WorldSize; FORCEINLINE bool operator==(const FConfigType& Other) const { diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/ConsumeEntityTimerType.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/ConsumeEntityTimerType.g.h index 8e39d568def..205e1ffca7f 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/ConsumeEntityTimerType.g.h +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/ConsumeEntityTimerType.g.h @@ -13,16 +13,16 @@ struct CLIENT_UNREAL_API FConsumeEntityTimerType GENERATED_BODY() // NOTE: uint64 field not exposed to Blueprint due to non-blueprintable elements - uint64 ScheduledId = 0; + uint64 ScheduledId; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") FSpacetimeDBScheduleAt ScheduledAt; - // NOTE: uint32 field not exposed to Blueprint due to non-blueprintable elements - uint32 ConsumedEntityId = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + int32 ConsumedEntityId; - // NOTE: uint32 field not exposed to Blueprint due to non-blueprintable elements - uint32 ConsumerEntityId = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + int32 ConsumerEntityId; FORCEINLINE bool operator==(const FConsumeEntityTimerType& Other) const { diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/EntityType.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/EntityType.g.h index 1f96c2718e5..90ed122bfb1 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/EntityType.g.h +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/EntityType.g.h @@ -12,14 +12,14 @@ struct CLIENT_UNREAL_API FEntityType { GENERATED_BODY() - // NOTE: uint32 field not exposed to Blueprint due to non-blueprintable elements - uint32 EntityId = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + int32 EntityId; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") FDbVector2Type Position; - // NOTE: uint32 field not exposed to Blueprint due to non-blueprintable elements - uint32 Mass = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + int32 Mass; FORCEINLINE bool operator==(const FEntityType& Other) const { diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/FoodType.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/FoodType.g.h index 8daa507ddce..5beb28d8442 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/FoodType.g.h +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/FoodType.g.h @@ -11,8 +11,8 @@ struct CLIENT_UNREAL_API FFoodType { GENERATED_BODY() - // NOTE: uint32 field not exposed to Blueprint due to non-blueprintable elements - uint32 EntityId = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + int32 EntityId; FORCEINLINE bool operator==(const FFoodType& Other) const { diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/PlayerType.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/PlayerType.g.h index d627117b8f3..2b437ad4ea0 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/PlayerType.g.h +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Types/PlayerType.g.h @@ -15,8 +15,8 @@ struct CLIENT_UNREAL_API FPlayerType UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") FSpacetimeDBIdentity Identity; - // NOTE: uint32 field not exposed to Blueprint due to non-blueprintable elements - uint32 PlayerId = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") + int32 PlayerId; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SpacetimeDB") FString Name; diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/PlayerPawn.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/PlayerPawn.h index 8215e4f0e12..7864c46be6b 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/PlayerPawn.h +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/PlayerPawn.h @@ -18,7 +18,8 @@ class CLIENT_UNREAL_API APlayerPawn : public APawn APlayerPawn(); void Initialize(FPlayerType Player); - uint32 PlayerId = 0; + UPROPERTY(BlueprintReadWrite, Category="BH|Player") + int32 PlayerId = 0; UPROPERTY(BlueprintReadWrite, Category="BH|Player") bool bIsLocalPlayer = false; @@ -37,7 +38,7 @@ class CLIENT_UNREAL_API APlayerPawn : public APawn UFUNCTION(BlueprintCallable, Category="BH|Input") void Suicide(); - uint32 TotalMass() const; + int32 TotalMass() const; UFUNCTION(BlueprintPure, Category="BH|Player") FVector CenterOfMass() const; diff --git a/demo/Blackholio/server-csharp/Lib.cs b/demo/Blackholio/server-csharp/Lib.cs index 3545ce5cfa7..20e26cf99e6 100644 --- a/demo/Blackholio/server-csharp/Lib.cs +++ b/demo/Blackholio/server-csharp/Lib.cs @@ -2,16 +2,16 @@ public static partial class Module { - const uint START_PLAYER_MASS = 15; - const uint START_PLAYER_SPEED = 10; - const uint FOOD_MASS_MIN = 2; - const uint FOOD_MASS_MAX = 4; - const uint TARGET_FOOD_COUNT = 600; + const int START_PLAYER_MASS = 15; + const int START_PLAYER_SPEED = 10; + const int FOOD_MASS_MIN = 2; + const int FOOD_MASS_MAX = 4; + const int TARGET_FOOD_COUNT = 600; const float MINIMUM_SAFE_MASS_RATIO = 0.85f; const float MIN_OVERLAP_PCT_TO_CONSUME = 0.1f; - const uint MIN_MASS_TO_SPLIT = START_PLAYER_MASS * 2; - const uint MAX_CIRCLES_PER_PLAYER = 16; + const int MIN_MASS_TO_SPLIT = START_PLAYER_MASS * 2; + const int MAX_CIRCLES_PER_PLAYER = 16; const float SPLIT_RECOMBINE_DELAY_SEC = 5f; const float SPLIT_GRAV_PULL_BEFORE_RECOMBINE_SEC = 2f; const float ALLOWED_SPLIT_CIRCLE_OVERLAP_PCT = 0.9f; @@ -24,8 +24,8 @@ public static partial class Module public partial struct Config { [PrimaryKey] - public uint id; - public ulong world_size; + public int id; + public long world_size; } [Table(Name = "entity", Public = true)] @@ -33,9 +33,9 @@ public partial struct Config public partial struct Entity { [PrimaryKey, AutoInc] - public uint entity_id; + public int entity_id; public DbVector2 position; - public uint mass; + public int mass; } [Table(Name = "circle", Public = true)] @@ -44,8 +44,8 @@ public partial struct Entity public partial struct Circle { [PrimaryKey] - public uint entity_id; - public uint player_id; + public int entity_id; + public int player_id; public DbVector2 direction; public float speed; public SpacetimeDB.Timestamp last_split_time; @@ -58,7 +58,7 @@ public partial struct Player [PrimaryKey] public Identity identity; [Unique, AutoInc] - public uint player_id; + public int player_id; public string name; } @@ -66,7 +66,7 @@ public partial struct Player public partial struct Food { [PrimaryKey] - public uint entity_id; + public int entity_id; } [Table(Name = "move_all_players_timer", Scheduled = nameof(MoveAllPlayers), ScheduledAt = nameof(scheduled_at))] @@ -99,7 +99,7 @@ public partial struct CircleRecombineTimer [PrimaryKey, AutoInc] public ulong scheduled_id; public ScheduleAt scheduled_at; - public uint player_id; + public int player_id; } [Table(Name = "consume_entity_timer", Scheduled = nameof(ConsumeEntity), ScheduledAt = nameof(scheduled_at))] @@ -108,8 +108,8 @@ public partial struct ConsumeEntityTimer [PrimaryKey, AutoInc] public ulong scheduled_id; public ScheduleAt scheduled_at; - public uint consumed_entity_id; - public uint consumer_entity_id; + public int consumed_entity_id; + public int consumer_entity_id; } #endregion @@ -208,7 +208,7 @@ public static void Suicide(ReducerContext ctx) } } - public static Entity SpawnPlayerInitialCircle(ReducerContext ctx, uint player_id) + public static Entity SpawnPlayerInitialCircle(ReducerContext ctx, int player_id) { var rng = ctx.Rng; var world_size = (ctx.Db.config.id.Find(0) ?? throw new Exception("Config not found")).world_size; @@ -224,7 +224,7 @@ public static Entity SpawnPlayerInitialCircle(ReducerContext ctx, uint player_id ); } - public static Entity SpawnCircleAt(ReducerContext ctx, uint player_id, uint mass, DbVector2 position, SpacetimeDB.Timestamp timestamp) + public static Entity SpawnCircleAt(ReducerContext ctx, int player_id, int mass, DbVector2 position, SpacetimeDB.Timestamp timestamp) { var entity = ctx.Db.entity.Insert(new Entity { @@ -269,9 +269,9 @@ public static bool IsOverlapping(Entity a, Entity b) return distance_sq <= radius_sum * radius_sum; } - public static float MassToRadius(uint mass) => MathF.Sqrt(mass); + public static float MassToRadius(int mass) => MathF.Sqrt(mass); - public static float MassToMaxMoveSpeed(uint mass) => 2f * START_PLAYER_SPEED / (1f + MathF.Sqrt((float)mass / START_PLAYER_MASS)); + public static float MassToMaxMoveSpeed(int mass) => 2f * START_PLAYER_SPEED / (1f + MathF.Sqrt((float)mass / START_PLAYER_MASS)); [Reducer] public static void MoveAllPlayers(ReducerContext ctx, MoveAllPlayersTimer timer) @@ -379,7 +379,7 @@ public static void MoveAllPlayers(ReducerContext ctx, MoveAllPlayersTimer timer) } // Check collisions - Dictionary entities = ctx.Db.entity.Iter().Select(e => (e.entity_id, e)).ToDictionary(); + Dictionary entities = ctx.Db.entity.Iter().Select(e => (e.entity_id, e)).ToDictionary(); foreach (var circle in ctx.Db.circle.Iter()) { //var span = new SpacetimeDB.LogStopwatch("collisions"); @@ -418,7 +418,7 @@ public static void MoveAllPlayers(ReducerContext ctx, MoveAllPlayersTimer timer) //span.End(); } - private static void schedule_consume_entity(ReducerContext ctx, uint consumer_id, uint consumed_id) + private static void schedule_consume_entity(ReducerContext ctx, int consumer_id, int consumed_id) { ctx.Db.consume_entity_timer.Insert(new ConsumeEntityTimer { @@ -439,7 +439,7 @@ public static void ConsumeEntity(ReducerContext ctx, ConsumeEntityTimer request) ctx.Db.entity.entity_id.Update(consumer_entity); } - public static void DestroyEntity(ReducerContext ctx, uint entityId) + public static void DestroyEntity(ReducerContext ctx, int entityId) { ctx.Db.food.entity_id.Delete(entityId); ctx.Db.circle.entity_id.Delete(entityId); @@ -536,7 +536,7 @@ public static void CircleDecay(ReducerContext ctx, CircleDecayTimer timer) { continue; } - circle_entity.mass = (uint)(circle_entity.mass * 0.99f); + circle_entity.mass = (int)(circle_entity.mass * 0.99f); ctx.Db.entity.entity_id.Update(circle_entity); } } @@ -574,5 +574,5 @@ public static void CircleRecombine(ReducerContext ctx, CircleRecombineTimer time public static float Range(this Random rng, float min, float max) => rng.NextSingle() * (max - min) + min; - public static uint Range(this Random rng, uint min, uint max) => (uint)rng.NextInt64(min, max); + public static int Range(this Random rng, int min, int max) => (int)rng.NextInt64(min, max); } \ No newline at end of file diff --git a/demo/Blackholio/server-rust/src/lib.rs b/demo/Blackholio/server-rust/src/lib.rs index f68fca90532..08a70a7a767 100644 --- a/demo/Blackholio/server-rust/src/lib.rs +++ b/demo/Blackholio/server-rust/src/lib.rs @@ -13,15 +13,15 @@ use std::{collections::HashMap, time::Duration}; // - [ ] Ejecting mass // - [ ] Leaderboard -const START_PLAYER_MASS: u32 = 15; -const START_PLAYER_SPEED: u32 = 10; -const FOOD_MASS_MIN: u32 = 2; -const FOOD_MASS_MAX: u32 = 4; +const START_PLAYER_MASS: i32 = 15; +const START_PLAYER_SPEED: i32 = 10; +const FOOD_MASS_MIN: i32 = 2; +const FOOD_MASS_MAX: i32 = 4; const TARGET_FOOD_COUNT: usize = 600; const MINIMUM_SAFE_MASS_RATIO: f32 = 0.85; -const MIN_MASS_TO_SPLIT: u32 = START_PLAYER_MASS * 2; -const MAX_CIRCLES_PER_PLAYER: u32 = 16; +const MIN_MASS_TO_SPLIT: i32 = START_PLAYER_MASS * 2; +const MAX_CIRCLES_PER_PLAYER: i32 = 16; const SPLIT_RECOMBINE_DELAY_SEC: f32 = 5.0; const SPLIT_GRAV_PULL_BEFORE_RECOMBINE_SEC: f32 = 2.0; const ALLOWED_SPLIT_CIRCLE_OVERLAP_PCT: f32 = 0.9; @@ -30,8 +30,8 @@ const SELF_COLLISION_SPEED: f32 = 0.05; //1 == instantly separate circles. less #[spacetimedb::table(name = config, public)] pub struct Config { #[primary_key] - pub id: u32, - pub world_size: u64, + pub id: i32, + pub world_size: i64, } #[spacetimedb::table(name = entity, public)] @@ -40,9 +40,9 @@ pub struct Config { pub struct Entity { #[auto_inc] #[primary_key] - pub entity_id: u32, + pub entity_id: i32, pub position: DbVector2, - pub mass: u32, + pub mass: i32, } #[spacetimedb::table(name = circle, public)] @@ -50,9 +50,9 @@ pub struct Entity { #[derive(Debug, Clone)] pub struct Circle { #[primary_key] - pub entity_id: u32, + pub entity_id: i32, #[index(btree)] - pub player_id: u32, + pub player_id: i32, pub direction: DbVector2, pub speed: f32, pub last_split_time: Timestamp, @@ -66,14 +66,14 @@ pub struct Player { identity: Identity, #[unique] #[auto_inc] - player_id: u32, + player_id: i32, name: String, } #[spacetimedb::table(name = food, public)] pub struct Food { #[primary_key] - pub entity_id: u32, + pub entity_id: i32, } #[spacetimedb::table(name = move_all_players_timer, scheduled(move_all_players))] @@ -106,7 +106,7 @@ pub struct CircleRecombineTimer { #[auto_inc] scheduled_id: u64, scheduled_at: spacetimedb::ScheduleAt, - player_id: u32, + player_id: i32, } #[spacetimedb::table(name = consume_entity_timer, scheduled(consume_entity))] @@ -115,8 +115,8 @@ pub struct ConsumeEntityTimer { #[auto_inc] scheduled_id: u64, scheduled_at: spacetimedb::ScheduleAt, - consumed_entity_id: u32, - consumer_entity_id: u32, + consumed_entity_id: i32, + consumer_entity_id: i32, } #[spacetimedb::reducer(init)] @@ -196,7 +196,7 @@ pub fn enter_game(ctx: &ReducerContext, name: String) -> Result<(), String> { Ok(()) } -fn spawn_player_initial_circle(ctx: &ReducerContext, player_id: u32) -> Result { +fn spawn_player_initial_circle(ctx: &ReducerContext, player_id: i32) -> Result { let mut rng = ctx.rng(); let world_size = ctx.db.config().id().find(&0).ok_or("Config not found")?.world_size; let player_start_radius = mass_to_radius(START_PLAYER_MASS); @@ -207,8 +207,8 @@ fn spawn_player_initial_circle(ctx: &ReducerContext, player_id: u32) -> Result Result { @@ -285,11 +285,11 @@ fn is_overlapping(a: &Entity, b: &Entity) -> bool { distance_sq <= max_radius * max_radius } -fn mass_to_radius(mass: u32) -> f32 { +fn mass_to_radius(mass: i32) -> f32 { (mass as f32).sqrt() } -fn mass_to_max_move_speed(mass: u32) -> f32 { +fn mass_to_max_move_speed(mass: i32) -> f32 { 2.0 * START_PLAYER_SPEED as f32 / (1.0 + (mass as f32 / START_PLAYER_MASS as f32).sqrt()) } @@ -299,7 +299,7 @@ pub fn move_all_players(ctx: &ReducerContext, _timer: MoveAllPlayersTimer) -> Re // let span = spacetimedb::log_stopwatch::LogStopwatch::new("tick"); let world_size = ctx.db.config().id().find(0).ok_or("Config not found")?.world_size; - let mut circle_directions: HashMap = ctx + let mut circle_directions: HashMap = ctx .db .circle() .iter() @@ -394,7 +394,7 @@ pub fn move_all_players(ctx: &ReducerContext, _timer: MoveAllPlayersTimer) -> Re } // Check collisions - let entities: HashMap = ctx.db.entity().iter().map(|e| (e.entity_id, e)).collect(); + let entities: HashMap = ctx.db.entity().iter().map(|e| (e.entity_id, e)).collect(); for circle in ctx.db.circle().iter() { // let span = spacetimedb::time_span::Span::start("collisions"); let circle_entity = entities.get(&circle.entity_id).unwrap(); @@ -424,7 +424,7 @@ pub fn move_all_players(ctx: &ReducerContext, _timer: MoveAllPlayersTimer) -> Re Ok(()) } -fn schedule_consume_entity(ctx: &ReducerContext, consumer_id: u32, consumed_id: u32) { +fn schedule_consume_entity(ctx: &ReducerContext, consumer_id: i32, consumed_id: i32) { ctx.db.consume_entity_timer().insert(ConsumeEntityTimer { scheduled_id: 0, scheduled_at: ScheduleAt::Time(ctx.timestamp.clone()), @@ -453,7 +453,7 @@ pub fn consume_entity(ctx: &ReducerContext, request: ConsumeEntityTimer) -> Resu Ok(()) } -pub fn destroy_entity(ctx: &ReducerContext, entity_id: u32) -> Result<(), String> { +pub fn destroy_entity(ctx: &ReducerContext, entity_id: i32) -> Result<(), String> { ctx.db.food().entity_id().delete(&entity_id); ctx.db.circle().entity_id().delete(&entity_id); ctx.db.entity().entity_id().delete(&entity_id); @@ -470,7 +470,7 @@ pub fn player_split(ctx: &ReducerContext) -> Result<(), String> { .find(&ctx.sender) .ok_or("Sender has no player")?; let circles: Vec = ctx.db.circle().player_id().filter(&player.player_id).collect(); - let mut circle_count = circles.len() as u32; + let mut circle_count = circles.len() as i32; if circle_count >= MAX_CIRCLES_PER_PLAYER { return Ok(()); } @@ -558,7 +558,7 @@ pub fn circle_decay(ctx: &ReducerContext, _timer: CircleDecayTimer) -> Result<() if circle_entity.mass <= START_PLAYER_MASS { continue; } - circle_entity.mass = (circle_entity.mass as f32 * 0.99) as u32; + circle_entity.mass = (circle_entity.mass as f32 * 0.99) as i32; ctx.db.entity().entity_id().update(circle_entity); } @@ -566,7 +566,7 @@ pub fn circle_decay(ctx: &ReducerContext, _timer: CircleDecayTimer) -> Result<() } pub fn calculate_center_of_mass(entities: &[Entity]) -> DbVector2 { - let total_mass: u32 = entities.iter().map(|e| e.mass).sum(); + let total_mass: i32 = entities.iter().map(|e| e.mass).sum(); let center_of_mass: DbVector2 = entities.iter().map(|e| e.position * e.mass as f32).sum(); center_of_mass / total_mass as f32 } diff --git a/docs/docs/unity/part-2.md b/docs/docs/unity/part-2.md index ebfc7a695dd..0bf8efa005f 100644 --- a/docs/docs/unity/part-2.md +++ b/docs/docs/unity/part-2.md @@ -78,8 +78,8 @@ Let's start by defining the `Config` table. This is a simple table which will st #[spacetimedb::table(name = config, public)] pub struct Config { #[primary_key] - pub id: u32, - pub world_size: u64, + pub id: i32, + pub world_size: i64, } ``` @@ -101,8 +101,8 @@ Let's start by defining the `Config` table. This is a simple table which will st public partial struct Config { [PrimaryKey] - public uint id; - public ulong world_size; + public int id; + public long world_size; } ``` @@ -150,17 +150,17 @@ pub struct Entity { // this value should be determined by SpacetimeDB on insert. #[auto_inc] #[primary_key] - pub entity_id: u32, + pub entity_id: i32, pub position: DbVector2, - pub mass: u32, + pub mass: i32, } #[spacetimedb::table(name = circle, public)] pub struct Circle { #[primary_key] - pub entity_id: u32, + pub entity_id: i32, #[index(btree)] - pub player_id: u32, + pub player_id: i32, pub direction: DbVector2, pub speed: f32, pub last_split_time: Timestamp, @@ -169,7 +169,7 @@ pub struct Circle { #[spacetimedb::table(name = food, public)] pub struct Food { #[primary_key] - pub entity_id: u32, + pub entity_id: i32, } ``` ::: @@ -201,18 +201,18 @@ Let's create a few tables to represent entities in our game by adding the follow public partial struct Entity { [PrimaryKey, AutoInc] - public uint entity_id; + public int entity_id; public DbVector2 position; - public uint mass; + public int mass; } [Table(Name = "circle", Public = true)] public partial struct Circle { [PrimaryKey] - public uint entity_id; + public int entity_id; [SpacetimeDB.Index.BTree] - public uint player_id; + public int player_id; public DbVector2 direction; public float speed; public SpacetimeDB.Timestamp last_split_time; @@ -222,7 +222,7 @@ public partial struct Circle public partial struct Food { [PrimaryKey] - public uint entity_id; + public int entity_id; } ``` ::: @@ -248,7 +248,7 @@ pub struct Player { identity: Identity, #[unique] #[auto_inc] - player_id: u32, + player_id: i32, name: String, } ``` @@ -263,7 +263,7 @@ public partial struct Player [PrimaryKey] public Identity identity; [Unique, AutoInc] - public uint player_id; + public int player_id; public string name; } ``` @@ -294,7 +294,7 @@ Add this function to the `Module` class in `Lib.cs`: [Reducer] public static void Debug(ReducerContext ctx) { - Log.Info($"This reducer was called by {ctx.Sender}"); + Log.Info($"This reducer was called by {ctx.Sender}"); } ``` ::: diff --git a/docs/docs/unity/part-3.md b/docs/docs/unity/part-3.md index d12ef5ef8de..072bc378af9 100644 --- a/docs/docs/unity/part-3.md +++ b/docs/docs/unity/part-3.md @@ -31,11 +31,11 @@ This reducer also demonstrates how to insert new rows into a table. Here we are Now that we've ensured that our database always has a valid `world_size` let's spawn some food into the map. Add the following code to the end of the file. ```rust -const FOOD_MASS_MIN: u32 = 2; -const FOOD_MASS_MAX: u32 = 4; +const FOOD_MASS_MIN: i32 = 2; +const FOOD_MASS_MAX: i32 = 4; const TARGET_FOOD_COUNT: usize = 600; -fn mass_to_radius(mass: u32) -> f32 { +fn mass_to_radius(mass: i32) -> f32 { (mass as f32).sqrt() } @@ -99,11 +99,11 @@ This reducer also demonstrates how to insert new rows into a table. Here we are Now that we've ensured that our database always has a valid `world_size` let's spawn some food into the map. Add the following code to the end of the `Module` class. ```csharp -const uint FOOD_MASS_MIN = 2; -const uint FOOD_MASS_MAX = 4; -const uint TARGET_FOOD_COUNT = 600; +const int FOOD_MASS_MIN = 2; +const int FOOD_MASS_MAX = 4; +const int TARGET_FOOD_COUNT = 600; -public static float MassToRadius(uint mass) => MathF.Sqrt(mass); +public static float MassToRadius(int mass) => MathF.Sqrt(mass); [Reducer] public static void SpawnFood(ReducerContext ctx) @@ -138,14 +138,14 @@ public static void SpawnFood(ReducerContext ctx) public static float Range(this Random rng, float min, float max) => rng.NextSingle() * (max - min) + min; -public static uint Range(this Random rng, uint min, uint max) => (uint)rng.NextInt64(min, max); +public static int Range(this Random rng, int min, int max) => (int)rng.NextInt64(min, max); ``` ::: In this reducer, we are using the `world_size` we configured along with the `ReducerContext`'s random number generator `.rng()` function to place 600 food uniformly randomly throughout the map. We've also chosen the `mass` of the food to be a random number between 2 and 4 inclusive. :::server-csharp -We also added two helper functions so we can get a random range as either a `uint` or a `float`. +We also added two helper functions so we can get a random range as either a `int` or a `float`. ::: Although, we've written the reducer to spawn food, no food will actually be spawned until we call the function while players are logged in. This raises the question, who should call this function and when? @@ -276,7 +276,7 @@ pub struct Player { identity: Identity, #[unique] #[auto_inc] - player_id: u32, + player_id: i32, name: String, } ``` @@ -290,7 +290,7 @@ public partial struct Player [PrimaryKey] public Identity identity; [Unique, AutoInc] - public uint player_id; + public int player_id; public string name; } ``` @@ -392,7 +392,7 @@ Now that we've got our food spawning and our players set up, let's create a matc Add the following to the bottom of your file. ```rust -const START_PLAYER_MASS: u32 = 15; +const START_PLAYER_MASS: i32 = 15; #[spacetimedb::reducer] pub fn enter_game(ctx: &ReducerContext, name: String) -> Result<(), String> { @@ -406,7 +406,7 @@ pub fn enter_game(ctx: &ReducerContext, name: String) -> Result<(), String> { Ok(()) } -fn spawn_player_initial_circle(ctx: &ReducerContext, player_id: u32) -> Result { +fn spawn_player_initial_circle(ctx: &ReducerContext, player_id: i32) -> Result { let mut rng = ctx.rng(); let world_size = ctx .db @@ -429,8 +429,8 @@ fn spawn_player_initial_circle(ctx: &ReducerContext, player_id: u32) -> Result Result { @@ -457,7 +457,7 @@ The `enter_game` reducer takes one argument, the player's `name`. We can use thi Add the following to the end of the `Module` class. ```csharp -const uint START_PLAYER_MASS = 15; +const int START_PLAYER_MASS = 15; [Reducer] public static void EnterGame(ReducerContext ctx, string name) @@ -469,7 +469,7 @@ public static void EnterGame(ReducerContext ctx, string name) SpawnPlayerInitialCircle(ctx, player.player_id); } -public static Entity SpawnPlayerInitialCircle(ReducerContext ctx, uint player_id) +public static Entity SpawnPlayerInitialCircle(ReducerContext ctx, int player_id) { var rng = ctx.Rng; var world_size = (ctx.Db.config.id.Find(0) ?? throw new Exception("Config not found")).world_size; @@ -485,7 +485,7 @@ public static Entity SpawnPlayerInitialCircle(ReducerContext ctx, uint player_id ); } -public static Entity SpawnCircleAt(ReducerContext ctx, uint player_id, uint mass, DbVector2 position, SpacetimeDB.Timestamp timestamp) +public static Entity SpawnCircleAt(ReducerContext ctx, int player_id, int mass, DbVector2 position, SpacetimeDB.Timestamp timestamp) { var entity = ctx.Db.entity.Insert(new Entity { @@ -663,14 +663,14 @@ public abstract class EntityController : MonoBehaviour private static readonly int ShaderColorProperty = Shader.PropertyToID("_Color"); - [DoNotSerialize] public uint EntityId; + [DoNotSerialize] public int EntityId; protected float LerpTime; protected Vector3 LerpStartPosition; protected Vector3 LerpTargetPosition; protected Vector3 TargetScale; - protected virtual void Spawn(uint entityId) + protected virtual void Spawn(int entityId) { EntityId = entityId; @@ -706,14 +706,14 @@ public abstract class EntityController : MonoBehaviour transform.localScale = Vector3.Lerp(transform.localScale, TargetScale, Time.deltaTime * 8); } - public static Vector3 MassToScale(uint mass) + public static Vector3 MassToScale(int mass) { var diameter = MassToDiameter(mass); return new Vector3(diameter, diameter, 1); } - public static float MassToRadius(uint mass) => Mathf.Sqrt(mass); - public static float MassToDiameter(uint mass) => MassToRadius(mass) * 2; + public static float MassToRadius(int mass) => Mathf.Sqrt(mass); + public static float MassToDiameter(int mass) => MassToRadius(mass) * 2; } ``` @@ -853,7 +853,7 @@ public class PlayerController : MonoBehaviour public static PlayerController Local { get; private set; } - private uint PlayerId; + private int PlayerId; private float LastMovementSendTimestamp; private Vector2? LockInputPosition; private List OwnedCircles = new List(); @@ -898,9 +898,9 @@ public class PlayerController : MonoBehaviour } } - public uint TotalMass() + public int TotalMass() { - return (uint)OwnedCircles + return (int)OwnedCircles .Select(circle => GameManager.Conn.Db.Entity.EntityId.Find(circle.EntityId)) .Sum(e => e?.Mass ?? 0); //If this entity is being deleted on the same frame that we're moving, we can have a null entity here. } @@ -1003,8 +1003,8 @@ Add a couple dictionaries at the top of your `GameManager` class which we'll use ```cs public static DbConnection Conn { get; private set; } - public static Dictionary Entities = new Dictionary(); - public static Dictionary Players = new Dictionary(); + public static Dictionary Entities = new Dictionary(); + public static Dictionary Players = new Dictionary(); ``` Next lets add some callbacks when rows change in the database. Modify the `HandleConnect` method as below. @@ -1079,7 +1079,7 @@ Next add the following implementations for those callbacks to the `GameManager` } } - private static PlayerController GetOrCreatePlayer(uint playerId) + private static PlayerController GetOrCreatePlayer(int playerId) { if (!Players.TryGetValue(playerId, out var playerController)) { diff --git a/docs/docs/unity/part-4.md b/docs/docs/unity/part-4.md index 7b04c6b00cb..a3e05efe8b9 100644 --- a/docs/docs/unity/part-4.md +++ b/docs/docs/unity/part-4.md @@ -224,9 +224,9 @@ pub struct MoveAllPlayersTimer { scheduled_at: spacetimedb::ScheduleAt, } -const START_PLAYER_SPEED: u32 = 10; +const START_PLAYER_SPEED: i32 = 10; -fn mass_to_max_move_speed(mass: u32) -> f32 { +fn mass_to_max_move_speed(mass: i32) -> f32 { 2.0 * START_PLAYER_SPEED as f32 / (1.0 + (mass as f32 / START_PLAYER_MASS as f32).sqrt()) } @@ -273,9 +273,9 @@ public partial struct MoveAllPlayersTimer public ScheduleAt scheduled_at; } -const uint START_PLAYER_SPEED = 10; +const int START_PLAYER_SPEED = 10; -public static float MassToMaxMoveSpeed(uint mass) => 2f * START_PLAYER_SPEED / (1f + MathF.Sqrt((float)mass / START_PLAYER_MASS)); +public static float MassToMaxMoveSpeed(int mass) => 2f * START_PLAYER_SPEED / (1f + MathF.Sqrt((float)mass / START_PLAYER_MASS)); [Reducer] public static void MoveAllPlayers(ReducerContext ctx, MoveAllPlayersTimer timer) diff --git a/docs/docs/unreal/part-1.md b/docs/docs/unreal/part-1.md index db60c9ebd07..d993e926a67 100644 --- a/docs/docs/unreal/part-1.md +++ b/docs/docs/unreal/part-1.md @@ -69,6 +69,8 @@ Once the SDK is stabilized, we'll find a more ergonomic way to distribute it. ### Create the GameManager Actor +:::server-cpp + 1. Open the `client_unreal` project in your IDE (Visual Studio or JetBrains Rider) and run the project to launch the Unreal Editor. - This will enable **Live Coding**, making the workflow a bit smoother. - Unreal will prompt you to build the `SpacetimeDbSdk` plugin. Do so. @@ -80,10 +82,23 @@ The `GameManager` class will be where we will put the high level initialization > **Note:** In a production Unreal project, you would typically implement this logic in a Subsystem. For simplicity, this tutorial uses a singleton actor. +::: +:::server-blueprint + +1. Open the `client_unreal` project to launch the Unreal Editor. +2. **Create a GameManager Blueprint** + - In the **Content Drawer**, click **Add**, then select **Blueprint -> Blueprint Class**. + - Click **Actor**. + - Name the blueprint `BP_GameManager`. + +::: + ### Set Up the Level Set up the empty level, add the new `GameManager` to the level, and add lighting. +:::server-cpp + 1. **Create a new level** - Open **File -> New Level** in the top menu, select **Empty Level**, and click **Create**. - Save the level and name it `Blackholio`. @@ -114,10 +129,40 @@ Set up the empty level, add the new `GameManager` to the level, and add lighting - Enable and set **Exposure -> Max EV100** to 1.0. - Enable **Post Process Volume Settings -> Infinite Extend (Unbounded)**. +::: +:::server-blueprint + +1. **Create a new level** + - Open **File -> New Level** in the top menu, select **Empty Level**, and click **Create**. + - Save the level and name it `Blackholio`. + +2. **Update Maps & Modes** + - Open **Edit -> Project Settings** in the top menu, then select **Project -> Maps & Modes** on the left. + - Set **Editor Startup Map** to `Blackholio`. + - Set **Game Default Map** to `Blackholio`. + +3. **Add to the Level** + - Drag the `BP_GameManager` blueprint from the **Content Drawer** into the scene view. + +4. **Add a Directional Light** + - Click **Add** in the top toolbar, then select **Lights -> Directional Light**. + - Set **Rotation** to -105.0, -31.0, -14.0. + +5. **Add a Post Process Volume** + - Click **Add** in the top toolbar, then select **Volumes -> Post Process Volume**. + - Enable and set **Exposure -> Exposure Compensation** to 0.0. + - Enable and set **Exposure -> Min EV100** to 1.0. + - Enable and set **Exposure -> Max EV100** to 1.0. + - Enable **Post Process Volume Settings -> Infinite Extend (Unbounded)**. + +::: + ### Add a Simple GameMode Create a simple GameMode to tweak the startup settings and connect it to the World Settings. +:::server-cpp + 1. **Create the C++ class** - Open **Tools -> New C++ Class** in the top menu, select **GameModeBase** as the parent, and click **Next**. - Select **Public** as the class type. @@ -133,6 +178,21 @@ Create a simple GameMode to tweak the startup settings and connect it to the Wor - Change **GameMode Override** from **None** to `BP_BlackholioGameMode`. - Save the level. +::: +:::server-blueprint + +1. **Create a GameMode Blueprint** + - In the **Content Drawer**, click **Add**, then select **Blueprint -> Blueprint Class**. + - Expand **All Classes**, and click `Game Mode Base`. + - Name the blueprint `BP_GameMode`. + +2. **Update World Settings** + - Open **Window -> World Settings** in the top menu. + - Change **GameMode Override** from **None** to `BP_GameMode`. + - Save the level. + +::: + At this point, the foundation of the Unreal project is set up. Pressing Play will show a blank screen, but the game should start without errors. Next, we’ll create the SpacetimeDB server module so we have something to connect to. ### Create the Server Module diff --git a/docs/docs/unreal/part-2-01-blueprint-variable.png b/docs/docs/unreal/part-2-01-blueprint-variable.png new file mode 100644 index 00000000000..904bdcd4aa9 Binary files /dev/null and b/docs/docs/unreal/part-2-01-blueprint-variable.png differ diff --git a/docs/docs/unreal/part-2-02-blueprint-getmanager.png b/docs/docs/unreal/part-2-02-blueprint-getmanager.png new file mode 100644 index 00000000000..89f05e7a867 Binary files /dev/null and b/docs/docs/unreal/part-2-02-blueprint-getmanager.png differ diff --git a/docs/docs/unreal/part-2-02-blueprint-setmanager.png b/docs/docs/unreal/part-2-02-blueprint-setmanager.png new file mode 100644 index 00000000000..8c80846c108 Binary files /dev/null and b/docs/docs/unreal/part-2-02-blueprint-setmanager.png differ diff --git a/docs/docs/unreal/part-2-03-blueprint-add-variables.png b/docs/docs/unreal/part-2-03-blueprint-add-variables.png new file mode 100644 index 00000000000..6e1da2e76ec Binary files /dev/null and b/docs/docs/unreal/part-2-03-blueprint-add-variables.png differ diff --git a/docs/docs/unreal/part-2-04-blueprint-begin-play.png b/docs/docs/unreal/part-2-04-blueprint-begin-play.png new file mode 100644 index 00000000000..612906e4c7c Binary files /dev/null and b/docs/docs/unreal/part-2-04-blueprint-begin-play.png differ diff --git a/docs/docs/unreal/part-2-05-blueprint-buildconnection-1.png b/docs/docs/unreal/part-2-05-blueprint-buildconnection-1.png new file mode 100644 index 00000000000..d74f410b29c Binary files /dev/null and b/docs/docs/unreal/part-2-05-blueprint-buildconnection-1.png differ diff --git a/docs/docs/unreal/part-2-05-blueprint-buildconnection-2.png b/docs/docs/unreal/part-2-05-blueprint-buildconnection-2.png new file mode 100644 index 00000000000..f44b985f09b Binary files /dev/null and b/docs/docs/unreal/part-2-05-blueprint-buildconnection-2.png differ diff --git a/docs/docs/unreal/part-2-06-blueprint-add-build.png b/docs/docs/unreal/part-2-06-blueprint-add-build.png new file mode 100644 index 00000000000..6acf2c4dd13 Binary files /dev/null and b/docs/docs/unreal/part-2-06-blueprint-add-build.png differ diff --git a/docs/docs/unreal/part-2-07-blueprint-endplay-tick.png b/docs/docs/unreal/part-2-07-blueprint-endplay-tick.png new file mode 100644 index 00000000000..b9f817bb002 Binary files /dev/null and b/docs/docs/unreal/part-2-07-blueprint-endplay-tick.png differ diff --git a/docs/docs/unreal/part-2-08-blueprint-onconnect.png b/docs/docs/unreal/part-2-08-blueprint-onconnect.png new file mode 100644 index 00000000000..4966d739434 Binary files /dev/null and b/docs/docs/unreal/part-2-08-blueprint-onconnect.png differ diff --git a/docs/docs/unreal/part-2.md b/docs/docs/unreal/part-2.md index da7148589c9..2013d34156a 100644 --- a/docs/docs/unreal/part-2.md +++ b/docs/docs/unreal/part-2.md @@ -78,8 +78,8 @@ Let's start by defining the `Config` table. This is a simple table which will st #[spacetimedb::table(name = config, public)] pub struct Config { #[primary_key] - pub id: u32, - pub world_size: u64, + pub id: i32, + pub world_size: i64, } ``` @@ -101,8 +101,8 @@ Let's start by defining the `Config` table. This is a simple table which will st public partial struct Config { [PrimaryKey] - public uint id; - public ulong world_size; + public int id; + public long world_size; } ``` @@ -150,17 +150,17 @@ pub struct Entity { // this value should be determined by SpacetimeDB on insert. #[auto_inc] #[primary_key] - pub entity_id: u32, + pub entity_id: i32, pub position: DbVector2, - pub mass: u32, + pub mass: i32, } #[spacetimedb::table(name = circle, public)] pub struct Circle { #[primary_key] - pub entity_id: u32, + pub entity_id: i32, #[index(btree)] - pub player_id: u32, + pub player_id: i32, pub direction: DbVector2, pub speed: f32, pub last_split_time: Timestamp, @@ -169,7 +169,7 @@ pub struct Circle { #[spacetimedb::table(name = food, public)] pub struct Food { #[primary_key] - pub entity_id: u32, + pub entity_id: i32, } ``` ::: @@ -201,18 +201,18 @@ Let's create a few tables to represent entities in our game by adding the follow public partial struct Entity { [PrimaryKey, AutoInc] - public uint entity_id; + public int entity_id; public DbVector2 position; - public uint mass; + public int mass; } [Table(Name = "circle", Public = true)] public partial struct Circle { [PrimaryKey] - public uint entity_id; + public int entity_id; [SpacetimeDB.Index.BTree] - public uint player_id; + public int player_id; public DbVector2 direction; public float speed; public SpacetimeDB.Timestamp last_split_time; @@ -222,7 +222,7 @@ public partial struct Circle public partial struct Food { [PrimaryKey] - public uint entity_id; + public int entity_id; } ``` ::: @@ -248,7 +248,7 @@ pub struct Player { identity: Identity, #[unique] #[auto_inc] - player_id: u32, + player_id: i32, name: String, } ``` @@ -263,7 +263,7 @@ public partial struct Player [PrimaryKey] public Identity identity; [Unique, AutoInc] - public uint player_id; + public int player_id; public string name; } ``` @@ -294,7 +294,7 @@ Add this function to the `Module` class in `Lib.cs`: [Reducer] public static void Debug(ReducerContext ctx) { - Log.Info($"This reducer was called by {ctx.Sender}"); + Log.Info($"This reducer was called by {ctx.Sender}"); } ``` ::: @@ -491,6 +491,8 @@ Update `client_unreal.Build.cs` to include the `SpacetimeDbSdk`. Add `SpacetimeD }); ``` +:::server-cpp + Update `GameManager.h` as follows to set up the Unreal client connection to the server: ```cpp @@ -584,7 +586,7 @@ void AGameManager::BeginPlay() FOnDisconnectDelegate DisconnectDelegate; BIND_DELEGATE_SAFE(DisconnectDelegate, this, AGameManager, HandleDisconnect); FOnConnectErrorDelegate ConnectErrorDelegate; - BIND_DELEGATE_SAFE(ConnectErrorDelegate, this, AGameManager, HandleConnect); + BIND_DELEGATE_SAFE(ConnectErrorDelegate, this, AGameManager, HandleConnectError); UCredentials::Init(*TokenFilePath); FString Token = UCredentials::LoadToken(); @@ -659,6 +661,99 @@ Here we configure the connection to the database, by passing it some callbacks i In our `HandleConnect` callback we build a subscription and are calling `Subscribe` and subscribing to all data in the database. This will cause SpacetimeDB to synchronize the state of all your tables with your Unreal client's SpacetimeDB SDK's "client cache". You can also subscribe to specific tables using SQL syntax, e.g. `SELECT * FROM my_table`. Our [SQL documentation](/docs/sql) enumerates the operations that are accepted in our SQL syntax. +::: +:::server-blueprint + +> **NOTE:** Close down, rebuild, and relaunch the project to update the plugin and generated code references. + +To start off edit `BP_GameMode` to provide easy access to the `BP_GameManager`. + +Open `BP_GameMode` and update to the following: + +1. Add a **Variable** + - Change **Variable Name** to `GameManager` + - Change **Variable Type** to **BP Game Manager > Object Reference** + + +![Add Variable](./part-2-01-blueprint-variable.png) + +2. Add a **Function** named `GetGameManager` and set up as below: + + +![Add GetGameManager](./part-2-02-blueprint-getmanager.png) + +- Add **Output** as `GameManager` with **BP Game Manager > Object Reference** as the type. + +3. Add a **Function** named `SetGameManager` and set up as below: + + +![Add SetGameManager](./part-2-02-blueprint-setmanager.png) + +- Add **Input** as `GameManager` with **BP Game Manager > Object Reference** as the type. + +--- + +Next, open and update `BP_GameManager` to add the following **Variables**: + +1. Add `ServerUri` + - Change **Variable Type** to **String** + - Check **Instance Editable** + - Change **Category** to `Connection` + - Change **Default Value** to `127.0.0.1:3000` +2. Add `ModuleName` + - Change **Variable Type** to **String** + - Check **Instance Editable** + - Change **Category** to `Connection` + - Change **Default Value** to `blackholio` +3. Add `TokenFilePath` + - Change **Variable Type** to **String** + - Check **Instance Editable** + - Change **Category** to `Connection` + - Change **Default Value** to `.spacetime_blackholio` +4. Add `LocalIdentity` + - Change **Variable Type** to **Spacetime DBIdentity** + - Change **Category** to `Connection` +5. Add `Conn` + - Change **Variable Type** to **Db Connection > Object Reference** + - Check **Instance Editable** + - Change **Category** to `Connection` +6. Add `Token` + - Change **Variable Type** to **String** + - Change **Category** to `Connection` + + +![Add GameManager Variables](./part-2-03-blueprint-add-variables.png) + +Continue with `BP_GameManager` to add the logic, starting with **Event BeginPlay**: + +![Start BeginPlay](./part-2-04-blueprint-begin-play.png) + +Add **Function** named `BuildConnection`: + +![Start BuildConnection](./part-2-05-blueprint-buildconnection-1.png) + +![End BuildConnection](./part-2-05-blueprint-buildconnection-2.png) + +> **Note:** Dragging off the **Event** pin will provide **Event Dispatchers -> Create Event** then set **Select Function..** to **Create a matching event** to generate the events on the **EventGraph** that we'll fill in soon. The naming scheme we're using in this tutorial is `_Event`, eg. `OnConnect_Event`. + +Now attach `BuildConnection` to the end of **Event BeginPlay**: + +![Add BuildConnection](./part-2-06-blueprint-add-build.png) + +Update the **Event EndPlay** and **Event Tick** to the following: + +![Update Events](./part-2-07-blueprint-endplay-tick.png) + +Update the **OnConnect_Event**: + +![Update OnConnect](./part-2-08-blueprint-onconnect.png) + +Here we configure the connection to the database, by passing it some callbacks in addition to providing the `SERVER_URI` and `MODULE_NAME` to the connection. When the client connects, the SpacetimeDB SDK will call the `OnConnect_Event` method, allowing us to start up the game. + +In our `OnConnect_Event` callback we build a subscription and are calling `Subscribe` and subscribing to all data in the database. This will cause SpacetimeDB to synchronize the state of all your tables with your Unreal client's SpacetimeDB SDK's "client cache". You can also subscribe to specific tables using SQL syntax, e.g. `SELECT * FROM my_table`. Our [SQL documentation](/docs/sql) enumerates the operations that are accepted in our SQL syntax. + +::: + --- **SDK Client Cache** diff --git a/docs/docs/unreal/part-3-01-blueprint-setup-arena-1.png b/docs/docs/unreal/part-3-01-blueprint-setup-arena-1.png new file mode 100644 index 00000000000..8105c1bacea Binary files /dev/null and b/docs/docs/unreal/part-3-01-blueprint-setup-arena-1.png differ diff --git a/docs/docs/unreal/part-3-01-blueprint-setup-arena-2.png b/docs/docs/unreal/part-3-01-blueprint-setup-arena-2.png new file mode 100644 index 00000000000..889da05870f Binary files /dev/null and b/docs/docs/unreal/part-3-01-blueprint-setup-arena-2.png differ diff --git a/docs/docs/unreal/part-3-01-blueprint-setup-arena-3.png b/docs/docs/unreal/part-3-01-blueprint-setup-arena-3.png new file mode 100644 index 00000000000..837237a78fc Binary files /dev/null and b/docs/docs/unreal/part-3-01-blueprint-setup-arena-3.png differ diff --git a/docs/docs/unreal/part-3-01-blueprint-setup-arena-4.png b/docs/docs/unreal/part-3-01-blueprint-setup-arena-4.png new file mode 100644 index 00000000000..71d7a4f210c Binary files /dev/null and b/docs/docs/unreal/part-3-01-blueprint-setup-arena-4.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-circle-1.png b/docs/docs/unreal/part-3-02-blueprint-circle-1.png new file mode 100644 index 00000000000..2c8d2c18376 Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-circle-1.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-circle-2.png b/docs/docs/unreal/part-3-02-blueprint-circle-2.png new file mode 100644 index 00000000000..37a546b6465 Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-circle-2.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-circle-3.png b/docs/docs/unreal/part-3-02-blueprint-circle-3.png new file mode 100644 index 00000000000..d12861478bb Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-circle-3.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-entity-1.png b/docs/docs/unreal/part-3-02-blueprint-entity-1.png new file mode 100644 index 00000000000..17e0209d4d9 Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-entity-1.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-entity-2.png b/docs/docs/unreal/part-3-02-blueprint-entity-2.png new file mode 100644 index 00000000000..11d1493a429 Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-entity-2.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-entity-3.png b/docs/docs/unreal/part-3-02-blueprint-entity-3.png new file mode 100644 index 00000000000..f4cff317f13 Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-entity-3.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-entity-4.png b/docs/docs/unreal/part-3-02-blueprint-entity-4.png new file mode 100644 index 00000000000..2d59e9fb802 Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-entity-4.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-entity-5.png b/docs/docs/unreal/part-3-02-blueprint-entity-5.png new file mode 100644 index 00000000000..5e131228a48 Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-entity-5.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-entity-6.png b/docs/docs/unreal/part-3-02-blueprint-entity-6.png new file mode 100644 index 00000000000..a8b13563277 Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-entity-6.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-entity-7.png b/docs/docs/unreal/part-3-02-blueprint-entity-7.png new file mode 100644 index 00000000000..1b66e314873 Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-entity-7.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-entity-8.png b/docs/docs/unreal/part-3-02-blueprint-entity-8.png new file mode 100644 index 00000000000..81d6e12e93c Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-entity-8.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-food-1.png b/docs/docs/unreal/part-3-02-blueprint-food-1.png new file mode 100644 index 00000000000..63bdcafd6d1 Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-food-1.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-food-2.png b/docs/docs/unreal/part-3-02-blueprint-food-2.png new file mode 100644 index 00000000000..6122f65d898 Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-food-2.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-player-1.png b/docs/docs/unreal/part-3-02-blueprint-player-1.png new file mode 100644 index 00000000000..0c4aa6170f1 Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-player-1.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-player-10.png b/docs/docs/unreal/part-3-02-blueprint-player-10.png new file mode 100644 index 00000000000..e0a159bbd6a Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-player-10.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-player-2.png b/docs/docs/unreal/part-3-02-blueprint-player-2.png new file mode 100644 index 00000000000..a577f6d613d Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-player-2.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-player-3.png b/docs/docs/unreal/part-3-02-blueprint-player-3.png new file mode 100644 index 00000000000..69b1215405f Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-player-3.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-player-4.png b/docs/docs/unreal/part-3-02-blueprint-player-4.png new file mode 100644 index 00000000000..124f63d7eb6 Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-player-4.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-player-5.png b/docs/docs/unreal/part-3-02-blueprint-player-5.png new file mode 100644 index 00000000000..47d4eae70f6 Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-player-5.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-player-6.png b/docs/docs/unreal/part-3-02-blueprint-player-6.png new file mode 100644 index 00000000000..64036aafc26 Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-player-6.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-player-7.png b/docs/docs/unreal/part-3-02-blueprint-player-7.png new file mode 100644 index 00000000000..fbe128581b8 Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-player-7.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-player-8.png b/docs/docs/unreal/part-3-02-blueprint-player-8.png new file mode 100644 index 00000000000..5efae87fa2a Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-player-8.png differ diff --git a/docs/docs/unreal/part-3-02-blueprint-player-9.png b/docs/docs/unreal/part-3-02-blueprint-player-9.png new file mode 100644 index 00000000000..912fe108d83 Binary files /dev/null and b/docs/docs/unreal/part-3-02-blueprint-player-9.png differ diff --git a/docs/docs/unreal/part-3-03-blueprint-gamemanager-1.png b/docs/docs/unreal/part-3-03-blueprint-gamemanager-1.png new file mode 100644 index 00000000000..306a0e3f14f Binary files /dev/null and b/docs/docs/unreal/part-3-03-blueprint-gamemanager-1.png differ diff --git a/docs/docs/unreal/part-3-03-blueprint-gamemanager-10.png b/docs/docs/unreal/part-3-03-blueprint-gamemanager-10.png new file mode 100644 index 00000000000..9052b3eff2e Binary files /dev/null and b/docs/docs/unreal/part-3-03-blueprint-gamemanager-10.png differ diff --git a/docs/docs/unreal/part-3-03-blueprint-gamemanager-2.png b/docs/docs/unreal/part-3-03-blueprint-gamemanager-2.png new file mode 100644 index 00000000000..4b5d3470de7 Binary files /dev/null and b/docs/docs/unreal/part-3-03-blueprint-gamemanager-2.png differ diff --git a/docs/docs/unreal/part-3-03-blueprint-gamemanager-3.png b/docs/docs/unreal/part-3-03-blueprint-gamemanager-3.png new file mode 100644 index 00000000000..8405f9b610d Binary files /dev/null and b/docs/docs/unreal/part-3-03-blueprint-gamemanager-3.png differ diff --git a/docs/docs/unreal/part-3-03-blueprint-gamemanager-4.png b/docs/docs/unreal/part-3-03-blueprint-gamemanager-4.png new file mode 100644 index 00000000000..cb8cd120edf Binary files /dev/null and b/docs/docs/unreal/part-3-03-blueprint-gamemanager-4.png differ diff --git a/docs/docs/unreal/part-3-03-blueprint-gamemanager-5.png b/docs/docs/unreal/part-3-03-blueprint-gamemanager-5.png new file mode 100644 index 00000000000..44ddd136c1d Binary files /dev/null and b/docs/docs/unreal/part-3-03-blueprint-gamemanager-5.png differ diff --git a/docs/docs/unreal/part-3-03-blueprint-gamemanager-6.png b/docs/docs/unreal/part-3-03-blueprint-gamemanager-6.png new file mode 100644 index 00000000000..f55359e8622 Binary files /dev/null and b/docs/docs/unreal/part-3-03-blueprint-gamemanager-6.png differ diff --git a/docs/docs/unreal/part-3-03-blueprint-gamemanager-7.png b/docs/docs/unreal/part-3-03-blueprint-gamemanager-7.png new file mode 100644 index 00000000000..ffc8b5ec6fb Binary files /dev/null and b/docs/docs/unreal/part-3-03-blueprint-gamemanager-7.png differ diff --git a/docs/docs/unreal/part-3-03-blueprint-gamemanager-8.png b/docs/docs/unreal/part-3-03-blueprint-gamemanager-8.png new file mode 100644 index 00000000000..2b26995c4b8 Binary files /dev/null and b/docs/docs/unreal/part-3-03-blueprint-gamemanager-8.png differ diff --git a/docs/docs/unreal/part-3-03-blueprint-gamemanager-9.png b/docs/docs/unreal/part-3-03-blueprint-gamemanager-9.png new file mode 100644 index 00000000000..cf002b48a0c Binary files /dev/null and b/docs/docs/unreal/part-3-03-blueprint-gamemanager-9.png differ diff --git a/docs/docs/unreal/part-3-04-blueprint-playercontroller-1.png b/docs/docs/unreal/part-3-04-blueprint-playercontroller-1.png new file mode 100644 index 00000000000..7d436a44957 Binary files /dev/null and b/docs/docs/unreal/part-3-04-blueprint-playercontroller-1.png differ diff --git a/docs/docs/unreal/part-3-04-blueprint-playercontroller-2.png b/docs/docs/unreal/part-3-04-blueprint-playercontroller-2.png new file mode 100644 index 00000000000..9842d86a696 Binary files /dev/null and b/docs/docs/unreal/part-3-04-blueprint-playercontroller-2.png differ diff --git a/docs/docs/unreal/part-3-04-blueprint-playercontroller-3.png b/docs/docs/unreal/part-3-04-blueprint-playercontroller-3.png new file mode 100644 index 00000000000..5cd577031ca Binary files /dev/null and b/docs/docs/unreal/part-3-04-blueprint-playercontroller-3.png differ diff --git a/docs/docs/unreal/part-3-05-blueprint-gamemanager-1.png b/docs/docs/unreal/part-3-05-blueprint-gamemanager-1.png new file mode 100644 index 00000000000..80246f97976 Binary files /dev/null and b/docs/docs/unreal/part-3-05-blueprint-gamemanager-1.png differ diff --git a/docs/docs/unreal/part-3.md b/docs/docs/unreal/part-3.md index d4974d7640a..cf5fcd9a777 100644 --- a/docs/docs/unreal/part-3.md +++ b/docs/docs/unreal/part-3.md @@ -31,11 +31,11 @@ This reducer also demonstrates how to insert new rows into a table. Here we are Now that we've ensured that our database always has a valid `world_size` let's spawn some food into the map. Add the following code to the end of the file. ```rust -const FOOD_MASS_MIN: u32 = 2; -const FOOD_MASS_MAX: u32 = 4; +const FOOD_MASS_MIN: i32 = 2; +const FOOD_MASS_MAX: i32 = 4; const TARGET_FOOD_COUNT: usize = 600; -fn mass_to_radius(mass: u32) -> f32 { +fn mass_to_radius(mass: i32) -> f32 { (mass as f32).sqrt() } @@ -99,11 +99,11 @@ This reducer also demonstrates how to insert new rows into a table. Here we are Now that we've ensured that our database always has a valid `world_size` let's spawn some food into the map. Add the following code to the end of the `Module` class. ```csharp -const uint FOOD_MASS_MIN = 2; -const uint FOOD_MASS_MAX = 4; -const uint TARGET_FOOD_COUNT = 600; +const int FOOD_MASS_MIN = 2; +const int FOOD_MASS_MAX = 4; +const int TARGET_FOOD_COUNT = 600; -public static float MassToRadius(uint mass) => MathF.Sqrt(mass); +public static float MassToRadius(int mass) => MathF.Sqrt(mass); [Reducer] public static void SpawnFood(ReducerContext ctx) @@ -138,7 +138,7 @@ public static void SpawnFood(ReducerContext ctx) public static float Range(this Random rng, float min, float max) => rng.NextSingle() * (max - min) + min; -public static uint Range(this Random rng, uint min, uint max) => (uint)rng.NextInt64(min, max); +public static int Range(this Random rng, int min, int max) => (int)rng.NextInt64(min, max); ``` ::: @@ -276,7 +276,7 @@ pub struct Player { identity: Identity, #[unique] #[auto_inc] - player_id: u32, + player_id: i32, name: String, } ``` @@ -290,7 +290,7 @@ public partial struct Player [PrimaryKey] public Identity identity; [Unique, AutoInc] - public uint player_id; + public int player_id; public string name; } ``` @@ -392,7 +392,7 @@ Now that we've got our food spawning and our players set up, let's create a matc Add the following to the bottom of your file. ```rust -const START_PLAYER_MASS: u32 = 15; +const START_PLAYER_MASS: i32 = 15; #[spacetimedb::reducer] pub fn enter_game(ctx: &ReducerContext, name: String) -> Result<(), String> { @@ -406,7 +406,7 @@ pub fn enter_game(ctx: &ReducerContext, name: String) -> Result<(), String> { Ok(()) } -fn spawn_player_initial_circle(ctx: &ReducerContext, player_id: u32) -> Result { +fn spawn_player_initial_circle(ctx: &ReducerContext, player_id: i32) -> Result { let mut rng = ctx.rng(); let world_size = ctx .db @@ -429,8 +429,8 @@ fn spawn_player_initial_circle(ctx: &ReducerContext, player_id: u32) -> Result Result { @@ -457,7 +457,7 @@ The `enter_game` reducer takes one argument, the player's `name`. We can use thi Add the following to the end of the `Module` class. ```csharp -const uint START_PLAYER_MASS = 15; +const int START_PLAYER_MASS = 15; [Reducer] public static void EnterGame(ReducerContext ctx, string name) @@ -469,7 +469,7 @@ public static void EnterGame(ReducerContext ctx, string name) SpawnPlayerInitialCircle(ctx, player.player_id); } -public static Entity SpawnPlayerInitialCircle(ReducerContext ctx, uint player_id) +public static Entity SpawnPlayerInitialCircle(ReducerContext ctx, int player_id) { var rng = ctx.Rng; var world_size = (ctx.Db.config.id.Find(0) ?? throw new Exception("Config not found")).world_size; @@ -485,7 +485,7 @@ public static Entity SpawnPlayerInitialCircle(ReducerContext ctx, uint player_id ); } -public static Entity SpawnCircleAt(ReducerContext ctx, uint player_id, uint mass, DbVector2 position, SpacetimeDB.Timestamp timestamp) +public static Entity SpawnCircleAt(ReducerContext ctx, int player_id, int mass, DbVector2 position, SpacetimeDB.Timestamp timestamp) { var entity = ctx.Db.entity.Insert(new Entity { @@ -567,12 +567,14 @@ Deleting the data is optional in this case, but in case you've been messing arou With the server logic in place to spawn food and players, extend the Unreal client to display the current state. +:::server-cpp + Add the `SetupArena` and `CreateBorderCube` methods and properties to your `GameManager.h` class. Place them below the `Handle{}` functions in the private block: ```cpp /* Border */ UFUNCTION() - void SetupArena(uint64 WorldSizeMeters); + void SetupArena(int64 WorldSizeMeters); UFUNCTION() void CreateBorderCube(const FVector2f Position, const FVector2f Size) const; @@ -625,7 +627,7 @@ AGameManager::AGameManager() Add the implementations of `SetupArena` and `CreateBorderCube` to the end of `GameManager.cpp`: ```cpp -void AGameManager::SetupArena(uint64 WorldSizeMeters) +void AGameManager::SetupArena(int64 WorldSizeMeters) { if (!BorderISM || !CubeMesh) return; @@ -636,7 +638,7 @@ void AGameManager::SetupArena(uint64 WorldSizeMeters) BorderISM->SetMaterial(0, BorderMaterial); } - // Convert from meters (uint64) → centimeters (double for precision) + // Convert from meters (int64) → centimeters (double for precision) const double worldSizeCmDouble = static_cast(WorldSizeMeters) * 100.0; // Clamp to avoid float overflow in transforms @@ -695,17 +697,80 @@ void AGameManager::HandleSubscriptionApplied(FSubscriptionEventContext& Context) // Once we have the initial subscription sync'd to the client cache // Get the world size from the config table and set up the arena - uint64 WorldSize = Conn->Db->Config->Id->Find(0).WorldSize; + int64 WorldSize = Conn->Db->Config->Id->Find(0).WorldSize; SetupArena(WorldSize); } ``` +::: +:::server-blueprint + +Open `BP_GameManager` and update to the following: + +1. Add a **Variable** + - Change **Variable Name** to `BorderThickness` + - Change **Variable Type** to **Float** + - Change **Default Value** to `50.0` + - Change **Category** to `Arena` +2. Add a **Variable** + - Change **Variable Name** to `BorderHeight` + - Change **Variable Type** to **Float** + - Change **Default Value** to `100.0` + - Change **Category** to `Arena` +3. Add a **Variable** + - Change **Variable Name** to `BorderMaterial` + - Change **Variable Type** to **Material Instance > Object Reference** + - Change **Default Value** to `BasicShapeMaterial_Inst` + - Change **Category** to `Arena` +4. Add a **Component** + - Click **Add** button + - Select **Instanced Static Mesh** + - Rename to `BorderISM` + +Add the `CreateBorderCube` and `SetupArena` functions and properties to `BP_GameManager`: + +Add **Function** named `CreateBorderCube` as follows: + +![Add CreateBorderCube](./part-3-01-blueprint-setup-arena-1.png) + +- Add **Input** as `Position` with **Vector 2D** as the type. +- Add **Input** as `Size` with **Vector 2D** as the type. + +Add **Function** named `SetupArena` as follows: + +![Add SetupArena](./part-3-01-blueprint-setup-arena-2.png) + + +![Continue SetupArena](./part-3-01-blueprint-setup-arena-3.png) + +- Add **Input** as `WorldSizeMeters` with **Integer 64** as the type. +- Add **Local Variable** as `WorldSizeCm` with **Float** as the type. +- Add **Local Variable** as `HalfWorldSize` with **Float** as the type. +- Add **Local Variable** as `BorderWidth` with **Float** as the type. +- Add **Local Variable** as `HalfBorder` with **Float** as the type. + +Add **Function** named `IsConnected` as follows: + +![Add IsConnected](./part-3-03-blueprint-gamemanager-2.png) + +- Add **Output** as `Result` with **Boolean** as the type. +- Check **Pure** + +In `OnApplied_Event`, call the `SetupArena` function. Update `OnApplied_Event` as follows: + + +![Call SetupArena](./part-3-01-blueprint-setup-arena-4.png) + +::: + The `OnApplied` callback is called after the server synchronizes the initial state of your tables with the client. After the sync, look up the world size from the `config` table and use it to set up the arena. ### Create Entity Blueprints With the arena set up, use the row data that SpacetimeDB syncs with the client to create and display **Blueprints** on the screen. +:::server-cpp + Start by making a C++ class for each entity you want in the scene. If the Unreal project is not running, start it now. From the top menu, choose **Tools -> New C++ Class...** to create the following classes (you’ll modify these later): > **Note:** After creating the first class, wait for **Live Coding** to finish before creating the next classes. @@ -722,34 +787,67 @@ Next add blueprints for our these classes: ![Add Circle](https://tmp-unreal-engine-tutorial-images.nyc3.digitaloceanspaces.com/part-3-01-create-blueprint.png) 1. **Circle Blueprint** - - In the **Content Drawer**, right-click and choose **Blueprint -> Blueprint Class**. - - Expand **All Classes**, search for `Circle`, highlight `Circle`, and click **Select**. + - In the **Content Drawer**, right-click and choose **Blueprint -> Blueprint Class**. + - Expand **All Classes**, search for `Circle`, highlight `Circle`, and click **Select**. - Rename the new Blueprint to `BP_Circle`. 2. **Food Blueprint** - - In the **Content Drawer**, right-click and choose **Blueprint -> Blueprint Class**. - - Expand **All Classes**, search for `Food`, highlight `Food`, and click **Select**. + - In the **Content Drawer**, right-click and choose **Blueprint -> Blueprint Class**. + - Expand **All Classes**, search for `Food`, highlight `Food`, and click **Select**. - Rename the new Blueprint to `BP_Food`. 3. **Player Blueprint** - - In the **Content Drawer**, right-click and choose **Blueprint -> Blueprint Class**. - - Expand **All Classes**, search for `PlayerPawn`, highlight `PlayerPawn`, and click **Select**. + - In the **Content Drawer**, right-click and choose **Blueprint -> Blueprint Class**. + - Expand **All Classes**, search for `PlayerPawn`, highlight `PlayerPawn`, and click **Select**. - Rename the new Blueprint to `BP_PlayerPawn`. 4. **Player Controller Blueprint** - - In the **Content Drawer**, right-click and choose **Blueprint -> Blueprint Class**. - - Expand **All Classes**, search for `BlackholioPlayerController`, highlight `BlackholioPlayerController`, and click **Select**. - - Rename the new Blueprint to `BP_BlackholioPlayerController`. + - In the **Content Drawer**, right-click and choose **Blueprint -> Blueprint Class**. + - Expand **All Classes**, search for `BlackholioPlayerController`, highlight `BlackholioPlayerController`, and click **Select**. + - Rename the new Blueprint to `BP_BlackholioPlayerController`. + - Open **Window -> World Settings** in the top menu. + - Change **Player Controller Class** from **PlayerController** to `BP_BlackholioPlayerController`. + - Save the level. + +::: +:::server-blueprint + +Add blueprints for our entities: + +1. **Entity Blueprint** + - In the **Content Drawer**, right-click and choose **Blueprint -> Blueprint Class**. + - Click **Actor**. + - Rename the new Blueprint to `BP_Entity`. + +2. **Circle Blueprint** + - In the **Content Drawer**, find and right-click **BP_Entity** and choose **Create Child Blueprint Class**. + - Rename the new Blueprint to `BP_Circle`. + +3. **Food Blueprint** + - In the **Content Drawer**, find and right-click **BP_Entity** and choose **Create Child Blueprint Class**. + - Rename the new Blueprint to `BP_Food`. + +4. **Player Blueprint** + - In the **Content Drawer**, right-click and choose **Blueprint -> Blueprint Class**. + - Click **Pawn**. + - Rename the new Blueprint to `BP_PlayerPawn`. + +5. **Player Controller Blueprint** + - In the **Content Drawer**, right-click and choose **Blueprint -> Blueprint Class**. + - Click **Player Controller**. + - Rename the new Blueprint to `BP_PlayerController`. - Open **Window -> World Settings** in the top menu. - - Change **Player Controller Class** from **PlayerController** to `BP_BlackholioPlayerController`. + - Change **Player Controller Class** from **PlayerController** to `BP_PlayerController`. - Save the level. +::: + ### Set Up the Nameplate Blueprint Create a widget Blueprint for the player nameplate: -- In the **Content Drawer**, right-click and choose **Blueprint -> Blueprint Class**. -- Expand **All Classes**, search for **UserWidget**, highlight **UserWidget**, and click **Select**. +- In the **Content Drawer**, right-click and choose **User Interface -> Widget Blueprint**. +- Click **User Widget**. - Name the new Blueprint `WBP_Nameplate`. Double-click `WBP_Nameplate` to open it, then make the following changes: @@ -831,6 +929,8 @@ Open `BP_PlayerPawn` and make the following changes: ### Update Classes +:::server-cpp + With the Blueprints set up, return to the source code behind the entities. First, add helper functions to translate server-side vectors to Unreal vectors. Open `DbVector2.h` and update it as follows: @@ -888,7 +988,7 @@ class CLIENT_UNREAL_API AEntity : public AActor { GENERATED_BODY() -public: +public: AEntity(); protected: @@ -902,17 +1002,18 @@ protected: float TargetScale = 1.f; public: - uint32 EntityId = 0; + UPROPERTY(EditDefaultsOnly, Category="BH|Entity") + int32 EntityId = 0; virtual void Tick(float DeltaTime) override; - void Spawn(uint32 InEntityId); + void Spawn(int32 InEntityId); virtual void OnEntityUpdated(const FEntityType& NewVal); virtual void OnDelete(const FEventContext& Context); void SetColor(const FLinearColor& Color) const; - static float MassToRadius(uint32 Mass) { return FMath::Sqrt(static_cast(Mass)); } - static float MassToDiameter(uint32 Mass) { return MassToRadius(Mass) * 2.f; } + static float MassToRadius(int32 Mass) { return FMath::Sqrt(static_cast(Mass)); } + static float MassToDiameter(int32 Mass) { return MassToRadius(Mass) * 2.f; } }; ``` @@ -943,7 +1044,7 @@ void AEntity::Tick(float DeltaTime) SetActorScale3D(FVector(NewScale)); } -void AEntity::Spawn(uint32 InEntityId) +void AEntity::Spawn(int32 InEntityId) { EntityId = InEntityId; @@ -1004,7 +1105,8 @@ class CLIENT_UNREAL_API ACircle : public AEntity public: ACircle(); - uint32 OwnerPlayerId = 0; + UPROPERTY(BlueprintReadOnly, Category="BH|Circle") + int32 OwnerPlayerId = 0; UPROPERTY(BlueprintReadOnly, Category="BH|Circle") FString Username; @@ -1170,7 +1272,8 @@ public: APlayerPawn(); void Initialize(FPlayerType Player); - uint32 PlayerId = 0; + UPROPERTY(BlueprintReadOnly, Category="BH|Player") + int32 PlayerId = 0; UPROPERTY(BlueprintReadOnly, Category="BH|Player") FString Username; UPROPERTY(BlueprintReadWrite, Category="BH|Player") @@ -1184,7 +1287,8 @@ public: UFUNCTION() void OnCircleDeleted(ACircle* Circle); - uint32 TotalMass() const; + UFUNCTION(BlueprintPure, Category="BH|Player") + int32 TotalMass() const; UFUNCTION(BlueprintPure, Category="BH|Player") FVector CenterOfMass() const; @@ -1260,16 +1364,16 @@ void APlayerPawn::OnCircleDeleted(ACircle* Circle) } } -uint32 APlayerPawn::TotalMass() const +int32 APlayerPawn::TotalMass() const { - uint32 Total = 0; + int32 Total = 0; for (int32 Index = 0; Index < OwnedCircles.Num(); ++Index) { const TWeakObjectPtr& Weak = OwnedCircles[Index]; if (!Weak.IsValid()) continue; const ACircle* Circle = Weak.Get(); - const uint32 Id = Circle->EntityId; + const int32 Id = Circle->EntityId; const FEntityType Entity = AGameManager::Instance->Conn->Db->Entity->EntityId->Find(Id); Total += Entity.Mass; @@ -1285,7 +1389,7 @@ FVector APlayerPawn::CenterOfMass() const } FVector WeightedPosition = FVector::ZeroVector; // Σ (pos * mass) - double TotalMass = 0.0; // Σ mass + double TotalMass = 0.0; // Σ mass const int32 Count = OwnedCircles.Num(); @@ -1295,7 +1399,7 @@ FVector APlayerPawn::CenterOfMass() const if (!Weak.IsValid()) continue; const ACircle* Circle = Weak.Get(); - const uint32 Id = Circle->EntityId; + const int32 Id = Circle->EntityId; const FEntityType Entity = AGameManager::Instance->Conn->Db->Entity->EntityId->Find(Id); const double Mass = Entity.Mass; @@ -1356,8 +1460,201 @@ void APlayerPawn::Tick(float DeltaTime) } ``` +::: +:::server-blueprint + +#### Entity Blueprint + +With the foundation in place, implement the core entity class. Edit `BP_Entity` add the following **Variables**: + +1. Add `LerpStartPosition` + - Change **Variable Type** to **Vector** +2. Add `LerpTargetPosition` + - Change **Variable Type** to **Vector** +3. Add `TargetScale` + - Change **Variable Type** to **Float** + - Change **Default Value** to `1.0` +4. Add `LerpTime` + - Change **Variable Type** to **Float** +5. Add `LerpDuration` + - Change **Variable Type** to **Float** + - Change **Default Value** to `0.1` +6. Add `EntityId` + - Change **Variable Type** to **Integer** +7. Add `Alpha` + - Change **Variable Type** to **Float** + + +![Add Variables](./part-3-02-blueprint-entity-1.png) + +Add the following to **Event Tick**: + + +![Update Event Tick](./part-3-02-blueprint-entity-2.png) + +Add **Function** named `MassToRadius` as follows: + +![Add MassToRadius](./part-3-02-blueprint-entity-7.png) + +- Add **Input** as `Mass` with **Integer** as the type. +- Add **Output** as `Radius` with **Float** as the type. + +Add **Function** named `MassToDiameter` as follows: + +![Add MassToDiameter](./part-3-02-blueprint-entity-8.png) + +- Add **Input** as `Mass` with **Integer** as the type. +- Add **Output** as `Diameter` with **Float** as the type. + +Add **Function** named `OnUpdated` as follows: + +![Add OnUpdated](./part-3-02-blueprint-entity-3.png) + +- Add **Input** as `NewRow` with **Entity Type** as the type. + +Add **Function** named `OnDeleted` as follows: + +![Add OnDeleted](./part-3-02-blueprint-entity-4.png) + +- Add **Input** as `Context` with **Event Context** as the type. + +Add **Function** named `Spawn` as follows: + +![Add Spawn](./part-3-02-blueprint-entity-5.png) + +- Add **Input** as `In Entity Id` with **Integer** as the type. + +Add **Function** named `SetColor` as follows: + +![Add SetColor](./part-3-02-blueprint-entity-6.png) + +- Add **Input** as `Color` with **Linear Color** as the type. + +The `Entity` class provides helper functions and basic functionality to manage game objects based on entity updates. + +> **Note:** One notable feature is linear interpolation (lerp) between the server-reported entity position and the client-drawn position. This technique produces smoother movement. +> +> If you're interested in learning more checkout [this demo](https://gabrielgambetta.com/client-side-prediction-live-demo.html) from Gabriel Gambetta. + +#### PlayerPawn Blueprint + +Open `BP_PlayerPawn` and add the following **Variables**: + +1. Add `Username` + - Change **Variable Type** to **String** + - Check **Instance Editable** +2. Add `PlayerId` + - Change **Variable Type** to an **Integer** +3. Add `IsLocalPlayer` + - Change **Variable Type** to an **Boolean** +4. Add `OwnedCircles` + - Change **Variable Type** to an **Array** **BP Circle -> Object References** +5. Add `GameManager` + - Change **Variable Type** to an **BP GameManager -> Object References** +6. Add `Target` + - Change **Variable Type** to an **Vector** + + +![Add Variables](./part-3-02-blueprint-player-1.png) + +Add **Function** named `GetGameManager` as follows: + +![Add GetGameManager](./part-3-02-blueprint-player-2.png) + +- Add **Output** as `GameManager` with **BP Game Manager** as the type. + +Add **Function** named `Initialize` as follows: + +![Add Initialize](./part-3-02-blueprint-player-3.png) + +- Add **Input** as `PlayerRow` with **Player Type** as the type. + +Add **Function** named `OnCircleSpawned` as follows: + +![Add OnCircleSpawned](./part-3-02-blueprint-player-4.png) + +- Add **Input** as `Circle` with **BP Circle -> Object Reference** as the type. + +Add **Function** named `OnCircleDeleted` as follows: + +![Add OnCircleDeleted](./part-3-02-blueprint-player-5.png) + +- Add **Input** as `Circle` with **BP Circle -> Object Reference** as the type. + +Add **Function** named `CenterOfMass` as follows: + +![Add CenterOfMass](./part-3-02-blueprint-player-6.png) + +- Add **Output** as `Center` with **Vector** as the type. +- Add **Local Variable** as `WeightedPosition` with **Vector** as the type. +- Add **Local Variable** as `TotalMass` with **Float** as the type. + +Add **Function** named `UpdateTargetLocation` as follows: + +![Add UpdateTargetLocation](./part-3-02-blueprint-player-7.png) + +Add **Function** named `GetUsername` as follows: + +![Add GetUsername](./part-3-02-blueprint-player-10.png) + +- Add **Output** as `Output` with **String** as the type. +- Check **Pure** + +Update **Event Tick** to: + +![Update Event Tick](./part-3-02-blueprint-player-8.png) + +Update **Event Destroyed** to: + +![Update Event Destroyed](./part-3-02-blueprint-player-9.png) + +#### Circle Blueprint + +Open `BP_Circle` and add the following **Variables**: + +1. Add `OwningPlayer` + - Change **Variable Type** to **BP Player Pawn -> Object Reference** +2. Add `ColorPalette` + - Change **Variable Type** to an **Array** of **Linear Color** + + +![Color Palette](./part-3-02-blueprint-circle-1.png) + +Override **Function** `OnDeleted` as follows: + +![Override OnDeleted](./part-3-02-blueprint-circle-2.png) + +- Add **Input** as `Context` with **Entity Context** as the type. + +Add **Function** named `SpawnCircle` as follows: + +![Add SpawnCircle](./part-3-02-blueprint-circle-3.png) + +- Add **Input** as `Circle` with **Circle Type** as the type. +- Add **Input** as `InOwner` with **BP Player Pawn -> Object Reference** as the type + +#### Food Blueprint + +Open `BP_Food` and add the following **Variables**: + +1. Add `ColorPalette` + - Change **Variable Type** to an **Array** of **Linear Color** + + +![Color Palette](./part-3-02-blueprint-food-1.png) + +Add **Function** named `SpawnFood` as follows: + +![Add SpawnFood](./part-3-02-blueprint-food-2.png) + +- Add **Input** as `Food Entity` with **Food Type** as the type. + +::: + ### Spawning Blueprints +:::server-cpp + Update `GameManager.h` to support spawning Blueprints. Make the following edits to the file: @@ -1404,9 +1701,9 @@ Below the `/* Border */` section, add code to link the SpacetimeDB tables to the /* Data Bindings */ UPROPERTY() - TMap> EntityMap; + TMap> EntityMap; UPROPERTY() - TMap> PlayerMap; + TMap> PlayerMap; APlayerPawn* SpawnOrGetPlayer(const FPlayerType& PlayerRow); ACircle* SpawnCircle(const FCircleType& CircleRow); @@ -1501,7 +1798,7 @@ void AGameManager::OnEntityDelete(const FEventContext& Context, const FEntityTyp { TWeakObjectPtr EntityPtr; const bool bHadEntry = EntityMap.RemoveAndCopyValue(RemovedRow.EntityId, EntityPtr); - const bool bIsValid =EntityPtr.IsValid(); + const bool bIsValid = EntityPtr.IsValid(); if (!bHadEntry || !bIsValid) { return; @@ -1606,11 +1903,82 @@ AFood* AGameManager::SpawnFood(const FFoodType& FoodEntity) } ``` +::: +:::server-blueprint + +Open `BP_GameManager` and add the following **Variables**: + +1. Add `CircleClass` + - Change **Variable Type** to **BP Circle -> Class Reference** + - Check **Instance Editable** + - Change **Category** to `Classes` + - Change **Default Value** to `BP_Circle` +2. Add `FoodClass` + - Change **Variable Type** to **BP Food -> Class Reference** + - Check **Instance Editable** + - Change **Category** to `Classes` + - Change **Default Value** to `BP_Food` +3. Add `PlayerClass` + - Change **Variable Type** to **BP Player Pawn -> Class Reference** + - Check **Instance Editable** + - Change **Category** to `Classes` + - Change **Default Value** to `BP_PlayerPawn` +4. Add `EntityMap` + - Change **Variable Type** to an **Integer** + - Change **Variable Type** to **Map** and set value type to **BP Entity -> Object Reference** +5. Add `PlayerMap` + - Change **Variable Type** to an **Integer** + - Change **Variable Type** to **Map** and set value type to **BP Player Pawn -> Object Reference** + + +![Add Variables](./part-3-03-blueprint-gamemanager-1.png) + +Add **Function** named `SpawnOrGetPlayer` as follows: + +![Add SpawnOrGetPlayer](./part-3-03-blueprint-gamemanager-3.png) + +- Add **Input** as `PlayerRow` with **Player Type** as the type. +- Add **Output** as `PlayerPawn` with **BP Player Pawn -> Object Reference** as the type. + +With the functions and variables in place next we'll expand the **EventGraph**: + +Extened **OnConnect_Event** as follows: + +![Update OnConnect_Event](./part-3-03-blueprint-gamemanager-4.png) + +![Update OnConnect_Event](./part-3-03-blueprint-gamemanager-5.png) + +> **Note:** For the events the naming scheme for this tutorial is `__Event` for example `Circle_OnInsert_Event`. + +Update **Circle_OnInsert_Event** as follows: + +![Update Circle_OnInsert_Event](./part-3-03-blueprint-gamemanager-6.png) + +Update **Entity_OnUpdate_Event** as follows: + +![Update Entity_OnUpdate_Event](./part-3-03-blueprint-gamemanager-7.png) + +Update **Entity_OnDelete_Event** as follows: + +![Update Entity_OnDelete_Event](./part-3-03-blueprint-gamemanager-8.png) + +Update **Food_OnInsert_Event** as follows: + +![Update Food_OnInsert_Event](./part-3-03-blueprint-gamemanager-9.png) + +Update **Player_OnInsert_Event** and **Player_OnDelete_Event** as follows: + +![Update Player Events](./part-3-03-blueprint-gamemanager-10.png) + +::: + ### Player Controller In most Unreal projects, proper input handling depends on setting up the PlayerController. We’ll finish that setup in the next part of the tutorial. For now, add the possession logic. +:::server-cpp + Edit `BlackholioPlayerController.h` as follows: ```cpp @@ -1681,6 +2049,37 @@ FVector2D ABlackholioPlayerController::ComputeDesiredDirection() const } ``` +::: +:::server-blueprint + +Last update `BP_PlayerController` for the basics by adding the following **Variables**: + +1. Add `GameManger` + - Change **Variable Type** to **BP Game Manager -> Object Reference** +2. Add `LocalPlayer` + - Change **Variable Type** to **BP Player Pawn -> Object Reference** +3. Add `LastMovementSendTime` + - Change **Variable Type** to **Float** +4. Add `SendUpdateFrequency` + - Change **Variable Type** to **Float** + - Change **Default Value** to `0.0333` + +Add **Function** named `GetGameManager` as follows: + +![Add GetGameManager](./part-3-04-blueprint-playercontroller-1.png) + +- Add **Output** as `GameManager` with **BP Game Manager** as the type. + +Override **Function -> On Possess** as follows: + +![Add GetGameManager](./part-3-04-blueprint-playercontroller-2.png) + +Update **Event BeginPlay** as follows: + +![Update BeginPlay](./part-3-04-blueprint-playercontroller-3.png) + +::: + ### Entering the Game :::server-rust @@ -1694,6 +2093,8 @@ At this point, you may need to regenerate your bindings the following command fr spacetime generate --lang unrealcpp --uproject-dir ../client_unreal --project-path ./ --module-name client_unreal ``` +:::server-cpp + The last step is to call the `enter_game` reducer on the server, passing in a username for the player. For simplicity, call `enter_game` from the `HandleSubscriptionApplied` callback with the name `TestPlayer`. @@ -1706,7 +2107,7 @@ void AGameManager::HandleSubscriptionApplied(FSubscriptionEventContext& Context) // Once we have the initial subscription sync'd to the client cache // Get the world size from the config table and set up the arena - uint64 WorldSize = Conn->Db->Config->Id->Find(0).WorldSize; + int64 WorldSize = Conn->Db->Config->Id->Find(0).WorldSize; SetupArena(WorldSize); Context.Reducers->EnterGame("TestPlayer"); @@ -1715,12 +2116,26 @@ void AGameManager::HandleSubscriptionApplied(FSubscriptionEventContext& Context) > **Reminder:** Be sure to rebuild your project after making changes to the code. +::: +:::server-blueprint + +The last step is to call the `enter_game` reducer on the server, passing in a username for the player. +For simplicity, call `enter_game` from the `OnApplied_Event` callback with the name `TestPlayer`. + +Open up `BP_GameManager` and edit `OnApplied_Event` to match the following: + + +![Update OnApplied_Event](./part-3-05-blueprint-gamemanager-1.png) + +::: + ### Trying It Out +:::server-cpp Almost everything is ready to play. Before launching, set up the spawning classes: 1. Open `BP_GameManager`. -2. Update the spawning classes: +2. Make sure the spawning classes are set: - **Circle Class** → `BP_Circle` - **Food Class** → `BP_Food` - **Player Class** → `BP_PlayerPawn` @@ -1729,11 +2144,24 @@ Almost everything is ready to play. Before launching, set up the spawning classe Next, wire up `SetUsername` to update the Nameplate: -1. Open `BP_Circle`. +1. Open `BP_Circle`. 2. In **Event BeginPlay**, add the following: ![Nameplate Update](https://tmp-unreal-engine-tutorial-images.nyc3.digitaloceanspaces.com/part-3-04-nameplate-change.png) +::: +:::server-blueprint +Almost everything is ready to play. Before launching, set up the spawning classes: + +1. Open `BP_GameManager`. +2. Make sure the spawning classes are set: + - **Circle Class** → `BP_Circle` + - **Food Class** → `BP_Food` + - **Player Class** → `BP_PlayerPawn` + +> **Reminder:** Compile and save your changes. +::: + --- After publishing the module, press **Play** to see it in action. diff --git a/docs/docs/unreal/part-4-01-blueprint-playercontroller-1.png b/docs/docs/unreal/part-4-01-blueprint-playercontroller-1.png new file mode 100644 index 00000000000..10d3eb023b9 Binary files /dev/null and b/docs/docs/unreal/part-4-01-blueprint-playercontroller-1.png differ diff --git a/docs/docs/unreal/part-4-01-blueprint-playercontroller-2.png b/docs/docs/unreal/part-4-01-blueprint-playercontroller-2.png new file mode 100644 index 00000000000..eb108600e2a Binary files /dev/null and b/docs/docs/unreal/part-4-01-blueprint-playercontroller-2.png differ diff --git a/docs/docs/unreal/part-4-01-blueprint-playercontroller-3.png b/docs/docs/unreal/part-4-01-blueprint-playercontroller-3.png new file mode 100644 index 00000000000..f80e5e914b5 Binary files /dev/null and b/docs/docs/unreal/part-4-01-blueprint-playercontroller-3.png differ diff --git a/docs/docs/unreal/part-4.md b/docs/docs/unreal/part-4.md index 257d7cfafc1..59f53c370a6 100644 --- a/docs/docs/unreal/part-4.md +++ b/docs/docs/unreal/part-4.md @@ -224,9 +224,9 @@ pub struct MoveAllPlayersTimer { scheduled_at: spacetimedb::ScheduleAt, } -const START_PLAYER_SPEED: u32 = 10; +const START_PLAYER_SPEED: i32 = 10; -fn mass_to_max_move_speed(mass: u32) -> f32 { +fn mass_to_max_move_speed(mass: i32) -> f32 { 2.0 * START_PLAYER_SPEED as f32 / (1.0 + (mass as f32 / START_PLAYER_MASS as f32).sqrt()) } @@ -273,9 +273,9 @@ public partial struct MoveAllPlayersTimer public ScheduleAt scheduled_at; } -const uint START_PLAYER_SPEED = 10; +const int START_PLAYER_SPEED = 10; -public static float MassToMaxMoveSpeed(uint mass) => 2f * START_PLAYER_SPEED / (1f + MathF.Sqrt((float)mass / START_PLAYER_MASS)); +public static float MassToMaxMoveSpeed(int mass) => 2f * START_PLAYER_SPEED / (1f + MathF.Sqrt((float)mass / START_PLAYER_MASS)); [Reducer] public static void MoveAllPlayers(ReducerContext ctx, MoveAllPlayersTimer timer) @@ -347,6 +347,8 @@ spacetime generate --lang unrealcpp --uproject-dir ../client_unreal --project-pa ### Moving on the Client +:::server-cpp + The final step is to update `BlackholioPlayerController` on the client to call the `update_player_input` reducer. Open `BlackholioPlayerController.cpp` and replace the stubbed function added earlier with the following: @@ -428,6 +430,28 @@ void ABlackholioPlayerController::Tick(float DeltaSeconds) > **Reminder:** Be sure to rebuild your project after making changes to the code. +::: +:::server-blueprint + +The final step is to update `BP_PlayerController` on the client to call the `update_player_input` reducer. + +Add **Function** named `ComputeDesiredDirection` as follows: + +![Add ComputeDesiredDirection](./part-4-01-blueprint-playercontroller-1.png) + +![Add ComputeDesiredDirection](./part-4-01-blueprint-playercontroller-2.png) + +- Add **Output** as `Result` with **Vector 2D** as the type. +- Add **Local Variable** as `ViewpointCenter` with **Vector 2D** as the type. +- Add **Local Variable** as `MousePosition` with **Vector 2D** as the type. +- Check **Pure** + +Finally, update the `Event Tick` function to use this logic and trigger the reducer: + +![Update Event Tick](./part-4-01-blueprint-playercontroller-3.png) + +::: + Let's try it out! Press play and roam freely around the arena! Now we're cooking with gas. ### Collisions and Eating Food @@ -518,6 +542,7 @@ pub fn move_all_players(ctx: &ReducerContext, _timer: MoveAllPlayersTimer) -> Re Ok(()) } ``` + ::: :::server-csharp Wrong. With SpacetimeDB it's extremely easy. All we have to do is add an `IsOverlapping` helper function which does some basic math based on mass radii, and modify our `MoveAllPlayers` reducer to loop through every entity in the arena for every circle, checking each for overlaps. This may not be the most efficient way to do collision checking (building a quad tree or doing [spatial hashing](https://conkerjo.wordpress.com/2009/06/13/spatial-hashing-implementation-for-fast-2d-collisions/) might be better), but SpacetimeDB is very fast so for this number of entities it'll be a breeze for SpacetimeDB.