|
|
@@ -20,8 +20,6 @@
|
|
|
// THE SOFTWARE.
|
|
|
//
|
|
|
|
|
|
-#include <Urho3D/Urho3D.h>
|
|
|
-
|
|
|
#include <Urho3D/Core/CoreEvents.h>
|
|
|
#include <Urho3D/Engine/Engine.h>
|
|
|
#include <Urho3D/Graphics/Camera.h>
|
|
|
@@ -44,10 +42,8 @@
|
|
|
#include <Urho3D/Physics/PhysicsWorld.h>
|
|
|
#include <Urho3D/Physics/RigidBody.h>
|
|
|
#include <Urho3D/Resource/ResourceCache.h>
|
|
|
-#include <Urho3D/Resource/XMLFile.h>
|
|
|
#include <Urho3D/Scene/Scene.h>
|
|
|
#include <Urho3D/UI/Button.h>
|
|
|
-#include <Urho3D/UI/Cursor.h>
|
|
|
#include <Urho3D/UI/Font.h>
|
|
|
#include <Urho3D/UI/LineEdit.h>
|
|
|
#include <Urho3D/UI/Text.h>
|
|
|
@@ -85,10 +81,10 @@ void SceneReplication::Start()
|
|
|
|
|
|
// Create the scene content
|
|
|
CreateScene();
|
|
|
-
|
|
|
+
|
|
|
// Create the UI content
|
|
|
CreateUI();
|
|
|
-
|
|
|
+
|
|
|
// Setup the viewport for displaying the scene
|
|
|
SetupViewport();
|
|
|
|
|
|
@@ -99,15 +95,15 @@ void SceneReplication::Start()
|
|
|
void SceneReplication::CreateScene()
|
|
|
{
|
|
|
scene_ = new Scene(context_);
|
|
|
-
|
|
|
+
|
|
|
// Create scene content on the server only
|
|
|
ResourceCache* cache = GetSubsystem<ResourceCache>();
|
|
|
-
|
|
|
+
|
|
|
// Create octree and physics world with default settings. Create them as local so that they are not needlessly replicated
|
|
|
// when a client connects
|
|
|
scene_->CreateComponent<Octree>(LOCAL);
|
|
|
scene_->CreateComponent<PhysicsWorld>(LOCAL);
|
|
|
-
|
|
|
+
|
|
|
// All static scene content and the camera are also created as local, so that they are unaffected by scene replication and are
|
|
|
// not removed from the client upon connection. Create a Zone component first for ambient lighting & fog control.
|
|
|
Node* zoneNode = scene_->CreateChild("Zone", LOCAL);
|
|
|
@@ -116,7 +112,7 @@ void SceneReplication::CreateScene()
|
|
|
zone->SetAmbientColor(Color(0.1f, 0.1f, 0.1f));
|
|
|
zone->SetFogStart(100.0f);
|
|
|
zone->SetFogEnd(300.0f);
|
|
|
-
|
|
|
+
|
|
|
// Create a directional light without shadows
|
|
|
Node* lightNode = scene_->CreateChild("DirectionalLight", LOCAL);
|
|
|
lightNode->SetDirection(Vector3(0.5f, -1.0f, 0.5f));
|
|
|
@@ -124,7 +120,7 @@ void SceneReplication::CreateScene()
|
|
|
light->SetLightType(LIGHT_DIRECTIONAL);
|
|
|
light->SetColor(Color(0.2f, 0.2f, 0.2f));
|
|
|
light->SetSpecularIntensity(1.0f);
|
|
|
-
|
|
|
+
|
|
|
// Create a "floor" consisting of several tiles. Make the tiles physical but leave small cracks between them
|
|
|
for (int y = -20; y <= 20; ++y)
|
|
|
{
|
|
|
@@ -136,14 +132,14 @@ void SceneReplication::CreateScene()
|
|
|
StaticModel* floorObject = floorNode->CreateComponent<StaticModel>();
|
|
|
floorObject->SetModel(cache->GetResource<Model>("Models/Box.mdl"));
|
|
|
floorObject->SetMaterial(cache->GetResource<Material>("Materials/Stone.xml"));
|
|
|
-
|
|
|
+
|
|
|
RigidBody* body = floorNode->CreateComponent<RigidBody>();
|
|
|
body->SetFriction(1.0f);
|
|
|
CollisionShape* shape = floorNode->CreateComponent<CollisionShape>();
|
|
|
shape->SetBox(Vector3::ONE);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Create the camera. Limit far clip distance to match the fog
|
|
|
// The camera needs to be created into a local node so that each client can retain its own camera, that is unaffected by
|
|
|
// network messages. Furthermore, because the client removes all replicated scene nodes when connecting to a server scene,
|
|
|
@@ -152,7 +148,7 @@ void SceneReplication::CreateScene()
|
|
|
cameraNode_ = scene_->CreateChild("Camera", LOCAL);
|
|
|
Camera* camera = cameraNode_->CreateComponent<Camera>();
|
|
|
camera->SetFarClip(300.0f);
|
|
|
-
|
|
|
+
|
|
|
// Set an initial position for the camera scene node above the plane
|
|
|
cameraNode_->SetPosition(Vector3(0.0f, 5.0f, 0.0f));
|
|
|
}
|
|
|
@@ -174,7 +170,7 @@ void SceneReplication::CreateUI()
|
|
|
// Set starting position of the cursor at the rendering window center
|
|
|
Graphics* graphics = GetSubsystem<Graphics>();
|
|
|
cursor->SetPosition(graphics->GetWidth() / 2, graphics->GetHeight() / 2);
|
|
|
-
|
|
|
+
|
|
|
// Construct the instructions text element
|
|
|
instructionsText_ = ui->GetRoot()->CreateChild<Text>();
|
|
|
instructionsText_->SetText(
|
|
|
@@ -187,26 +183,26 @@ void SceneReplication::CreateUI()
|
|
|
instructionsText_->SetPosition(0, graphics->GetHeight() / 4);
|
|
|
// Hide until connected
|
|
|
instructionsText_->SetVisible(false);
|
|
|
-
|
|
|
+
|
|
|
buttonContainer_ = root->CreateChild<UIElement>();
|
|
|
buttonContainer_->SetFixedSize(500, 20);
|
|
|
buttonContainer_->SetPosition(20, 20);
|
|
|
buttonContainer_->SetLayoutMode(LM_HORIZONTAL);
|
|
|
-
|
|
|
+
|
|
|
textEdit_ = buttonContainer_->CreateChild<LineEdit>();
|
|
|
textEdit_->SetStyleAuto();
|
|
|
-
|
|
|
+
|
|
|
connectButton_ = CreateButton("Connect", 90);
|
|
|
disconnectButton_ = CreateButton("Disconnect", 100);
|
|
|
startServerButton_ = CreateButton("Start Server", 110);
|
|
|
-
|
|
|
+
|
|
|
UpdateButtons();
|
|
|
}
|
|
|
|
|
|
void SceneReplication::SetupViewport()
|
|
|
{
|
|
|
Renderer* renderer = GetSubsystem<Renderer>();
|
|
|
-
|
|
|
+
|
|
|
// Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
|
|
|
SharedPtr<Viewport> viewport(new Viewport(context_, scene_, cameraNode_->GetComponent<Camera>()));
|
|
|
renderer->SetViewport(0, viewport);
|
|
|
@@ -216,12 +212,12 @@ void SceneReplication::SubscribeToEvents()
|
|
|
{
|
|
|
// Subscribe to fixed timestep physics updates for setting or applying controls
|
|
|
SubscribeToEvent(E_PHYSICSPRESTEP, HANDLER(SceneReplication, HandlePhysicsPreStep));
|
|
|
-
|
|
|
+
|
|
|
// Subscribe HandlePostUpdate() method for processing update events. Subscribe to PostUpdate instead
|
|
|
// of the usual Update so that physics simulation has already proceeded for the frame, and can
|
|
|
// accurately follow the object with the camera
|
|
|
SubscribeToEvent(E_POSTUPDATE, HANDLER(SceneReplication, HandlePostUpdate));
|
|
|
-
|
|
|
+
|
|
|
// Subscribe to button actions
|
|
|
SubscribeToEvent(connectButton_, E_RELEASED, HANDLER(SceneReplication, HandleConnect));
|
|
|
SubscribeToEvent(disconnectButton_, E_RELEASED, HANDLER(SceneReplication, HandleDisconnect));
|
|
|
@@ -243,16 +239,16 @@ Button* SceneReplication::CreateButton(const String& text, int width)
|
|
|
{
|
|
|
ResourceCache* cache = GetSubsystem<ResourceCache>();
|
|
|
Font* font = cache->GetResource<Font>("Fonts/Anonymous Pro.ttf");
|
|
|
-
|
|
|
+
|
|
|
Button* button = buttonContainer_->CreateChild<Button>();
|
|
|
button->SetStyleAuto();
|
|
|
button->SetFixedWidth(width);
|
|
|
-
|
|
|
+
|
|
|
Text* buttonText = button->CreateChild<Text>();
|
|
|
buttonText->SetFont(font, 12);
|
|
|
buttonText->SetAlignment(HA_CENTER, VA_CENTER);
|
|
|
buttonText->SetText(text);
|
|
|
-
|
|
|
+
|
|
|
return button;
|
|
|
}
|
|
|
|
|
|
@@ -261,7 +257,7 @@ void SceneReplication::UpdateButtons()
|
|
|
Network* network = GetSubsystem<Network>();
|
|
|
Connection* serverConnection = network->GetServerConnection();
|
|
|
bool serverRunning = network->IsServerRunning();
|
|
|
-
|
|
|
+
|
|
|
// Show and hide buttons so that eg. Connect and Disconnect are never shown at the same time
|
|
|
connectButton_->SetVisible(!serverConnection && !serverRunning);
|
|
|
disconnectButton_->SetVisible(serverConnection || serverRunning);
|
|
|
@@ -272,7 +268,7 @@ void SceneReplication::UpdateButtons()
|
|
|
Node* SceneReplication::CreateControllableObject()
|
|
|
{
|
|
|
ResourceCache* cache = GetSubsystem<ResourceCache>();
|
|
|
-
|
|
|
+
|
|
|
// Create the scene node & visual representation. This will be a replicated object
|
|
|
Node* ballNode = scene_->CreateChild("Ball");
|
|
|
ballNode->SetPosition(Vector3(Random(40.0f) - 20.0f, 5.0f, Random(40.0f) - 20.0f));
|
|
|
@@ -280,7 +276,7 @@ Node* SceneReplication::CreateControllableObject()
|
|
|
StaticModel* ballObject = ballNode->CreateComponent<StaticModel>();
|
|
|
ballObject->SetModel(cache->GetResource<Model>("Models/Sphere.mdl"));
|
|
|
ballObject->SetMaterial(cache->GetResource<Material>("Materials/StoneSmall.xml"));
|
|
|
-
|
|
|
+
|
|
|
// Create the physics components
|
|
|
RigidBody* body = ballNode->CreateComponent<RigidBody>();
|
|
|
body->SetMass(1.0f);
|
|
|
@@ -290,12 +286,12 @@ Node* SceneReplication::CreateControllableObject()
|
|
|
body->SetAngularDamping(0.5f);
|
|
|
CollisionShape* shape = ballNode->CreateComponent<CollisionShape>();
|
|
|
shape->SetSphere(1.0f);
|
|
|
-
|
|
|
+
|
|
|
// Create a random colored point light at the ball so that can see better where is going
|
|
|
Light* light = ballNode->CreateComponent<Light>();
|
|
|
light->SetRange(3.0f);
|
|
|
light->SetColor(Color(0.5f + (Rand() & 1) * 0.5f, 0.5f + (Rand() & 1) * 0.5f, 0.5f + (Rand() & 1) * 0.5f));
|
|
|
-
|
|
|
+
|
|
|
return ballNode;
|
|
|
}
|
|
|
|
|
|
@@ -305,10 +301,10 @@ void SceneReplication::MoveCamera()
|
|
|
UI* ui = GetSubsystem<UI>();
|
|
|
Input* input = GetSubsystem<Input>();
|
|
|
ui->GetCursor()->SetVisible(!input->GetMouseButtonDown(MOUSEB_RIGHT));
|
|
|
-
|
|
|
+
|
|
|
// Mouse sensitivity as degrees per pixel
|
|
|
const float MOUSE_SENSITIVITY = 0.1f;
|
|
|
-
|
|
|
+
|
|
|
// Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch and only move the camera
|
|
|
// when the cursor is hidden
|
|
|
if (!ui->GetCursor()->IsVisible())
|
|
|
@@ -318,10 +314,10 @@ void SceneReplication::MoveCamera()
|
|
|
pitch_ += MOUSE_SENSITIVITY * mouseMove.y_;
|
|
|
pitch_ = Clamp(pitch_, 1.0f, 90.0f);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
|
|
|
cameraNode_->SetRotation(Quaternion(pitch_, yaw_, 0.0f));
|
|
|
-
|
|
|
+
|
|
|
// Only move the camera / show instructions if we have a controllable object
|
|
|
bool showInstructions = false;
|
|
|
if (clientObjectID_)
|
|
|
@@ -330,13 +326,13 @@ void SceneReplication::MoveCamera()
|
|
|
if (ballNode)
|
|
|
{
|
|
|
const float CAMERA_DISTANCE = 5.0f;
|
|
|
-
|
|
|
+
|
|
|
// Move camera some distance away from the ball
|
|
|
cameraNode_->SetPosition(ballNode->GetPosition() + cameraNode_->GetRotation() * Vector3::BACK * CAMERA_DISTANCE);
|
|
|
showInstructions = true;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
instructionsText_->SetVisible(showInstructions);
|
|
|
}
|
|
|
|
|
|
@@ -353,17 +349,17 @@ void SceneReplication::HandlePhysicsPreStep(StringHash eventType, VariantMap& ev
|
|
|
// fixed rate, by default 30 FPS. The server will actually apply the controls (authoritative simulation.)
|
|
|
Network* network = GetSubsystem<Network>();
|
|
|
Connection* serverConnection = network->GetServerConnection();
|
|
|
-
|
|
|
+
|
|
|
// Client: collect controls
|
|
|
if (serverConnection)
|
|
|
{
|
|
|
UI* ui = GetSubsystem<UI>();
|
|
|
Input* input = GetSubsystem<Input>();
|
|
|
Controls controls;
|
|
|
-
|
|
|
+
|
|
|
// Copy mouse yaw
|
|
|
controls.yaw_ = yaw_;
|
|
|
-
|
|
|
+
|
|
|
// Only apply WASD controls if there is no focused UI element
|
|
|
if (!ui->GetFocusElement())
|
|
|
{
|
|
|
@@ -372,7 +368,7 @@ void SceneReplication::HandlePhysicsPreStep(StringHash eventType, VariantMap& ev
|
|
|
controls.Set(CTRL_LEFT, input->GetKeyDown('A'));
|
|
|
controls.Set(CTRL_RIGHT, input->GetKeyDown('D'));
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
serverConnection->SetControls(controls);
|
|
|
// In case the server wants to do position-based interest management using the NetworkPriority components, we should also
|
|
|
// tell it our observer (camera) position. In this sample it is not in use, but eg. the NinjaSnowWar game uses it
|
|
|
@@ -382,7 +378,7 @@ void SceneReplication::HandlePhysicsPreStep(StringHash eventType, VariantMap& ev
|
|
|
else if (network->IsServerRunning())
|
|
|
{
|
|
|
const Vector<SharedPtr<Connection> >& connections = network->GetClientConnections();
|
|
|
-
|
|
|
+
|
|
|
for (unsigned i = 0; i < connections.Size(); ++i)
|
|
|
{
|
|
|
Connection* connection = connections[i];
|
|
|
@@ -390,16 +386,16 @@ void SceneReplication::HandlePhysicsPreStep(StringHash eventType, VariantMap& ev
|
|
|
Node* ballNode = serverObjects_[connection];
|
|
|
if (!ballNode)
|
|
|
continue;
|
|
|
-
|
|
|
+
|
|
|
RigidBody* body = ballNode->GetComponent<RigidBody>();
|
|
|
-
|
|
|
+
|
|
|
// Get the last controls sent by the client
|
|
|
const Controls& controls = connection->GetControls();
|
|
|
// Torque is relative to the forward vector
|
|
|
Quaternion rotation(0.0f, controls.yaw_, 0.0f);
|
|
|
-
|
|
|
+
|
|
|
const float MOVE_TORQUE = 3.0f;
|
|
|
-
|
|
|
+
|
|
|
// Movement torque is applied before each simulation step, which happen at 60 FPS. This makes the simulation
|
|
|
// independent from rendering framerate. We could also apply forces (which would enable in-air control),
|
|
|
// but want to emphasize that it's a ball which should only control its motion by rolling along the ground
|
|
|
@@ -421,11 +417,11 @@ void SceneReplication::HandleConnect(StringHash eventType, VariantMap& eventData
|
|
|
String address = textEdit_->GetText().Trimmed();
|
|
|
if (address.Empty())
|
|
|
address = "localhost"; // Use localhost to connect if nothing else specified
|
|
|
-
|
|
|
+
|
|
|
// Connect to server, specify scene to use as a client for replication
|
|
|
clientObjectID_ = 0; // Reset own object ID from possible previous connection
|
|
|
network->Connect(address, SERVER_PORT, scene_);
|
|
|
-
|
|
|
+
|
|
|
UpdateButtons();
|
|
|
}
|
|
|
|
|
|
@@ -447,7 +443,7 @@ void SceneReplication::HandleDisconnect(StringHash eventType, VariantMap& eventD
|
|
|
network->StopServer();
|
|
|
scene_->Clear(true, false);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
UpdateButtons();
|
|
|
}
|
|
|
|
|
|
@@ -455,7 +451,7 @@ void SceneReplication::HandleStartServer(StringHash eventType, VariantMap& event
|
|
|
{
|
|
|
Network* network = GetSubsystem<Network>();
|
|
|
network->StartServer(SERVER_PORT);
|
|
|
-
|
|
|
+
|
|
|
UpdateButtons();
|
|
|
}
|
|
|
|
|
|
@@ -467,15 +463,15 @@ void SceneReplication::HandleConnectionStatus(StringHash eventType, VariantMap&
|
|
|
void SceneReplication::HandleClientConnected(StringHash eventType, VariantMap& eventData)
|
|
|
{
|
|
|
using namespace ClientConnected;
|
|
|
-
|
|
|
+
|
|
|
// When a client connects, assign to scene to begin scene replication
|
|
|
Connection* newConnection = static_cast<Connection*>(eventData[P_CONNECTION].GetPtr());
|
|
|
newConnection->SetScene(scene_);
|
|
|
-
|
|
|
+
|
|
|
// Then create a controllable object for that client
|
|
|
Node* newObject = CreateControllableObject();
|
|
|
serverObjects_[newConnection] = newObject;
|
|
|
-
|
|
|
+
|
|
|
// Finally send the object's node ID using a remote event
|
|
|
VariantMap remoteEventData;
|
|
|
remoteEventData[P_ID] = newObject->GetID();
|
|
|
@@ -485,13 +481,13 @@ void SceneReplication::HandleClientConnected(StringHash eventType, VariantMap& e
|
|
|
void SceneReplication::HandleClientDisconnected(StringHash eventType, VariantMap& eventData)
|
|
|
{
|
|
|
using namespace ClientConnected;
|
|
|
-
|
|
|
+
|
|
|
// When a client disconnects, remove the controlled object
|
|
|
Connection* connection = static_cast<Connection*>(eventData[P_CONNECTION].GetPtr());
|
|
|
Node* object = serverObjects_[connection];
|
|
|
if (object)
|
|
|
object->Remove();
|
|
|
-
|
|
|
+
|
|
|
serverObjects_.Erase(connection);
|
|
|
}
|
|
|
|