1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # Simple API for 2D games, based around `Sprite` and `App::update`
17 # Client programs should implement `App::update` to execute game logic and
18 # add instances of `Sprite` to `App::sprites` and `App::ui_sprites`.
19 # At each frame, all sprites are drawn to the screen.
21 # This system relies on two cameras `App::world_camera` and `App::ui_camera`.
23 # * `App::world_camera` applies a perspective effect to draw the game world.
24 # This camera is designed to be moved around to see the world as well as to zoom in and out.
25 # It is used to position the sprites in `App::sprites`.
27 # * `App::ui_camera` is a simple orthogonal camera to display UI objects.
28 # This camera should mostly be still, it can still move for chock effects and the like.
29 # It can be used to standardize the size of the UI across devices.
30 # It is used to position the sprites in `App::ui_sprites`.
32 # See the sample game at `contrib/asteronits/`.
37 import geometry
::points_and_lines
38 import matrix
::projection
39 import more_collections
41 import performance_analysis
44 import gamnit
::cameras
45 import gamnit
::limit_fps
47 import android_two_fingers_motion
is conditional
(android
)
49 # Image to draw on screen
52 # Texture drawn to screen
53 var texture
: Texture is writable
55 # Position of this sprite in world coordinates
56 var center
: Point3d[Float] is writable
58 # Rotation on the Z axis, where 0.0 points right and `0.5*pi` points up
59 var rotation
= 0.0 is writable
61 # Mirror `texture` horizontally, inverting each pixel on the X axis
62 var invert_x
= false is writable
64 # Scale applied to this sprite
65 var scale
= 1.0 is writable
67 # Transparency applied to the texture on draw
68 fun alpha
: Float do return tint
[3]
70 # Transparency applied to the texture on draw
71 fun alpha
=(value
: Float) do tint
[3] = value
73 # Tint applied to the texture on draw
75 # Require: `tint.length == 4`
76 var tint
: Array[Float] = [1.0, 1.0, 1.0, 1.0] is writable
80 var simple_2d_program
= app
.simple_2d_program
82 glActiveTexture gl_TEXTURE0
83 glBindTexture
(gl_TEXTURE_2D
, texture
.root
.gl_texture
)
85 simple_2d_program
.translation
.array_enabled
= false
86 simple_2d_program
.color
.array_enabled
= false
87 simple_2d_program
.scale
.array_enabled
= false
89 simple_2d_program
.translation
.uniform
(center
.x
, center
.y
, center
.z
, 0.0)
90 simple_2d_program
.color
.uniform
(tint
[0], tint
[1], tint
[2], tint
[3])
91 simple_2d_program
.scale
.uniform scale
93 simple_2d_program
.use_texture
.uniform
true
94 simple_2d_program
.texture
.uniform
0
95 simple_2d_program
.tex_coord
.array
(
97 texture
.texture_coords_invert_x
98 else texture
.texture_coords
, 2)
99 simple_2d_program
.coord
.array
(texture
.vertices
, 3)
101 simple_2d_program
.rotation
.uniform
new Matrix.rotation
(rotation
, 0.0, 0.0, -1.0)
103 glDrawArrays
(gl_TRIANGLE_STRIP
, 0, 4)
108 # Default graphic program to draw `sprites`
109 var simple_2d_program
= new Simple2dProgram is lazy
# TODO private
111 # Camera for world objects with perspective
113 # By default, the camera is configured to respect the resolution
114 # of the screen in world coordinates at `z == 0.0`.
115 var world_camera
: EulerCamera is lazy
do
116 var camera
= new EulerCamera(app
.display
.as(not null))
118 # Aim for pixel resolution at level 0
125 # Camera for UI elements using an orthogonal view
126 var ui_camera
: UICamera = new UICamera(app
.display
.as(not null)) is lazy
128 # Live sprites to draw in reference to `world_camera`
129 var sprites
: Sequence[Sprite] = new List[Sprite]
131 # UI sprites to draw in reference to `ui_camera`, over world `sprites`
132 var ui_sprites
: Sequence[Sprite] = new List[Sprite]
134 # Main clock used to count each frame `dt`, lapsed for `update` only
135 private var clock
= new Clock is lazy
137 # Performance clock to for `frame_core_draw` operations
138 private var perf_clock_main
= new Clock
143 var display
= display
144 assert display
!= null
146 var gl_error
= glGetError
147 assert gl_error
== gl_NO_ERROR
else print gl_error
150 var program
= simple_2d_program
151 program
.compile_and_link
153 var gamnit_error
= program
.error
154 assert gamnit_error
== null else print_error gamnit_error
157 gl
.capabilities
.blend
.enable
158 glBlendFunc
(gl_SRC_ALPHA
, gl_ONE_MINUS_SRC_ALPHA
)
161 gl
.capabilities
.depth_test
.enable
162 glDepthFunc gl_LEQUAL
165 # Prepare viewport and background color
166 glViewport
(0, 0, display
.width
, display
.height
)
167 glClearColor
(0.0, 0.0, 0.0, 1.0)
169 gl_error
= glGetError
170 assert gl_error
== gl_NO_ERROR
else print gl_error
173 for tex
in all_root_textures
do
175 gamnit_error
= tex
.error
176 if gamnit_error
!= null then print_error gamnit_error
178 glTexParameteri
(gl_TEXTURE_2D
, gl_TEXTURE_MIN_FILTER
, gl_LINEAR
)
179 glTexParameteri
(gl_TEXTURE_2D
, gl_TEXTURE_MAG_FILTER
, gl_LINEAR
)
183 redef fun frame_core
(display
)
185 # Prepare to draw, clear buffers
186 glClear
(gl_COLOR_BUFFER_BIT
| gl_DEPTH_BUFFER_BIT
)
189 var gl_error
= glGetError
190 assert gl_error
== gl_NO_ERROR
else print gl_error
192 # Update game logic and set sprites
193 perf_clock_main
.lapse
194 var dt
= clock
.lapse
.to_f
196 sys
.perfs
["gamnit flat update client"].add perf_clock_main
.lapse
198 # Draw and flip screen
199 frame_core_draw display
203 gl_error
= glGetError
204 assert gl_error
== gl_NO_ERROR
else print gl_error
207 # Draw the whole screen, all `glDraw...` calls should be executed here
208 protected fun frame_core_draw
(display
: GamnitDisplay)
210 perf_clock_main
.lapse
212 frame_core_world_sprites display
213 perfs
["gamnit flat world_sprites"].add perf_clock_main
.lapse
215 frame_core_ui_sprites display
216 perfs
["gamnit flat ui_sprites"].add perf_clock_main
.lapse
220 # Draw world sprites from `sprites`
221 protected fun frame_core_world_sprites
(display
: GamnitDisplay)
223 simple_2d_program
.use
225 # Set constant configs
226 simple_2d_program
.coord
.array_enabled
= true
227 simple_2d_program
.tex_coord
.array_enabled
= true
228 simple_2d_program
.color
.array_enabled
= false
230 # TODO optimize this draw to store constant values on the GPU
232 simple_2d_program
.mvp
.uniform world_camera
.mvp_matrix
233 for sprite
in sprites
do sprite
.draw
236 # Draw UI sprites from `ui_sprites`
237 protected fun frame_core_ui_sprites
(display
: GamnitDisplay)
239 simple_2d_program
.use
241 # Set constant configs
242 simple_2d_program
.coord
.array_enabled
= true
243 simple_2d_program
.tex_coord
.array_enabled
= true
244 simple_2d_program
.color
.array_enabled
= false
246 # Reset only the depth buffer
247 glClear gl_DEPTH_BUFFER_BIT
250 simple_2d_program
.mvp
.uniform ui_camera
.mvp_matrix
251 for sprite
in ui_sprites
do sprite
.draw
254 # Main method to refine in clients to update game logic and `sprites`
255 fun update
(dt
: Float) do end
257 # Display `texture` as a splash screen
259 # Load `texture` if needed and resets `ui_camera` to 1080 units on the Y axis.
260 fun show_splash_screen
(texture
: Texture)
264 ui_camera
.reset_height
1080.0
266 var splash
= new Sprite(texture
, ui_camera
.center
)
267 ui_sprites
.add splash
269 var display
= display
270 assert display
!= null
271 glClear gl_COLOR_BUFFER_BIT
272 frame_core_ui_sprites display
275 ui_sprites
.remove splash
281 simple_2d_program
.delete
284 var display
= display
285 if display
!= null then display
.close
291 # Vertices coordinates of the base geometry
292 private var vertices
: Array[Float] is lazy
do
296 var a
= [-0.5*w
, -0.5*h
, 0.0]
297 var b
= [ 0.5*w
, -0.5*h
, 0.0]
298 var c
= [-0.5*w
, 0.5*h
, 0.0]
299 var d
= [ 0.5*w
, 0.5*h
, 0.0]
301 var vertices
= new Array[Float]
302 for v
in [c
, d
, a
, b
] do vertices
.add_all v
306 # Coordinates of this texture on the `root` texture, with `[0..1.0]`
307 private var texture_coords
: Array[Float] is lazy
do
308 var a
= [offset_left
, offset_bottom
]
309 var b
= [offset_right
, offset_bottom
]
310 var c
= [offset_left
, offset_top
]
311 var d
= [offset_right
, offset_top
]
313 var texture_coords
= new Array[Float]
314 for v
in [c
, d
, a
, b
] do texture_coords
.add_all v
315 return texture_coords
318 # Coordinates of this texture on the `root` texture, with the X axis inverted
319 private var texture_coords_invert_x
: Array[Float] is lazy
do
320 var a
= [offset_left
, offset_bottom
]
321 var b
= [offset_right
, offset_bottom
]
322 var c
= [offset_left
, offset_top
]
323 var d
= [offset_right
, offset_top
]
325 var texture_coords
= new Array[Float]
326 for v
in [d
, c
, b
, a
] do texture_coords
.add_all v
327 return texture_coords
331 # Graphic program to display simple models with a texture, translation, rotation and scale
332 class Simple2dProgram
333 super GamnitProgramFromSource
335 redef var vertex_shader_source
= """
336 // Vertex coordinates
337 attribute vec4 coord;
340 attribute vec4 color;
342 // Vertex translation
343 attribute vec4 translation;
346 attribute float scale;
348 // Vertex coordinates on textures
349 attribute vec2 tex_coord;
351 // Model view projection matrix
355 uniform mat4 rotation;
357 // Output for the fragment shader
358 varying vec4 v_color;
359 varying vec2 v_coord;
364 gl_Position = (vec4(coord.xyz * scale, 1.0) * rotation + translation) * mvp;
367 """ @ glsl_vertex_shader
369 redef var fragment_shader_source
= """
370 precision mediump float;
372 // Does this object use a texture?
373 uniform bool use_texture;
375 // Texture to apply on this object
376 uniform sampler2D texture0;
378 // Input from the vertex shader
379 varying vec4 v_color;
380 varying vec2 v_coord;
385 gl_FragColor = v_color * texture2D(texture0, v_coord);
386 if (gl_FragColor.a == 0.0) discard;
388 gl_FragColor = v_color;
391 """ @ glsl_fragment_shader
393 # Vertices coordinates
394 var coord
= attributes
["coord"].as(AttributeVec4) is lazy
396 # Should this program use the texture `texture`?
397 var use_texture
= uniforms
["use_texture"].as(UniformBool) is lazy
399 # Visible texture unit
400 var texture
= uniforms
["texture0"].as(UniformSampler2D) is lazy
402 # Coordinates on the textures, per vertex
403 var tex_coord
= attributes
["tex_coord"].as(AttributeVec2) is lazy
405 # Color tint per vertex
406 var color
= attributes
["color"].as(AttributeVec4) is lazy
408 # Translation applied to each vertex
409 var translation
= attributes
["translation"].as(AttributeVec4) is lazy
412 var rotation
= uniforms
["rotation"].as(UniformMat4) is lazy
415 var scale
= attributes
["scale"].as(AttributeFloat) is lazy
417 # Model view projection matrix
418 var mvp
= uniforms
["mvp"].as(UniformMat4) is lazy
421 redef class Point3d[N
]
422 # Get a new `Point3d[Float]` with an offset of each axis of `x, y, z`
423 fun offset
(x
, y
, z
: Numeric): Point3d[Float]
425 return new Point3d[Float](self.x
.to_f
+x
.to_f
, self.y
.to_f
+y
.to_f
, self.z
.to_f
+z
.to_f
)