moved utilities for rhythmbullet to separate project for a better custom content loaders for the monogame pipeline as well as for future projects and of course learning how things work.

This commit is contained in:
Harrison Deng 2018-11-29 20:41:06 -06:00
parent b61f390121
commit 698dd9d2f0
26 changed files with 1787 additions and 3 deletions

View File

@ -0,0 +1,45 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RhythmBullet.Utilities.Camera
{
public class Camera2D
{
public float Zoom;
public Vector2 Position;
public Matrix TransformMatrix { get; private set; }
public GraphicsDevice graphicsDevice;
public Camera2D(GraphicsDevice graphicsDevice)
{
this.graphicsDevice = graphicsDevice;
Zoom = 1f;
Position.X = this.graphicsDevice.Viewport.Width * 0.5f;
Position.Y = this.graphicsDevice.Viewport.Height * 0.5f;
}
public void Update()
{
Rectangle bounds = graphicsDevice.Viewport.Bounds;
TransformMatrix =
Matrix.CreateTranslation(new Vector3(-Position.X, -Position.Y, 0)) *
Matrix.CreateScale(Zoom) *
Matrix.CreateTranslation(new Vector3(bounds.Width * 0.5f, bounds.Height * 0.5f, 0f));
}
public void LinearInterpolationToPosition(float alpha, Vector2 targetPosition, float delta)
{
if (alpha <= 0 && alpha > 1f) throw new ArgumentException("Alpha can't be greater than 1f, less than or equal to 0.");
Vector2 distance = targetPosition - Position;
distance *= (float)(1.0f - Math.Pow(1 - alpha, delta / 0.02f));
Position += distance;
}
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RhythmBullet.Utilities.ContentSystem
{
struct LoadableContent
{
internal Type type;
internal string assetName;
internal bool usePathModifier;
public LoadableContent(string assetName, Type type, bool usePathModifier)
{
this.type = type;
this.assetName = assetName;
this.usePathModifier = usePathModifier;
}
}
}

View File

@ -0,0 +1,157 @@
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace RhythmBullet.Utilities.ContentSystem
{
public class ContentManagerController
{
Thread thread;
readonly ContentManager contentManager;
readonly Queue<LoadableContent> queue;
Dictionary<string, IDisposable> assets;
public readonly Dictionary<Type, IContentPathModifier> contentPathModifier;
volatile float progress;
volatile bool running;
public ContentManagerController(ContentManager contentManager)
{
this.contentManager = contentManager;
assets = new Dictionary<string, IDisposable>();
queue = new Queue<LoadableContent>();
contentPathModifier = new Dictionary<Type, IContentPathModifier>();
}
private void Load(string assetName, Type type, bool usePathModifier)
{
string path = assetName;
if (usePathModifier)
{
IContentPathModifier handler = contentPathModifier[type];
path = handler.Modify(assetName);
}
assets.Add(assetName, contentManager.Load<IDisposable>(path));
Debug.WriteLine("Loaded asset: " + assetName);
}
public T Get<T>(string assetName)
{
lock (queue)
{
return (T)assets[assetName];
}
}
public void Queue<T>(string assetName, bool usePathModifier = true) where T : IDisposable
{
lock (queue)
{
if (!assets.ContainsKey(assetName))
{
queue.Enqueue(new LoadableContent(assetName, typeof(T), usePathModifier));
Debug.WriteLine("Queued asset: " + assetName);
}
else
{
throw new InvalidOperationException("Did not queue asset due to asset with same name being loaded: " + assetName);
}
}
}
/// <summary>
/// Called whenever a batch of assets should be loaded from the queue. Safe to call once every frame.
/// </summary>
public void Update()
{
if (queue.Count > 0 && (thread == null || !thread.IsAlive))
{
thread = new Thread(LoadBatch);
thread.Start();
}
}
private void LoadBatch()
{
running = true;
int totalTasks = queue.Count;
int tasksCompleted = 0;
while (queue.Count != 0)
{
lock (queue)
{
LoadableContent content = queue.Dequeue();
Load(content.assetName, content.type, content.usePathModifier);
tasksCompleted++;
progress = (float)tasksCompleted / totalTasks;
}
}
running = false;
}
/// <summary>
/// Removes the asset from the list of assets in the system.
/// Cannot remove from queue.
/// </summary>
/// <param name="name">the string name used to load the asset</param>
public void Remove(string name)
{
lock (queue)
{
if (assets.ContainsKey(name))
{
assets[name].Dispose();
assets.Remove(name);
}
}
}
/// <summary>
/// Clears the queue.
/// </summary>
public void ClearQueue()
{
lock (queue)
{
queue.Clear();
}
}
/// <summary>
/// Unloads everything from both queue and loaded list while properly disposing of the assets loaded.
/// </summary>
public void UnloadAll()
{
lock (queue)
{
contentManager.Unload();
assets.Clear();
ClearQueue();
Debug.WriteLine("Unloaded all assets.");
}
}
public bool Done
{
get
{
return !running && queue.Count == 0;
}
}
public float Progress
{
get
{
return progress;
}
}
}
}

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RhythmBullet.Utilities.ContentSystem
{
public interface IContentPathModifier
{
/// <summary>
/// Returns the complete path with the content folder as root.
/// </summary>
/// <param name="assetName">Is the asset's name</param>
/// <returns></returns>
string Modify(string assetName);
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RhythmBullet.Utilities.ContentSystem
{
class NormalContentResolver : IContentPathModifier
{
public string Modify(string assetName)
{
return assetName;
}
}
}

View File

@ -0,0 +1,160 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RhythmBullet.Utilities.DataTypes
{
public class NinePatch
{
public Color color;
readonly Texture2D texture;
readonly int a, b, c, d;
private Rectangle sourceRectangle;
private Rectangle drawnRectangle;
/// <summary>
/// A nine patch object.
/// </summary>
/// <param name="texture">Texture used for the nine patch. Dimensions must be greater than their sum border counter parts.</param>
/// <param name="a">Left side.</param>
/// <param name="b">Right side.</param>
/// <param name="c">Bottom side.</param>
/// <param name="d">Top side.</param>
public NinePatch(Texture2D texture, int a, int b, int c, int d)
{
this.texture = texture;
if (a + b >= texture.Width) throw new ArgumentOutOfRangeException("a and b cannot be greater than or equal to the width of texture.");
if (c + d >= texture.Height) throw new ArgumentOutOfRangeException("c and d cannot be greater or equal to the height of the texture.");
this.a = a;
this.b = b;
this.c = c;
this.d = d;
color = Color.White;
}
public void Draw(SpriteBatch batch, Rectangle destination)
{
//1x1
drawnRectangle.X = destination.X;
drawnRectangle.Y = destination.Y;
drawnRectangle.Width = a;
drawnRectangle.Height = c;
sourceRectangle.X = 0;
sourceRectangle.Y = 0;
sourceRectangle.Width = a;
sourceRectangle.Height = c;
batch.Draw(texture, drawnRectangle, sourceRectangle, color);
//2x1
drawnRectangle.X = destination.X + a;
drawnRectangle.Y = destination.Y;
drawnRectangle.Width = destination.Width - a - b;
drawnRectangle.Height = c;
sourceRectangle.X = a;
sourceRectangle.Y = 0;
sourceRectangle.Width = texture.Width - a - b;
sourceRectangle.Height = c;
batch.Draw(texture, drawnRectangle, sourceRectangle, color);
//3x1
drawnRectangle.X = destination.X + destination.Width - b;
drawnRectangle.Y = destination.Y;
drawnRectangle.Width = b;
drawnRectangle.Height = c;
sourceRectangle.X = texture.Width - b;
sourceRectangle.Y = 0;
sourceRectangle.Width = b;
sourceRectangle.Height = c;
batch.Draw(texture, drawnRectangle, sourceRectangle, color);
//1x2
drawnRectangle.X = destination.X;
drawnRectangle.Y = destination.Y + c;
drawnRectangle.Width = a;
drawnRectangle.Height = destination.Height - d - c;
sourceRectangle.X = 0;
sourceRectangle.Y = c;
sourceRectangle.Width = a;
sourceRectangle.Height = texture.Height - c - d;
batch.Draw(texture, drawnRectangle, sourceRectangle, color);
//2x2
drawnRectangle.X = destination.X + a;
drawnRectangle.Y = destination.Y + c;
drawnRectangle.Width = destination.Width - a - b;
drawnRectangle.Height = destination.Height - c - d;
sourceRectangle.X = a;
sourceRectangle.Y = c;
sourceRectangle.Width = texture.Width - a - b;
sourceRectangle.Height = texture.Height - c - d;
batch.Draw(texture, drawnRectangle, sourceRectangle, color);
//3x2
drawnRectangle.X = destination.X + destination.Width - b;
drawnRectangle.Y = destination.Y + c;
drawnRectangle.Width = b;
drawnRectangle.Height = destination.Height - c - d;
sourceRectangle.X = texture.Width - b;
sourceRectangle.Y = c;
sourceRectangle.Width = b;
sourceRectangle.Height = texture.Height - c - d;
batch.Draw(texture, drawnRectangle, sourceRectangle, color);
//1x3
drawnRectangle.X = destination.X;
drawnRectangle.Y = destination.Height - d;
drawnRectangle.Width = a;
drawnRectangle.Height = d;
sourceRectangle.X = a;
sourceRectangle.Y = texture.Height - d;
sourceRectangle.Width = a;
sourceRectangle.Height = d;
batch.Draw(texture, drawnRectangle, sourceRectangle, color);
//2x3
drawnRectangle.X = destination.X + a;
drawnRectangle.Y = destination.Height - d;
drawnRectangle.Width = destination.Width - a - b;
drawnRectangle.Height = d;
sourceRectangle.X = a;
sourceRectangle.Y = texture.Height - d;
sourceRectangle.Width = texture.Width - a - b;
sourceRectangle.Height = d;
batch.Draw(texture, drawnRectangle, sourceRectangle, color);
//3x3
drawnRectangle.X = destination.X + destination.Width - b;
drawnRectangle.Y = destination.Height - d;
drawnRectangle.Width = b;
drawnRectangle.Height = d;
sourceRectangle.X = texture.Width - b;
sourceRectangle.Y = texture.Height - d;
sourceRectangle.Width = b;
sourceRectangle.Height = d;
batch.Draw(texture, drawnRectangle, sourceRectangle, color);
}
}
}

View File

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RhythmBullet.Utilities.DataTypes
{
public class Resolution : IComparable<Resolution>
{
public int Width, Height;
public Resolution(int width, int height)
{
Width = width;
Height = height;
}
public Resolution()
{
}
public int CompareTo(Resolution other)
{
return Area() - other.Area();
}
public override string ToString()
{
return Width + "x" + Height;
}
public int Area()
{
return Width * Height;
}
}
}

View File

@ -0,0 +1,16 @@
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RhythmBullet.Utilities.Input
{
public interface IInputListener
{
bool KeyboardStateChanged(KeyboardState state);
bool MouseStateChanged(MouseState state);
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xna.Framework.Input;
namespace RhythmBullet.Utilities.Input
{
class InputListener : IInputListener
{
public bool KeyboardStateChanged(KeyboardState state)
{
return true;
}
public bool MouseStateChanged(MouseState state)
{
return true;
}
}
}

View File

@ -0,0 +1,73 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using RhythmBullet.Utilities.Input;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RhythmBullet.Utilities.Input
{
public static class InputUtilities
{
public static List<IInputListener> InputListeners;
static KeyboardState keyboardState;
static MouseState mouseState;
static InputUtilities()
{
InputListeners = new List<IInputListener>();
}
public static void Update()
{
KeyboardState updatedKeyboardState = Keyboard.GetState();
MouseState updatedMouseState = Mouse.GetState();
bool disableKeyboard = false;
bool disableMouse = false;
foreach (IInputListener inputListener in InputListeners)
{
if (!disableKeyboard && keyboardState != updatedKeyboardState)
{
disableKeyboard = inputListener.KeyboardStateChanged(updatedKeyboardState);
}
if (!disableMouse && mouseState != updatedMouseState)
{
disableMouse = inputListener.MouseStateChanged(updatedMouseState);
}
if (disableKeyboard && disableMouse)
{
break;
}
}
UpdateStates();
}
public static bool MouseClicked()
{
if (mouseState.LeftButton == ButtonState.Pressed)
{
return Mouse.GetState().LeftButton != ButtonState.Pressed;
}
return false;
}
private static void UpdateStates()
{
keyboardState = Keyboard.GetState();
mouseState = Mouse.GetState();
}
public static bool MouseWithinBoundries(Rectangle bounds)
{
MouseState mouseState = Mouse.GetState();
if (mouseState.X >= bounds.X && mouseState.X <= (bounds.X + bounds.Width) && mouseState.Y >= bounds.Y && mouseState.Y <= (bounds.Y + bounds.Height))
{
return true;
}
return false;
}
}
}

View File

@ -4,9 +4,9 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Recrowned_Athenaeum namespace RhythmBullet.Utilities.ParticleSystem
{ {
public class Class1 class Particle
{ {
} }
} }

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
namespace RhythmBullet.Utilities.Persistence
{
public class Preferences
{
}
}

View File

@ -0,0 +1,79 @@
using RhythmBullet.Preferences;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Xml.Serialization;
namespace RhythmBullet.Utilities.Persistence
{
public class PreferencesManager
{
private readonly Dictionary<Type, Preferences> preferenceList;
string savePath;
XmlSerializer xmlSerializer;
public PreferencesManager(string savePath, params Preferences[] preferences)
{
this.savePath = savePath;
Directory.CreateDirectory(savePath);
preferenceList = new Dictionary<Type, Preferences>();
Debug.WriteLine("Preference manager setting up...");
Type[] preferenceTypes = new Type[preferences.Length];
for (int prefID = 0; prefID < preferences.Length; prefID++)
{
preferenceList.Add(preferences[prefID].GetType(), preferences[prefID]);
preferenceTypes[prefID] = preferences[prefID].GetType();
Debug.WriteLine(preferences[prefID] + " added to list of preferences");
}
xmlSerializer = new XmlSerializer(typeof(Preferences), preferenceTypes);
}
public T GetPreferences<T>() where T : Preferences
{
return (T)preferenceList[typeof(T)];
}
public void Load()
{
List<Type> keys = new List<Type>(preferenceList.Keys);
foreach (Type key in keys)
{
if (!LoadSpecific(key))
{
SaveSpecific(key);
}
}
}
public void Save()
{
foreach (KeyValuePair<Type, Preferences> prefs in preferenceList)
{
SaveSpecific(prefs.Key);
}
}
private bool LoadSpecific(Type preference)
{
string path = savePath + "/" + preference.Name + ".xml";
if (File.Exists(path))
{
Stream stream = new FileStream(path, FileMode.Open);
preferenceList[preference] = (Preferences)xmlSerializer.Deserialize(stream);
stream.Close();
return true;
}
return false;
}
private void SaveSpecific(Type preference)
{
Stream stream = new FileStream(savePath + "/" + preference.Name + ".xml", FileMode.Create);
xmlSerializer.Serialize(stream, preferenceList[preference]);
stream.Close();
}
}
}

View File

@ -31,6 +31,7 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="MonoGame.Framework, Version=3.6.0.1625, Culture=neutral, processorArchitecture=MSIL" />
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
@ -41,8 +42,46 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Class1.cs" /> <Compile Include="Camera\Camera2D.cs" />
<Compile Include="ContentSystem\ContentLoad.cs" />
<Compile Include="ContentSystem\ContentManagerController.cs" />
<Compile Include="ContentSystem\IContentPathModifier.cs" />
<Compile Include="ContentSystem\NormalContentResolver.cs" />
<Compile Include="DataTypes\NinePatch.cs" />
<Compile Include="DataTypes\Resolution.cs" />
<Compile Include="Input\IInputListener.cs" />
<Compile Include="Input\InputListener.cs" />
<Compile Include="Input\InputUtilities.cs" />
<Compile Include="obj\Debug\TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs" />
<Compile Include="obj\Debug\TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs" />
<Compile Include="obj\Debug\TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs" />
<Compile Include="ParticleSystem\Particle.cs" />
<Compile Include="Persistence\Preferences.cs" />
<Compile Include="Persistence\PreferencesManager.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ScreenSystem\ITransition.cs" />
<Compile Include="ScreenSystem\LoadingScreen.cs" />
<Compile Include="ScreenSystem\Screen.cs" />
<Compile Include="ScreenSystem\ScreenManager.cs" />
<Compile Include="UI\Book\Book.cs" />
<Compile Include="UI\Book\Page.cs" />
<Compile Include="UI\Modular\Modules\Image.cs" />
<Compile Include="UI\Modular\Modules\Interactive\Button.cs" />
<Compile Include="UI\Modular\Modules\Interactive\TextButton.cs" />
<Compile Include="UI\Modular\Modules\TextLabel.cs" />
<Compile Include="UI\Modular\UIModule.cs" />
<Compile Include="UI\Modular\UIModuleGroup.cs" />
</ItemGroup>
<ItemGroup>
<None Include="obj\Debug\DesignTimeResolveAssemblyReferencesInput.cache" />
<None Include="obj\Debug\Recrowned-Athenaeum.csproj.CoreCompileInputs.cache" />
<None Include="obj\Debug\Recrowned-Athenaeum.csprojAssemblyReference.cache" />
<None Include="obj\Release\Recrowned-Athenaeum.csproj.CoreCompileInputs.cache" />
</ItemGroup>
<ItemGroup>
<Folder Include="bin\Debug\" />
<Folder Include="bin\Release\" />
<Folder Include="obj\Debug\TempPE\" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>

View File

@ -0,0 +1,48 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RhythmBullet.Utilities.ScreenSystem
{
public interface ITransition
{
/// <summary>
/// Called once when the transition is needed.
/// <param name="Dimensions">The dimensions of the screen.</param>
/// </summary>
void InitiateTransition(Rectangle dimensions);
/// <summary>
/// Called every frame if the state of the screen this transition is placed upon is in the enter transition phase.
/// </summary>
/// <param name="delta">The time passed in seconds since the last frame.</param>
/// <param name="assetsLoaded">Whether or not if all assets have been loaded by the <see cref="ContentSystem"/>.</param>
/// <param name="previousScreenExitFrame">The frame being rendered by the screen as it exits. This can be null.</param>
/// <returns>If this returns true, then it is considered that this transition is complete.</returns>
bool EnteringTransition(double delta, bool assetsLoaded);
/// <summary>
/// Called every frame if the state of the screen this transition is placed upon is in the exit phase.
/// </summary>
/// <param name="delta">The time passed in seconds since the last frame.</param>
/// <param name="assetsLoaded">Whether or not if all assets have been loaded by the <see cref="ContentSystem"/>.</param>
/// <returns>If this returns true, then it is considered that this transition is complete.</returns>
bool ExitingTransition(double delta, bool assetsLoaded);
/// <summary>
/// Called once every frame while transition is active. Meant to draw transition.
/// </summary>
/// <param name="spriteBatch"></param>
void DrawTransition(SpriteBatch spriteBatch);
/// <summary>
/// Updates if the previous screen uses a render target for exit transition.
/// </summary>
/// <param name="previousScreenFrame">The frame of the previous screen.</param>
void UpdatePreviousScreenFrame(RenderTarget2D previousScreenFrame);
}
}

View File

@ -0,0 +1,136 @@

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Diagnostics;
namespace RhythmBullet.Utilities.ScreenSystem
{
public class LoadingScreen : Screen, ITransition
{
private const float ENTER_TIME = 2f;
private const float EXIT_TIME = 1f;
Game game;
readonly Texture2D texture;
Color color;
Rectangle textureBounds;
readonly float proportion;
bool recorded;
float rR, rG, rB;
float progR, progG, progB;
float progC = 254;
float rotation;
readonly bool rotate;
Vector2 origin;
public LoadingScreen(Game game, Texture2D screenImage, float proportion, bool rotate = false) : base(true)
{
this.game = game;
this.texture = screenImage;
this.proportion = proportion;
this.rotate = rotate;
Transitions.Add(this);
}
public override void Show()
{
game.IsMouseVisible = false;
base.Show();
}
public override void Hide()
{
game.IsMouseVisible = true;
base.Hide();
}
public void InitiateTransition(Rectangle dimensions)
{
color = Color.White;
textureBounds.Width = (int)(ScreenSize.Height * proportion);
textureBounds.Height = (int)(ScreenSize.Height * proportion);
textureBounds.X = (ScreenSize.Width) / 2;
textureBounds.Y = (ScreenSize.Height) / 2;
origin.X = texture.Width / 2;
origin.Y = texture.Height / 2;
}
void DoRotate(float deltaf)
{
rotation += (2f / 3f) * (float)Math.PI * deltaf;
if (rotation >= 2 * Math.PI)
{
rotation = 0;
}
}
public void DrawTransition(SpriteBatch spriteBatch)
{
spriteBatch.Draw(texture, textureBounds, null, color, rotation, origin, SpriteEffects.None, 0f);
}
public bool EnteringTransition(double delta, bool assetsLoaded)
{
float deltaf = (float)delta;
if (rotate)
{
DoRotate(deltaf);
}
if (assetsLoaded)
{
if (progR < 254 || progG < 254 || progB < 254)
{
if (!recorded)
{
rR = (Color.White.R - BackgroundColor.R) / ENTER_TIME;
rG = (Color.White.G - BackgroundColor.G) / ENTER_TIME;
rB = (Color.White.B - BackgroundColor.B) / ENTER_TIME;
recorded = true;
}
progR += rR * deltaf;
progR = Math.Min(progR, 254);
progG += rG * deltaf;
progG = Math.Min(progG, 254);
progB += rB * deltaf;
progB = Math.Min(progB, 254);
BackgroundColor.R = (byte)progR;
BackgroundColor.G = (byte)progG;
BackgroundColor.B = (byte)progB;
}
else
{
StartExitTransition(true);
return true;
}
}
return false;
}
public bool ExitingTransition(double delta, bool assetsLoaded)
{
float deltaf = (float)delta;
if (rotate)
{
DoRotate(deltaf);
}
if (progC > 0)
{
float rate = deltaf * 255 / EXIT_TIME;
progC -= rate;
progC = Math.Max(progC, 0);
color.R = (byte)progC;
color.G = (byte)progC;
color.B = (byte)progC;
color.A = (byte)progC;
}
else
{
return true;
}
return false;
}
}
}

View File

@ -0,0 +1,146 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using RhythmBullet.Utilities.Camera;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RhythmBullet.Utilities.ScreenSystem
{
public enum ScreenState { EnterTransition, ExitTransition, Normal }
public class Screen
{
public List<ITransition> Transitions;
public bool UseRenderTargetForExitTransition;
public Color BackgroundColor;
public GraphicsDevice GraphicsDevice { get; private set; }
public Screen NextScreen { get; set; }
public Rectangle ScreenSize { get; private set; }
public bool Initiated { get; private set; }
public Camera2D Camera { get; private set; }
public Screen(bool useEnterTransition = false)
{
State = useEnterTransition ? ScreenState.EnterTransition : ScreenState.Normal;
Transitions = new List<ITransition>();
}
/// <summary>
/// Called only once after initialization to give required parameters.
/// </summary>
/// <param name="graphicsDevice"></param>
/// <param name="screenSize"></param>
/// <param name="camera"></param>
public virtual void Initiate(GraphicsDevice graphicsDevice, Rectangle screenSize, Camera2D camera)
{
this.Camera = camera;
this.ScreenSize = screenSize;
GraphicsDevice = graphicsDevice;
Initiated = true;
}
public virtual void Update(GameTime gameTime)
{
}
public virtual void Draw(SpriteBatch spriteBatch)
{
foreach (ITransition transition in Transitions)
{
transition.DrawTransition(spriteBatch);
}
}
/// <summary>
/// Updates the transition.
/// </summary>
/// <param name="delta">Time passed since last frame in seconds.</param>
/// <param name="assetsLoaded">If waiting on assets.</param>
/// <param name="previousScreenExitFrame">The previous screen's frame. May be null.</param>
/// <returns>Only returns true if exit transition is complete. Returns false otherwise.</returns>
public bool UpdateTransition(double delta, bool assetsLoaded)
{
switch (State)
{
case ScreenState.EnterTransition:
EnteringTransition(delta, assetsLoaded);
break;
case ScreenState.ExitTransition:
return ExitingTransition(delta, assetsLoaded);
}
return false;
}
private void EnteringTransition(double delta, bool assetsLoaded)
{
bool complete = true;
for (int transitionID = 0; transitionID < Transitions.Count; transitionID++)
{
ITransition transition = Transitions[transitionID];
if (!transition.EnteringTransition(delta, assetsLoaded))
{
complete = false;
}
}
if (complete && State != ScreenState.ExitTransition)
{
State = ScreenState.Normal;
}
}
private bool ExitingTransition(double delta, bool assetsLoaded)
{
bool complete = true;
foreach (ITransition transition in Transitions)
{
if (!transition.ExitingTransition(delta, assetsLoaded))
{
complete = false;
}
}
return complete;
}
/// <summary>
/// Called when the screen is shown.
/// </summary>
public virtual void Show()
{
foreach (ITransition transition in Transitions)
{
transition.InitiateTransition(ScreenSize);
}
}
public virtual void Hide()
{
}
public virtual void AssetLoadStateChange(bool state)
{
}
public ScreenState State { get; private set; }
public void StartExitTransition(bool UseRenderTargetForExitTransition = false)
{
this.UseRenderTargetForExitTransition = UseRenderTargetForExitTransition;
State = ScreenState.ExitTransition;
}
public virtual void UpdatePreviousScreenFrame(RenderTarget2D previousScreenFrame)
{
foreach (ITransition transition in Transitions)
{
transition.UpdatePreviousScreenFrame(previousScreenFrame);
}
}
}
}

View File

@ -0,0 +1,137 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using RhythmBullet.Utilities.Camera;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RhythmBullet.Utilities.ScreenSystem
{
public delegate void FirstScreenChange(Screen screen);
public class ScreenManager : IDisposable
{
public event FirstScreenChange RequireNextScreenEvent;
private GraphicsDeviceManager graphics;
private Screen previousScreen;
private RenderTarget2D previousScreenRenderTarget;
private Screen currentScreen;
private Camera2D camera;
private bool firstScreenChangeComplete;
public Screen Screen
{
get
{
return currentScreen;
}
set
{
previousScreen = currentScreen;
currentScreen = value;
if (!Screen.Initiated)
{
Screen.Initiate(graphics.GraphicsDevice, new Rectangle(0, 0, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight), camera);
}
if (previousScreen != null && previousScreenRenderTarget == null && previousScreen.UseRenderTargetForExitTransition)
{
previousScreenRenderTarget = new RenderTarget2D(graphics.GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight);
}
graphics.GraphicsDevice.SetRenderTarget(previousScreenRenderTarget);
graphics.GraphicsDevice.Clear(Color.Black);
Debug.WriteLine("Showing " + value.GetType().Name);
Screen.Show();
previousScreen?.Hide();
}
}
public ScreenManager(GraphicsDeviceManager graphicsDeviceManager, Camera2D camera)
{
this.graphics = graphicsDeviceManager;
this.camera = camera;
}
public void UpdateCurrentScreen(GameTime gameTime, bool assetsDone)
{
switch (Screen.State)
{
case ScreenState.EnterTransition:
if (previousScreen != null && previousScreen.UseRenderTargetForExitTransition)
{
previousScreen.UpdateTransition(gameTime.ElapsedGameTime.TotalSeconds, assetsDone);
}
Screen.UpdateTransition(gameTime.ElapsedGameTime.TotalSeconds, assetsDone);
break;
case ScreenState.ExitTransition:
if (Screen.UseRenderTargetForExitTransition || Screen.UpdateTransition(gameTime.ElapsedGameTime.TotalSeconds, assetsDone))
{
if (!firstScreenChangeComplete)
{
firstScreenChangeComplete = true;
OnFirstScreenChange(Screen);
}
if (Screen.NextScreen != null)
{
Debug.WriteLine("Changing to the next given screen.");
Screen = Screen.NextScreen;
}
else if (previousScreen != null)
{
Debug.WriteLine("Changing to previous screen.");
Screen = previousScreen;
}
else
{
throw new InvalidOperationException("No next screen provided and no previous screen to return to.");
}
}
break;
}
Screen.Update(gameTime);
}
public void DrawCurrentScreen(SpriteBatch spriteBatch)
{
graphics.GraphicsDevice.Clear(Screen.BackgroundColor);
if (Screen.State == ScreenState.EnterTransition && previousScreen != null && previousScreen.UseRenderTargetForExitTransition)
{
graphics.GraphicsDevice.SetRenderTarget(previousScreenRenderTarget);
graphics.GraphicsDevice.Clear(previousScreen.BackgroundColor);
spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null, null, camera.TransformMatrix);
previousScreen.Draw(spriteBatch);
spriteBatch.End();
graphics.GraphicsDevice.SetRenderTarget(null);
Screen.UpdatePreviousScreenFrame(previousScreenRenderTarget);
}
spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null, null, camera.TransformMatrix);
Screen.Draw(spriteBatch);
spriteBatch.End();
}
public void Resize(LoadingScreen loadingScreen)
{
Screen.AssetLoadStateChange(true);
Screen = loadingScreen;
previousScreenRenderTarget.Dispose();
previousScreenRenderTarget = null;
}
public void PostResize()
{
Screen.AssetLoadStateChange(false);
}
public void OnFirstScreenChange(Screen screen)
{
RequireNextScreenEvent?.Invoke(screen);
}
public void Dispose()
{
previousScreenRenderTarget?.Dispose();
}
}
}

View File

@ -0,0 +1,93 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using RhythmBullet.Utilities.Camera;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RhythmBullet.UI.Book
{
class Book
{
Camera2D camera;
Page targetPage;
Rectangle dimensions;
Dictionary<string, Page> pages = new Dictionary<string, Page>();
/// <summary>
/// Should be called whenever a valid camera and dimensions for the book exist.
/// Initializes book with given parameters.
/// </summary>
/// <param name="camera">Camera game is currently using.</param>
/// <param name="dimensions">Dimensions of the book and the dimensions the pages will use.</param>
public void Initiate(Camera2D camera, Rectangle dimensions)
{
this.camera = camera;
this.dimensions = dimensions;
}
public void Draw(SpriteBatch batch)
{
for (int pageIndex = 0; pageIndex < pages.Count; pageIndex++)
{
Page page = pages.ElementAt(pageIndex).Value;
page.Draw(batch);
}
}
public void Update(GameTime gameTime)
{
if (targetPage != null)
{
Vector2 position;
Rectangle targetBounds = targetPage.bounds;
position.X = targetBounds.X + (targetBounds.Width * 0.5f);
position.Y = targetBounds.Y + (targetBounds.Height * 0.5f);
camera.LinearInterpolationToPosition(0.4f, position, (float)gameTime.ElapsedGameTime.TotalSeconds);
if (camera.Position == position)
{
targetPage = null;
}
}
for (int pageIndex = 0; pageIndex < pages.Count; pageIndex++)
{
Page page = pages.ElementAt(pageIndex).Value;
if (page.NeedsSizeUpdate) page.ApplySize(dimensions.Width, dimensions.Height);
page.Update(gameTime);
}
}
public void AddPages(params Page[] pages)
{
foreach (Page page in pages)
{
this.pages.Add(page.Name, page);
}
}
public void RemovePage(Page page)
{
RemovePage(page.Name);
}
public void RemovePage(string name)
{
pages.Remove(name);
}
public void LerpToPage(Page page)
{
targetPage = page;
}
public void GoToPage(Page page)
{
Rectangle targetBounds = page.bounds;
camera.Position.X = targetBounds.X + (targetBounds.Width * 0.5f);
camera.Position.Y = targetBounds.Y + (targetBounds.Height * 0.5f);
}
}
}

View File

@ -0,0 +1,35 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using RhythmBullet.UI.Modular;
using RhythmBullet.Utilities.Camera;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RhythmBullet.UI.Book
{
class Page : UIModuleGroup
{
private readonly int pageX, pageY;
public bool NeedsSizeUpdate;
public Page(int pageX, int pageY) : base(false)
{
this.pageX = pageX;
this.pageY = pageY;
NeedsSizeUpdate = true;
Name = ToString();
}
public virtual void ApplySize(int width, int height)
{
bounds.X = pageX * width;
bounds.Y = pageY * height;
bounds.Width = width;
bounds.Height = height;
NeedsSizeUpdate = false;
}
}
}

View File

@ -0,0 +1,63 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using RhythmBullet.UI.Modular;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RhythmBullet.Utilities.UI.Modular.Modules
{
class Image : UIModule
{
public float rotation = 0f;
public Texture2D Texture { get; set; }
public float ScaleX
{
get
{
return (float)bounds.Width / Texture.Width;
}
set
{
bounds.Width = (int)(Texture.Width * value);
}
}
public float ScaleY
{
get
{
return (float)bounds.Height / Texture.Height;
}
set
{
bounds.Height = (int)(Texture.Height * value);
}
}
public float Scale
{
set
{
bounds.Height = (int)(Texture.Height * value);
bounds.Width = (int)(Texture.Width * value);
}
}
public Image(Texture2D texture)
{
Texture = texture ?? throw new ArgumentException("Image requires a texture.");
bounds = texture.Bounds;
}
public override void Draw(SpriteBatch batch)
{
batch.Draw(Texture, bounds, null, color, rotation, origin, SpriteEffects.None, 0f);
base.Draw(batch);
}
}
}

View File

@ -0,0 +1,62 @@
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using RhythmBullet.UI.Modular;
using RhythmBullet.Utilities.DataTypes;
using RhythmBullet.Utilities.Input;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RhythmBullet.Utilities.UI.Modular.Modules.Interactive
{
public delegate bool Clicked();
public class Button : UIModule
{
private NinePatch background;
public event Clicked Listeners;
public Button(NinePatch background = null)
{
this.background = background;
}
public override void Draw(SpriteBatch batch)
{
background?.Draw(batch, bounds);
base.Draw(batch);
}
public sealed override bool MouseStateChanged(MouseState state)
{
if (InputUtilities.MouseWithinBoundries(bounds))
{
if (InputUtilities.MouseClicked())
{
OnClick();
}
Highlighted = true;
}
else
{
Highlighted = false;
}
return base.MouseStateChanged(state);
}
public sealed override bool KeyboardStateChanged(KeyboardState state)
{
return base.KeyboardStateChanged(state);
}
protected void OnClick()
{
Listeners?.Invoke();
}
public bool Highlighted { get; private set; }
}
}

View File

@ -0,0 +1,37 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using RhythmBullet.UI.Modular.Modules;
using RhythmBullet.Utilities.DataTypes;
using RhythmBullet.Utilities.UI.Modular.Modules.Interactive;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RhythmBullet.Utilities.UI.Modular.Modules.Interactive
{
internal class TextButton : Button
{
private TextLabel label;
internal TextButton(string text, SpriteFont font, NinePatch background) : base(background)
{
label = new TextLabel(font, text);
label.autoScale = true;
}
public override void Update(GameTime gameTime)
{
label.bounds = bounds;
label.Update(gameTime);
base.Update(gameTime);
}
public override void Draw(SpriteBatch batch)
{
label.Draw(batch);
base.Draw(batch);
}
}
}

View File

@ -0,0 +1,146 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using RhythmBullet.Utilities.Camera;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RhythmBullet.UI.Modular.Modules
{
public class TextLabel : UIModule
{
private SpriteFont font;
private float scale;
private Vector2 position;
private string originalText;
private string displayedText;
private Vector2 modifiedTextSize;
public bool autoWrap;
public bool autoScale;
public bool useEllipses;
public string ellipsis = "...";
private string ModifiedText
{
get
{
return displayedText;
}
set
{
displayedText = value;
modifiedTextSize = font.MeasureString(value);
}
}
public string Text
{
get
{
return originalText;
}
set
{
if (value == null) value = "...";
modifiedTextSize = font.MeasureString(value);
originalText = value;
displayedText = value;
}
}
public TextLabel(SpriteFont font, string text = null)
{
Text = text;
this.font = font;
}
public override void Update(GameTime gameTime)
{
position.X = bounds.X;
position.Y = bounds.Y;
if (useEllipses) AttemptToApplyEllipsis();
if (autoWrap) AttemptToWrapText();
if (autoScale) AttemptToScaleFont();
base.Update(gameTime);
}
public override void Draw(SpriteBatch batch)
{
batch.DrawString(font, Text, position, color, 0f, origin, scale, SpriteEffects.None, 0f);
base.Draw(batch);
}
public void AttemptToApplyEllipsis()
{
if (modifiedTextSize.X * scale > bounds.Width && ModifiedText.Length > ellipsis.Length + 1)
{
RemoveLineBreaks();
StringBuilder stringBuilder = new StringBuilder(ModifiedText);
do
{
stringBuilder.Remove(stringBuilder.Length, ellipsis.Length - 1);
stringBuilder.Insert(stringBuilder.Length, ellipsis);
}
while (font.MeasureString(stringBuilder).X * scale > bounds.Width);
ModifiedText = stringBuilder.ToString();
}
}
public void AttemptToScaleFont()
{
if (modifiedTextSize.X * scale > bounds.Width || modifiedTextSize.X * scale < bounds.Width - 5)
{
scale = bounds.Width / modifiedTextSize.X;
}
if (modifiedTextSize.Y * scale > bounds.Height || modifiedTextSize.Y * scale < bounds.Height - 5)
{
scale = bounds.Height / modifiedTextSize.Y;
}
}
public void RemoveLineBreaks()
{
ModifiedText = ModifiedText.Replace("\n", " ");
}
public void ResetToOriginalText()
{
ModifiedText = originalText;
}
public void AttemptToWrapText(bool unwrap = false)
{
if (unwrap) RemoveLineBreaks();
if (modifiedTextSize.X * scale > bounds.Width)
{
ModifiedText = ModifiedText.Replace("\n", " ");
string[] words = ModifiedText.Split(' ');
StringBuilder stringBuilder = new StringBuilder();
float currentScaledLineWidth = 0f;
for (int w = 0; w < words.Length; w++)
{
string word = words[w];
float scaledWidth = font.MeasureString(word).X * scale;
if (currentScaledLineWidth + scaledWidth <= bounds.Width)
{
stringBuilder.Append(word);
currentScaledLineWidth += scaledWidth;
}
else
{
stringBuilder.AppendLine();
currentScaledLineWidth = 0;
}
}
ModifiedText = stringBuilder.ToString();
}
}
}
}

View File

@ -0,0 +1,74 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using RhythmBullet.Utilities.Camera;
using RhythmBullet.Utilities.Input;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RhythmBullet.UI.Modular
{
public class UIModule : IInputListener
{
public Rectangle bounds;
public Vector2 origin;
public UIModuleGroup Parent;
public string Name;
public Color color = Color.White;
public virtual void Update(GameTime gameTime)
{
}
public virtual void Draw(SpriteBatch batch)
{
}
public Rectangle ConvertToParentCoordinates(Rectangle bounds)
{
if (Parent != null)
{
Rectangle parentHitbox = Parent.ConvertToParentCoordinates(bounds);
int tX = bounds.X + parentHitbox.X;
int tY = bounds.Y + parentHitbox.Y;
return new Rectangle(tX, tY, bounds.Width, bounds.Height);
} else
{
return bounds;
}
}
public void RemoveFromParent()
{
if (Parent == null)
{
throw new InvalidOperationException("Parent is null.");
}
Parent.RemoveModule(this);
}
/// <summary>
/// Called whenever the keyboard state is changed.
/// </summary>
/// <param name="state">The current keyboard state.</param>
/// <returns>Returning whether or not to continue to call the next listener.</returns>
public virtual bool KeyboardStateChanged(KeyboardState state)
{
return true;
}
/// <summary>
/// Called whenever the state of the mouse changes. This includes movement.
/// </summary>
/// <param name="state">The current state of the mouse.</param>
/// <returns>Returning whether or not to continue to call the next listener.</returns>
public virtual bool MouseStateChanged(MouseState state)
{
return true;
}
}
}

View File

@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using RhythmBullet.Utilities.Camera;
using RhythmBullet.Utilities.Input;
namespace RhythmBullet.UI.Modular
{
public class UIModuleGroup : UIModule
{
List<UIModule> modules = new List<UIModule>();
Rectangle scissorBounds;
RasterizerState scissorRasterizer = new RasterizerState();
public Camera2D Camera { get; set; }
public UIModuleGroup(bool crop = false, Camera2D camera = null)
{
Camera = camera;
if (crop)
{
scissorRasterizer.ScissorTestEnable = true;
scissorBounds = new Rectangle();
}
}
public override void Draw(SpriteBatch batch)
{
if (scissorBounds != null)
{
batch.End();
batch.Begin(SpriteSortMode.Deferred, null, null, null, scissorRasterizer, null, Camera?.TransformMatrix);
scissorBounds.Width = bounds.Width;
scissorBounds.Height = bounds.Height;
scissorBounds.X = bounds.X;
scissorBounds.Y = bounds.Y;
Rectangle scissor = scissorBounds;
scissorBounds = batch.GraphicsDevice.ScissorRectangle;
batch.GraphicsDevice.ScissorRectangle = scissor;
}
foreach (UIModule module in modules)
{
int offsetX = module.bounds.X;
int offsetY = module.bounds.Y;
module.bounds.X = bounds.X + offsetX - (int)module.origin.X;
module.bounds.Y = bounds.Y + offsetY - (int)module.origin.Y;
module.Draw(batch);
module.bounds.X = offsetX;
module.bounds.Y = offsetY;
}
if (scissorBounds != null)
{
batch.GraphicsDevice.ScissorRectangle = scissorBounds;
batch.End();
batch.Begin(SpriteSortMode.Deferred, null, null, null, null, null, Camera?.TransformMatrix);
}
}
public override void Update(GameTime gameTime)
{
foreach (UIModule module in modules)
{
module.Update(gameTime);
}
}
public void AddModule(params UIModule[] addModules)
{
foreach (UIModule module in addModules)
{
if (modules.Contains(module))
{
throw new InvalidOperationException(module.ToString() + " already exists in " + this.ToString());
}
module.Parent = this;
modules.Add(module);
}
}
public void RemoveModule(UIModule module)
{
module.Parent = null;
modules.Remove(module);
}
public override bool KeyboardStateChanged(KeyboardState state)
{
foreach (UIModule module in modules)
{
module.KeyboardStateChanged(state);
}
return base.KeyboardStateChanged(state);
}
public override bool MouseStateChanged(MouseState state)
{
foreach (UIModule module in modules)
{
module.MouseStateChanged(state);
}
return base.MouseStateChanged(state);
}
}
}