views:

88

answers:

2

I'm having troubles wrapping my head around how to organize the classes within my RPG project that I'm making. I've tried to implement a battle system but I am not comfortable with my initial results.

This is the basic layout I currently have for my classes.

clsCharacter > clsTeam > clsBattle


clsCharacter contains statistics pertaining to one character, including magic available to that character to use. Also includes what weapons that character has equipped.

clsTeam contains multiple clsCharacter, as well as a list of items that each clsCharacter in the team can use.

clsBattle contains two clsTeams. One team is the player and the other is the computer.


Now, this is a wonderful way to organize data. However, I see limitations with this approach. For instance, to pass data from Battle to Character, it has to pass through the team class and vice-versa. Plus, if I use Properties, it transfers data as ByVal instead of ByRef, so I cannot guarentee that I'm editing the original and not a copy of the passed object (IIRC)

In addition, I feel it is just messy code to include methods within the clsCharacter class that invoke this: MyTeam.MyBattle.DoAction(). Plus, the clsCharacter might not even be in the battle at the time - I don't won't to bog down that class featuring code that is exclusive for battling when I also need to be concerned about moving around the map, saving/loading data, etc.

So, any suggestions? Right now, I'm burnt out of ideas. One idea I have so far is to include a function for the clsCharacter that exports a list of all possible moves the character could make, and if the character is CPU choose the most optimal one, and if Human than wrap it up in some nifty GUI so they can choose what action to take. But at the same time, how do I use that information within the context of the battle?

A: 

There isn't anything "wrong" with exposing your characters as a collection property on the team, that's assuming that a battle will always have two teams (even if the teams only have one player on them)

You can then perform some method on the team members from the battle e.g.

 var dwarfs = _teamA.Players.Where(p => p.CharacterType == Character.Dwarf);
 foreach(var dwarf in dwarfs)
    AddHealth(dwarf);

The code is C# but hopefully you get the idea. Essentially you're right to question adding methods to the character class. The methods should be defined in the class that manages that part of the game

Dylan
+1  A: 

NOTE: This answer ended up being WAY longer than I expected, but I taylored it specifically to your RPG situation, so hopefully you find it helpful. It might be useful to actually copy the code into a Console application and play around with it to understand how it actually works.

Have you considered using interfaces in your design? I think they would really help a lot. I asked a question a week ago and got some great suggestions about using interfaces with Dependency Injection.

Using interfaces, you can avoid thinking "what containers of data do I need to perform this action?" and instead think "what action do I need?" Here's a simple example of how you might model your code to pull out the method of attacking in a battle:

Public Interface IAttacker
    Function GetAttackDamage() As Double 
    Property Name As String
End Interface 

Public Class Character
    Implements IAttacker

    Public Property Name As String Implements IAttacker.Name
    Public Property Weapons As List(Of IWeapon)
    Public Property SpecialTeamAttackMove As IWeapon

    Public Function GetAttackDamage() As Double _
      Implements IAttacker.GetAttackDamage()
        'Get the sum of the damage in the weapons list'
        Return Weapons.Select(Function(iw) iw.Damage).Sum()
    End Function
End Class

Public Class Team
    Implements IAttacker

    Public Property Characters As List(Of Character) = _
        New List(Of Character)()
    Public Property Name As String Implements IAttacker.Name

    Public Function GetAttackDamage() As Double _
      Implements IAttacker.GetAttackDamage()
        'Get the sum of the damage of the SpecialTeamAttackMove'
        'of each character on the team'
        Return Characters.Select(Function(c) _
            c.SpecialTeamAttackMove.Damage).Sum())
    End Function
End Public

Public Class Battle
    Public Property AttackerA As IAttacker
    Public Property AttackerB As IAttacker

    Public Sub New(attackerA As IAttacker, attackerB As IAttacker)
        Me.AttackerA = attackerA
        Me.AttackerB = attackerB
    End Sub

    'This function returns the "winner" of the fight, unless it is a tie'
    Public Function Fight() As IAttacker
        If Me.AttackerA.GetAttackDamage() = Me.AttackerB.GetAttackDamage() Then
            Return Nothing
        ElseIf Me.AttackerA.GetAttackDamage() > _
               Me.AttackerB.GetAttackDamage() Then
            Return Me.AttackerA
        Else
            Return Me.AttackerB
        End If
    End Function

 End Class

In the above code, the Battle class only knows that it needs an object that implements IAttacker. It doesn't care how that object implements GetAttackDamage(), only that it can call the method when it needs to. The Name property is also abstracted into the interface. See the following procedural examples of how this might work:

Public Sub BattleBetweenTwoCharacters()
  'In this example, two characters are instantiated. Assume the "..." is where'
  'you add weapons and other properties. The characters fight and the winning'
  'IAttacker is returned'
    Dim charA As Character = New Character() With {...}
    Dim charB As Character = New Character() With {...}

    Dim winningChar As IAttacker = New Battle(charA, charB).Fight()

    If winningChar Is Nothing Then
        Console.WriteLine("It was a tie.")
    Else
        Console.WriteLine("{0} was the winner",winningChar.Name)
    End If
End Sub


Public Sub BattleBetweenTwoCharacters()
  'In this example, several characters and a team are instantiated. '
  'Assume the "..." is where you add weapons and other properties. A '
  'fight takes place and the winner is returned.'
    Dim charA As Character = New Character() With {...}
    Dim charB As Character = New Character() With {...}
    Dim charC As Character = New Character() With {...}

    Dim teamAB As New Team()
    teamAB.Characters.Add(charA)
    teamAB.Characters.Add(charB)

    'Here, a team fights a character. The Battle.Fight method will call '
    'GetAttackDamage() on each object, even though a team implements'
    'it differently than a single character.'
    Dim winner As IAttacker = New Battle(teamAB, charB).Fight()

    If winningChar Is Nothing Then
        Console.WriteLine("It was a tie.")
    Else
        Console.WriteLine("{0} was the winner",winningChar.Name)
    End If
End Sub    

Hopefully you can see from this (woefully lengthy) example the advantage of using interfaces to help encapsulate specific functionality. This allows you to decouple the functionality you expect from a class from the class itself and allow the objects that consume that class to call the functionality it needs directly through the interface.

In the example I showed, the Battle didn't care if it had a character. All it need to know about was that it had things that could attack, that is, things which implemented the IAttacker interface. Because we wrote it this way, we weren't limiting our battles to just Characters. We are also giving the Battle object only the things necessary to fight. The Battle doesn't necessarily need to know about all of the experience points, items, pets, etc. associated with an attacker. The only thing it cares about is that when it asks for the total damage that an attacker can give, the attacker provides that information. Each IAttacker might implement it differently, but that's OK, as long as the Battle object gets the information it needs.

If you mix interfaces with base classes in your design, you'll be off to a very good start in structuring your class hierarchy.

Ben McCormack