General question about programming patterns

This is a pretty general question about programming patterns for a game I'm making. I want to get this right before I start writing chunks of code so that I don't code myself into a hole, so to speak. Let's say I'm making a video game, and the player-controlled "Character" class has a move function that looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Character
{
     public:
     Character();
     void move();
}

void Character::move()
{
     if(leftbuttonpressed)
          {
          if(tilemap.lefttile == wall)
          {
               moveleft();
          }
          else
          {
               stay();
          }
     //and so on, for up, down, right, etc.
     return;
}


The move() function requires knowledge of the Character's surroundings, which are contained in a vector called "tilemap". In the above example the character can only move left if there is not a wall in the position he is trying to move in to.

My question is, how should I allow the Character class to access tilemap? There are four ways I thought about doing this, and I was wondering if one of these implementation is better than the others.

1) Declare a global variable.
Pro: Conceptually the easiest to grasp
Con: Everyone says global pointers are a bad practice, and I tend to defer to the experience of the experts...

2) Give Character a pointer to tilemap.
Pro: Easy to implement and use afterwards
Cons: Logically not the most straight forward. Why does a Character have ownership of the level map? With one class and one pointer it's not bad, but if I extend this concept to every interaction between objects it will get messy fast.

3) Pass a tilemap pointer to Character's move function as a parameter. Something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Character
{
     move(std::vector<tile*> tilemap_);
}

Character character;
std::vector<tile*> tilemap;

void update_game_state()
{
     character.move(tilemap);
     //update other entities
}


Pro: Easy to implement
Con: Can't really think of a negative; parameters may add up making code more difficult to parse?

4) Have some mediator external to Character that checks if a given movement is allowed. Implement something like this:

1
2
3
4
5
6
void update_game_state()
{
     character.trymove();
     check_move();
     character.respond_to_movement_check();
}


Obviously the above code is kind of ugly, but I'm just trying to get the idea across.

Pro: Logically it makes sense to have some class responsible for regulating the movement of all entities.
Con: Seems like the most difficult to implement.


Any insight on the best/a preferable way of doing this would be awesome. Thanks!
Last edited on
I remember working on an RPG waaaaaay back when where it was finally decided that the container object (in this case a room) would decide/process move requests in and out of itself. The room would supposedly have players, npcs, other objects as "contents" and also have a list of possible entrances and exits available. I think once you decide the basic logistics the implementation will be easier as it will follow a logical pattern in the game. Depending on how you choose to implement class hierarchy this sort of logic can stem from a very base level (like a Generic Container Class that can apply to anything in the game which can possibly hold other items) This is assuming you want to employ multiple inheritance.
In game development you'll want to go with "Data Oriented Design"

In short: your objects only hold the data and an other class uses the data for further operations.

This other class may have a name like System.

Alltogether it may look like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
template<typename T> 
struct Vector2 
{ 
    T x, y; 

    Vector2& operator+=(const Vector2<T>& rhs) { x += rhs.x; y += rhs.y; return *this; }
    Vector2& operator-=(const Vector2<T>& rhs) { x -= rhs.x; y -= rhs.y; return *this; }
};

struct Character
{
    Vector2<int> position;
    Vector2<int> velocity;
} character;

struct Tile{ unsigned int id; };


class System
{
private:
    struct Character
    {
        Vector2<int> position;
        Vector2<int> velocity;
    } character;
    std::vector< std::vector<Tile> > tilemap;

protected:
    void handle_inputs()
    {
        if(leftbuttonpressed)
            character.velocity.x = -1;
        if(rightbuttonpressed)
            character.velocity.x = 1;
        if(upbuttonpressed)
            character.velocity.y = -1;
        if(downbuttonpressed)
            character.velocity.y = 1;
    }
    void update() 
    {
        player.position += player.velocity;

        if(tilemap[player.position.x][player.position.y] == WALL)
            player.position -= player.velocity;
    }
    void render() 
    {
        // draw map
    }

public:
    void run()
    {
        while(true)
        {
            while(!new_frame); // Note: active waiting: try to avoid something like this 
                                         // this is pseudocode 

            handle_inputs();
            update();
            render();
        }
    }
};
Thanks for the replies!

So, in your example you are encapsulating the Character class into a generic all-encompassing class called System. If I were to add more entities to the game, would they also go under the same System class?
Virtual functions allow for some amazing versatility. They allow building in blocks, but eventually tie together.... so to speak.
cheers!
Virtual functions allow for some amazing versatility. They allow building in blocks, but eventually tie together.... so to speak.
cheers!


Could you elaborate or provide a simple example? Thanks!
The basic principal that I have in mind can be understood through a simple diagram of the behavior with pointers.

nonvirtual pointer access
-------------------------------
ptr
&derived1
ptr-> show()-------------------> base class show()


ptr
&derived2
ptr-> show()--------------------> base class show()



derived1
show()

derived2
show()
----------------------------------------------------
Virtual pointer access

Base
virtual
show()

ptr
&Derived1
ptr->show() ------------------------> Derived1
show()

ptr
&Derived2
ptr->show() -------------------------> Derived2
show()
Last edited on
The nonvirtual only allows access to the base class
while the virtual access allows access to the derived classes.
This method allows a solid interface for the class heirarchy.
Ah, got it. Yeah I think that that's what I'll have to do, since in the past I've preferred aggregating all components into a single vector and performing iterations over them:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Base
{
     public:
     virtual void doSomething();
}

class Derived1: public Base
{
     public:
     void doSomething();
}

class Derived2: public Base
{
     public:
     void doSomething();
}

std::vector<Base*> basepointervector;

Base* base;
Derived1* derived1;
Derived2* derived2;

basepointervector.push_back(derived1);
basepointervector.push_back(derived2);

for(int i = 0; i < basepointervector.size(); i++ )
{
     basepointervector[i]->doSomething(); //i = 0 does things non-specific, i = 1 does things specific to Derived1, i = 2 does things specific to Derived2
}


So yeah, I'll probably end up having a lot of my program written like that if possible and when classes have to interact I'll probably just insert the necessary information as a function parameter.
One last thought...
The destructors have to be virtual as well.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//tests virtual and non virtual destructors
#include<iostream>
using namespace std;
class Base
{
public:
	//~Base()                    //non - virtual destructor
	virtual ~Base()      // virtual destructor
	{
		cout << "base destroyed ";
	}
};
//////////////////////////////////////
class Derived : public Base
{
public:
	~Derived()
	{
		cout << "Derived destroyed";
	}
};
///////////////////////////////////////////////////////
int main()
{
	Base* pBase = new Derived;
	delete pBase;
	return 0;
}


Cheers!
Cool, thanks!
Topic archived. No new replies allowed.