diff --git a/ThemeParkClicker/Attractions/Attraction.cs b/ThemeParkClicker/Attractions/Attraction.cs index e2813cc..11530d6 100644 --- a/ThemeParkClicker/Attractions/Attraction.cs +++ b/ThemeParkClicker/Attractions/Attraction.cs @@ -14,12 +14,12 @@ namespace ThemeParkClicker.Attractions { static Attraction() { Attractions.Add("Carousel", () => new Attraction(new[,] {{true}}, Texture[0, 0, 1, 1], 0.5F, 50)); - Attractions.Add("FoodCourt", () => new Attraction(new[,] {{true, true}}, Texture[1, 0, 2, 1], 0.7F, 300)); + Attractions.Add("FoodCourt", () => new Attraction(new[,] {{true, true}}, Texture[1, 0, 2, 1], 1.1F, 300)); } private readonly bool[,] area; - public int Width => this.area.GetLength(0); - public int Height => this.area.GetLength(1); + public int Width => this.area.GetLength(1); + public int Height => this.area.GetLength(0); public readonly TextureRegion TextureRegion; public readonly float GenerationPerSecond; public readonly long InitialPrice; @@ -32,8 +32,13 @@ namespace ThemeParkClicker.Attractions { this.InitialPrice = initialPrice; } - public bool HasTileAt(int x, int y) { - return this.area[y, x]; + public IEnumerable GetCoveredTiles() { + for (var x = 0; x < this.Width; x++) { + for (var y = 0; y < this.Height; y++) { + if (this.area[y, x]) + yield return new Point(x, y); + } + } } public delegate Attraction Constructor(); diff --git a/ThemeParkClicker/Content/Textures/Attractions.aseprite b/ThemeParkClicker/Content/Textures/Attractions.aseprite index d45f860..00e2ffe 100644 Binary files a/ThemeParkClicker/Content/Textures/Attractions.aseprite and b/ThemeParkClicker/Content/Textures/Attractions.aseprite differ diff --git a/ThemeParkClicker/Content/Textures/Attractions.png b/ThemeParkClicker/Content/Textures/Attractions.png index 876ed72..0a05d4a 100644 Binary files a/ThemeParkClicker/Content/Textures/Attractions.png and b/ThemeParkClicker/Content/Textures/Attractions.png differ diff --git a/ThemeParkClicker/GameImpl.cs b/ThemeParkClicker/GameImpl.cs index 3e683fe..acb90e2 100644 --- a/ThemeParkClicker/GameImpl.cs +++ b/ThemeParkClicker/GameImpl.cs @@ -11,12 +11,25 @@ namespace ThemeParkClicker { public class GameImpl : MlemGame { public static GameImpl Instance { get; private set; } - public BigInteger Tickets = 200; + private BigInteger tickets; + public BigInteger Tickets { + get => this.tickets; + set { + var diff = value - this.tickets; + if (diff > 0) + this.ticketsPerSecondCounter += (long) diff; + this.tickets = value; + } + } public ParkMap Map { get; private set; } public Camera Camera { get; private set; } public Ui Ui { get; private set; } public bool DrawMap; + public float TicketsPerSecond { get; private set; } + private long ticketsPerSecondCounter; + private double secondCounter; + public GameImpl() { Instance = this; } @@ -26,17 +39,20 @@ namespace ThemeParkClicker { this.Ui = new Ui(this.UiSystem); this.Map = new ParkMap(10, 10); this.Camera = new Camera(this.GraphicsDevice) { - Scale = 16, + Scale = 5, AutoScaleWithScreen = true, AutoScaleReferenceSize = new Point(720, 1280), MaxScale = 24, - MinScale = 4 + MinScale = 3 }; + #if DEBUG + this.Tickets = 1000; this.Map.Place(Point.Zero, Attraction.Attractions["Carousel"]()); this.Map.Place(new Point(1, 0), Attraction.Attractions["Carousel"]()); this.Map.Place(new Point(3, 0), Attraction.Attractions["Carousel"]()); this.Map.Place(new Point(1, 2), Attraction.Attractions["FoodCourt"]()); + #endif } public string DisplayTicketCount() { @@ -47,13 +63,21 @@ namespace ThemeParkClicker { base.DoUpdate(gameTime); this.Map.Update(gameTime); this.Ui.Update(gameTime); + + // we average tickets per second over two seconds + this.secondCounter += gameTime.ElapsedGameTime.TotalSeconds; + if (this.secondCounter >= 2) { + this.secondCounter -= 2; + this.TicketsPerSecond = this.ticketsPerSecondCounter / 2F; + this.ticketsPerSecondCounter = 0; + } } protected override void DoDraw(GameTime gameTime) { this.GraphicsDevice.Clear(Color.Black); if (this.DrawMap) { this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, transformMatrix: this.Camera.ViewMatrix); - this.Map.Draw(gameTime, this.SpriteBatch, Vector2.Zero, 1); + this.Map.Draw(gameTime, this.SpriteBatch, Vector2.Zero, 1, 1); this.SpriteBatch.End(); } base.DoDraw(gameTime); diff --git a/ThemeParkClicker/ParkMap.cs b/ThemeParkClicker/ParkMap.cs index d2d618c..06f83c1 100644 --- a/ThemeParkClicker/ParkMap.cs +++ b/ThemeParkClicker/ParkMap.cs @@ -19,6 +19,10 @@ namespace ThemeParkClicker { private readonly Attraction[,] grid; private readonly Dictionary attractions = new Dictionary(); + public Attraction PlacingAttraction; + public Point PlacingPosition; + private bool draggingAttraction; + public ParkMap(int width, int height) { this.Width = width; this.Height = height; @@ -43,35 +47,55 @@ namespace ThemeParkClicker { camera.Zoom(-pinch.Delta.Length() / camera.Scale); } } else if (MlemGame.Input.GetGesture(GestureType.FreeDrag, out var drag)) { - camera.Position -= drag.Delta / camera.Scale; + if (this.draggingAttraction) { + // move the current placing position + var nextPos = (camera.ToWorldPos(drag.Position + drag.Delta) / Attraction.TileSize).ToPoint(); + if (nextPos.X >= 0 && nextPos.Y >= 0 && nextPos.X < this.Width && nextPos.Y < this.Height) + this.PlacingPosition = nextPos; + } else { + // move the camera + camera.Position -= drag.Delta / camera.Scale; + } + } else if (this.PlacingAttraction != null) { + foreach (var touch in MlemGame.Input.TouchState) { + if (touch.State != TouchLocationState.Pressed) + continue; + // when first pressing down, go into attraction drag mode if we're touching the place location + var offset = (camera.ToWorldPos(touch.Position) / Attraction.TileSize).ToPoint(); + this.draggingAttraction = this.PlacingAttraction.GetCoveredTiles() + .Any(p => this.PlacingPosition + p == offset); + } } } } - public void Draw(GameTime time, SpriteBatch batch, Vector2 position, float scale) { + public void Draw(GameTime time, SpriteBatch batch, Vector2 position, float scale, float alpha) { var tileSize = Attraction.TileSize * scale; // draw ground - batch.Draw(batch.GetBlankTexture(), new RectangleF(position, new Vector2(this.Width, this.Height) * tileSize), ColorExtensions.FromHex(0xff53a662)); + batch.Draw(batch.GetBlankTexture(), new RectangleF(position, new Vector2(this.Width, this.Height) * tileSize), ColorExtensions.FromHex(0xff53a662) * alpha); // draw attractions foreach (var kv in this.attractions) - batch.Draw(kv.Value.TextureRegion, position + kv.Key.ToVector2() * tileSize, Color.White); + batch.Draw(kv.Value.TextureRegion, position + kv.Key.ToVector2() * tileSize, Color.White * alpha, 0, Vector2.Zero, scale, SpriteEffects.None, 0); + // placing attraction + if (this.PlacingAttraction != null) { + var placingPos = position + this.PlacingPosition.ToVector2() * tileSize; + foreach (var pos in this.PlacingAttraction.GetCoveredTiles()) + batch.Draw(batch.GetBlankTexture(), new RectangleF(placingPos + pos.ToVector2() * tileSize, tileSize), Color.Black * 0.15F * alpha); + batch.Draw(this.PlacingAttraction.TextureRegion, placingPos, Color.White * alpha * 0.5F, 0, Vector2.Zero, scale, SpriteEffects.None, 0); + } } public bool CanPlace(Point position, Attraction attraction) { - for (var x = 0; x < attraction.Width; x++) { - for (var y = 0; y < attraction.Height; y++) { - if (this.grid[position.X + x, position.Y + y] != null) - return false; - } + foreach (var (x, y) in attraction.GetCoveredTiles()) { + if (this.grid[position.X + x, position.Y + y] != null) + return false; } return true; } public void Place(Point position, Attraction attraction) { - for (var x = 0; x < attraction.Width; x++) { - for (var y = 0; y < attraction.Height; y++) - this.grid[position.X + x, position.Y + y] = attraction; - } + foreach (var (x, y) in attraction.GetCoveredTiles()) + this.grid[position.X + x, position.Y + y] = attraction; this.attractions[position] = attraction; } diff --git a/ThemeParkClicker/RainingTicket.cs b/ThemeParkClicker/RainingTicket.cs index 7ae81ac..f26403d 100644 --- a/ThemeParkClicker/RainingTicket.cs +++ b/ThemeParkClicker/RainingTicket.cs @@ -26,9 +26,9 @@ namespace ThemeParkClicker { return this.position.Y >= 1.15F; } - public void Draw(SpriteBatch batch, Vector2 viewport, float scale) { + public void Draw(SpriteBatch batch, Vector2 viewport, float scale, Color color) { var tex = Ui.Texture[2, 0]; - batch.Draw(tex, this.position * viewport, Color.White, this.rotation, tex.Size.ToVector2() / 2, scale, SpriteEffects.None, 0); + batch.Draw(tex, this.position * viewport, color, this.rotation, tex.Size.ToVector2() / 2, scale, SpriteEffects.None, 0); } } diff --git a/ThemeParkClicker/ThemeParkClicker.csproj b/ThemeParkClicker/ThemeParkClicker.csproj index b11e499..6342d6a 100644 --- a/ThemeParkClicker/ThemeParkClicker.csproj +++ b/ThemeParkClicker/ThemeParkClicker.csproj @@ -5,6 +5,7 @@ + all diff --git a/ThemeParkClicker/Ui.cs b/ThemeParkClicker/Ui.cs index 8104e1a..4f2ac05 100644 --- a/ThemeParkClicker/Ui.cs +++ b/ThemeParkClicker/Ui.cs @@ -28,7 +28,7 @@ namespace ThemeParkClicker { private bool finishingSwipe; public Ui(UiSystem uiSystem) { - InputHandler.EnableGestures(GestureType.HorizontalDrag, GestureType.FreeDrag, GestureType.Pinch); + InputHandler.EnableGestures(GestureType.HorizontalDrag, GestureType.FreeDrag, GestureType.Pinch, GestureType.Tap); this.uiSystem = uiSystem; this.uiSystem.GlobalScale = 4; this.uiSystem.AutoScaleWithScreen = true; @@ -45,19 +45,22 @@ namespace ThemeParkClicker { if (rainingTickets[i].Update()) rainingTickets.RemoveAt(i); } - while (rainingTickets.Count < BigInteger.Min(GameImpl.Instance.Tickets / 100, 500)) + while (rainingTickets.Count < Math.Min(GameImpl.Instance.TicketsPerSecond / 10, 500)) rainingTickets.Add(new RainingTicket()); }, OnDrawn = (e, time, batch, alpha) => { - batch.Draw(batch.GetBlankTexture(), e.DisplayArea, ColorExtensions.FromHex(0xff86cfcb)); + batch.Draw(batch.GetBlankTexture(), e.DisplayArea, ColorExtensions.FromHex(0xff86cfcb) * alpha); foreach (var ticket in rainingTickets) - ticket.Draw(batch, e.DisplayArea.Size, e.Scale); + ticket.Draw(batch, e.DisplayArea.Size, e.Scale, Color.White * alpha); } }; var ticketGroup = main.AddChild(new Group(Anchor.AutoCenter, new Vector2(1, 0.15F), false)); ticketGroup.AddChild(new Paragraph(Anchor.AutoCenter, 1, p => GameImpl.Instance.DisplayTicketCount(), true) { TextScale = 0.3F }); + ticketGroup.AddChild(new Paragraph(Anchor.AutoCenter, 1, p => GameImpl.Instance.TicketsPerSecond.ToString("0.##") + "/s", true) { + PositionOffset = new Vector2(0, -8) + }); BigInteger lastTickets = 0; var storeGroup = main.AddChild(new CustomDrawGroup(Anchor.AutoCenter, new Vector2(1, 0.5F), null, null, false) { OnUpdated = (e, time) => { @@ -80,9 +83,21 @@ namespace ThemeParkClicker { var (scaleX, scaleY) = e.DisplayArea.Size / mapSize; var scale = Math.Min(scaleX, scaleY); var pos = e.DisplayArea.Location + (e.DisplayArea.Size - mapSize * scale) / 2; - map.Draw(time, batch, pos, scale); + map.Draw(time, batch, pos, scale, alpha); }, - OnPressed = e => CoroutineHandler.Start(this.SwipeUi(true)) + OnPressed = e => { + var map = GameImpl.Instance.Map; + GameImpl.Instance.Camera.LookingPosition = new Vector2(map.Width, map.Height) / 2 * Attraction.TileSize; + + var backUi = new Group(Anchor.BottomLeft, new Vector2(1)); + backUi.AddChild(new Button(Anchor.AutoInlineIgnoreOverflow, new Vector2(1, 40), "Back") { + OnPressed = e2 => this.FadeUi(false, () => this.uiSystem.Remove(e2.Root.Name)) + }); + // we want this to render below the main ui while it fades away + this.uiSystem.Add("MapViewBack", backUi).Priority = -100; + + this.FadeUi(true); + } }); this.currentUi = main; this.uiSystem.Add("Main", main); @@ -96,12 +111,36 @@ namespace ThemeParkClicker { var instance = attraction.Value(); var button = buyUi.AddChild(new Button(Anchor.AutoLeft, new Vector2(1, 40)) { ChildPadding = new Vector2(4), - Padding = new Padding(0, 0, 0, 4) + Padding = new Padding(0, 0, 0, 4), + OnPressed = e => { + var map = GameImpl.Instance.Map; + var pos = new Vector2(map.Width, map.Height) / 2; + GameImpl.Instance.Camera.LookingPosition = (pos + new Vector2(0.5F)) * Attraction.TileSize; + map.PlacingAttraction = attraction.Value(); + map.PlacingPosition = pos.ToPoint(); + + var yesNoUi = new Group(Anchor.BottomLeft, new Vector2(1)); + yesNoUi.AddChild(new Button(Anchor.AutoInlineIgnoreOverflow, new Vector2(0.5F, 40), "Back") { + OnPressed = e2 => this.FadeUi(false, () => this.uiSystem.Remove(e2.Root.Name)) + }); + yesNoUi.AddChild(new Button(Anchor.AutoInlineIgnoreOverflow, new Vector2(0.5F, 40), "Place") { + OnPressed = e2 => { + GameImpl.Instance.Tickets -= map.PlacingAttraction.GetPrice(); + map.Place(map.PlacingPosition, map.PlacingAttraction); + this.FadeUi(false, () => this.uiSystem.Remove(e2.Root.Name)); + }, + OnUpdated = (e2, time) => ((Button) e2).IsDisabled = !map.CanPlace(map.PlacingPosition, map.PlacingAttraction) + }); + // we want this to render below the main ui while it fades away + this.uiSystem.Add("PlacingYesNo", yesNoUi).Priority = -100; + + this.FadeUi(true); + } }); button.OnUpdated += (e, time) => { button.IsDisabled = GameImpl.Instance.Tickets < instance.GetPrice(); }; - var center = button.AddChild(new Group(Anchor.Center, new Vector2(0.8F, 1), false)); + var center = button.AddChild(new Group(Anchor.Center, new Vector2(0.8F, 1), false) {CanBeMoused = false}); center.AddChild(new Paragraph(Anchor.AutoCenter, 1, attraction.Key, true)); center.AddChild(new Paragraph(Anchor.AutoCenter, 1, instance.GenerationPerSecond + "/s", true) {TextScale = 0.08F}); var image = button.AddChild(new Image(Anchor.CenterLeft, new Vector2(1), instance.TextureRegion) { @@ -167,21 +206,29 @@ namespace ThemeParkClicker { } } - private IEnumerator SwipeUi(bool swipeOut) { - GameImpl.Instance.DrawMap = true; - this.currentUi.IsHidden = false; - var offset = 0F; - while (offset < 1) { - offset += 0.025F; - var trans = (!swipeOut ? 1 - offset : offset) * this.currentUi.DisplayArea.Height; - this.currentUi.Root.Transform = Matrix.CreateTranslation(0, trans, 0); - yield return new WaitEvent(CoroutineEvents.Update); + private void FadeUi(bool fadeOut, Action after = null) { + IEnumerator Impl() { + // disable input handling during fade + this.uiSystem.Controls.HandleTouch = false; + GameImpl.Instance.DrawMap = true; + this.currentUi.IsHidden = false; + var alpha = 1F; + while (alpha > 0) { + alpha -= 0.03F; + this.currentUi.DrawAlpha = !fadeOut ? 1 - alpha : alpha; + yield return new WaitEvent(CoroutineEvents.Update); + } + this.uiSystem.Controls.HandleTouch = true; + GameImpl.Instance.DrawMap = fadeOut; + this.currentUi.IsHidden = fadeOut; + // disable horizontal and vertical drag on map view to allow free drag to take priority + InputHandler.SetGesturesEnabled(!fadeOut, GestureType.HorizontalDrag, GestureType.VerticalDrag); + if (!fadeOut) + GameImpl.Instance.Map.PlacingAttraction = null; + after?.Invoke(); } - this.currentUi.Root.Transform = Matrix.Identity; - GameImpl.Instance.DrawMap = swipeOut; - this.currentUi.IsHidden = swipeOut; - // disable horizontal and vertical drag on map view to allow free drag to take priority - InputHandler.SetGesturesEnabled(!swipeOut, GestureType.HorizontalDrag, GestureType.VerticalDrag); + + CoroutineHandler.Start(Impl()); } private static IEnumerator WobbleElement(CustomDrawGroup element, float intensity = 0.02F) {