From 8d102ea46d7eb7f8b69bdffc594ab97da1c53c23 Mon Sep 17 00:00:00 2001 From: Nik Cherednik Date: Wed, 3 Jul 2024 23:45:23 +0300 Subject: [PATCH] feat: camera clamp, lerp & fixed camera jittering with gaps --- .../Components/ComponentsCompositionRoot.cs | 4 +-- .../GameDesktop/Content/TileMaps/Test.ldtk | 8 ++--- src/Apps/GameDesktop/Game.cs | 9 ++++++ src/Apps/GameDesktop/GameDesktop.csproj | 3 ++ src/Libs/Components/Data/CameraComponent.cs | 14 ++++++++- .../CharacterMovementAnimationSystem.cs | 0 .../Systems/Render/CameraFollowingSystem.cs | 31 ++++++++----------- .../RenderCharacterMovementAnimationSystem.cs | 10 ++++-- .../Systems/Render/TilesRenderingSystem.cs | 7 +++-- src/Libs/Systems/WorldInitializer.cs | 7 ++++- 10 files changed, 63 insertions(+), 30 deletions(-) rename src/Libs/Systems/{Render => }/CharacterMovementAnimationSystem.cs (100%) diff --git a/src/Apps/GameDesktop/CompositionRoots/Components/ComponentsCompositionRoot.cs b/src/Apps/GameDesktop/CompositionRoots/Components/ComponentsCompositionRoot.cs index a19f946..09146bf 100644 --- a/src/Apps/GameDesktop/CompositionRoots/Components/ComponentsCompositionRoot.cs +++ b/src/Apps/GameDesktop/CompositionRoots/Components/ComponentsCompositionRoot.cs @@ -150,7 +150,7 @@ private static void RegisterTransformComponent(IServiceRegistry serviceRegistry) serviceRegistry.RegisterTransient(_ => new TransformComponent()); serviceRegistry.RegisterSingleton(_ => - new TransformComponent { Position = new(0, 4) }, "PlayerAnimations"); + new TransformComponent { Position = new(0, 3) }, "PlayerAnimationsOffset"); serviceRegistry.RegisterSingleton(_ => new TransformComponent { Position = new(316, 116), Pivot = Sector.Up }, "PlayerEntity"); @@ -194,7 +194,7 @@ private static void RegisterCharacterAnimatorComponent(IServiceRegistry serviceR const Sector facing = Sector.Right; return new CharacterAnimatorComponent(facing, movementAnimations.IdleAnimations[facing], - factory.GetInstance("PlayerAnimations")); + factory.GetInstance("PlayerAnimationsOffset")); }, "PlayerEntity"); } } diff --git a/src/Apps/GameDesktop/Content/TileMaps/Test.ldtk b/src/Apps/GameDesktop/Content/TileMaps/Test.ldtk index 34de78b..8fac07f 100644 --- a/src/Apps/GameDesktop/Content/TileMaps/Test.ldtk +++ b/src/Apps/GameDesktop/Content/TileMaps/Test.ldtk @@ -2360,7 +2360,7 @@ }, { "__identifier": "Player", - "__grid": [5,5], + "__grid": [26,13], "__pivot": [0.5,1], "__tags": ["Transform"], "__tile": { "tilesetUid": 3, "x": 48, "y": 128, "w": 32, "h": 32 }, @@ -2369,10 +2369,10 @@ "width": 32, "height": 32, "defUid": 120, - "px": [88,96], + "px": [424,224], "fieldInstances": [], - "__worldX": 88, - "__worldY": 96 + "__worldX": 424, + "__worldY": 224 }, { "__identifier": "Red_Flower", diff --git a/src/Apps/GameDesktop/Game.cs b/src/Apps/GameDesktop/Game.cs index 679a46e..d6173c5 100644 --- a/src/Apps/GameDesktop/Game.cs +++ b/src/Apps/GameDesktop/Game.cs @@ -18,6 +18,15 @@ namespace GameDesktop; +// TODO: 1. Камера, зум, границы уровня +// 2. Переход между уровнями +// 3. Меню, экран загрузки, настройки игры, пауза +// 4. Окно, фпс и скорость игры +// 5. Сохранение, загрузка +// 6. Инвентарь, добыча ресурсов, квесты, награды +// +// Наполнение уровня энтити, компоненты, коллайдеры, рефакторинг +// Пофиксить при движении по диагонали полосы между тайлами public class Game : Microsoft.Xna.Framework.Game { private readonly ILogger _logger; diff --git a/src/Apps/GameDesktop/GameDesktop.csproj b/src/Apps/GameDesktop/GameDesktop.csproj index babcb09..e7a8969 100644 --- a/src/Apps/GameDesktop/GameDesktop.csproj +++ b/src/Apps/GameDesktop/GameDesktop.csproj @@ -71,6 +71,9 @@ Always + + Always + diff --git a/src/Libs/Components/Data/CameraComponent.cs b/src/Libs/Components/Data/CameraComponent.cs index 2341c26..8f11d72 100644 --- a/src/Libs/Components/Data/CameraComponent.cs +++ b/src/Libs/Components/Data/CameraComponent.cs @@ -1,6 +1,7 @@ -using System.Numerics; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Scellecs.Morpeh; +using Vector2 = System.Numerics.Vector2; namespace Components.Data; @@ -12,4 +13,15 @@ public struct CameraComponent(Viewport viewport) : IComponent public Vector2 Position; public Vector2 WorldToScreen(Vector2 other) => other - Position; + + public Vector2 GetCenteredPosition(Viewport viewport, Vector2 off) + { + var cameraX = off.X - (float)viewport.Width / 2; + var cameraY = off.Y - (float)viewport.Height / 2; + + cameraX = MathHelper.Clamp(cameraX, 0, 900 - viewport.Width); + cameraY = MathHelper.Clamp(cameraY, 0, 600 - viewport.Height); + + return new Vector2(cameraX, cameraY); + } } diff --git a/src/Libs/Systems/Render/CharacterMovementAnimationSystem.cs b/src/Libs/Systems/CharacterMovementAnimationSystem.cs similarity index 100% rename from src/Libs/Systems/Render/CharacterMovementAnimationSystem.cs rename to src/Libs/Systems/CharacterMovementAnimationSystem.cs diff --git a/src/Libs/Systems/Render/CameraFollowingSystem.cs b/src/Libs/Systems/Render/CameraFollowingSystem.cs index 7445e7c..6962288 100644 --- a/src/Libs/Systems/Render/CameraFollowingSystem.cs +++ b/src/Libs/Systems/Render/CameraFollowingSystem.cs @@ -1,13 +1,15 @@ -using System.Numerics; -using Components.Data; +using Components.Data; using Components.Render.Animation; using Components.Render.Static; using Components.Tags; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Scellecs.Morpeh; +using Vector2 = System.Numerics.Vector2; namespace Systems.Render; +// issue: https://gamedev.stackexchange.com/questions/46963/how-to-avoid-texture-bleeding-in-a-texture-atlas public class CameraFollowingSystem(World world) : ILateSystem { // private readonly ICamera _camera; @@ -38,27 +40,20 @@ public void OnUpdate(float deltaTime) ref var transform = ref e.GetComponent(); ref var camera = ref e.GetComponent(); - camera.Position = GetCenteredPosition(camera.Viewport, off: transform.Position); + camera.Position = Vector2.Lerp(camera.Position, GetCenteredPosition(camera.Viewport, off: transform.Position), + .25f); + // camera.Position = Vector2.Lerp(camera.Position, transform.Position, .2f); } - private static Vector2 GetCenteredPosition(Viewport viewport, Vector2 off) => new( - off.X - viewport.Width / 2, - off.Y - viewport.Height / 2); - - private static IEnumerable SortEntitiesByYPosition(Filter filter) + private static Vector2 GetCenteredPosition(Viewport viewport, Vector2 off) { - List entities = new List(); + var cameraX = off.X - (float)viewport.Width / 2; + var cameraY = off.Y - (float)viewport.Height / 2; - foreach (Entity e in filter) - { - entities.Add(e); - } + cameraX = MathHelper.Clamp(cameraX, 0, 900 - viewport.Width); + cameraY = MathHelper.Clamp(cameraY, 0, 600 - viewport.Height); - return entities.OrderBy(x => - { - ref var transform = ref x.GetComponent(); - return transform.Position.Y; - }); + return new Vector2(cameraX, cameraY); } public void Dispose() diff --git a/src/Libs/Systems/Render/RenderCharacterMovementAnimationSystem.cs b/src/Libs/Systems/Render/RenderCharacterMovementAnimationSystem.cs index 91a28b4..d1437d1 100644 --- a/src/Libs/Systems/Render/RenderCharacterMovementAnimationSystem.cs +++ b/src/Libs/Systems/Render/RenderCharacterMovementAnimationSystem.cs @@ -2,6 +2,7 @@ using Components.Render.Animation; using Components.Render.Static; using Components.Tags; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Scellecs.Morpeh; using Scellecs.Morpeh.Extended; @@ -45,8 +46,11 @@ public void OnUpdate(float deltaTime) ref var animator = ref e.GetComponent(); var pivotOffset = animator.GetOffPivot(transform); var localTransform = animator.LocalTransform; + var position = at + localTransform.Position - pivotOffset; - animator.Animation.Draw(spriteBatch, at + localTransform.Position - pivotOffset); + // casting to int for pixel perfect matching + animator.Animation.Draw(spriteBatch, + new Vector2((int)Math.Round(position.X), (int)Math.Round(position.Y))); } if (e.Has()) @@ -54,8 +58,10 @@ public void OnUpdate(float deltaTime) ref var sprite = ref e.GetComponent(); var pivotOffset = sprite.GetOffPivot(transform); var localTransform = sprite.LocalTransform; + var position = at + localTransform.Position - pivotOffset; - sprite.Sprite.Draw(spriteBatch, at + localTransform.Position - pivotOffset); + // casting to int for pixel perfect matching + sprite.Sprite.Draw(spriteBatch, new Vector2((int)Math.Round(position.X), (int)Math.Round(position.Y))); } } } diff --git a/src/Libs/Systems/Render/TilesRenderingSystem.cs b/src/Libs/Systems/Render/TilesRenderingSystem.cs index ab54dc9..03d163e 100644 --- a/src/Libs/Systems/Render/TilesRenderingSystem.cs +++ b/src/Libs/Systems/Render/TilesRenderingSystem.cs @@ -103,10 +103,13 @@ private void RenderLevel(LDtkLevel level, CameraComponent camera) { Vector2 tilePos = new(tile.Px.X + layer._PxTotalOffsetX, tile.Px.Y + layer._PxTotalOffsetY); - var position = camera.WorldToScreen(tilePos); + var camPos = camera.WorldToScreen(tilePos); + // casting to int for pixel perfect matching + var position = new Microsoft.Xna.Framework.Vector2((int)Math.Round(camPos.X), + (int)Math.Round(camPos.Y)); Rectangle rect = new(tile.Src.X, tile.Src.Y, layer._GridSize, layer._GridSize); SpriteEffects mirror = (SpriteEffects)tile.F; - spriteBatch.Draw(texture, position, rect, new Color(1f, 1f, 1f, layer._Opacity), 0, + spriteBatch.Draw(texture, position, rect, new Color(Color.White, layer._Opacity), 0, Vector2.Zero, 1f, mirror, 0); } } diff --git a/src/Libs/Systems/WorldInitializer.cs b/src/Libs/Systems/WorldInitializer.cs index 2cd1192..f3d9e59 100644 --- a/src/Libs/Systems/WorldInitializer.cs +++ b/src/Libs/Systems/WorldInitializer.cs @@ -29,7 +29,6 @@ public void OnAwake() throw new Exception("Level has no layers."); } - for (int i = level.LayerInstances.Length - 1; i >= 0; --i) { LayerInstance layer = level.LayerInstances[i]; @@ -56,6 +55,12 @@ public void OnAwake() // var pivotOffset = MathUtils.SectorToVector(transform.Pivot); transform.Position = new Vector2((float)entity._WorldX, (float)entity._WorldY); + + if (e.Has()) + { + ref var camera = ref e.GetComponent(); + camera.Position = camera.GetCenteredPosition(camera.Viewport, transform.Position); + } } } }