0
\$\begingroup\$

i would like your opinion on two different coding styles to solve the same problem, namely, react to user input actions.

To do this, two solutions.

  • Subscribe to events (usually "when").
  • Real time verification (usually "while").

Event pros:

  • Optimized
  • Easy to read.

Event cons:

  • Disperse logic to many places (other events, update loop, etc ...)
  • Not so flexible than update way.

Update pros:

  • Concentrate all logic in one place.
  • Very useful to group logic like you want (not only by input state).
  • Sequential processing.

Update cons:

  • Hard to optimize when code complexity increase, bad performance.

Sample following the event-driven way:

void CollisionPolygon2DTool::HandleSceneUpdate(StringHash, VariantMap& eventData)
{
    if (!IsEnabled() || !checkBox_->IsChecked())
    {
        return;
    }

    if (mode_ == Mode::INSERTION)
    {   
        ShowShadowVertex();
        shadowVertexNode_->SetWorldPosition2D(sceneView_->GetMouseWorldPosition());
    }
    else
    {
        HideShadowVertex();
    }
}

void CollisionPolygon2DTool::HandleShortcutsKeyDown()
{
    if (!IsEnabled() || !checkBox_->IsChecked())
    {
        return;
    }

    if (input->GetKeyDown(KEY_I))
    {
        mode_ = Mode::INSERTION;
    }
    else if (input->GetKeyDown(KEY_S))
    {
        mode_ = Mode::SELECTION;
    }
    else if (input->GetKeyDown(KEY_DELETE) && mode_ == Mode::SELECTION && selectedVertexIndex_ != -1)
    {
        DeleteVertex(selectedVertexIndex_);
    }
}

void CollisionPolygon2DTool::HandleSceneViewClick(StringHash, VariantMap&)
{
    if (!IsEnabled() || !checkBox_->IsChecked())
    {
        return;
    }

    if (mode_ == Mode::SELECTION && button == MOUSEB_LEFT)
    {
        selectedVertexIndex_ = GetVertexIndexAt(sceneView_->GetMouseWorldPosition());
    }

    if (mode_ == Mode::INSERTION && button == MOUSEB_LEFT)
    {
        auto mouseWorldPosition = eventData["Position"].GetVector2();
        AddVertex(mouseWorldPosition.x_, mouseWorldPosition.y_);
    }
}

void CollisionPolygon2DTool::HandleSceneViewDragBegin(StringHash, VariantMap&)
{
    if (!IsEnabled() || !checkBox_->IsChecked())
    {
        return;
    }

    if (mode_ == Mode::SELECTION && selectedVertexIndex != -1)
    {
        beginSelectedVertexPosition_ = vertices[selectedVertexIndex];
    }
}

void CollisionPolygon2DTool::HandleSceneViewDragMove(StringHash, VariantMap& eventData)
{
    if (!IsEnabled() || !checkBox_->IsChecked())
    {
        return;
    }

    if (mode_ == Mode::SELECTION && selectedVertexIndex != -1)
    {
        auto delta = eventData["Delta"].GetVector2();
        MoveVertex(selectedVertexIndex, beginSelectedVertexPosition_ + delta);
    }
}

Sample following the update-driven way:

void CollisionPolygon2DTool::Update()
{
    selectedCollisionPolygon2D_ = GetSelectedCollisionPolygon2DComponent();

    if (selectedCollisionPolygon2D_ == nullptr)
    {
        SetEnabled(false);
    }
    else
    {
        SetEnabled(true);
    }

    if (!IsEnabled() || !checkBox_->IsChecked())
    {
        return;
    }

    auto input = GetSubsystem<Input>();

    if (input->GetKeyDown(KEY_S))
    {
        mode_ = Mode::SELECTION;
    }
    else if (input->GetKeyDown(KEY_I))
    {
        mode_ = Mode::INSERTION;
    }

    if (mode_ == Mode::SELECTION)
    {
        auto input = GetSubsystem<Input>();

        if (sceneView_->GetMouseButtonPress(MOUSEB_LEFT))
        {
            auto vertexIndex = GetVertexIndexAt(sceneView_->GetMouseWorldPosition());

            if (vertexIndex != -1)
            {
                SelectVertex(vertexIndex);
            }
            else
            {
                UnselectVertex();
            }
        }

        if (input->GetKeyPress(KEY_DELETE) && selectedVertexIndex_ != -1)
        {
            DeleteVertex(selectedVertexIndex_);
        }

        if (!isMoving_ && sceneView_->GetMouseButtonDown(MOUSEB_LEFT) && selectedVertexIndex_ != -1)
        {
            beginSelectedVertexPosition_ = vertices_[selectedVertexIndex_];
            isMoving_ = true;
        }

        if (isMoving_ && sceneView_->GetMouseButtonDown(MOUSEB_LEFT) && selectedVertexIndex_ != -1)
        {
            auto newPos = vertices_[selectedVertexIndex_] + sceneView_->GetMouseMove();
            MoveVertex(selectedVertexIndex_, newPos.x_, newPos.y_);
        }

        if (isMoving_ && !sceneView_->GetMouseButtonDown(MOUSEB_LEFT))
        {
            isMoving_ = false;
        }
    }

    if (mode_ == Mode::INSERTION)
    {
        auto mouseWorldPosition = sceneView_->GetMouseWorldPosition();

        ShowShadowVertex();
        shadowVertexNode_->SetWorldPosition2D(mouseWorldPosition);

        if (sceneView_->GetMouseButtonPress(MOUSEB_LEFT))
        {
            AddVertex(mouseWorldPosition.x_, mouseWorldPosition.y_);
        }
    }
    else
    {
        HideShadowVertex();
    }
}

Seriously, maybe i think too much but i do not know which one to choose.

\$\endgroup\$
1
\$\begingroup\$

Please keep in mind that asking for opinions on Gamdev.se is considered off-topic here. With this answer, I will try to help you make your mind.


This kind of decision generally integrates within the broader style of your architecture. You'll want to use repeating patterns for similar contexts. If you define an event architecture for a specific context, you'll most likely want to reuse it for other contexts of your software, where it would apply. If you make an event system just for one part of your game, maybe you're wasting your time and energy.

Now I'd like to add something about the concept of 'Optimized'. Did you profile your example in an actual context? Was the 'optimized' approach actually faster? If so, by what percentage?

In a game, the time and resource taken by management of the input is generally nothing compared to other aspects like physics simulation and graphics rendering. This would make the arguments "more optimized" or "less optimized" weight very little in the balance.

This leaves you with these questions:

  • Did I hate myself in the past because I used approach X?
  • Am I likely to thank myself in the future because I used approach X?
  • Since code will be read more often than modified, which approach seems the clearest and understandable (because I'll have to work with again in 8 months and I'll have to understand what I did fast)?

I generally suggest to go with the cleanest solution first, then optimize what's actually a bottleneck (found by profiling).

\$\endgroup\$
  • \$\begingroup\$ Ok, sorry for the off-topic ! So for you what is the cleanest solution ? \$\endgroup\$ – AntiLoxy Sep 6 '19 at 20:18
  • 1
    \$\begingroup\$ It's not my project, it's yours :) if both solutions seem equally clean, then pick the easiest to implement. \$\endgroup\$ – Vaillancourt Sep 6 '19 at 20:29

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.