using System; using System.Linq; using Coroutine; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using MLEM.Animations; using MLEM.Extended.Extensions; using MLEM.Extended.Tiled; using MLEM.Extensions; using MLEM.Input; using MLEM.Misc; using MLEM.Startup; using MLEM.Textures; using MonoGame.Extended; using MonoGame.Extended.Tiled; using RectangleF = MLEM.Misc.RectangleF; namespace GreatSpringGameJam { public class Player : Entity { private static readonly UniformTextureAtlas PlayerTexture = new(MlemGame.LoadContent("Textures/Player"), 4, 3); private static readonly SoundEffectInstance[] HeldSounds = EnumHelper.GetValues().Select(h => MlemGame.LoadContent("Sounds/" + h).CreateInstance(isLooped: true)).ToArray(); public RectangleF Bounds => new(this.Position + new Vector2(4 / 16F, 0), new Vector2(9 / 16F, 1)); private readonly SpriteAnimationGroup animations; private bool walking; private bool onGround; private Vector2 velocity; private TimeSpan jumpTime; private bool facingRight; private HeldItem heldItem; private float currJumpHeight; private bool climbingVine; private bool invisible; public Player(Map map, Vector2 position) : base(map, position) { this.animations = new SpriteAnimationGroup(); // walking animations this.animations.Add(new SpriteAnimation(1, PlayerTexture[0]), () => !this.walking); this.animations.Add(new SpriteAnimation(0.15F, PlayerTexture[1], PlayerTexture[2], PlayerTexture[3], PlayerTexture[0]), () => this.walking); // jumping animation this.animations.Add(new SpriteAnimation(1, PlayerTexture[0, 1]), () => this.jumpTime > TimeSpan.Zero || !this.onGround, 1); // climbing animations this.animations.Add(new SpriteAnimation(1, PlayerTexture[0, 2]), () => this.climbingVine && !this.walking, 2); this.animations.Add(new SpriteAnimation(0.18F, PlayerTexture[1, 2], PlayerTexture[2, 2], PlayerTexture[3, 2], PlayerTexture[0, 2]), () => this.climbingVine && this.walking, 2); this.facingRight = true; } public override void Update(GameTime time) { base.Update(time); if (!GameImpl.Instance.IsInCutscene) { var jumpHeight = 0.15F; var (_, tileBelow) = this.GetTileBelow(); if (tileBelow != null && tileBelow.Properties.GetBool("JumpPad")) jumpHeight = 0.25F; // input var move = Vector2.Zero; if (MlemGame.Input.IsAnyDown(Keys.A, Keys.Left)) { move.X -= 0.04F; this.facingRight = false; } if (MlemGame.Input.IsAnyDown(Keys.D, Keys.Right)) { move.X += 0.04F; this.facingRight = true; } // climbing var (posInside, tileInside) = this.GetTileBelow(Vector2.Zero); if (tileInside != null && tileInside.Properties.GetBool("Vine")) { if (MlemGame.Input.IsAnyDown(Keys.W, Keys.Up)) { this.climbingVine = true; move.Y -= 0.03F; } if (MlemGame.Input.IsAnyDown(Keys.S, Keys.Down)) { this.climbingVine = true; move.Y += 0.03F; } } else { this.climbingVine = false; } // entering doors if (MlemGame.Input.IsAnyPressed(Keys.W, Keys.Up) && this.onGround) { if (tileInside != null && tileInside.Objects.Any(o => o.Properties.GetBool("LevelExit") && this.Map.GetArea(o, posInside.ToVector2()).Intersects(this.Bounds))) { this.invisible = true; GameImpl.Instance.Finish(); } else { var door = this.Map.GetLevelEntrances().FirstOrDefault(e => this.Map.GetArea(e).Intersects(this.Bounds)); if (door != null) { this.invisible = true; GameImpl.Instance.StartLevel(door.Name, Color.White); } } } this.walking = move != Vector2.Zero; this.velocity += move; if (MlemGame.Input.IsDown(Keys.Space)) { // only start jumping if we just started pressing the buttons if ((this.onGround || this.climbingVine) && MlemGame.Input.IsPressed(Keys.Space)) { this.jumpTime = TimeSpan.FromSeconds(0.3F); this.currJumpHeight = jumpHeight; this.climbingVine = false; } this.jumpTime -= time.ElapsedGameTime; if (this.jumpTime > TimeSpan.Zero) this.velocity.Y = -this.currJumpHeight; } else { this.jumpTime = TimeSpan.Zero; } // restart if we fell if (this.Position.Y > this.Map.Size.Y) GameImpl.Instance.StartLevel(this.Map.Name, Color.Black); } // movement and collisions this.Position += this.velocity; this.Collide(() => this.Bounds, ref this.velocity, ref this.onGround); this.velocity *= new Vector2(this.onGround ? 0.5F : 0.6F, this.climbingVine ? 0.5F : 0.95F); if (!this.climbingVine) this.velocity.Y += 0.015F; // item usage HeldItem? usingItem = null; if (!GameImpl.Instance.IsInCutscene) { var rot = this.GetHeldRotation(); var rotVec = new Vector2(MathF.Cos(rot), MathF.Sin(rot)); if (MlemGame.Input.IsDown(MouseButton.Left)) { usingItem = this.heldItem = HeldItem.SnowBlower; Random.NextUnitVector(out var vel); vel = vel * 0.03F + rotVec * 0.13F; this.Map.AddEntity(new SnowBlowerWind(this.Map, this.Position + Vector2.One / 2 + rotVec * 0.75F, vel)); } else if (MlemGame.Input.IsDown(MouseButton.Right)) { usingItem = this.heldItem = HeldItem.WateringCan; if (Random.NextSingle() <= 0.45F) { Random.NextUnitVector(out var vel); vel = vel * 0.015F + rotVec * 0.03F; this.Map.AddEntity(new WaterDrop(this.Map, this.Position + Vector2.One / 2 + rotVec * 0.65F, vel)); } } } for (var i = 0; i < HeldSounds.Length; i++) { if (usingItem == (HeldItem) i) { HeldSounds[i].Play(); } else { HeldSounds[i].Pause(); } } this.animations.Update(time); } public override void Draw(GameTime time, SpriteBatch batch) { if (this.invisible) return; var pos = this.Position * this.Map.TileSize; void DrawHeldItem() { var tex = this.heldItem switch { HeldItem.SnowBlower => StuffTexture[0, 0], HeldItem.WateringCan => StuffTexture[2, 0], _ => null }; var origin = new Vector2(tex.Width * 0.25F, tex.Height / 2); var rot = this.GetHeldRotation(); var flip = rot >= MathHelper.PiOver2 && rot <= MathHelper.Pi * 1.5F ? SpriteEffects.FlipVertically : SpriteEffects.None; batch.Draw(tex, pos + this.Map.TileSize / 2, Color.White, rot, origin, 1, flip, 0); } if (this.climbingVine) DrawHeldItem(); // draw self var effects = this.facingRight ? SpriteEffects.FlipHorizontally : SpriteEffects.None; batch.Draw(this.animations.CurrentRegion, pos, Color.White, 0, Vector2.Zero, 1, effects, 0); if (!this.climbingVine) DrawHeldItem(); } private float GetHeldRotation() { var myPos = GameImpl.Instance.Camera.ToCameraPos((this.Position + Vector2.One / 2) * this.Map.TileSize); var mousePos = MlemGame.Input.MousePosition.ToVector2(); var (x, y) = myPos - mousePos; return MathF.Atan2(y, x) + MathHelper.Pi; } private enum HeldItem { SnowBlower, WateringCan } } }