2
\$\begingroup\$

How do I handle user input having "good" class structure and following normal game architecture*?(see below). At first I didn't think this question was Android specific, although, after seeing a great response in C++ I would appreciate some insight on this question using Android architecture.

High Level Game Architecture

Current way I handle input for my Android Game:

  • Created controller class that takes X,Y screen events and X,Y character position and returns an int that represents a direction the character should move.

  • Call the controller method in the game's update.

  • Send that direction value to my character update.

  • Character's update calls move (which is also passed this value).

  • Basic switch statement determines the direction my character moves.

  • Getters and setters for the position of the character.(should I have them for this direction also)

Is there a better way of handling input? This doesn't feel like the best way. Where does input fit in with class structure and game architecture?

I'm going to accept the C++ answer but I would really like to see a Java answer for Android in the future. :D

\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

If i'm not mistaken that Idea is generally considered and Anti-Pattern named Anemic Domain Model, but if you use it wisely it can be beneficial. I Prefer one big function if you are not going to implement many controllers with different behaviors, but if you are going to have more than one controller and decide in runtime what that controller should do, it's not a very bad idea to use them but you have to be very careful to prevent possible errors (like controllers call order).

--EDIT--

I guess if change your function to this way there won't be any problem, and you'll get smaller functions:

class workerA
{
    static point nextPos(point currentPos, ...)
    {
    }
};

class workerB
{
    .
    .
    .
};

class player
{
    void move()
    {
        pos = workerA.nextPos(pos,...);

    }
}

I'm not sure if this is a valid design pattern or not but at least classes doesn't have bidirectional pointers, and player class can always guaranty its members, since it's move that calls other methods.

--EDIT2-- This is the best way I could describe my architecture. It's a complete code, with all the thing available in every game. I'm not sure if this is like your previous style or not or if you can convert your code (or even if you are willing to). if you still think there is anything unclear or want to discuss about this code, I think I'll be available in chatroom in 12 hours meaning something around 1:00 GMT.

#include <iostream>
#include <vector>

class Keyboard;
class RenderTarget;

struct Point
{
    int x,y;
}

class Rendereable
{
protected :
    RenderTarget* mRenderTarget
public:
    virtual void draw() = 0;
    void setRenderTarget(RenderTarget* pRenderTarget);
}

class world;

class Entity
{
protected:
    Point mPosition;
    Point mRadius;
public:
    virtual void update(world* pWorld) = 0;
    void setPosition(Point pPosition);
    Point getPosition() const;
    bool isColliding(const Entity* pOther) const;
    bool isColliding(Point pOther) const;
};

class world
{
protected:
    vector<entity*> mEntitys;
    int mWidth,mHeight;
public:
    void update();
    void draw();
    void addEntity(const Entity* pEntity);
    void removeEntity(const Entity* pEntity);
    bool isFree(const Entity* pEntity);
    bool isFree(Point* pPoint);
};

class KeyboardController
{
protected:
    Keyboard* mKeyboard;
public:
    Point getNextPosition(const pEntity* pTarget,World* pWorld);
    void setKeyboard(KeyBoard* pKeyboard);
};

class Player : public Entity
{
protected:
    KeyboardController* mController;
public:
    void setController(KeyBoardController* pController);
    virtual void update(World* pWorld);
    virtual void draw();
};

class Game
{
    World* mWorld;
    RenderTarget* mRenderTarget;
    Keyboard* mKeyboard;
public:
    bool init();
    bool update();
    void draw();
    void shut();
};

int main()
{
    Game* game = new Game;
    if (game->init())
    {
        while (game->update())
            game->draw();
        game->shut();
        return 0;
    }
    return 1; //this means error! only useful when your program is going to be used in batch files!
}

void Renderable::setRenderTarget(RenderTarget* pRenderTarget)
{
    mRenderTarget = pRenderTarget;
}

void Entity::setPosition(Point pPosition)
{
    mPosition = pPosition;
}

Point Entity::getPosition() const
{
    return mPosition;
}

bool Entity::isColliding(const Entity* pOther) const
{
    int deltaX = mPosition.x - pOther->mPosition.x;
    int deltaY = mPosition.y - pOther->mPosition.y;
    int sumRadius = mRadius + pOther->mRadius;
    if (sumRadius * sumRadius < deltaX * deltaX + deltaY * deltaY)
        return true;
    else
        return false;
}

bool Entity::isColliding(Point pOther) const
{
    int deltaX = mPosition.x - pOther.x;
    int deltaY = mPosition.y - pOther.y;
    if (mRadius * mRadius < deltaX * deltaX + deltaY * deltaY)
        return true;
    else
        return false;
}

void World::Update()
{
    for (auto i = mEntitys.begin();i != mEntity.end();i++)
        i->update(this);
}

void World::draw()
{
    for (auto i = mEntitys.begin();i != mEntity.end();i++)
        i->draw();
}

void World::addEntity(const Entity* pEntity)
{
    for (auto i = mEntitys.begin();i != mEntity.end();i++)
        if(*i == pEntity)
            throw Exception("Entity is already in world");
    mEntitys.push_back(pEntity);
}

void World::removeEntity(const Entity* pEntity)
{
    for(auto i = mEntitys.begin();i != mEntitys.end();i++)
        if(*i == pEntity)
        {
            mEntitys.erase(i);
            return;
        }
    throw Exception("Entity is not in world");
}

bool World::isFree(const Entity* pEntity)
{
    if (pEntity->x < pEntity->radius || pEntity->x > width - pEntity->radius)
        return true;
    if (pEntity->y < pEntity->radius || pEntity->y > height - pEntity->radius)
        return true;
    for(auto i = mEntitys.begin();i != mEntitys.end();i++)
        if(*i != pEntity)
            if(i->isColliding(pEntity))
                return true;
    return false;
}

bool World::isFree(Point* pPoint)
{
    if (pPoint.x < 0 || pPoint.x > mWidth)
        return true;
    if (pPoint.y < 0 || pPointy > mHeight)
        return true;
    for(auto i = mEntitys.begin();i != mEntitys.end();i++)
        if(i->isColliding(pPoint))
            return true;
    return false;
}

Point KeyboardController::getNextPosition(const pEntity* pTarget,World* pWorld)
{
    Entity x(*pTarget);
    if (mKeyboard->isKeyDown(K_UP))
    {
        y --;
        if (pWorld->isFree (x))
            return x.getPosition();
        y ++;
    }
    // same function goes for other directions
}
void KeyboardController::setKeyboard(KeyBoard* pKeyboard)
{
    mKeyBoard = pKeyBoard;
}

void Player::setController(KeyBoardController* pController)
{
    mController = pController;
}

virtual void Player::update(World* pWorld)
{
    mPosition = pController->getNextPosition(this, pWorld); 
}

virtual void Player::draw()
{
    mRenderTarget->Circle(mPosition.x, mPosition.y, mRadius); // a simple drawing function
}

bool Game::init()
{
    mWorld = new World;
    mKeyboard = new KeyBoard;
    mRenderTarget = new RenderTarget;
    Player* player = new Player;
    KeyBoardController* controller = new KeyBoardController;
    player->setController(controller);
    player->setRenderTarget(RenderTarget);
    mWorld->addEntity(player);
}
bool Game::update()
{
    mWorld->update();
}
void Game::draw()
{
    mRenderTarget->clearDevice();
    mWorld->draw();
}
void Game::shut()
{

}
\$\endgroup\$
8
  • \$\begingroup\$ Do you know if there is a "best practice" when it comes to this with Android Game programming? \$\endgroup\$
    – KRB
    Sep 27, 2011 at 19:37
  • \$\begingroup\$ I don't really know if programming for android is any different than other devices, I think the only difference is the API you are using. anyhow I've edited my answer and added some other way to reduce player update size. \$\endgroup\$
    – Ali1S232
    Sep 28, 2011 at 7:22
  • \$\begingroup\$ Definitely helpful @Gajet, do you know how these classes would fit into a game architecture? Completely ignore the idea we are dealing with Android, I actually am going to remove that from the title in hopes for a broader view. My problem is, I don't want to call move from player outside of its update. So I call player.update() in the "game loop" and inside update, move() is called. How would I get the values from controller.update() to player.update() for the player to move? \$\endgroup\$
    – KRB
    Sep 28, 2011 at 15:04
  • \$\begingroup\$ @KBR: move is one of player's methods. it means when you call player.update(), that function can call move, and move will change values inside player class. and to transfer data you can just call some method in controller class with all the data it needs as parameters, and then that method returns its result back to player's method. you can also give const player * to the controller method. if you still don't get the idea I can write a sample code to show what I mean although it'll be in c++. \$\endgroup\$
    – Ali1S232
    Sep 28, 2011 at 19:21
  • \$\begingroup\$ Okay, say we have a game loop that is called over and over. We Update and Draw over and over. I have the player.update() and player.draw(). As of now I pass in my eventX, eventY to handle movement for my player. I want to simply only pass a constant to this player.update() that represents a direction for it to move. I want to know how I should deal with this controller class in the main loop. Should I pass my eventX, eventY to the controller during each loop of the game? I then determine direction in controller... then what? Return that constant to game loop and pass it into update()? \$\endgroup\$
    – KRB
    Sep 28, 2011 at 20:10
0
\$\begingroup\$

What I found to work best is to have

  • Android's UI thread subscribe to onTouch events on the Game's SurfaceView (either OpenGL or Canvas)
  • Setup a global InputSystem singleton.
  • Have the UI thread's onTouch call the InputSystem's onTouch passing the MotionEvent.
  • InputSystem then saves this event (optionally in a cyclic queue - although using a single event works best for us). On the same occasion InputSystem can do all sorts of short calculations such as the last move distance, and various "was X event triggered in the last X ms"
    • InputSystem provides a peek and consume methods.
    • Various game logic components running on the Game Thread consult the inputsystem for the event, and potentially act upon it, and consume it.
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .