Jump to content
  • entries
    4
  • comment
    1
  • views
    4,248

Mouse Movement Controller


Andy90

1,575 views

 Share

This component generates a Mouse Movement Controller featuring a free-look mode. Typically, such character controllers find application in ARPG (Action Role-Playing Game) or RTS (Real-Time Strategy) games. In this setup, you observe the character from a top-down perspective and direct their movement to a specified position by clicking the left mouse button.

image.thumb.png.d92e724e8dd5b7da95d276be4efd0c17.png

The Component itself need the terrain name and the name for the walk animation. Also your character needs an idle animation. 

You can switch into the "free look mode" by pressing the tab key. Within the "free look mode" you are able to move the camera with "W" "A" "S" "D" free around. If you press tab again you switch the mode back to the character. 

1. C++ Header

#pragma once
#include "UltraEngine.h"
#include "GatherWaterTask.h"

using namespace UltraEngine;

class MouseMovementController : public Component
{
private:
    shared_ptr<Map> scene;
    shared_ptr<NavMesh> nav_mesh;

    bool freeLook = false;
public:
    string terrainName;
    string animationName;
    Vec3 destination;

    shared_ptr<NavAgent> agent;
    shared_ptr<Terrain> terrain;
    shared_ptr<Camera> camera;
    
    MouseMovementController();

    void Start();
    void Update();
    bool Load(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const LoadFlags flags);
    bool Save(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const SaveFlags flags);
    shared_ptr<Component> Copy();
};

 

2. C++ Source File

#include "UltraEngine.h"
#include "MouseMovementController.h"

MouseMovementController::MouseMovementController() {
    this->name = "MouseMovementController";
}

/// <summary>
/// Setup the Controller
/// </summary>
void MouseMovementController::Start() {
    auto entity = GetEntity();
    ///Create the camera and attach them to the player character
    this->camera = CreateCamera(entity->GetWorld());
    Vec3 currentcameraposition = TransformPoint(0, 15, 5, entity, nullptr);
    //this->camera->SetParent(entity);
    this->camera->SetPosition(currentcameraposition, true);
    this->camera->SetRotation(Vec3(45, 0, 0));
    this->camera->SetFov(40);
    this->camera->Point(entity);

    ///Creates a navmesh ToDo: Has to changed when the NavMesh bug is fixed
    this->nav_mesh = CreateNavMesh(entity->GetWorld(), 5, 8, 8);
    this->nav_mesh->SetPosition(0, 0, 0);
    this->nav_mesh->Build();

    ///Create a new NavAgent for the player character
    this->agent = CreateNavAgent(this->nav_mesh);
    entity->Attach(agent);
}

//Update method, called once per loop
void MouseMovementController::Update()
{
    auto entity = GetEntity();
    auto window = ActiveWindow();
    auto world = entity->GetWorld();

    if (window) {
        ///Set the destination if the player hits the left mouse button
        if (window->MouseHit(MOUSE_LEFT)) {
            auto mousePosition = window->GetMousePosition();
            auto pick = this->camera->Pick(window->GetFramebuffer(), mousePosition.x, mousePosition.y, 0, true);
            if (pick.success)
            {
                auto pos = pick.position;
                if (pick.entity->name == this->terrainName) {
                    this->destination = pos;
                }
            }
        }

        ///Move to the destination if the destination is not null
        if (this->destination != NULL) {
            if (entity->position.DistanceToPoint(this->destination) > 1.0f)
            {
                this->agent->Navigate(this->destination);
                auto model = entity->As<Model>();
                model->Animate(this->animationName);
            }
            else {
                this->agent->Stop();
                auto model = entity->As<Model>();
                model->Animate("idle");
                this->destination == NULL;
            }
        }

        ///Toggle the freelook mode
        if (window->KeyHit(KEY_TAB)) {
            if (this->freeLook) {
                this->freeLook = false;
            }
            else {
                this->freeLook = true;
                Vec3 currentcameraposition = TransformPoint(0, 15, 0, GetEntity(), nullptr);
                this->camera->SetPosition(currentcameraposition, true);
                this->camera->SetRotation(Vec3(90, 0, 0));
            }
        }

        ///Position the camera according to the selected mode
        if (!this->freeLook) {
            Vec3 currentcameraposition = TransformPoint(0, 15, 5, GetEntity(), nullptr);
            this->camera->SetPosition(currentcameraposition, true);
            this->camera->Point(entity);
        }
        else {
            Vec3 camPos = this->camera->GetPosition();
            if (window->KeyDown(KEY_W)) {
                camPos.z += 0.5f;
            }
            else if (window->KeyDown(KEY_S)) {
                camPos.z -= 0.5f;
            }
            else if (window->KeyDown(KEY_A)) {
                camPos.x -= 0.5f;
            }
            else if (window->KeyDown(KEY_D)) {
                camPos.x += 0.5f;
            }
            this->camera->SetPosition(camPos);
        }
    }
}

bool MouseMovementController::Load(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const LoadFlags flags)
{
    this->scene = scene;
    this->terrainName = (String)properties["terrainName"];
    this->animationName = (String)properties["animationName"];
    for (auto e : scene->entities) {
        if (e->name == terrainName)
        {
            this->terrain = std::static_pointer_cast<Terrain>(e);
        }
    }
    return true;
}

bool MouseMovementController::Save(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const SaveFlags flags)
{
    properties["terrainName"] = (String)terrain->name;
    properties["animationName"] = (String)this->animationName;
    return true;
}

shared_ptr<Component> MouseMovementController::Copy()
{
    return std::make_shared<MouseMovementController>(*this);
}

3. JSON File

{
  "component": {
    "properties": [
      {
        "name": "terrainName",
        "label": "Terrain Name",
        "value": ""
      },
      {
        "name": "animationName",
        "label": "Animation Name",
        "value": ""
      }
    ]
  }
}

 

  • Like 3
 Share

1 Comment


Recommended Comments

I updated today the controller to a new version. I adjusted some parameters that the camera looks more like an isometric view. 

MouseMovementController.h

#pragma once
#include "UltraEngine.h"
#include "../BaseComponent.h"

using namespace UltraEngine;

class MouseMovementController : public BaseComponent
{
public: 
    std::weak_ptr<NavMesh> navmesh;
    std::shared_ptr<Camera> camera;
    std::shared_ptr<NavAgent> navagent;

    string runanimation;
    string idleanimation;
    Vec3 destination;

    MouseMovementController();

    virtual void Start();
    virtual void Update();
    virtual void Collide(std::shared_ptr<Entity> collidedentity, const Vec3& position, const Vec3& normal, const float speed);
    virtual bool ProcessEvent(const Event& e);
	virtual bool Load(table& properties, std::shared_ptr<Stream> binstream, std::shared_ptr<Scene> scene, const LoadFlags flags, std::shared_ptr<Object> extra);
    virtual bool Save(table& properties, std::shared_ptr<Stream> binstream, std::shared_ptr<Scene> scene, const SaveFlags flags, std::shared_ptr<Object> extra);
    virtual std::shared_ptr<Component> Copy();
    virtual std::any CallMethod(shared_ptr<Component> sender, const WString& name, const std::vector<std::any>& arguments);
};

MouseMovementController.cpp

#pragma once
#include "UltraEngine.h"
#include "MouseMovementController.h"

using namespace UltraEngine;

bool PickFilter(std::shared_ptr<Entity> entity, std::shared_ptr<Object> extra)
{
    if (entity->As<Terrain>()) {
        return true;
    }
    return false;
}

MouseMovementController::MouseMovementController()
{ 
    name = "MouseMovementController";
}

void MouseMovementController::Start()
{
    //Listen(EVENT_KEYDOWN, NULL);// makes this component listen for keydown events from all windows
    auto entity = GetEntity();
    ///Create the camera and attach them to the player character
    this->camera = CreateCamera(entity->GetWorld());
    Vec3 currentcameraposition = TransformPoint(0, 15, 5, entity, nullptr);
    //this->camera->SetParent(entity);
    this->camera->SetPosition(currentcameraposition, true);
    this->camera->SetRotation(Vec3(45, 0, 0));
    this->camera->SetFov(40);
    this->camera->Point(entity);

    auto navmesh = this->navmesh.lock();
    this->navagent = CreateNavAgent(navmesh);
    this->navagent->SetPosition(entity->GetPosition(true));
    this->navagent->SetMaxSpeed(5.0f);
    entity->SetPosition(0, 0, 0);
    entity->SetRotation(0, 0, 0);
    entity->Attach(this->navagent);
}

void MouseMovementController::Update()
{
    auto entity = GetEntity();
    auto window = ActiveWindow();
    auto world = entity->GetWorld();

    if (window) {
        ///Set the destination if the player hits the left mouse button
        if (window->MouseDown(MOUSE_LEFT)) {
            auto mousePosition = window->GetMousePosition();
            auto pick = this->camera->Pick(window->GetFramebuffer(), mousePosition.x, mousePosition.y, 0, false, PickFilter);
            if (pick.success)
            {
                this->destination = pick.position;
            }
        }

        ///Move to the destination if the destination is not null
        if (this->destination != NULL) {
            if (entity->position.DistanceToPoint(this->destination) > 1.0f)
            {
                this->navagent->Navigate(this->destination);
                auto model = entity->As<Model>();
                model->Animate(this->runanimation);
            }
            else {
                this->navagent->Stop();
                auto model = entity->As<Model>();
                model->Animate(idleanimation);
                this->destination == NULL;
            }
        }


        Vec3 offset = Vec3(-10, 15, -10);
        Vec3 currentcameraposition = entity->position + offset;
        this->camera->SetPosition(currentcameraposition, true);
        this->camera->Point(entity);
    }
}

void MouseMovementController::Collide(shared_ptr<Entity> collidedentity, const Vec3& position, const Vec3& normal, const float speed)
{
    
}

bool MouseMovementController::ProcessEvent(const Event& e)
{
    /*switch (e.id)
    {
    case EVENT_KEYDOWN:
        if (e.data == KEY_SPACE)
        {
            Print("Space key pressed");
        }
        break;
    }*/
    return true;
}

bool MouseMovementController::Load(table& properties, shared_ptr<Stream> binstream, shared_ptr<Scene> scene, const LoadFlags flags, std::shared_ptr<Object> extra)
{
    this->runanimation = (String)properties["runanimation"];
    this->idleanimation = (String)properties["idleanimation"];

    auto navmesh_entity = scene->GetEntity((String)properties["navmesh"]);
    this->navmesh = scene->navmeshes[(int)navmesh_entity->properties["navmesh"]];

    //return true;
    return BaseComponent::Load(properties, binstream, scene, flags, extra);
}

bool MouseMovementController::Save(table& properties, shared_ptr<Stream> binstream, shared_ptr<Scene> scene, const SaveFlags flags, std::shared_ptr<Object> extra)
{
    return BaseComponent::Save(properties, binstream, scene, flags, extra);
}

//This method will work with simple components
shared_ptr<Component> MouseMovementController::Copy()
{
    return std::make_shared<MouseMovementController>(*this);
}

std::any MouseMovementController::CallMethod(shared_ptr<Component> sender, const WString& name, const std::vector<std::any>& arguments)
{
    /*if (name == "MyMethod")
    {
        MyMethod();
        return false;
    }*/
    return BaseComponent::CallMethod(sender, name, arguments);
}

MouseMovementController.json

{
    "component":
    {
        "properties":
        [
            {
                "name": "runanimation",
                "label": "Run Animation",
                "value": ""
            },
            {
                "name": "idleanimation",
                "label": "Idle Animation",
                "value": ""
            },
            {
                "name": "navmesh",
                "label": "Navmesh",
                "value": null
            }
        ]
    }
}

Here you can find how it looks now: Watch 2024-12-12 17-12-32 | Streamable

Link to comment
Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...