I've been learning C# over the summer and now feel like making a small project out of what I've done so far. I've decided on a sort of text based adventure game.
The basic structure of the game will involve having a number of sectors(or rooms). Upon entry into a room, a description will be outputted and a number of actions and such you may take; the ability to examine, pick up, use stuff in that room; possibly a battle system, etc etc. A sector may be connected up to 4 other sectors.
Anyway, scribbling ideas on paper on how to design the code for this, I'm scratching my head over the structure of part of my code.
I've decided on a player class, and a 'level' class that represents a level/dungeon/area. This level class would consist of a number of interconnected 'sectors'. At any given time, the player would be present in one certain sector in the level.
So here's the confusion:
Logically, one would expect a method such as player.Move(Dir d)
Such a method should change the 'current sector' field in the level object. This means class Player would need to know about class Level. Hmmm.
And Level may have to manipulate the Player object (eg. player enters room, ambushed by something, loses something from inventory.) So now Level also needs to hold a reference to the Player object?
This doesn't feel nice; everything having to hold a reference to everything else.
At this point I remembered reading about delegates from the book I'm using. Though I know about function pointers from C++, the chapter on delegates was presented with examples with a sort of 'event based' programming viewpoint, with which I did not have much enlightenment about.
That gave me the idea to design the classes as follows:
Player:
class Player
{
//...
public delegate void Movement(Dir d); //enum Dir{NORTH, SOUTH, ...}
public event Movement PlayerMoved;
public void Move(Dir d)
{
PlayerMoved(d);
//Other code...
}
}
Level:
class Level
{
private Sector currSector;
private Player p;
//etc etc...
private void OnMove(Dir d)
{
switch (d)
{
case Dir.NORTH:
//change currSector
//other code
break;
//other cases
}
}
public Level(Player p)
{
p.PlayerMoved += OnMove;
currSector = START_SECTOR;
//other code
}
//etc...
}
Is this an alright way to do this?
If the delegate chapter was not presented the way it was, I would not have thought of using such 'events'. So what would be a good way to implement this without using callbacks?
I have a habit of making highly detailed posts... sorry v__v
What about a 'Game' class which would hold the majority of the information like a Player and a current room. For an operation such as moving the player, the Game class could move the player to a different room based on the room's level map.
The game class would manage all the interactions between the various components of the games.
Using events for something like this brings the danger that your events will get tangled. If you're not careful you'll end up with events firing each other off and overflowing your stack, which will lead to flags to turn events off under special circumstances, and a less understandable program.
UDPATE:
To make the code more manageable, you could model some of the interactions between the main classes as classes themselves, such as a Fight class. Use interfaces to enable your main classes to perform certain interactions. (Note that I have taken the liberty of inventing a few things you may not want in your game).
For example:
// Supports existance in a room.
interface IExistInRoom { Room GetCurrentRoom(); }
// Supports moving from one room to another.
interface IMoveable : IExistInRoom { void SetCurrentRoom(Room room); }
// Supports being involved in a fight.
interface IFightable
{
Int32 HitPoints { get; set; }
Int32 Skill { get; }
Int32 Luck { get; }
}
// Example class declarations.
class RoomFeature : IExistInRoom
class Player : IMoveable, IFightable
class Monster : IMoveable, IFightable
// I'd proably choose to have this method in Game, as it alters the
// games state over one turn only.
void Move(IMoveable m, Direction d)
{
// TODO: Check whether move is valid, if so perform move by
// setting the player's location.
}
// I'd choose to put a fight in its own class because it might
// last more than one turn, and may contain some complex logic
// and involve player input.
class Fight
{
public Fight(IFightable[] participants)
public void Fight()
{
// TODO: Logic to perform the fight between the participants.
}
}
In your question, you identified the fact that you'd have many classes which have to know about each other if you stuck something like a Move method on your Player class. This is because something like a move neither belongs to a player or to a room - the move affects both objects mutually. By modelling the 'interactions' between the main objects you can avoid many of those dependencies.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With