1
\$\begingroup\$

In my game, I will have more than 100 types of cards,
and before the actual game, player can select 6 of them and will be shown as 6 buttons on the scene.
The card have its own ID, card name, and a 3D game Object prefab, and have a function call perfromCardAction which performs different actions.

public class cardA:cardInterface {
     private int ID;
     private string cardName;
     private Texture2D cardImage;
     private GameObject card3DObject;

     ...Constructor...

     private override void prefromCardAction(){
     ...do Card A action...
     }
}

A script cardManager should get the 6 object player selected and stored in an array.
And set the button's Image as card's 2D image.
when the button is click, that card's prefromCardAction should be called.

So my main quesstions are:
What pattern should I used so that easier manage my cards objects?
I believe I should use Factory so that I can do something like:

selectedCardArray[0]=CardFactory.createCard("CardA");

If yes, I would like to ask:
How should I code all my card class (cardA, cardB, cardC...)?
separate into different c# script and implement cardInterface and override the perfromCardAction?

\$\endgroup\$
  • 1
    \$\begingroup\$ I'd probably try to extract data from the cards and have a way to not implement CardA, CardB, etc, but only a Card, that could be CardA, CardB, etc.. depending on the description given in the data, something like data driven (graphics, effects, etc). This question here is a bit relevant, but does not answer your question. \$\endgroup\$ – Vaillancourt Jul 7 '15 at 9:57
  • \$\begingroup\$ Card games! Unrelated to the question, but related to card games. Here are two I've played, you should check them out for cool functionality, and great AI : Elements the Game elementsthegame.com basic no "unit" animation, and Rise of Mythos on Kongregate www.kongregate.com/games/GameFuse/rise-of-mythos involved with animation of the unit depicted by the card. \$\endgroup\$ – VISQL Jul 7 '15 at 19:40
2
\$\begingroup\$

I 'll try this aproach: I'm lazy so I included the factory inside the class card. I suggest using delegates to forge the behaviour of each card type, and a big and maybe brute, swith case inside the factory method (createCard).

public class card{
     private int ID; 
     private string cardName;//getters setters?
     private Texture2D cardImage;//getters setters?
     private GameObject card3DObject;//getters setters?

     ...Constructor...

     public delegate void theFuncPrototype();
     private theFuncPrototype theFunc = {return;} ;  //initialize?   
     public doAction()
     {
        this.theFunc();
     }


// define all actions
    private static actionCardA(){
    //...
    }
    private static actionCardB(){
    //...
    }
//...

     public static createCard(string cardType){
    switch (cardType)
    {
        case "CardA":
            card N = new card(....); 
            N.theFunc = actionCardA; 
            return N;
        case "CardB":
            card N = new card(....); 
            N.theFunc = actionCardB; 
        return N;
        //...
        default:
                        //... manage wrong cardType?
    }
     }

}
\$\endgroup\$
2
\$\begingroup\$

While the answer posted @dnk drone.vs.drones is technically exactly about what you asked for and a viable solution, here is another suggestion that goes a bit further, and is based on my comment on your question.

I'm assuming:

  • the card effects are quite small and generic, and act on either the player or the opponent
  • the game is played against an opponent, you chose the card and the effect is applied, either on yourself or on the opponent, depending on the card
  • the card can be left on the table and be part of the game

The idea is to have data describe your cards, and have your code architecture read it.

The first part would be to split your actions into functions and have them mapped to a unique value.

I'll use pseudo c++ because it's what I know, equivalents exist in c# I presume.

void actionDestroyWeakestOpponentCard() {...}
void actionStrongestOpponentHalfHP() {...}
void actionHealAllMyUnitsOneHP() {...}
...

std::map<std::string, std::function<void()> > mNameToAction;

And the mapping:

void applyMappingNameToAction() {
  mNameToAction["DestroyWeakestOpponentCard"] = actionDestroyWeakestOpponentCard;
  mNameToAction["StrongestOpponentHalfHP"]    = actionStrongestOpponentHalfHP;
  mNameToAction["DestroyHealAllMyUnitsOneHP"] = actionHealAllMyUnitsOneHP;
  ...
}

The second part would require you to build the data file(s) required:

<cards>
  <card>
    <name>cardA</name>
    <graphic>images/cards/cardA.png</graphic>
    ...
    <effects>
      <action>DestroyWeakestOpponentCard</action>
    </effects>
  </card>
  <card>
    <name>cardB</name>
    <graphic>images/cards/cardB.png</graphic>
    ...
    <effects>
      <action>StrongestOpponentHalfHP</action>
    </effects>
  </card>
  <card>
    <name>cardC</name>
    <graphic>images/cards/cardC.png</graphic>
    ...
    <effects>
      <action>DestroyHealAllMyUnitsOneHP</action>
    </effects>
  </card>
</cards>

Note that I use file(s) because you could have one card per file and you'd read all the files in a folder, for instance; this is for those who like to have many small files rather than one big file.

Then you'd need to specify your card structure:

struct Card
{
  std::string mName;
  std::string mImagePath;
  std::vector< std::function<void()> > mActions;
  ...
  void performCardAction()
  {
    for( action in mActions )
      action(); // perform the action
  }
}

// and something to contain them:
std::map<std::string, Card> mAvailableCardsByName;

And then you load them:

for ( cardDataXml in read_cards_data_from_xml )
{
  Card newCard;
  newCard.mName = cardDataXml["name"],
  ...
  for ( cardDataXmlAction in cardDataXmlActions )
  {
    newCard.mActions.push_back( mAvailableCardsByName[cardDataXmlAction] );
  }
  ...
  mAvailableCardsByName[newCard.mName] = newCard;
}

This way of doing things allows you to combine small effects/actions into a card, which can offer a bit more flexibility in the design: you can create new cards by simply adding a chunk of xml.


You could push it a bit more by having your actions take paremeters

void actionHealUnitsParamDecode(
  XmlChunk aParameters, 
  PlayerType& aPlayerType, // passing parameter by reference so the call gets modified
  int& aHealAmout)         // passing parameter by reference so the call gets modified
{
  if ( aParameters["target"] == "self" )
    aPlayerType = PlayerTypes::SELF;
  else if ( aParameters["target"] == "opponent" )
    aPlayerType = PlayerTypes::OPPONENT;

  if ( isPositiveInteger( aParameters["heal_amount"] )
    aHealAmout = toPositiveInteger( aParameters["heal_amount"] );
}

void actionHealUnits(Player aSelf, Player aOpponent, XmlChunk aParameters)
{
  PlayerType target = PlayerTypes::SELF;
  int        healAmount = 0;

  actionHealUnitsParamDecode( aParameters, target, healAmount );

  if ( target == PlayerTypes::SELF )
  {
    aSelf.heal( healAmount );
  }
  else
  {
    aOpponent.heal( healAmount );
  }
}

And with the (XML) data looking like this:

<effects>
  <action>
    <name>HealUnits</name>
    <params target="self" heal_amount="3" />
  </action>
</effects>

You need to modify your Card:

struct Card
{
  ...
  std::vector< std::function<void(Player, Player, XmlChunk)> > mActions;
  XmlChunk mXmlParams;

  void performCardAction( Player aSelf, Player aOpponent, XmlChunk aParameters )
  {
    for( action in mActions )
      action(aSelf, aOpponent, aParameters); // perform the action
  }
}

Now with this your get a whole lot of flexibility in the tweaking of your cards :) And you can add some without having to recompile, provided that all the effects are already programmed in the game.


This might be a bit more than you asked for :P

If it is, please use the answer already posted by @dnk drone.vs.drones as it is a more generic and flexible way to tackle it than using pure inheritance as you were suggesting.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.