1 /** 2 * Defines the GameObject class, to be subclassed by scripts and instantiated for static objects. 3 */ 4 module core.gameobject; 5 import core.prefabs, core.properties; 6 import components; 7 import graphics.graphics, graphics.shaders; 8 import utility.config; 9 10 import yaml; 11 import gl3n.linalg, gl3n.math; 12 13 import std.signals, std.conv, std.variant; 14 15 class GameObject 16 { 17 public: 18 /** 19 * The current transform of the object. 20 */ 21 mixin Property!( "Transform", "transform", "public" ); 22 /** 23 * The Material belonging to the object 24 */ 25 mixin Property!( "Material", "material", "public" ); 26 /** 27 * The Mesh belonging to the object 28 */ 29 mixin Property!( "Mesh", "mesh", "public" ); 30 /** 31 * The light attached to this object 32 */ 33 mixin Property!( "Light", "light", "public" ); 34 /** 35 * The camera attached to this object 36 */ 37 mixin Property!( "Camera", "camera", "public" ); 38 /** 39 * The object that this object belongs to 40 */ 41 mixin Property!( "GameObject", "parent" ); 42 /** 43 * All of the objects which list this as parent 44 */ 45 mixin Property!( "GameObject[]", "children" ); 46 47 mixin Signal!( string, string ); 48 49 /** 50 * Create a GameObject from a Yaml node. 51 */ 52 static GameObject createFromYaml( Node yamlObj ) 53 { 54 GameObject obj; 55 Variant prop; 56 Node innerNode; 57 58 // Try to get from script 59 if( Config.tryGet!string( "Script.ClassName", prop, yamlObj ) ) 60 { 61 const ClassInfo scriptClass = ClassInfo.find( prop.get!string ); 62 63 if( Config.tryGet!string( "InstanceOf", prop, yamlObj ) ) 64 { 65 obj = Prefabs[ prop.get!string ].createInstance( scriptClass ); 66 } 67 else 68 { 69 obj = cast(GameObject)scriptClass.create(); 70 } 71 } 72 73 if( Config.tryGet!string( "InstanceOf", prop, yamlObj ) ) 74 { 75 obj = Prefabs[ prop.get!string ].createInstance(); 76 } 77 else 78 { 79 obj = new GameObject; 80 } 81 82 if( Config.tryGet!string( "Camera", prop, yamlObj ) ) 83 { 84 auto cam = new Camera; 85 obj.addComponent( cam ); 86 cam.owner = obj; 87 } 88 89 if( Config.tryGet!string( "Material", prop, yamlObj ) ) 90 { 91 obj.addComponent( Assets.get!Material( prop.get!string ) ); 92 } 93 94 if( Config.tryGet!string( "Mesh", prop, yamlObj ) ) 95 { 96 obj.addComponent( Assets.get!Mesh( prop.get!string ) ); 97 } 98 99 if( Config.tryGet( "Transform", innerNode, yamlObj ) ) 100 { 101 vec3 transVec; 102 if( Config.tryGet( "Scale", transVec, innerNode ) ) 103 obj.transform.scale = transVec; 104 if( Config.tryGet( "Position", transVec, innerNode ) ) 105 obj.transform.position = transVec; 106 if( Config.tryGet( "Rotation", transVec, innerNode ) ) 107 obj.transform.rotation = quat.euler_rotation( radians(transVec.y), radians(transVec.z), radians(transVec.x) ); 108 } 109 110 if( Config.tryGet!Light( "Light", prop, yamlObj ) ) 111 { 112 obj.addComponent( prop.get!Light ); 113 } 114 115 obj.transform.updateMatrix(); 116 return obj; 117 } 118 119 /** 120 * Creates basic GameObject with transform and connection to transform's emitter. 121 */ 122 this() 123 { 124 transform = new Transform( this ); 125 transform.connect( &emit ); 126 } 127 128 ~this() 129 { 130 destroy( transform ); 131 } 132 133 /** 134 * Called once per frame to update all components. 135 */ 136 final void update() 137 { 138 onUpdate(); 139 140 foreach( ci, component; componentList ) 141 component.update(); 142 } 143 144 /** 145 * Called once per frame to draw all components. 146 */ 147 final void draw() 148 { 149 onDraw(); 150 151 if( mesh !is null ) 152 { 153 Graphics.drawObject( this ); 154 } 155 if( light !is null ) 156 { 157 Graphics.addLight( light ); 158 } 159 } 160 161 /** 162 * Called when the game is shutting down, to shutdown all components. 163 */ 164 final void shutdown() 165 { 166 onShutdown(); 167 168 /*foreach_reverse( ci, component; componentList ) 169 { 170 component.shutdown(); 171 componentList.remove( ci ); 172 }*/ 173 } 174 175 /** 176 * Adds a component to the object. 177 */ 178 final void addComponent( T )( T newComponent ) if( is( T : Component ) ) 179 { 180 componentList[ T.classinfo ] = newComponent; 181 182 // Add component to proper property 183 if( typeid( newComponent ) == typeid( Material ) ) 184 material = cast(Material)newComponent; 185 else if( typeid( newComponent ) == typeid( Mesh ) ) 186 mesh = cast(Mesh)newComponent; 187 else if( typeid( newComponent ) == typeid( DirectionalLight ) || 188 typeid( newComponent ) == typeid( AmbientLight ) ) 189 light = cast(Light)newComponent; 190 else if( typeid( newComponent ) == typeid( Camera ) ) 191 camera = cast(Camera)newComponent; 192 } 193 194 /** 195 * Gets a component of the given type. 196 */ 197 final T getComponent( T )() if( is( T : Component ) ) 198 { 199 return componentList[ T.classinfo ]; 200 } 201 202 final void addChild( GameObject object ) 203 { 204 object._children ~= object; 205 object.parent = this; 206 } 207 208 /// Called on the update cycle. 209 void onUpdate() { } 210 /// Called on the draw cycle. 211 void onDraw() { } 212 /// Called on shutdown. 213 void onShutdown() { } 214 /// Called when the object collides with another object. 215 void onCollision( GameObject other ) { } 216 217 private: 218 Component[ClassInfo] componentList; 219 } 220 221 class Transform 222 { 223 public: 224 this( GameObject obj = null ) 225 { 226 owner = obj; 227 position = vec3(0,0,0); 228 scale = vec3(1,1,1); 229 rotation = quat.identity; 230 updateMatrix(); 231 } 232 233 ~this() 234 { 235 //destroy( position ); 236 //destroy( rotation ); 237 //destroy( scale ); 238 } 239 240 mixin Property!( "GameObject", "owner" ); 241 vec3 position; 242 quat rotation; 243 vec3 scale; 244 //mixin EmmittingProperty!( "vec3", "position", "public" ); 245 //mixin EmmittingProperty!( "quat", "rotation", "public" ); 246 //mixin EmmittingProperty!( "vec3", "scale", "public" ); 247 248 /** 249 * This returns the object's position relative to the world origin, not the parent 250 */ 251 final @property vec3 worldPosition() 252 { 253 if( owner.parent is null ) 254 return position; 255 else 256 return owner.parent.transform.worldPosition + position; 257 } 258 259 /** 260 * This returns the object's rotation relative to the world origin, not the parent 261 */ 262 final @property quat worldRotation() 263 { 264 if( owner.parent is null ) 265 return rotation; 266 else 267 return owner.parent.transform.worldRotation * rotation; 268 } 269 270 final @property mat4 matrix() 271 { 272 if( _matrixIsDirty ) 273 updateMatrix(); 274 275 if( owner.parent is null ) 276 return _matrix; 277 else 278 return owner.parent.transform.matrix * _matrix; 279 } 280 281 mixin Signal!( string, string ); 282 283 final void updateMatrix() 284 { 285 _matrix = mat4.identity; 286 // Scale 287 _matrix.scale([scale.x, scale.y, scale.z]); 288 //Rotate 289 _matrix.rotation( rotation.to_matrix!( 3, 3 ) ); 290 // Translate 291 _matrix.translation([position.x, position.y, position.z]); 292 293 _matrixIsDirty = false; 294 } 295 296 private: 297 mat4 _matrix; 298 bool _matrixIsDirty; 299 300 final void setMatrixDirty( string prop, string newVal ) 301 { 302 _matrixIsDirty = true; 303 } 304 }