1 module game_core.basic;
2 
3 import bubel.ecs.core;
4 import bubel.ecs.attributes;
5 
6 import ecs_utils.math.vector;
7 
8 import gui.attributes;
9 
10 import ecs_utils.utils;
11 
12 import app : launcher;
13 
14 import bindbc.sdl;
15 
16 //position component
17 struct CLocation
18 {
19     //adds some extra functionality. Not required. Will be probably removed from library in the future.
20     mixin ECS.Component;
21 
22     alias value this;//use component as it value
23 
24     //default values work properly
25     vec2 value = vec2(0);
26 }
27 
28 //scale component
29 struct CScale
30 {
31     mixin ECS.Component;
32 
33     alias value this;//use component as it value
34 
35     vec2 value = vec2(16,16);
36 }
37 
38 //rotation component
39 struct CRotation
40 {
41     mixin ECS.Component;
42 
43     alias value this;//use component as it value
44 
45     float value = 0;
46 }
47 
48 //depth component. Entity with higher depth will be rendered on top
49 struct CDepth
50 {
51     mixin ECS.Component;
52 
53     alias value this;
54 
55     short value;
56 }
57 
58 //color component
59 struct CColor
60 {
61     mixin ECS.Component;
62 
63     alias value this;
64 
65     @GUIColor uint value;
66 }
67 
68 //component used for selection
69 struct CSelected
70 {
71     mixin ECS.Component;
72 
73     bool value = false;
74 }
75 
76 //component indicating that entity should receive input from mouse, keyboard, etc.
77 struct CInput
78 {
79     mixin ECS.Component;
80 }
81 
82 //component with damping value
83 struct CDamping
84 {
85     mixin ECS.Component;
86 
87     alias value this;
88 
89     @GUIRange(0,9) byte value = 1;
90 }
91 
92 //velocity component
93 struct CVelocity
94 {
95     mixin ECS.Component;
96 
97     alias value this;
98 
99     vec2 value = vec2(0,0);
100 }
101 
102 //factor which is used for velocity masking
103 struct CVelocityFactor
104 {
105     mixin ECS.Component;
106 
107     alias value this;
108 
109     vec2 value = vec2(1);
110 }
111 
112 //flag indicating that entity is static and shouldn't be updated by most systems in every frame
113 struct CStatic
114 {
115     mixin ECS.Component;
116 }
117 
118 //system which slowing down entities
119 struct DampingSystem
120 {
121     //system will generate up to 32 jobs
122     mixin ECS.System!32;
123 
124     struct EntitiesData
125     {
126         uint length;
127         const (Entity)[] entity; //entity is readonly
128         @readonly CDamping[] damping;//damping is readonly. Marking with @readonly will help multithreading algorithm
129         CVelocity[] velocity;//velocity is wirtable as it will be modified for entities in this system
130     }
131 
132     //20 predefined damping speeds. Gives possibility to store damping as single byte.
133     float[20] damp = 0;
134 
135     bool onBegin()
136     {
137         //calculate damping values
138         foreach(i;0..20)
139         {
140             damp[i] = powf((0.99 - cast(float)i * 0.01),launcher.deltaTime*0.1);
141         }
142 
143         return true;
144     }
145 
146     void onUpdate(EntitiesData data)
147     {
148         foreach(i; 0..data.length)
149         {
150             //constantly slow down entity
151             data.velocity[i] = data.velocity[i] * damp[data.damping[i]];
152         }
153     }
154 }
155 
156 //system used for entity movement
157 struct MoveSystem
158 {
159     mixin ECS.System!64;
160 
161     struct EntitiesData
162     {
163         uint length;
164         CLocation[] location;
165         @readonly CVelocity[] velocity;
166         @optional @readonly CVelocityFactor[] vel_factor;//CVeclocityFactor is not required so entites without this component will be also updated
167     }
168 
169     void onUpdate(EntitiesData data)
170     {
171         //split into two loops for two types of entities. Doing it in "normal" way by testing data.vel_factor inside loop in every iteration will be probably compiled as same machine code in release build (it works in LDC)
172         if(data.vel_factor)
173         {
174             foreach(i; 0..data.length)
175             {
176                 data.location[i] += data.velocity[i] * data.vel_factor[i] * launcher.deltaTime;
177             }
178         }
179         else
180         {
181             foreach(i; 0..data.length)
182             {
183                 data.location[i] += data.velocity[i] * launcher.deltaTime;
184             }
185         }
186     }
187 }
188 
189 /**
190 *System is responsible for movement of objects with CInput component.
191 *In this example every entity has same speed when using movement system.
192 */
193 struct InputMovementSystem
194 {
195     mixin ECS.System!32;
196 
197     vec2 move_vector;
198 
199     struct EntitiesData
200     {
201         uint length;
202         //read only components can be marked with @readonly attribute or with const expression instead 
203         const (CInput)[] input;
204         //components are treated as required by default
205         //CLocation[] locations;
206         CVelocity[] velocity;
207         //CTexture[] textures;
208     }
209 
210     /**
211     *onBegin gives opportunity to check keys once and call update on entities only when
212     *one key is pressed.
213     */
214     bool onBegin()
215     {
216         move_vector = vec2(0,0);
217         if(launcher.getKeyState(SDL_SCANCODE_W))
218         {
219             move_vector += vec2(0,1);
220         }
221         else if(launcher.getKeyState(SDL_SCANCODE_S))
222         {
223             move_vector += vec2(0,-1);
224         } 
225         if(launcher.getKeyState(SDL_SCANCODE_A))
226         {
227             move_vector += vec2(-1,0);
228         }
229         else if(launcher.getKeyState(SDL_SCANCODE_D))
230         {   
231             move_vector += vec2(1,0);
232         }
233 
234         if(move_vector.x != 0 ||move_vector.y != 0)
235         {
236             move_vector = move_vector / sqrtf(move_vector.x * move_vector.x + move_vector.y * move_vector.y);
237             return true;
238         }
239         //don't call system update because no key pressed 
240         return false;
241     }
242 
243     /**
244     *Update is called multiple times in one "manager.update()" call.
245     *Number of "onUpdate" calls is count of buffers which must be updated during pass.
246     *When multithreading is used, number of "onUpdate" calls can be greater due to fact that
247     *JobSystem can split buffers for better data packing.
248     */
249     void onUpdate(EntitiesData data)
250     {
251         foreach(i; 0..data.length)
252         {
253             data.velocity[i] += move_vector * launcher.deltaTime * 0.005;
254             if(data.velocity[i].x > 0.5)data.velocity[i].x = 0.5;
255             else if(data.velocity[i].x < -0.5)data.velocity[i].x = -0.5;
256             if(data.velocity[i].y > 0.5)data.velocity[i].y = 0.5;
257             else if(data.velocity[i].y < -0.5)data.velocity[i].y = -0.5;
258         }
259     }
260 }