System itemów zbudowany z interfejsy i klas abstrakcyjnych.

0

Próbuję zrobić coś a'la system przedmiotów z gier RPG. Mam coś takiego na obecny moment (uproszczony kod niżej). Mógłbym prosić o uwagi co poprawić albo dodać w obecnym kodzie aby 'łatwo' można było rozszerzać funkcjonalność? Powiedzmy teraz chciałbym dodać czary to dodam klasę abstrakcyjną Spell, która implementuje interface IWeapon i klasę specjalistyczną Fireball która nadpisze metodę Hit w zależności od potrzeb. Czy może lepszym rozwiązaniem jest całkowite pozbycie się interfejsu IWeapon tylko abstrakcyjna metoda Hit w klasie Weapon albo jak w Potion , że interface implementują klasy szczegółowe?

public interface IWeapon
{
	void Hit(IAttackable target);
}

public abstract class Weapon : IWeapon
{
	public string Name { get; protected set; }
	public int AttackDamage { get; protected set; }

	protected Weapon(string name, int attackDamage)
	{
		Name = name;
		AttackDamage = attackDamage;
	}

	public abstract void Hit(IAttackable target);
}

public sealed class Sword : Weapon
{
	public Sword(string name, int attackDamage)
		: base(name, attackDamage)
	{
	}

	public override void Hit(IAttackable target)
	{
		target.TakeDamage(AttackDamage);
	}
}

public sealed class Axe : Weapon
{
	public int ArmorPenetration { get; }

	public Axe(string name, int attackDamage, int armorPenetration)
		: base(name, attackDamage)
	{
		ArmorPenetration = armorPenetration;
	}

	public override void Hit(IAttackable target)
	{
		var pen = (int)(target.Defense * ArmorPenetration);

		target.TakeDamage(AttackDamage - (target.Defense - pen));
	}
}
public interface IUsable
{
	void Use(Unit unit);
}

public abstract class Potion
{
	public string Name { get; protected set; }
	public int Points { get; protected set; }

	protected Potion(string name, int points)
	{
		Name = name;
		Points = points;
	}
}

public sealed class HealthPotion : Potion, IUsable
{
	public HealthPotion(string name, int points)
		: base(name, points)
	{
	}

	public void Use(Unit unit)
	{
		unit.HealthPoints = unit.HealthPoints + Points > unit.MaxHealth
			? unit.MaxHealth
			: unit.HealthPoints + Points;
	}
}
1
  1. czemu bron ma wiedziec jak zadawac obrazenia? Lepiej zeby bron byla przekazywana do jakiegos processora ktory oblicza na podstawie broni odpowiedni attackDamage

  2. Weapon powinno byc prosta klasa ktora przechowywuje informacje, wiec IMO wywalic interfejs

  3. Spell niech bedzie osobnym tworem i mozesz przekazywac do procesora albo spell albo weapon i miec odpowiednie kalkulacje dla nich osobno (bo np mozesz miec physical armor i magical armor)

  4. Potion podonie jak na gorze. ale zrobilbym osobny processor ktory sie tym zajmuje.

0
fasadin napisał(a):
  1. czemu bron ma wiedziec jak zadawac obrazenia? Lepiej zeby bron byla przekazywana do jakiegos processora ktory oblicza na podstawie broni odpowiedni attackDamage

Czyli coś a'la serwis domenowy który zajmuje się logiką ataku?

public class FightService
{
	public int Attack(Player player, Player enemy)
	{
		var damage = player.GetTrueDamage();
		var resistance = enemy.GetTrueResist();

		return damage - resistance;
	}
}
public class Player
{
	private readonly FightService _fightService;

	public Health Health { get; set; }
	public Attribute Strength { get; set; }
	public Attribute Stamina { get; set; }

	public Weapon Weapon { get; set; }
	public Armor Armor { get; set; }

	public void Attack(Player enemy)
	{
		_fightService.Attack(this, enemy);
	}

	public void TakeDamage(int points)
	{
		Health.Update(points);
	}

	public bool IsAlive()
	{
		return Health.CurrentHealth > 0;
	}

	public GetTrueDamage()
		=> Weapon.RawDamage * Strength;

	public GetTrueResistance()
		=> Armor.RawResistance * Stamina;
}
  1. Weapon powinno byc prosta klasa ktora przechowywuje informacje, wiec IMO wywalic interfejs

A jest w ogóle sens tworzenia Weapon jako klasy abstrakcyjnej? Myślałem, żeby uczynić ją zwykłą klasą z właściwością enum WeaponType i zrobić WeaponBuilder ale z drugiej strony miecz jest czymś innym od kostura, chociaż obie bronie są melee to mają inne zachowanie a zarazem różnią się od łuku, który jest range.

  1. Spell niech bedzie osobnym tworem i mozesz przekazywac do procesora albo spell albo weapon i miec odpowiednie kalkulacje dla nich osobno (bo np mozesz miec physical armor i magical armor)

Spell jako osobna klasa i razem z Weapon implementują wspólny interface (broń jest czym innym od zaklęcia chociaż oba zadają obrażenia)?

  1. Potion podonie jak na gorze. ale zrobilbym osobny processor ktory sie tym zajmuje.

Tylko, że jak powiedzmy do ataku, używania itemów, chodzenia będę miał osobne procesy to moja klasa gracza będzie się składała np. z 10 prywatnych pól gdzie będę wstrzykiwał procesy. Chyba, że o coś innego ci chodziło?

1
Czyli coś a'la serwis domenowy który zajmuje się logiką ataku?

tak

skorzystaj sobie z IoC i wstrzykuj FightService (a raczej jego interfejs) do konstruktora

Player tez nie powinien decydowac o sobie czy jest martwy czy nie. Przeciez to nie on ustala reguly gry ;)

jezeli bedziesz miec 100 graczy, to kazdy bedzie sam sobie sprawdzal czy zyje czy nie? Czy lepiej zeby to serwis robil?

jest w ogóle sens tworzenia Weapon jako klasy abstrakcyjnej?

zalezy co chcesz dalej z tym robic. Nie wniknalem w to, ale jezeli

Myślałem, żeby uczynić ją zwykłą klasą z właściwością enum WeaponType i zrobić WeaponBuilder

to zapewne nie

ale z drugiej strony miecz jest czymś innym od kostura

rozni sie statystykami i wygladem (jakis plik graficzny). Chyba, ze maja wiecej roznic? O jakich roznicach dokladnie mowisz? Bo od tego moze zalezec architektura

chociaż obie bronie są melee to mają inne zachowanie a zarazem różnią się od łuku, który jest range.

jezeli masz podzial broni na Melee i Range to posiadaj abstrakcyjna klase Weapon i konkretne klasy WeaponRange i WeaponMelee dodaj do nich buildery.

chociaz tez mozesz w builderze zrobic. CreateMeleeWeapon (ustawia range na 1 np) albo CreateRangeWeaponWithRange(5). Wtedy nadal weapon jest tylko weaponem i nie potrzeba ich rozdzielac

Spell jako osobna klasa i razem z Weapon implementują wspólny interface 

nie, po prostu masz dwie rozne funkcje. Jedna do obslugi Weapon drugi do spelli Nazywa sie tak samo ale przyjmuje inny typ. (przeciazenie funkcji)

Tylko, że jak powiedzmy do ataku, używania itemów, chodzenia będę miał osobne procesy to moja klasa gracza będzie się składała np. z 10 prywatnych pól gdzie będę wstrzykiwał procesy.

i dokladnie o to chodzi

Przeciez Player sam w sobie jest obiektem. On nic nie moze. Dopiero po dodaniu komponentow do niego zaczyna miec jakies wlasciwosci. Dzieki temu, bedziesz mogl robic np potrowy ktore nie beda potrafily uzywac itemow, albo bossowe ktore beda mogli korzystac z potionow.

0

Dziękuję za odpowiedź, sporo mi to rozjaśniło. Jeszcze chciałem się upewnić odnośnie życia czy dobrze Cię zrozumiałem;

  1. sprawdzenie życia obiektu albo zadanie obrażeń powinno być dostępne tylko przez serwis?
  2. Player powinien mieć metody IsAlive i TakeDamage, które będą wywoływać serwis?
public class HealthService
{
	public bool IsAlive(ILivingUnit unit)
		=> unit.Health.HealthPoints > 0;

	public void TakeDamage(ILivingUnit unit, int points)
		=> unit.Health.Update(points);
}

public class Player : ILivingUnit
{
	public Health Health { get; set; }
}

public class Skeleton : ILivingUnit
{
	private readonly HealthService _healthService = new HealthService();

	public Health Health { get; set; }

	public bool IsAlive()
		=> _healthService.IsAlive(this);

	public void TakeDamage(int points)
		=> _healthService.TakeDamage(this, points);
}

public class Test
{
	private readonly HealthService _healthService = new HealthService();
	private readonly Player _player = new Player();
	private readonly Skeleton _skeleton = new Skeleton();

	// pierwsze podejście
	public void Opcja_Pierwsza()
	{
		var result = _healthService.IsAlive(_player);
		_healthService.TakeDamage(_player, 100);
	}

	// drugie podejście
	public void Opcja_Druga()
	{
		var result = _skeleton.IsAlive();
		_skeleton.TakeDamage(100);
	}
}
2

z playera wyrzucilbym serwisy. Player jest prostym modelem. On nie powinien moc decydowac czy zyje czy nie.

Lepiej jezeli bedziesz miec serwis ktory obsluguje gre w ktorej sa playerzy. On sprawdza czy dany player istnieje na zasadzie

this.gameService->IsPlayerAlive(player);

a to wywola (moze byc tez private, zalezy czy potrzebna jest ta informacja na zewnatrz, ale raczej nie)

public bool IsPlayerAlive(Player player)
{
  return this.healthService.IsPlayerAlive(player);
}

jezeli rozrosnie Ci sie GameService to podziel GameService w zaleznosci od tego co robi GameServiceInventory GameServiceBattle a glowny GameService bedzie mial odpowiednie implementacje GameService

  1. dzieki temu bedziesz mogl korzystac z IoC dosc wygodnie
  2. testowanie kodu bedzie proste
  3. jedna klasa bedzie miala jedna odpowiedzialnosc

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