The component system in SunBurn is really quite an amazing concept and opens up a huge array of opportunity for the engine. Components can be added to Scene Objects; they are called every update and allow you to apply game logic directly to an object that you have added to the editor without needing to identify the object in the code and add corresponding logic.
Simple Component
Starting with a simple example I created a simple component which can be added to a Scene Object and will cause the Scene Object to start spinning slowly on the Z Axis.
[Serializable] public class SpinZSlowComponent : BaseComponentAutoSerialization<ISceneEntity> { public override void OnUpdate(GameTime gameTime) { // Calculate the elapsed rotation based on the time. float angle = (float)gameTime.ElapsedGameTime.TotalSeconds * 0.3f; Matrix rotation = Matrix.CreateRotationZ(angle); // Apply the rotation to the parent object (owner of the component). ParentObject.World = rotation * ParentObject.World; } }
Once the Component has been defined in the code, open up the editor and then right click on the Object, Chose Add Component to Objects and then select the newly defined component.
The object in the editor immediately starts executing the new game logic; obligatory static screen shots of a cube rotating; doesn’t have the same effect as video but this is just the introduction.
A rotating cube is interesting and all but considering that the code for this is really just 3 lines, we haven’t saved all that much time (unless your whole scene is going to be slowly rotating cubes, in which case we have just saved you a little bit more). What is useful though is this component can just be dropped in to the your SunBurn project and used straight away, nothing else needs to be setup or known.
Taking this a bit further; games need a method of handling player input, in a 3D game that player input will need to interact with an object (the player) – what a great opportunity for a component.
Player Component
This component demonstrates how to take player input, via the keyboard and then create the relative updates and rotations for the player. I, J, K, L are mapped to Forward, Left, Backwards and Right respectively. U & O are mapped to rotations, Left and Right respectively. A single variable, forceToApply is looking up an external value, which we will discuss shortly but you can put a direct value in here if you want as well.
[Serializable] public class PlayerMovement_Keyboard_Editor_Component : BaseComponentAutoSerialization<ISceneEntity> { public override void OnUpdate(GameTime gameTime) { KeyboardState keyboard = Keyboard.GetState(); // Cast the Object the component is attached to, to a scene object SceneObject sceneobject = (SceneObject)ParentObject; // Create a new set of vectors to store the movements Vector3 additionalForce = new Vector3(); float forceToApply = PassThrough.PlayerForce; // Move Forward if (keyboard.IsKeyDown(Keys.I)) { additionalForce.Z = -forceToApply; } // Move Backwards if (keyboard.IsKeyDown(Keys.K)) { additionalForce.Z = forceToApply; } // Slide Left if (keyboard.IsKeyDown(Keys.J)) { additionalForce.X = -forceToApply; } // Slide Right if (keyboard.IsKeyDown(Keys.L)) { additionalForce.X = forceToApply; } // Rotate to the Left if (keyboard.IsKeyDown(Keys.U)) { float angle = (float)gameTime.ElapsedGameTime.TotalSeconds * forceToApply; Matrix rotation = Matrix.CreateRotationY(angle); sceneobject.World = rotation * sceneobject.World; } // Rotate to the Right if (keyboard.IsKeyDown(Keys.O)) { float angle = (float)gameTime.ElapsedGameTime.TotalSeconds * -forceToApply; Matrix rotation = Matrix.CreateRotationY(angle); sceneobject.World = rotation * sceneobject.World; } // Defy Gravity - Float Upwards if (keyboard.IsKeyDown(Keys.Space)) { additionalForce.Y = forceToApply; } // Apply the new force to the Object that the component is attached to sceneobject.CollisionMove.ApplyObjectForce(additionalForce); } }
Adding this component to an object in the editor allows the object to immediately receive keyboard input and move around the world space. That’s a reusable component for sure.
During testing of this component I found that depending on the scale I was working with I got mixed results due to the way that objects slide along surfaces, the bigger the object the less force I needed to apply (I could have used the materials and dropped down the Friction on the materials..) to assist this testing, without needing to modify every other object in the scene, we can use a Custom Scene Entity to provide input to modify the variable without needing to go back to the component code and edit directly.
A Short Cut
That variable I mentioned earlier, it was referring to a static variable so that it can be updated outside of the component. I’m sure there is a more elegant solution to this, however I’m not actually sure what that solution is ![]()
public static class PassThrough { // This static variable is used to marshal the values between the Custom Editor SceneEntity and the Component // It also holds the default value, so if the component is added without the Custom Editor SceneEntity it will still do something. public static float PlayerForce = 0.5f; }
Custom Editor Scene Entity
Enabling a variable to be updated during run time can be achieved through the use of a Editor Created Object, it’s derived from Scene Entity and once added to a scene through the editor it can then be used to update variables. Using this process, when the scene object is updated, we can update the related static variable and change the amount of force that is applied with the player movements.
The Editor Created Object is also [Serializable] so the updates through the UI will be persisted between the various edits and runs.
[Serializable] [EditorCreatedObject] public class PlayerForce_Entity : SceneEntity { private float _PlayerForce = 0.5f; [EditorProperty(true, Description = "PlayerForce", ToolTipText = "This is the amount of force to use when the player is moving. Only add one to the scene")] public float PlayerForce { get { return _PlayerForce; } set { _PlayerForce = value; PassThrough.PlayerForce = value; } } public override void SetObjectData(SerializationInfo info, StreamingContext c) { base.SetObjectData(info, c); SerializationHelper.DeserializeField(ref _PlayerForce, info, "PlayerForce", true); PassThrough.PlayerForce = _PlayerForce; } public override void GetObjectData(SerializationInfo info, StreamingContext c) { base.GetObjectData(info, c); info.AddValue("PlayerForce", _PlayerForce); } }
The Custom Editor Scene Object now just needs to be added to the scene like a normal object would.
Putting-It-All-Together
A short obligatory video of how quickly these components can be added to a new project and have a player model moving around interacting with the objects.
Hope that this encourages you to build some components and share them with the SunBurn community.
Great tutorials! Learning all these “ins and outs” for Sunburn hasn’t been easy, so these tutorials have been great. Thanks!
Pingback: DSebJ's SunBurn Tutorials - Community Blogs - Synapse Gaming
Hi,
excellent tutorials. I really like it! It’s a pity that SunBurn doesn’t have complete documentation like this.
I would like to add one think:
I like the component concept. There are some black boxes which have some functionality. We can add this functionality to different scene objects. The main advantage is(my opinion) that there aren’t dependencies between this component objects and another objects. You’ve broken this advantage -> you have created dependence between PlayerMovement_Keyboard_Editor_Component and PassThrough static class. If you have more static variables in the PassThrough class and more components with dependencies to PassThrough, you will be lost in the mess. Also if you delete some variables from PassThrough, it can cause, that you will have to rewrite code in your components because you will not be able to run your game anymore.
I think that better solution is implement static PlayerForce variable into PlayerMovement_Keyboard_Editor_Component and make it public(create property for it). This will not create any dependencies between component and another objects.
Sorry for my English.
Thank you again for awesome tutorial.
Great tutorial! Thanks so much for spending the time to write this.
One question… I can’t find the SerializationHelper class in the .net libraries. Any chance you could point me in the right direction?
Thanks.
Never mind… I was looking in the wrong place… it’s under…
… SynapseGaming.LightingSystem.Serialization;
Where does PlayerForce_Entity : SceneEntity supposed to go?
It just goes in your game class.
Pingback: Show me the Player! | DSebJ
Hi,
I am always getting the BaseComponentAutoSerialization not found error and I am using Sunburn Pro 2.0.17…. Where should I place this class and which headers should I use? Following are the headers file that I am currently using.
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
// Include the necessary SunBurn namespaces.
using SynapseGaming;
using SynapseGaming.LightingSystem.Serialization;
using SynapseGaming.LightingSystem.Core;
using SynapseGaming.LightingSystem.Lights;
using SynapseGaming.LightingSystem.Rendering;
using SynapseGaming.LightingSystem.Rendering.Forward;
using SynapseGaming.LightingSystem.Shadows;
#if !SUNBURN_FRAMEWORK
using SynapseGaming.LightingSystem.Editor;
#endif
You don’t need the conditional if statement.
In 2.0.7.16 the components were moved into their own namespace. Add this using statement.
//New in 2.0.17.6
using SynapseGaming.LightingSystem.Components;
The class needs to be in the same name space as your game, other than that it can be anywhere in your project.
Cool.
Thanks for your help. Now the errors are gone. One more thing is that
when I create a new scene and run the project and the exception says that
“Type ‘SpinZSlowComponent’ not registered with the serialization type dictionary.”
and I switched back to my old scene, Add the SpinZ component to my object . The errors is gone but the model is not rotating.
Is it possible to get the working source code of this example? It would be really helpful… Thanks
Aww…your obligatory video saved me! I needed to drag the Scene Type from the left onto my object. Now I think it’s clicking! What does [SerializedMember] do for me anyways?