Tworzenie nowych obiektów w losowych odstępach czasu

0

Witam!
Zostałem niedawno zmuszony do napisania gry. Gra działa, ale kod mi się nie do końca podoba - jest powielony, a nie bardzo wiem, jak go uprościć.

Jest abstrakcyjna klasa bazowa PoruszanySilnikiem, dziedziczą po niej wszystkie klasy, za których ruch odpowiada "silnik" gry. Część obiektów tych klas pojawia się w losowych odstępach, więc klasy posiadają pola const opisujące minimalny i maksymalny odstęp między ich pojawianiem się, oraz pole statyczne przechowujące czas ostatniego pojawienia się.
Te klasy wyglądają np. tak:
internal class Apteczka : PoruszanySilnikiem
{
internal const int odstępMinimalny = 10000;
internal const int odstępMaksymalny = 50000;
internal static long czasOstatniego;

internal Apteczka() : base()
{
    //....
}

}

internal class WrógZwykły : WrogiObiekt //WrogiObiekt : PoruszanySilnikiem
{
internal const int odstępMinimalny = 1000;
internal const int odstępMaksymalny = 10000;
internal static long czasOstatniego;

internal WrógZwykły() : base()
{
    //....
}

}


Za pojawianie się nowych obiektów każdej z tych klas na planszy odpowiadają oddzielne metody klasy Silnik, wywoływane przy tworzeniu każdej klatki gry:
<code class="c#">private void GenerujApteczki()
{
    int czas = los.Next(Apteczka.odstępMinimalny, Apteczka.odstępMaksymalny);
    long aktualny = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
    //jeśli od pojawienia się ostatniej apteczki minął wymagany czas
    if (aktualny - Apteczka.czasOstatniego > czas)
    {
        //utworzenie nowej apteczki i dodanie jej do listy
        this.poruszalneSilnikiem.Add(new Apteczka());
        //zapamiętanie czasu ostatnio wygenerowanej apteczki
        Apteczka.czasOstatniego = aktualny;
    }
}

private void GenerujWrogówZwykłych()
{
    int czas = los.Next(WrógZwykły.odstępMinimalny, WrógZwykły.odstępMaksymalny);
    long aktualny = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
    //jeśli od pojawienia się ostatniego wroga minął wymagany czas
    if (aktualny - WrógZwykły.czasOstatniego > czas)
    {
        //utworzenie nowego wroga i dodanie go do listy
        this.poruszalneSilnikiem.Add(new WrógZwykły());
        //zapamiętanie czasu ostatniego wroga
        WrógZwykły.czasOstatniego = aktualny;
    }
}

Dlatego chcąc dodajać nowy typ, muszę po pierwsze utworzyć jego klasę, a pod drugie skopiować jedną z powyższych metod i zmienić w niej nazwy typu. Wiem, że jeśli potrzeba mieć jedną metodę dla wielu typów, to używa się metod generycznych - ale nie bardzo udało mi się tutaj coś takiego wymyślić. Kombinowałem też coś z interfejsami, ale również bezskutecznie.
Jak to poprawić?

0

Jak się nie chce kopiować kodu to należy kombinować. Czasami nawet naokoło ;)
Zrobiłem taki mały przykład podobny do Twojego problemu.
Klasy bazowe:

        public class Movable1 : Movable
        {
            public static int MinDistance = 1;
            public static int MaxDistance = 13;
            public static int LastCreated = Environment.TickCount;
        }

        public class Movable2 : Movable
        {
            public static int MinDistance = 20;
            public static int MaxDistance = 30;
            public static int LastCreated = Environment.TickCount;
        }

        public abstract class Movable
        {

        }

Silnik:

        public class Engine
        {
            private List<Movable> _list = new List<Movable>();
            private Random _rand = new Random(Environment.TickCount);

            public void Add<T>(T type, int min, int max, ref int last) where T : Movable
            {
                int time = this._rand.Next(min, max);
                if (Environment.TickCount - last > time)
                {
                    _list.Add(type);
                    last = Environment.TickCount;
                }
            }

            public override string ToString()
            {
                StringBuilder builder = new StringBuilder();
                for (int i = 0; i < this._list.Count; i++)
                    builder.AppendLine(this._list[i].ToString());
                return builder.ToString();
            }
        }

Przykładowe użycie:

            Engine engine = new Engine();
            engine.Add<Movable1>(new Movable1(), Movable1.MinDistance, Movable1.MaxDistance, ref Movable1.LastCreated);
            engine.Add<Movable2>(new Movable2(), Movable2.MinDistance, Movable2.MaxDistance, ref Movable2.LastCreated);

            Console.WriteLine(engine.ToString());

Mam nadzieję że naprowadzi Cię to na jakiś pomysł.

0

Właściwie eleganckie z punktu widzenia obiektowości powinno być inne rozwiązanie. Odstęp minimalny i maksymalny są tylko wartościami pól. Nie powinno się tylko z tego powodu, że obiekty maja inne wartości, tworzyć osobnych klas (no chyba, że i funkcjonalność będzie się różniła, z czym w powyższym nie mamy do czynienia); tak samo jak nie tworzy się podklas tylko dlatego, że każdy przycisk ma inny napis. Wszystkie obiekty działają tak samo, więc wywodzą się z jednej klasy, a jak któryś ma nową funkcjonalność (a nie inną wartość właściwości!) to można pomyśleć o podklasie.

// Nasza klasa bazowa z jakąś funkcjonalnością
class MyObject
{
//..
}


// Dostępne w grze typy obiektów
enum MyObjectType { Bandage, Enemy, Vehicle };


// Opis parametrów generatora obiektów
class Parameter
{
  public int Max { get; private set; }
  public int Min { get; private set; }
  public int LastCreated { get; private set; }

  public Parameter(int min, int max)
  {
    Min = min;
    Max = max;
    LastCreated = Environment.TickCount;
  }

  public void Update()
  {
    LastCreated = Environment.TickCount;
  }
}


// Generator obiektów
class MyObjectFactory
{
  public IList<MyObject> MyObjects { get; private set; }
  private Dictionary<MyObjectType, Parameter> Descriptions;
  private Random Random;

  public MyObjectFactory()
  {
    Random = new Random(Environment.TickCount);
    MyObjects = new List<MyObject>();
    Descriptions = new Dictionary<MyObjectType, Parameter>();
    Descriptions.Add(MyObjectType.Bandage, new Parameter(10, 20));
    Descriptions.Add(MyObjectType.Enemy, new Parameter(50, 100));
    Descriptions.Add(MyObjectType.Vehicle, new Parameter(3, 4));
  }

  public MyObject Add(MyObjectType type)
  {
    if (Descriptions.ContainsKey(type))
    {
      Parameter parameter = Descriptions[type];
      int time = this.Random.Next(parameter.Min, parameter.Max);
      if (Environment.TickCount - parameter.LastCreated > time)
      {
        MyObject myObject = new MyObject();
        Parameter.Update();
        MyObjects.Add(myObject);
        return myObject;
      }
    }
    return null;
  }
}

Przykładowe użycie:

MyObjectFactory factory = new MyObjectFactory();

factory.Add(MyObjectType.Enemy);
MyObject newObject = factory.Add(MyObjectType.Vehicle);

Pisane z głowy, ale powinno przedstawiać zagadnienie.

0

@mykhaylo - dobry pomysł podsunąłeś :) Ja po prostu nie wpadłem, żeby do metody generycznej oprócz parametru typu podawać także inne parametry, a jest to jakieś rozwiązanie.
Drugim rozwiązaniem jest zrobienie metody przyjmującej argument typu Type i użycie refleksji do pobrania z niego interesujących wartości (GetConstructor, GetField).

Ale rację ma oczywiście Szczawik - po prostu przesadziłem w projekcie z klasami, na początkowym etapie widziałem różnice w zachowaniach, których w rzeczywistości nie ma, albo można je obsłużyć inaczej, niż tworząc kolejne klasy. Dlatego teraz, po przeanalizowaniu i poprawieniu projektu, z 3 klas abstrakcyjnych i 10 konkretnych zrobiła mi się jedna abstrakcyjna i 4 konkretne, więc kod tworzący jest bardzo zbliżony do tego w poście powyżej.

Dzięki Wam za pomoc i zainteresowanie :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1