První 3 monstra z plánovaných pěti. Kompletní pathfinding i zrcadlení do clienta. Útoky implementované nejsou. Lurk a Neko jsou hardcoded aby útočili na P1.

This commit is contained in:
Perry 2026-03-08 16:55:49 +01:00
parent 4484b127c5
commit 9bfe63a166
27 changed files with 772 additions and 47 deletions

21
FNAF_Clone/ClientEnemy.cs Normal file
View file

@ -0,0 +1,21 @@
using FNAF_Clone.GUI;
using FNAF_Clone.Map;
using GlobalClassLib;
using MonoGameLibrary.Graphics;
namespace FNAF_Clone;
public class ClientEnemy : GlobalEnemy<MapTileProjection, TileConnectorProjection> {
public ClientEnemy(int typeId, string name, int id, UIElement sprite, int difficulty, MapTileProjection location) : base(difficulty, id) {
Name = name;
TypeId = typeId;
Sprite = sprite;
Location = location;
}
// public TextureRegion Sprite { get; set; }
public UIElement Sprite { get; set; }
public override string Name{ get; }
public override int TypeId{ get; }
}

View file

@ -0,0 +1,53 @@
using System.Collections.Generic;
using System.Data;
using System.Linq;
using FNAF_Clone.GUI;
using FNAF_Clone.Map;
using GlobalClassLib;
using Microsoft.Xna.Framework;
namespace FNAF_Clone;
public class ClientEnemyManager {
private static Dictionary<int, ClientEnemy> enemies = new();
private static Point cameraCorner = new Point(64, 64);
public static void AddEnemy(ClientEnemy enemy) {
enemies.Add(enemy.Id, enemy);
UIManager.AddEnemySprite(enemy.Id, enemy.Sprite);
}
public static void AddEnemyByTemplate(EnemyType type, int id, int difficulty, MapTileProjection location) {
switch (type){
case EnemyType.LURK:
AddEnemy(new ClientEnemy((int)type, "Lurk", id, new UIElement(UIManager.enemyAtlas[0], cameraCorner), difficulty, location));
break;
case EnemyType.NEKO:
AddEnemy(new ClientEnemy((int)type, "Neko", id, new UIElement(UIManager.enemyAtlas[1], cameraCorner), difficulty, location));
break;
case EnemyType.SPOT:
UIElement element =
new UIElement([UIManager.enemyAtlas[2], UIManager.enemyAtlas[3], UIManager.enemyAtlas[4], UIManager.enemyAtlas[5]], cameraCorner);
element.SetTexture(2);
AddEnemy(new ClientEnemy((int)type, "Spot", id, element, difficulty, location));
break;
}
}
public static void Move(int id, MapTileProjection tile) {
enemies[id].Location = tile;
}
public static ClientEnemy Get(int id) => enemies[id];
public static ClientEnemy[] GetByLocation(MapTileProjection tile) {
List<ClientEnemy> output = new();
foreach (var e in enemies.Values){
if (e.Location == tile){
output.Add(e);
}
}
return output.ToArray();
}
}

View file

@ -43,6 +43,8 @@ public class CommandManager {
SendToggleRemoteDoor(direction);
return;
}
if (direction == Direction.SOUTH) return;
Client.Player.state.doorStates[(int)direction] = !Client.Player.state.doorStates[(int)direction];
UIManager.ChangeDoorState(direction, Client.Player.state.doorStates[(int)direction]);
Client.SendCommands([PlayerCommand.SET_DOOR_OFFICE(direction, Client.Player.state.doorStates[(int)direction])]);

View file

@ -13,12 +13,27 @@
#---------------------------------- Content ---------------------------------#
#begin images/enemies-definition.xml
/copy:images/enemies-definition.xml
#begin images/monitor-definition.xml
/copy:images/monitor-definition.xml
#begin images/office-definition.xml
/copy:images/office-definition.xml
#begin images/SpriteSheet_enemies.png
/importer:TextureImporter
/processor:TextureProcessor
/processorParam:ColorKeyColor=255,0,255,255
/processorParam:ColorKeyEnabled=True
/processorParam:GenerateMipmaps=False
/processorParam:PremultiplyAlpha=True
/processorParam:ResizeToPowerOfTwo=False
/processorParam:MakeSquare=False
/processorParam:TextureFormat=Color
/build:images/SpriteSheet_enemies.png
#begin images/SpriteSheet_monitor.png
/importer:TextureImporter
/processor:TextureProcessor

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<TextureAtlas count="9">
<Texture>images/SpriteSheet_enemies</Texture>
<Regions>
<Region id = "0" name="lurk" x="0" y="0" width="240" height="240"/>
<Region id = "1" name="neko" x="0" y="240" width="240" height="240"/>
<Region id = "2" name="spot-awake-lit" x="0" y="480" width="240" height="240"/>
<Region id = "3" name="spot-awake-unlit" x="240" y="480" width="240" height="240"/>
<Region id = "4" name="spot-asleep-lit" x="0" y="720" width="240" height="240"/>
<Region id = "5" name="spot-asleep-unlit" x="240" y="720" width="240" height="240"/>
<Region id = "6" name="dash" x="0" y="960" width="240" height="240"/>
<Region id = "7" name="mare-lit" x="0" y="1200" width="240" height="240"/>
<Region id = "8" name="mare-unlit" x="240" y="1200" width="240" height="240"/>
</Regions>
</TextureAtlas>

View file

@ -64,9 +64,30 @@ public class EventProcessor {
UIManager.ChangeRemoteDoorState((e.Args[1], e.Args[2]), e.Args[3] == 1);
break;
case -1: // movement
throw new NotImplementedException();
case 6: // spawn
Console.WriteLine($"E: Spawned enemy {e.Args[0]} at {e.Args[3]}");
ClientEnemyManager.AddEnemyByTemplate((EnemyType)e.Args[0], e.Args[1], e.Args[2], ClientMapManager.Get(e.Args[3]));
UIManager.UpdateCameras([e.Args[3]]);
break;
case 7: // movement
Console.WriteLine($"E: Enemy {e.Args[0]} moved to {e.Args[1]}");
int oldPos = ClientEnemyManager.Get(e.Args[0]).Location!.Id;
ClientEnemyManager.Move(e.Args[0], ClientMapManager.Get(e.Args[1]));
UIManager.UpdateCameras([oldPos, e.Args[1]]);
break;
case 9:
Console.WriteLine($"E: Enemy {e.Args[0]} reset to {e.Args[1]}");
int preResetPos = ClientEnemyManager.Get(e.Args[0]).Location!.Id;
ClientEnemyManager.Move(e.Args[0], ClientMapManager.Get(e.Args[1]));
UIManager.UpdateCameras([preResetPos, e.Args[1]]);
break;
case 10:
Console.WriteLine($"E: Spot:{e.Args[0]} turned {(e.Args[1] == 1 ? "on" : " off")}");
ClientEnemyManager.Get(e.Args[0]).Sprite.SetTexture(e.Args[1] == 1 ? 0 : 2);
break;
}
}
}

View file

@ -52,6 +52,7 @@ public class Screen {
}
public UIElement this[string id] => elements[id];
public UIElement TryGetElement(string id) => elements.TryGetValue(id, out var val) ? val : null;
public void AddElement(string id, UIElement element) {
elements.Add(id, element);
@ -80,8 +81,6 @@ public class Screen {
}
public void Update() {
foreach (var keyValuePair in elements){
keyValuePair.Value.Update();
}

View file

@ -69,9 +69,7 @@ public class UIElement {
currentTextureId = textureId;
}
public void Update() {
}
public virtual void Update() { }
public bool IsWithinBounds(Point pos) {
return pos.X >= Math.Min(screenSpaceBounds.Item1.X, screenSpaceBounds.Item2.X) && pos.X <= Math.Max(screenSpaceBounds.Item1.X, screenSpaceBounds.Item2.X) &&

View file

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using FNAF_Clone.Map;
using GlobalClassLib;
using Microsoft.Xna.Framework;
@ -21,15 +23,19 @@ public class UIManager {
private static TextureAtlas testAtlas;
private static TextureAtlas officeAtlas;
private static TextureAtlas monitorAtlas;
public static TextureAtlas enemyAtlas;
public static int GlobalPixelMultiplier{ get; private set; }
private Dictionary<(int, int), UIElement> doorElements = new();
private static Dictionary<int, UIElement> enemyElements = new();
public static void InitUI() {
GlobalPixelMultiplier = Core.graphicsDevice.Viewport.Height / 360;
testAtlas = TextureAtlas.FromFile(Core.content, "images/testBlocks-definition.xml");
officeAtlas = TextureAtlas.FromFile(Core.content, "images/office-definition.xml");
monitorAtlas = TextureAtlas.FromFile(Core.content, "images/monitor-definition.xml");
enemyAtlas = TextureAtlas.FromFile(Core.content, "images/enemies-definition.xml");
Screen.AddScreens([officeScreen, monitorScreen]);
Screen.SetScreen(ScreenTypes.OFFICE);
@ -67,6 +73,7 @@ public class UIManager {
monitorScreen.AddElement("eye-player", new UIElement(monitorAtlas[24], monitorScreen["room"+Client.Player.state.camera].Bounds.Item1));
monitorScreen.AddElement("eye-opponent", new UIElement([monitorAtlas[23], monitorAtlas[22]], monitorScreen["room"+Client.Opponent.state.camera].Bounds.Item1));
UpdateCameras([Client.Player.state.camera]);
}
public static void SpawnDoors(TileConnectorProjection[] doors) {
@ -91,6 +98,13 @@ public class UIManager {
monitorScreen.AddElement("p2-office-door-left", new UIElement([monitorAtlas[17], monitorAtlas[18]], new Point(400, 144)));
}
public static void AddEnemySprite(int id, UIElement sprite) {
monitorScreen.AddElement($"enemy{id}", sprite);
enemyElements.Add(id, sprite);
sprite.Visible = false;
}
public static void ChangeDoorState(Direction dir, bool state) {
int stateInt = state ? 1 : 0;
@ -141,12 +155,25 @@ public class UIManager {
public static void ChangeCamera(int id) {
monitorScreen["eye-player"].SetPosition(monitorScreen["room"+id].Bounds.Item1);
UpdateCameras([id]);
}
public static void UpdateCameras(int[] camIds) {
if (camIds.Contains(Client.Player.state.camera)){
enemyElements.Values.Where(e => e.Visible).ToList().ForEach(e => e.Visible = false);
ClientEnemy[] enemies = ClientEnemyManager.GetByLocation(ClientMapManager.Get(Client.Player.state.camera));;
foreach (var enemy in enemies){
enemyElements.TryGetValue(enemy.Id, out var element);
if (element == null) continue;
element.Visible = true;
}
}
}
public static void ChangeCameraOpponent(int id) {
monitorScreen["eye-opponent"].SetPosition(monitorScreen["room"+id].Bounds.Item1);
}
// private static Point GetRoomUIPos((int x, int y) pos) {
// return new Point(336 + (32 * pos.x), 144 + (32 * pos.y));
// }