如何重置/重启一个Urho3D应用场景到初始状态?
How to reset / restart a Urho3D application scene to its initial state?
我想在用户按键时将我的场景重置为原始状态,以方便测试或允许用户重新启动游戏。
更准确地说,如何组织我的代码,以及我必须放入什么:
void HandleKeyDown(StringHash /*eventType*/, VariantMap& eventData) {
using namespace KeyDown;
int key = eventData[P_KEY].GetInt();
if (key == KEY_R) {
// TODO
}
}
要重新启动应用程序?
这是一个完整的可运行示例,我在其中尝试创建一个类似乒乓球的游戏:
#include <iostream>
#include <Urho3D/Core/CoreEvents.h>
#include <Urho3D/Core/Object.h>
#include <Urho3D/Engine/Application.h>
#include <Urho3D/Engine/Engine.h>
#include <Urho3D/Engine/EngineDefs.h>
#include <Urho3D/Graphics/Camera.h>
#include <Urho3D/Graphics/DebugRenderer.h>
#include <Urho3D/Graphics/Graphics.h>
#include <Urho3D/Graphics/Octree.h>
#include <Urho3D/Graphics/Renderer.h>
#include <Urho3D/IO/File.h>
#include <Urho3D/IO/FileSystem.h>
#include <Urho3D/IO/MemoryBuffer.h>
#include <Urho3D/Input/Input.h>
#include <Urho3D/Input/InputEvents.h>
#include <Urho3D/Resource/ResourceCache.h>
#include <Urho3D/Scene/Node.h>
#include <Urho3D/Scene/Scene.h>
#include <Urho3D/Scene/SceneEvents.h>
#include <Urho3D/UI/Font.h>
#include <Urho3D/UI/Text.h>
#include <Urho3D/UI/UI.h>
#include <Urho3D/Urho2D/CollisionBox2D.h>
#include <Urho3D/Urho2D/CollisionCircle2D.h>
#include <Urho3D/Urho2D/ConstraintPrismatic2D.h>
#include <Urho3D/Urho2D/PhysicsEvents2D.h>
#include <Urho3D/Urho2D/PhysicsWorld2D.h>
#include <Urho3D/Urho2D/RigidBody2D.h>
using namespace Urho3D;
class Main : public Application {
URHO3D_OBJECT(Main, Application);
public:
Main(Context* context) : Application(context) {}
virtual void Setup() override {
engineParameters_[EP_FULL_SCREEN] = false;
engineParameters_[EP_WINDOW_TITLE] = __FILE__;
engineParameters_[EP_WINDOW_HEIGHT] = 512;
engineParameters_[EP_WINDOW_WIDTH] = 512;
}
void Start() {
auto windowHeight = this->windowWidth;
auto wallLength = this->windowWidth;
auto wallWidth = this->windowWidth / 20.0f;
auto ballRadius = this->windowWidth / 20.0f;
auto ballRestitution = 1.0f;
auto playerLength = windowHeight / 4.0f;
this->score = 0;
this->steps = 0;
this->input = this->GetSubsystem<Input>();
// Events
SubscribeToEvent(E_KEYDOWN, URHO3D_HANDLER(Main, HandleKeyDown));
SubscribeToEvent(E_PHYSICSBEGINCONTACT2D, URHO3D_HANDLER(Main, HandlePhysicsBeginContact2D));
SubscribeToEvent(E_POSTRENDERUPDATE, URHO3D_HANDLER(Main, HandlePostRenderUpdate));
SubscribeToEvent(E_UPDATE, URHO3D_HANDLER(Main, HandleUpdate));
// Scene
this->scene = new Scene(this->context_);
this->scene->CreateComponent<Octree>();
this->scene->CreateComponent<DebugRenderer>();
this->scene->CreateComponent<PhysicsWorld2D>();
auto physicsWorld = scene->GetComponent<PhysicsWorld2D>();
physicsWorld->SetGravity(Vector2(0.0f, 0.0f));
this->physicsWorld = this->scene->GetComponent<PhysicsWorld2D>();
// Graphics
auto cameraNode = this->scene->CreateChild("Camera");
cameraNode->SetPosition(Vector3(windowWidth / 2.0f, windowHeight / 2.0f, -1.0f));
auto camera = cameraNode->CreateComponent<Camera>();
camera->SetOrthographic(true);
camera->SetOrthoSize(windowWidth);
auto renderer = GetSubsystem<Renderer>();
SharedPtr<Viewport> viewport(new Viewport(context_, this->scene, cameraNode->GetComponent<Camera>()));
renderer->SetViewport(0, viewport);
// Score
ResourceCache *cache = this->GetSubsystem<ResourceCache>();
auto ui = this->GetSubsystem<UI>();
this->text = ui->GetRoot()->CreateChild<Text>();
this->text->SetText(std::to_string(this->score).c_str());
this->text->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 15);
this->text->SetHorizontalAlignment(HA_CENTER);
this->text->SetVerticalAlignment(VA_CENTER);
Node *wallNode;
RigidBody2D *wallBody;
{
wallNode = this->scene->CreateChild("BottomWall");
wallNode->SetPosition(Vector3(this->windowWidth / 2.0, wallWidth / 2.0f, 0.0f));
wallBody = wallNode->CreateComponent<RigidBody2D>();
auto box = wallNode->CreateComponent<CollisionBox2D>();
box->SetSize(Vector2(wallLength, wallWidth));
box->SetRestitution(0.0);
}
{
auto node = wallNode->Clone();
node->SetName("TopWall");
node->SetPosition(Vector3(this->windowWidth / 2.0f, windowHeight - (wallWidth / 2.0f), 0.0f));
}
{
auto& node = this->rightWallNode;
node = wallNode->Clone();
node->SetName("RightWall");
node->SetRotation(Quaternion(0.0f, 0.0f, 90.0f));
node->SetPosition(Vector3(this->windowWidth - (wallWidth / 2.0f), windowHeight / 2.0f, 0.0f));
}
{
auto& node = this->leftWallNode;
node = wallNode->Clone();
node->SetName("LeftWall");
node->SetRotation(Quaternion(0.0f, 0.0f, 90.0f));
node->SetPosition(Vector3(-wallWidth / 2.0f, windowHeight / 2.0f, 0.0f));
}
{
auto& node = this->playerNode;
node = wallNode->Clone();
node->SetName("Player");
node->SetRotation(Quaternion(0.0f, 0.0f, 90.0f));
node->SetPosition(Vector3(wallWidth / 2.0f, windowHeight / 2.0f, 0.0f));
auto body = node->GetComponent<RigidBody2D>();
body->SetBodyType(BT_DYNAMIC);
body->SetFixedRotation(true);
body->SetLinearDamping(4.0);
//auto constraint = node->CreateComponent<ConstraintPrismatic2D>();
//constraint->SetOtherBody(wallBody);
//constraint->SetAxis(Vector2(0.0f, 1.0f));
auto shape = node->GetComponent<CollisionBox2D>();
shape->SetDensity(this->playerDensity);
shape->SetFriction(1.0f);
shape->SetRestitution(0.0);
shape->SetSize(Vector2(playerLength, wallWidth));
}
{
this->ballNode = this->scene->CreateChild("Ball");
this->ballNode->SetPosition(Vector3(this->windowWidth / 4.0f, windowHeight / 2.0f, 0.0f));
auto body = this->ballNode->CreateComponent<RigidBody2D>();
body->SetBodyType(BT_DYNAMIC);
body->SetLinearVelocity(Vector2(2 * this->windowWidth, -windowHeight / 2.0f));
auto shape = this->ballNode->CreateComponent<CollisionCircle2D>();
shape->SetDensity(1.0f);
shape->SetFriction(1.0f);
shape->SetRadius(ballRadius);
shape->SetRestitution(ballRestitution);
}
}
void Stop() {}
private:
SharedPtr<Scene> scene;
Node *ballNode, *playerNode, *leftWallNode, *rightWallNode;
Text *text;
Input *input;
PhysicsWorld2D *physicsWorld;
uint64_t score, steps;
/// Larger means that the controls will react faster.
static constexpr float playerDensity = 10.0f;
static constexpr float windowWidth = 1.0f;
void HandleKeyDown(StringHash /*eventType*/, VariantMap& eventData) {
using namespace KeyDown;
int key = eventData[P_KEY].GetInt();
if (key == KEY_ESCAPE) {
engine_->Exit();
} else if (key == KEY_R) {
this->Start();
}
}
void HandlePhysicsBeginContact2D(StringHash eventType, VariantMap& eventData) {
using namespace PhysicsBeginContact2D;
auto nodea = static_cast<Node*>(eventData[P_NODEA].GetPtr());
auto nodeb = static_cast<Node*>(eventData[P_NODEB].GetPtr());
Node *otherNode;
bool isBall = false;
if (nodea == this->ballNode) {
otherNode = nodeb;
isBall = true;
}
if (nodeb == this->ballNode) {
otherNode = nodea;
isBall = true;
}
if (otherNode == this->rightWallNode) {
this->score++;
} else if (otherNode == this->leftWallNode) {
this->score = 0;
}
this->text->SetText(std::to_string(this->score).c_str());
File saveFile(this->context_, GetSubsystem<FileSystem>()->GetProgramDir() + String(this->steps) + ".xml", FILE_WRITE);
this->scene->SaveXML(saveFile);
}
void HandleUpdate(StringHash eventType, VariantMap& eventData) {
using namespace Update;
auto timeStep = eventData[P_TIMESTEP].GetFloat();
auto impuseMagnitude = this->windowWidth * this->playerDensity * timeStep * 10.0;
auto body = this->playerNode->GetComponent<RigidBody2D>();
if (this->input->GetKeyDown(KEY_DOWN)) {
body->ApplyForceToCenter(Vector2(0.0f, -impuseMagnitude), true);
} else if (this->input->GetKeyDown(KEY_UP)) {
body->ApplyForceToCenter(Vector2(0.0f, impuseMagnitude), true);
} else if (this->input->GetKeyDown(KEY_RIGHT)) {
body->ApplyForceToCenter(Vector2(impuseMagnitude, 0.0f), true);
} else if (input->GetKeyDown(KEY_LEFT)) {
body->ApplyForceToCenter(Vector2(-impuseMagnitude, 0.0f), true);
}
this->steps++;
}
void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData) {
this->physicsWorld->DrawDebugGeometry();
}
};
URHO3D_DEFINE_APPLICATION_MAIN(Main);
在我天真的尝试中,我尝试简单地从回调中调用 this->Start()
,但它并没有清除所有内容:UI
仍然显示旧分数,除了新的乐谱叠加在上面。
GitHub 上游:https://github.com/cirosantilli/Urho3D-cheat/blob/e58a643ccf855d659ee292aeb513e191e0c5bb95/pong.cpp
要清空 Scene
,请对其调用 Scene::Clear()
。这将删除所有 Scene
的 Components
和子 Node
。之后你可以调用一个重建场景的函数。
或者,您可以创建一个 自定义 重置事件,所有需要某种形式的重置的 Object
订阅他们的 HandleReset
方法。要重新启动游戏,您可以调用 SendEvent(E_RESET)
.
根据@Frode 的回答,我重新组织了我的代码:
void Start() override {
if (this->scene) {
this->scene->Clear();
} else {
this->scene = new Scene(this->context_);
// Setup text.
this->text = this->GetSubsystem<UI>()->GetRoot()->CreateChild<Text>();
// Other things that only need to be done once.
}
// Application state.
this->score = 0;
// Scene setup
{
this->scene->CreateComponent<Octree>();
this->scene->CreateChild("BottomWall");
}
}
然后:
void HandleKeyDown(StringHash /*eventType*/, VariantMap& eventData) {
using namespace KeyDown;
int key = eventData[P_KEY].GetInt();
if (key == KEY_R) {
this->Start();
}
}
我有点希望整个应用程序都有一个类似于 Clear()
的方法(特别是 UI
不会被 Scene->Clear
重置,但是这个解决方案我可以接受。
更新的工作代码位于:https://github.com/cirosantilli/Urho3D-cheat/blob/cda28fea8028f4c7a5ebb7c4d7bace4a7d78d8e8/pong.cpp
我想在用户按键时将我的场景重置为原始状态,以方便测试或允许用户重新启动游戏。
更准确地说,如何组织我的代码,以及我必须放入什么:
void HandleKeyDown(StringHash /*eventType*/, VariantMap& eventData) {
using namespace KeyDown;
int key = eventData[P_KEY].GetInt();
if (key == KEY_R) {
// TODO
}
}
要重新启动应用程序?
这是一个完整的可运行示例,我在其中尝试创建一个类似乒乓球的游戏:
#include <iostream>
#include <Urho3D/Core/CoreEvents.h>
#include <Urho3D/Core/Object.h>
#include <Urho3D/Engine/Application.h>
#include <Urho3D/Engine/Engine.h>
#include <Urho3D/Engine/EngineDefs.h>
#include <Urho3D/Graphics/Camera.h>
#include <Urho3D/Graphics/DebugRenderer.h>
#include <Urho3D/Graphics/Graphics.h>
#include <Urho3D/Graphics/Octree.h>
#include <Urho3D/Graphics/Renderer.h>
#include <Urho3D/IO/File.h>
#include <Urho3D/IO/FileSystem.h>
#include <Urho3D/IO/MemoryBuffer.h>
#include <Urho3D/Input/Input.h>
#include <Urho3D/Input/InputEvents.h>
#include <Urho3D/Resource/ResourceCache.h>
#include <Urho3D/Scene/Node.h>
#include <Urho3D/Scene/Scene.h>
#include <Urho3D/Scene/SceneEvents.h>
#include <Urho3D/UI/Font.h>
#include <Urho3D/UI/Text.h>
#include <Urho3D/UI/UI.h>
#include <Urho3D/Urho2D/CollisionBox2D.h>
#include <Urho3D/Urho2D/CollisionCircle2D.h>
#include <Urho3D/Urho2D/ConstraintPrismatic2D.h>
#include <Urho3D/Urho2D/PhysicsEvents2D.h>
#include <Urho3D/Urho2D/PhysicsWorld2D.h>
#include <Urho3D/Urho2D/RigidBody2D.h>
using namespace Urho3D;
class Main : public Application {
URHO3D_OBJECT(Main, Application);
public:
Main(Context* context) : Application(context) {}
virtual void Setup() override {
engineParameters_[EP_FULL_SCREEN] = false;
engineParameters_[EP_WINDOW_TITLE] = __FILE__;
engineParameters_[EP_WINDOW_HEIGHT] = 512;
engineParameters_[EP_WINDOW_WIDTH] = 512;
}
void Start() {
auto windowHeight = this->windowWidth;
auto wallLength = this->windowWidth;
auto wallWidth = this->windowWidth / 20.0f;
auto ballRadius = this->windowWidth / 20.0f;
auto ballRestitution = 1.0f;
auto playerLength = windowHeight / 4.0f;
this->score = 0;
this->steps = 0;
this->input = this->GetSubsystem<Input>();
// Events
SubscribeToEvent(E_KEYDOWN, URHO3D_HANDLER(Main, HandleKeyDown));
SubscribeToEvent(E_PHYSICSBEGINCONTACT2D, URHO3D_HANDLER(Main, HandlePhysicsBeginContact2D));
SubscribeToEvent(E_POSTRENDERUPDATE, URHO3D_HANDLER(Main, HandlePostRenderUpdate));
SubscribeToEvent(E_UPDATE, URHO3D_HANDLER(Main, HandleUpdate));
// Scene
this->scene = new Scene(this->context_);
this->scene->CreateComponent<Octree>();
this->scene->CreateComponent<DebugRenderer>();
this->scene->CreateComponent<PhysicsWorld2D>();
auto physicsWorld = scene->GetComponent<PhysicsWorld2D>();
physicsWorld->SetGravity(Vector2(0.0f, 0.0f));
this->physicsWorld = this->scene->GetComponent<PhysicsWorld2D>();
// Graphics
auto cameraNode = this->scene->CreateChild("Camera");
cameraNode->SetPosition(Vector3(windowWidth / 2.0f, windowHeight / 2.0f, -1.0f));
auto camera = cameraNode->CreateComponent<Camera>();
camera->SetOrthographic(true);
camera->SetOrthoSize(windowWidth);
auto renderer = GetSubsystem<Renderer>();
SharedPtr<Viewport> viewport(new Viewport(context_, this->scene, cameraNode->GetComponent<Camera>()));
renderer->SetViewport(0, viewport);
// Score
ResourceCache *cache = this->GetSubsystem<ResourceCache>();
auto ui = this->GetSubsystem<UI>();
this->text = ui->GetRoot()->CreateChild<Text>();
this->text->SetText(std::to_string(this->score).c_str());
this->text->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 15);
this->text->SetHorizontalAlignment(HA_CENTER);
this->text->SetVerticalAlignment(VA_CENTER);
Node *wallNode;
RigidBody2D *wallBody;
{
wallNode = this->scene->CreateChild("BottomWall");
wallNode->SetPosition(Vector3(this->windowWidth / 2.0, wallWidth / 2.0f, 0.0f));
wallBody = wallNode->CreateComponent<RigidBody2D>();
auto box = wallNode->CreateComponent<CollisionBox2D>();
box->SetSize(Vector2(wallLength, wallWidth));
box->SetRestitution(0.0);
}
{
auto node = wallNode->Clone();
node->SetName("TopWall");
node->SetPosition(Vector3(this->windowWidth / 2.0f, windowHeight - (wallWidth / 2.0f), 0.0f));
}
{
auto& node = this->rightWallNode;
node = wallNode->Clone();
node->SetName("RightWall");
node->SetRotation(Quaternion(0.0f, 0.0f, 90.0f));
node->SetPosition(Vector3(this->windowWidth - (wallWidth / 2.0f), windowHeight / 2.0f, 0.0f));
}
{
auto& node = this->leftWallNode;
node = wallNode->Clone();
node->SetName("LeftWall");
node->SetRotation(Quaternion(0.0f, 0.0f, 90.0f));
node->SetPosition(Vector3(-wallWidth / 2.0f, windowHeight / 2.0f, 0.0f));
}
{
auto& node = this->playerNode;
node = wallNode->Clone();
node->SetName("Player");
node->SetRotation(Quaternion(0.0f, 0.0f, 90.0f));
node->SetPosition(Vector3(wallWidth / 2.0f, windowHeight / 2.0f, 0.0f));
auto body = node->GetComponent<RigidBody2D>();
body->SetBodyType(BT_DYNAMIC);
body->SetFixedRotation(true);
body->SetLinearDamping(4.0);
//auto constraint = node->CreateComponent<ConstraintPrismatic2D>();
//constraint->SetOtherBody(wallBody);
//constraint->SetAxis(Vector2(0.0f, 1.0f));
auto shape = node->GetComponent<CollisionBox2D>();
shape->SetDensity(this->playerDensity);
shape->SetFriction(1.0f);
shape->SetRestitution(0.0);
shape->SetSize(Vector2(playerLength, wallWidth));
}
{
this->ballNode = this->scene->CreateChild("Ball");
this->ballNode->SetPosition(Vector3(this->windowWidth / 4.0f, windowHeight / 2.0f, 0.0f));
auto body = this->ballNode->CreateComponent<RigidBody2D>();
body->SetBodyType(BT_DYNAMIC);
body->SetLinearVelocity(Vector2(2 * this->windowWidth, -windowHeight / 2.0f));
auto shape = this->ballNode->CreateComponent<CollisionCircle2D>();
shape->SetDensity(1.0f);
shape->SetFriction(1.0f);
shape->SetRadius(ballRadius);
shape->SetRestitution(ballRestitution);
}
}
void Stop() {}
private:
SharedPtr<Scene> scene;
Node *ballNode, *playerNode, *leftWallNode, *rightWallNode;
Text *text;
Input *input;
PhysicsWorld2D *physicsWorld;
uint64_t score, steps;
/// Larger means that the controls will react faster.
static constexpr float playerDensity = 10.0f;
static constexpr float windowWidth = 1.0f;
void HandleKeyDown(StringHash /*eventType*/, VariantMap& eventData) {
using namespace KeyDown;
int key = eventData[P_KEY].GetInt();
if (key == KEY_ESCAPE) {
engine_->Exit();
} else if (key == KEY_R) {
this->Start();
}
}
void HandlePhysicsBeginContact2D(StringHash eventType, VariantMap& eventData) {
using namespace PhysicsBeginContact2D;
auto nodea = static_cast<Node*>(eventData[P_NODEA].GetPtr());
auto nodeb = static_cast<Node*>(eventData[P_NODEB].GetPtr());
Node *otherNode;
bool isBall = false;
if (nodea == this->ballNode) {
otherNode = nodeb;
isBall = true;
}
if (nodeb == this->ballNode) {
otherNode = nodea;
isBall = true;
}
if (otherNode == this->rightWallNode) {
this->score++;
} else if (otherNode == this->leftWallNode) {
this->score = 0;
}
this->text->SetText(std::to_string(this->score).c_str());
File saveFile(this->context_, GetSubsystem<FileSystem>()->GetProgramDir() + String(this->steps) + ".xml", FILE_WRITE);
this->scene->SaveXML(saveFile);
}
void HandleUpdate(StringHash eventType, VariantMap& eventData) {
using namespace Update;
auto timeStep = eventData[P_TIMESTEP].GetFloat();
auto impuseMagnitude = this->windowWidth * this->playerDensity * timeStep * 10.0;
auto body = this->playerNode->GetComponent<RigidBody2D>();
if (this->input->GetKeyDown(KEY_DOWN)) {
body->ApplyForceToCenter(Vector2(0.0f, -impuseMagnitude), true);
} else if (this->input->GetKeyDown(KEY_UP)) {
body->ApplyForceToCenter(Vector2(0.0f, impuseMagnitude), true);
} else if (this->input->GetKeyDown(KEY_RIGHT)) {
body->ApplyForceToCenter(Vector2(impuseMagnitude, 0.0f), true);
} else if (input->GetKeyDown(KEY_LEFT)) {
body->ApplyForceToCenter(Vector2(-impuseMagnitude, 0.0f), true);
}
this->steps++;
}
void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData) {
this->physicsWorld->DrawDebugGeometry();
}
};
URHO3D_DEFINE_APPLICATION_MAIN(Main);
在我天真的尝试中,我尝试简单地从回调中调用 this->Start()
,但它并没有清除所有内容:UI
仍然显示旧分数,除了新的乐谱叠加在上面。
GitHub 上游:https://github.com/cirosantilli/Urho3D-cheat/blob/e58a643ccf855d659ee292aeb513e191e0c5bb95/pong.cpp
要清空 Scene
,请对其调用 Scene::Clear()
。这将删除所有 Scene
的 Components
和子 Node
。之后你可以调用一个重建场景的函数。
或者,您可以创建一个 自定义 重置事件,所有需要某种形式的重置的 Object
订阅他们的 HandleReset
方法。要重新启动游戏,您可以调用 SendEvent(E_RESET)
.
根据@Frode 的回答,我重新组织了我的代码:
void Start() override {
if (this->scene) {
this->scene->Clear();
} else {
this->scene = new Scene(this->context_);
// Setup text.
this->text = this->GetSubsystem<UI>()->GetRoot()->CreateChild<Text>();
// Other things that only need to be done once.
}
// Application state.
this->score = 0;
// Scene setup
{
this->scene->CreateComponent<Octree>();
this->scene->CreateChild("BottomWall");
}
}
然后:
void HandleKeyDown(StringHash /*eventType*/, VariantMap& eventData) {
using namespace KeyDown;
int key = eventData[P_KEY].GetInt();
if (key == KEY_R) {
this->Start();
}
}
我有点希望整个应用程序都有一个类似于 Clear()
的方法(特别是 UI
不会被 Scene->Clear
重置,但是这个解决方案我可以接受。
更新的工作代码位于:https://github.com/cirosantilli/Urho3D-cheat/blob/cda28fea8028f4c7a5ebb7c4d7bace4a7d78d8e8/pong.cpp